13 Commits

Author SHA1 Message Date
0de9c0088c fix possible division by zero when rounding 2022-06-05 03:59:29 +03:00
3fbf4e9c20 Merge branch 'master' of https://github.com/GenZmeY/KF2-ZedSpawner 2022-06-01 17:39:03 +03:00
40c9546858 add missing check for SingleSpawnLimitMultiplier 2022-06-01 17:38:54 +03:00
83286ea046 Update README.md 2022-06-01 11:41:08 +03:00
88fcd8b235 Update README.md 2022-06-01 11:39:45 +03:00
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
28 changed files with 690 additions and 1368 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 +1,52 @@
# KF2-ZedSpawner
# ZedSpawner
[![Steam Workshop](https://img.shields.io/static/v1?message=workshop&logo=steam&labelColor=gray&color=blue&logoColor=white&label=steam%20)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
[![Steam Subscriptions](https://img.shields.io/steam/subscriptions/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
[![Steam Favorites](https://img.shields.io/steam/favorites/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
[![Steam Update Date](https://img.shields.io/steam/update-date/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/GenZmeY/KF2-ZedSpawner)](https://github.com/GenZmeY/KF2-ZedSpawner/tags)
[![GitHub](https://img.shields.io/github/license/GenZmeY/KF2-ZedSpawner)](LICENSE)
# Description
Spawner for zeds. Started as a modification of the [PissJar](https://steamcommunity.com/sharedfiles/filedetails/?id=1082749153) and [Windows 10](https://steamcommunity.com/sharedfiles/filedetails/?id=2488241348) version, but grew into something completely different...
# Features
- spawn without increasing zed counter;
- spawn depends on the number of players;
- cyclic spawn (useful for endless mode);
- separate spawn for special waves and boss waves;
- spawn after a certain percentage of killed zeds.
# Usage
**UNDER CONSTRUCTION**
[Spawn Calculator](https://docs.google.com/spreadsheets/d/1q67WJ36jhj6Y0lPNO5tS2bU79Wphu4Xmi62me6DAwtM/edit?usp=sharing)
# Build
**Note:** If you want to build/test/brew/publish a mutator without git-bash and/or scripts, follow [these instructions](https://tripwireinteractive.atlassian.net/wiki/spaces/KF2SW/pages/26247172/KF2+Code+Modding+How-to) instead of what is described here.
1. Install [Killing Floor 2](https://store.steampowered.com/app/232090/Killing_Floor_2/), Killing Floor 2 - SDK and [git for windows](https://git-scm.com/download/win);
2. open git-bash and go to any folder where you want to store sources:
`cd <ANY_FOLDER_YOU_WANT>`
3. Clone this repository and go to the source folder:
`git clone https://github.com/GenZmeY/KF2-ZedSpawner && cd KF2-ZedSpawner`
4. Download dependencies:
`git submodule init && git submodule update`
5. Compile:
`./tools/builder -c`
5. The compiled files will be here:
`C:\Users\<USERNAME>\Documents\My Games\KillingFloor2\KFGame\Unpublished\BrewedPC\Script\`
# Testing
Open git-bash in the source folder and run command:
`./tools/builder -t`
(or `./tools/builder -ct` if you haven't compiled the mutator yet)
A local single-user test will be launched with parameters from `builder.cfg` (edit this file if you want to test mutator with different parameters).
# Bug reports
If you find a bug, go to the [issue page](https://github.com/GenZmeY/KF2-ZedSpawner/issues) and check if there is a description of your bug. If not, create a new issue.
Describe what the bug looks like and how reproduce it.
Attaching your KFZedSpawner.ini and Launch.log can also be helpful.
# License
[GNU GPLv3](LICENSE)

View File

@ -1,350 +0,0 @@
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

@ -1,118 +0,0 @@
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

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

View File

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

View File

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

View File

@ -1,61 +0,0 @@
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

@ -1,71 +0,0 @@
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,37 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,184 +0,0 @@
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,78 +0,0 @@
class Config_Spawner extends Object
dependson(ZedSpawner)
config(ZedSpawner);
var config bool bCyclicalSpawn;
var config bool bShadowSpawn;
var config float ZedTotalMultiplier;
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;
default.bShadowSpawn = true;
default.ZedTotalMultiplier = 1.0;
default.SpawnTotalPlayerMultiplier = 0.75;
default.SpawnTotalCycleMultiplier = 0.75;
default.SingleSpawnLimitPlayerMultiplier = 0.75;
default.SingleSpawnLimitCycleMultiplier = 0.75;
default.AliveSpawnLimit = 0;
StaticSaveConfig();
}
public static function bool Load(E_LogLevel LogLevel)
{
local bool Errors;
if (default.ZedTotalMultiplier <= 0.f)
{
`ZS_Error("ZedTotalMultiplier" @ "(" $ default.ZedTotalMultiplier $ ")" @ "must be greater than 0.0");
Errors = true;
}
if (default.SpawnTotalPlayerMultiplier < 0.f)
{
`ZS_Error("SpawnTotalPlayerMultiplier" @ "(" $ default.SpawnTotalPlayerMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SpawnTotalCycleMultiplier < 0.f)
{
`ZS_Error("SpawnTotalCycleMultiplier" @ "(" $ default.SpawnTotalCycleMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SingleSpawnLimitPlayerMultiplier < 0.f)
{
`ZS_Error("SingleSpawnLimitPlayerMultiplier" @ "(" $ default.SingleSpawnLimitPlayerMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SingleSpawnLimitCycleMultiplier < 0.f)
{
`ZS_Error("SingleSpawnLimitCycleMultiplier" @ "(" $ default.SingleSpawnLimitCycleMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.AliveSpawnLimit < 0)
{
`ZS_Error("AliveSpawnLimit" @ "(" $ default.AliveSpawnLimit $ ")" @ "must be greater than or equal 0");
Errors = true;
}
return !Errors;
}
defaultproperties
{
}

View File

@ -1,6 +1,39 @@
class KFGI_Access extends Object
within KFGameInfo;
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;
}
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;
@ -9,7 +42,6 @@ public function bool IsCustomZed(class<KFPawn_Monster> KFPM)
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;

View File

@ -0,0 +1,96 @@
class Spawn extends Object
dependson(ZedSpawner)
config(ZedSpawner);
var public config bool bCyclicalSpawn;
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;
public static function InitConfig(int Version, int LatestVersion)
{
switch (Version)
{
case `NO_CONFIG:
ApplyDefault();
default: break;
}
if (LatestVersion != Version)
{
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)
{
local bool Errors;
if (default.ZedTotalMultiplier <= 0.f)
{
`ZS_Error("ZedTotalMultiplier" @ "(" $ default.ZedTotalMultiplier $ ")" @ "must be greater than 0.0");
Errors = true;
}
if (default.SpawnTotalPlayerMultiplier < 0.f)
{
`ZS_Error("SpawnTotalPlayerMultiplier" @ "(" $ default.SpawnTotalPlayerMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SpawnTotalCycleMultiplier < 0.f)
{
`ZS_Error("SpawnTotalCycleMultiplier" @ "(" $ default.SpawnTotalCycleMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SingleSpawnLimitMultiplier <= 0.f)
{
`ZS_Error("SingleSpawnLimitMultiplier" @ "(" $ default.SingleSpawnLimitMultiplier $ ")" @ "must be greater than 0.0");
Errors = true;
}
if (default.SingleSpawnLimitPlayerMultiplier < 0.f)
{
`ZS_Error("SingleSpawnLimitPlayerMultiplier" @ "(" $ default.SingleSpawnLimitPlayerMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.SingleSpawnLimitCycleMultiplier < 0.f)
{
`ZS_Error("SingleSpawnLimitCycleMultiplier" @ "(" $ default.SingleSpawnLimitCycleMultiplier $ ")" @ "must be greater than or equal 0.0");
Errors = true;
}
if (default.AliveSpawnLimit < 0)
{
`ZS_Error("AliveSpawnLimit" @ "(" $ default.AliveSpawnLimit $ ")" @ "must be greater than or equal 0");
Errors = true;
}
return !Errors;
}
defaultproperties
{
}

View File

@ -1,4 +1,4 @@
class Config_SpawnListBossWaves extends Object
class SpawnListBossWaves extends Object
dependson(ZedSpawner)
config(ZedSpawner);
@ -13,31 +13,47 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart;
};
var config bool bStopRegularSpawn;
var config Array<S_SpawnEntryCfg> Spawn;
var public config bool bStopRegularSpawn;
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;
SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing";
default.bStopRegularSpawn = true;
default.Spawn.Length = 0;
SpawnEntry.ZedClass = "SomePackage.SomeClass";
SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.Delay = 60;
SpawnEntry.Delay = 30;
SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry);
// TODO:
//SpawnEntry.BossClass = "KFGameContent.KFPawn_ZedFleshpoundKing";
//default.Spawn.AddItem(SpawnEntry);
StaticSaveConfig();
KFPM_Bosses = KFGIA.GetAIBossClassList();
foreach KFPM_Bosses(KFPMC)
{
SpawnEntry.BossClass = "KFGameContent." $ String(KFPMC);
default.Spawn.AddItem(SpawnEntry);
}
}
public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
@ -47,8 +63,9 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
local S_SpawnEntry SpawnEntry;
local int Line;
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)
{
Errors = false;
@ -101,11 +118,21 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
if (!Errors)
{
Loaded++;
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;
}

View File

@ -1,4 +1,4 @@
class Config_SpawnListRegular extends Object
class SpawnListRegular extends Object
dependson(ZedSpawner)
config(ZedSpawner);
@ -14,35 +14,47 @@ struct S_SpawnEntryCfg
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;
SpawnEntry.Wave = 1;
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.Wave = 0;
SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.RelativeStart = 25;
SpawnEntry.Delay = 30;
SpawnEntry.Probability = 50;
SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100;
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)
@ -52,6 +64,7 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
local S_SpawnEntry SpawnEntry;
local int Line;
local bool Errors;
local int Loaded;
`ZS_Info("Load spawn list:");
foreach default.Spawn(SpawnEntryCfg, Line)
@ -111,11 +124,21 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
if (!Errors)
{
Loaded++;
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;
}

View File

@ -1,4 +1,4 @@
class Config_SpawnListSpecialWaves extends Object
class SpawnListSpecialWaves extends Object
dependson(ZedSpawner)
config(ZedSpawner);
@ -14,18 +14,32 @@ struct S_SpawnEntryCfg
var bool bSpawnAtPlayerStart;
};
var config bool bStopRegularSpawn;
var config Array<S_SpawnEntryCfg> Spawn;
var public config bool bStopRegularSpawn;
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 EAIType AIType;
default.bStopRegularSpawn = true;
default.Spawn.Length = 0;
SpawnEntry.Wave = AT_Husk;
SpawnEntry.ZedClass = "SomePackage.SomeClass";
SpawnEntry.SpawnCountBase = 2;
SpawnEntry.SingleSpawnLimit = 1;
@ -33,9 +47,11 @@ public static function InitConfig()
SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
default.Spawn.AddItem(SpawnEntry);
StaticSaveConfig();
foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType)
{
SpawnEntry.Wave = AIType;
default.Spawn.AddItem(SpawnEntry);
}
}
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 int Line;
local bool Errors;
local int Loaded;
if (KFGIE == None)
{
@ -110,11 +127,21 @@ public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogL
if (!Errors)
{
Loaded++;
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;
}

View File

@ -1,89 +1,78 @@
class ZedSpawner extends Info
config(ZedSpawner);
const LatestVersion = 1;
const dt = 1;
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';
const CfgSpawn = class'Spawn';
const CfgSpawnListRW = class'SpawnListRegular';
const CfgSpawnListBW = class'SpawnListBossWaves';
const CfgSpawnListSW = class'SpawnListSpecialWaves';
const SpawnManagerS = class'AISpawnManager_Short';
const SpawnManagerN = class'AISpawnManager_Normal';
const SpawnManagerL = class'AISpawnManager_Long';
const SpawnManagerE = class'AISpawnManager_Endless';
enum E_LogLevel
{
LL_WrongLevel,
LL_Fatal,
LL_Error,
LL_Warning,
LL_Info,
LL_Debug,
LL_Trace,
LL_All
};
struct S_SpawnEntry
{
var class<KFPawn_Monster> BossClass;
var class<KFPawn_Monster> ZedClass;
var int Wave;
var int SpawnCountBase;
var int SingleSpawnLimitDefault;
var int SingleSpawnLimit;
var float Probability;
var float RelativeStartDefault;
var float RelativeStart;
var int DelayDefault;
var int Delay;
var int SpawnsLeft;
var int SpawnsTotal;
var bool SpawnAtPlayerStart;
var byte Wave;
var int SpawnCountBase;
var int SingleSpawnLimitDefault;
var int SingleSpawnLimit;
var float Probability;
var float RelativeStartDefault;
var float RelativeStart;
var int DelayDefault;
var int Delay;
var int PawnsLeft;
var int PawnsTotal;
var bool SpawnAtPlayerStart;
var bool ForceSpawn;
var String ZedNameFiller;
};
var config bool bConfigInitialized;
var config E_LogLevel LogLevel;
var private config int Version;
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> 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_Endless KFGIE;
var private KFGI_Access KFGIA;
var private KFGI_Access KFGIA;
var private int CurrentWave;
var private int SpecialWave;
var private int CurrentCycle;
var private int CycleWaveShift;
var private int CycleWaveSize;
var private int WaveTotalAI;
var private class<KFPawn_Monster> CurrentBossClass;
var private int SpecialWave;
var private String SpawnTimerLastMessage;
var private Array<class<KFPawn_Monster> > BossClassCache;
var private class<KFPawn_Monster> CurrentBossClass;
var private Array<class<KFPawn_Monster> > CustomZeds;
delegate bool WaveCondition(S_SpawnEntry SE);
var private bool SpawnActive;
var private String SpawnListsComment;
event PreBeginPlay()
{
`ZS_Trace(`Location);
Super.PreBeginPlay();
PreInit();
}
event PostBeginPlay()
{
`ZS_Trace(`Location);
if (bPendingDelete) return;
Super.PostBeginPlay();
PostInit();
}
private function PreInit()
public event PreBeginPlay()
{
`ZS_Trace(`Location);
@ -94,52 +83,66 @@ private function PreInit()
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();
Super.PreBeginPlay();
}
if (LogLevel == LL_WrongLevel)
public event PostBeginPlay()
{
`ZS_Trace(`Location);
if (bPendingDelete) return;
Super.PostBeginPlay();
Init();
}
private function InitConfig()
{
if (Version == `NO_CONFIG)
{
LogLevel = LL_Info;
`ZS_Warn("Wrong 'LogLevel', return to default value");
SaveConfig();
}
`ZS_Log("LogLevel:" @ LogLevel);
CfgSpawn.static.InitConfig(Version, LatestVersion);
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
if (!CfgSpawn.static.Load(LogLevel))
switch (Version)
{
`ZS_Fatal("Wrong settings, Destroy...");
Destroy();
return;
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 PostInit()
private function Init()
{
local S_SpawnEntry SE;
local bool NeedInitSM;
`ZS_Trace(`Location);
CurrentWave = INDEX_NONE;
KFGIS = KFGameInfo_Survival(WorldInfo.Game);
if (KFGIS == None)
{
@ -151,76 +154,61 @@ private function PostInit()
KFGIA = new(KFGIS) class'KFGI_Access';
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);
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
CfgSpawnManagerE.static.Load(LogLevel);
CfgSpawnManagerS.static.Load(LogLevel);
CfgSpawnManagerN.static.Load(LogLevel);
CfgSpawnManagerL.static.Load(LogLevel);
CurrentWave = INDEX_NONE;
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)
if (CfgSpawn.default.bCyclicalSpawn)
{
NeedInitSM = (KFGIS.SpawnManager != None);
KFGIS.SpawnManagerClasses.Length = 0;
if (KFGIE != None)
CycleWaveSize = 0;
CycleWaveShift = MaxInt;
foreach SpawnListRW(SE)
{
KFGIE.SpawnManagerClasses.AddItem(SpawnManagerE);
CycleWaveShift = Min(CycleWaveShift, SE.Wave);
CycleWaveSize = Max(CycleWaveSize, SE.Wave);
}
else
{
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerS);
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerN);
KFGIS.SpawnManagerClasses.AddItem(SpawnManagerL);
}
if (NeedInitSM)
{
KFGIS.InitSpawnManager();
}
`ZS_Info("SpawnManager replaced");
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
}
PreparePreloadContent();
PreloadContent();
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);
}
SetTimer(float(dt), true, nameof(SpawnTimer));
}
private function PreparePreloadContent()
private function PreloadContent()
{
local class<KFPawn_Monster> PawnClass;
ExtractCustomZedsFromSpawnList(SpawnListR, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
ExtractCustomZedsFromSpawnManager(AISpawnManager(KFGIS.SpawnManager), 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)
@ -232,67 +220,17 @@ private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, o
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)
{
`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);
}
private function SpawnTimer()
{
local S_SpawnEntry SE;
local int Index;
local float Threshold;
`ZS_Trace(`Location);
if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum)
@ -306,47 +244,102 @@ private function SpawnTimer()
return;
}
if (SpawnListCurrent.Length == 0)
{
SpawnTimerLogger(true, "No spawn list for this wave");
return;
}
if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit)
{
SpawnTimerLogger(true, "alive spawn limit reached");
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)
{
SpawnTimerLogger(true, "shadow spawn is active and no free spawn slots");
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)
{
SpawnZeds(SpawnListSW, WaveConditionSpecial);
}
if (KFGIS.MyKFGRI.IsBossWave())
{
SpawnZeds(SpawnListBW, WaveConditionBoss);
if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold)
{
continue;
}
if (SE.Delay > 0)
{
SpawnListCurrent[Index].Delay -= dt;
continue;
}
if (SE.PawnsLeft > 0)
{
SpawnEntry(SpawnListCurrent, Index);
}
}
}
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);
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle)
{
CurrentCycle++;
`ZS_Info("Spawn cycle started:" @ CurrentCycle);
}
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;
KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier;
@ -358,49 +351,83 @@ private function SetupWave()
{
`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++;
`ZS_Info("Next spawn cycle started:" @ CurrentCycle);
SpawnListNames.AddItem("regular");
foreach SpawnListRW(SE)
if (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1))
SpawnListCurrent.AddItem(SE);
}
ResetSpawnList(SpawnListR);
ResetSpawnList(SpawnListSW);
ResetSpawnList(SpawnListBW);
CurrentBossClass = None;
if (KFGIE != None)
if (UseSpecialSpawnList)
{
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 int Index;
local float Cycle, Players;
local float MSB, MSC, MSP;
local float MLB, MLC, MLP;
local float B, TM, TCM, TPM;
local float L, LM, LCM, LPM;
local float PawnTotalF, PawnLimitF;
local int ZedNameMaxLength;
`ZS_Trace(`Location);
Cycle = float(CurrentCycle);
Players = float(PlayerCount());
MSB = CfgSpawn.default.ZedTotalMultiplier;
MSC = CfgSpawn.default.SpawnTotalCycleMultiplier;
MSP = CfgSpawn.default.SpawnTotalPlayerMultiplier;
MLB = CfgSpawn.default.SingleSpawnLimitMultiplier;
MLC = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
MLP = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
B = float(SE.SpawnCountBase);
L = float(SE.SingleSpawnLimitDefault);
TM = CfgSpawn.default.ZedTotalMultiplier;
TCM = CfgSpawn.default.SpawnTotalCycleMultiplier;
TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier;
LM = CfgSpawn.default.SingleSpawnLimitMultiplier;
LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
ZedNameMaxLength = 0;
foreach List(SE, Index)
{
ZedNameMaxLength = Max(ZedNameMaxLength, Len(String(SE.ZedClass)));
if (KFGIS.MyKFGRI.IsBossWave())
{
List[Index].RelativeStart = 0.f;
@ -409,22 +436,30 @@ private function ResetSpawnList(out Array<S_SpawnEntry> List)
else
{
List[Index].RelativeStart = SE.RelativeStartDefault;
if (SE.RelativeStart == 0.f)
if (List[Index].RelativeStart == 0.f)
List[Index].Delay = SE.DelayDefault;
else
List[Index].Delay = 0;
}
List[Index].SpawnsTotal = Round(SE.SpawnCountBase * (MSB + MSC * (Cycle - 1.0f) + MSP * (Players - 1.0f)));
List[Index].SpawnsLeft = List[Index].SpawnsTotal;
List[Index].SingleSpawnLimit = Round(SE.SingleSpawnLimitDefault * (MLB + MLC * (Cycle - 1.0f) + MLP * (Players - 1.0f)));
PawnTotalF = B * (TM + TCM * (Cycle - 1.0f) + TPM * (Players - 1.0f));
PawnLimitF = L * (LM + LCM * (Cycle - 1.0f) + LPM * (Players - 1.0f));
`ZS_Debug(SE.ZedClass @ "SpawnsTotal:" @ List[Index].SpawnsTotal @ "SingleSpawnLimit:" @ List[Index].SingleSpawnLimit);
List[Index].ForceSpawn = false;
List[Index].PawnsTotal = Max(Round(PawnTotalF), 1);
List[Index].SingleSpawnLimit = Max(Round(PawnLimitF), 1);
List[Index].PawnsLeft = List[Index].PawnsTotal;
}
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;
@ -435,101 +470,95 @@ private function SpawnTimerLogger(bool Stop, optional String Reason)
else
Message = "Start spawn";
if (Reason != "")
Message @= "(" $ Reason $ ")";
if (Comment != "")
Message @= "(" $ Comment $ ")";
if (Message != SpawnTimerLastMessage)
if (SpawnActive == Stop)
{
`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)
{
local S_SpawnEntry SE;
local int FreeSpawnSlots, SpawnCount, Spawned;
local String Message;
local int FreeSpawnSlots, PawnCount, Spawned;
local String Action, Comment, NextSpawn;
`ZS_Trace(`Location);
SE = SpawnList[Index];
SpawnList[Index].Delay = SE.DelayDefault;
if (FRand() <= SE.Probability)
if (FRand() <= SE.Probability || SE.ForceSpawn)
{
if (SE.SingleSpawnLimit == 0 || SE.SpawnsLeft < SE.SingleSpawnLimit)
SpawnCount = SE.SpawnsLeft;
if (SE.SingleSpawnLimit == 0 || SE.PawnsLeft < SE.SingleSpawnLimit)
{
PawnCount = SE.PawnsLeft;
}
else
SpawnCount = SE.SingleSpawnLimit;
{
PawnCount = SE.SingleSpawnLimit;
}
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount;
if (SpawnCount > FreeSpawnSlots)
if (FreeSpawnSlots == 0)
{
`ZS_Info("Not enough free slots to spawn, will spawn" @ FreeSpawnSlots @ "instead of" @ SpawnCount);
SpawnCount = FreeSpawnSlots;
NoFreeSpawnSlots = true;
SpawnList[Index].PawnsLeft = 0;
return;
}
else if (PawnCount > FreeSpawnSlots)
{
PawnCount = FreeSpawnSlots;
}
}
Spawned = SpawnZed(SE.ZedClass, SpawnCount, SE.SpawnAtPlayerStart);
Message = "Spawned:" @ SE.ZedClass @ "x" $ Spawned;
Spawned = SpawnZed(SE.ZedClass, PawnCount, SE.SpawnAtPlayerStart);
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
{
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;
}
SpawnList[Index].SpawnsLeft -= Spawned;
if (SpawnList[Index].SpawnsLeft > 0)
SpawnList[Index].PawnsLeft -= Spawned;
if (SpawnList[Index].PawnsLeft > 0)
{
Message @= "(Next spawn after" @ SE.DelayDefault $ "sec," @ "spawns left:" @ SpawnList[Index].SpawnsLeft $ ")";
NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].PawnsLeft;
}
`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()
@ -571,62 +600,79 @@ private function Vector PlayerStartLocation()
return KFGIS.FindPlayerStart(None, 0).Location;
}
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int SpawnCount, bool SpawnAtPlayerStart)
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, bool SpawnAtPlayerStart)
{
local Array<class<KFPawn_Monster> > CustomSquad;
local Vector SpawnLocation;
local Vector SpawnLocation, PlayerStart;
local KFSpawnVolume SpawnVolume;
local KFPawn_Monster KFPM;
local Controller C;
local int SpawnFailed;
local int Failed, Spawned;
local int Index;
`ZS_Trace(`Location);
for (Index = 0; Index < SpawnCount; Index++)
CustomSquad.AddItem(ZedClass);
PlayerStart = PlayerStartLocation();
if (SpawnAtPlayerStart)
{
SpawnLocation = PlayerStartLocation();
SpawnLocation = PlayerStart;
SpawnLocation.Y += 64;
SpawnLocation.Z += 64;
}
else
{
SpawnLocation = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad).Location;
for (Index = 0; Index < PawnCount; 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;
}
SpawnFailed = 0;
for (Index = 0; Index < SpawnCount; Index++)
Spawned = 0; Failed = 0;
while (Failed + Spawned < PawnCount)
{
KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true);
if (KFPM == None)
{
`ZS_Error("Can't spawn" @ ZedClass);
SpawnFailed++;
Failed++;
continue;
}
C = KFPM.Spawn(KFPM.ControllerClass);
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();
SpawnFailed++;
Failed++;
continue;
}
C.Possess(KFPM, false);
}
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
KFGIS.MyKFGRI.AIRemaining -= (SpawnCount - SpawnFailed);
Spawned++;
}
KFGIS.RefreshMonsterAliveCount();
return SpawnCount - SpawnFailed;
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
KFGIS.NumAIFinishedSpawning += Spawned;
KFGIS.NumAISpawnsQueued += Spawned;
}
KFGIS.UpdateAIRemaining();
return Spawned;
}
public function NotifyLogin(Controller C)

View File

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

View File

@ -8,3 +8,5 @@
`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_Trace(msg) `log("TRACE:" @ `msg, (LogLevel >= LL_Trace), `ZS_Tag)
`define NO_CONFIG 0