rott/kf_sources/TehSpawns/Classes/SpawnManager.uc
2026-07-14 20:27:09 +07:00

559 lines
17 KiB
Ucode

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<ZombieVolume> cachedZombieVolumes;
var public array<KFUseTrigger> cachedTriggers;
var public array<KFHumanPawn> cachedPawns;
var public array<Vector> cachedEyes;
struct Node
{
var public NavigationPoint point;
var public array<int> adjacentNodesIndicies;
var public array<float> adjacentNodesDistances;
var public array<int> connectedVolumesIndicies;
var public array<KFDoorMover> connectedDoors;
var public bool populated;
};
var public array<Node> 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<Vector> checkPositions;
};
var public array<Spawner> 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<KFDoorMover> doors;
local int i, j, otherIndex, spawnerIndex;
local Vector testPosition;
local array<int> 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<Vector> 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<KFDoorMover> 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<ReachSpec> nextSpecs;
local array<int> nextIndicies;
local array<float> 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<Vector> spawnPositions, checkPositions;
local array<int> 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
}