1
0
KF2-Dev-Scripts/KFGame/Classes/KFZedArmorInfo.uc

379 lines
10 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// KFZedArmorInfo
//=============================================================================
// Handles armor info that can be attached to any Zed.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2018 Tripwire Interactive LLC
//=============================================================================
class KFZedArmorInfo extends Object
within KFPawn_Monster
config(Game)
abstract;
struct ArmorZoneInfo
{
var name ArmorZoneName; // Name of the armor zone
var array<name> AffectedHitZones; // Hit zones that map to this armor zone
var bool bAffectedByFrontDamage; // Whether frontal attacks affect this armor zone
var bool bAffectedByBackDamage; // Whether attacks from behind affect this armor zone
var name SocketName; // Name of the socket explosion FX play from
var int ArmorHealth; // Amount of health the armor absorbs before it blows off
var int ObliterateDmgThreshold; // Amount of single-shot damage that will obliterate the armor and negate its protection
var transient int ArmorHealthMax;
var ParticleSystem ExplosionTemplate;
var AkEvent ExplosionSFXTemplate;
var array<name> BonesToHideOnExplode;
var array<name> BonesToShowOnExplode;
var Texture2D ZoneIcon;
structdefaultproperties
{
bAffectedByFrontDamage=true
bAffectedByBackDamage=true
ObliterateDmgThreshold=MaxInt
}
};
var array<ArmorZoneInfo> ArmorZones;
var float ArmorScale;
/** These damage types cause this zed to receive increased damage */
var array<DamageModifierInfo> ArmorDamageTypeModifiers;
var transient float LastTakeDamageTime;
function byte GetArmorZonePctByte(int ZoneIdx)
{
return FloatToByte(float(ArmorZones[ZoneIdx].ArmorHealth) / float(ArmorZones[ZoneIdx].ArmorHealthMax));
}
function InitArmor()
{
local KFGameInfo KFGI;
local float HealthMod;
local float HeadHealthMod;
local int i, j;
KFGI = KFGameInfo(WorldInfo.Game);
if (KFGI != None)
{
HealthMod = 1.f;
HeadHealthMod = 1.f;
KFGI.DifficultyInfo.GetAIHealthModifier(Outer, KFGI.GetModifiedGameDifficulty(), KFGI.GetLivingPlayerCount(), HealthMod, HeadHealthMod);
for (i = 0; i < ArmorZones.Length; ++i)
{
ArmorZones[i].ArmorHealth *= HealthMod;
ArmorZones[i].ArmorHealthMax = ArmorZones[i].ArmorHealth;
RepArmorPct[i] = GetArmorZonePctByte(i);
}
}
for (i = 0; i < ArmorZones.Length; ++i)
{
for (j = 0; j < ArmorZones[i].BonesToShowOnExplode.length; ++j)
{
Mesh.HideBoneByName(ArmorZones[i].BonesToShowOnExplode[j], PBO_Disable);
}
}
UpdateArmorUI();
}
function ExplodeArmor(int ArmorZoneIdx)
{
if (bool(ArmorZoneStatus & (1 << ArmorZoneIdx)))
{
ArmorZones[ArmorZoneIdx].ArmorHealth = 0;
RepArmorPct[ArmorZoneIdx] = 0;
ArmorZoneStatus = ArmorZoneStatus ^ (1 << ArmorZoneIdx);
UpdateArmorPiece(ArmorZoneIdx);
}
}
simulated function UpdateArmorPieces()
{
local int i, UpdatedZones;
if (WorldInfo.NetMode != NM_DedicatedServer)
{
UpdatedZones = ArmorZoneStatus ^ PreviousArmorZoneStatus;
for (i = 0; i < ArmorZones.Length; ++i)
{
if (bool(UpdatedZones & (1 << i)))
{
UpdateArmorPiece(i);
}
}
UpdateArmorUI();
}
PreviousArmorZoneStatus = ArmorZoneStatus;
}
simulated function UpdateArmorPiece(int PieceIdx)
{
local Vector SocketLocation;
local Rotator SocketRotation;
local KFCharacterInfo_Monster MonsterArch;
local int i;
Mesh.GetSocketWorldLocationAndRotation(
default.ArmorZones[PieceIdx].SocketName, SocketLocation, SocketRotation);
PlaySoundBase(default.ArmorZones[PieceIdx].ExplosionSFXTemplate, true, true, true,
SocketLocation, true, SocketRotation);
if (WorldInfo.NetMode != NM_DedicatedServer)
{
MonsterArch = GetCharacterMonsterInfo();
if (MonsterArch.ExtraVFX.length > PieceIdx)
{
WorldInfo.MyEmitterPool.SpawnEmitter(
MonsterArch.ExtraVFX[PieceIdx].VFX, SocketLocation, SocketRotation);
}
}
for (i = 0; i < ArmorZones[PieceIdx].BonesToHideOnExplode.Length; ++i)
{
BonesToHidePostAsyncWork.AddItem(ArmorZones[PieceIdx].BonesToHideOnExplode[i]);
}
for (i = 0; i < ArmorZones[PieceIdx].BonesToShowOnExplode.Length; ++i)
{
BonesToShowPostAsyncWork.AddItem(ArmorZones[PieceIdx].BonesToShowOnExplode[i]);
}
if (BonesToHidePostAsyncWork.Length > 0 || BonesToShowPostAsyncWork.Length > 0)
{
Outer.SetTickGroup(TG_PostAsyncWork);
}
}
simulated function UpdateArmorUI()
{
local SCompressedArmorInfo CompressedArmorInfoList[3];
local int i;
local KFPlayerController KFPC;
if (!IsABoss())
{
return;
}
KFPC = KFPlayerController(GetALocalPlayerController());
if (KFPC == none)
{
return;
}
for (i = 0; i < ArmorZones.length; i++)
{
CompressedArmorInfoList[i].Percentage = ByteToFloat(RepArmorPct[i]);
CompressedArmorInfoList[i].IconTexture = default.ArmorZones[i].ZoneIcon;
}
if (KFPC.MyGFxHUD != none && KFPC.MyGFxHUD.bossHealthBar != none)
{
KFPC.MyGFxHUD.bossHealthBar.UpdateArmorUI(CompressedArmorInfoList);
}
}
function SetShieldScale(float InScale)
{
local int i;
ArmorScale = InScale;
for (i = 0; i < ArmorZones.Length; ++i)
{
ArmorZones[i].ArmorHealth *= InScale;
ArmorZones[i].ArmorHealthMax = ArmorZones[i].ArmorHealth;
RepArmorPct[i] = GetArmorZonePctByte(i);
}
}
function int GetArmorZoneIndex(name BoneName, vector DamagerSource)
{
local int HitZoneIdx, i;
local bool bFrontDamage, bBackDamage;
bFrontDamage = (DamagerSource - Location) dot Vector(Rotation) > 0;
bBackDamage = !bFrontDamage;
HitZoneIdx = HitZones.Find('ZoneName', BoneName);
if (HitZoneIdx >= 0)
{
for (i = 0; i < ArmorZones.Length; ++i)
{
if (ArmorZones[i].AffectedHitZones.Find(HitZones[HitZoneIdx].ZoneName) != INDEX_NONE)
{
if ((ArmorZones[i].bAffectedByFrontDamage && bFrontDamage) ||
(ArmorZones[i].bAffectedByBackDamage && bBackDamage))
{
return i;
}
}
}
}
return INDEX_NONE;
}
function TakeArmorZoneDamage(int ZoneIdx, int InDamage, out int OutDamage)
{
InDamage = Min(ArmorZones[ZoneIdx].ArmorHealth, InDamage);
ArmorZones[ZoneIdx].ArmorHealth -= InDamage;
OutDamage = Max(0, OutDamage - InDamage);
RepArmorPct[ZoneIdx] = GetArmorZonePctByte(ZoneIdx);
if (ArmorZones[ZoneIdx].ArmorHealth <= 0)
{
ZedExplodeArmor(ZoneIdx, ArmorZones[ZoneIdx].ArmorZoneName);
}
UpdateArmorUI();
LastTakeDamageTime = WorldInfo.TimeSeconds;
}
function AdjustBoneDamage(out int InDamage, name BoneName, Vector DamagerSource, class<DamageType> DamageType)
{
local int ArmorZoneIdx, ModDmgMax, ModDmgRem, ObliterateDamage;
local float Modifier;
2022-09-01 15:58:51 +00:00
local class<KFDamageType> mykfDamageType;
mykfDamageType = class<KFDamageType>(DamageType);
2020-12-13 15:01:13 +00:00
// modify damage done and apply to armor
Modifier = GetArmorDamageTypeModifier(DamageType);
ModDmgMax = InDamage * Modifier;
ModDmgRem = ModDmgMax;
ArmorZoneIdx = GetArmorZoneIndex(BoneName, DamagerSource);
if (ArmorZoneIdx != INDEX_NONE && ArmorZones[ArmorZoneIdx].ArmorHealth > 0)
{
if (ModDmgMax >= ArmorZones[ArmorZoneIdx].ObliterateDmgThreshold)
{
// obliterate armor and leave InDamage unmodified
ObliterateDamage = ArmorZones[ArmorZoneIdx].ArmorHealth;
TakeArmorZoneDamage(ArmorZoneIdx, ObliterateDamage, ObliterateDamage);
}
else
{
2022-09-01 15:58:51 +00:00
// If we have armour penetration damage, we calculate the Damage % that is base (and applied to armor)
// And we calculate the AP one, that's applied as minimum always
if (mykfDamageType != none && mykfDamageType.default.DamageModifierAP > 0.f)
{
ModDmgRem = (1.0 - mykfDamageType.default.DamageModifierAP) * ModDmgRem;
}
2020-12-13 15:01:13 +00:00
TakeArmorZoneDamage(ArmorZoneIdx, ModDmgRem, ModDmgRem);
2022-09-01 15:58:51 +00:00
// Apply to Zed: % AP (unmodified InDamage) + what's left of base after armor applied (unmodified ModDmgRem)
if (mykfDamageType != none && mykfDamageType.default.DamageModifierAP > 0.f)
{
InDamage = InDamage * mykfDamageType.default.DamageModifierAP;
InDamage += float(ModDmgRem) / Modifier;
return;
}
2020-12-13 15:01:13 +00:00
}
}
// unmodify any damage remaining and return it (apply it to zed)
InDamage = float(ModDmgRem) / Modifier;
}
function AdjustNonBoneDamage(out int InDamage, class<DamageType> DamageType)
{
local int i, NumValidZones, ModDmgMax, ModDmgRem, PrevModDmgRem, ModDmgToDo;
local float Modifier;
// modify damage done and apply to armor
Modifier = GetArmorDamageTypeModifier(DamageType);
ModDmgMax = InDamage * Modifier;
ModDmgRem = ModDmgMax;
do
{
PrevModDmgRem = ModDmgRem;
NumValidZones = 0;
for (i = 0; i < ArmorZones.Length; ++i)
{
if (ArmorZones[i].ArmorHealth > 0)
{
NumValidZones++;
}
}
if (NumValidZones > 0)
{
ModDmgToDo = Max(1, ModDmgRem / NumValidZones);
for (i = 0; i < ArmorZones.Length && ModDmgRem > 0; ++i)
{
if (ArmorZones[i].ArmorHealth > 0)
{
TakeArmorZoneDamage(i, ModDmgToDo, ModDmgRem);
}
}
}
} until (ModDmgRem == 0 || PrevModDmgRem == ModDmgRem);
// unmodify any damage remaining and return it (apply it to zed)
InDamage = float(ModDmgRem) / Modifier;
}
/** Returns damage multiplier for an incoming damage type @todo: c++?*/
function float GetArmorDamageTypeModifier(class<DamageType> DT)
{
local int i;
local int DifficultyIdx;
local float DamageModifier;
// reverse iteration so that most specific damage type can be last on the array
for (i = ArmorDamageTypeModifiers.Length - 1; i >= 0; --i)
{
if ( ClassIsChildOf(DT, ArmorDamageTypeModifiers[i].DamageType) )
{
if (WorldInfo.Game != None && ArmorDamageTypeModifiers[i].DamageScale.Length > 1)
{
DifficultyIdx = Min(WorldInfo.Game.GetModifiedGameDifficulty(), ArmorDamageTypeModifiers[i].DamageScale.Length);
DamageModifier = ArmorDamageTypeModifiers[i].DamageScale[DifficultyIdx];
}
else
{
DamageModifier = ArmorDamageTypeModifiers[i].DamageScale[0];
}
if (DamageModifier < 1.f)
{
DamageModifier = FMax(Lerp(1.f, DamageModifier, GameResistancePct), 0.f);
}
2021-03-02 11:56:51 +00:00
if(DT.default.ArmorDamageModifier != 1)
{
DamageModifier *= DT.default.ArmorDamageModifier;
}
2020-12-13 15:01:13 +00:00
return DamageModifier;
}
}
2021-03-02 11:56:51 +00:00
if(DT.default.ArmorDamageModifier != 1)
{
return DT.default.ArmorDamageModifier;
}
2020-12-13 15:01:13 +00:00
return 1.f;
}
function ExplodeAllArmor()
{
local int i, outDamage;
for (i = 0; i < ArmorZones.Length; ++i)
{
TakeArmorZoneDamage(i, ArmorZones[i].ArmorHealth, outDamage);
}
}