diff --git a/plugins/sample_plugin/Plugin.cs b/plugins/sample_plugin/Plugin.cs
new file mode 100644
index 0000000..5c0325a
--- /dev/null
+++ b/plugins/sample_plugin/Plugin.cs
@@ -0,0 +1,36 @@
+using System;
+using HarmonyLib;
+
+namespace SoDOff_MMO_Plugin {
+ // all classes from SoDOff_MMO_Plugin namespace will be created on dll load
+ public class SamplePlugin1 {
+ public SamplePlugin1() {
+ Console.WriteLine("SamplePlugin1 constructor");
+ var harmony = new Harmony("SamplePlugin1");
+ harmony.PatchAll();
+ }
+ }
+ public class SamplePlugin2 {
+ public SamplePlugin2() {
+ Console.WriteLine("SamplePlugin2 constructor");
+ }
+ }
+}
+
+namespace Internal {
+ // classes from other namespace will be NOT created on dll load
+ public class SamplePlugin3 {
+ public SamplePlugin3() {
+ Console.WriteLine("SamplePlugin3 constructor");
+ }
+ }
+}
+
+
+[HarmonyPatch("ChatMessageHandler", "Chat")]
+public class ChatMessageHandler_Chat {
+ [HarmonyPrefix]
+ public static void Chat(sodoffmmo.Core.Client client, string message) {
+ Console.WriteLine($"Chat message: {message}");
+ }
+}
diff --git a/plugins/sample_plugin/SamplePlugin.csproj b/plugins/sample_plugin/SamplePlugin.csproj
new file mode 100644
index 0000000..56ee8bf
--- /dev/null
+++ b/plugins/sample_plugin/SamplePlugin.csproj
@@ -0,0 +1,17 @@
+
+
+ net6.0
+ SamplePlugin
+ enable
+
+
+
+
+
+
+
+
+ ../../src/bin/Debug/net6.0/sodoffmmo.dll
+
+
+
diff --git a/src/CommandHandlers/ChatMessageHandler.cs b/src/CommandHandlers/ChatMessageHandler.cs
index ffdacf7..d62a4d2 100644
--- a/src/CommandHandlers/ChatMessageHandler.cs
+++ b/src/CommandHandlers/ChatMessageHandler.cs
@@ -11,33 +11,35 @@ class ChatMessageHandler : CommandHandler {
string message = receivedObject.Get("p").Get("chm");
if (ManagementCommandProcessor.ProcessCommand(message, client))
return Task.CompletedTask;
- if (client.TempMuted) {
- ClientMuted(client);
- return Task.CompletedTask;
- }
if (!Configuration.ServerConfiguration.EnableChat && !client.Room.AllowChatOverride) {
- ChatDisabled(client);
+ ChatDisabled(client, message);
+ } else if (Configuration.ServerConfiguration.Authentication >= AuthenticationMode.RequiredForChat && client.PlayerData.VikingId != 0) {
+ ChatRequiredAuthentication(client, message);
+ } else if (client.Muted) {
+ ClientMuted(client, message);
} else {
Chat(client, message);
}
return Task.CompletedTask;
}
- public void ChatDisabled(Client client) {
+ // NOTE: message is passed also for functions that refuse to send messages (this can be useful at the level of the chat moderation plugin)
+
+ public void ChatDisabled(Client client, string message) {
client.Send(Utils.BuildServerSideMessage("Unfortunately, chat has been disabled by server administrators", "Server"));
}
- public void ClientMuted(Client client) {
+ public void ChatRequiredAuthentication(Client client, string message) {
+ client.Send(Utils.BuildServerSideMessage("You must be authenticated to use the chat", "Server"));
+ }
+
+ public void ClientMuted(Client client, string message) {
client.Send(Utils.BuildServerSideMessage("You have been muted by the moderators", "Server"));
}
public void Chat(Client client, string message) {
- if (Configuration.ServerConfiguration.Authentication >= AuthenticationMode.RequiredForChat && client.PlayerData.DiplayName == "placeholder") {
- client.Send(Utils.BuildServerSideMessage("You must be authenticated to use the chat", "Server"));
- return;
- }
-
- client.Room.Send(Utils.BuildChatMessage(client.PlayerData.Uid, message, client.PlayerData.DiplayName), client);
+ client.Room.Send(Utils.BuildChatMessage(client.PlayerData.Uid, message, client.PlayerData.DisplayName), client);
+ // NOTE: using DisplayName not VikingName here for consistency with client behavior, preventing "placeholder" in not authenticated modes and support for changing name while logged on MMO
NetworkObject cmd = new();
NetworkObject data = new();
diff --git a/src/CommandHandlers/LoginHandler.cs b/src/CommandHandlers/LoginHandler.cs
index 0714226..6620957 100644
--- a/src/CommandHandlers/LoginHandler.cs
+++ b/src/CommandHandlers/LoginHandler.cs
@@ -84,7 +84,8 @@ class LoginHandler : CommandHandler
AuthenticationInfo info = Utils.DeserializeXml(responseString);
if (info.Authenticated) {
- client.PlayerData.DiplayName = info.DisplayName;
+ client.PlayerData.VikingName = info.VikingName;
+ client.PlayerData.VikingId = info.Id;
client.PlayerData.Role = info.Role;
return true;
}
diff --git a/src/Core/Client.cs b/src/Core/Client.cs
index 9743489..c4f1b2d 100644
--- a/src/Core/Client.cs
+++ b/src/Core/Client.cs
@@ -12,7 +12,8 @@ public class Client {
public PlayerData PlayerData { get; set; } = new();
public Room? Room { get; private set; }
public bool OldApi { get; set; } = false;
- public bool TempMuted { get; set; } = false;
+
+ public bool Muted { get; set; } = false;
private readonly Socket socket;
SocketBuffer socketBuffer = new();
diff --git a/src/Core/SpecialRoom.cs b/src/Core/SpecialRoom.cs
index e61e4fe..f48f510 100644
--- a/src/Core/SpecialRoom.cs
+++ b/src/Core/SpecialRoom.cs
@@ -90,7 +90,7 @@ public class SpecialRoom : Room {
Console.WriteLine("Started event " +alert + " in room " + Name);
} else {
specificClient.Send(packet);
- Console.WriteLine("Added " + specificClient.PlayerData.DiplayName + " to event " + alert + " with " + duration + " seconds remaining");
+ Console.WriteLine("Added " + specificClient.PlayerData.DisplayName + " to event " + alert + " with " + duration + " seconds remaining");
}
}
diff --git a/src/Data/PlayerData.cs b/src/Data/PlayerData.cs
index 47eed62..9c6bdc0 100644
--- a/src/Data/PlayerData.cs
+++ b/src/Data/PlayerData.cs
@@ -2,6 +2,7 @@
using System.Globalization;
using sodoffmmo.Core;
using sodoffmmo.Management;
+using System.Text.Json;
namespace sodoffmmo.Data;
public class PlayerData {
@@ -35,7 +36,13 @@ public class PlayerData {
// animation bitfield (animations used by avatar, e.g. mounted, swim, ...)
public int Mbf { get; set; }
- public string DiplayName { get; set; } = "placeholder";
+ // Name of player used by MMO
+ public string DisplayName { get; set; } = "placeholder";
+ // Name of player from API database
+ public string VikingName { get; set; } = "";
+ // ID of player from API database, zero for not authenticated players
+ public int VikingId { get; set; } = 0;
+ // role of player for management
public Role Role { get; set; } = Role.User;
public long last_ue_time { get; set; } = 0;
@@ -90,6 +97,18 @@ public class PlayerData {
value = FixMountState(value);
}
+ // update the playername (sent with avatardata) if it changes
+ // this is also sent when the player joins the room
+ if (varName == "A" && variables.GetValueOrDefault("A") != value) {
+ try {
+ string? name = JsonSerializer.Deserialize(value)?.DisplayName;
+ if (name != null && name != DisplayName) {
+ DisplayName = name;
+ }
+ }
+ catch (JsonException) {} // Don't break if the data is malformed or something.
+ }
+
// store in directory
variables[varName] = value;
return value;
diff --git a/src/Management/Commands/TempMuteCommand.cs b/src/Management/Commands/TempMuteCommand.cs
index 6479173..38fd94d 100644
--- a/src/Management/Commands/TempMuteCommand.cs
+++ b/src/Management/Commands/TempMuteCommand.cs
@@ -6,19 +6,27 @@ namespace sodoffmmo.Management.Commands;
[ManagementCommand("tempmute", Role.Moderator)]
class TempMuteCommand : IManagementCommand {
public void Handle(Client client, string[] arguments) {
- if (arguments.Length != 1) {
+ if (arguments.Length < 1) {
client.Send(Utils.BuildServerSideMessage("TempMute: Invalid number of arguments", "Server"));
return;
}
- Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == arguments[0]);
- if (target == null) {
- client.Send(Utils.BuildServerSideMessage($"TempMute: user {arguments[0]} not found", "Server"));
+ string name = string.Join(' ', arguments);
+ var targets = client.Room.Clients.Where(x => x.PlayerData.DisplayName == name);
+ // NOTE: using DisplayName here because this is the name displayed in client-side chat history
+ if (targets.Count() == 0) {
+ client.Send(Utils.BuildServerSideMessage($"TempMute: user {name} not found", "Server"));
+ return;
+ } else if (targets.Count() > 1) {
+ client.Send(Utils.BuildServerSideMessage($"TempMute: found multiple users with the name: {name}", "Server"));
return;
}
- target.TempMuted = !target.TempMuted;
- if (target.TempMuted)
- client.Send(Utils.BuildServerSideMessage($"TempMute: {arguments[0]} has been temporarily muted", "Server"));
- else
- client.Send(Utils.BuildServerSideMessage($"TempMute: {arguments[0]} has been unmuted", "Server"));
+
+ Client target = targets.First();
+ if (target.Muted) {
+ client.Send(Utils.BuildServerSideMessage($"TempMute: {name} is already muted", "Server"));
+ } else {
+ target.Muted = true;
+ client.Send(Utils.BuildServerSideMessage($"TempMute: {name} has been temporarily muted", "Server"));
+ }
}
}
diff --git a/src/Management/ManagementCommandProcessor.cs b/src/Management/ManagementCommandProcessor.cs
index c4b35eb..fbab23d 100644
--- a/src/Management/ManagementCommandProcessor.cs
+++ b/src/Management/ManagementCommandProcessor.cs
@@ -42,7 +42,7 @@ public class ManagementCommandProcessor {
if (commands.TryGetValue(new Tuple(commandName, currentRole), out Type? commandType)) {
IManagementCommand command = (IManagementCommand)Activator.CreateInstance(commandType)!;
- Console.WriteLine($"Management command {commandName} by {client.PlayerData.DiplayName} ({client.PlayerData.Uid}) in {client.Room.Name}");
+ Console.WriteLine($"Management command {commandName} by {client.PlayerData.VikingName} ({client.PlayerData.Uid}) in {client.Room.Name}");
command.Handle(client, arguments);
return true;
}
diff --git a/src/Program.cs b/src/Program.cs
index 6878f22..75ed5d0 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,11 +1,30 @@
using sodoffmmo;
using sodoffmmo.Core;
using System.Net;
+using System.IO;
+using System.Reflection;
Configuration.Initialize();
-Server server;
+string pluginsDir = Path.GetFullPath("plugins");
+Console.WriteLine($"Loading plugins from '{pluginsDir}' ...");
+List plugins = new();
+try {
+ foreach (var dllFile in Directory.GetFiles(pluginsDir, "*.dll")) {
+ var dll = Assembly.LoadFrom(dllFile);
+ foreach(Type type in dll.GetExportedTypes()) {
+ if (type.Namespace == "SoDOff_MMO_Plugin") {
+ Console.WriteLine($" * {type} ({Path.GetFileName(dllFile)})");
+ plugins.Add(Activator.CreateInstance(type));
+ }
+ }
+ }
+} catch (System.IO.DirectoryNotFoundException) {
+ Console.WriteLine(" - Skipped: plugins directory do not exist");
+}
+Console.WriteLine($"Starting MMO server ...");
+Server server;
if (String.IsNullOrEmpty(Configuration.ServerConfiguration.ListenIP) || Configuration.ServerConfiguration.ListenIP == "*") {
server = new(
IPAddress.IPv6Any,
diff --git a/src/Management/AuthenticationInfo.cs b/src/Schema/AuthenticationInfo.cs
similarity index 77%
rename from src/Management/AuthenticationInfo.cs
rename to src/Schema/AuthenticationInfo.cs
index 266ebc1..d64860d 100644
--- a/src/Management/AuthenticationInfo.cs
+++ b/src/Schema/AuthenticationInfo.cs
@@ -8,7 +8,10 @@ public class AuthenticationInfo {
public bool Authenticated { get; set; }
[XmlElement]
- public string DisplayName { get; set; }
+ public int Id { get; set; }
+
+ [XmlElement]
+ public string VikingName { get; set; }
[XmlElement]
public Role Role { get; set; }
@@ -17,4 +20,4 @@ public class AuthenticationInfo {
[Serializable]
public enum Role {
User = 0, Moderator = 1, Admin = 2
-}
\ No newline at end of file
+}
diff --git a/src/Schema/AvatarData.cs b/src/Schema/AvatarData.cs
new file mode 100644
index 0000000..013f490
--- /dev/null
+++ b/src/Schema/AvatarData.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace sodoffmmo.Management;
+
+public class AvatarData {
+ public string? DisplayName { get; set; }
+}
\ 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);
}