diff --git a/ZedSpawner/Classes/AISpawnManager.uc b/ZedSpawner/Classes/AISpawnManager.uc new file mode 100644 index 0000000..6c772ce --- /dev/null +++ b/ZedSpawner/Classes/AISpawnManager.uc @@ -0,0 +1,350 @@ +class AISpawnManager extends KFAISpawnManager + abstract; + +const class Config; + +struct S_DifficultyWaveInfo +{ + var Array Waves; +}; + +var protected ZedSpawner ZS; + +var protected Array V_DifficultyWaveSettings; +var protected S_DifficultyWaveInfo V_WaveSettings; +var protected Array V_AvailableSquads; + +var public E_LogLevel LogLevel; + +private function CopySpawnSquadArray(Array From, out Array 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 SquadsList, out Array > AISpawnList) +{ + local AISpawnSquad Squad; + local EAIType AIType; + local int i, j, RandNum; + local ESquadType LargestMonsterSquadType; + local Array > 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 > GetNextSpawnList() +{ + local Array > 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 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' +} diff --git a/ZedSpawner/Classes/AISpawnManager_Endless.uc b/ZedSpawner/Classes/AISpawnManager_Endless.uc new file mode 100644 index 0000000..5fb3d76 --- /dev/null +++ b/ZedSpawner/Classes/AISpawnManager_Endless.uc @@ -0,0 +1,118 @@ +class AISpawnManager_Endless extends AISpawnManager + within KFGameInfo_Endless; + +struct S_MacroDifficultyWaveInfo +{ + var Array MacroDifficultyWaveSettings; +}; + +struct MacroDifficultyWaveInfo +{ + var Array MacroDifficultyWaveSettings; +}; + +var protected Array DifficultyWaves; +var protected Array 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' +} diff --git a/ZedSpawner/Classes/AISpawnManager_Long.uc b/ZedSpawner/Classes/AISpawnManager_Long.uc new file mode 100644 index 0000000..8cc4f69 --- /dev/null +++ b/ZedSpawner/Classes/AISpawnManager_Long.uc @@ -0,0 +1,6 @@ +class AISpawnManager_Long extends AISpawnManager; + +DefaultProperties +{ + Config = class'Config_SpawnManager_Long' +} diff --git a/ZedSpawner/Classes/AISpawnManager_Normal.uc b/ZedSpawner/Classes/AISpawnManager_Normal.uc new file mode 100644 index 0000000..d777da3 --- /dev/null +++ b/ZedSpawner/Classes/AISpawnManager_Normal.uc @@ -0,0 +1,6 @@ +class AISpawnManager_Normal extends AISpawnManager; + +DefaultProperties +{ + Config = class'Config_SpawnManager_Normal' +} diff --git a/ZedSpawner/Classes/AISpawnManager_Short.uc b/ZedSpawner/Classes/AISpawnManager_Short.uc new file mode 100644 index 0000000..65d7e91 --- /dev/null +++ b/ZedSpawner/Classes/AISpawnManager_Short.uc @@ -0,0 +1,6 @@ +class AISpawnManager_Short extends AISpawnManager; + +DefaultProperties +{ + Config = class'Config_SpawnManager_Short' +} diff --git a/ZedSpawner/Classes/AISpawnSquad.uc b/ZedSpawner/Classes/AISpawnSquad.uc new file mode 100644 index 0000000..44a0576 --- /dev/null +++ b/ZedSpawner/Classes/AISpawnSquad.uc @@ -0,0 +1,61 @@ +class AISpawnSquad extends Object + hidecategories(Object); + +struct S_AISquadElement +{ + var() EAIType Type; + var() byte Num ; + + var class CustomClass; + + structdefaultproperties + { + Num = 1 + } +}; + +var() ESquadType MinVolumeType; +var() array 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 +} diff --git a/ZedSpawner/Classes/AIWaveInfo.uc b/ZedSpawner/Classes/AIWaveInfo.uc new file mode 100644 index 0000000..ebd4fdf --- /dev/null +++ b/ZedSpawner/Classes/AIWaveInfo.uc @@ -0,0 +1,71 @@ +class AIWaveInfo extends Object + dependson(AISpawnSquad) + hidecategories(Object); + +var() bool bRecycleWave; +var() Array Squads; +var() Array SpecialSquads; +var() int MaxAI; +var() Array EventSquads; + +public function GetNewSquadList(out Array out_SquadList) +{ + local AISpawnSquad SS; + + out_SquadList.Length = 0; + foreach Squads(SS) + if (SS != None) + out_SquadList.AddItem(SS); +} + +public function GetSpecialSquad(out Array out_SquadList) +{ + if (SpecialSquads.Length > 0) + out_SquadList.AddItem(SpecialSquads[Rand(SpecialSquads.Length)]); +} + +public function GetEventSquadList(out Array 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 +} diff --git a/ZedSpawner/Classes/SpawnListBossWaves.uc b/ZedSpawner/Classes/Config_SpawnListBossWaves.uc similarity index 89% rename from ZedSpawner/Classes/SpawnListBossWaves.uc rename to ZedSpawner/Classes/Config_SpawnListBossWaves.uc index f58cf70..e3ae24f 100644 --- a/ZedSpawner/Classes/SpawnListBossWaves.uc +++ b/ZedSpawner/Classes/Config_SpawnListBossWaves.uc @@ -1,4 +1,4 @@ -class SpawnListBossWaves extends Object +class Config_SpawnListBossWaves extends Object dependson(ZedSpawner) config(ZedSpawner); @@ -25,7 +25,7 @@ public static function InitConfig() default.Spawn.Length = 0; SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing"; - SpawnEntry.ZedClass = "SomePackage.SomeFleshpoundClass"; + SpawnEntry.ZedClass = "SomePackage.SomeClass"; SpawnEntry.SpawnCountBase = 2; SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.Delay = 60; @@ -33,6 +33,10 @@ public static function InitConfig() SpawnEntry.bSpawnAtPlayerStart = false; default.Spawn.AddItem(SpawnEntry); + // TODO: + //SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing"; + //default.Spawn.AddItem(SpawnEntry); + StaticSaveConfig(); } diff --git a/ZedSpawner/Classes/SpawnListRegular.uc b/ZedSpawner/Classes/Config_SpawnListRegular.uc similarity index 95% rename from ZedSpawner/Classes/SpawnListRegular.uc rename to ZedSpawner/Classes/Config_SpawnListRegular.uc index f7c9fcc..af4f66d 100644 --- a/ZedSpawner/Classes/SpawnListRegular.uc +++ b/ZedSpawner/Classes/Config_SpawnListRegular.uc @@ -1,4 +1,4 @@ -class SpawnListRegular extends Object +class Config_SpawnListRegular extends Object dependson(ZedSpawner) config(ZedSpawner); diff --git a/ZedSpawner/Classes/SpawnListSpecialWaves.uc b/ZedSpawner/Classes/Config_SpawnListSpecialWaves.uc similarity index 95% rename from ZedSpawner/Classes/SpawnListSpecialWaves.uc rename to ZedSpawner/Classes/Config_SpawnListSpecialWaves.uc index ff3e648..ba6e86a 100644 --- a/ZedSpawner/Classes/SpawnListSpecialWaves.uc +++ b/ZedSpawner/Classes/Config_SpawnListSpecialWaves.uc @@ -1,4 +1,4 @@ -class SpawnListSpecialWaves extends Object +class Config_SpawnListSpecialWaves extends Object dependson(ZedSpawner) config(ZedSpawner); diff --git a/ZedSpawner/Classes/Config_SpawnManager.uc b/ZedSpawner/Classes/Config_SpawnManager.uc new file mode 100644 index 0000000..1dbf2ae --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager.uc @@ -0,0 +1,37 @@ +class Config_SpawnManager extends Object + config(ZedSpawner); + +var const class 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' +} diff --git a/ZedSpawner/Classes/Config_SpawnManager_Endless.uc b/ZedSpawner/Classes/Config_SpawnManager_Endless.uc new file mode 100644 index 0000000..b03a015 --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager_Endless.uc @@ -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' +} diff --git a/ZedSpawner/Classes/Config_SpawnManager_Long.uc b/ZedSpawner/Classes/Config_SpawnManager_Long.uc new file mode 100644 index 0000000..6c344a1 --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager_Long.uc @@ -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' +} diff --git a/ZedSpawner/Classes/Config_SpawnManager_Normal.uc b/ZedSpawner/Classes/Config_SpawnManager_Normal.uc new file mode 100644 index 0000000..ee58e7f --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager_Normal.uc @@ -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' +} diff --git a/ZedSpawner/Classes/Config_SpawnManager_Short.uc b/ZedSpawner/Classes/Config_SpawnManager_Short.uc new file mode 100644 index 0000000..af82a7a --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager_Short.uc @@ -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' +} diff --git a/ZedSpawner/Classes/Config_SpawnManager_WaveInfo.uc b/ZedSpawner/Classes/Config_SpawnManager_WaveInfo.uc new file mode 100644 index 0000000..542ab51 --- /dev/null +++ b/ZedSpawner/Classes/Config_SpawnManager_WaveInfo.uc @@ -0,0 +1,184 @@ +class Config_SpawnManager_WaveInfo extends Object + abstract + config(ZedSpawnManager); + +var const class DefSpawnManager; +var const byte Difficulty; +var const byte Wave; + +struct Unit +{ + var int Num; + var String ZedClass; +}; + +struct Squad +{ + var ESquadType MinVolumeType; + var Array Units; +}; + +var config bool bRecycleWave; +var config int MaxAI; +var config Array Squads; +var config Array SquadsSpecial; +var config Array 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 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 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(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(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(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 +} diff --git a/ZedSpawner/Classes/Spawn.uc b/ZedSpawner/Classes/Config_Spawner.uc similarity index 94% rename from ZedSpawner/Classes/Spawn.uc rename to ZedSpawner/Classes/Config_Spawner.uc index 6f07682..5163431 100644 --- a/ZedSpawner/Classes/Spawn.uc +++ b/ZedSpawner/Classes/Config_Spawner.uc @@ -1,4 +1,4 @@ -class Spawn extends Object +class Config_Spawner extends Object dependson(ZedSpawner) config(ZedSpawner); diff --git a/ZedSpawner/Classes/KFGI_Access.uc b/ZedSpawner/Classes/KFGI_Access.uc index 771fa16..c69e779 100644 --- a/ZedSpawner/Classes/KFGI_Access.uc +++ b/ZedSpawner/Classes/KFGI_Access.uc @@ -1,9 +1,7 @@ class KFGI_Access extends Object - within KFGameInfo_Survival; + within KFGameInfo; -// Bypass protected modifier for these lists - -function bool IsCustomZed(class KFPM) +public function bool IsCustomZed(class KFPM) { if (AIClassList.Find(KFPM) != INDEX_NONE) return false; if (NonSpawnAIClassList.Find(KFPM) != INDEX_NONE) return false; @@ -11,6 +9,51 @@ function bool IsCustomZed(class KFPM) return true; } +// WARN: - can it work? need check +public function bool IsOriginalAI(class 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 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 AITypePawn(EAIType AIType) +{ + if (AIType < AIClassList.Length) + return AIClassList[AIType]; + else + return None; +} + +public function class BossAITypePawn(EBossAIType AIType) +{ + if (AIType < AIBossClassList.Length) + return AIBossClassList[AIType]; + else + return None; +} + defaultproperties { diff --git a/ZedSpawner/Classes/ZedSpawner.uc b/ZedSpawner/Classes/ZedSpawner.uc index 28a06d2..52185aa 100644 --- a/ZedSpawner/Classes/ZedSpawner.uc +++ b/ZedSpawner/Classes/ZedSpawner.uc @@ -3,22 +3,19 @@ class ZedSpawner extends Info const dt = 1; -const CfgSpawn = class'Spawn'; -const CfgSpawnListR = class'SpawnListRegular'; -const CfgSpawnListBW = class'SpawnListBossWaves'; -const CfgSpawnListSW = class'SpawnListSpecialWaves'; +const CfgSpawn = class'Config_Spawner'; +const CfgSpawnListR = class'Config_SpawnListRegular'; +const CfgSpawnListBW = class'Config_SpawnListBossWaves'; +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 -{ - LL_WrongLevel, - LL_Fatal, - LL_Error, - LL_Warning, - LL_Info, - LL_Debug, - LL_Trace, - LL_All -}; +const SpawnManagerS = class'AISpawnManager_Short'; +const SpawnManagerN = class'AISpawnManager_Normal'; +const SpawnManagerL = class'AISpawnManager_Long'; +const SpawnManagerE = class'AISpawnManager_Endless'; struct S_SpawnEntry { @@ -66,6 +63,192 @@ var private Array > CustomZeds; 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 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 SpawnList, out Array > 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 > Out) +{ + // TODO +} + +private function PreloadContent(Array > Pawns) +{ + local class KFPM; + foreach Pawns(KFPM) KFPM.static.PreloadContent(); +} + public function bool WaveConditionRegular(S_SpawnEntry SE) { `ZS_Trace(`Location); @@ -108,119 +291,6 @@ public function bool WaveConditionSpecial(S_SpawnEntry SE) 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() { `ZS_Trace(`Location); diff --git a/ZedSpawner/Classes/ZedSpawnerRepLink.uc b/ZedSpawner/Classes/ZedSpawnerRepLink.uc index b2329df..22c04b7 100644 --- a/ZedSpawner/Classes/ZedSpawnerRepLink.uc +++ b/ZedSpawner/Classes/ZedSpawnerRepLink.uc @@ -10,6 +10,8 @@ replication LogLevel; } +public simulated function bool SafeDestroy() { if (!bPendingDelete) return Destroy(); else return true; } + public reliable client function ClientSync(class CustomZed) { `ZS_Trace(`Location); @@ -31,18 +33,18 @@ public reliable client function SyncFinished() CustomZed.static.PreloadContent(); } - Destroy(); + SafeDestroy(); } public reliable server function ServerSync() { `ZS_Trace(`Location); - if (CustomZeds.Length == Recieved) + if (CustomZeds.Length == Recieved || WorldInfo.NetMode == NM_StandAlone) { `ZS_Debug("Sync finished"); SyncFinished(); - Destroy(); + SafeDestroy(); } else { diff --git a/ZedSpawner/Classes/_CommonTypes.uc b/ZedSpawner/Classes/_CommonTypes.uc new file mode 100644 index 0000000..0484371 --- /dev/null +++ b/ZedSpawner/Classes/_CommonTypes.uc @@ -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 +{ + +}