rott/kf_sources/ScrnBalanceSrv/Classes/ScrnGameLength.uc
2026-07-14 20:27:09 +07:00

852 lines
25 KiB
Ucode

Class ScrnGameLength extends Object
dependson(ScrnWaveInfo)
PerObjectConfig
Config(ScrnWaves);
var ScrnGameType Game;
var config string GameTitle;
var config string Author;
var config float BountyScale;
var config int StartingCashBonus;
var config array<string> Mutators;
var config array<string> Waves;
var config array<string> Zeds;
var config bool bLogStats;
struct SHL {
var byte Difficulty;
var int HL;
};
var config array<SHL> HardcoreLevel;
var class<KFMonster> FallbackZed;
var array<ScrnZedInfo> ZedInfos;
var array<string> ZedVotes;
struct SSpawnCandidate {
var class<KFMonster> ZedClass;
var float Chance;
};
struct SActiveZed {
var string Alias;
var array<SSpawnCandidate> Candidates;
var int WaveSpawns, TotalSpawns;
};
var array<SActiveZed> ActiveZeds;
// per-wave data
struct SSquadMember {
var int ActiveZedIndex; // item index in ActiveZeds array
var int Count;
};
struct SSquad {
var byte MinPlayers;
var byte MaxPlayers;
var byte ScaleByPlayers;
var array<SSquadMember> Members;
};
var array<SSquad> Squads;
var array<SSquad> SpecialSquads;
var array<int> PendingSquads;
var array<int> PendingSpecialSquads;
var ScrnWaveInfo Wave;
var transient int ZedsBeforeSpecial;
var transient bool bLoadedSpecial;
var transient float PlayerCountOverrideForHealth;
var float WaveEndTime;
var int WaveCounter;
function LoadGame(ScrnGameType MyGame)
{
local int i, j;
local ScrnZedInfo zi;
local class<KFMonster> zedc;
Game = MyGame;
for ( i = 0; i < Mutators.length; ++i ) {
if ( Mutators[i] != "" ) {
Log("Loading additional mutator: " $ Mutators[i], 'ScrnGameLength');
Game.AddMutator(Mutators[i], true);
}
}
ZedInfos.length = Zeds.length;
for ( i = 0; i < Zeds.length; ++i ) {
zi = new(none, Zeds[i]) class'ScrnZedInfo';
for ( j = 0; j < zi.Zeds.length; ++j ) {
if ( zi.Zeds[j].Vote != "" )
AddZedVote(zi.Zeds[j].Vote);
if ( zi.Zeds[j].bDisabled )
continue;
zedc = class<KFMonster>(DynamicLoadObject(zi.Zeds[j].ZedClass, class'Class'));
if ( zedc == none ) {
log("Unable to load zed class '" $ zi.Zeds[j].ZedClass $ "' for " $ zi.Zeds[j].Alias, class.name);
continue;
}
if ( zedc.outer.name != 'KFChar' ) {
Game.AddToPackageMap(string(zedc.outer.name));
if ( zi.Zeds[j].Package != "" )
Game.AddToPackageMap(zi.Zeds[j].Package);
}
AddActiveZed(zi.Zeds[j].Alias, zedc, zi.Zeds[j].Pct);
}
// store zed info for config save after voting
ZedInfos[i] = zi;
}
RecalculateSpawnChances();
for ( i = 0; i < ActiveZeds.length; ++i ) {
for ( j = 0; j < ActiveZeds[i].Candidates.length; ++j ) {
ActiveZeds[i].Candidates[j].ZedClass.static.PreCacheAssets(Game.Level);
}
}
if ( ZedVotes.length > 0 )
AddVoting();
if ( HardcoreLevel.length > 0 ) {
j = 0;
for ( i = 0; i < HardcoreLevel.length; ++i ) {
if ( Game.GameDifficulty >= HardcoreLevel[i].Difficulty && j < HardcoreLevel[i].HL )
j = HardcoreLevel[i].HL;
}
if ( j > 0 ) {
log("HL forced to " $ j, class.name);
Game.ScrnBalanceMut.GameRules.ForceHardcoreLevel(j);
}
}
// this makes sure the Wave is never none
if ( Waves.length == 0 ) {
warn("ScrnGameLength: NO WAVES DEFINED!");
Wave = new(none, "Wave1") class'ScrnWaveInfo';
}
else {
Wave = CreateWave(Waves[0]);
}
}
function AddVoting()
{
local ScrnVotingHandlerMut VH;
local ScrnZedVoting VO;
VH = class'ScrnVotingHandlerMut'.static.GetVotingHandler(Game);
if ( VH != none ) {
Game.AddMutator(string(class'ScrnVotingHandlerMut'), false);
VH = class'ScrnVotingHandlerMut'.static.GetVotingHandler(Game);
}
if ( VH == none ) {
log("Unable to spawn voting handler mutator", class.name);
return;
}
VO = ScrnZedVoting(VH.AddVotingOptions(class'ScrnBalanceSrv.ScrnZedVoting'));
if ( VO != none ) {
VO.GL = self;
}
}
// allows admins to control zed infos via MUTATE ZED <cmd>
function ZedCmd(PlayerController Sender, string cmd)
{
local array<string> args;
local int search_idx, cur_idx; // starts with 1
local int BoolValue;
local int i, j;
local bool bChanged;
local color c;
local float Pct;
local bool bSetPct;
BoolValue = -1;
c.B = 1;
Split(cmd, " ", args);
if ( args.length == 0 || args[0] == "" ) {
Sender.ClientMessage("MUTATE ZED LIST|(<alias> [<index> [ON|OFF] [PCT <val>]])");
return;
}
if ( args[0] ~= "LIST" ) {
PrintAliases(Sender);
return;
}
if ( args.length >= 2 ) {
search_idx = int(args[1]);
for ( i = 2; i < args.length - 1; ++i ) {
if ( args[i] ~= "PCT" ) {
bSetPct = true;
Pct = float(args[i+1]);
args.remove(i,2);
break;
}
}
if ( args.length >= 3 ) {
BoolValue = class'ScrnVotingOptions'.static.TryStrToBoolStatic(args[2]);
}
}
Sender.ClientMessage("INDEX / STATUS / SPAWN CHANCE / ZED CLASS");
Sender.ClientMessage("=========================================================");
for ( i = 0; i < ZedInfos.length; ++i ) {
bChanged = false;
for ( j = 0; j < ZedInfos[i].Zeds.length; ++j ) {
if ( ZedInfos[i].Zeds[j].Alias == args[0] ) {
++cur_idx;
if ( ZedInfos[i].Zeds[j].bDisabled ) {
c.R = 255;
c.G = 1;
}
else {
c.R = 1;
c.G = 255;
}
if ( cur_idx == search_idx ) {
if ( BoolValue != -1 ) {
ZedInfos[i].Zeds[j].bDisabled = !bool(BoolValue);
bChanged = true;
}
if ( bSetPct) {
ZedInfos[i].Zeds[j].Pct = Pct;
bChanged = true;
}
c.R = 255;
c.G = 255;
}
Sender.ClientMessage(class'ScrnBalance'.static.ColorStringC(
class'ScrnFunctions'.static.LPad(string(cur_idx), 3)
@ class'ScrnFunctions'.static.RPad(eval(ZedInfos[i].Zeds[j].bDisabled, "OFF", "ON"), 5)
@ class'ScrnFunctions'.static.LPad(eval(ZedInfos[i].Zeds[j].Pct == 0, "AUTO", string(ZedInfos[i].Zeds[j].Pct)), 7)
@ ZedInfos[i].Zeds[j].ZedClass
, c ));
}
}
if ( bChanged )
ZedInfos[i].SaveConfig();
}
if ( cur_idx == 0 ) {
Sender.ClientMessage("No zeds with alias '"$args[0]$"' found!");
}
}
function PrintAliases(PlayerController Sender)
{
local array<string> aliases;
local array<int> count;
local int i, j, k;
Sender.ClientMessage("Zed aliases (candidate count):");
for ( i = 0; i < ZedInfos.length; ++i ) {
for ( j = 0; j < ZedInfos[i].Zeds.length; ++j ) {
for ( k = 0; k < aliases.length; ++k ) {
if ( ZedInfos[i].Zeds[j].Alias == aliases[k] )
break;
}
if ( k == aliases.length ) {
aliases[k] = ZedInfos[i].Zeds[j].Alias;
count[k] = 1;
}
else {
++count[k];
}
}
}
for ( k = 0; k < aliases.length; ++k ) {
Sender.ClientMessage(aliases[k] $ " ("$count[k]$")");
}
}
function bool LoadWave(int WaveNum)
{
local int i;
local SSquad squad;
local Controller C;
local KFPlayerController KFPC;
ZedsBeforeSpecial = 0;
PendingSquads.length = 0;
PendingSpecialSquads.length = 0;
Squads.length = 0;
SpecialSquads.length = 0;
if ( bLogStats && Wave != none )
LogStats();
if ( WaveNum >= Waves.length ) {
warn("ScrnGameLength: Illegal wave number: " $ WaveNum);
if (Wave == none ) {
log("Using fallback wave info", class.name);
Wave = new(none, "Wave1") class'ScrnWaveInfo';
}
return false;
}
Wave = CreateWave(Waves[WaveNum]);
for ( i = 0; i < ActiveZeds.length; ++i ) {
ActiveZeds[i].WaveSpawns = 0;
}
for ( i = 0; i < Wave.Squads.length; ++i ) {
if ( ParseSquad(Wave.Squads[i], squad) ) {
Squads[Squads.length] = squad;
}
}
if ( Squads.length == 0 ) {
log("No squads in wave", class.name);
return false;
}
for ( i = 0; i < Wave.SpecialSquads.length; ++i ) {
if ( ParseSquad(Wave.SpecialSquads[i], squad) ) {
SpecialSquads[SpecialSquads.length] = squad;
}
}
if ( Wave.MaxZombiesOnce > 0 )
Game.MaxZombiesOnce = Wave.MaxZombiesOnce;
else
Game.MaxZombiesOnce = Game.StandardMaxZombiesOnce;
Game.ScrnGRI.bTraderArrow = Wave.bTraderArrow || Wave.bOpenTrader;
Game.ScrnGRI.WaveEndRule = Wave.EndRule;
Game.bZedPickupDosh = Wave.EndRule == RULE_GrabDoshZed;
Game.bZedDropDosh = Wave.EndRule == RULE_GrabDoshZed || Wave.EndRule == RULE_GrabDosh;
SetWaveInfo();
if ( Wave.TraderMessage != "" ) {
for ( C = Game.Level.ControllerList; C != none; C = C.NextController ) {
KFPC = KFPlayerController(C);
if ( C.PlayerReplicationInfo != none && KFPC != none ) {
// this is needed to show a trader portrait
KFPC.ClientLocationalVoiceMessage(C.PlayerReplicationInfo, none, 'TRADER', 0);
KFPC.TeamMessage(C.PlayerReplicationInfo, Wave.TraderMessage, 'TRADER');
}
}
}
DoorControl(Wave.DoorControl);
return true;
}
function ScrnWaveInfo CreateWave(string WaveDefinition)
{
local array<string> Parts;
local string WaveName;
// get rid of spaces
WaveName = Repl(WaveDefinition, " ", "", true);
if ( InStr(WaveName, "|") != -1 ) {
Split(WaveName, "|", Parts);
WaveName = Parts[rand(Parts.length)];
}
log("Creating wave " $ WaveName, class.name);
return new(none, WaveName) class'ScrnWaveInfo';
}
// Called by Game.SetupWave() when Battle Time starts (Trader/Cooldown Time ends)
function RunWave()
{
local string s;
SetWaveInfo();
DoorControl(Wave.DoorControl2);
Game.ScrnGRI.bTraderArrow = Wave.bTraderArrow;
if ( Wave.bRandomSpawnLoc ) {
Game.ZedSpawnLoc = ZSLOC_RANDOM;
}
if ( Game.ScrnGRI.WaveTitle != "" || Game.ScrnGRI.WaveMessage != "" ) {
if ( Game.ScrnGRI.WaveTitle != "" ) {
s = Game.ScrnBalanceMut.ColorString(Game.ScrnGRI.WaveTitle, 255, 204, 1);
}
if ( Game.ScrnGRI.WaveMessage != "" ) {
if ( s != "" )
s $= ": ";
s $= Game.ScrnGRI.WaveMessage;
}
Game.Broadcast(Game, s);
}
}
function SetWaveInfo()
{
if (Wave.PerPlayerMult == 0) {
WaveCounter = Wave.Counter;
switch ( Wave.EndRule ) {
case RULE_KillEmAll:
case RULE_SpawnEmAll:
WaveCounter = Game.ScaleMonsterCount(WaveCounter); // apply default scaling
}
}
else {
WaveCounter = Wave.Counter * ( 1.0 + Wave.PerPlayerMult
* (max(Game.AlivePlayerCount, Game.ScrnGRI.FakedPlayers) - 1) );
}
if (Wave.MaxCounter > 0 && WaveCounter > Wave.MaxCounter)
WaveCounter = Wave.MaxCounter;
WaveEndTime = Game.Level.TimeSeconds + WaveCounter + 0.1;
switch (Wave.EndRule) {
case RULE_GrabAmmo:
if ( Game.DesiredAmmoBoxCount < WaveCounter ) {
Game.ScrnBalanceMut.AdjustAmmoBoxCount(min(WaveCounter, Game.AmmoPickups.length * 0.8));
}
break;
}
Game.ScrnGRI.WaveHeader = Wave.Header;
Game.ScrnGRI.WaveTitle = Wave.Title;
Game.ScrnGRI.WaveMessage = Wave.Message;
ReplaceText(Game.ScrnGRI.WaveMessage, "%c", string(WaveCounter));
Game.ScrnGRI.WaveCounter = 0;
WaveTimer();
}
function WaveTimer()
{
switch (Wave.EndRule) {
case RULE_Timeout:
Game.WaveEndTime = WaveEndTime;
if ( Game.bWaveBossInProgress || Game.bWaveInProgress) {
Game.ScrnGRI.TimeToNextWave = WaveEndTime - Game.Level.TimeSeconds;
}
break;
case RULE_EarnDosh:
case RULE_GrabDosh:
case RULE_GrabDoshZed:
Game.ScrnGRI.WaveCounter = max(0, WaveCounter - max(Game.Teams[0].Score, Game.Teams[1].Score));
break;
case RULE_GrabAmmo:
Game.ScrnGRI.WaveCounter = max(0, WaveCounter - Game.ScrnBalanceMut.GameRules.WaveAmmoPickups);
break;
}
}
// Called by ScrnGameType at the end of the wave - just before loading the next wave
function WaveEnded()
{
local ScrnPlayerInfo SPI;
if ( !(Wave.XP_Bonus ~= 0 && Wave.XP_BonusAlive ~= 0) ) {
for ( SPI=Game.ScrnBalanceMut.GameRules.PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
if ( SPI.PlayerOwner == none || SPI.PlayerOwner.PlayerReplicationInfo == none )
continue;
if ( SPI.bDied || Wave.XP_BonusAlive ~= 0 ) {
SPI.BonusStats(SPI.GameStartStats, Wave.XP_Bonus);
}
else {
SPI.BonusStats(SPI.GameStartStats, Wave.XP_BonusAlive);
}
}
}
}
function DoorControl(ScrnWaveInfo.EDoorControl dc)
{
switch (dc) {
case DOOR_Default:
if (Game.ScrnBalanceMut.bRespawnDoors || Game.ScrnBalanceMut.bTSCGame) {
Game.ScrnBalanceMut.RespawnDoors();
}
break;
case DOOR_Respawn:
Game.ScrnBalanceMut.RespawnDoors();
break;
case DOOR_Blow:
Game.ScrnBalanceMut.BlowDoors();
break;
case DOOR_Unweld:
Game.ScrnBalanceMut.UnweldDoors();
break;
case DOOR_UnweldRespawn:
Game.ScrnBalanceMut.WeldDoors(0);
break;
case DOOR_Weld1p:
Game.ScrnBalanceMut.WeldDoors(0.01);
break;
case DOOR_WeldHalf:
Game.ScrnBalanceMut.WeldDoors(0.5);
break;
case DOOR_WeldFull:
Game.ScrnBalanceMut.WeldDoors(1.0);
break;
case DOOR_WeldRandom:
Game.ScrnBalanceMut.WeldDoors(-1.0);
break;
case DOOR_Randomize:
Game.ScrnBalanceMut.RandomizeDoors();
break;
}
}
function LogStats()
{
local int i, total;
for ( i = 0; i < ActiveZeds.length; ++i )
total += ActiveZeds[i].WaveSpawns;
if ( total == 0 )
return;
log("Spawn Stats for " $ string(Wave.name) $ ":", class.name);
for ( i = 0; i < ActiveZeds.length; ++i ) {
if ( ActiveZeds[i].WaveSpawns > 0 )
log(ActiveZeds[i].Alias $ ": " $ ActiveZeds[i].WaveSpawns $ " / "
$ string(ActiveZeds[i].WaveSpawns * 100.0 / total) $ "%");
}
}
function bool CheckWaveEnd()
{
local int i;
switch ( Wave.EndRule ) {
case RULE_KillEmAll:
return Game.TotalMaxMonsters <= 0 && Game.NumMonsters <= 0;
case RULE_SpawnEmAll:
return Game.TotalMaxMonsters <= 0;
case RULE_KillBoss:
if ( Game.bBossSpawned && Game.Bosses.length > 0 ) {
for ( i = 0; i < Game.Bosses.length; ++i ) {
if ( Game.Bosses[i] != none && Game.Bosses[i].Health > 0 )
return false; // boss is alive
}
return true; // all bosses are dead
}
break;
case RULE_Timeout:
return Game.Level.TimeSeconds >= WaveEndTime;
case RULE_EarnDosh:
case RULE_GrabDosh:
case RULE_GrabDoshZed:
return max(Game.Teams[0].Score, Game.Teams[1].Score) >= WaveCounter;
case RULE_GrabAmmo:
return Game.ScrnBalanceMut.GameRules.WaveAmmoPickups >= WaveCounter;
}
// fallback scenario
return Game.Level.TimeSeconds >= Game.WaveEndTime
|| (Game.TotalMaxMonsters <= 0 && Game.NumMonsters <= 0);
}
function int GetWaveZedCount()
{
switch ( Wave.EndRule ) {
case RULE_KillBoss:
return 1;
case RULE_Timeout:
case RULE_EarnDosh:
case RULE_GrabDosh:
case RULE_GrabDoshZed:
case RULE_GrabAmmo:
return 999;
}
return WaveCounter;
}
function float GetWaveEndTime()
{
switch ( Wave.EndRule ) {
case RULE_Timeout:
return WaveEndTime;
}
return Game.Level.TimeSeconds + 60;
}
function AdjustNextSpawnTime(out float NextSpawnTime)
{
NextSpawnTime /= Wave.SpawnRateMod;
if ( bLoadedSpecial )
NextSpawnTime *= 2.0; // give players slight break after spawning a special squad
}
function LoadNextSpawnSquad(out array < class<KFMonster> > NextSpawnSquad)
{
if ( ZedsBeforeSpecial <= 0 && SpecialSquads.length > 0 ) {
LoadNextSpawnSquadInternal(NextSpawnSquad, SpecialSquads, PendingSpecialSquads, Wave.bRandomSquads);
ZedsBeforeSpecial = Wave.ZedsPerSpecialSquad * (0.85 + 0.3*frand());
bLoadedSpecial = true;
}
else {
LoadNextSpawnSquadInternal(NextSpawnSquad, Squads, PendingSquads, Wave.bRandomSpecialSquads);
ZedsBeforeSpecial -= NextSpawnSquad.length;
bLoadedSpecial = false;
}
}
protected function LoadNextSpawnSquadInternal(out array < class<KFMonster> > NextSpawnSquad,
out array<SSquad> AllSquads, out array<int> Pending, bool bRandom, optional int Recursions)
{
local int i, j, c, r;
local int PlayerCount;
PlayerCountOverrideForHealth = 0;
PlayerCount = Game.GetPlayerCountForMonsterHealth();
if ( AllSquads.length == 0 ) {
NextSpawnSquad.length = 1;
NextSpawnSquad[0] = FallbackZed;
return;
}
NextSpawnSquad.length = 0;
if ( Pending.length == 0 ) {
Pending.length = AllSquads.length;
for ( i = 0; i < Pending.length; ++i ) {
Pending[i] = i;
}
}
if (bRandom)
i = rand(Pending.length);
else
i = 0;
r = Pending[i];
Pending.remove(i, 1);
if ( Game.WavePlayerCount >= AllSquads[r].MinPlayers
&& (Game.WavePlayerCount <= AllSquads[r].MaxPlayers || AllSquads[r].MaxPlayers == 0) )
{
for ( i = 0; i < AllSquads[r].Members.length; ++i ) {
for ( j = 0; j < AllSquads[r].Members[i].Count; ++j ) {
if (AllSquads[r].ScaleByPlayers == 0) {
c = 1;
}
else {
c = 1 + int( (PlayerCount - 0.5) / AllSquads[r].ScaleByPlayers );
PlayerCountOverrideForHealth = float(PlayerCount) / c;
}
while (c-- > 0) {
NextSpawnSquad[NextSpawnSquad.length] = ActivateZed(AllSquads[r].Members[i].ActiveZedIndex);
}
}
}
}
else if ( Recursions < 10 ){
// squad didn't passed player count restriction - pick another squad
LoadNextSpawnSquadInternal(NextSpawnSquad, AllSquads, Pending, bRandom, Recursions + 1);
}
}
function class<KFMonster> ActivateZed(int idx)
{
local float r;
local int i;
ActiveZeds[idx].WaveSpawns++;
ActiveZeds[idx].TotalSpawns++;
if ( ActiveZeds[idx].Candidates.length == 1 )
return ActiveZeds[idx].Candidates[0].ZedClass;
r = frand();
for ( i = 0; r > ActiveZeds[idx].Candidates[i].Chance && i < ActiveZeds[idx].Candidates.length - 1; ++i ) {
r -= ActiveZeds[idx].Candidates[i].Chance;
}
return ActiveZeds[idx].Candidates[i].ZedClass;
}
function bool ParseSquad(string SquadDef, out SSquad Squad)
{
local int i, j, idx, count;
local array<string> parts;
local string s, count_str, alias, fallback;
Squad.Members.length = 0;
Squad.MinPlayers = 0;
Squad.MaxPlayers = 0;
Squad.ScaleByPlayers = 0;
// format example: 0-6: 2*CL/GF + BR/TH/HU + BL
// another example: ~6: BOSS
// get rid of spaces
s = Repl(SquadDef, " ", "", true);
Divide(s, ":", count_str, s);
if ( count_str != "" ) {
if ( Left(count_str, 1) == "~" ) {
Squad.ScaleByPlayers = int(Mid(count_str, 1));
}
else {
Divide(count_str, "-", count_str, fallback);
Squad.MinPlayers = int(count_str);
Squad.MaxPlayers = int(fallback);
}
}
Split(s, "+", parts);
for ( i = 0; i < parts.length; ++i ) {
s = parts[i];
do {
if ( !Divide(s, "/", alias, fallback) ) {
alias = s;
fallback = "";
}
s = alias;
if ( !Divide(s, "*", count_str, alias) ) {
count = 1;
alias = s;
}
else {
count = int(count_str);
}
idx = FindActiveZed(alias);
s = fallback;
} until ( idx != -1 || fallback == "" );
if ( idx == -1 ) {
log("No zeds available to fit " $ parts[i] $ " in " $ SquadDef, class.name);
Squad.Members.length = 0;
return false;
}
else {
j = Squad.Members.length;
Squad.Members.insert(j, 1);
Squad.Members[j].ActiveZedIndex = idx;
Squad.Members[j].Count = count;
}
}
return true;
}
function int FindActiveZed(string alias)
{
local int i;
for ( i = 0; i < ActiveZeds.length; ++i ) {
if ( ActiveZeds[i].Alias == alias )
return i;
}
log("Zed with alias '"$alias$"' not found", class.name);
return -1;
}
function AddZedVote(string VoteString)
{
local int i;
for ( i = 0; i < ZedVotes.length; ++i ) {
if ( VoteString == ZedVotes[i] )
return; // already added
else if ( VoteString < ZedVotes[i])
break; // sort alphabetically
}
ZedVotes.insert(i, 1);
ZedVotes[i] = VoteString;
}
function AddActiveZed(string Alias, class<KFMonster> ZedClass, optional float Chance)
{
local int i, j;
for ( i = 0; i < ActiveZeds.length; ++i ) {
if ( ActiveZeds[i].Alias == Alias )
break;
}
if ( i == ActiveZeds.length ) {
ActiveZeds.insert(i, 1);
ActiveZeds[i].Alias = Alias;
}
j = ActiveZeds[i].Candidates.length;
ActiveZeds[i].Candidates.insert(j, 1);
ActiveZeds[i].Candidates[j].ZedClass = ZedClass;
ActiveZeds[i].Candidates[j].Chance = Chance;
}
// Recalculate spawn chances, removes records that have zero chance to spawn.
function RecalculateSpawnChances()
{
local int i, j;
local float chance;
local int AutoChanceCount;
for ( i = 0; i < ActiveZeds.length; ++i ) {
if ( ActiveZeds[i].Candidates.length == 0 ) {
log("Alias " $ ActiveZeds[i].Alias $ " has no zeds", class.name);
ActiveZeds[i].Candidates.remove(i--, 1);
continue;
}
chance = 0;
AutoChanceCount = 0;
for ( j = 0; j < ActiveZeds[i].Candidates.length; ++j ) {
if ( ActiveZeds[i].Candidates[j].Chance == 0.0 )
++AutoChanceCount;
else
chance += ActiveZeds[i].Candidates[j].Chance;
}
if ( AutoChanceCount > 0 ) {
// split remaining change equally on remaining records
chance = (1.0 - chance) / AutoChanceCount;
for ( j = 0; j < ActiveZeds[i].Candidates.length; ++j ) {
if ( ActiveZeds[i].Candidates[j].Chance == 0.0 ) {
if ( chance > 0.0 )
ActiveZeds[i].Candidates[j].Chance = chance;
else
ActiveZeds[i].Candidates.remove(j--, 1);
}
}
}
else if ( abs(chance) - 1.0 > 0.01 ) {
// modify all records to have total chance = 1.0
chance = 1.0 / chance;
for ( j = 0; j < ActiveZeds[i].Candidates.length; ++j ) {
ActiveZeds[i].Candidates[j].Chance *= chance;
}
}
}
}
function bool ShouldBoostAmmo()
{
return Wave != none && Wave.EndRule == RULE_GrabAmmo;
}
function float GetBountyScale()
{
if (Wave.BountyScale > 0)
return Wave.BountyScale;
return BountyScale;
}
defaultproperties
{
BountyScale=1.000000
Waves(0)="Wave1"
Zeds(0)="NormalZeds"
bLogStats=True
FallbackZed=Class'KFChar.ZombieClot_STANDARD'
}