From eb5b23ae172e741c6b1a25a7d2e568b4e07f6948 Mon Sep 17 00:00:00 2001 From: Sardelka Date: Fri, 1 Jul 2022 19:02:38 +0800 Subject: [PATCH] Server side entities and custom resource location --- RageCoop.Client/Networking/DownloadManager.cs | 2 +- RageCoop.Client/Networking/MapLoader.cs | 12 +- RageCoop.Client/Scripting/API.cs | 16 +- RageCoop.Client/Scripting/BaseScript.cs | 4 +- RageCoop.Client/Settings.cs | 4 + RageCoop.Client/Sync/EntityPool.cs | 14 ++ RageCoop.Client/Sync/StateThreads.cs | 34 --- RageCoop.Core/Scripting/CustomEvents.cs | 3 +- RageCoop.Core/Worker.cs | 2 - RageCoop.Server/Client.cs | 55 +++-- RageCoop.Server/Scripting/API.cs | 41 +--- RageCoop.Server/Scripting/BaseScript.cs | 10 +- RageCoop.Server/Server.cs | 21 +- RageCoop.Server/ServerEntities.cs | 228 ++++++++++++++++++ RageCoop.Server/ServerSettings.cs | 6 +- 15 files changed, 325 insertions(+), 127 deletions(-) delete mode 100644 RageCoop.Client/Sync/StateThreads.cs create mode 100644 RageCoop.Server/ServerEntities.cs diff --git a/RageCoop.Client/Networking/DownloadManager.cs b/RageCoop.Client/Networking/DownloadManager.cs index 6ac2821..b4b7ecb 100644 --- a/RageCoop.Client/Networking/DownloadManager.cs +++ b/RageCoop.Client/Networking/DownloadManager.cs @@ -52,7 +52,7 @@ namespace RageCoop.Client } }); } - static string downloadFolder = $"RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}"; + static string downloadFolder = Path.Combine(Main.Settings.ResourceDirectory, Main.Settings.LastServerAddress.Replace(":", ".")); private static readonly Dictionary InProgressDownloads = new Dictionary(); public static bool AddFile(int id, string name, long length) diff --git a/RageCoop.Client/Networking/MapLoader.cs b/RageCoop.Client/Networking/MapLoader.cs index 552d185..0102791 100644 --- a/RageCoop.Client/Networking/MapLoader.cs +++ b/RageCoop.Client/Networking/MapLoader.cs @@ -14,7 +14,7 @@ namespace RageCoop.Client /// /// [XmlRoot(ElementName = "Map")] - public class CoopMap + public class Map { /// /// @@ -54,7 +54,7 @@ namespace RageCoop.Client internal static class MapLoader { // string = file name - private static readonly Dictionary _maps = new Dictionary(); + private static readonly Dictionary _maps = new Dictionary(); private static readonly List _createdObjects = new List(); public static void LoadAll() @@ -84,14 +84,14 @@ namespace RageCoop.Client string filePath = files[i]; string fileName = Path.GetFileName(filePath); - XmlSerializer serializer = new XmlSerializer(typeof(CoopMap)); - CoopMap map; + XmlSerializer serializer = new XmlSerializer(typeof(Map)); + Map map; using (var stream = new FileStream(filePath, FileMode.Open)) { try { - map = (CoopMap)serializer.Deserialize(stream); + map = (Map)serializer.Deserialize(stream); } catch (Exception ex) { @@ -117,7 +117,7 @@ namespace RageCoop.Client return; } - CoopMap map = _maps[name]; + Map map = _maps[name]; foreach (CoopProp prop in map.Props) { diff --git a/RageCoop.Client/Scripting/API.cs b/RageCoop.Client/Scripting/API.cs index 89d8f73..d22dc60 100644 --- a/RageCoop.Client/Scripting/API.cs +++ b/RageCoop.Client/Scripting/API.cs @@ -217,7 +217,7 @@ namespace RageCoop.Client.Scripting get { return Main.CurrentVersion; } } /// - /// Send an event and data to the specified clients. + /// Send an event and data to the server. /// /// An unique identifier of the event /// The objects conataing your data, supported types: @@ -231,6 +231,20 @@ namespace RageCoop.Client.Scripting }; Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered); } + /// + /// Send an event and data to the server. + /// + /// + /// + public static void SendCustomEvent(int eventHash,params object[] args) + { + var p = new Packets.CustomEvent() + { + Args=new List(args), + Hash=eventHash + }; + Networking.Send(p, 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. diff --git a/RageCoop.Client/Scripting/BaseScript.cs b/RageCoop.Client/Scripting/BaseScript.cs index 72361d5..93a34cf 100644 --- a/RageCoop.Client/Scripting/BaseScript.cs +++ b/RageCoop.Client/Scripting/BaseScript.cs @@ -11,7 +11,9 @@ namespace RageCoop.Client.Scripting { API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn); API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall); - + API.Events.OnPedDeleted+=(s,p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted,p.ID); }; + API.Events.OnVehicleDeleted+=(s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); }; + } public override void OnStop() diff --git a/RageCoop.Client/Settings.cs b/RageCoop.Client/Settings.cs index e6a8a16..641a5c1 100644 --- a/RageCoop.Client/Settings.cs +++ b/RageCoop.Client/Settings.cs @@ -63,5 +63,9 @@ namespace RageCoop.Client /// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended). /// public int WorldPedSoftLimit { get; set; } = 50; + /// + /// The directory where resources downloaded from server will be placed. + /// + public string ResourceDirectory { get; set; } = "RageCoop\\Resources"; } } diff --git a/RageCoop.Client/Sync/EntityPool.cs b/RageCoop.Client/Sync/EntityPool.cs index 28bf55a..a79bb12 100644 --- a/RageCoop.Client/Sync/EntityPool.cs +++ b/RageCoop.Client/Sync/EntityPool.cs @@ -5,6 +5,7 @@ using GTA.Native; using RageCoop.Core; using System.Collections.Generic; using System.Linq; +using RageCoop.Client.Scripting; using System.Text; using System.Diagnostics; using System.Threading.Tasks; @@ -138,6 +139,10 @@ namespace RageCoop.Client { Handle_Peds.Add(c.MainPed.Handle, c); } + if (c.IsMine) + { + API.Events.InvokePedSpawned(c); + } } public static void RemovePed(int id,string reason="Cleanup") { @@ -160,6 +165,10 @@ namespace RageCoop.Client c.PedBlip?.Delete(); c.ParachuteProp?.Delete(); ID_Peds.Remove(id); + if (c.IsMine) + { + API.Events.InvokePedDeleted(c); + } } } #endregion @@ -196,6 +205,10 @@ namespace RageCoop.Client { Handle_Vehicles.Add(v.MainVehicle.Handle, v); } + if (v.IsMine) + { + API.Events.InvokeVehicleSpawned(v); + } } public static void RemoveVehicle(int id,string reason = "Cleanup") { @@ -215,6 +228,7 @@ namespace RageCoop.Client veh.Delete(); } ID_Vehicles.Remove(id); + if (v.IsMine) { API.Events.InvokeVehicleDeleted(v); } } } diff --git a/RageCoop.Client/Sync/StateThreads.cs b/RageCoop.Client/Sync/StateThreads.cs deleted file mode 100644 index 1c61832..0000000 --- a/RageCoop.Client/Sync/StateThreads.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using GTA; -/* -namespace RageCoop.Client.Sync -{ - internal class VehicleStateThread : Script - { - public VehicleStateThread() - { - Tick+=OnTick; - } - int current; - int toSendPerFrame; - int sent; - private void OnTick(object sender, EventArgs e) - { - toSendPerFrame=EntityPool.allVehicles.Length*5/(int)Game.FPS+1; - if (!Networking.IsOnServer) { return; } - for(; sent=EntityPool.allVehicles.Length) - { - current=0; - } - Networking.SendVehicleState(EntityPool.allVehicles[current]) - } - } - } -} -*/ \ No newline at end of file diff --git a/RageCoop.Core/Scripting/CustomEvents.cs b/RageCoop.Core/Scripting/CustomEvents.cs index e27c388..f78e45b 100644 --- a/RageCoop.Core/Scripting/CustomEvents.cs +++ b/RageCoop.Core/Scripting/CustomEvents.cs @@ -13,7 +13,8 @@ namespace RageCoop.Core.Scripting static MD5 Hasher = MD5.Create(); static Dictionary Hashed=new Dictionary(); internal static readonly int SetWeather = Hash("RageCoop.SetWeather"); - internal static readonly int OnPlayerDied = Hash("RageCoop.OnPlayerDied"); + internal static readonly int OnPedDeleted = Hash("RageCoop.OnPedDeleted"); + internal static readonly int OnVehicleDeleted = Hash("RageCoop.OnVehicleDeleted"); internal static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn"); internal static readonly int NativeCall = Hash("RageCoop.NativeCall"); internal static readonly int NativeResponse = Hash("RageCoop.NativeResponse"); diff --git a/RageCoop.Core/Worker.cs b/RageCoop.Core/Worker.cs index 792eb94..546d9d9 100644 --- a/RageCoop.Core/Worker.cs +++ b/RageCoop.Core/Worker.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using System.Collections.Concurrent; diff --git a/RageCoop.Server/Client.cs b/RageCoop.Server/Client.cs index c7d8fea..d5cdb89 100644 --- a/RageCoop.Server/Client.cs +++ b/RageCoop.Server/Client.cs @@ -2,38 +2,12 @@ using System.Collections.Generic; using RageCoop.Core; using Lidgren.Network; -using GTA.Math; +using System.Linq; using RageCoop.Core.Scripting; using System.Security.Cryptography; namespace RageCoop.Server { - /// - /// Represents a ped from a client - /// - public class ServerPed - { - /// - /// The that is responsible synchronizing for this ped. - /// - public Client Owner { get; internal set; } - /// - /// The ped's ID (not handle!). - /// - public int ID { get;internal set; } - /// - /// The ID of the ped's last vehicle. - /// - public int VehicleID { get; internal set; } - /// - /// Position of this ped - /// - public Vector3 Position { get; internal set; } - /// - /// Health - /// - public int Health { get; internal set; } - } /// /// /// @@ -84,7 +58,7 @@ namespace RageCoop.Server /// /// Th client's IP address and port. /// - public System.Net.IPEndPoint EndPoint { get { return Connection.RemoteEndPoint; } } + public System.Net.IPEndPoint EndPoint { get { return Connection?.RemoteEndPoint; } } internal long NetID = 0; internal NetConnection Connection { get;set; } /// @@ -260,6 +234,31 @@ namespace RageCoop.Server Server.Logger?.Error(ex); } } + public void SendCustomEvent(int hash,params object[] args) + { + if (!IsReady) + { + Server.Logger?.Warning($"Player \"{Username}\" is not ready!"); + } + + try + { + + NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); + new Packets.CustomEvent() + { + Hash=hash, + Args=new(args) + }.Pack(outgoingMessage); + Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event); + + } + catch (Exception ex) + { + Server.Logger?.Error(ex); + } + } + #endregion } } diff --git a/RageCoop.Server/Scripting/API.cs b/RageCoop.Server/Scripting/API.cs index 95c2830..2f79130 100644 --- a/RageCoop.Server/Scripting/API.cs +++ b/RageCoop.Server/Scripting/API.cs @@ -144,45 +144,12 @@ namespace RageCoop.Server.Scripting /// Server side events /// public readonly APIEvents Events; - #region FUNCTIONS - /* + /// - /// Send a native call (Function.Call) to all players. - /// Keys = int, float, bool, string and lvector3 + /// All synchronized entities on this server. /// - /// The hash (Example: 0x25223CA6B4D20B7F = GET_CLOCK_HOURS) - /// The arguments (Example: string = int, object = 5) - public void SendNativeCallToAll(GTA.Native.Hash hash, params object[] args) - { - try - { - if (Server.MainNetServer.ConnectionsCount == 0) - { - return; - } - - if (args != null && args.Length == 0) - { - Server.Logger?.Error($"[ServerScript->SendNativeCallToAll(ulong hash, params object[] args)]: args is not null!"); - return; - } - - Packets.NativeCall packet = new() - { - Hash = (ulong)hash, - Args = new List(args) ?? new List() - }; - - NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); - packet.Pack(outgoingMessage); - Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native); - } - catch (Exception e) - { - Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); - } - } - */ + public ServerEntities Entities { get { return Server.Entities; } } + #region FUNCTIONS /// /// Get a list of all Clients /// diff --git a/RageCoop.Server/Scripting/BaseScript.cs b/RageCoop.Server/Scripting/BaseScript.cs index 496ca8b..cd46632 100644 --- a/RageCoop.Server/Scripting/BaseScript.cs +++ b/RageCoop.Server/Scripting/BaseScript.cs @@ -12,13 +12,21 @@ namespace RageCoop.Server.Scripting public override void OnStart() { API.RegisterCustomEventHandler(CustomEvents.NativeResponse, NativeResponse); + API.RegisterCustomEventHandler(CustomEvents.OnVehicleDeleted, (e) => + { + API.Entities.RemoveVehicle((int)e.Args[0]); + }); + API.RegisterCustomEventHandler(CustomEvents.OnPedDeleted, (e) => + { + API.Entities.RemovePed((int)e.Args[0]); + }); } public override void OnStop() { } public void SetAutoRespawn(Client c,bool toggle) { - c.SendCustomEvent(CustomEvents.SetAutoRespawn, new() { toggle }); + c.SendCustomEvent(CustomEvents.SetAutoRespawn, toggle ); } void NativeResponse(CustomEventReceivedArgs e) { diff --git a/RageCoop.Server/Server.cs b/RageCoop.Server/Server.cs index 2df8e6c..ee42b7a 100644 --- a/RageCoop.Server/Server.cs +++ b/RageCoop.Server/Server.cs @@ -36,6 +36,7 @@ namespace RageCoop.Server internal BaseScript BaseScript { get; set; }=new BaseScript(); internal readonly ServerSettings Settings; internal NetServer MainNetServer; + internal ServerEntities Entities; internal readonly Dictionary> Commands = new(); internal readonly Dictionary Clients = new(); @@ -67,6 +68,7 @@ namespace RageCoop.Server API=new API(this); Resources=new Resources(this); Security=new Security(Logger); + Entities=new ServerEntities(this); } /// @@ -670,6 +672,7 @@ namespace RageCoop.Server }.Pack(outgoingMessage); MainNetServer.SendMessage(outgoingMessage,cons , NetDeliveryMethod.ReliableOrdered, 0); } + Entities.CleanUp(localClient); _worker.QueueJob(() => API.Events.InvokePlayerDisconnected(localClient)); Logger?.Info($"Player {localClient.Username} disconnected! ID:{localClient.Player.ID}"); Clients.Remove(localClient.NetID); @@ -693,14 +696,8 @@ namespace RageCoop.Server } private void VehicleStateSync(Packets.VehicleStateSync packet, Client client) { - // Save the new data - _worker.QueueJob(() => - { - if (packet.Passengers.ContainsValue(client.Player.ID)) - { - client.Player.VehicleID = packet.ID; - } - }); + _worker.QueueJob(() => Entities.Update(packet, client)); + foreach (var c in Clients.Values) { @@ -712,12 +709,11 @@ namespace RageCoop.Server } private void PedSync(Packets.PedSync packet, Client client) { + _worker.QueueJob(() => Entities.Update(packet, client)); + bool isPlayer = packet.ID==client.Player.ID; if (isPlayer) { - client.Player.Position=packet.Position; - client.Player.Health=packet.Health ; - client.Player.Owner=client; _worker.QueueJob(() => API.Events.InvokePlayerUpdate(client)); } @@ -747,7 +743,8 @@ namespace RageCoop.Server } private void VehicleSync(Packets.VehicleSync packet, Client client) { - bool isPlayer = packet.ID==client.Player.VehicleID; + _worker.QueueJob(() => Entities.Update(packet, client)); + bool isPlayer = packet.ID==client.Player?.LastVehicle?.ID; foreach (var c in Clients.Values) { if (c.NetID==client.NetID) { continue; } diff --git a/RageCoop.Server/ServerEntities.cs b/RageCoop.Server/ServerEntities.cs new file mode 100644 index 0000000..3a0a96a --- /dev/null +++ b/RageCoop.Server/ServerEntities.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using RageCoop.Core; +using GTA.Math; +using GTA; + +namespace RageCoop.Server +{ + + /// + /// Represents a ped from a client + /// + public class ServerPed + { + /// + /// The that is responsible synchronizing for this ped. + /// + public Client Owner { get; internal set; } + + /// + /// The ped's network ID (not handle!). + /// + public int ID { get; internal set; } + + /// + /// Whether this ped is a player. + /// + public bool IsPlayer { get { return Owner?.Player==this; } } + + /// + /// The ped's last vehicle. + /// + public ServerVehicle LastVehicle { get; internal set; } + + /// + /// Position of this ped + /// + public Vector3 Position { get; internal set; } + + + /// + /// Gets or sets this ped's rotation + /// + public Vector3 Rotation { get; internal set; } + + /// + /// Health + /// + public int Health { get; internal set; } + } + /// + /// Represents a vehicle from a client + /// + public class ServerVehicle + { + /// + /// The that is responsible synchronizing for this vehicle. + /// + public Client Owner { get; internal set; } + + /// + /// The vehicle's network ID (not handle!). + /// + public int ID { get; internal set; } + + /// + /// Position of this vehicle + /// + public Vector3 Position { get; internal set; } + + /// + /// Gets or sets this vehicle's quaternion + /// + public Quaternion Quaternion { get; internal set; } + } + + /// + /// Represents an object owned by server. + /// + public class ServerObject + { + internal ServerObject() + { + + } + /// + /// The object's model + /// + public Model Model { get; internal set; } + + /// + /// Gets or sets this object's position + /// + public Vector3 Position { get;set; } + + /// + /// Gets or sets this object's quaternion + /// + public Quaternion Quaternion { get; set; } + + /// + /// Whether this object is invincible + /// + public bool IsInvincible { get; set; } + } + + /// + /// Manipulate entities from the server + /// + public class ServerEntities + { + private readonly Server Server; + internal ServerEntities(Server server) + { + Server = server; + } + internal Dictionary Peds { get; set; } = new(); + internal Dictionary Vehicles { get; set; } = new(); + internal Dictionary ServerObjects { get; set; }=new(); + + /// + /// Get all peds on this server + /// + /// + public ServerPed[] GetAllPeds() + { + return Peds.Values.ToArray(); + } + + /// + /// Get all vehicles on this server + /// + /// + public ServerVehicle[] GetAllVehicle() + { + return Vehicles.Values.ToArray(); + } + + /// + /// Get all static objects owned by server + /// + /// + public ServerObject[] GetAllObjects() + { + return ServerObjects.Values.ToArray(); + } + + /// + /// Not thread safe + /// + internal void Update(Packets.PedSync p,Client sender) + { + ServerPed ped; + if(!Peds.TryGetValue(p.ID,out ped)) + { + Peds.Add(p.ID,ped=new ServerPed()); + ped.ID=p.ID; + } + ped.Position = p.Position; + ped.Owner=sender; + ped.Health=p.Health; + ped.Rotation=p.Rotation; + ped.Owner=sender; + } + internal void Update(Packets.VehicleSync p, Client sender) + { + ServerVehicle veh; + if (!Vehicles.TryGetValue(p.ID, out veh)) + { + Vehicles.Add(p.ID, veh=new ServerVehicle()); + veh.ID=p.ID; + } + veh.Position = p.Position; + veh.Owner=sender; + veh.Quaternion=p.Quaternion; + } + internal void Update(Packets.VehicleStateSync p, Client sender) + { + ServerVehicle veh; + if (!Vehicles.TryGetValue(p.ID, out veh)) + { + Vehicles.Add(p.ID, veh=new ServerVehicle()); + veh.ID=p.ID; + } + foreach(var pair in p.Passengers) + { + if(Peds.TryGetValue(pair.Value,out var ped)) + { + ped.LastVehicle=veh; + } + } + } + internal void CleanUp(Client left) + { + Server.Logger?.Trace("Removing all entities from: "+left.Username); + + foreach (var pair in Peds) + { + if (pair.Value.Owner==left) + { + Server.QueueJob(()=>Peds.Remove(pair.Key)); + } + } + foreach (var pair in Vehicles) + { + if (pair.Value.Owner==left) + { + Server.QueueJob(() => Vehicles.Remove(pair.Key)); + } + } + Server.QueueJob(() => + Server.Logger?.Trace("Remaining entities: "+(Peds.Count+Vehicles.Count))); + } + internal void RemoveVehicle(int id) + { + // Server.Logger?.Trace($"Removing vehicle:{id}"); + if (Vehicles.ContainsKey(id)) { Vehicles.Remove(id); } + } + internal void RemovePed(int id) + { + // Server.Logger?.Trace($"Removing ped:{id}"); + if (Peds.ContainsKey(id)) { Peds.Remove(id); } + } + } +} diff --git a/RageCoop.Server/ServerSettings.cs b/RageCoop.Server/ServerSettings.cs index d0a46e2..736d9be 100644 --- a/RageCoop.Server/ServerSettings.cs +++ b/RageCoop.Server/ServerSettings.cs @@ -27,18 +27,18 @@ public string WelcomeMessage { get; set; } = "Welcome on this server :)"; // public bool HolePunch { get; set; } = true; /// - /// Whether or not to announce this server so that it'll appear on server list. + /// Whether or not to announce this server so it'll appear on server list. /// public bool AnnounceSelf { get; set; } = false; /// - /// Master server address, mostly doesn't to be changed. + /// Master server address, mostly doesn't need to be changed. /// public string MasterServer { get; set; } = "[AUTO]"; /// /// See . /// - public int LogLevel=2; + public int LogLevel { get; set; }=2; /// /// NPC data won't be sent to a player if their distance is greater than this value. -1 for unlimited. ///