#undef DEBUG using System; using System.Collections.Generic; using System.Net; using System.Threading; using GTA; using Lidgren.Network; using Newtonsoft.Json; using RageCoop.Client.Menus; using RageCoop.Core; using RageCoop.Core.Scripting; 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 { /// /// 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) { 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 /// /// 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 => 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 all players indexed by their ID /// public static Dictionary Players => new Dictionary(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); } /// /// 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, 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, 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 var 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 } }