1281 lines
37 KiB
Ucode
1281 lines
37 KiB
Ucode
//=============================================================================
|
|
// KFWeaponAttachment
|
|
//=============================================================================
|
|
// Base class for third person weapon attachments
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// - Andrew "Strago" Ladenberger
|
|
//=============================================================================
|
|
class KFWeaponAttachment extends Actor
|
|
hidecategories(Object, Movement, Attachment, Collision, Physics, Advanced, Debug, Mobile)
|
|
native;
|
|
|
|
/*********************************************************************************************
|
|
* @name Mesh Components
|
|
********************************************************************************************* */
|
|
|
|
/** Editable SkeletalMesh set by the archetype */
|
|
var() const SkeletalMesh SkelMesh;
|
|
|
|
/** Extra pawn anim set when weapon is equipped */
|
|
var() AnimSet CharacterAnimSet;
|
|
/** Anim set to use for 3rd person weapon anims */
|
|
var() AnimSet WeaponAnimSet;
|
|
|
|
/** Third Person Weapon SkelMesh */
|
|
var transient SkeletalMeshComponent WeapMesh;
|
|
|
|
/** If true this third person weapon is part of the pawn's mesh */
|
|
var() bool bWeapMeshIsPawnMesh;
|
|
|
|
/*********************************************************************************************
|
|
* @name LaserSight
|
|
********************************************************************************************* */
|
|
|
|
var() bool bHasLaserSight;
|
|
var() const KFLaserSightAttachment LaserSightArchetype;
|
|
var transient KFLaserSightAttachment LaserSight;
|
|
|
|
/*********************************************************************************************
|
|
* @name Fire Effects
|
|
********************************************************************************************* */
|
|
|
|
/** A muzzle flash instance */
|
|
var KFMuzzleFlash MuzzleFlash;
|
|
/** A reference to the muzzle flash template */
|
|
var() const KFMuzzleFlash MuzzleFlashTemplate;
|
|
|
|
var float MaxFireEffectDistance;
|
|
|
|
/** replicated information on a hit we've taken */
|
|
struct native KFTracerInfo
|
|
{
|
|
/** Tracer Effect */
|
|
var() ParticleSystem TracerTemplate;
|
|
/** The velocity the tracer should travel at */
|
|
var() int TracerVelocity;
|
|
/** Show the tracer when the weapon is firing in normal time */
|
|
var() bool bDoTracerDuringNormalTime;
|
|
/** Show the tracer when the weapon is firing in zed time */
|
|
var() bool bDoTracerDuringZedTime;
|
|
/** How far away the hitlocation has to be to spawn this tracer */
|
|
var int MinTracerEffectDistanceSquared;
|
|
/** Actual tracer velocity vector, set at runtime */
|
|
var vector VelocityVector;
|
|
|
|
structdefaultproperties
|
|
{
|
|
TracerVelocity=7000
|
|
bDoTracerDuringNormalTime=true
|
|
bDoTracerDuringZedTime=false
|
|
MinTracerEffectDistanceSquared=40000
|
|
}
|
|
};
|
|
|
|
/** Tracer effect info per fire mode */
|
|
var() array<KFTracerInfo> TracerInfos;
|
|
|
|
/*********************************************************************************************
|
|
* @name Material Effects
|
|
********************************************************************************************* */
|
|
|
|
/** MIC for blood effects */
|
|
var transient MaterialInstanceConstant WeaponMIC;
|
|
var transient float BloodParamValue;
|
|
|
|
const BloodParamName = 'Scalar_Blood_Contrast';
|
|
const MinBloodParamValue = 0.20f;
|
|
|
|
/*********************************************************************************************
|
|
* @name Animation - (using const FNames for 'static' data)
|
|
********************************************************************************************* */
|
|
|
|
/** Replicated state of the 1st person weapon */
|
|
enum EWeaponState
|
|
{
|
|
WEP_Idle,
|
|
WEP_Reload,
|
|
WEP_ReloadEmpty,
|
|
WEP_Reload_Elite,
|
|
WEP_ReloadEmpty_Elite,
|
|
WEP_ReloadSingle,
|
|
WEP_ReloadSingleEmpty,
|
|
WEP_ReloadSingle_Elite,
|
|
WEP_ReloadSingleEmpty_Elite,
|
|
WEP_ReloadSecondary,
|
|
WEP_ReloadSecondary_Elite,
|
|
WEP_ReloadSecondaryEmpty,
|
|
WEP_ReloadSecondaryEmpty_Elite,
|
|
WEP_ReloadDualsOneEmpty,
|
|
WEP_ReloadDualsOneEmpty_Elite,
|
|
WEP_MeleeBasic,
|
|
WEP_MeleeChain, // @deprecated
|
|
WEP_MeleeSustained,
|
|
WEP_Melee_L,
|
|
WEP_Melee_R,
|
|
WEP_Melee_F,
|
|
WEP_Melee_B,
|
|
WEP_MeleeHeavy_L,
|
|
WEP_MeleeHeavy_R,
|
|
WEP_MeleeHeavy_F,
|
|
WEP_MeleeHeavy_B,
|
|
WEP_MeleeBlock,
|
|
WEP_Cleaning,
|
|
WEP_Equipping,
|
|
WEP_PutAway,
|
|
WEP_Grenade,
|
|
WEP_Heal,
|
|
WEP_HealQuick,
|
|
WEP_Weld,
|
|
WEP_Custom0,
|
|
WEP_Custom1,
|
|
WEP_Custom2,
|
|
WEP_Custom3,
|
|
WEP_Custom4,
|
|
WEP_Custom5,
|
|
WEP_Custom6,
|
|
WEP_Custom7,
|
|
WEP_Custom8,
|
|
WEP_Custom9,
|
|
};
|
|
|
|
/** Animation rate to scale all of our 3rd person animations by */
|
|
var protected transient float ThirdPersonAnimRate;
|
|
|
|
/** Animation Physics Recoil */
|
|
var GameSkelCtrl_Recoil.RecoilDef Recoil_Hand;
|
|
var GameSkelCtrl_Recoil.RecoilDef Recoil_Spine;
|
|
|
|
/** MELEE */
|
|
const MeleeBasic = 'Melee';
|
|
const CH_MeleeBasic = 'Melee_CH';
|
|
/** directional attack anims */
|
|
const MeleeAnim_F = 'Atk_F';
|
|
const MeleeAnim_B = 'Atk_B';
|
|
const MeleeAnim_L = 'Atk_L';
|
|
const MeleeAnim_R = 'Atk_R';
|
|
const CH_MeleeAnim_F = 'Atk_F_CH';
|
|
const CH_MeleeAnim_B = 'Atk_B_CH';
|
|
const CH_MeleeAnim_L = 'Atk_L_CH';
|
|
const CH_MeleeAnim_R = 'Atk_R_CH';
|
|
/** heavy attack anims */
|
|
const MeleeHeavy_F = 'Atk_H_F';
|
|
const MeleeHeavy_B = 'Atk_H_B';
|
|
const MeleeHeavy_L = 'Atk_H_L';
|
|
const MeleeHeavy_R = 'Atk_H_R';
|
|
const CH_MeleeHeavy_F = 'Atk_H_F_CH';
|
|
const CH_MeleeHeavy_B = 'Atk_H_B_CH';
|
|
const CH_MeleeHeavy_L = 'Atk_H_L_CH';
|
|
const CH_MeleeHeavy_R = 'Atk_H_R_CH';
|
|
/** Continious (aka chainsaw) melee */
|
|
const MeleeLoopAnim = 'Atk_F_Loop';
|
|
const MeleeStartAnim = 'Atk_F_In';
|
|
const MeleeEndAnim = 'Atk_F_Out';
|
|
const CH_MeleeLoopAnim = 'Atk_F_Loop_CH';
|
|
const CH_MeleeStartAnim = 'Atk_F_In_CH';
|
|
const CH_MeleeEndAnim = 'Atk_F_Out_CH';
|
|
/** blocking/parry anims */
|
|
const BlockLoopAnim = 'Brace_Loop';
|
|
const BlockStartAnim = 'Brace_In';
|
|
const BlockEndAnim = 'Brace_Out';
|
|
const CH_BlockLoopAnim = 'Brace_Loop_CH';
|
|
const CH_BlockStartAnim = 'Brace_In_CH';
|
|
const CH_BlockEndAnim = 'Brace_Out_CH';
|
|
/** melee misc */
|
|
const CleanWeaponAnim = 'Clean_NoBlood';
|
|
const CH_CleanWeaponAnim = 'Clean_NoBlood_CH';
|
|
|
|
/** General, all inventory items anims */
|
|
const GrenadeAnim = 'Nade_Throw';
|
|
const EquipAnim = 'Equip';
|
|
const PutAwayAnim = 'PutAway';
|
|
const CH_GrenadeAnim = 'Nade_Throw_CH';
|
|
const CH_EquipAnim = 'Equip_CH';
|
|
const CH_PutAwayAnim = 'PutAway_CH';
|
|
|
|
/** Reload (magazine) anims */
|
|
const ReloadEmptyAnim = 'Reload_Empty';
|
|
const ReloadHalfAnim = 'Reload_Half';
|
|
const ReloadEmptyEliteAnim = 'Reload_Empty_Elite';
|
|
const ReloadHalfEliteAnim = 'Reload_Half_Elite';
|
|
const ReloadDualsOneEmptyAnim = 'Reload_Empty_Half';
|
|
const ReloadDualsOneEmptyEliteAnim = 'Reload_Empty_Half_Elite';
|
|
const CH_ReloadEmptyAnim = 'Reload_Empty_CH';
|
|
const CH_ReloadHalfAnim = 'Reload_Half_CH';
|
|
const CH_ReloadEmptyEliteAnim = 'Reload_Empty_Elite_CH';
|
|
const CH_ReloadHalfEliteAnim = 'Reload_Half_Elite_CH';
|
|
const CH_ReloadDualsOneEmptyAnim = 'Reload_Empty_Half_CH';
|
|
const CH_ReloadDualsOneEmptyEliteAnim = 'Reload_Empty_Half_Elite_CH';
|
|
/** Reload (single) anims */
|
|
const ReloadOpenAnim = 'Reload_Open';
|
|
const ReloadInsertAnim = 'Reload_Insert';
|
|
const ReloadCloseAnim = 'Reload_Close';
|
|
const ReloadOpenEliteAnim = 'Reload_Open_Elite';
|
|
const ReloadInsertEliteAnim = 'Reload_Insert_Elite';
|
|
const ReloadCloseEliteAnim = 'Reload_Close_Elite';
|
|
const ReloadOpenEmptyAnim = 'Reload_Open_Shell';
|
|
const ReloadOpenEmptyEliteAnim = 'Reload_Open_Shell_Elite';
|
|
const CH_ReloadOpenAnim = 'Reload_Open_CH';
|
|
const CH_ReloadInsertAnim = 'Reload_Insert_CH';
|
|
const CH_ReloadCloseAnim = 'Reload_Close_CH';
|
|
const CH_ReloadOpenEliteAnim = 'Reload_Open_Elite_CH';
|
|
const CH_ReloadInsertEliteAnim = 'Reload_Insert_Elite_CH';
|
|
const CH_ReloadCloseEliteAnim = 'Reload_Close_Elite_CH';
|
|
const CH_ReloadOpenEmptyAnim = 'Reload_Open_Shell_CH';
|
|
const CH_ReloadOpenEmptyEliteAnim = 'Reload_Open_Shell_Elite_CH';
|
|
|
|
/** Used only by the healing syringe */
|
|
const HealSelfAnim = 'Healer_Self';
|
|
const HealOtherAnim = 'Healer_F';
|
|
const QuickHealAnim = 'Heal_Quick';
|
|
const CH_HealSelfAnim = 'Healer_Self_CH';
|
|
const CH_HealOtherAnim = 'Healer_F_CH';
|
|
const CH_QuickHealAnim = 'Heal_Quick_CH';
|
|
/** Used only by the welder */
|
|
const WeldStartAnim = 'Welder_Start';
|
|
const WeldLoopAnim = 'Welder_Loop';
|
|
const WeldEndAnim = 'Welder_End';
|
|
const CH_WeldStartAnim = 'Welder_Start_CH';
|
|
const CH_WeldLoopAnim = 'Welder_Loop_CH';
|
|
const CH_WeldEndAnim = 'Welder_End_CH';
|
|
|
|
/** Additive shoot anims */
|
|
const ShootAnim = 'ADD_Shoot';
|
|
const CrouchShootAnim = 'ADD_CH_Shoot';
|
|
const IronShootAnim = 'ADD_Iron_Shoot';
|
|
/** Weapon shoots */
|
|
const WeaponFireAnim = 'Shoot';
|
|
const WeaponAltFireAnim = 'Shoot';
|
|
const WeaponIronFireAnim = 'Iron_Shoot';
|
|
|
|
/** (TEMP) blend settings */
|
|
var(Anims) float DefaultBlendInTime;
|
|
var(Anims) float DefaultBlendOutTime;
|
|
var(Anims) float ShootBlendInTime;
|
|
var(Anims) float ShootBlendOutTime;
|
|
|
|
/** Enable recoile skeletal controls */
|
|
var(Anims) bool bPlayIKRecoil;
|
|
|
|
/** Save the last random index so we can choose a different one */
|
|
var transient byte LastMeleeAnimIdx;
|
|
|
|
/** If set, check a character AnimNodeSlot and attempt to synchronize the weapon */
|
|
var transient bool bSynchronizeWeaponAnim;
|
|
var transient AnimNodeSlot SyncPawnNode;
|
|
var transient name SyncAnimName;
|
|
var transient bool bSyncAnimCheckRelevance;
|
|
|
|
/** Info for state LoopingWeaponAction */
|
|
var transient name LoopingAnim;
|
|
var transient name LoopIntroAnim;
|
|
var transient name LoopOutroAnim;
|
|
var transient bool bLoopSynchedWeaponAnim;
|
|
|
|
/** Cached AnimNodeSequence for the 3rd person weapon mesh */
|
|
var transient AnimNodeSequence WeapAnimNode;
|
|
|
|
/** Profile name for this weapon */
|
|
var(Anims) name AimOffsetProfileName;
|
|
|
|
/** Used by AimOffset node when bTurnOffWhenReloadingWeapon==TRUE */
|
|
var transient bool bIsReloading;
|
|
|
|
var transient int WeaponSkinId;
|
|
var bool bWaitingForWeaponSkinLoad;
|
|
|
|
/*********************************************************************************************
|
|
* @name Attach / Detach
|
|
********************************************************************************************* */
|
|
|
|
/** Weapon Mesh Attachment */
|
|
native event ChangeVisibility(bool bIsVisible);
|
|
|
|
/*********************************************************************************************
|
|
* @name Skins
|
|
********************************************************************************************* */
|
|
|
|
native function bool StartLoadWeaponSkin(int SkinId);
|
|
|
|
event PreBeginPlay()
|
|
{
|
|
local int i;
|
|
|
|
if ( WeapMesh != None && !bWeapMeshIsPawnMesh )
|
|
{
|
|
if ( WeaponAnimSet != None )
|
|
{
|
|
WeapMesh.AnimSets[0] = WeaponAnimSet;
|
|
}
|
|
|
|
if ( SkelMesh != none )
|
|
{
|
|
// set the skeletal mesh from our archetype to the WeaponAttachment
|
|
WeapMesh.SkeletalMesh = SkelMesh;
|
|
}
|
|
|
|
WeapAnimNode = AnimNodeSequence(WeapMesh.Animations);
|
|
}
|
|
|
|
for( i = 0; i < TracerInfos.Length; ++i )
|
|
{
|
|
TracerInfos[i].VelocityVector = vect(1,0,0) * TracerInfos[i].TracerVelocity;
|
|
}
|
|
|
|
super.PreBeginPlay();
|
|
}
|
|
|
|
/** Attach weapon to owner's skeletal mesh */
|
|
simulated function AttachTo(KFPawn P)
|
|
{
|
|
local byte WeaponAnimSetIdx;
|
|
|
|
if ( bWeapMeshIsPawnMesh )
|
|
{
|
|
WeapMesh = P.Mesh;
|
|
}
|
|
else if ( WeapMesh != None )
|
|
{
|
|
// Attach Weapon mesh to player skel mesh
|
|
WeapMesh.SetShadowParent(P.Mesh);
|
|
P.Mesh.AttachComponent(WeapMesh, P.WeaponAttachmentSocket);
|
|
}
|
|
|
|
// Additional attachments
|
|
if( bHasLaserSight && !P.IsFirstPerson() )
|
|
{
|
|
AttachLaserSight();
|
|
}
|
|
|
|
// Animation
|
|
if ( CharacterAnimSet != None )
|
|
{
|
|
WeaponAnimSetIdx = P.CharacterArch.GetWeaponAnimSetIdx();
|
|
P.Mesh.AnimSets[WeaponAnimSetIdx] = CharacterAnimSet;
|
|
// update animations will reload all AnimSeqs with the new AnimSet
|
|
P.Mesh.UpdateAnimations();
|
|
}
|
|
|
|
// update aim offset nodes with new profile for this weapon
|
|
P.SetAimOffsetNodesProfile(AimOffsetProfileName);
|
|
|
|
//Do a first chance weapon skin switch (EX: changed weapon w/o ID changing by throwing a dualie)
|
|
if (KFPawn_Human(P) != None && KFPawn_Human(P).WeaponSkinItemId > 0)
|
|
{
|
|
SetWeaponSkin(KFPawn_Human(P).WeaponSkinItemId);
|
|
}
|
|
}
|
|
|
|
/** Detach weapon from owner's skeletal mesh */
|
|
simulated function DetachFrom(KFPawn P)
|
|
{
|
|
// detach effects
|
|
if (MuzzleFlash != None)
|
|
{
|
|
MuzzleFlash.DetachMuzzleFlash(WeapMesh);
|
|
}
|
|
|
|
// Finally, detach weapon mesh
|
|
if ( bWeapMeshIsPawnMesh )
|
|
{
|
|
WeapMesh = None;
|
|
}
|
|
else if ( WeapMesh != None )
|
|
{
|
|
WeapMesh.SetShadowParent(None);
|
|
P.Mesh.DetachComponent( WeapMesh );
|
|
}
|
|
}
|
|
|
|
simulated function AttachMuzzleFlash()
|
|
{
|
|
if ( WeapMesh != none && MuzzleFlash == None )
|
|
{
|
|
MuzzleFlash = new(self) Class'KFMuzzleFlash'(MuzzleFlashTemplate);
|
|
MuzzleFlash.AttachMuzzleFlash(WeapMesh);
|
|
}
|
|
}
|
|
|
|
simulated function AttachLaserSight()
|
|
{
|
|
if ( WeapMesh != none && LaserSight == None && LaserSightArchetype != None )
|
|
{
|
|
LaserSight = new(self) Class'KFLaserSightAttachment' (LaserSightArchetype);
|
|
LaserSight.AttachLaserSight(WeapMesh, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign weapon skin to 3rd person mesh
|
|
*/
|
|
event SetWeaponSkin(int ItemId, optional bool bFinishedLoading = false)
|
|
{
|
|
local array<MaterialInterface> SkinMICs;
|
|
|
|
if ( ItemId > 0 && WorldInfo.NetMode != NM_DedicatedServer && !bWaitingForWeaponSkinLoad)
|
|
{
|
|
if (!bFinishedLoading && StartLoadWeaponSkin(ItemId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SkinMICs = class'KFWeaponSkinList'.static.GetWeaponSkin(ItemId, WST_ThirdPerson);
|
|
if ( SkinMICs.Length > 0 )
|
|
{
|
|
WeapMesh.SetMaterial(0, SkinMICs[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Fire Effect Methods
|
|
********************************************************************************************* */
|
|
|
|
/**
|
|
* Need to either call Instigator.ActorEffectIsRelevant(), or determine our
|
|
* own Location and LastRenderTime before calling Super.ActorEffectIsRelevant().
|
|
*/
|
|
simulated function bool ActorEffectIsRelevant(Pawn EffectInstigator, bool bForceDedicated, optional float VisibleCullDistance=5000.0, optional float HiddenCullDistance=350.0 )
|
|
{
|
|
if ( Instigator != None )
|
|
{
|
|
return Instigator.ActorEffectIsRelevant(EffectInstigator, bForceDedicated, VisibleCullDistance, HiddenCullDistance);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* The Weapon attachment, though hidden, is also responsible for controlling
|
|
* the first person effects for a weapon.
|
|
*/
|
|
simulated function FirstPersonFireEffects(Weapon W, vector HitLocation) // Should be subclassed
|
|
{
|
|
if ( W != None )
|
|
{
|
|
SpawnTracer(W.GetMuzzleLoc(), HitLocation);
|
|
}
|
|
}
|
|
|
|
simulated function StopFirstPersonFireEffects(Weapon W) // Should be subclassed
|
|
{
|
|
if ( W != None )
|
|
{
|
|
// Tell the weapon to cause the muzzle flash, etc.
|
|
W.StopFireEffects( Pawn(Owner).FiringMode );
|
|
}
|
|
}
|
|
|
|
simulated function StartFire()
|
|
{
|
|
|
|
}
|
|
|
|
simulated function StartPawnCrouch ()
|
|
{
|
|
|
|
}
|
|
|
|
simulated function EndPawnCrouch ()
|
|
{
|
|
|
|
}
|
|
|
|
simulated function SetWeaponUsingIronSights (bool bUsingIronSights)
|
|
{
|
|
}
|
|
|
|
simulated function SetWeaponAltFireMode (bool bUsingAltFireMode)
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* Spawn all of the effects that will be seen in behindview/remote clients. This
|
|
* function is called from the pawn, and should only be called when on a remote client or
|
|
* if the local client is in a 3rd person mode.
|
|
* @return TRUE if the effect culling check passes
|
|
*/
|
|
simulated function bool ThirdPersonFireEffects( vector HitLocation, KFPawn P, byte ThirdPersonAnimRateByte )
|
|
{
|
|
local EAnimSlotStance AnimType;
|
|
|
|
SpawnTracer(GetMuzzleLocation(), HitLocation);
|
|
|
|
// Effects below this point are culled based on visibility and distance
|
|
if ( !ActorEffectIsRelevant(P, false, MaxFireEffectDistance) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DecodeThirdPersonAnimRate( ThirdPersonAnimRateByte );
|
|
|
|
// Weapon shoot anims
|
|
if( !bWeapMeshIsPawnMesh )
|
|
{
|
|
PlayWeaponFireAnim();
|
|
}
|
|
|
|
if( P.IsDoingSpecialMove() && P.SpecialMoves[P.SpecialMove].bAllowFireAnims )
|
|
{
|
|
AnimType = EAS_Additive;
|
|
}
|
|
else
|
|
{
|
|
AnimType = EAS_FullBody;
|
|
}
|
|
|
|
// Character shoot anims
|
|
if ( !P.IsDoingSpecialMove() || AnimType == EAS_Additive )
|
|
{
|
|
PlayPawnFireAnim( P, AnimType );
|
|
|
|
// interrupt other weapon action anims (e.g. Reload)
|
|
if( !P.IsDoingSpecialMove() )
|
|
{
|
|
P.StopBodyAnim(P.bIsCrouched ? EAS_CH_UpperBody : EAS_UpperBody, 0.1f);
|
|
}
|
|
|
|
if ( OnWeaponStateChanged != None )
|
|
{
|
|
OnWeaponStateChanged(true);
|
|
}
|
|
}
|
|
|
|
CauseMuzzleFlash(P.FiringMode);
|
|
return true;
|
|
}
|
|
|
|
/** Plays fire animation on weapon mesh */
|
|
simulated function PlayWeaponFireAnim()
|
|
{
|
|
local float Duration;
|
|
|
|
if ( Instigator.bIsWalking )
|
|
{
|
|
Duration = WeapMesh.GetAnimLength( WeaponIronFireAnim );
|
|
WeapMesh.PlayAnim( WeaponIronFireAnim, Duration / ThirdPersonAnimRate,, true );
|
|
}
|
|
else
|
|
{
|
|
Duration = WeapMesh.GetAnimLength( WeaponFireAnim );
|
|
WeapMesh.PlayAnim( WeaponFireAnim, Duration / ThirdPersonAnimRate,, true );
|
|
}
|
|
}
|
|
|
|
/** Plays fire animation on pawn */
|
|
simulated function PlayPawnFireAnim( KFPawn P, EAnimSlotStance AnimType )
|
|
{
|
|
if ( P.bIsCrouched )
|
|
{
|
|
P.PlayBodyAnim(CrouchShootAnim, AnimType, ThirdPersonAnimRate, ShootBlendInTime, ShootBlendOutTime);
|
|
}
|
|
else if ( P.bIsWalking )
|
|
{
|
|
P.PlayBodyAnim(IronShootAnim, AnimType, ThirdPersonAnimRate, ShootBlendInTime, ShootBlendOutTime);
|
|
}
|
|
else
|
|
{
|
|
P.PlayBodyAnim(ShootAnim, AnimType, ThirdPersonAnimRate, ShootBlendInTime, ShootBlendOutTime);
|
|
}
|
|
}
|
|
|
|
simulated function StopThirdPersonFireEffects(optional bool bForce)
|
|
{
|
|
if (MuzzleFlash != None)
|
|
{
|
|
MuzzleFlash.StopMuzzleFlash(bForce);
|
|
}
|
|
}
|
|
|
|
/** @return the starting location for effects (e.g. tracers) */
|
|
simulated function vector GetMuzzleLocation(optional byte MuzzleID)
|
|
{
|
|
local vector SocketLocation;
|
|
|
|
if ( MuzzleFlashTemplate != none )
|
|
{
|
|
WeapMesh.GetSocketWorldLocationAndRotation(MuzzleFlashTemplate.GetSocketName(), SocketLocation);
|
|
return SocketLocation;
|
|
}
|
|
else
|
|
{
|
|
`log("Missing 3rd person muzzle socket for"@SkelMesh);
|
|
return WeapMesh.Bounds.Origin + (vect(45,0,0) >> Instigator.Rotation);
|
|
}
|
|
}
|
|
|
|
/** Spawn tracer effects for this weapon */
|
|
simulated function SpawnTracer(vector EffectLocation, vector HitLocation)
|
|
{
|
|
local ParticleSystemComponent E;
|
|
local vector Dir;
|
|
local float DistSQ;
|
|
local float TracerDuration;
|
|
local KFTracerInfo TracerInfo;
|
|
|
|
if ( Instigator == None || Instigator.FiringMode >= TracerInfos.Length )
|
|
{
|
|
return;
|
|
}
|
|
|
|
TracerInfo = TracerInfos[Instigator.FiringMode];
|
|
if( ((`NotInZedTime(self) && TracerInfo.bDoTracerDuringNormalTime)
|
|
|| (`IsInZedTime(self) && TracerInfo.bDoTracerDuringZedTime))
|
|
&& TracerInfo.TracerTemplate != none )
|
|
{
|
|
Dir = HitLocation - EffectLocation;
|
|
DistSQ = VSizeSq(Dir);
|
|
if ( DistSQ > TracerInfo.MinTracerEffectDistanceSquared )
|
|
{
|
|
// Lifetime scales based on the distance from the impact point. Subtract a frame so it doesn't clip.
|
|
TracerDuration = fMin( (Sqrt(DistSQ) - 100.f) / TracerInfo.TracerVelocity, 1.f );
|
|
if( TracerDuration > 0.f )
|
|
{
|
|
E = WorldInfo.MyEmitterPool.SpawnEmitter( TracerInfo.TracerTemplate, EffectLocation, rotator(Dir) );
|
|
E.SetVectorParameter( 'Tracer_Velocity', TracerInfo.VelocityVector );
|
|
E.SetFloatParameter( 'Tracer_Lifetime', TracerDuration );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Attach if needed, trigger muzzle flash, trigger shell eject */
|
|
simulated function CauseMuzzleFlash(byte FiringMode)
|
|
{
|
|
if (MuzzleFlash == None && MuzzleFlashTemplate != None)
|
|
{
|
|
AttachMuzzleFlash();
|
|
}
|
|
|
|
if (MuzzleFlash != None )
|
|
{
|
|
MuzzleFlash.CauseMuzzleFlash(FiringMode);
|
|
if ( MuzzleFlash.bAutoActivateShellEject )
|
|
{
|
|
MuzzleFlash.CauseShellEject();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** notify to spawn a shell eject from the muzzle flash component */
|
|
simulated function ANIMNOTIFY_ShellEject()
|
|
{
|
|
if (MuzzleFlash == None && MuzzleFlashTemplate != None)
|
|
{
|
|
MuzzleFlash = new(self) Class'KFMuzzleFlash'(MuzzleFlashTemplate);
|
|
MuzzleFlash.AttachMuzzleFlash(WeapMesh);
|
|
}
|
|
|
|
if (MuzzleFlash != None )
|
|
{
|
|
MuzzleFlash.CauseShellEject();
|
|
}
|
|
}
|
|
|
|
/** Adds some value to this weapon's blood material parameter */
|
|
simulated function AddBattleBlood(float InBloodParamIncrementValue)
|
|
{
|
|
// Weapon shoot anims
|
|
if( !bWeapMeshIsPawnMesh )
|
|
{
|
|
if ( WeaponMIC == None && WeapMesh != None )
|
|
{
|
|
WeaponMIC = WeapMesh.CreateAndSetMaterialInstanceConstant(0);
|
|
}
|
|
|
|
if ( WeaponMIC != None )
|
|
{
|
|
BloodParamValue = FMax(BloodParamValue + InBloodParamIncrementValue, MinBloodParamValue);
|
|
WeaponMIC.SetScalarParameterValue(BloodParamName, BloodParamValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Character Animation
|
|
********************************************************************************************* */
|
|
|
|
delegate OnWeaponStateChanged(optional bool bInterrupted);
|
|
|
|
/**
|
|
* Plays a split (upper and lower body) animation on the owning pawn
|
|
* Network: All but dedicated
|
|
*
|
|
* @param P Owning pawn to play animation on
|
|
* @param AnimName Anim to play
|
|
* @param bPlaySynchronizedWeaponAnim If true, try to play the same animation on the weapon mesh
|
|
*/
|
|
simulated function float PlayCharacterMeshAnim(KFPawn P, name AnimName, optional bool bPlaySynchedWeaponAnim, optional bool bLooping)
|
|
{
|
|
local float Duration;
|
|
local EAnimSlotStance Stance;
|
|
|
|
// skip weapon anims while in a special move
|
|
if( P.IsDoingSpecialMove() && !P.SpecialMoves[P.SpecialMove].bAllowThirdPersonWeaponAnims )
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
Stance = (!P.bIsCrouched) ? EAS_UpperBody : EAS_CH_UpperBody;
|
|
Duration = P.PlayBodyAnim(AnimName, Stance, ThirdPersonAnimRate, DefaultBlendInTime, DefaultBlendOutTime, bLooping);
|
|
|
|
if ( Duration > 0 && bPlaySynchedWeaponAnim )
|
|
{
|
|
PlayWeaponMeshAnim(AnimName, P.BodyStanceNodes[Stance], bLooping);
|
|
}
|
|
|
|
`log(GetFuncName()@"called on:"$P@"Anim:"$AnimName@"Duration:"$Duration, bDebug);
|
|
|
|
return Duration;
|
|
}
|
|
|
|
function DecodeThirdPersonAnimRate( byte ThirdPersonAnimRateByte )
|
|
{
|
|
ThirdPersonAnimRate = 1.f + ByteToFloat( ThirdPersonAnimRateByte );
|
|
}
|
|
|
|
/** Called from the pawn when our first person weapon changes states */
|
|
simulated function UpdateThirdPersonWeaponAction(EWeaponState NewWeaponState, KFPawn P, byte ThirdPersonAnimRateByte )
|
|
{
|
|
ClearTimer(nameof(LoopWeaponMeleeAnim));
|
|
bIsReloading = false;
|
|
|
|
// We need our anim rate scale before doing anything
|
|
DecodeThirdPersonAnimRate( ThirdPersonAnimRateByte );
|
|
|
|
if ( OnWeaponStateChanged != None )
|
|
{
|
|
OnWeaponStateChanged();
|
|
}
|
|
|
|
`log(GetFuncName()@"called on"@self@"State:"@EWeaponState(NewWeaponState), bDebug);
|
|
|
|
switch (NewWeaponState)
|
|
{
|
|
case WEP_Equipping:
|
|
PlayCharacterMeshAnim(P, P.bIsCrouched ? CH_EquipAnim : EquipAnim);
|
|
break;
|
|
case WEP_PutAway:
|
|
PlayCharacterMeshAnim(P, P.bIsCrouched ? CH_PutAwayAnim : PutAwayAnim);
|
|
break;
|
|
case WEP_Grenade:
|
|
PlayCharacterMeshAnim(P, P.bIsCrouched ? CH_GrenadeAnim : GrenadeAnim);
|
|
break;
|
|
case WEP_Heal:
|
|
PlayHealAnim(P);
|
|
break;
|
|
case WEP_HealQuick:
|
|
PlayCharacterMeshAnim(P, P.bIsCrouched ? CH_QuickHealAnim : QuickHealAnim);
|
|
break;
|
|
case WEP_Weld:
|
|
PlayWeldAnim(P);
|
|
break;
|
|
case WEP_Cleaning:
|
|
PlayCharacterMeshAnim(P, P.bIsCrouched ? CH_CleanWeaponAnim : CleanWeaponAnim);
|
|
break;
|
|
case WEP_MeleeBasic:
|
|
//case WEP_MeleeChain:
|
|
case WEP_Melee_B:
|
|
case WEP_Melee_F:
|
|
case WEP_Melee_L:
|
|
case WEP_Melee_R:
|
|
case WEP_MeleeHeavy_B:
|
|
case WEP_MeleeHeavy_F:
|
|
case WEP_MeleeHeavy_L:
|
|
case WEP_MeleeHeavy_R:
|
|
PlayMeleeAtkAnim(NewWeaponState, P);
|
|
break;
|
|
case WEP_MeleeSustained:
|
|
PlayMeleeSustainedAnim(P);
|
|
break;
|
|
case WEP_MeleeBlock:
|
|
PlayMeleeBlockAnim(P);
|
|
break;
|
|
case WEP_Reload:
|
|
case WEP_ReloadEmpty:
|
|
case WEP_Reload_Elite:
|
|
case WEP_ReloadEmpty_Elite:
|
|
case WEP_ReloadSecondary:
|
|
case WEP_ReloadSecondary_Elite:
|
|
case WEP_ReloadDualsOneEmpty:
|
|
case WEP_ReloadDualsOneEmpty_Elite:
|
|
case WEP_ReloadSecondaryEmpty:
|
|
case WEP_ReloadSecondaryEmpty_Elite:
|
|
bIsReloading = true;
|
|
PlayReloadMagazineAnim(NewWeaponState, P);
|
|
break;
|
|
case WEP_ReloadSingle:
|
|
case WEP_ReloadSingle_Elite:
|
|
case WEP_ReloadSingleEmpty:
|
|
case WEP_ReloadSingleEmpty_Elite:
|
|
bIsReloading = true;
|
|
PlayReloadSingleAnim(NewWeaponState, P);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Play a melee attack animation */
|
|
simulated function float PlayMeleeAtkAnim(EWeaponState NewWeaponState, KFPawn P)
|
|
{
|
|
local name AnimName;
|
|
local float Duration;
|
|
|
|
// third-person melee notifies don't properly handle the bIgnoreIfActorHidden setting because their owning actor isn't ever hidden (because it's the pawn),
|
|
// so just don't play third-person melee anims on first-person pawns (there's no need to anyway)
|
|
if( P.IsFirstPerson() )
|
|
{
|
|
return Duration;
|
|
}
|
|
|
|
switch (NewWeaponState)
|
|
{
|
|
case WEP_MeleeBasic:
|
|
AnimName = P.bIsCrouched ? CH_MeleeBasic : MeleeBasic;
|
|
break;
|
|
case WEP_MeleeHeavy_B:
|
|
AnimName = P.bIsCrouched ? CH_MeleeHeavy_B : MeleeHeavy_B;
|
|
break;
|
|
case WEP_MeleeHeavy_F:
|
|
AnimName = P.bIsCrouched ? CH_MeleeHeavy_F : MeleeHeavy_F;
|
|
break;
|
|
case WEP_MeleeHeavy_L:
|
|
AnimName = P.bIsCrouched ? CH_MeleeHeavy_L : MeleeHeavy_L;
|
|
break;
|
|
case WEP_MeleeHeavy_R:
|
|
AnimName = P.bIsCrouched ? CH_MeleeHeavy_R : MeleeHeavy_R;
|
|
break;
|
|
case WEP_Melee_B:
|
|
AnimName = P.bIsCrouched ? CH_MeleeAnim_B : MeleeAnim_B;
|
|
break;
|
|
case WEP_Melee_F:
|
|
AnimName = P.bIsCrouched ? CH_MeleeAnim_F : MeleeAnim_F;
|
|
break;
|
|
case WEP_Melee_L:
|
|
AnimName = P.bIsCrouched ? CH_MeleeAnim_L : MeleeAnim_L;
|
|
break;
|
|
case WEP_Melee_R:
|
|
AnimName = P.bIsCrouched ? CH_MeleeAnim_R : MeleeAnim_R;
|
|
break;
|
|
//case WEP_MeleeChain:
|
|
// AnimName = GetRandomDirectionalMeleeAnim(P);
|
|
// break;
|
|
}
|
|
|
|
if ( AnimName != '' )
|
|
{
|
|
Duration = PlayCharacterMeshAnim(P, AnimName);
|
|
|
|
//if ( WeaponState == WEP_MeleeChain && Duration > 0 )
|
|
//{
|
|
// SetTimer(FMax(0.01, Duration - MeleeLoopAtkTimeFromEnd), false, nameof(LoopWeaponMeleeAnim));
|
|
//}
|
|
}
|
|
|
|
return Duration;
|
|
}
|
|
|
|
/** Helper for PlayMeleeAtkAnim */
|
|
simulated function name GetRandomDirectionalMeleeAnim(Pawn P)
|
|
{
|
|
local byte AnimIdx, NumTries;
|
|
|
|
do
|
|
{
|
|
// pick a random animation, different than the last
|
|
AnimIdx = Rand(4);
|
|
NumTries++;
|
|
} until ( LastMeleeAnimIdx != AnimIdx || NumTries >= 4 );
|
|
|
|
LastMeleeAnimIdx = AnimIdx;
|
|
|
|
switch (AnimIdx)
|
|
{
|
|
case 0:
|
|
return P.bIsCrouched ? CH_MeleeAnim_F : MeleeAnim_F;
|
|
case 1:
|
|
return P.bIsCrouched ? CH_MeleeAnim_B : MeleeAnim_B;
|
|
case 2:
|
|
return P.bIsCrouched ? CH_MeleeAnim_L : MeleeAnim_L;
|
|
case 3:
|
|
return P.bIsCrouched ? CH_MeleeAnim_R : MeleeAnim_R;
|
|
}
|
|
}
|
|
|
|
/** Restart (aka Loop) this weapon state animation */
|
|
simulated function LoopWeaponMeleeAnim()
|
|
{
|
|
local KFPawn P;
|
|
|
|
P = KFPawn(Owner);
|
|
if ( P != None && !P.IsDoingSpecialMove() )
|
|
{
|
|
UpdateThirdPersonWeaponAction( WEP_MeleeChain, P, P.GetWeaponAttachmentAnimRateByte() );
|
|
}
|
|
}
|
|
|
|
/** Play a 3rd person weapon action anim */
|
|
simulated function PlayMeleeBlockAnim(KFPawn P)
|
|
{
|
|
if ( P.bIsCrouched )
|
|
{
|
|
StartLoopingAnim(P, CH_BlockLoopAnim, CH_BlockStartAnim, CH_BlockEndAnim);
|
|
}
|
|
else
|
|
{
|
|
StartLoopingAnim(P, BlockLoopAnim, BlockStartAnim, BlockEndAnim);
|
|
}
|
|
}
|
|
|
|
/** Play a 3rd person weapon action anim */
|
|
simulated function PlayMeleeSustainedAnim(KFPawn P)
|
|
{
|
|
if ( P.bIsCrouched )
|
|
{
|
|
StartLoopingAnim(P, CH_MeleeLoopAnim, CH_MeleeStartAnim, CH_MeleeEndAnim);
|
|
}
|
|
else
|
|
{
|
|
StartLoopingAnim(P, MeleeLoopAnim, MeleeStartAnim, MeleeEndAnim);
|
|
}
|
|
}
|
|
|
|
/** Play a 3rd person reload animation */
|
|
simulated function PlayReloadMagazineAnim(EWeaponState NewWeaponState, KFPawn P)
|
|
{
|
|
local name AnimName;
|
|
|
|
switch (NewWeaponState)
|
|
{
|
|
case WEP_Reload:
|
|
AnimName = (!P.bIsCrouched) ? ReloadHalfAnim : CH_ReloadHalfAnim;
|
|
break;
|
|
case WEP_ReloadEmpty:
|
|
AnimName = (!P.bIsCrouched) ? ReloadEmptyAnim : CH_ReloadEmptyAnim;
|
|
break;
|
|
case WEP_ReloadDualsOneEmpty:
|
|
AnimName = (!P.bIsCrouched) ? ReloadDualsOneEmptyAnim : CH_ReloadDualsOneEmptyAnim;
|
|
break;
|
|
case WEP_Reload_Elite:
|
|
AnimName = (!P.bIsCrouched) ? ReloadHalfEliteAnim : CH_ReloadHalfEliteAnim;
|
|
break;
|
|
case WEP_ReloadEmpty_Elite:
|
|
AnimName = (!P.bIsCrouched) ? ReloadEmptyEliteAnim : CH_ReloadEmptyEliteAnim;
|
|
break;
|
|
case WEP_ReloadDualsOneEmpty_Elite:
|
|
AnimName = (!P.bIsCrouched) ? ReloadDualsOneEmptyEliteAnim : CH_ReloadDualsOneEmptyEliteAnim;
|
|
break;
|
|
}
|
|
|
|
PlayCharacterMeshAnim(P, AnimName, true);
|
|
}
|
|
|
|
/** Play a 3rd person reload animation */
|
|
simulated function PlayReloadSingleAnim(EWeaponState NewWeaponState, KFPawn P)
|
|
{
|
|
switch (NewWeaponState)
|
|
{
|
|
case WEP_ReloadSingle:
|
|
if ( P.bIsCrouched )
|
|
StartLoopingAnim(P, CH_ReloadInsertAnim, CH_ReloadOpenAnim, CH_ReloadCloseAnim, true);
|
|
else
|
|
StartLoopingAnim(P, ReloadInsertAnim, ReloadOpenAnim, ReloadCloseAnim, true);
|
|
break;
|
|
case WEP_ReloadSingle_Elite:
|
|
if ( P.bIsCrouched )
|
|
StartLoopingAnim(P, CH_ReloadInsertEliteAnim, CH_ReloadOpenEliteAnim, CH_ReloadCloseEliteAnim, true);
|
|
else
|
|
StartLoopingAnim(P, ReloadInsertEliteAnim, ReloadOpenEliteAnim, ReloadCloseEliteAnim, true);
|
|
break;
|
|
case WEP_ReloadSingleEmpty:
|
|
if ( P.bIsCrouched )
|
|
StartLoopingAnim(P, CH_ReloadInsertAnim, CH_ReloadOpenEmptyAnim, CH_ReloadCloseAnim, true);
|
|
else
|
|
StartLoopingAnim(P, ReloadInsertAnim, ReloadOpenEmptyAnim, ReloadCloseAnim, true);
|
|
break;
|
|
case WEP_ReloadSingleEmpty_Elite:
|
|
if ( P.bIsCrouched )
|
|
StartLoopingAnim(P, CH_ReloadInsertEliteAnim, CH_ReloadOpenEmptyEliteAnim, CH_ReloadCloseEliteAnim, true);
|
|
else
|
|
StartLoopingAnim(P, ReloadInsertEliteAnim, ReloadOpenEmptyEliteAnim, ReloadCloseEliteAnim, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Play a 3rd person heal animation */
|
|
simulated function PlayHealAnim(KFPawn P)
|
|
{
|
|
if ( P.bIsCrouched )
|
|
{
|
|
PlayCharacterMeshAnim(P, (P.FiringMode == 0) ? CH_HealOtherAnim : CH_HealSelfAnim);
|
|
}
|
|
else
|
|
{
|
|
PlayCharacterMeshAnim(P, (P.FiringMode == 0) ? HealOtherAnim : HealSelfAnim);
|
|
}
|
|
}
|
|
|
|
/* Plays a 3rd person weld animation */
|
|
simulated function PlayWeldAnim(KFPawn P)
|
|
{
|
|
if ( P.bIsCrouched )
|
|
{
|
|
StartLoopingAnim(P, CH_WeldLoopAnim, CH_WeldStartAnim, CH_WeldEndAnim);
|
|
}
|
|
else
|
|
{
|
|
StartLoopingAnim(P, WeldLoopAnim, WeldStartAnim, WeldEndAnim);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Weapon Animation
|
|
********************************************************************************************* */
|
|
|
|
simulated function PlayWeaponMeshAnim(name AnimName, AnimNodeSlot SyncNode, bool bLoop)
|
|
{
|
|
local float Duration;
|
|
|
|
// Weapon shoot anims
|
|
if( !bWeapMeshIsPawnMesh )
|
|
{
|
|
Duration = WeapMesh.GetAnimLength(AnimName);
|
|
WeapMesh.PlayAnim(AnimName, Duration / ThirdPersonAnimRate, bLoop);
|
|
|
|
// syncronize this with the character anim
|
|
if ( SyncNode != None )
|
|
{
|
|
bSynchronizeWeaponAnim = true;
|
|
SyncPawnNode = SyncNode;
|
|
SyncAnimName = AnimName;
|
|
bSyncAnimCheckRelevance = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Tick is used by bSynchronizeWeaponAnim */
|
|
simulated event Tick( float DeltaTime )
|
|
{
|
|
// If we're playing a a syncronzied weapon action, check the owner pawn's animation
|
|
if ( !bWeapMeshIsPawnMesh && bSynchronizeWeaponAnim && SyncPawnNode != None && WeapMesh.bForceRefpose == 0 )
|
|
{
|
|
// check to see if the character anim is still playing
|
|
if ( !SyncPawnNode.bIsPlayingCustomAnim
|
|
|| (!SyncPawnNode.bRelevant && bSyncAnimCheckRelevance)
|
|
|| SyncPawnNode.GetPlayedAnimation() != SyncAnimName )
|
|
{
|
|
if ( WeapAnimNode.bPlaying && WeapAnimNode.AnimSeqName == SyncAnimName )
|
|
{
|
|
InterruptWeaponAnim();
|
|
}
|
|
bSynchronizeWeaponAnim = false;
|
|
}
|
|
|
|
// After character mesh has been ticked once start checking node relevance
|
|
bSyncAnimCheckRelevance = true;
|
|
}
|
|
}
|
|
|
|
/** Stops a currently playing 3rd person weapon animation */
|
|
simulated function InterruptWeaponAnim()
|
|
{
|
|
// Weapon shoot anims
|
|
if( !bWeapMeshIsPawnMesh )
|
|
{
|
|
WeapAnimNode.StopAnim();
|
|
|
|
// Return to RefPos, because StopAnim doesn't call OnAnimEnd
|
|
if ( WeapAnimNode.bForceRefposeWhenNotPlaying )
|
|
{
|
|
WeapMesh.SetForceRefPose(TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Rendering/Lighting
|
|
********************************************************************************************* */
|
|
|
|
/** Set the lighting channels on all the appropriate weapon attachment mesh(es) */
|
|
simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
|
|
{
|
|
if( !bWeapMeshIsPawnMesh )
|
|
{
|
|
WeapMesh.SetLightingChannels(NewLightingChannels);
|
|
}
|
|
|
|
if( LaserSight != none )
|
|
{
|
|
LaserSight.SetMeshLightingChannels(NewLightingChannels);
|
|
}
|
|
}
|
|
|
|
/** Debug */
|
|
simulated function bool HasIndoorLighting()
|
|
{
|
|
return WeapMesh.LightingChannels.Indoor;
|
|
}
|
|
|
|
/** Debug */
|
|
simulated function bool HasOutdoorLighting()
|
|
{
|
|
return WeapMesh.LightingChannels.Outdoor;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State LoopingWeaponAction
|
|
* Plays a looping character animation with optional intro/outro anims. Most weapon
|
|
* actions don't use state logic, but looping is complex enough that states are useful.
|
|
*********************************************************************************************/
|
|
|
|
simulated function StartLoopingAnim(KFPawn P, name InLoopAnim, optional name InIntroAnim, optional name InOutroAnim, optional bool bPlaySynchedWeaponAnim)
|
|
{
|
|
if ( !P.IsDoingSpecialMove() )
|
|
{
|
|
LoopingAnim = InLoopAnim;
|
|
LoopIntroAnim = InIntroAnim;
|
|
LoopOutroAnim = InOutroAnim;
|
|
bLoopSynchedWeaponAnim = bPlaySynchedWeaponAnim;
|
|
|
|
GotoState('LoopingWeaponAction');
|
|
}
|
|
}
|
|
|
|
// Global declarations for LoopingWeaponAction
|
|
simulated function PlayLoopAnim();
|
|
|
|
simulated State LoopingWeaponAction
|
|
{
|
|
/** Play intro anim and start timer */
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
local KFPawn P;
|
|
local float Duration;
|
|
|
|
P = KFPawn(Owner);
|
|
if ( LoopIntroAnim != '' )
|
|
{
|
|
Duration = PlayCharacterMeshAnim(P, LoopIntroAnim, bLoopSynchedWeaponAnim);
|
|
if ( Duration > 0 )
|
|
{
|
|
// 0.2f should match the blend in time of the loop anim
|
|
SetTimer(Duration - 0.2f, false, nameof(PlayLoopAnim));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// no intro, start immediately
|
|
PlayLoopAnim();
|
|
}
|
|
|
|
/** Make sure looping anim is stopped */
|
|
simulated function EndState(Name NextStateName)
|
|
{
|
|
local KFPawn P;
|
|
|
|
P = KFPawn(Owner);
|
|
if ( P != None )
|
|
{
|
|
P.StopBodyAnim(EAS_UpperBody, 0.1f);
|
|
P.StopBodyAnim(EAS_CH_UpperBody, 0.1f);
|
|
}
|
|
|
|
if ( bLoopSynchedWeaponAnim && bSynchronizeWeaponAnim )
|
|
{
|
|
InterruptWeaponAnim();
|
|
}
|
|
}
|
|
|
|
/** Play main looping anim */
|
|
simulated function PlayLoopAnim()
|
|
{
|
|
local KFPawn P;
|
|
P = KFPawn(Owner);
|
|
if ( P != None )
|
|
{
|
|
PlayCharacterMeshAnim(P, LoopingAnim, bLoopSynchedWeaponAnim, true);
|
|
}
|
|
}
|
|
|
|
/** Stop looping state and play outro anim */
|
|
simulated function UpdateThirdPersonWeaponAction( EWeaponState NewWeaponState, KFPawn P, byte ThirdPersonAnimRateByte )
|
|
{
|
|
GotoState('');
|
|
|
|
if( LoopOutroAnim != '' )
|
|
{
|
|
DecodeThirdPersonAnimRate( ThirdPersonAnimRateByte );
|
|
PlayCharacterMeshAnim(P, LoopOutroAnim, bLoopSynchedWeaponAnim);
|
|
}
|
|
|
|
Global.UpdateThirdPersonWeaponAction( NewWeaponState, P, ThirdPersonAnimRateByte );
|
|
}
|
|
|
|
/** Stop looping state */
|
|
simulated function bool ThirdPersonFireEffects( vector HitLocation, KFPawn P, byte ThirdPersonAnimRateByte )
|
|
{
|
|
GotoState('');
|
|
return Global.ThirdPersonFireEffects( HitLocation, P, ThirdPersonAnimRateByte );
|
|
}
|
|
|
|
/** Stop looping state */
|
|
simulated function DetachFrom(KFPawn P)
|
|
{
|
|
GotoState('');
|
|
Global.DetachFrom(P);
|
|
}
|
|
}
|
|
|
|
/** Special event added for weap attachments. Free for use */
|
|
function OnSpecialEvent(int Arg);
|
|
|
|
defaultproperties
|
|
{
|
|
Begin Object class=AnimNodeSequence Name=MeshSequenceA
|
|
bForceRefposeWhenNotPlaying=true
|
|
End Object
|
|
|
|
// Weapon SkeletalMesh
|
|
Begin Object Class=SkeletalMeshComponent Name=SkeletalMeshComponent0
|
|
bOwnerNoSee=true
|
|
bOnlyOwnerSee=false
|
|
CollideActors=false
|
|
AlwaysLoadOnClient=true
|
|
AlwaysLoadOnServer=true
|
|
MaxDrawDistance=4000
|
|
bUpdateSkelWhenNotRendered=false
|
|
bIgnoreControllersWhenNotRendered=true
|
|
bOverrideAttachmentOwnerVisibility=true
|
|
bAcceptsDynamicDecals=FALSE
|
|
Animations=MeshSequenceA
|
|
CastShadow=true
|
|
bCastDynamicShadow=true
|
|
bPerBoneMotionBlur=true
|
|
bForceRefPose=1
|
|
// Default to outdoor. If indoor, this will be set when TWIndoorLightingVolume::Touch() event is received at spawn.
|
|
LightingChannels=(Outdoor=TRUE,bInitialized=TRUE)
|
|
End Object
|
|
WeapMesh=SkeletalMeshComponent0
|
|
|
|
LaserSightArchetype=KFLaserSightAttachment'FX_LaserSight_ARCH.Default_LaserSight_3P'
|
|
|
|
TickGroup=TG_DuringAsyncWork
|
|
NetUpdateFrequency=10
|
|
RemoteRole=ROLE_None
|
|
bReplicateInstigator=true
|
|
|
|
MaxFireEffectDistance=5000.0
|
|
AimOffsetProfileName=Default
|
|
|
|
// temp
|
|
DefaultBlendInTime=0.2f
|
|
DefaultBlendOutTime=0.2f
|
|
ShootBlendInTime=0.1
|
|
ShootBlendOutTime=0.1
|
|
|
|
ThirdPersonAnimRate=1.0f
|
|
LastMeleeAnimIdx=255
|
|
bWaitingForWeaponSkinLoad=false
|
|
}
|