mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-07 05:45:46 +08:00
307 lines
11 KiB
C#
307 lines
11 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 是否启用插件
|
||
/// </summary>
|
||
public bool Enabled { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 触发关键字列表
|
||
/// </summary>
|
||
public List<string> TriggerKeywords { get; set; } = new List<string> { "op", "admin", "管理员" };
|
||
|
||
/// <summary>
|
||
/// OP权限组关键字 (包含此权限的组被视为OP)
|
||
/// </summary>
|
||
public string OpPermissionKeyword { get; set; } = "cameraman";
|
||
|
||
/// <summary>
|
||
/// OP白名单 (这些SteamID即使有权限也不会显示为在线OP)
|
||
/// </summary>
|
||
public List<string> Whitelist { get; set; } = new List<string>();
|
||
|
||
/// <summary>
|
||
/// 广播消息模板 ({OpList} 会被替换为在线OP名字列表)
|
||
/// </summary>
|
||
public string BroadcastTemplate { get; set; } = "在线OP名单: {OpList}";
|
||
|
||
/// <summary>
|
||
/// 通知OP的消息模板 ({Name}=玩家名, {Team}=阵营, {Squad}=小队, {Content}=内容)
|
||
/// </summary>
|
||
public string OpWarningTemplate { get; set; } = "玩家 {Name} [{Team} - {Squad}] 正在呼叫OP! 内容: {Content}";
|
||
|
||
/// <summary>
|
||
/// 冷却时间 (秒)
|
||
/// </summary>
|
||
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<CallOpConfig>(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<PlayerInfo> GetOnlineOps()
|
||
{
|
||
var ops = new List<PlayerInfo>();
|
||
// 使用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<string> ParseAdminConfigForOps()
|
||
{
|
||
var opSteamIds = new HashSet<string>();
|
||
var opGroups = new HashSet<string>();
|
||
|
||
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: \[(?<steamId>\d+)\] (?<name>.+) : (?<content>.+)");
|
||
if (!match.Success)
|
||
{
|
||
// 格式 2: [Chat...] [Online IDs:EOS: ... steam: SteamID] Name : Content
|
||
match = Regex.Match(msg, @"\[Chat.+?\] \[Online IDs:EOS: .+? steam: (?<steamId>\d+)\]\s+(?<name>.+?) : (?<content>.+)");
|
||
}
|
||
|
||
if (match.Success)
|
||
{
|
||
return (match.Groups["steamId"].Value, match.Groups["name"].Value, match.Groups["content"].Value);
|
||
}
|
||
return (null, null, null);
|
||
}
|
||
}
|
||
}
|