- now the type of unit affects the choice of spawn location
- bSpawnAtPlayerStart removed from spawn list
- SpawnAtPlayerStart can be set separately for specified maps or zed classes
- optimized spawn list loading
- added handling of the situation when the player leaves the game before preloadcontent synchronization ends
- fixed calculation of the number of zeds in some cases
This commit is contained in:
GenZmeY 2022-06-13 17:02:03 +03:00
parent e1face5c04
commit 90a69ef739
8 changed files with 200 additions and 51 deletions

View File

@ -56,7 +56,6 @@ Use the [b][ZedSpawner.SpawnListBossWaves][/b] and [b][ZedSpawner.SpawnListSpeci
[*][b]Probability[/b] - the chance (%) of each spawn (1-100). [*][b]Probability[/b] - the chance (%) of each spawn (1-100).
[*][b]SpawnCountBase[/b] - The base number of zeds to spawn, aka the number of zeds that will be spawned on the first cycle with one player. Can be adjusted by modifiers, number of players and cycle number. [*][b]SpawnCountBase[/b] - The base number of zeds to spawn, aka the number of zeds that will be spawned on the first cycle with one player. Can be adjusted by modifiers, number of players and cycle number.
[*][b]SingleSpawnLimit[/b] - maximum number of zeds for one spawn. Can be adjusted by modifiers, number of players and cycle number. [*][b]SingleSpawnLimit[/b] - maximum number of zeds for one spawn. Can be adjusted by modifiers, number of players and cycle number.
[*][b]bSpawnAtPlayerStart[/b] - exactly what is written.
[/list] [/list]
[h1]Spawn logic[/h1] [h1]Spawn logic[/h1]
@ -65,7 +64,5 @@ I really tried to describe in text how it works, but every time I got some kind
[h1]📌[url=https://redirect.genzmey.su/kf2-zedspawner-calc]Spawn calculator[/url][/h1] [h1]📌[url=https://redirect.genzmey.su/kf2-zedspawner-calc]Spawn calculator[/url][/h1]
[i]Just please try not to interfere with each other if you see that someone is already using a calculator.[/i] [i]Just please try not to interfere with each other if you see that someone is already using a calculator.[/i]
By the way, ZedSpawner logs everything it does, so reading the logs can also help you figure out how it works.
[h1]Sources[/h1] [h1]Sources[/h1]
[url=https://github.com/GenZmeY/KF2-ZedSpawner]https://github.com/GenZmeY/KF2-ZedSpawner[/url] (GNU GPLv3) [url=https://github.com/GenZmeY/KF2-ZedSpawner]https://github.com/GenZmeY/KF2-ZedSpawner[/url] (GNU GPLv3)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -0,0 +1,77 @@
class SpawnAtPlayerStart extends Object
dependson(ZedSpawner)
config(ZedSpawner);
var private config Array<String> ZedClass;
var public config Array<String> Map;
public static function InitConfig(int Version, int LatestVersion)
{
switch (Version)
{
case `NO_CONFIG:
case 2:
ApplyDefault();
default: break;
}
if (LatestVersion != Version)
{
StaticSaveConfig();
}
}
private static function ApplyDefault()
{
default.ZedClass.Length = 0;
default.ZedClass.AddItem("HL2Monsters.Combine_Strider");
default.ZedClass.AddItem("HL2Monsters.Combine_Gunship");
default.ZedClass.AddItem("HL2Monsters.Hunter_Chopper");
default.ZedClass.AddItem("SomePackage.SomeZedClassYouWantToSpawnAtPlayerStart");
default.Map.Length = 0;
default.Map.AddItem("KF-SomeMapNameWhereYouWantSpawnZedsAtPlayerStart");
}
public static function Array<class<KFPawn_Monster> > Load(E_LogLevel LogLevel)
{
local Array<class<KFPawn_Monster> > ZedList;
local class<KFPawn_Monster> KFPMC;
local String ZedClassTmp;
local int Line, Loaded;
Loaded = 0;
`ZS_Info("Load zeds to spawn at player start:");
foreach default.ZedClass(ZedClassTmp, Line)
{
KFPMC = class<KFPawn_Monster>(DynamicLoadObject(ZedClassTmp, class'Class'));
if (KFPMC == None)
{
`ZS_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ ZedClassTmp);
}
else
{
Loaded++;
ZedList.AddItem(KFPMC);
`ZS_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ZedClassTmp);
}
}
if (Loaded == default.ZedClass.Length)
{
`ZS_Info("Spawn at player start list (Zeds) loaded successfully (" $ default.ZedClass.Length @ "entries)");
}
else
{
`ZS_Info("Spawn at player start list (Zeds): loaded" @ Loaded @ "of" @ default.ZedClass.Length @ "entries");
}
return ZedList;
}
defaultproperties
{
}

View File

@ -10,7 +10,6 @@ struct S_SpawnEntryCfg
var int Probability; var int Probability;
var int SpawnCountBase; var int SpawnCountBase;
var int SingleSpawnLimit; var int SingleSpawnLimit;
var bool bSpawnAtPlayerStart;
}; };
var public config bool bStopRegularSpawn; var public config bool bStopRegularSpawn;
@ -47,7 +46,6 @@ private static function ApplyDefault(KFGI_Access KFGIA)
SpawnEntry.SingleSpawnLimit = 1; SpawnEntry.SingleSpawnLimit = 1;
SpawnEntry.Delay = 30; SpawnEntry.Delay = 30;
SpawnEntry.Probability = 100; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
KFPM_Bosses = KFGIA.GetAIBossClassList(); KFPM_Bosses = KFGIA.GetAIBossClassList();
foreach KFPM_Bosses(KFPMC) foreach KFPM_Bosses(KFPMC)
{ {
@ -65,7 +63,7 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
local bool Errors; local bool Errors;
local int Loaded; local int Loaded;
`ZS_Info("Load boss waves spawn list..."); `ZS_Info("Load boss waves spawn list:");
foreach default.Spawn(SpawnEntryCfg, Line) foreach default.Spawn(SpawnEntryCfg, Line)
{ {
Errors = false; Errors = false;
@ -114,8 +112,6 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
Errors = true; Errors = true;
} }
SpawnEntry.SpawnAtPlayerStart = SpawnEntryCfg.bSpawnAtPlayerStart;
if (!Errors) if (!Errors)
{ {
Loaded++; Loaded++;
@ -126,7 +122,7 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
if (Loaded == default.Spawn.Length) if (Loaded == default.Spawn.Length)
{ {
`ZS_Info("Boss spawn list loaded successfully"); `ZS_Info("Boss spawn list loaded successfully (" $ default.Spawn.Length @ "entries)");
} }
else else
{ {

View File

@ -11,11 +11,15 @@ struct S_SpawnEntryCfg
var int Probability; var int Probability;
var int SpawnCountBase; var int SpawnCountBase;
var int SingleSpawnLimit; var int SingleSpawnLimit;
var bool bSpawnAtPlayerStart;
}; };
var public config Array<S_SpawnEntryCfg> Spawn; var public config Array<S_SpawnEntryCfg> Spawn;
delegate int SpawnListSort(S_SpawnEntryCfg A, S_SpawnEntryCfg B)
{
return A.Wave > B.Wave ? -1 : 0;
}
public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA) public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA)
{ {
switch (Version) switch (Version)
@ -46,8 +50,7 @@ private static function ApplyDefault(KFGI_Access KFGIA)
SpawnEntry.RelativeStart = 25; SpawnEntry.RelativeStart = 25;
SpawnEntry.Delay = 60; SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
KFPM_Zeds = KFGIA.GetAIClassList(); KFPM_Zeds = KFGIA.GetAIClassList();
foreach KFPM_Zeds(KFPMC) foreach KFPM_Zeds(KFPMC)
{ {
@ -120,8 +123,6 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
Errors = true; Errors = true;
} }
SpawnEntry.SpawnAtPlayerStart = SpawnEntryCfg.bSpawnAtPlayerStart;
if (!Errors) if (!Errors)
{ {
Loaded++; Loaded++;
@ -130,9 +131,11 @@ public static function Array<S_SpawnEntry> Load(E_LogLevel LogLevel)
} }
} }
default.Spawn.Sort(SpawnListSort);
if (Loaded == default.Spawn.Length) if (Loaded == default.Spawn.Length)
{ {
`ZS_Info("Regular spawn list loaded successfully"); `ZS_Info("Regular spawn list loaded successfully (" $ default.Spawn.Length @ "entries)");
} }
else else
{ {

View File

@ -11,7 +11,6 @@ struct S_SpawnEntryCfg
var int Probability; var int Probability;
var int SpawnCountBase; var int SpawnCountBase;
var int SingleSpawnLimit; var int SingleSpawnLimit;
var bool bSpawnAtPlayerStart;
}; };
var public config bool bStopRegularSpawn; var public config bool bStopRegularSpawn;
@ -46,7 +45,6 @@ private static function ApplyDefault()
SpawnEntry.RelativeStart = 0; SpawnEntry.RelativeStart = 0;
SpawnEntry.Delay = 60; SpawnEntry.Delay = 60;
SpawnEntry.Probability = 100; SpawnEntry.Probability = 100;
SpawnEntry.bSpawnAtPlayerStart = false;
foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType) foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType)
{ {
SpawnEntry.Wave = AIType; SpawnEntry.Wave = AIType;
@ -123,8 +121,6 @@ public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogL
Errors = true; Errors = true;
} }
SpawnEntry.SpawnAtPlayerStart = SpawnEntryCfg.bSpawnAtPlayerStart;
if (!Errors) if (!Errors)
{ {
Loaded++; Loaded++;
@ -135,7 +131,7 @@ public static function Array<S_SpawnEntry> Load(KFGameInfo_Endless KFGIE, E_LogL
if (Loaded == default.Spawn.Length) if (Loaded == default.Spawn.Length)
{ {
`ZS_Info("Special spawn list loaded successfully"); `ZS_Info("Special spawn list loaded successfully (" $ default.Spawn.Length @ "entries)");
} }
else else
{ {

View File

@ -1,12 +1,13 @@
class ZedSpawner extends Info class ZedSpawner extends Info
config(ZedSpawner); config(ZedSpawner);
const LatestVersion = 2; const LatestVersion = 3;
const CfgSpawn = class'Spawn'; const CfgSpawn = class'Spawn';
const CfgSpawnListRW = class'SpawnListRegular'; const CfgSpawnAtPlayerStart = class'SpawnAtPlayerStart';
const CfgSpawnListBW = class'SpawnListBossWaves'; const CfgSpawnListRW = class'SpawnListRegular';
const CfgSpawnListSW = class'SpawnListSpecialWaves'; const CfgSpawnListBW = class'SpawnListBossWaves';
const CfgSpawnListSW = class'SpawnListSpecialWaves';
enum E_LogLevel enum E_LogLevel
{ {
@ -35,7 +36,6 @@ struct S_SpawnEntry
var float Delay; var float Delay;
var int PawnsLeft; var int PawnsLeft;
var int PawnsTotal; var int PawnsTotal;
var bool SpawnAtPlayerStart;
var bool ForceSpawn; var bool ForceSpawn;
var String ZedNameFiller; var String ZedNameFiller;
}; };
@ -55,6 +55,7 @@ var private bool NoFreeSpawnSlots;
var private bool UseRegularSpawnList; var private bool UseRegularSpawnList;
var private bool UseBossSpawnList; var private bool UseBossSpawnList;
var private bool UseSpecialSpawnList; var private bool UseSpecialSpawnList;
var private bool GlobalSpawnAtPlayerStart;
var private KFGameInfo_Survival KFGIS; var private KFGameInfo_Survival KFGIS;
var private KFGameInfo_Endless KFGIE; var private KFGameInfo_Endless KFGIE;
@ -69,10 +70,18 @@ var private int WaveTotalAI;
var private class<KFPawn_Monster> CurrentBossClass; var private class<KFPawn_Monster> CurrentBossClass;
var private Array<class<KFPawn_Monster> > CustomZeds; var private Array<class<KFPawn_Monster> > CustomZeds;
var private Array<class<KFPawn_Monster> > SpawnAtPlayerStartZeds;
var private bool SpawnActive; var private bool SpawnActive;
var private String SpawnListsComment; var private String SpawnListsComment;
var private Array<ZedSpawnerRepLink> RepLinks;
public simulated function bool SafeDestroy()
{
return (bPendingDelete || bDeleteMe || Destroy());
}
public event PreBeginPlay() public event PreBeginPlay()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -80,7 +89,7 @@ public event PreBeginPlay()
if (WorldInfo.NetMode == NM_Client) if (WorldInfo.NetMode == NM_Client)
{ {
`ZS_Fatal("NetMode == NM_Client, Destroy..."); `ZS_Fatal("NetMode == NM_Client, Destroy...");
Destroy(); SafeDestroy();
return; return;
} }
@ -91,7 +100,7 @@ public event PostBeginPlay()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (bPendingDelete) return; if (bPendingDelete || bDeleteMe) return;
Super.PostBeginPlay(); Super.PostBeginPlay();
@ -102,12 +111,12 @@ private function InitConfig()
{ {
if (Version == `NO_CONFIG) if (Version == `NO_CONFIG)
{ {
Tickrate = 1.0f;
LogLevel = LL_Info; LogLevel = LL_Info;
SaveConfig(); SaveConfig();
} }
CfgSpawn.static.InitConfig(Version, LatestVersion); CfgSpawn.static.InitConfig(Version, LatestVersion);
CfgSpawnAtPlayerStart.static.InitConfig(Version, LatestVersion);
CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA); CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA); CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA);
CfgSpawnListSW.static.InitConfig(Version, LatestVersion); CfgSpawnListSW.static.InitConfig(Version, LatestVersion);
@ -119,6 +128,8 @@ private function InitConfig()
case 1: case 1:
Tickrate = 1.0f; Tickrate = 1.0f;
case 2:
case MaxInt: case MaxInt:
`ZS_Info("Config updated to version"@LatestVersion); `ZS_Info("Config updated to version"@LatestVersion);
@ -145,6 +156,7 @@ private function InitConfig()
private function Init() private function Init()
{ {
local S_SpawnEntry SE; local S_SpawnEntry SE;
local String CurrentMap;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -152,7 +164,7 @@ private function Init()
if (KFGIS == None) if (KFGIS == None)
{ {
`ZS_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy..."); `ZS_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy...");
Destroy(); SafeDestroy();
return; return;
} }
@ -177,7 +189,7 @@ private function Init()
if (!CfgSpawn.static.Load(LogLevel) || Tickrate <= 0) if (!CfgSpawn.static.Load(LogLevel) || Tickrate <= 0)
{ {
`ZS_Fatal("Wrong settings, Destroy..."); `ZS_Fatal("Wrong settings, Destroy...");
Destroy(); SafeDestroy();
return; return;
} }
@ -187,6 +199,11 @@ private function Init()
SpawnListRW = CfgSpawnListRW.static.Load(LogLevel); SpawnListRW = CfgSpawnListRW.static.Load(LogLevel);
SpawnListBW = CfgSpawnListBW.static.Load(LogLevel); SpawnListBW = CfgSpawnListBW.static.Load(LogLevel);
SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel); SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel);
SpawnAtPlayerStartZeds = CfgSpawnAtPlayerStart.static.Load(LogLevel);
CurrentMap = String(WorldInfo.GetPackageName());
GlobalSpawnAtPlayerStart = (CfgSpawnAtPlayerStart.default.Map.Find(CurrentMap) != INDEX_NONE);
`ZS_Info("GlobalSpawnAtPlayerStart:" @ GlobalSpawnAtPlayerStart $ GlobalSpawnAtPlayerStart ? "(" $ CurrentMap $ ")" : "");
CurrentWave = INDEX_NONE; CurrentWave = INDEX_NONE;
SpecialWave = INDEX_NONE; SpecialWave = INDEX_NONE;
@ -311,6 +328,7 @@ private function SetupWave()
{ {
local Array<String> SpawnListNames; local Array<String> SpawnListNames;
local int WaveTotalAIDef; local int WaveTotalAIDef;
local byte BaseWave;
local String WaveTypeInfo; local String WaveTypeInfo;
local S_SpawnEntry SE; local S_SpawnEntry SE;
local EAIType SWType; local EAIType SWType;
@ -379,9 +397,12 @@ private function SetupWave()
if (UseRegularSpawnList) if (UseRegularSpawnList)
{ {
SpawnListNames.AddItem("regular"); SpawnListNames.AddItem("regular");
BaseWave = KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1);
foreach SpawnListRW(SE) foreach SpawnListRW(SE)
if (SE.Wave == KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1)) if (SE.Wave == BaseWave)
SpawnListCurrent.AddItem(SE); SpawnListCurrent.AddItem(SE);
else if (SE.Wave > BaseWave)
break;
} }
if (UseSpecialSpawnList) if (UseSpecialSpawnList)
@ -426,9 +447,6 @@ private function AdjustSpawnList(out Array<S_SpawnEntry> List)
Cycle = float(CurrentCycle); Cycle = float(CurrentCycle);
Players = float(PlayerCount()); Players = float(PlayerCount());
B = float(SE.SpawnCountBase);
L = float(SE.SingleSpawnLimitDefault);
TM = CfgSpawn.default.ZedTotalMultiplier; TM = CfgSpawn.default.ZedTotalMultiplier;
TCM = CfgSpawn.default.SpawnTotalCycleMultiplier; TCM = CfgSpawn.default.SpawnTotalCycleMultiplier;
TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier; TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier;
@ -436,7 +454,7 @@ private function AdjustSpawnList(out Array<S_SpawnEntry> List)
LM = CfgSpawn.default.SingleSpawnLimitMultiplier; LM = CfgSpawn.default.SingleSpawnLimitMultiplier;
LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier; LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier;
LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier; LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier;
ZedNameMaxLength = 0; ZedNameMaxLength = 0;
foreach List(SE, Index) foreach List(SE, Index)
{ {
@ -455,6 +473,9 @@ private function AdjustSpawnList(out Array<S_SpawnEntry> List)
List[Index].Delay = 0.0f; List[Index].Delay = 0.0f;
} }
B = float(SE.SpawnCountBase);
L = float(SE.SingleSpawnLimitDefault);
PawnTotalF = B * (TM + TCM * (Cycle - 1.0f) + TPM * (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)); PawnLimitF = L * (LM + LCM * (Cycle - 1.0f) + LPM * (Players - 1.0f));
@ -498,6 +519,7 @@ private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
local S_SpawnEntry SE; local S_SpawnEntry SE;
local int FreeSpawnSlots, PawnCount, Spawned; local int FreeSpawnSlots, PawnCount, Spawned;
local String Action, Comment, NextSpawn; local String Action, Comment, NextSpawn;
local bool SpawnAtPlayerStart;
`ZS_Trace(`Location); `ZS_Trace(`Location);
@ -530,7 +552,9 @@ private function SpawnEntry(out Array<S_SpawnEntry> SpawnList, int Index)
} }
} }
Spawned = SpawnZed(SE.ZedClass, PawnCount, SE.SpawnAtPlayerStart); SpawnAtPlayerStart = (GlobalSpawnAtPlayerStart || (SpawnAtPlayerStartZeds.Find(SE.ZedClass) != INDEX_NONE));
Spawned = SpawnZed(SE.ZedClass, PawnCount, SpawnAtPlayerStart);
if (Spawned == INDEX_NONE) if (Spawned == INDEX_NONE)
{ {
SpawnList[Index].Delay = 5.0f; SpawnList[Index].Delay = 5.0f;
@ -613,9 +637,10 @@ private function Vector PlayerStartLocation()
return KFGIS.FindPlayerStart(None, 0).Location; return KFGIS.FindPlayerStart(None, 0).Location;
} }
private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, bool SpawnAtPlayerStart) private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, optional bool SpawnAtPlayerStart = false)
{ {
local Array<class<KFPawn_Monster> > CustomSquad; local Array<class<KFPawn_Monster> > CustomSquad;
local ESquadType PrevDesiredSquadType;
local Vector SpawnLocation, PlayerStart; local Vector SpawnLocation, PlayerStart;
local KFSpawnVolume SpawnVolume; local KFSpawnVolume SpawnVolume;
local KFPawn_Monster KFPM; local KFPawn_Monster KFPM;
@ -639,12 +664,18 @@ private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, boo
CustomSquad.AddItem(ZedClass); CustomSquad.AddItem(ZedClass);
} }
PrevDesiredSquadType = KFGIS.SpawnManager.DesiredSquadType;
KFGIS.SpawnManager.SetDesiredSquadTypeForZedList(CustomSquad);
SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad); SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad);
KFGIS.SpawnManager.DesiredSquadType = PrevDesiredSquadType;
if (SpawnVolume == None) if (SpawnVolume == None)
{ {
return INDEX_NONE; return INDEX_NONE;
} }
SpawnVolume.VolumeChosenCount++;
SpawnLocation = SpawnVolume.Location; SpawnLocation = SpawnVolume.Location;
if (SpawnLocation == PlayerStart) if (SpawnLocation == PlayerStart)
{ {
@ -677,6 +708,11 @@ private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, boo
Spawned++; Spawned++;
} }
if (Spawned > 0)
{
KFGIS.SpawnManager.LastAISpawnVolume = SpawnVolume;
}
if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave())
{ {
KFGIS.NumAIFinishedSpawning += Spawned; KFGIS.NumAIFinishedSpawning += Spawned;
@ -690,21 +726,55 @@ private function int SpawnZed(class<KFPawn_Monster> ZedClass, int PawnCount, boo
public function NotifyLogin(Controller C) public function NotifyLogin(Controller C)
{ {
local ZedSpawnerRepLink RepLink;
`ZS_Trace(`Location); `ZS_Trace(`Location);
RepLink = Spawn(class'ZedSpawnerRepLink', C); CreateRepLink(C);
RepLink.LogLevel = LogLevel;
RepLink.CustomZeds = CustomZeds;
RepLink.ServerSync();
} }
public function NotifyLogout(Controller C) public function NotifyLogout(Controller C)
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
DestroyRepLink(C);
}
public function CreateRepLink(Controller C)
{
local ZedSpawnerRepLink RepLink;
return; `ZS_Trace(`Location);
if (C == None) return;
RepLink = Spawn(class'ZedSpawnerRepLink', C);
RepLink.LogLevel = LogLevel;
RepLink.CustomZeds = CustomZeds;
RepLink.ZS = Self;
RepLinks.AddItem(RepLink);
RepLink.ServerSync();
}
public function bool DestroyRepLink(Controller C)
{
local int i;
`ZS_Trace(`Location);
if (C == None) return false;
for (i = RepLinks.Length - 1; i >= 0; --i)
{
if (RepLinks[i].Owner == C)
{
RepLinks[i].SafeDestroy();
RepLinks.Remove(i, 1);
return true;
}
}
return false;
} }
DefaultProperties DefaultProperties

View File

@ -1,8 +1,9 @@
class ZedSpawnerRepLink extends ReplicationInfo; class ZedSpawnerRepLink extends ReplicationInfo;
var public E_LogLevel LogLevel; var public ZedSpawner ZS;
var public Array<class<KFPawn_Monster> > CustomZeds; var public E_LogLevel LogLevel;
var private int Recieved; var public Array<class<KFPawn_Monster> > CustomZeds;
var private int Recieved;
replication replication
{ {
@ -10,7 +11,11 @@ replication
LogLevel; LogLevel;
} }
public simulated function bool SafeDestroy() { if (!bPendingDelete) return Destroy(); else return true; } public simulated function bool SafeDestroy()
{
`ZS_Debug(`Location @ "bPendingDelete:" @ bPendingDelete @ "bDeleteMe" @ bDeleteMe);
return (bPendingDelete || bDeleteMe || Destroy());
}
public reliable client function ClientSync(class<KFPawn_Monster> CustomZed) public reliable client function ClientSync(class<KFPawn_Monster> CustomZed)
{ {
@ -40,11 +45,16 @@ public reliable server function ServerSync()
{ {
`ZS_Trace(`Location); `ZS_Trace(`Location);
if (bPendingDelete || bDeleteMe) return;
if (CustomZeds.Length == Recieved || WorldInfo.NetMode == NM_StandAlone) if (CustomZeds.Length == Recieved || WorldInfo.NetMode == NM_StandAlone)
{ {
`ZS_Debug("Sync finished"); `ZS_Debug("Sync finished");
SyncFinished(); SyncFinished();
SafeDestroy(); if (!ZS.DestroyRepLink(Controller(Owner)))
{
SafeDestroy();
}
} }
else else
{ {