Server side entities and custom resource location

This commit is contained in:
Sardelka
2022-07-01 19:02:38 +08:00
parent 77999fe8f3
commit eb5b23ae17
15 changed files with 325 additions and 127 deletions

View File

@ -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<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
public static bool AddFile(int id, string name, long length)

View File

@ -14,7 +14,7 @@ namespace RageCoop.Client
///
/// </summary>
[XmlRoot(ElementName = "Map")]
public class CoopMap
public class Map
{
/// <summary>
///
@ -54,7 +54,7 @@ namespace RageCoop.Client
internal static class MapLoader
{
// string = file name
private static readonly Dictionary<string, CoopMap> _maps = new Dictionary<string, CoopMap>();
private static readonly Dictionary<string, Map> _maps = new Dictionary<string, Map>();
private static readonly List<int> _createdObjects = new List<int>();
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)
{

View File

@ -217,7 +217,7 @@ namespace RageCoop.Client.Scripting
get { return Main.CurrentVersion; }
}
/// <summary>
/// Send an event and data to the specified clients.
/// Send an event and data to the server.
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, supported types:
@ -231,6 +231,20 @@ namespace RageCoop.Client.Scripting
};
Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Send an event and data to the server.
/// </summary>
/// <param name="eventHash"></param>
/// <param name="args"></param>
public static void SendCustomEvent(int eventHash,params object[] args)
{
var p = new Packets.CustomEvent()
{
Args=new List<object>(args),
Hash=eventHash
};
Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread.

View File

@ -11,6 +11,8 @@ 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); };
}

View File

@ -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).
/// </summary>
public int WorldPedSoftLimit { get; set; } = 50;
/// <summary>
/// The directory where resources downloaded from server will be placed.
/// </summary>
public string ResourceDirectory { get; set; } = "RageCoop\\Resources";
}
}

View File

@ -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); }
}
}

View File

@ -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<toSendPerFrame; sent++)
{
if (current>=EntityPool.allVehicles.Length)
{
current=0;
}
Networking.SendVehicleState(EntityPool.allVehicles[current])
}
}
}
}
*/

View File

@ -13,7 +13,8 @@ namespace RageCoop.Core.Scripting
static MD5 Hasher = MD5.Create();
static Dictionary<int,string> Hashed=new Dictionary<int,string>();
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");

View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;

View File

@ -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
{
/// <summary>
/// Represents a ped from a client
/// </summary>
public class ServerPed
{
/// <summary>
/// The <see cref="Client"/> that is responsible synchronizing for this ped.
/// </summary>
public Client Owner { get; internal set; }
/// <summary>
/// The ped's ID (not handle!).
/// </summary>
public int ID { get;internal set; }
/// <summary>
/// The ID of the ped's last vehicle.
/// </summary>
public int VehicleID { get; internal set; }
/// <summary>
/// Position of this ped
/// </summary>
public Vector3 Position { get; internal set; }
/// <summary>
/// Health
/// </summary>
public int Health { get; internal set; }
}
/// <summary>
///
/// </summary>
@ -84,7 +58,7 @@ namespace RageCoop.Server
/// <summary>
/// Th client's IP address and port.
/// </summary>
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; }
/// <summary>
@ -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
}
}

View File

