diff --git a/src/Core/Client.cs b/src/Core/Client.cs index e3acae9..0466978 100644 --- a/src/Core/Client.cs +++ b/src/Core/Client.cs @@ -4,12 +4,12 @@ using System; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Xml.Linq; namespace sodoffmmo.Core; public class Client { static int id; static object lck = new(); - protected static List clients = new(); public int ClientID { get; private set; } public PlayerData PlayerData { get; set; } = new(); @@ -28,16 +28,9 @@ public class Client { socket = clientSocket; lock (lck) { ClientID = ++id; - clients.Add(this); } } - public int TotalClientCount => clients.Count; - - public static Client[] GetAllClients() { - return clients.ToArray(); - } - public async Task Receive() { byte[] buffer = new byte[2048]; int len = await socket.ReceiveAsync(buffer, SocketFlags.None); @@ -72,7 +65,7 @@ public class Client { PlayerData.IsValid = false; if (Room != null) { - if (Room is not BanRoom) Console.WriteLine($"Leave room: {Room.Name} (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); + Console.WriteLine($"Leave room: {Room.Name} (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); Room.RemoveClient(this); NetworkObject data = new(); @@ -85,7 +78,7 @@ public class Client { Room = room; if (Room != null) { - if (Room is not BanRoom) Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); + Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); Room.AddClient(this); Send(Room.SubscribeRoom()); @@ -110,9 +103,6 @@ public class Client { } public void Disconnect() { - lock (lck) { - clients.Remove(this); - } try { socket.Shutdown(SocketShutdown.Both); } finally { @@ -130,11 +120,6 @@ public class Client { scheduledDisconnect = true; } - public void Ban() { - Banned = true; - SetRoom(PunishmentManager.BanRoom); - } - public bool Connected { get { return socket.Connected && !scheduledDisconnect; diff --git a/src/Management/BanRoom.cs b/src/Management/BanRoom.cs new file mode 100644 index 0000000..e0e9593 --- /dev/null +++ b/src/Management/BanRoom.cs @@ -0,0 +1,63 @@ +using sodoffmmo.Core; +using sodoffmmo.Data; + +namespace sodoffmmo.Management; +public class BanRoom : Room { + public BanRoom(string name) : base(name, name) {} + + public void UpdateClient(Client client) { + client.Send(RespondJoinRoom()); + } + + public override void Send(NetworkPacket packet, Client? skip = null) { + // Do nothing. + } + + public override NetworkPacket RespondJoinRoom() { + NetworkObject obj = new(); + NetworkArray roomInfo = new(); + roomInfo.Add(Id); + roomInfo.Add(Name); // Room Name + roomInfo.Add(Group); // Group Name + roomInfo.Add(true); // is game + roomInfo.Add(false); // is hidden + roomInfo.Add(false); // is password protected + roomInfo.Add((short)0); // player count + roomInfo.Add((short)1); // max player count + roomInfo.Add(new NetworkArray()); // empty roomvars + roomInfo.Add((short)0); // spectator count + roomInfo.Add((short)0); // max spectator count + + obj.Add("r", roomInfo); + obj.Add("ul", new NetworkArray()); // Empty userlist. + + NetworkPacket packet = NetworkObject.WrapObject(0, 4, obj).Serialize(); + packet.Compress(); + + return packet; + } + + public override NetworkPacket SubscribeRoom() { + NetworkObject obj = new(); + NetworkArray list = new(); + + NetworkArray r1 = new(); + r1.Add(Id); + r1.Add(Name); // Room Name + r1.Add(Group); // Group Name + r1.Add(true); + r1.Add(false); + r1.Add(false); + r1.Add((short)0); // player count + r1.Add((short)1); // max player count + r1.Add(new NetworkArray()); + r1.Add((short)0); + r1.Add((short)0); + + list.Add(r1); + + obj.Add("rl", list); + obj.Add("g", Group); + return NetworkObject.WrapObject(0, 15, obj).Serialize(); + } +} \ No newline at end of file diff --git a/src/Management/Commands/BanCommand.cs b/src/Management/Commands/BanCommand.cs index dc2a986..7bbc89a 100644 --- a/src/Management/Commands/BanCommand.cs +++ b/src/Management/Commands/BanCommand.cs @@ -13,53 +13,21 @@ class BanCommand : IManagementCommand { string name = string.Join(' ', arguments); Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room. if (target == null) { // Search all clients in case the offender left the room. - target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); + target = Server.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); if (target == null) { // If the target is still null, call it a failure. client.Send(Utils.BuildServerSideMessage($"Ban: user {name} not found", "Server")); return; } } - if (target.Banned) { - client.Send(Utils.BuildServerSideMessage($"Ban: {name} is already banned", "Server")); - } else { - target.Ban(); - client.Send(Utils.BuildServerSideMessage($"Ban: {name} has been banned", "Server")); - if (target.PlayerData.VikingId != null) { - PunishmentManager.BannedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); - PunishmentManager.SaveMutedBanned(); + if (target.Ban()) { + if (target?.PlayerData.VikingId == null) { + client.Send(Utils.BuildServerSideMessage($"Ban: {name} has been banned", "Server")); } else { - client.Send(Utils.BuildServerSideMessage($"Ban: This user is not authenticated. They have been merely kicked instead.", "Server")); + client.Send(Utils.BuildServerSideMessage($"Ban: {name}'s session has been banned (they are not authenticated)", "Server")); } - } - } -} - -[ManagementCommand("unban", Role.Moderator)] -class UnbanCommand : IManagementCommand { - public void Handle(Client client, string[] arguments) { - if (arguments.Length < 1) { - client.Send(Utils.BuildServerSideMessage("Unban: Invalid number of arguments", "Server")); - return; - } - string name = string.Join(' ', arguments); - int? id = null; - if (PunishmentManager.BannedList.ContainsValue(name)) // Find in list in case they're offline. - id = PunishmentManager.BannedList.FirstOrDefault(p => p.Value == name).Key; - - if (id == null) { // Failure - client.Send(Utils.BuildServerSideMessage($"Unban: User named \"{name}\" is not banned", "Server")); - return; - } - - Client? target = PunishmentManager.BanRoom.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); // Find in ban room. - - client.Send(Utils.BuildServerSideMessage($"Unban: {name} has been unbanned", "Server")); - PunishmentManager.BannedList.Remove((int)id); - PunishmentManager.SaveMutedBanned(); - if (target != null) { - target.Banned = false; - target.Disconnect(); // Client will handle relogging. + } else { + client.Send(Utils.BuildServerSideMessage($"Ban: {name} is already banned", "Server")); } } } \ No newline at end of file diff --git a/src/Management/Commands/MuteCommand.cs b/src/Management/Commands/MuteCommand.cs index 55e9a8c..6c0a753 100644 --- a/src/Management/Commands/MuteCommand.cs +++ b/src/Management/Commands/MuteCommand.cs @@ -13,60 +13,18 @@ class MuteCommand : IManagementCommand { string name = string.Join(' ', arguments); Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room. if (target == null) { // Search all clients in case the offender left the room. - target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); + target = Server.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); if (target == null) { // Failure client.Send(Utils.BuildServerSideMessage($"Mute: user {name} not found", "Server")); return; } } - if (target.PlayerData.VikingId != null) { - if (PunishmentManager.MutedList.ContainsKey((int)target.PlayerData.VikingId)) { - client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server")); - } else { - target.Muted = true; - client.Send(Utils.BuildServerSideMessage($"Mute: {name} has been muted", "Server")); - PunishmentManager.MutedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); - PunishmentManager.SaveMutedBanned(); - } - } else { - target.Muted = true; + if (target.Mute()) { client.Send(Utils.BuildServerSideMessage($"Mute: {name} has been muted", "Server")); - client.Send(Utils.BuildServerSideMessage($"Mute: This user is not authenticated. Their mute will not be permanent!", "Server")); - } - } -} - -[ManagementCommand("unmute", Role.Moderator)] -class UnmuteCommand : IManagementCommand { - public void Handle(Client client, string[] arguments) { - if (arguments.Length < 1) { - client.Send(Utils.BuildServerSideMessage("Unmute: Invalid number of arguments", "Server")); - return; - } - string name = string.Join(' ', arguments); - int? id = null; - if (PunishmentManager.MutedList.ContainsValue(name)) id = PunishmentManager.MutedList.FirstOrDefault(p => p.Value == name).Key; // Find in list in case they're offline. - Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); // Find in this room. - if (target == null) { // Search all clients in case the target left the room. - target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); - if (target == null && id == null) { // Failure - client.Send(Utils.BuildServerSideMessage($"Unmute: user {name} not found", "Server")); - return; - } - } - - if (target?.Muted == false || (id != null && !PunishmentManager.MutedList.ContainsKey((int)id))) { - client.Send(Utils.BuildServerSideMessage($"Unmute: {name} is not muted", "Server")); } else { - if (target != null) { - target.Muted = false; - } - client.Send(Utils.BuildServerSideMessage($"Unmute: {name} has been unmuted", "Server")); - if (id != null) { - PunishmentManager.MutedList.Remove((int)id); - PunishmentManager.SaveMutedBanned(); - } + client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server")); } + if (target?.PlayerData.VikingId == null) client.Send(Utils.BuildServerSideMessage($"Mute: Their mute will not be persistent since they are not authenticated", "Server")); } } \ No newline at end of file diff --git a/src/Management/Commands/UnbanCommand.cs b/src/Management/Commands/UnbanCommand.cs new file mode 100644 index 0000000..510a64d --- /dev/null +++ b/src/Management/Commands/UnbanCommand.cs @@ -0,0 +1,31 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; + +namespace sodoffmmo.Management.Commands; + +[ManagementCommand("unban", Role.Moderator)] +class UnbanCommand : IManagementCommand { + public void Handle(Client client, string[] arguments) { + if (arguments.Length < 1) { + client.Send(Utils.BuildServerSideMessage("Unban: Invalid number of arguments", "Server")); + return; + } + string name = string.Join(' ', arguments); + int? id = null; + if (PunishmentManager.BannedList.ContainsValue(name)) // Find in list in case they're offline. + id = PunishmentManager.BannedList.FirstOrDefault(p => p.Value == name).Key; + + if (id == null) { // Failure + client.Send(Utils.BuildServerSideMessage($"Unban: User named \"{name}\" is not banned", "Server")); + return; + } + + Client? target = PunishmentManager.BanRoom.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); // Find in ban room. + + if (target.Unban(id)) { + client.Send(Utils.BuildServerSideMessage($"Unban: {name} has been unbanned", "Server")); + } else { + client.Send(Utils.BuildServerSideMessage($"Unban: {name} is not banned", "Server")); + } + } +} \ No newline at end of file diff --git a/src/Management/Commands/UnmuteCommand.cs b/src/Management/Commands/UnmuteCommand.cs new file mode 100644 index 0000000..de2c4f2 --- /dev/null +++ b/src/Management/Commands/UnmuteCommand.cs @@ -0,0 +1,31 @@ +using sodoffmmo.Attributes; +using sodoffmmo.Core; + +namespace sodoffmmo.Management.Commands; + +[ManagementCommand("unmute", Role.Moderator)] +class UnmuteCommand : IManagementCommand { + public void Handle(Client client, string[] arguments) { + if (arguments.Length < 1) { + client.Send(Utils.BuildServerSideMessage("Unmute: Invalid number of arguments", "Server")); + return; + } + string name = string.Join(' ', arguments); + int? id = null; + if (PunishmentManager.MutedList.ContainsValue(name)) id = PunishmentManager.MutedList.FirstOrDefault(p => p.Value == name).Key; // Find in list in case they're offline. + Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); // Find in this room. + if (target == null) { // Search all clients in case the target left the room. + target = Server.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); + if (target == null && id == null) { // Failure + client.Send(Utils.BuildServerSideMessage($"Unmute: user {name} not found", "Server")); + return; + } + } + + if (target.Unmute(id)) { + client.Send(Utils.BuildServerSideMessage($"Unmute: {name} has been unmuted", "Server")); + } else { + client.Send(Utils.BuildServerSideMessage($"Unmute: {name} is not muted", "Server")); + } + } +} diff --git a/src/Management/PunishmentManager.cs b/src/Management/PunishmentManager.cs index a00c2f0..929f08e 100644 --- a/src/Management/PunishmentManager.cs +++ b/src/Management/PunishmentManager.cs @@ -1,10 +1,9 @@ using sodoffmmo.Core; -using sodoffmmo.Data; using System.Text.Json; namespace sodoffmmo.Management; -public class PunishmentManager { +public static class PunishmentManager { // VikingId, Reason (nullable) public static readonly Dictionary MutedList; public static readonly Dictionary BannedList; @@ -35,69 +34,97 @@ public class PunishmentManager { WriteIndented = true })); } + + /// + /// Mute a user. + /// + /// User to mute - can be null, but id should be set in that case. + /// Optional vikingid of the user to mute. + /// False if already muted, otherwise true. + public static bool Mute(this Client? target, int? id = null) { + if (target != null) { + id ??= target.PlayerData.VikingId; + if (target.Muted && id == null) return false; + target.Muted = true; + } + + if (id != null) { + if (MutedList.ContainsKey((int)id)) return false; + MutedList.Add((int)id, target?.PlayerData.DiplayName); + SaveMutedBanned(); + } + + return true; + } + + /// + /// Unmute a user. + /// + /// User to unmute - can be null, but id should be set in that case. + /// Optional vikingid of the user to unmute. + /// False if the user isn't muted, otherwise true. + public static bool Unmute(this Client? target, int? id=null) { + if (target != null) { + if (!target.Muted) return false; + target.Muted = false; + id ??= target.PlayerData.VikingId; + } + + if (id != null) { + if (!MutedList.Remove((int)id)) return false; + SaveMutedBanned(); + } + + return true; + } + + /// + /// Ban a user. + /// + /// User to ban - can be null, but id should be set in that case. + /// Optional vikingid of the user to ban. + /// False if already banned, otherwise true. + public static bool Ban(this Client? target, int? id=null) { + if (target != null) { + if (target.Banned) return false; + target.Banned = true; + target.SetRoom(BanRoom); + id ??= target.PlayerData.VikingId; + } + + if (id != null) { + if (BannedList.ContainsKey((int)id)) return false; + BannedList.Add((int)id, target?.PlayerData.DiplayName); + SaveMutedBanned(); + } + + return true; + } + + /// + /// Unban a user. + /// + /// User to unban - can be null, but id should be set in that case. + /// Optional vikingid of the user to unban. + /// False if the user isn't banned, otherwise true. + public static bool Unban(this Client? target, int? id=null) { + if (target != null) { + if (!target.Banned) return false; + target.Banned = false; + target.Disconnect(); // Client will handle relogging. + id ??= target.PlayerData.VikingId; + } + + if (id != null) { + if (!BannedList.Remove((int)id)) return false; + SaveMutedBanned(); + } + + return true; + } } internal sealed class MutedBannedData { public Dictionary? MutedList { get; set; } public Dictionary? BannedList { get; set; } -} - -public class BanRoom : Room { - public BanRoom(string name) : base(name, name) {} - - public void UpdateClient(Client client) { - client.Send(RespondJoinRoom()); - } - - public override void Send(NetworkPacket packet, Client? skip = null) { - // Do nothing. - } - - public override NetworkPacket RespondJoinRoom() { - NetworkObject obj = new(); - NetworkArray roomInfo = new(); - roomInfo.Add(Id); - roomInfo.Add(Name); // Room Name - roomInfo.Add(Group); // Group Name - roomInfo.Add(true); // is game - roomInfo.Add(false); // is hidden - roomInfo.Add(false); // is password protected - roomInfo.Add((short)0); // player count - roomInfo.Add((short)1); // max player count - roomInfo.Add(new NetworkArray()); // empty roomvars - roomInfo.Add((short)0); // spectator count - roomInfo.Add((short)0); // max spectator count - - obj.Add("r", roomInfo); - obj.Add("ul", new NetworkArray()); // Empty userlist. - - NetworkPacket packet = NetworkObject.WrapObject(0, 4, obj).Serialize(); - packet.Compress(); - - return packet; - } - - public override NetworkPacket SubscribeRoom() { - NetworkObject obj = new(); - NetworkArray list = new(); - - NetworkArray r1 = new(); - r1.Add(Id); - r1.Add(Name); // Room Name - r1.Add(Group); // Group Name - r1.Add(true); - r1.Add(false); - r1.Add(false); - r1.Add((short)0); // player count - r1.Add((short)1); // max player count - r1.Add(new NetworkArray()); - r1.Add((short)0); - r1.Add((short)0); - - list.Add(r1); - - obj.Add("rl", list); - obj.Add("g", Group); - return NetworkObject.WrapObject(0, 15, obj).Serialize(); - } } \ No newline at end of file diff --git a/src/Server.cs b/src/Server.cs index 08b9d40..07795cb 100644 --- a/src/Server.cs +++ b/src/Server.cs @@ -7,18 +7,26 @@ using System.Net.Sockets; namespace sodoffmmo; public class Server { + static object lck = new(); + static readonly List clients = new(); readonly int port; readonly IPAddress ipAddress; readonly bool IPv6AndIPv4; ModuleManager moduleManager = new(); + public static int TotalClientCount => clients.Count; + public Server(IPAddress ipAdress, int port, bool IPv6AndIPv4) { this.ipAddress = ipAdress; this.port = port; this.IPv6AndIPv4 = IPv6AndIPv4; } + public static Client[] GetAllClients() { + return clients.ToArray(); + } + public async Task Run() { moduleManager.RegisterModules(); ManagementCommandProcessor.Initialize(); @@ -47,6 +55,7 @@ public class Server { private async Task HandleClient(Socket handler) { Client client = new(handler); + lock (lck) clients.Add(client); try { while (client.Connected) { await client.Receive(); @@ -60,6 +69,7 @@ public class Server { try { client.SetRoom(null); } catch (Exception) { } + lock (lck) clients.Remove(client); client.Disconnect(); Console.WriteLine("Socket disconnected IID: " + client.ClientID); }