8 Commits

Author SHA1 Message Date
fd3c9116e4 refactoring, improvements, fixes
- fixed spawn when all spawn volumes are busy;
- refactoring (removed obsolete code, improved readability);
- improved logging.
2022-06-01 07:40:23 +03:00
b5355d9fb5 optimize spawn cycle (improved performance on large spawn lists) 2022-05-31 06:36:42 +03:00
62c3f79c5e fixes: stop spawn, AIRemaining counter, boss wave spawn 2022-05-31 04:47:29 +03:00
d466d1fc79 remove incorrect wave0 from default regular spawn list 2022-05-23 19:06:49 +03:00
f0b10fb158 PubContent 2022-05-22 16:51:48 +03:00
b0727dd677 more sugar 2022-05-21 00:31:35 +03:00
25b3e85009 add default value for SingleSpawnLimitMultiplier 2022-05-21 00:11:08 +03:00
41f186ee15 sugar 2022-05-20 23:45:26 +03:00
13 changed files with 616 additions and 326 deletions

View File

@ -0,0 +1,7 @@
[h1]ZedSpawner[/h1]
[h1]Description[/h1]
Work In Progress...
[h1]Sources[/h1]
[url=https://github.com/GenZmeY/KF2-ZedSpawner]https://github.com/GenZmeY/KF2-ZedSpawner[/url]

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

View File

@ -0,0 +1 @@
Mutators

View File

@ -0,0 +1 @@
ZedSpawner

View File

@ -1,9 +1,40 @@
class KFGI_Access extends Object class KFGI_Access extends Object
within KFGameInfo_Survival; within KFGameInfo;
// Bypass protected modifier for these lists public function Array<class<KFPawn_Monster> > GetAIClassList()
{
local Array<class<KFPawn_Monster> > RV;
local class<KFPawn_Monster> KFPMC;
foreach AIClassList(KFPMC)
RV.AddItem(KFPMC);
return RV;
}
function bool IsCustomZed(class<KFPawn_Monster> KFPM) public function Array<class<KFPawn_Monster> > GetNonSpawnAIClassList()
{
local Array<class<KFPawn_Monster> > RV;
local class<KFPawn_Monster> KFPMC;
foreach NonSpawnAIClassList(KFPMC)
RV.AddItem(KFPMC);
return RV;
}
public function Array<class<KFPawn_Monster> > GetAIBossClassList()
{
local Array<class<KFPawn_Monster> > RV;
local class<KFPawn_Monster> KFPMC;
foreach AIBossClassList(KFPMC)
RV.AddItem(KFPMC);
return RV;
}
public function bool IsCustomZed(class<KFPawn_Monster> KFPM)
{ {
if (AIClassList.Find(KFPM) != INDEX_NONE) return false; if (AIClassList.Find(KFPM) != INDEX_NONE) return false;
if (NonSpawnAIClassList.Find(KFPM) != INDEX_NONE) return false; if (NonSpawnAIClassList.Find(KFPM) != INDEX_NONE) return false;
@ -11,6 +42,50 @@ function bool IsCustomZed(class<KFPawn_Monster> KFPM)
return true; return true;
} }
public function bool IsOriginalAI(class<KFPawn_Monster> KFPM, optional out EAIType AIType)
{
local int Type;
Type = AIClassList.Find(KFPM);
if (Type != INDEX_NONE)
{
AIType = EAIType(Type);
return true;
}
return false;
}
public function bool IsOriginalAIBoss(class<KFPawn_Monster> KFPM, optional out EBossAIType AIType)
{
local int Type;
Type = AIBossClassList.Find(KFPM);
if (Type != INDEX_NONE)
{
AIType = EBossAIType(Type);
return true;
}
return false;
}
public function class<KFPawn_Monster> AITypePawn(EAIType AIType)
{
if (AIType < AIClassList.Length)
return AIClassList[AIType];
else
return None;
}
public function class<KFPawn_Monster> BossAITypePawn(EBossAIType AIType)
{
if (AIType < AIBossClassList.Length)
return AIBossClassList[AIType];
else
return None;
}
defaultproperties defaultproperties
{ {

View File

@ -2,31 +2,43 @@ class Spawn extends Object
dependson(ZedSpawner) dependson(ZedSpawner)
config(ZedSpawner); config(ZedSpawner);
var config bool bCyclicalSpawn; var public config bool bCyclicalSpawn;
var config bool bShadowSpawn; var public config bool bShadowSpawn;
var public config float ZedTotalMultiplier;
var public config float SpawnTotalPlayerMultiplier;
var public config float SpawnTotalCycleMultiplier;
var public config float SingleSpawnLimitMultiplier;
var public config float SingleSpawnLimitPlayerMultiplier;
var public config float SingleSpawnLimitCycleMultiplier;
var public config int AliveSpawnLimit;
var config float ZedTotalMultiplier; public static function InitConfig(int Version, int LatestVersion)
var config float SpawnTotalPlayerMultiplier;
var config float SpawnTotalCycleMultiplier;
var config float SingleSpawnLimitMultiplier;
var config float SingleSpawnLimitPlayerMultiplier;
var config float SingleSpawnLimitCycleMultiplier;
var config int AliveSpawnLimit;
public static function InitConfig()
{ {
default.bCyclicalSpawn = true; switch (Version)
default.bShadowSpawn = true; {
default.ZedTotalMultiplier = 1.0; case `NO_CONFIG:
default.SpawnTotalPlayerMultiplier = 0.75; ApplyDefault();
default.SpawnTotalCycleMultiplier = 0.75;
default.SingleSpawnLimitPlayerMultiplier = 0.75; default: break;
default.SingleSpawnLimitCycleMultiplier = 0.75; }
default.AliveSpawnLimit = 0;
if (LatestVersion != Version)
{
StaticSaveConfig();
}
}
StaticSaveConfig(); private static function ApplyDefault()
{
default.bCyclicalSpawn = true;
default.bShadowSpawn = true;
default.ZedTotalMultiplier = 1.0;
default.SpawnTotalPlayerMultiplier = 0.75;
default.SpawnTotalCycleMultiplier = 0.75;
default.SingleSpawnLimitMultiplier = 1.0;
default.SingleSpawnLimitPlayerMultiplier = 0.75;
default.SingleSpawnLimitCycleMultiplier = 0.75;
default.AliveSpawnLimit = 0;
} }
public static function bool Load(E_LogLevel LogLevel) public static function bool Load(E_LogLevel LogLevel)

View File

@ -13,27 +13,47 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart; var bool bSpawnAtPlayerStart;
}; };
var config bool bStopRegularSpawn; var public config bool bStopRegularSpawn;
var config Array<S_SpawnEntryCfg> Spawn; var private config Array<S_SpawnEntryCfg> Spawn;
public static function InitConfig() public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA)
{ {
local S_SpawnEntryCfg SpawnEntry; switch (Version)
{
case `NO_CONFIG:
ApplyDefault(KFGIA);
default: break;
}
default.bStopRegularSpawn = true; if (LatestVersion != Version)
{
StaticSaveConfig();
}
}
private static function ApplyDefault(KFGI_Access KFGIA)
{
local S_SpawnEntryCfg SpawnEntry;
local Array<class<KFPawn_Monster> > KFPM_Bosses;
local class<KFPawn_Monster> KFPMC;
default.Spawn.Length = 0; default.Spawn.Length = 0;
SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing"; default.bStopRegularSpawn = true;
SpawnEntry.ZedClass = "SomePackage.SomeFleshpoundClass"; default.Spawn.Length = 0;
SpawnEntry.ZedClass = "SomePackage.SomeClass";
SpawnEntry.SpawnCountBase = 2; SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.Delay = 60; SpawnEntry.Delay = 30;
SpawnEntry.Probability = 100; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false; SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry); KFPM_Bosses = KFGIA.GetAIBossClassList();
foreach KFPM_Bosses(KFPMC)
StaticSaveConfig(); {
SpawnEntry.BossClass = "KFGameContent." $ String(KFPMC);
default.Spawn.AddItem(SpawnEntry);
}
} }
public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel) public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
@ -43,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;
@ -97,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

@ -14,35 +14,47 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart; var bool bSpawnAtPlayerStart;
}; };
var config Array<S_SpawnEntryCfg> Spawn; var public config Array<S_SpawnEntryCfg> Spawn;
public static function InitConfig() public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA)
{ {
local S_SpawnEntryCfg SpawnEntry; switch (Version)
{
case `NO_CONFIG:
ApplyDefault(KFGIA);
default: break;
}
if (LatestVersion != Version)
{
StaticSaveConfig();
}
}
private static function ApplyDefault(KFGI_Access KFGIA)
{
local S_SpawnEntryCfg SpawnEntry;
local Array<class<KFPawn_Monster> > KFPM_Zeds;
local class<KFPawn_Monster> KFPMC;
default.Spawn.Length = 0; default.Spawn.Length = 0;
SpawnEntry.Wave = 1; SpawnEntry.Wave = 0;
SpawnEntry.ZedClass = "SomePackage.SomeZedClass1";
SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.RelativeStart = 0;
SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry);
SpawnEntry.Wave = 2;
SpawnEntry.ZedClass = "SomePackage.SomeZedClass2";
SpawnEntry.SpawnCountBase = 2; SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.RelativeStart = 25; SpawnEntry.RelativeStart = 25;
SpawnEntry.Delay = 30; SpawnEntry.Delay = 60;
SpawnEntry.Probability = 50; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false; SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry);
StaticSaveConfig(); KFPM_Zeds = KFGIA.GetAIClassList();
foreach KFPM_Zeds(KFPMC)
{
++SpawnEntry.Wave;
SpawnEntry.ZedClass = "KFGameContent." $ String(KFPMC);
default.Spawn.AddItem(SpawnEntry);
}
} }
public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel) public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
@ -52,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)
@ -111,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

@ -14,18 +14,32 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart; var bool bSpawnAtPlayerStart;
}; };
var config bool bStopRegularSpawn; var public config bool bStopRegularSpawn;
var config Array<S_SpawnEntryCfg> Spawn; var private config Array<S_SpawnEntryCfg> Spawn;
public static function InitConfig() public static function InitConfig(int Version, int LatestVersion)
{
switch (Version)
{
case `NO_CONFIG:
ApplyDefault();
default: break;
}
if (LatestVersion != Version)
{
StaticSaveConfig();
}
}
private static function ApplyDefault()
{ {
local S_SpawnEntryCfg SpawnEntry; local S_SpawnEntryCfg SpawnEntry;
local EAIType AIType;
default.bStopRegularSpawn = true; default.bStopRegularSpawn = true;
default.Spawn.Length = 0; default.Spawn.Length = 0;
SpawnEntry.Wave = AT_Husk;
SpawnEntry.ZedClass = "SomePackage.SomeClass"; SpawnEntry.ZedClass = "SomePackage.SomeClass";
SpawnEntry.SpawnCountBase = 2; SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.SingleSpawnLimit = 1;
@ -33,9 +47,11 @@ public static function InitConfig()
SpawnEntry.Delay = 60; SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false; SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry); foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType)
{
StaticSaveConfig(); SpawnEntry.Wave = AIType;
default.Spawn.AddItem(SpawnEntry);
}
} }
public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogLevel LogLevel) public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogLevel LogLevel)
@ -45,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)
{ {
@ -110,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

@ -1,10 +1,12 @@
class ZedSpawner extends Info class ZedSpawner extends Info
config(ZedSpawner); config(ZedSpawner);
const LatestVersion = 1;
const dt = 1; const dt = 1;
const CfgSpawn = class'Spawn'; const CfgSpawn = class'Spawn';
const CfgSpawnListR = class'SpawnListRegular'; const CfgSpawnListRW = class'SpawnListRegular';
const CfgSpawnListBW = class'SpawnListBossWaves'; const CfgSpawnListBW = class'SpawnListBossWaves';
const CfgSpawnListSW = class'SpawnListSpecialWaves'; const CfgSpawnListSW = class'SpawnListSpecialWaves';
@ -24,140 +26,123 @@ struct S_SpawnEntry
{ {
var class<KFPawn_Monster> BossClass; var class<KFPawn_Monster> BossClass;
var class<KFPawn_Monster> ZedClass; var class<KFPawn_Monster> ZedClass;
var int 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 config bool bConfigInitialized; var private config int Version;
var config E_LogLevel LogLevel; var private config E_LogLevel LogLevel;
var private Array<S_SpawnEntry> SpawnListR; var private Array<S_SpawnEntry> SpawnListRW;
var private Array<S_SpawnEntry> SpawnListBW; var private Array<S_SpawnEntry> SpawnListBW;
var private Array<S_SpawnEntry> SpawnListSW; var private Array<S_SpawnEntry> SpawnListSW;
var private Array<S_SpawnEntry> SpawnListCurrent;
var private bool NoFreeSpawnSlots;
var private bool UseRegularSpawnList;
var private bool UseBossSpawnList;
var private bool UseSpecialSpawnList;
var private KFGameInfo_Survival KFGIS; var private KFGameInfo_Survival KFGIS;
var private KFGameInfo_Endless KFGIE; var private KFGameInfo_Endless KFGIE;
var private KFGI_Access KFGIA;
var private KFGI_Access KFGIA;
var private int CurrentWave; var private int CurrentWave;
var private int SpecialWave;
var private int CurrentCycle; var private int CurrentCycle;
var private int CycleWaveShift; var private int CycleWaveShift;
var private int CycleWaveSize; var private int CycleWaveSize;
var private int WaveTotalAI; var private int WaveTotalAI;
var private class<KFPawn_Monster> CurrentBossClass;
var private int SpecialWave;
var private String SpawnTimerLastMessage; 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;
delegate bool WaveCondition(S_SpawnEntry SE); var private bool SpawnActive;
var private String SpawnListsComment;
public function bool WaveConditionRegular(S_SpawnEntry SE) public event PreBeginPlay()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
return (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1));
}
public function bool WaveConditionBoss(S_SpawnEntry SE)
{
local KFPawn_Monster KFPM;
local int Index;
`ZS_Trace(`Location);
if (CurrentBossClass == None)
{
foreach WorldInfo.AllPawns(class'KFPawn_Monster', KFPM)
{
Index = BossClassCache.Find(KFPM.class);
if (Index != INDEX_NONE)
{
CurrentBossClass = BossClassCache[Index];
break;
}
}
}
if (CurrentBossClass == None)
{
return false;
}
return (SE.BossClass == CurrentBossClass);
}
public function bool WaveConditionSpecial(S_SpawnEntry SE)
{
`ZS_Trace(`Location);
return (SE.Wave == SpecialWave);
}
event PostBeginPlay()
{
`ZS_Trace(`Location);
Super.PostBeginPlay();
if (WorldInfo.NetMode == NM_Client) if (WorldInfo.NetMode == NM_Client)
{ {
`ZS_Fatal("NetMode == NM_Client, Destroy...");
Destroy(); Destroy();
return; return;
} }
Super.PreBeginPlay();
}
public event PostBeginPlay()
{
`ZS_Trace(`Location);
if (bPendingDelete) return;
Super.PostBeginPlay();
Init(); Init();
} }
private function InitConfig()
{
if (Version == `NO_CONFIG)
{
LogLevel = LL_Info;
SaveConfig();
}
CfgSpawn.static.InitConfig(Version, LatestVersion);
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
switch (Version)
{
case `NO_CONFIG:
`ZS_Info("Config created");
case MaxInt:
`ZS_Info("Config updated to version"@LatestVersion);
break;
case LatestVersion:
`ZS_Info("Config is up-to-date");
break;
default:
`ZS_Warn("The config version is higher than the current version (are you using an old mutator?)");
`ZS_Warn("Config version is" @ Version @ "but current version is" @ LatestVersion);
`ZS_Warn("The config version will be changed to" @ LatestVersion);
break;
}
if (LatestVersion != Version)
{
Version = LatestVersion;
SaveConfig();
}
}
private function Init() private function Init()
{ {
local S_SpawnEntry SE; local S_SpawnEntry SE;
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (!bConfigInitialized)
{
bConfigInitialized = true;
LogLevel = LL_Info;
SaveConfig();
CfgSpawn.static.InitConfig();
CfgSpawnListR.static.InitConfig();
CfgSpawnListBW.static.InitConfig();
CfgSpawnListSW.static.InitConfig();
`ZS_Info("Config initialized.");
}
if (LogLevel == LL_WrongLevel)
{
LogLevel = LL_Info;
`ZS_Warn("Wrong 'LogLevel', return to default value");
SaveConfig();
}
`ZS_Log("LogLevel:" @ LogLevel);
if (!CfgSpawn.static.Load(LogLevel))
{
`ZS_Fatal("Wrong settings, Destroy...");
Destroy();
return;
}
CurrentWave = INDEX_NONE;
KFGIS = KFGameInfo_Survival(WorldInfo.Game); KFGIS = KFGameInfo_Survival(WorldInfo.Game);
if (KFGIS == None) if (KFGIS == None)
{ {
@ -167,62 +152,85 @@ private function Init()
} }
KFGIA = new(KFGIS) class'KFGI_Access'; KFGIA = new(KFGIS) class'KFGI_Access';
KFGIE = KFGameInfo_Endless(KFGIS); KFGIE = KFGameInfo_Endless(KFGIS);
SpawnListR = CfgSpawnListR.static.Load(LogLevel); InitConfig();
if (LogLevel == LL_WrongLevel)
{
LogLevel = LL_Info;
`ZS_Warn("Wrong 'LogLevel', return to default value");
SaveConfig();
}
`ZS_Log("LogLevel:" @ LogLevel);
if (!CfgSpawn.static.Load(LogLevel))
{
`ZS_Fatal("Wrong settings, Destroy...");
Destroy();
return;
}
SpawnListRW = CfgSpawnListRW.static.Load(LogLevel);
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel); SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel); SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
CurrentWave = INDEX_NONE;
SpecialWave = INDEX_NONE; SpecialWave = INDEX_NONE;
CurrentCycle = 1; CurrentCycle = 1;
CycleWaveSize = 0;
CycleWaveShift = MaxInt;
foreach SpawnListR(SE)
{
if (CustomZeds.Find(SE.ZedClass) == INDEX_NONE
&& KFGIA.IsCustomZed(SE.ZedClass))
{
`ZS_Debug("Add custom zed:" @ SE.ZedClass);
CustomZeds.AddItem(SE.ZedClass);
SE.ZedClass.static.PreloadContent();
}
CycleWaveShift = Min(CycleWaveShift, SE.Wave);
CycleWaveSize = Max(CycleWaveSize, SE.Wave);
}
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
foreach SpawnListBW(SE) if (CfgSpawn.default.bCyclicalSpawn)
{ {
if (CustomZeds.Find(SE.ZedClass) == INDEX_NONE CycleWaveSize = 0;
&& KFGIA.IsCustomZed(SE.ZedClass)) CycleWaveShift = MaxInt;
foreach SpawnListRW(SE)
{ {
`ZS_Debug("Add custom zed:" @ SE.ZedClass); CycleWaveShift = Min(CycleWaveShift, SE.Wave);
CustomZeds.AddItem(SE.ZedClass); CycleWaveSize = Max(CycleWaveSize, SE.Wave);
SE.ZedClass.static.PreloadContent();
} }
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
if (BossClassCache.Find(SE.BossClass) == INDEX_NONE)
BossClassCache.AddItem(SE.BossClass);
} }
foreach SpawnListSW(SE) PreloadContent();
{
if (CustomZeds.Find(SE.ZedClass) == INDEX_NONE
&& KFGIA.IsCustomZed(SE.ZedClass))
{
`ZS_Debug("Add custom zed:" @ SE.ZedClass);
CustomZeds.AddItem(SE.ZedClass);
SE.ZedClass.static.PreloadContent();
}
}
SetTimer(float(dt), true, nameof(SpawnTimer)); SetTimer(float(dt), true, nameof(SpawnTimer));
} }
private function PreloadContent()
{
local class<KFPawn_Monster> PawnClass;
ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
foreach CustomZeds(PawnClass)
{
`ZS_Info("Preload content:" @ PawnClass);
PawnClass.static.PreloadContent();
}
}
private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, out Array<class<KFPawn_Monster> > Out)
{
local S_SpawnEntry SE;
foreach SpawnList(SE)
{
if (Out.Find(SE.ZedClass) == INDEX_NONE
&& KFGIA.IsCustomZed(SE.ZedClass))
{
Out.AddItem(SE.ZedClass);
}
}
}
private function SpawnTimer() private function SpawnTimer()
{ {
local S_SpawnEntry SE;
local int Index;
local float Threshold;
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum) if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum)
@ -236,47 +244,102 @@ 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");
return; return;
} }
if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn)
{
if (NoFreeSpawnSlots || KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount)
{
NoFreeSpawnSlots = true;
SpawnTimerLogger(true, "no free spawn slots");
return;
}
}
SpawnTimerLogger(false, SpawnListsComment);
Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI));
foreach SpawnListCurrent(SE, Index)
{
if (NoFreeSpawnSlots)
{
break;
}
if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn && KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount) if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold)
{ {
SpawnTimerLogger(true, "shadow spawn is active and no free spawn slots"); continue;
return; }
}
if (SE.Delay > 0)
SpawnTimerLogger(false); {
SpawnListCurrent[Index].Delay -= dt;
if ((SpecialWave == INDEX_NONE && !KFGIS.MyKFGRI.IsBossWave()) continue;
|| (SpecialWave != INDEX_NONE && !CfgSpawnListSW.default.bStopRegularSpawn) }
|| (KFGIS.MyKFGRI.IsBossWave() && !CfgSpawnListBW.default.bStopRegularSpawn))
{ if (SE.SpawnsLeft > 0)
SpawnZeds(SpawnListR, WaveConditionRegular); {
} SpawnEntry(SpawnListCurrent, Index);
}
if (SpecialWave != INDEX_NONE)
{
SpawnZeds(SpawnListSW, WaveConditionSpecial);
}
if (KFGIS.MyKFGRI.IsBossWave())
{
SpawnZeds(SpawnListBW, WaveConditionBoss);
} }
} }
private function SetupWave() private function SetupWave()
{ {
local int WaveTotalAIDef; local Array<String> SpawnListNames;
local int WaveTotalAIDef;
local String WaveTypeInfo;
local S_SpawnEntry SE;
local EAIType SWType;
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle)
{
CurrentCycle++;
`ZS_Info("Spawn cycle started:" @ CurrentCycle);
}
CurrentWave = KFGIS.WaveNum; CurrentWave = KFGIS.WaveNum;
if (!KFGIS.MyKFGRI.IsBossWave()) if (KFGIE != None)
{
if (KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode != INDEX_NONE)
{
WaveTypeInfo = "Weekly:" @ KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode;
}
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode;
if (SpecialWave != INDEX_NONE)
{
SWType = EAIType(SpecialWave);
WaveTypeInfo = "Special:" @ SWType;
}
}
if (KFGIS.MyKFGRI.IsBossWave())
{
CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex));
if (CurrentBossClass == None)
{
`ZS_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex);
}
else
{
WaveTypeInfo = "Boss:" @ CurrentBossClass;
}
}
else
{ {
WaveTotalAIDef = KFGIS.SpawnManager.WaveTotalAI; WaveTotalAIDef = KFGIS.SpawnManager.WaveTotalAI;
KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier; KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier;
@ -288,33 +351,61 @@ private function SetupWave()
{ {
`ZS_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")"); `ZS_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")");
} }
CurrentBossClass = None;
} }
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle) NoFreeSpawnSlots = false;
UseBossSpawnList = KFGIS.MyKFGRI.IsBossWave();
UseSpecialSpawnList = (SpecialWave != INDEX_NONE);
UseRegularSpawnList = ((!UseSpecialSpawnList && !UseBossSpawnList)
|| (UseSpecialSpawnList && !CfgSpawnListSW.default.bStopRegularSpawn)
|| (UseBossSpawnList && !CfgSpawnListBW.default.bStopRegularSpawn));
SpawnListCurrent.Length = 0;
if (UseRegularSpawnList)
{ {
CurrentCycle++; SpawnListNames.AddItem("regular");
`ZS_Info("Next spawn cycle started:" @ CurrentCycle); foreach SpawnListRW(SE)
if (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1))
SpawnListCurrent.AddItem(SE);
} }
ResetSpawnList(SpawnListR); if (UseSpecialSpawnList)
ResetSpawnList(SpawnListSW);
ResetSpawnList(SpawnListBW);
CurrentBossClass = None;
if (KFGIE != None)
{ {
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode; SpawnListNames.AddItem("special");
foreach SpawnListSW(SE)
if (SE.Wave == SpecialWave)
SpawnListCurrent.AddItem(SE);
} }
if (UseBossSpawnList)
{
SpawnListNames.AddItem("boss");
foreach SpawnListBW(SE)
if (SE.BossClass == CurrentBossClass)
SpawnListCurrent.AddItem(SE);
}
JoinArray(SpawnListNames, SpawnListsComment, ", ");
AdjustSpawnList(SpawnListCurrent);
if (WaveTypeInfo != "")
{
WaveTypeInfo = "(" $ WaveTypeInfo $ ")";
}
`ZS_Info("Wave" @ CurrentWave @ WaveTypeInfo);
} }
private function ResetSpawnList(out Array<S_SpawnEntry> List) private function AdjustSpawnList(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);
@ -329,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;
@ -339,22 +432,27 @@ private function ResetSpawnList(out Array<S_SpawnEntry> List)
else else
{ {
List[Index].RelativeStart = SE.RelativeStartDefault; List[Index].RelativeStart = SE.RelativeStartDefault;
if (SE.RelativeStart == 0.f) if (List[Index].RelativeStart == 0.f)
List[Index].Delay = SE.DelayDefault; List[Index].Delay = SE.DelayDefault;
else else
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;
`ZS_Debug(SE.ZedClass @ "SpawnsTotal:" @ List[Index].SpawnsTotal @ "SingleSpawnLimit:" @ List[Index].SingleSpawnLimit); }
foreach List(SE, Index)
{
List[Index].ZedNameFiller = "";
while (Len(String(SE.ZedClass)) + Len(List[Index].ZedNameFiller) < ZedNameMaxLength)
List[Index].ZedNameFiller @= "";
} }
} }
private function SpawnTimerLogger(bool Stop, optional String Reason) private function SpawnTimerLogger(bool Stop, optional String Comment)
{ {
local String Message; local String Message;
@ -365,101 +463,95 @@ private function SpawnTimerLogger(bool Stop, optional String Reason)
else else
Message = "Start spawn"; Message = "Start spawn";
if (Reason != "") if (Comment != "")
Message @= "(" $ Reason $ ")"; Message @= "(" $ Comment $ ")";
if (Message != SpawnTimerLastMessage) if (SpawnActive == Stop)
{ {
`ZS_Info(Message); `ZS_Info(Message);
SpawnTimerLastMessage = Message; SpawnActive = !Stop;
} }
} }
private function SpawnZeds(out Array<S_SpawnEntry> SpawnList, delegate<WaveCondition> Condition)
{
local S_SpawnEntry SE;
local int Index;
`ZS_Trace(`Location);
foreach SpawnList(SE, Index)
{
if (Condition(SE))
{
if (!ReadyToStart(SE)) continue;
if (ReadyToSpawn(SE))
SpawnEntry(SpawnListR, Index);
else
SpawnListR[Index].Delay -= dt;
}
}
}
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)));
}
}
private function bool ReadyToSpawn(S_SpawnEntry SE)
{
`ZS_Trace(`Location);
return SE.Delay <= 0 && SE.SpawnsLeft > 0;
}
private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index) 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())
{ {
FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount; FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount;
if (SpawnCount > FreeSpawnSlots) if (FreeSpawnSlots == 0)
{
NoFreeSpawnSlots = true;
SpawnList[Index].SpawnsLeft = 0;
return;
}
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()
@ -504,59 +596,76 @@ 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; 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++;
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
KFGIS.MyKFGRI.AIRemaining -= (SpawnCount - SpawnFailed);
} }
KFGIS.RefreshMonsterAliveCount(); if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
return SpawnCount - SpawnFailed; KFGIS.NumAIFinishedSpawning += Spawned;
KFGIS.NumAISpawnsQueued += Spawned;
}
KFGIS.UpdateAIRemaining();
return Spawned;
} }
public function NotifyLogin(Controller C) public function NotifyLogin(Controller C)

View File

@ -10,6 +10,8 @@ replication
LogLevel; LogLevel;
} }
public simulated function bool SafeDestroy() { if (!bPendingDelete) return Destroy(); else return true; }
public reliable client function ClientSync(class<KFPawn_Monster> CustomZed) public reliable client function ClientSync(class<KFPawn_Monster> CustomZed)
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -31,18 +33,18 @@ public reliable client function SyncFinished()
CustomZed.static.PreloadContent(); CustomZed.static.PreloadContent();
} }
Destroy(); SafeDestroy();
} }
public reliable server function ServerSync() public reliable server function ServerSync()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (CustomZeds.Length == Recieved) if (CustomZeds.Length == Recieved || WorldInfo.NetMode == NM_StandAlone)
{ {
`ZS_Debug("Sync finished"); `ZS_Debug("Sync finished");
SyncFinished(); SyncFinished();
Destroy(); SafeDestroy();
} }
else else
{ {

View File

@ -8,3 +8,5 @@
`define ZS_Info(msg) `log("INFO:" @ `msg, (LogLevel >= LL_Info), `ZS_Tag) `define ZS_Info(msg) `log("INFO:" @ `msg, (LogLevel >= LL_Info), `ZS_Tag)
`define ZS_Debug(msg) `log("DEBUG:" @ `msg, (LogLevel >= LL_Debug), `ZS_Tag) `define ZS_Debug(msg) `log("DEBUG:" @ `msg, (LogLevel >= LL_Debug), `ZS_Tag)
`define ZS_Trace(msg) `log("TRACE:" @ `msg, (LogLevel >= LL_Trace), `ZS_Tag) `define ZS_Trace(msg) `log("TRACE:" @ `msg, (LogLevel >= LL_Trace), `ZS_Tag)
`define NO_CONFIG 0