Clean up
This commit is contained in:
@ -1,356 +1,55 @@
|
||||
#undef DEBUG
|
||||
using GTA;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using GTA;
|
||||
using Lidgren.Network;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CustomEventReceivedArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The event hash
|
||||
/// The event hash
|
||||
/// </summary>
|
||||
public int Hash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion
|
||||
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion
|
||||
/// </summary>
|
||||
public object[] Args { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides vital functionality to interact with RAGECOOP
|
||||
/// Provides vital functionality to interact with RAGECOOP
|
||||
/// </summary>
|
||||
public static class API
|
||||
{
|
||||
#region INTERNAL
|
||||
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Client configuration, this will conflict with server-side config.
|
||||
/// </summary>
|
||||
public static class Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or set local player's username, set won't be effective if already connected to a server.
|
||||
/// </summary>
|
||||
public static string Username
|
||||
{
|
||||
get => Main.Settings.Username;
|
||||
set
|
||||
{
|
||||
if (Networking.IsOnServer || string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Main.Settings.Username = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Enable automatic respawn for this player.
|
||||
/// </summary>
|
||||
public static bool EnableAutoRespawn { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip color
|
||||
/// </summary>
|
||||
public static BlipColor BlipColor { get; set; } = BlipColor.White;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip sprite
|
||||
/// </summary>
|
||||
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set scale of player's blip
|
||||
/// </summary>
|
||||
public static float BlipScale { get; set; } = 1;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Base events for RageCoop
|
||||
/// </summary>
|
||||
public static class Events
|
||||
{
|
||||
#region DELEGATES
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public delegate void EmptyEvent();
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="args"></param>
|
||||
public delegate void CustomEvent(int hash, List<object> args);
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// The local player is dead
|
||||
/// </summary>
|
||||
public static event EmptyEvent OnPlayerDied;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="GTA.Script.Tick"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.Tick"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static event EmptyEvent OnTick;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyDown"/>
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyDown"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static KeyEventHandler OnKeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyUp"/>
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyUp"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static KeyEventHandler OnKeyUp;
|
||||
|
||||
#region INVOKE
|
||||
internal static void InvokeVehicleSpawned(SyncedVehicle v) { OnVehicleSpawned?.Invoke(null, v); }
|
||||
internal static void InvokeVehicleDeleted(SyncedVehicle v) { OnVehicleDeleted?.Invoke(null, v); }
|
||||
internal static void InvokePedSpawned(SyncedPed p) { OnPedSpawned?.Invoke(null, p); }
|
||||
internal static void InvokePedDeleted(SyncedPed p) { OnPedDeleted?.Invoke(null, p); }
|
||||
internal static void InvokePlayerDied() { OnPlayerDied?.Invoke(); }
|
||||
internal static void InvokeTick() { OnTick?.Invoke(); }
|
||||
|
||||
internal static void InvokeKeyDown(object s, KeyEventArgs e) { OnKeyDown?.Invoke(s, e); }
|
||||
|
||||
internal static void InvokeKeyUp(object s, KeyEventArgs e) { OnKeyUp?.Invoke(s, e); }
|
||||
|
||||
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
|
||||
{
|
||||
var args = new CustomEventReceivedArgs() { Hash = p.Hash, Args = p.Args };
|
||||
|
||||
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
|
||||
|
||||
if (CustomEventHandlers.TryGetValue(p.Hash, out List<Action<CustomEventReceivedArgs>> handlers))
|
||||
{
|
||||
handlers.ForEach((x) => { x.Invoke(args); });
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
/// <summary>
|
||||
/// Get the local player's ID
|
||||
/// </summary>
|
||||
/// <returns>PlayerID</returns>
|
||||
public static int LocalPlayerID => Main.LocalPlayerID;
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is connected to a server
|
||||
/// </summary>
|
||||
public static bool IsOnServer => Networking.IsOnServer;
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="System.Net.IPEndPoint"/> that the player is currently connected to, or null if not connected to the server
|
||||
/// </summary>
|
||||
public static System.Net.IPEndPoint ServerEndPoint => Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a RAGECOOP menu is visible
|
||||
/// </summary>
|
||||
public static bool IsMenuVisible => Menus.CoopMenu.MenuPool.AreAnyVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP chat is visible
|
||||
/// </summary>
|
||||
public static bool IsChatFocused => Main.MainChat.Focused;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP list of players is visible
|
||||
/// </summary>
|
||||
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of RAGECOOP
|
||||
/// </summary>
|
||||
public static Version CurrentVersion => Main.Version;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Logger Logger => Main.Logger;
|
||||
/// <summary>
|
||||
/// Get all players indexed by their ID
|
||||
/// </summary>
|
||||
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
|
||||
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers =
|
||||
new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region FUNCTIONS
|
||||
/// <summary>
|
||||
/// Connect to a server
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
|
||||
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
|
||||
public static void Connect(string address)
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot connect to server when another connection is active");
|
||||
}
|
||||
Networking.ToggleConnection(address);
|
||||
}
|
||||
/// <summary>
|
||||
/// Disconnect from current server or cancel the connection attempt.
|
||||
/// </summary>
|
||||
public static void Disconnect()
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting)
|
||||
{
|
||||
Networking.ToggleConnection(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all servers from master server address
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<ServerInfo> ListServers()
|
||||
{
|
||||
return JsonConvert.DeserializeObject<List<ServerInfo>>(HttpHelper.DownloadString(Main.Settings.MasterServer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a local chat message to this player
|
||||
/// </summary>
|
||||
/// <param name="from">Name of the sender</param>
|
||||
/// <param name="message">The player's message</param>
|
||||
public static void LocalChatMessage(string from, string message)
|
||||
{
|
||||
Main.MainChat.AddMessage(from, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message or command to server/other players
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
Networking.SendChatMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server.
|
||||
/// </summary>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
|
||||
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent()
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
/// <summary>
|
||||
/// Send an event and data to the server
|
||||
/// </summary>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
|
||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread.
|
||||
/// </summary>
|
||||
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
|
||||
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||
{
|
||||
lock (CustomEventHandlers)
|
||||
{
|
||||
if (!CustomEventHandlers.TryGetValue(hash, out List<Action<CustomEventReceivedArgs>> handlers))
|
||||
{
|
||||
CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
|
||||
}
|
||||
handlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static void RequestSharedFile(string name, Action<string> callback)
|
||||
{
|
||||
EventHandler<string> handler = (s, e) =>
|
||||
{
|
||||
if (e.EndsWith(name))
|
||||
{
|
||||
callback(e);
|
||||
}
|
||||
};
|
||||
DownloadManager.DownloadCompleted += handler;
|
||||
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest()
|
||||
{
|
||||
Name = name,
|
||||
},
|
||||
(p) =>
|
||||
{
|
||||
if (p.Response != FileResponse.Loaded)
|
||||
{
|
||||
DownloadManager.DownloadCompleted -= handler;
|
||||
throw new ArgumentException("Requested file was not found on the server: " + name);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick.
|
||||
/// Queue an action to be executed on next tick.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
public static void QueueAction(Action a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
|
||||
public static void QueueActionAndWait(Action a, int timeout = 15000)
|
||||
{
|
||||
var done = new AutoResetEvent(false);
|
||||
@ -362,19 +61,376 @@ namespace RageCoop.Client.Scripting
|
||||
a();
|
||||
done.Set();
|
||||
}
|
||||
catch (Exception ex) { e = ex; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
e = ex;
|
||||
}
|
||||
});
|
||||
if (e != null) { throw e; }
|
||||
else if (!done.WaitOne(timeout)) { throw new TimeoutException(); }
|
||||
if (e != null)
|
||||
throw e;
|
||||
if (!done.WaitOne(timeout)) throw new TimeoutException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
|
||||
/// <param name="a">
|
||||
/// An <see cref="Func{T, TResult}" /> to be executed with a return value indicating whether it can be
|
||||
/// removed after execution.
|
||||
/// </param>
|
||||
public static void QueueAction(Func<bool> a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client configuration, this will conflict with server-side config.
|
||||
/// </summary>
|
||||
public static class Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or set local player's username, set won't be effective if already connected to a server.
|
||||
/// </summary>
|
||||
public static string Username
|
||||
{
|
||||
get => Main.Settings.Username;
|
||||
set
|
||||
{
|
||||
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return;
|
||||
Main.Settings.Username = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable automatic respawn for this player.
|
||||
/// </summary>
|
||||
public static bool EnableAutoRespawn { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip color
|
||||
/// </summary>
|
||||
public static BlipColor BlipColor { get; set; } = BlipColor.White;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip sprite
|
||||
/// </summary>
|
||||
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set scale of player's blip
|
||||
/// </summary>
|
||||
public static float BlipScale { get; set; } = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base events for RageCoop
|
||||
/// </summary>
|
||||
public static class Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyDown" />
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="GTA.Script.Yield" /> in the handler will interfer other scripts, subscribe to
|
||||
/// <see cref="GTA.Script.KeyDown" /> instead.
|
||||
/// </remarks>
|
||||
[Obsolete] public static KeyEventHandler OnKeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyUp" />
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="GTA.Script.Yield" /> in the handler will interfer other scripts, subscribe to
|
||||
/// <see cref="GTA.Script.KeyUp" /> instead.
|
||||
/// </remarks>
|
||||
[Obsolete] public static KeyEventHandler OnKeyUp;
|
||||
|
||||
/// <summary>
|
||||
/// The local player is dead
|
||||
/// </summary>
|
||||
public static event EmptyEvent OnPlayerDied;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="GTA.Script.Tick" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="GTA.Script.Yield" /> in the handler will interfer other scripts, subscribe to
|
||||
/// <see cref="GTA.Script.Tick" /> instead.
|
||||
/// </remarks>
|
||||
[Obsolete]
|
||||
public static event EmptyEvent OnTick;
|
||||
|
||||
#region DELEGATES
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public delegate void EmptyEvent();
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="args"></param>
|
||||
public delegate void CustomEvent(int hash, List<object> args);
|
||||
|
||||
#endregion
|
||||
|
||||
#region INVOKE
|
||||
|
||||
internal static void InvokeVehicleSpawned(SyncedVehicle v)
|
||||
{
|
||||
OnVehicleSpawned?.Invoke(null, v);
|
||||
}
|
||||
|
||||
internal static void InvokeVehicleDeleted(SyncedVehicle v)
|
||||
{
|
||||
OnVehicleDeleted?.Invoke(null, v);
|
||||
}
|
||||
|
||||
internal static void InvokePedSpawned(SyncedPed p)
|
||||
{
|
||||
OnPedSpawned?.Invoke(null, p);
|
||||
}
|
||||
|
||||
internal static void InvokePedDeleted(SyncedPed p)
|
||||
{
|
||||
OnPedDeleted?.Invoke(null, p);
|
||||
}
|
||||
|
||||
internal static void InvokePlayerDied()
|
||||
{
|
||||
OnPlayerDied?.Invoke();
|
||||
}
|
||||
|
||||
internal static void InvokeTick()
|
||||
{
|
||||
OnTick?.Invoke();
|
||||
}
|
||||
|
||||
internal static void InvokeKeyDown(object s, KeyEventArgs e)
|
||||
{
|
||||
OnKeyDown?.Invoke(s, e);
|
||||
}
|
||||
|
||||
internal static void InvokeKeyUp(object s, KeyEventArgs e)
|
||||
{
|
||||
OnKeyUp?.Invoke(s, e);
|
||||
}
|
||||
|
||||
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
|
||||
{
|
||||
var args = new CustomEventReceivedArgs { Hash = p.Hash, Args = p.Args };
|
||||
|
||||
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
|
||||
|
||||
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
|
||||
handlers.ForEach(x => { x.Invoke(args); });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
/// <summary>
|
||||
/// Get the local player's ID
|
||||
/// </summary>
|
||||
/// <returns>PlayerID</returns>
|
||||
public static int LocalPlayerID => Main.LocalPlayerID;
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is connected to a server
|
||||
/// </summary>
|
||||
public static bool IsOnServer => Networking.IsOnServer;
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="System.Net.IPEndPoint" /> that the player is currently connected to, or null if not connected to
|
||||
/// the server
|
||||
/// </summary>
|
||||
public static IPEndPoint ServerEndPoint =>
|
||||
Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a RAGECOOP menu is visible
|
||||
/// </summary>
|
||||
public static bool IsMenuVisible => CoopMenu.MenuPool.AreAnyVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP chat is visible
|
||||
/// </summary>
|
||||
public static bool IsChatFocused => Main.MainChat.Focused;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP list of players is visible
|
||||
/// </summary>
|
||||
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of RAGECOOP
|
||||
/// </summary>
|
||||
public static Version CurrentVersion => Main.Version;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Core.Logger" /> that RAGECOOP is currently using.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Logger Logger => Main.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// Get all players indexed by their ID
|
||||
/// </summary>
|
||||
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
|
||||
|
||||
#endregion
|
||||
|
||||
#region FUNCTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Connect to a server
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
|
||||
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
|
||||
public static void Connect(string address)
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting)
|
||||
throw new InvalidOperationException("Cannot connect to server when another connection is active");
|
||||
Networking.ToggleConnection(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from current server or cancel the connection attempt.
|
||||
/// </summary>
|
||||
public static void Disconnect()
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all servers from master server address
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<ServerInfo> ListServers()
|
||||
{
|
||||
return JsonConvert.DeserializeObject<List<ServerInfo>>(
|
||||
HttpHelper.DownloadString(Main.Settings.MasterServer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a local chat message to this player
|
||||
/// </summary>
|
||||
/// <param name="from">Name of the sender</param>
|
||||
/// <param name="message">The player's message</param>
|
||||
public static void LocalChatMessage(string from, string message)
|
||||
{
|
||||
Main.MainChat.AddMessage(from, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message or command to server/other players
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
Networking.SendChatMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server.
|
||||
/// </summary>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">
|
||||
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
|
||||
/// types
|
||||
/// </param>
|
||||
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server
|
||||
/// </summary>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">
|
||||
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
|
||||
/// types
|
||||
/// </param>
|
||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
|
||||
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
|
||||
/// </summary>
|
||||
/// <param name="hash">
|
||||
/// An unique identifier of the event, you can hash your event name with
|
||||
/// <see cref="Core.Scripting.CustomEvents.Hash(string)" />
|
||||
/// </param>
|
||||
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||
{
|
||||
lock (CustomEventHandlers)
|
||||
{
|
||||
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
||||
CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
|
||||
handlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static void RequestSharedFile(string name, Action<string> callback)
|
||||
{
|
||||
EventHandler<string> handler = (s, e) =>
|
||||
{
|
||||
if (e.EndsWith(name)) callback(e);
|
||||
};
|
||||
DownloadManager.DownloadCompleted += handler;
|
||||
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
|
||||
{
|
||||
Name = name
|
||||
},
|
||||
p =>
|
||||
{
|
||||
if (p.Response != FileResponse.Loaded)
|
||||
{
|
||||
DownloadManager.DownloadCompleted -= handler;
|
||||
throw new ArgumentException("Requested file was not found on the server: " + name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal static class BaseScript
|
||||
{
|
||||
private static bool _isHost = false;
|
||||
private static bool _isHost;
|
||||
|
||||
public static void OnStart()
|
||||
{
|
||||
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
|
||||
@ -29,28 +31,28 @@ namespace RageCoop.Client.Scripting
|
||||
API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle);
|
||||
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost = (bool)e.Args[0]; });
|
||||
API.RegisterCustomEventHandler(CustomEvents.IsHost, e => { _isHost = (bool)e.Args[0]; });
|
||||
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
|
||||
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show($"~h~{e.Args[0]}~h~ died."); });
|
||||
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied,
|
||||
e => { Notification.Show($"~h~{e.Args[0]}~h~ died."); });
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_isHost)
|
||||
{
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var time = World.CurrentTimeOfDay;
|
||||
int weather1 = default(int);
|
||||
int weather2 = default(int);
|
||||
float percent2 = default(float);
|
||||
var weather1 = default(int);
|
||||
var weather2 = default(int);
|
||||
var percent2 = default(float);
|
||||
Function.Call(Hash._GET_WEATHER_TYPE_TRANSITION, &weather1, &weather2, &percent2);
|
||||
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2);
|
||||
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
|
||||
time.Seconds, weather1, weather2, percent2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
@ -66,13 +68,13 @@ namespace RageCoop.Client.Scripting
|
||||
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; }
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
|
||||
private static void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = Entity.FromHandle((int)e.Args[0]);
|
||||
if (p == null) { return; }
|
||||
if (p == null) return;
|
||||
if (p.Handle == Game.Player.Character.Handle)
|
||||
{
|
||||
API.Config.BlipColor = (BlipColor)(byte)e.Args[1];
|
||||
@ -82,7 +84,7 @@ namespace RageCoop.Client.Scripting
|
||||
else
|
||||
{
|
||||
var b = p.AttachedBlip;
|
||||
if (b == null) { b = p.AddBlip(); }
|
||||
if (b == null) b = p.AddBlip();
|
||||
b.Color = (BlipColor)(byte)e.Args[1];
|
||||
b.Sprite = (BlipSprite)(ushort)e.Args[2];
|
||||
b.Scale = (float)e.Args[3];
|
||||
@ -95,15 +97,13 @@ namespace RageCoop.Client.Scripting
|
||||
vehicleModel.Request(1000);
|
||||
Vehicle veh;
|
||||
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
veh.CanPretendOccupants = false;
|
||||
var v = new SyncedVehicle()
|
||||
var v = new SyncedVehicle
|
||||
{
|
||||
ID = (int)e.Args[0],
|
||||
MainVehicle = veh,
|
||||
OwnerID = Main.LocalPlayerID,
|
||||
OwnerID = Main.LocalPlayerID
|
||||
};
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
@ -119,17 +119,15 @@ namespace RageCoop.Client.Scripting
|
||||
|
||||
private static void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
{
|
||||
int id = (int)obj.Args[0];
|
||||
var id = (int)obj.Args[0];
|
||||
var sprite = (BlipSprite)(ushort)obj.Args[1];
|
||||
var color = (BlipColor)(byte)obj.Args[2];
|
||||
var scale = (float)obj.Args[3];
|
||||
var pos = (Vector3)obj.Args[4];
|
||||
int rot = (int)obj.Args[5];
|
||||
var rot = (int)obj.Args[5];
|
||||
var name = (string)obj.Args[6];
|
||||
if (!EntityPool.ServerBlips.TryGetValue(id, out Blip blip))
|
||||
{
|
||||
if (!EntityPool.ServerBlips.TryGetValue(id, out var blip))
|
||||
EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
|
||||
}
|
||||
blip.Sprite = sprite;
|
||||
blip.Color = color;
|
||||
blip.Scale = scale;
|
||||
@ -147,15 +145,14 @@ namespace RageCoop.Client.Scripting
|
||||
private static void SetNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null)
|
||||
{
|
||||
p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
|
||||
private static void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
{
|
||||
API.Config.EnableAutoRespawn = (bool)args.Args[0];
|
||||
}
|
||||
|
||||
private static void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
{
|
||||
var id = (int)e.Args[0];
|
||||
@ -164,8 +161,8 @@ namespace RageCoop.Client.Scripting
|
||||
EntityPool.ServerProps.Remove(id);
|
||||
prop?.MainProp?.Delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
SyncedProp prop;
|
||||
@ -173,10 +170,9 @@ namespace RageCoop.Client.Scripting
|
||||
lock (EntityPool.PropsLock)
|
||||
{
|
||||
if (!EntityPool.ServerProps.TryGetValue(id, out prop))
|
||||
{
|
||||
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
|
||||
}
|
||||
}
|
||||
|
||||
prop.LastSynced = Main.Ticked + 1;
|
||||
prop.Model = (Model)e.Args[1];
|
||||
prop.Position = (Vector3)e.Args[2];
|
||||
@ -184,90 +180,88 @@ namespace RageCoop.Client.Scripting
|
||||
prop.Model.Request(1000);
|
||||
prop.Update();
|
||||
}
|
||||
|
||||
private static void NativeCall(CustomEventReceivedArgs e)
|
||||
{
|
||||
List<InputArgument> arguments = new List<InputArgument>();
|
||||
var arguments = new List<InputArgument>();
|
||||
int i;
|
||||
var ty = (byte)e.Args[0];
|
||||
TypeCode returnType = (TypeCode)ty;
|
||||
var returnType = (TypeCode)ty;
|
||||
i = returnType == TypeCode.Empty ? 1 : 2;
|
||||
var hash = (Hash)e.Args[i++];
|
||||
for (; i < e.Args.Length; i++)
|
||||
{
|
||||
arguments.Add(GetInputArgument(e.Args[i]));
|
||||
}
|
||||
for (; i < e.Args.Length; i++) arguments.Add(GetInputArgument(e.Args[i]));
|
||||
|
||||
if (returnType == TypeCode.Empty)
|
||||
{
|
||||
Function.Call(hash, arguments.ToArray());
|
||||
return;
|
||||
}
|
||||
|
||||
var t = returnType;
|
||||
int id = (int)e.Args[1];
|
||||
var id = (int)e.Args[1];
|
||||
|
||||
|
||||
switch (returnType)
|
||||
{
|
||||
case TypeCode.Boolean:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<bool>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<bool>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<byte>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<byte>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Char:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<char>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<char>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Single:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<float>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<float>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Double:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<double>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<double>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<short>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<short>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<int>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id, Function.Call<int>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<long>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<long>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.String:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<string>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<string>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<ushort>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<ushort>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<uint>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<uint>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<ulong>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Function.Call<ulong>(hash, arguments.ToArray()));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static InputArgument GetInputArgument(object obj)
|
||||
{
|
||||
// Implicit conversion
|
||||
@ -292,10 +286,10 @@ namespace RageCoop.Client.Scripting
|
||||
case bool _:
|
||||
return (bool)obj;
|
||||
case string _:
|
||||
return (obj as string);
|
||||
return obj as string;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +1,39 @@
|
||||
using RageCoop.Core.Scripting;
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
|
||||
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded,
|
||||
/// you should use <see cref="OnStart" />. to initiate your script.
|
||||
/// </summary>
|
||||
public abstract class ClientScript : GTA.Script
|
||||
public abstract class ClientScript : Script
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from main thread, right after all script constructors are invoked.
|
||||
/// </summary>
|
||||
public abstract void OnStart();
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from main thread right before the whole <see cref="System.AppDomain"/> is unloded but prior to <see cref="GTA.Script.Aborted"/>.
|
||||
/// </summary>
|
||||
public abstract void OnStop();
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ResourceFile"/> instance where this script is loaded from.
|
||||
/// Get the <see cref="ResourceFile" /> instance where this script is loaded from.
|
||||
/// </summary>
|
||||
public ResourceFile CurrentFile { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientResource"/> that this script belongs to.
|
||||
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
||||
/// </summary>
|
||||
public ClientResource CurrentResource { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Eqivalent of <see cref="ClientResource.Logger"/> in <see cref="CurrentResource"/>
|
||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
|
||||
/// </summary>
|
||||
public Core.Logger Logger => CurrentResource.Logger;
|
||||
public Logger Logger => CurrentResource.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from main thread, right after all script constructors are invoked.
|
||||
/// </summary>
|
||||
public abstract void OnStart();
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from main thread right before the whole <see cref="System.AppDomain" /> is unloded but
|
||||
/// prior to <see cref="GTA.Script.Aborted" />.
|
||||
/// </summary>
|
||||
public abstract void OnStop();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +1,82 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using RageCoop.Client.Loader;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ClientResource
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the resource
|
||||
/// Name of the resource
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory where the scripts is loaded from
|
||||
/// Directory where the scripts is loaded from
|
||||
/// </summary>
|
||||
public string ScriptsDirectory { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A resource-specific folder that can be used to store your files.
|
||||
/// A resource-specific folder that can be used to store your files.
|
||||
/// </summary>
|
||||
public string DataFolder { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all <see cref="ClientScript"/> instance in this resource.
|
||||
/// Get all <see cref="ClientScript" /> instance in this resource.
|
||||
/// </summary>
|
||||
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ResourceFile"/> where this script is loaded from.
|
||||
/// Get the <see cref="ResourceFile" /> where this script is loaded from.
|
||||
/// </summary>
|
||||
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Core.Logger"/> instance that can be used to debug your resource.
|
||||
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
|
||||
/// </summary>
|
||||
public Logger Logger { get; internal set; }
|
||||
}
|
||||
|
||||
internal class Resources
|
||||
{
|
||||
public static string TempPath;
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new ConcurrentDictionary<string, ClientResource>();
|
||||
private Logger Logger { get; set; }
|
||||
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources =
|
||||
new ConcurrentDictionary<string, ClientResource>();
|
||||
|
||||
static Resources()
|
||||
{
|
||||
TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
|
||||
if (Directory.Exists(TempPath)) { try { Directory.Delete(TempPath, true); } catch { } }
|
||||
if (Directory.Exists(TempPath))
|
||||
try
|
||||
{
|
||||
Directory.Delete(TempPath, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
TempPath = CoreUtils.GetTempDirectory(TempPath);
|
||||
Directory.CreateDirectory(TempPath);
|
||||
}
|
||||
|
||||
public Resources()
|
||||
{
|
||||
Logger = Main.Logger;
|
||||
}
|
||||
|
||||
private Logger Logger { get; }
|
||||
|
||||
public void Load(string path, string[] zips)
|
||||
{
|
||||
LoadedResources.Clear();
|
||||
@ -67,7 +86,9 @@ namespace RageCoop.Client.Scripting
|
||||
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
||||
Unpack(zipPath, Path.Combine(path, "Data"));
|
||||
}
|
||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored()).ForEach(x => File.Delete(x));
|
||||
|
||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored())
|
||||
.ForEach(x => File.Delete(x));
|
||||
|
||||
// Load it in main thread
|
||||
API.QueueActionAndWait(() =>
|
||||
@ -75,48 +96,46 @@ namespace RageCoop.Client.Scripting
|
||||
Main.QueueToMainThreadAndWait(() =>
|
||||
{
|
||||
foreach (var res in LoadedResources)
|
||||
{
|
||||
Directory.GetFiles(res.Value.ScriptsDirectory, "*.dll", SearchOption.AllDirectories).ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
|
||||
}
|
||||
Directory.GetFiles(res.Value.ScriptsDirectory, "*.dll", SearchOption.AllDirectories)
|
||||
.ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
|
||||
SetupScripts();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
|
||||
StopScripts();
|
||||
LoadedResources.Clear();
|
||||
Loader.LoaderContext.RequestUnload();
|
||||
LoaderContext.RequestUnload();
|
||||
}
|
||||
|
||||
private ClientResource Unpack(string zipPath, string dataFolderRoot)
|
||||
{
|
||||
var r = new ClientResource()
|
||||
var r = new ClientResource
|
||||
{
|
||||
Logger = API.Logger,
|
||||
Scripts = new List<ClientScript>(),
|
||||
Name = Path.GetFileNameWithoutExtension(zipPath),
|
||||
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
|
||||
ScriptsDirectory = Path.Combine(TempPath, Path.GetFileNameWithoutExtension(zipPath)),
|
||||
ScriptsDirectory = Path.Combine(TempPath, Path.GetFileNameWithoutExtension(zipPath))
|
||||
};
|
||||
Directory.CreateDirectory(r.DataFolder);
|
||||
var scriptsDir = r.ScriptsDirectory;
|
||||
if (Directory.Exists(scriptsDir)) { Directory.Delete(scriptsDir, true); }
|
||||
else if (File.Exists(scriptsDir)) { File.Delete(scriptsDir); }
|
||||
if (Directory.Exists(scriptsDir))
|
||||
Directory.Delete(scriptsDir, true);
|
||||
else if (File.Exists(scriptsDir)) File.Delete(scriptsDir);
|
||||
Directory.CreateDirectory(scriptsDir);
|
||||
|
||||
new FastZip().ExtractZip(zipPath, scriptsDir, null);
|
||||
|
||||
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
r.Files.Add(dir, new ResourceFile()
|
||||
r.Files.Add(dir, new ResourceFile
|
||||
{
|
||||
IsDirectory = true,
|
||||
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
|
||||
});
|
||||
}
|
||||
var assemblies = new Dictionary<ResourceFile, Assembly>();
|
||||
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
@ -128,12 +147,15 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Warning($"Failed to delete API assembly: {file}. This may or may cause some unexpected behaviours.\n{ex}");
|
||||
API.Logger.Warning(
|
||||
$"Failed to delete API assembly: {file}. This may or may cause some unexpected behaviours.\n{ex}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
|
||||
var rfile = new ResourceFile()
|
||||
var rfile = new ResourceFile
|
||||
{
|
||||
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },
|
||||
IsDirectory = false,
|
||||
@ -150,21 +172,18 @@ namespace RageCoop.Client.Scripting
|
||||
{
|
||||
foreach (var s in GetClientScripts())
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
API.Logger.Debug("Starting script: " + s.GetType().FullName);
|
||||
var script = (ClientScript)s;
|
||||
if (LoadedResources.TryGetValue(Directory.GetParent(script.Filename).Name, out var r))
|
||||
{
|
||||
script.CurrentResource = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
API.Logger.Warning("Failed to locate resource for script: " + script.Filename);
|
||||
}
|
||||
var res = script.CurrentResource;
|
||||
script.CurrentFile = res?.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
|
||||
script.CurrentFile = res?.Files.Values.Where(x =>
|
||||
x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1)
|
||||
.Replace('\\', '/')).FirstOrDefault();
|
||||
res?.Scripts.Add(script);
|
||||
script.OnStart();
|
||||
}
|
||||
@ -180,7 +199,6 @@ namespace RageCoop.Client.Scripting
|
||||
private void StopScripts()
|
||||
{
|
||||
foreach (var s in GetClientScripts())
|
||||
{
|
||||
try
|
||||
{
|
||||
API.Logger.Debug("Stopping script: " + s.GetType().FullName);
|
||||
@ -190,13 +208,12 @@ namespace RageCoop.Client.Scripting
|
||||
{
|
||||
API.Logger.Error($"Failed to stop {s.GetType().FullName}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static object[] GetClientScripts()
|
||||
{
|
||||
return ScriptDomain.CurrentDomain.RunningScripts.Where(x =>
|
||||
x.ScriptInstance.GetType().IsScript(typeof(ClientScript))).Select(x => x.ScriptInstance).ToArray();
|
||||
x.ScriptInstance.GetType().IsScript(typeof(ClientScript))).Select(x => x.ScriptInstance).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user