2022-05-11 12:43:39 +00:00
|
|
|
class ZedSpawner extends Info
|
|
|
|
config(ZedSpawner);
|
|
|
|
|
2022-06-05 23:44:39 +00:00
|
|
|
const LatestVersion = 2;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-13 07:02:34 +00:00
|
|
|
const CfgSpawn = class'Spawn';
|
2022-05-31 03:36:42 +00:00
|
|
|
const CfgSpawnListRW = class'SpawnListRegular';
|
2022-05-13 07:02:34 +00:00
|
|
|
const CfgSpawnListBW = class'SpawnListBossWaves';
|
|
|
|
const CfgSpawnListSW = class'SpawnListSpecialWaves';
|
2022-05-11 12:43:39 +00:00
|
|
|
|
|
|
|
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;
|
2022-06-01 00:03:02 +00:00
|
|
|
var byte Wave;
|
|
|
|
var int SpawnCountBase;
|
|
|
|
var int SingleSpawnLimitDefault;
|
|
|
|
var int SingleSpawnLimit;
|
|
|
|
var float Probability;
|
|
|
|
var float RelativeStartDefault;
|
|
|
|
var float RelativeStart;
|
|
|
|
var int DelayDefault;
|
2022-06-05 23:44:39 +00:00
|
|
|
var float Delay;
|
2022-06-05 00:58:53 +00:00
|
|
|
var int PawnsLeft;
|
|
|
|
var int PawnsTotal;
|
2022-06-01 00:03:02 +00:00
|
|
|
var bool SpawnAtPlayerStart;
|
|
|
|
var bool ForceSpawn;
|
|
|
|
var String ZedNameFiller;
|
2022-05-11 12:43:39 +00:00
|
|
|
};
|
|
|
|
|
2022-05-20 21:31:35 +00:00
|
|
|
var private config int Version;
|
|
|
|
var private config E_LogLevel LogLevel;
|
2022-06-05 23:44:39 +00:00
|
|
|
var private config float Tickrate;
|
|
|
|
|
|
|
|
var private float dt;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-31 03:36:42 +00:00
|
|
|
var private Array<S_SpawnEntry> SpawnListRW;
|
2022-05-11 12:43:39 +00:00
|
|
|
var private Array<S_SpawnEntry> SpawnListBW;
|
|
|
|
var private Array<S_SpawnEntry> SpawnListSW;
|
2022-05-31 03:36:42 +00:00
|
|
|
var private Array<S_SpawnEntry> SpawnListCurrent;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
var private bool NoFreeSpawnSlots;
|
|
|
|
var private bool UseRegularSpawnList;
|
|
|
|
var private bool UseBossSpawnList;
|
|
|
|
var private bool UseSpecialSpawnList;
|
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
var private KFGameInfo_Survival KFGIS;
|
|
|
|
var private KFGameInfo_Endless KFGIE;
|
2022-05-20 21:31:35 +00:00
|
|
|
var private KFGI_Access KFGIA;
|
2022-05-13 07:02:34 +00:00
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
var private int CurrentWave;
|
2022-05-20 21:31:35 +00:00
|
|
|
var private int SpecialWave;
|
2022-05-11 12:43:39 +00:00
|
|
|
var private int CurrentCycle;
|
|
|
|
var private int CycleWaveShift;
|
|
|
|
var private int CycleWaveSize;
|
|
|
|
var private int WaveTotalAI;
|
|
|
|
|
2022-05-20 21:31:35 +00:00
|
|
|
var private class<KFPawn_Monster> CurrentBossClass;
|
2022-05-13 07:02:34 +00:00
|
|
|
var private Array<class<KFPawn_Monster> > CustomZeds;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
var private bool SpawnActive;
|
2022-05-31 01:44:12 +00:00
|
|
|
var private String SpawnListsComment;
|
2022-05-20 21:31:35 +00:00
|
|
|
|
|
|
|
public event PreBeginPlay()
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-16 02:42:13 +00:00
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
if (WorldInfo.NetMode == NM_Client)
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
`ZS_Fatal("NetMode == NM_Client, Destroy...");
|
|
|
|
Destroy();
|
|
|
|
return;
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
Super.PreBeginPlay();
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
|
2022-05-20 21:31:35 +00:00
|
|
|
public event PostBeginPlay()
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-16 02:42:13 +00:00
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
if (bPendingDelete) return;
|
|
|
|
|
|
|
|
Super.PostBeginPlay();
|
|
|
|
|
|
|
|
Init();
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
private function InitConfig()
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
if (Version == `NO_CONFIG)
|
|
|
|
{
|
2022-06-05 23:44:39 +00:00
|
|
|
Tickrate = 1.0f;
|
2022-05-20 20:45:26 +00:00
|
|
|
LogLevel = LL_Info;
|
|
|
|
SaveConfig();
|
|
|
|
}
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
CfgSpawn.static.InitConfig(Version, LatestVersion);
|
2022-05-31 03:36:42 +00:00
|
|
|
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
|
2022-05-20 20:45:26 +00:00
|
|
|
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
|
|
|
|
CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
switch (Version)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
case `NO_CONFIG:
|
|
|
|
`ZS_Info("Config created");
|
2022-06-05 23:44:39 +00:00
|
|
|
|
|
|
|
case 1:
|
|
|
|
Tickrate = 1.0f;
|
2022-05-20 20:45:26 +00:00
|
|
|
|
|
|
|
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();
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function Init()
|
|
|
|
{
|
2022-05-16 02:42:13 +00:00
|
|
|
local S_SpawnEntry SE;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
KFGIS = KFGameInfo_Survival(WorldInfo.Game);
|
|
|
|
if (KFGIS == None)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
`ZS_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy...");
|
|
|
|
Destroy();
|
|
|
|
return;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
2022-05-20 20:45:26 +00:00
|
|
|
|
|
|
|
KFGIA = new(KFGIS) class'KFGI_Access';
|
|
|
|
KFGIE = KFGameInfo_Endless(KFGIS);
|
|
|
|
|
|
|
|
InitConfig();
|
2022-05-11 12:43:39 +00:00
|
|
|
|
|
|
|
if (LogLevel == LL_WrongLevel)
|
|
|
|
{
|
|
|
|
LogLevel = LL_Info;
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Warn("Wrong 'LogLevel', return to default value");
|
2022-05-11 12:43:39 +00:00
|
|
|
SaveConfig();
|
|
|
|
}
|
|
|
|
`ZS_Log("LogLevel:" @ LogLevel);
|
|
|
|
|
2022-06-05 23:44:39 +00:00
|
|
|
if (Tickrate <= 0)
|
|
|
|
{
|
|
|
|
`ZS_Error("Spawner tickrate must be positive (current value:" @ Tickrate $ ")");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!CfgSpawn.static.Load(LogLevel) || Tickrate <= 0)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Fatal("Wrong settings, Destroy...");
|
2022-05-11 12:43:39 +00:00
|
|
|
Destroy();
|
|
|
|
return;
|
|
|
|
}
|
2022-06-05 23:44:39 +00:00
|
|
|
|
|
|
|
dt = 1 / Tickrate;
|
|
|
|
`ZS_Info("Spawner tickrate:" @ Tickrate @ "(update every" @ dt $ "s)");
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-31 03:36:42 +00:00
|
|
|
SpawnListRW = CfgSpawnListRW.static.Load(LogLevel);
|
2022-05-11 12:43:39 +00:00
|
|
|
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
|
|
|
|
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
|
|
|
|
|
2022-05-20 20:45:26 +00:00
|
|
|
CurrentWave = INDEX_NONE;
|
2022-05-16 02:42:13 +00:00
|
|
|
SpecialWave = INDEX_NONE;
|
2022-05-11 12:43:39 +00:00
|
|
|
CurrentCycle = 1;
|
2022-05-20 20:45:26 +00:00
|
|
|
|
|
|
|
if (CfgSpawn.default.bCyclicalSpawn)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
CycleWaveSize = 0;
|
|
|
|
CycleWaveShift = MaxInt;
|
2022-05-31 03:36:42 +00:00
|
|
|
foreach SpawnListRW(SE)
|
2022-05-13 07:02:34 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
CycleWaveShift = Min(CycleWaveShift, SE.Wave);
|
|
|
|
CycleWaveSize = Max(CycleWaveSize, SE.Wave);
|
2022-05-13 07:02:34 +00:00
|
|
|
}
|
2022-05-20 20:45:26 +00:00
|
|
|
CycleWaveSize = CycleWaveSize - CycleWaveShift + 1;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
2022-05-20 20:45:26 +00:00
|
|
|
|
|
|
|
PreloadContent();
|
|
|
|
|
2022-06-05 23:44:39 +00:00
|
|
|
SetTimer(dt, true, nameof(SpawnTimer));
|
2022-05-20 20:45:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function PreloadContent()
|
|
|
|
{
|
|
|
|
local class<KFPawn_Monster> PawnClass;
|
|
|
|
|
2022-05-31 03:36:42 +00:00
|
|
|
ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
|
2022-05-20 20:45:26 +00:00
|
|
|
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
|
|
|
|
ExtractCustomZedsFromSpawnList(SpawnListSW, 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)
|
|
|
|
{
|
|
|
|
local S_SpawnEntry SE;
|
|
|
|
|
|
|
|
foreach SpawnList(SE)
|
2022-05-13 07:02:34 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
if (Out.Find(SE.ZedClass) == INDEX_NONE
|
2022-05-16 02:42:13 +00:00
|
|
|
&& KFGIA.IsCustomZed(SE.ZedClass))
|
2022-05-13 07:02:34 +00:00
|
|
|
{
|
2022-05-20 20:45:26 +00:00
|
|
|
Out.AddItem(SE.ZedClass);
|
2022-05-13 07:02:34 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-20 20:45:26 +00:00
|
|
|
}
|
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
private function SpawnTimer()
|
|
|
|
{
|
2022-05-31 03:36:42 +00:00
|
|
|
local S_SpawnEntry SE;
|
2022-06-01 00:03:02 +00:00
|
|
|
local int Index;
|
|
|
|
local float Threshold;
|
2022-05-31 03:36:42 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-16 02:42:13 +00:00
|
|
|
if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-16 02:42:13 +00:00
|
|
|
SetupWave();
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!KFGIS.IsWaveActive())
|
|
|
|
{
|
|
|
|
SpawnTimerLogger(true, "wave is not active");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
if (SpawnListCurrent.Length == 0)
|
|
|
|
{
|
|
|
|
SpawnTimerLogger(true, "No spawn list for this wave");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit)
|
|
|
|
{
|
|
|
|
SpawnTimerLogger(true, "alive spawn limit reached");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn)
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-05-31 01:44:12 +00:00
|
|
|
if (NoFreeSpawnSlots || KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount)
|
|
|
|
{
|
|
|
|
NoFreeSpawnSlots = true;
|
|
|
|
SpawnTimerLogger(true, "no free spawn slots");
|
|
|
|
return;
|
|
|
|
}
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
SpawnTimerLogger(false, SpawnListsComment);
|
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI));
|
2022-05-31 03:36:42 +00:00
|
|
|
foreach SpawnListCurrent(SE, Index)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
if (NoFreeSpawnSlots)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold)
|
2022-05-31 03:36:42 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:44:39 +00:00
|
|
|
if (SE.Delay > 0.0f)
|
2022-05-31 03:36:42 +00:00
|
|
|
{
|
|
|
|
SpawnListCurrent[Index].Delay -= dt;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
if (SE.PawnsLeft > 0)
|
2022-05-31 03:36:42 +00:00
|
|
|
{
|
|
|
|
SpawnEntry(SpawnListCurrent, Index);
|
|
|
|
}
|
|
|
|
}
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 02:42:13 +00:00
|
|
|
private function SetupWave()
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-31 01:44:12 +00:00
|
|
|
local Array<String> SpawnListNames;
|
|
|
|
local int WaveTotalAIDef;
|
|
|
|
local String WaveTypeInfo;
|
2022-06-01 00:03:02 +00:00
|
|
|
local S_SpawnEntry SE;
|
|
|
|
local EAIType SWType;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle)
|
|
|
|
{
|
|
|
|
CurrentCycle++;
|
|
|
|
`ZS_Info("Spawn cycle started:" @ CurrentCycle);
|
|
|
|
}
|
|
|
|
|
2022-05-16 02:42:13 +00:00
|
|
|
CurrentWave = KFGIS.WaveNum;
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
if (KFGIE != None)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
if (KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode != INDEX_NONE)
|
|
|
|
{
|
|
|
|
WaveTypeInfo = "Weekly:" @ KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode;
|
|
|
|
}
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode;
|
|
|
|
if (SpecialWave != INDEX_NONE)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
SWType = EAIType(SpecialWave);
|
|
|
|
WaveTypeInfo = "Special:" @ SWType;
|
2022-05-31 01:44:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (KFGIS.MyKFGRI.IsBossWave())
|
|
|
|
{
|
|
|
|
CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex));
|
|
|
|
if (CurrentBossClass == None)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
`ZS_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex);
|
2022-05-31 01:44:12 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
WaveTypeInfo = "Boss:" @ CurrentBossClass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
|
|
|
WaveTotalAIDef = KFGIS.SpawnManager.WaveTotalAI;
|
|
|
|
KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier;
|
|
|
|
WaveTotalAI = KFGIS.SpawnManager.WaveTotalAI;
|
|
|
|
KFGIS.MyKFGRI.WaveTotalAICount = KFGIS.SpawnManager.WaveTotalAI;
|
|
|
|
KFGIS.MyKFGRI.AIRemaining = KFGIS.SpawnManager.WaveTotalAI;
|
|
|
|
|
|
|
|
if (WaveTotalAIDef != KFGIS.SpawnManager.WaveTotalAI)
|
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")");
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
2022-05-31 01:44:12 +00:00
|
|
|
|
|
|
|
CurrentBossClass = None;
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
NoFreeSpawnSlots = false;
|
|
|
|
UseBossSpawnList = KFGIS.MyKFGRI.IsBossWave();
|
|
|
|
UseSpecialSpawnList = (SpecialWave != INDEX_NONE);
|
|
|
|
UseRegularSpawnList = ((!UseSpecialSpawnList && !UseBossSpawnList)
|
|
|
|
|| (UseSpecialSpawnList && !CfgSpawnListSW.default.bStopRegularSpawn)
|
|
|
|
|| (UseBossSpawnList && !CfgSpawnListBW.default.bStopRegularSpawn));
|
2022-05-31 03:36:42 +00:00
|
|
|
|
|
|
|
SpawnListCurrent.Length = 0;
|
|
|
|
if (UseRegularSpawnList)
|
|
|
|
{
|
|
|
|
SpawnListNames.AddItem("regular");
|
2022-06-01 00:03:02 +00:00
|
|
|
foreach SpawnListRW(SE)
|
|
|
|
if (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1))
|
|
|
|
SpawnListCurrent.AddItem(SE);
|
2022-05-31 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (UseSpecialSpawnList)
|
|
|
|
{
|
|
|
|
SpawnListNames.AddItem("special");
|
2022-06-01 00:03:02 +00:00
|
|
|
foreach SpawnListSW(SE)
|
|
|
|
if (SE.Wave == SpecialWave)
|
|
|
|
SpawnListCurrent.AddItem(SE);
|
2022-05-31 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (UseBossSpawnList)
|
|
|
|
{
|
|
|
|
SpawnListNames.AddItem("boss");
|
2022-06-01 00:03:02 +00:00
|
|
|
foreach SpawnListBW(SE)
|
|
|
|
if (SE.BossClass == CurrentBossClass)
|
|
|
|
SpawnListCurrent.AddItem(SE);
|
2022-05-31 03:36:42 +00:00
|
|
|
}
|
2022-05-16 02:42:13 +00:00
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
JoinArray(SpawnListNames, SpawnListsComment, ", ");
|
2022-06-01 00:03:02 +00:00
|
|
|
AdjustSpawnList(SpawnListCurrent);
|
2022-05-31 01:44:12 +00:00
|
|
|
|
|
|
|
if (WaveTypeInfo != "")
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-05-31 01:44:12 +00:00
|
|
|
WaveTypeInfo = "(" $ WaveTypeInfo $ ")";
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
2022-05-31 01:44:12 +00:00
|
|
|
|
|
|
|
`ZS_Info("Wave" @ CurrentWave @ WaveTypeInfo);
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
private function AdjustSpawnList(out Array<S_SpawnEntry> List)
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
|
|
|
local S_SpawnEntry SE;
|
|
|
|
local int Index;
|
|
|
|
local float Cycle, Players;
|
2022-06-05 00:58:53 +00:00
|
|
|
local float B, TM, TCM, TPM;
|
|
|
|
local float L, LM, LCM, LPM;
|
|
|
|
local float PawnTotalF, PawnLimitF;
|
2022-06-01 00:03:02 +00:00
|
|
|
local int ZedNameMaxLength;
|
2022-05-16 02:42:13 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-16 02:42:13 +00:00
|
|
|
|
|
|
|
Cycle = float(CurrentCycle);
|
|
|
|
Players = float(PlayerCount());
|
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
B = float(SE.SpawnCountBase);
|
|
|
|
L = float(SE.SingleSpawnLimitDefault);
|
|
|
|
|
|
|
|
TM = CfgSpawn.default.ZedTotalMultiplier;
|
|
|
|
TCM = CfgSpawn.default.SpawnTotalCycleMultiplier;
|
|
|
|
TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
LM = CfgSpawn.default.SingleSpawnLimitMultiplier;
|
|
|
|
LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
|
|
|
|
LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
|
2022-05-16 02:42:13 +00:00
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
ZedNameMaxLength = 0;
|
2022-05-16 02:42:13 +00:00
|
|
|
foreach List(SE, Index)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
ZedNameMaxLength = Max(ZedNameMaxLength, Len(String(SE.ZedClass)));
|
2022-05-11 12:43:39 +00:00
|
|
|
if (KFGIS.MyKFGRI.IsBossWave())
|
|
|
|
{
|
2022-05-16 02:42:13 +00:00
|
|
|
List[Index].RelativeStart = 0.f;
|
2022-06-05 23:44:39 +00:00
|
|
|
List[Index].Delay = float(SE.DelayDefault);
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-05-16 02:42:13 +00:00
|
|
|
List[Index].RelativeStart = SE.RelativeStartDefault;
|
2022-05-31 03:36:42 +00:00
|
|
|
if (List[Index].RelativeStart == 0.f)
|
2022-06-05 23:44:39 +00:00
|
|
|
List[Index].Delay = float(SE.DelayDefault);
|
2022-05-11 12:43:39 +00:00
|
|
|
else
|
2022-06-05 23:44:39 +00:00
|
|
|
List[Index].Delay = 0.0f;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
2022-06-05 00:58:53 +00:00
|
|
|
|
|
|
|
PawnTotalF = B * (TM + TCM * (Cycle - 1.0f) + TPM * (Players - 1.0f));
|
|
|
|
PawnLimitF = L * (LM + LCM * (Cycle - 1.0f) + LPM * (Players - 1.0f));
|
|
|
|
|
|
|
|
List[Index].ForceSpawn = false;
|
|
|
|
List[Index].PawnsTotal = Max(Round(PawnTotalF), 1);
|
|
|
|
List[Index].SingleSpawnLimit = Max(Round(PawnLimitF), 1);
|
|
|
|
List[Index].PawnsLeft = List[Index].PawnsTotal;
|
2022-06-01 00:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach List(SE, Index)
|
|
|
|
{
|
|
|
|
List[Index].ZedNameFiller = "";
|
|
|
|
while (Len(String(SE.ZedClass)) + Len(List[Index].ZedNameFiller) < ZedNameMaxLength)
|
|
|
|
List[Index].ZedNameFiller @= "";
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
private function SpawnTimerLogger(bool Stop, optional String Comment)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
|
|
|
local String Message;
|
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
|
|
|
if (Stop)
|
|
|
|
Message = "Stop spawn";
|
|
|
|
else
|
|
|
|
Message = "Start spawn";
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
if (Comment != "")
|
|
|
|
Message @= "(" $ Comment $ ")";
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
if (SpawnActive == Stop)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Info(Message);
|
2022-06-01 00:03:02 +00:00
|
|
|
SpawnActive = !Stop;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-16 02:42:13 +00:00
|
|
|
private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-16 02:42:13 +00:00
|
|
|
local S_SpawnEntry SE;
|
2022-06-05 00:58:53 +00:00
|
|
|
local int FreeSpawnSlots, PawnCount, Spawned;
|
2022-06-01 00:03:02 +00:00
|
|
|
local String Action, Comment, NextSpawn;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-16 02:42:13 +00:00
|
|
|
SE = SpawnList[Index];
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-06-05 23:44:39 +00:00
|
|
|
SpawnList[Index].Delay = float(SE.DelayDefault);
|
2022-06-01 00:03:02 +00:00
|
|
|
if (FRand() <= SE.Probability || SE.ForceSpawn)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
if (SE.SingleSpawnLimit == 0 || SE.PawnsLeft < SE.SingleSpawnLimit)
|
2022-06-01 00:03:02 +00:00
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
PawnCount = SE.PawnsLeft;
|
2022-06-01 00:03:02 +00:00
|
|
|
}
|
2022-05-16 02:42:13 +00:00
|
|
|
else
|
2022-06-01 00:03:02 +00:00
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
PawnCount = SE.SingleSpawnLimit;
|
2022-06-01 00:03:02 +00:00
|
|
|
}
|
2022-05-16 02:42:13 +00:00
|
|
|
|
|
|
|
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
|
|
|
|
{
|
|
|
|
FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount;
|
2022-05-31 01:44:12 +00:00
|
|
|
if (FreeSpawnSlots == 0)
|
|
|
|
{
|
|
|
|
NoFreeSpawnSlots = true;
|
2022-06-05 00:58:53 +00:00
|
|
|
SpawnList[Index].PawnsLeft = 0;
|
2022-05-31 01:44:12 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-06-05 00:58:53 +00:00
|
|
|
else if (PawnCount > FreeSpawnSlots)
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
PawnCount = FreeSpawnSlots;
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
Spawned = SpawnZed(SE.ZedClass, PawnCount, SE.SpawnAtPlayerStart);
|
2022-06-01 00:03:02 +00:00
|
|
|
if (Spawned == INDEX_NONE)
|
|
|
|
{
|
2022-06-05 23:44:39 +00:00
|
|
|
SpawnList[Index].Delay = 5.0f;
|
2022-06-01 00:03:02 +00:00
|
|
|
SpawnList[Index].ForceSpawn = true;
|
|
|
|
Action = "Skip spawn";
|
2022-06-05 23:44:39 +00:00
|
|
|
Comment = "no free spawn volume, try to spawn it again in" @ Round(SpawnList[Index].Delay) @ "seconds...";
|
2022-06-01 00:03:02 +00:00
|
|
|
SpawnLog(SE, Action, Comment);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (Spawned == 0)
|
|
|
|
{
|
|
|
|
Action = "Spawn failed";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SpawnList[Index].ForceSpawn = false;
|
|
|
|
Action = "Spawned";
|
|
|
|
Comment = "x" $ Spawned;
|
|
|
|
}
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
Action = "Skip spawn";
|
|
|
|
Comment = "due to" @ Round(SE.Probability * 100) $ "%" @ "probability";
|
2022-05-16 02:42:13 +00:00
|
|
|
Spawned = SE.SingleSpawnLimit;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
SpawnList[Index].PawnsLeft -= Spawned;
|
|
|
|
if (SpawnList[Index].PawnsLeft > 0)
|
2022-05-16 02:42:13 +00:00
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].PawnsLeft;
|
2022-05-16 02:42:13 +00:00
|
|
|
}
|
2022-06-01 00:03:02 +00:00
|
|
|
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);
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function int PlayerCount()
|
|
|
|
{
|
|
|
|
local PlayerController PC;
|
|
|
|
local int HumanPlayers;
|
|
|
|
local KFOnlineGameSettings KFGameSettings;
|
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
|
|
|
if (KFGIS.PlayfabInter != None && KFGIS.PlayfabInter.GetGameSettings() != None)
|
|
|
|
{
|
|
|
|
KFGameSettings = KFOnlineGameSettings(KFGIS.PlayfabInter.GetGameSettings());
|
|
|
|
HumanPlayers = KFGameSettings.NumPublicConnections - KFGameSettings.NumOpenPublicConnections;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
HumanPlayers = 0;
|
|
|
|
foreach WorldInfo.AllControllers(class'PlayerController', PC)
|
|
|
|
if (PC.bIsPlayer
|
|
|
|
&& PC.PlayerReplicationInfo != None
|
|
|
|
&& !PC.PlayerReplicationInfo.bOnlySpectator
|
|
|
|
&& !PC.PlayerReplicationInfo.bBot)
|
|
|
|
HumanPlayers++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return HumanPlayers;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function Vector PlayerStartLocation()
|
|
|
|
{
|
|
|
|
local PlayerController PC;
|
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
|
|
|
foreach WorldInfo.AllControllers(class'PlayerController', PC)
|
|
|
|
return KFGIS.FindPlayerStart(PC, 0).Location;
|
|
|
|
|
|
|
|
return KFGIS.FindPlayerStart(None, 0).Location;
|
|
|
|
}
|
|
|
|
|
2022-06-05 00:58:53 +00:00
|
|
|
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, bool SpawnAtPlayerStart)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
|
|
|
local Array<class<KFPawn_Monster> > CustomSquad;
|
2022-06-01 00:03:02 +00:00
|
|
|
local Vector SpawnLocation, PlayerStart;
|
|
|
|
local KFSpawnVolume SpawnVolume;
|
2022-05-11 12:43:39 +00:00
|
|
|
local KFPawn_Monster KFPM;
|
|
|
|
local Controller C;
|
2022-06-01 00:03:02 +00:00
|
|
|
local int Failed, Spawned;
|
2022-05-16 02:42:13 +00:00
|
|
|
local int Index;
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
PlayerStart = PlayerStartLocation();
|
2022-05-11 12:43:39 +00:00
|
|
|
if (SpawnAtPlayerStart)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
SpawnLocation = PlayerStart;
|
2022-05-11 12:43:39 +00:00
|
|
|
SpawnLocation.Y += 64;
|
|
|
|
SpawnLocation.Z += 64;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-05 00:58:53 +00:00
|
|
|
for (Index = 0; Index < PawnCount; Index++)
|
2022-06-01 00:03:02 +00:00
|
|
|
{
|
|
|
|
CustomSquad.AddItem(ZedClass);
|
|
|
|
}
|
|
|
|
|
|
|
|
SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad);
|
|
|
|
if (SpawnVolume == None)
|
|
|
|
{
|
|
|
|
return INDEX_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
SpawnLocation = SpawnVolume.Location;
|
|
|
|
if (SpawnLocation == PlayerStart)
|
|
|
|
{
|
|
|
|
return INDEX_NONE;
|
|
|
|
}
|
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
SpawnLocation.Z += 10;
|
|
|
|
}
|
2022-05-31 01:44:12 +00:00
|
|
|
|
2022-06-01 00:03:02 +00:00
|
|
|
Spawned = 0; Failed = 0;
|
2022-06-05 00:58:53 +00:00
|
|
|
while (Failed + Spawned < PawnCount)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
|
|
|
KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true);
|
|
|
|
if (KFPM == None)
|
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Error("Can't spawn" @ ZedClass);
|
2022-06-01 00:03:02 +00:00
|
|
|
Failed++;
|
2022-05-11 12:43:39 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
C = KFPM.Spawn(KFPM.ControllerClass);
|
|
|
|
if (C == None)
|
|
|
|
{
|
2022-06-01 00:03:02 +00:00
|
|
|
`ZS_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this" @ KFPM $ "...");
|
2022-05-11 12:43:39 +00:00
|
|
|
KFPM.Destroy();
|
2022-06-01 00:03:02 +00:00
|
|
|
Failed++;
|
2022-05-11 12:43:39 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
C.Possess(KFPM, false);
|
2022-06-01 00:03:02 +00:00
|
|
|
Spawned++;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
2022-05-31 01:44:12 +00:00
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
|
|
|
|
{
|
2022-05-31 01:44:12 +00:00
|
|
|
KFGIS.NumAIFinishedSpawning += Spawned;
|
2022-06-01 00:03:02 +00:00
|
|
|
KFGIS.NumAISpawnsQueued += Spawned;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 01:44:12 +00:00
|
|
|
KFGIS.UpdateAIRemaining();
|
|
|
|
|
|
|
|
return Spawned;
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 07:02:34 +00:00
|
|
|
public function NotifyLogin(Controller C)
|
|
|
|
{
|
|
|
|
local ZedSpawnerRepLink RepLink;
|
|
|
|
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-13 07:02:34 +00:00
|
|
|
|
|
|
|
RepLink = Spawn(class'ZedSpawnerRepLink', C);
|
|
|
|
RepLink.LogLevel = LogLevel;
|
|
|
|
RepLink.CustomZeds = CustomZeds;
|
|
|
|
RepLink.ServerSync();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function NotifyLogout(Controller C)
|
2022-05-11 12:43:39 +00:00
|
|
|
{
|
2022-05-17 09:56:31 +00:00
|
|
|
`ZS_Trace(`Location);
|
2022-05-11 12:43:39 +00:00
|
|
|
|
2022-05-13 07:02:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DefaultProperties
|
|
|
|
{
|
|
|
|
|
2022-05-11 12:43:39 +00:00
|
|
|
}
|