using System.Collections.Generic; using System.Diagnostics; namespace Harmony.AIWanderingHordeSpawnerPatches { [HarmonyPatch(typeof(AIDirectorWanderingHordeComponent))] [HarmonyPatch("StartSpawning")] public class StartSpawningPatch { public static bool Prefix(AIDirectorWanderingHordeComponent __instance, AIWanderingHordeSpawner.SpawnType _spawnType) { RebirthVariables.isWHSpawned = false; RebirthVariables.stopWHSpawns = false; RebirthVariables.herdStartTime = Time.time; float optionWanderingHordeHerdProb = float.Parse(RebirthVariables.customWanderingHordeHerdProb); //Log.Out("AIWanderingHordeSpawnerPatches-StartSpawning optionWanderingHordeHerdProb: " + optionWanderingHordeHerdProb); int random = UnityEngine.Random.Range(1, 100); //Log.Out("AIWanderingHordeSpawnerPatches-StartSpawning random: " + random); RebirthVariables.isHerd = random <= optionWanderingHordeHerdProb; if (RebirthVariables.forceHerd) { RebirthVariables.isHerd = true; RebirthVariables.forceHerd = false; } int optionWanderingHordeHerdDur = RebirthVariables.customWanderingHordeHerdDur; RebirthVariables.herdDuration = UnityEngine.Random.Range((optionWanderingHordeHerdDur * 60) - 30, (optionWanderingHordeHerdDur * 60) + 30); // START OF VANILLA CODE AIDirector.LogAI("Wandering StartSpawning {0}", (object)_spawnType); __instance.CleanupType(_spawnType); bool flag = false; DictionaryList trackedPlayers = __instance.Director.GetComponent().trackedPlayers; for (int index = 0; index < trackedPlayers.list.Count; ++index) { if (!trackedPlayers.list[index].Dead) { flag = true; break; } } if (!flag) { AIDirector.LogAI("Spawn {0}, no living players, wait 4 hours", (object)_spawnType); __instance.SetNextTime(_spawnType, __instance.Director.World.worldTime + 4000UL); } else { List directorPlayerStateList = new List(); Vector3 startPos; Vector3 pitStop; Vector3 endPos; uint targets = __instance.FindTargets(out startPos, out pitStop, out endPos, directorPlayerStateList); if (targets > 0U) { AIDirector.LogAI("Spawn {0}, find targets, wait {1} hours", (object)_spawnType, (object)targets); __instance.SetNextTime(_spawnType, __instance.Director.World.worldTime + (ulong)(1000U * targets)); } else { int searchDistance = 150; List entitiesInBounds = GameManager.Instance.World.GetEntitiesInBounds(typeof(EntityZombieScreamerRebirth), BoundsUtils.BoundsForMinMax(startPos.x - searchDistance, startPos.y - 50, startPos.z - searchDistance, startPos.x + searchDistance, startPos.y + 50, startPos.z + searchDistance), new List()); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn NUM Screamers: " + entitiesInBounds.Count); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn RebirthVariables.customScreamerMax: " + RebirthVariables.customScreamerMax); int numActiveScreamers = 0; if (entitiesInBounds.Count >= 1) { for (int index = 0; index < entitiesInBounds.Count; ++index) { EntityZombieScreamerRebirth screamer = (EntityZombieScreamerRebirth)entitiesInBounds[index]; if (!screamer.IsDead()) { numActiveScreamers++; } } } if (numActiveScreamers > 0 && RebirthVariables.isHerd) { Log.Out("AIDirectorChunkEventComponentPatches-SpawnScouts CAN'T START AN OUTBREAK WHILE SCREAMERS ARE STILL ACTIVE"); RebirthVariables.isHerd = false; RebirthVariables.stopWHSpawns = true; RebirthVariables.herdDuration = 0f; } else { __instance.ChooseNextTime(_spawnType); __instance.spawners.Add(new AIWanderingHordeSpawner(__instance.Director, _spawnType, (AIWanderingHordeSpawner.HordeArrivedDelegate)null, directorPlayerStateList, __instance.Director.World.worldTime + 12000UL, startPos, pitStop, endPos)); } } } return false; } } [HarmonyPatch(typeof(GameStageDefinition))] [HarmonyPatch("GetStage")] public class GetStagePatch { public static bool Prefix(GameStageDefinition __instance, ref GameStageDefinition.Stage __result, int stage) { if (new StackTrace().FrameCount > 5) { MethodBase caller = new StackTrace().GetFrame(5).GetMethod(); string callerClassName = caller.ReflectedType.Name; if (callerClassName != "AIWanderingHordeSpawner") { return true; } //Log.Out("AIDirectorGameStagePartySpawnerPatches-GetStage stage: " + stage); //Log.Out("AIDirectorGameStagePartySpawnerPatches-GetStage callerClassName: " + callerClassName); if (__instance.stages.Count < 1) { //Log.Out("AIDirectorGameStagePartySpawnerPatches-GetStage NO STAGES"); __result = (GameStageDefinition.Stage)null; return false; } } //Log.Out("AIDirectorGameStagePartySpawnerPatches-GetStage __instance.stages[0].stageNum: " + __instance.stages[0].stageNum); // Set the result to the same value to keep the spawning consistent __result = __instance.stages[0]; return false; } } [HarmonyPatch(typeof(AIDirectorGameStagePartySpawner))] [HarmonyPatch("Tick")] public class TickPatch { public static bool Prefix(AIDirectorGameStagePartySpawner __instance, ref bool __result, double _deltaTime ) { if (__instance.spawnGroup != null) { bool flag = true; if (__instance.spawnCount >= __instance.numToSpawn) { __instance.interval -= _deltaTime; flag = __instance.interval <= 0.0; } if (flag) { //Log.Out("AIDirectorGameStagePartySpawner-Tick: " + __instance.ToString()); __instance.SetupGroup(); } } __result = __instance.spawnGroup != null; return false; } } [HarmonyPatch(typeof(AIWanderingHordeSpawner))] [HarmonyPatch("UpdateHorde")] public class UpdateHordePatch { public static bool Prefix(AIWanderingHordeSpawner __instance, float dt) { int index = 0; while (index < __instance.commandList.Count) { AIWanderingHordeSpawner.ZombieCommand command = __instance.commandList[index]; bool hasAttackTaget = false; // command.Enemy.GetAttackTarget() != null; bool hasOther = false; bool flag = command.Enemy.IsDead() || hasAttackTaget; if (!flag) { if (command.Command == AIWanderingHordeSpawner.ECommand.PitStop || command.Command == AIWanderingHordeSpawner.ECommand.EndPos) { if (command.Enemy.HasInvestigatePosition) { /*if (command.Enemy.InvestigatePosition != command.TargetPos) { flag = true; AIDirector.LogAIExtra("Wandering horde zombie '" + command.Enemy?.ToString() + "' removed from horde control. Was killed or investigating"); } else*/ { command.Enemy.SetInvestigatePosition(command.TargetPos, 6000, false); } } else if (command.Command == AIWanderingHordeSpawner.ECommand.PitStop) { AIDirector.LogAIExtra("Wandering horde zombie '" + command.Enemy?.ToString() + "' reached pitstop. Wander around for awhile"); command.WanderTime = (float)(90.0 + (double)__instance.director.random.RandomFloat * 4.0); command.Command = AIWanderingHordeSpawner.ECommand.Wander; } else { //command.Enemy.SetInvestigatePosition(command.TargetPos, 6000, false); /*Log.Out("AIWanderingHordeSpawner-UpdateHorde command.Command: " + command.Command + " / " + command.Enemy?.ToString()); hasOther = true; flag = true;*/ } } else { command.WanderTime -= dt; command.Enemy.ResetDespawnTime(); if ((double)command.WanderTime <= 0.0 && command.Enemy.GetAttackTarget() == null) { AIDirector.LogAIExtra("Wandering horde zombie '" + command.Enemy?.ToString() + "' wandered long enough. Going to endstop"); command.Command = AIWanderingHordeSpawner.ECommand.EndPos; command.TargetPos = AIWanderingHordeSpawner.RandomPos(__instance.director, __instance.endPos, 6f); command.Enemy.SetInvestigatePosition(command.TargetPos, 6000, false); command.Enemy.IsHordeZombie = false; } } } if (flag) { string additionalInfo = ""; if (hasOther) { additionalInfo = " / HAS OTHER"; } if (hasAttackTaget) { additionalInfo = " / HAS ATTACK TARGET"; } AIDirector.LogAIExtra("Wandering horde zombie '" + command.Enemy?.ToString() + "' removed from control" + additionalInfo); command.Enemy.IsHordeZombie = false; command.Enemy.bIsChunkObserver = false; __instance.commandList.RemoveAt(index); } else ++index; } return false; } } [HarmonyPatch(typeof(AIWanderingHordeSpawner))] [HarmonyPatch("Update")] public class UpdatePatch { public static bool Prefix(AIWanderingHordeSpawner __instance, ref bool __result, World world, float _deltaTime) { if (world.GetPlayers().Count == 0) { //Log.Out("AIWanderingHordeSpawnerPatches-Update NO PLAYERS"); __result = true; return false; } if (world.worldTime >= __instance.endTime) { if (__instance.arrivedCallback != null) __instance.arrivedCallback(); //Log.Out("AIWanderingHordeSpawnerPatches-Update OVER END TIME: " + __instance.endTime); __result = true; return false; } bool flag = __instance.UpdateSpawn(world, _deltaTime); //Log.Out("AIWanderingHordeSpawnerPatches-Update Wandering Horde size: " + __instance.commandList.Count); //Log.Out("AIWanderingHordeSpawnerPatches-Update Wandering Horde Total Size: " + RebirthVariables.wanderingHordeCount); if (flag) { //Log.Out("AIWanderingHordeSpawnerPatches-Update CANNOT SPAWN"); if (!RebirthVariables.isHerd) { if (__instance.commandList.Count == 0 || __instance.commandList.Count == RebirthVariables.wanderingHordeCount) { if (__instance.arrivedCallback != null) __instance.arrivedCallback(); //Log.Out("AIWanderingHordeSpawnerPatches-Update __instance.commandList.Count: " + __instance.commandList.Count); __result = true; return false; } } } else { //Log.Out("AIWanderingHordeSpawnerPatches-Update CAN SPAWN"); } if (!flag) { AstarManager.Instance.AddLocationLine(__instance.startPos, __instance.endPos, 64); } else { Vector3 zero = Vector3.zero; int num = 0; for (int index = 0; index < __instance.commandList.Count; ++index) { Entity enemy = (Entity)__instance.commandList[index].Enemy; if (!enemy.IsDead()) { zero += enemy.position; ++num; } } if (num > 0) { Vector3 pos3d = zero * (1f / (float)num); AstarManager.Instance.AddLocation(pos3d, 64); } } __instance.UpdateHorde(_deltaTime); __result = false; return false; } } [HarmonyPatch(typeof(AIWanderingHordeSpawner))] [HarmonyPatch("UpdateSpawn")] public class UpdateSpawnPatch { public static bool Prefix(AIWanderingHordeSpawner __instance, ref bool __result, World _world, float _deltaTime) { //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn RebirthVariables.isWHSpawned: " + RebirthVariables.isWHSpawned); EntityPlayer closestPlayer = _world.GetClosestPlayer(__instance.startPos, 100, false); if (closestPlayer != null) { int searchDistance = 150; List entitiesInBounds = GameManager.Instance.World.GetEntitiesInBounds(typeof(EntityZombieScreamerRebirth), BoundsUtils.BoundsForMinMax(closestPlayer.position.x - searchDistance, closestPlayer.position.y - 50, closestPlayer.position.z - searchDistance, closestPlayer.position.x + searchDistance, closestPlayer.position.y + 50, closestPlayer.position.z + searchDistance), new List()); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn NUM Screamers: " + entitiesInBounds.Count); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn RebirthVariables.customScreamerMax: " + RebirthVariables.customScreamerMax); if (entitiesInBounds.Count >= 1) { int numActiveScreamers = 0; for (int index = 0; index < entitiesInBounds.Count; ++index) { EntityZombieScreamerRebirth screamer = (EntityZombieScreamerRebirth)entitiesInBounds[index]; if (!screamer.IsDead()) { numActiveScreamers++; } } if (numActiveScreamers >= RebirthVariables.customScreamerMax) { Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn STOPPING WANDERING HORDE, TOO MANY SCREAMERS FOUND"); RebirthVariables.isHerd = false; RebirthVariables.stopWHSpawns = true; RebirthVariables.herdDuration = 0f; __instance.Cleanup(); AIDirectorWanderingHordeComponent director = GameManager.Instance.World.aiDirector.GetComponent(); if (director != null && director.spawners.Count > 0) { AIWanderingHordeSpawner spawner = director.spawners[director.spawners.Count - 1]; if (spawner != null) { director.spawners.RemoveAt(director.spawners.Count - 1); } } } } } if (!RebirthVariables.isWHSpawned) { //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn ATTEMPTING TO FIND A PLAYER"); if (closestPlayer != null) { RebirthVariables.wanderingHordeGameStage = closestPlayer.gameStage; string optionWanderingHordeHerdRestriction = RebirthVariables.customWanderingHordeHerdRestriction; if (optionWanderingHordeHerdRestriction == "low") { if (RebirthVariables.wanderingHordeGameStage >= RebirthVariables.spawnThresholdFeral) { RebirthVariables.wanderingHordeGameStage = RebirthVariables.spawnThresholdFeral; } } else if (optionWanderingHordeHerdRestriction == "medium") { if (RebirthVariables.wanderingHordeGameStage >= RebirthVariables.spawnThresholdRadiated) { RebirthVariables.wanderingHordeGameStage = RebirthVariables.spawnThresholdRadiated; } } else if (optionWanderingHordeHerdRestriction == "high") { if (RebirthVariables.wanderingHordeGameStage >= RebirthVariables.spawnThresholdTainted) { RebirthVariables.wanderingHordeGameStage = RebirthVariables.spawnThresholdTainted; } } //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn FOUND CLOSE MEMBER: " + closestPlayer.EntityName + " / gamestage: " + closestPlayer.gameStage); } RebirthVariables.stopWHSpawns = false; RebirthVariables.isWHSpawned = true; } else { if (RebirthVariables.isHerd) { float diff = Time.time - RebirthVariables.herdStartTime; //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn diff: " + diff + " / " + RebirthVariables.herdDuration); if (diff >= RebirthVariables.herdDuration || RebirthUtilities.IsHordeNight()) // 1 in-game hour (170) { //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn OVER TIME LIMIT"); RebirthVariables.isHerd = false; RebirthVariables.stopWHSpawns = true; RebirthVariables.herdDuration = 0f; __instance.Cleanup(); AIDirectorWanderingHordeComponent director = GameManager.Instance.World.aiDirector.GetComponent(); if (director != null && director.spawners.Count > 0) { AIWanderingHordeSpawner spawner = director.spawners[director.spawners.Count - 1]; if (spawner != null) { director.spawners.RemoveAt(director.spawners.Count - 1); } } } } } bool canSpawnDirector = GameStats.GetInt(EnumGameStats.EnemyCount) < GamePrefs.GetInt(EnumGamePrefs.MaxSpawnedZombies); int whSizeMultiplier = RebirthVariables.wanderingHordeMultiplier; if (canSpawnDirector) { if (__instance.commandList.Count >= whSizeMultiplier * RebirthVariables.wanderingHordeSize) { canSpawnDirector = false; } } if (!canSpawnDirector && !RebirthVariables.stopWHSpawns) { if (!RebirthVariables.isHerd) { //Log.Out("Reached wandering horde size: " + __instance.commandList.Count); RebirthVariables.stopWHSpawns = true; } } bool tick = __instance.spawner.Tick((double)_deltaTime); if (!canSpawnDirector || !tick || RebirthVariables.stopWHSpawns) { //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn CANNOT SPAWN canSpawnDirector: " + canSpawnDirector + " / tick: " + tick); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn GameStats.GetInt(EnumGameStats.EnemyCount): " + GameStats.GetInt(EnumGameStats.EnemyCount)); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn GamePrefs.GetInt(EnumGamePrefs.MaxSpawnedZombies): " + (double)GamePrefs.GetInt(EnumGamePrefs.MaxSpawnedZombies)); //Log.Out("AIWanderingHordeSpawnerPatches-UpdateSpawn RebirthVariables.stopWHSpawns: " + RebirthVariables.stopWHSpawns); __result = true; return false; } __instance.spawnDelay -= _deltaTime; if ((double)__instance.spawnDelay >= 0.0) { __result = false; return false; } __instance.spawnDelay = 1f; Vector3 _position; bool canSpawn = __instance.spawner.canSpawn; bool spawnPosition = _world.GetMobRandomSpawnPosWithWater(__instance.startPos, 1, 6, 15, true, out _position); if (!canSpawn || !spawnPosition) { __result = false; return false; } RebirthVariables.wanderingHordeCount = whSizeMultiplier * RebirthVariables.wanderingHordeSize; if (__instance.commandList.Count >= whSizeMultiplier * RebirthVariables.wanderingHordeSize) { if (!RebirthVariables.isHerd) { RebirthVariables.stopWHSpawns = true; } __result = true; return false; } if (RebirthUtilities.ScenarioSkip()) { string biomeName = RebirthUtilities.GetBiomeName(__instance.startPos); if (biomeName == "desert") { biomeName = "pine_forest"; } RebirthUtilities.SetHiveSpawnGroup(biomeName); } EntityEnemy entity = (EntityEnemy)EntityFactory.CreateEntity(EntityGroups.GetRandomFromGroup(__instance.spawner.spawnGroupName, ref __instance.lastClassId), _position); if (entity is EntityZombieSDX zombie) { zombie.wanderingHorde = 1; } if (RebirthVariables.isHerd) { entity.Buffs.SetCustomVar("$herdEntity", 1f); } _world.SpawnEntityInWorld((Entity)entity); entity.SetSpawnerSource(EnumSpawnerSource.Dynamic); entity.IsHordeZombie = true; entity.bIsChunkObserver = true; entity.IsHordeZombie = true; entity.bIsChunkObserver = true; if (++__instance.bonusLootSpawnCount >= GameStageDefinition.LootWanderingBonusEvery) { __instance.bonusLootSpawnCount = 0; entity.lootDropProb *= GameStageDefinition.LootWanderingBonusScale; } AIWanderingHordeSpawner.ZombieCommand zombieCommand = new AIWanderingHordeSpawner.ZombieCommand(); zombieCommand.Enemy = entity; zombieCommand.TargetPos = AIWanderingHordeSpawner.RandomPos(__instance.director, __instance.endPos, 6f); zombieCommand.Command = AIWanderingHordeSpawner.ECommand.EndPos; __instance.commandList.Add(zombieCommand); entity.SetInvestigatePosition(zombieCommand.TargetPos, 6000, false); AIDirector.LogAI("Spawned wandering horde (group {0}, zombie {1})", (object)__instance.spawner.spawnGroupName, (object)entity); __instance.spawner.IncSpawnCount(); __result = false; return false; } } }