1543 lines
58 KiB
Ucode
1543 lines
58 KiB
Ucode
class ScrnGameRules extends GameRules
|
|
Config(ScrnBalanceSrv);
|
|
|
|
var ScrnBalance Mut;
|
|
var KFGameType KF;
|
|
|
|
var bool bShowDamages;
|
|
|
|
var int HardcoreLevel;
|
|
var float HardcoreLevelFloat;
|
|
var bool bForceHardcoreLevel;
|
|
var localized string msgHardcore;
|
|
var bool bUseAchievements;
|
|
|
|
var bool bScrnDoom; // is serrver running ScrN version of Doom3 mutator?
|
|
var localized string msgDoom3Monster, msgDoomPercent, msgDoom3Boss;
|
|
var transient int WaveTotalKills, WaveDoom3Kills, GameDoom3Kills;
|
|
var transient int WaveAmmoPickups;
|
|
var deprecated transient int DoomHardcorePointsGained; // left for backwards compatibility
|
|
|
|
var transient bool bHasCustomZeds;
|
|
|
|
var class<KFMonster> BossClass;
|
|
var KFMonster Boss;
|
|
var transient bool bSuperPat, bDoomPat;
|
|
var bool bFinalWave; // is this a final wave
|
|
|
|
|
|
// Damage flags
|
|
const DF_STUNNED = 0x01; // stun shot
|
|
const DF_RAGED = 0x02; // rage shot
|
|
const DF_STUPID = 0x04; // damage shouldn't be done to this zed
|
|
const DF_DECAP = 0x08; // decapitaion shot
|
|
const DF_HEADSHOT = 0x10; // 100% sure that this damage was made by headshot
|
|
|
|
struct MonsterInfo {
|
|
var KFMonster Monster;
|
|
var float SpawnTime;
|
|
var float FirstHitTime; // time when Monster took first damage
|
|
var int HitCount; // how many hits monster received
|
|
|
|
var byte PlayerKillCounter; // how many players this monster killed
|
|
var int DamageCounter; // how much damage monster delivered to players
|
|
var float PlayerKillTime; // last time when monster killed a player
|
|
|
|
var ScrnPlayerInfo KillAss1, KillAss2; // who helped player to kill this monster
|
|
var class<KFWeaponDamageType> DamType1, DamType2;
|
|
var float DamTime1, DamTime2;
|
|
var int DamageFlags1, DamageFlags2;
|
|
var bool TW_Ach_Failed; //if true, no TeamWork achievements can be earned on this zed
|
|
|
|
var bool bHeadshot; // was last damage from headshot?
|
|
var int RowHeadshots; // headshots in a row done to this monster. RowHeadshots doesn't reset after decapitation
|
|
var int Headshots; // number of headshots made to this monster
|
|
var int BodyShots; // number of bodyshots made to this monster by weapons, which can score headshots
|
|
var int OtherHits; // number of hits made to this monster by weapons, which can NOT score headshots
|
|
|
|
// variables below are set after NetDamage() call
|
|
var float LastHitTime; // time when Monster took last damage
|
|
var int HeadHealth; // track head health to check headshots
|
|
var bool bWasDecapitated; // was the monster decapitated before last damage? If bWasDecapitated=true then bHeadshot=false
|
|
var bool bWasBackstabbed; // previous hit was a melee backstab
|
|
};
|
|
var array<MonsterInfo> MonsterInfos;
|
|
var private transient KFMonster LastSeachedMonster; //used to optimize GetMonsterIndex()
|
|
var private transient int LastFoundMonsterIndex;
|
|
|
|
|
|
struct MapAlias {
|
|
var string FileName;
|
|
var string AchName;
|
|
};
|
|
var config array<MapAlias> MapAliases;
|
|
|
|
var array< class<KFMonster> > CheckedMonsterClasses;
|
|
|
|
var array<ScrnAchHandlerBase> AchHandlers;
|
|
var transient ScrnPlayerInfo PlayerInfo;
|
|
var private transient ScrnPlayerInfo BackupPlayerInfo;
|
|
var protected int WavePlayerCount, WaveDeadPlayers;
|
|
|
|
var class<ScrnAchievements> AchClass;
|
|
var class<ScrnMapAchievements> MapAchClass;
|
|
|
|
var localized string strWeaponLocked, strWeaponLockedOwn, strPerkedWeaponsOnly;
|
|
var localized string strWaveAccuracy;
|
|
|
|
// moved here from ScrnWeaponPack for extended compatibility with other muts
|
|
var array< class<KFWeaponDamageType> > SovietDamageTypes;
|
|
|
|
struct SHardcoreMonster {
|
|
var config string MonsterClass;
|
|
var config float HL;
|
|
var transient bool bUsed;
|
|
};
|
|
var config float HL_Normal, HLMult_Normal, HL_Hard, HLMult_Hard, HL_Suicidal, HLMult_Suicidal, HL_HoE, HLMult_HoE;
|
|
var config float HL_Hardcore;
|
|
var config array<SHardcoreMonster> HardcoreZeds, HardcoreBosses;
|
|
var transient float ZedHLMult;
|
|
var config bool bBroadcastHL;
|
|
|
|
struct SHardcoreGame
|
|
{
|
|
var config string GameClass;
|
|
var config float HL;
|
|
};
|
|
var config array<SHardcoreGame> HardcoreGames;
|
|
|
|
|
|
function PostBeginPlay()
|
|
{
|
|
if( Level.Game.GameRulesModifiers==None )
|
|
Level.Game.GameRulesModifiers = Self;
|
|
else
|
|
Level.Game.GameRulesModifiers.AddGameRules(Self);
|
|
|
|
KF = KFGameType(Level.Game);
|
|
Mut = class'ScrnBalance'.static.Myself(Level);
|
|
|
|
MonsterInfos.Length = Mut.KF.MaxZombiesOnce; //reserve a space that will be required anyway
|
|
InitHardcoreLevel();
|
|
}
|
|
|
|
event Destroyed()
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
local int i;
|
|
|
|
log("ScrnGameRules destroyed", 'ScrnBalance');
|
|
// clear all ScrnPlayerInfo objects
|
|
while ( PlayerInfo != none ) {
|
|
SPI = PlayerInfo;
|
|
PlayerInfo = SPI.NextPlayerInfo;
|
|
SPI.Destroy();
|
|
}
|
|
while ( BackupPlayerInfo != none ) {
|
|
SPI = BackupPlayerInfo;
|
|
BackupPlayerInfo = SPI.NextPlayerInfo;
|
|
SPI.Destroy();
|
|
}
|
|
|
|
// destroy all ach handlers
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
if ( AchHandlers[i] != none )
|
|
AchHandlers[i].Destroy();
|
|
}
|
|
AchHandlers.length = 0;
|
|
|
|
super.Destroyed();
|
|
|
|
log("ScrnGameRules destroyed", 'ScrnBalance');
|
|
}
|
|
|
|
|
|
function AddGameRules(GameRules GR)
|
|
{
|
|
if ( GR!=Self ) //prevent adding same rules more than once
|
|
Super.AddGameRules(GR);
|
|
}
|
|
|
|
|
|
// more players = faster zed spawn
|
|
function AdjustZedSpawnRate()
|
|
{
|
|
local int PlayerCount;
|
|
|
|
if ( Mut.bStoryMode )
|
|
return;
|
|
|
|
Mut.KF.KFLRules.WaveSpawnPeriod = Mut.OriginalWaveSpawnPeriod;
|
|
|
|
// TSC adjusts spawn period itself
|
|
if ( !Mut.bTSCGame ) {
|
|
PlayerCount = AlivePlayerCount();
|
|
if ( PlayerCount > 6 )
|
|
Mut.KF.KFLRules.WaveSpawnPeriod /= 1.0 + Mut.Post6ZedSpawnInc * (PlayerCount - 6);
|
|
}
|
|
}
|
|
|
|
function DestroyBuzzsawBlade()
|
|
{
|
|
local ScrnCrossbuzzsawBlade Blade;
|
|
local array<ScrnCrossbuzzsawBlade> Blades;
|
|
local int i;
|
|
|
|
foreach DynamicActors(class'ScrnCrossbuzzsawBlade', Blade) {
|
|
Blades[i] = Blade;
|
|
}
|
|
|
|
for ( i = 0; i < Blades.length; ++i )
|
|
Blades[i].ReplicatedDestroy();
|
|
}
|
|
|
|
function WaveStarted()
|
|
{
|
|
local int i;
|
|
local Controller P;
|
|
local PlayerController PC;
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
WaveDoom3Kills = 0;
|
|
WaveTotalKills = 0;
|
|
WavePlayerCount = 0;
|
|
WaveDeadPlayers = 0;
|
|
WaveAmmoPickups = 0;
|
|
bFinalWave = Mut.KF.WaveNum == Mut.KF.FinalWave;
|
|
|
|
ClearNonePlayerInfos();
|
|
for ( P = Level.ControllerList; P != none; P = P.nextController ) {
|
|
PC = PlayerController(P);
|
|
if ( PC != none && PC.Pawn != none && PC.Pawn.Health > 0 ) {
|
|
++WavePlayerCount;
|
|
if ( ScrnPlayerController(PC) != none )
|
|
ScrnPlayerController(PC).ResetWaveStats();
|
|
SPI = CreatePlayerInfo(PC, false);
|
|
// in case when stats were created before ClientReplLink created
|
|
if ( !SPI.GameStartStats.bSet )
|
|
SPI.BackupStats(SPI.GameStartStats);
|
|
SPI.WaveStarted(Mut.KF.WaveNum);
|
|
SPI.ProgressAchievement('Welcome', 1);
|
|
}
|
|
}
|
|
|
|
AdjustZedSpawnRate();
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
AchHandlers[i].WaveStarted(Mut.KF.WaveNum);
|
|
}
|
|
|
|
if ( Mut.bStoryMode )
|
|
log("Wave "$(Mut.KF.WaveNum+1)$" started", 'ScrnBalance');
|
|
else if (bFinalWave)
|
|
log("Final wave started", 'ScrnBalance');
|
|
else
|
|
log("Wave "$(Mut.KF.WaveNum+1)$"/"$(Mut.KF.FinalWave)$" started", 'ScrnBalance');
|
|
|
|
DestroyBuzzsawBlade(); // prevent cheating
|
|
}
|
|
|
|
function PlayerLeaving(ScrnPlayerController PC)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
if ( Level.Game.bGameEnded )
|
|
return; // game over
|
|
|
|
SPI = GetPlayerInfo(PC);
|
|
if ( SPI != none ) {
|
|
SPI.BackupPRI();
|
|
SPI.PlayerOwner = none;
|
|
}
|
|
}
|
|
|
|
// Ensure that PC has valid SteamID before this function call!
|
|
function PlayerEntering(ScrnPlayerController PC)
|
|
{
|
|
CreatePlayerInfo(PC, true);
|
|
}
|
|
|
|
|
|
function WaveEnded()
|
|
{
|
|
local int i;
|
|
local string s;
|
|
local ScrnPlayerInfo SPI;
|
|
local byte WaveNum;
|
|
local Controller P;
|
|
local PlayerController PC;
|
|
local float Accuracy;
|
|
|
|
// KF.WaveNum already set to a next wave, when WaveEnded() has been called
|
|
// E.g. KF.WaveNum == 1 means that first wave has been ended (wave with index 0)
|
|
WaveNum = KF.WaveNum-1;
|
|
|
|
for ( SPI=PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
|
|
SPI.WaveEnded(WaveNum);
|
|
SPI.BackupPRI(); // just in case
|
|
|
|
// broadcast players with high accuracy
|
|
if ( bFinalWave || SPI.DecapsPerWave >= 30 ) {
|
|
Accuracy = SPI.GetAccuracyWave();
|
|
if ( Accuracy > 0.75 ) {
|
|
s = strWaveAccuracy;
|
|
ReplaceText(s, "%p", Mut.ColoredPlayerName(SPI.PlayerOwner.PlayerReplicationInfo));
|
|
ReplaceText(s, "%a", Mut.GetColoredPercent(Accuracy));
|
|
mut.BroadcastMessage(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
AchHandlers[i].WaveEnded(WaveNum);
|
|
}
|
|
|
|
// create player infos for the newcomers
|
|
for ( P = Level.ControllerList; P != none; P = P.nextController ) {
|
|
PC = PlayerController(P);
|
|
if ( PC != none ) {
|
|
if ( P.bIsPlayer && P.Pawn != none && P.Pawn.Health > 0 )
|
|
CreatePlayerInfo(PC);
|
|
|
|
// call this in story mode to enable perk selection in ServerPerks
|
|
if ( Mut.bStoryMode ) {
|
|
if ( KFSteamStatsAndAchievements(PC.SteamStatsAndAchievements) != none )
|
|
KFSteamStatsAndAchievements(PC.SteamStatsAndAchievements).WaveEnded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function bool IsMapBlackListed(string MapName)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//returns true if any custom or super specimens are found
|
|
function bool HasCustomZeds()
|
|
{
|
|
return bHasCustomZeds;
|
|
}
|
|
|
|
function bool CheckMapAlias(out String MapName)
|
|
{
|
|
local int i;
|
|
|
|
for ( i = 0; i < MapAliases.length; ++i ) {
|
|
if ( MapName ~= MapAliases[i].FileName ) {
|
|
MapName = MapAliases[i].AchName;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool CheckEndGame(PlayerReplicationInfo Winner, string Reason)
|
|
{
|
|
local bool bWin;
|
|
local string MapName;
|
|
local int i;
|
|
|
|
if ( Level.Game.bGameEnded ) {
|
|
log("Calling CheckEndGame() for already ended game!", 'ScrnBalance');
|
|
return true;
|
|
}
|
|
else if ( Level.Game.bWaitingToStartMatch ) {
|
|
return false;
|
|
}
|
|
|
|
if ( NextGameRules != None && !NextGameRules.CheckEndGame(Winner,Reason) )
|
|
return false;
|
|
|
|
// KFStoryGameInfo first call GameRules.CheckEndGame() and only then sets EndGameType
|
|
if ( Mut.bStoryMode )
|
|
bWin = Reason ~= "WinAction";
|
|
else {
|
|
bWin = KFGameReplicationInfo(Level.GRI)!=None && KFGameReplicationInfo(Level.GRI).EndGameType==2;
|
|
}
|
|
|
|
if ( bWin ) {
|
|
mut.BroadcastMessage("Game WON in " $ Mut.FormatTime(Level.Game.GameReplicationInfo.ElapsedTime)
|
|
$". HL="$HardcoreLevel, true);
|
|
// Map achievements
|
|
MapName = Mut.KF.GetCurrentMapName(Level);
|
|
CheckMapAlias(MapName);
|
|
if ( !IsMapBlackListed(MapName) ) {
|
|
Mut.bNeedToSaveStats = false;
|
|
GiveMapAchievements(MapName);
|
|
for ( i=0; i<AchHandlers.length; ++i )
|
|
AchHandlers[i].GameWon(MapName);
|
|
// no need to save stats at this moment, because Level.Game.bGameEnded=False yet,
|
|
// i.e. ServerPerks hasn't done the final save yet
|
|
//Mut.SaveStats();
|
|
}
|
|
Mut.bNeedToSaveStats = false;
|
|
Mut.bSaveStatsOnAchievementEarned = false;
|
|
}
|
|
else {
|
|
if ( Mut.ScrnGT != none ) {
|
|
for ( i = 0; i < Mut.ScrnGT.Bosses.Length; ++i ) {
|
|
if ( Mut.ScrnGT.Bosses[i] != none && Mut.ScrnGT.Bosses[i].Health > 0 ) {
|
|
Boss = Mut.ScrnGT.Bosses[i];
|
|
mut.BroadcastMessage(Boss.MenuName $ "'s HP = " $ Boss.Health $" / " $ int(Boss.HealthMax)
|
|
$ " ("$100.0*Boss.Health/Boss.HealthMax$"%)");
|
|
}
|
|
}
|
|
}
|
|
else if ( Boss != none && Boss.Health > 0 )
|
|
mut.BroadcastMessage(Boss.MenuName $ "'s HP = " $ Boss.Health $" / " $ int(Boss.HealthMax)
|
|
$ " ("$100.0*Boss.Health/Boss.HealthMax$"%)");
|
|
|
|
mut.BroadcastMessage("Game LOST in " $ Mut.FormatTime(Level.Game.GameReplicationInfo.ElapsedTime)
|
|
$ " @ wave "$(Mut.KF.WaveNum+1)
|
|
$", HL="$HardcoreLevel, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function GiveMapAchievements(optional String MapName)
|
|
{
|
|
local bool bCustomMap, bGiveHardAch, bGiveSuiAch, bGiveHoeAch, bNewAch;
|
|
local ScrnPlayerInfo SPI;
|
|
local ClientPerkRepLink PerkLink;
|
|
local TeamInfo WinnerTeam;
|
|
local float BonusMult;
|
|
local bool bGiveBonus;
|
|
local int i;
|
|
local int MapResult;
|
|
|
|
WinnerTeam = TeamInfo(Level.Game.GameReplicationInfo.Winner);
|
|
if ( Mut.bStoryMode ) {
|
|
bGiveHardAch = Level.Game.GameDifficulty >= 4;
|
|
bGiveSuiAch = Level.Game.GameDifficulty >= 5;
|
|
bGiveHoeAch = Level.Game.GameDifficulty >= 7;
|
|
}
|
|
else {
|
|
bGiveHardAch = HardcoreLevel >= 5 && HasCustomZeds();
|
|
bGiveSuiAch = HardcoreLevel >= 10 && ( Mut.KF.IsA('TurboGame') || Mut.KF.KFGameLength == 9
|
|
|| (Mut.KF.KFGameLength >= 72 && Mut.KF.KFGameLength <= 99) );
|
|
bGiveHoeAch = HardcoreLevel >= 15 && ( GameDoom3Kills > 0 ) || Mut.KF.IsA('FtgGame');
|
|
}
|
|
|
|
// end game bonus
|
|
BonusMult = Mut.EndGameStatBonus;
|
|
if ( Mut.bStatBonusUsesHL )
|
|
BonusMult *= fmax(1.0, HardcoreLevel - Mut.StatBonusMinHL);
|
|
i = Mut.MapInfo.FindMapInfo();
|
|
if ( i != -1 )
|
|
BonusMult *= 1.0 + Mut.MapInfo.MapInfo[i].Difficulty;
|
|
bGiveBonus = BonusMult >= 0.1;
|
|
if ( bGiveBonus )
|
|
log("Giving bonus xp to winners (x"$BonusMult$")", 'ScrnBalance');
|
|
|
|
for ( SPI=PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
|
|
if ( SPI.PlayerOwner == none || SPI.PlayerOwner.PlayerReplicationInfo == none )
|
|
continue;
|
|
|
|
PerkLink = SPI.GetRep();
|
|
if ( PerkLink == none )
|
|
continue;
|
|
|
|
if ( WinnerTeam != none && SPI.PlayerOwner.PlayerReplicationInfo.Team != WinnerTeam )
|
|
continue; // no candies for loosers
|
|
|
|
// additional achievements that are granted only when surviving the game
|
|
if ( ScrnPlayerController(SPI.PlayerOwner) != none && !ScrnPlayerController(SPI.PlayerOwner).bChangedPerkDuringGame )
|
|
SPI.ProgressAchievement('PerkFavorite', 1);
|
|
|
|
//unlock "Normal" achievement and see if the map is found
|
|
MapResult = MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 0);
|
|
bCustomMap = (MapResult == -2);
|
|
bNewAch = (MapResult == 1);
|
|
if ( bCustomMap ) {
|
|
//map not found - progress custom map achievements
|
|
if ( bGiveHardAch && AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsHard', 1) )
|
|
bNewAch = true;
|
|
if ( bGiveSuiAch && AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsSui', 1) )
|
|
bNewAch = true;
|
|
if ( bGiveHoeAch && AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsHoE', 1) )
|
|
bNewAch = true;
|
|
AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsNormal', 1);
|
|
if ( AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMaps', 1) )
|
|
bNewAch = true;
|
|
}
|
|
else {
|
|
//map found - give related achievements
|
|
if ( bGiveHardAch && MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 1) == 1 )
|
|
bNewAch = true;
|
|
if ( bGiveSuiAch && MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 2) == 1 )
|
|
bNewAch = true;
|
|
if ( bGiveHoeAch && MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 3) == 1 )
|
|
bNewAch = true;
|
|
}
|
|
// END-GAME STAT BONUS
|
|
if ( bGiveBonus ) {
|
|
if ( bNewAch )
|
|
SPI.BonusStats(SPI.GameStartStats, BonusMult * fmax(1.0, Mut.FirstStatBonusMult));
|
|
else
|
|
SPI.BonusStats(SPI.GameStartStats, BonusMult);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static function TrimCollectionToDefault(out class<KFMonstersCollection> Collection, class<KFMonstersCollection> DefaultCollection)
|
|
{
|
|
local int i, j, L;
|
|
|
|
log ("Resetting Collection '"$Collection$"' to default '"$DefaultCollection$"'", 'ScrnBalance');
|
|
// v7.51
|
|
// seems like simple assigning of multi-dimensional arrays (i.e. arrays of structs of arrays) doesn't work correctly
|
|
// that's why we assigning each element individually
|
|
Collection.default.MonsterClasses.length = DefaultCollection.default.MonsterClasses.length;
|
|
// StandardMonsterClasses isn't in use anywhere now, but who now that bloody Tripwire? ;)
|
|
Collection.default.StandardMonsterClasses.length = DefaultCollection.default.MonsterClasses.length;
|
|
for ( i=0; i<DefaultCollection.default.MonsterClasses.length; ++i ) {
|
|
Collection.default.MonsterClasses[i].MClassName = DefaultCollection.default.MonsterClasses[i].MClassName;
|
|
Collection.default.StandardMonsterClasses[i].MClassName = DefaultCollection.default.MonsterClasses[i].MClassName;
|
|
|
|
}
|
|
|
|
// v7.51: fix to replace empty squads
|
|
Collection.default.SpecialSquads.length = 0;
|
|
|
|
Collection.default.ShortSpecialSquads.length = Collection.default.ShortSpecialSquads.length;
|
|
for ( i=0; i<DefaultCollection.default.ShortSpecialSquads.length; ++i ) {
|
|
L = DefaultCollection.default.ShortSpecialSquads[i].ZedClass.length;
|
|
Collection.default.ShortSpecialSquads[i].ZedClass.length = L;
|
|
Collection.default.ShortSpecialSquads[i].NumZeds.length = L;
|
|
for ( j=0; j<L; ++j ) {
|
|
Collection.default.ShortSpecialSquads[i].ZedClass[j] = DefaultCollection.default.ShortSpecialSquads[i].ZedClass[j];
|
|
Collection.default.ShortSpecialSquads[i].NumZeds[j] = DefaultCollection.default.ShortSpecialSquads[i].NumZeds[j];
|
|
}
|
|
}
|
|
Collection.default.NormalSpecialSquads.length = Collection.default.NormalSpecialSquads.length;
|
|
for ( i=0; i<DefaultCollection.default.NormalSpecialSquads.length; ++i ) {
|
|
L = DefaultCollection.default.NormalSpecialSquads[i].ZedClass.length;
|
|
Collection.default.NormalSpecialSquads[i].ZedClass.length = L;
|
|
Collection.default.NormalSpecialSquads[i].NumZeds.length = L;
|
|
for ( j=0; j<L; ++j ) {
|
|
Collection.default.NormalSpecialSquads[i].ZedClass[j] = DefaultCollection.default.NormalSpecialSquads[i].ZedClass[j];
|
|
Collection.default.NormalSpecialSquads[i].NumZeds[j] = DefaultCollection.default.NormalSpecialSquads[i].NumZeds[j];
|
|
}
|
|
}
|
|
Collection.default.LongSpecialSquads.length = Collection.default.LongSpecialSquads.length;
|
|
for ( i=0; i<DefaultCollection.default.LongSpecialSquads.length; ++i ) {
|
|
L = DefaultCollection.default.LongSpecialSquads[i].ZedClass.length;
|
|
Collection.default.LongSpecialSquads[i].ZedClass.length = L;
|
|
Collection.default.LongSpecialSquads[i].NumZeds.length = L;
|
|
for ( j=0; j<L; ++j ) {
|
|
Collection.default.LongSpecialSquads[i].ZedClass[j] = DefaultCollection.default.LongSpecialSquads[i].ZedClass[j];
|
|
Collection.default.LongSpecialSquads[i].NumZeds[j] = DefaultCollection.default.LongSpecialSquads[i].NumZeds[j];
|
|
}
|
|
}
|
|
Collection.default.FinalSquads.length = Collection.default.FinalSquads.length;
|
|
for ( i=0; i<DefaultCollection.default.FinalSquads.length; ++i ) {
|
|
L = DefaultCollection.default.FinalSquads[i].ZedClass.length;
|
|
Collection.default.FinalSquads[i].ZedClass.length = L;
|
|
Collection.default.FinalSquads[i].NumZeds.length = L;
|
|
for ( j=0; j<L; ++j ) {
|
|
Collection.default.FinalSquads[i].ZedClass[j] = DefaultCollection.default.FinalSquads[i].ZedClass[j];
|
|
Collection.default.FinalSquads[i].NumZeds[j] = DefaultCollection.default.FinalSquads[i].NumZeds[j];
|
|
}
|
|
}
|
|
|
|
Collection.default.FallbackMonsterClass = DefaultCollection.default.FallbackMonsterClass;
|
|
Collection.default.EndGameBossClass = DefaultCollection.default.EndGameBossClass;
|
|
}
|
|
|
|
function ResetSquads()
|
|
{
|
|
ResetGameSquads(Mut.KF, Mut.CurrentEventNum);
|
|
}
|
|
|
|
//resets squads to default ones, because default ones are screwed
|
|
static function ResetGameSquads(KFGameType Game, byte EventNum)
|
|
{
|
|
local class<KFMonstersCollection> DefaultCollection;
|
|
local int i;
|
|
|
|
for ( i=0; i<16; ++i ) {
|
|
Game.ShortWaves[i] = Game.default.ShortWaves[i];
|
|
Game.NormalWaves[i] = Game.default.NormalWaves[i];
|
|
Game.LongWaves[i] = Game.default.LongWaves[i];
|
|
}
|
|
|
|
switch ( EventNum ) {
|
|
case 0:
|
|
DefaultCollection = class'DefaultMonstersCollection';
|
|
break;
|
|
case 1:
|
|
DefaultCollection = class'DefaultMonstersCollectionSummer';
|
|
break;
|
|
case 2:
|
|
DefaultCollection = class'DefaultMonstersCollectionHalloween';
|
|
break;
|
|
case 3:
|
|
DefaultCollection = class'DefaultMonstersCollectionXmas';
|
|
break;
|
|
default:
|
|
return; // custom collection used, don't screw it
|
|
}
|
|
|
|
Game.EndGameBossClass = DefaultCollection.default.EndGameBossClass;
|
|
Game.StandardMonsterClasses.Length = 0; //fill MonstersCollection instead
|
|
TrimCollectionToDefault(Game.MonsterCollection, DefaultCollection);
|
|
// if ( Game.MonsterCollection != Game.default.MonsterCollection )
|
|
// TrimCollectionToDefault(Game.default.MonsterCollection, DefaultCollection);
|
|
}
|
|
|
|
|
|
function int NetDamage( int OriginalDamage, int Damage, pawn injured, pawn instigatedBy,
|
|
vector HitLocation, out vector Momentum, class<DamageType> DamageType )
|
|
{
|
|
local byte DamTypeNum;
|
|
local int idx, i;
|
|
local ScrnPlayerInfo SPI;
|
|
local class<KFWeaponDamageType> KFDamType;
|
|
local bool bP2M;
|
|
local ScrnPlayerController ScrnPC;
|
|
local KFMonster ZedVictim;
|
|
|
|
// log("NetDamage: " $ injured $ " took damage from " $ instigatedBy $ " with " $ DamageType, 'ScrnBalance');
|
|
|
|
// forward call to next rules
|
|
if ( NextGameRules != None )
|
|
Damage = NextGameRules.NetDamage( OriginalDamage,Damage,injured,instigatedBy,HitLocation,Momentum,DamageType );
|
|
|
|
if ( Damage == 0 )
|
|
return 0;
|
|
|
|
KFDamType = class<KFWeaponDamageType>(damageType);
|
|
ZedVictim = KFMonster(injured);
|
|
bP2M = ZedVictim != none && KFDamType != none && instigatedBy != none && PlayerController(instigatedBy.Controller) != none;
|
|
if ( instigatedBy != none )
|
|
ScrnPC = ScrnPlayerController(instigatedBy.Controller);
|
|
|
|
// prevent zed from rotating while stunned
|
|
// Special case fo Husks - 201+ sniper damage stuns them
|
|
if ( ZedVictim != none && ZedVictim.Controller != none
|
|
&& (Damage*1.5 >= ZedVictim.default.Health
|
|
|| (Damage > 200 && KFDamType != none && KFDamType.default.bSniperWeapon && ZedVictim.IsA('ZombieHusk') && !ZedVictim.IsA('TeslaHusk'))) )
|
|
{
|
|
ZedVictim.Controller.Focus = none;
|
|
ZedVictim.Controller.FocalPoint = ZedVictim.Location + 512 * vector(ZedVictim.Rotation);
|
|
}
|
|
|
|
if ( bP2M ) {
|
|
idx = GetMonsterIndex(ZedVictim);
|
|
MonsterInfos[idx].HitCount++;
|
|
if ( MonsterInfos[idx].FirstHitTime == 0 )
|
|
MonsterInfos[idx].FirstHitTime = Level.TimeSeconds;
|
|
|
|
MonsterInfos[idx].bHeadshot = !MonsterInfos[idx].bWasDecapitated && KFDamType.default.bCheckForHeadShots
|
|
&& (ZedVictim.bDecapitated || int(ZedVictim.HeadHealth) < MonsterInfos[idx].HeadHealth);
|
|
|
|
|
|
if ( MonsterInfos[idx].bHeadshot ) {
|
|
MonsterInfos[idx].RowHeadshots++;
|
|
MonsterInfos[idx].Headshots++;
|
|
|
|
if ( KFDamType.default.bSniperWeapon && Damage > Mut.SharpProgMinDmg && !ZedVictim.bDecapitated
|
|
&& SRStatsBase(PlayerController(instigatedBy.Controller).SteamStatsAndAchievements) != none )
|
|
SRStatsBase(PlayerController(instigatedBy.Controller).SteamStatsAndAchievements).AddHeadshotKill(false);
|
|
}
|
|
else if ( !ZedVictim.bDecapitated ) {
|
|
MonsterInfos[idx].RowHeadshots = 0;
|
|
if ( KFDamType.default.bCheckForHeadShots )
|
|
MonsterInfos[idx].Bodyshots++;
|
|
else
|
|
MonsterInfos[idx].OtherHits++;
|
|
}
|
|
|
|
// display damages on the hud
|
|
if ( bShowDamages && ScrnPC != none && ScrnPC.bDamageAck ) {
|
|
if ( MonsterInfos[idx].bHeadshot )
|
|
DamTypeNum = 1;
|
|
else if ( KFDamType.default.bDealBurningDamage )
|
|
DamTypeNum = 2;
|
|
|
|
if ( HitLocation == vect(0, 0, 0) ) //DoT
|
|
HitLocation = injured.Location;
|
|
ScrnPC.DamageMade(Damage, HitLocation, DamTypeNum);
|
|
}
|
|
|
|
SPI = GetPlayerInfo(PlayerController(instigatedBy.Controller));
|
|
// SPI.MadeDamage() calls AchHandlers.MonsterDamaged()
|
|
if ( SPI != none )
|
|
SPI.MadeDamage(Damage, ZedVictim, KFDamType, MonsterInfos[idx].bHeadshot, MonsterInfos[idx].bWasDecapitated);
|
|
}
|
|
else if ( KFHumanPawn(injured) != none ) {
|
|
// game bug: Siren doesn't set herself as instigator when dealing screaming damage - sneaky bicth
|
|
if ( DamageType == class'KFMod.SirenScreamDamage' ) {
|
|
SPI = GetPlayerInfo(PlayerController(injured.Controller));
|
|
if ( SPI != none )
|
|
SPI.TookDamage(Damage, none, DamageType);
|
|
}
|
|
else if ( KFMonster(instigatedBy) != none ) {
|
|
// M2P damage
|
|
idx = GetMonsterIndex(KFMonster(instigatedBy));
|
|
MonsterInfos[idx].DamageCounter += Damage;
|
|
// SPI.TookDamage() calls AchHandlers.PlayerDamaged()
|
|
SPI = GetPlayerInfo(PlayerController(injured.Controller));
|
|
if ( SPI != none )
|
|
SPI.TookDamage(Damage, KFMonster(instigatedBy), DamageType);
|
|
}
|
|
else if ( ScrnPC != none ) {
|
|
// P2P damage
|
|
if ( bShowDamages ) {
|
|
if ( HitLocation == vect(0, 0, 0) ) //DoT
|
|
HitLocation = injured.Location;
|
|
ScrnPC.ClientPlayerDamaged(Damage, HitLocation, 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
AchHandlers[i].NetDamage(Damage, injured, instigatedBy, HitLocation, Momentum, DamageType);
|
|
}
|
|
|
|
if ( bP2M ) {
|
|
MonsterInfos[idx].LastHitTime = Level.TimeSeconds;
|
|
MonsterInfos[idx].HeadHealth = ZedVictim.HeadHealth;
|
|
MonsterInfos[idx].bWasDecapitated = ZedVictim.bDecapitated;
|
|
MonsterInfos[idx].bWasBackstabbed = ZedVictim.bBackstabbed;
|
|
}
|
|
|
|
return Damage;
|
|
}
|
|
|
|
|
|
function ScoreKill(Controller Killer, Controller Killed)
|
|
{
|
|
local int i;
|
|
|
|
if ( NextGameRules != None )
|
|
NextGameRules.ScoreKill(Killer, Killed);
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
AchHandlers[i].ScoreKill(Killer, Killed);
|
|
}
|
|
|
|
if ( Killed.bIsPlayer ) {
|
|
AdjustZedSpawnRate();
|
|
if ( Mut.bPlayerZEDTime && Killer != none && Killer != Killed )
|
|
Mut.KF.DramaticEvent(1.0); // always zed time on player death
|
|
}
|
|
}
|
|
|
|
|
|
function bool PreventDeath(Pawn Killed, Controller Killer, class<DamageType> damageType, vector HitLocation)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
local int idx;
|
|
local class<KFWeaponDamageType> KFDamType;
|
|
|
|
if ( (NextGameRules != None) && NextGameRules.PreventDeath(Killed,Killer, damageType,HitLocation) )
|
|
return true;
|
|
|
|
KFDamType = class<KFWeaponDamageType>(DamageType);
|
|
|
|
++WaveTotalKills;
|
|
if ( Killed.IsA('DoomMonster') ) {
|
|
++WaveDoom3Kills;
|
|
++GameDoom3Kills;
|
|
}
|
|
|
|
if ( Killer != none && KFDamType != none && ScrnHumanPawn(Killer.Pawn) != none && ClassIsChildOf(KFDamType, class'DamTypeMachete') ) {
|
|
ScrnHumanPawn(Killer.Pawn).MacheteResetTime = Level.TimeSeconds + 5.0;
|
|
}
|
|
|
|
if ( KFMonster(Killed) != none ) {
|
|
if ( PlayerController(Killer) != none && KFDamType != none ) {
|
|
SPI = GetPlayerInfo(PlayerController(Killer));
|
|
if ( SPI != none ) {
|
|
SPI.KilledMonster(KFMonster(Killed), KFDamType);
|
|
}
|
|
}
|
|
}
|
|
else if ( KFHumanPawn(Killed) != none && PlayerController(Killed.Controller) != none ) {
|
|
++WaveDeadPlayers;
|
|
if ( Killer != none && KFMonster(Killer.Pawn) != none ) {
|
|
// player killed by monster
|
|
idx = GetMonsterIndex(KFMonster(Killer.Pawn));
|
|
MonsterInfos[idx].PlayerKillCounter++;
|
|
MonsterInfos[idx].PlayerKillTime = Level.TimeSeconds;
|
|
}
|
|
// don't count suicide deaths during trader time
|
|
if ( !Mut.KF.bTradingDoorsOpen ) {
|
|
SPI = GetPlayerInfo(PlayerController(Killed.Controller));
|
|
if ( SPI != none ) {
|
|
SPI.Died(Killer, DamageType);
|
|
}
|
|
}
|
|
if ( Mut.bSpawn0 || Mut.bStoryMode ) {
|
|
idx = Killed.Health;
|
|
Killed.Health = max(Killed.Health, 1); // requires health to spawn second pistol
|
|
class'ScrnHumanPawn'.static.DropAllWeapons(Killed); // toss all weapons after death, if bSpawn0=true or in Story Mode
|
|
Killed.Health = idx;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// reward assistants with TeamWork achievements
|
|
function RewardTeamwork(ScrnPlayerInfo KillerInfo, int MonsterIndex, name AchievementName)
|
|
{
|
|
local int tc;
|
|
if ( MonsterInfos[MonsterIndex].TW_Ach_Failed || MonsterInfos[MonsterIndex].Monster.bDamagedAPlayer )
|
|
return;
|
|
|
|
if ( KillerInfo != none )
|
|
tc++;
|
|
if ( MonsterInfos[MonsterIndex].KillAss1 != none && MonsterInfos[MonsterIndex].KillAss1 != KillerInfo)
|
|
tc++;
|
|
if ( MonsterInfos[MonsterIndex].KillAss2 != none && MonsterInfos[MonsterIndex].KillAss2 != KillerInfo
|
|
&& MonsterInfos[MonsterIndex].KillAss2 != MonsterInfos[MonsterIndex].KillAss1 )
|
|
tc++;
|
|
if ( tc < 2 )
|
|
return; // Teamwork requires TEAM
|
|
|
|
if ( KillerInfo != none )
|
|
KillerInfo.ProgressAchievement(AchievementName, 1);
|
|
|
|
if ( MonsterInfos[MonsterIndex].KillAss1 != none && MonsterInfos[MonsterIndex].KillAss1 != KillerInfo ) {
|
|
MonsterInfos[MonsterIndex].KillAss1.ProgressAchievement(AchievementName, 1);
|
|
}
|
|
|
|
if ( MonsterInfos[MonsterIndex].KillAss2 != none && MonsterInfos[MonsterIndex].KillAss2 != KillerInfo
|
|
&& MonsterInfos[MonsterIndex].KillAss2 != MonsterInfos[MonsterIndex].KillAss1 ) {
|
|
MonsterInfos[MonsterIndex].KillAss2.ProgressAchievement(AchievementName, 1);
|
|
}
|
|
}
|
|
|
|
|
|
function ClearMonsterInfo(int index)
|
|
{
|
|
MonsterInfos[index].Monster = none;
|
|
MonsterInfos[index].HitCount = 0;
|
|
MonsterInfos[index].FirstHitTime = 0;
|
|
MonsterInfos[index].PlayerKillCounter = 0;
|
|
MonsterInfos[index].DamageCounter = 0;
|
|
MonsterInfos[index].PlayerKillTime = 0;
|
|
MonsterInfos[index].KillAss1 = none;
|
|
MonsterInfos[index].DamType1 = none;
|
|
MonsterInfos[index].DamageFlags1 = 0;
|
|
MonsterInfos[index].KillAss2 = none;
|
|
MonsterInfos[index].DamType2 = none;
|
|
MonsterInfos[index].DamageFlags2 = 0;
|
|
MonsterInfos[index].TW_Ach_Failed = false;
|
|
MonsterInfos[index].bHeadshot = false;
|
|
MonsterInfos[index].RowHeadshots = 0;
|
|
MonsterInfos[index].Headshots = 0;
|
|
MonsterInfos[index].BodyShots = 0;
|
|
MonsterInfos[index].OtherHits = 0;
|
|
MonsterInfos[index].LastHitTime = 0;
|
|
MonsterInfos[index].HeadHealth = 0;
|
|
MonsterInfos[index].bWasDecapitated = false;
|
|
}
|
|
|
|
//creates a new record, if monster not found
|
|
function int GetMonsterIndex(KFMonster Monster)
|
|
{
|
|
local int i, count, free_index;
|
|
|
|
if ( LastSeachedMonster == Monster )
|
|
return LastFoundMonsterIndex;
|
|
|
|
count = MonsterInfos.length;
|
|
free_index = count;
|
|
LastSeachedMonster = Monster;
|
|
for ( i = 0; i < count; ++i ) {
|
|
if ( MonsterInfos[i].Monster == Monster ) {
|
|
LastFoundMonsterIndex = i;
|
|
return i;
|
|
}
|
|
if ( free_index == count && MonsterInfos[i].Monster == none )
|
|
free_index = i;
|
|
}
|
|
// if reached here - no monster is found, so init a first free record
|
|
if ( free_index >= MonsterInfos.length ) {
|
|
// if free_index out of bounds, maybe MaxZombiesOnce is changed during the game
|
|
if ( MonsterInfos.length < Mut.KF.MaxZombiesOnce )
|
|
MonsterInfos.insert(free_index, Mut.KF.MaxZombiesOnce - MonsterInfos.length);
|
|
// MaxZombiesOnce was ok, just added extra monsters
|
|
if ( free_index >= MonsterInfos.length )
|
|
MonsterInfos.insert(free_index, 1);
|
|
}
|
|
ClearMonsterInfo(free_index);
|
|
MonsterInfos[free_index].Monster = Monster;
|
|
MonsterInfos[free_index].HeadHealth = Monster.HeadHealth * Monster.DifficultyHeadHealthModifer() * Monster.NumPlayersHeadHealthModifer();
|
|
MonsterInfos[free_index].SpawnTime = Level.TimeSeconds;
|
|
LastFoundMonsterIndex = free_index;
|
|
return free_index;
|
|
}
|
|
|
|
// if monster not found - returns false
|
|
function bool RetrieveMonsterInfo(KFMonster Monster, out int index)
|
|
{
|
|
if ( LastSeachedMonster == Monster ) {
|
|
index = LastFoundMonsterIndex;
|
|
return true;
|
|
}
|
|
|
|
for ( index = 0; index < MonsterInfos.length; ++index ) {
|
|
if ( MonsterInfos[index].Monster == Monster ) {
|
|
LastSeachedMonster = Monster;
|
|
LastFoundMonsterIndex = index;
|
|
return true;
|
|
}
|
|
}
|
|
index = -1;
|
|
return false;
|
|
}
|
|
|
|
function RegisterMonster(KFMonster Monster)
|
|
{
|
|
local int i;
|
|
|
|
GetMonsterIndex(Monster); // add to MonsterInfos
|
|
|
|
if ( Mut.KF.bUseEndGameBoss && Mut.KF.WaveNum == Mut.KF.FinalWave && BossClass == none
|
|
&& GetItemName(Mut.KF.MonsterCollection.default.EndGameBossClass) ~= GetItemName(String(Monster.class)) )
|
|
{
|
|
InitBoss(Monster);
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < CheckedMonsterClasses.Length; ++i ) {
|
|
if ( Monster.class == CheckedMonsterClasses[i] )
|
|
return;
|
|
}
|
|
|
|
if ( Mut.IsSquadWaitingToSpawn() )
|
|
return; // this monster is spawned by "mvote spawn", so ignore it in HL and achievement calculations
|
|
|
|
//log("Monster=" $ String(Other) @ "Outer="$String(Other.outer) @ "OuterClass="$String(Other.class.outer), 'ScrnBalance');
|
|
CheckedMonsterClasses[CheckedMonsterClasses.length] = Monster.class;
|
|
CheckNewMonster(Monster);
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i )
|
|
AchHandlers[i].MonsterIntroduced(Monster);
|
|
}
|
|
|
|
function CheckNewMonster(KFMonster Monster)
|
|
{
|
|
local int i;
|
|
local string MCS;
|
|
|
|
MCS = GetItemName(string(Monster.Class));
|
|
|
|
for ( i=0; i<HardcoreZeds.length; ++i ) {
|
|
if ( !HardcoreZeds[i].bUsed && HardcoreZeds[i].MonsterClass ~= MCS ) {
|
|
HardcoreZeds[i].bUsed = true;
|
|
bHasCustomZeds = true;
|
|
RaiseHardcoreLevel(HardcoreZeds[i].HL * ZedHLMult, Monster.MenuName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function InitBoss(KFMonster Monster)
|
|
{
|
|
local int i;
|
|
local string BossClassStr;
|
|
|
|
Boss = Monster;
|
|
BossClass = Monster.class;
|
|
BossClassStr = GetItemName(string(BossClass.name));
|
|
bSuperPat = Monster.IsA('ZombieSuperBoss') || Monster.IsA('HardPat');
|
|
bDoomPat = Monster.IsA('DoomMonster');
|
|
|
|
for ( i=0; i<HardcoreBosses.length; ++i ) {
|
|
if ( !HardcoreBosses[i].bUsed && HardcoreBosses[i].MonsterClass ~= BossClassStr ) {
|
|
HardcoreBosses[i].bUsed = true;
|
|
RaiseHardcoreLevel(HardcoreBosses[i].HL * ZedHLMult, Boss.MenuName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i )
|
|
AchHandlers[i].BossSpawned(Monster);
|
|
}
|
|
|
|
function ReinitMonster(KFMonster Monster)
|
|
{
|
|
MonsterInfos[GetMonsterIndex(Monster)].HeadHealth = Monster.HeadHealth;
|
|
}
|
|
|
|
// called from ScrnNade
|
|
function ScrakeNaded(ZombieScrake Scrake)
|
|
{
|
|
local int index;
|
|
|
|
index = GetMonsterIndex(Scrake);
|
|
MonsterInfos[index].DamType2 = class'KFMod.DamTypeFrag';
|
|
MonsterInfos[index].DamageFlags2 = DF_RAGED | DF_STUPID;
|
|
}
|
|
|
|
// returns true if last damage to zed was a headshot
|
|
function bool WasHeadshot(KFMonster Monster)
|
|
{
|
|
local int idx;
|
|
|
|
return RetrieveMonsterInfo(Monster, idx) && MonsterInfos[idx].bHeadshot;
|
|
}
|
|
|
|
function protected InitHardcoreLevel()
|
|
{
|
|
local int i;
|
|
local string GameClass;
|
|
|
|
GameClass = GetItemName(string(Level.Game.Class));
|
|
|
|
if ( Level.Game.GameDifficulty >= 7 ) {
|
|
HardcoreLevelFloat = HL_HoE;
|
|
ZedHLMult = HLMult_HoE;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 5 ) {
|
|
HardcoreLevelFloat = HL_Suicidal;
|
|
ZedHLMult = HLMult_Suicidal;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 4 ) {
|
|
HardcoreLevelFloat = HL_Hard;
|
|
ZedHLMult = HLMult_Hard;
|
|
}
|
|
else {
|
|
HardcoreLevelFloat = HL_Normal;
|
|
ZedHLMult = HLMult_Normal;
|
|
}
|
|
|
|
if ( Mut.bHardcore )
|
|
HardcoreLevelFloat += HL_Hardcore;
|
|
|
|
for ( i=0; i<HardcoreGames.length; ++i ) {
|
|
if ( HardcoreGames[i].GameClass ~= GameClass ) {
|
|
HardcoreLevelFloat += HardcoreGames[i].HL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HardcoreLevel = int(HardcoreLevelFloat+0.01);
|
|
// replicate to clients
|
|
Mut.HardcoreLevel = clamp(HardcoreLevel,0,255);
|
|
|
|
}
|
|
|
|
function RaiseHardcoreLevel(float inc, string reason)
|
|
{
|
|
local string s;
|
|
|
|
if ( bForceHardcoreLevel )
|
|
return;
|
|
|
|
if ( HardcoreLevelFloat < HardcoreLevel )
|
|
HardcoreLevelFloat = HardcoreLevel; // just to be sure
|
|
|
|
HardcoreLevelFloat += inc;
|
|
HardcoreLevel = int(HardcoreLevelFloat+0.01);
|
|
// replicate to clients
|
|
Mut.HardcoreLevel = clamp(HardcoreLevel,0,255);
|
|
Mut.NetUpdateTime = Level.TimeSeconds - 1;
|
|
|
|
if ( bBroadcastHL ) {
|
|
s = msgHardcore;
|
|
ReplaceText(s, "%a", String(HardcoreLevel));
|
|
ReplaceText(s, "%i", String(inc));
|
|
ReplaceText(s, "%r", reason);
|
|
Mut.BroadcastMessage(s, true);
|
|
}
|
|
}
|
|
|
|
function ForceHardcoreLevel(int value)
|
|
{
|
|
HardcoreLevel = value;
|
|
HardcoreLevelFloat = value;
|
|
bForceHardcoreLevel = true;
|
|
// replicate to clients
|
|
Mut.HardcoreLevel = clamp(HardcoreLevel,0,255);
|
|
}
|
|
|
|
function WeaponReloaded(PlayerController WeaponOwner, KFWeapon W)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
SPI = GetPlayerInfo(WeaponOwner);
|
|
if ( SPI != none )
|
|
SPI.WeaponReloaded(W);
|
|
}
|
|
|
|
function WeaponFire(PlayerController WeaponOwner, KFWeapon W, byte FireMode)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
SPI = GetPlayerInfo(WeaponOwner);
|
|
if ( SPI != none )
|
|
SPI.WeaponFired(W, FireMode);
|
|
}
|
|
|
|
|
|
function RegisterAchHandler(ScrnAchHandlerBase Handler)
|
|
{
|
|
local int i;
|
|
|
|
for ( i=0; i<AchHandlers.length; ++i ) {
|
|
if ( AchHandlers[i] == Handler )
|
|
return;
|
|
}
|
|
AchHandlers[AchHandlers.length] = Handler;
|
|
}
|
|
|
|
function ScrnPlayerInfo GetPlayerInfo(PlayerController PlayerOwner)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
if ( PlayerOwner == none )
|
|
return none;
|
|
|
|
for ( SPI=PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
|
|
if ( SPI.PlayerOwner == PlayerOwner )
|
|
return SPI;
|
|
}
|
|
return none;
|
|
}
|
|
|
|
function int AlivePlayerCount()
|
|
{
|
|
return WavePlayerCount - WaveDeadPlayers;
|
|
}
|
|
|
|
function int PlayerCountInWave()
|
|
{
|
|
return WavePlayerCount;
|
|
}
|
|
|
|
/**
|
|
* Increments achievement progress for all players who have PlayerInfo object, except ExcludeSPI.
|
|
* @param AchID Achievement ID
|
|
* @param Inc Achievement progress. Usually 1.
|
|
* @param bOnlyAlive if true, then achievement will not be granted for dead players (SPI.bDied = true)
|
|
* @param ExcludeSPI player to exclude from achievement progress.
|
|
*/
|
|
// if bOnlyAlive=true, then achievment will not be granted for dead players (bDied = true)
|
|
function ProgressAchievementForAllPlayers(name AchID, int Inc, optional bool bOnlyAlive, optional ScrnPlayerInfo ExcludeSPI, optional TeamInfo Team)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
for ( SPI=PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
|
|
if ( SPI != ExcludeSPI && SPI.PlayerOwner != none
|
|
&& (!SPI.bDied || !bOnlyAlive)
|
|
&& SPI.PlayerOwner.PlayerReplicationInfo != none
|
|
&& (Team == none || SPI.PlayerOwner.PlayerReplicationInfo.Team == Team)
|
|
)
|
|
{
|
|
SPI.ProgressAchievement(AchID, Inc);
|
|
}
|
|
}
|
|
}
|
|
|
|
final private function BackupOrDestroySPI(ScrnPlayerInfo SPI)
|
|
{
|
|
if ( SPI.SteamID32 > 0 && (SPI.PRI_Kills > 0 || SPI.PRI_Deaths > 0 || SPI.PRI_KillAssists > 0) ) {
|
|
if ( BackupPlayerInfo == none ) {
|
|
SPI.NextPlayerInfo = none;
|
|
BackupPlayerInfo = SPI;
|
|
}
|
|
else {
|
|
SPI.NextPlayerInfo = BackupPlayerInfo;
|
|
BackupPlayerInfo = SPI;
|
|
}
|
|
}
|
|
else
|
|
SPI.Destroy();
|
|
}
|
|
|
|
// destroys player infos without PlayerOwner
|
|
final function ClearNonePlayerInfos()
|
|
{
|
|
local ScrnPlayerInfo SPI, PrevSPI;
|
|
|
|
while ( PlayerInfo!=none && (PlayerInfo.PlayerOwner == none || PlayerInfo.PlayerOwner.Pawn == none) )
|
|
{
|
|
PrevSPI = PlayerInfo;
|
|
PlayerInfo = PrevSPI.NextPlayerInfo;
|
|
BackupOrDestroySPI(PrevSPI);
|
|
}
|
|
|
|
if ( PlayerInfo == none )
|
|
return;
|
|
|
|
PrevSPI = PlayerInfo;
|
|
// we already know that PlayerInfo has PlayerOwner, otherwise we won't reach here
|
|
SPI = PrevSPI.NextPlayerInfo;
|
|
while ( SPI != none ) {
|
|
if ( SPI.PlayerOwner == none || PlayerInfo.PlayerOwner.Pawn == none ) {
|
|
PrevSPI.NextPlayerInfo = SPI.NextPlayerInfo;
|
|
BackupOrDestroySPI(SPI);
|
|
}
|
|
else {
|
|
PrevSPI = SPI;
|
|
}
|
|
SPI = PrevSPI.NextPlayerInfo;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns ScrnPlayerInfo (SPI) object for given PlayerController. If SPI object already exists and
|
|
* active (i.e. player is in the game), then just returns it. If there is no active SPI record found,
|
|
* function looks for backup, comparing by SteamID32 (in case of reconnecting players).
|
|
* If SPI object is not found in both active and backup lists, then creates a new one.
|
|
*
|
|
* @param PlayerOwner PlayerController, which owns the SPI object. Note that it means
|
|
* SPI.PlayerOwner = PlayerOwner, NOT SPI.Owner (latter is none)
|
|
* @param bDontRestoreFromBackup This parameter is used only when SPI object is in the backup.
|
|
* True means it will be kept in backup.
|
|
* False means it will be restored to active player info list.
|
|
* @return SPI object which is linked to given PlayerOwner. Theoretically function always should returns
|
|
* a valid pointer.
|
|
*/
|
|
final function ScrnPlayerInfo CreatePlayerInfo(PlayerController PlayerOwner, optional bool bDontRestoreFromBackup)
|
|
{
|
|
local ScrnPlayerInfo SPI, PrevSPI;
|
|
local ScrnCustomPRI ScrnPRI;
|
|
local int SteamID32;
|
|
|
|
if ( PlayerOwner == none )
|
|
return none;
|
|
|
|
// Does it exist and active?
|
|
for ( SPI=PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
|
|
if ( SPI.PlayerOwner == PlayerOwner )
|
|
return SPI;
|
|
}
|
|
|
|
ScrnPRI = class'ScrnCustomPRI'.static.FindMe(PlayerOwner.PlayerReplicationInfo);
|
|
if ( ScrnPRI != none )
|
|
SteamID32 = ScrnPRI.GetSteamID32();
|
|
// check by SteamID for quickly reconnecting players (e.g. crashed)
|
|
if ( SteamID32 > 0 ) {
|
|
for ( SPI = PlayerInfo; SPI != none; SPI = SPI.NextPlayerInfo ) {
|
|
if ( SPI.SteamID32 == SteamID32 ) {
|
|
SPI.PlayerOwner = PlayerOwner;
|
|
SPI.RestorePRI();
|
|
return SPI;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for backup
|
|
if ( BackupPlayerInfo != none && SteamID32 > 0 ) {
|
|
for ( SPI = BackupPlayerInfo; SPI != none; SPI = SPI.NextPlayerInfo ) {
|
|
if ( SPI.SteamID32 == SteamID32) {
|
|
if ( SPI.PlayerOwner != PlayerOwner ) {
|
|
SPI.PlayerOwner = PlayerOwner;
|
|
SPI.RestorePRI();
|
|
}
|
|
|
|
if ( bDontRestoreFromBackup ) {
|
|
//remove from backup
|
|
if ( PrevSPI != none )
|
|
PrevSPI.NextPlayerInfo = SPI.NextPlayerInfo;
|
|
else
|
|
BackupPlayerInfo = SPI.NextPlayerInfo;
|
|
SPI.NextPlayerInfo = none;
|
|
|
|
// link to PlayerInfo list
|
|
if ( PlayerInfo == none )
|
|
PlayerInfo = SPI;
|
|
else {
|
|
SPI.NextPlayerInfo = PlayerInfo;
|
|
PlayerInfo = SPI;
|
|
}
|
|
}
|
|
|
|
return SPI;
|
|
}
|
|
PrevSPI = SPI;
|
|
}
|
|
}
|
|
|
|
// not active and not backed up - create a new one
|
|
SPI = spawn(class'ScrnPlayerInfo');
|
|
if ( SPI == none ) {
|
|
// this never should happen
|
|
log("Unable to spawn ScrnPlayerInfo!", 'ScrnBalance');
|
|
return none;
|
|
}
|
|
|
|
if ( PlayerInfo == none )
|
|
PlayerInfo = SPI;
|
|
else {
|
|
SPI.NextPlayerInfo = PlayerInfo;
|
|
PlayerInfo = SPI;
|
|
}
|
|
// initial data
|
|
SPI.PlayerOwner = PlayerOwner;
|
|
SPI.GameRules = self;
|
|
SPI.SteamID32 = SteamID32;
|
|
SPI.StartWave = Mut.KF.WaveNum;
|
|
SPI.BackupStats(SPI.GameStartStats);
|
|
return SPI;
|
|
}
|
|
|
|
function DebugSPI(PlayerController Sender)
|
|
{
|
|
local ScrnPlayerInfo SPI;
|
|
|
|
Sender.ClientMessage("Active SPIs:");
|
|
Sender.ClientMessage("------------------------------------");
|
|
for ( SPI = Mut.GameRules.PlayerInfo; SPI != none; SPI = SPI.NextPlayerInfo ) {
|
|
if ( SPI.PlayerOwner != none )
|
|
Sender.ClientMessage(SPI.SteamID32 @ SPI.PlayerOwner.PlayerReplicationInfo.PlayerName
|
|
@ SPI.PRI_Kills @ "Machete-steps: " $ SPI.GetCustomValue(none, 'MacheteWalker') );
|
|
else
|
|
Sender.ClientMessage(SPI.SteamID32 @ "none" @ SPI.PRI_Kills );
|
|
}
|
|
Sender.ClientMessage("------------------------------------");
|
|
Sender.ClientMessage("Backup SPIs:");
|
|
Sender.ClientMessage("------------------------------------");
|
|
for ( SPI = Mut.GameRules.BackupPlayerInfo; SPI != none; SPI = SPI.NextPlayerInfo ) {
|
|
if ( SPI.PlayerOwner != none )
|
|
Sender.ClientMessage(SPI.SteamID32 @ SPI.PlayerOwner.PlayerReplicationInfo.PlayerName @ SPI.PRI_Kills );
|
|
else
|
|
Sender.ClientMessage(SPI.SteamID32 @ "none" @ SPI.PRI_Kills );
|
|
}
|
|
}
|
|
|
|
|
|
/* OverridePickupQuery()
|
|
when pawn wants to pickup something, gamerules given a chance to modify it. If this function
|
|
returns true, bAllowPickup will determine if the object can be picked up.
|
|
*/
|
|
function bool OverridePickupQuery(Pawn Other, Pickup item, out byte bAllowPickup)
|
|
{
|
|
local bool result;
|
|
local KFWeaponPickup WP;
|
|
local ScrnPlayerInfo SPI;
|
|
local string str;
|
|
local ScrnHumanPawn ScrnPawn;
|
|
|
|
if ( Other.Health <= 0 ) {
|
|
bAllowPickup = 0; // prevent dying bodies of picking up items
|
|
return true;
|
|
}
|
|
|
|
ScrnPawn = ScrnHumanPawn(Other);
|
|
|
|
Mut.ReplacePickup(item); // replace pickup's inventory with ScrN version
|
|
WP = KFWeaponPickup(item);
|
|
if ( Mut.bPickPerkedWeaponsOnly && WP != none && ScrnPawn != none
|
|
&& WP.CorrespondingPerkIndex != 7 //off-perk
|
|
&& WP.CorrespondingPerkIndex != ScrnPawn.ScrnPerk.default.PerkIndex
|
|
&& !ScrnPawn.ScrnPerk.static.OverridePerkIndex(WP.class) )
|
|
{
|
|
if ( PlayerController(Other.Controller) != none )
|
|
PlayerController(Other.Controller).ClientMessage(Mut.ColorString(strPerkedWeaponsOnly,192,100,1));
|
|
result = true;
|
|
bAllowPickup = 0;
|
|
}
|
|
else if ( NextGameRules != None )
|
|
result = NextGameRules.OverridePickupQuery(Other, item, bAllowPickup);
|
|
|
|
if ( !result || bAllowPickup == 1 ) {
|
|
if ( WP != none ) {
|
|
// weapon lock and broadcast
|
|
if ( WP.DroppedBy != Other.Controller
|
|
&& WP.DroppedBy != none && WP.DroppedBy.PlayerReplicationInfo != none )
|
|
{
|
|
if ( WP.SellValue > 0 && ScrnPlayerController(WP.DroppedBy) != none && ScrnPlayerController(WP.DroppedBy).bWeaponsLocked
|
|
&& (Other.PlayerReplicationInfo == none || WP.DroppedBy.PlayerReplicationInfo.Team == Other.PlayerReplicationInfo.Team) )
|
|
{
|
|
// ScrN Players can lock weapons from picking up by teammates
|
|
result = true;
|
|
bAllowPickup = 0;
|
|
if ( ScrnPlayerController(Other.Controller) != none && Level.TimeSeconds > ScrnPlayerController(Other.Controller).LastLockMsgTime + 1.0 ) {
|
|
str = strWeaponLocked;
|
|
ReplaceText(str, "%o", Mut.ColoredPlayerName(WP.DroppedBy.PlayerReplicationInfo));
|
|
ReplaceText(str, "%w", WP.ItemName);
|
|
ScrnPlayerController(Other.Controller).ClientMessage(Mut.ColorString(str,192,1,1));
|
|
ScrnPlayerController(Other.Controller).LastLockMsgTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
else if ( ScrnPlayerController(Other.Controller) != none && ScrnPlayerController(Other.Controller).bWeaponsLocked ) {
|
|
// Players cannot pick other's weapons while own weapons are locked
|
|
result = true;
|
|
bAllowPickup = 0;
|
|
if ( Level.TimeSeconds > ScrnPlayerController(Other.Controller).LastLockMsgTime + 1.0 ) {
|
|
ScrnPlayerController(Other.Controller).ClientMessage(Mut.ColorString(strWeaponLockedOwn,192,128,1));
|
|
ScrnPlayerController(Other.Controller).LastLockMsgTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
else if ( WP.SellValue > 0 && Mut.bBroadcastPickups && !HasInventoryClass(Other, WP.InventoryType) )
|
|
Mut.StolenWeapon(Other, WP);
|
|
}
|
|
}
|
|
|
|
if ( !result || bAllowPickup == 1 ) {
|
|
SPI = GetPlayerInfo(PlayerController(Other.Controller));
|
|
if ( SPI != none ) {
|
|
if ( WP != none )
|
|
SPI.PickedWeapon(WP);
|
|
else if ( CashPickup(item) != none )
|
|
SPI.PickedCash(CashPickup(item));
|
|
else
|
|
SPI.PickedItem(item);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function bool HasInventoryClass( Pawn P, class<Inventory> IC )
|
|
{
|
|
local Inventory I;
|
|
|
|
if ( P == none || IC == none )
|
|
return false;
|
|
|
|
for ( I=P.Inventory; I!=None; I=I.Inventory )
|
|
if( I.Class==IC )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
// from time to time monster becomes frustrated with enemy and finds a new one
|
|
function MonsterFrustration()
|
|
{
|
|
local int i;
|
|
local KFMonsterController MC;
|
|
local ScrnHumanPawn ScrnPawn;
|
|
|
|
for ( i=0; i<MonsterInfos.length; ++i ) {
|
|
if ( MonsterInfos[i].Monster == none
|
|
|| MonsterInfos[i].Monster.Health <= 0 || MonsterInfos[i].Monster.bDecapitated
|
|
|| MonsterInfos[i].DamageCounter > 0
|
|
|| Level.TimeSeconds - MonsterInfos[i].SpawnTime < 30.0
|
|
|| Level.TimeSeconds - MonsterInfos[i].LastHitTime < 15.0 )
|
|
continue;
|
|
|
|
MC = KFMonsterController(MonsterInfos[i].Monster.Controller);
|
|
if ( MC == none )
|
|
continue;
|
|
|
|
ScrnPawn = ScrnHumanPawn(MC.Enemy);
|
|
if ( ScrnPawn == none )
|
|
continue;
|
|
|
|
if ( ScrnPawn.GroundSpeed > fmax(220, MonsterInfos[i].Monster.GroundSpeed) ) {
|
|
// make ScrnPawn.AssessThreatTo() return 0.1 (it will be called by FindNewEnemy())
|
|
ScrnPawn.LastThreatMonster = MC;
|
|
ScrnPawn.LastThreatTime = Level.TimeSeconds;
|
|
ScrnPawn.LastThreat = 0.1;
|
|
MC.FindNewEnemy();
|
|
// todo - the next FindNewEnemy() will probably reset this enemy again
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
defaultproperties
|
|
{
|
|
msgHardcore="Hardcore level raised to %a (+%i for %r)"
|
|
msgDoom3Monster="Doom3 monsters"
|
|
msgDoomPercent="Doom3 monster count in wave = %p%"
|
|
msgDoom3Boss="Doom Boss"
|
|
MapAliases(0)=(Filename="KF-HarbourV3-fix",AchName="KF-HarbourV3")
|
|
MapAliases(1)=(Filename="KF-Harbor",AchName="KF-HarbourV3")
|
|
MapAliases(2)=(Filename="KF-SantasRetreatFinal1-1",AchName="KF-SantasRetreat")
|
|
MapAliases(3)=(Filename="KF-BigSunriseBeta1-6",AchName="KF-BigSunrise")
|
|
MapAliases(4)=(Filename="KF-SunnyLandSanitariumBeta1-5",AchName="KF-SunnyLandSanitarium")
|
|
MapAliases(5)=(Filename="KF-SilentHillBeta2-0",AchName="KF-SilentHill")
|
|
MapAliases(6)=(Filename="KF-HellGateFinal1-2",AchName="KF-HellGate")
|
|
MapAliases(7)=(Filename="Kf-HellFinal1-5",AchName="KF-Hell")
|
|
MapAliases(8)=(Filename="KF-Doom2-Final-V7",AchName="KF-D2M1")
|
|
MapAliases(9)=(Filename="KF-Doom2-HiRes",AchName="KF-D2M1")
|
|
MapAliases(10)=(Filename="KF-Doom2-HiRes11",AchName="KF-D2M1")
|
|
MapAliases(11)=(Filename="KF-ZedDiscoThe1stFloor",AchName="KF-ZedDisco")
|
|
MapAliases(12)=(Filename="KF-ZedDiscoThe2ndFloor",AchName="KF-ZedDisco")
|
|
MapAliases(13)=(Filename="KF-Abandoned-Moonbase",AchName="KF-MoonBase")
|
|
MapAliases(14)=(Filename="KF-DepartedNight",AchName="KF-Departed")
|
|
MapAliases(15)=(Filename="KF-FoundryLightsOut",AchName="KF-Foundry")
|
|
MapAliases(16)=(Filename="KF-HospitalhorrorsLightsOut",AchName="KF-Hospitalhorrors")
|
|
MapAliases(17)=(Filename="KF-Doom2-SE",AchName="KF-D2M1")
|
|
MapAliases(18)=(Filename="KF-Icebreaker-SE",AchName="KF-Icebreaker")
|
|
MapAliases(19)=(Filename="KF-HellFreezesOver1-2",AchName="KF-Hell")
|
|
MapAliases(20)=(Filename="KF-Train-fix",AchName="KF-Train")
|
|
MapAliases(21)=(Filename="KF-PandorasBoxV2-fix",AchName="KF-PandorasBox")
|
|
MapAliases(22)=(Filename="KF-Constriction-SE",AchName="KF-Constriction")
|
|
AchClass=Class'ScrnBalanceSrv.ScrnAchievements'
|
|
MapAchClass=Class'ScrnBalanceSrv.ScrnMapAchievements'
|
|
strWeaponLocked="%w locked by %o"
|
|
strWeaponLockedOwn="Can not pickup another's weapons while own are locked"
|
|
strPerkedWeaponsOnly="You can pickup perked weapons only"
|
|
strWaveAccuracy="%p scored %a accuracy in this wave!"
|
|
SovietDamageTypes(0)=Class'KFMod.DamTypeKnife'
|
|
SovietDamageTypes(1)=Class'KFMod.DamTypeFrag'
|
|
SovietDamageTypes(2)=Class'KFMod.DamTypeAK47AssaultRifle'
|
|
SovietDamageTypes(3)=Class'ScrnBalanceSrv.ScrnDamTypeAK47AssaultRifle'
|
|
HLMult_Normal=0.500000
|
|
HL_Hard=2.000000
|
|
HLMult_Hard=0.750000
|
|
HL_Suicidal=5.000000
|
|
HLMult_Suicidal=1.000000
|
|
HL_HoE=7.000000
|
|
HLMult_HoE=1.250000
|
|
HL_Hardcore=2.000000
|
|
HardcoreZeds(0)=(MonsterClass="ZombieGhost",HL=0.500000)
|
|
HardcoreZeds(1)=(MonsterClass="ZombieShiver",HL=1.000000)
|
|
HardcoreZeds(2)=(MonsterClass="ZombieJason",HL=1.500000)
|
|
HardcoreZeds(3)=(MonsterClass="TeslaHusk",HL=1.500000)
|
|
HardcoreZeds(4)=(MonsterClass="ZombieJason",HL=1.500000)
|
|
HardcoreZeds(5)=(MonsterClass="ZombieBrute",HL=2.000000)
|
|
HardcoreZeds(6)=(MonsterClass="FemaleFP",HL=2.500000)
|
|
HardcoreZeds(7)=(MonsterClass="FemaleFP_MKII",HL=2.500000)
|
|
HardcoreZeds(8)=(MonsterClass="ZombieSuperStalker",HL=0.300000)
|
|
HardcoreZeds(9)=(MonsterClass="ZombieSuperGorefast",HL=0.300000)
|
|
HardcoreZeds(10)=(MonsterClass="ZombieGorefast_GRITTIER",HL=0.300000)
|
|
HardcoreZeds(11)=(MonsterClass="ZombieSuperCrawler",HL=0.300000)
|
|
HardcoreZeds(12)=(MonsterClass="ZombieSuperBloat",HL=0.300000)
|
|
HardcoreZeds(13)=(MonsterClass="ZombieBloat_GRITTIER",HL=0.300000)
|
|
HardcoreZeds(14)=(MonsterClass="ZombieSuperSiren",HL=1.000000)
|
|
HardcoreZeds(15)=(MonsterClass="ZombieSuperFP",HL=1.000000)
|
|
HardcoreZeds(16)=(MonsterClass="ZombieSuperHusk",HL=1.400000)
|
|
HardcoreZeds(17)=(MonsterClass="ZombieHusk_GRITTIER",HL=1.400000)
|
|
HardcoreZeds(18)=(MonsterClass="ZombieSuperScrake",HL=1.400000)
|
|
HardcoreZeds(19)=(MonsterClass="Imp",HL=1.000000)
|
|
HardcoreZeds(20)=(MonsterClass="Pinky",HL=1.000000)
|
|
HardcoreZeds(21)=(MonsterClass="Archvile",HL=1.000000)
|
|
HardcoreZeds(22)=(MonsterClass="HellKnight",HL=1.000000)
|
|
HardcoreZeds(23)=(MonsterClass="Sabaoth",HL=1.000000)
|
|
HardcoreZeds(24)=(MonsterClass="Vagary",HL=1.000000)
|
|
HardcoreZeds(25)=(MonsterClass="Maledict",HL=1.000000)
|
|
HardcoreZeds(26)=(MonsterClass="HunterInvul",HL=1.000000)
|
|
HardcoreZeds(27)=(MonsterClass="HunterBerserk",HL=1.000000)
|
|
HardcoreZeds(28)=(MonsterClass="HunterHellTime",HL=1.000000)
|
|
HardcoreZeds(29)=(MonsterClass="Guardian",HL=1.000000)
|
|
HardcoreZeds(30)=(MonsterClass="Cyberdemon",HL=1.000000)
|
|
HardcoreBosses(0)=(MonsterClass="HardPat",HL=2.000000)
|
|
HardcoreBosses(1)=(MonsterClass="HardPat_CIRCUS",HL=2.000000)
|
|
HardcoreBosses(2)=(MonsterClass="HardPat_GRITTIER",HL=2.000000)
|
|
HardcoreBosses(3)=(MonsterClass="HardPat_HALLOWEEN",HL=2.000000)
|
|
HardcoreBosses(4)=(MonsterClass="HardPat_XMAS",HL=2.000000)
|
|
HardcoreBosses(5)=(MonsterClass="ZombieSuperBoss",HL=2.000000)
|
|
HardcoreBosses(6)=(MonsterClass="Sabaoth",HL=2.000000)
|
|
HardcoreBosses(7)=(MonsterClass="Vagary",HL=1.000000)
|
|
HardcoreBosses(8)=(MonsterClass="Maledict",HL=2.000000)
|
|
HardcoreBosses(9)=(MonsterClass="HunterInvul",HL=2.000000)
|
|
HardcoreBosses(10)=(MonsterClass="HunterBerserk",HL=2.000000)
|
|
HardcoreBosses(11)=(MonsterClass="HunterHellTime",HL=2.000000)
|
|
HardcoreBosses(12)=(MonsterClass="Guardian",HL=2.000000)
|
|
HardcoreBosses(13)=(MonsterClass="Cyberdemon",HL=2.000000)
|
|
bBroadcastHL=True
|
|
HardcoreGames(0)=(GameClass="ScrnStoryGameInfo",HL=3.000000)
|
|
HardcoreGames(1)=(GameClass="TSCGame",HL=6.000000)
|
|
HardcoreGames(2)=(GameClass="FtgGame",HL=6.000000)
|
|
HardcoreGames(3)=(GameClass="TurboGame",HL=3.000000)
|
|
HardcoreGames(4)=(GameClass="FscGame",HL=10.000000)
|
|
}
|