add custom spawn manager

This commit is contained in:
GenZmeY 2022-05-17 21:02:07 +03:00
parent e5e2b711f0
commit e7e141d379
21 changed files with 1172 additions and 140 deletions

View File

@ -0,0 +1,350 @@
class AISpawnManager extends KFAISpawnManager
abstract;
const class<Config_SpawnManager> Config;
struct S_DifficultyWaveInfo
{
var Array<AIWaveInfo> Waves;
};
var protected ZedSpawner ZS;
var protected Array<S_DifficultyWaveInfo> V_DifficultyWaveSettings;
var protected S_DifficultyWaveInfo V_WaveSettings;
var protected Array<AISpawnSquad> V_AvailableSquads;
var public E_LogLevel LogLevel;
private function CopySpawnSquadArray(Array<KFAISpawnSquad> From, out Array<AISpawnSquad> To)
{
local KFAISpawnSquad SS;
`ZS_Trace(`Location);
To.Length = 0;
foreach From(SS)
To.AddItem(class'AISpawnSquad'.static.CreateFrom(SS));
}
private function ZedSpawner GetZedSpawner()
{
foreach WorldInfo.DynamicActors(class'ZedSpawner', ZS)
return ZS;
return None;
}
public function Initialize()
{
ZS = GetZedSpawner();
if (ZS != None)
{
LogLevel = ZS.LogLevel;
`ZS_Trace(`Location);
}
else
{
`ZS_Log("FATAL: no ZedSpawner found! Destroy" @ Self.class);
`ZS_Log("FATAL:" @ `Location);
Destroy();
return;
}
// TODO:
Super.Initialize();
}
public function GetWaveSettings(out DifficultyWaveInfo WaveInfo)
{
`ZS_Trace(`Location);
if (DifficultyWaveSettings.Length > 0)
WaveInfo = DifficultyWaveSettings[Clamp(GameDifficulty, 0, DifficultyWaveSettings.Length - 1)];
V_GetWaveSettings(V_WaveSettings);
}
protected function V_GetWaveSettings(out S_DifficultyWaveInfo WaveInfo)
{
`ZS_Trace(`Location);
if (V_DifficultyWaveSettings.Length > 0)
WaveInfo = V_DifficultyWaveSettings[Clamp(GameDifficulty, 0, V_DifficultyWaveSettings.Length - 1)];
}
public function SetupNextWave(byte NextWaveIndex, int TimeToNextWaveBuffer = 0)
{
local KFGameReplicationInfo KFGRI;
`ZS_Trace(`Location);
if (OutbreakEvent.ActiveEvent.bBossRushMode)
{
NextWaveIndex = MyKFGRI.WaveMax - 1;
}
if (NextWaveIndex < V_WaveSettings.Waves.Length)
{
if (GameDifficulty < RecycleSpecialSquad.Length)
{
bRecycleSpecialSquad = RecycleSpecialSquad[GameDifficulty];
}
else
{
bRecycleSpecialSquad = RecycleSpecialSquad[RecycleSpecialSquad.Length - 1];
}
LeftoverSpawnSquad.Length = 0;
NumSpawnListCycles = 1;
NumSpecialSquadRecycles = 0;
if (MyKFGRI.IsBossWave() || OutbreakEvent.ActiveEvent.bBossRushMode)
{
WaveTotalAI = 1;
}
else
{
if (V_WaveSettings.Waves[NextWaveIndex].bRecycleWave)
{
WaveTotalAI = V_WaveSettings.Waves[NextWaveIndex].MaxAI *
DifficultyInfo.GetPlayerNumMaxAIModifier(GetNumHumanTeamPlayers()) *
DifficultyInfo.GetDifficultyMaxAIModifier();
}
else
{
WaveTotalAI = V_WaveSettings.Waves[NextWaveIndex].MaxAI;
}
WaveTotalAI *= GetTotalWaveCountScale();
WaveTotalAI = Max(1, WaveTotalAI);
}
GetAvailableSquads(NextWaveIndex, true);
WaveStartTime = WorldInfo.TimeSeconds;
TimeUntilNextSpawn = 5.f + TimeToNextWaveBuffer;
if (NextWaveIndex == 0)
{
TotalWavesActiveTime = 0;
}
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if (KFGRI != None && (KFGRI.bDebugSpawnManager || KFGRI.bGameConductorGraphingEnabled))
{
KFGRI.CurrentSineMod = GetSineMod();
KFGRI.CurrentNextSpawnTime = TimeUntilNextSpawn;
KFGRI.CurrentSineWavFreq = GetSineWaveFreq();
KFGRI.CurrentNextSpawnTimeMod = GetNextSpawnTimeMod();
KFGRI.CurrentTotalWavesActiveTime = TotalWavesActiveTime;
KFGRI.CurrentMaxMonsters = GetMaxMonsters();
KFGRI.CurrentTimeTilNextSpawn = TimeUntilNextSpawn;
}
}
LastAISpawnVolume = None;
}
public function GetAvailableSquads(byte MyWaveIndex, optional bool bNeedsSpecialSquad=false)
{
local int i, j, TotalZedsInSquads;
`ZS_Trace(`Location);
if (V_WaveSettings.Waves[MyWaveIndex] != None)
{
NumSpawnListCycles++;
V_WaveSettings.Waves[MyWaveIndex].GetNewSquadList(V_AvailableSquads);
if (bNeedsSpecialSquad)
{
V_WaveSettings.Waves[MyWaveIndex].GetSpecialSquad(V_AvailableSquads);
for (i = 0; i < V_AvailableSquads.Length; i++)
{
for (j = 0; j < V_AvailableSquads[i].MonsterList.Length; j++)
{
TotalZedsInSquads += V_AvailableSquads[i].MonsterList[j].Num;
}
}
if (WaveTotalAI < TotalZedsInSquads)
{
bForceRequiredSquad = true;
}
}
}
}
public function V_GetSpawnListFromSquad(byte SquadIdx, out Array<AISpawnSquad> SquadsList, out Array<class<KFPawn_Monster> > AISpawnList)
{
local AISpawnSquad Squad;
local EAIType AIType;
local int i, j, RandNum;
local ESquadType LargestMonsterSquadType;
local Array<class<KFPawn_Monster> > TempSpawnList;
local int RandBossIndex;
`ZS_Trace(`Location);
Squad = SquadsList[SquadIdx];
LargestMonsterSquadType = EST_Crawler;
for (i = 0; i < Squad.MonsterList.Length; i++)
{
for (j = 0; j < Squad.MonsterList[i].Num; j++)
{
if (Squad.MonsterList[i].CustomClass != None)
{
TempSpawnList.AddItem(Squad.MonsterList[i].CustomClass);
}
else
{
AIType = Squad.MonsterList[i].Type;
if (AIType == AT_BossRandom)
{
if (OutbreakEvent.ActiveEvent.bBossRushMode)
{
RandBossIndex = Rand(BossRushEnemies.length);
TempSpawnList.AddItem( default.AIBossClassList[BossRushEnemies[RandBossIndex]]);
BossRushEnemies.Remove(RandBossIndex, 1);
}
else
{
TempSpawnList.AddItem(GetBossAISpawnType());
}
LargestMonsterSquadType = EST_Boss;
}
else
{
TempSpawnList.AddItem(GetAISpawnType(AIType));
}
}
if (TempSpawnList[TempSpawnList.Length - 1].default.MinSpawnSquadSizeType < LargestMonsterSquadType)
{
LargestMonsterSquadType = TempSpawnList[TempSpawnList.Length - 1].default.MinSpawnSquadSizeType;
}
}
}
if (TempSpawnList.Length > 0)
{
while (TempSpawnList.Length > 0)
{
RandNum = Rand( TempSpawnList.Length);
AISpawnList.AddItem( TempSpawnList[RandNum]);
TempSpawnList.Remove( RandNum, 1);
}
DesiredSquadType = Squad.MinVolumeType;
if (LargestMonsterSquadType < DesiredSquadType)
{
DesiredSquadType = LargestMonsterSquadType;
}
}
}
public function Array<class<KFPawn_Monster> > GetNextSpawnList()
{
local Array<class<KFPawn_Monster> > NewSquad, RequiredSquad;
local int RandNum, AINeeded;
`ZS_Trace(`Location);
if (LeftoverSpawnSquad.Length > 0)
{
NewSquad = LeftoverSpawnSquad;
SetDesiredSquadTypeForZedList(NewSquad);
}
else
{
if (!IsAISquadAvailable())
{
if (!bSummoningBossMinions)
{
if (bRecycleSpecialSquad && NumSpawnListCycles % 2 == 1 && (MaxSpecialSquadRecycles == -1 || NumSpecialSquadRecycles < MaxSpecialSquadRecycles))
{
GetAvailableSquads(MyKFGRI.WaveNum - 1, true);
++NumSpecialSquadRecycles;
}
else
{
GetAvailableSquads(MyKFGRI.WaveNum - 1);
}
}
else
{
CopySpawnSquadArray(BossMinionsSpawnSquads, V_AvailableSquads);
}
}
RandNum = Rand(V_AvailableSquads.Length);
if (bForceRequiredSquad && RandNum == (V_AvailableSquads.Length - 1))
{
bForceRequiredSquad=false;
}
V_GetSpawnListFromSquad(RandNum, V_AvailableSquads, NewSquad);
if (bForceRequiredSquad)
{
V_GetSpawnListFromSquad((V_AvailableSquads.Length - 1), V_AvailableSquads, RequiredSquad);
if ((NumAISpawnsQueued + NewSquad.Length + RequiredSquad.Length) > WaveTotalAI)
{
NewSquad = RequiredSquad;
RandNum = (V_AvailableSquads.Length - 1);
bForceRequiredSquad=false;
}
}
V_AvailableSquads.Remove(RandNum, 1);
}
AINeeded = GetNumAINeeded();
if (AINeeded < NewSquad.Length)
{
LeftoverSpawnSquad = NewSquad;
LeftoverSpawnSquad.Remove(0, AINeeded);
NewSquad.Length = AINeeded;
}
else
{
LeftoverSpawnSquad.Length = 0;
}
return NewSquad;
}
public function bool IsAISquadAvailable()
{
`ZS_Trace(`Location);
return (V_AvailableSquads.Length > 0);
}
public function SummonBossMinions(Array<KFAISpawnSquad> NewMinionSquad, int NewMaxBossMinions, optional bool bUseLivingPlayerScale = true)
{
`ZS_Trace(`Location);
CopySpawnSquadArray(NewMinionSquad, V_AvailableSquads);
Super.SummonBossMinions(NewMinionSquad, NewMaxBossMinions, bUseLivingPlayerScale);
}
public function StopSummoningBossMinions()
{
`ZS_Trace(`Location);
V_AvailableSquads.Length = 0;
Super.StopSummoningBossMinions();
}
defaultproperties
{
Config = class'Config_SpawnManager'
}

View File

@ -0,0 +1,118 @@
class AISpawnManager_Endless extends AISpawnManager
within KFGameInfo_Endless;
struct S_MacroDifficultyWaveInfo
{
var Array<S_DifficultyWaveInfo> MacroDifficultyWaveSettings;
};
struct MacroDifficultyWaveInfo
{
var Array<DifficultyWaveInfo> MacroDifficultyWaveSettings;
};
var protected Array<MacroDifficultyWaveInfo> DifficultyWaves;
var protected Array<S_MacroDifficultyWaveInfo> V_DifficultyWaves;
public function SetupNextWave(byte NextWaveIndex, int TimeToNextWaveBuffer = 0)
{
`ZS_Trace(`Location);
Super.SetupNextWave(NextWaveIndex % WaveSettings.Waves.length, TimeToNextWaveBuffer);
}
public function GetAvailableSquads(byte MyWaveIndex, optional bool bNeedsSpecialSquad = false)
{
`ZS_Trace(`Location);
Super.GetAvailableSquads(MyWaveIndex % WaveSettings.Waves.length, bNeedsSpecialSquad);
}
public function GetWaveSettings(out DifficultyWaveInfo WaveInfo)
{
local int AdGD; // AdjustedGameDifficulty
local int AvAdGD; // AvailableAdjustedGameDifficulty
local int AvGD; // AvailableGameDifficulty
local int DWL; // DifficultyWavesLength
local int MDWSL; // MacroDifficultyWaveSettingsLength
`ZS_Trace(`Location);
DWL = DifficultyWaves.Length;
if (DWL > 0)
{
AvGD = Clamp(GameDifficulty, 0, DWL - 1);
MDWSL = DifficultyWaves[AvGD].MacroDifficultyWaveSettings.Length;
if (MDWSL > 0)
{
AdGD = EndlessDifficulty.GetCurrentDifficultyIndex();
AvAdGD = Clamp(AdGD, 0, MDWSL - 1);
WaveInfo = DifficultyWaves[AvGD].MacroDifficultyWaveSettings[AvAdGD];
}
}
V_GetWaveSettings(V_WaveSettings);
}
protected function V_GetWaveSettings(out S_DifficultyWaveInfo WaveInfo)
{
local int AdGD; // AdjustedGameDifficulty
local int AvAdGD; // AvailableAdjustedGameDifficulty
local int AvGD; // AvailableGameDifficulty
local int VDWL; // V_DifficultyWavesLength
local int MDWSL; // MacroDifficultyWaveSettingsLength
`ZS_Trace(`Location);
VDWL = V_DifficultyWaves.Length;
if (VDWL > 0)
{
AvGD = Clamp(GameDifficulty, 0, VDWL - 1);
MDWSL = V_DifficultyWaves[AvGD].MacroDifficultyWaveSettings.Length;
if (MDWSL > 0)
{
AdGD = EndlessDifficulty.GetCurrentDifficultyIndex();
AvAdGD = Clamp(AdGD, 0, MDWSL - 1);
WaveInfo = V_DifficultyWaves[AvGD].MacroDifficultyWaveSettings[AvAdGD];
}
}
}
public function OnDifficultyUpdated()
{
`ZS_Trace(`Location);
GetWaveSettings(WaveSettings);
}
public function OnBossDied()
{
`ZS_Trace(`Location);
BossMinionsSpawnSquads.length = 0;
AvailableSquads.length = 0;
V_AvailableSquads.length = 0;
}
public function float GetNextSpawnTimeMod()
{
local float SpawnTimeMod, SpawnTimeModMin;
local int TempModIdx;
`ZS_Trace(`Location);
SpawnTimeMod = super.GetNextSpawnTimeMod();
if (MyKFGRI.IsSpecialWave(TempModIdx))
{
SpawnTimeModMin = EndlessDifficulty.GetSpecialWaveSpawnTimeModMin(SpecialWaveType);
SpawnTimeMod = Max(SpawnTimeMod, SpawnTimeModMin);
}
return SpawnTimeMod;
}
defaultproperties
{
Config = class'Config_SpawnManager_Endless'
}

View File

@ -0,0 +1,6 @@
class AISpawnManager_Long extends AISpawnManager;
DefaultProperties
{
Config = class'Config_SpawnManager_Long'
}

View File

@ -0,0 +1,6 @@
class AISpawnManager_Normal extends AISpawnManager;
DefaultProperties
{
Config = class'Config_SpawnManager_Normal'
}

View File

@ -0,0 +1,6 @@
class AISpawnManager_Short extends AISpawnManager;
DefaultProperties
{
Config = class'Config_SpawnManager_Short'
}

View File

@ -0,0 +1,61 @@
class AISpawnSquad extends Object
hidecategories(Object);
struct S_AISquadElement
{
var() EAIType Type;
var() byte Num <ClampMin=1 | ClampMax=6>;
var class<KFPawn_Monster> CustomClass;
structdefaultproperties
{
Num = 1
}
};
var() ESquadType MinVolumeType;
var() array<S_AISquadElement> MonsterList;
public function AISpawnSquad InitFrom(KFAISpawnSquad SpawnSquad)
{
local AISquadElement SE;
local S_AISquadElement SSE;
MinVolumeType = SpawnSquad.MinVolumeType;
foreach SpawnSquad.MonsterList(SE)
{
SSE.Type = SE.Type;
SSE.Num = SE.Num;
SSE.CustomClass = SE.CustomClass;
MonsterList.AddItem(SSE);
}
return Self;
}
public static function AISpawnSquad CreateFrom(KFAISpawnSquad SpawnSquad)
{
local AISquadElement SE;
local S_AISquadElement SSE;
local AISpawnSquad NewSpawnSquad;
NewSpawnSquad = new class'AISpawnSquad';
NewSpawnSquad.MinVolumeType = SpawnSquad.MinVolumeType;
foreach SpawnSquad.MonsterList(SE)
{
SSE.Type = SE.Type;
SSE.Num = SE.Num;
SSE.CustomClass = SE.CustomClass;
NewSpawnSquad.MonsterList.AddItem(SSE);
}
return NewSpawnSquad;
}
defaultproperties
{
MinVolumeType = EST_Medium
}

View File

@ -0,0 +1,71 @@
class AIWaveInfo extends Object
dependson(AISpawnSquad)
hidecategories(Object);
var() bool bRecycleWave;
var() Array<AISpawnSquad> Squads;
var() Array<AISpawnSquad> SpecialSquads;
var() int MaxAI<ClampMin=1|ClampMax=200|DisplayName=TotalAIBase>;
var() Array<AISpawnSquad> EventSquads;
public function GetNewSquadList(out Array<AISpawnSquad> out_SquadList)
{
local AISpawnSquad SS;
out_SquadList.Length = 0;
foreach Squads(SS)
if (SS != None)
out_SquadList.AddItem(SS);
}
public function GetSpecialSquad(out Array<AISpawnSquad> out_SquadList)
{
if (SpecialSquads.Length > 0)
out_SquadList.AddItem(SpecialSquads[Rand(SpecialSquads.Length)]);
}
public function GetEventSquadList(out Array<AISpawnSquad> out_SquadList)
{
local AISpawnSquad SS;
out_SquadList.Length = 0;
foreach EventSquads(SS)
if (SS != None)
out_SquadList.AddItem(SS);
}
public function InitFrom(KFAIWaveInfo WaveInfo)
{
local KFAISpawnSquad KFSS;
bRecycleWave = WaveInfo.bRecycleWave;
MaxAI = WaveInfo.MaxAI;
Squads.Length = 0;
foreach WaveInfo.Squads(KFSS)
Squads.AddItem(class'AISpawnSquad'.static.CreateFrom(KFSS));
SpecialSquads.Length = 0;
foreach WaveInfo.SpecialSquads(KFSS)
SpecialSquads.AddItem(class'AISpawnSquad'.static.CreateFrom(KFSS));
EventSquads.Length = 0;
foreach WaveInfo.EventSquads(KFSS)
EventSquads.AddItem(class'AISpawnSquad'.static.CreateFrom(KFSS));
}
public static function AIWaveInfo CreateFrom(KFAIWaveInfo WaveInfo)
{
local AIWaveInfo AIWI;
AIWI = new class'AIWaveInfo';
AIWI.InitFrom(WaveInfo);
return AIWI;
}
defaultproperties
{
bRecycleWave = true
MaxAI = 32
}

View File

@ -1,4 +1,4 @@
class SpawnListBossWaves extends Object class Config_SpawnListBossWaves extends Object
dependson(ZedSpawner) dependson(ZedSpawner)
config(ZedSpawner); config(ZedSpawner);
@ -25,7 +25,7 @@ public static function InitConfig()
default.Spawn.Length = 0; default.Spawn.Length = 0;
SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing"; SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing";
SpawnEntry.ZedClass = "SomePackage.SomeFleshpoundClass"; SpawnEntry.ZedClass = "SomePackage.SomeClass";
SpawnEntry.SpawnCountBase = 2; SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.Delay = 60; SpawnEntry.Delay = 60;
@ -33,6 +33,10 @@ public static function InitConfig()
SpawnEntry.bSpawnAtPlayerStart = false; SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry); default.Spawn.AddItem(SpawnEntry);
// TODO:
//SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing";
//default.Spawn.AddItem(SpawnEntry);
StaticSaveConfig(); StaticSaveConfig();
} }

View File

@ -1,4 +1,4 @@
class SpawnListRegular extends Object class Config_SpawnListRegular extends Object
dependson(ZedSpawner) dependson(ZedSpawner)
config(ZedSpawner); config(ZedSpawner);

View File

@ -1,4 +1,4 @@
class SpawnListSpecialWaves extends Object class Config_SpawnListSpecialWaves extends Object
dependson(ZedSpawner) dependson(ZedSpawner)
config(ZedSpawner); config(ZedSpawner);

View File

@ -0,0 +1,37 @@
class Config_SpawnManager extends Object
config(ZedSpawner);
var const class<KFAISpawnManager> DefSpawnManager;
public static function InitConfig()
{
local DifficultyWaveInfo DWI;
local KFAIWaveInfo KFAIWI;
local AIWaveInfo AIWI;
local int Diff, Wave;
`ZS_Log("InitConfig:" @ default.DefSpawnManager);
foreach default.DefSpawnManager.default.DifficultyWaveSettings(DWI, Diff)
{
`ZS_Log(" Diff:" @ Diff);
foreach DWI.Waves(KFAIWI, Wave)
{
`ZS_Log(" Wave:" @ Wave);
AIWI = class'AIWaveInfo'.static.CreateFrom(KFAIWI);
}
}
//StaticSaveConfig();
}
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
Errors = false;
return !Errors;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager'
}

View File

@ -0,0 +1,14 @@
class Config_SpawnManager_Endless extends Config_SpawnManager
config(ZedSpawner);
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
Errors = false;
return !Errors;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager_Endless'
}

View File

@ -0,0 +1,14 @@
class Config_SpawnManager_Long extends Config_SpawnManager
config(ZedSpawner);
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
Errors = false;
return !Errors;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager_Long'
}

View File

@ -0,0 +1,14 @@
class Config_SpawnManager_Normal extends Config_SpawnManager
config(ZedSpawner);
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
Errors = false;
return !Errors;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager_Normal'
}

View File

@ -0,0 +1,14 @@
class Config_SpawnManager_Short extends Config_SpawnManager
config(ZedSpawner);
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
Errors = false;
return !Errors;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager_Short'
}

View File

@ -0,0 +1,184 @@
class Config_SpawnManager_WaveInfo extends Object
abstract
config(ZedSpawnManager);
var const class<KFAISpawnManager> DefSpawnManager;
var const byte Difficulty;
var const byte Wave;
struct Unit
{
var int Num;
var String ZedClass;
};
struct Squad
{
var ESquadType MinVolumeType;
var Array<Unit> Units;
};
var config bool bRecycleWave;
var config int MaxAI;
var config Array<Squad> Squads;
var config Array<Squad> SquadsSpecial;
var config Array<Squad> SquadsEvent;
public static function InitConfig(KFGI_Access KFGIA)
{
local KFAIWaveInfo KFAIWI;
local KFAISpawnSquad KFAISS;
local AISquadElement AISE;
local Squad S;
local Unit U;
KFAIWI = default.DefSpawnManager.default.DifficultyWaveSettings[default.Difficulty].Waves[default.Wave];
default.bRecycleWave = KFAIWI.bRecycleWave;
default.MaxAI = KFAIWI.MaxAI;
default.Squads.Length = 0;
foreach KFAIWI.Squads(KFAISS)
{
S.MinVolumeType = KFAISS.MinVolumeType;
foreach KFAISS.MonsterList(AISE)
{
U.ZedClass = GetPawnClassString(KFGIA, AISE);
U.Num = AISE.Num;
S.Units.AddItem(U);
}
default.Squads.AddItem(S);
}
default.SquadsSpecial.Length = 0;
foreach KFAIWI.SpecialSquads(KFAISS)
{
S.MinVolumeType = KFAISS.MinVolumeType;
foreach KFAISS.MonsterList(AISE)
{
U.ZedClass = GetPawnClassString(KFGIA, AISE);
U.Num = AISE.Num;
S.Units.AddItem(U);
}
default.SquadsSpecial.AddItem(S);
}
default.SquadsEvent.Length = 0;
foreach KFAIWI.EventSquads(KFAISS)
{
S.MinVolumeType = KFAISS.MinVolumeType;
foreach KFAISS.MonsterList(AISE)
{
U.ZedClass = GetPawnClassString(KFGIA, AISE);
U.Num = AISE.Num;
S.Units.AddItem(U);
}
default.SquadsEvent.AddItem(S);
}
StaticSaveConfig();
}
private static function String GetPawnClassString(KFGI_Access KFGIA, AISquadElement AISE)
{
local class<KFPawn_Monster> KFPMC;
KFPMC = KFGIA.AITypePawn(AISE.Type);
if (KFPMC == None)
KFPMC = AISE.CustomClass;
return "KFGameContent." $ String(KFPMC);
}
public static function AIWaveInfo Load(E_LogLevel LogLevel, KFGI_Access KFGIA)
{
local class<KFPawn_Monster> KFPMC;
local AIWaveInfo AIWI;
local AISpawnSquad AISS;
local S_AISquadElement AISE;
local Squad S;
local Unit U;
AIWI = new class'AIWaveInfo';
AIWI.bRecycleWave = default.bRecycleWave;
AIWI.MaxAI = default.MaxAI;
foreach default.Squads(S)
{
AISS = new class'AISpawnSquad';
AISS.MinVolumeType = S.MinVolumeType;
foreach S.Units(U)
{
KFPMC = class<KFPawn_Monster>(DynamicLoadObject(U.ZedClass, class'Class'));
if (KFPMC == None)
{
`ZS_Warn("Can't load zed class:" @ U.ZedClass);
continue;
}
if (!KFGIA.IsOriginalAI(KFPMC, AISE.Type))
AISE.CustomClass = KFPMC;
AISE.Num = AISE.Num;
AISS.MonsterList.AddItem(AISE);
}
AIWI.Squads.AddItem(AISS);
}
foreach default.SquadsSpecial(S)
{
AISS = new class'AISpawnSquad';
AISS.MinVolumeType = S.MinVolumeType;
foreach S.Units(U)
{
KFPMC = class<KFPawn_Monster>(DynamicLoadObject(U.ZedClass, class'Class'));
if (KFPMC == None)
{
`ZS_Warn("Can't load zed class:" @ U.ZedClass);
continue;
}
if (!KFGIA.IsOriginalAI(KFPMC, AISE.Type))
AISE.CustomClass = KFPMC;
AISE.Num = AISE.Num;
AISS.MonsterList.AddItem(AISE);
}
AIWI.SpecialSquads.AddItem(AISS);
}
foreach default.SquadsEvent(S)
{
AISS = new class'AISpawnSquad';
AISS.MinVolumeType = S.MinVolumeType;
foreach S.Units(U)
{
KFPMC = class<KFPawn_Monster>(DynamicLoadObject(U.ZedClass, class'Class'));
if (KFPMC == None)
{
`ZS_Warn("Can't load zed class:" @ U.ZedClass);
continue;
}
if (!KFGIA.IsOriginalAI(KFPMC, AISE.Type))
AISE.CustomClass = KFPMC;
AISE.Num = AISE.Num;
AISS.MonsterList.AddItem(AISE);
}
AIWI.EventSquads.AddItem(AISS);
}
return AIWI;
}
defaultproperties
{
DefSpawnManager = class'KFAISpawnManager'
Difficulty = 255
Wave = 255
}

View File

@ -1,4 +1,4 @@
class Spawn extends Object class Config_Spawner extends Object
dependson(ZedSpawner) dependson(ZedSpawner)
config(ZedSpawner); config(ZedSpawner);

View File

@ -1,9 +1,7 @@
class KFGI_Access extends Object class KFGI_Access extends Object
within KFGameInfo_Survival; within KFGameInfo;
// Bypass protected modifier for these lists public function bool IsCustomZed(class<KFPawn_Monster> KFPM)
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 +9,51 @@ function bool IsCustomZed(class<KFPawn_Monster> KFPM)
return true; return true;
} }
// WARN: <optional out> - can it work? need check
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

@ -3,22 +3,19 @@ class ZedSpawner extends Info
const dt = 1; const dt = 1;
const CfgSpawn = class'Spawn'; const CfgSpawn = class'Config_Spawner';
const CfgSpawnListR = class'SpawnListRegular'; const CfgSpawnListR = class'Config_SpawnListRegular';
const CfgSpawnListBW = class'SpawnListBossWaves'; const CfgSpawnListBW = class'Config_SpawnListBossWaves';
const CfgSpawnListSW = class'SpawnListSpecialWaves'; const CfgSpawnListSW = class'Config_SpawnListSpecialWaves';
const CfgSpawnManagerE = class'Config_SpawnManager_Endless';
const CfgSpawnManagerS = class'Config_SpawnManager_Short';
const CfgSpawnManagerN = class'Config_SpawnManager_Normal';
const CfgSpawnManagerL = class'Config_SpawnManager_Long';
enum E_LogLevel const SpawnManagerS = class'AISpawnManager_Short';
{ const SpawnManagerN = class'AISpawnManager_Normal';
LL_WrongLevel, const SpawnManagerL = class'AISpawnManager_Long';
LL_Fatal, const SpawnManagerE = class'AISpawnManager_Endless';
LL_Error,
LL_Warning,
LL_Info,
LL_Debug,
LL_Trace,
LL_All
};
struct S_SpawnEntry struct S_SpawnEntry
{ {
@ -66,6 +63,192 @@ var private Array<class<KFPawn_Monster> > CustomZeds;
delegate bool WaveCondition(S_SpawnEntry SE); delegate bool WaveCondition(S_SpawnEntry SE);
event PreBeginPlay()
{
`ZS_Trace(`Location);
Super.PreBeginPlay();
PreInit();
}
event PostBeginPlay()
{
`ZS_Trace(`Location);
if (bPendingDelete) return;
Super.PostBeginPlay();
PostInit();
}
private function PreInit()
{
`ZS_Trace(`Location);
if (WorldInfo.NetMode == NM_Client)
{
`ZS_Fatal("NetMode == NM_Client, Destroy...");
Destroy();
return;
}
if (!bConfigInitialized)
{
bConfigInitialized = true;
LogLevel = LL_Info;
SaveConfig();
CfgSpawn.static.InitConfig();
CfgSpawnListR.static.InitConfig();
CfgSpawnListBW.static.InitConfig();
CfgSpawnListSW.static.InitConfig();
CfgSpawnManagerS.static.InitConfig();
CfgSpawnManagerN.static.InitConfig();
CfgSpawnManagerL.static.InitConfig();
CfgSpawnManagerE.static.InitConfig();
`ZS_Info("Config initialized.");
}
CfgSpawnManagerS.static.InitConfig();
CfgSpawnManagerN.static.InitConfig();
CfgSpawnManagerL.static.InitConfig();
CfgSpawnManagerE.static.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;
}
}
private function PostInit()
{
local S_SpawnEntry SE;
local bool NeedInitSM;
`ZS_Trace(`Location);
CurrentWave = INDEX_NONE;
KFGIS = KFGameInfo_Survival(WorldInfo.Game);
if (KFGIS == None)
{
`ZS_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy...");
Destroy();
return;
}
KFGIA = new(KFGIS) class'KFGI_Access';
KFGIE = KFGameInfo_Endless(KFGIS);
SpawnListR = CfgSpawnListR.static.Load(LogLevel);
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
CfgSpawnManagerE.static.Load(LogLevel);
CfgSpawnManagerS.static.Load(LogLevel);
CfgSpawnManagerN.static.Load(LogLevel);
CfgSpawnManagerL.static.Load(LogLevel);
SpecialWave = INDEX_NONE;
CurrentCycle = 1;
CycleWaveSize = 0;
CycleWaveShift = MaxInt;
foreach SpawnListR(SE)
{
CycleWaveShift = Min(CycleWaveShift, SE.Wave);
CycleWaveSize = Max(CycleWaveSize, SE.Wave);
}
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
foreach SpawnListBW(SE)
if (BossClassCache.Find(SE.BossClass) == INDEX_NONE)
BossClassCache.AddItem(SE.BossClass);
if (true)
{
NeedInitSM = (KFGIS.SpawnManager != None);
KFGIS.SpawnManagerClasses.Length = 0;
if (KFGIE != None)
{
KFGIE.SpawnManagerClasses.AddItem(SpawnManagerE);
}
else
{
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerS);
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerN);
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerL);
}
if (NeedInitSM)
{
KFGIS.InitSpawnManager();
}
`ZS_Info("SpawnManager replaced");
}
PreparePreloadContent();
if (SpawnListSW.Length > 0 || SpawnListBW.Length > 0 || SpawnListR.Length > 0)
{
SetTimer(float(dt), true, nameof(SpawnTimer));
}
else
{
`ZS_Info("Spawn timer disabled (no spawn lists)", LogLevel);
}
}
private function PreparePreloadContent()
{
local class<KFPawn_Monster> PawnClass;
ExtractCustomZedsFromSpawnList(SpawnListR, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
ExtractCustomZedsFromSpawnManager(AISpawnManager(KFGIS.SpawnManager), CustomZeds);
foreach CustomZeds(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))
{
`ZS_Debug("Add custom zed:" @ SE.ZedClass);
Out.AddItem(SE.ZedClass);
}
}
}
private function ExtractCustomZedsFromSpawnManager(AISpawnManager SpawnManager, out Array<class<KFPawn_Monster> > Out)
{
// TODO
}
private function PreloadContent(Array<class<KFPawn_Monster> > Pawns)
{
local class<KFPawn_Monster> KFPM;
foreach Pawns(KFPM) KFPM.static.PreloadContent();
}
public function bool WaveConditionRegular(S_SpawnEntry SE) public function bool WaveConditionRegular(S_SpawnEntry SE)
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -108,119 +291,6 @@ public function bool WaveConditionSpecial(S_SpawnEntry SE)
return (SE.Wave == SpecialWave); return (SE.Wave == SpecialWave);
} }
event PostBeginPlay()
{
`ZS_Trace(`Location);
Super.PostBeginPlay();
if (WorldInfo.NetMode == NM_Client)
{
Destroy();
return;
}
Init();
}
private function Init()
{
local S_SpawnEntry SE;
`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);
if (KFGIS == None)
{
`ZS_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy...");
Destroy();
return;
}
KFGIA = new(KFGIS) class'KFGI_Access';
KFGIE = KFGameInfo_Endless(KFGIS);
SpawnListR = CfgSpawnListR.static.Load(LogLevel);
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
SpecialWave = INDEX_NONE;
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 (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();
}
if (BossClassCache.Find(SE.BossClass) == INDEX_NONE)
BossClassCache.AddItem(SE.BossClass);
}
foreach SpawnListSW(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();
}
}
SetTimer(float(dt), true, nameof(SpawnTimer));
}
private function SpawnTimer() private function SpawnTimer()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);

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

@ -0,0 +1,18 @@
class _CommonTypes extends Object;
enum E_LogLevel
{
LL_WrongLevel,
LL_Fatal,
LL_Error,
LL_Warning,
LL_Info,
LL_Debug,
LL_Trace,
LL_All
};
defaultproperties
{
}