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

490 lines
16 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.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}\"");
}
}
}
}