refactoring, improvements, fixes

- fixed spawn when all spawn volumes are busy;
- refactoring (removed obsolete code, improved readability);
- improved logging.
This commit is contained in:
GenZmeY 2022-06-01 03:03:02 +03:00
parent b5355d9fb5
commit fd3c9116e4
4 changed files with 177 additions and 116 deletions

View File

@ -13,7 +13,7 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart; var bool bSpawnAtPlayerStart;
}; };
var public config bool bStopRegularSpawn; var public config bool bStopRegularSpawn;
var private config Array<S_SpawnEntryCfg> Spawn; var private config Array<S_SpawnEntryCfg> Spawn;
public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA) public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA)
@ -34,9 +34,11 @@ public static function InitConfig(int Version, int LatestVersion, KFGI_Access KF
private static function ApplyDefault(KFGI_Access KFGIA) private static function ApplyDefault(KFGI_Access KFGIA)
{ {
local S_SpawnEntryCfg SpawnEntry; local S_SpawnEntryCfg SpawnEntry;
local Array<class<KFPawn_Monster> > KFPM_Bosses; local Array<class<KFPawn_Monster> > KFPM_Bosses;
local class<KFPawn_Monster> KFPMC; local class<KFPawn_Monster> KFPMC;
default.Spawn.Length = 0;
default.bStopRegularSpawn = true; default.bStopRegularSpawn = true;
default.Spawn.Length = 0; default.Spawn.Length = 0;
@ -61,8 +63,9 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
local S_SpawnEntry SpawnEntry; local S_SpawnEntry SpawnEntry;
local int Line; local int Line;
local bool Errors; local bool Errors;
local int Loaded;
`ZS_Info("Load boss waves spawn list:"); `ZS_Info("Load boss waves spawn list...");
foreach default.Spawn(SpawnEntryCfg, Line) foreach default.Spawn(SpawnEntryCfg, Line)
{ {
Errors = false; Errors = false;
@ -115,11 +118,21 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
if (!Errors) if (!Errors)
{ {
Loaded++;
SpawnList.AddItem(SpawnEntry); SpawnList.AddItem(SpawnEntry);
`ZS_Info("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ SpawnEntryCfg.BossClass @ SpawnEntryCfg.ZedClass); `ZS_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.BossClass $ ")" @ SpawnEntryCfg.ZedClass);
} }
} }
if (Loaded == default.Spawn.Length)
{
`ZS_Info("Boss spawn list loaded successfully");
}
else
{
`ZS_Info("Boss spawn list: loaded" @ Loaded @ "of" @ default.Spawn.Length @ "entries");
}
return SpawnList; return SpawnList;
} }

View File

@ -34,9 +34,9 @@ public static function InitConfig(int Version, int LatestVersion, KFGI_Access KF
private static function ApplyDefault(KFGI_Access KFGIA) private static function ApplyDefault(KFGI_Access KFGIA)
{ {
local S_SpawnEntryCfg SpawnEntry; local S_SpawnEntryCfg SpawnEntry;
local Array<class<KFPawn_Monster> > KFPM_Zeds; local Array<class<KFPawn_Monster> > KFPM_Zeds;
local class<KFPawn_Monster> KFPMC; local class<KFPawn_Monster> KFPMC;
default.Spawn.Length = 0; default.Spawn.Length = 0;
@ -64,6 +64,7 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
local S_SpawnEntry SpawnEntry; local S_SpawnEntry SpawnEntry;
local int Line; local int Line;
local bool Errors; local bool Errors;
local int Loaded;
`ZS_Info("Load spawn list:"); `ZS_Info("Load spawn list:");
foreach default.Spawn(SpawnEntryCfg, Line) foreach default.Spawn(SpawnEntryCfg, Line)
@ -123,11 +124,21 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
if (!Errors) if (!Errors)
{ {
Loaded++;
SpawnList.AddItem(SpawnEntry); SpawnList.AddItem(SpawnEntry);
`ZS_Info("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ SpawnEntryCfg.Wave @ SpawnEntryCfg.ZedClass); `ZS_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (w" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass);
} }
} }
if (Loaded == default.Spawn.Length)
{
`ZS_Info("Regular spawn list loaded successfully");
}
else
{
`ZS_Info("Regular spawn list: loaded" @ Loaded @ "of" @ default.Spawn.Length @ "entries");
}
return SpawnList; return SpawnList;
} }

View File

