Prepare fixtures

This commit is contained in:
dkanus 2026-07-14 20:27:09 +07:00
commit 9c94356263
6021 changed files with 722805 additions and 22 deletions

View file

@ -0,0 +1,137 @@
class ReportInteraction extends Interaction;
event NotifyLevelChange()
{
if (master != none) {
master.RemoveInteraction(self);
}
}
final private function KFPlayerController getController()
{
return KFPlayerController(viewportOwner.actor);
}
exec function mySp(string s)
{
local bool b;
if (s ~= "1") {
b = true;
}
ShowPathNodes(b);
ShowPaths(b);
}
simulated function ShowPathNodes(bool b)
{
local NavigationPoint np;
local int i, j;
if (b)
{
foreach GetController().AllActors(class'NavigationPoint', np)
{
if (np == none)
continue;
np.bHidden = false;
np.resetStaticFilterState();
j++;
i++;
}
}
else
{
foreach GetController().AllActors(class'NavigationPoint', np)
{
if (np == none)
continue;
np.bHidden = true;
np.resetStaticFilterState();
}
}
}
// show zed paths:
// Orange - Flying path, narrow.
// Light Orange - Flying path, wide.
// Light Purple - requires high jump (higher than normal jump capability).
// Blue - narrow path.
// Green - normal width.
// White - wide path.
// Pink - very wide path.
// Yellow - forced path.
// Purple - "advanced" path (requires "intelligence" to be used).
// Red - "one-way" path (i.e. path going from node A->B, but B->A is proscribed).
final function ShowPaths(bool b)
{
local KFPlayerController pc;
local NavigationPoint np;
local array<NavigationPoint> np_array;
local ReachSpec Spec;
local vector SpecColor;
local int i, j;
local int debugLinesDrawn;
pc = getController();
if (!b)
{
pc.ClearStayingDebugLines();
return;
}
foreach GetController().AllActors(class'NavigationPoint', np)
{
if (np == none)
continue;
np_array.length = i + 1;
np_array[i] = np;
i++;
}
for (i = np_array.length - 1; i >= 0; i--)
{
for (j = 0; j < np_array[i].PathList.Length; j++)
{
Spec = np_array[i].PathList[j];
SpecColor = vect(0, 255, 0);
// 260 = MAXVEHICLERADIUS 80 = MINCOMMONHEIGHT
if (( Spec.CollisionRadius >= 260 ) && (Spec.CollisionHeight >= 80))
{
SpecColor = vect( 255,255,255 );
}
// Jump spec
else if( ( Spec.reachFlags & 8 ) == Spec.reachFlags )
{
SpecColor = vect( 191, 165, 99 );
}
// Proscribed
else if( ( Spec.reachFlags & 128 ) == Spec.reachFlags )
{
SpecColor = vect( 255, 0, 0 );
}
// Forced
else if( ( Spec.reachFlags & 256 ) == Spec.reachFlags )
{
SpecColor = vect( 255, 255, 0 );
}
// Blue (narrow)
else if( ( Spec.CollisionRadius < 72 || Spec.CollisionHeight < 80 ) )
{
SpecColor = vect( 0, 0, 255 );
}
pc.DrawStayingDebugLine(np_array[i].PathList[j].Start.Location, np_array[i].PathList[j].End.Location, SpecColor.X, SpecColor.Y, SpecColor.Z);
debugLinesDrawn += 1;
}
}
GetController().ClientMessage("Path segments amount is:" @ debugLinesDrawn);
}
defaultproperties
{
bVisible = true
}

View file

@ -0,0 +1,559 @@
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
}

View file

