Cleaned up code a bit and changed the unoptimized ban system.

This commit is contained in:
Hipposgrumm 2025-06-11 15:20:09 -06:00
parent f3b80729fd
commit 599ddc3dc2
9 changed files with 202 additions and 171 deletions

View File

@ -11,7 +11,7 @@ class ChatMessageHandler : CommandHandler {
string message = receivedObject.Get<NetworkObject>("p").Get<string>("chm"); string message = receivedObject.Get<NetworkObject>("p").Get<string>("chm");
if (ManagementCommandProcessor.ProcessCommand(message, client)) if (ManagementCommandProcessor.ProcessCommand(message, client))
return Task.CompletedTask; return Task.CompletedTask;
if (client.Muted()) { if (client.Muted) {
ClientMuted(client); ClientMuted(client);
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -88,8 +88,8 @@ class LoginHandler : CommandHandler
client.PlayerData.Role = info.Role; client.PlayerData.Role = info.Role;
client.PlayerData.VikingId = info.Id; client.PlayerData.VikingId = info.Id;
if (info.Id != null) { if (info.Id != null) {
client.RealMuted = Client.MutedList.ContainsKey((int)info.Id); client.Muted = PunishmentManager.MutedList.ContainsKey((int)info.Id);
client.Banned = Client.BannedList.ContainsKey((int)info.Id); client.Banned = PunishmentManager.BannedList.ContainsKey((int)info.Id);
} }
return true; return true;
} }

View File

@ -1,4 +1,5 @@
using sodoffmmo.Data; using sodoffmmo.Data;
using sodoffmmo.Management;
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -8,39 +9,15 @@ namespace sodoffmmo.Core;
public class Client { public class Client {
static int id; static int id;
static object lck = new(); static object lck = new();
protected static List<Client> clients = new();
// VikingId, Reason (nullable)
public static readonly Dictionary<int, string?> MutedList;
public static readonly Dictionary<int, string?> BannedList;
static Client() { // Runs when Client class is loaded
if (File.Exists("muted_banned_users.json")) {
try {
MutedBannedData? data = JsonSerializer.Deserialize<MutedBannedData>(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();
}
public int ClientID { get; private set; } public int ClientID { get; private set; }
public PlayerData PlayerData { get; set; } = new(); public PlayerData PlayerData { get; set; } = new();
public Room? Room { get; private set; } 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 OldApi { get; set; } = false;
public bool TempMuted { get; set; } = false; public bool Muted { get; set; } = false;
public bool RealMuted { get; set; } = false;
public bool Banned { get; set; } = false; public bool Banned { get; set; } = false;
public bool Muted() => TempMuted || RealMuted || Banned;
private readonly Socket socket; private readonly Socket socket;
SocketBuffer socketBuffer = new(); SocketBuffer socketBuffer = new();
@ -51,9 +28,16 @@ public class Client {
socket = clientSocket; socket = clientSocket;
lock (lck) { lock (lck) {
ClientID = ++id; ClientID = ++id;
clients.Add(this);
} }
} }
public int TotalClientCount => clients.Count;
public static Client[] GetAllClients() {
return clients.ToArray();
}
public async Task Receive() { public async Task Receive() {
byte[] buffer = new byte[2048]; byte[] buffer = new byte[2048];
int len = await socket.ReceiveAsync(buffer, SocketFlags.None); int len = await socket.ReceiveAsync(buffer, SocketFlags.None);
@ -75,13 +59,20 @@ public class Client {
} }
public void SetRoom(Room? room) { 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) { lock(clientLock) {
// set variable player data as not valid, but do not reset all player data // set variable player data as not valid, but do not reset all player data
PlayerData.IsValid = false; PlayerData.IsValid = false;
ReturnRoomOnPardon = room; // This is needed so that users are put where they're supposed to when being unbanned. if (Room != null) {
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. 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); Room.RemoveClient(this);
NetworkObject data = new(); NetworkObject data = new();
@ -90,32 +81,16 @@ public class Client {
Room.Send(NetworkObject.WrapObject(0, 1004, data).Serialize()); Room.Send(NetworkObject.WrapObject(0, 1004, data).Serialize());
} }
if (Banned && room?.Name != "LIMBO") { // set new room (null when SetRoom is used as LeaveRoom)
if (room != null) { Room = room;
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;
if (Room != null) { if (Room != null) {
Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}"); if (Room is not BanRoom) Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}");
Room.AddClient(this); Room.AddClient(this);
Send(Room.SubscribeRoom()); Send(Room.SubscribeRoom());
if (Room.Name != "LIMBO") UpdatePlayerUserVariables(); // do not update user vars if room is limbo if (Room.Name != "LIMBO") UpdatePlayerUserVariables(); // do not update user vars if room is limbo
}
} }
} }
} }
@ -135,6 +110,9 @@ public class Client {
} }
public void Disconnect() { public void Disconnect() {
lock (lck) {
clients.Remove(this);
}
try { try {
socket.Shutdown(SocketShutdown.Both); socket.Shutdown(SocketShutdown.Both);
} finally { } finally {
@ -152,22 +130,14 @@ public class Client {
scheduledDisconnect = true; scheduledDisconnect = true;
} }
public void Ban() {
Banned = true;
SetRoom(PunishmentManager.BanRoom);
}
public bool Connected { public bool Connected {
get { get {
return socket.Connected && !scheduledDisconnect; 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<int, string?>? MutedList { get; set; }
public Dictionary<int, string?>? BannedList { get; set; }
} }

View File

@ -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) { foreach (var roomClient in Clients) {
if (roomClient != skip) { if (roomClient != skip) {
roomClient.Send(packet); roomClient.Send(packet);
@ -100,18 +100,12 @@ public class Room {
} }
} }
public NetworkPacket RespondJoinRoom() { public virtual NetworkPacket RespondJoinRoom() {
NetworkObject obj = new(); NetworkObject obj = new();
NetworkArray roomInfo = new(); NetworkArray roomInfo = new();
roomInfo.Add(Id); roomInfo.Add(Id);
if (Name.StartsWith("BannedUserRoom_")) { roomInfo.Add(Name); // Room Name
// Send these fake names to the client. roomInfo.Add(Group); // Group Name
roomInfo.Add("YouAreBanned"); // Room Name
roomInfo.Add("YouAreBanned"); // Group Name
} else {
roomInfo.Add(Name); // Room Name
roomInfo.Add(Group); // Group Name
}
roomInfo.Add(true); // is game roomInfo.Add(true); // is game
roomInfo.Add(false); // is hidden roomInfo.Add(false); // is hidden
roomInfo.Add(false); // is password protected roomInfo.Add(false); // is password protected
@ -136,20 +130,14 @@ public class Room {
return packet; return packet;
} }
public NetworkPacket SubscribeRoom() { public virtual NetworkPacket SubscribeRoom() {
NetworkObject obj = new(); NetworkObject obj = new();
NetworkArray list = new(); NetworkArray list = new();
NetworkArray r1 = new(); NetworkArray r1 = new();
r1.Add(Id); r1.Add(Id);
if (Name.StartsWith("BannedUserRoom_")) { r1.Add(Name); // Room Name
// Send these fake names to the client. r1.Add(Group); // Group Name
r1.Add("YouAreBanned"); // Room Name
r1.Add("YouAreBanned"); // Group Name
} else {
r1.Add(Name); // Room Name
r1.Add(Group); // Group Name
}
r1.Add(true); r1.Add(true);
r1.Add(false); r1.Add(false);
r1.Add(false); r1.Add(false);

View File

@ -93,17 +93,15 @@ public class PlayerData {
} }
// update the playername (sent with avatardata) if it changes // 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) { if (varName == "A" && variables.GetValueOrDefault("A") != value) {
try { 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<AvatarData>(value)?.DisplayName; string? name = JsonSerializer.Deserialize<AvatarData>(value)?.DisplayName;
if (name != null && name != DiplayName) { if (name != null && name != DiplayName) {
DiplayName = name; DiplayName = name;
} }
} }
catch (Exception) {} // Pokemon-style exception handling catch (JsonException) {} // Don't break if the data is malformed or something.
} }
// store in directory // store in directory

View File

@ -1,6 +1,5 @@
using sodoffmmo.Attributes; using sodoffmmo.Attributes;
using sodoffmmo.Core; using sodoffmmo.Core;
using System.Numerics;
namespace sodoffmmo.Management.Commands; namespace sodoffmmo.Management.Commands;
@ -13,30 +12,24 @@ class BanCommand : IManagementCommand {
} }
string name = string.Join(' ', arguments); string name = string.Join(' ', arguments);
Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room. Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room.
if (target == null) { // Find through all the rooms. if (target == null) { // Search all clients in case the offender left the room.
foreach (Room room in Room.AllRooms()) { target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name);
if (room == client.Room) continue; // Skip; we already checked here. if (target == null) { // If the target is still null, call it a failure.
target = room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); client.Send(Utils.BuildServerSideMessage($"Ban: user {name} not found", "Server"));
if (target != null) break; return;
} }
} }
if (target == null) { // Failure
client.Send(Utils.BuildServerSideMessage($"Ban: user {name} not found", "Server"));
return;
}
if (target.Banned) { if (target.Banned) {
client.Send(Utils.BuildServerSideMessage($"Ban: {name} is already banned", "Server")); client.Send(Utils.BuildServerSideMessage($"Ban: {name} is already banned", "Server"));
} else { } else {
target.Banned = true; target.Ban();
target.SetRoom(target.Room); // This will run the client through the 'if Banned' conditions.
client.Send(Utils.BuildServerSideMessage($"Ban: {name} has been banned", "Server")); client.Send(Utils.BuildServerSideMessage($"Ban: {name} has been banned", "Server"));
if (target.PlayerData.VikingId != null) { if (target.PlayerData.VikingId != null) {
Client.BannedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName); PunishmentManager.BannedList.Add((int)target.PlayerData.VikingId, target.PlayerData.DiplayName);
Client.SaveMutedBanned(); PunishmentManager.SaveMutedBanned();
} else { } 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); string name = string.Join(' ', arguments);
int? id = null; 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. if (PunishmentManager.BannedList.ContainsValue(name)) // 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. id = PunishmentManager.BannedList.FirstOrDefault(p => p.Value == name).Key;
if (target == null) { // Find through all the rooms.
foreach (Room room in Room.AllRooms()) { if (id == null) { // Failure
if (room == client.Room) continue; // Skip; we already checked here. client.Send(Utils.BuildServerSideMessage($"Unban: User named \"{name}\" is not banned", "Server"));
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"));
return; return;
} }
if (target?.Muted() == false || (id != null && !Client.BannedList.ContainsKey((int)id))) { 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} is not banned", "Server"));
} else { client.Send(Utils.BuildServerSideMessage($"Unban: {name} has been unbanned", "Server"));
if (target != null) { PunishmentManager.BannedList.Remove((int)id);
target.Banned = false; PunishmentManager.SaveMutedBanned();
target.SetRoom(target.ReturnRoomOnPardon); // Put the target back. if (target != null) {
} target.Banned = false;
client.Send(Utils.BuildServerSideMessage($"Unban: {name} has been unbanned", "Server")); target.Disconnect(); // Client will handle relogging.
if (id != null) {
Client.BannedList.Remove((int)id);
Client.SaveMutedBanned();
}
} }
} }
} }

