// Alternate Burning Mechanism // Big thanx to Scary Ghost for inspiring me to do it this way class ScrnBurnMech extends Info; struct BurningMonster { var KFMonster Victim; var Pawn Instigator; var int BurnDown; var int BurnDamage; var class FireDamageClass; var float NextBurnTime; var int BurnTicks; //how many ticks zed is already burning var int FlareCount; //how many flares are burning inside a zed var int TotalInputDamage; // total amount of incoming damage }; var array Monsters; var array FlareClouds; var int BurnDuration; // tick count from ignition till the end of burning var int BurnInCount; // tick count from ignition till reaching the maximum burn damage var float BurnPeriod; // how often zeds will receive damage from burning var(Sound) Sound FlareSound; var string FlareSoundRef; var bool bOutputDamage; function PostBeginPlay() { FlareSound = sound(DynamicLoadObject(FlareSoundRef, class'Sound', true)); } function int FindMonsterIndex(KFMonster Monster ) { local int i; for ( i = 0; i < Monsters.Length; ++i ) { if ( Monsters[i].Victim == Monster ) return i; } return -1; } function int GetAvgBurnDamage(int BurnDown, int InitialDamage) { local float AvgTickInc; // Fire damage is increasing by (3-4 points per tick) * 1.5. 10 ticks total. Average = sum / 2 = 18. // Ignition takes 2 ticks, then average, constant damage is applied till the end of burning process // Total DoT is weaker comparing to original game due to 2 less ticks and burn-in damage decrement if ( class'ScrnBalance'.default.Mut.bHardcore || BurnDown >= BurnDuration ) AvgTickInc = 6; else if ( BurnDown > (BurnDuration - BurnInCount) ) AvgTickInc = 12; else AvgTickInc = 18; return InitialDamage + AvgTickInc; } function FlareMonster(int idx) { local ScrnPlayerController InstigatorController; local ScrnPlayerController PC; local Controller C; Monsters[idx].Victim.AmbientSound = FlareSound; if ( Monsters[idx].Instigator != none ) InstigatorController = ScrnPlayerController(Monsters[idx].Instigator.Controller); for ( C = Level.ControllerList; C != none; C = C.NextController ) { PC = ScrnPlayerController(C); if ( PC != none && (PC == InstigatorController || PC.LineOfSightTo(Monsters[idx].Victim)) ) { PC.ClientFlareDamage(Monsters[idx].Victim, Monsters[idx].BurnDamage/4, Monsters[idx].BurnDown); } } } function StopFlareFX(KFMonster Monster) { if ( Monster.AmbientSound == FlareSound ) Monster.AmbientSound = none; } function StopBurningBehavior(KFMonster Monster) { Monster.SetTimer(0, false); Monster.bSTUNNED = false; // maybe timer was set to turn it off? Monster.bBurnified = false; Monster.BurnDown = 0; //if monster is raged don't slow him down Monster.GroundSpeed = max(Monster.GroundSpeed, Monster.default.GroundSpeed); Monster.UnSetBurningBehavior(); Monster.RemoveFlamingEffects(); Monster.StopBurnFX(); StopFlareFX(Monster); } function MakeBurnDamage(KFMonster Victim, int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class DamType, optional int HitIndex) { local bool bIncDamage; local int OldHealth; local int i, FreeSlot; local class FlareDamType; local float AdjustedFlareCount; // ScrnDamTypeHuskGun_Alt required for achievement bIncDamage = ClassIsChildOf(DamType, class'DamTypeMAC10MPInc') || DamType == class'ScrnBalanceSrv.ScrnDamTypeHuskGun_Alt'; FlareDamType = class(DamType); // check if zed is already burning FreeSlot = Monsters.Length; for( i = 0; i < Monsters.Length; ++i ) { if ( Monsters[i].Victim == none ) { if ( FreeSlot == Monsters.Length ) FreeSlot = i; } else if ( Monsters[i].Victim == Victim // Flare and regular burn damages occupy different slots && (FlareDamType == none) == (Monsters[i].FlareCount == 0) ) { if ( FlareDamType != none ) { // Flares make incremental damage, i.e. each next flare raises damage per tick // Adjust flare count for this amount of damage. // For instance, monster was hit by 4 small flares (15 damage each) and now hit by // a big flare which causing 60 damage. Total flare count = 5, but if scaled by current damage, // then adjusted flare count = 2. AdjustedFlareCount = float(Monsters[i].TotalInputDamage) / Damage; if ( bOutputDamage && InstigatedBy != none && PlayerController(InstigatedBy.Controller) != none ) PlayerController(InstigatedBy.Controller).ClientMessage( "AdjustedFlareCount="$AdjustedFlareCount @ "TotalInputDamage="$Monsters[i].TotalInputDamage @ "Damage="$Damage ); Monsters[i].BurnDown += FlareDamType.default.BurnTimeBoost; Monsters[i].BurnDown = max(Monsters[i].BurnDown, FlareDamType.default.MinBurnTime + AdjustedFlareCount * FlareDamType.default.BurnTimeInc); Monsters[i].BurnDown = min(Monsters[i].BurnDown, FlareDamType.default.MaxBurnTime); Monsters[i].BurnDamage += max(Damage * FlareDamType.default.iDoT_MinBoostRatio, Damage / (1.0 + AdjustedFlareCount * FlareDamType.default.iDoT_FadeFactor)); Monsters[i].FlareCount++; } else if ( Damage >= Monsters[i].BurnDamage * 0.8 ) { //lower fire damage can't increase burn ticks, e.g. shooting with MAC10 after Husk gun //received extra burn damage while still burning Monsters[i].BurnDown = max(Monsters[i].BurnDown, BurnDuration - BurnInCount); //increase burn time to the maximum again (excluding 2 burn-in ticks) Monsters[i].BurnDamage = max(Damage, Monsters[i].BurnDamage); if ( bIncDamage ) Monsters[i].FireDamageClass = DamType; else Monsters[i].FireDamageClass = class'DamTypeFlamethrower'; } Monsters[i].Instigator = instigatedBy; Monsters[i].TotalInputDamage += Damage; Victim.BurnDown = Monsters[i].BurnDown; Victim.TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType, HitIndex); if ( Victim.Health <= 0 ) StopBurningBehavior(Victim); else if ( FlareDamType != none ) FlareMonster(i); return; } } // If we've reached here, zed isn't burning yet. // First we need to check if zed can be set on fire, otherwise don't apply burning mechanism // on it (e.g. some Doom3 monsters are immune to fire). if ( FlareDamType != none ) { Victim.HeatAmount = 5; // just to be sure this gay feature don't block zed's ignition } OldHealth = Victim.Health; //damage zed and check if it's burning Victim.TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType, HitIndex); if ( bOutputDamage && InstigatedBy != none && PlayerController(InstigatedBy.Controller) != none ) PlayerController(InstigatedBy.Controller).ClientMessage( "Initial Damage: " $ Damage $ "/" $ String(OldHealth - Victim.Health) @ " -> "@ Victim.MenuName); //store burned damage in this variable. It is of Byte type, so can't write big values into it //Victim.HeatAmount = min(127, Victim.HeatAmount + OldHealth - Victim.Health); if ( !Victim.bBurnified && Victim.BurnDown == 0 ) return; // can't be set on fire if ( Victim.Health <= 0 ) { StopBurningBehavior(Victim); return; } // if we reached here, zed is ignited and should burning - OUR WAY // KFMonster.Timer() makes fire damage based on LastBurnDamage + 3 + rand(2) // if player is lucky, monster will take another +1 damage per tick :) Victim.SetTimer(0, false); //disable timer, cuz we'll be using our own Victim.LastBurnDamage = -3; Victim.BurnDown = BurnDuration; //add new record i = FreeSlot; if ( i == Monsters.Length ) { Monsters.insert( i, 1 ); } Monsters[i].Victim = Victim; Monsters[i].Instigator = InstigatedBy; Monsters[i].NextBurnTime = Level.TimeSeconds + BurnPeriod; Monsters[i].BurnTicks = 0; Monsters[i].BurnDamage = Damage; Monsters[i].TotalInputDamage = Damage; if ( FlareDamType != none ) { Monsters[i].FireDamageClass = FlareDamType; Monsters[i].BurnDown = FlareDamType.default.MinBurnTime; Monsters[i].FlareCount = 1; FlareMonster(i); } else { if ( bIncDamage ) Monsters[i].FireDamageClass = DamType; else Monsters[i].FireDamageClass = class'DamTypeFlamethrower'; Monsters[i].BurnDown = BurnDuration; Monsters[i].FlareCount = 0; } if ( !bTimerLoop ) { SetTimer(0.1, true); } } function Timer() { local int i, EmptyTailBegin; local int OldHealth; local int Damage; local KFMonster M; if ( Monsters.Length == 0 ) { SetTimer(0, false); return; } EmptyTailBegin = Monsters.Length; for ( i = 0; i < Monsters.Length; ++i ) { if ( Monsters[i].Victim != none && Monsters[i].Victim.Health <= 0 ) { StopBurningBehavior(Monsters[i].Victim); Monsters[i].Victim = none; } if ( Monsters[i].Victim == none ) { if ( EmptyTailBegin == Monsters.Length ) { EmptyTailBegin = i; } continue; } EmptyTailBegin = Monsters.Length; M = Monsters[i].Victim; M.SetTimer(0, false); // ensure that timer is disabled if ( Monsters[i].NextBurnTime < Level.TimeSeconds ) { M.bSTUNNED = false; // manualy remove flinching effect here, cuz we disabled the timer M.BurnDown = max(Monsters[i].BurnDown, 2); //can't be less, or TakeFireDamage() will decrease it to 0 and turn off burning if ( Monsters[i].FlareCount > 0 ) Damage = Monsters[i].BurnDamage; // flares don't use burn in else Damage = GetAvgBurnDamage(Monsters[i].BurnDown, Monsters[i].BurnDamage); OldHealth = Monsters[i].Victim.Health; M.TakeDamage(Damage*BurnPeriod, Monsters[i].Instigator, M.Location, vect(0, 0, 0), Monsters[i].FireDamageClass); M.LastBurnDamage = -3; //reset to default if ( bOutputDamage && Monsters[i].Instigator != none && PlayerController(Monsters[i].Instigator.Controller) != none) PlayerController(Monsters[i].Instigator.Controller).ClientMessage( "Fire DoT: " $ String(Damage)$ "/" $ String(OldHealth - M.Health) @ " -> "@ M.MenuName @ "("$String(i+1)$"/"$Monsters.Length$"). Burns = " $ String(Monsters[i].BurnDown) ); Monsters[i].BurnDown--; Monsters[i].BurnTicks++; Monsters[i].NextBurnTime += BurnPeriod; if ( Monsters[i].FlareCount == 0 && Monsters[i].BurnTicks * BurnPeriod > 10 - M.CrispUpThreshhold ) M.ZombieCrispUp(); // Melt em' :) if ( M.Health <= 0 ) { Monsters[i].Victim = none; StopBurningBehavior(M); } else if ( Monsters[i].BurnDown <= 0 ) { Monsters[i].Victim = none; if ( FindMonsterIndex(M) == -1 ) { StopBurningBehavior(M); } else if ( Monsters[i].FlareCount > 0 ) { StopFlareFX(M); // flares burned down, but other burning continues } } } } if ( EmptyTailBegin != Monsters.Length ) { // Remove empty records from the end of the monsters array. // Shouldn't do it in the middle of array to prevent record shifts in memory. Monsters.Length = EmptyTailBegin; } } defaultproperties { BurnDuration=8 BurnInCount=2 BurnPeriod=1.000000 FlareSoundRef="KF_IJC_HalloweenSnd.KF_FlarePistol_Projectile_Loop" }