using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using RainOpsMini.Helpers; using RainOpsMini.Helpers.RCON; using RainOpsMini.Models; namespace RainOpsMini.Plugins { public class CallOpConfig { /// /// 是否启用插件 /// public bool Enabled { get; set; } = false; /// /// 触发关键字列表 /// public List TriggerKeywords { get; set; } = new List { "op", "admin", "管理员" }; /// /// OP权限组关键字 (包含此权限的组被视为OP) /// public string OpPermissionKeyword { get; set; } = "cameraman"; /// /// OP白名单 (这些SteamID即使有权限也不会显示为在线OP) /// public List Whitelist { get; set; } = new List(); /// /// 广播消息模板 ({OpList} 会被替换为在线OP名字列表) /// public string BroadcastTemplate { get; set; } = "在线OP名单: {OpList}"; /// /// 通知OP的消息模板 ({Name}=玩家名, {Team}=阵营, {Squad}=小队, {Content}=内容) /// public string OpWarningTemplate { get; set; } = "玩家 {Name} [{Team} - {Squad}] 正在呼叫OP! 内容: {Content}"; /// /// 冷却时间 (秒) /// public int CooldownSeconds { get; set; } = 60; } public class CallOpPlugin : IPlugin, IDisposable { public string Name => "呼叫在线OP"; public string Description => "玩家输入特定关键字时通知在线OP,并向全服广播在线OP名单"; public PluginCategory Category => PluginCategory.BasicFunction; public bool IsEnabled => _config.Enabled; private CallOpConfig _config = new CallOpConfig(); private DateTime _lastCallTime = DateTime.MinValue; private readonly object _lock = new object(); public CallOpPlugin() { ApplyConfig(); } public object GetConfig() => _config; public void UpdateConfig(JsonElement config) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; try { _config = JsonSerializer.Deserialize(config.GetRawText(), options) ?? new CallOpConfig(); ApplyConfig(); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] 配置更新失败: {ex.Message}"); } } public void ResetConfig() { _config = new CallOpConfig(); ApplyConfig(); } private void ApplyConfig() { Program.OnRconMessageReceived -= OnRconMessage; if (_config.Enabled) { Program.OnRconMessageReceived += OnRconMessage; } } public void Dispose() { Program.OnRconMessageReceived -= OnRconMessage; } private async void OnRconMessage(string msg) { if (!_config.Enabled) return; try { // 解析聊天消息 var (steamId, name, content) = ParseChat(msg); if (string.IsNullOrEmpty(steamId)) return; // 检查是否包含触发关键字 if (!IsTriggerWord(content)) return; // 检查冷却时间 lock (_lock) { if ((DateTime.Now - _lastCallTime).TotalSeconds < _config.CooldownSeconds) { return; } _lastCallTime = DateTime.Now; } await HandleCallOp(steamId, name, content); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] 处理消息失败: {ex.Message}"); } } private bool IsTriggerWord(string content) { if (_config.TriggerKeywords == null || _config.TriggerKeywords.Count == 0) return false; foreach (var keyword in _config.TriggerKeywords) { if (content.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private async Task HandleCallOp(string callerSteamId, string callerName, string content) { // 1. 获取在线OP列表 var onlineOps = GetOnlineOps(); // 2. 广播在线OP名单 string opListStr = onlineOps.Count > 0 ? string.Join(", ", onlineOps.Select(p => p.Name)) : "当前无在线OP"; string broadcastMsg = _config.BroadcastTemplate.Replace("{OpList}", opListStr); await Program.Client.SendCommandAsync($"AdminBroadcast {broadcastMsg}"); // 3. 通知所有在线OP if (onlineOps.Count > 0) { // 获取呼叫者详细信息 // 使用GetData()获取线程安全的数据副本 var allPlayers = Program.RconCache.GetData().Players; var caller = allPlayers.FirstOrDefault(p => p.SteamId == callerSteamId); string teamName = caller != null ? GetTeamName(caller.TeamId) : "未知阵营"; string squadName = caller != null ? (caller.SquadId.HasValue ? $"小队{caller.SquadId}" : "无小队") : "未知小队"; string warnMsg = _config.OpWarningTemplate .Replace("{Name}", callerName) .Replace("{Team}", teamName) .Replace("{Squad}", squadName) .Replace("{Content}", content); foreach (var op in onlineOps) { await Program.Client.SendCommandAsync($"AdminWarn {op.SteamId} {warnMsg}"); } } } private List GetOnlineOps() { var ops = new List(); // 使用GetData()获取线程安全的数据副本 var allPlayers = Program.RconCache.GetData().Players; // 解析Admin.cfg获取OP SteamID var opSteamIds = ParseAdminConfigForOps(); foreach (var player in allPlayers) { if (opSteamIds.Contains(player.SteamId)) { // 检查白名单 if (_config.Whitelist != null && _config.Whitelist.Contains(player.SteamId)) { continue; } ops.Add(player); } } return ops; } private HashSet ParseAdminConfigForOps() { var opSteamIds = new HashSet(); var opGroups = new HashSet(); string adminConfigPath = Path.Combine(Environment.CurrentDirectory, "SquadGame", "ServerConfig", "Admins.cfg"); if (!File.Exists(adminConfigPath)) { RainOpsLog.Log($"[{Name}] Admins.cfg not found at {adminConfigPath}"); return opSteamIds; } try { var lines = File.ReadAllLines(adminConfigPath); // 1. 找出有cameraman权限的组 foreach (var line in lines) { var trimLine = line.Trim(); if (trimLine.StartsWith("Group=", StringComparison.OrdinalIgnoreCase)) { // Group=GroupName:perm1,perm2... var parts = trimLine.Substring(6).Split(':'); if (parts.Length >= 2) { string groupName = parts[0].Trim(); string perms = parts[1].ToLower(); // 检查权限 if (perms.Contains(_config.OpPermissionKeyword.ToLower())) { opGroups.Add(groupName); } } } } // 2. 找出属于这些组的Admin foreach (var line in lines) { var trimLine = line.Trim(); if (trimLine.StartsWith("Admin=", StringComparison.OrdinalIgnoreCase)) { // Admin=SteamID:GroupName//Comment // 或者 Admin=SteamID:GroupName var content = trimLine.Substring(6); // 分离注释 string[] commentParts = content.Split(new[] { "//" }, StringSplitOptions.None); string configPart = commentParts[0].Trim(); var parts = configPart.Split(':'); if (parts.Length >= 2) { string steamId = parts[0].Trim(); string groupName = parts[1].Trim(); if (opGroups.Contains(groupName)) { opSteamIds.Add(steamId); } } } } } catch (Exception ex) { RainOpsLog.Log($"[{Name}] 解析Admins.cfg失败: {ex.Message}"); } return opSteamIds; } private string GetTeamName(int teamId) { return teamId == 1 ? "阵营1" : (teamId == 2 ? "阵营2" : "未知"); } private (string steamId, string name, string content) ParseChat(string msg) { // 格式 1: ChatMessage: [SteamID] Name : Content var match = Regex.Match(msg, @"ChatMessage: \[(?\d+)\] (?.+) : (?.+)"); if (!match.Success) { // 格式 2: [Chat...] [Online IDs:EOS: ... steam: SteamID] Name : Content match = Regex.Match(msg, @"\[Chat.+?\] \[Online IDs:EOS: .+? steam: (?\d+)\]\s+(?.+?) : (?.+)"); } if (match.Success) { return (match.Groups["steamId"].Value, match.Groups["name"].Value, match.Groups["content"].Value); } return (null, null, null); } } }