#undef DEBUG using GTA; using Newtonsoft.Json; using RageCoop.Core; using RageCoop.Core.Scripting; using SHVDN; using System; using System.Collections.Generic; using System.Threading; using System.Windows.Forms; namespace RageCoop.Client.Scripting { /// /// /// public class CustomEventReceivedArgs : EventArgs { /// /// The event hash /// public int Hash { get; set; } /// /// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion /// public object[] Args { get; set; } } /// /// Provides vital functionality to interact with RAGECOOP /// public static class API { #region INTERNAL internal static Dictionary>> CustomEventHandlers = new Dictionary>>(); #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 => Main.Settings.Username; set { if (Networking.IsOnServer || string.IsNullOrEmpty(value)) { return; } Main.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; } /// /// Base events for RageCoop /// public static class Events { #region DELEGATES /// /// /// public delegate void EmptyEvent(); /// /// /// /// /// public delegate void CustomEvent(int hash, List args); #endregion /// /// 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; /// /// This is equivalent of . /// /// Calling in the handler will interfer other scripts, subscribe to instead. [Obsolete] public static event EmptyEvent OnTick; /// /// This is equivalent of /// /// Calling in the handler will interfer other scripts, subscribe to instead. [Obsolete] public static KeyEventHandler OnKeyDown; /// /// This is equivalent of /// /// Calling in the handler will interfer other scripts, subscribe to instead. [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> handlers)) { handlers.ForEach((x) => { x.Invoke(args); }); } } #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 System.Net.IPEndPoint ServerEndPoint => Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null; /// /// Check if a RAGECOOP menu is visible /// public static bool IsMenuVisible => Menus.CoopMenu.MenuPool.AreAnyVisible; /// /// Check if the RAGECOOP chat is visible /// public static bool IsChatFocused => Main.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.Version; /// /// Get a that RAGECOOP is currently using. /// /// public static Logger Logger => Main.Logger; /// /// Get all players indexed by their ID /// public static Dictionary Players => new Dictionary(PlayerList.Players); #endregion #region FUNCTIONS /// /// Connect to a server /// /// Address of the server, e.g. 127.0.0.1:4499 /// When a connection is active or being established 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. /// public static void Disconnect() { if (Networking.IsOnServer || Networking.IsConnecting) { Networking.ToggleConnection(null); } } /// /// List all servers from master server address /// /// public static List ListServers() { return JsonConvert.DeserializeObject>(HttpHelper.DownloadString(Main.Settings.MasterServer)); } /// /// Send a local chat message to this player /// /// Name of the sender /// The player's message public static void LocalChatMessage(string from, string message) { Main.MainChat.AddMessage(from, message); } /// /// Send a chat message or command to server/other players /// /// public static void SendChatMessage(string message) { Networking.SendChatMessage(message); } /// /// 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) { Networking.Peer.SendTo(new Packets.CustomEvent() { Args = args, Hash = eventHash }, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered); } /// /// 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) { Networking.Peer.SendTo(new Packets.CustomEvent(flags) { Args = args, Hash = eventHash }, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.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, you can hash your event name with /// An handler to be invoked when the event is received from the server. public static void RegisterCustomEventHandler(CustomEventHash hash, Action handler) { lock (CustomEventHandlers) { if (!CustomEventHandlers.TryGetValue(hash, out List> handlers)) { CustomEventHandlers.Add(hash, handlers = new List>()); } handlers.Add(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); } }); } #endregion /// /// 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; } else 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); } } }