Error handling, bug fixes, new API functions and more added!
This commit is contained in:
@ -442,7 +442,7 @@ namespace CoopClient
|
|||||||
|
|
||||||
if (VehicleSteeringAngle != MainVehicle.SteeringAngle)
|
if (VehicleSteeringAngle != MainVehicle.SteeringAngle)
|
||||||
{
|
{
|
||||||
MainVehicle.Handle.CustomSteeringAngle((float)(Math.PI / 180) * VehicleSteeringAngle);
|
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * VehicleSteeringAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good enough for now, but we need to create a better sync
|
// Good enough for now, but we need to create a better sync
|
||||||
|
@ -25,8 +25,11 @@ namespace FirstGameMode
|
|||||||
RunningSinceTimer.Start();
|
RunningSinceTimer.Start();
|
||||||
RunningSinceTimer.Elapsed += new ElapsedEventHandler((sender, e) => RunningSince += 1);
|
RunningSinceTimer.Elapsed += new ElapsedEventHandler((sender, e) => RunningSince += 1);
|
||||||
|
|
||||||
|
API.OnStart += OnResourceStarted;
|
||||||
API.OnPlayerConnected += OnPlayerConnected;
|
API.OnPlayerConnected += OnPlayerConnected;
|
||||||
API.OnPlayerDisconnected += OnPlayerDisconnected;
|
API.OnPlayerDisconnected += OnPlayerDisconnected;
|
||||||
|
API.OnPlayerHealthUpdate += OnPlayerHealthUpdate;
|
||||||
|
API.OnPlayerPositionUpdate += OnPlayerPositionUpdate;
|
||||||
API.OnChatMessage += OnChatMessage;
|
API.OnChatMessage += OnChatMessage;
|
||||||
API.OnModPacketReceived += OnModPacketReceived;
|
API.OnModPacketReceived += OnModPacketReceived;
|
||||||
|
|
||||||
@ -34,7 +37,25 @@ namespace FirstGameMode
|
|||||||
API.RegisterCommands<Commands>();
|
API.RegisterCommands<Commands>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnModPacketReceived(long from, long target, string mod, byte customID, byte[] bytes, CancelEventArgs args)
|
public void OnResourceStarted()
|
||||||
|
{
|
||||||
|
Logging.Info("Resource started successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerHealthUpdate(Client client)
|
||||||
|
{
|
||||||
|
if (client.Player.Health == 0)
|
||||||
|
{
|
||||||
|
Logging.Warning($"Player {client.Player.Username} has died!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerPositionUpdate(Client client)
|
||||||
|
{
|
||||||
|
// Code...
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnModPacketReceived(long from, long target, string mod, byte customID, byte[] bytes, CancelEventArgs args)
|
||||||
{
|
{
|
||||||
if (mod != "FirstScript" || customID != 1)
|
if (mod != "FirstScript" || customID != 1)
|
||||||
{
|
{
|
||||||
@ -56,22 +77,22 @@ namespace FirstGameMode
|
|||||||
targetClient.SendNativeCall(0x47C3B5848C3E45D8, setPlayerTime.Hours, setPlayerTime.Minutes, setPlayerTime.Seconds);
|
targetClient.SendNativeCall(0x47C3B5848C3E45D8, setPlayerTime.Hours, setPlayerTime.Minutes, setPlayerTime.Seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RunningCommand(CommandContext ctx)
|
public void RunningCommand(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ctx.Client.SendChatMessage("Server has been running for: " + RunningSince + " seconds!");
|
ctx.Client.SendChatMessage($"Server has been running for: {RunningSince} seconds!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnPlayerConnected(Client client)
|
public void OnPlayerConnected(Client client)
|
||||||
{
|
{
|
||||||
API.SendChatMessageToAll("Player " + client.Player.Username + " connected!");
|
client.SendChatMessage($"Welcome {client.Player.Username}!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnPlayerDisconnected(Client client)
|
public void OnPlayerDisconnected(Client client)
|
||||||
{
|
{
|
||||||
API.SendChatMessageToAll("Player " + client.Player.Username + " disconnected!");
|
API.SendChatMessageToAll($"Player {client.Player.Username} disconnected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnChatMessage(string username, string message, CancelEventArgs e)
|
public void OnChatMessage(string username, string message, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Lidgren.Network;
|
using Lidgren.Network;
|
||||||
|
|
||||||
@ -43,14 +44,21 @@ namespace CoopServer
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region FUNCTIONS
|
||||||
public void Kick(string[] reason)
|
public void Kick(string[] reason)
|
||||||
{
|
{
|
||||||
Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID).Disconnect(string.Join(" ", reason));
|
Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID)?.Disconnect(string.Join(" ", reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendChatMessage(string message, string from = "Server")
|
public void SendChatMessage(string message, string from = "Server")
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
||||||
|
if (userConnection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ChatMessagePacket packet = new()
|
ChatMessagePacket packet = new()
|
||||||
{
|
{
|
||||||
@ -62,10 +70,21 @@ namespace CoopServer
|
|||||||
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SendNativeCall(ulong hash, params object[] args)
|
public void SendNativeCall(ulong hash, params object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
||||||
|
if (userConnection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<NativeArgument> arguments = Util.ParseNativeArguments(args);
|
List<NativeArgument> arguments = Util.ParseNativeArguments(args);
|
||||||
if (arguments == null)
|
if (arguments == null)
|
||||||
@ -83,15 +102,26 @@ namespace CoopServer
|
|||||||
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SendModPacket(string mod, byte customID, byte[] bytes)
|
public void SendModPacket(string mod, byte customID, byte[] bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == ID);
|
||||||
|
if (userConnection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
||||||
new ModPacket()
|
new ModPacket()
|
||||||
{
|
{
|
||||||
ID = -1,
|
ID = 0,
|
||||||
Target = 0,
|
Target = 0,
|
||||||
Mod = mod,
|
Mod = mod,
|
||||||
CustomPacketID = customID,
|
CustomPacketID = customID,
|
||||||
@ -100,5 +130,11 @@ namespace CoopServer
|
|||||||
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
Server.MainNetServer.FlushSendQueue();
|
Server.MainNetServer.FlushSendQueue();
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,7 @@
|
|||||||
private LVector3 CurrentPosition { get; set; }
|
private LVector3 CurrentPosition { get; set; }
|
||||||
public LVector3 Position
|
public LVector3 Position
|
||||||
{
|
{
|
||||||
get
|
get => CurrentPosition;
|
||||||
{
|
|
||||||
return CurrentPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
LastPosition = CurrentPosition;
|
LastPosition = CurrentPosition;
|
||||||
@ -24,6 +20,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private int CurrentHealth { get; set; }
|
||||||
|
public int Health
|
||||||
|
{
|
||||||
|
get => CurrentHealth;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (CurrentHealth != value && Server.MainResource != null)
|
||||||
|
{
|
||||||
|
Server.MainResource.InvokePlayerHealthUpdate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentHealth = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsInRangeOf(LVector3 position, float distance)
|
public bool IsInRangeOf(LVector3 position, float distance)
|
||||||
{
|
{
|
||||||
|
161
Server/Server.cs
161
Server/Server.cs
@ -299,15 +299,16 @@ namespace CoopServer
|
|||||||
if (modPacket.Target != 0)
|
if (modPacket.Target != 0)
|
||||||
{
|
{
|
||||||
NetConnection target = MainNetServer.Connections.FirstOrDefault(x => x.RemoteUniqueIdentifier == modPacket.Target);
|
NetConnection target = MainNetServer.Connections.FirstOrDefault(x => x.RemoteUniqueIdentifier == modPacket.Target);
|
||||||
if (target == null)
|
if (target.Equals(default(Client)))
|
||||||
{
|
{
|
||||||
Logging.Error($"[ModPacket] target \"{modPacket.Target}\" not found!");
|
Logging.Error($"[ModPacket] target \"{modPacket.Target}\" not found!");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Send back to target
|
// Send back to target
|
||||||
MainNetServer.SendMessage(outgoingMessage, target, NetDeliveryMethod.ReliableOrdered, 0);
|
MainNetServer.SendMessage(outgoingMessage, target, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Send back to all players
|
// Send back to all players
|
||||||
@ -331,7 +332,7 @@ namespace CoopServer
|
|||||||
break;
|
break;
|
||||||
case NetIncomingMessageType.ConnectionLatencyUpdated:
|
case NetIncomingMessageType.ConnectionLatencyUpdated:
|
||||||
Client client = Clients.FirstOrDefault(x => x.ID == message.SenderConnection.RemoteUniqueIdentifier);
|
Client client = Clients.FirstOrDefault(x => x.ID == message.SenderConnection.RemoteUniqueIdentifier);
|
||||||
if (client != default)
|
if (!client.Equals(default(Client)))
|
||||||
{
|
{
|
||||||
client.Latency = message.ReadFloat();
|
client.Latency = message.ReadFloat();
|
||||||
}
|
}
|
||||||
@ -420,9 +421,13 @@ namespace CoopServer
|
|||||||
|
|
||||||
long localID = local.RemoteUniqueIdentifier;
|
long localID = local.RemoteUniqueIdentifier;
|
||||||
|
|
||||||
|
Client tmpClient;
|
||||||
|
|
||||||
// Add the player to Players
|
// Add the player to Players
|
||||||
|
lock (Clients)
|
||||||
|
{
|
||||||
Clients.Add(
|
Clients.Add(
|
||||||
new Client()
|
tmpClient = new Client()
|
||||||
{
|
{
|
||||||
ID = localID,
|
ID = localID,
|
||||||
Player = new()
|
Player = new()
|
||||||
@ -432,6 +437,7 @@ namespace CoopServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
|
||||||
@ -447,47 +453,51 @@ namespace CoopServer
|
|||||||
|
|
||||||
// Accept the connection and send back a new handshake packet with the connection ID
|
// Accept the connection and send back a new handshake packet with the connection ID
|
||||||
local.Approve(outgoingMessage);
|
local.Approve(outgoingMessage);
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerHandshake(tmpClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The connection has been approved, now we need to send all other players to the new player and the new player to all players
|
// The connection has been approved, now we need to send all other players to the new player and the new player to all players
|
||||||
private static void SendPlayerConnectPacket(NetConnection local, PlayerConnectPacket packet)
|
private static void SendPlayerConnectPacket(NetConnection local, PlayerConnectPacket packet)
|
||||||
{
|
{
|
||||||
|
Client localClient = Clients.FirstOrDefault(x => x.ID == packet.ID);
|
||||||
|
if (localClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
local.Disconnect("No data found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage))
|
if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage))
|
||||||
{
|
{
|
||||||
SendChatMessage(new ChatMessagePacket() { Username = "Server", Message = MainSettings.WelcomeMessage }, new List<NetConnection>() { local });
|
SendChatMessage(new ChatMessagePacket() { Username = "Server", Message = MainSettings.WelcomeMessage }, new List<NetConnection>() { local });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MainResource != null)
|
|
||||||
{
|
|
||||||
MainResource.InvokePlayerConnected(Clients.Find(x => x.ID == packet.ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<NetConnection> clients;
|
List<NetConnection> clients;
|
||||||
if ((clients = Util.FilterAllLocal(local)).Count == 0)
|
if ((clients = Util.FilterAllLocal(local)).Count > 0)
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send all players to local
|
// Send all players to local
|
||||||
clients.ForEach(targetPlayer =>
|
clients.ForEach(targetPlayer =>
|
||||||
{
|
{
|
||||||
long targetPlayerID = targetPlayer.RemoteUniqueIdentifier;
|
long targetPlayerID = targetPlayer.RemoteUniqueIdentifier;
|
||||||
|
|
||||||
Client targetEntity = Clients.First(x => x.ID == targetPlayerID);
|
Client targetClient = Clients.FirstOrDefault(x => x.ID == targetPlayerID);
|
||||||
|
if (!targetClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
new PlayerConnectPacket()
|
new PlayerConnectPacket()
|
||||||
{
|
{
|
||||||
ID = targetPlayerID,
|
ID = targetPlayerID,
|
||||||
SocialClubName = targetEntity.Player.SocialClubName,
|
SocialClubName = targetClient.Player.SocialClubName,
|
||||||
Username = targetEntity.Player.Username
|
Username = targetClient.Player.Username
|
||||||
}.PacketToNetOutGoingMessage(outgoingMessage);
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0);
|
MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send local to all players
|
// Send local to all players
|
||||||
Client localClient = Clients.First(x => x.ID == packet.ID);
|
|
||||||
|
|
||||||
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
new PlayerConnectPacket()
|
new PlayerConnectPacket()
|
||||||
{
|
{
|
||||||
@ -498,14 +508,15 @@ namespace CoopServer
|
|||||||
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
|
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerConnected(localClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send all players a message that someone has left the server
|
// Send all players a message that someone has left the server
|
||||||
private static void SendPlayerDisconnectPacket(PlayerDisconnectPacket packet)
|
private static void SendPlayerDisconnectPacket(PlayerDisconnectPacket packet)
|
||||||
{
|
{
|
||||||
if (MainResource != null)
|
|
||||||
{
|
|
||||||
MainResource.InvokePlayerDisconnected(Clients.Find(x => x.ID == packet.ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<NetConnection> clients;
|
List<NetConnection> clients;
|
||||||
if ((clients = Util.FilterAllLocal(packet.ID)).Count > 0)
|
if ((clients = Util.FilterAllLocal(packet.ID)).Count > 0)
|
||||||
{
|
{
|
||||||
@ -514,16 +525,40 @@ namespace CoopServer
|
|||||||
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
|
MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Clients.Remove(Clients.Find(x => x.ID == packet.ID));
|
Client localClient = Clients.FirstOrDefault(x => x.ID == packet.ID);
|
||||||
|
if (localClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerDisconnected(localClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (Clients)
|
||||||
|
{
|
||||||
|
Clients.Remove(localClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FullSyncPlayer(FullSyncPlayerPacket packet)
|
private static void FullSyncPlayer(FullSyncPlayerPacket packet)
|
||||||
{
|
{
|
||||||
Client client = Clients.First(x => x.ID == packet.Extra.ID);
|
Client tmpClient = Clients.FirstOrDefault(x => x.ID == packet.Extra.ID);
|
||||||
client.Player.Position = packet.Extra.Position;
|
if (tmpClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
NetConnection localConn = MainNetServer.Connections.Find(x => packet.Extra.ID == x.RemoteUniqueIdentifier);
|
||||||
|
if (localConn != null)
|
||||||
|
{
|
||||||
|
localConn.Disconnect("No data found!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tmpClient.Player.Position = packet.Extra.Position;
|
||||||
|
tmpClient.Player.Health = packet.Extra.Health;
|
||||||
|
|
||||||
PlayerPacket playerPacket = packet.Extra;
|
PlayerPacket playerPacket = packet.Extra;
|
||||||
playerPacket.Latency = client.Latency;
|
playerPacket.Latency = tmpClient.Latency;
|
||||||
|
|
||||||
packet.Extra = playerPacket;
|
packet.Extra = playerPacket;
|
||||||
|
|
||||||
@ -545,15 +580,30 @@ namespace CoopServer
|
|||||||
|
|
||||||
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerUpdate(tmpClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FullSyncPlayerVeh(FullSyncPlayerVehPacket packet)
|
private static void FullSyncPlayerVeh(FullSyncPlayerVehPacket packet)
|
||||||
{
|
{
|
||||||
Client client = Clients.First(x => x.ID == packet.Extra.ID);
|
Client tmpClient = Clients.FirstOrDefault(x => x.ID == packet.Extra.ID);
|
||||||
client.Player.Position = packet.Extra.Position;
|
if (tmpClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
NetConnection localConn = MainNetServer.Connections.Find(x => packet.Extra.ID == x.RemoteUniqueIdentifier);
|
||||||
|
if (localConn != null)
|
||||||
|
{
|
||||||
|
localConn.Disconnect("No data found!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tmpClient.Player.Position = packet.Extra.Position;
|
||||||
|
tmpClient.Player.Health = packet.Extra.Health;
|
||||||
|
|
||||||
PlayerPacket playerPacket = packet.Extra;
|
PlayerPacket playerPacket = packet.Extra;
|
||||||
playerPacket.Latency = client.Latency;
|
playerPacket.Latency = tmpClient.Latency;
|
||||||
|
|
||||||
packet.Extra = playerPacket;
|
packet.Extra = playerPacket;
|
||||||
|
|
||||||
@ -575,15 +625,30 @@ namespace CoopServer
|
|||||||
|
|
||||||
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerUpdate(tmpClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LightSyncPlayer(LightSyncPlayerPacket packet)
|
private static void LightSyncPlayer(LightSyncPlayerPacket packet)
|
||||||
{
|
{
|
||||||
Client client = Clients.First(x => x.ID == packet.Extra.ID);
|
Client tmpClient = Clients.FirstOrDefault(x => x.ID == packet.Extra.ID);
|
||||||
client.Player.Position = packet.Extra.Position;
|
if (tmpClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
NetConnection localConn = MainNetServer.Connections.Find(x => packet.Extra.ID == x.RemoteUniqueIdentifier);
|
||||||
|
if (localConn != null)
|
||||||
|
{
|
||||||
|
localConn.Disconnect("No data found!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tmpClient.Player.Position = packet.Extra.Position;
|
||||||
|
tmpClient.Player.Health = packet.Extra.Health;
|
||||||
|
|
||||||
PlayerPacket playerPacket = packet.Extra;
|
PlayerPacket playerPacket = packet.Extra;
|
||||||
playerPacket.Latency = client.Latency;
|
playerPacket.Latency = tmpClient.Latency;
|
||||||
|
|
||||||
packet.Extra = playerPacket;
|
packet.Extra = playerPacket;
|
||||||
|
|
||||||
@ -605,15 +670,30 @@ namespace CoopServer
|
|||||||
|
|
||||||
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerUpdate(tmpClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LightSyncPlayerVeh(LightSyncPlayerVehPacket packet)
|
private static void LightSyncPlayerVeh(LightSyncPlayerVehPacket packet)
|
||||||
{
|
{
|
||||||
Client client = Clients.First(x => x.ID == packet.Extra.ID);
|
Client tmpClient = Clients.FirstOrDefault(x => x.ID == packet.Extra.ID);
|
||||||
client.Player.Position = packet.Extra.Position;
|
if (tmpClient.Equals(default(Client)))
|
||||||
|
{
|
||||||
|
NetConnection localConn = MainNetServer.Connections.Find(x => packet.Extra.ID == x.RemoteUniqueIdentifier);
|
||||||
|
if (localConn != null)
|
||||||
|
{
|
||||||
|
localConn.Disconnect("No data found!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tmpClient.Player.Position = packet.Extra.Position;
|
||||||
|
tmpClient.Player.Health = packet.Extra.Health;
|
||||||
|
|
||||||
PlayerPacket playerPacket = packet.Extra;
|
PlayerPacket playerPacket = packet.Extra;
|
||||||
playerPacket.Latency = client.Latency;
|
playerPacket.Latency = tmpClient.Latency;
|
||||||
|
|
||||||
packet.Extra = playerPacket;
|
packet.Extra = playerPacket;
|
||||||
|
|
||||||
@ -635,6 +715,11 @@ namespace CoopServer
|
|||||||
|
|
||||||
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (MainResource != null)
|
||||||
|
{
|
||||||
|
MainResource.InvokePlayerUpdate(tmpClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a message to targets or all players
|
// Send a message to targets or all players
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -13,18 +14,16 @@ namespace CoopServer
|
|||||||
{
|
{
|
||||||
private static Thread _mainThread;
|
private static Thread _mainThread;
|
||||||
private static bool _hasToStop = false;
|
private static bool _hasToStop = false;
|
||||||
private static Queue<Action> _actionQueue;
|
private static Queue _actionQueue;
|
||||||
private static TaskFactory _factory;
|
|
||||||
private static ServerScript _script;
|
private static ServerScript _script;
|
||||||
|
|
||||||
public Resource(ServerScript script)
|
public Resource(ServerScript script)
|
||||||
{
|
{
|
||||||
_factory = new();
|
_actionQueue = Queue.Synchronized(new Queue());
|
||||||
_actionQueue = new();
|
|
||||||
_mainThread = new(ThreadLoop) { IsBackground = true };
|
_mainThread = new(ThreadLoop) { IsBackground = true };
|
||||||
_mainThread.Start();
|
_mainThread.Start();
|
||||||
|
|
||||||
lock (_actionQueue)
|
lock (_actionQueue.SyncRoot)
|
||||||
{
|
{
|
||||||
_actionQueue.Enqueue(() =>
|
_actionQueue.Enqueue(() =>
|
||||||
{
|
{
|
||||||
@ -36,60 +35,88 @@ namespace CoopServer
|
|||||||
|
|
||||||
private void ThreadLoop()
|
private void ThreadLoop()
|
||||||
{
|
{
|
||||||
do
|
while (!_hasToStop)
|
||||||
{
|
{
|
||||||
if (_actionQueue.Count != 0)
|
Queue localQueue;
|
||||||
|
lock (_actionQueue.SyncRoot)
|
||||||
{
|
{
|
||||||
lock (_actionQueue)
|
localQueue = new(_actionQueue);
|
||||||
{
|
_actionQueue.Clear();
|
||||||
_factory.StartNew(() => _actionQueue.Dequeue()?.Invoke());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (localQueue.Count > 0)
|
||||||
|
{
|
||||||
|
(localQueue.Dequeue() as Action)?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16 milliseconds to sleep to reduce CPU usage
|
// 16 milliseconds to sleep to reduce CPU usage
|
||||||
Thread.Sleep(1000 / 60);
|
Thread.Sleep(1000 / 60);
|
||||||
} while (_hasToStop);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InvokeModPacketReceived(long from, long target, string mod, byte customID, byte[] bytes)
|
public bool InvokeModPacketReceived(long from, long target, string mod, byte customID, byte[] bytes)
|
||||||
{
|
{
|
||||||
Task<bool> shutdownTask = new(() => _script.API.InvokeModPacketReceived(from, target, mod, customID, bytes));
|
Task<bool> task = new(() => _script.API.InvokeModPacketReceived(from, target, mod, customID, bytes));
|
||||||
shutdownTask.Start();
|
task.Start();
|
||||||
shutdownTask.Wait(5000);
|
task.Wait(5000);
|
||||||
|
|
||||||
return shutdownTask.Result;
|
return task.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokePlayerHandshake(Client client)
|
||||||
|
{
|
||||||
|
lock (_actionQueue.SyncRoot)
|
||||||
|
{
|
||||||
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerConnected(client)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokePlayerConnected(Client client)
|
public void InvokePlayerConnected(Client client)
|
||||||
{
|
{
|
||||||
lock (_actionQueue)
|
lock (_actionQueue.SyncRoot)
|
||||||
{
|
{
|
||||||
_actionQueue.Enqueue(() => _script.API.InvokePlayerConnected(client));
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerConnected(client)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokePlayerDisconnected(Client client)
|
public void InvokePlayerDisconnected(Client client)
|
||||||
{
|
{
|
||||||
lock (_actionQueue)
|
lock (_actionQueue.SyncRoot)
|
||||||
{
|
{
|
||||||
_actionQueue.Enqueue(() => _script.API.InvokePlayerDisconnected(client));
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerDisconnected(client)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InvokeChatMessage(string username, string message)
|
public bool InvokeChatMessage(string username, string message)
|
||||||
{
|
{
|
||||||
Task<bool> shutdownTask = new(() => _script.API.InvokeChatMessage(username, message));
|
Task<bool> task = new(() => _script.API.InvokeChatMessage(username, message));
|
||||||
shutdownTask.Start();
|
task.Start();
|
||||||
shutdownTask.Wait(5000);
|
task.Wait(5000);
|
||||||
|
|
||||||
return shutdownTask.Result;
|
return task.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokePlayerPositionUpdate(PlayerData playerData)
|
public void InvokePlayerPositionUpdate(PlayerData playerData)
|
||||||
{
|
{
|
||||||
lock (_actionQueue)
|
lock (_actionQueue.SyncRoot)
|
||||||
{
|
{
|
||||||
_actionQueue.Enqueue(() => _script.API.InvokePlayerPositionUpdate(playerData));
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerPositionUpdate(playerData)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokePlayerUpdate(Client client)
|
||||||
|
{
|
||||||
|
lock (_actionQueue.SyncRoot)
|
||||||
|
{
|
||||||
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerUpdate(client)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokePlayerHealthUpdate(PlayerData playerData)
|
||||||
|
{
|
||||||
|
lock (_actionQueue.SyncRoot)
|
||||||
|
{
|
||||||
|
_actionQueue.Enqueue(new Action(() => _script.API.InvokePlayerHealthUpdate(playerData)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,22 +129,31 @@ namespace CoopServer
|
|||||||
public class API
|
public class API
|
||||||
{
|
{
|
||||||
#region DELEGATES
|
#region DELEGATES
|
||||||
|
public delegate void EmptyEvent();
|
||||||
public delegate void ChatEvent(string username, string message, CancelEventArgs cancel);
|
public delegate void ChatEvent(string username, string message, CancelEventArgs cancel);
|
||||||
public delegate void PlayerEvent(Client client);
|
public delegate void PlayerEvent(Client client);
|
||||||
public delegate void ModEvent(long from, long target, string mod, byte customID, byte[] bytes, CancelEventArgs args);
|
public delegate void ModEvent(long from, long target, string mod, byte customID, byte[] bytes, CancelEventArgs args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region EVENTS
|
#region EVENTS
|
||||||
public event EventHandler OnStart;
|
public event EmptyEvent OnStart;
|
||||||
public event ChatEvent OnChatMessage;
|
public event ChatEvent OnChatMessage;
|
||||||
|
public event PlayerEvent OnPlayerHandshake;
|
||||||
public event PlayerEvent OnPlayerConnected;
|
public event PlayerEvent OnPlayerConnected;
|
||||||
public event PlayerEvent OnPlayerDisconnected;
|
public event PlayerEvent OnPlayerDisconnected;
|
||||||
|
public event PlayerEvent OnPlayerUpdate;
|
||||||
|
public event PlayerEvent OnPlayerHealthUpdate;
|
||||||
public event PlayerEvent OnPlayerPositionUpdate;
|
public event PlayerEvent OnPlayerPositionUpdate;
|
||||||
public event ModEvent OnModPacketReceived;
|
public event ModEvent OnModPacketReceived;
|
||||||
|
|
||||||
internal void InvokeStart()
|
internal void InvokeStart()
|
||||||
{
|
{
|
||||||
OnStart?.Invoke(this, EventArgs.Empty);
|
OnStart?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvokePlayerHandshake(Client client)
|
||||||
|
{
|
||||||
|
OnPlayerHandshake?.Invoke(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InvokePlayerConnected(Client client)
|
internal void InvokePlayerConnected(Client client)
|
||||||
@ -130,6 +166,16 @@ namespace CoopServer
|
|||||||
OnPlayerDisconnected?.Invoke(client);
|
OnPlayerDisconnected?.Invoke(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void InvokePlayerUpdate(Client client)
|
||||||
|
{
|
||||||
|
OnPlayerUpdate?.Invoke(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvokePlayerHealthUpdate(PlayerData playerData)
|
||||||
|
{
|
||||||
|
OnPlayerHealthUpdate?.Invoke(Server.Clients.First(x => x.Player.Username == playerData.Username));
|
||||||
|
}
|
||||||
|
|
||||||
internal bool InvokeChatMessage(string username, string message)
|
internal bool InvokeChatMessage(string username, string message)
|
||||||
{
|
{
|
||||||
CancelEventArgs args = new(false);
|
CancelEventArgs args = new(false);
|
||||||
@ -152,11 +198,13 @@ namespace CoopServer
|
|||||||
|
|
||||||
#region FUNCTIONS
|
#region FUNCTIONS
|
||||||
public static void SendModPacketToAll(string mod, byte customID, byte[] bytes)
|
public static void SendModPacketToAll(string mod, byte customID, byte[] bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
||||||
new ModPacket()
|
new ModPacket()
|
||||||
{
|
{
|
||||||
ID = -1,
|
ID = 0,
|
||||||
Target = 0,
|
Target = 0,
|
||||||
Mod = mod,
|
Mod = mod,
|
||||||
CustomPacketID = customID,
|
CustomPacketID = customID,
|
||||||
@ -165,8 +213,15 @@ namespace CoopServer
|
|||||||
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
Server.MainNetServer.FlushSendQueue();
|
Server.MainNetServer.FlushSendQueue();
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void SendNativeCallToAll(ulong hash, params object[] args)
|
public static void SendNativeCallToAll(ulong hash, params object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (Server.MainNetServer.ConnectionsCount == 0)
|
if (Server.MainNetServer.ConnectionsCount == 0)
|
||||||
{
|
{
|
||||||
@ -189,6 +244,11 @@ namespace CoopServer
|
|||||||
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static List<long> GetAllConnections()
|
public static List<long> GetAllConnections()
|
||||||
{
|
{
|
||||||
@ -211,26 +271,32 @@ namespace CoopServer
|
|||||||
|
|
||||||
public static Client GetClientByUsername(string username)
|
public static Client GetClientByUsername(string username)
|
||||||
{
|
{
|
||||||
return Server.Clients.FirstOrDefault(x => x.Player.Username == username);
|
Client client = Server.Clients.FirstOrDefault(x => x.Player.Username == username);
|
||||||
|
return client.Equals(default(Client)) ? null : client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendChatMessageToAll(string message, string username = "Server")
|
public static void SendChatMessageToAll(string message, string username = "Server")
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (Server.MainNetServer.ConnectionsCount == 0)
|
if (Server.MainNetServer.ConnectionsCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatMessagePacket packet = new()
|
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
||||||
|
new ChatMessagePacket()
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Message = message
|
Message = message
|
||||||
};
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
|
||||||
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
|
|
||||||
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
|
||||||
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void RegisterCommand(string name, Action<CommandContext> callback)
|
public static void RegisterCommand(string name, Action<CommandContext> callback)
|
||||||
{
|
{
|
||||||
|
@ -50,13 +50,13 @@ namespace CoopServer
|
|||||||
|
|
||||||
public static NetConnection GetConnectionByUsername(string username)
|
public static NetConnection GetConnectionByUsername(string username)
|
||||||
{
|
{
|
||||||
long clientID;
|
Client client = Server.Clients.FirstOrDefault(x => x.Player.Username == username);
|
||||||
if ((clientID = Server.Clients.FirstOrDefault(x => x.Player.Username == username).ID) == default)
|
if (client.Equals(default(Client)))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Server.MainNetServer.Connections.FirstOrDefault(x => x.RemoteUniqueIdentifier == clientID);
|
return Server.MainNetServer.Connections.FirstOrDefault(x => x.RemoteUniqueIdentifier == client.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a list of all connections but not the local connection
|
// Return a list of all connections but not the local connection
|
||||||
@ -72,12 +72,20 @@ namespace CoopServer
|
|||||||
// Return a list of players within range of ...
|
// Return a list of players within range of ...
|
||||||
public static List<NetConnection> GetAllInRange(LVector3 position, float range)
|
public static List<NetConnection> GetAllInRange(LVector3 position, float range)
|
||||||
{
|
{
|
||||||
return new(Server.MainNetServer.Connections.FindAll(e => Server.Clients.First(x => x.ID == e.RemoteUniqueIdentifier).Player.IsInRangeOf(position, range)));
|
return new(Server.MainNetServer.Connections.FindAll(e =>
|
||||||
|
{
|
||||||
|
Client client = Server.Clients.First(x => x.ID == e.RemoteUniqueIdentifier);
|
||||||
|
return !client.Equals(default(Client)) && client.Player.IsInRangeOf(position, range);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
// Return a list of players within range of ... but not the local one
|
// Return a list of players within range of ... but not the local one
|
||||||
public static List<NetConnection> GetAllInRange(LVector3 position, float range, NetConnection local)
|
public static List<NetConnection> GetAllInRange(LVector3 position, float range, NetConnection local)
|
||||||
{
|
{
|
||||||
return new(Server.MainNetServer.Connections.Where(e => e != local && Server.Clients.First(x => x.ID == e.RemoteUniqueIdentifier).Player.IsInRangeOf(position, range)));
|
return new(Server.MainNetServer.Connections.Where(e =>
|
||||||
|
{
|
||||||
|
Client client = Server.Clients.First(x => x.ID == e.RemoteUniqueIdentifier);
|
||||||
|
return e != local && !client.Equals(default(Client)) && client.Player.IsInRangeOf(position, range);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Read<T>(string file) where T : new()
|
public static T Read<T>(string file) where T : new()
|
||||||
|
Reference in New Issue
Block a user