@ -36,7 +36,7 @@ public static function InitConfig(int Version, int LatestVersion)
private static function ApplyDefault() private static function ApplyDefault()
{ {
local S_SpawnEntryCfg SpawnEntry; local S_SpawnEntryCfg SpawnEntry;
local EAIType AIType; local EAIType AIType;
default.bStopRegularSpawn = true; default.bStopRegularSpawn = true;
default.Spawn.Length = 0; default.Spawn.Length = 0;
@ -61,6 +61,7 @@ public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogL
local S_SpawnEntry SpawnEntry; local S_SpawnEntry SpawnEntry;
local int Line; local int Line;
local bool Errors; local bool Errors;
local int Loaded;
if (KFGIE == None) if (KFGIE == None)
{ {
@ -126,11 +127,21 @@ public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogL
if (!Errors) if (!Errors)
{ {
Loaded++;
SpawnList.AddItem(SpawnEntry); SpawnList.AddItem(SpawnEntry);
`ZS_Info("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ SpawnEntryCfg.Wave @ SpawnEntryCfg.ZedClass); `ZS_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass);
} }
} }
if (Loaded == default.Spawn.Length)
{
`ZS_Info("Special spawn list loaded successfully");
}
else
{
`ZS_Info("Special spawn list: loaded" @ Loaded @ "of" @ default.Spawn.Length @ "entries");
}
return SpawnList; return SpawnList;
} }

View File

