2 Commits

View File

@ -6,7 +6,7 @@ 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';
@ -43,9 +43,15 @@ struct S_SpawnEntry
var private config int Version; var private config int Version;
var private 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;
@ -63,6 +69,7 @@ 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 String SpawnTimerLastMessage;
var private String SpawnListsComment;
delegate bool WaveCondition(S_SpawnEntry SE); delegate bool WaveCondition(S_SpawnEntry SE);
@ -100,7 +107,7 @@ private function InitConfig()
} }
CfgSpawn.static.InitConfig(Version, LatestVersion); CfgSpawn.static.InitConfig(Version, LatestVersion);
CfgSpawnListR.static.InitConfig(Version, LatestVersion, KFGIA); CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA); CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListSW.static.InitConfig(Version, LatestVersion); CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
@ -165,7 +172,7 @@ private function Init()
return; return;
} }
SpawnListR = CfgSpawnListR.static.Load(LogLevel); 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);
@ -177,7 +184,7 @@ private function Init()
{ {
CycleWaveSize = 0; CycleWaveSize = 0;
CycleWaveShift = MaxInt; CycleWaveShift = MaxInt;
foreach SpawnListR(SE) foreach SpawnListRW(SE)
{ {
CycleWaveShift = Min(CycleWaveShift, SE.Wave); CycleWaveShift = Min(CycleWaveShift, SE.Wave);
CycleWaveSize = Max(CycleWaveSize, SE.Wave); CycleWaveSize = Max(CycleWaveSize, SE.Wave);
@ -204,7 +211,7 @@ private function PreloadContent()
{ {
local class<KFPawn_Monster> PawnClass; local class<KFPawn_Monster> PawnClass;
ExtractCustomZedsFromSpawnList(SpawnListR, CustomZeds); ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds); ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds); ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
@ -231,48 +238,28 @@ private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, o
public function bool WaveConditionRegular(S_SpawnEntry SE) public function bool WaveConditionRegular(S_SpawnEntry SE)
{ {
`ZS_Trace(`Location);
return (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1)); return (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1));
} }
public function bool WaveConditionBoss(S_SpawnEntry SE) public function bool WaveConditionBoss(S_SpawnEntry SE)
{ {
local KFPawn_Monster KFPM;
local int Index;
`ZS_Trace(`Location);
if (CurrentBossClass == None) 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 false;
} else
return (SE.BossClass == CurrentBossClass); return (SE.BossClass == CurrentBossClass);
} }
public function bool WaveConditionSpecial(S_SpawnEntry SE) public function bool WaveConditionSpecial(S_SpawnEntry SE)
{ {
`ZS_Trace(`Location);
return (SE.Wave == SpecialWave); return (SE.Wave == SpecialWave);
} }
private function SpawnTimer() private function SpawnTimer()
{ {
local S_SpawnEntry SE;
local int Index;
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum) if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum)
@ -292,41 +279,76 @@ private function SpawnTimer()
return; return;
} }
if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn && KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount) if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn)
{ {
SpawnTimerLogger(true, "shadow spawn is active and no free spawn slots"); if (NoFreeSpawnSlots || KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount)
{
NoFreeSpawnSlots = true;
SpawnTimerLogger(true, "no free spawn slots");
return; return;
} }
SpawnTimerLogger(false);
if ((SpecialWave == INDEX_NONE && !KFGIS.MyKFGRI.IsBossWave())
|| (SpecialWave != INDEX_NONE && !CfgSpawnListSW.default.bStopRegularSpawn)
|| (KFGIS.MyKFGRI.IsBossWave() && !CfgSpawnListBW.default.bStopRegularSpawn))
{
SpawnZeds(SpawnListR, WaveConditionRegular);
} }
if (SpecialWave != INDEX_NONE) SpawnTimerLogger(false, SpawnListsComment);
foreach SpawnListCurrent(SE, Index)
{ {
SpawnZeds(SpawnListSW, WaveConditionSpecial); if (!ReadyToStart(SE))
{
continue;
} }
if (KFGIS.MyKFGRI.IsBossWave()) if (SE.Delay > 0)
{ {
SpawnZeds(SpawnListBW, WaveConditionBoss); SpawnListCurrent[Index].Delay -= dt;
continue;
}
if (SE.SpawnsLeft > 0)
{
SpawnEntry(SpawnListCurrent, Index);
}
} }
} }
private function SetupWave() private function SetupWave()
{ {
local Array<String> SpawnListNames;
local int WaveTotalAIDef; local int WaveTotalAIDef;
local String WaveTypeInfo;
`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)
{
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode;
if (SpecialWave != INDEX_NONE)
{
WaveTypeInfo = "Special:" @ EAIType(SpecialWave);
}
}
if (KFGIS.MyKFGRI.IsBossWave())
{
CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex));
if (CurrentBossClass == None)
{
`ZS_Error("Can't determine boss class:" @ CurrentBossClass);
}
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;
@ -338,24 +360,56 @@ 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 $ ")");
} }
}
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle)
{
CurrentCycle++;
`ZS_Info("Next spawn cycle started:" @ CurrentCycle);
}
ResetSpawnList(SpawnListR);
ResetSpawnList(SpawnListSW);
ResetSpawnList(SpawnListBW);
CurrentBossClass = None; CurrentBossClass = None;
if (KFGIE != None)
{
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode;
} }
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)
{
SpawnListNames.AddItem("regular");
FillCurrentSpawnList(SpawnListRW, WaveConditionRegular);
}
if (UseSpecialSpawnList)
{
SpawnListNames.AddItem("special");
FillCurrentSpawnList(SpawnListSW, WaveConditionSpecial);
}
if (UseBossSpawnList)
{
SpawnListNames.AddItem("boss");
FillCurrentSpawnList(SpawnListBW, WaveConditionBoss);
}
JoinArray(SpawnListNames, SpawnListsComment, ", ");
ResetSpawnList(SpawnListCurrent);
if (WaveTypeInfo != "")
{
WaveTypeInfo = "(" $ WaveTypeInfo $ ")";
}
`ZS_Info("Wave" @ CurrentWave @ WaveTypeInfo);
}
private function FillCurrentSpawnList(Array<S_SpawnEntry> SpawnList, delegate<WaveCondition> Condition)
{
local S_SpawnEntry SE;
`ZS_Trace(`Location);
foreach SpawnList(SE)
if (Condition(SE))
SpawnListCurrent.AddItem(SE);
} }
private function ResetSpawnList(out Array<S_SpawnEntry> List) private function ResetSpawnList(out Array<S_SpawnEntry> List)
@ -389,7 +443,7 @@ 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;
@ -399,12 +453,10 @@ private function ResetSpawnList(out Array<S_SpawnEntry> List)
List[Index].SpawnsLeft = List[Index].SpawnsTotal; List[Index].SpawnsLeft = List[Index].SpawnsTotal;
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)));
`ZS_Debug(SE.ZedClass @ "SpawnsTotal:" @ List[Index].SpawnsTotal @ "SingleSpawnLimit:" @ List[Index].SingleSpawnLimit);
} }
} }
private function SpawnTimerLogger(bool Stop, optional String Reason) private function SpawnTimerLogger(bool Stop, optional String Comment)
{ {
local String Message; local String Message;
@ -415,8 +467,8 @@ 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 (Message != SpawnTimerLastMessage)
{ {
@ -425,27 +477,6 @@ private function SpawnTimerLogger(bool Stop, optional String Reason)
} }
} }
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) private function bool ReadyToStart(S_SpawnEntry SE)
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -460,13 +491,6 @@ private function bool ReadyToStart(S_SpawnEntry SE)
} }
} }
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;
@ -488,7 +512,13 @@ private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
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); `ZS_Info("Not enough free slots to spawn, will spawn" @ FreeSpawnSlots @ "instead of" @ SpawnCount);
SpawnCount = FreeSpawnSlots; SpawnCount = FreeSpawnSlots;
@ -557,7 +587,7 @@ private function int SpawnZed(class<KFPawn_Monster> ZedClass, int SpawnCount, bo
local Vector SpawnLocation; local Vector SpawnLocation;
local KFPawn_Monster KFPM; local KFPawn_Monster KFPM;
local Controller C; local Controller C;
local int SpawnFailed; local int SpawnFailed, Spawned;
local int Index; local int Index;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -599,14 +629,17 @@ private function int SpawnZed(class<KFPawn_Monster> ZedClass, int SpawnCount, bo
C.Possess(KFPM, false); C.Possess(KFPM, false);
} }
Spawned = (SpawnCount - SpawnFailed);
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{ {
KFGIS.MyKFGRI.AIRemaining -= (SpawnCount - SpawnFailed); KFGIS.NumAIFinishedSpawning += Spawned;
KFGIS.NumAISpawnsQueued += Spawned;
} }
KFGIS.RefreshMonsterAliveCount(); KFGIS.UpdateAIRemaining();
return SpawnCount - SpawnFailed; return Spawned;
} }
public function NotifyLogin(Controller C) public function NotifyLogin(Controller C)