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

1013 lines
31 KiB
Ucode

class ZombieVolume extends Volume;
struct KFDoorSelType
{
var() edfindable KFDoorMover DoorActor;
var() bool bOnlyWhenWelded;
};
var() float CanRespawnTime; // How long to save CanSpawn values before re-check
var() float TouchDisableTime; // How long time does this volume get disabled if a player touches it.
var() bool bMassiveZeds,bLeapingZeds,bNormalZeds,bRangedZeds; // The type of zombies that can spawn here.
var() float ZombieCountMulti; // Multiply zombie spawn count with this.
var() bool bVolumeIsEnabled;
var() array<int> DisabledWaveNums;
var() name ToggledEnabledEvent;
var() array< class<KFMonster> > OnlyAllowedZeds,DisallowedZeds;
var() float SpawnDesirability,MinDistanceToPlayer;
var() array<KFDoorSelType> RoomDoorsList;
var transient float LastCheckTime;
var transient bool bHasInitSpawnPoints;
var transient array<vector> SpawnPos;
var transient vector OldInitPos;
var() bool bDebugZombieSpawning; // Set this to true to view zombie spawning debug in game
var bool bDebugZoneSelection; // Set this to true to view zone selection debug in game
var bool bDebugSpawnSelection; // Set this to true to view individual zombie spawning debugging
var float LastSpawnTime; // Last time a zombie squad spawned here
var float LastFailedSpawnTime; // Last time a zombie squad failed spawning here
var() bool bNoZAxisDistPenalty; // Will ignore the Z axis distance when evaluating spawns. Helps if maps are large and hilly (wouldnt want to use this on a multi floored office building!)
/* tag to set on all Zombies spawned by this volume */
var(Events) name ZombieSpawnTag;
/* Death Event to set on all Zombies spawned by this volume */
var(Events) name ZombieDeathEvent;
/* Event to fire off every time this volume spawns any ZED*/
var(Events) name ZombieSpawnEvent;
/* array of all living Zombies spawned by this volume */
var array<KFMonster> ZEDList;
/* if true, ignore the lineofsight check in SpawnInHere() so that zombies can spawn in plain sight */
var() bool bAllowPlainSightSpawns;
/* This volume is only used for spawning in Objective Mode */
var(Advanced) bool bObjectiveModeOnly;
// Init the spawn points of this actor
function PostBeginPlay()
{
Super.PostBeginPlay();
InitSpawnPoints();
}
function Reset()
{
LastCheckTime = 0;
}
event Trigger( Actor Other, Pawn EventInstigator )
{
bVolumeIsEnabled = !bVolumeIsEnabled;
}
function NotifyNewWave( int CurWave )
{
local int i,l;
l = DisabledWaveNums.Length;
for( i=0; i<l; i++ )
if( DisabledWaveNums[i]==CurWave )
{
if( bVolumeIsEnabled )
{
bVolumeIsEnabled = False;
TriggerEvent(ToggledEnabledEvent,Self,None);
}
}
if( !bVolumeIsEnabled )
{
bVolumeIsEnabled = True;
TriggerEvent(ToggledEnabledEvent,Self,None);
}
}
// Reduces calls the CPU-heavy SpawnInHere function,
// at the cost of possibly returning occasional inaccurate values
function bool CanSpawnInHere( array< class<KFMonster> > zombies )
{
if(bObjectiveModeOnly && !Level.Game.IsA('KFStoryGameInfo'))
{
return false;
}
if( LastCheckTime < Level.TimeSeconds )
{
//LastCheckTime = Level.TimeSeconds+CanRespawnTime;
if( !bVolumeIsEnabled )
{
if(bDebugZoneSelection)
{
log("!CanSpawnInHere -> Reason = !bVolumeIsEnabled ");
}
return false;
}
if( SpawnPos.Length==0 )
{
if(bDebugZoneSelection)
{
log("!CanSpawnInHere -> Reason = SpawnPos.Length == 0 ");
}
return false; // Failed to find ANY possible spawn points.
}
return SpawnInHere(zombies,true);
}
else
{
if(bDebugZoneSelection)
{
log("!CanSpawnInHere -> Reason = LastCheckTime >= Level.TimeSeconds");
}
return false;
}
}
//Experimental volume for spawning squads of zombies.
function bool SpawnInHere( out array< class<KFMonster> > zombies, optional bool test,
optional out int numspawned, optional out int TotalMaxMonsters, optional int MaxMonstersAtOnceLeft,
optional out int TotalZombiesValue, optional bool bTryAllSpawns )
{
local int i,l,j,zc,yc;
local KFMonster Act;
local byte fl;
local rotator RandRot;
local vector TrySpawnPoint;
local int NumTries;
/* First make sure there are any zombie types allowed to spawn in here */
l = zombies.Length;
zc = DisallowedZeds.Length;
yc = OnlyAllowedZeds.Length;
for( i=0; i<l; i++ )
{
fl = zombies[i].Default.ZombieFlag;
if( (!bNormalZeds && fl==0) || (!bRangedZeds && fl==1) || (!bLeapingZeds && fl==2) || (!bMassiveZeds && fl==3) )
goto'RemoveEntry';
if( zc==0 && yc==0 )
continue;
for( j=0; j<zc; j++ )
if( ClassIsChildOf(zombies[i],DisallowedZeds[j]) )
goto'RemoveEntry';
if( yc>0 )
{
for( j=0; j<yc; j++ )
if( ClassIsChildOf(zombies[i],OnlyAllowedZeds[j]) )
goto'LoopEnd';
RemoveEntry:
zombies.Remove(i,1);
l--;
i--;
}
LoopEnd:
}
if( l==0 )
{
if(bDebugZoneSelection)
{
log("!SpawnInHere -> Reason = Zombie Squad array is empty! ");
}
return false;
}
if( !test )
{
if( ZombieCountMulti<1 )
zombies.Length = Max(zombies.Length*ZombieCountMulti,1); // Decrease the size.
else if( ZombieCountMulti>1 )
{
// Increase the size and scramble zombie spawn types.
zombies.Length = Max(zombies.Length*(ZombieCountMulti/2+ZombieCountMulti*FRand()),zombies.Length);
l = zombies.Length;
for( i=0; i<l; i++ )
if( zombies[i]==None )
zombies[i] = zombies[Rand(i)];
}
if( zombies.Length==0 )
return false;
}
/* Now do the actual spawning */
if( !test )
{
l = zombies.Length;
for( i=0; i<l; i++ )
{
if( TotalMaxMonsters>0 && MaxMonstersAtOnceLeft>0) // Always make sure we are allowed to spawn em.
{
RandRot.Yaw = Rand(65536);
if( bTryAllSpawns )
{
// Try spawning in all the points
NumTries = SpawnPos.Length;
}
else
{
// Try spawning 3 times in 3 dif points.
NumTries = 3;
}
/* We need to clear this every time. */
Act = none;
for( j=0; j<NumTries; j++ )
{
TrySpawnPoint = SpawnPos[Rand(SpawnPos.Length)];
if( !PlayerCanSeePoint(TrySpawnPoint, zombies[i]) )
{
Act = Spawn(zombies[i],,,TrySpawnPoint,RandRot);
}
else
{
if( bDebugZoneSelection )
{
log("Failed trying to spawn "$zombies[i]$" attempt "$j);
}
continue;
}
if(Act!=None)
{
break;
}
}
if(Act!=None)
{
// Triggers & Event Tracking
/* ========================================================================*/
if(ZombieSpawnTag != '')
{
Act.Tag = ZombieSpawnTag ;
}
if(ZombieDeathEvent != '')
{
Act.Event = ZombieDeathEvent;
}
if(ZombieSpawnEvent != '')
{
TriggerEvent(ZombieSpawnEvent,self,Act);
}
AddZEDToSpawnList(Act);
/*==========================================================================*/
if( bDebugSpawnSelection )
{
DrawDebugCylinder(Act.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Act.CollisionRadius,Act.CollisionHeight,5,0, 255, 0);
}
if( bDebugZoneSelection )
{
log(self@"Spawned "$zombies[i]$" on attempt "$j);
}
TotalMaxMonsters--;
MaxMonstersAtOnceLeft--;
numspawned++;
TotalZombiesValue += Act.ScoringValue;
}
else if( bDebugZoneSelection )
{
log(self@" completely failed spawning "$zombies[i]$" on attempt "$j);
}
}
}
if( numspawned>0 )
{
LastSpawnTime = Level.TimeSeconds;
LastFailedSpawnTime = 0;
return true;
}
else
{
LastFailedSpawnTime = Level.TimeSeconds;
return false;
}
}
return true;
}
//Spawn squads of zombies in story mode. Has special handling to make sure the
// whole squad gets spawned properly.
function bool StorySpawnInHere( out array< class<KFMonster> > NextSpawnSquad, optional bool test,
optional out int numspawned, optional out int TotalMaxMonsters, optional int MaxMonstersAtOnceLeft,
optional out int TotalZombiesValue, optional bool bTryAllSpawns )
{
local int i,l,j,zc,yc;
local KFMonster Act;
local byte fl;
local rotator RandRot;
local vector TrySpawnPoint;
local int NumTries;
Local array< class<KFMonster> > zombies;
//local array int RemovedIndexes;
local int k;
zombies = NextSpawnSquad;
/* First make sure there are any zombie types allowed to spawn in here */
l = zombies.Length;
zc = DisallowedZeds.Length;
yc = OnlyAllowedZeds.Length;
for( i=0; i<l; i++ )
{
fl = zombies[i].Default.ZombieFlag;
if( (!bNormalZeds && fl==0) || (!bRangedZeds && fl==1) || (!bLeapingZeds && fl==2) || (!bMassiveZeds && fl==3) )
goto'RemoveEntry';
if( zc==0 && yc==0 )
continue;
for( j=0; j<zc; j++ )
if( ClassIsChildOf(zombies[i],DisallowedZeds[j]) )
goto'RemoveEntry';
if( yc>0 )
{
for( j=0; j<yc; j++ )
if( ClassIsChildOf(zombies[i],OnlyAllowedZeds[j]) )
goto'LoopEnd';
RemoveEntry:
zombies.Remove(i,1);
l--;
i--;
}
LoopEnd:
}
if( l==0 )
{
if(bDebugZoneSelection)
{
log("!SpawnInHere -> Reason = Zombie Squad array is empty! ");
}
return false;
}
if( !test )
{
if( ZombieCountMulti<1 )
zombies.Length = Max(zombies.Length*ZombieCountMulti,1); // Decrease the size.
else if( ZombieCountMulti>1 )
{
// Increase the size and scramble zombie spawn types.
zombies.Length = Max(zombies.Length*(ZombieCountMulti/2+ZombieCountMulti*FRand()),zombies.Length);
l = zombies.Length;
for( i=0; i<l; i++ )
if( zombies[i]==None )
zombies[i] = zombies[Rand(i)];
}
if( zombies.Length==0 )
return false;
}
/* Now do the actual spawning */
if( !test )
{
l = zombies.Length;
for( i=0; i<l; i++ )
{
if( TotalMaxMonsters>0 && MaxMonstersAtOnceLeft>0) // Always make sure we are allowed to spawn em.
{
RandRot.Yaw = Rand(65536);
if( bTryAllSpawns )
{
// Try spawning in all the points
NumTries = SpawnPos.Length;
}
else
{
// Try spawning 3 times in 3 dif points.
NumTries = 3;
}
/* We need to clear this every time. */
Act = none;
for( j=0; j<NumTries; j++ )
{
TrySpawnPoint = SpawnPos[Rand(SpawnPos.Length)];
if( !PlayerCanSeePoint(TrySpawnPoint, zombies[i]) )
{
Act = Spawn(zombies[i],,,TrySpawnPoint,RandRot);
}
else
{
if( bDebugZoneSelection )
{
log("Failed trying to spawn "$zombies[i]$" attempt "$j);
}
continue;
}
if(Act!=None)
{
break;
}
}
if(Act!=None)
{
// Triggers & Event Tracking
/* ========================================================================*/
if(ZombieSpawnTag != '')
{
Act.Tag = ZombieSpawnTag ;
}
if(ZombieDeathEvent != '')
{
Act.Event = ZombieDeathEvent;
}
if(ZombieSpawnEvent != '')
{
TriggerEvent(ZombieSpawnEvent,self,Act);
}
AddZEDToSpawnList(Act);
/*==========================================================================*/
if( bDebugSpawnSelection )
{
DrawDebugCylinder(Act.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Act.CollisionRadius,Act.CollisionHeight,5,0, 255, 0);
}
if( bDebugZoneSelection )
{
log(self@"Spawned "$zombies[i]$" on attempt "$j);
}
TotalMaxMonsters--;
MaxMonstersAtOnceLeft--;
numspawned++;
TotalZombiesValue += Act.ScoringValue;
for(k=0; k < NextSpawnSquad.length ; k++)
{
if( NextSpawnSquad[k] == zombies[i] )
{
NextSpawnSquad.Remove(k, 1);
if( bDebugZoneSelection )
{
log(self@" Removed: zombie "$zombies[i]$" from NextSpawnSquad because we spawned it");
}
break;
}
}
}
else if( bDebugZoneSelection )
{
log(self@" completely failed spawning "$zombies[i]$" on attempt "$j);
}
}
}
if( numspawned>0 )
{
LastSpawnTime = Level.TimeSeconds;
LastFailedSpawnTime = 0;
return true;
}
else
{
LastFailedSpawnTime = Level.TimeSeconds;
return false;
}
}
return true;
}
/* Returns true if the supplied Monster was spawned by this volume. Using an array instead of tags for this
to give level designers greater flexibility. ie. There could be situations where you'd want your Volume to have
a different tag than the ZEDS it spawns */
function bool ZombieWasSpawnedHere(KFMonster TestZombie , optional out int SpawnIdx)
{
local int i;
for(i = 0 ; i < ZEDList.length ; i ++)
{
if(ZEDList[i] == TestZombie)
{
SpawnIdx = i ;
return true;
}
}
return false;
}
function AddZEDToSpawnList(KFMonster NewZED)
{
ZEDList.Insert(ZEDList.length,1);
ZEDList[ZEDList.length-1] = NewZED ;
NewZED.SpawnVolume = self;
}
function RemoveZEDFromSpawnList(KFMonster ExistingZED)
{
local int ZEDIndex;
if(ZombieWasSpawnedHere(ExistingZED,ZEDIndex))
{
ExistingZED.SpawnVolume = none;
ZEDList.Remove(ZEDIndex,1);
}
}
function bool PlayerCanSeePoint(vector TestLocation, class <KFMonster> TestMonster)
{
local Controller C;
local float dist;
local vector Right, Test;
local float CollRadius;
if(bAllowPlainSightSpawns)
{
return false;
}
// Now make sure no player sees the spawn point.
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if( C.Pawn!=none && C.Pawn.Health>0 && C.bIsPlayer )
{
dist = VSize(TestLocation - C.Pawn.Location);
CollRadius = TestMonster.Default.CollisionRadius;
CollRadius *= 1.1;
Right = ((TestLocation - C.Pawn.Location) cross vect(0.f,0.f,1.f));
Right = Normal(Right) * CollRadius;
Test.Z = TestMonster.Default.CollisionHeight;
Test.Z *= 1.25;
// DrawStayingDebugLine(TestLocation,C.Pawn.Location + C.Pawn.EyePosition(),255,0,0);
// DrawStayingDebugLine((TestLocation + Test) + Right,C.Pawn.Location + C.Pawn.EyePosition(),255,255,0);
// DrawStayingDebugLine((TestLocation + Test) - Right,C.Pawn.Location + C.Pawn.EyePosition(),0,0,255);
// Do three traces, one to the location, and one slightly above left and right of the collision
// cylinder size so we don't see this zed spawn
if( (!C.Pawn.Region.Zone.bDistanceFog || (dist < C.Pawn.Region.Zone.DistanceFogEnd)) &&
FastTrace(TestLocation,C.Pawn.Location + C.Pawn.EyePosition()) &&
FastTrace((TestLocation + Test) + Right,C.Pawn.Location + C.Pawn.EyePosition()) &&
FastTrace((TestLocation + Test) - Right,C.Pawn.Location + C.Pawn.EyePosition()) )
{
if( bDebugZoneSelection || bDebugSpawnSelection)
{
DrawStayingDebugLine(TestLocation + TestMonster.default.EyeHeight * vect(0,0,1),C.Pawn.Location + C.Pawn.EyePosition(),255,255,0);
}
return true;
}
else
{
if( bDebugZoneSelection || bDebugSpawnSelection)
{
DrawStayingDebugLine(TestLocation + TestMonster.default.EyeHeight * vect(0,0,1),C.Pawn.Location + C.Pawn.EyePosition(),0,255,0);
}
}
}
}
return false;
}
/* Code added by Marco, simple handler function for initilizing spawn points */
function InitSpawnPoints()
{
local VolumeColTester Tst;
local int x,y;
local vector Pos;
local vector PosOffset, NegOffset, TestOffset;
local int i;
local int StepSizeX, StepSizeY;
local vector OffSet, AnotherOffset;
if( bDebugZombieSpawning )
{
log("Initializing Spawn Point "$self);
}
OldInitPos = Location;
bHasInitSpawnPoints = True;
Tst = Spawn(Class'VolumeColTester');
if( Tst == none )
{
log(self$"couldn't spawn collision tester, move this volume!!!");
return;
}
Tst.bCollideWhenPlacing = True;
// Find the X and Y bounds of this volume
for( i=0; i<=10; i++ )
{
TestOffset = Location;
AnotherOffset = vect(0,0,0);
AnotherOffset.X = (100 * i);
TestOffset += AnotherOffset;
if( !Tst.SetLocation(TestOffset) || !Encompasses(Tst) )
{
//DrawDebugCylinder(TestOffset,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,255, 255, 0);
break;
}
else
{
//DrawDebugCylinder(Tst.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,0, 0, 255);
PosOffset.X = AnotherOffset.X;//Tst.Location.X;
}
}
for( i=0; i<=10; i++ )
{
TestOffset = Location;
AnotherOffset = vect(0,0,0);
AnotherOffset.Y = (100 * i);
TestOffset += AnotherOffset;
if( !Tst.SetLocation(TestOffset) || !Encompasses(Tst) )
{
//DrawDebugCylinder(TestOffset,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,255, 255, 0);
break;
}
else
{
//DrawDebugCylinder(Tst.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,0, 0, 255);
PosOffset.Y = AnotherOffset.Y;//Tst.Location.Y;
}
}
for( i=0; i<=10; i++ )
{
TestOffset = Location;
AnotherOffset = vect(0,0,0);
AnotherOffset.X = (100 * -i);
TestOffset += AnotherOffset;
if( !Tst.SetLocation(TestOffset) || !Encompasses(Tst) )
{
//DrawDebugCylinder(TestOffset,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,255, 255, 0);
break;
}
else
{
//DrawDebugCylinder(Tst.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,0, 0, 255);
NegOffset.X = AnotherOffset.X;
}
}
for( i=0; i<=10; i++ )
{
TestOffset = Location;
AnotherOffset = vect(0,0,0);
AnotherOffset.Y = (100 * -i);
TestOffset += AnotherOffset;
if( !Tst.SetLocation(TestOffset) || !Encompasses(Tst) )
{
//DrawDebugCylinder(TestOffset,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,255, 255, 0);
break;
}
else
{
//DrawDebugCylinder(Tst.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,0, 0, 255);
NegOffset.Y = AnotherOffset.Y;
}
}
// Scale the space between spawn points to match the size of the volume
StepSizeX = (Abs(NegOffset.X) + PosOffset.X)/10;
if( StepSizeX < 56 )
{
StepSizeX = 56;
}
StepSizeY = (Abs(NegOffset.Y) + PosOffset.Y)/10;
if( StepSizeY < 56 )
{
StepSizeY = 56;
}
// Search for places to spawn within the volume
for( x=-5; x<=5; x++ )
{
for( y=-5; y<=5; y++ )
{
Pos = Location;
OffSet.X=(StepSizeX*x);
OffSet.Y=(StepSizeY*y);
Pos += OffSet;
// Just continue if the offset is out of bounds
if( OffSet.X > 0 && OffSet.X > PosOffset.X )
{
continue;
}
else if( OffSet.X < 0 && OffSet.X < NegOffset.X )
{
continue;
}
if( OffSet.Y > 0 && OffSet.Y > PosOffset.Y )
{
continue;
}
else if( OffSet.Y < 0 && OffSet.Y < NegOffset.Y )
{
continue;
}
if( !Tst.SetLocation(Pos) || !Encompasses(Tst) || !FastTrace(Tst.Location+vect(0,0,22),Tst.Location-vect(0,0,22)) )
{
if( bDebugZombieSpawning )
{
DrawDebugCylinder(Pos,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,255, 0, 0);
}
continue;
}
else
{
if( bDebugZombieSpawning )
{
DrawDebugCylinder(Tst.Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),Tst.CollisionRadius,Tst.CollisionHeight,5,0, 255, 0);
}
}
SpawnPos[SpawnPos.Length] = Tst.Location;
}
}
if( SpawnPos.Length==0 )
{
log("No Spawn points found for "$self$"!!!!!!!!!!!!!");
}
Tst.Destroy();
}
// Draw a debugging cylinder
simulated function DrawDebugCylinder(vector Base,vector X, vector Y,vector Z, FLOAT Radius,float HalfHeight,int NumSides, byte R, byte G, byte B)
{
local float AngleDelta;
local vector LastVertex, Vertex;
local int SideIndex;
//Color = Color.RenderColor();
AngleDelta = 2.0f * PI / NumSides;
LastVertex = Base + X * Radius;
for(SideIndex = 0;SideIndex < NumSides;SideIndex++)
{
Vertex = Base + (X * Cos(AngleDelta * (SideIndex + 1)) + Y * Sin(AngleDelta * (SideIndex + 1))) * Radius;
DrawStayingDebugLine(LastVertex - Z * HalfHeight,Vertex - Z * HalfHeight,R,G,B);
DrawStayingDebugLine(LastVertex + Z * HalfHeight,Vertex + Z * HalfHeight,R,G,B);
DrawStayingDebugLine(LastVertex - Z * HalfHeight,LastVertex + Z * HalfHeight,R,G,B);
LastVertex = Vertex;
}
}
// Calculate spawning cost.
function float RateZombieVolume(KFGameType KFGT, ZombieVolume LastSpawnedVolume, Controller SpawnCloseTo, optional bool bIgnoreFailedSpawnTime, optional bool bBossSpawning)
{
local Controller C;
local float Score;
local float dist;
local byte i,l;
local float PlayerDistScoreZ, PlayerDistScoreXY, TotalPlayerDistScore, UsageScore;
local vector LocationZ, LocationXY, TestLocationZ, TestLocationXY;
local bool bTooCloseToPlayer;
if( bDebugZoneSelection )
{
DrawStayingDebugLine(Location,SpawnCloseTo.Pawn.Location,255,255,0);
}
if( !bIgnoreFailedSpawnTime && Level.TimeSeconds - LastFailedSpawnTime < 5.0 )
{
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume LastFailedSpawnTime < 5 seconds, returning");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
l = RoomDoorsList.Length;
for( i=0; i<l; i++ )
{
if( RoomDoorsList[i].DoorActor==None )
continue;
if( (!RoomDoorsList[i].bOnlyWhenWelded && RoomDoorsList[i].DoorActor.KeyNum==0) || RoomDoorsList[i].DoorActor.bSealed )
{
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume doors welded or shut, returning");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
}
if( !CanSpawnInHere(KFGT.NextSpawnSquad) )
{
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume !CanSpawnInHere, returning");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
// Start score with Spawn desirability
Score = SpawnDesirability;
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume initial Score = "$Score$" SpawnDesirability = "$SpawnDesirability);
}
// Rate how long its been since this spawn was used
UsageScore = FMin(Level.TimeSeconds - LastSpawnTime,30.0)/30.0;
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume Usage UsageScore = "$UsageScore$" Time = "$(Level.TimeSeconds - LastSpawnTime));
}
LocationZ = Location * vect(0,0,1);
LocationXY = Location * vect(1,1,0);
// Now make sure no player sees the spawn point.
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if( C.Pawn!=none && C.Pawn.Health>0 && C.bIsPlayer )
{
// If there is a player inside this volume, return
if( Encompasses(C.Pawn) )
{
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume player in volume, returning");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
// Rate the Volume on how close it is to the players.
TestLocationZ = C.Pawn.Location * vect(0,0,1);
TestLocationXY = C.Pawn.Location * vect(1,1,0);
PlayerDistScoreZ = FClamp(250.f-VSize(TestLocationZ-LocationZ),1.f,250.f)/250.0;
PlayerDistScoreXY = FClamp(2000.f-VSize(TestLocationXY-LocationXY),1.f,2000.f)/2000.0;
if( bNoZAxisDistPenalty )
{
TotalPlayerDistScore += PlayerDistScoreXY/KFGT.NumPlayers;
}
else
{
// Weight the XY distance much higher than the Z dist. This gets zombies spawning more
// on the same level as the player
TotalPlayerDistScore += ((PlayerDistScoreZ * 0.3) + (PlayerDistScoreXY * 0.7))/KFGT.NumPlayers;
}
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume Player DistCheck DistXY = "$VSize(TestLocationXY-LocationXY)/50.0$"m DistZ = "$VSize(TestLocationZ-LocationZ)/50.0$"m");
log(self$" RateZombieVolume Player DistCheck PlayerDistScoreZ = "$PlayerDistScoreZ$" PlayerDistScoreXY = "$PlayerDistScoreXY);
}
dist = VSize(Location - C.Pawn.Location);
// If the zone is too close to a boss character, reduce its desirability
if( bBossSpawning )
{
if( dist < 1000.0 ) // 20 meters
{
if( bDebugZoneSelection )
{
log(self$" too close to player, dist = "$(dist/50.0)$"m");
}
bTooCloseToPlayer = true;
}
}
// Do individual checks for spawn locations now, maybe add this back in later as an optimization
// if fog doesn't hide spawn & lineofsight possible
if( !bAllowPlainSightSpawns && ((!C.Pawn.Region.Zone.bDistanceFog || (dist < C.Pawn.Region.Zone.DistanceFogEnd)) && FastTrace(Location,C.Pawn.Location + C.Pawn.EyePosition())) )
{
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume player can see this zone, returning");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
else if( dist<MinDistanceToPlayer )
{
if( bDebugZoneSelection || bDebugSpawnSelection )
{
log(self$" RateZombieVolume player too close to zone, returning dist = "$dist$" MinDistanceToPlayer = "$MinDistanceToPlayer);
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius,SpawnCloseTo.Pawn.CollisionHeight,5,255, 0, 0);
}
return -1;
}
}
}
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume Player DistCheck TotalPlayerDistScore = "$TotalPlayerDistScore);
}
// Spawning score is 30% SpawnDesirability, 30% Distance from players, 30% when the spawn was last used, 10% random
Score = (Score * 0.30) + (TotalPlayerDistScore * ( Score * 0.30)) + (UsageScore * ( Score * 0.30)) + (FRand() * ( Score * 0.10));
if( bTooCloseToPlayer )
{
Score*=0.2;
// if the zone is too close to a boss character, reduce its desirability
if( bDebugZoneSelection )
{
log(self$" bTooCloseToPlayer, *= .2 new Score = "$Score);
}
}
// log("*** Base Score Part = "$(Score * 0.15));
// log("*** Dist Score Part = "$(TotalPlayerDistScore * ( Score * 0.15)));
// log("*** UsageScore Score Part = "$(UsageScore * ( Score * 0.70)));
// Try and prevent spawning in the same volume back to back
if( LastSpawnedVolume != none && LastSpawnedVolume==self )
{
Score*=0.2;
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume just used, *= .2 new Score = "$Score);
}
}
if( bDebugZoneSelection )
{
log(self$" RateZombieVolume final Score = "$Score);
log("******");
DrawDebugCylinder(Location,vect(1,0,0),vect(0,1,0),vect(0,0,1),SpawnCloseTo.Pawn.CollisionRadius * ((Score/2000) * 2),SpawnCloseTo.Pawn.CollisionHeight * ((Score/2000) * 2),5,0, 255, 0);
}
// if we get here, return at least a 1
return FMax(Score,1);
}
function Touch( Actor Other )
{
if( KFHumanPawn(Other)!=None )
LastCheckTime = Level.TimeSeconds+TouchDisableTime; // Disable me for ~30 seconds.
Super.Touch(Other);
}
defaultproperties
{
CanRespawnTime=1.5
bStatic=False
bNoDelete=True
bMassiveZeds=True
bLeapingZeds=True
bNormalZeds=True
bRangedZeds=True
ZombieCountMulti=1
bVolumeIsEnabled=True
RemoteRole=ROLE_None
SpawnDesirability=3000
MinDistanceToPlayer=600
TouchDisableTime=10
bDebugZoneSelection=False
bNoZAxisDistPenalty=false
}