This commit is contained in:
sardelka9515
2022-10-23 19:02:39 +08:00
parent 6b34ab6e36
commit 2828b9b74f
114 changed files with 7374 additions and 7205 deletions

View File

@ -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
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}
}