// FreezeRules controls Zed freezing on server side. // FreezeReplicationInfo is used to replciate freezing effects to clients. class FreezeRules extends GameRules; var ScrnGameRules ScrnGameRules; var FreezeReplicationInfo FreezeRI; var float FreezeTreshold; // % of HealthMax to be done to fully freeze zed var float FrozenDamageResistance; // frozen zed gets less damage var float FrozenDamageMult; // but it is possible to break the ice and instant kill zed var float FrozenDamageMultHS; // damage that can do headshots does more damage to ice var float FrozenDamageMultSG; // shotgun-specific damage mult var class ShatteredIce; var transient PlayerController LastShatteredBy; //last player who shattered zed var transient float LastShaterTime; var transient int InstantShatterCounter; var transient float NextChainsawTime; var bool bDosh; struct SFrozen { var KFMonster M; var KFPlayerController LastFrozenBy; var class LastFrozenDamType; var bool bFrozen, bWasFrozen; var float CurrentFreeze; var float FreezeTreshold; // when CurrentFreeze reaches this value, zed becomes completely frozen // FoT: Freeze over Time var float FoT; var float FoT_Remaining; var float WarmTime; // time after which zed should get warmed up var int Damage; // damage done during freeze var vector FocalPoint; }; var transient array Frozen; final static function FreezeRules FindOrSpawnMe(GameInfo Game) { local GameRules GR; local FreezeRules FR; FR = FreezeRules(Game.GameRulesModifiers); if ( FR == none ) { for ( GR = Game.GameRulesModifiers; GR != none && FR == none; GR = GR.NextGameRules ) FR = FreezeRules(Game.GameRulesModifiers); // Freeze rules not found. Spawn it now if ( FR == none ) { FR = Game.Spawn(Class'FreezeRules', Game); if( Game.GameRulesModifiers==None ) Game.GameRulesModifiers = FR; else Game.GameRulesModifiers.AddGameRules(FR); } } return FR; } function PostBeginPlay() { FreezeRI = spawn(class'FreezeReplicationInfo', self); class'ZombieSirenCryo'.default.FreezeRules = self; } function AddGameRules(GameRules GR) { if ( GR.class != self.class ) //prevent adding same rules more than once Super.AddGameRules(GR); } function int NetDamage( int OriginalDamage, int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class DamageType ) { local class KFDamType; local class FreezeDT; local KFPlayerController PC; local KFPlayerReplicationInfo KFPRI; local class Perk; local KFMonster ZedVictim; local int idx; KFDamType = class(damageType); ZedVictim = KFMonster(injured); if ( instigatedBy != none ) PC = KFPlayerController(instigatedBy.Controller); if ( PC != none ) KFPRI = KFPlayerReplicationInfo(PC.PlayerReplicationInfo); if ( KFPRI != none ) Perk = KFPRI.ClientVeteranSkill; else Perk = class'KFVeterancyTypes'; if ( KFDamType != none && ZedVictim != none && ZedVictim.Controller != none && ZedVictim.Health > 0 ) { FreezeDT = class(damageType); if ( FreezeDT != none ) { if ( ZedVictim.bBurnified ) { ZedVictim.BurnDown /= 2; if ( ZedVictim.BurnDown == 0 ) { ZedVictim.bBurnified = false; ZedVictim.Timer(); // stop burning behavior } } if ( !ZedVictim.bBurnified ) { idx = FrozenIndex(ZedVictim, true); if ( KFPRI != none && KFPRI.ClientVeteranSkill != none ) Frozen[idx].CurrentFreeze += Damage * FreezeDT.default.FreezeRatio; if ( Frozen[idx].bFrozen ) { Frozen[idx].WarmTime = fmax(Frozen[idx].WarmTime, Level.TimeSeconds + 0.5); // allow shattering for ice darts if ( FreezeDT.default.bCheckForHeadShots ) { Damage *= FrozenDamageResistance * FreezeDT.default.HeadShotDamageMult * FreezeDT.default.ShatteringDamageMult; Frozen[idx].Damage += Damage * FrozenDamageMultHS; if ( ZedVictim.Health <= Damage + Frozen[idx].Damage ) { Damage += Frozen[idx].Damage; ShatterZed(ZedVictim, idx, instigatedBy.Controller, DamageType); } } else Damage = 0; } else { Frozen[idx].LastFrozenBy = PC; Frozen[idx].LastFrozenDamType = FreezeDT; // Freeze over Time if ( FreezeDT.default.FoT_Duration > 0 && (Frozen[idx].FoT_Remaining < 0.5 || Frozen[idx].FoT < Damage * FreezeDT.default.FoT_Ratio) ) { Frozen[idx].FoT_Remaining = FreezeDT.default.FoT_Duration; Frozen[idx].FoT = Damage * FreezeDT.default.FoT_Ratio; } if ( Frozen[idx].CurrentFreeze >= Frozen[idx].FreezeTreshold ) { // enough damage to freeze zed FreezeZed(ZedVictim, idx); Frozen[idx].bFrozen = true; Frozen[idx].WarmTime = fmax(Frozen[idx].WarmTime, Level.TimeSeconds + 2.0); } else Frozen[idx].WarmTime = fmax(Frozen[idx].WarmTime, Level.TimeSeconds + 1.0); } } //debug // Level.GetLocalPlayerController().ClientMessage(Frozen[idx].CurrentFreeze $" / "$ Frozen[idx].FreezeTreshold); } else { idx = FrozenIndex(ZedVictim, false); if ( idx != -1 ) { if ( KFDamType.default.bDealBurningDamage ) { // fire instantly unfreezes zed if ( Frozen[idx].bFrozen ) UnfreezeZed(ZedVictim, idx); Frozen.remove(idx, 1); } else if ( Frozen[idx].bFrozen ) { if ( KFDamType.default.bCheckForHeadShots && class(KFDamType) == none ) { Damage *= KFDamType.default.HeadShotDamageMult; if ( KFDamType.default.bIsPowerWeapon || KFDamType.default.bIsMeleeDamage ) { Frozen[idx].Damage += Damage * FrozenDamageMultSG; if ( ClassIsChildOf(KFDamType, class'DamTypeChainsaw') && Level.TimeSeconds > NextChainsawTime ) { ProgressAchievement(PC, 'Freeze_Chainsaw', 1); ProgressAchievement(Frozen[idx].LastFrozenBy, 'Freeze_Chainsaw', 1); NextChainsawTime = Level.TimeSeconds + 1; } } else Frozen[idx].Damage += Damage * FrozenDamageMultHS; } else Frozen[idx].Damage += Damage * FrozenDamageMult; Damage *= FrozenDamageResistance; if ( ZedVictim.Health <= Damage + Frozen[idx].Damage ) { Damage += Frozen[idx].Damage; ShatterZed(ZedVictim, idx, instigatedBy.Controller, DamageType); } } } } } if ( NextGameRules != None ) return NextGameRules.NetDamage( OriginalDamage,Damage,injured,instigatedBy,HitLocation,Momentum,DamageType); return Damage; } function bool PreventDeath(Pawn Killed, Controller Killer, class damageType, vector HitLocation) { local KFMonster M; local PlayerController Zapper; if ( (NextGameRules != None) && NextGameRules.PreventDeath(Killed,Killer, damageType,HitLocation) ) return true; M = KFMonster(Killed); if ( M != none && M.ZappedBy != none ) Zapper = PlayerController(M.ZappedBy.Controller); // killing zapped zed gives perk progression to zapper if ( Zapper != none && SRStatsBase(Zapper.SteamStatsAndAchievements) != none && SRStatsBase(Zapper.SteamStatsAndAchievements).Rep != none ) SRStatsBase(Zapper.SteamStatsAndAchievements).Rep.ProgressCustomValue(Class'HTecProg', min(1, M.default.ZapThreshold * 4.01)); return false; } function bool IsFrozen(KFMonster M) { local int i; i = FrozenIndex(M, false); return i >= 0 && Frozen[i].bFrozen; } function int FrozenIndex(KFMonster M, bool bCreate) { local int i; for ( i=0; i < Frozen.length; ++i ) if ( Frozen[i].M == M ) return i; if ( bCreate ) { Frozen.insert(i, 1); Frozen[i].M = M; Frozen[i].FreezeTreshold = M.HealthMax * FreezeTreshold; Frozen[i].WarmTime = Level.TimeSeconds + 2.0; return i; } return -1; } function int GetFrozenCount() { local int i, c; for ( i=0; i < Frozen.length; ++i ) if ( Frozen[i].bFrozen && Frozen[i].M != none && Frozen[i].M.Health > 0 ) ++c; return c; } function Tick(float DeltaTime) { local int i; local bool bRemove; local byte FrozenCount; while ( i < Frozen.length ) { bRemove = Frozen[i].M == none || Frozen[i].M.Health <= 0; if ( !bRemove ) { if ( Frozen[i].FoT_Remaining > 0 ) { Frozen[i].CurrentFreeze += Frozen[i].FoT * DeltaTime; Frozen[i].FoT_Remaining -= DeltaTime; Frozen[i].WarmTime = fmax(Frozen[i].WarmTime, Level.TimeSeconds + 1.0); if ( !Frozen[i].bFrozen && Frozen[i].CurrentFreeze >= Frozen[i].FreezeTreshold ) { FreezeZed(Frozen[i].M, i); Frozen[i].bFrozen = true; } } else { if ( Frozen[i].CurrentFreeze > 0 && Level.TimeSeconds >= Frozen[i].WarmTime ) { Frozen[i].CurrentFreeze -= fmax(Frozen[i].CurrentFreeze * 0.2, 20.0) * DeltaTime; if ( Frozen[i].bFrozen && Frozen[i].CurrentFreeze < Frozen[i].FreezeTreshold ) { UnfreezeZed(Frozen[i].M, i); Frozen[i].bFrozen = false; Frozen[i].Damage = 0; Frozen[i].CurrentFreeze *= 0.80; } if ( Frozen[i].CurrentFreeze <= 0 ) bRemove = true; } } if ( Frozen[i].bFrozen ) { FrozenCount++; if ( Frozen[i].M.IsAnimating(0) || Frozen[i].M.IsAnimating(1) ) FreezeZed(Frozen[i].M, i); else if ( Frozen[i].M.Controller != none ) { // prevent rotating Frozen[i].M.Controller.FocalPoint = Frozen[i].FocalPoint; Frozen[i].M.Controller.Enemy = none; Frozen[i].M.Controller.Focus = none; } } } if ( bRemove ) Frozen.Remove(i, 1); else i++; } if ( FrozenCount != FreezeRI.FrozenCount ) { FreezeRI.FrozenCount = FrozenCount; FreezeRI.NetUpdateTime = Level.TimeSeconds - 1; } } // call it only if zed isn't frozen yet function FreezeZed(KFMonster M, int FrozenIndex) { if ( !Frozen[FrozenIndex].bWasFrozen ) { Frozen[FrozenIndex].bWasFrozen = true; if ( ZombieCrawler(M) != none && M.Physics == PHYS_Falling ) ProgressAchievement(Frozen[FrozenIndex].LastFrozenBy, 'Freeze_CR', 1); else if ( ZombieFleshpound(M) != none ) ProgressAchievement(Frozen[FrozenIndex].LastFrozenBy, 'Freeze_FP', 1); else if ( ZombieBoss(M) != none ) ProgressAchievement(Frozen[FrozenIndex].LastFrozenBy, 'Freeze_Pat', 1); ProgressAchievement(Frozen[FrozenIndex].LastFrozenBy, 'Freeze_IceAge', 1); } if ( Frozen.length >= 20 && GetFrozenCount() >= 20 ) ScrnGameRules.ProgressAchievementForAllPlayers('Freeze_Festival', 1, true); M.SetOverlayMaterial(FreezeRI.FrozenMaterial, 999, true); Frozen[FrozenIndex].FocalPoint = M.Location + 512*vector(M.Rotation); M.Velocity = M.PhysicsVolume.Gravity; M.AnimAction = ''; M.bShotAnim = true; M.bWaitForAnim = true; M.Disable('AnimEnd'); class'FreezeReplicationInfo'.static.RemoveAnimations(M); M.Acceleration = vect(0, 0, 0); M.SetTimer(0, false); M.StopAnimating(); if ( M.HeadRadius > 0 ) M.HeadRadius = -M.HeadRadius; // disable headshots if ( M.Controller != none ) { M.Controller.FocalPoint = Frozen[FrozenIndex].FocalPoint; M.Controller.Enemy = none; M.Controller.Focus = none; if ( !M.Controller.IsInState('WaitForAnim') ) M.Controller.GoToState('WaitForAnim'); KFMonsterController(M.Controller).bUseFreezeHack = True; } } function UnfreezeZed(KFMonster M, int FrozenIndex) { if ( M == none || M.Controller == none || M.Health <= 0 ) return; M.SetOverlayMaterial(none, 0.1, true); if ( M.HeadRadius < 0 ) M.HeadRadius = -M.HeadRadius; //enable headshots M.bShotAnim = false; M.bWaitForAnim = false; M.Enable('AnimEnd'); class'FreezeReplicationInfo'.static.RestoreAnimations(M); M.AnimEnd(1); M.AnimEnd(0); //KFMonsterController(M.Controller).WhatToDoNext(99); M.Controller.GotoState('ZombieHunt'); M.GroundSpeed = M.GetOriginalGroundSpeed(); // this should "wake up" raged scrakes M.TakeDamage(1, M, M.Location, vect(0,0,0), class'DamTypeFreezerBase'); } function bool ShatterZed(KFMonster ZedVictim, int FrozenIdx, Controller Killer, class DamageType) { local vector loc; local rotator r; local FrozenDosh Dosh, PrevDosh, HeadDosh; local int i; local int MaxDosh; local KFPlayerReplicationInfo KFPRI; local SRStatsBase FreezerStats; if ( FrozenIdx < 0 ) FrozenIdx = FrozenIndex(ZedVictim, false); if ( FrozenIdx < 0 ) return false; // zed is not frozen if ( Killer != none ) KFPRI = KFPlayerReplicationInfo(Killer.PlayerReplicationInfo); FreezerStats = SRStatsBase(Frozen[FrozenIdx].LastFrozenBy.SteamStatsAndAchievements); loc = ZedVictim.Location; if ( bDosh && !ZedVictim.bDecapitated && DamageType != class'SirenScreamDamage' ) { if ( KFPRI != none && KFPRI.ClientVeteranSkill != none ) MaxDosh = KFPRI.ClientVeteranSkill.static.GetCostScaling(KFPRI, class'FrozenDosh') * 0.2 * ZedVictim.ScoringValue; else MaxDosh = ZedVictim.ScoringValue / 5; } if ( LastShatteredBy == Killer && LastShaterTime == Level.TimeSeconds ) { if ( ++InstantShatterCounter == 10 ) { ProgressAchievement(PlayerController(Killer), 'Freeze_Storm', 1); InstantShatterCounter = 0; } } else { LastShatteredBy = PlayerController(Killer); LastShaterTime = Level.TimeSeconds; InstantShatterCounter = 1; } if ( FreezerStats != none && !ZedVictim.bDecapitated ) { if (ZedVictim.default.Health >= 1500) i = 4; if (ZedVictim.default.Health >= 1000) i = 3; else if (ZedVictim.default.Health >= 500) i = 2; else i = 1; FreezerStats.Rep.ProgressCustomValue(Class'HTecProg', i); if ( LastShatteredBy != none && LastShatteredBy != Frozen[FrozenIdx].LastFrozenBy ) { class'ScrnAchievements'.static.ProgressAchievementByID(FreezerStats.Rep, 'Freeze_ShatterZeds', 1); ProgressAchievement(LastShatteredBy, 'Freeze_ShatterZeds', 1); } } if ( ZombieBoss(ZedVictim) != none ) ScrnGameRules.ProgressAchievementForAllPlayers('Freeze_ShatterPat', 1, true); Spawn(ShatteredIce,,,loc); ZedVictim.bHidden = true; ZedVictim.Died(Killer, DamageType, loc ); // DO$H DO$H DO$H DO$H DO$H if ( MaxDosh > 0 ) { r = ZedVictim.GetViewRotation(); for ( i=0; i<5; ++i ) { Dosh = Spawn(class'FrozenDosh',,, loc); if ( Dosh != none ) { // link all doshes together if ( HeadDosh == none ) HeadDosh = Dosh; Dosh.HeadDosh = HeadDosh; if ( PrevDosh != none ) PrevDosh.NextDosh = Dosh; Dosh.CashAmount = 1 + rand(MaxDosh); Dosh.RespawnTime = 0; Dosh.bDroppedCash = True; if ( Killer == Frozen[FrozenIdx].LastFrozenBy ) Dosh.DroppedBy = Killer; Dosh.Velocity = Vector(r) * 100; Dosh.Velocity.Z += 500; Dosh.InitDroppedPickupFor(None); PrevDosh = Dosh; } r.Yaw += 13107; } } return true; } function bool ProgressAchievement(PlayerController PC, name ID, int Prog) { if ( PC != none && SRStatsBase(PC.SteamStatsAndAchievements) != none ) return class'ScrnAchievements'.static.ProgressAchievementByID(SRStatsBase(PC.SteamStatsAndAchievements).Rep, ID, Prog); return false; } defaultproperties { FreezeTreshold=0.333333 FrozenDamageResistance=0.500000 FrozenDamageMult=1.500000 FrozenDamageMultHS=4.500000 FrozenDamageMultSG=2.500000 ShatteredIce=Class'ScrnHTec.IceChunkEmitter' bDosh=True }