using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using RainOpsMini; using RainOpsMini.Helpers; using RainOpsMini.Helpers.RCON; using RainOpsMini.Models; namespace RainOpsMini.Plugins { /// /// 自动队伍平衡插件 /// public class TeamBalancePlugin : BackgroundService, IPlugin { /// /// 插件名称 /// public string Name => "自动队伍平衡"; /// /// 插件描述 /// public string Description => "当两队人数差大于设定值时,自动将优势方最后加入的玩家切换到劣势方"; /// /// 插件分类 /// public PluginCategory Category => PluginCategory.BasicFunction; /// /// 是否启用 /// public bool IsEnabled => _config.Enabled; private TeamBalanceConfig _config = new TeamBalanceConfig(); private readonly Dictionary _playerJoinTimes = new Dictionary(); // 冷却时间,防止频繁切换或反复切换同一人 private DateTime _lastSwitchTime = DateTime.MinValue; /// /// 自动队伍平衡配置类 /// public class TeamBalanceConfig { /// /// 是否启用插件 /// public bool Enabled { get; set; } = false; /// /// 检查间隔(秒) /// public int CheckIntervalSeconds { get; set; } = 30; /// /// 触发平衡的人数差阈值 /// public int PlayerCountDifferenceThreshold { get; set; } = 3; /// /// 白名单(不被自动切换的玩家SteamID) /// public List WhitelistSteamIds { get; set; } = new List(); /// /// 切换前是否发送警告/通知 /// public bool BroadcastOnSwitch { get; set; } = true; /// /// 切换时的通知消息 /// public string SwitchMessage { get; set; } = "由于队伍人数不平衡,您已被自动切换到另一方。"; } /// /// 获取当前配置 /// /// 配置对象 public object GetConfig() => _config; /// /// 重置配置为默认值 /// public void ResetConfig() { _config = new TeamBalanceConfig(); } /// /// 更新配置 /// /// JSON配置元素 public void UpdateConfig(JsonElement config) { try { var options = new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNameCaseInsensitive = true }; _config = JsonSerializer.Deserialize(config.GetRawText(), options) ?? new TeamBalanceConfig(); } catch (Exception ex) { RainOpsLog.Log($"[TeamBalance] 配置更新失败: {ex.Message}"); } } /// /// 后台任务执行方法 /// /// 取消令牌 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { if (_config.Enabled && Program.Client != null && Program.Client.IsConnected) { await CheckAndBalanceTeams(); } } catch (Exception ex) { RainOpsLog.Log($"[TeamBalance] Error: {ex.Message}"); } await Task.Delay(TimeSpan.FromSeconds(_config.CheckIntervalSeconds), stoppingToken); } } /// /// 检查并平衡队伍 /// private async Task CheckAndBalanceTeams() { // Rule 4: 完全拷贝一份数据 List players; lock (Program.RconCache) // 这里的lock可能不是必须的,因为RconCache.Players也是替换操作,但为了安全起见 { var cachePlayers = Program.RconCache.Players; if (cachePlayers == null) return; players = new List(cachePlayers); } if (players.Count == 0) return; // 更新加入时间 var currentSteamIds = players.Select(p => p.SteamId).ToHashSet(); // 移除离开的玩家 var leftPlayers = _playerJoinTimes.Keys.Where(k => !currentSteamIds.Contains(k)).ToList(); foreach (var left in leftPlayers) { _playerJoinTimes.Remove(left); } // 添加新玩家 foreach (var p in players) { if (!_playerJoinTimes.ContainsKey(p.SteamId)) { _playerJoinTimes[p.SteamId] = DateTime.Now; } } // 计算队伍人数 var team1 = players.Where(p => p.TeamId == 1).ToList(); var team2 = players.Where(p => p.TeamId == 2).ToList(); int count1 = team1.Count; int count2 = team2.Count; int diff = Math.Abs(count1 - count2); if (diff > _config.PlayerCountDifferenceThreshold) { // 确定人数较多的队伍 int largerTeamId = count1 > count2 ? 1 : 2; int smallerTeamId = count1 > count2 ? 2 : 1; var largerTeamPlayers = count1 > count2 ? team1 : team2; // 寻找待切换的候选人 // 过滤白名单 var candidates = largerTeamPlayers .Where(p => !_config.WhitelistSteamIds.Contains(p.SteamId)) .ToList(); if (candidates.Count > 0) { // 按加入时间倒序排序(最新加入的优先) // 如果加入时间相同(如初始加载),使用ID倒序作为打破平局的依据(假设ID越大越新) var target = candidates .OrderByDescending(p => _playerJoinTimes.TryGetValue(p.SteamId, out var time) ? time : DateTime.MinValue) .ThenByDescending(p => p.Id) .FirstOrDefault(); if (target != null) { await PerformSwitch(target, smallerTeamId); } } } } /// /// 执行玩家切换队伍操作 /// /// 目标玩家 /// 目标队伍ID private async Task PerformSwitch(PlayerInfo player, int targetTeamId) { if (_config.BroadcastOnSwitch) { // 通知玩家 // AdminWarn string warnCmd = $"AdminWarn \"{player.SteamId}\" {_config.SwitchMessage}"; await Program.Client.SendCommandAsync(warnCmd); // 可选:全服广播 // string broadcastCmd = $"AdminBroadcast balanced {player.Name} to Team {targetTeamId}"; // await Program.Client.SendCommandAsync(broadcastCmd); } // 执行切换命令 // Command: AdminForceTeamChange // 使用 SteamId 更安全 string command = $"AdminForceTeamChange \"{player.SteamId}\""; RainOpsLog.Log($"[TeamBalance] Switching player {player.Name} ({player.SteamId}) to Team {targetTeamId}"); await Program.Client.SendCommandAsync(command); } } }