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 DisabledWaveNums; var() name ToggledEnabledEvent; var() array< class > OnlyAllowedZeds,DisallowedZeds; var() float SpawnDesirability,MinDistanceToPlayer; var() array RoomDoorsList; var transient float LastCheckTime; var transient bool bHasInitSpawnPoints; var transient array 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 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 > 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 > 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; i0 ) { for( j=0; j 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; i0 && 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; j0 ) { 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 > 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 > 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; i0 ) { for( j=0; j 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; i0 && 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; j0 ) { 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 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