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

868 lines
34 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// KFPawnAnimInfo
//=============================================================================
// Contains information on using a character specific AnimSet. Since UObjects
// don't support static members, we can use abstract/archetype.
// Relates to KFPawnSoundGroup, but uses archetype for editability
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFPawnAnimInfo extends Object
hidecategories(Object)
editinlinenew;
/*********************************************************************************************
* @name Attack Animations
*********************************************************************************************/
enum EAttackRating
{
AR_Weak,
AR_Medium,
AR_Strong
};
struct AttackAnimInfo
{
/** Optional attack name for quick reference */
var() const name Tag;
/** Animation names/variants */
var() const array<Name> Anims;
/** If set, enable root motion for this attack */
var() const bool bUseRootMotion;
/** If set, play on the upper body (note: UpperBody and RootMotion are exclusive) */
var() const bool bPlayUpperBodyOnly;
/** If set, can be canceled by other actions (e.g. stumble). Use with caution if RootMotion is enabled! */
var() const bool bIsInterruptible;
/** If Interruptible, is this attack affected by parry stumble */
var() const bool bCannotBeParried<EditCondition=bIsInterruptible>;
/** If set, this attack will not be able to be pushed around by damage impulses */
var() const bool bNoDamageImpulses;
/** If set, only perform this attack while sprinting */
var() const bool bOnlyWhileSprinting;
/** If set, only perform this attack while not sprinting */
var() const bool bOnlyWhileNotSprinting;
/** If set, only perform this attack when enemy is moving */
var() const bool bOnlyWhileEnemyMoving;
/** If set, only perform this attack when enemy is stationary */
var() const bool bOnlyWhileEnemyNotMoving;
/** If set, only perform this attack when surrounded */
var() const bool bOnlyWhileSurrounded;
/** If set, this pawn has cloaking */
var() const bool bHasCloaking;
/** If set, only performs this attack when cloaked */
var() const bool bOnlyWhileCloaked<EditCondition=bHasCloaking>;
/** If set, only perform this attack when uncloaked */
var() const bool bOnlyWhileDeCloaked<EditCondition=bHasCloaking>;
/** The radius around the AI pawn to determine if surrounded */
var() const float SurroundedRadius<EditCondition=bOnlyWhileSurrounded | ClampMin=1>;
/** The minimum number of players that need to surround AI pawn for this attack to be considered */
var() const int MinSurroundedBy<EditCondition=bOnlyWhileSurrounded | ClampMin=2>;
/** If set, attack only usable when AI using custom mode/ability ("e.g, FleshPound rage, siren cloak, etc.") */
var() const bool bOnlyWhileInSpecializedMode;
/** If set, attack only usable when AI NOT using custom mode/ability ("e.g, FleshPound rage, siren cloak, etc.") */
var() const bool bOnlyWhileNotInSpecializedMode;
/** If set, use this EXCLUSIVELY when headless */
var() const bool bOnlyWhileHeadless;
/** If set, attack requires a clear path to enemy (no leg obstructions) to perform this anim */
var() const bool bOnlyWhilePathClear;
/** If set along with BattlePhaseMinimum, determines the battle phases this attack is allowed in (Bosses only) */
var() const bool bIsBattlePhaseAttack;
/** The minimum battle phase a boss must be at for this attack to be allowed */
var() const int BattlePhaseMinimum<EditCondition=bIsBattlePhaseAttack | ClampMin=1>;
/** The maximum battle phase a boss can be at to use this attack */
var() const int BattlePhaseMaximum<EditCondition=bIsBattlePhaseAttack>;
/** Chance this attack will be considered */
var() const float Chance<ClampMin=0.0>;
/** If > 0, will not target distance enemies */
var() const float MaxDistance<ClampMin=0.0>;
/** If > 0 and bUseRootMotion, will not target nearby enemies */
var() const float MinDistance<ClampMin=0.0>;
/** If > 0, no characters of this type can perform this attack */
var() const float GlobalCooldown<ClampMin=0.0>;
/** Create and manage an instanced (per zed) cooldown timer */
var const float InstancedCooldown<ClampMin=0.0>;
/** Attack rating that can be filtered by difficulty */
var() const EAttackRating DifficultyRating;
/** example: If DiffultyRating is weak and GameDifficulty allows medium attacks then ignore */
var() const bool bSkipIfDifficultyIsExceeded;
/** Global (any character of this type) picked this animation */
var transient float LastTimePlayed;
structdefaultproperties
{
//Priority=1.f
Chance=1.f
SurroundedRadius=200.f
BattlePhaseMinimum=1
BattlePhaseMaximum=4
MinSurroundedBy=3
}
};
struct sTheatricAnimInfo
{
var() const Name AnimationName;
var() const CameraAnim CameraAnimation;
var() const vector CameraAnimOffset;
};
var(Attacks) array<AttackAnimInfo> Attacks<DisplayName=MeleeAttacks>;
/** list of attack anim variants used on doors */
var(Attacks) array<Name> DoorAttacks;
/** If checked, allow game difficulty to modify attack selection */
var(Attacks) bool bEnableDifficultyScaling;
/** Chance of allowing this type of attack for a given difficulty */
var transient float WeakAttackChance;
var transient float MediumAttackChance;
var transient float HardAttackChance;
var(Debug) bool bDebugLog;
/*********************************************************************************************
* @name Hit Reaction Animation
*********************************************************************************************/
enum EHitReactionAnimType
{
HIT_Light, // AdditiveHitReaction
HIT_Medium,
HIT_Heavy, // CauseStumble, HighKickDeathAnimation, FullBodyHitReaction
};
struct native AnimVariants
{
var() array<Name> Anims;
};
/** array entry for each HitZoneGruop */
var(HitReaction) const array<AnimVariants> LightHitAnims;
var(HitReaction) const array<AnimVariants> MediumHitAnims<DisplayName=GunHitAnims>;
var(HitReaction) const array<AnimVariants> HeavyHitAnims<DisplayName=MeleeHitAnims>;
var(HitReaction) const array<name> DmgOverTimeHitAnims;
var(HitReaction) bool bCanPlayAnimHitReactions;
/** Anims for stumble special move
0: Body_Forward,
1: Body_Backward,
2: Body_Left,
3: Body_Right,
4: Leg_L_Forward,
5: Leg_L_Backward,
6: Leg_L_Left,
7: Leg_L_Right,
8: Leg_R_Forward,
9: Leg_R_Backward,
10: Leg_R_Left,
11: Leg_R_Right,
12: Parry
*/
var(HitReaction) editfixedsize array<AnimVariants> StumbleAnims;
/*********************************************************************************************
* @name Physics Hit Reactions
*********************************************************************************************/
/** If Pawn can or cannot play physics hit reactions. */
var(HitReaction) editconst bool bCanPlayPhysicsHitReactions;
/*********************************************************************************************
* @name Death Animations
*********************************************************************************************/
struct native DeathAnimInfo
{
/** Bone name to use these anims with */
var() const name BoneName;
/** Animation names/variants */
var() const array<Name> Anims_F;
var() const array<Name> Anims_B;
var() const array<Name> Anims_L;
var() const array<Name> Anims_R;
var() const array<Name> DismemberedAnims;
/** Chance no death animation will be selected */
var() const float RagdollChance<ClampMin=0.0>;
/** (Body Part Death) Chance the generic death will be selected */
var() const float IgnoreChance<ClampMin=0.0>;
structdefaultproperties
{
IgnoreChance=0.1f
}
};
/** Default death animation list */
var(Death) const DeathAnimInfo DefaultDeathAnims;
/** Death animas used by weapons with high kickback (explosives) */
var(Death) const DeathAnimInfo ExplosiveDeathAnims;
/** Death anims used when shot in specific location */
var(Death) const array<DeathAnimInfo> BodyPartDeathAnims;
/** Random chance that this death with play a hit zone specific animation */
//var(Death) const float BodyPartDeathChance;
/** If set, this actor can play death animations */
var(Death) const bool bCanPlayDeathAnimations;
/** Global (for this character) tracking of previous animation */
var transient byte LastDeathAnimIndex;
/** temp */
var(Death) const Vector2D DeathPhysMotorStrength;
/*********************************************************************************************
* @name Taunt Animations
*********************************************************************************************/
/** List of randomly selected taunt anims */
var(Taunt) array<name> TauntAnims;
var(Taunt) array<name> TauntKillAnims;
var(Taunt) array<name> TauntEnragedAnims;
/*********************************************************************************************
* @name Theatric Boss Animations
*********************************************************************************************/
var(BossTheatrics) array<sTheatricAnimInfo> TheatricBossEntranceAnimInfos;
var(BossTheatrics) array<sTheatricAnimInfo> TheatricBossVictoryAnimInfos;
/*********************************************************************************************
* @name Melee attack anim helpers
*********************************************************************************************/
/** Finds a attack info by index. */
function name InitMeleeSpecialMove(KFSM_MeleeAttack InSpecialMove, int Index, int Variant)
{
local name AnimName;
AnimName = Attacks[Index].Anims[Variant];
if ( AnimName == '' )
{
`log(self@GetFuncName()@"failed to find a valid attack");
}
// if a special move is passed in set up the flags based on this attack. This is a little bit reverse
// logic, but it gets around having to pass the AttackAnimInfo structs by value all over script
if ( InSpecialMove != None )
{
InSpecialMove.bUseRootMotion = Attacks[Index].bUseRootMotion;
InSpecialMove.bCanBeInterrupted = Attacks[Index].bIsInterruptible;
InSpecialMove.bCannotBeParried = Attacks[Index].bCannotBeParried;
InSpecialMove.bAllowMomentumPush = !Attacks[Index].bNoDamageImpulses;
}
return AnimName;
}
/** Finds a attack animation by name */
function Name ChooseAttackByName(name AttackName, optional KFPawn_Monster Instigator, optional Actor Target, optional out byte outFlags)
{
local int idx, variant;
idx = Attacks.Find('Tag', AttackName);
if ( idx != INDEX_NONE )
{
// if there is an instigator, check to see if this is a valid attack
if ( Instigator != None && !CanDoAttackAnim(idx, Instigator, Target) )
{
return '';
}
variant = Rand(Attacks[idx].Anims.Length);
// update the global cooldown
if ( Attacks[idx].GlobalCooldown > 0 )
{
Attacks[idx].LastTimePlayed = class'Engine'.static.GetCurrentWorldInfo().TimeSeconds;
}
outFlags = (idx + (variant << 4));
return Attacks[idx].Anims[variant];
}
`log(self@GetFuncName()@"failed to find a valid attack");
return '';
}
/** Chooses a valid (random) attack animation */
function int ChooseNextStrike(KFPawn Instigator, optional Actor Target)
{
local int i;
local array<int> IndexList;
// Add up the total weight for all valid attacks
for(i = 0; i < Attacks.Length; i++)
{
if ( CanDoAttackAnim(i, Instigator, Target) )
{
IndexList.AddItem(i);
}
}
// If we have valid animations to choose from
if ( IndexList.Length > 0 )
{
// return one of the possible attacks
return IndexList[Rand(IndexList.Length)];
}
return 255;
}
function UpdateAttackCooldown( KFAIController KFAIC, byte DesiredStrikeIndex )
{
// update the global cooldown
if( Attacks[DesiredStrikeIndex].GlobalCooldown > 0 )
{
Attacks[DesiredStrikeIndex].LastTimePlayed = class'Engine'.static.GetCurrentWorldInfo().TimeSeconds;
}
// Assume attack is going to play, if a cooldown is set handle it here
if( Attacks[DesiredStrikeIndex].InstancedCooldown > 0 )
{
/** Once attack has been selected activate a cooldown time on the owning pawn */
KFAIC.AddCooldownTimer(Attacks[DesiredStrikeIndex].Tag, Attacks[DesiredStrikeIndex].InstancedCooldown);
}
// Let the Game Conductor know an attack happened
if( KFAIC != none && KFGameInfo(KFAIC.WorldInfo.Game) != none && KFGameInfo(KFAIC.WorldInfo.Game).GameConductor != none )
{
KFGameInfo(KFAIC.WorldInfo.Game).GameConductor.UpdateOverallAttackCoolDowns(KFAIC);
}
}
/** Find the min/max range of each Tag (attack category) defined in my KFPawn_AnimGroup. Used to determine when
a zed should consider attacking */
function float GetAttackRange( KFAIController KFAIC )
{
local int TagIdx;
local float MaxAttackRange;
for( TagIdx = 0; TagIdx < Attacks.Length; TagIdx++ )
{
if( Attacks[TagIdx].Tag == '' )
{
continue;
}
if( MaxAttackRange == 0 || (Attacks[TagIdx].MaxDistance > MaxAttackRange) )
{
MaxAttackRange = Attacks[TagIdx].MaxDistance;
}
}
return MaxAttackRange;
}
/** Find the min/max range of each Tag (attack category) defined in my KFPawn_AnimGroup. This is per-Tag,
* not per attack animation, and is used to pre-determine if any of the attacks for a Tag is within range. */
function float GetMedianStrikeRange( byte MeleeAttackIndex, float StrikeRangePercentage, float CollisionRadius )
{
local float MinMeleeRange, MaxMeleeRange;
MaxMeleeRange = Attacks[MeleeAttackIndex].MaxDistance;
MinMeleeRange = Attacks[MeleeAttackIndex].MinDistance;
if( MinMeleeRange < CollisionRadius )
{
MinMeleeRange = CollisionRadius;
}
return MinMeleeRange + (MaxMeleeRange - MinMeleeRange) * StrikeRangePercentage;
}
function int GetAttackIndexByTag( name AttackTag )
{
return Attacks.Find('Tag', AttackTag);
}
function name GetAttackAnimName( int AttackIndex, byte VariantIndex )
{
return Attacks[AttackIndex].Anims[VariantIndex];
}
function byte GetStrikeFlags(int DesiredStrikeIndex)
{
local int Variant;
if( DesiredStrikeIndex > Attacks.length )
{
return 255;
}
// Get a random animation (random variant)
Variant = Rand(Attacks[DesiredStrikeIndex].Anims.Length);
return (DesiredStrikeIndex + (variant << 4));
}
/** Ensures that LastTimePlayed is never greater than world time */
function CheckForValidCooldown( int AtkIdx )
{
if( class'WorldInfo'.static.GetWorldInfo().TimeSeconds < Attacks[AtkIdx].LastTimePlayed )
{
`log("[ANIMINFO] WorldInfo.TimeSeconds mismatch! Resetting cooldowns.");
Attacks[AtkIdx].LastTimePlayed = 0.f;
}
}
/** Checks the AttackAnimInfo to see if this attack is valid to perform */
function bool CanDoAttackAnim(int Idx, KFPawn P, optional Actor Target)
{
local AttackAnimInfo Attack;
// Solve edge case where world time has reset but LastUsedTime hasn't
CheckForValidCooldown( Idx );
Attack = Attacks[Idx];
if ( Attack.Anims.Length <= 0 )
{
`log(self@"CanDoAttackAnim missing attack anim names for"@Attacks[Idx].Tag);
return false;
}
if( Attack.bOnlyWhilePathClear && P.MyKFAIC.bIsBodyBlocked )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhilePathClear is true but Pawn is blocked", bDebugLog );
return false;
}
if( Attack.bHasCloaking && (Attack.bOnlyWhileCloaked && !P.bIsCloaking) || (Attack.bOnlyWhileDeCloaked && P.bIsCloaking) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because it is not allowed in this cloaking state", bDebugLog );
return false;
}
if( Attack.bIsBattlePhaseAttack && (P.GetCurrentBattlePhase() < Attack.BattlePhaseMinimum || P.GetCurrentBattlePhase() > Attack.BattlePhaseMaximum) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because not allowed in this Battle Phase", bDebugLog );
return false;
}
if( P.MyKFAIC != none && !P.MyKFAIC.CheckOverallCooldownTimer())
{
`AILog_Ext( GetFuncName()$"() PREVENTING attack "$Attack.Tag$" because Overall Cooldown is active", 'Command_Attack_Melee', P.MyKFAIC );
return false;
}
if ( Attack.GlobalCooldown > 0 && Attack.LastTimePlayed > 0.f
&& `TimeSinceEx(P, Attack.LastTimePlayed) < Attack.GlobalCooldown )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because GlobalCooldown ("$Attack.GlobalCooldown$") LastTimePlayed:"$`TimeSinceEx(P, Attack.LastTimePlayed), bDebugLog );
return false;
}
if ( Attack.Chance < 1.f && FRand() > Attack.Chance )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because random chance missed (Chance:"$Attack.Chance$")", bDebugLog );
return false;
}
if( !AllowAttackByMovement( Idx, P ) )
{
return false;
}
if ( Attack.bOnlyWhileHeadless != P.ShouldPlayHeadlessMeleeAnims() )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhileHeadless is true but Pawn has a head", bDebugLog );
return false;
}
if ( Attack.InstancedCooldown > 0 && IsAttackOnCooldown(P, Attack.Tag, Attack.InstancedCooldown) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because Cooldown is still active", bDebugLog );
return false;
}
if ( Attack.bOnlyWhileSurrounded && !P.IsSurrounded(true, Attack.MinSurroundedBy, Attack.SurroundedRadius) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because pawn is not surrounded", bDebugLog );
return false;
}
if ( Target != None )
{
if ( Attack.bOnlyWhileEnemyMoving && IsZero(Target.Velocity) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because target velocity is 0", bDebugLog );
return false;
}
else if ( Attack.bOnlyWhileEnemyNotMoving && !IsZero(Target.Velocity) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because target velocity is not 0", bDebugLog );
return false;
}
if ( Attack.MaxDistance > 0 && VSizeSq(P.Location - Target.Location) > Square(Attack.MaxDistance) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because target dist ("$VSize(P.Location - Target.Location)$") is greater than "$Attack.MaxDistance, bDebugLog );
return false;
}
if ( Attack.MinDistance > 0 && VSizeSq(P.Location - Target.Location) < Square(Attack.MinDistance) )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because target dist is less than "$Attack.MinDistance, bDebugLog );
return false;
}
}
if ( !AllowAttackByDifficulty(Idx) )
{
if( P.MyKFAIC != none )
{
`AILog_Ext( GetFuncName()$"() PREVENTING attack "$Attack.Tag$" because it has a DifficultyRating of "$Attack.DifficultyRating, 'Command_Attack_Melee', P.MyKFAIC );
}
return false; // do this last so perf is consistant
}
return true;
}
/** Return true if this attack was recently used and has a cooldown timer */
function bool IsAttackOnCooldown(KFPawn P, name CooldownTag, float CooldownTime)
{
if ( P.MyKFAIC != None )
{
return !P.MyKFAIC.CheckCooldownTimer(CooldownTag);
}
return false;
}
/** Returns the range of an attack for AI behavior modification */
function Vector2D GetAttackRangeByName(name AttackName)
{
local int idx;
idx = Attacks.Find('Tag', AttackName);
if ( idx != INDEX_NONE )
{
return vect2d(Attacks[Idx].MinDistance, Attacks[Idx].MaxDistance);
}
return vect2d(0,0);
}
/** Returns the range of an attack for AI behavior modification */
function Vector2D GetAttackRangeExtent(name AttackName)
{
local int i;
local float MinRange, MaxRange;
MinRange = MAXINT;
// Find smallest min and greatest max
for(i = 0; i < Attacks.Length; i++)
{
if ( Attacks[i].Chance > 0.f )
{
MinRange = Min(MinRange, Attacks[i].MinDistance);
MaxRange = Max(MaxRange, Attacks[i].MaxDistance);
}
}
return vect2d(MinRange,MaxRange);
}
/*********************************************************************************************
* @name Difficulty Scaling
*********************************************************************************************/
/** Cache any values from the difficulty info that we may be using */
function SetDifficultyValues( KFGameDifficultyInfo DifficultyInfo )
{
WeakAttackChance = DifficultyInfo.GetWeakAttackChance();
MediumAttackChance = DifficultyInfo.GetMediumAttackChance();
HardAttackChance = DifficultyInfo.GetHardAttackChance();
}
function bool AllowAttackByMovement( byte Idx, KFPawn P )
{
if ( Attacks[Idx].bOnlyWhileSprinting && !P.bIsSprinting )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhileSprinting is true but Pawn is not sprinting", bDebugLog );
return false;
}
else if ( Attacks[Idx].bOnlyWhileNotSprinting && P.bIsSprinting )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhileNotSprinting is true but Pawn is sprinting", bDebugLog );
return false;
}
if( Attacks[Idx].bOnlyWhileInSpecializedMode && !P.ShouldPlaySpecialMeleeAnims() )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhileInSpecializedMode is true but Pawn not in SpecializedMode", bDebugLog );
return false;
}
else if( Attacks[Idx].bOnlyWhileNotInSpecializedMode && P.ShouldPlaySpecialMeleeAnims() )
{
`log( P@GetFuncName()$"() rejecting attack idx "$idx$" because bOnlyWhileNotInSpecializedMode is true but Pawn is in SpecializedMode", bDebugLog );
return false;
}
return true;
}
function bool AllowAttackByDifficulty( byte Index )
{
if ( bEnableDifficultyScaling )
{
switch ( Attacks[Index].DifficultyRating )
{
case AR_Weak:
if ( MediumAttackChance >= WeakAttackChance && Attacks[Index].bSkipIfDifficultyIsExceeded )
return false;
return (WeakAttackChance >= 1.f || FRand() < WeakAttackChance);
case AR_Medium:
if ( HardAttackChance >= MediumAttackChance && Attacks[Index].bSkipIfDifficultyIsExceeded )
return false;
return (MediumAttackChance >= 1.f || FRand() < MediumAttackChance);
case AR_Strong:
return (HardAttackChance >= 1.f || FRand() < HardAttackChance);
}
}
return true;
}
/*********************************************************************************************
* @name Death Animations
*********************************************************************************************/
/**
* Pick a random death animation from supplied list.
* Also makes sure we don't pick up the same animation twice in a row.
*/
function Name ChooseDeathAnimation(KFPawn P, class<DamageType> DamageType, vector HitDir, optional name HitBoneName)
{
local int idx;
// First try to do a 'high kickback' death animation
if ( ClassIsChildOf(damageType, class'KFDT_Explosive') )
{
return EvaulateDeathAnimList(ExplosiveDeathAnims, P, HitDir);
}
// Then look for a hit zone specific animation
idx = BodyPartDeathAnims.Find('BoneName', HitBoneName);
if ( idx != INDEX_NONE )
{
if ( BodyPartDeathAnims[idx].IgnoreChance > 0.f && FRand() < BodyPartDeathAnims[idx].IgnoreChance )
{
// use the default death animation list
return EvaulateDeathAnimList(DefaultDeathAnims, P, HitDir);
}
return EvaulateDeathAnimList(BodyPartDeathAnims[idx], P, HitDir);
}
// Finally, use the default death animation list
return EvaulateDeathAnimList(DefaultDeathAnims, P, HitDir);
}
/** Once a death type is selected, choose animation variant based on hit direction */
function Name EvaulateDeathAnimList(const out DeathAnimInfo DeathAnims, KFPawn P, vector HitDir)
{
local EPawnOctant Region;
// If this list has a ragdoll chance it may return NAME_None
if ( DeathAnims.RagdollChance > 0.f && FRand() < DeathAnims.RagdollChance )
{
return '';
}
if ( DeathAnims.DismemberedAnims.Length > 0 && P.bHasBrokenConstraints && P.Mesh.IsBrokenConstraint(DeathAnims.BoneName) )
{
LastDeathAnimIndex = Rand(DeathAnims.DismemberedAnims.Length);
return DeathAnims.DismemberedAnims[LastDeathAnimIndex];
}
// if this set has direction anims than calculate hit direction
if ( DeathAnims.Anims_B.Length > 0 )
{
Region = P.CalcQuadRegion(P.Rotation, -HitDir);
switch (Region)
{
case DIR_Backward:
if ( DeathAnims.Anims_B.Length > 0 )
{
LastDeathAnimIndex = Rand(DeathAnims.Anims_B.Length);
return DeathAnims.Anims_B[LastDeathAnimIndex];
}
break;
case DIR_Left:
if ( DeathAnims.Anims_L.Length > 0 )
{
LastDeathAnimIndex = Rand(DeathAnims.Anims_L.Length);
return DeathAnims.Anims_L[LastDeathAnimIndex];
}
break;
case DIR_Right:
if ( DeathAnims.Anims_R.Length > 0 )
{
LastDeathAnimIndex = Rand(DeathAnims.Anims_R.Length);
return DeathAnims.Anims_R[LastDeathAnimIndex];
}
break;
}
}
LastDeathAnimIndex = Rand(DeathAnims.Anims_F.Length);
return DeathAnims.Anims_F[LastDeathAnimIndex];
}
/*********************************************************************************************
* @name Hit Reactions
*********************************************************************************************/
function bool PlayHitReactionAnim(KFPawn P, EHitReactionAnimType Type, EPawnOctant Dir)
{
local name AnimName;
local float Duration;
if ( !bCanPlayAnimHitReactions )
{
return false;
}
if ( Dir == DIR_None && DmgOverTimeHitAnims.Length > 0 )
{
AnimName = DmgOverTimeHitAnims[Rand(DmgOverTimeHitAnims.Length)];
Duration = P.PlayBodyAnim(AnimName, EAS_Additive, 1.f, 0.1f);
P.NextHitReactionAnim_ActorTime = P.GetActorTimeSeconds() + RandRange(0.15f, 0.33f);
}
else if ( Type == HIT_Light && LightHitAnims[Dir].Anims.Length > 0 )
{
AnimName = LightHitAnims[Dir].Anims[Rand(LightHitAnims[Dir].Anims.Length)];
Duration = P.PlayBodyAnim(AnimName, EAS_Additive, 1.f, 0.1f);
P.NextHitReactionAnim_ActorTime = P.GetActorTimeSeconds() + RandRange(0.15f, 0.33f);
}
else if ( Type == HIT_Medium && MediumHitAnims[Dir].Anims.Length > 0 )
{
AnimName = MediumHitAnims[Dir].Anims[Rand(MediumHitAnims[Dir].Anims.Length)];
Duration = P.PlayBodyAnim(AnimName, EAS_UpperBody);
P.NextHitReactionAnim_ActorTime = P.GetActorTimeSeconds() + 0.33f;//RandRange(0.0f, 0.1f);
P.SoundGroupArch.PlayPainSound(P);
}
else if ( Type == HIT_Heavy && HeavyHitAnims[Dir].Anims.Length > 0 )
{
AnimName = HeavyHitAnims[Dir].Anims[Rand(HeavyHitAnims[Dir].Anims.Length)];
Duration = P.PlayBodyAnim(AnimName, EAS_UpperBody);
P.NextHitReactionAnim_ActorTime = P.GetActorTimeSeconds() + 0.33f;//RandRange(0.5f, 1.5f);
P.SoundGroupArch.PlayPainSound(P);
}
return ( Duration > 0 );
}
defaultproperties
{
bCanPlayDeathAnimations=true
bCanPlayAnimHitReactions=true
LightHitAnims(DIR_Forward)=(Anims=(ADD_Bullet_F_V1, ADD_Bullet_F_V2, ADD_Bullet_F_V3))
LightHitAnims(DIR_Backward)=(Anims=(ADD_Bullet_B_V1, ADD_Bullet_B_V2, ADD_Bullet_B_V3))
LightHitAnims(DIR_ForwardLeft)=(Anims=(ADD_Bullet_FL_V1, ADD_Bullet_FL_V2, ADD_Bullet_FL_V3))
LightHitAnims(DIR_ForwardRight)=(Anims=(ADD_Bullet_FR_V1, ADD_Bullet_FR_V2, ADD_Bullet_FR_V3))
LightHitAnims(DIR_BackwardLeft)=(Anims=(ADD_Bullet_BL_V1, ADD_Bullet_BL_V2, ADD_Bullet_BL_V3))
LightHitAnims(DIR_BackwardRight)=(Anims=(ADD_Bullet_BR_V1, ADD_Bullet_BR_V2, ADD_Bullet_BR_V3))
LightHitAnims(DIR_Left)=(Anims=(ADD_Bullet_L_V1, ADD_Bullet_L_V2, ADD_Bullet_L_V3))
LightHitAnims(DIR_Right)=(Anims=(ADD_Bullet_R_V1, ADD_Bullet_R_V2, ADD_Bullet_R_V3))
MediumHitAnims(DIR_Forward)=(Anims=(Hit_F_V1, Hit_F_V2, Hit_F_V3))
MediumHitAnims(DIR_Backward)=(Anims=(Hit_B_V1, Hit_B_V2, Hit_B_V3))
MediumHitAnims(DIR_ForwardLeft)=(Anims=(Hit_FL_V1, Hit_FL_V2, Hit_FL_V3))
MediumHitAnims(DIR_ForwardRight)=(Anims=(Hit_FR_V1, Hit_FR_V2, Hit_FR_V3))
MediumHitAnims(DIR_BackwardLeft)=(Anims=(Hit_BL_V1, Hit_BL_V2, Hit_BL_V3))
MediumHitAnims(DIR_BackwardRight)=(Anims=(Hit_BR_V1, Hit_BR_V2, Hit_BR_V3))
MediumHitAnims(DIR_Left)=(Anims=(Hit_L_V1, Hit_L_V2, Hit_L_V3))
MediumHitAnims(DIR_Right)=(Anims=(Hit_R_V1, Hit_R_V2, Hit_R_V3))
HeavyHitAnims(DIR_Forward)=(Anims=(Hit_Hard_F_V1, Hit_Hard_F_V2, Hit_Hard_F_V3))
HeavyHitAnims(DIR_Backward)=(Anims=(Hit_Hard_B_V1, Hit_Hard_B_V2, Hit_Hard_B_V3))
HeavyHitAnims(DIR_ForwardLeft)=(Anims=(Hit_Hard_FL_V1, Hit_Hard_FL_V2, Hit_Hard_FL_V3))
HeavyHitAnims(DIR_ForwardRight)=(Anims=(Hit_Hard_FR_V1, Hit_Hard_FR_V2, Hit_Hard_FR_V3))
HeavyHitAnims(DIR_BackwardLeft)=(Anims=(Hit_Hard_BL_V1, Hit_Hard_BL_V2, Hit_Hard_BL_V3))
HeavyHitAnims(DIR_BackwardRight)=(Anims=(Hit_Hard_BR_V1, Hit_Hard_BR_V2, Hit_Hard_BR_V3))
HeavyHitAnims(DIR_Left)=(Anims=(Hit_Hard_L_V1, Hit_Hard_L_V2, Hit_Hard_L_V3))
HeavyHitAnims(DIR_Right)=(Anims=(Hit_Hard_R_V1, Hit_Hard_R_V2, Hit_Hard_R_V3))
DmgOverTimeHitAnims=(ADD_Bullet_F_V1, ADD_Bullet_F_V2, ADD_Bullet_F_V3, ADD_Bullet_FL_V1, ADD_Bullet_FL_V2, ADD_Bullet_FL_V3, ADD_Bullet_FR_V1, ADD_Bullet_FR_V2, ADD_Bullet_FR_V3)
DefaultDeathAnims={( IgnoreChance=0.f,
Anims_F=(Death_F_V1, Death_F_V2, Death_F_V3, Death_F_V4, Death_F_V5, Death_F_V6, Death_F_V7, Death_F_V8),
Anims_B=(Death_B_V1, Death_B_V2, Death_B_V3, Death_B_V4, Death_B_V5, Death_B_V6),
Anims_L=(Death_L_V1, Death_L_V2, Death_L_V3, Death_L_V4, Death_L_V5, Death_L_V6),
Anims_R=(Death_R_V1, Death_R_V2, Death_R_V3, Death_R_V4, Death_R_V5, Death_R_V6) )}
ExplosiveDeathAnims={( IgnoreChance=0.f,
Anims_F=(DeathEXP_F_V1, DeathEXP_F_V2, DeathEXP_F_V3, DeathEXP_F_V4, DeathEXP_Up_V1, DeathEXP_Up_V2),
Anims_B=(DeathEXP_B_V1, DeathEXP_B_V2, DeathEXP_B_V3, DeathEXP_Up_V1, DeathEXP_Up_V2),
Anims_L=(DeathEXP_L_V1, DeathEXP_L_V2, DeathEXP_L_V3, DeathEXP_L_V4),
Anims_R=(DeathEXP_R_V1, DeathEXP_R_V2, DeathEXP_R_V3, DeathEXP_R_V4) )}
// Hit zone specific death anims
BodyPartDeathAnims.Add((BoneName=Head, RagdollChance=0.25f, Anims_F=(Death_Head_V1, Death_Head_V2, Death_Head_V3, Death_Head_V4, Death_Head_V5, Death_Head_V6, Death_Head_V7, Death_Head_V8, Death_Convulsion_V1, Death_Convulsion_V2, Death_Neck_V4, Death_Neck_V5, Death_Neck_V6))
BodyPartDeathAnims.Add((BoneName=Neck, Anims_F=(Death_Neck_V1, Death_Neck_V2, Death_Neck_V3, Death_Neck_V4, Death_Neck_V5, Death_Neck_V6, Death_Neck_V7))
BodyPartDeathAnims.Add((BoneName=Hips, RagdollChance=0.20f, Anims_F=(Death_Stomach_V1, Death_Stomach_V2, Death_Stomach_V3, Death_Stomach_V4, Death_Stomach_V5, Death_Stomach_V6, Death_Stomach_V7, Death_Neck_V1, Death_Neck_V2, Death_Neck_V7))
BodyPartDeathAnims.Add((BoneName=Spine1, RagdollChance=0.20f, Anims_F=(Death_Stomach_V1, Death_Stomach_V2, Death_Stomach_V3, Death_Stomach_V4, Death_Stomach_V5, Death_Stomach_V6, Death_Stomach_V7, Death_Neck_V1, Death_Neck_V2, Death_Neck_V7))
BodyPartDeathAnims.Add((BoneName=Spine2, IgnoreChance=0.50f, RagdollChance=0.1f, Anims_F=(Death_FL_V5, Death_FL_V6, Death_FR_V6, Death_Head_V5, Death_Head_V7, Death_Head_V8, Death_Neck_V5, Death_Neck_V6, Death_Neck_V7, Death_Stomach_V5, Death_Convulsion_V2, Death_Convulsion_V4))
BodyPartDeathAnims.Add((BoneName=LeftUpLeg, Anims_F=(Death_LegL_V1, Death_LegL_V2, Death_LegL_V3, Death_LegL_V4))
BodyPartDeathAnims.Add((BoneName=LeftLeg, Anims_F=(Death_LegL_V1, Death_LegL_V2, Death_LegL_V3, Death_LegL_V4))
BodyPartDeathAnims.Add((BoneName=LeftFoot, Anims_F=(Death_LegL_V1, Death_LegL_V2, Death_LegL_V3, Death_LegL_V4))
BodyPartDeathAnims.Add((BoneName=RightUpLeg, Anims_F=(Death_LegR_V1, Death_LegR_V2, Death_LegR_V3, Death_LegR_V4))
BodyPartDeathAnims.Add((BoneName=RightLeg, Anims_F=(Death_LegR_V1, Death_LegR_V2, Death_LegR_V3, Death_LegR_V4))
BodyPartDeathAnims.Add((BoneName=RightFoot, Anims_F=(Death_LegR_V1, Death_LegR_V2, Death_LegR_V3, Death_LegR_V4))
BodyPartDeathAnims.Add((BoneName=LeftShoulder, Anims_F=(Death_FL_V1, Death_FL_V2, Death_FL_V3, Death_LegL_V2, Death_LegR_V1), Anims_B=(Death_BL_V1, Death_BL_V2, Death_BL_V3))
BodyPartDeathAnims.Add((BoneName=RightShoulder, Anims_F=(Death_FR_V1, Death_FR_V2, Death_FR_V3, Death_LegL_V1), Anims_B=(Death_BR_V1, Death_BR_V2, Death_BR_V3))
BodyPartDeathAnims.Add((BoneName=LeftArm, Anims_F=(Death_FL_V1, Death_FL_V2, Death_FL_V3, Death_FL_V4, Death_FL_V5), Anims_B=(Death_BL_V1, Death_BL_V2, Death_BL_V3))
BodyPartDeathAnims.Add((BoneName=LeftForearm, Anims_F=(Death_FL_V1, Death_FL_V2, Death_FL_V3, Death_FL_V4, Death_FL_V5), Anims_B=(Death_BL_V1, Death_BL_V2, Death_BL_V3))
BodyPartDeathAnims.Add((BoneName=LeftHand, Anims_F=(Death_FL_V1, Death_FL_V2, Death_FL_V3, Death_FL_V4, Death_FL_V5), Anims_B=(Death_BL_V1, Death_BL_V2, Death_BL_V3))
BodyPartDeathAnims.Add((BoneName=RightArm, Anims_F=(Death_FR_V1, Death_FR_V2, Death_FR_V3, Death_FR_V4), Anims_B=(Death_BR_V1, Death_BR_V2, Death_BR_V3))
BodyPartDeathAnims.Add((BoneName=RightForearm, Anims_F=(Death_FR_V1, Death_FR_V2, Death_FR_V3, Death_FR_V4), Anims_B=(Death_BR_V1, Death_BR_V2, Death_BR_V3))
BodyPartDeathAnims.Add((BoneName=RightHand, Anims_F=(Death_FR_V1, Death_FR_V2, Death_FR_V3, Death_FR_V4), Anims_B=(Death_BR_V1, Death_BR_V2, Death_BR_V3))
DeathPhysMotorStrength=(X=70000,Y=2000)
WeakAttackChance=1.f
MediumAttackChance=1.f
HardAttackChance=1.f
bEnableDifficultyScaling=true
bDebugLog=false
// Taunts
TauntAnims.Add(Taunt_V1)
TauntAnims.Add(Taunt_V2)
TauntKillAnims.Add(Taunt_Kill_V1)
TauntKillAnims.Add(Taunt_Kill_V2)
// KFSM_Stumble
StumbleAnims(ESA_Body_Forward)=(Anims=(Stumble_F_V1, Stumble_F_V2, Stumble_F_V3, Stumble_F_V4))
StumbleAnims(ESA_Body_Backward)=(Anims=(Stumble_B_V1, Stumble_B_V2, Stumble_B_V3, Stumble_B_V4))
StumbleAnims(ESA_Body_Left)=(Anims=(Stumble_L_V1, Stumble_L_V2, Stumble_L_V3, Stumble_L_V4))
StumbleAnims(ESA_Body_Right)=(Anims=(Stumble_R_V1, Stumble_R_V2, Stumble_R_V3, Stumble_R_V4))
StumbleAnims(ESA_Leg_L_Forward)=(Anims=(Stumble_L_Leg_F_V1, Stumble_L_Leg_F_V2))
StumbleAnims(ESA_Leg_L_Backward)=(Anims=(Stumble_L_Leg_B_V1, Stumble_L_Leg_B_V2))
StumbleAnims(ESA_Leg_L_Left)=(Anims=(Stumble_L_Leg_L_V1, Stumble_L_Leg_L_V2))
StumbleAnims(ESA_Leg_L_Right)=(Anims=(Stumble_L_Leg_R_V1, Stumble_L_Leg_R_V2))
StumbleAnims(ESA_Leg_R_Forward)=(Anims=(Stumble_R_Leg_F_V1, Stumble_R_Leg_F_V2))
StumbleAnims(ESA_Leg_R_Backward)=(Anims=(Stumble_R_Leg_B_V1, Stumble_R_Leg_B_V2))
StumbleAnims(ESA_Leg_R_Left)=(Anims=(Stumble_R_Leg_L_V1, Stumble_R_Leg_L_V2))
StumbleAnims(ESA_Leg_R_Right)=(Anims=(Stumble_R_Leg_R_V1, Stumble_R_Leg_R_V2))
StumbleAnims(ESA_Parry)=(Anims=(Stumble_F_V1, Stumble_F_V2, Stumble_F_V3, Stumble_F_V4))
}