Files
squad-rain-ops-mini/Plugins/CallOpPlugin.cs

307 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}