class SpawnManager extends Actor; var private const int canBuildNodesAtOnce; var private const int canBuildSpawnersAtOnce; var public int nodesBuilt;//for building nodes only var public int spawnersBuilt;//for building nodes only var private bool isInitializing; var private int initializationStage; var public array cachedZombieVolumes; var public array cachedTriggers; var public array cachedPawns; var public array cachedEyes; struct Node { var public NavigationPoint point; var public array adjacentNodesIndicies; var public array adjacentNodesDistances; var public array connectedVolumesIndicies; var public array connectedDoors; var public bool populated; }; var public array allNodes; var public int depopulationIndex; var public int populatedSigIndex; struct Spawner { var public ZombieVolume volume; var public int connectedNodeIndex; var public bool active; var public array checkPositions; }; var public array allSpawners; var public int counter; public function Initialize() { isInitializing = true; initializationStage = 0; Log("PHASE #1"); StopWatch(false); CacheNodes(); CacheZombieVolumes(); CacheUseTriggers(); StopWatch(true); KFGameType(level.game).bDisableZedSpawning = true; } public function /*function*/ ReportConnectedSpawners() { local int i; for (i = 0; i < allNodes.length; i += 1) { Log("Node #" $ i $ " has " $ allNodes[i].connectedVolumesIndicies.length $ " conencted ZVs!"); } } public function Tick(float delta) { local int i; if (isInitializing) { HandleInitialization(); return; } CacheKFPawns(); //StopWatch(false); for (i = 0; i < cachedPawns.length; i += 1) { DisableVisibleZombieVolumes(cachedPawns[i]); } CacheEyes(); DepopulateNodes(); EnableHiddenZombieVolumes(); //StopWatch(true); /*counter += 1; if (counter % 60 == 0) { LogActiveZV(); }*/ } private function LogActiveZV() { local int i, j; local int scount; local string s; for (i = 0; i < allSpawners.length; i += 1) { if (allSpawners[i].active) { scount += 1; } else { Log("Disabled spawner has populated point tho:" @ allNodes[allSpawners[i].connectedNodeIndex].populated); Log("Also" @ allSpawners[i].volume.disabledWaveNums.length); Log("But" @ allSpawners[i].volume.bVolumeIsEnabled); } } Log("Active spawners right now:" @ scount); scount = 0; for (i = 0; i < allNodes.length; i += 1) { if (allNodes[i].populated) { scount += 1; } else { s = "Node" @ i @ "is not populated, but:"; for (j = 0; j < allNodes[i].adjacentNodesIndicies.length; j += 1) { s = s @ allNodes[i].adjacentNodesIndicies[j] $ ", "; } Log(s); } } Log("Populated nodes right now:" @ scount); } private function DepopulateNodes() { local int i, j; local int nodesPerUpdate; local Vector testPosition; nodesPerUpdate = allNodes.length / 30; while (i < nodesPerUpdate) { testPosition = allNodes[depopulationIndex].point.location; for (j = 0; j < cachedEyes.length; j += 1) { if ( allNodes[depopulationIndex].populated && FastTrace(testPosition, cachedEyes[j])) { allNodes[depopulationIndex].populated = false; break; } } i += 1; depopulationIndex += 1; if (depopulationIndex >= allNodes.length) { depopulationIndex = 0; } } } private function PopulateActiveSpawnerNodes() { local int i; for (i = 0; i < allSpawners.length; i += 1) { if (allSpawners[i].active) { allSpawners[i].volume.bVolumeIsEnabled = true; allNodes[allSpawners[i].connectedNodeIndex].populated = true; } } } private function CacheEyes() { local int i; cachedEyes.length = 0; for (i = 0; i < cachedPawns.length; i += 1) { cachedEyes[i] = cachedPawns[i].location + cachedPawns[i].EyePosition(); } } private function EnableHiddenZombieVolumes() { local int i; local int nodesPerUpdate; nodesPerUpdate = allNodes.length / 29; while (i < nodesPerUpdate) { PropagateEnableSignal(populatedSigIndex); i += 1; populatedSigIndex += 1; if (populatedSigIndex >= allNodes.length) { populatedSigIndex = 0; break; } } if (populatedSigIndex == 0 ) { PopulateActiveSpawnerNodes(); } } private function PropagateEnableSignal(int nodeIndex) { local bool canPropogate; local bool allDoorsLocked; local array doors; local int i, j, otherIndex, spawnerIndex; local Vector testPosition; local array adjacentIdx, spawnerIdx; if (!allNodes[nodeIndex].populated) { return; } doors = allNodes[nodeIndex].connectedDoors; if (doors.length > 0) { allDoorsLocked = true; } // TODO: might need to open doors if such spawner is used... for (i = 0; i < doors.length; i += 1) { if (doors[i].bDoorIsDead || !doors[i].bSealed) { allDoorsLocked = false; break; } } if (allDoorsLocked) { return; } testPosition = allNodes[nodeIndex].point.location; for (j = 0; j < cachedEyes.length; j += 1) { if (FastTrace(testPosition, cachedEyes[j])) { allNodes[nodeIndex].populated = false; return; } } adjacentIdx = allNodes[nodeIndex].adjacentNodesIndicies; for (i = 0; i < adjacentIdx.length; i += 1) { canPropogate = true; otherIndex = adjacentIdx[i]; testPosition = allNodes[otherIndex].point.location; for (j = 0; j < cachedEyes.length; j += 1) { if (FastTrace(testPosition, cachedEyes[j])) { canPropogate = false; break; } } testPosition = (testPosition + allNodes[nodeIndex].point.location) * 0.5; for (j = 0; j < cachedEyes.length; j += 1) { if (FastTrace(testPosition, cachedEyes[j])) { canPropogate = false; break; } } allNodes[otherIndex].populated = true; spawnerIdx = allNodes[otherIndex].connectedVolumesIndicies; for (j = 0; j < spawnerIdx.length; j += 1) { spawnerIndex = spawnerIdx[j]; allSpawners[spawnerIndex].active = true; allSpawners[spawnerIndex].volume.bVolumeIsEnabled = true; } } } private function DisableVisibleZombieVolumes(KFHumanPawn pawn) { local int i, j; local bool doDisable; local bool testFog; local float spawnZedHeight; local float distance, fogDistance; local int spawnersAmount, spawnPointsAmount; local Vector testPosition, pawnEyePosition; local array spawnPositions; if (pawn == none) { return; } spawnZedHeight = class'ZombieFleshPoundBase'.default.collisionRadius * 1.25; pawnEyePosition = pawn.location + pawn.EyePosition(); testFog = pawn.region.zone.bDistanceFog; fogDistance = FMax(0.0, pawn.region.zone.distanceFogEnd); spawnersAmount = allSpawners.length; for (i = 0; i < spawnersAmount; i += 1) { doDisable = false; spawnPositions = allSpawners[i].checkPositions; spawnPointsAmount = spawnPositions.length; for (j = 0; j < spawnPositions.length; j += 1) { testPosition = spawnPositions[j]; if (testFog) { distance = VSize(testPosition - pawn.location); } if (testFog && distance >= fogDistance) continue; if (!FastTrace(testPosition, pawnEyePosition)) continue; testPosition.z += spawnZedHeight; if (!FastTrace(testPosition, pawnEyePosition)) continue; doDisable = true; break; } if (doDisable) { allSpawners[i].active = false; } allSpawners[i].volume.bVolumeIsEnabled = allSpawners[i].active; } } private function HandleInitialization() { if (initializationStage == 0) { Log("PHASE #2"); StopWatch(false); if (BuildNodes()) { initializationStage += 1; } StopWatch(true); } else if (initializationStage == 1) { Log("PHASE #3"); StopWatch(false); if (BuildSpawners()) { initializationStage += 1; } StopWatch(true); } else if (initializationStage == 2) { Log("PHASE #4"); StopWatch(false); ConnectDoors(); StopWatch(true); initializationStage += 1; } else if (initializationStage == 3) { isInitializing = false; Log("RESULT"); ReportConnectedSpawners(); } } private function CacheNodes() { local Node nextNode; local NavigationPoint nextPoint; foreach AllActors(class'NavigationPoint', nextPoint) { nextNode.point = nextPoint; nextNode.populated = true; allNodes[allNodes.length] = nextNode; } } private function CacheZombieVolumes() { local ZombieVolume nextVolume; foreach AllActors(class'ZombieVolume', nextVolume) { if (!nextVolume.bVolumeIsEnabled) continue; cachedZombieVolumes[cachedZombieVolumes.length] = nextVolume; } } private function CacheUseTriggers() { local KFUseTrigger nextTrigger; foreach AllActors(class'KFUseTrigger', nextTrigger) { cachedTriggers[cachedTriggers.length] = nextTrigger; } } private function CacheKFPawns() { local KFHumanPawn nextPawn; local Controller nextController; nextController = level.controllerList; cachedPawns.length = 0; while (nextController != none) { nextPawn = KFHumanPawn(nextController.pawn); if ( nextPawn != none && nextPawn.health > 0 && nextController.bIsPlayer) { cachedPawns[cachedPawns.length] = nextPawn; } nextController = nextController.nextController; } } private function ConnectDoors() { local int i, j, k; local KFUseTrigger nextTrigger; local int nodesAmount, triggersAmount; local array nextDoors; nodesAmount = allNodes.length; nextDoors[0] = none; Log("CHECKING DOORS:" @ cachedTriggers.length); for (i = 0; i < nodesAmount; i += 1) { nextDoors.length = 0; triggersAmount = cachedTriggers.length; for (j = 0; j < triggersAmount; j += 1) { nextTrigger = cachedTriggers[j]; for (k = 0; k < nextTrigger.doorOwners.length; k += 1) { if (allNodes[i].point == nextTrigger.doorOwners[k].doorPathNode) { nextDoors[nextDoors.length] = nextTrigger.doorOwners[k]; Log("!FOUND AND ADDED!"); } } } allNodes[i].connectedDoors = nextDoors; Log("Node" @ i @ "has" @ allNodes[i].connectedDoors.length @ "doors!"); } } private function bool BuildNodes() { local int nodesDoneThisCall; local int i, j; local int nodesAmount, specsAmount; local NavigationPoint nextPoint; local array nextSpecs; local array nextIndicies; local array nextDistances; nodesAmount = allNodes.length; // To avoid possible crash on `.length = 0` in the loop nextIndicies[0] = -1; nextDistances[0] = 0; for (i = nodesBuilt; i < nodesAmount; i += 1) { nextIndicies.length = 0; nextDistances.length = 0; nextPoint = allNodes[i].point; nextSpecs = nextPoint.pathList; specsAmount = nextSpecs.length; for (j = 0; j < specsAmount; j += 1) { if (nextPoint == nextSpecs[j].start){ nextIndicies[j] = FindNodeIndex(nextSpecs[j].end); } else { nextIndicies[j] = FindNodeIndex(nextSpecs[j].start); } nextDistances[j] = VSize(nextSpecs[j].start.location - nextSpecs[j].end.location); } allNodes[i].adjacentNodesIndicies = nextIndicies; allNodes[i].adjacentNodesDistances = nextDistances; // Done too much, bail nodesDoneThisCall += 1; if (nodesDoneThisCall >= canBuildNodesAtOnce) { nodesBuilt = i + 1; return false; } } return true; } private function int FindNodeIndex(NavigationPoint point) { local int i; local int nodesAmount; nodesAmount = allNodes.length; for (i = 0; i < nodesAmount; i += 1) { if (allNodes[i].point == point) { return i; } } return -1; } private function bool BuildSpawners() { local int i, j; local int spawnersDoneThisCall; local int volumesAmount, pointsAmount; local int selectedNodeIndex; local float boxMinX, boxMaxX, boxMinY, boxMaxY, boxZ; local float nextDistance, minDistance; local Spawner nextSpawner; local ZombieVolume nextVolume; local NavigationPoint nextPoint; local array spawnPositions, checkPositions; local array volumesCopy; volumesAmount = cachedZombieVolumes.length; pointsAmount = allNodes.length; for (i = 0; spawnersBuilt < volumesAmount; spawnersBuilt += 1) { nextVolume = cachedZombieVolumes[spawnersBuilt]; selectedNodeIndex = -1; for (j = 0; j < pointsAmount; j += 1) { nextPoint = allNodes[j].point; if (!FastTrace(nextPoint.location, nextVolume.location)) { continue; } nextDistance = VSize(nextVolume.location - nextPoint.location); if (selectedNodeIndex < 0 || nextDistance < minDistance) { minDistance = nextDistance; selectedNodeIndex = j; } } if (selectedNodeIndex >= 0) { nextSpawner.volume = nextVolume; nextSpawner.connectedNodeIndex = selectedNodeIndex; nextSpawner.active = true; volumesCopy = allNodes[selectedNodeIndex].connectedVolumesIndicies; volumesCopy[volumesCopy.length] = allSpawners.length; allNodes[selectedNodeIndex].connectedVolumesIndicies = volumesCopy; spawnPositions = nextVolume.spawnPos; boxMinX = spawnPositions[0].x; boxMaxX = spawnPositions[0].x; boxMinY = spawnPositions[0].y; boxMaxY = spawnPositions[0].y; boxZ = spawnPositions[0].z; for (i = 1; i < spawnPositions.length; i += 1) { boxMinX = FMin(boxMinX, spawnPositions[i].x); boxMaxX = FMax(boxMaxX, spawnPositions[i].x); boxMinY = FMin(boxMinY, spawnPositions[i].y); boxMaxY = FMax(boxMaxY, spawnPositions[i].y); } checkPositions[0] = GetVect(boxMinX, boxMinY, boxZ); checkPositions[1] = GetVect(boxMinX, boxMaxY, boxZ); checkPositions[2] = GetVect(boxMaxX, boxMinY, boxZ); checkPositions[3] = GetVect(boxMaxX, boxMaxY, boxZ); checkPositions[4] = nextVolume.location; nextSpawner.checkPositions = checkPositions; allSpawners[allSpawners.length] = nextSpawner; } spawnersDoneThisCall += 1; if (spawnersDoneThisCall >= canBuildSpawnersAtOnce) { return false; } } return true; } public function Vector GetVect(float x, float y, float z) { local Vector result; result.x = x; result.y = y; result.z = z; return result; } // TODO: what is ZVs are so huge 5 points is not enough? defaultproperties { canBuildNodesAtOnce = 100 canBuildSpawnersAtOnce = 100 }