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 { /// /// 个人BUFF插件配置 /// public class PersonalBuffConfig { /// /// 是否启用插件 /// public bool Enabled { get; set; } = false; /// /// 触发关键字 (逗号分隔,支持拼音首字母) /// public string LikeKey { get; set; } = "buff,grbuff,战斗模式"; /// /// 需要扣除的积分数量 /// public int Integral { get; set; } = 100; /// /// 击杀提醒是否开启 /// public bool EnableDie { get; set; } = true; /// /// 受伤提醒是否开启 /// public bool EnableWound { get; set; } = true; /// /// 救治提醒是否开启 /// public bool EnableRevive { get; set; } = true; /// /// 攻击伤害提醒是否开启 /// public bool EnableAttack { get; set; } = true; /// /// 激活模式: Map (整张地图有效) 或 Time (时间有效) /// public string Type { get; set; } = "Time"; /// /// 激活时长(分钟),仅当Type为Time时有效 /// public int Minute { get; set; } = 120; /// /// 切换静默模式指令 /// public string ToggleSilentCommand { get; set; } = "静默BUFF"; /// /// 激活成功提示模板 /// public string MsgTemplate_ActiveSuccess { get; set; } = "BUFF激活成功!消耗{Points}积分,{ExpiryMsg}。"; /// /// 重复激活提示模板 /// public string MsgTemplate_AlreadyActive { get; set; } = "您的BUFF正在生效中,剩余时间:{TimeLeft}。"; /// /// 击伤攻击者提示模板 /// public string MsgTemplate_Wound_Attacker { get; set; } = "你击伤了 {Victim} (伤害: {Damage}, 武器: {Weapon})"; /// /// 击伤受害者提示模板 /// public string MsgTemplate_Wound_Victim { get; set; } = "{Attacker} 击伤了你 (伤害: {Damage}, 武器: {Weapon})"; /// /// 击杀攻击者提示模板 /// public string MsgTemplate_Die_Attacker { get; set; } = "你击杀了 {Victim} (武器: {Weapon})"; /// /// 击杀受害者提示模板 /// public string MsgTemplate_Die_Victim { get; set; } = "{Attacker} 击杀了你 (武器: {Weapon})"; /// /// 救人者提示模板 /// public string MsgTemplate_Revive_Reviver { get; set; } = "你救起了 {Revived}"; /// /// 被救者提示模板 /// public string MsgTemplate_Revive_Revived { get; set; } = "{Reviver} 救起了你"; /// /// 造成伤害提示模板 /// public string MsgTemplate_Attack { get; set; } = "造成伤害: {Damage}"; /// /// VIP豁免列表 /// public List VipList { get; set; } = new List(); } /// /// VIP信息类 /// public class BUFFVipInfo { /// /// 玩家ID /// public string SteamId { get; set; } /// /// 昵称(可不填) /// public string Name { get; set; } /// /// 开通原因(可不填) /// public string Reason { get; set; } /// /// 过期时间(可不填) /// public DateTime ExpireTime { get; set; } } /// /// BUFF状态类 /// public class BuffState { public DateTime ExpireTime { get; set; } public bool IsSilent { get; set; } public bool IsVip { get; set; } } /// /// 个人BUFF插件 /// public class PersonalBuffPlugin : IPlugin, IDisposable { public string Name => "个人BUFF系统"; public string Description => "允许玩家消耗积分开启战斗日志通知(BUFF),支持击杀、受伤、攻击、救治提示"; public PluginCategory Category => PluginCategory.PointsFunction; public bool IsEnabled => _config.Enabled; private PersonalBuffConfig _config = new PersonalBuffConfig(); // SteamID -> BuffState private Dictionary _activeBuffs = new Dictionary(); private string _currentMap = ""; private readonly object _lock = new object(); private readonly string _storagePath; private System.Timers.Timer _vipTimer; /// /// 构造函数,初始化存储路径并加载数据,注册事件 /// public PersonalBuffPlugin() { _storagePath = Path.Combine(Environment.CurrentDirectory, "PluginData", "PersonalBuffData.json"); LoadBuffs(); // 初始化VIP检查定时器 (每60秒检查一次) _vipTimer = new System.Timers.Timer(60000); _vipTimer.Elapsed += (s, e) => CheckVips(); ApplyConfig(); } /// /// 从本地JSON文件加载活跃的BUFF数据 /// private void LoadBuffs() { try { if (File.Exists(_storagePath)) { string json = File.ReadAllText(_storagePath); // 尝试先读取为 JsonDocument 以判断格式 using (JsonDocument doc = JsonDocument.Parse(json)) { JsonElement root = doc.RootElement; string map = ""; if (root.TryGetProperty("CurrentMap", out JsonElement mapEl)) { map = mapEl.GetString() ?? ""; } var newBuffs = new Dictionary(); if (root.TryGetProperty("ActiveBuffs", out JsonElement buffsEl)) { foreach (JsonProperty property in buffsEl.EnumerateObject()) { string steamId = property.Name; JsonElement value = property.Value; if (value.ValueKind == JsonValueKind.String) { // 旧格式: Value 是 DateTime 字符串 if (DateTime.TryParse(value.GetString(), out DateTime expiry)) { newBuffs[steamId] = new BuffState { ExpireTime = expiry, IsSilent = false, IsVip = false }; } } else if (value.ValueKind == JsonValueKind.Object) { // 新格式: Value 是 BuffState 对象 var state = JsonSerializer.Deserialize(value.GetRawText()); if (state != null) { newBuffs[steamId] = state; } } } } lock (_lock) { _activeBuffs = newBuffs; _currentMap = map; } } } } catch (Exception ex) { RainOpsLog.Log($"[{Name}] LoadBuffs Error: {ex.Message}"); } } /// /// 定期检查VIP玩家并自动激活BUFF /// private async void CheckVips() { try { if (!_config.Enabled || _config.VipList == null || _config.VipList.Count == 0) return; var players = Program.RconCache?.Players; if (players == null || players.Count == 0) return; foreach (var player in players) { var vip = _config.VipList.FirstOrDefault(v => v.SteamId == player.SteamId); if (vip != null) { if (DateTime.Now < vip.ExpireTime) { // 检查是否已经激活BUFF bool needActivate = false; lock (_lock) { if (!_activeBuffs.ContainsKey(player.SteamId)) { needActivate = true; } else { // 如果已激活但不是VIP模式,或者是VIP但时间快到了(小于5分钟),刷新时间 var state = _activeBuffs[player.SteamId]; if (!state.IsVip || state.ExpireTime < DateTime.Now.AddMinutes(5)) { needActivate = true; } } } if (needActivate) { DateTime newExpiry = DateTime.Now.AddHours(1); // VIP自动续期,每次1小时 string expiryMsg = "VIP自动激活"; lock (_lock) { _activeBuffs[player.SteamId] = new BuffState { ExpireTime = newExpiry, IsSilent = false, // 默认不静默,用户可自行切换 IsVip = true }; } SaveBuffs(); RainOpsLog.Log($"[BUFF] VIP Auto Activate: {player.Name} ({player.SteamId}), Reason: {vip.Reason}"); // 发送通知 string msg = FormatMessage(_config.MsgTemplate_ActiveSuccess, new Dictionary { { "Points", "0 (VIP)" }, { "ExpiryMsg", expiryMsg } }); await SendWarn(player.SteamId, msg); } } } } } catch (Exception ex) { RainOpsLog.Log($"[{Name}] CheckVips Error: {ex.Message}"); } } /// /// 将当前活跃的BUFF数据保存到本地JSON文件 /// private void SaveBuffs() { try { string dir = Path.GetDirectoryName(_storagePath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } BuffStorageData data; lock (_lock) { data = new BuffStorageData { CurrentMap = _currentMap, ActiveBuffs = _activeBuffs }; } string json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(_storagePath, json); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] SaveBuffs Error: {ex.Message}"); } } /// /// BUFF数据存储结构类 /// private class BuffStorageData { public string CurrentMap { get; set; } public Dictionary ActiveBuffs { get; set; } } /// /// 获取当前配置 /// /// public object GetConfig() => _config; /// /// 更新配置,反序列化JSON并处理大小写 /// /// public void UpdateConfig(JsonElement config) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; try { _config = JsonSerializer.Deserialize(config.GetRawText(), options) ?? new PersonalBuffConfig(); ApplyConfig(); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] UpdateConfig Error: {ex.Message}"); } } /// /// 应用配置,根据启用状态管理事件订阅和定时器 /// private void ApplyConfig() { Program.OnRconMessageReceived -= OnRconMessage; SquadLogHelper.squadLogCleaning.OnLogReceived -= OnLogMessage; _vipTimer.Stop(); if (_config.Enabled) { Program.OnRconMessageReceived += OnRconMessage; SquadLogHelper.squadLogCleaning.OnLogReceived += OnLogMessage; _vipTimer.Start(); } } /// /// 重置配置为默认值 /// public void ResetConfig() { _config = new PersonalBuffConfig(); } /// /// 释放资源,取消事件订阅 /// public void Dispose() { Program.OnRconMessageReceived -= OnRconMessage; // 取消订阅日志事件 SquadLogHelper.squadLogCleaning.OnLogReceived -= OnLogMessage; _vipTimer?.Stop(); _vipTimer?.Dispose(); } /// /// 处理游戏日志消息 /// /// private async void OnLogMessage(string msg) { if (!_config.Enabled) return; try { // 解析战斗日志 (仅当有活跃BUFF时) if (_activeBuffs.Count > 0 && (_config.EnableDie || _config.EnableWound || _config.EnableRevive || _config.EnableAttack)) { await ProcessGameLog(msg); } } catch (Exception ex) { RainOpsLog.Log($"[{Name}] Log Error: {ex.Message}"); } } /// /// 处理RCON消息的主入口 /// /// RCON消息内容 private async void OnRconMessage(string msg) { if (!_config.Enabled) return; try { // 1. 检查地图变更 CheckMapStatus(); // 2. 解析聊天指令 await ProcessChatCommand(msg); } catch (Exception ex) { RainOpsLog.Log($"[{Name}] Error: {ex.Message}"); } } /// /// 检查地图状态,处理换图时的BUFF清理 /// private void CheckMapStatus() { var serverInfo = Program.RconCache?.ServerInfo; if (serverInfo == null) return; string map = serverInfo.MapName_s ?? ""; if (map != _currentMap) { lock (_lock) { _currentMap = map; // 如果是Map模式,换图时清除所有BUFF // 如果是Time模式,清理已过期的BUFF (虽然OnRconMessage也会用到,但换图清理一下也好) if (_config.Type.Equals("Map", StringComparison.OrdinalIgnoreCase)) { _activeBuffs.Clear(); SaveBuffs(); } } } // 清理过期BUFF (通用) CleanupExpiredBuffs(); } /// /// 清理已过期的Time模式BUFF /// private void CleanupExpiredBuffs() { bool changed = false; lock (_lock) { var now = DateTime.Now; var expired = _activeBuffs.Where(kv => kv.Value.ExpireTime < now).Select(kv => kv.Key).ToList(); if (expired.Count > 0) { foreach (var id in expired) { _activeBuffs.Remove(id); } changed = true; } } if (changed) { SaveBuffs(); } } /// /// 切换静默模式 /// /// /// private async Task ToggleSilent(string steamId) { bool currentState = false; bool hasBuff = false; lock (_lock) { if (_activeBuffs.TryGetValue(steamId, out var state)) { hasBuff = true; currentState = state.IsSilent; state.IsSilent = !currentState; } } if (hasBuff) { SaveBuffs(); string status = !currentState ? "已开启" : "已关闭"; await SendWarn(steamId, $"静默模式{status}。"); } else { await SendWarn(steamId, "您未激活BUFF,无法设置静默模式。"); } } /// /// 解析聊天消息,检测是否为触发指令 /// /// /// private async Task ProcessChatCommand(string msg) { // RainOpsLog.Log($"[BUFF] Debug Chat: {msg}"); // 简单解析聊天 var match = Regex.Match(msg, @"ChatMessage: \[(?\d+)\] (?.+) : (?.+)"); if (!match.Success) { match = Regex.Match(msg, @"\[Chat.+?\] \[Online IDs:EOS: .+? steam: (?\d+)\]\s+(?.+?) : (?.+)"); } if (match.Success) { string steamId = match.Groups["steamId"].Value; string content = match.Groups["content"].Value.Trim(); // RainOpsLog.Log($"[BUFF] Chat Match: {steamId} said '{content}'"); if (!string.IsNullOrEmpty(_config.ToggleSilentCommand) && content.Equals(_config.ToggleSilentCommand, StringComparison.OrdinalIgnoreCase)) { await ToggleSilent(steamId); return; } if (IsTriggerCommand(content)) { RainOpsLog.Log($"[BUFF] Trigger Command Detected from {steamId}: {content}"); await ActivateBuff(steamId); } } } /// /// 判断内容是否匹配触发关键字(支持模糊匹配、拼音首字母) /// /// /// private bool IsTriggerCommand(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; } /// /// 激活玩家BUFF,执行扣分并设置过期时间 /// /// /// private async Task ActivateBuff(string steamId) { // 检查是否已经激活 BuffState state = null; lock (_lock) { if (_activeBuffs.TryGetValue(steamId, out var s)) { if (s.ExpireTime > DateTime.Now) { state = s; } } } if (state != null) { // 计算剩余时间 TimeSpan left = state.ExpireTime - DateTime.Now; string timeLeftStr; if (state.ExpireTime == DateTime.MaxValue) timeLeftStr = "本局结束前"; else if (left.TotalHours >= 1) timeLeftStr = $"{(int)left.TotalHours}小时{left.Minutes}分"; else timeLeftStr = $"{left.Minutes}分{left.Seconds}秒"; string msg = FormatMessage(_config.MsgTemplate_AlreadyActive, new Dictionary { { "TimeLeft", timeLeftStr } }); await SendWarn(steamId, msg); return; } // 扣分 int points = await PointsService.GetPointsAsync(steamId); if (points >= _config.Integral) { if (await PointsService.AdjustPointsAsync(steamId, -_config.Integral) != 0) { // 激活成功 DateTime expiry; string expiryMsg; if (_config.Type.Equals("Map", StringComparison.OrdinalIgnoreCase)) { expiry = DateTime.MaxValue; // Map模式,直到换图 expiryMsg = "本局地图结束前有效"; } else { expiry = DateTime.Now.AddMinutes(_config.Minute); expiryMsg = $"{_config.Minute}分钟内有效"; } lock (_lock) { _activeBuffs[steamId] = new BuffState { ExpireTime = expiry, IsSilent = false, IsVip = false }; } SaveBuffs(); string msg = FormatMessage(_config.MsgTemplate_ActiveSuccess, new Dictionary { { "Points", _config.Integral.ToString() }, { "ExpiryMsg", expiryMsg } }); await SendWarn(steamId, msg); } else { await SendWarn(steamId, "扣分失败,请联系管理员。"); } } else { await SendWarn(steamId, $"积分不足!需要{_config.Integral}积分,当前{points}积分。"); } } /// /// 解析游戏日志,分发各类战斗事件 /// /// /// private async Task ProcessGameLog(string msg) { // 过滤旧日志 (超过3秒) // 格式: [2024.02.11-12.00.00:123] var match = Regex.Match(msg, @"^\[(\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}:\d{3})\]"); if (match.Success) { // 尝试解析时间 if (DateTime.TryParseExact(match.Groups[1].Value, "yyyy.MM.dd-HH.mm.ss:fff", null, System.Globalization.DateTimeStyles.None, out DateTime logTime)) { // 日志时间通常是UTC,转换为UTC+8北京时间以匹配系统时间 logTime = logTime.AddHours(8); // 假设日志时间与系统时间一致(或时区匹配),若差异过大则忽略 if (Math.Abs((DateTime.Now - logTime).TotalSeconds) > 3) { // RainOpsLog.Log($"[BUFF] Log Time Ignored: LogTime(UTC+8)={logTime}, SysTime={DateTime.Now}"); return; } } } // Wound if (_config.EnableWound && msg.Contains("Wound()")) { string cleanMsg = msg.Replace("ASQSoldier::", ""); RainOpsLog.Log($"[BUFF] Processing Wound: {cleanMsg}"); await HandleWound(cleanMsg); } // Die else if (_config.EnableDie && msg.Contains("Die()")) { string cleanMsg = msg.Replace("ASQSoldier::", ""); RainOpsLog.Log($"[BUFF] Processing Die: {cleanMsg}"); await HandleDie(cleanMsg); } // Revived else if (_config.EnableRevive && msg.Contains("has revived")) { RainOpsLog.Log($"[BUFF] Processing Revived: {msg}"); await HandleRevived(msg); } // Attack else if (_config.EnableAttack && msg.Contains("ActualDamage=")) { // RainOpsLog.Log($"[BUFF] Processing Attack: {msg}"); await HandleAttack(msg); } } /// /// 处理受伤(Wound)日志,通知双方 /// /// /// private async Task HandleWound(string log) { // 优化正则:使用 \s* 允许空格波动,且忽略 Controller ID 具体文本 string pattern = @"\[(.*?)\]\[(.*?)\]LogSquadTrace: \[DedicatedServer\]Wound\(\): Player:\s*(.*?)\s*KillingDamage=(.*?) from (.*?) \(Online IDs: (.*?) steam:\s*(.*?)\s*\|.*?\) caused by (.*)"; if (!log.Contains("steam:")) log = log.Replace("INVALID", "EOS: 0000000000000000000000000000000 steam: 00000000000000000"); var match = Regex.Match(log, pattern); if (match.Success) { string victimName = match.Groups[3].Value.Trim(); string damage = match.Groups[4].Value.Trim(); string attackerSteamId = match.Groups[7].Value.Trim(); string weapon = ExtractMiddlePart(match.Groups[8].Value.Trim()); // 查找受害者SteamID string victimSteamId = FindSteamIdByName(victimName); RainOpsLog.Log($"[BUFF] Wound Match: Victim={victimName}({victimSteamId}), Attacker={attackerSteamId}, Dmg={damage}"); if (HasBuff(attackerSteamId) || HasBuff(victimSteamId)) { string attackerName = FindNameBySteamId(attackerSteamId) ?? "Unknown"; // 通知攻击者 if (!string.IsNullOrEmpty(attackerSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Wound_Attacker, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(attackerSteamId, msg, true); } // 通知受害者 if (!string.IsNullOrEmpty(victimSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Wound_Victim, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(victimSteamId, msg, true); } } else { RainOpsLog.Log($"[BUFF] Wound No Buff Active: {attackerSteamId} or {victimSteamId}"); } } else { RainOpsLog.Log($"[BUFF] Wound Match Failed: {log}"); } } /// /// 处理死亡(Die)日志,通知双方 /// /// /// private async Task HandleDie(string log) { // 优化正则:忽略 Controller ID 具体文本 string pattern = @"\[(.*?)\]\[(.*?)\]LogSquadTrace: \[DedicatedServer\]Die\(\): Player:\s*(.*?)\s*KillingDamage=(.*?) from (.*?) \(Online IDs: EOS: (.*?) steam:\s*(.*?)\s*\|.*?\) caused by (.*)"; if (!log.Contains("steam:")) log = log.Replace("INVALID", "EOS: 0000000000000000000000000000000 steam: 00000000000000000"); var match = Regex.Match(log, pattern); if (match.Success) { string victimName = match.Groups[3].Value.Trim(); string damage = match.Groups[4].Value.Trim(); string attackerSteamId = match.Groups[7].Value.Trim(); string weapon = ExtractMiddlePart(match.Groups[8].Value.Trim()); string victimSteamId = FindSteamIdByName(victimName); RainOpsLog.Log($"[BUFF] Die Match: Victim={victimName}({victimSteamId}), Attacker={attackerSteamId}"); if (HasBuff(attackerSteamId) || HasBuff(victimSteamId)) { string attackerName = FindNameBySteamId(attackerSteamId) ?? "Unknown"; // 通知攻击者 if (!string.IsNullOrEmpty(attackerSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Die_Attacker, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(attackerSteamId, msg, true); } // 通知受害者 if (!string.IsNullOrEmpty(victimSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Die_Victim, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(victimSteamId, msg, true); } } else { RainOpsLog.Log($"[BUFF] Die No Buff Active: {attackerSteamId} or {victimSteamId}"); } } else { RainOpsLog.Log($"[BUFF] Die Match Failed: {log}"); } } /// /// 处理救治(Revived)日志,通知双方 /// /// /// private async Task HandleRevived(string log) { string pattern = @"\[.*?\]\[.*?\]LogSquad:(.*?)\s*\(Online IDs: EOS: (.*?) steam:\s*(.*?)\) has revived (.*?)\s*\(Online IDs: EOS: (.*?) steam:\s*(.*?)\)\."; var match = Regex.Match(log, pattern); if (match.Success) { string reviverName = match.Groups[1].Value.Trim(); string reviverSteamId = match.Groups[3].Value.Trim(); string revivedName = match.Groups[4].Value.Trim(); string revivedSteamId = match.Groups[6].Value.Trim(); RainOpsLog.Log($"[BUFF] Revive Match: Reviver={reviverName}({reviverSteamId}), Revived={revivedName}({revivedSteamId})"); if (HasBuff(reviverSteamId) || HasBuff(revivedSteamId)) { if (!string.IsNullOrEmpty(reviverSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Revive_Reviver, new Dictionary { { "Revived", revivedName }, { "Reviver", reviverName } }); await SendWarn(reviverSteamId, msg, true); } if (!string.IsNullOrEmpty(revivedSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Revive_Revived, new Dictionary { { "Revived", revivedName }, { "Reviver", reviverName } }); await SendWarn(revivedSteamId, msg, true); } } else { RainOpsLog.Log($"[BUFF] Revive No Buff Active: {reviverSteamId} or {revivedSteamId}"); } } else { RainOpsLog.Log($"[BUFF] Revive Match Failed: {log}"); } } /// /// 处理攻击(Attack)日志,通知双方 /// /// /// private async Task HandleAttack(string log) { // 优化正则:忽略 Controller ID 具体文本 string pattern = @"\[.*?\]\[.*?\]LogSquad: Player:\s*(.*?)\s*ActualDamage=(.*?) from (.*?) \(Online IDs: EOS: (.*?) steam:\s*(.*?)\s*\|.*?\)caused by (.*)"; if (!log.Contains("steam:")) log = log.Replace("INVALID", "EOS: 0000000000000000000000000000000 steam: 00000000000000000"); var match = Regex.Match(log, pattern); if (match.Success) { string victimName = match.Groups[1].Value.Trim(); string damage = match.Groups[2].Value.Trim(); string attackerSteamId = match.Groups[5].Value.Trim(); string weapon = ExtractMiddlePart(match.Groups[6].Value.Trim()); // 查找受害者SteamID string victimSteamId = FindSteamIdByName(victimName); RainOpsLog.Log($"[BUFF] Attack Match: Victim={victimName}({victimSteamId}), Attacker={attackerSteamId}, Dmg={damage}"); if (HasBuff(attackerSteamId) || HasBuff(victimSteamId)) { string attackerName = FindNameBySteamId(attackerSteamId) ?? "Unknown"; // 通知攻击者 if (!string.IsNullOrEmpty(attackerSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Attack, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(attackerSteamId, msg, true); } // 通知受害者 if (!string.IsNullOrEmpty(victimSteamId)) { string msg = FormatMessage(_config.MsgTemplate_Attack, new Dictionary { { "Victim", victimName }, { "Damage", damage }, { "Weapon", weapon }, { "Attacker", attackerName } }); await SendWarn(victimSteamId, msg, true); } } else { RainOpsLog.Log($"[BUFF] Attack No Buff Active: {attackerSteamId} or {victimSteamId}"); } } else { RainOpsLog.Log($"[BUFF] Attack Match Failed: {log}"); } } /// /// 检查玩家是否拥有活跃BUFF /// /// /// private bool HasBuff(string steamId) { if (string.IsNullOrEmpty(steamId)) return false; lock (_lock) { if (_activeBuffs.TryGetValue(steamId, out var state)) { return state.ExpireTime > DateTime.Now; } } return false; } /// /// 根据玩家名称查找SteamID /// /// /// private string FindSteamIdByName(string name) { var players = Program.RconCache?.Players; if (players == null) return null; return players.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))?.SteamId; } /// /// 根据SteamID查找玩家名称 /// /// /// private string FindNameBySteamId(string steamId) { var players = Program.RconCache?.Players; if (players == null) return null; return players.FirstOrDefault(p => p.SteamId == steamId)?.Name; } /// /// 提取字符串中间部分(通常用于简化武器名称) /// /// /// private string ExtractMiddlePart(string input) { if (string.IsNullOrEmpty(input)) return input; // 移除末尾的 _C_xxxxx 部分 string cleaned = Regex.Replace(input, @"_C_\d+$", ""); // 分割字符串 string[] parts = cleaned.Split('_'); // 从数组中间提取关键部分 // BP_Soldier_PLA_SquadLeader_Woodland 的关键是 PLA 和 SquadLeader if (parts.Length >= 4) { // 提取阵营(通常是第3部分) string faction = parts[2]; // 提取兵种(通常是第4部分) string role = parts[3]; return $"{faction}_{role}"; } else if (parts.Length >= 2) { // 如果格式不标准,至少返回后两个部分 return $"{parts[parts.Length - 2]}_{parts[parts.Length - 1]}"; } return input; } /// /// 格式化消息模板 /// private string FormatMessage(string template, Dictionary args) { if (string.IsNullOrEmpty(template)) return ""; string result = template; foreach (var kv in args) { result = result.Replace("{" + kv.Key + "}", kv.Value); } return result; } /// /// 发送AdminWarn警告通知给玩家 /// /// /// /// 是否检查静默模式 /// private async Task SendWarn(string steamId, string msg, bool checkSilent = false) { if (checkSilent) { lock (_lock) { if (_activeBuffs.TryGetValue(steamId, out var state) && state.IsSilent) { return; } } } if (Program.Client != null && Program.Client.IsConnected && !string.IsNullOrEmpty(steamId)) { RainOpsLog.Log($"[BUFF] Sending AdminWarn to {steamId}: {msg}"); // 防止消息中的双引号破坏RCON命令结构 msg = msg.Replace("\"", "'"); // 移除换行符 msg = msg.Replace("\r", "").Replace("\n", " "); // AdminWarn "SteamId" "Message" await Program.Client.SendCommandAsync($"AdminWarn \"{steamId}\" \"{msg}\""); } else { RainOpsLog.Log($"[BUFF] SendWarn Failed: Client Connected={Program.Client?.IsConnected}, SteamId={steamId}"); } } } }