559 lines
17 KiB
Ucode
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
|
|
} |