@ -26,18 +26,20 @@ struct S_SpawnEntry
{ {
var class<KFPawn_Monster> BossClass; var class<KFPawn_Monster> BossClass;
var class<KFPawn_Monster> ZedClass; var class<KFPawn_Monster> ZedClass;
var byte Wave; var byte Wave;
var int SpawnCountBase; var int SpawnCountBase;
var int SingleSpawnLimitDefault; var int SingleSpawnLimitDefault;
var int SingleSpawnLimit; var int SingleSpawnLimit;
var float Probability; var float Probability;
var float RelativeStartDefault; var float RelativeStartDefault;
var float RelativeStart; var float RelativeStart;
var int DelayDefault; var int DelayDefault;
var int Delay; var int Delay;
var int SpawnsLeft; var int SpawnsLeft;
var int SpawnsTotal; var int SpawnsTotal;
var bool SpawnAtPlayerStart; var bool SpawnAtPlayerStart;
var bool ForceSpawn;
var String ZedNameFiller;
}; };
var private config int Version; var private config int Version;
@ -65,14 +67,11 @@ var private int CycleWaveSize;
var private int WaveTotalAI; var private int WaveTotalAI;
var private class<KFPawn_Monster> CurrentBossClass; var private class<KFPawn_Monster> CurrentBossClass;
var private Array<class<KFPawn_Monster> > BossClassCache;
var private Array<class<KFPawn_Monster> > CustomZeds; var private Array<class<KFPawn_Monster> > CustomZeds;
var private String SpawnTimerLastMessage; var private bool SpawnActive;
var private String SpawnListsComment; var private String SpawnListsComment;
delegate bool WaveCondition(S_SpawnEntry SE);
public event PreBeginPlay() public event PreBeginPlay()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -192,21 +191,11 @@ private function Init()
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1; CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
} }
CreateBossCache();
PreloadContent(); PreloadContent();
SetTimer(float(dt), true, nameof(SpawnTimer)); SetTimer(float(dt), true, nameof(SpawnTimer));
} }
private function CreateBossCache()
{
local S_SpawnEntry SE;
foreach SpawnListBW(SE)
if (BossClassCache.Find(SE.BossClass) == INDEX_NONE)
BossClassCache.AddItem(SE.BossClass);
}
private function PreloadContent() private function PreloadContent()
{ {
local class<KFPawn_Monster> PawnClass; local class<KFPawn_Monster> PawnClass;
@ -236,29 +225,11 @@ private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, o
} }
} }
public function bool WaveConditionRegular(S_SpawnEntry SE)
{
return (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1));
}
public function bool WaveConditionBoss(S_SpawnEntry SE)
{
if (CurrentBossClass == None)
return false;
else
return (SE.BossClass == CurrentBossClass);
}
public function bool WaveConditionSpecial(S_SpawnEntry SE)
{
return (SE.Wave == SpecialWave);
}
private function SpawnTimer() private function SpawnTimer()
{ {
local S_SpawnEntry SE; local S_SpawnEntry SE;
local int Index; local int Index;
local float Threshold;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -273,6 +244,12 @@ private function SpawnTimer()
return; return;
} }
if (SpawnListCurrent.Length == 0)
{
SpawnTimerLogger(true, "No spawn list for this wave");
return;
}
if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit) if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit)
{ {
SpawnTimerLogger(true, "alive spawn limit reached"); SpawnTimerLogger(true, "alive spawn limit reached");
@ -291,9 +268,15 @@ private function SpawnTimer()
SpawnTimerLogger(false, SpawnListsComment); SpawnTimerLogger(false, SpawnListsComment);
Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI));
foreach SpawnListCurrent(SE, Index) foreach SpawnListCurrent(SE, Index)
{ {
if (!ReadyToStart(SE)) if (NoFreeSpawnSlots)
{
break;
}
if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold)
{ {
continue; continue;
} }
@ -316,6 +299,8 @@ private function SetupWave()
local Array<String> SpawnListNames; local Array<String> SpawnListNames;
local int WaveTotalAIDef; local int WaveTotalAIDef;
local String WaveTypeInfo; local String WaveTypeInfo;
local S_SpawnEntry SE;
local EAIType SWType;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -329,10 +314,16 @@ private function SetupWave()
if (KFGIE != None) if (KFGIE != None)
{ {
if (KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode != INDEX_NONE)
{
WaveTypeInfo = "Weekly:" @ KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode;
}
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode; SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode;
if (SpecialWave != INDEX_NONE) if (SpecialWave != INDEX_NONE)
{ {
WaveTypeInfo = "Special:" @ EAIType(SpecialWave); SWType = EAIType(SpecialWave);
WaveTypeInfo = "Special:" @ SWType;
} }
} }
@ -341,7 +332,7 @@ private function SetupWave()
CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex)); CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex));
if (CurrentBossClass == None) if (CurrentBossClass == None)
{ {
`ZS_Error("Can't determine boss class:" @ CurrentBossClass); `ZS_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex);
} }
else else
{ {
@ -375,23 +366,29 @@ private function SetupWave()
if (UseRegularSpawnList) if (UseRegularSpawnList)
{ {
SpawnListNames.AddItem("regular"); SpawnListNames.AddItem("regular");
FillCurrentSpawnList(SpawnListRW, WaveConditionRegular); foreach SpawnListRW(SE)
if (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1))
SpawnListCurrent.AddItem(SE);
} }
if (UseSpecialSpawnList) if (UseSpecialSpawnList)
{ {
SpawnListNames.AddItem("special"); SpawnListNames.AddItem("special");
FillCurrentSpawnList(SpawnListSW, WaveConditionSpecial); foreach SpawnListSW(SE)
if (SE.Wave == SpecialWave)
SpawnListCurrent.AddItem(SE);
} }
if (UseBossSpawnList) if (UseBossSpawnList)
{ {
SpawnListNames.AddItem("boss"); SpawnListNames.AddItem("boss");
FillCurrentSpawnList(SpawnListBW, WaveConditionBoss); foreach SpawnListBW(SE)
if (SE.BossClass == CurrentBossClass)
SpawnListCurrent.AddItem(SE);
} }
JoinArray(SpawnListNames, SpawnListsComment, ", "); JoinArray(SpawnListNames, SpawnListsComment, ", ");
ResetSpawnList(SpawnListCurrent); AdjustSpawnList(SpawnListCurrent);
if (WaveTypeInfo != "") if (WaveTypeInfo != "")
{ {
@ -401,24 +398,14 @@ private function SetupWave()
`ZS_Info("Wave" @ CurrentWave @ WaveTypeInfo); `ZS_Info("Wave" @ CurrentWave @ WaveTypeInfo);
} }
private function FillCurrentSpawnList(Array<S_SpawnEntry> SpawnList, delegate<WaveCondition> Condition) private function AdjustSpawnList(out Array<S_SpawnEntry> List)
{
local S_SpawnEntry SE;
`ZS_Trace(`Location);
foreach SpawnList(SE)
if (Condition(SE))
SpawnListCurrent.AddItem(SE);
}
private function ResetSpawnList(out Array<S_SpawnEntry> List)
{ {
local S_SpawnEntry SE; local S_SpawnEntry SE;
local int Index; local int Index;
local float Cycle, Players; local float Cycle, Players;
local float MSB, MSC, MSP; local float MSB, MSC, MSP;
local float MLB, MLC, MLP; local float MLB, MLC, MLP;
local int ZedNameMaxLength;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -433,8 +420,10 @@ private function ResetSpawnList(out Array<S_SpawnEntry> List)
MLC = CfgSpawn.default.SingleSpawnLimitCycleMultiplier; MLC = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
MLP = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier; MLP = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
ZedNameMaxLength = 0;
foreach List(SE, Index) foreach List(SE, Index)
{ {
ZedNameMaxLength = Max(ZedNameMaxLength, Len(String(SE.ZedClass)));
if (KFGIS.MyKFGRI.IsBossWave()) if (KFGIS.MyKFGRI.IsBossWave())
{ {
List[Index].RelativeStart = 0.f; List[Index].RelativeStart = 0.f;
@ -449,10 +438,17 @@ private function ResetSpawnList(out Array<S_SpawnEntry> List)
List[Index].Delay = 0; List[Index].Delay = 0;
} }
List[Index].SpawnsTotal = Round(SE.SpawnCountBase * (MSB + MSC * (Cycle - 1.0f) + MSP * (Players - 1.0f))); List[Index].ForceSpawn = false;
List[Index].SpawnsLeft = List[Index].SpawnsTotal; List[Index].SpawnsTotal = Round(SE.SpawnCountBase * (MSB + MSC * (Cycle - 1.0f) + MSP * (Players - 1.0f)));
List[Index].SingleSpawnLimit = Round(SE.SingleSpawnLimitDefault * (MLB + MLC * (Cycle - 1.0f) + MLP * (Players - 1.0f))); List[Index].SingleSpawnLimit = Round(SE.SingleSpawnLimitDefault * (MLB + MLC * (Cycle - 1.0f) + MLP * (Players - 1.0f)));
List[Index].SpawnsLeft = List[Index].SpawnsTotal;
}
foreach List(SE, Index)
{
List[Index].ZedNameFiller = "";
while (Len(String(SE.ZedClass)) + Len(List[Index].ZedNameFiller) < ZedNameMaxLength)
List[Index].ZedNameFiller @= "";
} }
} }
@ -470,24 +466,10 @@ private function SpawnTimerLogger(bool Stop, optional String Comment)
if (Comment != "") if (Comment != "")
Message @= "(" $ Comment $ ")"; Message @= "(" $ Comment $ ")";
if (Message != SpawnTimerLastMessage) if (SpawnActive == Stop)
{ {
`ZS_Info(Message); `ZS_Info(Message);
SpawnTimerLastMessage = Message; SpawnActive = !Stop;
}
}
private function bool ReadyToStart(S_SpawnEntry SE)
{
`ZS_Trace(`Location);
if (SE.RelativeStart == 0.f)
{
return true;
}
else
{
return (SE.RelativeStart <= 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI)));
} }
} }
@ -495,19 +477,23 @@ private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
{ {
local S_SpawnEntry SE; local S_SpawnEntry SE;
local int FreeSpawnSlots, SpawnCount, Spawned; local int FreeSpawnSlots, SpawnCount, Spawned;
local String Message; local String Action, Comment, NextSpawn;
`ZS_Trace(`Location); `ZS_Trace(`Location);
SE = SpawnList[Index]; SE = SpawnList[Index];
SpawnList[Index].Delay = SE.DelayDefault; SpawnList[Index].Delay = SE.DelayDefault;
if (FRand() <= SE.Probability) if (FRand() <= SE.Probability || SE.ForceSpawn)
{ {
if (SE.SingleSpawnLimit == 0 || SE.SpawnsLeft < SE.SingleSpawnLimit) if (SE.SingleSpawnLimit == 0 || SE.SpawnsLeft < SE.SingleSpawnLimit)
{
SpawnCount = SE.SpawnsLeft; SpawnCount = SE.SpawnsLeft;
}
else else
{
SpawnCount = SE.SingleSpawnLimit; SpawnCount = SE.SingleSpawnLimit;
}
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{ {
@ -520,26 +506,52 @@ private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
} }
else if (SpawnCount > FreeSpawnSlots) else if (SpawnCount > FreeSpawnSlots)
{ {
`ZS_Info("Not enough free slots to spawn, will spawn" @ FreeSpawnSlots @ "instead of" @ SpawnCount);
SpawnCount = FreeSpawnSlots; SpawnCount = FreeSpawnSlots;
} }
} }
Spawned = SpawnZed(SE.ZedClass, SpawnCount, SE.SpawnAtPlayerStart); Spawned = SpawnZed(SE.ZedClass, SpawnCount, SE.SpawnAtPlayerStart);
Message = "Spawned:" @ SE.ZedClass @ "x" $ Spawned; if (Spawned == INDEX_NONE)
{
SpawnList[Index].Delay = 5;
SpawnList[Index].ForceSpawn = true;
Action = "Skip spawn";
Comment = "no free spawn volume, try to spawn it again in" @ SpawnList[Index].Delay @ "seconds...";
SpawnLog(SE, Action, Comment);
return;
}
else if (Spawned == 0)
{
Action = "Spawn failed";
}
else
{
SpawnList[Index].ForceSpawn = false;
Action = "Spawned";
Comment = "x" $ Spawned;
}
} }
else else
{ {
Message = "Skip spawn" @ SE.ZedClass @ "due to probability:" @ SE.Probability * 100 $ "%"; Action = "Skip spawn";
Comment = "due to" @ Round(SE.Probability * 100) $ "%" @ "probability";
Spawned = SE.SingleSpawnLimit; Spawned = SE.SingleSpawnLimit;
} }
SpawnList[Index].SpawnsLeft -= Spawned; SpawnList[Index].SpawnsLeft -= Spawned;
if (SpawnList[Index].SpawnsLeft > 0) if (SpawnList[Index].SpawnsLeft > 0)
{ {
Message @= "(Next spawn after" @ SE.DelayDefault $ "sec," @ "spawns left:" @ SpawnList[Index].SpawnsLeft $ ")"; NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].SpawnsLeft;
} }
`ZS_Info(Message); SpawnLog(SE, Action, Comment, NextSpawn);
}
private function SpawnLog(S_SpawnEntry SE, String Action, optional String Comment, optional String NextSpawn)
{
if (Comment != "") Comment = ":" @ Comment;
if (NextSpawn != "") NextSpawn = "(" $ NextSpawn $ ")";
`ZS_Info(String(SE.ZedClass) $ SE.ZedNameFiller @ ">" @ Action $ Comment @ NextSpawn);
} }
private function int PlayerCount() private function int PlayerCount()
@ -584,57 +596,71 @@ private function Vector PlayerStartLocation()
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int SpawnCount, bool SpawnAtPlayerStart) private function int SpawnZed(class<KFPawn_Monster> ZedClass, int SpawnCount, bool SpawnAtPlayerStart)
{ {
local Array<class<KFPawn_Monster> > CustomSquad; local Array<class<KFPawn_Monster> > CustomSquad;
local Vector SpawnLocation; local Vector SpawnLocation, PlayerStart;
local KFSpawnVolume SpawnVolume;
local KFPawn_Monster KFPM; local KFPawn_Monster KFPM;
local Controller C; local Controller C;
local int SpawnFailed, Spawned; local int Failed, Spawned;
local int Index; local int Index;
`ZS_Trace(`Location); `ZS_Trace(`Location);
for (Index = 0; Index < SpawnCount; Index++) PlayerStart = PlayerStartLocation();
CustomSquad.AddItem(ZedClass);
if (SpawnAtPlayerStart) if (SpawnAtPlayerStart)
{ {
SpawnLocation = PlayerStartLocation(); SpawnLocation = PlayerStart;
SpawnLocation.Y += 64; SpawnLocation.Y += 64;
SpawnLocation.Z += 64; SpawnLocation.Z += 64;
} }
else else
{ {
SpawnLocation = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad).Location; for (Index = 0; Index < SpawnCount; Index++)
{
CustomSquad.AddItem(ZedClass);
}
SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad);
if (SpawnVolume == None)
{
return INDEX_NONE;
}
SpawnLocation = SpawnVolume.Location;
if (SpawnLocation == PlayerStart)
{
return INDEX_NONE;
}
SpawnLocation.Z += 10; SpawnLocation.Z += 10;
} }
SpawnFailed = 0; Spawned = 0; Failed = 0;
for (Index = 0; Index < SpawnCount; Index++) while (Failed + Spawned < SpawnCount)
{ {
KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true); KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true);
if (KFPM == None) if (KFPM == None)
{ {
`ZS_Error("Can't spawn" @ ZedClass); `ZS_Error("Can't spawn" @ ZedClass);
SpawnFailed++; Failed++;
continue; continue;
} }
C = KFPM.Spawn(KFPM.ControllerClass); C = KFPM.Spawn(KFPM.ControllerClass);
if (C == None) if (C == None)
{ {
`ZS_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this KFPawn..."); `ZS_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this" @ KFPM $ "...");
KFPM.Destroy(); KFPM.Destroy();
SpawnFailed++; Failed++;
continue; continue;
} }
C.Possess(KFPM, false); C.Possess(KFPM, false);
Spawned++;
} }
Spawned = (SpawnCount - SpawnFailed);
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{ {
KFGIS.NumAIFinishedSpawning += Spawned; KFGIS.NumAIFinishedSpawning += Spawned;
KFGIS.NumAISpawnsQueued += Spawned; KFGIS.NumAISpawnsQueued += Spawned;
} }
KFGIS.UpdateAIRemaining(); KFGIS.UpdateAIRemaining();