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

410 lines
14 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 RainOpsMini.Helpers;
using RainOpsMini.Helpers.RCON;
using RainOpsMini;
using RainOpsMini.Models;
using RainOpsMini.Models;
using RainOpsMini.Helpers.SquadGameLog;
using SqlSugar;
namespace RainOpsMini.Plugins
{
/// <summary>
/// 建队自动播报插件
/// </summary>
public class SquadCreationAnnouncePlugin : IPlugin, IDisposable
{
public string Name => "建队自动播报";
public string Description => "建队自动播报与限制插件 - 监控建队行为,支持时长限制、积分豁免及自动播报";
public PluginCategory Category => PluginCategory.BasicFunction;
public bool IsEnabled => _config.Enabled ;
private SquadCreationAnnounceConfig _config = new SquadCreationAnnounceConfig();
// Track squad creation attempts per match/session: SteamId -> Count
private static readonly Dictionary<string, int> _creationAttempts = new Dictionary<string, int>();
// Track last match map to clear attempts on map change
private string _lastMapName = "";
/// <summary>
/// 构造函数订阅RCON消息
/// </summary>
public SquadCreationAnnouncePlugin()
{
Program.OnRconMessageReceived += OnRconMessage;
}
/// <summary>
/// 获取当前配置
/// </summary>
/// <returns>配置对象</returns>
public object GetConfig() => _config;
/// <summary>
/// 更新配置
/// </summary>
/// <param name="config">JSON配置元素</param>
public void UpdateConfig(JsonElement config)
{
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
_config = JsonSerializer.Deserialize<SquadCreationAnnounceConfig>(config.GetRawText(), options) ?? new SquadCreationAnnounceConfig();
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] 配置更新失败: {ex.Message}");
}
}
/// <summary>
/// 重置配置为默认值
/// </summary>
public void ResetConfig()
{
_config = new SquadCreationAnnounceConfig();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Program.OnRconMessageReceived -= OnRconMessage;
}
/// <summary>
/// 处理RCON消息
/// </summary>
/// <param name="msg">RCON消息内容</param>
private void OnRconMessage(string msg)
{
if (_config.Enabled != true) return;
try
{
// 检测地图变更以重置计数器
CheckMapChange();
// 1. 建队检测
// LogSquad: Player: "PlayerName" (SteamId: 7656xxx) has created Squad 1 (Squad Name) on Team 1 (Team Name)
if (msg.Contains("has created Squad"))
{
HandleSquadCreation(msg);
}
// 2. 聊天查询
if (_config.ChatQuery == true)
{
var chatMsg = RconParser.ParseChatMessage(msg);
if (chatMsg != null)
{
HandleChatQuery(chatMsg);
}
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] 处理消息异常: {ex.Message}");
}
}
/// <summary>
/// 检测地图变更,重置建队尝试计数
/// </summary>
private void CheckMapChange()
{
var currentMap = Program.RconCache.ServerInfo?.MapName_s;
if (!string.IsNullOrEmpty(currentMap) && currentMap != _lastMapName)
{
_lastMapName = currentMap;
_creationAttempts.Clear();
}
}
/// <summary>
/// 处理建队事件
/// </summary>
/// <param name="msg">日志消息</param>
private async void HandleSquadCreation(string msg)
{
// 解析消息
// LogSquad: Player: "Name" (SteamId: 123) has created Squad 5 (Alpha) on Team 1 (USA)
var match = Regex.Match(msg, @"Player: ""(.*?)"" \(SteamId: (\d+)\) has created Squad (\d+) \((.*?)\) on Team (\d+)");
if (!match.Success) return;
string playerName = match.Groups[1].Value;
string steamId = match.Groups[2].Value;
string squadId = match.Groups[3].Value;
string squadName = match.Groups[4].Value;
string teamId = match.Groups[5].Value;
// 1. 检查忽略地图
if (!string.IsNullOrEmpty(_config.Ignore))
{
var map = Program.RconCache.ServerInfo?.MapName_s ?? "";
var ignoreList = _config.Ignore.Split(new[] { ',', '' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var ignore in ignoreList)
{
if (map.IndexOf(ignore, StringComparison.OrdinalIgnoreCase) >= 0)
{
return; // 忽略的地图
}
}
}
// 2. 追踪尝试次数
if (!_creationAttempts.ContainsKey(steamId)) _creationAttempts[steamId] = 0;
_creationAttempts[steamId]++;
if (_config.MaxTry > 0 && _creationAttempts[steamId] > _config.MaxTry)
{
// 超过最大尝试次数 -> 踢出
SendRconCommand($"AdminKick \"{steamId}\" \"建队尝试次数过多\"");
return;
}
// 3. 检查游戏时长
double playHours = GetPlayerTotalPlayHours(steamId);
double requiredHours = _config.MinRunHour; // 配置为整数但逻辑上是小时
bool isAllowed = false;
string allowReason = "";
if (requiredHours <= 0 || playHours >= requiredHours)
{
isAllowed = true;
allowReason = "满足时长要求";
}
else
{
// 时长不足,检查积分
if (_config.ExemptIntegral > 0)
{
if (TryDeductIntegral(steamId, _config.ExemptIntegral))
{
isAllowed = true;
allowReason = $"时长不足({playHours:F1}/{requiredHours}h),已扣除 {_config.ExemptIntegral} 积分抵扣";
SendRconCommand($"AdminWarn \"{steamId}\" {allowReason}");
}
else
{
isAllowed = false;
allowReason = $"时长不足({playHours:F1}/{requiredHours}h) 且积分不足,无法建队";
}
}
else
{
isAllowed = false;
allowReason = $"时长不足({playHours:F1}/{requiredHours}h)";
}
}
if (isAllowed)
{
// 如果启用则广播
if (_config.RCONOut == true)
{
SendRconCommand($"Broadcast 玩家 {playerName} 建立了小队 {squadId} ({squadName}) [{allowReason}]");
}
}
else
{
// 不允许 -> 警告并要求解散
SendRconCommand($"AdminWarn \"{steamId}\" 禁止建队: {allowReason}。请解散小队!");
}
}
/// <summary>
/// 处理聊天查询命令
/// </summary>
/// <param name="chatMsg">聊天消息</param>
private void HandleChatQuery(ChatMessage chatMsg)
{
if (string.IsNullOrWhiteSpace(_config.LikeKey)) return;
string msg = chatMsg.Message.Trim();
var keys = _config.LikeKey.Split(new[] { ',', '' }, StringSplitOptions.RemoveEmptyEntries);
bool matched = false;
foreach (var key in keys)
{
if (string.IsNullOrWhiteSpace(key)) continue;
// 1. 直接匹配
if (msg.Contains(key, StringComparison.OrdinalIgnoreCase)) { matched = true; break; }
// 2. 首字母匹配 (拼音)
string initials = PinyinHelper.GetInitials(key);
if (!string.IsNullOrEmpty(initials) && msg.Contains(initials, StringComparison.OrdinalIgnoreCase)) { matched = true; break; }
// 3. 全拼匹配
string pinyin = PinyinHelper.GetPinyin(key);
if (!string.IsNullOrEmpty(pinyin) && msg.Contains(pinyin, StringComparison.OrdinalIgnoreCase)) { matched = true; break; }
}
if (matched)
{
double hours = GetPlayerTotalPlayHours(chatMsg.SteamId);
int integral = GetPlayerIntegral(chatMsg.SteamId);
string reply = $"[查询结果] 您的游戏时长: {hours:F1}小时, 积分: {integral}。建队要求: {_config.MinRunHour}小时" +
(_config.ExemptIntegral > 0 ? $" 或 {_config.ExemptIntegral}积分" : "");
if (_config.ChatOutType.Equals("Broad", StringComparison.OrdinalIgnoreCase))
{
SendRconCommand($"Broadcast {reply}");
}
else if (_config.ChatOutType.Equals("War", StringComparison.OrdinalIgnoreCase))
{
SendRconCommand($"AdminWarn \"{chatMsg.SteamId}\" {reply}");
}
}
}
/// <summary>
/// 获取玩家总游戏时长(小时)
/// </summary>
/// <param name="steamId">玩家SteamID</param>
/// <returns>游戏时长</returns>
private double GetPlayerTotalPlayHours(string steamId)
{
try
{
using (var db = DbHelper.GetInstance())
{
// 将连接次数作为粗略估计 (每次0.5小时)
int count = db.Queryable<Log_NewPlayerConnect>()
.Where(x => x.SteamID == steamId)
.Count();
return count * 0.5;
}
}
catch
{
return 0;
}
}
/// <summary>
/// 获取玩家积分
/// </summary>
/// <param name="steamId">玩家SteamID</param>
/// <returns>当前积分</returns>
private int GetPlayerIntegral(string steamId)
{
try
{
using (var db = DbHelper.GetInstance())
{
var user = db.Queryable<UserIntegral>().InSingle(steamId);
return user?.Points ?? 0;
}
}
catch
{
return 0;
}
}
/// <summary>
/// 尝试扣除积分
/// </summary>
/// <param name="steamId">玩家SteamID</param>
/// <param name="amount">扣除数量</param>
/// <returns>是否扣除成功</returns>
private bool TryDeductIntegral(string steamId, int amount)
{
try
{
using (var db = DbHelper.GetInstance())
{
var user = db.Queryable<UserIntegral>().InSingle(steamId);
if (user != null && user.Points >= amount)
{
user.Points -= amount;
user.LastUpdated = DateTime.UtcNow;
db.Updateable(user).ExecuteCommand();
return true;
}
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] 积分扣除失败: {ex.Message}");
}
return false;
}
/// <summary>
/// 发送RCON命令
/// </summary>
/// <param name="cmd">命令内容</param>
private async void SendRconCommand(string cmd)
{
if (Program.Client != null && Program.Client.IsConnected)
{
await Program.Client.SendCommandAsync(cmd);
}
}
}
public class SquadCreationAnnounceConfig
{
/// <summary>
/// 开启此插件
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// 查询建队时间
/// </summary>
public string LikeKey { get; set; } = "建队时间";
/// <summary>
/// 收到建队推送是否发黄字
/// </summary>
public bool RCONOut { get; set; } = false;
/// <summary>
/// 是否允许手动查询
/// </summary>
public bool ChatQuery { get; set; } = false;
/// <summary>
/// 玩家手动查询时播报方式【Broad黄字】【War警告】
/// </summary>
public string ChatOutType { get; set; } = "Broad";
/// <summary>
/// 设置最小建队时长限制 0为关闭功能 不为1时代表建队所需最小时长例如300
/// </summary>
public int MinRunHour { get; set; } = 300;
/// <summary>
/// 不启用的地图关键字默认seed和巴士拉关闭 每个地图英文逗号间隔 只需要地图部分名称即可不需要全部名称
/// </summary>
public string Ignore { get; set; } = "seed,basrah";
/// <summary>
/// 玩家建队最大尝试次数(防止玩家无限建队导致卡很多白字) 超出次数直接Kick出服务器
/// </summary>
public int MaxTry { get; set; } = 10;
/// <summary>
/// 不满足建队要求时使用积分抵扣【0不开启】【50需要消耗积分数正整数】
/// </summary>
public int ExemptIntegral { get; set; } = 0;
}
}