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/CHRANIBotTNG.dll b/CHRANIBotTNG/CHRANIBotTNG.dll new file mode 100644 index 0000000..5eddca0 Binary files /dev/null and b/CHRANIBotTNG/CHRANIBotTNG.dll differ diff --git a/CHRANIBotTNG/ModInfo.xml b/CHRANIBotTNG/ModInfo.xml new file mode 100644 index 0000000..68b39b7 --- /dev/null +++ b/CHRANIBotTNG/ModInfo.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Harmony/BotCommandPatch.cs b/Harmony/BotCommandPatch.cs deleted file mode 100644 index 3fe543c..0000000 --- a/Harmony/BotCommandPatch.cs +++ /dev/null @@ -1,664 +0,0 @@ -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Xml.Linq; - -public class BotCommandMod : IModApi -{ - private static Harmony harmony; - - public void InitMod(Mod _modInstance) - { - Console.WriteLine("[BotCommandMod] Loading"); - - // Initialize managers - string modPath = _modInstance.Path; - MuteManager.Initialize(modPath); - AdminPermissionManager.Initialize(); - - // Apply Harmony patches - harmony = new Harmony("wwevo.botcommand"); - harmony.PatchAll(Assembly.GetExecutingAssembly()); - - Console.WriteLine("[BotCommandMod] Loaded successfully"); - Console.WriteLine($"[BotCommandMod] Muted players: {MuteManager.GetMutedCount()}"); - Console.WriteLine($"[BotCommandMod] Admin count: {AdminPermissionManager.GetAdminCount()}"); - } -} - -// ==================== MUTE MANAGER ==================== -public static class MuteManager -{ - private static string dataFilePath; - private static MuteData muteData = new MuteData(); - private static readonly object fileLock = new object(); - - public class MutedPlayer - { - public string SteamID { get; set; } - public string PlayerName { get; set; } - public long MutedUntilTimestamp { get; set; } // Unix timestamp, 0 = permanent - public string Reason { get; set; } - public string MutedBy { get; set; } - public long MutedAtTimestamp { get; set; } - } - - public class MuteData - { - public List MutedPlayers { get; set; } = new List(); - } - - public static void Initialize(string modPath) - { - string dataDir = Path.Combine(modPath, "Data"); - if (!Directory.Exists(dataDir)) - { - Directory.CreateDirectory(dataDir); - } - - dataFilePath = Path.Combine(dataDir, "muted_players.json"); - LoadData(); - Console.WriteLine($"[MuteManager] Initialized with file: {dataFilePath}"); - } - - private static void LoadData() - { - lock (fileLock) - { - try - { - if (File.Exists(dataFilePath)) - { - string json = File.ReadAllText(dataFilePath); - muteData = ParseJson(json); - - // Clean up expired mutes - long currentTime = GetUnixTimestamp(); - int removed = muteData.MutedPlayers.RemoveAll(p => - p.MutedUntilTimestamp > 0 && p.MutedUntilTimestamp < currentTime); - - if (removed > 0) - { - SaveData(); - Console.WriteLine($"[MuteManager] Removed {removed} expired mutes"); - } - - Console.WriteLine($"[MuteManager] Loaded {muteData.MutedPlayers.Count} muted players"); - } - else - { - Console.WriteLine("[MuteManager] No existing data file, starting fresh"); - } - } - catch (Exception e) - { - Console.WriteLine($"[MuteManager] Error loading data: {e.Message}"); - muteData = new MuteData(); - } - } - } - - private static void SaveData() - { - lock (fileLock) - { - try - { - string json = ToJson(muteData); - File.WriteAllText(dataFilePath, json); - } - catch (Exception e) - { - Console.WriteLine($"[MuteManager] Error saving data: {e.Message}"); - } - } - } - - public static void MutePlayer(string steamID, string playerName, long durationSeconds, string reason, string mutedBy) - { - lock (fileLock) - { - // Remove existing mute if present - muteData.MutedPlayers.RemoveAll(p => p.SteamID == steamID); - - long currentTime = GetUnixTimestamp(); - long mutedUntil = durationSeconds > 0 ? currentTime + durationSeconds : 0; // 0 = permanent - - muteData.MutedPlayers.Add(new MutedPlayer - { - SteamID = steamID, - PlayerName = playerName, - MutedUntilTimestamp = mutedUntil, - Reason = reason ?? "No reason specified", - MutedBy = mutedBy, - MutedAtTimestamp = currentTime - }); - - SaveData(); - Console.WriteLine($"[MuteManager] Muted player {playerName} ({steamID}) until {(mutedUntil > 0 ? DateTimeOffset.FromUnixTimeSeconds(mutedUntil).ToString() : "permanent")}"); - } - } - - public static void UnmutePlayer(string steamID) - { - lock (fileLock) - { - int removed = muteData.MutedPlayers.RemoveAll(p => p.SteamID == steamID); - if (removed > 0) - { - SaveData(); - Console.WriteLine($"[MuteManager] Unmuted player with SteamID {steamID}"); - } - } - } - - public static bool IsPlayerMuted(string steamID) - { - long currentTime = GetUnixTimestamp(); - var mute = muteData.MutedPlayers.FirstOrDefault(p => p.SteamID == steamID); - - if (mute == null) return false; - - // Check if temporary mute has expired - if (mute.MutedUntilTimestamp > 0 && mute.MutedUntilTimestamp < currentTime) - { - UnmutePlayer(steamID); - return false; - } - - return true; - } - - public static MutedPlayer GetMuteInfo(string steamID) - { - return muteData.MutedPlayers.FirstOrDefault(p => p.SteamID == steamID); - } - - public static List GetAllMutedPlayers() - { - return muteData.MutedPlayers.ToList(); - } - - public static int GetMutedCount() - { - return muteData.MutedPlayers.Count; - } - - private static long GetUnixTimestamp() - { - return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - } - - // Simple JSON serialization (manual to avoid dependencies) - private static string ToJson(MuteData data) - { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("{"); - sb.AppendLine(" \"MutedPlayers\": ["); - - for (int i = 0; i < data.MutedPlayers.Count; i++) - { - var player = data.MutedPlayers[i]; - sb.AppendLine(" {"); - sb.AppendLine($" \"SteamID\": \"{EscapeJson(player.SteamID)}\","); - sb.AppendLine($" \"PlayerName\": \"{EscapeJson(player.PlayerName)}\","); - sb.AppendLine($" \"MutedUntilTimestamp\": {player.MutedUntilTimestamp},"); - sb.AppendLine($" \"Reason\": \"{EscapeJson(player.Reason)}\","); - sb.AppendLine($" \"MutedBy\": \"{EscapeJson(player.MutedBy)}\","); - sb.AppendLine($" \"MutedAtTimestamp\": {player.MutedAtTimestamp}"); - sb.Append(" }"); - if (i < data.MutedPlayers.Count - 1) sb.Append(","); - sb.AppendLine(); - } - - sb.AppendLine(" ]"); - sb.AppendLine("}"); - return sb.ToString(); - } - - private static MuteData ParseJson(string json) - { - MuteData data = new MuteData(); - - try - { - // Simple manual JSON parsing - string[] lines = json.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - MutedPlayer currentPlayer = null; - - foreach (string line in lines) - { - string trimmed = line.Trim(); - - if (trimmed.StartsWith("{") && currentPlayer == null && !trimmed.Contains("MutedPlayers")) - { - currentPlayer = new MutedPlayer(); - } - else if (trimmed.StartsWith("}") && currentPlayer != null) - { - data.MutedPlayers.Add(currentPlayer); - currentPlayer = null; - } - else if (currentPlayer != null) - { - if (trimmed.Contains("\"SteamID\"")) - currentPlayer.SteamID = ExtractStringValue(trimmed); - else if (trimmed.Contains("\"PlayerName\"")) - currentPlayer.PlayerName = ExtractStringValue(trimmed); - else if (trimmed.Contains("\"MutedUntilTimestamp\"")) - currentPlayer.MutedUntilTimestamp = ExtractLongValue(trimmed); - else if (trimmed.Contains("\"Reason\"")) - currentPlayer.Reason = ExtractStringValue(trimmed); - else if (trimmed.Contains("\"MutedBy\"")) - currentPlayer.MutedBy = ExtractStringValue(trimmed); - else if (trimmed.Contains("\"MutedAtTimestamp\"")) - currentPlayer.MutedAtTimestamp = ExtractLongValue(trimmed); - } - } - } - catch (Exception e) - { - Console.WriteLine($"[MuteManager] Error parsing JSON: {e.Message}"); - } - - return data; - } - - private static string ExtractStringValue(string line) - { - int firstQuote = line.IndexOf('"', line.IndexOf(':')); - int lastQuote = line.LastIndexOf('"'); - if (firstQuote >= 0 && lastQuote > firstQuote) - { - return line.Substring(firstQuote + 1, lastQuote - firstQuote - 1); - } - return ""; - } - - private static long ExtractLongValue(string line) - { - int colonIndex = line.IndexOf(':'); - if (colonIndex >= 0) - { - string valueStr = line.Substring(colonIndex + 1).Trim().TrimEnd(','); - if (long.TryParse(valueStr, out long value)) - { - return value; - } - } - return 0; - } - - private static string EscapeJson(string str) - { - if (string.IsNullOrEmpty(str)) return ""; - return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r"); - } -} - -// ==================== ADMIN PERMISSION MANAGER ==================== -public static class AdminPermissionManager -{ - private static List admins = new List(); - private static string serverAdminPath; - - public class AdminUser - { - public string Platform { get; set; } - public string SteamID { get; set; } - public string Name { get; set; } - public int PermissionLevel { get; set; } - } - - public static void Initialize() - { - // Try to find serveradmin.xml - serverAdminPath = FindServerAdminXml(); - - if (serverAdminPath != null) - { - LoadAdmins(); - } - else - { - Console.WriteLine("[AdminPermissionManager] serveradmin.xml not found, admin checking disabled"); - } - } - - private static string FindServerAdminXml() - { - // Common paths for serveradmin.xml - 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($"[AdminPermissionManager] Found serveradmin.xml at: {fullPath}"); - return fullPath; - } - } - catch (Exception e) - { - Console.WriteLine($"[AdminPermissionManager] Error checking path {path}: {e.Message}"); - } - } - - return null; - } - - private static void LoadAdmins() - { - try - { - if (!File.Exists(serverAdminPath)) - { - Console.WriteLine("[AdminPermissionManager] serveradmin.xml does not exist"); - return; - } - - XDocument doc = XDocument.Load(serverAdminPath); - - admins = doc.Descendants("user") - .Where(u => - { - var permStr = u.Attribute("permission_level")?.Value; - return int.TryParse(permStr, out int perm) && perm < 1000; - }) - .Select(u => new AdminUser - { - Platform = u.Attribute("platform")?.Value ?? "Steam", - SteamID = u.Attribute("userid")?.Value, - Name = u.Attribute("name")?.Value, - PermissionLevel = int.TryParse(u.Attribute("permission_level")?.Value, out int perm) ? perm : 1000 - }) - .ToList(); - - Console.WriteLine($"[AdminPermissionManager] Loaded {admins.Count} admins with permission level < 1000:"); - foreach (var admin in admins) - { - Console.WriteLine($" - {admin.Name} ({admin.SteamID}) [Level {admin.PermissionLevel}]"); - } - } - catch (Exception e) - { - Console.WriteLine($"[AdminPermissionManager] Error loading serveradmin.xml: {e.Message}"); - } - } - - public static bool IsAdmin(string steamID) - { - return admins.Any(a => a.SteamID == steamID); - } - - public static AdminUser GetAdmin(string steamID) - { - return admins.FirstOrDefault(a => a.SteamID == steamID); - } - - public static int GetPermissionLevel(string steamID) - { - var admin = GetAdmin(steamID); - return admin?.PermissionLevel ?? 1000; // Default permission level for non-admins - } - - public static List GetAllAdmins() - { - return admins.ToList(); - } - - public static int GetAdminCount() - { - return admins.Count; - } - - public static void Reload() - { - Console.WriteLine("[AdminPermissionManager] Reloading serveradmin.xml"); - LoadAdmins(); - } -} - -// ==================== HARMONY PATCH FOR CHAT COMMANDS ==================== -[HarmonyPatch(typeof(ChatCommandManager))] -[HarmonyPatch("ProcessCommand")] -public class ChatCommandPatch -{ - static bool Prefix(string _command, ClientInfo _cInfo) - { - try - { - if (_cInfo == null) return true; - - string steamID = _cInfo.InternalId.ReadablePlatformUserIdentifier; - string playerName = _cInfo.playerName; - - // Check if player is muted - if (MuteManager.IsPlayerMuted(steamID)) - { - var muteInfo = MuteManager.GetMuteInfo(steamID); - if (muteInfo != null) - { - string message; - if (muteInfo.MutedUntilTimestamp == 0) - { - message = $"You are permanently muted. Reason: {muteInfo.Reason}"; - } - else - { - long remainingSeconds = muteInfo.MutedUntilTimestamp - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - message = $"You are muted for {remainingSeconds / 60} more minutes. Reason: {muteInfo.Reason}"; - } - - _cInfo.SendPackage(NetPackageManager.GetPackage().Setup( - EChatType.Whisper, -1, message, "", false, null)); - } - - return false; // Block the command - } - - // Handle bot commands - if (_command.StartsWith("/bot")) - { - HandleBotCommand(_command, _cInfo, steamID, playerName); - return false; // Prevent default processing - } - } - catch (Exception e) - { - Console.WriteLine($"[ChatCommandPatch] Error in Prefix: {e}"); - } - - return true; // Allow default processing - } - - private static void HandleBotCommand(string command, ClientInfo cInfo, string steamID, string playerName) - { - string[] parts = command.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) - { - SendMessage(cInfo, "Bot commands: /bot mute [duration] [reason] | /bot unmute | /bot mutelist | /bot admins | /bot reload"); - return; - } - - string subCommand = parts[1].ToLower(); - - switch (subCommand) - { - case "mute": - if (!AdminPermissionManager.IsAdmin(steamID)) - { - SendMessage(cInfo, "You don't have permission to use this command."); - return; - } - - if (parts.Length < 3) - { - SendMessage(cInfo, "Usage: /bot mute [durationMinutes] [reason]"); - return; - } - - string targetPlayer = parts[2]; - long durationSeconds = 3600; // Default 1 hour - string reason = "No reason specified"; - - if (parts.Length >= 4 && int.TryParse(parts[3], out int minutes)) - { - durationSeconds = minutes * 60; - } - - if (parts.Length >= 5) - { - reason = string.Join(" ", parts.Skip(4)); - } - - // Find target player - ClientInfo targetCInfo = FindPlayer(targetPlayer); - if (targetCInfo != null) - { - string targetSteamID = targetCInfo.InternalId.ReadablePlatformUserIdentifier; - MuteManager.MutePlayer(targetSteamID, targetCInfo.playerName, durationSeconds, reason, playerName); - SendMessage(cInfo, $"Muted player {targetCInfo.playerName} for {durationSeconds / 60} minutes. Reason: {reason}"); - } - else - { - SendMessage(cInfo, $"Player '{targetPlayer}' not found."); - } - break; - - case "unmute": - if (!AdminPermissionManager.IsAdmin(steamID)) - { - SendMessage(cInfo, "You don't have permission to use this command."); - return; - } - - if (parts.Length < 3) - { - SendMessage(cInfo, "Usage: /bot unmute "); - return; - } - - string unmuteTarget = parts[2]; - ClientInfo unmuteCInfo = FindPlayer(unmuteTarget); - - if (unmuteCInfo != null) - { - string targetSteamID = unmuteCInfo.InternalId.ReadablePlatformUserIdentifier; - MuteManager.UnmutePlayer(targetSteamID); - SendMessage(cInfo, $"Unmuted player {unmuteCInfo.playerName}"); - } - else - { - SendMessage(cInfo, $"Player '{unmuteTarget}' not found."); - } - break; - - case "mutelist": - if (!AdminPermissionManager.IsAdmin(steamID)) - { - SendMessage(cInfo, "You don't have permission to use this command."); - return; - } - - var mutedPlayers = MuteManager.GetAllMutedPlayers(); - if (mutedPlayers.Count == 0) - { - SendMessage(cInfo, "No players are currently muted."); - } - else - { - SendMessage(cInfo, $"Muted players ({mutedPlayers.Count}):"); - foreach (var muted in mutedPlayers) - { - string until = muted.MutedUntilTimestamp == 0 ? "permanent" : - DateTimeOffset.FromUnixTimeSeconds(muted.MutedUntilTimestamp).ToString("yyyy-MM-dd HH:mm"); - SendMessage(cInfo, $" {muted.PlayerName} until {until} - {muted.Reason}"); - } - } - break; - - case "admins": - var admins = AdminPermissionManager.GetAllAdmins(); - if (admins.Count == 0) - { - SendMessage(cInfo, "No admins loaded (serveradmin.xml not found or empty)."); - } - else - { - SendMessage(cInfo, $"Admins ({admins.Count}):"); - foreach (var admin in admins) - { - SendMessage(cInfo, $" {admin.Name} [Level {admin.PermissionLevel}]"); - } - } - break; - - case "reload": - if (!AdminPermissionManager.IsAdmin(steamID)) - { - SendMessage(cInfo, "You don't have permission to use this command."); - return; - } - - AdminPermissionManager.Reload(); - SendMessage(cInfo, "Reloaded admin permissions from serveradmin.xml"); - break; - - default: - SendMessage(cInfo, $"Unknown bot command: {subCommand}"); - break; - } - } - - private static ClientInfo FindPlayer(string nameOrId) - { - // Try exact name match first - foreach (var client in ConnectionManager.Instance.Clients.List) - { - if (client.playerName.Equals(nameOrId, StringComparison.OrdinalIgnoreCase)) - { - return client; - } - } - - // Try partial name match - foreach (var client in ConnectionManager.Instance.Clients.List) - { - if (client.playerName.IndexOf(nameOrId, StringComparison.OrdinalIgnoreCase) >= 0) - { - return client; - } - } - - // Try Steam ID match - foreach (var client in ConnectionManager.Instance.Clients.List) - { - if (client.InternalId.ReadablePlatformUserIdentifier == nameOrId) - { - return client; - } - } - - return null; - } - - private static void SendMessage(ClientInfo cInfo, string message) - { - cInfo.SendPackage(NetPackageManager.GetPackage().Setup( - EChatType.Whisper, -1, message, "", false, null)); - } -} diff --git a/Harmony/CHRANIBotTNG.cs b/Harmony/CHRANIBotTNG.cs new file mode 100644 index 0000000..d74793b --- /dev/null +++ b/Harmony/CHRANIBotTNG.cs @@ -0,0 +1,374 @@ +using HarmonyLib; +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 - {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(); + } + } +} + +[HarmonyPatch(typeof(GameManager), "ChatMessageServer")] +public class ChatMessagePatch +{ + static void Prefix(ClientInfo _cInfo, string _msg, ref List _recipientEntityIds) + { + // Handle /bot commands + if (_msg != null && _msg.StartsWith("/bot ")) + { + string[] parts = _msg.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length >= 3 && parts[1].ToLower() == "mute") + { + // Check if user is admin + if (!AdminManager.IsAdmin(_cInfo)) + { + Console.WriteLine($"[CHRANIBotTNG] Non-admin {_cInfo?.playerName} tried to mute - denied"); + } + else + { + 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"); + } + } + + // 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(); + } + else + { + _recipientEntityIds.Clear(); + } + } + } + + 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 diff --git a/ModInfo.xml b/ModInfo.xml deleted file mode 100644 index 6b26943..0000000 --- a/ModInfo.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/build.sh b/build.sh index 3ff7db0..49c3519 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/CHRANIBotTNG.dll \ -nostdlib \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/mscorlib.dll" \ -r:"$GAME_DIR/7DaysToDie_Data/Managed/netstandard.dll" \ @@ -15,5 +15,5 @@ csc -target:library \ -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:"$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