@ -144,45 +144,12 @@ namespace RageCoop.Server.Scripting
/// Server side events
/// </summary>
public readonly APIEvents Events;
#region FUNCTIONS
/*
/// <summary>
/// Send a native call (Function.Call) to all players.
/// Keys = int, float, bool, string and lvector3
/// All synchronized entities on this server.
/// </summary>
/// <param name="hash">The hash (Example: 0x25223CA6B4D20B7F = GET_CLOCK_HOURS)</param>
/// <param name="args">The arguments (Example: string = int, object = 5)</param>
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<object>(args) ?? new List<object>()
};
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
/// <summary>
/// Get a list of all Clients
/// </summary>

View File

@ -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)
{

View File

@ -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<Command, Action<CommandContext>> Commands = new();
internal readonly Dictionary<long,Client> 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);
}
/// <summary>
@ -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; }

View File

@ -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
{
/// <summary>
/// Represents a ped from a client
/// </summary>
public class ServerPed
{
/// <summary>
/// The <see cref="Client"/> that is responsible synchronizing for this ped.
/// </summary>
public Client Owner { get; internal set; }
/// <summary>
/// The ped's network ID (not handle!).
/// </summary>
public int ID { get; internal set; }
/// <summary>
/// Whether this ped is a player.
/// </summary>
public bool IsPlayer { get { return Owner?.Player==this; } }
/// <summary>
/// The ped's last vehicle.
/// </summary>
public ServerVehicle LastVehicle { get; internal set; }
/// <summary>
/// Position of this ped
/// </summary>
public Vector3 Position { get; internal set; }
/// <summary>
/// Gets or sets this ped's rotation
/// </summary>
public Vector3 Rotation { get; internal set; }
/// <summary>
/// Health
/// </summary>
public int Health { get; internal set; }
}
/// <summary>
/// Represents a vehicle from a client
/// </summary>
public class ServerVehicle
{
/// <summary>
/// The <see cref="Client"/> that is responsible synchronizing for this vehicle.
/// </summary>
public Client Owner { get; internal set; }
/// <summary>
/// The vehicle's network ID (not handle!).
/// </summary>
public int ID { get; internal set; }
/// <summary>
/// Position of this vehicle
/// </summary>
public Vector3 Position { get; internal set; }
/// <summary>
/// Gets or sets this vehicle's quaternion
/// </summary>
public Quaternion Quaternion { get; internal set; }
}
/// <summary>
/// Represents an object owned by server.
/// </summary>
public class ServerObject
{
internal ServerObject()
{
}
/// <summary>
/// The object's model
/// </summary>
public Model Model { get; internal set; }
/// <summary>
/// Gets or sets this object's position
/// </summary>
public Vector3 Position { get;set; }
/// <summary>
/// Gets or sets this object's quaternion
/// </summary>
public Quaternion Quaternion { get; set; }
/// <summary>
/// Whether this object is invincible
/// </summary>
public bool IsInvincible { get; set; }
}
/// <summary>
/// Manipulate entities from the server
/// </summary>
public class ServerEntities
{
private readonly Server Server;
internal ServerEntities(Server server)
{
Server = server;
}
internal Dictionary<int, ServerPed> Peds { get; set; } = new();
internal Dictionary<int, ServerVehicle> Vehicles { get; set; } = new();
internal Dictionary<int,ServerObject> ServerObjects { get; set; }=new();
/// <summary>
/// Get all peds on this server
/// </summary>
/// <returns></returns>
public ServerPed[] GetAllPeds()
{
return Peds.Values.ToArray();
}
/// <summary>
/// Get all vehicles on this server
/// </summary>
/// <returns></returns>
public ServerVehicle[] GetAllVehicle()
{
return Vehicles.Values.ToArray();
}
/// <summary>
/// Get all static objects owned by server
/// </summary>
/// <returns></returns>
public ServerObject[] GetAllObjects()
{
return ServerObjects.Values.ToArray();
}
/// <summary>
/// Not thread safe
/// </summary>
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); }
}
}
}

View File

@ -27,18 +27,18 @@
public string WelcomeMessage { get; set; } = "Welcome on this server :)";
// public bool HolePunch { get; set; } = true;
/// <summary>
/// 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.
/// </summary>
public bool AnnounceSelf { get; set; } = false;
/// <summary>
/// Master server address, mostly doesn't to be changed.
/// Master server address, mostly doesn't need to be changed.
/// </summary>
public string MasterServer { get; set; } = "[AUTO]";
/// <summary>
/// See <see cref="Core.Logger.LogLevel"/>.
/// </summary>
public int LogLevel=2;
public int LogLevel { get; set; }=2;
/// <summary>
/// NPC data won't be sent to a player if their distance is greater than this value. -1 for unlimited.
/// </summary>