using astealz.SmartSpawnController.Utils; using EFT; using System; using System.Collections.Generic; using UnityEngine; namespace astealz.SmartSpawnController.Behaviors { class BotUnspawnController : MonoBehaviour { private const int playersPool = 40; private const int maxAttempts = 2; private const float updateRate = 5f; private float timer = 0; private Player localPlayer = null; private float maxDist = float.MaxValue; private float sqrMaxDist = float.MaxValue; private readonly List players = new List(playersPool); private readonly Dictionary playerCounters = new Dictionary(playersPool); private readonly List rolesToLeave = new List(Enum.GetValues(typeof(WildSpawnType)).Length) { WildSpawnType.assault, WildSpawnType.assaultGroup, WildSpawnType.cursedAssault, WildSpawnType.pmcBot }; public bool DEBUG_ENABLED => Globals.Config.Debug; public bool VERBOSE_ENABLED => Globals.Config.Verbose; public BotUnspawnController() { enabled = false; } public void SetRoles(WildSpawnType[] roles) { if (roles == null || roles.Length == 0) return; rolesToLeave.Clear(); foreach (var role in roles) { rolesToLeave.Add(role); } } public void SetMaxDist(float maxDist) { if (maxDist < 100f || maxDist > 1000f) { maxDist = 500f; Utils.Logger.Info("Maximal distance must be in range [100, 1000]"); } this.maxDist = maxDist; this.sqrMaxDist = maxDist * maxDist; } void OnEnable() { Utils.Logger.Info($"BotUnspawnController activated!"); Utils.Logger.Info($"Max distance to unspawn: {maxDist.ToString("F1")} / roles to unspawn: {string.Join(", ", rolesToLeave)}"); } void OnDisable() { Utils.Logger.Info($"BotUnspawnController deactivated!"); } void Update() { timer += Time.deltaTime; if (timer < updateRate) return; timer = 0; if (localPlayer == null) localPlayer = Globals.LocalPlayer; if (localPlayer == null) return; players.Clear(); foreach (var player in Globals.GameWorld.RegisteredPlayers) { if (!player.isActiveAndEnabled || !player.IsAI) continue; players.Add(player); } foreach (var player in players) { BotOwner botOwner = player.GetBotOwner(); // we need an active bot if (botOwner.BotState != EBotState.Active) continue; string profileId = player.ProfileId; if (string.IsNullOrWhiteSpace(profileId)) { Utils.Logger.Error("Bot ProfileId is empty!"); continue; } // don't bother those who already wanna leave (game logic, especially when cultists are spawned) if (botOwner.IsWannaLeave()) continue; //var role = botOwner.Profile.Info.Settings.Role; WildSpawnType role = botOwner.Profile.GetRole(); // filter by bot role if (false == rolesToLeave.Contains(role)) continue; // calc distance var sqrDist = (botOwner.Transform.position - localPlayer.Transform.position).sqrMagnitude; // bot has 'maxAttempts' attempts to get closer to local player int playerCounter = maxAttempts; if (!playerCounters.TryGetValue(profileId, out playerCounter)) playerCounters.Add(profileId, playerCounter); if (sqrDist > sqrMaxDist) // and minus one playerCounter -= 1; else if (playerCounter < maxAttempts) // oh, he's trying... playerCounter += 1; if (playerCounter > 0) { playerCounters[profileId] = playerCounter; // next continue; } // he didn't make it... BotZone botZone = botOwner.GetBotZone(); string zone = botZone != null ? botZone.NameZone : null; if (string.IsNullOrWhiteSpace(zone)) zone = "unknown"; if (VERBOSE_ENABLED) Utils.Logger.Verbose($"[{role}] '{botOwner.Profile.GetNickname().TransliterateThis()}' is leaving zone '{zone}'"); // remove bot from dictionary playerCounters.Remove(profileId); // despawning player.KillMe(EBodyPart.Head, 999f); player.PlayerBody.PlayerBones.transform.position += Vector3.down * 100f; } } } }