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);
}
}
}