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);
}
}
}