From 599ddc3dc2440835ce8acb50f011cef4d96ed5c1 Mon Sep 17 00:00:00 2001 From: Hipposgrumm Date: Wed, 11 Jun 2025 15:20:09 -0600 Subject: [PATCH] Cleaned up code a bit and changed the unoptimized ban system. --- src/CommandHandlers/ChatMessageHandler.cs | 2 +- src/CommandHandlers/LoginHandler.cs | 4 +- src/Core/Client.cs | 100 +++++++------------- src/Core/Room.cs | 26 ++---- src/Data/PlayerData.cs | 6 +- src/Management/Commands/BanCommand.cs | 61 +++++------- src/Management/Commands/MuteCommand.cs | 60 ++++++------ src/Management/Commands/TempMuteCommand.cs | 11 +-- src/Management/PunishmentManager.cs | 103 +++++++++++++++++++++ 9 files changed, 202 insertions(+), 171 deletions(-) create mode 100644 src/Management/PunishmentManager.cs diff --git a/src/CommandHandlers/ChatMessageHandler.cs b/src/CommandHandlers/ChatMessageHandler.cs index 6fa22e2..b692952 100644 --- a/src/CommandHandlers/ChatMessageHandler.cs +++ b/src/CommandHandlers/ChatMessageHandler.cs @@ -11,7 +11,7 @@ class ChatMessageHandler : CommandHandler { string message = receivedObject.Get("p").Get("chm"); if (ManagementCommandProcessor.ProcessCommand(message, client)) return Task.CompletedTask; - if (client.Muted()) { + if (client.Muted) { ClientMuted(client); return Task.CompletedTask; } diff --git a/src/CommandHandlers/LoginHandler.cs b/src/CommandHandlers/LoginHandler.cs index 110f688..2fe31da 100644 --- a/src/CommandHandlers/LoginHandler.cs +++ b/src/CommandHandlers/LoginHandler.cs @@ -88,8 +88,8 @@ class LoginHandler : CommandHandler client.PlayerData.Role = info.Role; client.PlayerData.VikingId = info.Id; if (info.Id != null) { - client.RealMuted = Client.MutedList.ContainsKey((int)info.Id); - client.Banned = Client.BannedList.ContainsKey((int)info.Id); + client.Muted = PunishmentManager.MutedList.ContainsKey((int)info.Id); + client.Banned = PunishmentManager.BannedList.ContainsKey((int)info.Id); } return true; } diff --git a/src/Core/Client.cs b/src/Core/Client.cs index 1511b75..e3acae9 100644 --- a/src/Core/Client.cs +++ b/src/Core/Client.cs @@ -1,4 +1,5 @@ using sodoffmmo.Data; +using sodoffmmo.Management; using System; using System.Net.Sockets; using System.Runtime.CompilerServices; @@ -8,39 +9,15 @@ namespace sodoffmmo.Core; public class Client { static int id; static object lck = new(); - - // VikingId, Reason (nullable) - public static readonly Dictionary MutedList; - public static readonly Dictionary BannedList; - - static Client() { // Runs when Client class is loaded - if (File.Exists("muted_banned_users.json")) { - try { - MutedBannedData? data = JsonSerializer.Deserialize(File.ReadAllText("muted_banned_users.json")); - if (data != null) { - MutedList = data.MutedList ?? new(); - BannedList = data.BannedList ?? new(); - return; - } - } catch { - Console.WriteLine("Data for Muted/Banned users wasn't read. Is it corrupted?"); - } - } - MutedList = new(); - BannedList = new(); - } + protected static List clients = new(); public int ClientID { get; private set; } public PlayerData PlayerData { get; set; } = new(); public Room? Room { get; private set; } - // Used by ban system for smooth unban transitions. - public Room? ReturnRoomOnPardon { get; private set; } = null; public bool OldApi { get; set; } = false; - public bool TempMuted { get; set; } = false; - public bool RealMuted { get; set; } = false; + public bool Muted { get; set; } = false; public bool Banned { get; set; } = false; - public bool Muted() => TempMuted || RealMuted || Banned; private readonly Socket socket; SocketBuffer socketBuffer = new(); @@ -51,9 +28,16 @@ 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); @@ -75,13 +59,20 @@ public class Client { } public void SetRoom(Room? room) { + if (Banned && room != null) { + if (Room == PunishmentManager.BanRoom) { + PunishmentManager.BanRoom.UpdateClient(this); // This is needed for every time the client changes scene. + return; + } + room = PunishmentManager.BanRoom; + } + lock(clientLock) { // set variable player data as not valid, but do not reset all player data PlayerData.IsValid = false; - ReturnRoomOnPardon = room; // This is needed so that users are put where they're supposed to when being unbanned. - if (Room != null && (!Banned || room?.Name == "LIMBO" || !Room.Name.StartsWith("BannedUserRoom_"))) { // If user is in the ban room they're just fine exactly where they are. - Console.WriteLine($"Leave room: {Room.Name} (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); + if (Room != null) { + if (Room is not BanRoom) Console.WriteLine($"Leave room: {Room.Name} (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); Room.RemoveClient(this); NetworkObject data = new(); @@ -90,32 +81,16 @@ public class Client { Room.Send(NetworkObject.WrapObject(0, 1004, data).Serialize()); } - if (Banned && room?.Name != "LIMBO") { - if (room != null) { - Room = Room.GetOrAdd("BannedUserRoom_" + ClientID, autoRemove: true); - if (Room.Clients.Contains(this)) { - Send(Room.RespondJoinRoom()); - } else { - Room.AddClient(this); - } - Send(Room.SubscribeRoom()); - } else { - Room?.RemoveClient(this); - Room = null; - } - } else { - // set new room (null when SetRoom is used as LeaveRoom) - Room = room; + // set new room (null when SetRoom is used as LeaveRoom) + Room = room; - if (Room != null) { - Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); - Room.AddClient(this); + if (Room != null) { + if (Room is not BanRoom) Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); + Room.AddClient(this); - Send(Room.SubscribeRoom()); - if (Room.Name != "LIMBO") UpdatePlayerUserVariables(); // do not update user vars if room is limbo - } + Send(Room.SubscribeRoom()); + if (Room.Name != "LIMBO") UpdatePlayerUserVariables(); // do not update user vars if room is limbo } - } } @@ -135,6 +110,9 @@ public class Client { } public void Disconnect() { + lock (lck) { + clients.Remove(this); + } try { socket.Shutdown(SocketShutdown.Both); } finally { @@ -152,22 +130,14 @@ public class Client { scheduledDisconnect = true; } + public void Ban() { + Banned = true; + SetRoom(PunishmentManager.BanRoom); + } + public bool Connected { get { return socket.Connected && !scheduledDisconnect; } } - public static void SaveMutedBanned() { - File.WriteAllText("muted_banned_users.json", JsonSerializer.Serialize(new MutedBannedData { - MutedList = MutedList, - BannedList = BannedList - }, new JsonSerializerOptions { - WriteIndented = true - })); - } -} - -internal sealed class MutedBannedData { - public Dictionary? MutedList { get; set; } - public Dictionary? BannedList { get; set; } } diff --git a/src/Core/Room.cs b/src/Core/Room.cs index b379f44..601f9c8 100644 --- a/src/Core/Room.cs +++ b/src/Core/Room.cs @@ -68,7 +68,7 @@ public class Room { } } - public void Send(NetworkPacket packet, Client? skip = null) { + public virtual void Send(NetworkPacket packet, Client? skip = null) { foreach (var roomClient in Clients) { if (roomClient != skip) { roomClient.Send(packet); @@ -100,18 +100,12 @@ public class Room { } } - public NetworkPacket RespondJoinRoom() { + public virtual NetworkPacket RespondJoinRoom() { NetworkObject obj = new(); NetworkArray roomInfo = new(); roomInfo.Add(Id); - if (Name.StartsWith("BannedUserRoom_")) { - // Send these fake names to the client. - roomInfo.Add("YouAreBanned"); // Room Name - roomInfo.Add("YouAreBanned"); // Group Name - } else { - roomInfo.Add(Name); // Room Name - roomInfo.Add(Group); // Group Name - } + 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 @@ -136,20 +130,14 @@ public class Room { return packet; } - public NetworkPacket SubscribeRoom() { + public virtual NetworkPacket SubscribeRoom() { NetworkObject obj = new(); NetworkArray list = new(); NetworkArray r1 = new(); r1.Add(Id); - if (Name.StartsWith("BannedUserRoom_")) { - // Send these fake names to the client. - r1.Add("YouAreBanned"); // Room Name - r1.Add("YouAreBanned"); // Group Name - } else { - r1.Add(Name); // Room Name - r1.Add(Group); // Group Name - } + r1.Add(Name); // Room Name + r1.Add(Group); // Group Name r1.Add(true); r1.Add(false); r1.Add(false); diff --git a/src/Data/PlayerData.cs b/src/Data/PlayerData.cs index d958f04..d21802c 100644 --- a/src/Data/PlayerData.cs +++ b/src/Data/PlayerData.cs @@ -93,17 +93,15 @@ public class PlayerData { } // update the playername (sent with avatardata) if it changes - // this is also hit when the player joins the room + // this is also sent when the player joins the room if (varName == "A" && variables.GetValueOrDefault("A") != value) { try { - // Gonna surround this in a try/catch in case for some - // reason it breaks so that it doesn't break MMO. string? name = JsonSerializer.Deserialize(value)?.DisplayName; if (name != null && name != DiplayName) { DiplayName = name; } } - catch (Exception) {} // Pokemon-style exception handling + catch (JsonException) {} // Don't break if the data is malformed or something. } // store in directory diff --git a/src/Management/Commands/BanCommand.cs b/src/Management/Commands/BanCommand.cs index eadd73b..dc2a986 100644 --- a/src/Management/Commands/BanCommand.cs +++ b/src/Management/Commands/BanCommand.cs @@ -1,6 +1,5 @@ using sodoffmmo.Attributes; using sodoffmmo.Core; -using System.Numerics; namespace sodoffmmo.Management.Commands; @@ -13,30 +12,24 @@ 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) { // Find through all the rooms. - foreach (Room room in Room.AllRooms()) { - if (room == client.Room) continue; // Skip; we already checked here. - target = room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); - if (target != null) break; + if (target == null) { // Search all clients in case the offender left the room. + target = Client.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 == null) { // 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.Banned = true; - target.SetRoom(target.Room); // This will run the client through the 'if Banned' conditions. + target.Ban(); client.Send(Utils.BuildServerSideMessage($"Ban: {name} has been banned", "Server")); if (target.PlayerData.VikingId != null) { - Client.BannedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); - Client.SaveMutedBanned(); + PunishmentManager.BannedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); + PunishmentManager.SaveMutedBanned(); } else { - client.Send(Utils.BuildServerSideMessage($"Mute: This user is not authenticated. Their ban will not be permanent!", "Server")); - + client.Send(Utils.BuildServerSideMessage($"Ban: This user is not authenticated. They have been merely kicked instead.", "Server")); } } } @@ -51,32 +44,22 @@ class UnbanCommand : IManagementCommand { } string name = string.Join(' ', arguments); int? id = null; - if (Client.BannedList.ContainsValue(name)) id = Client.BannedList.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) { // Find through all the rooms. - foreach (Room room in Room.AllRooms()) { - if (room == client.Room) continue; // Skip; we already checked here. - target = room.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); - if (target != null) break; - } - } - if (target == null && id == null) { // Failure - client.Send(Utils.BuildServerSideMessage($"Unban: user {name} not found", "Server")); + 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; } - if (target?.Muted() == false || (id != null && !Client.BannedList.ContainsKey((int)id))) { - client.Send(Utils.BuildServerSideMessage($"Unban: {name} is not banned", "Server")); - } else { - if (target != null) { - target.Banned = false; - target.SetRoom(target.ReturnRoomOnPardon); // Put the target back. - } - client.Send(Utils.BuildServerSideMessage($"Unban: {name} has been unbanned", "Server")); - if (id != null) { - Client.BannedList.Remove((int)id); - Client.SaveMutedBanned(); - } + 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. } } } \ No newline at end of file diff --git a/src/Management/Commands/MuteCommand.cs b/src/Management/Commands/MuteCommand.cs index d5cd3a9..55e9a8c 100644 --- a/src/Management/Commands/MuteCommand.cs +++ b/src/Management/Commands/MuteCommand.cs @@ -12,30 +12,27 @@ 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) { // Find through all the rooms. - foreach (Room room in Room.AllRooms()) { - if (room == client.Room) continue; // Skip; we already checked here. - target = room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); - if (target != null) break; + if (target == null) { // Search all clients in case the offender left the room. + target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name); + if (target == null) { // Failure + client.Send(Utils.BuildServerSideMessage($"Mute: user {name} not found", "Server")); + return; } } - if (target == null) { // Failure - client.Send(Utils.BuildServerSideMessage($"Mute: user {name} not found", "Server")); - return; - } - if (target.RealMuted) { - client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server")); - } else { - target.RealMuted = true; - client.Send(Utils.BuildServerSideMessage($"Mute: {name} has been muted", "Server")); - if (target.PlayerData.VikingId != null) { - Client.MutedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); - Client.SaveMutedBanned(); + if (target.PlayerData.VikingId != null) { + if (PunishmentManager.MutedList.ContainsKey((int)target.PlayerData.VikingId)) { + client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server")); } else { - client.Send(Utils.BuildServerSideMessage($"Mute: This user is not authenticated. Their mute will not be permanent!", "Server")); - + 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; + 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")); } } } @@ -49,31 +46,26 @@ class UnmuteCommand : IManagementCommand { } string name = string.Join(' ', arguments); int? id = null; - if (Client.MutedList.ContainsValue(name)) id = Client.MutedList.FirstOrDefault(p => p.Value == name).Key; // Find in list in case they're offline. + 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) { // Find through all the rooms. - foreach (Room room in Room.AllRooms()) { - if (room == client.Room) continue; // Skip; we already checked here. - target = room.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); - if (target != null) break; + 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 == null && id == null) { // Failure - client.Send(Utils.BuildServerSideMessage($"Unmute: user {name} not found", "Server")); - return; - } - if (target?.Muted() == false || (id != null && !Client.MutedList.ContainsKey((int)id))) { + 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.RealMuted = false; - target.TempMuted = false; // Undo this too I guess. + target.Muted = false; } client.Send(Utils.BuildServerSideMessage($"Unmute: {name} has been unmuted", "Server")); if (id != null) { - Client.MutedList.Remove((int)id); - Client.SaveMutedBanned(); + PunishmentManager.MutedList.Remove((int)id); + PunishmentManager.SaveMutedBanned(); } } } diff --git a/src/Management/Commands/TempMuteCommand.cs b/src/Management/Commands/TempMuteCommand.cs index 1303685..7c26954 100644 --- a/src/Management/Commands/TempMuteCommand.cs +++ b/src/Management/Commands/TempMuteCommand.cs @@ -17,14 +17,11 @@ class TempMuteCommand : IManagementCommand { return; } - if (target.RealMuted) { - client.Send(Utils.BuildServerSideMessage($"TempMute: {name} is already regular muted", "Server")); + if (target.Muted) { + client.Send(Utils.BuildServerSideMessage($"TempMute: {name} is already muted", "Server")); } else { - target.TempMuted = !target.TempMuted; - if (target.TempMuted) - client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been temporarily muted", "Server")); - else - client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been unmuted", "Server")); + target.Muted = true; + client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been temporarily muted", "Server")); } } } diff --git a/src/Management/PunishmentManager.cs b/src/Management/PunishmentManager.cs new file mode 100644 index 0000000..a00c2f0 --- /dev/null +++ b/src/Management/PunishmentManager.cs @@ -0,0 +1,103 @@ +using sodoffmmo.Core; +using sodoffmmo.Data; +using System.Text.Json; + +namespace sodoffmmo.Management; + +public class PunishmentManager { + // VikingId, Reason (nullable) + public static readonly Dictionary MutedList; + public static readonly Dictionary BannedList; + + public static readonly BanRoom BanRoom = new("YouAreBanned"); + + static PunishmentManager() { // Runs when Client class is loaded + if (File.Exists("muted_banned_users.json")) { + try { + MutedBannedData? data = JsonSerializer.Deserialize(File.ReadAllText("muted_banned_users.json")); + if (data != null) { + MutedList = data.MutedList ?? new(); + BannedList = data.BannedList ?? new(); + return; + } + } catch { + Console.WriteLine("Data for Muted/Banned users wasn't read. It might be corrupted or malformed."); + } + } + MutedList = new(); + BannedList = new(); + } + public static void SaveMutedBanned() { + File.WriteAllText("muted_banned_users.json", JsonSerializer.Serialize(new MutedBannedData { + MutedList = MutedList, + BannedList = BannedList + }, new JsonSerializerOptions { + WriteIndented = 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