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