@ -0,0 +1,135 @@
class TehSpawnsMut extends Mutator;
var bool interactionAdded;
function Mutate(string mutateString, PlayerController player)
{
local SpawnManager manager;
if (mutateString ~= "pathinfo") {
ShowMeInfo(player);
}
if (mutateString ~= "doit")
{
manager = Spawn(class'SpawnManager');
manager.Initialize();
}
if (mutateString ~= "predoit") {
DoIt(player, false);
}
if (mutateString ~= "predoit2") {
DoIt(player, true);
}
if (mutateString ~= "check") {
CheckSin(player);
}
if (nextMutator != none) {
nextMutator.Mutate(mutateString, player);
}
}
function ShowMeInfo(PlayerController player)
{
local int nodeCount, volumeCount;
local ZombieVolume nextVolume;
local NavigationPoint nextNode;
StopWatch(false);
foreach AllActors(class'NavigationPoint', nextNode) {
nodeCount += 1;
}
StopWatch(true);
StopWatch(false);
foreach AllActors(class'ZombieVolume', nextVolume) {
volumeCount += 1;
}
StopWatch(true);
player.ClientMessage("Path nodes count:" @ nodeCount);
player.ClientMessage("`ZomvieVolume`s count:" @ volumeCount);
}
function CheckSin(PlayerController player)
{
local int i;
StopWatch(false);
for (i = 0; i < 1000; i += 1) {
sin(float(i));
}
StopWatch(true);
}
function doit(PlayerController player, bool nav)
{
local int iter;
local int totalZV, fullZV;
local float perc;
local float distance, maxDistance;
local NavigationPoint nextPoint, furthestPoint, selectedPoint;
local ZombieVolume nextVolume, isolatedVolume;
maxDistance = -1;
StopWatch(false);
foreach AllActors(class'ZombieVolume', nextVolume)
{
distance = 1000000;
selectedPoint = none;
foreach AllActors(class'NavigationPoint', nextPoint)
{
iter += 1;
if (VSize(nextPoint.location - nextVolume.location) < distance)
{
if (!FastTrace(nextPoint.location, nextVolume.location)) {
continue;
}
distance = VSize(nextPoint.location - nextVolume.location);
selectedPoint = nextPoint;
}
}
totalZV += 1;
if (selectedPoint != none && nextVolume.Encompasses(selectedPoint)) {
fullZV += 1;
}
//Log("Min distance:" @ distance);
if (maxDistance < distance && selectedPoint != none)
{
maxDistance = distance;
isolatedVolume = nextVolume;
furthestPoint = selectedPoint;
}
}
StopWatch(true);
perc = (float(fullZV) / float(totalZV)) * 100;
player.ClientMessage("Max ditance is:" @ maxDistance);
player.ClientMessage("Iterations done:" @ iter);
player.ClientMessage("Full volume percentage:" @ perc);
if (nav) {
player.SetLocation(furthestPoint.location);
}
else {
player.SetLocation(isolatedVolume.location);
}
}
simulated function Tick(float delta)
{
local ReportInteraction myInteraction;
local Player localPlayer;
if (role == Role_AUTHORITY) return;
if (interactionAdded) return;
localPlayer = level.GetLocalPlayerController().player;
if (localPlayer == none) return;
myInteraction = ReportInteraction(localPlayer.interactionMaster
.AddInteraction("TehSpawns.ReportInteraction", localPlayer));
if (myInteraction != none) {
interactionAdded = true;
}
}
defaultproperties
{
// Mutator description
GroupName = "Test mutators"
FriendlyName = "Zed spawner mutator"
Description = "Mutator that provides clients with information about how many zeds (and their spawn rate) are present on the map."
bAddToServerPackages = true
bAlwaysRelevant = true
RemoteRole = ROLE_SimulatedProxy
}

View file

@ -0,0 +1,9 @@
<html>
<head><title>Index of /kf_sources/TehSpawns/Classes/</title></head>
<body>
<h1>Index of /kf_sources/TehSpawns/Classes/</h1><hr><pre><a href="../">../</a>
<a href="ReportInteraction.uc">ReportInteraction.uc</a> 18-Jan-2022 20:46 3201
<a href="SpawnManager.uc">SpawnManager.uc</a> 20-Jan-2022 20:48 17485
<a href="TehSpawnsMut.uc">TehSpawnsMut.uc</a> 19-Jan-2022 19:01 4143
</pre><hr></body>
</html>