using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using RainOpsMini.Helpers; using RainOpsMini.Services; namespace RainOpsMini.Plugins { /// /// 战队跳边插件配置类 /// public class TeamSwitchConfig { /// /// 启用插件 /// public bool Enabled { get; set; } = false; /// /// 触发关键字 (逗号分隔,支持拼音首字母) /// public string LikeKey { get; set; } = "tb,stb,跳边"; /// /// 免费额度耗尽提示内容 /// public string LimitExhaustedMsg { get; set; } = "免费更换阵营额度已经耗尽,请使用STB命令更换阵营!"; /// /// 每张地图允许免费更换阵营的个数 /// public int LimitExhaustedCount { get; set; } = 10; /// /// 使用积分跳边所需要的积分数量 /// public int STBIntegrate { get; set; } = 30; /// /// 有权限协助他人进行跳边的SteamID列表 (包含SteamID即可) /// public List STBSteamID { get; set; } = new List(); /// /// 开局多少秒内允许使用免费额度 /// public int STBSecond { get; set; } = 300; /// /// 免费跳边时间已过提示语 /// public string STBSecondMsg { get; set; } = "免费跳边时间已过,请使用STB命令更换阵营"; /// /// 玩家求助广播内容模板 ({Name}为玩家名,{Code}为指令) /// public string STBHelpMsg { get; set; } = "玩家【{Name}】想要更换阵营!快来帮帮Ta:{Code}"; /// /// 帮助指令前缀 (后面跟4位随机码) /// public string STBHelpCMD { get; set; } = "hp"; } /// /// 战队跳边插件 /// public class TeamSwitchPlugin : IPlugin, IDisposable { /// /// 插件名称 /// public string Name => "玩家跳边系统"; /// /// 插件描述 /// public string Description => "允许玩家通过聊天指令或积分更换阵营,支持免费额度和战队协助"; /// /// 插件分类 /// public PluginCategory Category => PluginCategory.PointsFunction; /// /// 是否启用 /// public bool IsEnabled => _config.Enabled; private TeamSwitchConfig _config = new TeamSwitchConfig(); // 状态 private string _currentMap = ""; private Dictionary _freeUsage = new Dictionary(); // SteamID -> 次数 private Dictionary _pendingHelp = new Dictionary(); // 验证码 -> SteamId private HashSet _helperSteamIds = new HashSet(); private readonly object _lock = new object(); private readonly Random _random = new Random(); /// /// 构造函数,初始化插件并订阅RCON消息事件 /// public TeamSwitchPlugin() { ApplyConfig(); } /// /// 获取当前配置 /// /// 配置对象 public object GetConfig() => _config; /// /// 更新配置 /// /// JSON配置元素 public void UpdateConfig(JsonElement config) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; try { _config = JsonSerializer.Deserialize(config.GetRawText(), options) ?? new TeamSwitchConfig(); ParseHelperSteamIds(); ApplyConfig(); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] UpdateConfig Error: {ex.Message}"); } } /// /// 应用配置,根据启用状态订阅或取消订阅事件 /// private void ApplyConfig() { Program.OnRconMessageReceived -= OnRconMessage; if (_config.Enabled) { Program.OnRconMessageReceived += OnRconMessage; } } /// /// 重置配置为默认值 /// public void ResetConfig() { _config = new TeamSwitchConfig(); ParseHelperSteamIds(); } /// /// 解析协助者SteamID列表 /// private void ParseHelperSteamIds() { lock (_lock) { _helperSteamIds.Clear(); if (_config.STBSteamID == null || _config.STBSteamID.Count == 0) return; foreach (var id in _config.STBSteamID) { if (!string.IsNullOrWhiteSpace(id)) { _helperSteamIds.Add(id.Trim()); } } } } /// /// 释放资源,取消订阅事件 /// public void Dispose() { Program.OnRconMessageReceived -= OnRconMessage; } /// /// 处理RCON消息 /// /// 消息内容 private async void OnRconMessage(string msg) { if (!_config.Enabled) return; try { // 检查地图状态 CheckMapStatus(); // 解析聊天 var (steamId, name, content) = ParseChat(msg); if (string.IsNullOrEmpty(steamId)) return; content = content.Trim(); // 1. 检查协助指令 if (content.StartsWith(_config.STBHelpCMD, StringComparison.OrdinalIgnoreCase)) { await HandleHelpCommand(steamId, name, content); return; } // 2. 检查跳边指令 if (IsSwitchCommand(content)) { await HandleSwitchRequest(steamId, name, content); } } catch (Exception ex) { RainOpsLog.Log($"[{Name}] Error: {ex.Message}"); } } /// /// 检查地图状态,如果地图更换则重置状态 /// private void CheckMapStatus() { var serverInfo = Program.RconCache?.ServerInfo; if (serverInfo == null) return; string map = serverInfo.MapName_s ?? ""; if (map != _currentMap) { lock (_lock) { _currentMap = map; _freeUsage.Clear(); _pendingHelp.Clear(); } RainOpsLog.Log($"[{Name}] Map changed to {map}. Reset limits."); } } /// /// 解析聊天消息 /// /// 原始消息 /// 解析出的SteamID、名字和内容 private (string steamId, string name, string content) ParseChat(string msg) { // 格式 1 var match = Regex.Match(msg, @"ChatMessage: \[(?\d+)\] (?.+) : (?.+)"); if (!match.Success) { // 格式 2 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); } /// /// 检查是否为跳边指令 /// /// 聊天内容 /// 是否匹配 private bool IsSwitchCommand(string content) { if (string.IsNullOrWhiteSpace(_config.LikeKey)) return false; var keys = _config.LikeKey.Split(new[] { ',', ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var key in keys) { if (string.IsNullOrWhiteSpace(key)) continue; // 精确匹配 if (content.Equals(key, StringComparison.OrdinalIgnoreCase)) return true; // 拼音首字母 string initials = PinyinHelper.GetInitials(key); if (!string.IsNullOrEmpty(initials) && content.Equals(initials, StringComparison.OrdinalIgnoreCase)) return true; // 全拼 string pinyin = PinyinHelper.GetPinyin(key); if (!string.IsNullOrEmpty(pinyin) && content.Equals(pinyin, StringComparison.OrdinalIgnoreCase)) return true; } return false; } /// /// 处理跳边请求 /// /// 玩家SteamID /// 玩家名字 /// 指令内容 private async Task HandleSwitchRequest(string steamId, string name, string content) { bool isFree = false; string failReason = ""; lock (_lock) { // 检查时间 int matchTime = Program.RconCache?.ServerInfo?.PlayTime_I ?? 999999; bool timeValid = matchTime <= _config.STBSecond; if (!timeValid) failReason = _config.STBSecondMsg; // 检查限制 if (!_freeUsage.TryGetValue(steamId, out int count)) count = 0; bool limitValid = count < _config.LimitExhaustedCount; if (!limitValid) failReason = _config.LimitExhaustedMsg; if (timeValid && limitValid) { isFree = true; } } if (isFree) { if (await ExecuteSwitch(steamId)) { lock (_lock) { if (!_freeUsage.ContainsKey(steamId)) _freeUsage[steamId] = 0; _freeUsage[steamId]++; } await SendWarn(steamId, $"已为您更换阵营 (免费额度: {_freeUsage[steamId]}/{_config.LimitExhaustedCount})"); } } else { // 如果不是使用STB指令,则提示错误并返回,不进行扣分尝试 if (!content.Equals("stb", StringComparison.OrdinalIgnoreCase)) { await SendWarn(steamId, failReason); return; } // 尝试扣分 await SendWarn(steamId, failReason); // 通知为什么免费失败 int points = await PointsService.GetPointsAsync(steamId); if (points >= _config.STBIntegrate) { if (await PointsService.AdjustPointsAsync(steamId, -_config.STBIntegrate) != 0) { if (await ExecuteSwitch(steamId)) { await SendWarn(steamId, $"已消耗 {_config.STBIntegrate} 积分更换阵营 (剩余: {points - _config.STBIntegrate})"); } else { // 失败退款?通常比较复杂,假设切换有效或手动退款。 // 暂时只记录日志。 RainOpsLog.Log($"[{Name}] Switch failed for {steamId} but points deducted."); } } } else { // 积分不足 -> 生成求助码 string code = GenerateHelpCode(); lock (_lock) { _pendingHelp[code] = steamId; } string helpCmd = $"{_config.STBHelpCMD}{code}"; string broadcastMsg = _config.STBHelpMsg .Replace("{Name}", name) .Replace("{Code}", helpCmd); await SendBroadcast(broadcastMsg); await SendWarn(steamId, $"积分不足,已发送求助广播!"); } } } /// /// 生成随机求助代码 /// /// 4位随机代码 private string GenerateHelpCode() { // 生成4位随机码 return _random.Next(1000, 9999).ToString(); } /// /// 处理协助指令 /// /// 协助者SteamID /// 协助者名字 /// 指令内容 private async Task HandleHelpCommand(string helperSteamId, string helperName, string content) { // content 类似 "hp1234" string code = content.Substring(_config.STBHelpCMD.Length).Trim(); // 验证协助者权限 bool isHelper = false; lock (_lock) { if (_helperSteamIds.Contains(helperSteamId)) isHelper = true; } if (!isHelper) { await SendWarn(helperSteamId, "您没有权限执行此操作!"); return; } string requesterSteamId = null; lock (_lock) { if (_pendingHelp.TryGetValue(code, out var sid)) { requesterSteamId = sid; _pendingHelp.Remove(code); } } if (requesterSteamId != null) { if (await ExecuteSwitch(requesterSteamId)) { await SendBroadcast($"感谢 {helperName} 协助玩家更换阵营!"); } else { await SendWarn(helperSteamId, "操作失败,玩家可能已不在服务器或无法更换。"); } } else { await SendWarn(helperSteamId, "无效的求助代码或请求已过期。"); } } /// /// 执行跳边操作 /// /// 玩家SteamID /// 是否成功 private async Task ExecuteSwitch(string steamId) { if (Program.Client != null && Program.Client.IsConnected) { // Squad AdminForceTeamChange 逻辑 // 命令: AdminForceTeamChange // 注意: 这会杀死玩家并切换队伍。 string cmd = $"AdminForceTeamChange \"{steamId}\""; await Program.Client.SendCommandAsync(cmd); return true; } return false; } /// /// 发送警告消息给玩家 /// /// 玩家SteamID /// 消息内容 private async Task SendWarn(string steamId, string msg) { if (Program.Client != null && Program.Client.IsConnected) { await Program.Client.SendCommandAsync($"AdminWarn \"{steamId}\" \"{msg}\""); } } /// /// 发送全服广播 /// /// 广播内容 private async Task SendBroadcast(string msg) { if (Program.Client != null && Program.Client.IsConnected) { await Program.Client.SendCommandAsync($"AdminBroadcast \"{msg}\""); } } } }