From aa713a093a768a1e13aab840341371976d4ebb61 Mon Sep 17 00:00:00 2001 From: ecv Date: Sun, 23 Nov 2025 20:23:53 +0100 Subject: [PATCH 01/11] update damit claude mir nicht immer meinen mod-namen ruiniert. --- .gitignore | 30 ++++++++++++++++++++++++++++++ CHRANIBotTNG.dll | Bin 0 -> 4096 bytes Harmony/BotCommandPatch.cs | 31 ------------------------------- Harmony/CHRANIBotTNG.cs | 29 +++++++++++++++++++++++++++++ ModInfo.xml | 16 ++++++++-------- build.sh | 8 ++++---- 6 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 .gitignore create mode 100644 CHRANIBotTNG.dll delete mode 100644 Harmony/BotCommandPatch.cs create mode 100644 Harmony/CHRANIBotTNG.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a731290 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/aws.xml +.idea/**/contentModel.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/gradle.xml +.idea/**/libraries +cmake-build-*/ +.idea/**/mongoSettings.xml +*.iws +out/ +.idea_modules/ +atlassian-ide-plugin.xml +.idea/replstate.xml +.idea/sonarlint/ +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.idea/httpRequests +.idea/caches/build_file_checksums.ser diff --git a/CHRANIBotTNG.dll b/CHRANIBotTNG.dll new file mode 100644 index 0000000000000000000000000000000000000000..204a18722eae303fcda6079a5e08751b47a7dd06 GIT binary patch literal 4096 zcmeHKO>7)h68^e989U>6h!brL0zxO|$HdMsk&Oa~z>LSnG5Aj&)6;$#H}rH5 z-95nu2?Zqn#Dz#J?cUIG;0D@5Rv>;PZd^cudysNktq>AikPs4tuikXqM_xr2fDE6@mw|UU3Y0Yd@8}%W`5WHNMFrg-Kw>=)yNA2itq zkBBoodnT@7l}<6+e&Q^-Q;hy$;@x-xOSleY(g(1C_l%oKe~)3@sx68b$RgQT!Ypqy3G@7nw$8O*+ zD$icmRJ5gEZ27UUsK@_`Al!}tv3hf%Gu&TnduA`!)P;6dCN|sf2>=^U4 z#~20{LUqPn$CQdE*Ok+XX_f|oVzP63vkyOt3Qy=FPu(0(**F5kJdtyl!XIH_hP;hc zo;4qJ;8urxgXhlS2@k39sj2BXv9>5s8_*uZKhLic+uqv}9~RJ8g`(%P8l&&gBqE6g zJMI#U7nk39>F@oc<=50-@9G=uE+bg4h@W0zjmM%V8|#@utH|8^=2{q_R(%iXbL?mH3M z&AGszTz6Dcc6@0q44#Y(+I8!uSv64Y*y0f@Eq${8Bi6DCBThV4ACW~CB@6P`C~ zyMDWGmD0yqw3lz95B)W9rI+7=L~efIYT(@VY}#+l;=hrLoqRhyR02-&g_pufbByFN zs#qbOWdBszJ@f3F6^?Vbul?194>Di>{*vuU+aOtW!~pO)OnUc9Pk+%iMWnwmb|Gtk5k_Qe zt~|ljBlf%aX>MD`qHU3_v1PlgyEf{GrO3Nt>yZz%e%ebmv9|RYyT^Ljyf+m6{inOA zz _recipientEntityIds) - { - if (_msg != null && _msg.StartsWith("/bot ")) - { - string playerName = _cInfo != null ? _cInfo.playerName : "Server"; - Console.WriteLine($"[Bot] {playerName}: {_msg}"); - return false; - } - return true; - } -} diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs new file mode 100644 index 0000000..76b71cb --- /dev/null +++ b/Harmony/CHRANIBotTNG.cs @@ -0,0 +1,29 @@ +using HarmonyLib; +using System.Reflection; +using System; + +public class CHRANIBotTNG : IModApi +{ + public void InitMod(Mod _modInstance) + { + Console.WriteLine("[CHRANIBotTNG] Loading"); + var harmony = new Harmony("com.chranibottng.mod"); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + Console.WriteLine("[CHRANIBotTNG] Loaded"); + } +} + +[HarmonyPatch(typeof(ChatCommandManager), "ProcessCommand")] +public class ChatCommandPatch +{ + static bool Prefix(ChatCommandManager __instance, string _chatText, ClientInfo _cInfo) + { + if (_chatText != null && _chatText.StartsWith("/bot ")) + { + string playerName = _cInfo != null ? _cInfo.playerName : "Server"; + Console.WriteLine($"[Bot] {playerName}: {_chatText}"); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/ModInfo.xml b/ModInfo.xml index c070ccf..68b39b7 100644 --- a/ModInfo.xml +++ b/ModInfo.xml @@ -1,9 +1,9 @@ - + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/build.sh b/build.sh index 9cf126c..88cd9be 100755 --- a/build.sh +++ b/build.sh @@ -1,11 +1,11 @@ #!/bin/bash # Set your 7DTD installation path -GAME_DIR="${GAME_DIR:-$HOME/.local/share/7DaysToDie}" +GAME_DIR="${GAME_DIR:-$HOME/Software/SteamLibrary/steamapps/common/7 Days To Die}" # Use csc (Roslyn compiler) instead of mcs csc -target:library \ - -out:BotCommandMod.dll \ + -out:CHRANIBotTNG.dll \ -nostdlib \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/mscorlib.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/netstandard.dll" \ @@ -13,5 +13,5 @@ csc -target:library \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/System.Core.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/Assembly-CSharp.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/UnityEngine.CoreModule.dll" \ - -r:"$GAME_DIR/7DaysToDie_Data/Managed/0Harmony.dll" \ - Harmony/BotCommandPatch.cs + -r:"/home/ecv/Software/SteamLibrary/steamapps/common/7 Days To Die/Mods/0_TFP_Harmony/0Harmony.dll" \ + Harmony/CHRANIBotTNG.cs From f12fc20b3e2f5c64a303f6776458362afab00d14 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:27:09 +0000 Subject: [PATCH 02/11] Add introspection to discover Chat methods and fix ModInfo V2 format --- Harmony/CHRANIBotTNG.cs | 31 ++++++++++++++----------------- ModInfo.xml | 20 +++++++++++--------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index 76b71cb..d61cedc 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -1,29 +1,26 @@ using HarmonyLib; using System.Reflection; using System; +using System.Linq; public class CHRANIBotTNG : IModApi { public void InitMod(Mod _modInstance) { Console.WriteLine("[CHRANIBotTNG] Loading"); - var harmony = new Harmony("com.chranibottng.mod"); - harmony.PatchAll(Assembly.GetExecutingAssembly()); + + // List all GameManager methods containing "Chat" + var gmType = typeof(GameManager); + Console.WriteLine("[CHRANIBotTNG] GameManager methods with 'Chat':"); + foreach (var method in gmType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + { + if (method.Name.Contains("Chat")) + { + var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name)); + Console.WriteLine($" {method.Name}({parameters})"); + } + } + Console.WriteLine("[CHRANIBotTNG] Loaded"); } -} - -[HarmonyPatch(typeof(ChatCommandManager), "ProcessCommand")] -public class ChatCommandPatch -{ - static bool Prefix(ChatCommandManager __instance, string _chatText, ClientInfo _cInfo) - { - if (_chatText != null && _chatText.StartsWith("/bot ")) - { - string playerName = _cInfo != null ? _cInfo.playerName : "Server"; - Console.WriteLine($"[Bot] {playerName}: {_chatText}"); - return false; - } - return true; - } } \ No newline at end of file diff --git a/ModInfo.xml b/ModInfo.xml index 68b39b7..37f7e9f 100644 --- a/ModInfo.xml +++ b/ModInfo.xml @@ -1,9 +1,11 @@ - - - - - - - - - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file From c92100329ff9b069187ca4bcb59d1d7ee1564aac Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:36:47 +0000 Subject: [PATCH 03/11] Revert ModInfo.xml to original format --- ModInfo.xml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ModInfo.xml b/ModInfo.xml index 37f7e9f..68b39b7 100644 --- a/ModInfo.xml +++ b/ModInfo.xml @@ -1,11 +1,9 @@ - - - - - - - - - - - \ No newline at end of file + + + + + + + + + \ No newline at end of file From 4a7fc2c3ecd6d507544f678826e4caf0e7efe8e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:39:06 +0000 Subject: [PATCH 04/11] Implement ChatMessageServer patch with correct signature Intercepts /bot messages and logs to console only --- Harmony/CHRANIBotTNG.cs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index d61cedc..693df61 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -1,26 +1,30 @@ using HarmonyLib; using System.Reflection; using System; -using System.Linq; +using System.Collections.Generic; public class CHRANIBotTNG : IModApi { public void InitMod(Mod _modInstance) { Console.WriteLine("[CHRANIBotTNG] Loading"); - - // List all GameManager methods containing "Chat" - var gmType = typeof(GameManager); - Console.WriteLine("[CHRANIBotTNG] GameManager methods with 'Chat':"); - foreach (var method in gmType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) - { - if (method.Name.Contains("Chat")) - { - var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name)); - Console.WriteLine($" {method.Name}({parameters})"); - } - } - + var harmony = new Harmony("com.chranibottng.mod"); + harmony.PatchAll(Assembly.GetExecutingAssembly()); Console.WriteLine("[CHRANIBotTNG] Loaded"); } +} + +[HarmonyPatch(typeof(GameManager), "ChatMessageServer")] +public class ChatMessagePatch +{ + static bool Prefix(ClientInfo _cInfo, EChatType _chatType, int _senderEntityId, string _msg, List _recipientEntityIds, EMessageSender _msgSender, BbCodeSupportMode _bbMode) + { + if (_msg != null && _msg.StartsWith("/bot ")) + { + string playerName = _cInfo != null ? _cInfo.playerName : "Server"; + Console.WriteLine($"[Bot] {playerName}: {_msg}"); + return false; + } + return true; + } } \ No newline at end of file From 464176481918500cb5d738ec3de4fe83ed6e39a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:45:10 +0000 Subject: [PATCH 05/11] Simplify patch signature - only use needed parameters --- Harmony/CHRANIBotTNG.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index 693df61..4e643ae 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -17,7 +17,7 @@ public class CHRANIBotTNG : IModApi [HarmonyPatch(typeof(GameManager), "ChatMessageServer")] public class ChatMessagePatch { - static bool Prefix(ClientInfo _cInfo, EChatType _chatType, int _senderEntityId, string _msg, List _recipientEntityIds, EMessageSender _msgSender, BbCodeSupportMode _bbMode) + static bool Prefix(ClientInfo _cInfo, string _msg) { if (_msg != null && _msg.StartsWith("/bot ")) { From 1217cc30be314be546015205c719efdecb7c90a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:50:39 +0000 Subject: [PATCH 06/11] Use Log.Out to write chat message to telnet-visible server log --- Harmony/CHRANIBotTNG.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index 4e643ae..b38d38f 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -17,12 +17,16 @@ public class CHRANIBotTNG : IModApi [HarmonyPatch(typeof(GameManager), "ChatMessageServer")] public class ChatMessagePatch { - static bool Prefix(ClientInfo _cInfo, string _msg) + static bool Prefix(ClientInfo _cInfo, string _msg, List _recipientEntityIds) { if (_msg != null && _msg.StartsWith("/bot ")) { string playerName = _cInfo != null ? _cInfo.playerName : "Server"; - Console.WriteLine($"[Bot] {playerName}: {_msg}"); + + // Write to server log (visible in telnet) + Log.Out($"Chat (from '{playerName}', entity id '{(_cInfo != null ? _cInfo.entityId.ToString() : "-1")}', to '{(_recipientEntityIds != null && _recipientEntityIds.Count > 0 ? "players" : "all")}'): '{_msg}'"); + + // Block in-game chat display return false; } return true; From 2aca59b25ec7d1cda2b2735baa3e8ba25a619395 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:53:50 +0000 Subject: [PATCH 07/11] Use Console.WriteLine instead of non-existent Log.Out --- Harmony/CHRANIBotTNG.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index b38d38f..a4a25e4 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -24,7 +24,7 @@ public class ChatMessagePatch string playerName = _cInfo != null ? _cInfo.playerName : "Server"; // Write to server log (visible in telnet) - Log.Out($"Chat (from '{playerName}', entity id '{(_cInfo != null ? _cInfo.entityId.ToString() : "-1")}', to '{(_recipientEntityIds != null && _recipientEntityIds.Count > 0 ? "players" : "all")}'): '{_msg}'"); + Console.WriteLine($"Chat (from '{playerName}', entity id '{(_cInfo != null ? _cInfo.entityId.ToString() : "-1")}', to '{(_recipientEntityIds != null && _recipientEntityIds.Count > 0 ? "players" : "all")}'): '{_msg}'"); // Block in-game chat display return false; From 96d535633a937523a1897551a6a0f0c90c38f4c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 19:54:53 +0000 Subject: [PATCH 08/11] Clear recipients instead of blocking - allow server logging --- Harmony/CHRANIBotTNG.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index a4a25e4..f630012 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -17,18 +17,19 @@ public class CHRANIBotTNG : IModApi [HarmonyPatch(typeof(GameManager), "ChatMessageServer")] public class ChatMessagePatch { - static bool Prefix(ClientInfo _cInfo, string _msg, List _recipientEntityIds) + static void Prefix(string _msg, ref List _recipientEntityIds) { if (_msg != null && _msg.StartsWith("/bot ")) { - string playerName = _cInfo != null ? _cInfo.playerName : "Server"; - - // Write to server log (visible in telnet) - Console.WriteLine($"Chat (from '{playerName}', entity id '{(_cInfo != null ? _cInfo.entityId.ToString() : "-1")}', to '{(_recipientEntityIds != null && _recipientEntityIds.Count > 0 ? "players" : "all")}'): '{_msg}'"); - - // Block in-game chat display - return false; + // Clear recipients so no players see it in-game, but server still logs it + if (_recipientEntityIds == null) + { + _recipientEntityIds = new List(); + } + else + { + _recipientEntityIds.Clear(); + } } - return true; } } \ No newline at end of file From c7b93dc0a84b8d0fe2768d18cfa4343c6fdffeb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 20:34:29 +0000 Subject: [PATCH 09/11] Add mute/unmute functionality for chat - /bot mute {id} - mutes player by EntityID, SteamID, or name - /bot unmute {id} - unmutes player - Muted players' messages blocked in-game but visible in telnet - All /bot commands only visible in telnet --- Harmony/CHRANIBotTNG.cs | 63 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index f630012..65e717b 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -2,9 +2,12 @@ using HarmonyLib; using System.Reflection; using System; using System.Collections.Generic; +using System.Linq; public class CHRANIBotTNG : IModApi { + public static HashSet MutedPlayers = new HashSet(); + public void InitMod(Mod _modInstance) { Console.WriteLine("[CHRANIBotTNG] Loading"); @@ -17,11 +20,50 @@ public class CHRANIBotTNG : IModApi [HarmonyPatch(typeof(GameManager), "ChatMessageServer")] public class ChatMessagePatch { - static void Prefix(string _msg, ref List _recipientEntityIds) + static void Prefix(ClientInfo _cInfo, string _msg, ref List _recipientEntityIds) { + // Handle /bot commands if (_msg != null && _msg.StartsWith("/bot ")) { - // Clear recipients so no players see it in-game, but server still logs it + string[] parts = _msg.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length >= 3 && parts[1].ToLower() == "mute") + { + string targetId = parts[2]; + CHRANIBotTNG.MutedPlayers.Add(targetId); + Console.WriteLine($"[CHRANIBotTNG] Muted player: {targetId}"); + } + else if (parts.Length >= 3 && parts[1].ToLower() == "unmute") + { + string targetId = parts[2]; + if (CHRANIBotTNG.MutedPlayers.Remove(targetId)) + { + Console.WriteLine($"[CHRANIBotTNG] Unmuted player: {targetId}"); + } + else + { + Console.WriteLine($"[CHRANIBotTNG] Player was not muted: {targetId}"); + } + } + + // Clear recipients so no players see /bot commands in-game + if (_recipientEntityIds == null) + { + _recipientEntityIds = new List(); + } + else + { + _recipientEntityIds.Clear(); + } + return; + } + + // Check if sender is muted + if (_cInfo != null && IsPlayerMuted(_cInfo)) + { + Console.WriteLine($"[CHRANIBotTNG] Blocked message from muted player: {_cInfo.playerName}"); + + // Clear recipients so no players see the message if (_recipientEntityIds == null) { _recipientEntityIds = new List(); @@ -32,4 +74,21 @@ public class ChatMessagePatch } } } + + static bool IsPlayerMuted(ClientInfo _cInfo) + { + // Check EntityID + if (CHRANIBotTNG.MutedPlayers.Contains(_cInfo.entityId.ToString())) + return true; + + // Check player name + if (CHRANIBotTNG.MutedPlayers.Contains(_cInfo.playerName)) + return true; + + // Check SteamID (InternalId) + if (_cInfo.InternalId != null && CHRANIBotTNG.MutedPlayers.Contains(_cInfo.InternalId.ReadablePlatformUserIdentifier)) + return true; + + return false; + } } \ No newline at end of file From 056ea86566dc9e2309e90969f1f10aaa45a7246a Mon Sep 17 00:00:00 2001 From: ecv Date: Sun, 23 Nov 2025 21:57:15 +0100 Subject: [PATCH 10/11] put the dll in a proper folder --- CHRANIBotTNG.dll | Bin 4096 -> 0 bytes CHRANIBotTNG/CHRANIBotTNG.dll | Bin 0 -> 5120 bytes ModInfo.xml => CHRANIBotTNG/ModInfo.xml | 0 build.sh | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 CHRANIBotTNG.dll create mode 100644 CHRANIBotTNG/CHRANIBotTNG.dll rename ModInfo.xml => CHRANIBotTNG/ModInfo.xml (100%) diff --git a/CHRANIBotTNG.dll b/CHRANIBotTNG.dll deleted file mode 100644 index 204a18722eae303fcda6079a5e08751b47a7dd06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeHKO>7)h68^e989U>6h!brL0zxO|$HdMsk&Oa~z>LSnG5Aj&)6;$#H}rH5 z-95nu2?Zqn#Dz#J?cUIG;0D@5Rv>;PZd^cudysNktq>AikPs4tuikXqM_xr2fDE6@mw|UU3Y0Yd@8}%W`5WHNMFrg-Kw>=)yNA2itq zkBBoodnT@7l}<6+e&Q^-Q;hy$;@x-xOSleY(g(1C_l%oKe~)3@sx68b$RgQT!Ypqy3G@7nw$8O*+ zD$icmRJ5gEZ27UUsK@_`Al!}tv3hf%Gu&TnduA`!)P;6dCN|sf2>=^U4 z#~20{LUqPn$CQdE*Ok+XX_f|oVzP63vkyOt3Qy=FPu(0(**F5kJdtyl!XIH_hP;hc zo;4qJ;8urxgXhlS2@k39sj2BXv9>5s8_*uZKhLic+uqv}9~RJ8g`(%P8l&&gBqE6g zJMI#U7nk39>F@oc<=50-@9G=uE+bg4h@W0zjmM%V8|#@utH|8^=2{q_R(%iXbL?mH3M z&AGszTz6Dcc6@0q44#Y(+I8!uSv64Y*y0f@Eq${8Bi6DCBThV4ACW~CB@6P`C~ zyMDWGmD0yqw3lz95B)W9rI+7=L~efIYT(@VY}#+l;=hrLoqRhyR02-&g_pufbByFN zs#qbOWdBszJ@f3F6^?Vbul?194>Di>{*vuU+aOtW!~pO)OnUc9Pk+%iMWnwmb|Gtk5k_Qe zt~|ljBlf%aX>MD`qHU3_v1PlgyEf{GrO3Nt>yZz%e%ebmv9|RYyT^Ljyf+m6{inOA zztWndAmdlCLQJ`YHVl19s>=WRy~#=uVTFFD zB{9U@n|6|2gHvG`Yy9031`u!2jgvpp!e;|s^%d zux`?7Q#><(*w#y3Ps=Gf+ zP0=ZJ@5v!o27`%^jz1{u~aP6iHOfE5}(3gf?_vK8W=pPXX3gH zDaz2(@l-tXFubSeNdJ6~40b)@&{apO<2_tqFCf>aA}c2AsRGx}Z;@_#iQGP-L^KoW z>l_>n2h|)(&Zc4LsHTXoA@#pxBqFL7Y6lLNf!gpa;m=6_y-feO7s3>Mf{rkMjqw<@neNi-rpua zA;(5+wh2BPGXZt7Ks>Ijx0JNAV8E z^(d|@{973MJ*ZMOiXZ6bwFFL3ElIl(uGVw((2ckBEJuF?=r{GRV8)QT zf7iZ@i`02eanEkOMxQ#qrMQb*Z)o2p_uY`g&-E9`{Y$!~%`#qQZCdSQwKRNP;paJ< zjtp@d4#-mDc}>SQbl@7Rl_HLkyT;f};?4B-Q{0OJ>7956uj%)bE@^|X zbDiZ0tAv_gc0HR!aoG<d1YtZuj09WME+!HrsG)p7m6D*IUQMA@p#(8ihzx~A5w zCpUwvyYPy_J7blF-&`CvPZotNS2i#$hjyx?7V9khbX{#8s0|VB4W(E%15B5mE5!vH#J>>&XJ z@W`D#Og8l@jIz$2VzR3U#`JOW=xQ*UHBXP;+0(HW-P?M5{Lc0>EwdXq%Se32;Hat? zYf)6f$i$s}jnJ3N>YipUGL{c4&I{LvgM7BK}eN{G8K!=RLDNuc1D zX|i)asiD|%*fYlJu5UJljB#9eK3m=49N%U5%NY5F9W*>~*b$AuGwqBq(~ZmVf8&d99hV_-l%O)@@$6>2q9FblcP5V z=tF(+-0J21B=Y@2y>F~+)C}L?a=qjxfz$ zu)zBi9t*#^@OtE**X4V}&G2Ybz+J>Qj*T!%R-;#w`}Z;S44b>Tz%#PQ-7Mig^#q@5 z_7q>8sDOGdbwc=AY1>&))pHT%)6p1z}Wa#;NxWM^RwkYPmH+RUTsRF##VI{mHB zV^BB8&9|<5oVp%Q;JQwZ{crt}A80A+%WP!d@_08H{}i!2y?D?5n&k<%`IE3)#Wz9y zs Date: Mon, 24 Nov 2025 07:04:40 +0000 Subject: [PATCH 11/11] Add local storage for muted players and serveradmin.xml integration **MuteStorage (JSON Persistence)**: - Added persistent storage in CHRANIBotTNG/Data/muted_players.json - Auto-loads muted players on mod init - Auto-saves on mute/unmute operations - Thread-safe file operations - Simple JSON serialization without external dependencies **AdminManager (serveradmin.xml Integration)**: - Reads serveradmin.xml to load admin SteamIDs (permission_level < 1000) - Auto-discovers serveradmin.xml location in common paths - Caches admin list in memory for fast permission checks - Supports /bot reload command to refresh admin list **Updated Commands (Admin-only)**: - /bot mute - Now requires admin permission and persists - /bot unmute - Now requires admin permission and persists - /bot mutelist - List all muted players (admin-only) - /bot reload - Reload serveradmin.xml (admin-only) **Build System**: - Added System.Xml.dll and System.Xml.Linq.dll references All features integrate seamlessly with existing codebase structure. --- Harmony/CHRANIBotTNG.cs | 302 ++++++++++++++++++++++++++++++++++++++-- build.sh | 2 + 2 files changed, 293 insertions(+), 11 deletions(-) diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs index 65e717b..d74793b 100644 --- a/Harmony/CHRANIBotTNG.cs +++ b/Harmony/CHRANIBotTNG.cs @@ -3,17 +3,250 @@ using System.Reflection; using System; using System.Collections.Generic; using System.Linq; +using System.IO; +using System.Text; +using System.Xml.Linq; public class CHRANIBotTNG : IModApi { public static HashSet MutedPlayers = new HashSet(); + private static string modPath; public void InitMod(Mod _modInstance) { Console.WriteLine("[CHRANIBotTNG] Loading"); + + modPath = _modInstance.Path; + + // Load persisted mute list + MuteStorage.Initialize(modPath); + MutedPlayers = MuteStorage.LoadMutedPlayers(); + + // Load admin list from serveradmin.xml + AdminManager.Initialize(); + var harmony = new Harmony("com.chranibottng.mod"); harmony.PatchAll(Assembly.GetExecutingAssembly()); - Console.WriteLine("[CHRANIBotTNG] Loaded"); + + Console.WriteLine($"[CHRANIBotTNG] Loaded - {MutedPlayers.Count} muted players, {AdminManager.GetAdminCount()} admins"); + } +} + +// ==================== MUTE STORAGE (JSON Persistence) ==================== +public static class MuteStorage +{ + private static string muteFilePath; + private static readonly object fileLock = new object(); + + public static void Initialize(string modPath) + { + string dataDir = Path.Combine(modPath, "Data"); + if (!Directory.Exists(dataDir)) + { + Directory.CreateDirectory(dataDir); + } + muteFilePath = Path.Combine(dataDir, "muted_players.json"); + Console.WriteLine($"[MuteStorage] Initialized: {muteFilePath}"); + } + + public static HashSet LoadMutedPlayers() + { + lock (fileLock) + { + try + { + if (File.Exists(muteFilePath)) + { + string json = File.ReadAllText(muteFilePath); + var players = ParseJsonArray(json); + Console.WriteLine($"[MuteStorage] Loaded {players.Count} muted players"); + return players; + } + } + catch (Exception e) + { + Console.WriteLine($"[MuteStorage] Error loading: {e.Message}"); + } + } + return new HashSet(); + } + + public static void SaveMutedPlayers(HashSet players) + { + lock (fileLock) + { + try + { + string json = ToJsonArray(players); + File.WriteAllText(muteFilePath, json); + Console.WriteLine($"[MuteStorage] Saved {players.Count} muted players"); + } + catch (Exception e) + { + Console.WriteLine($"[MuteStorage] Error saving: {e.Message}"); + } + } + } + + private static string ToJsonArray(HashSet players) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("{"); + sb.AppendLine(" \"MutedPlayers\": ["); + + var list = players.ToList(); + for (int i = 0; i < list.Count; i++) + { + sb.Append($" \"{EscapeJson(list[i])}\""); + if (i < list.Count - 1) sb.Append(","); + sb.AppendLine(); + } + + sb.AppendLine(" ]"); + sb.AppendLine("}"); + return sb.ToString(); + } + + private static HashSet ParseJsonArray(string json) + { + HashSet result = new HashSet(); + try + { + // Simple parser: extract strings between quotes in the array + bool inArray = false; + for (int i = 0; i < json.Length; i++) + { + if (json[i] == '[') inArray = true; + if (json[i] == ']') break; + + if (inArray && json[i] == '"') + { + int start = i + 1; + int end = json.IndexOf('"', start); + if (end > start) + { + string value = json.Substring(start, end - start); + if (!string.IsNullOrWhiteSpace(value)) + { + result.Add(value); + } + i = end; + } + } + } + } + catch (Exception e) + { + Console.WriteLine($"[MuteStorage] Parse error: {e.Message}"); + } + return result; + } + + private static string EscapeJson(string str) + { + if (string.IsNullOrEmpty(str)) return ""; + return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r"); + } +} + +// ==================== ADMIN MANAGER (serveradmin.xml) ==================== +public static class AdminManager +{ + private static HashSet adminSteamIDs = new HashSet(); + private static string serverAdminPath; + + public static void Initialize() + { + serverAdminPath = FindServerAdminXml(); + if (serverAdminPath != null) + { + LoadAdmins(); + } + else + { + Console.WriteLine("[AdminManager] serveradmin.xml not found - admin checks disabled"); + } + } + + private static string FindServerAdminXml() + { + try + { + // Try common locations + string[] possiblePaths = new[] + { + Path.Combine(GameIO.GetSaveGameDir(), "..", "serveradmin.xml"), + Path.Combine(GameIO.GetSaveGameDir(), "serveradmin.xml"), + "serveradmin.xml" + }; + + foreach (string path in possiblePaths) + { + try + { + string fullPath = Path.GetFullPath(path); + if (File.Exists(fullPath)) + { + Console.WriteLine($"[AdminManager] Found serveradmin.xml: {fullPath}"); + return fullPath; + } + } + catch { } + } + } + catch (Exception e) + { + Console.WriteLine($"[AdminManager] Error finding serveradmin.xml: {e.Message}"); + } + return null; + } + + private static void LoadAdmins() + { + try + { + XDocument doc = XDocument.Load(serverAdminPath); + + adminSteamIDs = doc.Descendants("user") + .Where(u => + { + var permStr = u.Attribute("permission_level")?.Value; + return int.TryParse(permStr, out int perm) && perm < 1000; + }) + .Select(u => u.Attribute("userid")?.Value) + .Where(id => !string.IsNullOrEmpty(id)) + .ToHashSet(); + + Console.WriteLine($"[AdminManager] Loaded {adminSteamIDs.Count} admins (permission < 1000)"); + foreach (var id in adminSteamIDs) + { + Console.WriteLine($" Admin: {id}"); + } + } + catch (Exception e) + { + Console.WriteLine($"[AdminManager] Error loading serveradmin.xml: {e.Message}"); + } + } + + public static bool IsAdmin(ClientInfo _cInfo) + { + if (_cInfo == null || _cInfo.InternalId == null) return false; + return adminSteamIDs.Contains(_cInfo.InternalId.ReadablePlatformUserIdentifier); + } + + public static int GetAdminCount() + { + return adminSteamIDs.Count; + } + + public static void Reload() + { + if (serverAdminPath != null) + { + Console.WriteLine("[AdminManager] Reloading serveradmin.xml"); + LoadAdmins(); + } } } @@ -29,20 +262,67 @@ public class ChatMessagePatch if (parts.Length >= 3 && parts[1].ToLower() == "mute") { - string targetId = parts[2]; - CHRANIBotTNG.MutedPlayers.Add(targetId); - Console.WriteLine($"[CHRANIBotTNG] Muted player: {targetId}"); - } - else if (parts.Length >= 3 && parts[1].ToLower() == "unmute") - { - string targetId = parts[2]; - if (CHRANIBotTNG.MutedPlayers.Remove(targetId)) + // Check if user is admin + if (!AdminManager.IsAdmin(_cInfo)) { - Console.WriteLine($"[CHRANIBotTNG] Unmuted player: {targetId}"); + Console.WriteLine($"[CHRANIBotTNG] Non-admin {_cInfo?.playerName} tried to mute - denied"); } else { - Console.WriteLine($"[CHRANIBotTNG] Player was not muted: {targetId}"); + string targetId = parts[2]; + CHRANIBotTNG.MutedPlayers.Add(targetId); + MuteStorage.SaveMutedPlayers(CHRANIBotTNG.MutedPlayers); + Console.WriteLine($"[CHRANIBotTNG] Admin {_cInfo?.playerName} muted: {targetId}"); + } + } + else if (parts.Length >= 3 && parts[1].ToLower() == "unmute") + { + // Check if user is admin + if (!AdminManager.IsAdmin(_cInfo)) + { + Console.WriteLine($"[CHRANIBotTNG] Non-admin {_cInfo?.playerName} tried to unmute - denied"); + } + else + { + string targetId = parts[2]; + if (CHRANIBotTNG.MutedPlayers.Remove(targetId)) + { + MuteStorage.SaveMutedPlayers(CHRANIBotTNG.MutedPlayers); + Console.WriteLine($"[CHRANIBotTNG] Admin {_cInfo?.playerName} unmuted: {targetId}"); + } + else + { + Console.WriteLine($"[CHRANIBotTNG] Player was not muted: {targetId}"); + } + } + } + else if (parts.Length >= 2 && parts[1].ToLower() == "mutelist") + { + // Show muted players list + if (!AdminManager.IsAdmin(_cInfo)) + { + Console.WriteLine($"[CHRANIBotTNG] Non-admin {_cInfo?.playerName} tried to view mutelist - denied"); + } + else + { + Console.WriteLine($"[CHRANIBotTNG] Muted players ({CHRANIBotTNG.MutedPlayers.Count}):"); + foreach (var muted in CHRANIBotTNG.MutedPlayers) + { + Console.WriteLine($" - {muted}"); + } + } + } + else if (parts.Length >= 2 && parts[1].ToLower() == "reload") + { + // Reload serveradmin.xml + if (!AdminManager.IsAdmin(_cInfo)) + { + Console.WriteLine($"[CHRANIBotTNG] Non-admin {_cInfo?.playerName} tried to reload - denied"); + } + else + { + AdminManager.Reload(); + Console.WriteLine($"[CHRANIBotTNG] Admin {_cInfo?.playerName} reloaded serveradmin.xml"); } } diff --git a/build.sh b/build.sh index cef7d2d..49c3519 100755 --- a/build.sh +++ b/build.sh @@ -11,6 +11,8 @@ csc -target:library \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/netstandard.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/System.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/System.Core.dll" \ + -r:"$GAME_DIR/7DaysToDie_Data/Managed/System.Xml.dll" \ + -r:"$GAME_DIR/7DaysToDie_Data/Managed/System.Xml.Linq.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/Assembly-CSharp.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/UnityEngine.CoreModule.dll" \ -r:"/home/ecv/Software/SteamLibrary/steamapps/common/7 Days To Die/Mods/0_TFP_Harmony/0Harmony.dll" \