using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Handlers; using LabApi.Features; using LabApi.Features.Wrappers; using LabApi.Loader.Features.Plugins; using MEC; using PlayerRoles; using UnityEngine; using Logger = LabApi.Features.Console.Logger; using Random = UnityEngine.Random; using Version = System.Version; namespace AfkSwap; public class AfkSwap : Plugin { private const float AfkTimeLimit = 60; // 1 minute in seconds private readonly Dictionary _afkPlayers = new(); private readonly object _lock = new(); private readonly Dictionary _playerPositions = new(); private readonly Dictionary _playerSpawnTimes = new(); public override string Name => "AfkSwap"; public override string Author => "Code002Lover"; public override Version Version { get; } = new(1, 0, 0); public override string Description => "Swaps AFK players with spectators after one minute."; public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion); public override void Enable() { PlayerEvents.Spawned += OnPlayerSpawned; Timing.RunCoroutine(CheckAfkPlayers()); } public override void Disable() { PlayerEvents.Spawned -= OnPlayerSpawned; lock (_lock) { _playerSpawnTimes.Clear(); _playerPositions.Clear(); _afkPlayers.Clear(); } } private void OnPlayerSpawned(PlayerSpawnedEventArgs ev) { var player = ev.Player; Timing.CallDelayed(1, () => { lock (_lock) { _playerSpawnTimes[player] = DateTime.Now; _playerPositions[player] = player.Position; _afkPlayers[player] = DateTime.Now; } Logger.Debug($"Player {player.DisplayName} spawned"); }); } private IEnumerator CheckAfkPlayers() { Logger.Debug("Starting Afk Checking"); while (true) { lock (_lock) { foreach (var playerTime in _playerSpawnTimes.ToList().Where(playerTime => (DateTime.Now - playerTime.Value).TotalSeconds >= AfkTimeLimit)) { if (playerTime.Key.Role is RoleTypeId.Spectator or RoleTypeId.Destroyed or RoleTypeId.Overwatch or RoleTypeId.Tutorial) { _playerSpawnTimes.Remove(playerTime.Key); _playerPositions.Remove(playerTime.Key); continue; } if (!_playerPositions[playerTime.Key].Equals(playerTime.Key.Position)) { _playerSpawnTimes.Remove(playerTime.Key); _playerPositions.Remove(playerTime.Key); continue; // Player has moved, don't swap } _afkPlayers[playerTime.Key] = DateTime.Now; SwapWithSpectator(playerTime.Key); } } yield return Timing.WaitForSeconds(1); } // ReSharper disable once IteratorNeverReturns } private void SwapWithSpectator(Player afkPlayer) { var spectators = Player.ReadyList .Where(p => p.Role == RoleTypeId.Spectator && (DateTime.Now - _afkPlayers[p]).TotalSeconds > 10).ToList(); if (!spectators.Any()) { Logger.Warn("No spectators to swap to"); return; } var randomSpectator = spectators[Random.Range(0, spectators.Count)]; Logger.Debug($"Swapping {afkPlayer.DisplayName} with {randomSpectator.DisplayName}"); // Store the AFK player's position and role var afkPosition = afkPlayer.Position; var afkRole = afkPlayer.Role; // Give the spectator the AFK player's role and position randomSpectator.Role = afkRole; randomSpectator.Position = afkPosition; // Make the AFK player a spectator afkPlayer.Role = RoleTypeId.Spectator; // Remove the AFK player from tracking _playerSpawnTimes.Remove(afkPlayer); _playerPositions.Remove(afkPlayer); _playerSpawnTimes[randomSpectator] = DateTime.Now; _playerPositions[randomSpectator] = randomSpectator.Position; // Broadcast the swap afkPlayer.SendBroadcast($"You were swapped with {randomSpectator.DisplayName} due to inactivity.", 10); randomSpectator.SendBroadcast($"You were swapped with {afkPlayer.DisplayName} due to them being AFK.", 5); } }