Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
0de9c0088c | |||
3fbf4e9c20 | |||
40c9546858 | |||
83286ea046 | |||
88fcd8b235 | |||
fd3c9116e4 | |||
b5355d9fb5 | |||
62c3f79c5e |
53
README.md
53
README.md
@ -1 +1,52 @@
|
||||
# KF2-ZedSpawner
|
||||
# ZedSpawner
|
||||
|
||||
[](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
|
||||
[](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
|
||||
[](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
|
||||
[](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
|
||||
[](https://github.com/GenZmeY/KF2-ZedSpawner/tags)
|
||||
[](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)
|
||||
|
@ -63,6 +63,12 @@ public static function bool Load(E_LogLevel LogLevel)
|
||||
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");
|
||||
|
@ -13,7 +13,7 @@ struct S_SpawnEntryCfg
|
||||
var bool bSpawnAtPlayerStart;
|
||||
};
|
||||
|
||||
var public config bool bStopRegularSpawn;
|
||||
var public config bool bStopRegularSpawn;
|
||||
var private config Array<S_SpawnEntryCfg> Spawn;
|
||||
|
||||
public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA)
|
||||
@ -34,9 +34,11 @@ public static function InitConfig(int Version, int LatestVersion, KFGI_Access KF
|
||||
|
||||
private static function ApplyDefault(KFGI_Access KFGIA)
|
||||
{
|
||||
local S_SpawnEntryCfg SpawnEntry;
|
||||
local S_SpawnEntryCfg SpawnEntry;
|
||||
local Array<class<KFPawn_Monster> > KFPM_Bosses;
|
||||
local class<KFPawn_Monster> KFPMC;
|
||||
local class<KFPawn_Monster> KFPMC;
|
||||
|
||||
default.Spawn.Length = 0;
|
||||
|
||||
default.bStopRegularSpawn = true;
|
||||
default.Spawn.Length = 0;
|
||||
@ -61,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;
|
||||
@ -115,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;
|
||||
}
|
||||
|
||||
|
@ -34,9 +34,9 @@ public static function InitConfig(int Version, int LatestVersion, KFGI_Access KF
|
||||
|
||||
private static function ApplyDefault(KFGI_Access KFGIA)
|
||||
{
|
||||
local S_SpawnEntryCfg SpawnEntry;
|
||||
local S_SpawnEntryCfg SpawnEntry;
|
||||
local Array<class<KFPawn_Monster> > KFPM_Zeds;
|
||||
local class<KFPawn_Monster> KFPMC;
|
||||
local class<KFPawn_Monster> KFPMC;
|
||||
|
||||
default.Spawn.Length = 0;
|
||||
|
||||
@ -64,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)
|
||||
@ -123,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;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ public static function InitConfig(int Version, int LatestVersion)
|
||||
private static function ApplyDefault()
|
||||
{
|
||||
local S_SpawnEntryCfg SpawnEntry;
|
||||
local EAIType AIType;
|
||||
local EAIType AIType;
|
||||
|
||||
default.bStopRegularSpawn = true;
|
||||
default.Spawn.Length = 0;
|
||||
@ -61,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)
|
||||
{
|
||||
@ -126,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;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ const LatestVersion = 1;
|
||||
const dt = 1;
|
||||
|
||||
const CfgSpawn = class'Spawn';
|
||||
const CfgSpawnListR = class'SpawnListRegular';
|
||||
const CfgSpawnListRW = class'SpawnListRegular';
|
||||
const CfgSpawnListBW = class'SpawnListBossWaves';
|
||||
const CfgSpawnListSW = class'SpawnListSpecialWaves';
|
||||
|
||||
@ -26,26 +26,34 @@ struct S_SpawnEntry
|
||||
{
|
||||
var class<KFPawn_Monster> BossClass;
|
||||
var class<KFPawn_Monster> ZedClass;
|
||||
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 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 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;
|
||||
@ -59,12 +67,10 @@ var private int CycleWaveSize;
|
||||
var private int WaveTotalAI;
|
||||
|
||||
var private class<KFPawn_Monster> CurrentBossClass;
|
||||
var private Array<class<KFPawn_Monster> > BossClassCache;
|
||||
var private Array<class<KFPawn_Monster> > CustomZeds;
|
||||
|
||||
var private String SpawnTimerLastMessage;
|
||||
|
||||
delegate bool WaveCondition(S_SpawnEntry SE);
|
||||
var private bool SpawnActive;
|
||||
var private String SpawnListsComment;
|
||||
|
||||
public event PreBeginPlay()
|
||||
{
|
||||
@ -100,7 +106,7 @@ private function InitConfig()
|
||||
}
|
||||
|
||||
CfgSpawn.static.InitConfig(Version, LatestVersion);
|
||||
CfgSpawnListR.static.InitConfig(Version, LatestVersion, KFGIA);
|
||||
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
|
||||
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
|
||||
CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
|
||||
|
||||
@ -165,7 +171,7 @@ private function Init()
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnListR = CfgSpawnListR.static.Load(LogLevel);
|
||||
SpawnListRW = CfgSpawnListRW.static.Load(LogLevel);
|
||||
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
|
||||
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
|
||||
|
||||
@ -177,7 +183,7 @@ private function Init()
|
||||
{
|
||||
CycleWaveSize = 0;
|
||||
CycleWaveShift = MaxInt;
|
||||
foreach SpawnListR(SE)
|
||||
foreach SpawnListRW(SE)
|
||||
{
|
||||
CycleWaveShift = Min(CycleWaveShift, SE.Wave);
|
||||
CycleWaveSize = Max(CycleWaveSize, SE.Wave);
|
||||
@ -185,26 +191,16 @@ private function Init()
|
||||
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
|
||||
}
|
||||
|
||||
CreateBossCache();
|
||||
PreloadContent();
|
||||
|
||||
SetTimer(float(dt), true, nameof(SpawnTimer));
|
||||
}
|
||||
|
||||
private function CreateBossCache()
|
||||
{
|
||||
local S_SpawnEntry SE;
|
||||
|
||||
foreach SpawnListBW(SE)
|
||||
if (BossClassCache.Find(SE.BossClass) == INDEX_NONE)
|
||||
BossClassCache.AddItem(SE.BossClass);
|
||||
}
|
||||
|
||||
private function PreloadContent()
|
||||
{
|
||||
local class<KFPawn_Monster> PawnClass;
|
||||
|
||||
ExtractCustomZedsFromSpawnList(SpawnListR, CustomZeds);
|
||||
ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
|
||||
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
|
||||
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
|
||||
|
||||
@ -229,50 +225,12 @@ private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, o
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -286,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;
|
||||
@ -338,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;
|
||||
@ -389,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;
|
||||
|
||||
@ -415,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()
|
||||
@ -551,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)
|
||||
|
Reference in New Issue
Block a user