1
0
KF2-Dev-Scripts/KFGame/Classes/KFPawn.uc
2022-09-01 18:58:51 +03:00

5824 lines
185 KiB
Ucode

//=============================================================================
// KFPawn
//=============================================================================
// Base Pawn for KFGame
//=============================================================================
// Killing Floor 2
// Copyright (C) 2012 Tripwire Interactive LLC
//=============================================================================
class KFPawn extends BaseAIPawn
abstract
nativereplication
native(Pawn)
dependson(KFPhysicalMaterialProperty,KFPawnVoiceGroup,KFAfflictionManager);
`include(KFGame\KFGameAnalytics.uci);
`include(KFGame\KFMatchStats.uci);
/*********************************************************************************************
* @name Character Info
********************************************************************************************* */
/** This pawn's current family/class info **/
var KFCharacterInfoBase CharacterArch;
/** This pawn's sound group archetype based on character info */
var KFPawnSoundGroup SoundGroupArch;
/** This pawn's dialog group class based on character info */
var class<KFPawnVoiceGroup> VoiceGroupArch;
/** This pawn's anim info archetype based on character info */
var KFPawnAnimInfo PawnAnimInfo;
/** This classes key to look up name to be displayed in UI **/
var name LocalizationKey;
var Texture2D CharacterPortrait;
/*********************************************************************************************
* @name Pawn meshes and mesh components
********************************************************************************************* */
/** The lighting channel used by the pawn's meshes including the weapon and weapon attachments */
var transient LightingChannelContainer PawnLightingChannel;
/** Third person head component. Used by customizable characters only */
var SkeletalMeshComponent ThirdPersonHeadMeshComponent;
/** Third person cosmetic attachments. Used by customizable characters only */
var name ThirdPersonAttachmentSocketNames[`MAX_COSMETIC_ATTACHMENTS];
var MeshComponent ThirdPersonAttachments[`MAX_COSMETIC_ATTACHMENTS];
/** First person cosmetic attachments. */
var name FirstPersonAttachmentSocketNames[`MAX_COSMETIC_ATTACHMENTS];
var MeshComponent FirstPersonAttachments[`MAX_COSMETIC_ATTACHMENTS];
/**
* Character mesh MICs that are used for material params during gameplay
* 0: Always the main body (Replaced with the gore mesh)
* 1: Head MIC for Humans. Alternate body MIC for complex zeds
*/
var array<MaterialInstanceConstant> CharacterMICs;
/** Whether to allow always on physics such as jiggly parts, cloth, etc */
var globalconfig bool bAllowAlwaysOnPhysics;
/** True if switched to Gore Skeleton/Physics Asset */
var const bool bIsGoreMesh;
/*********************************************************************************************
* @name Damage & Hit Zones
********************************************************************************************* */
/** All pawns share a common head index. Use enum to access from any class */
enum EHitZoneIndex
{
HZI_HEAD,
};
var transient float LastHeadShotReceivedTime;
struct native HitZoneInfo
{
var() name ZoneName; // The name of this hitzone
var() name BoneName; // Name of the bone that corresponds to this hitzone
var() int GoreHealth; // The base amount of health for this hitzone, and stores health this zone has left (Not Replicated)
var() int MaxGoreHealth; // Max for this hit zone. Copied from GoreHealth if it isn't initialized at runtime
var() float DmgScale; // Damage multiplier for damage taken on this hitzone
var() EHitZoneBodyPart Limb; // Group zones together for hit reactions
var() byte SkinID; // ID used for impact effects
var transient bool bPlayedInjury; // When this hit zone was dismembered from the body
//var delegate<OnHitZoneInjury> OnInjury; // Called when zone Health reaches 0
structdefaultproperties
{
GoreHealth=50
MaxGoreHealth=-1
DmgScale=1.f
}
};
var() array<HitZoneInfo> HitZones;
/** Tracks when the Pawn died */
var transient float TimeOfDeath;
/** Set only while inside TakeRadiusDamage */
var transient bool bTakingRadiusDamage;
/** How much resistance this pawn has to penetration. Whenever a projectile or
* weapon traces passes through this pawn and damages it, that projectile
* or weapon trace's PenetrationPower will be reduced by this amount.
* Additionally a weapon trace or projectile won't penetrate this pawn if the
* PenetrationPower is not greater than this resistance.
*/
var() float PenetrationResistance;
/** Used to track damage over time */
struct native DamageOverTimeInfo
{
/** How much damage to apply at the Interval */
var transient int Damage;
/** The type of damage to apply at the Interval */
var transient class<KFDamageType> DamageType;
/** The type of damage to apply at the Interval */
var transient byte DoT_Type;
/** How long to keep applying damage over time */
var transient float Duration;
/** How often to apply the DoT_Damage */
var transient float Interval;
/** Countdown timer for next damage interval */
var transient float TimeUntilNextDamage;
/** The controller that caused this damage */
var transient Controller InstigatedBy;
};
/** Array of damage that must be applied over time */
var array<DamageOverTimeInfo> DamageOverTimeArray;
/** Track explosives which deal reduced damage when stacked */
struct native ExplosiveStackInfo
{
var GameExplosionActor Explosion;
var float LastHitTime;
};
/** List of active explosives that deal reduced damage when stacked */
var array<ExplosiveStackInfo> RecentExplosiveStacks;
/** The last time this pawn dealt or received any damage */
var transient float LastTimeDamageHappened;
/** Scale used in crease damage when crushed. */
var float CrushScale;
/** Current damage scalar from any volume I may be in */
var float VolumeDamageScale;
/*********************************************************************************************
* Scoring/Dosh Distribution
********************************************************************************************* */
struct native DamageInfo
{
/** PRI from the player that did damage to us */
var Controller DamagerController;
/** PRI from the player that did damage to us */
var PlayerReplicationInfo DamagerPRI;
/** damage done from one player, might be reset during gameplay */
var float Damage;
/** Total damage done from one player total */
var float TotalDamage;
/** Last time the zed was damaged */
var float LastTimeDamaged;
/** List of each perk that should get xp for this damage */
var array<class<KFPerk> > DamagePerks;
var array<class<Actor> > DamageCausers;
var array<class<KFDamageType> > DamageTypes;
};
// VFX that need to linger when a specific DamageType starts (those should loop), we stop them when the DamageType ends affecting
var() ParticleSystem Toxic_HRG_Locust_LoopingParticleEffect;
var transient ParticleSystemComponent Toxic_HRG_Locust_LoopingPSC;
/** List of PRIs who damaged the specimen */
var array<DamageInfo> DamageHistory;
/*********************************************************************************************
* @name HitFX & Gore
********************************************************************************************* */
/** replicated information on a hit we've taken */
struct native KFHitFxInfo
{
/** the location of the hit - Do not use this directly */
var vector HitLocation;
/** Momentum is only replicated as a normalized HitDir. Momentum transfer is handled server-side */
var vector EncodedHitDirection;
/** the damage type we were hit with */
var class<KFDamageType> DamageType;
/** the zone that was hit on our Mesh (if any) */
var byte HitBoneIndex;
/** Whether the pawn dealt radial damage */
var bool bRadialDamage;
/** Whether the pawn was obliterated */
var bool bObliterated;
//for special hit effects
var PlayerReplicationInfo DamagerPRI;
};
/** Additional replicated data if bRadialDamage = true */
struct native KFRadialHitFxInfo
{
/** Radius damage scale (falloff) used for ragdoll impulses */
var byte RadiusDamageScale;
/** Radius damage hurt origin used for ragdoll impulses. Will be heavily compressed (11 bits?) when it's 0 */
var vector RadiusHurtOrigin;
};
/** replicated information on a hit we've taken */
var repnotify KFHitFxInfo HitFxInfo;
/** replicated information radial information */
var KFRadialHitFxInfo HitFxRadialInfo;
/** the weapon that shot us */
var Pawn HitFxInstigator;
/** Replicate additionational hit locations for blood fx for certain weapons (ex. Shotguns) */
const MAX_ADDED_HITFX = 7;
/** Store the additional hit locations in local space to replicate easier */
var vector HitFxAddedRelativeLocs[MAX_ADDED_HITFX];
/** The number of hits this pawn has taken in a frame */
var byte HitFxAddedHitCount;
/** Set to true when we want to update HitFx at the end of the tick */
var bool bNeedsProcessHitFx;
/** stop considering HitFxInfo for replication when world time passes this (so we don't replicate out-of-date hits when pawns become relevant) */
var float LastTakeHitTimeout;
/** record value from TakeRadiusDamage to be used later this frame for PlayHit, these are not replicated */
var byte LastRadiusDamageScale;
var vector LastRadiusHurtOrigin;
/** The max number of times we should try to grab a parent bone if no bone is found on our ragdoll */
const MAX_GET_RBBONE_CHECKS = 3;
// Gore Materials
var array<MaterialInstance> BloodSplatterDecalMaterials;
var array<MaterialInstance> BloodPoolDecalMaterials;
// For ambient battle blood
var const name BattleBloodParamName;
var const float MinBattleBloodValue;
var const float BattleBloodRangeSq;
var transient float BattleBloodParamValue;
/** Names for specific bones in the skeleton */
var name LeftFootBoneName;
var name RightFootBoneName;
var name LeftHandBoneName;
var name RightHandBoneName;
var name HeadBoneName;
var name TorsoBoneName;
var name PelvisBoneName;
/** If TRUE we broke at least one constraints off the Gore mesh. */
var bool bHasBrokenConstraints;
/** Setting to control whether we allow ragdoll and dismemberment on dead bodies */
var globalconfig bool bAllowRagdollAndGoreOnDeadBodies;
/** The time when a gib last collided with something in the world (relative to WorldInfo.TimeSeconds) */
var transient float LastGibCollisionTime;
/** Whether or not to reinit phys asset on death */
var bool bReinitPhysAssetOnDeath;
/** Whether or not to allow the use of the death special move */
var bool bAllowDeathSM;
/** Scale to apply to mesh when changed */
var float IntendedBodyScale;
var float CurrentBodyScale;
/** Max amount per second that can change */
var float BodyScaleChangePerSecond;
/** Scales for tracking head size, IE: Big head mode */
var repnotify float IntendedHeadScale;
var float CurrentHeadScale;
/** Whether pawn can be pinned to a wall by pinning projectiles */
var bool bCanBePinned;
/*********************************************************************************************
* @name Status Effects
********************************************************************************************* */
/* Manages various types of afflictions that this pawn may have that has smoe type of gameplay affect (such as panicking from fire, disrupted by EMP, etc) */
var instanced KFAfflictionManager AfflictionHandler;
/** Afflications that accumlate/decay over time and can stack with eachother (e.g. Panic, Burning) and are triggered when the accumulated value crosses the threshold */
var array<IncapSettingsInfo> IncapSettings;
/** Bit-flags 0:Alive 1:Dead. Up to (first) 32 hit zones */
var repnotify int InjuredHitZones;
/** This pawn is currently under the influense of the EMP distruption effect */
var repnotify bool bEmpDisrupted;
/** This pawn is currently Panicked by the EMP effect */
var repnotify bool bEmpPanicked;
/** This pawn is currently Panicked by the EMP effect */
var repnotify bool bFirePanicked;
/** Replicated the remaining StackedPower for fire on death so the zed keeps burning */
var byte DeathFireStackedPower;
/** How burned is this zed currently? Replicated version */
var repnotify byte RepFireBurnedAmount;
/** The last time a damage-type specific impact effect was spawned */
var float LastImpactParticleEffectTime;
var float LastImpactSoundTime;
/*********************************************************************************************
* @name Rigid Body Hit reactions
********************************************************************************************* */
struct native KnockdownImpulseInfo
{
/** Linear velocity to apply to entire rigid body. */
var vector LinearVelocity;
/** Angular velocity to apply to entire rigid body. */
var vector AngularVelocity;
/** Dual purpose: Point Impulse Position | Radial Impulse Origin */
var vector ImpulsePosition;
/** Dual purpose: Point Impulse | Radial (X=Radius, Y=Strength) */
var vector ImpulseStrength;
/** Bone that receives point impulse. */
//var Name PointImpulseBoneName;
/** Use hit zone id instead for net bandwidth */
var byte PointImpulseHitZone;
/** Should Impulse Pos/Str be applied as point or radial */
var bool bIsRadialImpulse;
};
var KnockdownImpulseInfo KnockdownImpulse;
/** Struct used to replicated root body position when knocked down (not dead) */
struct native ReplicatedRootPosInfo
{
var vector Position;
var bool bNewData;
};
var ReplicatedRootPosInfo ReplicatedRootBodyPos;
/** If Pawn can or cannot play physics hit reactions. */
var(Physics) bool bCanPlayPhysicsHitReactions;
/** Scale impulses by this amount for Physics Hit Reactions. */
var(Physics) float PhysicsHitReactionImpulseScale;
/** List of RB bone names which should be UnFixed when playing a Physics Body Impact. */
var(Physics) editinline Array<Name> PhysicsBodyImpactBoneList;
/** List of RidigBodies where a spring should be attached to animation. */
var(Physics) editinline Array<Name> PhysicsImpactSpringList;
/** Time it takes to blend back to animations after being turned into physics. */
var(Physics) float PhysicsImpactBlendOutTime;
/** Time left to play this physics impact */
var float PhysicsImpactBlendTimeToGo;
/** Artifically scale impulses, to counter mass, for ragdoll hit reactions. */
var(Physics) float PhysRagdollImpulseScale;
/** As above, but applied to living (aka knockdown) impulse */
var(Physics) float KnockdownImpulseScale;
/** Ragdoll insomnia detection */
var const byte RagdollWarningLevel;
var const float NextRagdollFailsafeTime;
var const vector LastRootRigidBodyTestLoc;
/*********************************************************************************************
* @name Movement & Physics
********************************************************************************************* */
/** Base movement speed while sprinting */
var float SprintSpeed;
/** currently sprinting */
var bool bIsSprinting;
/** whether or not sprinting is allowed */
var bool bAllowSprinting;
/** Movement rate while sprinting + strafe/backpedal. If zero, ignore and move at full speed */
var float SprintStrafeSpeed;
/** Replicated Floor property, used to set Floor on the client (for anims), currently used by Crawler */
var repnotify vector ReplicatedFloor;
/** Collision property for KFGameInfo's bDisableTeamCollision */
var bool bIgnoreTeamCollision;
/** Reduces the collision radius between pawns on the same team */
var float TeammateCollisionRadiusPercent;
/** If set, counter the World's TimeDilation using this Pawn's CustomTimeDilation */
var bool bUnaffectedByZedTime;
/** If true, will always move at normal speed in zed time */
var bool bMovesFastInZedTime;
/** Scale to use when moving in zed time. bMovesFastInZedTime must be set to TRUE */
var protected float ZedTimeSpeedScale;
/** Scale to use when moving with a speed reducing affliction. */
var float AfflictionSpeedModifier;
/** Scale to use when doing an attack */
var float AttackSpeedModifier;
/** Whether or not we are currently jumping. Allows for more specific checking of PHYS_Falling */
var bool bJumping;
/** Amount of multi-jumps allowed */
var int NumJumpsAllowed;
/** Amount remaining in current multi-jump cycle */
var int NumJumpsRemaining;
/*********************************************************************************************
* @name Camera
********************************************************************************************* */
/** Base crouched eye height from bottom of the collision cylinder. */
var(Camera) float BaseCrouchEyeHeight;
/** If true enable weapon view bob */
var const bool bWeaponBob;
/** view bob properties */
var const float Bob<ClampMin=-0.05 | ClampMax=0.05>;
/** if true, UpdateEyeheight() will get called every tick */
var bool bUpdateEyeheight;
/** Old Z Location - used for eyeheight smoothing */
var float OldZ;
var float LandBob;
var float JumpBob;
var float AppliedBob;
var float BobTime;
var vector WalkBob;
var bool bJustLanded; /** used by eyeheight adjustment. True if pawn recently landed from a fall */
var bool bLandRecovery; /** used by eyeheight adjustment. True if pawn recovering (eyeheight increasing) after lowering from a landing */
/*********************************************************************************************
* @name Weapon
********************************************************************************************* */
/** Default inventory added via AddDefaultInventory() */
var() array< class<Inventory> > DefaultInventory;
var() array<Inventory> DefaultInventoryArchetypes;
/** Holds the class type of the current weapon attachment. */
var KFWeaponAttachment WeaponAttachmentTemplate;
/** Holds the class type of the current weapon attachment's weapon class. Replicated to all clients. */
var repnotify class<KFWeapon> WeaponClassForAttachmentTemplate;
/** This holds the local copy of the current attachment. This "attachment" actor will exist independantly on all clients */
var KFWeaponAttachment WeaponAttachment;
/** client side flag indicating whether attachment should be visible - primarily used when spawning the initial weapon attachment
* as events that change its visibility might have happened before we received a WeaponAttachmentTemplate
* to spawn and call the visibility functions on
*/
var bool bWeaponAttachmentVisible;
/** First person view left and right arm meshes */
var KFSkeletalMeshComponent ArmsMesh;
/** Name of the socket used for attaching third person weapons to this pawn. */
var name WeaponAttachmentSocket;
/** Reference to Weapon carried by Pawn. Set in PlayWeaponSwitch() when a new weapon is set. */
var KFWeapon MyKFWeapon;
/** Last time WeaponFired() was called, used by animation */
var transient float LastWeaponFireTime;
/** Whether this pawn needs a crosshair regardless of whether it has a weapon */
var bool bNeedsCrosshair;
/*********************************************************************************************
* @name Animation
********************************************************************************************* */
/* 2d division of space around this pawn */
enum EPawnOctant
{
DIR_Forward,
DIR_Backward,
DIR_Left,
DIR_Right,
DIR_ForwardLeft,
DIR_ForwardRight,
DIR_BackwardLeft,
DIR_BackwardRight,
DIR_None,
};
enum EAnimSlotStance
{
EAS_FullBody,
EAS_UpperBody,
EAS_LowerBody,
EAS_Additive,
EAS_CH_UpperBody,
EAS_CH_LowerBody,
EAS_Face
};
/** Cache nodes for convenience */
var transient Array<AnimNodeAimOffset> AimOffsetNodes;
var() transient Array<AnimNodeSlot> BodyStanceNodes;
/** List of RB bone names which should be UnFixed when playing a Physics Body Impact. */
var(Animation) editinline Array<Name> ArmPhysicsBoneList;
/**
* 2d vector, representing a rotation offset percentage from actor's rotation. Used by Aim anim nodes.
* Range used (-1,-1) to (+1,+1) (angle equivalent : (-90d,-90d) to (+90d,+90d))
*/
var() vector2d AimOffsetPct;
var bool bEnableAimOffset;
/**
* This is the replicated version of AimOffsetPct that other clients will interpolate to.
* Using FRotator compression with roll forced to 0
*/
var rotator ReplicatedAimOffsetPct;
/** If set, turns off KFAnim_TurnInPlace (and related) nodes */
var(Animation) bool bDisableTurnInPlace;
/** Scales the anim rate of the KFAnimSeq_TurnInPlace node */
var float TurnInPlaceAnimRate;
/** Time when AnimGroup can play a new hit reaction */
var float NextHitReactionAnim_ActorTime;
/** Timer for swipe damage tick notify */
var const transient float LastMeleeNotify_ActorTime;
/*********************************************************************************************
* @name Skeletal Controls
********************************************************************************************* */
var transient Array<GameSkelCtrl_Recoil> RecoilNodes;
struct native LookAtInfo
{
/** The actor that our head tracking should be looking at */
var Actor LookAtTarget;
/** Whether or not to use the spine to look at our target */
var bool bUseSpine;
/** Blend in/out values for the skeletal controls */
var float BlendIn;
var float BlendOut;
/** Offset from our target's location to look at */
var vector TargetOffset;
/** Pct to look at the target */
var float LookAtPct;
/** Forced lookat location, overrides lookattarget */
var vector ForcedLookAtLocation;
};
/** Skeletal controller for head bone */
var transient SkelControlLookAt IK_Look_Head;
/** Skeletal controller for spine bone */
var transient SkelControlLookAt IK_Look_Spine;
/** Skeletal controller for scaling the head */
var transient SkelControlSingleBone HeadScaleControl;
/** Current look at information (make repnotify?) */
var transient LookAtInfo MyLookAtInfo;
/** Whether or not this pawn is capable of head tracking */
var bool bCanHeadTrack;
/** Whether or not this pawn's head tracking mode is on or off */
var bool bIsHeadTrackingActive;
/** IK Controller for feet bones */
var transient KFSkelControl_FootPlacement IKFootLeft, IKFootRight;
/*********************************************************************************************
* @name Special Moves
********************************************************************************************* */
/** Defines the current move the player is doing */
enum ESpecialMove
{
SM_None,
/** ZED standard attacks */
SM_MeleeAttack,
SM_MeleeAttackDoor,
SM_GrappleAttack,
/** ZED Hit Reactions */
SM_Stumble,
SM_RecoverFromRagdoll,
SM_Knockdown,
SM_DeathAnim,
SM_Stunned,
SM_Frozen,
SM_GorgeZedVictim,
/** ZED Misc */
SM_Emerge,
SM_Jump,
SM_Taunt,
SM_WalkingTaunt,
SM_Evade,
SM_Evade_Fear,
SM_Block,
SM_Heal,
SM_Rally,
SM_SprintStart,
/** ZED special attacks */
SM_SonicAttack,
SM_StandAndShootAttack,
SM_HoseWeaponAttack,
SM_Suicide,
/** Versus */
/** Correlates to firemode input (label w/ default bind for readability) */
SM_PlayerZedMove_LMB,
SM_PlayerZedMove_RMB,
SM_PlayerZedMove_V,
SM_PlayerZedMove_MMB,
SM_PlayerZedMove_Q,
SM_PlayerZedMove_G,
/** Human Moves */
SM_GrappleVictim,
SM_DisabledGrappleVictim,
SM_HansGrappleVictim,
SM_SirenVortexVictim,
SM_Emote,
SM_DARGrappleVictim,
SM_BloatKingGorgeVictim,
/** Boss special attacks */
SM_BossTheatrics,
SM_ChangeStance,
SM_Hans_ThrowGrenade,
SM_Hans_GrenadeHalfBarrage,
SM_Hans_GrenadeBarrage,
/** Scripted Pawn */
SM_ScriptedPawnStateChange,
/** Custom entries for extendability */
SM_Custom1,
SM_Custom2,
SM_Custom3,
SM_Custom4,
SM_Custom5,
/** Dummy entry to avoid SM_MAX native collision */
ESpecialMove_Blank,
};
/** Container for all special move properties (optimized for net bandwidth) */
struct native KFSpecialMoveStruct
{
/** Special Move Enum being performed. */
var ESpecialMove SpecialMove;
/** Interaction Pawn */
var Pawn InteractionPawn;
/** Additional Replicated Flags */
var byte Flags;
};
/** Special move currently performed by Pawn. SM_None, when doing none. */
var ESpecialMove SpecialMove;
/** Pawn ref used for Pawn to Pawn Interactions */
var KFPawn InteractionPawn;
/** Pack any additional data into special moves, and get it replicated so it's consistent across network. */
var byte SpecialMoveFlags;
/** Array of instanced special moves */
var(SpecialMoves) editinline editconst transient Array<KFSpecialMove> SpecialMoves;
/** Replicated information about the current SpecialMove */
var repretry repnotify KFSpecialMoveStruct ReplicatedSpecialMove;
/** Helper object (within pawn) that implements special moves */
var(SpecialMoves) instanced KFSpecialMoveHandler SpecialMoveHandler;
/** If this is greater than zero, we can't be grabbed by clots or other weak zeds until this time is hit */
var(Grab) Float WeakZedGrabCooldown;
/** If this pawn does zed grabs, they are weak zed grabs */
var(Grab) bool bWeakZedGrab;
/*********************************************************************************************
* @name Mesh Translation / Smoothing (Based on UDKPawn)
********************************************************************************************* */
/** Additional translation for mesh */
var float BaseTranslationOffset;
/** Mesh Translation Offset (MTO) is used to modify Mesh.Translation value**/
/** MeshLocSmootherOffset:
* This variable is used to compensate mesh's translation
* when steps/inclines/bulky geometry causing cylinder to move up/downs rapidly
* To make it smooth this interpolates and creates smoother location transition **/
var const vector MTO_PhysSmoothOffset;
/** SpecialMoveOffset:
* This doesn't have to be used by SpecialMove but it's in-game usable mesh offset
* SpecialMoveInterp is interpolation variable to reach target SpecialMoveOffset
* SpecialMoveSpeed is interpolate speed to reach target SpecialMoveOffset per second
*/
var vector MTO_SpecialMoveOffset;
var vector MTO_SpecialMoveInterp;
var float MTO_SpecialMoveSpeed;
/** Mesh translation calculated by UpdateFloorConform */
var const vector MTO_IKFloorConform;
/** Floor up vector used by floor conforming */
var const Vector MeshFloorConformNormal;
/** If set, interp the MeshFloorConformNormal */
var const bool bDoFloorConformBlend;
/** Track last Pawn rotation, to make any updates if necessary */
var const Rotator FloorConformLastPawnRotation;
/** Current floor conform rotation yaw */
var const int FloorConformMeshRotationYaw;
/** If set, floor conforming assumes this creature is a quadruped. See also GetFloorConformNormal() */
var const bool bUseQuadrupedFloorConform;
/** Cached values for Z smoothing */
var const float LastPhysSmoothDeltaZ;
/** if TRUE, disable mesh offset steps smoothing */
var() protected bool bDisableMeshSmoothing;
/** if TRUE, disable mesh replicated rotation smoothing */
var() protected bool bDisableMeshRotationSmoothing;
/** Additional yaw for mesh */
var const transient float MeshYawOffset;
var float MeshRotSmoothingInterpSpeed;
/*********************************************************************************************
* @name Audio
********************************************************************************************* */
/** pawn ambient sound (for powerups and such) - Access via SetPawnAmbientSound() / GetPawnAmbientSound() */
var repnotify AkEvent AmbientSound;
var protected AkComponent AmbientAkComponent;
/** separate replicated ambient sound for weapon firing - access via SetWeaponAmbientSound() / GetWeaponAmbientSound() */
var repnotify AkEvent WeaponAmbientSound;
var protected AkComponent WeaponAkComponent;
var instanced KFWeaponAmbientEchoHandler WeaponAmbientEchoHandler;
var repnotify AkEvent SecondaryWeaponAmbientSound;
var protected AkComponent SecondaryWeaponAkComponent;
var protected AkComponent FootstepAkComponent;
var protected AkComponent DialogAkComponent;
struct native PowerUpAmbientSoundInfo
{
var AkEvent FirstPersonPowerUpAmbientSound;
var AkEvent ThirdPersonPowerUpAmbientSound;
var AkEvent FirstPersonStopPowerUpAmbientSound;
var AkEvent ThirdPersonStopPowerUpAmbientSound;
var byte Count;
};
var repnotify PowerUpAmbientSoundInfo PowerUpAmbientSound;
var protected AkComponent PowerUpAkComponent;
/** Whether foostep sounds are allowed. Always allow sounds for local player */
var globalconfig bool bAllowFootstepSounds;
/*********************************************************************************************
* @name Network
********************************************************************************************* */
var float LastReplicateTime;
/*********************************************************************************************
* @name AI
********************************************************************************************* */
var KFAIController MyKFAIC;
/** Extra path cost applied to each navigation point added to RouteCache to diversify paths */
var const float ExtraCostForPath;
/** Whether Pawn is using faster movement speed to catch up to player(s) */
var const bool bUseHiddenSpeed;
/** Whether Pawn can use faster movement speed to catch up to player(s) */
var bool bCanUseHiddenSpeed;
/** Whether or not to allow acceleration smoothing using TurningRadius parameters below */
var bool bAllowAccelSmoothing;
/** Maximum turning radius of this pawn when accel smoothing is on */
var float MaxTurningRadius;
/** Current turning radius we are using (based on TurningRadisu above, and scaled depending on how close to the goal we are) */
var transient float CurrentTurningRadius;
/** Acceleration smoothing will be ramped down within this distance from goal */
var float AccelConvergeFalloffDistance;
/** Pawn acceleration from previous frame */
var transient vector OldAcceleration;
/** AI using this pawn will pause for this amount of time when taking "heavy" damage */
var float DamageRecoveryTimeHeavy;
/** AI using this pawn will pause for this amount of time when taking "medium" damage */
var float DamageRecoveryTimeMedium;
/** How fast an AI will move when using this pawn when they are hidden from player view */
var float HiddenGroundSpeed;
/** AI Zeds won't target this pawn if this is true */
var bool bAIZedsIgnoreMe;
/** If other than none other AI should not target this pawn */
var Controller ExclusiveTargetingController;
/** If set to something other than zero, AI will ignore this pawn until the worldinfo timeseconds passes this value */
var float AIIgnoreEndTime;
/** Whether this AI pawn can cloak or not */
var bool bCanCloak;
/** Whether this AI pawn is cloaking or not */
var repnotify bool bIsCloaking;
/** KF1 legacy value for zed AIControl */
const AIAirControl = 0.35;
/*********************************************************************************************
* @name Dialog
********************************************************************************************* */
var transient int CurrDialogEventID;
var transient byte CurrDialogPriority;
/*********************************************************************************************
* @name ExtraVFX
********************************************************************************************* */
struct native ExtraVFXInfo
{
// Particle effect to play
var() ParticleSystem VFX;
// Socket to attach it to (if applicable)
var() Name SocketName;
// Label to use for code logic (if applicable)
var() Name Label;
// Audio event to play when vfx start
var() AkEvent SFXStartEvent;
// Audio event to play when vfx stop
var() AkEvent SFXStopEvent;
};
struct native ExtraVFXAttachmentInfo
{
var ParticleSystemComponent VFXComponent;
var ExtraVFXInfo Info;
};
var transient array<ExtraVFXAttachmentInfo> ExtraVFXAttachments;
/*********************************************************************************************
* @name Debug
********************************************************************************************* */
// `log() conditions. Much more efficient than using log tags, because the msg is not evaluated.
var bool bLogTakeDamage;
var bool bLogPhysicsBodyImpact;
var bool bLogSpecialMove;
var bool bLogCustomAnim;
var Texture2D DebugRadarTexture;
var repnotify bool bHasStartedFire;
var repnotify byte WeaponSpecialAction;
/*********************************************************************************************
* @name Cached last hit index.
* Afflictions are failing to get the proper zone index of the hit as it is read before
written. This is a temp fix for it.
********************************************************************************************* */
var transient byte LastHitZoneIndex;
/**
AutoTurret
*/
var const bool bIsTurret;
var repnotify bool bEnableSwarmVFX;
replication
{
// Replicated to ALL
if ( bNetDirty )
AmbientSound, WeaponClassForAttachmentTemplate, bIsSprinting, InjuredHitZones,
KnockdownImpulse, ReplicatedSpecialMove, bEmpDisrupted, bEmpPanicked, bFirePanicked,
RepFireBurnedAmount, bUnaffectedByZedTime, bMovesFastInZedTime, IntendedBodyScale,
IntendedHeadScale, AttackSpeedModifier, bHasStartedFire, PowerUpAmbientSound, BodyScaleChangePerSecond,
bEnableSwarmVFX;
if ( bNetDirty && WorldInfo.TimeSeconds < LastTakeHitTimeout )
HitFxInfo, HitFxRadialInfo, HitFxInstigator, HitFxAddedRelativeLocs, HitFxAddedHitCount;
if ( Physics == PHYS_RigidBody && !bTearOff )
ReplicatedRootBodyPos;
if ( Physics == PHYS_Spider )
ReplicatedFloor;
if( bNetDirty && bTearOff ) // on death/tearoff
DeathFireStackedPower;
// Replicated to owning client
if ( bNetDirty && bNetOwner )
SprintSpeed, AfflictionSpeedModifier, bAllowSprinting, bJumping, NumJumpsAllowed;
if ( bNetDirty && bNetOwner && bNetInitial )
bIgnoreTeamCollision;
// Replicated to all but the owning client
if ( bNetDirty && (!bNetOwner || bDemoRecording) )
WeaponAmbientSound, SecondaryWeaponAmbientSound, WeaponSpecialAction;
if ( bEnableAimOffset && (!bNetOwner || bDemoRecording) )
ReplicatedAimOffsetPct;
if ( bNetDirty && bCanCloak )
bIsCloaking;
}
cpptext
{
// actor
virtual void PostBeginPlay();
virtual void TickSpecial(FLOAT DeltaSeconds);
virtual void TickSimulated(FLOAT DeltaSeconds);
virtual void BeginTouch(AActor *Other, UPrimitiveComponent* OtherComp, const FVector &HitLocation, const FVector &HitNormal, UPrimitiveComponent* MyComp=NULL);
virtual void setPhysics(BYTE NewPhysics, AActor* NewFloor = NULL, FVector NewFloorV = FVector(0,0,1));
// network
INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
virtual UBOOL IsNetRelevantFor(APlayerController* RealViewer, AActor* Viewer, const FVector& SrcLocation);
virtual UBOOL ShouldDoSimpleNetRelevancy(APlayerController* RealViewer, const FVector& SrcLocation);
virtual void PreNetReceive();
virtual void PostNetReceive();
// sound
virtual void ReplicateSound(class UAkBaseSoundObject* InSoundCue, UBOOL bNotReplicated = FALSE, UBOOL bNoRepToOwner = FALSE, UBOOL bStopWhenOwnerDestroyed = FALSE, FVector* SoundLocation = NULL, UBOOL bNoRepToRelevant = FALSE, FRotator* SoundRotation = NULL );
virtual UBOOL HasAudibleAmbientSound(const FVector& SrcLocation);
// collision / physics
virtual void performPhysics(FLOAT DeltaSeconds);
virtual void PostProcessPhysics( FLOAT DeltaSeconds, const FVector& OldVelocity );
virtual void physFalling(FLOAT deltaTime, INT Iterations);
virtual void physRigidBody(FLOAT DeltaTime);
virtual void Crouch(INT bClientSimulation=0);
virtual void UnCrouch(INT bClientSimulation=0);
virtual FLOAT MaxSpeedModifier();
virtual void UpdateTimeDilation();
virtual void UpdateEyeHeight(FLOAT DeltaSeconds);
virtual void SyncActorToRBPhysics();
virtual void physicsRotation(FLOAT deltaTime, FVector OldVelocity);
virtual UBOOL ShouldBypassSimulatedClientPhysics();
virtual UBOOL IgnoreBlockingBy( const AActor *Other) const;
void MoveToFloor();
void TickKnockdownCorrection(FLOAT DeltaSeconds);
void TickPhysicsBodyImpact(FLOAT DeltaSeconds);
// ragdoll violations
UBOOL CheckRigidBodyLinearConstraintViolations();
UBOOL IsRigidBodyInterpenetratingWorld(FName& BoneName);
void SetRagdollSleepThreshold(FLOAT Threshold);
void TickRagdollSleepFailsafe(FLOAT DeltaSeconds);
// Mesh translation offset
void SmoothCorrection(const FVector& OldLocation);
void PerformStepsSmoothing(const FVector& OldLocation, FLOAT DeltaSeconds);
void TickMeshTranslationOffset(FLOAT DeltaSeconds);
void TickMeshRotationSmoothing( FLOAT DeltaSeconds );
// Floor conforming (IK, mesh translation/rotation, etc..)
UBOOL UpdateFloorConform(FLOAT DeltaSeconds);
void physicsRotationCrawler(FLOAT deltaTime, FRotator deltaRot, FRotator& NewRotation);
FVector GetFloorConformNormal(FLOAT& out_HeightAdjust);
FRotator FindSlopeMeshRotation(const FVector& FloorNormal);
// AI related
virtual void PrePhysicsAISteering();
virtual void physWalking( FLOAT deltaTime, INT Iterations );
virtual UBOOL IsValidAnchor( ANavigationPoint* AnchorCandidate );
// Never try to jump over a wall (for now)
virtual UBOOL TryJumpUp(FVector Dir, FVector Destination, DWORD TraceFlags, UBOOL bNoVisibility);
// Additional falling checks
virtual UBOOL TestWalkFall(FVector GravDir, FVector &CurrentPosition, FVector CollisionExtent, FCheckResult& Hit, AActor* GoalActor, const FVector StartLocation);
// Disable serpentine movement (for now)
virtual void HandleSerpentineMovement(FVector& out_Direction, FLOAT Distance, const FVector& Dest ) { }
virtual void HandleAccelerationSmoothing(FLOAT DeltaSeconds);
UBOOL moveToward(const FVector &Dest, AActor *GoalActor );
// Override of Pawn native function because that version considers the pawn to be dead if hidden. This version
// bypasses the hidden check to support ToggleDebugCamera so you can watch NPCs attacking your detached mesh.
virtual UBOOL IsAliveAndWell() const;
virtual UBOOL ReachedDestination(const FVector &Start, const FVector &Dest, AActor* GoalActor, UBOOL bCheckHandle=FALSE);
// animation
virtual void UpdateHeadTracking(FLOAT DeltaTime);
void TickAimOffset(FLOAT DeltaSeconds);
}
native final simulated function bool FitCollision();
/*********************************************************************************************
* @name Constructors, Destructors, and Loading
********************************************************************************************* */
/** Chris: See udn.unrealengine.com/questions/104007/replicated-clientgivento-has-none-newowner-paramet.html */
reliable client function ForceOpenActorChannel();
/** This will determine and then return the CharacterInfo for this pawn **/
native simulated function KFCharacterInfoBase GetCharacterInfo();
/** Queries the PRI and returns our current Perk */
native simulated function KFPerk GetPerk();
/** Whether the world can cast shadows on first person arms and weapon */
native function bool AllowFirstPersonPreshadows();
/** Removes all blood decals from pawn mesh */
native function ClearBloodDecals();
/** Force uncrouch */
native function ForceUnCrouch();
// Called immediately before gameplay begins.
simulated event PreBeginPlay()
{
local MapInfo MapInfo;
// Set the mesh to the proper offset
BaseTranslationOffset = Mesh.default.Translation.Z;
// If the pawn hasn't registered any touch/untouch events from a ligthing volume
// set it to the correct default lighting channel based on the map
if( LightingVolumeEnterCount == 0 )
{
MapInfo = WorldInfo.GetMapInfo();
if(MapInfo != None && MapInfo.bDefaultPawnsToOutdoor)
{
PawnLightingChannel.Indoor = false;
PawnLightingChannel.Outdoor = true;
PawnLightingChannel.bInitialized = true;
}
else
{
PawnLightingChannel.Indoor = true;
PawnLightingChannel.Outdoor = false;
PawnLightingChannel.bInitialized = true;
}
SetMeshLightingChannels(PawnLightingChannel);
}
InitRBSettings();
Super.PreBeginPlay();
}
event PostBeginPlay()
{
Super.PostBeginPlay();
SoundGroupArch.PlayEntranceSound( self );
// On the server only update the skelcomp when it needs to (see bTickDuringPausedAnims)
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
Mesh.bPauseAnims = true;
}
}
// called at the end of PostBeginPlay, when the pawn has been added to the world's pawn list
event PostAddPawn()
{
local KFGameInfo KFGI;
KFGI = KFGameInfo(WorldInfo.Game);
if (KFGI != none)
{
KFGI.UpdateAIRemaining();
}
}
/* Reset actor to initial state - used when restarting level without reloading. */
function Reset()
{
DetachFromController( true );
Destroy();
super(Actor).Reset();
}
/** End any active special moves when pawn is destroyed (rather than killed
from incoming damage) - this can happen when using a cheat like "killpawns"
*/
simulated event Destroyed()
{
// If we're currently doing a special move when we're being destroyed
if( SpecialMove != SM_None && SpecialMoves[SpecialMove] != None )
{
EndSpecialMove();
}
TerminateEffectsOnDeath();
super.Destroyed();
}
/**
* Check on various replicated data and act accordingly.
*/
simulated event ReplicatedEvent(name VarName)
{
switch( VarName )
{
case nameof(WeaponClassForAttachmentTemplate):
SetWeaponAttachmentFromWeaponClass(WeaponClassForAttachmentTemplate);
break;
case nameof(AmbientSound):
SetPawnAmbientSound(AmbientSound);
break;
case nameof(WeaponAmbientSound):
SetWeaponAmbientSound(WeaponAmbientSound);
break;
case nameof(PowerUpAmbientSound):
SetReplicatedPowerUpAmbientSound(PowerUpAmbientSound);
break;
case nameof(SecondaryWeaponAmbientSound):
SetSecondaryWeaponAmbientSound(SecondaryWeaponAmbientSound);
break;
case nameof(HitFxInfo):
bNeedsProcessHitFx = true;
break;
case nameof(InjuredHitZones):
HitZoneInjured();
break;
case nameof(ReplicatedSpecialMove):
`log("Received replicated special move:"@ReplicatedSpecialMove.SpecialMove, bLogSpecialMove);
DoSpecialMove(ReplicatedSpecialMove.SpecialMove, TRUE, ReplicatedSpecialMove.InteractionPawn, ReplicatedSpecialMove.Flags);
break;
case nameof(ReplicatedFloor):
Floor = ReplicatedFloor;
break;
case nameof(bEmpDisrupted):
AfflictionHandler.ToggleEffects(AF_EMP, bEmpDisrupted, bEmpPanicked);
break;
case nameof(bEmpPanicked):
AfflictionHandler.ToggleEffects(AF_EMP, bEmpDisrupted, bEmpPanicked);
break;
case nameof(bFirePanicked):
AfflictionHandler.ToggleEffects(AF_FirePanic, bFirePanicked);
break;
case nameof(RepFireBurnedAmount):
AfflictionHandler.UpdateMaterialParameter(AF_FirePanic, ByteToFloat(RepFireBurnedAmount));
break;
case nameof(IntendedHeadScale):
SetHeadScale(IntendedHeadScale, CurrentHeadScale);
break;
case nameof(bHasStartedFire):
if (bHasStartedFire)
{
OnStartFire();
}
break;
case nameof(WeaponSpecialAction):
OnWeaponSpecialAction(WeaponSpecialAction);
break;
case nameof(bEnableSwarmVFX):
if (bEnableSwarmVFX)
{
StartLocustVFX();
}
else
{
StopLocustVFX();
}
break;
}
Super.ReplicatedEvent(VarName);
}
/** Pawn is possessed by Controller */
function PossessedBy(Controller C, bool bVehicleTransition)
{
Super.PossessedBy(C, bVehicleTransition);
// By default, NotifyTeamChanged is only called on the client
NotifyTeamChanged();
}
/** Pawn is unpossessed by Controller */
function UnPossessed()
{
Super.UnPossessed();
bUnaffectedByZedTime = false;
}
/** Returns true of pawns are on the same team, false otherwise */
simulated event bool IsSameTeam( Pawn Other )
{
// Added a check in case the pawn isn't on a team. By default they
// will still have a team num (255). This can be used to identify
// if a pawn is on the human team (0) or the zed team
if( Other != none && Other.GetTeamNum() == GetTeamNum() )
{
return true;
}
return Super.IsSameTeam(Other);
}
/** one time init of physics related options */
simulated function InitRBSettings()
{
// If PhysAsset is set in the defaults
if ( Mesh != none && Mesh.PhysicsAssetInstance != None )
{
// Fix bodies so they update from animation until ragdoll
if ( Physics != PHYS_RigidBody )
{
Mesh.PhysicsAssetInstance.SetAllBodiesFixed(TRUE);
Mesh.PhysicsAssetInstance.SetFullAnimWeightBonesFixed(FALSE, Mesh);
}
}
// don't allow non-animating bones to collide with rigid bodies
// (the proper RB channel will be set upon ragdolling)
if( !Mesh.bUpdateKinematicBonesFromAnimation )
{
Mesh.SetRBChannel(RBCC_Nothing);
Mesh.SetRBCollidesWithChannel(RBCC_Pawn, FALSE);
}
UpdateMeshForFleXCollision();
}
/*********************************************************************************************
* @name Character Info Methods
********************************************************************************************* */
/** Notify pawn whenever mesh is swapped (e.g. new character or new outfit) */
simulated function OnCharacterMeshChanged();
/** SetCharacterAnimationInfo, separated out since this can be overriden if by certain classes */
simulated function SetCharacterArchAnimationInfo()
{
SetCharacterAnimationInfo();
}
/** Set various basic properties for this KFPawn based on the character class metadata */
simulated function SetCharacterArch( KFCharacterInfoBase Info, optional bool bForce )
{
local KFPlayerReplicationInfo KFPRI;
KFPRI = KFPlayerReplicationInfo( PlayerReplicationInfo );
if (Info != CharacterArch || bForce)
{
CharacterArch = Info;
CharacterArch.SetCharacterFromArch( self, KFPRI );
CharacterArch.SetCharacterMeshFromArch( self, KFPRI );
SetCharacterArchAnimationInfo();
// Sounds
SoundGroupArch = Info.SoundGroupArch;
if (WorldInfo.NetMode != NM_DedicatedServer)
{
// refresh weapon attachment (attachment bone may have changed)
if (WeaponAttachmentTemplate != None)
{
WeaponAttachmentChanged(true);
}
}
// Give a warning if any of these bones are invalid. They are needed for
// explosive damage calcs.
if(HeadBoneName != `NAME_NONE && Mesh.MatchRefBone(HeadBoneName) == INDEX_NONE)
{
`Warn("CharacterInfo HeadBone is invalid for" @ Self);
//ClientMessage("CharacterInfo HeadBone is invalid for" @ Self);
}
if(LeftFootBoneName != `NAME_NONE && Mesh.MatchRefBone(LeftFootBoneName) == INDEX_NONE)
{
`Warn("CharacterInfo LeftFootBone is invalid for" @ Self);
//ClientMessage("CharacterInfo LeftFootBone is invalid for" @ Self);
}
if(RightFootBoneName != `NAME_NONE && Mesh.MatchRefBone(RightFootBoneName) == INDEX_NONE)
{
`Warn("CharacterInfo RightFootBone is invalid for" @ Self);
//ClientMessage("CharacterInfo RightFootBone is invalid for" @ Self);
}
if(TorsoBoneName != `NAME_NONE && Mesh.MatchRefBone(TorsoBoneName) == INDEX_NONE)
{
`Warn("CharacterInfo TorsoBone is invalid for" @ Self);
//ClientMessage("CharacterInfo TorsoBone is invalid for" @ Self);
}
}
if( CharacterArch != none )
{
if( CharacterArch.VoiceGroupArchName != "" )
{
VoiceGroupArch = class<KFPawnVoiceGroup>(DynamicLoadObject(CharacterArch.VoiceGroupArchName, class'Class'));
}
}
}
/**
* Called from PlayerController UpdateRotation() -> ProcessViewRotation() to (pre)process player ViewRotation
* adds delta rot (player input), applies any limits and post-processing
* returns the final ViewRotation set on PlayerController
*
* @param DeltaTime, time since last frame
* @param ViewRotation, actual PlayerController view rotation
* @input out_DeltaRot, delta rotation to be applied on ViewRotation. Represents player's input.
* @return processed ViewRotation to be set on PlayerController.
*/
simulated function ProcessViewRotation( float DeltaTime, out rotator out_ViewRotation, out Rotator out_DeltaRot )
{
super.ProcessViewRotation( DeltaTime, out_ViewRotation, out_DeltaRot );
// Allow special moves to alter view rotation
if( SpecialMove != SM_None && SpecialMoves[SpecialMove] != none )
{
SpecialMoves[SpecialMove].ProcessViewRotation( DeltaTime, out_ViewRotation, out_DeltaRot );
}
}
/** Setup animation and ragdoll here */
simulated function SetCharacterAnimationInfo()
{
// Physics
if ( CharacterArch.PhysAsset != Mesh.PhysicsAsset )
{
// Reinitialized in SetPhysicsAsset only if it needs to be Enabled (PhysicsAsset has flappy bits)
Mesh.bEnableFullAnimWeightBodies = FALSE;
// Force it to reinitialize if the skeletal mesh has changed (might be flappy bones etc).
Mesh.SetPhysicsAsset(CharacterArch.PhysAsset);
// Fix bodies so they update from animation until ragdoll
if ( Physics != PHYS_RigidBody )
{
Mesh.PhysicsAssetInstance.SetAllBodiesFixed(TRUE);
if( bAllowAlwaysOnPhysics )
{
Mesh.PhysicsAssetInstance.SetFullAnimWeightBonesFixed(FALSE, Mesh);
}
}
}
// Character Animation+
CharacterArch.SetCharacterAnimFromArch( self );
if ( CharacterArch.AnimArchetype != None )
{
PawnAnimInfo = CharacterArch.AnimArchetype;
}
}
/** Reapply active gameplay related MIC params (e.g. when switching to the gore mesh) */
simulated function UpdateGameplayMICParams()
{
if ( WorldInfo.NetMode != NM_DedicatedServer )
{
AfflictionHandler.ToggleEffects(AF_EMP, bEmpDisrupted, bEmpPanicked);
AfflictionHandler.UpdateMaterialParameter(AF_FirePanic, ByteToFloat(RepFireBurnedAmount));
}
}
/** If true, assign custom player controlled skin when available */
simulated event bool UsePlayerControlledZedSkin();
/*********************************************************************************************
* @name Camera Methods
********************************************************************************************* */
/**
* Calculate camera view point, when viewing this pawn.
*
* @param fDeltaTime delta time seconds since last update
* @param out_CamLoc Camera Location
* @param out_CamRot Camera Rotation
* @param out_FOV Field of View
*
* @return true if Pawn should provide the camera point of view.
*/
simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV )
{
// since we've added a first-person camera class (KFFirstPersonCamera),
// we don't need this to do anything at the moment, and we don't want to
// call the super because Gears doesn't because it does traces
return false;
}
event BaseChange()
{
super.BaseChange();
// Push the pawn if it tries to stand on top of a door
if( KFDoorActor(Base) != none )
{
Velocity = KFDoorActor(Base).GetPushDirection( Location );
SetPhysics( PHYS_Falling );
}
}
/** Set BaseEyeheight/CameraLoc using a relative offset from the CylinderComponent center */
simulated function SetBaseEyeheight()
{
if ( !bIsCrouched )
BaseEyeheight = Default.BaseEyeheight * CurrentBodyScale;
else
BaseEyeheight = Default.BaseCrouchEyeHeight * CurrentBodyScale;
//`log(self@"Absolute BaseEyeheight="$BaseEyeheight + CylinderComponent.CollisionHeight);
}
/* UpdateEyeHeight()
* Update player eye position, based on smoothing view while moving up and down stairs, and adding view bobs for landing and taking steps.
* Called every tick only if bUpdateEyeHeight==true.
*/
event UpdateEyeHeight( float DeltaTime )
{
local float MaxEyeHeight;
local Actor HitActor;
local vector HitLocation,HitNormal;
if ( bTearOff )
{
// no eyeheight updates if dead
EyeHeight = BaseEyeheight;
bUpdateEyeHeight = false;
return;
}
if ( abs(Location.Z - OldZ) > 15 )
{
// if position difference too great, don't do smooth land recovery
bJustLanded = false;
bLandRecovery = false;
}
SmoothEyeHeight(DeltaTime);
UpdateWalkBob(DeltaTime);
if ( (CylinderComponent.CollisionHeight - Eyeheight < 12) && IsFirstPerson() )
{
// desired eye position is above collision box
// check to make sure that viewpoint doesn't penetrate another actor
// min clip distance 12
if (bCollideWorld)
{
HitActor = trace(HitLocation,HitNormal, Location + WalkBob + (MaxStepHeight + CylinderComponent.CollisionHeight) * vect(0,0,1),
Location + WalkBob, true, vect(12,12,12),, TRACEFLAG_Blocking);
MaxEyeHeight = (HitActor == None) ? CylinderComponent.CollisionHeight + MaxStepHeight : HitLocation.Z - Location.Z;
Eyeheight = FMin(Eyeheight, MaxEyeHeight);
}
}
}
/* @see UpdateEyeHeight() */
private function SmoothEyeHeight( float DeltaTime )
{
local float smooth, OldEyeHeight;
if ( !bJustLanded )
{
// normal walking around
// smooth eye position changes while going up/down stairs
smooth = FMin(0.9, 10.0 * DeltaTime/CustomTimeDilation);
LandBob *= (1 - smooth);
if( Physics == PHYS_Walking || Physics==PHYS_Spider || Controller.IsInState('PlayerSwimming') )
{
OldEyeHeight = EyeHeight;
EyeHeight = FMax((EyeHeight - Location.Z + OldZ) * (1 - smooth) + BaseEyeHeight * smooth,
-0.5 * CylinderComponent.CollisionHeight);
}
else
{
EyeHeight = EyeHeight * ( 1 - smooth) + BaseEyeHeight * smooth;
}
}
else if ( bLandRecovery )
{
// return eyeheight back up to full height
smooth = FMin(0.9, 9.0 * DeltaTime);
OldEyeHeight = EyeHeight;
LandBob *= (1 - smooth);
// linear interpolation at end
if ( Eyeheight > 0.9 * BaseEyeHeight )
{
Eyeheight = Eyeheight + 0.15*BaseEyeheight*Smooth; // 0.15 = (1-0.75)*0.6
}
else
EyeHeight = EyeHeight * (1 - 0.6*smooth) + BaseEyeHeight*0.6*smooth;
if ( Eyeheight >= BaseEyeheight)
{
bJustLanded = false;
bLandRecovery = false;
Eyeheight = BaseEyeheight;
}
}
else
{
// drop eyeheight a bit on landing
smooth = FMin(0.65, 8.0 * DeltaTime);
OldEyeHeight = EyeHeight;
EyeHeight = EyeHeight * (1 - 1.5*smooth);
LandBob += 0.08 * (OldEyeHeight - Eyeheight);
if ( (Eyeheight < 0.25 * BaseEyeheight + 1) || (LandBob > 2.4) )
{
bLandRecovery = true;
Eyeheight = 0.25 * BaseEyeheight + 1;
}
}
// moved from PerformPhysics so that it works right on client
OldZ = Location.Z;
}
/* @see UpdateEyeHeight() */
private function UpdateWalkBob( float DeltaTime )
{
local float Speed2D;//, OldBobTime;
local vector X, Y, Z;
//local int m,n;
// don't bob if disabled, or just landed
if( bJustLanded || !bUpdateEyeheight )
{
BobTime = 0;
WalkBob = Vect(0,0,0);
}
else
{
// add some weapon bob based on jumping
if ( Velocity.Z > 0.f )
{
JumpBob = FMax(-1.5f, JumpBob - 0.03f * DeltaTime * FMin(Velocity.Z,300.f));
}
else
{
JumpBob *= 1.f - FMin(1.f, 8.f * DeltaTime);
}
// Add walk bob to movement
//OldBobTime = BobTime;
if (Physics == PHYS_Walking )
{
GetAxes(Rotation,X,Y,Z);
Speed2D = VSize2D( Velocity );
if ( Speed2D < 10.f )
BobTime += 0.2f * DeltaTime;
else
BobTime += DeltaTime * (0.3f + 0.7f * Speed2D/GroundSpeed);
WalkBob = Y * Bob * Speed2D * sin(8.f * BobTime);
AppliedBob = AppliedBob * (1.f - FMin(1.f, 16.f * DeltaTime));
WalkBob.Z = AppliedBob;
if ( Speed2D > 10.f )
WalkBob.Z = WalkBob.Z + 0.75f * Bob * Speed2D * sin(16.f * BobTime);
}
else if ( Physics == PHYS_Swimming )
{
GetAxes(Rotation,X,Y,Z);
BobTime += DeltaTime;
Speed2D = VSize2D( Velocity );
WalkBob = Y * Bob * 0.5f * Speed2D * sin(4.0f * BobTime);
WalkBob.Z = Bob * 1.5f * Speed2D * sin(8.0f * BobTime);
}
else
{
BobTime = 0.f;
WalkBob = WalkBob * (1.f - FMin(1.f, 8.f * DeltaTime));
}
// Match footsteps sounds with walkbob
/*if ( (Physics == PHYS_Walking) && (VSizeSq(Velocity) > 100) && IsFirstPerson() )
{
m = int(0.5 * Pi + 9.0 * OldBobTime/Pi);
n = int(0.5 * Pi + 9.0 * BobTime/Pi);
if ( (m != n) && !bIsWalking && !bIsCrouched )
{
//ActuallyPlayFootStepSound(0);
}
}*/
}
}
/* GetPawnViewLocation()
Called by PlayerController to determine camera position in first person view. Returns
the location at which to place the camera
*/
simulated function Vector GetPawnViewLocation()
{
local PlayerController MyPC;
if( Controller != none )
{
if( bUpdateEyeHeight )
{
return Location + EyeHeight * vect(0,0,1) + WalkBob;
}
return Location + BaseEyeHeight * vect(0,0,1);
}
else
{
if( Role < ROLE_Authority && Mesh != none && Mesh.SkeletalMesh != none && Mesh.bAnimTreeInitialised )
{
MyPC = WorldInfo.GetALocalPlayerController();
if( MyPC != none )
{
return Mesh.GetPosition() + ((CylinderComponent.CollisionHeight + MyPC.TargetEyeHeight) * vect(0,0,1));
}
return Mesh.GetPosition() + ((CylinderComponent.CollisionHeight + BaseEyeHeight) * vect(0,0,1));
}
return Location + BaseEyeHeight * vect(0,0,1);
}
}
simulated function float GetEyeHeight()
{
if( bUpdateEyeHeight )
{
return EyeHeight;
}
else
{
return BaseEyeHeight;
}
}
/* BecomeViewTarget - Called by Camera when this actor becomes its ViewTarget */
simulated event BecomeViewTarget( PlayerController PC )
{
Super.BecomeViewTarget(PC);
if (LocalPlayer(PC.Player) != None)
{
// Toggle preshadows before attaching component (depending on graphics settings)
if( AllowFirstPersonPreshadows() )
{
ArmsMesh.bAllowPerObjectShadows = true;
}
else
{
ArmsMesh.bAllowPerObjectShadows = false;
}
SetMeshVisibility(!PC.UsingFirstPersonCamera());
bUpdateEyeHeight = true;
}
}
/* EndViewTarget - Called by Camera when this actor becomes its ViewTarget */
simulated event EndViewTarget( PlayerController PC )
{
if (LocalPlayer(PC.Player) != None)
{
SetMeshVisibility(true);
}
}
/**
* Event called from native code when Pawn stops crouching.
* Called on non owned Pawns through bIsCrouched replication.
* Network: ALL
*
* @param HeightAdjust height difference in unreal units between default collision height, and actual crouched cylinder height.
*/
simulated event EndCrouch(float HeightAdjust)
{
Super.EndCrouch(HeightAdjust);
OldZ += HeightAdjust;
// offset mesh by height adjustment
if( Mesh != None )
{
BaseTranslationOffset -= HeightAdjust;
Mesh.SetTranslation(Mesh.Translation - Vect(0,0,1)*HeightAdjust);
}
}
/**
* Event called from native code when Pawn starts crouching.
* Called on non owned Pawns through bIsCrouched replication.
* Network: ALL
*
* @param HeightAdjust height difference in unreal units between default collision height, and actual crouched cylinder height.
*/
simulated event StartCrouch(float HeightAdjust)
{
// Calculate our own HeightAdjust to fix engine off-by-one error for SimulatedProxies
// see PostNetRecieveLocation [aladenberger 4/11/2014]
HeightAdjust = default.CylinderComponent.CollisionHeight - CrouchHeight;
Super.StartCrouch(HeightAdjust);
OldZ -= HeightAdjust;
// offset mesh by height adjustment
if( Mesh != None )
{
BaseTranslationOffset += HeightAdjust;
Mesh.SetTranslation(Mesh.Translation + Vect(0,0,1)*HeightAdjust);
}
}
/*********************************************************************************************
* @name Weapon Methods
********************************************************************************************* */
/** sets whether or not the third person weapon of this pawn is visible */
native function SetWeaponAttachmentVisibility(bool bAttachmentVisible);
/** Returns TRUE if this pawn is currently performing a Special Melee Attack Move. */
native function bool IsDoingMeleeAttack() const;
/** Notification called from Pawn animation, by KFAnimNotify_MeleeImpact. */
simulated function MeleeImpactNotify(KFAnimNotify_MeleeImpact Notify);
/** Called when a pawn's weapon changes state. */
simulated function WeaponStateChanged(byte NewState, optional bool bViaReplication);
/** Reset/Update ground speed based on perk/weapon selection (Net: Server) */
function UpdateGroundSpeed();
/** Reset/Update speed modifier tied to afflictions. Called from the affliction to aggregate all active
* mods when one of them needs to update.
*/
function SetAfflictionSpeedModifier()
{
AfflictionSpeedModifier = AfflictionHandler.GetAfflictionSpeedModifier();
}
function SetAttackSpeedModifier()
{
AttackSpeedModifier = AfflictionHandler.GetAfflictionAttackSpeedModifier();
}
/** Toggle the flashlight on and off */
simulated function ToggleEquipment();
/** Server - Called when a new weapon is added or removed from inventory */
function NotifyInventoryWeightChanged()
{
UpdateGroundSpeed();
}
/**
* Player just changed weapon. Called from InventoryManager::ChangedWeapon().
* Network: Local Player and Server.
*
* @param OldWeapon Old weapon held by Pawn.
* @param NewWeapon New weapon held by Pawn.
*/
simulated function PlayWeaponSwitch(Weapon OldWeapon, Weapon NewWeapon)
{
// Save a reference to carried Weapon, so we don't cast all over the place.
MyKFWeapon = KFWeapon(Weapon);
}
/**
* Overridden to iterate through the DefaultInventory array and
* give each item to this Pawn.
*
* @see GameInfo.AddDefaultInventory
*/
function AddDefaultInventory()
{
local KFInventoryManager KFIM;
local class<Inventory> InvClass;
local Inventory Inv;
foreach DefaultInventory(InvClass)
{
// Ensure we don't give duplicate items
Inv = FindInventoryType(InvClass);
// if item not found
if( Inv == None )
{
// Create it and add to inventory chain, only activate if we don't have a weapon currently selected
Inv = CreateInventory(InvClass, Weapon != None);
if ( KFWeapon( Inv ) != none )
{
KFWeapon( Inv ).bGivenAtStart = true;
}
}
}
KFIM = KFInventoryManager(InvManager);
foreach DefaultInventoryArchetypes(Inv)
{
// Create it and add to inventory chain, only activate if we don't have a weapon currently selected
KFIM.CreateInventoryArchetype(Inv, Weapon != None);
}
if(Role == ROLE_Authority)
{
KFIM.ShowAllHUDGroups();
}
}
/** First person weapon visility */
simulated function SetFirstPersonVisibility(bool bWeaponVisible)
{
local int AttachmentIdx;
// Added Instigator check to catch out of date cached weapon, can happen on death
if ( MyKFWeapon != None && MyKFWeapon.Instigator == self )
{
MyKFWeapon.ChangeVisibility(bWeaponVisible);
}
else if ( ArmsMesh != None )
{
ArmsMesh.SetHidden(!bWeaponVisible);
for (AttachmentIdx = 0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++)
{
if (FirstPersonAttachments[AttachmentIdx] != none)
{
FirstPersonAttachments[AttachmentIdx].SetHidden(!bWeaponVisible);
}
}
}
}
/**
* Called when there is a need to change the weapon attachment (either via
* replication or locally if controlled.
*/
simulated function WeaponAttachmentChanged(optional bool bForceReattach)
{
if (WorldInfo.NetMode == NM_DedicatedServer)
{
return;
}
// Detach/Destroy the current attachment if we have one
if (WeaponAttachment != None)
{
if (WeaponAttachment.ObjectArchetype != WeaponAttachmentTemplate || bForceReattach)
{
WeaponAttachment.DetachFrom(self);
WeaponAttachment.Destroy();
WeaponAttachment = None;
}
}
// Create the new Attachment.
if (WeaponAttachment == None)
{
if (Mesh.SkeletalMesh != None && WeaponAttachmentTemplate != None)
{
WeaponAttachment = Spawn(WeaponAttachmentTemplate.Class, self,,,, WeaponAttachmentTemplate);
if (WeaponAttachment != None)
{
WeaponAttachment.Instigator = self;
WeaponAttachment.AttachTo(self);
WeaponAttachment.ChangeVisibility(bWeaponAttachmentVisible);
WeaponAttachment.SetMeshLightingChannels(PawnLightingChannel);
}
}
}
}
simulated function SetWeaponAttachmentFromWeaponClass(class<KFWeapon> WeaponClass)
{
if (WeaponClass == none)
{
WeaponAttachmentTemplate = none;
WeaponAttachmentChanged();
}
else
{
if (WeaponClass.default.AttachmentArchetype == none)
{
WeaponClass.static.TriggerAsyncContentLoad(WeaponClass);
}
else
{
WeaponAttachmentTemplate = WeaponClass.default.AttachmentArchetype;
WeaponAttachmentChanged();
}
}
}
simulated function OnStartFire()
{
if (Role == ROLE_Authority)
{
bHasStartedFire = true;
bNetDirty = true;
}
if(WeaponAttachment != none)
{
WeaponAttachment.StartFire();
}
}
simulated function OnWeaponSpecialAction(int Arg)
{
if (Role == ROLE_Authority)
{
WeaponSpecialAction = Arg;
bNetDirty = true;
}
if(WeaponAttachment != none)
{
WeaponAttachment.OnSpecialEvent(Arg);
}
}
/**
* Called when a pawn's weapon has fired and is responsibile for
* delegating the creation off all of the different effects.
*
* bViaReplication denotes if this call in as the result of the
* flashcount/flashlocation being replicated. It's used filter out
* when to make the effects.
*/
simulated function WeaponFired(Weapon InWeapon, bool bViaReplication, optional vector HitLocation)
{
local KFWeapon KFW;
local class< KFProjectile > KFProj;
local KFImpactEffectInfo ImpactFX;
//local Vector vPlayerDir;
local Vector HitNormal;
HitNormal = Normal(Location - HitLocation);
// skip replication for owning client. This is done clientside for immediate
// player feedback (see 'InstantFireClient')
if ( Role == ROLE_AutonomousProxy && bViaReplication )
{
return;
}
LastWeaponFireTime = WorldInfo.TimeSeconds;
if ( IsFirstPerson() )
{
// most first person FX are done in Weapon.PlayFireEffects, but this allows us
// to reuse some code that is implemented in the weapon attachment (e.g. tracers)
if (WeaponAttachment != None)
{
WeaponAttachment.FirstPersonFireEffects(Weapon, HitLocation);
}
}
else
{
if (WeaponAttachment != None)
{
WeaponAttachment.ThirdPersonFireEffects(HitLocation, self, GetWeaponAttachmentAnimRateByte() );
}
}
// Play 1stP & 3rdP Impact Effects for FireType == EWFT_InstantHit
if ( HitLocation != Vect(0,0,0) && WorldInfo.NetMode != NM_DedicatedServer )
{
if ( WorldInfo.MyImpactEffectManager != None )
{
// try to use custom impact info from projectile class
KFW = KFWeapon( InWeapon );
if( KFW != none )
{
KFProj = KFW.GetKFProjectileClass();
}
else
{
KFProj = WeaponClassForAttachmentTemplate.static.GetKFProjectileClassByFiringMode(FiringMode, GetPerk());
}
if (KFProj != none)
{
ImpactFX = KFProj.default.ImpactEffects;
KFProj.static.PlayAddedImpactEffect(HitLocation, HitNormal);
}
// no custom impact info, use ImpactEffectManager default
`ImpactEffectManager.PlayImpactEffects(HitLocation, self,, ImpactFX);
}
}
if ( Role == ROLE_Authority && bUnaffectedByZedTime && WorldInfo.TimeDilation < 1.f )
{
StopPartialZedTime();
}
}
simulated function WeaponStoppedFiring(Weapon InWeapon, bool bViaReplication)
{
if (WeaponAttachment != None)
{
// always call function for both viewpoints, as during the delay between calling EndFire() on the weapon
// and it actually stopping, we might have switched viewpoints (e.g. this commonly happens when entering a vehicle)
WeaponAttachment.StopThirdPersonFireEffects();
WeaponAttachment.StopFirstPersonFireEffects(Weapon);
}
}
/** Returns the animation rate to scale all animations in the WeaponAttachment by */
simulated function byte GetWeaponAttachmentAnimRateByte()
{
return 0;
}
simulated function vector WeaponBob( float BobDamping, float JumpDamping )
{
local Vector V;
local KFPerk OwnerPerk;
OwnerPerk = GetPerk();
if( OwnerPerk != none && MyKFWeapon != none && MyKFWeapon.bUsingSights )
{
OwnerPerk.ModifyWeaponBopDamping( BobDamping, MyKFWeapon );
}
V = BobDamping * WalkBob;
V.Z = (0.45 + 0.55 * BobDamping)*WalkBob.Z;
if ( !bWeaponBob )
{
//V *= 2.5; // legacy? --- Instead, match WalkBob
V = WalkBob;
}
V.Z += JumpDamping *(LandBob - JumpBob);
return V;
}
/** Can Pawn reload his weapon? */
final simulated function bool CanReloadWeapon()
{
if( Physics == PHYS_RigidBody )
{
return FALSE;
}
return TRUE;
}
function KFWeapon FindBestWeapon()
{
local float BestPrimaryRating, BestSecondaryRating, BestMeleeRating;
local KFWeapon BestWeapon, BestPrimary, BestSecondary, BestMelee, TempWeapon;
foreach InvManager.InventoryActors(class'KFWeapon', TempWeapon)
{
// We only care about weapons we can actually drop
if (!TempWeapon.bDropOnDeath || !TempWeapon.CanThrow())
{
continue;
}
// Collect the best weapons from each inventory group
if (TempWeapon.InventoryGroup == IG_Primary)
{
if (BestPrimaryRating == 0.f || TempWeapon.GroupPriority > BestPrimaryRating)
{
BestPrimary = TempWeapon;
BestPrimaryRating = TempWeapon.GroupPriority;
}
}
else if (TempWeapon.InventoryGroup == IG_Secondary)
{
if (BestSecondaryRating == 0.f || TempWeapon.GroupPriority > BestSecondaryRating)
{
BestSecondary = TempWeapon;
BestSecondaryRating = TempWeapon.GroupPriority;
}
}
else if (TempWeapon.InventoryGroup == IG_Melee)
{
if (BestMeleeRating == 0.f || TempWeapon.GroupPriority > BestMeleeRating)
{
BestMelee = TempWeapon;
BestMeleeRating = TempWeapon.GroupPriority;
}
}
}
// Get our best possible weapon from all 3 categories and throw it
BestWeapon = BestPrimary != none ? BestPrimary : (BestSecondary != none ? BestSecondary : BestMelee);
return BestWeapon;
}
/**
* Toss active weapon using default settings (location+velocity).
*
* @param DamageType allows this function to do different behaviors based on the damage type
*/
function ThrowActiveWeapon( optional bool bDestroyWeap )
{
local Inventory WeaponToThrow;
local bool bIsHoldingCarryable;
// Only throw on server
if( Role < ROLE_Authority )
{
return;
}
bIsHoldingCarryable = KFCarryableObject(Weapon) != none;
// If we're dead, throw our best weapon if the one we have out can't be thrown
if( InvManager != none && Health <= 0 && (Weapon == none || !Weapon.bDropOnDeath || !Weapon.CanThrow()) )
{
WeaponToThrow = FindBestWeapon();
}
else
{
// if the player has died and is going to throw a carryable, also throw their best weapon
if (Health <= 0 && bIsHoldingCarryable)
{
WeaponToThrow = FindBestWeapon();
}
super.ThrowActiveWeapon( bDestroyWeap );
}
// throw the best weapon if needed
if (WeaponToThrow != none)
{
TossInventory(WeaponToThrow);
}
// if the player has died and is going to throw a non-carryable, also throw a carryable if it is in the inventory
if (Health <= 0 && KFCarryableObject(WeaponToThrow) == none && !bIsHoldingCarryable)
{
WeaponToThrow = InvManager.FindInventoryType(class'KFCarryableObject', true);
TossInventory(WeaponToThrow);
}
}
/** Called by KFPawnAnimInfo when determining whether an attack can be performed */
function bool ShouldPlayHeadlessMeleeAnims();
/** Called by KFPawnAnimInfo when determining whether an attack can be performed */
function bool ShouldPlaySpecialMeleeAnims();
/** Called during zed time when this actor should become fully affected */
function StopPartialZedTime()
{
bUnaffectedByZedTime = false;
}
/** Enables/disables the light for night vision (humans only) */
simulated function SetNightVisionLight(bool bEnabled);
/** called from third person skel mesh */
simulated function ANIMNOTIFY_ShellEject()
{
if ( WeaponAttachment != None )
{
WeaponAttachment.ANIMNOTIFY_ShellEject();
}
}
/** called from ANIMNOTIFY_SpawnKActor -- allows access to the KActor after spawn */
simulated function ANIMNOTIFY_SpawnedKActor( KFKActorSpawnable NewKActor, AnimNodeSequence AnimSeqInstigator );
/**
* returns base Aim Rotation without any adjustment (no aim error, no autolock, no adhesion.. just clean initial aim rotation!)
*
* @return base Aim rotation.
*/
simulated event Rotator GetBaseAimRotation()
{
local rotator AimRot;
if( IsDoingSpecialMove() && SpecialMoves[SpecialMove].GetSMAimRotation(AimRot) )
{
return AimRot;
}
return super.GetBaseAimRotation();
}
/**
* Allow Controller.GetAdjustedAimFor() on clients
*/
simulated function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc )
{
// If controller doesn't exist or we're a client, get the where the Pawn is aiming at
if ( Controller == None /*|| Role < Role_Authority*/ )
{
return GetBaseAimRotation();
}
// otherwise, give a chance to controller to adjust this Aim Rotation
return Controller.GetAdjustedAimFor( W, StartFireLoc );
}
/*********************************************************************************************
* @name Aim Assist
********************************************************************************************* */
/** Returns bone names to use for ironsight lock-on targeting */
simulated function bool GetAutoTargetBones(out array<name> WeakBones, out array<name> NormalBones)
{
if ( !IsHeadless() )
{
WeakBones.AddItem(HeadBoneName);
}
NormalBones.AddItem('Spine1');
NormalBones.AddItem(PelvisBoneName);
return true;
}
/** For a given camera location give the best auto target location on this character */
simulated function vector GetAutoLookAtLocation(vector CamLoc, Pawn InstigatingPawn)
{
local vector HitLocation, HitNormal;
local Actor HitActor;
local TraceHitInfo HitInfo;
local vector HeadLocation, TorsoLocation, PelvisLocation;
// Check to see if we can hit the head
HeadLocation = Mesh.GetBoneLocation(HeadBoneName);
HitActor = InstigatingPawn.Trace(HitLocation, HitNormal, HeadLocation, CamLoc, TRUE, vect(0,0,0), HitInfo, TRACEFLAG_Bullet);
if( HitActor == none || HitActor == Self )
{
//`log("Autotarget - found head");
return HeadLocation + (vect(0,0,-10.0));
}
// Try for the torso
TorsoLocation = Mesh.GetBoneLocation(TorsoBoneName);
HitActor = InstigatingPawn.Trace(HitLocation, HitNormal, TorsoLocation, CamLoc, TRUE, vect(0,0,0), HitInfo, TRACEFLAG_Bullet);
if( HitActor == none || HitActor == Self)
{
//`log("Autotarget - found torso");
return TorsoLocation;
}
// Try for the pelvis
PelvisLocation = Mesh.GetBoneLocation(PelvisBoneName);
HitActor = InstigatingPawn.Trace(HitLocation, HitNormal, PelvisLocation, CamLoc, TRUE, vect(0,0,0), HitInfo, TRACEFLAG_Bullet);
if( HitActor == none || HitActor == Self)
{
//`log("Autotarget - found pelvis");
return PelvisLocation;
}
//`log("Autotarget - found noting - returning location");
return Location + BaseEyeHeight * vect(0,0,0.5f);
}
/*********************************************************************************************
* @name Movement Methods
********************************************************************************************* */
/** Cloaking & Spotted */
function SetCloaked( bool bNewCloaking );
/** Is this pawn using super speed? */
native final function bool IsUsingSuperSpeed() const;
native function bool IsPawnMovingAwayFromMe( Pawn CheckPawn, optional float MinSpeed );
native function bool IsPawnMovingTowardMe( Pawn CheckPawn, optional float MinSpeed );
/** returns true if this pawn is surrounded by other pawns */
native function bool IsSurrounded(bool bOnlyEnemies, optional int MinNearbyPawns=2, optional float Radius=200);
/** Called from KFAIController::MoveToward, a good time to update sprinting status right before a move. */
event UpdateSprinting( Actor Goal );
function SetSprinting(bool bNewSprintStatus)
{
if ( bNewSprintStatus )
{
//Sprinting completely disabled
if (!bAllowSprinting)
{
bNewSprintStatus = false;
}
// Wait for uncrouch; see CheckJumpOrDuck
else if ( bIsCrouched )
{
bNewSprintStatus = false;
}
else if ( MyKFWeapon != None && !MyKFWeapon.AllowSprinting() )
{
bNewSprintStatus = false;
}
}
bIsSprinting = bNewSprintStatus;
if ( MyKFWeapon != None )
{
MyKFWeapon.SetWeaponSprint(bNewSprintStatus);
}
}
/** Perform jump if bJumpCapable=TRUE */
function bool DoJump( bool bUpdating )
{
if ( Super.DoJump(bUpdating) && !IsDoingSpecialMove() )
{
// cancel ironsights
if ( MyKFWeapon != None && MyKFWeapon.bUsingSights && !MyKFWeapon.bKeepIronSightsOnJump )
{
MyKFWeapon.PerformZoom(false);
}
bJumping = true;
NumJumpsRemaining = NumJumpsAllowed - 1;
return true;
}
else if (NumJumpsRemaining > 0 && bJumping)
{
Velocity.Z = JumpZ;
NumJumpsRemaining--;
return true;
}
return false;
}
/** Local Player - Short jump delay while waiting on Uncrouch */
function bool CannotJumpNow()
{
local PlayerController PC;
local KFPlayerInput Input;
if ( bIsCrouched )
{
PC = PlayerController(Controller);
if ( PC != None && PC.bDuck == 0 )
{
Input = KFPlayerInput(PC.PlayerInput);
if ( Input != None && `TimeSince(Input.PressedJumpTime) < 0.05f )
{
return true;
}
}
}
return false;
}
/** Overridden for CanBecomeDynamic actors */
simulated event Touch( Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
{
local StaticMeshComponent OtherSMC;
// Check for CanBecomeDynamic - When BlockActors=TRUE MakeDynamic is handled through physics
// code, otherwise use the touch event to activate. Generally, we want BlockActors=FALSE
// because these are clientside actors that can be in different positions on client and server.
if ( Other.bWorldGeometry && !OtherComp.BlockActors && Physics != PHYS_Spider )
{
// since we allow this on remote clients, do EffectIsRelevant check
if ( !WorldInfo.bDropDetail && WorldInfo.GetDetailMode() > DM_Low && ActorEffectIsRelevant(self, false) )
{
OtherSMC = StaticMeshComponent(OtherComp);
if ( OtherSMC != None && OtherSMC.CanBecomeDynamic() )
{
class'KActorFromStatic'.Static.MakeDynamic(OtherSMC);
}
}
}
}
/**
* Are we allowing this Pawn to be based on us?
* Allow Interaction Pawns to be based, since we do a lot of attachment stuff there.
*/
simulated function bool CanBeBaseForPawn(Pawn APawn)
{
return bCanBeBaseForPawns || (InteractionPawn == APawn);
}
/** Modified with increased X/Y and decreased Z */
function JumpOffPawn()
{
local float Theta;
// Make sure we're jumping off a pawn that is below us, otherwise we could
// end up in a situation where both pawns are constantly calling JumpOffPawn()
// which results in... interesting physics issues. -MattF
if( Base == none || Base.Location.Z > Location.Z )
{
return;
}
// 2d version of vrand
Theta = 2.f * PI * FRand();
SetPhysics(PHYS_Falling);
if ( Controller != None && !IsHumanControlled() )
{
// push AI more aggresively
Velocity.X += Cos(Theta) * (750 + CylinderComponent.CollisionRadius);
Velocity.Y += Sin(Theta) * (750 + CylinderComponent.CollisionRadius);
if ( VSize2D(Velocity) > 2.0 * FMax(500.0, GroundSpeed) )
{
Velocity = 2.0 * FMax(500.0, GroundSpeed) * Normal(Velocity);
}
Velocity.Z = 400;
// counteract the AI's high AirControl
AirControl = 0.05f;
SetTimer(1.0, false, nameof(RestoreAirControlTimer));
}
else
{
Velocity.X += Cos(Theta) * (150 + CylinderComponent.CollisionRadius);
Velocity.Y += Sin(Theta) * (150 + CylinderComponent.CollisionRadius);
if ( VSize2D(Velocity) > FMax(500.0, GroundSpeed) )
{
Velocity = FMax(500.0, GroundSpeed) * Normal(Velocity);
}
Velocity.Z = 200;
}
}
/** Called from JumpOffPawn */
function RestoreAirControlTimer()
{
// temp until we investigate why it's set in PossessedBy
if ( Controller != None && !IsHumanControlled() )
{
AirControl = AIAirControl;
return;
}
AirControl = default.AirControl;
}
/** Let Kismet know we've teleported so it can handle any transitions */
function PostTeleport( Teleporter OutTeleporter )
{
local array<SequenceObject> AllTeleportEvents;
local KFSeqEvent_PawnTeleported TeleportEvt;
local Sequence GameSeq;
local int i;
if( WorldInfo.NetMode == NM_Client )
{
return;
}
// Get the gameplay sequence.
GameSeq = WorldInfo.GetGameSequence();
if( GameSeq != none )
{
GameSeq.FindSeqObjectsByClass( class'KFSeqEvent_PawnTeleported', true, AllTeleportEvents );
for( i = 0; i < AllTeleportEvents.Length; ++i )
{
TeleportEvt = KFSeqEvent_PawnTeleported( AllTeleportEvents[i] );
if( TeleportEvt != None )
{
TeleportEvt.Reset();
TeleportEvt.CheckActivate( self, self );
}
}
}
}
/*********************************************************************************************
* @name Rendering Methods
********************************************************************************************* */
/** sets whether or not the owner of this pawn can see it */
simulated function SetMeshVisibility(bool bVisible)
{
local bool bIsPlayingEmote;
// Handle the main player mesh
if (Mesh != None)
{
Mesh.SetOwnerNoSee(!bVisible);
Mesh.CastShadow = bVisible;
}
// Head Mesh
HideHead( !bVisible );
// Handle weapon attachment
bIsPlayingEmote = KFPlayerController(Controller) != none && KFPlayerController(Controller).PlayerCamera.CameraStyle == 'Emote';
SetWeaponAttachmentVisibility(bVisible && !bIsPlayingEmote);
// Handle any weapons they might have
SetFirstPersonVisibility(!bVisible);
// FleX collision is off while in 1st person
SetEnableFleXCollision(bVisible);
}
/** Set the lighting channels on all the appropriate pawn meshes */
simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
{
local int AttachmentIdx;
// When this function gets called during PostBeginPlay(), the pawn does not have
// a weapon or a weapon attachment. So, cache the current lighting channel here.
// This is set on the weapon and weapon attachment when they are assigned to the
// pawn in KFWeapon::AttachWeaponTo() and KFPawn::WeaponAttachmentChanged() respectively.
PawnLightingChannel = NewLightingChannels;
// Third Person
if( mesh != none )
{
Mesh.SetLightingChannels(NewLightingChannels);
}
if( ThirdPersonHeadMeshComponent != none )
{
ThirdPersonHeadMeshComponent.SetLightingChannels(NewLightingChannels);
}
for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
{
if( ThirdPersonAttachments[AttachmentIdx] != none )
{
ThirdPersonAttachments[AttachmentIdx].SetLightingChannels(NewLightingChannels);
}
}
// First Person
if( ArmsMesh != none )
{
ArmsMesh.SetLightingChannels(NewLightingChannels);
for (AttachmentIdx = 0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++)
{
if (FirstPersonAttachments[AttachmentIdx] != none)
{
FirstPersonAttachments[AttachmentIdx].SetLightingChannels(NewLightingChannels);
}
}
}
// Weapon Attachment
if( WeaponAttachment != none)
{
WeaponAttachment.SetMeshLightingChannels(NewLightingChannels);
}
// Weapon
if( Weapon != none )
{
Weapon.SetMeshLightingChannels(NewLightingChannels);
}
}
simulated function HideHead( bool bHide )
{
// Head Mesh
if( ThirdPersonHeadMeshComponent != none )
{
ThirdPersonHeadMeshComponent.SetOwnerNoSee(bHide);
}
// @fixme Assuming cosmetic attachment is part of head mesh for now
SetThirdPersonAttachmentVisibility( !bHide );
}
simulated function SetThirdPersonAttachmentVisibility( bool bVisible )
{
local int AttachmentIdx;
for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
{
if( ThirdPersonAttachments[AttachmentIdx] != none )
{
ThirdPersonAttachments[AttachmentIdx].SetOwnerNoSee(!bVisible);
}
}
}
/*********************************************************************************************
* @name Damage/Death Methods
********************************************************************************************* */
/** If returns true, this monster is vulnerable to this damage type damage */
function bool IsVulnerableTo(class<DamageType> DT, optional out float DamageMod);
/** If returns true, this monster is vulnerable to this damage type damage */
function bool IsResistantTo(class<DamageType> DT, optional out float DamageMod);
simulated final function float GetHealthPercentage()
{
return float(Health) / float(HealthMax);
}
simulated function bool CanBeHealed();
/**
* Applies a push-back velocity on taking damage
* @note: Momentum is actually velocity! For some reason it's been processed and divided by mass (see TakeDamage)
*/
function HandleMomentum( vector Momentum, Vector HitLocation, class<DamageType> DamageType, optional TraceHitInfo HitInfo )
{
// Does our current special move allow momentum pushback?
if ( SpecialMove != SM_None && !SpecialMoves[SpecialMove].bAllowMomentumPush )
{
return;
}
// Don't transfer momentum unless it's large enough to be useful. This will prevent rapid PHYS_Walking<->PHYS_Falling
if ( VSizeSq(Momentum) > Square(200) )
{
AddVelocity( Momentum, HitLocation, DamageType, HitInfo );
}
}
event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bRepairArmor=true, optional bool bMessageHealer=true)
{
local int i;
local int OldHealth;
local bool superResult;
// Reduce burn duration and damage in half if you heal while burning
for( i=0; i<DamageOverTimeArray.Length; ++i )
{
if( DamageOverTimeArray[i].DoT_Type == DOT_Fire )
{
DamageOverTimeArray[i].Duration *= 0.5;
DamageOverTimeArray[i].Damage *= 0.5;
break;
}
}
OldHealth = Health;
superResult = Super.HealDamage(Amount, Healer, DamageType);
if (Health - OldHealth > 0)
{
WorldInfo.Game.ScoreHeal(Health - OldHealth, OldHealth, Healer, self, DamageType);
}
return superResult;
}
/** Overloaded to use a custom damagetype */
function TakeFallingDamage()
{
local float EffectiveSpeed;
if (Velocity.Z < -0.5 * MaxFallSpeed)
{
if ( Role == ROLE_Authority )
{
MakeNoise(1.0);
if (Velocity.Z < -1 * MaxFallSpeed)
{
EffectiveSpeed = Velocity.Z;
if (TouchingWaterVolume())
{
EffectiveSpeed += 100;
}
if (EffectiveSpeed < -1 * MaxFallSpeed)
{
TakeDamage(-100 * (EffectiveSpeed + MaxFallSpeed)/MaxFallSpeed, None, Location, vect(0,0,0), class'KFDT_Falling');
}
}
}
}
else if (Velocity.Z < -1.4 * JumpZ)
MakeNoise(0.5);
else if ( Velocity.Z < -0.8 * JumpZ )
MakeNoise(0.2);
}
/** Radius damage (e.g. explosives) */
simulated function TakeRadiusDamage
(
Controller InstigatedBy,
float BaseDamage,
float DamageRadius,
class<DamageType> DamageType,
float Momentum,
vector HurtOrigin,
bool bFullDamage,
Actor DamageCauser,
optional float DamageFalloffExponent=1.f
)
{
bTakingRadiusDamage = true;
Super.TakeRadiusDamage(InstigatedBy, BaseDamage, DamageRadius, DamageType, Momentum, HurtOrigin, bFullDamage, DamageCauser, DamageFalloffExponent);
bTakingRadiusDamage = false;
}
/** apply some amount of damage to this actor */
event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
local int OldHealth, ActualDamage;
local class<KFDamageType> KFDT;
local KFGameInfo KFGI;
local KFPlayerController KFPC;
local KFPawn_Monster PawnMonster;
local bool bAllowHeadshot;
local KFPowerUp KFPowerUp;
// NVCHANGE_BEGIN - RLS - Debugging Effects
`if(`notdefined(ShippingPC))
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
{
HitFxInfo.HitBoneIndex = HZI_HEAD;
}
`endif
// NVCHANGE_BEGIN - RLS - Debugging Effects
bAllowHeadshot = CanCountHeadshots();
OldHealth = Health;
Super.TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
// using the passed in damage type instead of the hitfxinfo since that doesn't get updated when zero damage is done
HandleAfflictionsOnHit(InstigatedBy, Normal(Momentum), class<KFDamageType>(DamageType), DamageCauser);
ActualDamage = OldHealth - Health;
if( ActualDamage > 0 )
{
WorldInfo.Game.ScoreDamage( ActualDamage, OldHealth, InstigatedBy, self, DamageType );
}
// Handle DamageOverTime. Must be done in TakeDamage for access to original unmodified
// damage. Requires valid DamageCauser, 'None' for DoT, to prevent recursion
if ( Health < OldHealth && DamageCauser != None )
{
KFDT = class<KFDamageType>(DamageType);
if ( KFDT != None )
{
KFDT.static.ApplySecondaryDamage(self, Damage, InstigatedBy);
if (Health <= 0)
{
KFDT.static.ApplyKillResults(self);
}
}
}
// Apply secondary damages from power ups
KFPC = KFPlayerController(InstigatedBy);
if( KFPC != none )
{
KFPowerUp = KFPC.GetPowerUp();
if( KFPowerUp != none && KFPowerUp.default.SecondaryDamageType != DamageType )
{
KFPowerUp.ApplySecondaryDamage(self, Damage, InstigatedBy);
}
}
if( bAllowHeadshot && HitFxInfo.HitBoneIndex == HZI_HEAD && OldHealth > 0 && WorldInfo.Game != None )
{
KFPC = KFPlayerController(InstigatedBy);
KFGI = KFGameInfo(WorldInfo.Game);
if( KFPC != none && KFGI != none && (PlayerReplicationInfo == none || PlayerReplicationInfo.GetTeamNum() == 255) && LastHeadShotReceivedTime != WorldInfo.TimeSeconds)
{
LastHeadShotReceivedTime = WorldInfo.TimeSeconds;
// potentially gives player extra XP
KFPC.AddZedHeadshot( KFGI.GetModifiedGameDifficulty(), HitFxInfo.DamageType );
`RecordWeaponHeadShot(KFPC, HitFxInfo.DamageType)
}
// Notify game info of a headshot kill. Done here instead of Died() so we have an accurate HitInfo/HitZone
if ( bPlayedDeath )
{
KFGameInfo(WorldInfo.Game).NotifyHeadshotKill(InstigatedBy, self);
PawnMonster = KFPawn_Monster(self);
if(PawnMonster != none && KFPC != none && KFGI != none)
{
KFPC.AddZedHeadshotKill( PawnMonster.class, KFGI.GetModifiedGameDifficulty(), DamageType );
}
}
}
}
/** Adjust damage based on inventory, other attributes */
function AdjustDamage(out int InDamage, out vector Momentum, Controller InstigatedBy, vector HitLocation, class<DamageType> DamageType, TraceHitInfo HitInfo, Actor DamageCauser)
{
local int HitZoneIdx;
local KFPawn_Monster InstigatorMonster;
local class<KFDamageType> KFDT;
`log(self@GetFuncName()@"Starting Damage="$InDamage@"Momentum="$Momentum@"Zone="$HitInfo.BoneName@"DamageType="$DamageType, bLogTakeDamage);
// Allow current weapon state to reduce damage
if ( MyKFWeapon != None )
{
MyKFWeapon.AdjustDamage(InDamage, DamageType, DamageCauser);
}
InstigatorMonster = InstigatedBy == none ? none : KFPawn_Monster(InstigatedBy.Pawn);
if (InDamage > 0 && InstigatedBy != none && InstigatedBy.Pawn.IsA('KFPawn_Human'))
{
InDamage = InDamage * AfflictionHandler.GetAfflictionDamageTakenModifier();
}
if( InDamage > 0 && InstigatorMonster != None )
{
// Increase AI damage by AI Damage modifiers
`log(self@GetFuncName()@" Difficulty Damage Mod ="$InstigatorMonster.DifficultyDamageMod, bLogTakeDamage);
InDamage = Max(InDamage * InstigatorMonster.DifficultyDamageMod, 1);
//Modify based on AI pawn's affliction status
`log(self@GetFuncName()@" Affliction Damage Mod = "$InstigatorMonster.AfflictionHandler.GetAfflictionDamageModifier(), bLogTakeDamage);
InDamage = Max(InDamage * InstigatorMonster.AfflictionHandler.GetAfflictionDamageModifier(), 1);
}
// apply zone specific vulnerability/resistance
HitZoneIdx = HitZones.Find('ZoneName', HitInfo.BoneName);
if( HitZoneIdx != INDEX_NONE )
{
InDamage *= HitZones[HitZoneIdx].DmgScale;
}
InDamage *= VolumeDamageScale;
if (KFPlayerController(Controller) != none)
{
KFPlayerController(Controller).AdjustDamage(InDamage, InstigatedBy, DamageType, DamageCauser, self);
}
else if (KFPlayerController(InstigatedBy) != none)
{
KFPlayerController(InstigatedBy).AdjustDamage(InDamage, InstigatedBy, DamageType, DamageCauser, self);
}
// Check non lethal damage
KFDT = class<KFDamageType>(DamageType);
if ( InDamage >= Health && KFDT != none && KFDT.default.bNonLethalDamage )
{
InDamage = Health - 1;
}
LastHitZoneIndex = HitZoneIdx;
`log(self @ GetFuncName() @ " After KFPawn adjustment Damage=" $ InDamage @ "Momentum=" $ Momentum @ "Zone=" $ HitInfo.BoneName @ "DamageType=" $ DamageType, bLogTakeDamage);
}
/** Updates the time damage was dealt or received */
function UpdateLastTimeDamageHappened()
{
LastTimeDamageHappened = WorldInfo.TimeSeconds;
}
/** Adjusts RadiusDamage (called just before AdjustDamage) */
function AdjustRadiusDamage(out float InBaseDamage, float DamageScale, vector HurtOrigin)
{
// Reduce the damage by the amount of exposuse this pawn has to the explosion
InBaseDamage *= GetExposureTo(HurtOrigin);
// Replicated value for clientside ragdoll momentum
LastRadiusDamageScale = FloatToByte(DamageScale);
LastRadiusHurtOrigin = HurtOrigin;
// NVCHANGE_BEGIN - RLS - Debugging Effects
`if(`notdefined(ShippingPC))
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVDebugDamage)
{
FlushPersistentDebugLines();
DrawDebugSphere( LastRadiusHurtOrigin, LastRadiusDamageScale, 10, 255, 255, 0, true );
}
`endif
// NVCHANGE_BEGIN - RLS - Debugging Effects
}
/** Overridden in subclasses, determines if we should record a headshot when taking damage */
function bool CanCountHeadshots()
{
return true;
}
/** Overriden in subclasses. Determines if we should explode on death in specific game modes */
function bool WeeklyShouldExplodeOnDeath()
{
return true;
}
/*********************************************************************************************
* Damage (Taken) History
********************************************************************************************* */
function int GetMostRecentDamageHistoryIndexFor( Pawn CheckKFP )
{
local int i;
for ( i = 0; i < DamageHistory.Length; i++ )
{
if ( DamageHistory[i].DamagerController != none )
{
if( DamageHistory[i].DamagerController == CheckKFP.Controller )
{
return i;
}
}
}
return -1;
}
function int RecentDamageFrom( Pawn CheckKFP, optional out int DamageAmount )
{
local int i;
for ( i = 0; i < DamageHistory.Length; i++ )
{
if ( DamageHistory[i].DamagerController != none )
{
if( DamageHistory[i].DamagerController == CheckKFP.Controller )
{
DamageAmount += DamageHistory[i].Damage;
//return true;
}
}
}
return DamageAmount;
}
function AddTakenDamage( Controller DamagerController, int Damage, Actor DamageCauser, class<KFDamageType> DamageType )
{
// Allow AI to process friendly fire differently
if( !DamagerController.bIsPlayer && !DamagerController.bIsPlayer )
{
NotifyFriendlyAIDamageTaken( DamagerController, Damage, DamageCauser, DamageType );
}
else if( Damage > 0 && DamagerController.GetTeamNum() != GetTeamNum() )
{
UpdateDamageHistory( DamagerController, Damage, DamageCauser, DamageType );
}
}
function TimerRestartForceEnemy()
{
local KFAIController KFAIC;
local Pawn ForcedEnemy;
if (Controller != none)
{
KFAIC = KFAIController( Controller );
if (KFAIC != none)
{
// Forces the ForcedEnemy again
KFAIC.CanForceEnemy = true;
ForcedEnemy = KFAIC.FindForcedEnemy();
if (ForcedEnemy != none)
{
KFAIC.ChangeEnemy(ForcedEnemy);
}
}
}
}
function UpdateDamageHistory( Controller DamagerController, int Damage, Actor DamageCauser, class<KFDamageType> DamageType )
{
local DamageInfo Info;
local Pawn BlockerPawn;
local bool bChangedEnemies;
local int HistoryIndex;
local float DamageThreshold;
local KFAIController KFAIC;
if( !GetDamageHistory( DamagerController, Info, HistoryIndex ) )
{
DamageHistory.Insert(0, 1);
}
if( Controller != none && !Controller.bIsPlayer )
{
KFAIC = KFAIController( Controller );
if( KFAIC != none )
{
DamageThreshold = float(HealthMax) * KFAIC.AggroPlayerHealthPercentage;
UpdateDamageHistoryValues( DamagerController, Damage, DamageCauser, KFAIC.AggroPlayerResetTime, Info, DamageType );
if( `TimeSince(DamageHistory[KFAIC.CurrentEnemysHistoryIndex].LastTimeDamaged) > 10 )
{
DamageHistory[KFAIC.CurrentEnemysHistoryIndex].Damage = 0;
}
// If we have a forced Enemy break it only if we fulfill the minimum health value
if (KFAIC.ForcedEnemy != none)
{
if (GetHealthPercentage() < KFAIC.DamageRatioToChangeForcedEnemy)
{
// Only if X seconds passed since last time we choose the ForcedEnemy
if ((WorldInfo.TimeSeconds - KFAIC.ForcedEnemyLastTime) > KFAIC.TimeCannotChangeFromForcedEnemy)
{
KFAIC.CanForceEnemy = false; // The timer we will reenable this to allow selection again
// If we have forced enemy, reactivate ForcedEnemy after X seconds, so it can default to the ForcedEnemy again
ClearTimer('TimerRestartForceEnemy');
SetTimer(KFAIC.TimeCanRestartForcedEnemy, false, 'TimerRestartForceEnemy');
KFAIC.ChangeEnemy(DamagerController.Pawn);
}
}
}
else
{
if (KFAIC.IsAggroEnemySwitchAllowed()
&& DamagerController.Pawn != KFAIC.Enemy
&& Info.Damage >= DamageThreshold
&& Info.Damage > DamageHistory[KFAIC.CurrentEnemysHistoryIndex].Damage)
{
BlockerPawn = KFAIC.GetPawnBlockingPathTo( DamagerController.Pawn, true );
if( BlockerPawn == none )
{
bChangedEnemies = KFAIC.SetEnemy(DamagerController.Pawn);
}
else
{
bChangedEnemies = KFAIC.SetEnemy( BlockerPawn );
}
}
}
}
}
else
{
UpdateDamageHistoryValues( DamagerController, Damage, DamageCauser, 0, Info, DamageType );
}
DamageHistory[HistoryIndex] = Info;
if( KFAIC != none && bChangedEnemies )
{
KFAIC.CurrentEnemysHistoryIndex = HistoryIndex;
}
}
function bool GetDamageHistory( Controller DamagerController, out DamageInfo InInfo, out int InHistoryIndex )
{
// Check if this controller is already in our Damage History
InHistoryIndex = DamageHistory.Find( 'DamagerController', DamagerController );
if( InHistoryIndex != INDEX_NONE )
{
InInfo = DamageHistory[InHistoryIndex];
return true;
}
InHistoryIndex = 0;
return false;
}
function UpdateDamageHistoryValues( Controller DamagerController, int Damage, Actor DamageCauser, float DamageResetTime, out DamageInfo InInfo, class<KFDamageType> DamageType )
{
local class<KFPerk> WeaponPerk;
// Update the history
InInfo.DamagerController = DamagerController;
// if too much time has passed since our last damage, reset the damage history
if( `TimeSince( InInfo.LastTimeDamaged ) > DamageResetTime )
{
InInfo.Damage = 0;
}
InInfo.Damage += Damage;
InInfo.TotalDamage += Damage;
// Zeds will not have a PRI unless game analytics is on
if( DamagerController.PlayerReplicationInfo != none )
{
InInfo.DamagerPRI = DamagerController.PlayerReplicationInfo;
}
// Make sure we have a weapon perk class. Grab the active perk's class as a fallback.
// Helps with the shared weapons like the 9mm etc.
WeaponPerk = GetUsedWeaponPerk( DamagerController, DamageCauser, DamageType );
if( WeaponPerk != none && InInfo.DamagePerks.Find( WeaponPerk ) == INDEX_NONE )
{
InInfo.DamagePerks.AddItem( WeaponPerk );
}
if (DamageCauser != none && InInfo.DamageCausers.Find(DamageCauser.Class) == INDEX_NONE)
{
InInfo.DamageCausers.AddItem(DamageCauser.Class);
}
if (DamageType != none && InInfo.DamageTypes.Find(DamageType) == INDEX_NONE)
{
InInfo.DamageTypes.AddItem(DamageType);
}
}
function NotifyFriendlyAIDamageTaken( Controller DamagerController, int Damage, Actor DamageCauser, class<KFDamageType> DamageType );
function class<KFPerk> GetUsedWeaponPerk( Controller DamagerController, Actor DamageCauser, class<KFDamageType> DamageType )
{
local class<KFPerk> WeaponPerk, InstigatorPerkClass;
local KFPlayerController KFPC;
local KFWeapon KFW;
KFPC = KFPlayerController(DamagerController);
if( KFPC == none )
{
return none;
}
InstigatorPerkClass = KFPC.GetPerk().GetPerkClass();
if( InstigatorPerkClass == none )
{
return none;
}
// Make sure we have a weapon perk class. Grab the active perk's class as a fallback.
// Helps with the shared weapons like the 9mm etc.
WeaponPerk = class'KFPerk'.static.GetPerkFromDamageCauser( DamageCauser, InstigatorPerkClass );
if( WeaponPerk == none )
{
KFW = KFWeapon(DamageCauser);
if( KFW == none && DamageType != none && DamageType.static.IsNotPerkBound() )
{
KFW = KFWeapon(KFPC.Pawn.Weapon);
if( KFW != none )
{
WeaponPerk = class'KFPerk'.static.GetPerkFromDamageCauser( KFW, InstigatorPerkClass );
}
}
}
if( WeaponPerk == none && KFW != none && ( class'KFPerk'.static.IsBackupWeapon( KFW ) || class'KFPerk'.static.IsDoshinegun( KFW ) ))
{
WeaponPerk = InstigatorPerkClass;
}
return WeaponPerk;
}
/** Returns the best enemy based on damage history values */
function Pawn GetBestAggroEnemy()
{
local int i;
local int DamageThreshold;
local DamageInfo DamageHistoryInfo;
DamageThreshold = float(HealthMax) * KFAIController(Controller).AggroZedHealthPercentage;
for( i = 0; i < DamageHistory.Length; ++i )
{
DamageHistoryInfo = DamageHistory[i];
if( DamageHistoryInfo.DamagerController != none
&& DamageHistoryInfo.DamagerController.Pawn != none
&& DamageHistoryInfo.Damage >= DamageThreshold
&& `TimeSince(DamageHistory[i].LastTimeDamaged) < 5.f )
{
return DamageHistoryInfo.DamagerController.Pawn;
}
}
return none;
}
/**
* returns how exposed this pawn is to a location
*/
function float GetExposureTo(vector TraceStart)
{
local float PercentExposed;
// DrawDebugLine(Mesh.GetBoneLocation(HeadBoneName), TraceStart, 255, 0, 0, TRUE);
// DrawDebugLine(Mesh.GetBoneLocation(LeftFootBoneName), TraceStart, 255, 0, 0, TRUE);
// DrawDebugLine(Mesh.GetBoneLocation(RightFootBoneName), TraceStart, 255, 0, 0, TRUE);
// DrawDebugLine(Location, TraceStart, 255, 0, 0, TRUE);
if( FastTrace(Mesh.GetBoneLocation(HeadBoneName), TraceStart,, true) )
{
PercentExposed += 0.4;
}
// A trace to location is already done by GameExplosionActor.uc, so we always have
// a base exposure of 0.3
//if ( FastTrace(Location, TraceStart,, true) )
//{
// PercentExposed += 0.3;
//}
PercentExposed += 0.3f;
if( FastTrace(Mesh.GetBoneLocation(LeftFootBoneName), TraceStart,, true) )
{
PercentExposed += 0.15;
}
if( FastTrace(Mesh.GetBoneLocation(RightFootBoneName), TraceStart,, true) )
{
PercentExposed += 0.15;
}
return PercentExposed;
}
/** Called before InitRagdoll() to make sure kinematic update is active */
simulated function PrepareRagdoll()
{
// Turn off hit reactions
if( PhysicsImpactBlendTimeToGo > 0.f )
{
StopPhysicsBodyImpact();
}
// Ensure we are always updating kinematic
Mesh.MinDistFactorForKinematicUpdate = 0.0;
// Force update mesh when ragdoll starts
Mesh.bUpdateSkelWhenNotRendered = true;
// If we had stopped updating kinematic bodies on this character due to distance from camera, force an update of bones now.
// Also ensure rigid body collision is on.
if( Mesh.bNotUpdatingKinematicDueToDistance )
{
Mesh.ForceSkelUpdate();
Mesh.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
Mesh.SetBlockRigidBody(TRUE);
}
else if ( !Mesh.bUpdateKinematicBonesFromAnimation )
{
Mesh.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
}
else if ( !bPlayedDeath && Role < ROLE_Authority )
{
// @hack: fix a bug with character stuck in ground after knockdown
// This is probably happening because SyncActorToRBPhysics is called before InitRigidBody()
Mesh.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
}
}
/* PlayDying() is called on server/standalone game when killed
and also on net client when pawn gets bTearOff set to true (and bPlayedDeath is false)
*/
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
// Clean up and terminate any currently playing effects
TerminateEffectsOnDeath();
// Replicate input params to other clients. These are from Pawn.uc, but never get set.
if ( !bTearOff )
{
HitDamageType = DamageType;
TakeHitLocation = HitLoc;
}
// Abort current special move
if( IsDoingSpecialMove() )
{
SpecialMoveHandler.EndSpecialMove();
}
if( bCanHeadTrack && bIsHeadTrackingActive && MyLookAtInfo.LookAtTarget != none )
{
ClearHeadTrackTarget( MyLookAtInfo.LookAtTarget );
}
// If possible, initialize a ragdoll death
if( ShouldRagdollOnDeath() )
{
PlayRagdollDeath(DamageType, HitLoc);
}
else
{
HideMeshOnDeath();
}
// no need for cylinder to block anymore
CylinderComponent.SetTraceBlocking(FALSE, FALSE);
// Track start time for gore effects (see Gears)
if (TimeOfDeath == 0.0f)
{
TimeOfDeath = WorldInfo.TimeSeconds;
}
// @NOTE: Epic has code in the dying state that checks to see if bTearOff=true before setting the lifespan.
// The problem is that bTearOff is set in super.PlayDying(), _after_ entering the dying state. Fix it here. -MattF
bTearOff = true;
Super.PlayDying(DamageType, HitLoc);
// Undo the addtional velocity from super(). This causes problems for ApplyRagdollImpulse
Velocity -= TearOffMomentum;
}
/** Returns true if this pawn should call PlayRagdollDeath */
simulated function bool ShouldRagdollOnDeath()
{
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
return false;
}
// When DropDetail is set, cull non-visible deaths
if ( (WorldInfo.bDropDetail || WorldInfo.GetDetailMode() == DM_Low ) &&
!ActorEffectIsRelevant((LastHitBy != None) ? LastHitBy.Pawn : None, false, 50000) )
{
return false;
}
return true;
}
/* PlayDying() is called on server/standalone game when killed
and also on net client when pawn gets bTearOff set to true (and bPlayedDeath is false)
*/
simulated function PlayRagdollDeath(class<DamageType> DamageType, vector HitLoc)
{
local TraceHitInfo HitInfo;
local vector HitDirection;
if (bReinitPhysAssetOnDeath && CharacterArch != none && CharacterArch.PhysAsset != none)
{
Mesh.SetPhysicsAsset(CharacterArch.PhysAsset, , true);
}
PrepareRagdoll();
if ( InitRagdoll() )
{
// Switch to a good RigidBody TickGroup to fix projectiles passing through the mesh
// https://udn.unrealengine.com/questions/190581/projectile-touch-not-called.html
Mesh.SetTickGroup(TG_PostAsyncWork);
SetTickGroup(TG_PostAsyncWork);
// Allow all ragdoll bodies to collide with all physics objects (ie allow collision with things marked RigidBodyIgnorePawns)
Mesh.SetRBChannel(RBCC_DeadPawn);
Mesh.SetRBCollidesWithChannel(RBCC_DeadPawn, ShouldCorpseCollideWithDead());
// ignore blocking volumes, this is important for volumes that don't always block (e.g. PawnBlockingVolume)
Mesh.SetRBCollidesWithChannel(RBCC_BlockingVolume, FALSE);
// Call CheckHitInfo to give us a valid BoneName
HitDirection = Normal(TearOffMomentum);
CheckHitInfo(HitInfo, Mesh, HitDirection, HitLoc);
// Play ragdoll death animation (bSkipReplication=TRUE)
if( bAllowDeathSM && CanDoSpecialMove(SM_DeathAnim) && ClassIsChildOf(DamageType, class'KFDamageType') )
{
DoSpecialMove(SM_DeathAnim, TRUE,,,TRUE);
KFSM_DeathAnim(SpecialMoves[SM_DeathAnim]).PlayDeathAnimation(DamageType, HitDirection, HitInfo.BoneName);
}
else
{
StopAllAnimations(); // stops non-RBbones from animating (fingers)
}
}
}
/** Called when ShouldRagdollOnDeath returns FALSE (server or client) */
simulated event HideMeshOnDeath()
{
if ( !Mesh.HiddenGame )
{
// Hide Meshes. Can't use Actor.SetHidden because it will replicate
Mesh.SetHidden(true);
Mesh.SetTraceBlocking( false, false );
if( ThirdPersonHeadMeshComponent.bAttached )
{
ThirdPersonHeadMeshComponent.SetHidden(true);
ThirdPersonHeadMeshComponent.SetTraceBlocking( false, false );
}
// turn off skeletal anims
StopAllAnimations();
// Abuse SetOnlyOwnerSee/bOverrideAttachmentOwnerVisibility to hide attached FX (e.g. blood jets)
Mesh.SetOnlyOwnerSee(true);
// destroy soon after TearOff
LifeSpan = 2.f;
}
}
/** Changed to simulated so we don't have to replicate the sound */
simulated function PlayDyingSound()
{
SoundGroupArch.PlayDyingSound( self );
}
/** This pawn has died. */
function bool Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
if (Super.Died(Killer, DamageType, HitLocation))
{
// Other pathing NPCs will stop considering me as a potential path blocker.
bBlocksNavigation = false;
return true;
}
return false;
}
/** We do not want to be encroached. (Important for Knockdown) */
event EncroachedBy( actor Other )
{
}
/** CrushedBy()
* Called for pawns that have bCanBeBaseForPawns=false when another pawn becomes based on them
* DW: This is a copy/paste of the Pawn.uc implementation w/ a scalar added to allow for mutators/gametypes
* to ramp up crush damage in a cleaner fashion.
*/
function CrushedBy(Pawn OtherPawn)
{
TakeDamage( (1-OtherPawn.Velocity.Z/400) * OtherPawn.Mass/Mass * CrushScale, OtherPawn.Controller,Location, vect(0,0,0) , class'DmgType_Crushed');
}
/** Called when a melee attack has been parried by another pawn */
function bool NotifyAttackParried(Pawn InstigatedBy, byte InParryStrength)
{
local KFPawn InstigatorPawn;
local KFPerk InstigatorPerk;
if ( IsDoingSpecialMove() && SpecialMoves[SpecialMove].CanInterruptWithParry() )
{
if ( CanDoSpecialMove(SM_Stumble) || CanDoSpecialMove(SM_KnockDown) )
{
InstigatorPawn = KFPawn(InstigatedBy);
if( InstigatorPawn != none )
{
InstigatorPerk = InstigatorPawn.GetPerk();
}
if( CanDoSpecialMove(SM_KnockDown) && InstigatorPerk != none && InstigatorPerk.ShouldKnockdown() )
{
Knockdown(, vect(1,1,20),,,, 1000 * Normal(Location - InstigatorPawn.Location ), Location);
}
else
{
DoSpecialMove(SM_Stumble,,, class'KFSM_Stumble'.static.PackParrySMFlags(self, Location - InstigatedBy.Location));
}
return TRUE;
}
}
return FALSE;
}
/** Checks all factors that would impede combat and returns TRUE if none of them are active */
simulated function bool IsCombatCapable()
{
return IsAliveAndWell() && !IsHeadless() && !IsImpaired() && !IsIncapacitated();
}
/** Overridden in subclasses, determines if pawn is impaired (panicked, etc) */
simulated function bool IsImpaired();
/** Returns true if pawn is incapacitated in any way */
simulated function bool IsIncapacitated()
{
return IsDoingSpecialMove(SM_Stumble)
|| IsDoingSpecialMove(SM_Stunned)
|| IsDoingSpecialMove(SM_Frozen)
|| IsDoingSpecialMove(SM_Knockdown)
|| IsDoingSpecialMove(SM_RecoverFromRagdoll);
}
/** Overridden in subclasses, determines if a pawn is headless */
simulated function bool IsHeadless();
/** Clean up function to terminate any effects on death */
simulated function TerminateEffectsOnDeath()
{
local int i;
// Destroy our weapon attachment
if( WeaponAttachment != None && !WeaponAttachment.bPendingDelete )
{
WeaponAttachment.DetachFrom(self);
WeaponAttachment.Destroy();
}
// stop dialog and looping sounds
SetWeaponAmbientSound(None);
SetSecondaryWeaponAmbientSound(None);
SetPawnAmbientSound(None);
WeaponAmbientEchoHandler.StopAllEchoes(bPendingDelete); // force if called from Destroy
DialogAkComponent.StopEvents();
SetPowerUpAmbientSound(None, None, None, None);
AfflictionHandler.Shutdown();
StopLocustVFX();
// send a special stop event to the audio system
if ( SoundGroupArch.OnDeathStopEvent != None )
{
PostAkEvent( SoundGroupArch.OnDeathStopEvent );
}
for (i = 0 ; i < DamageOverTimeArray.Length; i++)
{
OnEndDamageType(DamageOverTimeArray[i].DamageType);
}
}
/*********************************************************************************************
* @name Gore / Hit Reactions
********************************************************************************************* */
/**
* Helper function to get a hexagon around the character
* @param R Orientation. Either pawn rotation or camera.
* @param V Incident vector
*/
native static function EPawnOctant CalcOctagonRegion(rotator R, vector V);
native static function EPawnOctant CalcQuadRegion(rotator R, vector V);
/* returns all hit zones where HitZoneInjuries is set */
native final iterator function DamagedHitZones( out int Idx );
/** Partial-ragdoll hit reactions */
native simulated function PlayPhysicsBodyImpact(vector HitLocation, vector Momentum, class<DamageType> DamageType, name HitBoneName);
native simulated function StartPhysicsBodyImpact(Name HitBoneName, bool bUseMotors, class<DamageType> DamageType);
native simulated function StopPhysicsBodyImpact();
native simulated function vector GetImpactPhysicsImpulse(class<DamageType> DamageType, vector HitLoc, vector Momentum, Name HitBoneName);
/** Enables partial ragdoll updates for physics effects while still alive */
native simulated function InitPartialKinematics();
/** (Network: All) Play any gore effects related to a zone/limb being damaged */
simulated function HitZoneInjured(optional int HitZoneIdx=INDEX_None);
/** Called before, and in addition to, NotifyTakeHit() to processes melee specifically (Network: Server) */
function NotifyMeleeTakeHit(Controller InstigatedBy, vector HitLocation);
/**
* Very small momentum values get truncated during replication. So, we need to scale the
* momentum vector during replication. This needs to match the function DecodeUnitVector()
*/
function vector EncodeUnitVector(vector V)
{
return Normal(V) * 256.f;
}
simulated function vector DecodeUnitVector(vector V)
{
return Normal(V / 256.f);
}
static function bool IsLargeZed()
{
return false;
}
function int GetHitZoneIndex(name BoneName)
{
return HitZones.Find('ZoneName', BoneName);
}
function PlayHit(float Damage, Controller InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, TraceHitInfo HitInfo)
{
local int HitZoneIdx;
local class<KFDamageType> KFDT;
if( Damage <= 0 || (Controller != none && Controller.bGodMode) )
{
return;
}
// Cached hit params
HitZoneIdx = GetHitZoneIndex(HitInfo.BoneName);
KFDT = class<KFDamageType>(damageType);
// NVCHANGE_BEGIN - RLS - Debugging Effects
`if(`notdefined(ShippingPC))
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
{
HitZoneIdx = HZI_HEAD;
}
`endif
// NVCHANGE_BEGIN - RLS - Debugging Effects
AddHitFX(Damage, InstigatedBy, HitZoneIdx, HitLocation, Momentum, KFDT);
// Damage hit zones for replicated gore effects
if ( HitZoneIdx != INDEX_None )
{
if( InstigatedBy != none )
{
// Let the accuracy tracking system know our head was hit
if( HitZoneIdx == HZI_HEAD && KFPlayerController(InstigatedBy) != none )
{
KFPlayerController(InstigatedBy).AddHeadHit(1);
}
TakeHitZoneDamage(Damage, HitFxInfo.DamageType, HitZoneIdx, InstigatedBy.Pawn.Location);
}
else
{
// after tear-off, on a simulated proxy, InstigatedBy is none
TakeHitZoneDamage(Damage, HitFxInfo.DamageType, HitZoneIdx, vect(0,0,0));
}
}
LastPainTime = WorldInfo.TimeSeconds;
//Record weapon Damage for AAR
`RecordWeaponDamage(InstigatedBy, KFDT, Damage, Self, HitZoneIdx);
}
function AddHitFX(int Damage, Controller InstigatedBy, int HitZoneIdx, Vector HitLocation, Vector Momentum, class<KFDamageType> KFDT)
{
local bool bHasNewHitEffect;
bHasNewHitEffect = true;
// We don't process any new effects until we are done processing old ones.
if (bNeedsProcessHitFx)
{
if (InstigatedBy != none && InstigatedBy.Pawn == HitFxInstigator && KFDT != none && KFDT == HitFXInfo.DamageType)
{
// Add any additional hits to a separate repliated array
// @note: Do not stack radial hits because they already affect the whole body (and are more expensive)
if (HitFxAddedHitCount < MAX_ADDED_HITFX && !bTakingRadiusDamage)
{
// Replicate any additional hits separately from the HitFxInfo struct and pass them as relative location to replicate easier
HitFxAddedRelativeLocs[HitFxAddedHitCount] = HitLocation - HitFxInfo.HitLocation;
HitFxAddedHitCount++;
}
bHasNewHitEffect = false;
}
else if (!bTakingRadiusDamage && HitFxInfo.bRadialDamage)
{
// if we have to choose (rare), prioritize radius damage
bHasNewHitEffect = false;
}
}
// This is the first (and usually only) hit that occured this frame
if (bHasNewHitEffect)
{
HitFxInstigator = InstigatedBy != None ? InstigatedBy.Pawn : None;
HitFxInfo.HitLocation = HitLocation;
HitFxInfo.EncodedHitDirection = (KFDT != none && KFDT.default.bPointImpulseTowardsOrigin && InstigatedBy.Pawn != none)
? EncodeUnitVector(Normal(Location - InstigatedBy.Pawn.Location))
: EncodeUnitVector(Normal(Momentum));
HitFxInfo.HitBoneIndex = HitZoneIdx;
HitFxInfo.bRadialDamage = bTakingRadiusDamage;
HitFxInfo.DamageType = KFDT; // If we do not have a damagetype, replicate none
if (InstigatedBy != none)
{
HitFxInfo.DamagerPRI = InstigatedBy.PlayerReplicationInfo;
}
LastTakeHitTimeout = WorldInfo.TimeSeconds + (0.5f);
if (bTakingRadiusDamage && !IsZero(LastRadiusHurtOrigin))
{
HitFxRadialInfo.RadiusDamageScale = LastRadiusDamageScale;
LastRadiusDamageScale = 255;
HitFxRadialInfo.RadiusHurtOrigin = LastRadiusHurtOrigin;
LastRadiusHurtOrigin = vect(0, 0, 0);
// Nudge HitLoc a bit for edge-case where Victim and Explosive are stationary (NEQ fails)
HitFxInfo.HitLocation.Z += FRand();
}
if (bPlayedDeath && KFDT != none && KFDT.default.bCanObliterate)
{
HitFxInfo.bObliterated = KFDT.static.CheckObliterate(self, Damage);
}
else
{
HitFxInfo.bObliterated = false;
}
HitFxAddedHitCount = 0;
}
bNeedsProcessHitFx = true;
}
/** Plays and replicates heal effects
* parallels PlayHit
*/
function PlayHeal( class<KFDamageType> DamageType, optional TraceHitInfo HitInfo )
{
HitFxInfo.HitLocation = Location;
HitFxInfo.HitLocation.Z += FRand();
HitFxInfo.DamageType = DamageType;
HitFxInfo.HitBoneIndex = HitZones.Find('ZoneName', HitInfo.BoneName);
LastTakeHitTimeout = WorldInfo.TimeSeconds + (0.5f);
if( WorldInfo.NetMode != NM_DedicatedServer )
{
PlayHealEffects(DamageType);
}
}
/** Apply damage to a specific zone (useful for gore effects) */
function TakeHitZoneDamage(float Damage, class<DamageType> DamageType, int HitZoneIdx, vector InstigatorLocation)
{
local float GoreDamage;
local class<KFDamageType> DmgType;
// Start with tha actual damage
GoreDamage = Damage;
// Scale the gore damage by the damage scale specified in the damage type.
// This allows us to customize the gore effects independently of the actual damage
// and also allow the damage type to attenuate the damage based on distance
DmgType = class<KFDamageType>(DamageType);
if( DmgType != none )
{
if( !IsZero(InstigatorLocation) )
{
GoreDamage *= DmgType.static.GetGoreDamageScale(Location, InstigatorLocation);
}
if ( bPlayedDeath && TimeOfDeath == WorldInfo.TimeSeconds )
{
GoreDamage *= DmgType.static.GetOnDeathGoreScale();
}
}
HitZones[HitZoneIdx].GoreHealth -= GoreDamage;
}
/** Called on the server when GoreHealth reaches zero */
function bool CanInjureHitZone(class<DamageType> DamageType, int HitZoneIdx)
{
local class<KFDamageType> KFDmgType;
local name HitZoneName;
//Don't attempt hit zone damage if we have an invalid hit zone
if (HitZoneIdx > HitZones.Length)
{
return false;
}
KFDmgType = class<KFDamageType>(DamageType);
HitZoneName = HitZones[HitZoneIdx].ZoneName;
// Added TimeOfDeath check, so if we've just died always do head gore
if( (!bPlayedDeath || WorldInfo.TimeSeconds == TimeOfDeath) && HitZoneIdx == HZI_HEAD )
{
if (KFDmgType.static.CanDismemberHitZoneWhileAlive(HitZoneName))
{
return true;
}
}
if ( bPlayedDeath )
{
if ( KFDmgType != none && KFDmgType.static.CanDismemberHitZone( HitZoneName ) )
{
return true;
}
}
return false;
}
/** Returns TRUE if any limbs were dismembered */
simulated function bool HasInjuredHitZones()
{
return InjuredHitZones > 0;
}
/** Plays clientside hit effects using the data in HitFxInfo */
simulated function PlayTakeHitEffects( vector HitDirection, vector HitLocation, optional bool bUseHitImpulse = true )
{
local KFPlayerController KFPC;
local class<KFDamageType> DmgType;
local KFPawn InstigatedBy;
DmgType = HitFxInfo.DamageType;
if( IsLocallyControlled() && !Controller.bGodMode )
{
// Apply gameplay post process effects
KFPC = KFPlayerController(Controller);
if( KFPC != none && DmgType != None )
{
KFPC.PlayScreenHitFX(DmgType, true);
// assumes all types with radial impulse (which include husk fireball) are "explosive"
if( Dmgtype.default.RadialDamageImpulse > 0 )
{
KFPC.PlayEarRingEffect( ByteToFloat(HitFxRadialInfo.RadiusDamageScale) );
}
}
// Allow weapon to play additional special effects
if( MyKFWeapon != None )
{
MyKFWeapon.PlayTakeHitEffects(HitFxInfo.HitLocation, HitFxInstigator);
}
}
// NVCHANGE_BEGIN - RLS - Debugging Effects
`if(`notdefined(ShippingPC))
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
{
HitFxInfo.HitBoneIndex = HZI_HEAD;
}
`endif
// NVCHANGE_BEGIN - RLS - Debugging Effects
if ( HitFxInfo.DamageType != None )
{
HitFxInfo.DamageType.static.PlayImpactHitEffects(self, HitLocation, HitDirection, HitFxInfo.HitBoneIndex, HitFxInstigator);
}
// Finally, notify the damage instigator (if net relevant) to play it's own hit effects
if ( HitFxInstigator != None )
{
InstigatedBy = KFPawn(HitFxInstigator);
InstigatedBy.PlayDamageInstigatorHitEffects(self);
}
LastPainTime = WorldInfo.TimeSeconds;
}
/** Plays clientside heal effects using the data in HitFxInfo (which is set in PlayHeal)
* parallels PlayTakeHitEffects
*/
simulated function PlayHealEffects(class<KFDamageType> DamageType)
{
local KFPlayerController KFPC;
KFPC = KFPlayerController(Controller);
if( KFPC != none )
{
KFPC.PlayScreenHitFX(DamageType, false);
}
if ( DamageType != None )
{
DamageType.static.PlayImpactHitEffects(self, Location, vect(0,0,1), HitFxInfo.HitBoneIndex, HitFxInstigator);
}
}
/** client side call to update visual scale of the mesh */
simulated event UpdateBodyScale(float NewScale)
{
//Since resetting phys asset after death caused rocket ships, just exit out of here.
// Let it continue updating internal scale value in case we need it in the future.
if (!IsAliveAndWell() || Physics == PHYS_RigidBody)
{
return;
}
bReinitPhysAssetOnDeath = true;
CurrentBodyScale = NewScale;
Mesh.SetScale(CurrentBodyScale);
PitchAudio(CurrentBodyScale);
SetBaseEyeheight();
}
simulated function PitchAudio(float NewScale)
{
SetRTPCValue('Visual_Scale', NewScale);
DialogAkComponent.SetRTPCValue("Visual_Scale", NewScale);
}
/** Called clientside by PlayTakeHitEffects on the Instigating Pawn */
simulated function PlayDamageInstigatorHitEffects(KFPawn Victim)
{
// Implemented in KFPawn_Human
}
/** Applies the contents of HitFxInfo. Called from PlayTakeHitEffects() */
simulated function ApplyRagdollImpulse(class<KFDamageType> DamageType, vector HitLoc, vector HitDirection, Name HitBoneName, optional float GoreImpulseScale=1.f)
{
local vector Impulse;
local vector LinearVelocity;
// Skip if no damage type
if( DamageType == None || Mesh.PhysicsWeight == 0.f )
{
return;
}
// Skip if impulse is scaled down to 0
if( GoreImpulseScale == 0.f )
{
return;
}
// Handle linear velocity
if ( !bHasBrokenConstraints && DamageType.default.KDeathVel > 0 )
{
LinearVelocity += HitDirection * DamageType.default.KDeathVel;
if ( !IsZero(LinearVelocity) )
{
`log("ApplyRagdollImpulse RBLinearVelocity:" @ VSize(LinearVelocity) @ "KDeathVel:" @ DamageType.default.KDeathVel, bLogPhysicsBodyImpact );
Mesh.SetRBLinearVelocity(LinearVelocity, TRUE);
}
}
// Add up PointImpulse from damage type
if ( DamageType.default.KDamageImpulse > 0.f )
{
Impulse += HitDirection * DamageType.default.KDamageImpulse;
}
if ( DamageType.default.KDeathUpKick > 0 )
{
Impulse += Vect(0,0,1) * DamageType.default.KDeathUpKick;
}
// Add RadialImpulse (e.g. Explosives)
if ( DamageType.default.RadialDamageImpulse > 0.f )
{
Impulse += HitDirection * DamageType.default.RadialDamageImpulse;
if ( HitFxInfo.bRadialDamage && HitFxRadialInfo.RadiusDamageScale < 255 )
{
// Calculate falloff
Impulse *= ByteToFloat(HitFxRadialInfo.RadiusDamageScale);
}
// AddRadialImpulse applies impulse to the root RB, however using the Torso gives better results. If
// we want this to truly function like AddRadialImpulse both HitBoneName and HitLoc should be zeroed
//if( HitBoneName == '' )
//{
// HitBoneName = TorsoBoneName;
//}
// HitLoc = vect(0,0,0);
}
// Reduce impulse if the gore system has dismembered this bone already
if ( bHasBrokenConstraints && Mesh.IsBrokenConstraint( HitBoneName ) )
{
GoreImpulseScale *= DamageType.default.GibImpulseScale;
}
// Lastly, multiply scaling factors and apply impulse
Impulse *= PhysRagdollImpulseScale * GoreImpulseScale;
if ( !IsZero(Impulse) )
{
`log("ApplyRagdollImpulse Impulse:"@VSize(Impulse)@"Bone:"$HitBoneName@"KDamageImpulse:"$DamageType.default.KDamageImpulse
@"RadialDamageImpulse:"$DamageType.default.RadialDamageImpulse, bLogPhysicsBodyImpact );
Mesh.AddImpulse(Impulse, HitLoc, HitBoneName);
}
}
/**
* Given a skeletal bone, returns the next rigid body bone in the heirarchy. This is useful
* if we hit a bone that doesn't have an RB (like Spine2) and we want to apply an impulse
*/
simulated function name GetRBBoneFromBoneName(Name BoneName)
{
local int BodyIndex;
local byte RBBoneCheckCount;
local name OriginalBoneName;
OriginalBoneName = BoneName;
// If our hit bone does not exist in the physics asset, use the hitbones parent
BodyIndex = mesh.PhysicsAsset.FindBodyIndex(BoneName);
while(BodyIndex == INDEX_NONE)
{
BoneName = mesh.GetParentBone(BoneName);
if( BoneName == '' )
{
return ''; // no more parents (root bone)
}
BodyIndex = mesh.PhysicsAsset.FindBodyIndex(BoneName);
if( (++RBBoneCheckCount) >= MAX_GET_RBBONE_CHECKS)
{
`log(Self @ GetFuncName() @"Rigidbody bone " @OriginalBoneName @" not found after" @MAX_GET_RBBONE_CHECKS @"Checks");
return ''; // exhausted tries
}
}
return BoneName;
}
/** On Injury ragdoll the arm */
simulated function RagdollArm(bool bUseMotors)
{
// Turn off hit reactions
if( PhysicsImpactBlendTimeToGo > 0.f )
{
StopPhysicsBodyImpact();
// For now disable physics hit reactions until they can be properly handled
bCanPlayPhysicsHitReactions = false;
}
// Unfix bones that should be affected by physics, fix others (Kinematic)
Mesh.PhysicsAssetInstance.SetNamedBodiesFixed(FALSE, ArmPhysicsBoneList, Mesh, FALSE, TRUE);
if ( Mesh.PhysicsWeight < 1.f )
{
InitPartialKinematics();
}
}
/** Called when a specific contraint is stretched further than it should be */
event OnRigidBodyLinearConstraintViolated(name StretchedBoneName)
{
// @todo: use the gore system to gib the closest valid bone
//mesh.HideBoneByName(StretchedBoneName, PBO_Term);
`log("Linear constraint violated, hiding bone " @ StretchedBoneName);
}
/** Called when all attempts fail to make this corpse go to sleep. */
event OnRigidBodyRefusedToSleep()
{
local KFGoreManager GM;
GM = `GoreManager;
if ( GM != None )
{
GM.RemoveAndDeleteCorpse(GM.CorpsePool.Find(self));
//GM.CauseObliteration(self, Location, None);
}
}
/** Starts at 0 when pawn dies */
native function SetRagdollWarningLevel(byte WarningLevel);
/**
* Send this pawn to ragdoll and apply the given forces. All params are optional, to enable
* any combination of forces (whole-body, radial, or point)
*
* @param RBLinearVelocity Linear velocity to apply to entire rigid body.
* @param RBAngularVelocity Angular velocity to apply to entire rigid body.
* @param RadialOrigin World-space origin of radial impulse.
* @param RadialRadius Radius of radial impulse. If 0.0, no radial impulse will be applied.
* @param RadialStrength Strength of radial impulse. If 0.0, no radial impulse will be applied.
* @param PointImpulse Point impulse to apply. if 0,0,0, no point impulse will be applied.
* @param PointImpulsePosition Position in world space at which PointImpulse should be applied.
* @param PointImpulseBoneName Bone that receives point impulse.
*/
function Knockdown( optional vector RBLinearVelocity, optional vector RBAngularVelocity, optional vector RadialOrigin,
optional float RadialRadius, optional float RadialStrength, optional vector PointImpulse,
optional vector PointImpulsePosition, optional byte HitZoneIdx=255 )
{
if (Role < ROLE_Authority)
{
`if(`notdefined(ShippingPC))
`warn("Only allowed on server");
ScriptTrace();
`endif
return;
}
if (IsZero(RBLinearVelocity) && IsZero(RBAngularVelocity))
{
`Warn("No linear or angular velocity - one or the other must be set for replication to work");
ScriptTrace();
return;
}
// only one knockdown allowed at a time and not at all if dead or DBNO
// don't knock them out if driving vehicle
if (Physics == PHYS_RigidBody || bPlayedDeath || DrivenVehicle != None)
{
return;
}
if( CanDoSpecialMove(SM_Knockdown) )
{
StopFiring();
// set the knockdown information for use in ApplyKnockdownImpulse()
KnockdownImpulse.LinearVelocity = RBLinearVelocity;
KnockdownImpulse.AngularVelocity = RBAngularVelocity;
if ( RadialStrength > 0 )
{
KnockdownImpulse.ImpulsePosition = RadialOrigin;
KnockdownImpulse.ImpulseStrength.X = RadialRadius;
KnockdownImpulse.ImpulseStrength.Y = RadialStrength;
KnockdownImpulse.ImpulseStrength.Z = 0;
KnockdownImpulse.bIsRadialImpulse = true;
}
else
{
KnockdownImpulse.ImpulsePosition = PointImpulsePosition;
KnockdownImpulse.ImpulseStrength = PointImpulse;
//KnockdownImpulse.PointImpulseBoneName = PointImpulseBoneName;
KnockdownImpulse.PointImpulseHitZone = HitZoneIdx;
KnockdownImpulse.bIsRadialImpulse = false;
}
// transition to the KnockedDown state
DoSpecialMove(SM_Knockdown, TRUE);
}
}
/** Gets skin effects associated with hit zone (allows pawns to override) */
simulated function KFSkinTypeEffects GetHitZoneSkinTypeEffects( int HitZoneIdx )
{
local int HitZoneSkinID;
if( HitZoneIdx != 255 )
{
HitZoneSkinID = HitZones[HitZoneIdx].SkinID;
}
if ( HitZoneSkinID >= CharacterArch.ImpactSkins.Length )
{
return none;
}
return CharacterArch.ImpactSkins[HitZoneSkinID];
}
/**
* Used to adjust strength of all incoming afflictions (similar to AdjustDamage)
* based on current situation / state.
*/
simulated function AdjustAffliction(out float AfflictionPower);
function HandleAfflictionsOnHit(Controller DamageInstigator, vector HitDir, class<KFDamageType> DamageType, Actor DamageCauser)
{
//Handle afflictions
if (AfflictionHandler != None)
{
AfflictionHandler.NotifyTakeHit(DamageInstigator, HitDir, DamageType, DamageCauser);
}
}
/*********************************************************************************************
* @name Damage over Time
********************************************************************************************* */
/** Add (or udpate existing) damage over time effect */
function ApplyDamageOverTime(int Damage, Controller InstigatedBy, class<KFDamageType> KFDT)
{
local DamageOverTimeInfo DoTInfo;
local int DoTIndex;
local int NewDoTDamage, NewTotalDamage, RemainingTotalDamage;
local float NewDoTDuration;
// Check to see if we already have this type of damage in the array
DoTIndex = KFDT.default.bStackDot ? -1 : DamageOverTimeArray.Find('DoT_Type', KFDT.default.DoT_Type);
NewDoTDamage = Round( Damage * KFDT.default.DoT_DamageScale );
NewDoTDuration = KFDT.default.DoT_Duration * GetPerkDoTScaler( InstigatedBy, KFDT );
// If we aren't already doing this type of damage over time, add it to the DamageOverTimeArray to be processed
if( DoTIndex < 0 )
{
// Only add more damage if its not zero
if( NewDoTDamage > 0 )
{
DoTInfo.Damage = NewDoTDamage;
DoTInfo.DamageType = KFDT;
DoTInfo.DoT_Type = KFDT.default.DoT_Type;
DoTInfo.Duration = NewDoTDuration;
DoTInfo.Interval = KFDT.default.DoT_Interval;
DoTInfo.InstigatedBy = InstigatedBy;
DoTInfo.TimeUntilNextDamage = KFDT.default.DoT_Interval;
OnStartDamageType(KFDT);
DamageOverTimeArray[DamageOverTimeArray.Length] = DoTInfo;
}
}
else
{
RemainingTotalDamage = (DamageOverTimeArray[DoTIndex].Duration / DamageOverTimeArray[DoTIndex].Interval) * DamageOverTimeArray[DoTIndex].Damage;
NewTotalDamage = (NewDoTDuration / KFDT.default.DoT_Interval) * NewDoTDamage;
if( NewTotalDamage > RemainingTotalDamage )
{
DamageOverTimeArray[DoTIndex].Damage = NewDoTDamage;
DamageOverTimeArray[DoTIndex].Duration = NewDoTDuration;
DamageOverTimeArray[DoTIndex].DamageType = KFDT;
}
}
}
function float GetPerkDoTScaler( optional Controller InstigatedBy, optional class<KFDamageType> KFDT ){ return 1.f; }
/** Tick the damage over time system */
function TickDamageOverTime(float DeltaTime)
{
local int i;
// Nothing to do if there is no damage over time to apply
if( DamageOverTimeArray.Length < 1 )
{
return;
}
for( i = DamageOverTimeArray.Length - 1; i >= 0; i-- )
{
DamageOverTimeArray[i].Duration -= DeltaTime;
DamageOverTimeArray[i].TimeUntilNextDamage -= DeltaTime;
// Do damage at each DoT interval
if( DamageOverTimeArray[i].TimeUntilNextDamage <= 0.f )
{
DamageOverTimeArray[i].TimeUntilNextDamage = DamageOverTimeArray[i].Interval;
TakeDamage(DamageOverTimeArray[i].Damage, DamageOverTimeArray[i].InstigatedBy, Location, vect(0,0,0), DamageOverTimeArray[i].DamageType);
}
// Remove damage over time elements from the array when they have timed out
if( DamageOverTimeArray[i].Duration <= 0 || DamageOverTimeArray[i].Duration < DamageOverTimeArray[i].Interval )
{
OnEndDamageType(DamageOverTimeArray[i].DamageType);
DamageOverTimeArray.Remove(i,1);
continue;
}
}
}
simulated function OnStartDamageType(class<KFDamageType> DamageType)
{
switch (DamageType.Name)
{
case 'KFDT_Toxic_HRG_Locust':
StartLocustVFX();
break;
}
}
simulated function OnEndDamageType(class<KFDamageType> DamageType)
{
switch (DamageType.Name)
{
case 'KFDT_Toxic_HRG_Locust':
StopLocustVFX();
break;
}
}
/*********************************************************************************************
* @name Animation
********************************************************************************************* */
/** Used by KFPawnAnimInfo and bosses/large Zeds that have different phases of combat */
simulated function int GetCurrentBattlePhase()
{
return 0;
}
function AnimInterruptNotifyTimer();
/** Play a body stance animation on slots defined in the Pawn's AnimTree */
native function float PlayBodyAnim(name AnimName, EAnimSlotStance BodyStance,
optional float Rate=1.f,
optional float BlendInTime=0.2f,
optional float BlendOutTime=0.2f,
optional bool bLooping,
optional bool bOverride=TRUE);
/** Stop playing body stance animation on slots defined in the Pawn's AnimTree */
native function StopBodyAnim(EAnimSlotStance BodyStance, optional float BlendOutTime);
/** Set new MeshTranslationOffset (world offset) **/
native simulated function bool UpdateMeshTranslationOffset(vector NewOffset, optional bool bForce);
/* Sets our head tracking target and turns on head tracking */
simulated native function SetHeadTrackTarget(Actor NewHeadTrackTarget, optional vector TargetOffset, optional float TargetTrackPct = 1.f, optional bool bUseSpine=false, optional float BlendIn=-1.f);
/* Clears our head tracking target and turns off head tracking */
simulated native function ClearHeadTrackTarget(Actor HeadTrackTargetToClear, optional float BlendOut=-1.f);
/** Save off commonly used nodes so the tree doesn't need to be iterated over often */
simulated native event CacheAnimNodes();
/** script version of CacheAnimNodes */
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
BodyStanceNodes[EAS_FullBody] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_FullBody'));
BodyStanceNodes[EAS_UpperBody] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_Upper'));
BodyStanceNodes[EAS_LowerBody] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_Lower'));
if( BodyStanceNodes[EAS_LowerBody] != none )
{
// we don't want anims played on lower body to duplicate notifies triggered by anims on the upper body
BodyStanceNodes[EAS_LowerBody].bNoNotifies = true;
}
// Optional additive node. The fullbody node can also be used, but this allows additives during another fullbody.
BodyStanceNodes[EAS_Additive] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_Additive'));
BodyStanceNodes[EAS_Face] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_Face'));
IKFootLeft = KFSkelControl_FootPlacement(SkelComp.FindSkelControl('FootIK_L'));
IKFootRight = KFSkelControl_FootPlacement(SkelComp.FindSkelControl('FootIK_R'));
// Always tick gameplay critical BodyStance nodes (anim notifies, etc...)
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
BodyStanceNodes[EAS_FullBody].bTickDuringPausedAnims = true;
BodyStanceNodes[EAS_UpperBody].bTickDuringPausedAnims = true;
}
}
/** Event called when an AnimNodeSequence reaches the end and stops. */
simulated event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
if( SpecialMove != SM_None )
{
//`Log"SpecialMove ==" @ SpecialMove @ "calling AnimEndNotify()");
if( Mesh.TickGroup == TG_DuringAsyncWork && SpecialMoves[SpecialMove].bShouldDeferToPostTick )
{
SpecialMoves[SpecialMove].DeferredSeqName = SeqNode.AnimSeqName;
`DeferredWorkManager.DeferSpecialMoveAnimEnd(SpecialMoves[SpecialMove]);
}
else
{
SpecialMoves[SpecialMove].AnimEndNotify(SeqNode, PlayedTime, ExcessTime);
}
}
}
/** Stops all animations on character */
simulated function StopAllAnimations()
{
Mesh.bPauseAnims = true;
if (Physics == PHYS_RigidBody)
{
Mesh.PhysicsWeight = 1.0;
Mesh.bUpdateKinematicBonesFromAnimation = false;
}
}
/**
* Set a new profile on all the AimOffset nodes.
*/
simulated final function SetAimOffsetNodesProfile(Name NewProfileName)
{
local int i;
for( i=0; i<AimOffsetNodes.Length; ++i )
{
AimOffsetNodes[i].SetActiveProfileByName( NewProfileName );
}
}
/**
* Returns aim offset profile to default (index 0)
*/
simulated final function SetDefaultAimOffsetNodesProfile()
{
local int i;
for( i=0; i<AimOffsetNodes.Length; ++i )
{
AimOffsetNodes[i].SetActiveProfileByIndex( 0 );
}
}
simulated function name GetSpecialMoveTag()
{
local byte AtkIndex;
if( IsDoingSpecialMove() && SpecialMoveFlags != 255 && PawnAnimInfo != none )
{
AtkIndex = SpecialMoveFlags & 15;
return PawnAnimInfo.Attacks[AtkIndex].Tag;
}
return '';
}
/**
* Configure mesh settings based on current FleX level.
* @param bResetDefaults If set, when flex is off reset to mesh defaults
*/
simulated function UpdateMeshForFleXCollision()
{
local GameEngine Engine;
if ( bPlayedDeath || Physics == PHYS_RigidBody )
return;
Engine = GameEngine(Class'Engine'.static.GetEngine());
// force update kinematic bones for flex collision, but still disable RBChannel iff
// default.bUpdateKinematicBonesFromAnimation==FALSE so this pawn doesn't push around corpses, etc...
if ( Mesh.RBCollideWithChannels.FlexAsset && class'Engine'.static.GetPhysXLevel() >= 2 && Engine.GetSystemSettingBool("FlexRigidBodiesCollisionAtHighLevel") )
{
// @note: also requires that scene query flag is set (see InstancePhysXGeom)
Mesh.bUpdateKinematicBonesFromAnimation = true;
Mesh.MinDistFactorForKinematicUpdate = 0.0;
}
}
/** Called when flex collision should be toggled regardless of PhysXLevel */
simulated function SetEnableFleXCollision(bool bEnabled)
{
//deprecated
return;
}
/** Called from SkeletalMeshComponent::PlayParticleEffect() */
simulated function OnAnimNotifyParticleSystemSpawned( const AnimNotify_PlayParticleEffect AnimNotifyData, ParticleSystemComponent PSC )
{
if( IsDoingSpecialMove() )
{
SpecialMoves[SpecialMove].OnAnimNotifyParticleSystemSpawned( AnimNotifyData, PSC );
}
}
/** Set the visual and hit zone scale of */
simulated native function SetHeadScale(float Scale, float OldScale);
/*********************************************************************************************
* @name Audio
********************************************************************************************* */
/** returns the rotation to use when playing AKEvent sounds that
* require a rotation
*/
native simulated function rotator GetAKRotation();
/** Same as PlaySound, but without playing it locally */
native noexport simulated function ReplicateSound(AkBaseSoundObject InSoundCue, optional bool bNotReplicated, optional bool bNoRepToOwner, optional bool bStopWhenOwnerDestroyed, optional vector SoundLocation, optional bool bNoRepToRelevant, optional rotator SoundRotation);
event Landed(vector HitNormal, actor FloorActor)
{
Super.Landed(HitNormal, FloorActor);
if ( Velocity.Z < -200 )
{
OldZ = Location.Z;
bJustLanded = bUpdateEyeHeight && (Controller != None) && Controller.LandingShake();
}
if (Velocity.Z < -MaxFallSpeed)
{
SoundGroupArch.PlayFallingDamageLandSound(self);
}
else if( Velocity.Z < MaxFallSpeed * -0.35 )
{
SoundGroupArch.PlayLandSound(self);
}
SetBaseEyeheight();
}
/** Triggered by AnimNotify_Footstep to select and play a footstep sound */
simulated event PlayFootStepSound(int FootDown)
{
local EMaterialTypes MaterialType;
local AkBaseSoundObject Sound;
local vector FootSoundLoc;
if ( WorldInfo.NetMode == NM_DedicatedServer
/* Validate footsetps */
|| Physics != PHYS_Walking || Base == None
/* Skip if we're encountering low frame rates */
|| WorldInfo.bDropDetail
/* Check if foostep sounds are enabled. Always allow footstep sounds from local player */
|| (!bAllowFootstepSounds && Controller != GetALocalPlayerController())
/* Is within audible range */
|| !ActorEffectIsRelevant(self, false, SoundGroupArch.MaxFootstepSoundRanges.X, SoundGroupArch.MaxFootstepSoundRanges.Y) )
{
return;
}
// Play the foot sound locational to where the foot actually is
switch( FootDown )
{
case 0:
FootSoundLoc = Mesh.GetBoneLocation(LeftFootBoneName, 0);
break;
case 1:
FootSoundLoc = Mesh.GetBoneLocation(RightFootBoneName, 0);
break;
case 2:
FootSoundLoc = Mesh.GetBoneLocation(LeftHandBoneName, 0);
break;
case 3:
FootSoundLoc = Mesh.GetBoneLocation(RightHandBoneName, 0);
break;
};
MaterialType = GetMaterialBelowFeet( FootSoundLoc );
if( bIsSprinting )
{
Sound = SoundGroupArch.GetSprintingFootstepSound(FootDown, MaterialType);
}
else
{
Sound = SoundGroupArch.GetFootstepSound(FootDown, MaterialType);
}
if ( AkEvent(Sound) != none )
{
FootstepAkComponent.PlayEvent( AkEvent(Sound), true );
}
}
/**
* Trace down and find the material type we are walking on
*/
simulated function EMaterialTypes GetMaterialBelowFeet( optional const vector FootSoundLoc )
{
local vector HitLocation, HitNormal;
local TraceHitInfo HitInfo;
local KFPhysicalMaterialProperty PhysicalProperty;
local float TraceDist;
local vector TraceLoc;
TraceDist = 1.5 * GetCollisionHeight();
TraceLoc = IsZero( FootSoundLoc ) ? Location : FootSoundLoc;
Trace(HitLocation, HitNormal, TraceLoc - TraceDist*vect(0,0,1), TraceLoc, false,, HitInfo);
if ( HitInfo.PhysMaterial != None )
{
PhysicalProperty = KFPhysicalMaterialProperty(HitInfo.PhysMaterial.GetPhysicalMaterialProperty(class'KFPhysicalMaterialProperty'));
if (PhysicalProperty != None)
{
return PhysicalProperty.MaterialType;
}
}
return EMT_None;
}
/** starts playing the given sound via the AmbientAkComponent and sets AmbientSound for replicating to clients
* @param NewAmbientSound the new sound to play, or None to stop any ambient that was playing
*/
simulated function SetPawnAmbientSound(AkEvent NewAmbientSound)
{
if ( NewAmbientSound == None )
{
AmbientAkComponent.StopEvents();
AmbientSound = None;
}
else
{
AmbientSound = NewAmbientSound;
AmbientAkComponent.StopEvents();
if (NewAmbientSound != None)
{
// play sound spatialized if this is not a player or this player is not locally controlled
AmbientAkComponent.PlayEvent( AmbientSound, !IsPlayerPawn() || !IsLocallyControlled() );
}
}
}
simulated function bool IsWeaponAmbientSoundPlaying(AkEvent AmbientSoundToCheck)
{
return WeaponAkComponent.IsPlaying(AmbientSoundToCheck);
}
/** starts playing the given sound via the WeaponAmbientSound AudioComponent and sets WeaponAmbientSoundCue for replicating to clients
* @param NewAmbientSound the new sound to play, or None to stop any ambient that was playing
*/
simulated function SetWeaponAmbientSound(AkEvent NewAmbientSound, optional AkEvent FirstPersonAmbientSound)
{
if ( NewAmbientSound == None )
{
WeaponAkComponent.StopEvents();
WeaponAmbientSound = None;
WeaponAmbientEchoHandler.HandleEchoes(none);
}
else if( !bPlayedDeath && !bPendingDelete && !bDeleteMe )
{
WeaponAmbientSound = NewAmbientSound;
WeaponAkComponent.StopEvents();
// Replicate 3rd person, but play 1st person
if ( FirstPersonAmbientSound != None && IsFirstPerson() )
{
// don't check occlusion for first person sound
WeaponAkComponent.OcclusionUpdateInterval = 0.f;
WeaponAkComponent.PlayEvent( FirstPersonAmbientSound );
if( FirstPersonAmbientSound.bUseAdvancedSoundFunctionality )
{
WeaponAmbientEchoHandler.HandleEchoes( FirstPersonAmbientSound );
}
}
else if (NewAmbientSound != None)
{
// check occlusion for third person sound
WeaponAkComponent.OcclusionUpdateInterval = 0.001f;
SetTimer( 0.2f, false, nameof(RestoreOcclusionUpdate) );
WeaponAkComponent.PlayEvent( NewAmbientSound, false );
if( NewAmbientSound.bUseAdvancedSoundFunctionality )
{
WeaponAmbientEchoHandler.HandleEchoes( NewAmbientSound );
}
}
}
}
simulated function RestoreOcclusionUpdate()
{
WeaponAkComponent.OcclusionUpdateInterval = 0.2f;
}
/** starts playing the given sound via the SecondaryWeaponAmbientSound AudioComponent and sets SecondaryWeaponAmbientSoundCue for replicating to clients
* @param NewAmbientSound the new sound to play, or None to stop any ambient that was playing
*/
simulated function SetSecondaryWeaponAmbientSound(AkEvent NewAmbientSound, optional AkEvent FirstPersonAmbientSound)
{
if ( NewAmbientSound == None )
{
SecondaryWeaponAkComponent.StopEvents();
SecondaryWeaponAmbientSound = None;
}
else if( !bPlayedDeath && !bPendingDelete && !bDeleteMe )
{
SecondaryWeaponAmbientSound = NewAmbientSound;
SecondaryWeaponAkComponent.StopEvents();
// Replicate 3rd person, but play 1st person
if ( FirstPersonAmbientSound != None && IsFirstPerson() )
{
// don't check occlusion for first person sound
SecondaryWeaponAkComponent.OcclusionUpdateInterval = 0.f;
SecondaryWeaponAkComponent.PlayEvent( FirstPersonAmbientSound );
}
else if (NewAmbientSound != None)
{
// check occlusion for third person sound
SecondaryWeaponAkComponent.OcclusionUpdateInterval = 0.2f;
SecondaryWeaponAkComponent.PlayEvent( NewAmbientSound, false );
}
}
}
/** starts playing the given sound via the PowerUpAmbientSound AudioComponent and sets PowerUpAmbientSoundCue for replicating to clients
* @param NewAmbientSound the new sound to play, or None to stop any ambient that was playing
*/
simulated function SetPowerUpAmbientSound(AkEvent NewAmbientSound,
optional AkEvent FirstPersonAmbientSound,
optional AkEvent StopAmbientSound,
optional AkEvent FirstPersonStopAmbientSound)
{
if ( NewAmbientSound == None && FirstPersonAmbientSound == None &&
StopAmbientSound == None && FirstPersonStopAmbientSound == None)
{
PowerUpAmbientSound.FirstPersonPowerUpAmbientSound = None;
PowerUpAmbientSound.ThirdPersonPowerUpAmbientSound = None;
PowerUpAmbientSound.FirstPersonStopPowerUpAmbientSound = None;
PowerUpAmbientSound.ThirdPersonStopPowerUpAmbientSound = None;
PowerUpAmbientSound.Count++;
PowerUpAkComponent.StopEvents();
}
else if( !bPlayedDeath && !bPendingDelete && !bDeleteMe )
{
PowerUpAmbientSound.FirstPersonPowerUpAmbientSound = FirstPersonAmbientSound;
PowerUpAmbientSound.ThirdPersonPowerUpAmbientSound = NewAmbientSound;
PowerUpAmbientSound.FirstPersonStopPowerUpAmbientSound = StopAmbientSound;
PowerUpAmbientSound.ThirdPersonStopPowerUpAmbientSound = FirstPersonStopAmbientSound;
PowerUpAmbientSound.Count++;
// Replicate 3rd person, but play 1st person
if ( FirstPersonAmbientSound != None && IsFirstPerson() )
{
PowerUpAkComponent.StopEvents();
// don't check occlusion for first person sound
PowerUpAkComponent.OcclusionUpdateInterval = 0.f;
PowerUpAkComponent.PlayEvent( FirstPersonAmbientSound );
}
else if (NewAmbientSound != None)
{
PowerUpAkComponent.StopEvents();
// check occlusion for third person sound
PowerUpAkComponent.OcclusionUpdateInterval = 0.2f;
PowerUpAkComponent.PlayEvent( NewAmbientSound, false );
}
else
{
if ( FirstPersonStopAmbientSound != None )
{
PowerUpAkComponent.PlayEvent( FirstPersonStopAmbientSound, IsFirstPerson() );
}
if( StopAmbientSound != None )
{
PowerUpAkComponent.PlayEvent( StopAmbientSound, IsFirstPerson() );
}
}
}
}
simulated function SetReplicatedPowerUpAmbientSound(PowerUpAmbientSoundInfo PowerUpReplicatedAmbientSound)
{
if ( PowerUpReplicatedAmbientSound.FirstPersonPowerUpAmbientSound == None &&
PowerUpReplicatedAmbientSound.ThirdPersonPowerUpAmbientSound == None &&
PowerUpReplicatedAmbientSound.FirstPersonStopPowerUpAmbientSound == None &&
PowerUpReplicatedAmbientSound.ThirdPersonStopPowerUpAmbientSound == None)
{
PowerUpAkComponent.StopEvents();
}
else if( !bPlayedDeath && !bPendingDelete && !bDeleteMe )
{
// Replicate 3rd person, but play 1st person
if ( PowerUpReplicatedAmbientSound.FirstPersonPowerUpAmbientSound != None && IsFirstPerson() )
{
PowerUpAkComponent.StopEvents();
// don't check occlusion for first person sound
PowerUpAkComponent.OcclusionUpdateInterval = 0.f;
PowerUpAkComponent.PlayEvent( PowerUpReplicatedAmbientSound.FirstPersonPowerUpAmbientSound );
}
else if ( PowerUpReplicatedAmbientSound.ThirdPersonPowerUpAmbientSound != None )
{
PowerUpAkComponent.StopEvents();
// check occlusion for third person sound
PowerUpAkComponent.OcclusionUpdateInterval = 0.2f;
PowerUpAkComponent.PlayEvent( PowerUpReplicatedAmbientSound.ThirdPersonPowerUpAmbientSound, false );
}
else
{
if ( PowerUpReplicatedAmbientSound.FirstPersonStopPowerUpAmbientSound != None )
{
PowerUpAkComponent.PlayEvent( PowerUpReplicatedAmbientSound.FirstPersonStopPowerUpAmbientSound, IsFirstPerson() );
}
if ( PowerUpReplicatedAmbientSound.ThirdPersonStopPowerUpAmbientSound != None )
{
PowerUpAkComponent.PlayEvent( PowerUpReplicatedAmbientSound.ThirdPersonStopPowerUpAmbientSound, IsFirstPerson() );
}
}
}
}
/**
* Set an RTPC value on the WeaponAKComponent
*/
function SetWeaponComponentRTPCValue( String InRTPC, float targetvalue )
{
WeaponAkComponent.SetRTPCValue( InRTPC, targetvalue);
}
/** starts playing the given sound event on the weapon audio component
*/
simulated function PlayWeaponSoundEvent(AkEvent NewSoundEvent)
{
WeaponAkComponent.PlayEvent(NewSoundEvent, true, true);
}
/**
* Set an RTPC value on the SecondaryWeaponAKComponent
*/
simulated function SetSecondaryWeaponComponentRTPCValue( String InRTPC, float targetvalue )
{
SecondaryWeaponAkComponent.SetRTPCValue( InRTPC, targetvalue);
}
/** starts playing the given sound event on the secondary weapon audio component
*/
simulated function PlaySecondaryWeaponSoundEvent(AkEvent NewSoundEvent)
{
SecondaryWeaponAkComponent.PlayEvent(NewSoundEvent, true, true);
}
/** starts playing the given sound event on the powerup audio component
*/
simulated function PlayPowerUpSoundEvent(AkEvent NewSoundEvent)
{
PowerUpAkComponent.PlayEvent(NewSoundEvent, true, true);
}
simulated event Tick( float DeltaTime )
{
if( Role == ROLE_Authority )
{
if( DamageOverTimeArray.Length > 0 )
{
TickDamageOverTime(DeltaTime);
}
}
if( WorldInfo.NetMode != NM_DedicatedServer )
{
if( bNeedsProcessHitFx )
{
ProcessHitFx();
bNeedsProcessHitFx = false;
}
if( WeaponAmbientEchoHandler.EchoSets.Length > 0 )
{
WeaponAmbientEchoHandler.TickEchoes();
}
}
// Tick special moves
if( SpecialMove != SM_None && SpecialMoves[SpecialMove] != none )
{
SpecialMoves[SpecialMove].Tick( DeltaTime );
}
// always clear for server (client already clears in ProcessHitFx)
bNeedsProcessHitFx = false;
}
/** Process all hit effects that have occured this frame */
simulated function ProcessHitFx()
{
local vector HitDirection;
// Skip FX if HideMeshOnDeath was called
if ( bPlayedDeath && Mesh.HiddenGame )
{
bNeedsProcessHitFx = false;
return;
}
if( HitFxInfo.DamageType != none && HitFxInfo.DamageType.default.bNoPain )
{
PlayHealEffects(HitFxInfo.DamageType);
}
else
{
HitDirection = DecodeUnitVector( HitFxInfo.EncodedHitDirection );
PlayTakeHitEffects( HitDirection, HitFxInfo.HitLocation );
// If the pawns taken multiple hits from the same DamageType, process additional hit effects
if( HitFxAddedHitCount > 0 )
{
ProcessAdditionalHitFx( HitDirection );
}
}
bNeedsProcessHitFx = false;
}
// If our weapon supports multiple hits, process the additional hit effects
simulated function ProcessAdditionalHitFx( vector HitDirection )
{
local byte i, MaxAddedHits;
local int InjuredHitZone;
local vector HitStartLoc, NewHitLocation, NewHitDir;
MaxAddedHits = min( HitFxAddedHitCount, MAX_ADDED_HITFX );
for( i = 0; i < MaxAddedHits; i++ )
{
// Put the relative hit locations in world space again
NewHitLocation = HitFxAddedRelativeLocs[i] + HitFxInfo.HitLocation;
// Calculate the direction of additional hits relative to the HitFxInstigator
if( HitFxInstigator != none )
{
HitStartLoc = HitFxInfo.HitLocation - (HitDirection * VSize(HitFxInfo.HitLocation - HitFxInstigator.Location));
NewHitDir = Normal(NewHitLocation - HitStartLoc);
}
else
{
NewHitDir = HitDirection;
}
PlayTakeHitEffects( NewHitDir, NewHitLocation, false );
}
if( bPlayedDeath )
{
foreach DamagedHitZones( InjuredHitZone )
{
if( !HitZones[InjuredHitZone].bPlayedInjury )
{
HitFxInfo.HitBoneIndex = InjuredHitZone;
PlayTakeHitEffects( HitDirection, HitFxInfo.HitLocation, false );
}
}
}
}
/** SERVER ONLY - Update any AI or animation behaviors based on state */
function OnStackingAfflictionChanged(byte Id);
/************************************************************************************
* @name Dialog
***********************************************************************************/
/**
* param DialogEvent: event to be played; if none, will stop dialog
*/
native function PlayDialog( optional AkEvent DialogEvent, optional byte bCanBeMinimized );
function StopDialog()
{
PlayDialog();
}
function bool IsSpeaking()
{
return CurrDialogEventID >= 0;
}
function bool IsPlayingDialogEvent( int EventID )
{
return IsSpeaking() && CurrDialogEventID == EventID;
}
simulated function PlayDialogEvent( AkEvent DialogEvent )
{
if( VoiceGroupArch == none )
{
return;
}
DialogAkComponent.StopEvents();
if( DialogEvent != none )
{
DialogAkComponent.PlayEvent( DialogEvent, Controller != GetALocalPlayerController() || !PlayerController(Controller).UsingFirstPersonCamera() );
}
}
function HandleDialogResponse();
function bool HasValidVoiceEventData()
{
return VoiceGroupArch != none && VoiceGroupArch.default.EventDataClass != none;
}
function class< KFPawnVoiceGroupEventData > GetVoiceGroupEventDataClass()
{
if( HasValidVoiceEventData() )
{
return VoiceGroupArch.default.EventDataClass;
}
return none;
}
/** Used as SetTimer callback. Set by KFDialogManager::PlayDialogEvent. */
function EndOfDialogTimer()
{
HandleDialogResponse();
CurrDialogEventID = -1;
}
/************************************************************************************
* @name Special Moves
***********************************************************************************/
/* Is this KFPawn doing a special move? */
simulated final native function bool IsDoingSpecialMove( optional ESpecialMove AMove ) const;
/**
* Start a special move.
* @Note this doesn't handle replication to owning client if called from server.
* See ServerDoSpecialMove() and LocalDoSpecialMove() for alternatives.
* @network: local player and server
*/
simulated event DoSpecialMove(ESpecialMove NewMove, optional bool bForceMove, optional Pawn InInteractionPawn, optional INT InSpecialMoveFlags, optional bool bSkipReplication)
{
// Debug warning since we now control initiation only on the server
if ( !bForceMove && Role < ROLE_Authority )
{
`Warn(Self @ GetFuncName() @ "initiated from client!" @ NewMove);
}
if( NewMove == SM_Emote && MyKFWeapon != None && MyKFWeapon.IsInState('WeaponFiring') )
{
return;
}
if ( SpecialMoveHandler != None )
{
SpecialMoveHandler.DoSpecialMove(NewMove, bForceMove, InInteractionPawn, InSpecialMoveFlags, bSkipReplication);
}
}
/**
* Replicate a client initiated special move
* @note: move entry validation happens independently and could become out of sync
*/
reliable server final function ServerDoSpecialMove(ESpecialMove NewMove, optional bool bForceMove, optional Pawn InInteractionPawn, optional byte InSpecialMoveFlags, optional bool bSkipReplication)
{
DoSpecialMove(NewMove, bForceMove, InInteractionPawn, InSpecialMoveFlags, bSkipReplication);
}
/**
* Request to abort/stop current SpecialMove
*/
simulated final event EndSpecialMove(optional ESpecialMove SpecialMoveToEnd, optional bool bForceNetSync)
{
if ( SpecialMoveHandler != None )
{
SpecialMoveHandler.EndSpecialMove(SpecialMoveToEnd, bForceNetSync);
}
}
simulated event bool CanDoSpecialMove(ESpecialMove AMove, optional bool bForceCheck)
{
return SpecialMoveHandler.CanDoSpecialMove( AMove, bForceCheck );
}
/** Called from KFSpecialMove::SpecialMoveEnded */
simulated function NotifySpecialMoveEnded( KFSpecialMove FinishedMove, ESpecialMove SMHandle );
simulated event bool IsMovementDisabledDuringSpecialMove()
{
if( IsDoingSpecialMove() )
{
return SpecialMoves[SpecialMove].bDisableMovement;
}
return false;
}
/** Can this pawn be grabbed by Zed performing grab special move (clots & Hans's energy drain) */
function bool CanBeGrabbed(KFPawn GrabbingPawn, optional bool bIgnoreFalling, optional bool bAllowSameTeamGrab)
{
if( Health <= 0 || (Physics == PHYS_Falling && !bIgnoreFalling) || (!bAllowSameTeamGrab && IsSameTeam(GrabbingPawn)) || IsDoingSpecialMove(SM_GrappleVictim) )
{
return false;
}
// Don't allow weak AI zed grabs if we're waiting for a cooldown
if( GrabbingPawn.MyKFAIC != None && GrabbingPawn.bWeakZedGrab && WeakZedGrabCooldown > 0
&& `TimeSince(WeakZedGrabCooldown) < 0 )
{
//`log("Can't be grabbed because cooldown "$`TimeSince(WeakZedGrabCooldown));
return false;
}
return true;
}
/** Set the cooldown time for weak zed grab */
function SetWeakGrabCoolDown(float CooldownTime)
{
WeakZedGrabCooldown = fMax(WorldInfo.TimeSeconds + CooldownTime, WeakZedGrabCooldown);
}
/** Puts a pawn into the panic wander state */
function CausePanicWander();
/** Used to detect if we should currently be doing a wander special move */
simulated function bool ShouldBeWandering();
/*********************************************************************************************
* @name AI
********************************************************************************************* */
/** Return true if AI can target this pawn */
function bool CanAITargetThisPawn(Controller TargetingController)
{
if( bAIZedsIgnoreMe )
{
return false;
}
if( ExclusiveTargetingController != none && ExclusiveTargetingController != TargetingController )
{
//`log(TargetingController$" FALSE trying to target "$self$" ExclusiveTargetingController = "$ExclusiveTargetingController);
return false;
}
if( AIIgnoreEndTime > 0 && `TimeSince(AIIgnoreEndTime) < 0 )
{
//`log(TargetingController$" FALSE trying to target "$self$" AIIgnoreEndTime = "$AIIgnoreEndTime$" TimeSeconds: "$WorldInfo.TimeSeconds);
return false;
}
//`log(TargetingController$" trying to target "$self$" ExclusiveTargetingController = "$ExclusiveTargetingController$" AIIgnoreEndTime = "$AIIgnoreEndTime$" TimeSeconds: "$WorldInfo.TimeSeconds);
return true;
}
/*********************************************************************************************
* @name Debug
********************************************************************************************* */
/** Just like Pawn.MessagePlayer, but this version lets you override the message type, message
* lifetime, and checks !ShippingPC instead of Final_Release*/
final event KFMessagePlayer( coerce String Msg, optional name Type, optional float MsgLifeTime )
{
`if(`notdefined(ShippingPC))
local PlayerController PC;
foreach LocalPlayerControllers(class'PlayerController', PC)
{
PC.ClientMessage( Msg, Type, MsgLifeTime );
}
`endif
}
/**
* list important Pawn variables on canvas. HUD will call DisplayDebug() on the current ViewTarget when
* the ShowDebug exec is used
*
* @param HUD - HUD with canvas to draw on
* @input out_YL - Height of the current font
* @input out_YPos - Y position on Canvas. out_YPos += out_YL, gives position to draw text for next debug line.
*/
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
local Canvas Canvas;
//local int i;
//local KFPerk CurrentPerk;
Super.DisplayDebug(HUD, out_YL, out_YPos);
Canvas = HUD.Canvas;
if (HUD.ShouldDisplayDebug('camera'))
{
HUD.DrawDebugSphere( Instigator.GetPawnViewLocation(), 10, 10, 0, 255, 0 );
}
if (HUD.ShouldDisplayDebug('movement'))
{
Canvas.SetDrawColor(0,255,255);
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("---------- KFPawn: movement ----------");
out_YPos += out_YL;
Canvas.DrawText("Velocity:" @ Velocity @ "Accel:" @ Acceleration, FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Physics:" @ Physics, FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Walking: "$bIsWalking$" Sprinting: "$bIsSprinting$" Crouched: "$bIsCrouched);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("GroundSpeed:" @ GroundSpeed, FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Speed:" @ VSize(Velocity) @ "Speed2D:" @ VSize2D(Velocity), FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Pawn state:" @ GetStateName(), FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Controller state:" @ Controller.GetStateName(), FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
if( HUD.ShouldDisplayDebug('physics') )
{
Canvas.SetDrawColor(0,255,255);
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Velocity:" @ VSize(Velocity)/100 @ "Meters Per Second");
out_YPos += out_YL;
}
if (HUD.ShouldDisplayDebug('rendering'))
{
Canvas.SetDrawColor(0,255,0);
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("---------- KFPawn: rendering ----------");
out_YPos += out_YL;
Canvas.DrawText("Lighting Channels:");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("-----------------------------");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Mesh - " $ mesh.LightingChannels.Indoor ?
(mesh.LightingChannels.Outdoor ? "Both Indoor and Outdoor" : "Indoor") : "Outdoor");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("ThirdPersonHeadMeshComponent - " $ ThirdPersonHeadMeshComponent.LightingChannels.Indoor ?
(ThirdPersonHeadMeshComponent.LightingChannels.Outdoor ? "Both Indoor and Outdoor" : "Indoor") : "Outdoor");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("ArmsMesh0 - " $ ArmsMesh.LightingChannels.Indoor ?
(ArmsMesh.LightingChannels.Outdoor ? "Both Indoor and Outdoor" : "Indoor") : "Outdoor");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
if( WeaponAttachment != none )
{
Canvas.DrawText("WeaponAttachment - " $ WeaponAttachment.HasIndoorLighting() ?
(WeaponAttachment.HasOutdoorLighting() ? "Both Indoor and Outdoor" : "Indoor") : "Outdoor");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
if( Weapon != none )
{
Canvas.DrawText("Weapon - " $ Weapon.Mesh.LightingChannels.Indoor ?
(Weapon.Mesh.LightingChannels.Outdoor ? "Both Indoor and Outdoor" : "Indoor") : "Outdoor");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
}
if( HUD.ShouldDisplayDebug('animation') )
{
if( Mesh != None && Mesh.Animations != None )
{
Canvas.DrawText("Left Hand IK:"@Mesh.FindSkelControl('HandIK_L').GetControlMetadataWeight());
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
}
if (HUD.ShouldDisplayDebug('perk'))
{
// CurrentPerk = GetPerk();
//
// if ( CurrentPerk != none )
// {
// Canvas.SetDrawColor(0,255,127);
// out_YPos += 30;
// Canvas.SetPos(4,out_YPos);
// Canvas.DrawText("Active perk:" @ CurrentPerk);
// out_YPos += out_YL;
// Canvas.SetPos(4,out_YPos);
// Canvas.DrawText("Current skilltree:");
// out_YPos += out_YL;
// Canvas.SetPos(4,out_YPos);
// Canvas.DrawText("-----------------------------");
// out_YPos += out_YL;
// Canvas.SetPos(4,out_YPos);
// Canvas.DrawText("PASSIVE NAME");
// Canvas.SetPos(154,out_YPos);
// Canvas.DrawText("PASSIVE POINTS (0-10)");
// Canvas.SetPos(304,out_YPos);
// Canvas.DrawText("PASSIVE INCREMENT(0-100%)");
// Canvas.SetPos(474,out_YPos);
// Canvas.DrawText("PASSIVE TOTAL");
// out_YPos += out_YL;
// for ( i = 0; i < 4; i++ )
// {
// Canvas.SetPos(4,out_YPos);
// Canvas.DrawText(CurrentPerk.SkillNames[i]);
// Canvas.SetPos(154,out_YPos);
// Canvas.DrawText(CurrentPerk.Skilltree[i]);
// Canvas.SetPos(304,out_YPos);
// Canvas.DrawText(CurrentPerk.SkillIncrement[i] * 100);
// Canvas.SetPos(474,out_YPos);
// Canvas.DrawText(CurrentPerk.Skilltree[i] * CurrentPerk.SkillIncrement[i] * 100 + 100);
// out_YPos += out_YL;
// }
// }
}
}
/*********************************************************************************************
* @name States
********************************************************************************************* */
/** System settings */
static native function bool ShouldCorpseCollideWithDead();
static native function bool ShouldCorpseCollideWithLiving();
static native function bool ShouldCorpseCollideWithDeadAfterSleep();
static native function bool ShouldCorpseCollideWithLivingAfterSleep();
State Dying
{
ignores Bump, HitWall, HeadVolumeChange, PhysicsVolumeChange, Falling, BreathTimer, FellOutOfWorld;
event Timer()
{
// destroy/lifespan is handled by the GoreEffectManager
}
/** RigidBody went to sleep after being awake - only valid if bCallRigidBodyWakeEvents==TRUE */
event OnSleepRBPhysics()
{
`log(self@"took"@WorldInfo.TimeSeconds - Max(LastPainTime,TimeOfDeath)@"for RB sleep. WarningLevel="$RagdolLWarningLevel, bLogPhysicsBodyImpact);
// Optimize out 'static' dead bodies. Could also reduce the cost by using SetTickIsDisabled
// or overriding the native Tick() function, but this solution should be good enough.
Mesh.bNoSkeletonUpdate=true;
if ( !ShouldCorpseCollideWithLivingAfterSleep() )
{
// Turn off collision with the living, unless their DeadPawn==TRUE
Mesh.SetRBCollidesWithChannel(RBCC_Pawn, FALSE);
}
}
/** RigidBody woke up after being stationary - only valid if bCallRigidBodyWakeEvents==TRUE */
event OnWakeRBPhysics()
{
`log(self@"wake RB physics", bLogPhysicsBodyImpact);
SetRagdollWarningLevel(0);
Mesh.bNoSkeletonUpdate=false;
// Wait until after the ragdoll is re-awakened and then turn off collision with dead
if ( TimeOfDeath < WorldInfo.TimeSeconds && SpecialMove != SM_DeathAnim && !ShouldCorpseCollideWithDeadAfterSleep() )
{
Mesh.SetRBCollidesWithChannel(RBCC_DeadPawn, FALSE);
}
}
event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
if ( damagetype == None )
{
// `warn("No damagetype for damage by "$instigatedby.pawn$" with weapon "$InstigatedBy.Pawn.Weapon);
DamageType = class'DamageType';
}
Health -= Damage;
Momentum = Momentum/Mass;
// Play clientside hit effects (only plays on LocalPlayer & ListenServer because bTearOff=true)
// Make sure any additional hits dealt during the time of the killing blow are also replicated to clients
if (WorldInfo.NetMode != NM_DedicatedServer || TimeOfDeath == WorldInfo.TimeSeconds)
{
SetRagdollWarningLevel(0); // restart the ragdoll warning/failsafe
PlayHit(Damage, EventInstigator, HitLocation, DamageType, Momentum, HitInfo);
}
// Notify the DeathAnim move that we were hit, since NotifyTakeHit is not called after death
if( SpecialMove == SM_DeathAnim )
{
SpecialMoves[SpecialMove].NotifyOwnerTakeHit(class<KFDamageType>(damageType), HitLocation, Normal(TearOffMomentum), EventInstigator);
}
}
/** simulated for clients (super will not be called). This is necessary because in Pawn.PlayDying GotoState is called before TearOff */
simulated event BeginState(Name PreviousStateName)
{
Super.BeginState(PreviousStateName);
// Add ragdolled corpse to the gore pool
if ( WorldInfo.NetMode != NM_DedicatedServer && Physics == PHYS_RigidBody )
{
ClearTimer();
bCallRigidBodyWakeEvents = true;
if ( !WorldInfo.bDropDetail && WorldInfo.GetDetailMode() != DM_LOW )
{
// Enable rigid body collision events (impact sounds, etc...)
Mesh.SetNotifyRigidBodyCollision(true);
}
if( WorldInfo.MyGoreEffectManager != none )
{
KFGoreManager(WorldInfo.MyGoreEffectManager).AddCorpse(self);
}
}
// otherwise, hide and destroy
else
{
HideMeshOnDeath();
}
}
}
/*********************************************************************************************
* @name UI / Localization
********************************************************************************************* */
/**Looks up and returns localized name */
static function string GetLocalizedName()
{
return "";
}
/*********************************************************************************************
* @name Particle systems
********************************************************************************************* */
/** Shuts down provided emitter */
simulated function DetachEmitter( out ParticleSystemComponent Emitter )
{
if( Emitter != none )
{
Emitter.DeactivateSystem();
DetachComponent(Emitter);
WorldInfo.MyEmitterPool.OnParticleSystemFinished(Emitter);
Emitter = None;
}
}
/*********************************************************************************************
* @name ExtraVFX
********************************************************************************************* */
simulated function ParticleSystemComponent SpawnExtraVFX(ExtraVFXInfo info)
{
local name SFXBoneName;
local ParticleSystemComponent VFXComponent;
if (info.SocketName == `NAME_NONE)
{
if (info.VFX != none)
{
VFXComponent = WorldInfo.MyEmitterPool.SpawnEmitter(info.VFX, Location, Rotation, self);
}
if (info.SFXStartEvent != none)
{
PostAkEvent(info.SFXStartEvent, false, true, false);
}
}
else
{
if (info.VFX != none)
{
VFXComponent = WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(info.VFX, Mesh, info.SocketName, true);
}
if (info.SFXStartEvent != none)
{
SFXBoneName = Mesh.GetSocketBoneName(info.SocketName);
if (SFXBoneName != `NAME_NONE)
{
PostAkEventOnBone(info.SFXStartEvent, SFXBoneName, false, true);
}
else
{
PostAkEvent(info.SFXStartEvent, false, true, false);
}
}
}
return VFXComponent;
}
// Plays extra VFX associated with FXLabel, or restarts deactivated effects if already attached
simulated function PlayExtraVFX(Name FXLabel)
{
local int i;
local ExtraVFXAttachmentInfo VFXAttachment;
local bool bActivatedExistingSystem;
if (WorldInfo.NetMode == NM_DedicatedServer || FXLabel == `NAME_NONE)
{
return;
}
// re-play an existing effect
for (i = 0; i < ExtraVFXAttachments.Length; ++i)
{
if (ExtraVFXAttachments[i].Info.Label == FXLabel)
{
if (ExtraVFXAttachments[i].VFXComponent != none)
{
ExtraVFXAttachments[i].VFXComponent.SetActive(false);
}
ExtraVFXAttachments[i].VFXComponent = SpawnExtraVFX(ExtraVFXAttachments[i].Info);
bActivatedExistingSystem = true;
}
}
if (bActivatedExistingSystem)
{
return;
}
// play a new effect
for (i = 0; i < CharacterArch.ExtraVFX.Length; ++i)
{
if (CharacterArch.ExtraVFX[i].Label == FXLabel)
{
VFXAttachment.VFXComponent = SpawnExtraVFX(CharacterArch.ExtraVFX[i]);
VFXAttachment.Info = CharacterArch.ExtraVFX[i];
ExtraVFXAttachments.AddItem(VFXAttachment);
}
}
}
// Stops all extra VFX associated with the FXLabel, or all extra VFX is FXLabel is NAME_NONE
simulated function StopExtraVFX(Name FXLabel)
{
local int i;
local name SFXBoneName;
if (WorldInfo.NetMode == NM_DedicatedServer)
{
return;
}
for (i = 0; i < ExtraVFXAttachments.Length; ++i)
{
if (FXLabel == `NAME_NONE || ExtraVFXAttachments[i].Info.Label == FXLabel)
{
if (ExtraVFXAttachments[i].VFXComponent != none)
{
ExtraVFXAttachments[i].VFXComponent.SetActive(false);
}
if (ExtraVFXAttachments[i].Info.SFXStopEvent != none)
{
SFXBoneName = Mesh.GetSocketBoneName(ExtraVFXAttachments[i].Info.SocketName);
if (SFXBoneName != `NAME_NONE)
{
PostAkEventOnBone(ExtraVFXAttachments[i].Info.SFXStopEvent, SFXBoneName, false, true);
}
else
{
PostAkEvent(ExtraVFXAttachments[i].Info.SFXStopEvent, false, true, false);
}
}
}
}
}
simulated function SetTurretWeaponAttachment(class<KFWeapon> WeaponClass) {}
simulated function StartLocustVFX()
{
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
bEnableSwarmVFX=true;
bNetDirty = true;
return;
}
if (Toxic_HRG_Locust_LoopingParticleEffect != none)
{
if (Toxic_HRG_Locust_LoopingPSC == none)
{
Toxic_HRG_Locust_LoopingPSC = WorldInfo.MyEmitterPool.SpawnEmitter(Toxic_HRG_Locust_LoopingParticleEffect, Location, Rotation, self);
}
else
{
Toxic_HRG_Locust_LoopingPSC.SetStopSpawning(-1, false);
}
}
}
simulated function StopLocustVFX()
{
if ( WorldInfo.NetMode == NM_DedicatedServer )
{
bEnableSwarmVFX=false;
bForceNetUpdate = true;
return;
}
if (Toxic_HRG_Locust_LoopingPSC != none)
{
Toxic_HRG_Locust_LoopingPSC.SetStopSpawning(-1, true);
}
}
defaultproperties
{
InventoryManagerClass=class'KFInventoryManager'
// Third person body component
Begin Object Class=KFSkeletalMeshComponent Name=KFPawnSkeletalMeshComponent
TickGroup=TG_DuringAsyncWork
// Animation
AnimTreeTemplate=None
bIgnoreControllersWhenNotRendered=true
Translation=(Z=-86) // based on CollisionHeight
// Physics
CollideActors=true
BlockZeroExtent=true
BlockRigidBody=true
RBChannel=RBCC_Pawn
RBCollideWithChannels=(Default=TRUE,Pawn=TRUE,Vehicle=TRUE,BlockingVolume=TRUE)
bHasPhysicsAssetInstance=true
MinDistFactorForKinematicUpdate=0.2
bUpdateKinematicBonesFromAnimation=false // perf
bSkipAllUpdateWhenPhysicsAsleep=true
RBDominanceGroup=20
ScriptRigidBodyCollisionThreshold=200
// Rendering
bOwnerNoSee=true
bOverrideAttachmentOwnerVisibility=true
CastShadow=true
bUseOnePassLightingOnTranslucency=true
bPerBoneMotionBlur=true
bCastDynamicShadow=true
bAllowPerObjectShadows=true
PerObjectShadowCullDistance=2500 //25m
`if(`__TW_PER_OBJECT_SHADOW_BATCHING_)
bAllowPerObjectShadowBatching=true
`endif
bAcceptsDynamicDecals=true
bChartDistanceFactor=true
End Object
Mesh=KFPawnSkeletalMeshComponent
Components.Add(KFPawnSkeletalMeshComponent)
// Third person head component
Begin Object class=SkeletalMeshComponent name=ThirdPersonHead0
bAcceptsDynamicDecals=true
End Object
ThirdPersonHeadMeshComponent=ThirdPersonHead0
// First person arms component
Begin Object Class=KFSkeletalMeshComponent Name=FirstPersonArms
bIgnoreControllersWhenNotRendered=true
// Rendering
DepthPriorityGroup=SDPG_Foreground
bOnlyOwnerSee=true
bOverrideAttachmentOwnerVisibility=true
bAcceptsDynamicDecals=false
CastShadow=true
bCastDynamicShadow=true
bAllowPerObjectShadows=true
bAllowBooleanPreshadows=false
End Object
ArmsMesh=FirstPersonArms
Begin Object Class=AkComponent name=AmbientAkSoundComponent_0
BoneName=dummy
bStopWhenOwnerDestroyed=true
bForceOcclusionUpdateInterval=true
// OcclusionUpdateInterval is set in SetWeaponAmbientSound based on first/third person
End Object
WeaponAkComponent=AmbientAkSoundComponent_0
Components.Add(AmbientAkSoundComponent_0)
Begin Object Class=AkComponent name=AmbientAkSoundComponent_1
BoneName=dummy // need bone name so it doesn't interfere with default PlaySoundBase functionality
bStopWhenOwnerDestroyed=true
End Object
AmbientAkComponent=AmbientAkSoundComponent_1
Components.Add(AmbientAkSoundComponent_1)
Begin Object Class=KFWeaponAmbientEchoHandler name=WeaponAmbientEchoHandler_0
End Object
WeaponAmbientEchoHandler=WeaponAmbientEchoHandler_0
Begin Object Class=AkComponent name=FootstepAkSoundComponent
BoneName=dummy
bForceOcclusionUpdateInterval=true
OcclusionUpdateInterval=0.f // never update occlusion for footsteps
bStopWhenOwnerDestroyed=true
End Object
FootstepAkComponent=FootstepAkSoundComponent
Components.Add(FootstepAkSoundComponent)
Begin Object Class=AkComponent name=DialogAkSoundComponent
BoneName=dummy
//bForceOcclusionUpdateInterval=true
//OcclusionUpdateInterval=0.2f
bStopWhenOwnerDestroyed=true
End Object
DialogAkComponent=DialogAkSoundComponent
Components.Add(DialogAkSoundComponent)
CurrDialogEventID=-1
Begin Object Class=AkComponent name=PowerUpAkSoundComponent
BoneName=dummy
bStopWhenOwnerDestroyed=true
bForceOcclusionUpdateInterval=true
// OcclusionUpdateInterval is set in SetPowerUpAmbientSound based on first/third person
End Object
PowerUpAkComponent=PowerUpAkSoundComponent
Components.Add(PowerUpAkSoundComponent)
Begin Object Class=AkComponent name=SecondaryWeaponAkSoundComponent
BoneName=dummy
bStopWhenOwnerDestroyed=true
bForceOcclusionUpdateInterval=true
// OcclusionUpdateInterval is set in SetSecondaryWeaponAmbientSound based on first/third person
End Object
SecondaryWeaponAkComponent=SecondaryWeaponAkSoundComponent
Components.Add(SecondaryWeaponAkSoundComponent)
PawnAnimInfo=KFPawnAnimInfo'ZED_Clot_Anim.AlphaClot_AnimGroup'
SoundGroupArch=KFPawnSoundGroup'FX_Pawn_Sounds_ARCH.DefaultPawnSounds'
// ---------------------------------------------
// Special moves
Begin Object Class=KFSpecialMoveHandler Name=SpecialMoveHandler_0
End Object
SpecialMoveHandler=SpecialMoveHandler_0
// ---------------------------------------------
// Gore
PhysRagdollImpulseScale=1.f
PhysicsHitReactionImpulseScale=1.f
PhysicsImpactBlendOutTime=0.45f
bRespondToExplosions=true
// Blood splats
BloodSplatterDecalMaterials[0]=MaterialInstanceConstant'FX_Gore_MAT.FX_CH_BloodSplatter_01_Mic'
BloodSplatterDecalMaterials[1]=MaterialInstanceConstant'FX_Gore_MAT.FX_CH_BloodSplatter_05_Mic'
// Blood pool
BloodPoolDecalMaterials[0]=MaterialInstanceTimeVarying'FX_Mat_Lib.FX_CH_Bloodpool_DM_TINST'
// Bone names
LeftFootBoneName=LeftFoot
RightFootBoneName=RightFoot
LeftHandBoneName=LeftHand
RightHandBoneName=RightHand
HeadBoneName=head
TorsoBoneName=Spine2
PelvisBoneName=Spine
// ---------------------------------------------
// Camera
BaseEyeHeight=+68
BaseCrouchEyeHeight=+48
// ---------------------------------------------
// Collision
CrouchHeight=+60.0
CrouchRadius=+36.0
Begin Object Name=CollisionCylinder
CollisionRadius=+0036.000000
CollisionHeight=+0086.000000
BlockZeroExtent=false // blocked by mesh
End Object
CylinderComponent=CollisionCylinder
// IgnoreBlockingBy ragdoll/knockdown pawns to avoid 'stepup' glitch.
// Common with fleshpound rage.
bIgnoreRigidBodyPawns=true
// ---------------------------------------------
// Movement & Physics (1uu = 1cm)
bCanCrouch=true
GroundSpeed=460.f
AirSpeed=460.f
SprintSpeed=460.f
WalkingPct=0.40
CrouchedPct=0.40
bWeaponBob=true
Bob=0.010
ExtraCostForPath=0
// This is true in KF1, but adds a line of sight check for any additional damage
bLOSHearing=false
HearingThreshold=4000.0
// PHYS_Walking
MaxStepHeight=70.0
WalkableFloorZ=0.7
// PHYS_Falling
JumpZ=650.f
AirControl=+0.15
bCanWalkOffLedges=true
bCanJumpOverWalls=true
bCanUseHiddenSpeed=true
Mass=65.f // (in kilograms) Used by HandleMomentum()
MeshRotSmoothingInterpSpeed=30.f
TurnInPlaceAnimRate=1.0f
bAllowSprinting=true
NumJumpsAllowed=1
// ---------------------------------------------
// Damage
MaxFallSpeed=1325.0
PenetrationResistance=1.0
CrushScale=1.0
VolumeDamageScale=1.0
// ---------------------------------------------
// Afflictions
Begin Object Class=KFAfflictionManager Name=Afflictions_0
FireFullyCharredDuration=2.5
FireCharPercentThreshhold=0.25
End Object
AfflictionHandler=Afflictions_0
IncapSettings(AF_EMP)=(Duration=5.0,Cooldown=5.0)
IncapSettings(AF_FirePanic)=(Duration=5.0,Cooldown=5.0)
AfflictionSpeedModifier=1.f
AttackSpeedModifier=1.f
// ---------------------------------------------
// AI / Navigation
DamageRecoveryTimeHeavy=1.f
DamageRecoveryTimeMedium=1.f
MaxJumpHeight=128.0
bWeakZedGrab=true
ZedTimeSpeedScale=1.f
WeaponAttachmentSocket=RW_Weapon
bWeaponAttachmentVisible=true
bNeedsCrosshair=false
TeammateCollisionRadiusPercent=0.80
// ---------------------------------------------
// Network
bReplicateHealthToAll=true
AlwaysRelevantDistanceSquared=1000000 // 10m (see KF1's CustomAmbientRelevancyScale)
`if(`notdefined(ShippingPC))
DebugRadarTexture=Texture2D'UI_ZEDRadar_TEX.MapIcon_Player';
`endif
// ---------------------------------------------
// Visuals
IntendedBodyScale=1.0
CurrentBodyScale=1.0
BodyScaleChangePerSecond=0.5f
IntendedHeadScale=1.0
CurrentHeadScale=1.0
bAllowDeathSM=true
bCanBePinned=false
LastHitZoneIndex=0
// ---------------------------------------------
// AutoTurret
bIsTurret=false
Toxic_HRG_Locust_LoopingParticleEffect=ParticleSystem'WEP_HRG_Locust_EMIT.FX_Flying_Bugs_attacking'
Toxic_HRG_Locust_LoopingPSC=none
bEnableSwarmVFX=false
}