View File

@ -12,30 +12,27 @@ class MuteCommand : IManagementCommand {
} }
string name = string.Join(' ', arguments); string name = string.Join(' ', arguments);
Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room. Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); // Find in this room.
if (target == null) { // Find through all the rooms. if (target == null) { // Search all clients in case the offender left the room.
foreach (Room room in Room.AllRooms()) { target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name);
if (room == client.Room) continue; // Skip; we already checked here. if (target == null) { // Failure
target = room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == name); client.Send(Utils.BuildServerSideMessage($"Mute: user {name} not found", "Server"));
if (target != null) break; return;
} }
} }
if (target == null) { // Failure
client.Send(Utils.BuildServerSideMessage($"Mute: user {name} not found", "Server"));
return;
}
if (target.RealMuted) { if (target.PlayerData.VikingId != null) {
client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server")); if (PunishmentManager.MutedList.ContainsKey((int)target.PlayerData.VikingId)) {
} else { client.Send(Utils.BuildServerSideMessage($"Mute: {name} is already muted", "Server"));
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();
} else { } 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); string name = string.Join(' ', arguments);
int? id = null; 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. 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. if (target == null) { // Search all clients in case the target left the room.
foreach (Room room in Room.AllRooms()) { target = Client.GetAllClients().FirstOrDefault(x => x.PlayerData.DiplayName == name);
if (room == client.Room) continue; // Skip; we already checked here. if (target == null && id == null) { // Failure
target = room.Clients.FirstOrDefault(x => x.PlayerData.VikingId == id || x.PlayerData.DiplayName == name); client.Send(Utils.BuildServerSideMessage($"Unmute: user {name} not found", "Server"));
if (target != null) break; 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")); client.Send(Utils.BuildServerSideMessage($"Unmute: {name} is not muted", "Server"));
} else { } else {
if (target != null) { if (target != null) {
target.RealMuted = false; target.Muted = false;
target.TempMuted = false; // Undo this too I guess.
} }
client.Send(Utils.BuildServerSideMessage($"Unmute: {name} has been unmuted", "Server")); client.Send(Utils.BuildServerSideMessage($"Unmute: {name} has been unmuted", "Server"));
if (id != null) { if (id != null) {
Client.MutedList.Remove((int)id); PunishmentManager.MutedList.Remove((int)id);
Client.SaveMutedBanned(); PunishmentManager.SaveMutedBanned();
} }
} }
} }

View File

@ -17,14 +17,11 @@ class TempMuteCommand : IManagementCommand {
return; return;
} }
if (target.RealMuted) { if (target.Muted) {
client.Send(Utils.BuildServerSideMessage($"TempMute: {name} is already regular muted", "Server")); client.Send(Utils.BuildServerSideMessage($"TempMute: {name} is already muted", "Server"));
} else { } else {
target.TempMuted = !target.TempMuted; target.Muted = true;
if (target.TempMuted) client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been temporarily muted", "Server"));
client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been temporarily muted", "Server"));
else
client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been unmuted", "Server"));
} }
} }
} }

View File

@ -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<int, string?> MutedList;
public static readonly Dictionary<int, string?> 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<MutedBannedData>(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<int, string?>? MutedList { get; set; }
public Dictionary<int, string?>? 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();
}
}