//============================================================================= // 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 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 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 OnInjury; // Called when zone Health reaches 0 structdefaultproperties { GoreHealth=50 MaxGoreHealth=-1 DmgScale=1.f } }; var() array 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 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 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 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 > DamagePerks; var array > DamageCausers; var array > 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 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 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 BloodSplatterDecalMaterials; var array 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 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 PhysicsBodyImpactBoneList; /** List of RidigBodies where a spring should be attached to animation. */ var(Physics) editinline Array 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; /** 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 > DefaultInventory; var() array 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 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 AimOffsetNodes; var() transient Array BodyStanceNodes; /** List of RB bone names which should be UnFixed when playing a Physics Body Impact. */ var(Animation) editinline Array 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 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 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 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(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 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 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 WeakBones, out array 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 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 DT, optional out float DamageMod); /** If returns true, this monster is vulnerable to this damage type damage */ function bool IsResistantTo(class 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, 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, 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 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, 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, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { local int OldHealth, ActualDamage; local class 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(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(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, TraceHitInfo HitInfo, Actor DamageCauser) { local int HitZoneIdx; local KFPawn_Monster InstigatorMonster; local class 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(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 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 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 DamageType ) { local class 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 DamageType ); function class GetUsedWeaponPerk( Controller DamagerController, Actor DamageCauser, class DamageType ) { local class 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, 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, 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, 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, name HitBoneName); native simulated function StartPhysicsBodyImpact(Name HitBoneName, bool bUseMotors, class DamageType); native simulated function StopPhysicsBodyImpact(); native simulated function vector GetImpactPhysicsImpulse(class 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, vector Momentum, TraceHitInfo HitInfo) { local int HitZoneIdx; local class KFDT; if( Damage <= 0 || (Controller != none && Controller.bGodMode) ) { return; } // Cached hit params HitZoneIdx = GetHitZoneIndex(HitInfo.BoneName); KFDT = class(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 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 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, int HitZoneIdx, vector InstigatorLocation) { local float GoreDamage; local class 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(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, int HitZoneIdx) { local class 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(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 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 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 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 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 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 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 DamageType) { switch (DamageType.Name) { case 'KFDT_Toxic_HRG_Locust': StartLocustVFX(); break; } } simulated function OnEndDamageType(class 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= 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, 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(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 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 }