KF2-ZedSpawner/ZedSpawner/Classes/ZedSpawner.uc

816 lines
20 KiB
Ucode
Raw Normal View History

2022-05-11 12:43:39 +00:00
class ZedSpawner extends Info
config(ZedSpawner);
2022-07-13 09:09:18 +00:00
const LatestVersion = 4;
2022-05-11 12:43:39 +00:00
const CfgSpawn = class'Spawn';
const CfgSpawnAtPlayerStart = class'SpawnAtPlayerStart';
const CfgSpawnListRW = class'SpawnListRegular';
const CfgSpawnListBW = class'SpawnListBossWaves';
const CfgSpawnListSW = class'SpawnListSpecialWaves';
2022-05-11 12:43:39 +00:00
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;
2022-06-05 23:44:39 +00:00
var float Delay;
var int PawnsLeft;
var int PawnsTotal;
var bool ForceSpawn;
var String ZedNameFiller;
2022-07-13 09:09:18 +00:00
var int SmoothPawnPool;
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
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;
var private Array<S_SpawnEntry> SpawnListCurrent;
2022-05-11 12:43:39 +00:00
var private bool NoFreeSpawnSlots;
var private bool UseRegularSpawnList;
var private bool UseBossSpawnList;
var private bool UseSpecialSpawnList;
var private bool GlobalSpawnAtPlayerStart;
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;
var private Array<class<KFPawn_Monster> > SpawnAtPlayerStartZeds;
2022-05-11 12:43:39 +00:00
var private bool SpawnActive;
var private String SpawnListsComment;
2022-05-20 21:31:35 +00:00
var private Array<ZedSpawnerRepLink> RepLinks;
public simulated function bool SafeDestroy()
{
return (bPendingDelete || bDeleteMe || Destroy());
}
2022-05-20 21:31:35 +00:00
public event PreBeginPlay()
2022-05-16 02:42:13 +00:00
{
2022-07-19 10:28:43 +00:00
`Log_Trace();
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-07-13 07:00:06 +00:00
`Log_Fatal("NetMode == NM_Client, Destroy...");
SafeDestroy();
2022-05-20 20:45:26 +00:00
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-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-16 02:42:13 +00:00
if (bPendingDelete || bDeleteMe) return;
2022-05-20 20:45:26 +00:00
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)
{
LogLevel = LL_Info;
SaveConfig();
}
2022-05-11 12:43:39 +00:00
2022-07-19 10:28:43 +00:00
CfgSpawn.static.InitConfig(Version, LatestVersion, LogLevel);
CfgSpawnAtPlayerStart.static.InitConfig(Version, LatestVersion, LogLevel);
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel);
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel);
CfgSpawnListSW.static.InitConfig(Version, LatestVersion, LogLevel);
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:
2022-07-13 07:00:06 +00:00
`Log_Info("Config created");
2022-06-05 23:44:39 +00:00
case 1:
Tickrate = 1.0f;
case 2:
2022-07-13 09:09:18 +00:00
case 3:
2022-05-20 20:45:26 +00:00
case MaxInt:
2022-07-13 07:00:06 +00:00
`Log_Info("Config updated to version"@LatestVersion);
2022-05-20 20:45:26 +00:00
break;
case LatestVersion:
2022-07-13 07:00:06 +00:00
`Log_Info("Config is up-to-date");
2022-05-20 20:45:26 +00:00
break;
default:
2022-07-13 07:00:06 +00:00
`Log_Warn("The config version is higher than the current version (are you using an old mutator?)");
`Log_Warn("Config version is" @ Version @ "but current version is" @ LatestVersion);
`Log_Warn("The config version will be changed to" @ LatestVersion);
2022-05-20 20:45:26 +00:00
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;
local String CurrentMap;
2022-05-11 12:43:39 +00:00
2022-07-19 10:28:43 +00:00
`Log_Trace();
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-07-13 07:00:06 +00:00
`Log_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy...");
SafeDestroy();
2022-05-20 20:45:26 +00:00
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-07-13 07:00:06 +00:00
`Log_Warn("Wrong 'LogLevel', return to default value");
2022-05-11 12:43:39 +00:00
SaveConfig();
}
2022-07-13 07:00:06 +00:00
`Log_Base("LogLevel:" @ LogLevel);
2022-05-11 12:43:39 +00:00
2022-06-05 23:44:39 +00:00
if (Tickrate <= 0)
{
2022-07-13 07:00:06 +00:00
`Log_Error("Spawner tickrate must be positive (current value:" @ Tickrate $ ")");
2022-06-05 23:44:39 +00:00
}
if (!CfgSpawn.static.Load(LogLevel) || Tickrate <= 0)
2022-05-11 12:43:39 +00:00
{
2022-07-13 07:00:06 +00:00
`Log_Fatal("Wrong settings, Destroy...");
SafeDestroy();
2022-05-11 12:43:39 +00:00
return;
}
2022-06-05 23:44:39 +00:00
dt = 1 / Tickrate;
2022-07-13 07:00:06 +00:00
`Log_Info("Spawner tickrate:" @ Tickrate @ "(update every" @ dt $ "s)");
2022-05-11 12:43:39 +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);
SpawnAtPlayerStartZeds = CfgSpawnAtPlayerStart.static.Load(LogLevel);
CurrentMap = String(WorldInfo.GetPackageName());
GlobalSpawnAtPlayerStart = (CfgSpawnAtPlayerStart.default.Map.Find(CurrentMap) != INDEX_NONE);
2022-07-13 07:00:06 +00:00
`Log_Info("GlobalSpawnAtPlayerStart:" @ GlobalSpawnAtPlayerStart $ GlobalSpawnAtPlayerStart ? "(" $ CurrentMap $ ")" : "");
2022-05-11 12:43:39 +00:00
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;
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-07-19 10:28:43 +00:00
`Log_Trace();
ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds);
2022-05-20 20:45:26 +00:00
ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds);
ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds);
foreach CustomZeds(PawnClass)
{
2022-07-13 07:00:06 +00:00
`Log_Info("Preload content:" @ PawnClass);
2022-05-20 20:45:26 +00:00
PawnClass.static.PreloadContent();
}
}
private function ExtractCustomZedsFromSpawnList(Array<S_SpawnEntry> SpawnList, out Array<class<KFPawn_Monster> > Out)
{
local S_SpawnEntry SE;
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-20 20:45:26 +00:00
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-07-19 10:28:43 +00:00
&& KFGIA.IsCustomZed(SE.ZedClass, LogLevel))
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()
{
local S_SpawnEntry SE;
local int Index;
local float Threshold;
2022-07-19 10:28:43 +00:00
`Log_Trace();
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;
}
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;
}
if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn)
2022-05-16 02:42:13 +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
SpawnTimerLogger(false, SpawnListsComment);
Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI));
foreach SpawnListCurrent(SE, Index)
{
if (NoFreeSpawnSlots)
{
break;
}
if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold)
{
continue;
}
2022-06-05 23:44:39 +00:00
if (SE.Delay > 0.0f)
{
SpawnListCurrent[Index].Delay -= dt;
continue;
}
if (SE.PawnsLeft > 0)
{
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
{
local Array<String> SpawnListNames;
local int WaveTotalAIDef;
local byte BaseWave;
local String WaveTypeInfo;
local S_SpawnEntry SE;
local EAIType SWType;
2022-05-11 12:43:39 +00:00
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-11 12:43:39 +00:00
if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle)
{
CurrentCycle++;
2022-07-13 07:00:06 +00:00
`Log_Info("Spawn cycle started:" @ CurrentCycle);
}
2022-05-16 02:42:13 +00:00
CurrentWave = KFGIS.WaveNum;
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())
{
2022-07-19 10:28:43 +00:00
CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex), LogLevel);
if (CurrentBossClass == None)
{
2022-07-13 07:00:06 +00:00
`Log_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex);
}
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-07-13 07:00:06 +00:00
`Log_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")");
2022-05-16 02:42:13 +00:00
}
CurrentBossClass = None;
2022-05-16 02:42:13 +00:00
}
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)
{
SpawnListNames.AddItem("regular");
BaseWave = KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1);
foreach SpawnListRW(SE)
if (SE.Wave == BaseWave)
SpawnListCurrent.AddItem(SE);
else if (SE.Wave > BaseWave)
break;
}
if (UseSpecialSpawnList)
{
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);
}
2022-05-16 02:42:13 +00:00
JoinArray(SpawnListNames, SpawnListsComment, ", ");
AdjustSpawnList(SpawnListCurrent);
if (WaveTypeInfo != "")
2022-05-16 02:42:13 +00:00
{
WaveTypeInfo = "(" $ WaveTypeInfo $ ")";
2022-05-16 02:42:13 +00:00
}
2022-07-13 07:00:06 +00:00
`Log_Info("Wave" @ CurrentWave @ WaveTypeInfo);
2022-05-16 02:42:13 +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;
local float B, TM, TCM, TPM;
local float L, LM, LCM, LPM;
local float PawnTotalF, PawnLimitF;
local int ZedNameMaxLength;
2022-05-16 02:42:13 +00:00
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-16 02:42:13 +00:00
Cycle = float(CurrentCycle);
Players = float(PlayerCount());
TM = CfgSpawn.default.ZedTotalMultiplier;
TCM = CfgSpawn.default.SpawnTotalCycleMultiplier;
TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier;
2022-05-11 12:43:39 +00:00
LM = CfgSpawn.default.SingleSpawnLimitMultiplier;
LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
ZedNameMaxLength = 0;
2022-05-16 02:42:13 +00:00
foreach List(SE, Index)
{
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;
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
}
B = float(SE.SpawnCountBase);
L = float(SE.SingleSpawnLimitDefault);
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;
}
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
}
}
private function SpawnTimerLogger(bool Stop, optional String Comment)
2022-05-11 12:43:39 +00:00
{
local String Message;
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-11 12:43:39 +00:00
if (Stop)
Message = "Stop spawn";
else
Message = "Start spawn";
if (Comment != "")
Message @= "(" $ Comment $ ")";
2022-05-11 12:43:39 +00:00
if (SpawnActive == Stop)
2022-05-11 12:43:39 +00:00
{
2022-07-13 07:00:06 +00:00
`Log_Info(Message);
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;
local int FreeSpawnSlots, PawnCount, Spawned;
local String Action, Comment, NextSpawn;
local bool SpawnAtPlayerStart;
2022-05-11 12:43:39 +00:00
2022-07-19 10:28:43 +00:00
`Log_Trace();
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-07-13 09:09:18 +00:00
if (FRand() <= SE.Probability || SE.ForceSpawn)
2022-05-11 12:43:39 +00:00
{
if (SE.SingleSpawnLimit == 0 || SE.PawnsLeft < SE.SingleSpawnLimit)
{
PawnCount = SE.PawnsLeft;
}
2022-05-16 02:42:13 +00:00
else
{
PawnCount = SE.SingleSpawnLimit;
}
2022-05-16 02:42:13 +00:00
2022-07-13 09:09:18 +00:00
if (CfgSpawn.default.bSmoothSpawn)
{
if (SE.SmoothPawnPool <= 0)
{
SpawnList[Index].SmoothPawnPool = PawnCount;
}
PawnCount = 1;
}
2022-05-16 02:42:13 +00:00
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount;
if (FreeSpawnSlots == 0)
{
NoFreeSpawnSlots = true;
SpawnList[Index].PawnsLeft = 0;
return;
}
else if (PawnCount > FreeSpawnSlots)
2022-05-16 02:42:13 +00:00
{
PawnCount = FreeSpawnSlots;
2022-05-16 02:42:13 +00:00
}
}
SpawnAtPlayerStart = (GlobalSpawnAtPlayerStart || (SpawnAtPlayerStartZeds.Find(SE.ZedClass) != INDEX_NONE));
Spawned = SpawnZed(SE.ZedClass, PawnCount, SpawnAtPlayerStart);
if (Spawned == INDEX_NONE)
{
2022-06-05 23:44:39 +00:00
SpawnList[Index].Delay = 5.0f;
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...";
SpawnLog(SE, Action, Comment);
return;
}
else if (Spawned == 0)
{
Action = "Spawn failed";
}
else
{
Action = "Spawned";
Comment = "x" $ Spawned;
2022-07-13 09:09:18 +00:00
if (CfgSpawn.default.bSmoothSpawn)
{
SpawnList[Index].SmoothPawnPool -= Spawned;
if (SpawnList[Index].SmoothPawnPool > 0)
{
SpawnList[Index].Delay = 1.0f;
SpawnList[Index].ForceSpawn = true;
}
else
{
SpawnList[Index].Delay = float(SE.DelayDefault);
SpawnList[Index].ForceSpawn = false;
}
}
else
{
SpawnList[Index].ForceSpawn = false;
}
}
2022-05-11 12:43:39 +00:00
}
else
{
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
}
SpawnList[Index].PawnsLeft -= Spawned;
if (SpawnList[Index].PawnsLeft > 0)
2022-05-16 02:42:13 +00:00
{
2022-07-13 09:09:18 +00:00
if (CfgSpawn.default.bSmoothSpawn && SpawnList[Index].SmoothPawnPool > 0)
{
NextSpawn = "next after" @ Round(SpawnList[Index].Delay) $ "sec," @ "pawns left:" @ SpawnList[Index].SmoothPawnPool @ "(" $ SpawnList[Index].PawnsLeft $ ")";
}
else
{
NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].PawnsLeft;
}
2022-05-16 02:42:13 +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 $ ")";
2022-07-13 07:00:06 +00:00
`Log_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-07-19 10:28:43 +00:00
`Log_Trace();
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-07-19 10:28:43 +00:00
`Log_Trace();
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;
}
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, optional bool SpawnAtPlayerStart = false)
2022-05-11 12:43:39 +00:00
{
local Array<class<KFPawn_Monster> > CustomSquad;
local ESquadType PrevDesiredSquadType;
local Vector SpawnLocation, PlayerStart;
local KFSpawnVolume SpawnVolume;
2022-05-11 12:43:39 +00:00
local KFPawn_Monster KFPM;
local Controller C;
local int Failed, Spawned;
2022-05-16 02:42:13 +00:00
local int Index;
2022-05-11 12:43:39 +00:00
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-11 12:43:39 +00:00
PlayerStart = PlayerStartLocation();
2022-05-11 12:43:39 +00:00
if (SpawnAtPlayerStart)
{
SpawnLocation = PlayerStart;
2022-05-11 12:43:39 +00:00
SpawnLocation.Y += 64;
SpawnLocation.Z += 64;
}
else
{
for (Index = 0; Index < PawnCount; Index++)
{
CustomSquad.AddItem(ZedClass);
}
PrevDesiredSquadType = KFGIS.SpawnManager.DesiredSquadType;
KFGIS.SpawnManager.SetDesiredSquadTypeForZedList(CustomSquad);
SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad);
KFGIS.SpawnManager.DesiredSquadType = PrevDesiredSquadType;
if (SpawnVolume == None)
{
return INDEX_NONE;
}
SpawnVolume.VolumeChosenCount++;
SpawnLocation = SpawnVolume.Location;
if (SpawnLocation == PlayerStart)
{
return INDEX_NONE;
}
2022-05-11 12:43:39 +00:00
SpawnLocation.Z += 10;
}
Spawned = 0; Failed = 0;
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-07-13 07:00:06 +00:00
`Log_Error("Can't spawn" @ ZedClass);
Failed++;
2022-05-11 12:43:39 +00:00
continue;
}
C = KFPM.Spawn(KFPM.ControllerClass);
if (C == None)
{
2022-07-13 07:00:06 +00:00
`Log_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this" @ KFPM $ "...");
2022-05-11 12:43:39 +00:00
KFPM.Destroy();
Failed++;
2022-05-11 12:43:39 +00:00
continue;
}
C.Possess(KFPM, false);
Spawned++;
2022-05-11 12:43:39 +00:00
}
if (Spawned > 0)
{
KFGIS.SpawnManager.LastAISpawnVolume = SpawnVolume;
}
2022-05-11 12:43:39 +00:00
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{
KFGIS.NumAIFinishedSpawning += Spawned;
KFGIS.NumAISpawnsQueued += Spawned;
2022-05-11 12:43:39 +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)
{
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-07-13 09:09:18 +00:00
CreateRepLink(C);
}
public function NotifyLogout(Controller C)
{
2022-07-19 10:28:43 +00:00
`Log_Trace();
DestroyRepLink(C);
}
2022-07-13 06:56:35 +00:00
public function bool CreateRepLink(Controller C)
2022-05-13 07:02:34 +00:00
{
local ZedSpawnerRepLink RepLink;
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-13 07:02:34 +00:00
2022-07-13 06:56:35 +00:00
if (C == None) return false;
2022-05-13 07:02:34 +00:00
RepLink = Spawn(class'ZedSpawnerRepLink', C);
2022-07-13 06:56:35 +00:00
if (RepLink == None) return false;
2022-05-13 07:02:34 +00:00
RepLink.LogLevel = LogLevel;
RepLink.CustomZeds = CustomZeds;
RepLink.ZS = Self;
RepLinks.AddItem(RepLink);
2022-05-13 07:02:34 +00:00
RepLink.ServerSync();
2022-07-13 06:56:35 +00:00
return true;
2022-05-13 07:02:34 +00:00
}
public function bool DestroyRepLink(Controller C)
2022-05-11 12:43:39 +00:00
{
2022-07-13 06:56:35 +00:00
local ZedSpawnerRepLink RepLink;
2022-07-19 10:28:43 +00:00
`Log_Trace();
2022-05-11 12:43:39 +00:00
if (C == None) return false;
2022-07-13 06:56:35 +00:00
foreach RepLinks(RepLink)
{
2022-07-13 06:56:35 +00:00
if (RepLink.Owner == C)
{
2022-07-13 06:56:35 +00:00
RepLink.SafeDestroy();
RepLinks.RemoveItem(RepLink);
return true;
}
}
return false;
2022-05-13 07:02:34 +00:00
}
DefaultProperties
{
2022-05-11 12:43:39 +00:00
}