1
0
KF2-Dev-Scripts/KFGame/Classes/KFAfflictionManager.uc
2021-03-02 14:56:51 +03:00

748 lines
23 KiB
Ucode

//=============================================================================
// KFAfflictionManager
//=============================================================================
// Handles negative status effects that can be applied to a KFPawn
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFAfflictionManager extends Object within KFPawn
native(Pawn);
const STUN_GUARANTEED_POWER = 10000.f;
/** Abstracted body parts that can be associated with multiple zones */
enum EHitZoneBodyPart
{
BP_Torso, // default to generic body shot
BP_Head,
BP_LeftArm,
BP_RightArm,
BP_LeftLeg,
BP_RightLeg,
BP_Special,
};
/** Index into IncapSettingsInfo.Vulnerability[] */
enum EAfflictionVulnerabilityType
{
AV_Default,
AV_Head,
AV_Legs,
AV_Arms,
AV_Special,
};
/*********************************************************************************************
* Affliction Classes
********************************************************************************************* */
/**
* Full body incap with stackable IncapPower
* @todo: this data is now all static and should be moved to an archetype or to KFAffliction
*/
struct native IncapSettingsInfo
{
/** How long this incap lasts once triggered. Only applies to non-specialmove entries */
var() float Duration;
/** How long this pawn is immune to additional incap of this type. If > 0, resets strength to zero on activation */
var() float Cooldown;
/** How long this pawn is immune to additional incap of children of this type */
var() float ChildAfflictionCooldown;
/** Array mapped to EHitZoneBodyPart. If out of bounds default to body (index 0) */
var() array<float> Vulnerability;
structdefaultproperties
{
Duration=5.0
}
};
/* Types of stacking afflictions that are used to index the IncapSettings array */
enum EAfflictionType
{
/** Place most common afflictions at top because array will resize up to the enum value */
/** All Pawns */
AF_EMP,
AF_FirePanic,
/** hit reactions (flinch) */
AF_MeleeHit,
AF_GunHit,
/** common */
AF_Stumble,
AF_Stun,
AF_Poison,
AF_Snare,
/** uncommon */
AF_Knockdown,
AF_Freeze,
AF_Microwave,
AF_Bleed,
AF_Custom1,
AF_Custom2,
AF_Custom3,
/** Dummy entry to avoid AF_MAX native collision */
EAfflictionType_Blank
};
/** Assign relevant class to each affliction corresponding to EAfflictionType */
var array< class<KFAfflictionBase> > AfflictionClasses;
/** Intanced affliction one per EAfflictionType that remain for the Pawn's lifespawn */
var array<KFAfflictionBase> Afflictions;
/** Afflictions that need updating each tick (e.g. Fire Panic) */
var array<KFAfflictionBase> AfflictionTickArray;
/** Debugging */
var bool bDebugLog;
/*********************************************************************************************
* Specific settings that have not been fully converted to the new system
********************************************************************************************* */
/** How long does a pawn have to be burning over the heat threshhold to get to fully charred skin */
var float FireFullyCharredDuration;
/** When over this % on the FirePanicResist.StackedPower, apply charring to the skin shader. Think of it like how "hot" the character needs to get before its shader gets char applied */
var float FireCharPercentThreshhold;
cpptext
{
/** Stacking afflictions must decay Power and check if Duration is over. This is done
* in c++ for speed, but could be done in script for custom afflictions */
/** SERVER ONLY - Clears or stacked incap effects when they need to be deactivated */
virtual void TickStackedIncapEffects(FLOAT DeltaTime, AKFPawn* P);
}
/*********************************************************************************************
* @name Take Damage
********************************************************************************************* */
/** Check, and if needed activate afflictions after being hit (Server only) */
function NotifyTakeHit(Controller DamageInstigator, vector HitDir, class<KFDamageType> DamageType, Actor DamageCauser)
{
local KFPerk InstigatorPerk;
if( DamageType == none )
{
return;
}
// Allow damage instigator perk to modify reaction
if ( DamageInstigator != None && DamageInstigator.bIsPlayer )
{
InstigatorPerk = KFPlayerController(DamageInstigator).GetPerk();
}
// For now all below effects are for Zeds
if( GetTeamNum() > 254 && !bPlayedDeath )
{
ProcessSpecialMoveAfflictions(InstigatorPerk, HitDir, DamageType, DamageCauser);
ProcessHitReactionAfflictions(InstigatorPerk, DamageType, DamageCauser);
}
ProcessEffectBasedAfflictions(InstigatorPerk, DamageType, DamageCauser);
}
/**
* Client side test to predict whether or not DoPauseAI is called. May not always
* be correct but as long as we use split-body anims it will still look okay
* @note: With the new cumulative system this is completely artificial
*/
function byte GetPredictedHitReaction(class<KFDamageType> DamageType, EHitZoneBodyPart BodyPart)
{
// This may not always match the server's result, but as long
// as we avoid playing FullBody anims it should look okay even if DoPauseAI is skipped.
if ( DamageType.default.MeleeHitPower > 0 )
{
return HIT_Heavy;
}
else if ( DamageType.default.GunHitPower > 0 )
{
return HIT_Medium;
}
return HIT_Light;
}
/*********************************************************************************************
* @name Internal (On Hit)
********************************************************************************************* */
/** Reaction based afflictions only apply to living pawns */
protected function ProcessSpecialMoveAfflictions(KFPerk InstigatorPerk, vector HitDir, class<KFDamageType> DamageType, Actor DamageCauser)
{
local EHitZoneBodyPart BodyPart;
local byte HitZoneIdx;
local float KnockdownPower, StumblePower, StunPower, SnarePower, FreezePower;
local float KnockdownModifier, StumbleModifier, StunModifier;
local KFInterface_DamageCauser KFDmgCauser;
local KFWeapon DamageWeapon;
// This is for damage over time, DoT shall never have momentum
if (IsZero(HitDir))
{
return;
}
HitZoneIdx = HitFxInfo.HitBoneIndex;
BodyPart = (HitZoneIdx != 255 && HitZoneIdx < HitZones.Length) ? HitZones[HitZoneIdx].Limb : BP_Torso;
// Get upgraded affliction power
DamageWeapon = class'KFPerk'.static.GetWeaponFromDamageCauser(DamageCauser);
if (DamageWeapon != none)
{
KnockdownPower = DamageWeapon.GetUpgradedAfflictionPower(AF_Knockdown, DamageType.default.KnockdownPower);
StumblePower = DamageWeapon.GetUpgradedAfflictionPower(AF_Stumble, DamageType.default.StumblePower);
StunPower = DamageWeapon.GetUpgradedAfflictionPower(AF_Stun, DamageType.default.StunPower);
SnarePower = DamageWeapon.GetUpgradedAfflictionPower(AF_Snare, DamageType.default.SnarePower);
FreezePower = DamageWeapon.GetUpgradedAfflictionPower(AF_Freeze, DamageType.default.FreezePower);
}
else
{
KnockdownPower = DamageType.default.KnockdownPower;
StumblePower = DamageType.default.StumblePower;
StunPower = DamageType.default.StunPower;
SnarePower = DamageType.default.SnarePower;
FreezePower = DamageType.default.FreezePower;
}
KFDmgCauser = KFInterface_DamageCauser(DamageCauser);
if (KFDmgCauser != None)
{
KnockdownPower *= KFDmgCauser.GetIncapMod();
StumblePower *= KFDmgCauser.GetIncapMod();
StunPower *= KFDmgCauser.GetIncapMod();
SnarePower *= KFDmgCauser.GetIncapMod();
}
KnockdownModifier = 1.f;
StumbleModifier = 1.f;
StunModifier = 1.f;
// Allow for known afflictions to adjust reaction
KnockdownModifier += GetAfflictionKnockdownModifier();
StumbleModifier += GetAfflictionStumbleModifier();
StunModifier += GetAfflictionStunModifier();
// Allow damage instigator perk to modify reaction
if (InstigatorPerk != None)
{
KnockdownModifier += InstigatorPerk.GetKnockdownPowerModifier( DamageType, BodyPart, bIsSprinting );
StumbleModifier += InstigatorPerk.GetStumblePowerModifier( Outer, DamageType,, BodyPart );
StunModifier += InstigatorPerk.GetStunPowerModifier( DamageType, HitZoneIdx );
//Snare power doesn't scale DT, it exists on its own (Ex: Gunslinger Skullcracker)
SnarePower += InstigatorPerk.GetSnarePowerModifier( DamageType, HitZoneIdx );
}
KnockdownPower *= KnockdownModifier;
StumblePower *= StumbleModifier;
StunPower *= StunModifier;
// [RMORENO @ SABER3D] //Overriding stun power with a High number so we can assure the stun independently of weapon, Zed resistances, body part hit. This does NOT ignores other factors like cooldowns.
if (InstigatorPerk != None && InstigatorPerk.IsStunGuaranteed( DamageType, HitZoneIdx ))
{
StunPower = STUN_GUARANTEED_POWER;
}
// increment affliction power
if (KnockdownPower > 0 && CanDoSpecialmove(SM_Knockdown))
{
AccrueAffliction(AF_Knockdown, KnockdownPower, BodyPart, InstigatorPerk);
}
if (StunPower > 0 && CanDoSpecialmove(SM_Stunned))
{
AccrueAffliction(AF_Stun, StunPower, BodyPart, InstigatorPerk);
}
if (StumblePower > 0 && CanDoSpecialmove(SM_Stumble))
{
AccrueAffliction(AF_Stumble, StumblePower, BodyPart, InstigatorPerk);
}
if (FreezePower > 0 && CanDoSpecialMove(SM_Frozen))
{
AccrueAffliction(AF_Freeze, FreezePower, BodyPart, InstigatorPerk);
}
if (SnarePower > 0)
{
AccrueAffliction(AF_Snare, SnarePower, BodyPart, InstigatorPerk);
}
}
/** Afflications which pause AI behavior temporarily */
protected function ProcessHitReactionAfflictions(KFPerk InstigatorPerk, class<KFDamageType> DamageType, Actor DamageCauser)
{
local EHitZoneBodyPart BodyPart;
local byte HitZoneIdx;
local float ReactionModifier, MeleeHitPower, GunHitPower;
local KFWeapon DamageWeapon;
local KFInterface_DamageCauser KFDmgCauser;
ReactionModifier = 1.f;
// Allow damage instigator perk to modify reaction
if (InstigatorPerk != None)
{
ReactionModifier = InstigatorPerk.GetReactionModifier(DamageType);
}
// Finally, 'Pause' the AI if we're going to play a medium or heavy hit reaction anim in TryPlayHitReactionAnim
if (MyKFAIC != None)
{
HitZoneIdx = HitFxInfo.HitBoneIndex;
BodyPart = (HitZoneIdx != 255 && HitZoneIdx < HitZones.Length) ? HitZones[HitZoneIdx].Limb : BP_Torso;
// Get upgraded affliction power
DamageWeapon = class'KFPerk'.static.GetWeaponFromDamageCauser(DamageCauser);
if (DamageWeapon != none)
{
MeleeHitPower = DamageWeapon.GetUpgradedAfflictionPower(AF_MeleeHit, DamageType.default.MeleeHitPower);
GunHitPower = DamageWeapon.GetUpgradedAfflictionPower(AF_GunHit, DamageType.default.GunHitPower);
}
else
{
MeleeHitPower = DamageType.default.MeleeHitPower;
GunHitPower = DamageType.default.GunHitPower;
}
KFDmgCauser = KFInterface_DamageCauser(DamageCauser);
if (KFDmgCauser != None)
{
MeleeHitPower *= KFDmgCauser.GetIncapMod();
GunHitPower *= KFDmgCauser.GetIncapMod();
}
// Check hard hit reaction
if (MeleeHitPower > 0)
{
AccrueAffliction(AF_MeleeHit, MeleeHitPower * ReactionModifier, BodyPart, InstigatorPerk);
}
// Force recovery time for the headless hit. GetTimerCount() is a dirty way to do this only on the frame of CauseHeadTrauma()
if (HitZoneIdx == HZI_Head && IsHeadless() && GetTimerCount('BleedOutTimer', Outer) == 0.f)
{
AccrueAffliction(AF_MeleeHit, 100.f, BodyPart, InstigatorPerk);
}
// Check medium hit reaction
if (GunHitPower > 0)
{
AccrueAffliction(AF_GunHit, GunHitPower * ReactionModifier, BodyPart, InstigatorPerk);
}
}
}
/** Effect based afflictions can apply even on dead bodies */
protected function ProcessEffectBasedAfflictions(KFPerk InstigatorPerk, class<KFDamageType> DamageType, Actor DamageCauser)
{
local KFWeapon DamageWeapon;
local float BurnPower, EMPPower, PoisonPower, MicrowavePower, BleedPower;
local KFInterface_DamageCauser KFDmgCauser;
// Get upgraded affliction power
DamageWeapon = class'KFPerk'.static.GetWeaponFromDamageCauser(DamageCauser);
if (DamageWeapon != none)
{
BurnPower = DamageWeapon.GetUpgradedAfflictionPower(AF_FirePanic, DamageType.default.BurnPower);
EMPPower = DamageWeapon.GetUpgradedAfflictionPower(AF_EMP, DamageType.default.EMPPower);
PoisonPower = DamageWeapon.GetUpgradedAfflictionPower(AF_Poison, DamageType.default.PoisonPower);
MicrowavePower = DamageWeapon.GetUpgradedAfflictionPower(AF_Microwave, DamageType.default.MicrowavePower);
BleedPower = DamageWeapon.GetUpgradedAfflictionPower(AF_Bleed, DamageType.default.BleedPower);
}
else
{
BurnPower = DamageType.default.BurnPower;
EMPPower = DamageType.default.EMPPower;
PoisonPower = DamageType.default.PoisonPower;
MicrowavePower = DamageType.default.MicrowavePower;
BleedPower = DamageType.default.BleedPower;
}
KFDmgCauser = KFInterface_DamageCauser(DamageCauser);
if (KFDmgCauser != None)
{
BurnPower *= KFDmgCauser.GetIncapMod();
EMPPower *= KFDmgCauser.GetIncapMod();
PoisonPower *= KFDmgCauser.GetIncapMod();
MicrowavePower *= KFDmgCauser.GetIncapMod();
BleedPower *= KFDmgCauser.GetIncapMod();
}
// these afflictions can apply on killing blow, but fire can apply after death
if (bPlayedDeath && WorldInfo.TimeSeconds > TimeOfDeath)
{
// If we're already dead, go ahead and apply burn stacking power, just
// so we can do the burn effects
if (BurnPower > 0)
{
AccrueAffliction(AF_FirePanic, BurnPower);
}
}
else
{
if (EMPPower > 0)
{
AccrueAffliction(AF_EMP, EMPPower);
}
else if (InstigatorPerk != none && InstigatorPerk.ShouldGetDaZeD(DamageType))
{
AccrueAffliction(AF_EMP, InstigatorPerk.GetDaZedEMPPower());
}
if (BurnPower > 0)
{
AccrueAffliction(AF_FirePanic, BurnPower);
}
if (PoisonPower > 0 || DamageType.static.AlwaysPoisons())
{
AccrueAffliction(AF_Poison, PoisonPower);
}
if (MicrowavePower > 0)
{
AccrueAfflictionMicrowave(AF_Microwave, MicrowavePower, DamageType.default.bHasToSpawnMicrowaveFire);
}
if (BleedPower > 0)
{
AccrueAffliction(AF_Bleed, BleedPower);
}
}
}
/*********************************************************************************************
* @name Stacked / Accumulated Afflications
********************************************************************************************* */
/**
* Adds StackedPower
* @return true if the affliction effect should be applied
*/
function AccrueAffliction(EAfflictionType Type, float InPower, optional EHitZoneBodyPart BodyPart, optional KFPerk InstigatorPerk)
{
if ( InPower <= 0 || Type >= IncapSettings.Length )
{
return; // immune
}
if ( !VerifyAfflictionInstance(Type, InstigatorPerk) )
{
return; // cannot create instance
}
// for radius damage apply falloff using most recent HitFxInfo
if ( HitFxInfo.bRadialDamage && HitFxRadialInfo.RadiusDamageScale != 255 )
{
InPower *= ByteToFloat(HitFxRadialInfo.RadiusDamageScale);
`log(Type@"Applied damage falloff modifier of"@ByteToFloat(HitFxRadialInfo.RadiusDamageScale), bDebugLog);
}
// scale by character vulnerability
if ( IncapSettings[Type].Vulnerability.Length > 0 )
{
InPower *= GetAfflictionVulnerability(Type, BodyPart);
`log(Type@"Applied hit zone vulnerability modifier of"@GetAfflictionVulnerability(Type, BodyPart)@"for"@BodyPart, bDebugLog);
}
// allow owning pawn final adjustment
AdjustAffliction(InPower);
if ( InPower > 0 )
{
Afflictions[Type].Accrue(InPower);
}
}
/**
* Adds StackedPower
* @return true if the affliction effect should be applied
*/
function AccrueAfflictionMicrowave(EAfflictionType Type, float InPower, bool bHasToSpawnFire, optional EHitZoneBodyPart BodyPart, optional KFPerk InstigatorPerk)
{
if ( InPower <= 0 || Type >= IncapSettings.Length )
{
return; // immune
}
if ( !VerifyAfflictionInstance(Type, InstigatorPerk) )
{
return; // cannot create instance
}
// for radius damage apply falloff using most recent HitFxInfo
if ( HitFxInfo.bRadialDamage && HitFxRadialInfo.RadiusDamageScale != 255 )
{
InPower *= ByteToFloat(HitFxRadialInfo.RadiusDamageScale);
`log(Type@"Applied damage falloff modifier of"@ByteToFloat(HitFxRadialInfo.RadiusDamageScale), bDebugLog);
}
// scale by character vulnerability
if ( IncapSettings[Type].Vulnerability.Length > 0 )
{
InPower *= GetAfflictionVulnerability(Type, BodyPart);
`log(Type@"Applied hit zone vulnerability modifier of"@GetAfflictionVulnerability(Type, BodyPart)@"for"@BodyPart, bDebugLog);
}
// allow owning pawn final adjustment
AdjustAffliction(InPower);
if ( InPower > 0 )
{
KFAffliction_Microwave(Afflictions[Type]).bHasToSpawnFire = bHasToSpawnFire;
Afflictions[Type].Accrue(InPower);
}
}
/** Returns an index into the vulnerabilities array based on what part of the body was hit */
simulated function float GetAfflictionVulnerability(EAfflictionType i, EHitZoneBodyPart BodyPart)
{
local EAfflictionVulnerabilityType j;
switch(BodyPart)
{
case BP_Head:
j = AV_Head;
break;
case BP_LeftArm:
case BP_RightArm:
j = AV_Arms;
break;
case BP_LeftLeg:
case BP_RightLeg:
j = AV_Legs;
break;
case BP_Special:
j = AV_Special;
break;
}
if ( j > AV_Default && j < IncapSettings[i].Vulnerability.Length )
{
return IncapSettings[i].Vulnerability[j];
}
return IncapSettings[i].Vulnerability[AV_Default];
}
/** Called whenever we need may need to initiatize the affliction class instance */
simulated function bool VerifyAfflictionInstance(EAfflictionType Type, optional KFPerk InstigatorPerk)
{
if( Type >= Afflictions.Length || Afflictions[Type] == None )
{
if( Type < AfflictionClasses.Length && AfflictionClasses[Type] != None )
{
Afflictions[Type] = new(Outer) AfflictionClasses[Type];
// Cache a reference to the owner to avoid passing parameters around.
Afflictions[Type].Init(Outer, Type, InstigatorPerk);
}
else
{
`log(GetFuncName() @ "Failed with afflication:" @ Type @ "class:" @ AfflictionClasses[Type] @ Self);
Afflictions[Type] = None;
return FALSE;
}
}
return true;
}
/** Accessor to get affliction duration for attack cooldowns in verus*/
function float GetAfflictionDuration( EAfflictionType Type )
{
if( Type < IncapSettings.Length )
{
return IncapSettings[Type].Duration;
}
}
/** Accessor to get known affliction knockdown modifier */
function float GetAfflictionKnockdownModifier()
{
local float KnockdownModifier;
local int i;
KnockdownModifier = 0.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
KnockdownModifier += Afflictions[i].GetKnockdownModifier();
}
}
return KnockdownModifier;
}
/** Accessor to get known affliction Stumble modifier */
function float GetAfflictionStumbleModifier()
{
local float StumbleModifier;
local int i;
StumbleModifier = 0.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
StumbleModifier += Afflictions[i].GetStumbleModifier();
}
}
return StumbleModifier;
}
/** Accessor to get known affliction Stun modifier */
function float GetAfflictionStunModifier()
{
local float StunModifier;
local int i;
StunModifier = 0.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
StunModifier += Afflictions[i].GetStunModifier();
}
}
return StunModifier;
}
/** Accessor to get known affliction Damage modifier */
function float GetAfflictionDamageModifier()
{
local float DamageModifier;
local int i;
DamageModifier = 1.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
DamageModifier += Afflictions[i].GetDamageModifier();
}
}
return DamageModifier;
}
/** Accessor to get known affliction speed modifier - Multiplicative from all mods */
function float GetAfflictionSpeedModifier()
{
local float SpeedModifier;
local int i;
SpeedModifier = 1.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
SpeedModifier *= Afflictions[i].GetSpeedModifier();
}
}
return SpeedModifier;
}
function float GetAfflictionAttackSpeedModifier()
{
local float SpeedModifier;
local int i;
SpeedModifier = 1.f;
for (i = 0; i < Afflictions.Length; ++i)
{
if (Afflictions[i] != none)
{
SpeedModifier *= Afflictions[i].GetAttackSpeedModifier();
}
}
return SpeedModifier;
}
/** Turns off all affliction sounds / effects */
simulated function Shutdown()
{
local int i;
// Call deactivate, but let the strength decay naturally before removing from array
for (i = Afflictions.Length - 1; i >= 0; --i)
{
if ( Afflictions[i] != None )
{
Afflictions[i].Shutdown();
}
}
}
/*********************************************************************************************
* @name Functions that are needed clientside for VFX.
********************************************************************************************* */
/** Called from the pawn when we need to update FX outside of the affliction class (e.g. client repnotify) */
function ToggleEffects(EAfflictionType Type, bool bPrimary, optional bool bSecondary)
{
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
// After death FX are soley owned by the affliction class (simplifies TearOff/Replication issues)
if ( bPlayedDeath )
{
return;
}
// If the value is zero no need to create an instance
if( Type >= Afflictions.Length || Afflictions[Type] == None )
{
if ( (!bPrimary && !bSecondary) || !VerifyAfflictionInstance(Type) )
{
return; // cannot create instance
}
}
Afflictions[Type].ToggleEffects(bPrimary, bSecondary);
}
function UpdateMaterialParameter(EAfflictionType Type, float Value)
{
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
// If the value is zero no need to create an instance
if( Type >= Afflictions.Length || Afflictions[Type] == None )
{
if ( Value == 0 || !VerifyAfflictionInstance(Type) )
{
return; // cannot create instance
}
}
Afflictions[Type].SetMaterialParameter(Value);
}
defaultproperties
{
AfflictionClasses(AF_EMP)=class'KFAffliction_EMP'
AfflictionClasses(AF_FirePanic)=class'KFAffliction_Fire'
AfflictionClasses(AF_Poison)=class'KFAffliction_Poison'
AfflictionClasses(AF_Microwave)=class'KFAffliction_Microwave'
AfflictionClasses(AF_Freeze)=class'KFAffliction_Freeze'
AfflictionClasses(AF_GunHit)=class'KFAffliction_MediumRecovery'
AfflictionClasses(AF_MeleeHit)=class'KFAffliction_HeavyRecovery'
AfflictionClasses(AF_Stun)=class'KFAffliction_Stun'
AfflictionClasses(AF_Stumble)=class'KFAffliction_Stumble'
AfflictionClasses(AF_Knockdown)=class'KFAffliction_Knockdown'
AfflictionClasses(AF_Snare)=class'KFAffliction_Snare'
AfflictionClasses(AF_Bleed)=class'KFAffliction_Bleed'
}