#undef DEBUG using Lidgren.Network; using RageCoop.Client.Menus; using RageCoop.Core; using RageCoop.Core.Scripting; using SHVDN; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; [assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge namespace RageCoop.Client.Scripting { /// /// Provides vital functionality to interact with RAGECOOP /// internal static unsafe partial class API { #region INTERNAL internal static Dictionary> CustomEventHandlers = new(); #endregion /// /// Client configuration, this will conflict with server-side config. /// public static class Config { /// /// Get or set local player's username, set won't be effective if already connected to a server. /// public static string Username { get => Settings.Username; set { if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return; Settings.Username = value; } } /// /// Enable automatic respawn for this player. /// public static bool EnableAutoRespawn { get; set; } = true; /// /// Get or set player's blip color /// public static BlipColor BlipColor { get; set; } = BlipColor.White; /// /// Get or set player's blip sprite /// public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard; /// /// Get or set scale of player's blip /// public static float BlipScale { get; set; } = 1; public static bool ShowPlayerNameTag { get => Settings.ShowPlayerNameTag; set { if (value == ShowPlayerNameTag) return; Settings.ShowPlayerNameTag = value; Util.SaveSettings(); } } } /// /// Base events for RageCoop /// public static class Events { /// /// The local player is dead /// public static event EmptyEvent OnPlayerDied; /// /// A local vehicle is spawned /// public static event EventHandler OnVehicleSpawned; /// /// A local vehicle is deleted /// public static event EventHandler OnVehicleDeleted; /// /// A local ped is spawned /// public static event EventHandler OnPedSpawned; /// /// A local ped is deleted /// public static event EventHandler OnPedDeleted; #region DELEGATES /// /// public delegate void EmptyEvent(); /// /// /// /// public delegate void CustomEvent(int hash, List 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 InvokeCustomEventReceived(Packets.CustomEvent p) { // Log.Debug($"CustomEvent:\n"+args.Args.DumpWithType()); if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers)) { fixed (byte* pData = p.Payload) { foreach (var handler in handlers) { try { handler.Invoke(p.Hash, pData, p.Payload.Length); } catch (Exception ex) { Log.Error("InvokeCustomEvent", ex); } } } } } #endregion } #region PROPERTIES /// /// Get the local player's ID /// /// PlayerID public static int LocalPlayerID => Main.LocalPlayerID; /// /// Check if player is connected to a server /// public static bool IsOnServer => Networking.IsOnServer; /// /// Get an that the player is currently connected to, or null if not connected to /// the server /// public static IPEndPoint ServerEndPoint => Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null; /// /// Check if a RAGECOOP menu is visible /// public static bool IsMenuVisible => CoopMenu.MenuPool.AreAnyVisible; /// /// Check if the RAGECOOP chat is visible /// public static bool IsChatFocused => MainChat.Focused; /// /// Check if the RAGECOOP list of players is visible /// public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000; /// /// Get the version of RAGECOOP /// public static Version CurrentVersion => Main.ModVersion; /// /// Get all players indexed by their ID /// public static Dictionary Players => new(PlayerList.Players); #endregion #region FUNCTIONS /// /// Queue an action to be executed on next tick. /// /// public static void QueueAction(Action a) { WorldThread.QueueAction(a); } public static void QueueActionAndWait(Action a, int timeout = 15000) { var done = new AutoResetEvent(false); Exception e = null; QueueAction(() => { try { a(); done.Set(); } catch (Exception ex) { e = ex; } }); if (e != null) throw e; if (!done.WaitOne(timeout)) throw new TimeoutException(); } /// /// Queue an action to be executed on next tick, allowing you to call scripting API from another thread. /// /// /// An to be executed with a return value indicating whether it can be /// removed after execution. /// public static void QueueAction(Func a) { WorldThread.QueueAction(a); } /// /// Send an event and data to the server. /// /// An unique identifier of the event /// /// The objects conataing your data, see for a list of supported /// types /// public static void SendCustomEvent(CustomEventHash eventHash, params object[] args) => SendCustomEvent(CustomEventFlags.None, eventHash, args); /// /// Send an event and data to the server /// /// /// An unique identifier of the event /// /// The objects conataing your data, see for a list of supported /// types /// public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args) { var writer = GetWriter(); CustomEvents.WriteObjects(writer, args); Networking.Peer.SendTo(new Packets.CustomEvent(flags) { Payload = writer.ToByteArray(writer.Position), Hash = eventHash }, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered); } /// /// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from /// backgound thread, use in the handler to dispatch code to script thread. /// /// /// An unique identifier of the event /// /// An handler to be invoked when the event is received from the server. public static void RegisterCustomEventHandler(CustomEventHash hash, Action handler) => RegisterCustomEventHandler(hash, (CustomEventHandler)handler); /// /// /// public static void RequestSharedFile(string name, Action callback) { EventHandler handler = (s, e) => { if (e.EndsWith(name)) callback(e); }; DownloadManager.DownloadCompleted += handler; Networking.GetResponse(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); } }); } /// /// Connect to a server /// /// Address of the server, e.g. 127.0.0.1:4499 /// When a connection is active or being established [Remoting] 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); } /// /// Disconnect from current server or cancel the connection attempt. /// [Remoting] public static void Disconnect() { if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null); } /// /// List all servers from master server address /// /// [Remoting] public static List ListServers() { return JsonDeserialize>( HttpHelper.DownloadString(Settings.MasterServer)); } /// /// Send a local chat message to this player /// /// Name of the sender /// The player's message [Remoting] public static void LocalChatMessage(string from, string message) { MainChat.AddMessage(from, message); } /// /// Send a chat message or command to server/other players /// /// [Remoting] public static void SendChatMessage(string message) { if (!IsOnServer) throw new InvalidOperationException("Not on server"); Networking.SendChatMessage(message); } /// /// Get the with this name /// /// /// [Remoting] public static ClientResource GetResource(string name) { if (MainRes.LoadedResources.TryGetValue(name, out var resource)) return resource; return null; } /// /// Get that contains the specified file or directory /// /// [Remoting] public static ClientResource GetResourceFromPath(string path) { path = Path.GetFullPath(path).ToLower(); foreach (var res in MainRes.LoadedResources.Values) { if (res.ScriptsDirectory.ToLower() == path || res.Files.Any(file => file.Value.FullPath.ToLower() == path)) return res; } return null; } [Remoting(GenBridge = false)] public static object GetProperty(string name) => typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null); [Remoting(GenBridge = false)] public static void SetProperty(string name, string jsonVal) { var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ; if (prop == null) throw new KeyNotFoundException($"Property {name} was not found"); prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType)); } [Remoting] public static object GetConfig(string name) => typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null); [Remoting] public static void SetConfig(string name, string jsonVal) { var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public); if (prop == null) throw new KeyNotFoundException($"Property {name} was not found"); prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType)); } /// /// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from /// backgound thread, use in the handler to dispatch code to script thread. /// /// /// An unique identifier of the event /// /// An handler to be invoked when the event is received from the server. [Remoting] public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler) { if (handler.Directory == default) throw new ArgumentException("Script directory not specified"); if (handler.FunctionPtr == default) throw new ArgumentException("Function pointer not specified"); lock (CustomEventHandlers) { if (!CustomEventHandlers.TryGetValue(hash, out var handlers)) CustomEventHandlers.Add(hash, handlers = new()); handlers.Add(handler); } } #endregion } }