mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-06 05:16:26 +08:00
490 lines
16 KiB
C#
490 lines
16 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 战队跳边插件配置类
|
||
/// </summary>
|
||
public class TeamSwitchConfig
|
||
{
|
||
|
||
/// <summary>
|
||
/// 启用插件
|
||
/// </summary>
|
||
public bool Enabled { get; set; } = false;
|
||
/// <summary>
|
||
/// 触发关键字 (逗号分隔,支持拼音首字母)
|
||
/// </summary>
|
||
public string LikeKey { get; set; } = "tb,stb,跳边";
|
||
|
||
/// <summary>
|
||
/// 免费额度耗尽提示内容
|
||
/// </summary>
|
||
public string LimitExhaustedMsg { get; set; } = "免费更换阵营额度已经耗尽,请使用STB命令更换阵营!";
|
||
|
||
/// <summary>
|
||
/// 每张地图允许免费更换阵营的个数
|
||
/// </summary>
|
||
public int LimitExhaustedCount { get; set; } = 10;
|
||
|
||
/// <summary>
|
||
/// 使用积分跳边所需要的积分数量
|
||
/// </summary>
|
||
public int STBIntegrate { get; set; } = 30;
|
||
|
||
/// <summary>
|
||
/// 有权限协助他人进行跳边的SteamID列表 (包含SteamID即可)
|
||
/// </summary>
|
||
public List<string> STBSteamID { get; set; } = new List<string>();
|
||
|
||
/// <summary>
|
||
/// 开局多少秒内允许使用免费额度
|
||
/// </summary>
|
||
public int STBSecond { get; set; } = 300;
|
||
|
||
/// <summary>
|
||
/// 免费跳边时间已过提示语
|
||
/// </summary>
|
||
public string STBSecondMsg { get; set; } = "免费跳边时间已过,请使用STB命令更换阵营";
|
||
|
||
/// <summary>
|
||
/// 玩家求助广播内容模板 ({Name}为玩家名,{Code}为指令)
|
||
/// </summary>
|
||
public string STBHelpMsg { get; set; } = "玩家【{Name}】想要更换阵营!快来帮帮Ta:{Code}";
|
||
|
||
/// <summary>
|
||
/// 帮助指令前缀 (后面跟4位随机码)
|
||
/// </summary>
|
||
public string STBHelpCMD { get; set; } = "hp";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 战队跳边插件
|
||
/// </summary>
|
||
public class TeamSwitchPlugin : IPlugin, IDisposable
|
||
{
|
||
/// <summary>
|
||
/// 插件名称
|
||
/// </summary>
|
||
public string Name => "玩家跳边系统";
|
||
|
||
/// <summary>
|
||
/// 插件描述
|
||
/// </summary>
|
||
public string Description => "允许玩家通过聊天指令或积分更换阵营,支持免费额度和战队协助";
|
||
|
||
/// <summary>
|
||
/// 插件分类
|
||
/// </summary>
|
||
public PluginCategory Category => PluginCategory.PointsFunction;
|
||
|
||
/// <summary>
|
||
/// 是否启用
|
||
/// </summary>
|
||
public bool IsEnabled => _config.Enabled;
|
||
|
||
private TeamSwitchConfig _config = new TeamSwitchConfig();
|
||
|
||
// 状态
|
||
private string _currentMap = "";
|
||
private Dictionary<string, int> _freeUsage = new Dictionary<string, int>(); // SteamID -> 次数
|
||
private Dictionary<string, string> _pendingHelp = new Dictionary<string, string>(); // 验证码 -> SteamId
|
||
private HashSet<string> _helperSteamIds = new HashSet<string>();
|
||
private readonly object _lock = new object();
|
||
private readonly Random _random = new Random();
|
||
|
||
/// <summary>
|
||
/// 构造函数,初始化插件并订阅RCON消息事件
|
||
/// </summary>
|
||
public TeamSwitchPlugin()
|
||
{
|
||
ApplyConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前配置
|
||
/// </summary>
|
||
/// <returns>配置对象</returns>
|
||
public object GetConfig() => _config;
|
||
|
||
/// <summary>
|
||
/// 更新配置
|
||
/// </summary>
|
||
/// <param name="config">JSON配置元素</param>
|
||
public void UpdateConfig(JsonElement config)
|
||
{
|
||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||
try
|
||
{
|
||
_config = JsonSerializer.Deserialize<TeamSwitchConfig>(config.GetRawText(), options) ?? new TeamSwitchConfig();
|
||
ParseHelperSteamIds();
|
||
ApplyConfig();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
RainOpsLog.Log($"[{Name}] UpdateConfig Error: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用配置,根据启用状态订阅或取消订阅事件
|
||
/// </summary>
|
||
private void ApplyConfig()
|
||
{
|
||
Program.OnRconMessageReceived -= OnRconMessage;
|
||
if (_config.Enabled)
|
||
{
|
||
Program.OnRconMessageReceived += OnRconMessage;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置配置为默认值
|
||
/// </summary>
|
||
public void ResetConfig()
|
||
{
|
||
_config = new TeamSwitchConfig();
|
||
ParseHelperSteamIds();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析协助者SteamID列表
|
||
/// </summary>
|
||
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());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源,取消订阅事件
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
Program.OnRconMessageReceived -= OnRconMessage;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理RCON消息
|
||
/// </summary>
|
||
/// <param name="msg">消息内容</param>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查地图状态,如果地图更换则重置状态
|
||
/// </summary>
|
||
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.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析聊天消息
|
||
/// </summary>
|
||
/// <param name="msg">原始消息</param>
|
||
/// <returns>解析出的SteamID、名字和内容</returns>
|
||
private (string steamId, string name, string content) ParseChat(string msg)
|
||
{
|
||
// 格式 1
|
||
var match = Regex.Match(msg, @"ChatMessage: \[(?<steamId>\d+)\] (?<name>.+) : (?<content>.+)");
|
||
if (!match.Success)
|
||
{
|
||
// 格式 2
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否为跳边指令
|
||
/// </summary>
|
||
/// <param name="content">聊天内容</param>
|
||
/// <returns>是否匹配</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理跳边请求
|
||
/// </summary>
|
||
/// <param name="steamId">玩家SteamID</param>
|
||
/// <param name="name">玩家名字</param>
|
||
/// <param name="content">指令内容</param>
|
||
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, $"积分不足,已发送求助广播!");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成随机求助代码
|
||
/// </summary>
|
||
/// <returns>4位随机代码</returns>
|
||
private string GenerateHelpCode()
|
||
{
|
||
// 生成4位随机码
|
||
return _random.Next(1000, 9999).ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理协助指令
|
||
/// </summary>
|
||
/// <param name="helperSteamId">协助者SteamID</param>
|
||
/// <param name="helperName">协助者名字</param>
|
||
/// <param name="content">指令内容</param>
|
||
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, "无效的求助代码或请求已过期。");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行跳边操作
|
||
/// </summary>
|
||
/// <param name="steamId">玩家SteamID</param>
|
||
/// <returns>是否成功</returns>
|
||
private async Task<bool> ExecuteSwitch(string steamId)
|
||
{
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
// Squad AdminForceTeamChange 逻辑
|
||
// 命令: AdminForceTeamChange <SteamId>
|
||
// 注意: 这会杀死玩家并切换队伍。
|
||
string cmd = $"AdminForceTeamChange \"{steamId}\"";
|
||
await Program.Client.SendCommandAsync(cmd);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送警告消息给玩家
|
||
/// </summary>
|
||
/// <param name="steamId">玩家SteamID</param>
|
||
/// <param name="msg">消息内容</param>
|
||
private async Task SendWarn(string steamId, string msg)
|
||
{
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
await Program.Client.SendCommandAsync($"AdminWarn \"{steamId}\" \"{msg}\"");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送全服广播
|
||
/// </summary>
|
||
/// <param name="msg">广播内容</param>
|
||
private async Task SendBroadcast(string msg)
|
||
{
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
await Program.Client.SendCommandAsync($"AdminBroadcast \"{msg}\"");
|
||
}
|
||
}
|
||
}
|
||
}
|