4931 lines
149 KiB
Ucode
4931 lines
149 KiB
Ucode
//=============================================================================
|
|
// KFPawn_Monster
|
|
//=============================================================================
|
|
// Base pawn class for Zeds
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// - Andrew "Strago" Ladenberger
|
|
//=============================================================================
|
|
|
|
class KFPawn_Monster extends KFPawn
|
|
abstract
|
|
nativereplication
|
|
native(Pawn)
|
|
dependson(KFPawnAnimInfo, KFPawnVoiceGroup, KFPawnVoiceGroupEventData, KFSpawnVolume, KFHeadShotEffectList);
|
|
|
|
`include(KFGame\KFGameAnalytics.uci);
|
|
`include(KFGame\KFMatchStats.uci);
|
|
|
|
/************************************
|
|
* @name General info for AAR/Stats
|
|
************************************/
|
|
|
|
var bool bLargeZed;
|
|
var bool bVersusZed;
|
|
|
|
/************************************
|
|
* @name Content
|
|
************************************/
|
|
|
|
/** Path for DLO of the MonsterArch */
|
|
var const string MonsterArchPath;
|
|
/**
|
|
* Default content loaded by this pawn. Private, use GetCharacterMonsterInfo()
|
|
* NOTE: DO NOT statically reference in defaults as it needs to be loaded dynamically
|
|
*/
|
|
var private const KFCharacterInfo_Monster CharacterMonsterArch;
|
|
|
|
var transient bool bArchLoaded;
|
|
|
|
/** List of variants that this pawn can be spawned as */
|
|
var const array<class<KFPawn_Monster> > ElitePawnClass;
|
|
|
|
/** Custom third person camera offsets */
|
|
var() ViewOffsetData ThirdPersonViewOffset;
|
|
|
|
/** Randomized color index from the monster arch if it exists for this zed */
|
|
var int RandomColorIdx;
|
|
|
|
/** List of static attachments from the character info */
|
|
var array<StaticMeshComponent> StaticAttachList;
|
|
|
|
/*********************************************************************************************
|
|
* @name Combat
|
|
********************************************************************************************* */
|
|
|
|
/** The chance that this monster pawn will sprint */
|
|
var(Combat) float SprintChance;
|
|
var(Combat) bool bCanGrabAttack;
|
|
/** Odds (0-1) of evaluating whether to do a grab attack instead of a basic melee attack */
|
|
var(Combat) float GrabAttackFrequency;
|
|
var(Combat) bool bCanMeleeAttack;
|
|
var(Combat) bool bHasExtraSprintJumpVelocity;
|
|
|
|
/** The amount to scale this Zed's damage based on difficulty */
|
|
var(Combat) float DifficultyDamageMod;
|
|
|
|
/** GameInfo based damage resistance modifier cached at spawn time (0: Ignore, 1: Standard) */
|
|
var(Combat) float GameResistancePct;
|
|
|
|
/** Time until death after head is taken off */
|
|
var(Combat) float HeadlessBleedOutTime;
|
|
/** Enables crippled animation and behavior state */
|
|
var repnotify bool bIsHeadless;
|
|
|
|
/** If > 0, clientside head injury gore can be applied while still alive */
|
|
var byte MaxHeadChunkGoreWhileAlive;
|
|
|
|
/** Object that manages melee attacks, and stores default damage */
|
|
var(Weapon) instanced KFMeleeHelperAI MeleeAttackHelper;
|
|
|
|
var protected bool bHasReducedMeleeDamage;
|
|
|
|
/** Total dosh this monster is worth on kill */
|
|
var private const int DoshValue;
|
|
/** XP this monster is worth (per difficulty) */
|
|
var private const float XPValues[4];
|
|
/** List of sockets representing weakpoint zone locations */
|
|
var() array<name> WeakSpotSocketNames;
|
|
|
|
/** Heal after die */
|
|
var int HealByKill;
|
|
var int HealByAssistance;
|
|
|
|
/**
|
|
* Information on resistant or vulnerable damage types
|
|
* @todo: This is all static data so we should consider moving to the archetype
|
|
*/
|
|
struct native DamageModifierInfo
|
|
{
|
|
/** A damage type to modify damage to this zed when it is received */
|
|
var() class<DamageType> DamageType;
|
|
/** Damage scale for this damage type. Additional array elements (MAX:4) can be used to modify for higher game difficulties */
|
|
var() array<float> DamageScale;
|
|
|
|
structdefaultproperties
|
|
{
|
|
DamageScale=(1.f)
|
|
}
|
|
};
|
|
|
|
/** These damage types cause this zed to receive increased damage */
|
|
var array<DamageModifierInfo> DamageTypeModifiers;
|
|
/** Additional damage type modifiers that can override the defaults */
|
|
var array<DamageModifierInfo> LiveDamageTypeModifiers;
|
|
|
|
/** Special move cooldown info */
|
|
struct native SpecialMoveCooldownInfo
|
|
{
|
|
var float CooldownTime;
|
|
var transient float LastUsedTime;
|
|
var ESpecialMove SMHandle;
|
|
|
|
/** Used by UI */
|
|
var Texture2D SpecialMoveIcon;
|
|
var int Charges;
|
|
|
|
var string NameLocalizationKey;
|
|
var bool bShowOnHud;
|
|
|
|
structdefaultproperties
|
|
{
|
|
SMHandle=SM_None
|
|
bShowOnHud=true
|
|
LastUsedTime=0.f
|
|
SpecialMoveIcon=Texture2D'UI_Widgets.MenuBarWidget_SWF_IF'
|
|
Charges=-1
|
|
}
|
|
};
|
|
|
|
/** The amount to scale damage dealt by the fleshpound */
|
|
var float ZedBumpDamageScale;
|
|
|
|
var protected bool bShowHealth;
|
|
|
|
/** Set only while inside AdjustDamage when calculating extra head explosion damage*/
|
|
var transient bool bCheckingExtraHeadDamage;
|
|
|
|
/** Base human-controlled melee damage */
|
|
var(Combat) float HumanBaseMeleeDamage;
|
|
|
|
/** Contains balance settings per game difficulty */
|
|
var class<KFMonsterDifficultyInfo> DifficultySettings;
|
|
|
|
/*********************************************************************************************
|
|
* @name Player-controlled
|
|
********************************************************************************************* */
|
|
|
|
/*
|
|
* Move list classification used by MoveListGamepadScheme
|
|
* Must be aligned firemode
|
|
* -- label w/ default bind for readability
|
|
*/
|
|
enum EPlayerZedGamepadMove
|
|
{
|
|
ZGM_Attack_R2, // firemode 0 -- R2
|
|
ZGM_Block_R1, // firemode 1 -- R1
|
|
ZGM_Melee_Square, // firemode 2 -- Square
|
|
ZGM_Special_R3, // firemode 3 -- R3
|
|
ZGM_Explosive_Ll, // firemode 4 -- L1
|
|
ZGM_Attack_L2, // firemode 5 -- L2
|
|
ZGM_Melee_Triangle, // firemode 6 -- Triangle
|
|
};
|
|
|
|
/* For gamepads remap the controls by type (aligned to EPlayerZedGamepadMove) */
|
|
var array<ESpecialMove> MoveListGamepadScheme;
|
|
|
|
/** Cooldown times for each special move ability */
|
|
var array<SpecialMoveCooldownInfo> SpecialMoveCooldowns;
|
|
|
|
/** Flag set after executing a jump */
|
|
var bool bJumped;
|
|
|
|
var transient float LastAttackHumanWarningTime;
|
|
|
|
/*********************************************************************************************
|
|
* @name Hit Reactions
|
|
********************************************************************************************* */
|
|
|
|
/** Settings for the slowed down incapacitation effect */
|
|
const SLOW_SPEED_MOD = 0.8f;
|
|
|
|
/** Resistance value for special move reaction to being parried */
|
|
var byte ParryResistance;
|
|
|
|
/** true if we currently incap poisoned */
|
|
var repnotify bool bIsPoisoned;
|
|
/** Is microwave panic active? (server only) */
|
|
var bool bMicrowavePanicked;
|
|
|
|
struct native RepInflateParams
|
|
{
|
|
var byte RepInflateMatParam;
|
|
var bool bHasToIgniteFlames;
|
|
var int Count;
|
|
};
|
|
|
|
var repnotify RepInflateParams RepInflateMatParams;
|
|
|
|
|
|
/** Replicated material parameter */
|
|
var repnotify byte RepInflateMatParam;
|
|
|
|
/** If true, jumping on this pawn triggers ragdoll knockdown */
|
|
var bool bKnockdownWhenJumpedOn;
|
|
|
|
/** Cached health before taking damage for status effect resistance */
|
|
var transient int OldHealth;
|
|
|
|
/** Server-controlled disable of the gore mesh while alive */
|
|
var bool bDisableGoreMeshWhileAlive;
|
|
|
|
/** Whether or not to explode on death (set by server) */
|
|
var bool bUseExplosiveDeath;
|
|
|
|
/** Inflate on damage (LERP between none and full inflation based on health) */
|
|
var bool bUseDamageInflation;
|
|
|
|
/** Max damage inflation value */
|
|
var float ZeroHealthInflation;
|
|
|
|
/** Rate of inflation per second when inflating from damage, up to current intended */
|
|
var float DamageInflationRate;
|
|
|
|
/** Rate of deflation per second for damage inflation */
|
|
var float DamageDeflationRate;
|
|
|
|
/** Intended inflation. If a pawn is still inflating, this will be higher */
|
|
var float IntendedDamageInflationPercent;
|
|
|
|
/** Current inflation percent stored as float */
|
|
var float DamageInflationPercent;
|
|
|
|
/** Current replicated damage inflation */
|
|
var repnotify byte RepDamageInflateParam;
|
|
|
|
/** Gravity value to use on inflate death */
|
|
var float InflateDeathGravity;
|
|
|
|
/** Timer after death until inflation explosion */
|
|
var float InflationExplosionTimer;
|
|
|
|
/** Deflate value when affected by bleed */
|
|
var repnotify byte RepBleedInflateMatParam;
|
|
|
|
// Base emitter to use for incap effect
|
|
var const ParticleSystem BleedIncapFX;
|
|
|
|
// PSC attached to effected pawn when incap is in effect
|
|
var ParticleSystemComponent BleedIncapPSC;
|
|
|
|
/*********************************************************************************************
|
|
* @name Anim Tree Controls
|
|
********************************************************************************************* */
|
|
|
|
/* Plays panicked animation (e.g. On Fire, Poisoned) */
|
|
var bool bPlayPanicked;
|
|
/* Plays shambling animation (e.g. Headless, EMP) */
|
|
var bool bPlayShambling;
|
|
|
|
/** Cached anim nodes */
|
|
var KFAnim_RandomScripted WalkBlendList;
|
|
var KFAnim_Movement MovementAnimNode;
|
|
|
|
/*********************************************************************************************
|
|
* @name Special Abilities
|
|
********************************************************************************************* */
|
|
|
|
|
|
/** Struct containing block settings, used on a per-difficulty basis */
|
|
struct native sBlockInfo
|
|
{
|
|
/** Chance of blocking an attack */
|
|
var float Chance;
|
|
/** Time from the start of a block until the zed unblocks again */
|
|
var float Duration;
|
|
/** Maximum number of attacks that can be blocked before the block is broken */
|
|
var float MaxBlocks;
|
|
/** Cooldown duration from the end of the last block */
|
|
var float Cooldown;
|
|
/** How much damage to accumulate (as a percentage of maximum health) before attempting to trigger a block */
|
|
var float DamagedHealthPctToTrigger;
|
|
/** How much to mitigate melee damage when blocking */
|
|
var float MeleeDamageModifier;
|
|
/** How much to mitigate non-melee damage when blocking */
|
|
var float DamageModifier;
|
|
/** How much to scale incap/affliction power by when blocking */
|
|
var float AfflictionModifier;
|
|
/** How much to multiply the chance by when in a solo game */
|
|
var float SoloChanceMultiplier;
|
|
|
|
structdefaultproperties
|
|
{
|
|
Chance=0.f
|
|
MeleeDamageModifier=1.f
|
|
DamageModifier=1.f
|
|
AfflictionModifier=1.f
|
|
}
|
|
};
|
|
|
|
var protected transient sBlockInfo DifficultyBlockSettings;
|
|
|
|
/** Minimum FOV angle attacks must fall in to be blocked */
|
|
var protected const float MinBlockFOV;
|
|
|
|
/** Whether this zed is blocking attacks */
|
|
var transient bool bIsBlocking;
|
|
|
|
/** Multiplier applied to sprint speed when blocking */
|
|
var protected const float BlockSprintSpeedModifier;
|
|
|
|
/** The last time a successful block ended */
|
|
var transient float LastBlockTime;
|
|
|
|
/** Multiplier applied to the vortex attraction force */
|
|
var protected float VortexAttracionModifier;
|
|
|
|
var float KnockedDownBySonicWaveOdds;
|
|
var bool bCloakOnMeleeEnd;
|
|
var bool bIsCloakingSpottedByLP;
|
|
var repnotify bool bIsCloakingSpottedByTeam;
|
|
var float LastSpottedStatusUpdate;
|
|
/** The most recent controller that made the pawn visible for other players */
|
|
var KFPlayerController LastStoredCC;
|
|
/** Rage flags */
|
|
var bool bCanRage;
|
|
var repnotify bool bIsEnraged;
|
|
|
|
var transient bool bRageAfterPanic;
|
|
|
|
/** Particle system component for player alpha rally effect */
|
|
var transient ParticleSystemComponent RallyPSC;
|
|
|
|
/** Particle system components attached to the hands when rallied */
|
|
var transient ParticleSystemComponent RallyHandPSCs[2];
|
|
|
|
/** AI rally buff variables */
|
|
struct native sRallyInfo
|
|
{
|
|
/** Whether this AI can rally in this difficulty */
|
|
var bool bCanRally;
|
|
/** Whether rally causes this AI to sprint */
|
|
var bool bCauseSprint;
|
|
/** How long the rally buff lasts for */
|
|
var float RallyBuffTime;
|
|
/** How much to modify incoming (taken) damage */
|
|
var float TakenDamageModifier;
|
|
/** How much to modify outgoing (dealt) damage */
|
|
var float DealtDamageModifier;
|
|
|
|
structdefaultproperties
|
|
{
|
|
bCanRally=true
|
|
bCauseSprint=false
|
|
RallyBuffTime=10.f
|
|
TakenDamageModifier=1.f
|
|
DealtDamageModifier=1.f
|
|
}
|
|
};
|
|
|
|
var protected transient sRallyInfo DifficultyRallySettings;
|
|
|
|
/*********************************************************************************************
|
|
* @name Perks
|
|
********************************************************************************************* */
|
|
|
|
var private bool bIsStalkerClass;
|
|
var private bool bIsCrawlerClass;
|
|
var private bool bIsFleshpoundClass;
|
|
var private bool bIsClotClass;
|
|
var private bool bIsBloatClass;
|
|
|
|
/*********************************************************************************************
|
|
* @name Movement
|
|
********************************************************************************************* */
|
|
|
|
/** The difficulty adjusted original GroundSpeed for this character */
|
|
var transient float NormalGroundSpeed;
|
|
/** The difficulty adjusted original SprintSpeed for this character */
|
|
var transient float NormalSprintSpeed;
|
|
/** The random ground speed modifier that is applied whent this character is initialized */
|
|
var transient float InitialGroundSpeedModifier;
|
|
/** Time interval for HiddenGroundSpeed check */
|
|
var transient float LastAISpeedCheckTime;
|
|
/** The last time this Zed was network relevant to someone, or had line of sight to someone */
|
|
var transient float LastLOSOrRelevantTime;
|
|
/** When true, will attempt to match player speed while in pursuit. Use only if you want to allow upperbody attacks while moving. */
|
|
var bool bMatchEnemySpeed;
|
|
/** Match enemy speed while in pursuit if enemy distance <= this value */
|
|
var float MatchEnemySpeedAtDistance;
|
|
/** Minimum speed of enemy required for me to begin matching speed */
|
|
var float MinimumEnemySpeedToMatch;
|
|
var float PursuitSpeedScale;
|
|
/** Scales distance threshold that enemy is considered reached */
|
|
var float ReachedEnemyThresholdScale;
|
|
/** Instead of scaling, will just use this value for reached checks, if value > 0 and using walking physics */
|
|
var float ReachedGoalThresh_Walking;
|
|
/** Instead of scaling, will just use this value for reached checks, if value > 0 and using spider physics */
|
|
var float ReachedGoalThresh_Spider;
|
|
var float LastBumpTime;
|
|
/** Used as an optimization for zeds and limits how frequently NotifyBump will be called */
|
|
var float BumpFrequency;
|
|
/** Set internally if pawn's collision size was modified at start of a jump */
|
|
var bool bRestoreCollisionOnLand;
|
|
|
|
/** Damage type applied to destructible actors when bumped */
|
|
var class<KFDamageType> BumpDamageType;
|
|
|
|
/** Damagetype applied to human players when hitting them in mid-air */
|
|
var class<KFDamageType> JumpBumpDamageType;
|
|
|
|
/** Footstep shakes activated in footstep sound animnotify */
|
|
var protected float FootstepCameraShakeInnerRadius;
|
|
var protected float FootstepCameraShakeOuterRadius;
|
|
var CameraShake FootstepCameraShake;
|
|
|
|
/** A ground speed this pawn has been set to blend to */
|
|
var float DesiredAdjustedGroundSpeed;
|
|
/** A sprint speed this pawn has been set to blend to */
|
|
var float DesiredAdjustedSprintSpeed;
|
|
/** The rate to transition speeds when blending to new movement speeds */
|
|
var float SpeedAdjustTransitionRate;
|
|
|
|
var AkComponent SprintAkComponent;
|
|
var AkComponent HeadShotAkComponent;
|
|
var AkEvent StartSprintingSound;
|
|
var AkEvent SprintLoopingSound;
|
|
var AkEvent StopSprintingSound;
|
|
|
|
var bool bPlayingSprintLoop;
|
|
|
|
var bool bSprintOverride;
|
|
|
|
/*********************************************************************************************
|
|
* @name Dismemberment / Gore
|
|
********************************************************************************************* */
|
|
|
|
/** The explosion effect should only be played once */
|
|
var transient bool bPlayedExplosionEffect;
|
|
|
|
/** Information regardint the currently attached gore chunks */
|
|
struct native AttachedGoreChunkInfo
|
|
{
|
|
var int AttachmentIndex;
|
|
var StaticMeshComponent AttachedComponent;
|
|
};
|
|
|
|
/** The currently attached gore chunks */
|
|
var transient array<AttachedGoreChunkInfo> AttachedGoreChunks;
|
|
|
|
/** Keeps track of the number of head chunks removed for the ZED */
|
|
var transient int NumHeadChunksRemoved;
|
|
|
|
/** Keep track of the head bones that have already been broken */
|
|
var transient array<Name> BrokenHeadBones;
|
|
|
|
/** How often to check for napalm infections */
|
|
//var protected const float NapalmCheckInterval;
|
|
|
|
/** Last time we checked for a napalm infection upon taking damage */
|
|
var transient protected float LastNapalmInfectCheckTime;
|
|
|
|
/** Is there a chanced that we explode when we die? Set by the firebug's Zed shrapnel skill */
|
|
var bool bCouldTurnIntoShrapnel;
|
|
|
|
/** We explode into a toxic cloud when we die? Set by the field medic's Zed zedative skill */
|
|
var bool bCouldTurnIntoToxicExplosion;
|
|
|
|
/** Gameplay-facing disable of head destruction */
|
|
var bool bDisableHeadless;
|
|
|
|
var array<name> BonesToHidePostAsyncWork;
|
|
var array<name> BonesToShowPostAsyncWork;
|
|
|
|
/*********************************************************************************************
|
|
* @name Dialog
|
|
********************************************************************************************* */
|
|
var transient int DeadHorseHitStreakAmt;
|
|
var transient float LastDeadHorseHitTime;
|
|
|
|
/*********************************************************************************************
|
|
* @name Debug
|
|
********************************************************************************************* */
|
|
|
|
var bool bDebug_DrawOverheadInfo;
|
|
var bool bDebug_DrawSprintingOverheadInfo;
|
|
var const bool bDebug_UseIconForShowingSprintingOverheadInfo;
|
|
var bool bDebug_SpawnedThroughCheat;
|
|
|
|
/*********************************************************************************************
|
|
* @name Door Navigation
|
|
********************************************************************************************* */
|
|
|
|
var float DefaultCollisionRadius;
|
|
|
|
var KFTrigger_ChokePoint CurrentChokePointTrigger;
|
|
|
|
var bool bReducedZedOnZedPinchPointCollisionStateActive;
|
|
var const float CollisionRadiusForReducedZedOnZedPinchPointCollisionState;
|
|
|
|
/*********************************************************************************************
|
|
* @name Spawning
|
|
********************************************************************************************* */
|
|
|
|
/** When spawning in a spawn volume, the squad type as to be at least this big (can be bigger
|
|
* if there are other zeds in the spawn squad that are larger). */
|
|
var() ESquadType MinSpawnSquadSizeType;
|
|
|
|
/*********************************************************************************************
|
|
* @name Achievements
|
|
********************************************************************************************* */
|
|
var protected const int OnDeathAchievementID;
|
|
var protected bool bOnDeathAchivementbDisabled;
|
|
|
|
/*********************************************************************************************
|
|
* @name Armor
|
|
********************************************************************************************* */
|
|
|
|
/** Armor component to handle armor damage and detachment. */
|
|
var class<KFZedArmorInfo> ArmorInfoClass;
|
|
var KFZedArmorInfo ArmorInfo;
|
|
|
|
//Byte array of armor percentages, replicated to clients
|
|
var repnotify byte RepArmorPct[3];
|
|
|
|
//Bit field for the status of the armor zones. 1 = attached
|
|
var repnotify byte ArmorZoneStatus;
|
|
var byte PreviousArmorZoneStatus;
|
|
|
|
//Hit FX overrides for hitting armor
|
|
var const int OverrideArmorFXIndex;
|
|
|
|
/*********************************************************************************************
|
|
* @name Delegates
|
|
********************************************************************************************* */
|
|
|
|
/** Declaration for attach/detach gore chunk delegates */
|
|
simulated delegate bool GoreChunkAttachmentCriteria();
|
|
simulated delegate bool GoreChunkDetachmentCriteria();
|
|
|
|
replication
|
|
{
|
|
// Replicated to ALL
|
|
if (bNetDirty)
|
|
bIsHeadless, bIsPoisoned, bPlayPanicked, bPlayShambling, MaxHeadChunkGoreWhileAlive,
|
|
RepInflateMatParams, RepInflateMatParam, RepDamageInflateParam, RepBleedInflateMatParam, bDisableGoreMeshWhileAlive,
|
|
bDisableHeadless, InflateDeathGravity, InflationExplosionTimer, bUseDamageInflation, bUseExplosiveDeath;
|
|
if ( bNetDirty && bCanCloak )
|
|
bIsCloakingSpottedByTeam;
|
|
if ( bNetDirty && bCanRage )
|
|
bIsEnraged;
|
|
if (Role == ROLE_Authority)
|
|
ArmorZoneStatus, RepArmorPct;
|
|
}
|
|
|
|
cpptext
|
|
{
|
|
// Actor
|
|
INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
|
|
virtual void TickSpecial( FLOAT DeltaSeconds );
|
|
virtual void TickAuthoritative( FLOAT DeltaSeconds );
|
|
|
|
// AI / navigation
|
|
virtual UBOOL IgnoreBlockingBy( const AActor *Other) const;
|
|
virtual void NotifyBump( AActor *Other, UPrimitiveComponent* OtherComp, const FVector &HitNormal );
|
|
virtual FLOAT MaxSpeedModifier();
|
|
virtual void UpdateAISuperSpeed();
|
|
virtual void UpdateAIPursuitSpeed();
|
|
virtual UBOOL IsGlider();
|
|
virtual UBOOL ReachThresholdTest( const FVector &TestPosition, const FVector &Dest, AActor* GoalActor, FLOAT UpThresholdAdjust, FLOAT DownThresholdAdjust, FLOAT ThresholdAdjust );
|
|
virtual void HandleSerpentineMovement(FVector& out_Direction, FLOAT Distance, const FVector& Dest);
|
|
virtual INT ModifyCostForReachSpec(UReachSpec* Spec, INT Cost);
|
|
virtual void NotifyBumpLevel(AActor* Wall, const FVector &HitLocation, const FVector& HitNormal);
|
|
virtual void OnRigidBodyCollision(const FRigidBodyCollisionInfo& MyInfo, const FRigidBodyCollisionInfo& OtherInfo, const FCollisionImpactData& RigidCollisionData);
|
|
virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive,AActor *SourceActor, DWORD TraceFlags);
|
|
virtual void physicsRotation(FLOAT deltaTime, FVector OldVelocity);
|
|
}
|
|
|
|
native function bool SpiderPhysicsWallAdjust( vector HitNormal, actor HitActor );
|
|
/** Tells the monster whether or not it should shrink its collision cylinder when going through a choke point */
|
|
native function SetChokePointCollision( bool bUseChokeCollision );
|
|
/** Checks if we are our current location encroaches any world geometry */
|
|
native function bool CheckEncroachingWorldGeometry();
|
|
|
|
native function float GetGravityZ();
|
|
|
|
/**
|
|
* Check on various replicated data and act accordingly.
|
|
*/
|
|
simulated event ReplicatedEvent(name VarName)
|
|
{
|
|
switch( VarName )
|
|
{
|
|
case nameof(bIsHeadless):
|
|
StopAkEventsOnBone( 'head' );
|
|
// No more auto aiming to this zed
|
|
bCanBeAdheredTo=false;
|
|
bCanBeFrictionedTo=false;
|
|
break;
|
|
|
|
case nameof(bIsPoisoned):
|
|
AfflictionHandler.ToggleEffects(AF_Poison, bIsPoisoned);
|
|
break;
|
|
|
|
case nameof(RepInflateMatParams):
|
|
KFAffliction_Microwave(AfflictionHandler.Afflictions[AF_Microwave]).bHasToSpawnFire = RepInflateMatParams.bHasToIgniteFlames;
|
|
AfflictionHandler.UpdateMaterialParameter(AF_Microwave, GetCurrentInflation());
|
|
break;
|
|
|
|
case nameof(RepBleedInflateMatParam):
|
|
AfflictionHandler.UpdateMaterialParameter(AF_Bleed, ByteToFloat(RepBleedInflateMatParam));
|
|
UpdateBleedIncapFX();
|
|
break;
|
|
|
|
case nameof(Controller):
|
|
// Set audio switch based on AI or player-controlled
|
|
SetSwitch( 'Player_Zed', IsHumanControlled() ? 'Player' : 'NotPlayer' );
|
|
break;
|
|
|
|
case nameof(bEmpDisrupted):
|
|
if( bEmpDisrupted )
|
|
{
|
|
// If this pawn was disrupted, put all of its moves on cooldown
|
|
PutAllMovesOnCooldown();
|
|
}
|
|
break;
|
|
|
|
case nameof(RepDamageInflateParam):
|
|
HandleDamageInflation();
|
|
break;
|
|
|
|
case nameof(RepArmorPct):
|
|
if(ArmorInfo != none)
|
|
{
|
|
ArmorInfo.UpdateArmorPieces();
|
|
}
|
|
break;
|
|
|
|
case nameof(ArmorZoneStatus):
|
|
if (ArmorInfo != none)
|
|
{
|
|
ArmorInfo.UpdateArmorPieces();
|
|
}
|
|
break;
|
|
|
|
case nameof(bIsEnraged):
|
|
SetEnraged(bIsEnraged);
|
|
break;
|
|
}
|
|
|
|
super.ReplicatedEvent( VarName );
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Static
|
|
********************************************************************************************* */
|
|
|
|
// returns the zed's score/dosh value
|
|
simulated static function int GetDoshValue()
|
|
{
|
|
return default.DoshValue;
|
|
}
|
|
|
|
simulated static function float GetXPValue(byte Difficulty)
|
|
{
|
|
return default.XPValues[Difficulty];
|
|
}
|
|
|
|
/** Gets the actual classes used for spawning. Can be overridden to replace this monster with another */
|
|
static event class<KFPawn_Monster> GetAIPawnClassToSpawn()
|
|
{
|
|
local WorldInfo WI;
|
|
|
|
WI = class'WorldInfo'.static.GetWorldInfo();
|
|
if (default.ElitePawnClass.length > 0 && default.DifficultySettings != none && fRand() < default.DifficultySettings.static.GetSpecialSpawnChance(KFGameReplicationInfo(WI.GRI)))
|
|
{
|
|
return default.ElitePawnClass[Rand(default.ElitePawnClass.length)];
|
|
}
|
|
|
|
return default.class;
|
|
}
|
|
|
|
/** Load content archetype when map loads */
|
|
native static final function PreloadContent();
|
|
/** Called if preload was not called before 1st spawn */
|
|
native private function LastChanceLoad();
|
|
|
|
/*********************************************************************************************
|
|
* @name Constructors, Destructors, and Loading
|
|
********************************************************************************************* */
|
|
|
|
/** Called immediately before gameplay begins */
|
|
simulated event PreBeginPlay()
|
|
{
|
|
local KFPawn_Monster KFPM;
|
|
local bool bArchAlreadyLoaded;
|
|
|
|
CheckShouldAlwaysBeRelevant(); //these guys could be alive already so this is unreliable
|
|
|
|
DefaultCollisionRadius = CylinderComponent.default.CollisionRadius;
|
|
|
|
Super.PreBeginPlay();
|
|
|
|
// Check if the global data was initialized
|
|
if( WorldInfo.NetMode != NM_DedicatedServer && ( WorldInfo.GRI == none || WorldInfo.GRI.GameClass == none ) )
|
|
{
|
|
`log("Preload global data not initialized");
|
|
bArchAlreadyLoaded = false;
|
|
foreach WorldInfo.AllPawns( class'KFPawn_Monster', KFPM )
|
|
{
|
|
if( KFPM != self && KFPM.class == class && KFPM.bArchLoaded )
|
|
{
|
|
`log("Archetype already loaded for class"@class);
|
|
bArchAlreadyLoaded = true;
|
|
bArchLoaded = true;
|
|
}
|
|
}
|
|
|
|
if( !bArchAlreadyLoaded )
|
|
{
|
|
`log("Initializing first archetype for class"@class);
|
|
LastChanceLoad();
|
|
bArchLoaded = true;
|
|
}
|
|
}
|
|
|
|
// If we don't have an archetype select one
|
|
if ( CharacterArch == None )
|
|
{
|
|
// Preload should have been called already, but if not do it now!
|
|
if ( CharacterMonsterArch == None )
|
|
{
|
|
LastChanceLoad();
|
|
}
|
|
|
|
if ( CharacterMonsterArch != None )
|
|
{
|
|
SetCharacterArch(CharacterMonsterArch);
|
|
}
|
|
}
|
|
|
|
if ( CharacterArch == None )
|
|
{
|
|
`warn("Failed to find character info for KFMonsterPawn!");
|
|
Destroy();
|
|
}
|
|
NormalGroundSpeed = default.GroundSpeed;
|
|
NormalSprintSpeed = default.SprintSpeed;
|
|
|
|
if (ArmorInfoClass != none)
|
|
{
|
|
ArmorInfo = new(self) ArmorInfoClass;
|
|
}
|
|
}
|
|
|
|
simulated event CheckShouldAlwaysBeRelevant()
|
|
{
|
|
local KFGameReplicationInfo KFGRI;
|
|
|
|
// Set to always relevant if we're the last few remaining
|
|
KFGRI = KFGameReplicationInfo( WorldInfo.GRI );
|
|
if( KFGRI != none && KFGRI.AIRemaining <= class'KFGameInfo'.static.GetNumAlwaysRelevantZeds() )
|
|
{
|
|
bAlwaysRelevant = true;
|
|
}
|
|
}
|
|
|
|
/** Called immediately after gameplay begins */
|
|
simulated event PostBeginPlay()
|
|
{
|
|
local KFGameReplicationInfo KFGRI;
|
|
|
|
super.PostBeginPlay();
|
|
|
|
// Set our (Network: ALL) difficulty-based settings
|
|
KFGRI = KFGameReplicationInfo( WorldInfo.GRI );
|
|
if( KFGRI != none )
|
|
{
|
|
SetRallySettings( DifficultySettings.static.GetRallySettings(self, KFGRI) );
|
|
SetZedTimeSpeedScale( DifficultySettings.static.GetZedTimeSpeedScale(self, KFGRI) );
|
|
}
|
|
|
|
if( (WorldInfo.NetMode != NM_Client) && IsABoss() )
|
|
{
|
|
`AnalyticsLog(("boss_spawn", None, Class.Name));
|
|
}
|
|
|
|
if (IsABoss())
|
|
{
|
|
class'KFPawn_MonsterBoss'.static.SetupHealthBar(self);
|
|
}
|
|
|
|
if (ArmorInfo != none)
|
|
{
|
|
ArmorInfo.InitArmor();
|
|
}
|
|
}
|
|
|
|
simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
|
|
{
|
|
local int i;
|
|
|
|
super.SetMeshLightingChannels(NewLightingChannels);
|
|
|
|
for (i = 0; i < StaticAttachList.Length; ++i)
|
|
{
|
|
if (StaticAttachList[i] != none)
|
|
{
|
|
StaticAttachList[i].SetLightingChannels(NewLightingChannels);
|
|
}
|
|
}
|
|
}
|
|
|
|
//update the portrait when we get a PRI
|
|
simulated function NotifyTeamChanged()
|
|
{
|
|
if(CharacterArch != none)
|
|
{
|
|
CharacterArch.SetCharacterFromArch( self, KFPlayerReplicationInfo(PlayerReplicationInfo) );
|
|
}
|
|
}
|
|
|
|
/** Called from Possessed event when this controller has taken control of a Pawn */
|
|
function PossessedBy( Controller C, bool bVehicleTransition )
|
|
{
|
|
local string NPCName;
|
|
local KFPlayerReplicationInfo KFPRI;
|
|
local KFGameReplicationInfo KFGRI;
|
|
|
|
Super.PossessedBy( C, bVehicleTransition );
|
|
|
|
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
|
|
|
|
/** Set MyKFAIC for convenience to avoid casting */
|
|
if( KFAIController(C) != none )
|
|
{
|
|
MyKFAIC = KFAIController( C );
|
|
|
|
// If AI Zed has spawned during trader time, should be killed immediately.
|
|
if (!(bDebug_SpawnedThroughCheat || WorldInfo.Game.GetStateName() == 'DebugSuspendWave') && KFGRI != none && !KFGRI.bWaveIsActive)
|
|
{
|
|
Suicide();
|
|
return;
|
|
}
|
|
}
|
|
|
|
bReducedZedOnZedPinchPointCollisionStateActive = false;
|
|
//CollisionRadiusBeforeReducedZedOnZedPinchPointCollisionState = CollisionCylinderReducedPercentForSameTeamCollision;
|
|
|
|
// Turn off air control for AI because it can mess up landing.
|
|
`if(`notdefined(ShippingPC))
|
|
if( IsHumanControlled() || MyKFAIC.bIsSimulatedPlayerController )
|
|
`else
|
|
if( IsHumanControlled() )
|
|
`endif
|
|
{
|
|
KFPRI = KFPlayerReplicationInfo(C.PlayerReplicationInfo);
|
|
if(KFPRI != none)
|
|
{
|
|
KFPRI.PlayerHealth = Health;
|
|
KFPRI.PlayerHealthPercent = FloatToByte( float(Health) / float(HealthMax) );
|
|
SetCharacterArch(CharacterMonsterArch, true);
|
|
}
|
|
|
|
if( Role == ROLE_Authority )
|
|
{
|
|
LastAttackHumanWarningTime = WorldInfo.TimeSeconds;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AirControl = AIAirControl;
|
|
}
|
|
|
|
KFGameInfo(WorldInfo.Game).SetMonsterDefaults( self );
|
|
|
|
if( MyKFAIC != none && MyKFAIC.PlayerReplicationInfo != None )
|
|
{
|
|
NPCName = string(self);
|
|
NPCName = Repl(NPCName,"KFPawn_Zed","",false);
|
|
PlayerReplicationInfo.PlayerName = NPCName;
|
|
MyKFAIC.PlayerReplicationInfo.PlayerName = NPCName;
|
|
}
|
|
|
|
// Set our (Network: Server) difficulty-based settings
|
|
if( KFGRI != none )
|
|
{
|
|
SetBlockSettings( DifficultySettings.static.GetBlockSettings(self, KFGRI) );
|
|
|
|
// Set any AI-specific difficulty settings
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.EvadeOnDamageSettings = DifficultySettings.static.GetEvadeOnDamageSettings( self, KFGRI );
|
|
}
|
|
}
|
|
|
|
// Set audio switch based on AI or player-controlled
|
|
`if(`notdefined(ShippingPC))
|
|
SetSwitch( 'Player_Zed', (IsHumanControlled() || (MyKFAIC != none && MyKFAIC.bIsSimulatedPlayerController)) ? 'Player' : 'NotPlayer' );
|
|
`else
|
|
SetSwitch( 'Player_Zed', IsHumanControlled() ? 'Player' : 'NotPlayer' );
|
|
`endif
|
|
}
|
|
|
|
/*
|
|
simulated event FellOutOfWorld(class<DamageType> dmgType)
|
|
{
|
|
local string Msg;
|
|
|
|
`warn( self$" FELL OUT OF WORLD!" );
|
|
if( Health > 0 )
|
|
{
|
|
msg = WorldInfo.TimeSeconds@self@GetFuncName()@"fell out of world! Current location:"@Location;
|
|
if( MyKFAIC != None )
|
|
{
|
|
msg = msg@"Controller:"@MyKFAIC@"active command:"@MyKFAIC.GetActiveCommand();
|
|
if( MyKFAIC.MoveTarget != none )
|
|
{
|
|
msg = msg@"MoveTarget:"@MyKFAIC.MoveTarget;
|
|
}
|
|
}
|
|
`warn( msg );
|
|
}
|
|
super.FellOutOfWorld(dmgType);
|
|
}*/ //log spam
|
|
|
|
event BaseChange()
|
|
{
|
|
if( IsAliveAndWell() && MyKFAIC != none )
|
|
{
|
|
if( MyKFAIC.NotifyBaseChange( Base, Floor ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
super.BaseChange();
|
|
}
|
|
|
|
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
|
|
{
|
|
Super.PostInitAnimTree(SkelComp);
|
|
|
|
WalkBlendList = KFAnim_RandomScripted(SkelComp.FindAnimNode('WalkRandomList'));
|
|
|
|
// Cache all AnimNodeSlots.
|
|
foreach SkelComp.AllAnimNodes(class'KFAnim_Movement', MovementAnimNode)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Initialize GoreHealth (Server only) */
|
|
function ApplySpecialZoneHealthMod(float HealthMod)
|
|
{
|
|
//Update head
|
|
HitZones[HZI_HEAD].GoreHealth = default.HitZones[HZI_HEAD].GoreHealth * HealthMod;
|
|
HitZones[HZI_HEAD].MaxGoreHealth = HitZones[HZI_HEAD].GoreHealth;
|
|
}
|
|
|
|
function SetShieldScale(float InScale)
|
|
{
|
|
if (ArmorInfo != none)
|
|
{
|
|
ArmorInfo.SetShieldScale(InScale);
|
|
}
|
|
}
|
|
|
|
/** Used by the Versus takeover code to determine if this zed can be taken over */
|
|
function bool CanTakeOver()
|
|
{
|
|
local KFVersusNoTakeoverVolume KFNTV;
|
|
|
|
if( bVersusZed || IsDoingSpecialMove() || IsHeadless() || !IsAliveAndWell() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach TouchingActors( class'KFVersusNoTakeoverVolume', KFNTV )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
{
|
|
super.Tick(DeltaTime);
|
|
|
|
if (WorldInfo.NetMode != NM_DedicatedServer && IsAliveAndWell())
|
|
{
|
|
if (bIsSprinting && VSizeSQ(Velocity) > 40000.f)
|
|
{
|
|
if (!bPlayingSprintLoop)
|
|
{
|
|
if (StartSprintingSound != none && !SprintAkComponent.IsPlaying(StartSprintingSound))
|
|
{
|
|
SprintAkComponent.PlayEvent(StartSprintingSound, true, true);
|
|
}
|
|
}
|
|
|
|
if(SprintLoopingSound != none && !SprintAkComponent.IsPlaying(SprintLoopingSound))
|
|
{
|
|
SprintAkComponent.PlayEvent(SprintLoopingSound, true, true);
|
|
bPlayingSprintLoop = true;
|
|
}
|
|
}
|
|
else if (bPlayingSprintLoop && (!bIsSprinting || VSizeSQ(Velocity) <= 40000.f))
|
|
{
|
|
if (SprintLoopingSound != none && SprintAkComponent.IsPlaying(SprintLoopingSound))
|
|
{
|
|
SprintAkComponent.StopEvents();
|
|
bPlayingSprintLoop = false;
|
|
}
|
|
|
|
if (StopSprintingSound != none && !SprintAkComponent.IsPlaying(StopSprintingSound))
|
|
{
|
|
SprintAkComponent.PlayEvent(StopSprintingSound, true, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Character Info Methods
|
|
********************************************************************************************* */
|
|
|
|
simulated function SetCharacterArch(KFCharacterInfoBase Info, optional bool bForce)
|
|
{
|
|
super.SetCharacterArch(Info, bForce);
|
|
PlayExtraVFX('AlwaysOn');
|
|
}
|
|
|
|
native function KFCharacterInfo_Monster GetCharacterMonsterInfo();
|
|
|
|
/** If true, assign custom player controlled skin when available */
|
|
simulated event bool UsePlayerControlledZedSkin()
|
|
{
|
|
return bVersusZed;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Movement Methods
|
|
********************************************************************************************* */
|
|
|
|
function float GetBlockingSprintSpeedModifier()
|
|
{
|
|
return BlockSprintSpeedModifier;
|
|
}
|
|
|
|
/** Set a desired movement speed adjustment that will blend in over time */
|
|
function AdjustMovementSpeed( float SpeedAdjust )
|
|
{
|
|
DesiredAdjustedGroundSpeed = default.GroundSpeed * SpeedAdjust * InitialGroundSpeedModifier;
|
|
|
|
if( IsDoingSpecialMove() )
|
|
{
|
|
DesiredAdjustedSprintSpeed = fMax( default.SprintSpeed * SpeedAdjust * InitialGroundSpeedModifier * SpecialMoves[SpecialMove].GetSprintSpeedModifier(), DesiredAdjustedGroundSpeed );
|
|
}
|
|
else
|
|
{
|
|
DesiredAdjustedSprintSpeed = fMax( default.SprintSpeed * SpeedAdjust * InitialGroundSpeedModifier, DesiredAdjustedGroundSpeed );
|
|
}
|
|
|
|
if (bPlayPanicked)
|
|
{
|
|
// Make sure groundspeed is in "walk" range so animtree can play correct panic anim
|
|
DesiredAdjustedGroundSpeed = Min(DesiredAdjustedGroundSpeed, MovementAnimNode.Constraints[1]);
|
|
}
|
|
|
|
NormalGroundSpeed = DesiredAdjustedGroundSpeed;
|
|
NormalSprintSpeed = DesiredAdjustedSprintSpeed;
|
|
}
|
|
|
|
/** Overridden to cause slight camera shakes when walking. */
|
|
simulated event PlayFootStepSound(int FootDown)
|
|
{
|
|
if( WorldInfo.NetMode == NM_DedicatedServer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Super.PlayFootStepSound(FootDown);
|
|
|
|
// The Zed has footstep notifies in one or more of his Idle anim sequences, where it kind of shuffles his foot as he shifts his weight.
|
|
// Changed the IsDoingLatentMoveCheck() to a velocity check, so player-controlled zeds trigger camera shake as well.
|
|
if( Physics == PHYS_Walking
|
|
&& Base != none
|
|
&& Mesh.RootMotionMode == RMM_Ignore
|
|
&& FootstepCameraShake != none
|
|
&& VSizeSQ(Velocity) >= 10000.f )
|
|
{
|
|
class'Camera'.static.PlayWorldCameraShake(FootstepCameraShake, self, Location, FootstepCameraShakeInnerRadius, FootstepCameraShakeOuterRadius, 1.3f, true);
|
|
}
|
|
}
|
|
|
|
event SpiderBumpLevel( vector HitLocation, vector HitNormal, optional actor Wall );
|
|
|
|
simulated event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
|
|
{
|
|
Super.Bump( Other, OtherComp, HitNormal );
|
|
|
|
if( SpecialMove != SM_None )
|
|
{
|
|
SpecialMoves[SpecialMove].NotifyBump( Other, HitNormal );
|
|
}
|
|
|
|
// Check for a midair bump
|
|
if( Physics == Phys_Falling && JumpBumpDamageType != none && Other.GetTeamNum() != GetTeamNum() && VSizeSq2D(Velocity) > Square(GroundSpeed * 1.1) )
|
|
{
|
|
Other.TakeDamage( MeleeAttackHelper.BaseDamage, Controller, Other.Location, Normal(Velocity), JumpBumpDamageType );
|
|
}
|
|
}
|
|
|
|
|
|
/** Called from AICommand_MoveToGoal, notifies us that we've bumped another KFPawn */
|
|
function HandleMonsterBump( KFPawn_Monster Other, Vector HitNormal )
|
|
{
|
|
local KFPlayerController KFPC;
|
|
|
|
if( !Other.IsNapalmInfected() && CanNapalmInfect(KFPC) )
|
|
{
|
|
InfectWithNapalm( Other, KFPC );
|
|
}
|
|
}
|
|
|
|
/** Override to handle special berserker functionality */
|
|
event HitWall( vector HitNormal, actor Wall, PrimitiveComponent WallComp )
|
|
{
|
|
local KFDoorActor Door;
|
|
|
|
if (IsHumanControlled())
|
|
{
|
|
if (!Wall.bStatic && IsAliveAndWell())
|
|
{
|
|
Door = KFDoorActor(Wall);
|
|
if (Door != none)
|
|
{
|
|
TryDestroyDoor(Door);
|
|
}
|
|
}
|
|
}
|
|
|
|
`AILog_Ext( GetFuncName()$"() Wall: "$Wall, 'BumpEvent', MyKFAIC );
|
|
// Call our special notification
|
|
NotifyCollideWithActor(HitNormal, Wall);
|
|
Super.HitWall(HitNormal, Wall, WallComp);
|
|
}
|
|
|
|
/** Allows pawn to handle door bump events instead of controller */
|
|
function bool HandleAIDoorBump(KFDoorActor Door)
|
|
{
|
|
return TryDestroyDoor(Door);
|
|
}
|
|
|
|
/** Destroy unwelded doors instantly when there are few players remaining if I'm a boss */
|
|
function bool TryDestroyDoor(KFDoorActor Door)
|
|
{
|
|
if (IsABoss() && Door != none && !Door.bIsDoorOpen && !Door.bIsDestroyed && Door.WeldIntegrity == 0 && CanObliterateDoors())
|
|
{
|
|
Door.IncrementHitCount(self);
|
|
Door.DestroyDoor(Controller);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Determines if this pawn can plow through doors */
|
|
function bool CanObliterateDoors()
|
|
{
|
|
// We only want the kool-aid man effect if we're sprinting
|
|
if (IsABoss() && !bIsSprinting)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Only allow door obliteration if there is one player remaining in a multiplayer game
|
|
return true;
|
|
}
|
|
|
|
/** Notification that Zed collided with an actor */
|
|
function bool NotifyCollideWithActor( Vector HitNormal, Actor Other )
|
|
{
|
|
local KFDestructibleActor KFDA;
|
|
|
|
if( !Other.bStatic && IsHumanControlled() )
|
|
{
|
|
KFDA = KFDestructibleActor( Other );
|
|
if( KFDA != none )
|
|
{
|
|
HandleDestructibleBump( KFDA, HitNormal );
|
|
}
|
|
}
|
|
|
|
`AILog_Ext( GetFuncName()$"() Other: "$Other, 'BumpEvent', MyKFAIC );
|
|
return false;
|
|
}
|
|
|
|
function HandleDestructibleBump( KFDestructibleActor Destructible, vector HitNormal )
|
|
{
|
|
Destructible.BumpedByMonster( self, HitNormal );
|
|
}
|
|
|
|
simulated event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal)
|
|
{
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.Touch( Other, OtherComp, HitLocation, HitNormal);
|
|
}
|
|
Super.Touch(Other, OtherComp, HitLocation, HitNormal);
|
|
}
|
|
|
|
function SetSprinting( bool bNewSprintStatus )
|
|
{
|
|
if( MyKFAIC != none )
|
|
{
|
|
if( !MyKFAIC.CanSetSprinting(bNewSprintStatus) && !bSprintOverride)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Disallow sprinting if blocking
|
|
if( bIsBlocking && IsHumanControlled() )
|
|
{
|
|
if( bIsSprinting )
|
|
{
|
|
bNewSprintStatus = false;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
super.SetSprinting(bNewSprintStatus);
|
|
}
|
|
|
|
/** Haven't seen this happen in a long time - logging it in case it does occur again */
|
|
event StuckOnPawn (Pawn OtherPawn)
|
|
{
|
|
JumpOffPawn();
|
|
if( MyKFAIC != none )
|
|
{
|
|
`AILog_Ext( self$" StuckOnPawn event at "$Location$" Base: "$Base,, MyKFAIC );
|
|
MyKFAIC.DumpCommandStack();
|
|
MyKFAIC.DumpCommandHistory();
|
|
}
|
|
}
|
|
|
|
/** Monster jumped */
|
|
function bool DoJump( bool bUpdating )
|
|
{
|
|
local vector JumpVelocity;
|
|
local rotator ViewRotation;
|
|
local vector ViewDirection;
|
|
|
|
if (bCanJump)
|
|
{
|
|
if (IsHumanControlled())
|
|
{
|
|
if (IsDoingSpecialMove())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ViewRotation = GetViewRotation();
|
|
ViewDirection = Normal(Vector(ViewRotation));
|
|
|
|
if (bJumpCapable && !bIsCrouched && !bWantsToCrouch && (Physics == PHYS_Walking || Physics == PHYS_Ladder || Physics == PHYS_Spider))
|
|
{
|
|
if (Physics == PHYS_Spider)
|
|
`if(`__TW_PATHFINDING_)
|
|
Velocity = Velocity + (GetJumpZ() * Floor);
|
|
`else
|
|
Velocity = GetJumpZ() * Floor;
|
|
`endif
|
|
else if (Physics == PHYS_Ladder)
|
|
Velocity.Z = 0;
|
|
else if (bIsWalking)
|
|
{
|
|
Velocity.Z = default.JumpZ;
|
|
}
|
|
else
|
|
{
|
|
if (bIsSprinting && bHasExtraSprintJumpVelocity)
|
|
{
|
|
JumpVelocity = GetSprintJumpVelocity(ViewDirection);
|
|
JumpVelocity.Z = GetJumpZ();
|
|
Velocity += JumpVelocity;
|
|
}
|
|
else
|
|
{
|
|
Velocity.Z = GetJumpZ();
|
|
}
|
|
}
|
|
if (Base != None && !Base.bWorldGeometry && Base.Velocity.Z > 0.f)
|
|
{
|
|
Velocity.Z += Base.Velocity.Z;
|
|
}
|
|
SetPhysics(PHYS_Falling);
|
|
bJumped = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return super.DoJump(bUpdating);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns the Z force used for a jump. Different move/physics states can return different values. */
|
|
simulated function float GetJumpZ()
|
|
{
|
|
return JumpZ;
|
|
}
|
|
|
|
/** Returns the sprint jump velocity, used in modifying jump mechanics while sprinting */
|
|
simulated function vector GetSprintJumpVelocity( vector ViewDirection )
|
|
{
|
|
return ViewDirection * GetJumpZ() * GetDirectionalJumpScale();
|
|
}
|
|
|
|
/** Determines how much a directional jump's velocity is scaled */
|
|
simulated function float GetDirectionalJumpScale()
|
|
{
|
|
return 1.f;
|
|
}
|
|
|
|
/** Overridden to access specific bumping damage types like the fleshpounds enraged hit */
|
|
function class<KFDamageType> GetBumpAttackDamageType();
|
|
|
|
/** Overridden to prevent falling damage for Zeds */
|
|
function TakeFallingDamage()
|
|
{
|
|
}
|
|
|
|
event Landed( vector HitNormal, Actor FloorActor )
|
|
{
|
|
local Controller StoredLastHitBy;
|
|
local int SMIndex;
|
|
|
|
StoredLastHitBy = LastHitBy;
|
|
|
|
if( bRestoreCollisionOnLand )
|
|
{
|
|
bRestoreCollisionOnLand = false;
|
|
SetCollisionSize( default.CylinderComponent.CollisionRadius, default.CylinderComponent.CollisionHeight );
|
|
FitCollision();
|
|
}
|
|
|
|
if( IsHumanControlled() )
|
|
{
|
|
if( bJumped && IsLocallyControlled() )
|
|
{
|
|
SMIndex = SpecialMoveCooldowns.Find( 'SMHandle', SM_Jump );
|
|
if( SMIndex != INDEX_NONE )
|
|
{
|
|
SpecialMoveCooldowns[SMIndex].LastUsedTime = WorldInfo.TimeSeconds;
|
|
}
|
|
}
|
|
|
|
bJumped = false;
|
|
}
|
|
|
|
super.Landed( HitNormal, FloorActor );
|
|
|
|
LastHitBy = StoredLastHitBy;
|
|
}
|
|
|
|
function SetMovementPhysics()
|
|
{
|
|
// do not change when in special move - special move will reset when it's ready
|
|
if( Physics == PHYS_None && IsDoingSpecialMove() )
|
|
return;
|
|
|
|
super.SetMovementPhysics();
|
|
}
|
|
|
|
/** Implements bKnockdownWhenJumpedOn */
|
|
function CrushedBy(Pawn OtherPawn)
|
|
{
|
|
local KFGameInfo KFGI;
|
|
local KFPlayerController_WeeklySurvival KFPC_WS;
|
|
local KFPawn_Human KFPH;
|
|
local KFPlayerController KFPC;
|
|
local KFPlayerReplicationInfo KFPRI;
|
|
|
|
KFPH = KFPawn_Human(OtherPawn);
|
|
|
|
if (KFPH != none)
|
|
{
|
|
/*
|
|
* For summer season event, we wanto to know if we stomp a zed.
|
|
* Adding the notify here will prevent more network data.
|
|
* DmgType_Crushed is enough for it, no need to add the goompa.
|
|
*/
|
|
KFPC = KFPlayerController(KFPH.Controller);
|
|
if (KFPC != none)
|
|
{
|
|
KFPC.NotifyHitGiven(class'DmgType_Crushed');
|
|
}
|
|
|
|
///
|
|
|
|
KFGI = KFGameInfo(WorldInfo.Game);
|
|
if (KFGI != none) // Only for players
|
|
{
|
|
if (KFGI.OutbreakEvent.ActiveEvent.bGoompaJumpEnabled)
|
|
{
|
|
OtherPawn.Velocity = OtherPawn.Velocity * vect(1,1,0);
|
|
OtherPawn.AddVelocity( vect(0,0,1) * KFGI.OutbreakEvent.ActiveEvent.GoompaJumpImpulse, Instigator.Location, none);
|
|
TakeDamage( KFGI.OutbreakEvent.ActiveEvent.GoompaJumpDamage, OtherPawn.Controller,Location, vect(0,0,0) , class'KFDT_GoompaStomp');
|
|
|
|
KFPC_WS = KFPlayerController_WeeklySurvival(OtherPawn.Controller);
|
|
if(KFPC_WS != none)
|
|
{
|
|
KFPC_WS.UpdateGoompaStreak();
|
|
}
|
|
|
|
// Registering awards information.
|
|
`RecordAARIntStat(KFPC, STOMP_GIVEN, 1);
|
|
KFPRI = KFPlayerReplicationInfo(KFPC.PlayerReplicationInfo);
|
|
if (KFPRI != none)
|
|
{
|
|
KFPRI.ZedStomps++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Super.CrushedBy(OtherPawn);
|
|
|
|
if ( bKnockdownWhenJumpedOn
|
|
// Still alive after crush damage
|
|
&& Health > 0
|
|
// Not emerging
|
|
&& !IsDoingSpecialMove(SM_Emerge)
|
|
// Actually above and not a side-swipe
|
|
&& ((OtherPawn.Location.Z - Location.Z) > (OtherPawn.CylinderComponent.CollisionHeight + CylinderCOmponent.CollisionHeight))
|
|
// Opposing team; player only
|
|
&& !IsHumanControlled()
|
|
&& GetTeamNum() != OtherPawn.GetTeamNum()
|
|
)
|
|
{
|
|
HandleAfflictionsOnHit(OtherPawn.Controller, vect(1,1,1), class'KFDT_Knockdown', OtherPawn);
|
|
}
|
|
}
|
|
|
|
/** Sets the speed scale this zed will use in zed time */
|
|
simulated function SetZedTimeSpeedScale( float SpeedScale )
|
|
{
|
|
if( SpeedScale > 0.f )
|
|
{
|
|
bMovesFastInZedTime = true;
|
|
ZedTimeSpeedScale = SpeedScale;
|
|
}
|
|
else
|
|
{
|
|
bMovesFastInZedTime = false;
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Combat Methods
|
|
********************************************************************************************* */
|
|
|
|
/** Override to handle cloaking */
|
|
native function bool IsValidEnemyTargetFor(const PlayerReplicationInfo PRI, bool bNoPRIisEnemy);
|
|
|
|
/** Is test location within charge range? */
|
|
native function bool InChargeRange( const Vector TestLocation );
|
|
/** Is test location within melee range? */
|
|
native function bool InMeleeRange( const Vector TestLocation, optional name AttackTag );
|
|
native function bool InAttackTagRange( const name AttackTag, const Vector TestLocation );
|
|
native function bool InAnyAttackTagRange( const Vector TestLocation, out name outAttackTag );
|
|
/** SERVER ONLY: Gets current enemy, to avoid having to get it from the controller */
|
|
native function KFPawn GetEnemy();
|
|
/** Checks to see if area between CheckLocation and pawn is clear for combat */
|
|
native final function bool IsLocationValidForCombat( KFPawn CheckPawn, const vector CheckLocation );
|
|
|
|
/** Used by subclass to implement rage mode */
|
|
simulated function bool SetEnraged(bool bNewEnraged)
|
|
{
|
|
if (!bCanRage)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
if (bNewEnraged == bIsEnraged)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bNewEnraged && ShouldBeWandering())
|
|
{
|
|
bRageAfterPanic = true;
|
|
return false;
|
|
}
|
|
|
|
bIsEnraged = bNewEnraged;
|
|
|
|
// End blocking on rage
|
|
if (IsDoingSpecialMove(SM_Block))
|
|
{
|
|
EndSpecialMove();
|
|
}
|
|
|
|
// Sprint right away if we're AI
|
|
if (!IsHumanControlled())
|
|
{
|
|
SetSprinting(bNewEnraged);
|
|
}
|
|
}
|
|
|
|
if (WorldInfo.NetMode != NM_DedicatedServer && !IsHumanControlled())
|
|
{
|
|
if (bNewEnraged)
|
|
{
|
|
PlayExtraVFX('Enraged');
|
|
}
|
|
else
|
|
{
|
|
StopExtraVFX('Enraged');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Used by subclass to check rage mode */
|
|
simulated event bool IsEnraged();
|
|
|
|
/** Notify the AI controller that we've taken damage from a friendly */
|
|
function NotifyFriendlyAIDamageTaken( Controller DamagerController, int Damage, Actor DamageCauser, class<KFDamageType> DamageType )
|
|
{
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.NotifyFriendlyAIDamageTaken( DamagerController, Damage, DamageCauser, DamageType );
|
|
}
|
|
}
|
|
|
|
/** Notification called from Pawn animation, by KFAnimNotify_MeleeImpact.
|
|
* Network: Server Only
|
|
*/
|
|
simulated function MeleeImpactNotify(KFAnimNotify_MeleeImpact Notify)
|
|
{
|
|
if ( MeleeAttackHelper != None )
|
|
{
|
|
MeleeAttackHelper.bHasAlreadyHit = false; // reset attack
|
|
MeleeAttackHelper.MeleeImpactNotify(Notify);
|
|
}
|
|
|
|
// check to see if our enemy was killed
|
|
if ( MyKFAIC != none && MyKFAIC.Enemy != None && MyKFAIC.Enemy.Health <= 0 )
|
|
{
|
|
ClearHeadTrackTarget(Controller.Enemy);
|
|
// TODO: Below command aborts could be problematic, but this code is pretty old- keep an eye on it
|
|
MyKFAIC.AbortCommand( None, class'AICommand_Attack_Grab' );
|
|
MyKFAIC.AbortCommand( None, class'AICommand_Attack_Melee' );
|
|
// Start up a taunt kill special move (used when enemy killed, as opposed to regular taunts)
|
|
class'AICommand_TauntEnemy'.static.Taunt( KFAIController( Controller ),, TAUNT_EnemyKilled );
|
|
}
|
|
}
|
|
|
|
/** Called from MeleeHelper when damage is done to another pawn */
|
|
function NotifyMeleeDamageDealt();
|
|
|
|
/** Am I within range to perform an attack in the AttackTag category? (see PawnAnimInfo) */
|
|
event bool IsInAttackTagRange( vector TestLocation, name AttackTag )
|
|
{
|
|
local vector2D MinMaxRange;
|
|
local float DistSq;
|
|
|
|
if( MyKFAIC == none || PawnAnimInfo == none )
|
|
{
|
|
MinMaxRange = PawnAnimInfo.GetAttackRangeByName( AttackTag );
|
|
DistSq = VSizeSq( TestLocation - Location );
|
|
|
|
if( (DistSq > (MinMaxRange.X * MinMaxRange.X)) && (DistSq < (MinMaxRange.Y * MinMaxRange.Y)) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Am I within range to perform any attack in my PawnAnimInfo? */
|
|
event bool IsInAnyAttackTagRange( vector TestLocation, out name outAttackTag )
|
|
{
|
|
local int idx;
|
|
|
|
if( MyKFAIC == none || PawnAnimInfo == none )
|
|
{
|
|
outAttackTag = '';
|
|
return false;
|
|
}
|
|
|
|
for( idx = 0; idx < PawnAnimInfo.Attacks.Length; idx++ )
|
|
{
|
|
if( PawnAnimInfo.Attacks[idx].Tag == '' || PawnAnimInfo.Attacks[idx].Chance <= 0.f )
|
|
{
|
|
continue;
|
|
}
|
|
if( IsInAttackTagRange(TestLocation, PawnAnimInfo.Attacks[idx].Tag) )
|
|
{
|
|
outAttackTag = PawnAnimInfo.Attacks[idx].Tag;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
outAttackTag = '';
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Network: Local Player
|
|
*
|
|
* @param FireModeNum fire mode number
|
|
*/
|
|
simulated function StartPlayerZedMove(ESpecialMove Move)
|
|
{
|
|
local byte SMFlags;
|
|
|
|
if( Move != SM_None )
|
|
{
|
|
// Make sure attack is off cooldown
|
|
if( GetSpecialMoveCooldownTimeRemaining(Move) > 0.f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Tell our currently-running special move to retrigger the button press if it was released mid-move
|
|
if( SpecialMove == Move )
|
|
{
|
|
SpecialMoves[SpecialMove].SpecialMoveButtonRetriggered();
|
|
}
|
|
else if( CanDoSpecialMove(Move) )
|
|
{
|
|
SMFlags = SpecialMoveHandler.SpecialMoveClasses[Move].static.PackFlagsBase(self);
|
|
DoSpecialMove(Move, true, InteractionPawn, SMFlags);
|
|
if ( Role < ROLE_Authority && IsDoingSpecialMove(Move) )
|
|
{
|
|
ServerDoSpecialMove(Move, true, InteractionPawn, SMFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Network: Local Player
|
|
*
|
|
* @param FireModeNum fire mode number
|
|
*/
|
|
simulated function StopPlayerZedMove(ESpecialMove Move)
|
|
{
|
|
if( !IsDoingSpecialMove() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// notify move that button has been released (for held moves)
|
|
if( SpecialMove == Move )
|
|
{
|
|
SpecialMoves[Move].SpecialMoveButtonReleased();
|
|
}
|
|
}
|
|
|
|
/** Called from KFSpecialMove::SpecialMoveEnded */
|
|
simulated function NotifySpecialMoveEnded( KFSpecialMove FinishedMove, ESpecialMove SMHandle )
|
|
{
|
|
local int SMIndex;
|
|
|
|
if( IsHumanControlled() && IsLocallyControlled() )
|
|
{
|
|
SMIndex = SpecialMoveCooldowns.Find( 'SMHandle', SMHandle );
|
|
if( SMIndex != INDEX_NONE )
|
|
{
|
|
SpecialMoveCooldowns[SMIndex].LastUsedTime = WorldInfo.TimeSeconds;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns cooldown time of a special move */
|
|
function float GetSpecialMoveCooldownPercent( const out SpecialMoveCooldownInfo Cooldown )
|
|
{
|
|
local float CDTime;
|
|
|
|
if( Cooldown.SMHandle != SM_None)
|
|
{
|
|
if( Cooldown.LastUsedTime > 0.f && Cooldown.CooldownTime > 0.f )
|
|
{
|
|
CDTime = (!bEmpDisrupted) ? Cooldown.CooldownTime : AfflictionHandler.GetAfflictionDuration(AF_EMP);
|
|
return fClamp( (WorldInfo.TimeSeconds - Cooldown.LastUsedTime) / CDTime, 0.f, 1.f );
|
|
}
|
|
}
|
|
|
|
return 1.f;
|
|
}
|
|
|
|
/** Returns amount of time remaining in cooldown */
|
|
function float GetSpecialMoveCooldownTimeRemaining( ESpecialMove SMHandle )
|
|
{
|
|
local float CDTime;
|
|
local int i;
|
|
|
|
i = SpecialMoveCooldowns.Find( 'SMHandle', SMHandle );
|
|
if( i != INDEX_NONE )
|
|
{
|
|
if( SpecialMoveCooldowns[i].LastUsedTime > 0.f && SpecialMoveCooldowns[i].CooldownTime > 0.f )
|
|
{
|
|
CDTime = (!bEmpDisrupted) ? SpecialMoveCooldowns[i].CooldownTime : AfflictionHandler.GetAfflictionDuration(AF_EMP);
|
|
return CDTime - fMin( WorldInfo.TimeSeconds - SpecialMoveCooldowns[i].LastUsedTime, CDTime );
|
|
}
|
|
|
|
// if 1st use or no cooldown set return 0 remaining time
|
|
return 0.f;
|
|
}
|
|
|
|
return 100.f; // invalid move
|
|
}
|
|
|
|
/** Allows access to the cooldowns array without being able to modify it */
|
|
simulated function array<SpecialMoveCooldownInfo> GetSpecialMoveCooldowns()
|
|
{
|
|
return SpecialMoveCooldowns;
|
|
}
|
|
|
|
/** Puts all moves on this pawn on cooldown */
|
|
function PutAllMovesOnCooldown()
|
|
{
|
|
local int i;
|
|
|
|
if( IsHumanControlled() && IsLocallyControlled() )
|
|
{
|
|
for( i = 0; i < SpecialMoveCooldowns.Length; ++i )
|
|
{
|
|
if( SpecialMoveCooldowns[i].SMHandle != SM_None )
|
|
{
|
|
SpecialMoveCooldowns[i].LastUsedTime = WorldInfo.TimeSeconds;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns TRUE if we should use adjusted controller sensitivity */
|
|
simulated function bool UseAdjustedControllerSensitivity();
|
|
|
|
/** Returns TRUE if this zed can block attacks */
|
|
function bool CanBlock()
|
|
{
|
|
// Make sure this zed is allowed to block first
|
|
if( Physics == PHYS_Walking
|
|
&& CanDoSpecialMove(SM_Block)
|
|
&& IsCombatCapable() )
|
|
{
|
|
// Check chance
|
|
if( fRand() > DifficultyBlockSettings.Chance * (WorldInfo.Game.NumPlayers == 1 ? DifficultyBlockSettings.SoloChanceMultiplier : 1.f) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Return cooldown status
|
|
return `TimeSince(LastBlockTime) >= DifficultyBlockSettings.Cooldown;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Returns the block settings for the current difficulty */
|
|
simulated function SetBlockSettings( const sBlockInfo NewBlockSettings )
|
|
{
|
|
DifficultyBlockSettings = NewBlockSettings;
|
|
}
|
|
|
|
/** Returns the block settings for the current difficulty */
|
|
simulated function sBlockInfo GetBlockSettings()
|
|
{
|
|
return DifficultyBlockSettings;
|
|
}
|
|
|
|
/** Returns the minimum field of view required to detect a block */
|
|
simulated function float GetMinBlockFOV()
|
|
{
|
|
return MinBlockFOV;
|
|
}
|
|
|
|
/** Reduce affliction power when blocking */
|
|
simulated function AdjustAffliction( out float AfflictionPower )
|
|
{
|
|
if( Role == ROLE_Authority && bIsBlocking )
|
|
{
|
|
AfflictionPower *= DifficultyBlockSettings.AfflictionModifier;
|
|
}
|
|
|
|
super.AdjustAffliction( AfflictionPower );
|
|
}
|
|
|
|
/** Used by subclasses to determine if the boss icon can be rendered */
|
|
function bool ShouldDrawBossIcon()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Damage/Death Methods
|
|
********************************************************************************************* */
|
|
|
|
/*
|
|
* PlayDying() is called upon death
|
|
* Network: ALL
|
|
*/
|
|
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local string ClassName;
|
|
|
|
Timer_EndRallyBoost();
|
|
|
|
super.PlayDying(DamageType, HitLoc);
|
|
|
|
//Shared functionality for boss death
|
|
if (IsABoss() && KFGameReplicationInfo(WorldInfo.GRI).ShouldSetBossCamOnBossDeath())
|
|
{
|
|
KFPC = KFPlayerController(GetALocalPlayerController());
|
|
if(KFPC != none)
|
|
{
|
|
KFPC.SetBossCamera( self );
|
|
}
|
|
|
|
//@HSL_BEGIN - JRO - 5/17/2016 - PS4 Activity Feeds
|
|
ClassName = string(Class.Name);
|
|
ClassName -= '_Versus';
|
|
class'GameEngine'.static.GetOnlineSubsystem().PlayerInterfaceEx.PostActivityFeedBossKill(GetLocalizedName(), ClassName, WorldInfo.GetMapName(true));
|
|
//@HSL_END
|
|
}
|
|
|
|
if (bUseExplosiveDeath)
|
|
{
|
|
PlayExplosiveDeath();
|
|
}
|
|
|
|
if (bUseDamageInflation)
|
|
{
|
|
PlayInflationDeath();
|
|
}
|
|
|
|
UpdateBleedIncapFX();
|
|
|
|
StopExtraVFX(`NAME_NONE);
|
|
}
|
|
|
|
simulated function PlayInflationDeath()
|
|
{
|
|
local int Idx;
|
|
|
|
for (Idx = 0; Idx < Mesh.PhysicsAssetInstance.Bodies.Length; ++Idx)
|
|
{
|
|
Mesh.PhysicsAssetInstance.Bodies[Idx].CustomGravityFactor = InflateDeathGravity;
|
|
|
|
if (InflationExplosionTimer > 0)
|
|
{
|
|
SetTimer(InflationExplosionTimer, false, 'InflationExplode');
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function PlayExplosiveDeath()
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
if (GoreManager != none && GoreManager.AllowMutilation())
|
|
{
|
|
HandlePartialGoreAndGibs(class'KFDT_Explosive_Sacrifice', Location, vect(0, 0, 0), 'hips', true);
|
|
GoreManager.SpawnObliterationBloodEffect(self);
|
|
}
|
|
else
|
|
{
|
|
HandlePartialGoreAndGibs(class'KFDT_Explosive_Sacrifice', Location, vect(0, 0, 0), 'hips', false);
|
|
}
|
|
}
|
|
|
|
simulated function InflationExplode()
|
|
{
|
|
local int Idx;
|
|
|
|
RepDamageInflateParam = 0.f;
|
|
bUseDamageInflation = false;
|
|
|
|
for (Idx = 0; Idx < Mesh.PhysicsAssetInstance.Bodies.Length; ++Idx)
|
|
{
|
|
Mesh.PhysicsAssetInstance.Bodies[Idx].CustomGravityFactor = 1.0;
|
|
}
|
|
|
|
PlayExplosiveDeath();
|
|
|
|
if ( WorldInfo.NetMode != NM_DedicatedServer )
|
|
{
|
|
UpdateVisualInflation(GetCurrentInflation() * 2.0);
|
|
}
|
|
}
|
|
|
|
State Dying
|
|
{
|
|
event OnSleepRBPhysics()
|
|
{
|
|
local int i;
|
|
|
|
// Shrink shadow cull distance when dead. We don't do this for characters because they
|
|
// have extra cosmetics and custom head components. Must reattach component so render data gets updated.
|
|
Mesh.PerObjectShadowCullDistance *= 0.6;
|
|
ReattachComponent(Mesh);
|
|
|
|
//Reattach monster PACs
|
|
for (i = 0; i < `MAX_COSMETIC_ATTACHMENTS; ++i)
|
|
{
|
|
if (ThirdPersonAttachments[i] != None)
|
|
{
|
|
ThirdPersonAttachments[i].PerObjectShadowCullDistance *= 0.6;
|
|
ReattachComponent(ThirdPersonAttachments[i]);
|
|
}
|
|
}
|
|
|
|
Super.OnSleepRBPhysics();
|
|
}
|
|
event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
|
|
{
|
|
local KFPawn_Human KFPH;
|
|
super.TakeDamage( Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser );
|
|
|
|
if( InstigatedBy != none && InstigatedBy.Pawn != none )
|
|
{
|
|
// beat dead horse
|
|
KFPH = KFPawn_Human( InstigatedBy.Pawn );
|
|
if( KFPH != none )
|
|
{
|
|
`DialogManager.PlayBeatDeadHorseDialog( KFPH, self );
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV )
|
|
{
|
|
local PlayerController PC;
|
|
local matrix HeadMatrix;
|
|
local vector HeadLoc;
|
|
//local rotator HeadRot;
|
|
|
|
PC = GetALocalPlayerController();
|
|
|
|
if( PC != none && !PC.IsSpectating() && PC.ViewTarget == self )
|
|
{
|
|
HeadMatrix = Mesh.GetBoneMatrix( Mesh.MatchRefBone('head') );
|
|
HeadLoc = MatrixGetOrigin( HeadMatrix );
|
|
//HeadMatrix = MakeRotationMatrix( rot(0, -16383, 16383) ) * HeadMatrix;
|
|
//HeadRot = MatrixGetRotator( HeadMatrix );
|
|
//DrawDebugLine(HeadLoc, HeadLoc + 100*vector(HeadRot), 0, 255, 0);
|
|
|
|
//out_CamLoc = VInterpTo( out_CamLoc, HeadLoc, fDeltaTime, 10.0 );
|
|
out_CamRot = RInterpTo( out_CamRot, rotator(HeadLoc - out_CamLoc), fDeltaTime, 10.0 );
|
|
|
|
return true;
|
|
}
|
|
|
|
return Global.CalcCamera( fDeltaTime, out_CamLoc, out_CamRot, out_FOV );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Don't allow headshots to be counted for any purpose if we were headless before entering the takedamage state
|
|
* NOTE: This addresses an issue with low gore where the head is not hidden after a headless event, allowing headshots
|
|
* to count when they shouldn't.
|
|
*/
|
|
function bool CanCountHeadshots()
|
|
{
|
|
return !bIsHeadless;
|
|
}
|
|
|
|
event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
|
|
{
|
|
local KFPawn_Human HumanInstigator;
|
|
local KFAIController_Monster AIMonster;
|
|
local class<KFDamageType> KFDT;
|
|
local KFPlayerController KFPC;
|
|
local KFPerk InstigatorPerk;
|
|
local KfPlayerReplicationInfo KFPRI;
|
|
local KFAIController KFAIC;
|
|
local KFPawn_Monster KFPM;
|
|
local float NapalmCheckDist;
|
|
|
|
AIMonster = KFAIController_Monster(InstigatedBy);
|
|
KFDT = class<KFDamageType>(DamageType);
|
|
KFPC = KFPlayerController(InstigatedBy);
|
|
if( KFPC != none )
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
|
|
KFAIC = KFAIController(Controller);
|
|
|
|
if( KFAIC != none && KFAIC.TimeFirstSawPlayer == 0 )
|
|
{
|
|
// If we took damage, count that as "seeing a player" for our purposes
|
|
KFAIC.TimeFirstSawPlayer = Worldinfo.TimeSeconds;
|
|
}
|
|
}
|
|
|
|
if( AIMonster != none && KFDT != none && !KFDT.default.bIgnoreZedOnZedScaling )
|
|
{
|
|
// Deal extra damage to other zeds if we have over 100 health
|
|
if( Health >= 100 )
|
|
{
|
|
Damage *= ( 2 - (HealthMax - Health ) / HealthMax );
|
|
}
|
|
}
|
|
|
|
if( Damage > 0 && InstigatorPerk != none && KFDT != none )
|
|
{
|
|
bCouldTurnIntoShrapnel = InstigatorPerk.CouldBeZedShrapnel( KFDT );
|
|
bCouldTurnIntoToxicExplosion = InstigatorPerk.CouldBeZedToxicCloud( KFDT );
|
|
}
|
|
|
|
super.TakeDamage( Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser );
|
|
|
|
if( InstigatedBy != none && InstigatedBy.Pawn != none )
|
|
{
|
|
HumanInstigator = KFPawn_Human( InstigatedBy.Pawn );
|
|
if( HumanInstigator != none )
|
|
{
|
|
HumanInstigator.ResetIdleStartTime();
|
|
}
|
|
}
|
|
|
|
if( Damage > 0
|
|
&& InstigatedBy != none
|
|
&& DamageCauser != none
|
|
&& KFDT != none
|
|
&& KFDT.default.DoT_Type == DOT_Fire
|
|
&& KFDT != class'KFDT_Fire_Napalm'
|
|
&& WorldInfo.RealTimeSeconds - LastNapalmInfectCheckTime > 0.25f )
|
|
{
|
|
if( KFPC != none
|
|
&& KFPC.GetPerk() != none
|
|
&& KFPC.GetPerk().CanSpreadNapalm()
|
|
&& class'KFPerk'.static.IsDamageTypeOnThisPerk(KFDT, class'KFPerk_Firebug') )
|
|
{
|
|
NapalmCheckDist = Square( CylinderComponent.CollisionRadius * class'KFPerk_Firebug'.static.GetNapalmCheckCollisionScale() );
|
|
foreach WorldInfo.AllPawns( class'KFPawn_Monster', KFPM, Location )
|
|
{
|
|
if( KFPM != self && KFPM.IsAliveAndWell() && !KFPM.IsNapalmInfected() )
|
|
{
|
|
if( VSizeSQ(Location - KFPM.Location) > NapalmCheckDist )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
InfectWithNapalm( KFPM, KFPC );
|
|
}
|
|
}
|
|
|
|
LastNapalmInfectCheckTime = WorldInfo.RealTimeSeconds;
|
|
|
|
// Make sure we're infected
|
|
if( !IsNapalmInfected() )
|
|
{
|
|
InfectWithNapalm( self, KFPC );
|
|
}
|
|
}
|
|
}
|
|
|
|
KFPRI = KFPlayerReplicationInfo( PlayerReplicationInfo );
|
|
if( KFPRI != none )
|
|
{
|
|
KFPRI.PlayerHealth = Health;
|
|
KFPRI.PlayerHealthPercent = FloatToByte( float(Health) / float(HealthMax) );
|
|
}
|
|
|
|
if (bUseDamageInflation)
|
|
{
|
|
IntendedDamageInflationPercent = float(Health) / float(HealthMax);
|
|
}
|
|
}
|
|
|
|
function AdjustDamageForArmor(out int InDamage, Controller InstigatedBy, class<DamageType> DamageType,
|
|
TraceHitInfo HitInfo, Actor DamageCauser)
|
|
{
|
|
local vector DamageSource;
|
|
|
|
// Let armor handle the full, unadjusted damage first
|
|
if (ArmorInfo != none)
|
|
{
|
|
//If the damage doesn't have a bone hit source, it's likely AoE. Split over all remaining armor evenly.
|
|
if (HitInfo.BoneName != '')
|
|
{
|
|
if (InstigatedBy != none && InstigatedBy.Pawn != none)
|
|
{
|
|
DamageSource = InstigatedBy.Pawn.Location;
|
|
}
|
|
else if (KFWeapon(DamageCauser) != none)
|
|
{
|
|
DamageSource = KFWeapon(DamageCauser).GetMuzzleLoc();
|
|
}
|
|
else
|
|
{
|
|
DamageSource = DamageCauser.Location;
|
|
}
|
|
|
|
ArmorInfo.AdjustBoneDamage(InDamage, HitInfo.BoneName, DamageSource, DamageType);
|
|
}
|
|
else
|
|
{
|
|
ArmorInfo.AdjustNonBoneDamage(InDamage, DamageType);
|
|
}
|
|
|
|
`log(self @ GetFuncName() @ " After armor adjustment Damage=" $ InDamage @ "Zone=" $ HitInfo.BoneName @ "DamageType=" $ DamageType, bLogTakeDamage);
|
|
}
|
|
}
|
|
|
|
function AdjustDamageForInstigator(out int InDamage, Controller InstigatedBy, class<DamageType> DamageType, Actor DamageCauser, int HitZoneIdx)
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local KFPawn_Human KFPH;
|
|
local KFPerk InstigatorPerk;
|
|
local KFPowerUp InstigatorPowerUp;
|
|
local KFGameInfo KFGI;
|
|
local float TempDamage;
|
|
|
|
// Let the instigator's perk adjust the damage
|
|
KFPC = KFPlayerController(InstigatedBy);
|
|
if (KFPC != none)
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
if (InstigatorPerk != none)
|
|
{
|
|
InstigatorPerk.ModifyDamageGiven(InDamage, DamageCauser, self, KFPC, class<KFDamageType>(DamageType), HitZoneIdx);
|
|
}
|
|
|
|
InstigatorPowerUp = KFPC.GetPowerUp();
|
|
if(InstigatorPowerUp != none)
|
|
{
|
|
InstigatorPowerUp.ModifyDamageGiven(InDamage, DamageCauser, self, KFPC, class<KFDamageType>(DamageType), HitZoneIdx);
|
|
}
|
|
|
|
KFGI = KFGameInfo(WorldInfo.Game);
|
|
if(KFGI != none && KFGI.OutbreakEvent != none)
|
|
{
|
|
KFGI.ModifyDamageGiven(InDamage, DamageCauser, self, KFPC, class<KFDamageType>(DamageType), HitZoneIdx);
|
|
}
|
|
|
|
if( KFPC.Pawn != none )
|
|
{
|
|
KFPH = KFPawn_Human(KFPC.Pawn);
|
|
if( KFPH != none )
|
|
{
|
|
TempDamage = InDamage;
|
|
TempDamage *= KFPH.GetHealingDamageBoostModifier();
|
|
InDamage = FCeil( TempDamage );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Disable falling damage, apply blocking modifier */
|
|
function AdjustDamage(out int InDamage, out vector Momentum, Controller InstigatedBy, vector HitLocation, class<DamageType> DamageType, TraceHitInfo HitInfo, Actor DamageCauser)
|
|
{
|
|
local int HitZoneIdx;
|
|
local int ExtraHeadDamage;
|
|
local class<KFDamageType> KFDT;
|
|
|
|
// Cached hit params
|
|
HitZoneIdx = HitZones.Find('ZoneName', HitInfo.BoneName);
|
|
|
|
AdjustDamageForInstigator(InDamage, InstigatedBy, DamageType, DamageCauser, HitZoneIdx);
|
|
|
|
AdjustDamageForArmor(InDamage, InstigatedBy, DamageType, HitInfo, DamageCauser);
|
|
|
|
Super.AdjustDamage(InDamage, Momentum, InstigatedBy, HitLocation, DamageType, HitInfo, DamageCauser);
|
|
|
|
if( DamageType.default.bCausedByWorld && ClassIsChildOf(DamageType, class'KFDT_Falling') )
|
|
{
|
|
InDamage = 0;
|
|
return;
|
|
}
|
|
|
|
// start with damage type resistance/vulnerability
|
|
InDamage *= GetDamageTypeModifier(DamageType);
|
|
|
|
// Applies rally damage resistance
|
|
GetRallyBoostResistance( InDamage );
|
|
|
|
// Apply blocking modifier
|
|
ApplyBlockingDamageModifier( InDamage, InstigatedBy, DamageType );
|
|
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
|
|
{
|
|
HitZoneIdx = HZI_HEAD;
|
|
}
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
|
|
// Do extra damage on blowing the head off
|
|
if( !bCheckingExtraHeadDamage && HitZoneIdx == HZI_Head &&
|
|
HitZones[HZI_Head].GoreHealth > 0 && InDamage > HitZones[HZI_Head].GoreHealth )
|
|
{
|
|
KFDT = class<KFDamageType>(DamageType);
|
|
|
|
// For certain weapons do even more damage on blowing the head off
|
|
if( KFDT != none )
|
|
{
|
|
InDamage *= KFDT.default.HeadDestructionDamageScale;
|
|
}
|
|
|
|
ExtraHeadDamage = InDamage + HealthMax * 0.25;
|
|
|
|
bCheckingExtraHeadDamage = true;
|
|
AdjustDamage( ExtraHeadDamage, Momentum, InstigatedBy, HitLocation, DamageType, HitInfo, DamageCauser );
|
|
bCheckingExtraHeadDamage = false;
|
|
|
|
InDamage += ExtraHeadDamage;
|
|
}
|
|
|
|
// register damage to divide up money/XP/Score. This is done here instead of NotifyTakeHit,
|
|
// so that it's called on the killing blow
|
|
if( !bCheckingExtraHeadDamage && InstigatedBy != none )
|
|
{
|
|
AddTakenDamage( InstigatedBy, FMin(Health, InDamage), DamageCauser, class<KFDamageType>(DamageType) );
|
|
}
|
|
|
|
// If the head has been dismembered reduce damage to 1 (non-zero so play hit is called).
|
|
// Since zed is bleeding out already this is not as big of a issue.
|
|
if ( HitZoneIdx == HZI_HEAD && bIsHeadless )
|
|
{
|
|
InDamage = 1;
|
|
}
|
|
|
|
// Never allow damage to reach zero if coming from opposing team
|
|
if( InstigatedBy == none || InstigatedBy.GetTeamNum() != GetTeamNum() )
|
|
{
|
|
InDamage = Max( InDamage, 1 );
|
|
}
|
|
|
|
`log(self @ "Adjusted Monster Damage=" $ InDamage, bLogTakeDamage);
|
|
}
|
|
|
|
/** Returns damage multiplier for an incoming damage type @todo: c++?*/
|
|
function float GetDamageTypeModifier(class<DamageType> DT)
|
|
{
|
|
local int i;
|
|
local int DifficultyIdx;
|
|
local float DamageModifier;
|
|
|
|
if ( LiveDamageTypeModifiers.Length > 0 )
|
|
{
|
|
AppendLiveDamageTypeModifiers();
|
|
}
|
|
|
|
// reverse iteration so that most specific damage type can be last on the array
|
|
for (i = DamageTypeModifiers.Length - 1; i >= 0; --i)
|
|
{
|
|
if ( ClassIsChildOf(DT, DamageTypeModifiers[i].DamageType) )
|
|
{
|
|
// If specified assign difficulty based value
|
|
if ( WorldInfo.Game != None && DamageTypeModifiers[i].DamageScale.Length > 1 )
|
|
{
|
|
DifficultyIdx = Min(WorldInfo.Game.GetModifiedGameDifficulty(), DamageTypeModifiers[i].DamageScale.Length);
|
|
DamageModifier = DamageTypeModifiers[i].DamageScale[DifficultyIdx];
|
|
}
|
|
else
|
|
{
|
|
DamageModifier = DamageTypeModifiers[i].DamageScale[0];
|
|
}
|
|
|
|
// for resistances only, allow game info to modify (e.g. num players)
|
|
if ( DamageModifier < 1.f )
|
|
{
|
|
DamageModifier = FMax(Lerp(1.f, DamageModifier, GameResistancePct), 0.f);
|
|
}
|
|
|
|
`log(self@"Scaling damage taken from"@DT@"by"@DamageModifier, bLogTakeDamage);
|
|
return DamageModifier;
|
|
}
|
|
}
|
|
|
|
return 1.f;
|
|
}
|
|
|
|
/** Append (one-time) damage type modifiers from the live balance into the damage modifiers array */
|
|
function AppendLiveDamageTypeModifiers()
|
|
{
|
|
local int i;
|
|
|
|
for (i = 0; i < LiveDamageTypeModifiers.Length; i++)
|
|
{
|
|
if ( LiveDamageTypeModifiers[i].DamageType != None )
|
|
{
|
|
DamageTypeModifiers.AddItem(LiveDamageTypeModifiers[i]);
|
|
}
|
|
else
|
|
{
|
|
break; // finished once we find an empty entry
|
|
}
|
|
}
|
|
|
|
LiveDamageTypeModifiers.Length = 0;
|
|
}
|
|
|
|
/** Applies the blocking damage modifier to incoming damage */
|
|
function ApplyBlockingDamageModifier( out int Damage, Controller InstigatedBy, class<DamageType> damageType )
|
|
{
|
|
local vector DamageDirNormal;
|
|
|
|
if( !bIsBlocking || Damage <= 0 || InstigatedBy == none || InstigatedBy.Pawn == none || InstigatedBy.GetTeamNum() == GetTeamNum() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only block attacks that come from the front
|
|
DamageDirNormal = Normal( InstigatedBy.Pawn.Location - Location );
|
|
if( DamageDirNormal dot vector(Rotation) > MinBlockFOV )
|
|
{
|
|
// Melee damage first
|
|
if( ClassIsChildOf(damageType, class'KFDT_Bludgeon') || ClassIsChildOf(damageType, class'KFDT_Slashing') )
|
|
{
|
|
Damage = int( float(Damage) * DifficultyBlockSettings.MeleeDamageModifier );
|
|
}
|
|
else
|
|
{
|
|
Damage = int( float(Damage) * DifficultyBlockSettings.DamageModifier );
|
|
}
|
|
}
|
|
}
|
|
|
|
event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bRepairArmor=true, optional bool bMessageHealer=true)
|
|
{
|
|
`DialogManager.PlaySpotZedHealingDialog( self );
|
|
Super.HealDamage(Amount, Healer, DamageType);
|
|
return true;
|
|
}
|
|
|
|
/** Is this monster effected by a melee damage debuff? */
|
|
function bool HasReducedMeleeDamage()
|
|
{
|
|
return bHasReducedMeleeDamage;
|
|
}
|
|
|
|
/** sends any notifications to anything that needs to know this pawn has taken damage */
|
|
function NotifyTakeHit(Controller InstigatedBy, vector HitLocation, int Damage, class<DamageType> DamageType, vector Momentum, Actor DamageCauser)
|
|
{
|
|
local KFPawn_Human KFPH_Instigator;
|
|
local KFGameInfo KFGI;
|
|
|
|
Super.NotifyTakeHit(InstigatedBy, HitLocation, Damage, DamageType, Momentum, DamageCauser);
|
|
|
|
// continuous damage check needs to happen before we set LastPainTime and LastHitBy for current hit
|
|
if( InstigatedBy != none && InstigatedBy.Pawn != none )
|
|
{
|
|
KFPH_Instigator = KFPawn_Human( InstigatedBy.Pawn );
|
|
if( KFPH_Instigator != none )
|
|
{
|
|
`DialogManager.PlayDamageZedContinuousDialog( KFPH_Instigator, self );
|
|
}
|
|
}
|
|
|
|
// Allow special move to react to TakeHit/TakeDamage
|
|
if( SpecialMove != SM_None )
|
|
{
|
|
SpecialMoves[SpecialMove].NotifyOwnerTakeHit(class<KFDamageType>(damageType), HitLocation, Normal(Momentum), InstigatedBy);
|
|
}
|
|
|
|
KFGI = KFGameInfo(WorldInfo.Game);
|
|
if (KFGI != none)
|
|
{
|
|
KFGI.NotifyTakeHit(self, InstigatedBy, HitLocation, Damage, DamageType, Momentum, DamageCauser);
|
|
}
|
|
|
|
`AILog_Ext(GetFuncName()$"() Instigator:"$InstigatedBy$" DT: "$DamageType, 'Damage', MyKFAIC);
|
|
}
|
|
|
|
function PlayHit(float Damage, Controller InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, TraceHitInfo HitInfo)
|
|
{
|
|
local KFPawn_Human KFPH_Instigator;
|
|
|
|
if (ArmorInfo != none && ArmorInfo.LastTakeDamageTime == WorldInfo.TimeSeconds)
|
|
{
|
|
AdjustPlayHitForArmor(Damage, HitInfo);
|
|
}
|
|
|
|
super.PlayHit( Damage, InstigatedBy, HitLocation, damageType, Momentum, HitInfo );
|
|
|
|
// play damage zed dialog after Afflictions happen in super
|
|
if( InstigatedBy != none && InstigatedBy.Pawn != none )
|
|
{
|
|
KFPH_Instigator = KFPawn_Human( InstigatedBy.Pawn );
|
|
if( KFPH_Instigator != none )
|
|
{
|
|
`DialogManager.PlayDamagedZedDialog( KFPH_Instigator, self, DamageType );
|
|
}
|
|
}
|
|
}
|
|
|
|
function AdjustPlayHitForArmor(out float InDamage, out TraceHitInfo InHitInfo)
|
|
{
|
|
InDamage = 1;
|
|
InHitInfo.BoneName = 'KBArmor';
|
|
}
|
|
|
|
/** Called before, and in addition to, NotifyTakeHit(), but processes melee specifically (Server only) */
|
|
function NotifyMeleeTakeHit(Controller InstigatedBy, vector HitLocation);
|
|
|
|
/** Delayed death from lethal damage */
|
|
function BleedOutTimer()
|
|
{
|
|
if ( !bPlayedDeath )
|
|
{
|
|
`log(GetFuncName() @ "LastHitBy" @ LastHitBy, bLogTakeDamage);
|
|
Died(LastHitBy, class'KFDT_Bleeding', Location);
|
|
}
|
|
}
|
|
|
|
function float GetVortexAttractionModifier ()
|
|
{
|
|
return VortexAttracionModifier;
|
|
}
|
|
|
|
/** Applies the rally buff and spawns a rally effect */
|
|
simulated function bool Rally(
|
|
KFPawn RallyInstigator,
|
|
ParticleSystem RallyEffect,
|
|
name EffectBoneName,
|
|
vector EffectOffset,
|
|
ParticleSystem AltRallyEffect,
|
|
name AltEffectBoneNames[2],
|
|
vector AltEffectOffset,
|
|
optional bool bSkipEffects=false
|
|
)
|
|
{
|
|
local sRallyInfo RallyInfo;
|
|
local KFAIController KFAIC;
|
|
local bool bStartedBoostRally;
|
|
|
|
local Name SocketBoneName;
|
|
local Name AltSocketBoneName;
|
|
|
|
GetDifficultyRallyInfo( RallyInfo );
|
|
|
|
if( !RallyInfo.bCanRally || !IsAliveAndWell() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( RallyInfo.DealtDamageModifier > 1.f )
|
|
{
|
|
if( !IsTimerActive(nameOf(Timer_EndRallyBoost)) )
|
|
{
|
|
SetTimer( RallyInfo.RallyBuffTime, false, nameOf(Timer_EndRallyBoost) );
|
|
bStartedBoostRally = true;
|
|
}
|
|
}
|
|
|
|
if( Role == ROLE_Authority && Controller != none && RallyInfo.bCauseSprint )
|
|
{
|
|
KFAIC = KFAIController( Controller );
|
|
|
|
KFAIC.SetSprintingDisabled( false );
|
|
KFAIC.SetCanSprint( true );
|
|
KFAIC.bDefaultCanSprint = true;
|
|
KFAIC.bCanSprintWhenDamaged = true;
|
|
KFAIC.bForceFrustration = true;
|
|
|
|
// Trigger sprint/rage immediately
|
|
SetSprinting( true );
|
|
SetEnraged( true );
|
|
|
|
if (KFGameInfo(WorldInfo.Game) != none)
|
|
{
|
|
KFGameInfo(WorldInfo.Game).NotifyRally(self);
|
|
}
|
|
}
|
|
|
|
if( !bSkipEffects && WorldInfo.NetMode != NM_DedicatedServer )
|
|
{
|
|
if( RallyPSC != none )
|
|
{
|
|
RallyPSC.DeactivateSystem();
|
|
DetachComponent( RallyPSC );
|
|
}
|
|
|
|
RallyPSC = WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment( RallyEffect, Mesh, EffectBoneName, false, EffectOffset );
|
|
|
|
// Spawn player rally particle systems and attach them
|
|
if( bStartedBoostRally )
|
|
{
|
|
SocketBoneName = Mesh.GetSocketBoneName(AltEffectBoneNames[0]);
|
|
if (SocketBoneName != '' && SocketBoneName != 'None')
|
|
{
|
|
RallyHandPSCs[0] = WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment( AltRallyEffect, Mesh, AltEffectBoneNames[0], true, AltEffectOffset );
|
|
}
|
|
|
|
AltSocketBoneName = Mesh.GetSocketBoneName(AltEffectBoneNames[1]);
|
|
if (AltSocketBoneName != '' && AltSocketBoneName != 'None')
|
|
{
|
|
RallyHandPSCs[1] = WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment( AltRallyEffect, Mesh, AltEffectBoneNames[1], true, AltEffectOffset );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Ends the rally boost and cancels effects */
|
|
simulated function Timer_EndRallyBoost()
|
|
{
|
|
if( RallyHandPSCs[0] != none && RallyHandPSCs[0].bIsActive )
|
|
{
|
|
RallyHandPSCs[0].DeactivateSystem();
|
|
}
|
|
if( RallyHandPSCs[1] != none && RallyHandPSCs[1].bIsActive )
|
|
{
|
|
RallyHandPSCs[1].DeactivateSystem();
|
|
}
|
|
}
|
|
|
|
/** Sets the difficulty-based rally settings for this pawn */
|
|
simulated function SetRallySettings( sRallyInfo NewRallyInfo )
|
|
{
|
|
DifficultyRallySettings = NewRallyInfo;
|
|
}
|
|
|
|
/** Returns the difficulty-based rally settings */
|
|
simulated function GetDifficultyRallyInfo( out sRallyInfo RallyInfo )
|
|
{
|
|
RallyInfo = DifficultyRallySettings;
|
|
}
|
|
|
|
/** Applies the rally damage boost if applicable */
|
|
simulated function int GetRallyBoostDamage( int NewDamage )
|
|
{
|
|
local sRallyInfo RallyInfo;
|
|
|
|
GetDifficultyRallyInfo( RallyInfo );
|
|
return NewDamage * ( IsTimerActive(nameOf(Timer_EndRallyBoost)) ? RallyInfo.DealtDamageModifier : 1.f );
|
|
}
|
|
|
|
/** Applies the rally damage reduction if applicable */
|
|
simulated function int GetRallyBoostResistance( int NewDamage )
|
|
{
|
|
local sRallyInfo RallyInfo;
|
|
|
|
GetDifficultyRallyInfo( RallyInfo );
|
|
return NewDamage * ( IsTimerActive(nameOf(Timer_EndRallyBoost)) ? RallyInfo.TakenDamageModifier : 1.f );
|
|
}
|
|
|
|
function bool Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local KFPerk InstigatorPerk;
|
|
|
|
if ( super.Died(Killer, damageType, HitLocation) )
|
|
{
|
|
if( Killer != none && Killer.Pawn != none && KFPawn_Human(Killer.Pawn) != none )
|
|
{
|
|
`DialogManager.PlayKilledZedDialog( KFPawn_Human(Killer.Pawn), self, DamageType, IsDoingSpecialMove(SM_Knockdown) || IsDoingSpecialMove(SM_RecoverFromRagdoll) );
|
|
}
|
|
|
|
if( bCouldTurnIntoShrapnel )
|
|
{
|
|
if( Killer != none )
|
|
{
|
|
KFPC = KFPlayerController(Killer);
|
|
if( KFPC != none )
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
if( InstigatorPerk != none && InstigatorPerk.ShouldShrapnel() )
|
|
{
|
|
ShrapnelExplode( Killer, InstigatorPerk );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bCouldTurnIntoToxicExplosion )
|
|
{
|
|
if( Killer != none )
|
|
{
|
|
KFPC = KFPlayerController(Killer);
|
|
if( KFPC != none )
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
if( InstigatorPerk != none && InstigatorPerk.IsZedativeActive() )
|
|
{
|
|
InstigatorPerk.ToxicCloudExplode( Killer, self );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OnZedDied(Killer);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Handle any subclassed functionality for when a zed dies */
|
|
function OnZedDied(Controller Killer);
|
|
|
|
/** Handle this pawn being destroyed
|
|
*/
|
|
simulated event Destroyed()
|
|
{
|
|
// Kill any affliction sounds/effects when this pawn is destroyed
|
|
AfflictionHandler.Shutdown();
|
|
super.Destroyed();
|
|
}
|
|
|
|
event OnRigidBodyLinearConstraintViolated(name StretchedBoneName)
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
|
|
`log("Linear constraint violated, hiding bone " @ StretchedBoneName);
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
if( GoreManager != none && GoreManager.AllowMutilation() )
|
|
{
|
|
if( !bIsGoreMesh )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
|
|
if( bIsGoreMesh )
|
|
{
|
|
GoreManager.CrushBone( self, StretchedBoneName );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// no gore fallback
|
|
mesh.HideBoneByName(StretchedBoneName, PBO_Term);
|
|
}
|
|
|
|
protected simulated function ResetHealthVisibilty()
|
|
{
|
|
bShowHealth = false;
|
|
}
|
|
|
|
simulated function bool CanShowHealth()
|
|
{
|
|
return bShowHealth;
|
|
}
|
|
|
|
simulated function bool IsNapalmInfected()
|
|
{
|
|
return DamageOverTimeArray.Find('DamageType', class'KFDT_Fire_Napalm') != INDEX_NONE;
|
|
}
|
|
|
|
function bool CanNapalmInfect( out KFPlayerController NapalmInstigator )
|
|
{
|
|
local int DoTIndex;
|
|
local KFPlayerController KFPC;
|
|
local KFPerk InstigatorPerk;
|
|
|
|
if( DamageOverTimeArray.Length > 0 )
|
|
{
|
|
DoTIndex = DamageOverTimeArray.Find( 'DoT_Type', class'KFDT_Fire'.default.DoT_Type );
|
|
if( DoTIndex != INDEX_NONE && DamageOverTimeArray[DoTIndex].InstigatedBy != none )
|
|
{
|
|
KFPC = KFPlayerController( DamageOverTimeArray[DoTIndex].InstigatedBy );
|
|
if( KFPC != none )
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
if( InstigatorPerk != none && InstigatorPerk.CanSpreadNapalm() )
|
|
{
|
|
NapalmInstigator = KFPC;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NapalmInstigator = none;
|
|
return false;
|
|
}
|
|
|
|
function InfectWithNapalm( KFPawn_Monster KFPM, KFPlayerController KFPC )
|
|
{
|
|
if( KFPC != none )
|
|
{
|
|
KFPM.TakeDamage( class'KFPerk_Firebug'.static.GetNapalmDamage(),
|
|
KFPC,
|
|
vect(0,0,0),
|
|
vect(0,0,0),
|
|
class'KFDT_Fire_Napalm',,
|
|
KFPC );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Spawns a radioactive cloud that hurts other Zeds
|
|
*
|
|
* @param Killer The monster's killer (that had the shrapnel skill enabled)
|
|
*/
|
|
function ShrapnelExplode( Controller Killer, KFPerk InstigatorPerk )
|
|
{
|
|
local KFExplosionActorReplicated ExploActor;
|
|
local Actor InstigatorActor;
|
|
local GameExplosion ExplosionTemplate;
|
|
|
|
if ( Role < ROLE_Authority )
|
|
{
|
|
return;
|
|
}
|
|
|
|
InstigatorActor = Killer.Pawn != none ? Killer.Pawn : Killer;
|
|
|
|
// explode using the given template
|
|
ExploActor = Spawn(class'KFExplosionActorReplicated', InstigatorActor,, Location,,, true);
|
|
if( ExploActor != None )
|
|
{
|
|
ExploActor.InstigatorController = Killer;
|
|
|
|
if( Killer.Pawn != none )
|
|
{
|
|
ExploActor.Instigator = Killer.Pawn;
|
|
}
|
|
|
|
if( InstigatorPerk != none )
|
|
{
|
|
ExplosionTemplate = InstigatorPerk.GetExplosionTemplate();
|
|
ExploActor.Explode( ExplosionTemplate );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Injuries & Status Effects
|
|
********************************************************************************************* */
|
|
|
|
/** Called to set any visual inflation that needs to occur */
|
|
simulated function UpdateVisualInflation(float InflationAmount)
|
|
{
|
|
local MaterialInstanceConstant MIC;
|
|
local int i, j;
|
|
|
|
//Turn off inflation entirely if we're in gore mode
|
|
if (bIsGoreMesh)
|
|
{
|
|
InflationAmount = 0.f;
|
|
}
|
|
|
|
//Base character MICs
|
|
foreach CharacterMICs(MIC)
|
|
{
|
|
MIC.SetScalarParameterValue('Scalar_Inflate', InflationAmount);
|
|
}
|
|
|
|
//Update MICs in any active PACs
|
|
for (i = 0; i < `MAX_COSMETIC_ATTACHMENTS; ++i)
|
|
{
|
|
if (ThirdPersonAttachments[i] != none)
|
|
{
|
|
for (j = 0; j < ThirdPersonAttachments[i].Materials.Length; ++j)
|
|
{
|
|
MIC = MaterialInstanceConstant(ThirdPersonAttachments[i].GetMaterial(j));
|
|
if (MIC != none)
|
|
{
|
|
MIC.SetScalarParameterValue('Scalar_Inflate', InflationAmount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Update MICs in any active static attachments
|
|
for (i = 0; i < StaticAttachList.Length; ++i)
|
|
{
|
|
if (StaticAttachList[i] != none)
|
|
{
|
|
for (j = 0; j < StaticAttachList[i].Materials.Length; ++j)
|
|
{
|
|
MIC = MaterialInstanceConstant(StaticAttachList[i].GetMaterial(j));
|
|
if (MIC != none)
|
|
{
|
|
MIC.SetScalarParameterValue('Scalar_Inflate', InflationAmount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Called on server when pawn should has been crippled (e.g. Headless) */
|
|
function CauseHeadTrauma(float BleedOutTime=5.f)
|
|
{
|
|
if ( !bIsHeadless && !bPlayedDeath && !bDisableHeadless )
|
|
{
|
|
if( MyKFAIC != none && KFGameInfo(WorldInfo.Game) != none && MyKFAIC.TimeFirstSawPlayer >= 0 )
|
|
{
|
|
KFGameInfo(WorldInfo.Game).GameConductor.HandleZedKill( FMax(`TimeSince(MyKFAIC.TimeFirstSawPlayer),0.0));
|
|
// Set this so we know we already logged a kill for our pawn
|
|
MyKFAIC.TimeFirstSawPlayer = -1;
|
|
}
|
|
|
|
bPlayShambling = true;
|
|
bIsHeadless = true;
|
|
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.SetSprintingDisabled(true);
|
|
}
|
|
|
|
// No more auto aiming to this zed
|
|
bCanBeAdheredTo=false;
|
|
bCanBeFrictionedTo=false;
|
|
|
|
StopAkEventsOnBone( 'head' );
|
|
|
|
// insti-kill while doing a root motion SM (uninterruptable)
|
|
if ( IsDoingSpecialMove() && Mesh.RootMotionMode == RMM_Accel )
|
|
{
|
|
Died(LastHitBy, class'DamageType', Location);
|
|
}
|
|
// initiate ragdoll knockdown while sprinting
|
|
else if ( bIsSprinting && FRand() < 0.25f )
|
|
{
|
|
Knockdown(Velocity, vect(1,1,1));
|
|
}
|
|
|
|
// initiate the "headless wander" AICommand
|
|
if( IsAliveAndWell() && MyKFAIC != none )
|
|
{
|
|
// Only allow headless wander if were doing an SM that allows a wander interupt
|
|
// otherwise wait until the end of the move
|
|
if ( SpecialMove == SM_None || !SpecialMoves[SpecialMove].bCanOnlyWanderAtEnd )
|
|
{
|
|
MyKFAIC.DoHeadlessWander();
|
|
}
|
|
}
|
|
|
|
if ( BleedOutTime > 0 )
|
|
{
|
|
SetTimer(BleedOutTime, false, nameof(BleedOutTimer));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SERVER ONLY - Update any AI or animation behaviors based on state
|
|
*/
|
|
function OnStackingAfflictionChanged(byte Id)
|
|
{
|
|
local bool bShouldBeWandering, bWasWandering;
|
|
|
|
bWasWandering = (bPlayShambling || bPlayPanicked);
|
|
|
|
bPlayShambling = bEmpPanicked || bIsHeadless;
|
|
bPlayPanicked = bFirePanicked || bIsPoisoned || bMicrowavePanicked;
|
|
|
|
bShouldBeWandering = (bPlayShambling || bPlayPanicked);
|
|
|
|
if( !bWasWandering && bShouldBeWandering )
|
|
{
|
|
CausePanicWander();
|
|
}
|
|
else if( bWasWandering )
|
|
{
|
|
EndPanicWander();
|
|
}
|
|
}
|
|
|
|
/** Called on server when pawn should do EMP Wandering */
|
|
function CausePanicWander()
|
|
{
|
|
if ( !bIsHeadless && !bPlayedDeath )
|
|
{
|
|
// if bCanOnlyWanderAtEnd is true, do not begin to wander until the current special move has ended
|
|
if ( SpecialMove != SM_None && SpecialMoves[SpecialMove].bCanOnlyWanderAtEnd )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsEnraged())
|
|
{
|
|
bRageAfterPanic = true;
|
|
SetEnraged(false);
|
|
}
|
|
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.SetSprintingDisabled(true);
|
|
}
|
|
|
|
// initiate the "EMP wander" AICommand
|
|
if( IsAliveAndWell() && MyKFAIC != none )
|
|
{
|
|
MyKFAIC.DoPanicWander();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Called on server when pawn should stop doing EMP Wandering */
|
|
function EndPanicWander()
|
|
{
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.SetSprintingDisabled(false);
|
|
}
|
|
|
|
// stop the "EMP wander" AICommand
|
|
if( IsAliveAndWell() && MyKFAIC != none )
|
|
{
|
|
MyKFAIC.EndPanicWander();
|
|
}
|
|
|
|
if (bRageAfterPanic)
|
|
{
|
|
bRageAfterPanic = false;
|
|
SetEnraged(true);
|
|
}
|
|
}
|
|
|
|
/** Returns true if a stacking incap is set that will cause a zed to wander */
|
|
simulated function bool ShouldBeWandering()
|
|
{
|
|
return bPlayShambling || bPlayPanicked;
|
|
}
|
|
|
|
/** Returns true if a stacking incap is set that will cause a zed to wander */
|
|
simulated function bool IsHeadless()
|
|
{
|
|
return bIsHeadless;
|
|
}
|
|
|
|
/** Returns true if a stacking incap is set that will prevent or interrupt a
|
|
* zed from using its special abilities (Ex Husk Flamethrower) */
|
|
simulated function bool IsImpaired()
|
|
{
|
|
return bPlayPanicked || bEmpDisrupted || (bPlayShambling && !bIsHeadless);
|
|
}
|
|
|
|
/** Reapply active gameplay related MIC params (e.g. when switching to the gore mesh) */
|
|
simulated function UpdateGameplayMICParams()
|
|
{
|
|
if ( AfflictionHandler != None )
|
|
{
|
|
AfflictionHandler.ToggleEffects(AF_Poison, bIsPoisoned);
|
|
|
|
// Turn off inflated if we are using the gore mic
|
|
if( bIsGoreMesh )
|
|
{
|
|
AfflictionHandler.UpdateMaterialParameter(AF_Microwave, 0.f);
|
|
}
|
|
}
|
|
super.UpdateGameplayMICParams();
|
|
}
|
|
|
|
/** Called when a melee attack has been parried by another pawn */
|
|
function bool NotifyAttackParried(Pawn InstigatedBy, byte InParryStrength)
|
|
{
|
|
if ( InParryStrength < ParryResistance )
|
|
{
|
|
return FALSE; // resisted
|
|
}
|
|
|
|
return Super.NotifyAttackParried(InstigatedBy, InParryStrength);
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Special Moves
|
|
********************************************************************************************* */
|
|
|
|
/** Cloaking & Spotted */
|
|
function SetCloaked(bool bNewCloaking)
|
|
{
|
|
if( bNewCloaking )
|
|
{
|
|
ClearBloodDecals();
|
|
}
|
|
}
|
|
|
|
function CallOutCloaking( optional KFPlayerController CallOutController );
|
|
|
|
/** Notification when a melee special move ends */
|
|
function MeleeSpecialMoveEnded();
|
|
|
|
/** Cause a physics knockdown (e.g. SM_Emerge) */
|
|
function ANIMNOTIFY_Knockdown()
|
|
{
|
|
// notify emerge anim to end with a ragdoll knockdown
|
|
if ( SpecialMove == SM_Emerge )
|
|
{
|
|
//Knockdown(Velocity, vect(1,1,1));
|
|
KFSM_Emerge(SpecialMoves[SM_Emerge]).bDoKnockdown = true;
|
|
}
|
|
}
|
|
|
|
/** Called from melee special move when it's interrupted */
|
|
function NotifyAnimInterrupt( optional AnimNodeSequence SeqNode );
|
|
|
|
/** Sends message to local players using ClientMessage */
|
|
function Msg( string MsgTxt )
|
|
{
|
|
MessagePlayer( MsgTxt );
|
|
}
|
|
|
|
function float GetCircleStrafeDuration()
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
function float GetStepBackInCombatOdds()
|
|
{
|
|
return 0.12f;
|
|
}
|
|
|
|
function Pawn GetPawnToLookAt( optional bool bRequireLOS )
|
|
{
|
|
local Pawn P;
|
|
local array<Pawn> InterestingPawns;
|
|
|
|
for( P = WorldInfo.PawnList; P != none; P = P.NextPawn )
|
|
{
|
|
if( P != self && P.IsAliveAndWell() && ( !bRequireLOS || LineOfSightTo( P ) ) )
|
|
{
|
|
InterestingPawns.AddItem( P );
|
|
}
|
|
}
|
|
|
|
if( InterestingPawns.Length > 0 )
|
|
{
|
|
return InterestingPawns[ Rand(InterestingPawns.Length) ];
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name HeadTracking
|
|
********************************************************************************************* */
|
|
|
|
simulated function bool LookAtPawn( optional Pawn P, optional float Strength=0.5f )
|
|
{
|
|
if( IK_Look_Head == none )
|
|
{
|
|
IK_Look_Head = SkelControlLookAt(Mesh.FindSkelControl('HeadLook'));
|
|
//IK_Look_Spine = SkelControlLookAt(Mesh.FindSkelControl('SpineLook'));
|
|
}
|
|
|
|
if( IK_Look_Head != none )
|
|
{
|
|
if( P == none )
|
|
{
|
|
P = GetPawnToLookAt( true );
|
|
}
|
|
|
|
if( P != none )
|
|
{
|
|
// if( IK_Look_Head.CanLookAtPoint( P.Location + ( vect(0,0,1) * P.BaseEyeHeight ) ) )
|
|
// {
|
|
bIsHeadTrackingActive = true;
|
|
`AILog_Ext( GetFuncName()@P, 'HeadTracking', MyKFAIC );
|
|
SetHeadTrackTarget( P, vect(0,0,1) * P.BaseEyeHeight, Strength, false );
|
|
return true;
|
|
// }
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
simulated function StopLookingAtPawn( optional Pawn P )
|
|
{
|
|
if( bCanHeadTrack && bIsHeadTrackingActive && MyLookAtInfo.LookAtTarget != none )
|
|
{
|
|
if( P == none )
|
|
{
|
|
P = Pawn( MyLookAtInfo.LookAtTarget );
|
|
}
|
|
if( P != none )
|
|
{
|
|
`AILog_Ext( GetFuncName()@P, 'HeadTracking', MyKFAIC );
|
|
ClearHeadTrackTarget( P );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Effects / Gore
|
|
********************************************************************************************* */
|
|
|
|
/**
|
|
* Performs the actual gore LOD switch, and optionally switches physics asset upon death
|
|
*
|
|
* @param GoreLODIndex LOD index for the gore LOD
|
|
*/
|
|
native final simulated function bool SwitchToGoreMesh();
|
|
|
|
/** Called when SwitchToGoreMesh is successful */
|
|
simulated event NotifyGoreMeshActive()
|
|
{
|
|
// Need to reset material params since our MIC was reassigned to the gore mesh
|
|
UpdateGameplayMICParams();
|
|
|
|
if ( SpecialMove != SM_None )
|
|
{
|
|
SpecialMoves[SpecialMove].OnGoreMeshSwap();
|
|
}
|
|
}
|
|
|
|
/** Returns n closest bones to the TestLocation that belong to the physics asset of the mesh */
|
|
simulated function GetClosestHitBones(int NumBones, vector TestLocation, out array<name> OutHitBoneList)
|
|
{
|
|
local int i;
|
|
local array<name> SearchList;
|
|
|
|
// Add bones in the physics asset to the search list
|
|
for( i=0; i<mesh.PhysicsAsset.BodySetup.length; i++ )
|
|
{
|
|
SearchList.AddItem(mesh.PhysicsAsset.BodySetup[i].BoneName);
|
|
}
|
|
|
|
mesh.FindClosestBones(TestLocation, NumBones, OutHitBoneList, SearchList);
|
|
}
|
|
|
|
simulated event RigidBodyCollision( PrimitiveComponent HitComponent, PrimitiveComponent OtherComponent,
|
|
const out CollisionImpactData RigidCollisionData, int ContactIndex )
|
|
{
|
|
local int i;
|
|
local KFGoreManager GoreManager;
|
|
local RigidBodyContactInfo ContactInfo;
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
|
|
if( GoreManager != none && `TimeSince(LastGibCollisionTime) > GoreManager.GetTimeBetweenGibBloodSplats() )
|
|
{
|
|
LastGibCollisionTime = WorldInfo.TimeSeconds;;
|
|
|
|
if ( OtherComponent != none && OtherComponent.Owner != none && !OtherComponent.Owner.IsA('KFPawn') ) // Skip pawn-on-pawn collisions
|
|
{
|
|
SoundGroupArch.PlayRigidBodyCollisionSound( self, RigidCollisionData.ContactInfos[ContactIndex].ContactPosition );
|
|
|
|
for( i=0; i<RigidCollisionData.ContactInfos.length; i++ )
|
|
{
|
|
ContactInfo = RigidCollisionData.ContactInfos[i];
|
|
GoreManager.LeaveAPersistentBloodSplat(ContactInfo.ContactPosition, -ContactInfo.ContactNormal);
|
|
//DrawDebugCoordinateSystem(ContactInfo.ContactPosition, rotator(-ContactInfo.ContactNormal), 10, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** plays clientside hit effects using the data in HitFxInfo */
|
|
simulated function PlayTakeHitEffects( vector HitDirection, vector HitLocation, optional bool bUseHitImpulse = true)
|
|
{
|
|
local class<KFDamageType> DmgType;
|
|
local name HitZoneName, HitBoneName;
|
|
local int HitZoneIndex;
|
|
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
|
|
{
|
|
HitFxInfo.HitBoneIndex = HZI_HEAD;
|
|
}
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
|
|
// Cached hit params
|
|
DmgType = HitFxInfo.DamageType;
|
|
|
|
HitZoneIndex = HitFxInfo.HitBoneIndex;
|
|
if ( HitZoneIndex != 255 && HitZoneIndex <= HitZones.Length) // INDEX_None -> 255 after byte conversion
|
|
{
|
|
HitZoneName = HitZones[HitZoneIndex].ZoneName;
|
|
HitBoneName = HitZones[HitZoneIndex].BoneName;
|
|
}
|
|
|
|
if( DmgType != none )
|
|
{
|
|
// If TornOff hasn't been called yet on client, PlayDying now before hit reactions
|
|
if ( bTearOff && !bPlayedDeath )
|
|
{
|
|
PlayDying(HitDamageType,TakeHitLocation);
|
|
}
|
|
|
|
if ( bPlayedDeath )
|
|
{
|
|
PlayDeadHitEffects(HitLocation, HitDirection, HitZoneIndex, HitZoneName, HitBoneName, DmgType, bUseHitImpulse);
|
|
}
|
|
else
|
|
{
|
|
PlayLivingHitEffects(HitLocation, HitDirection, HitZoneIndex, HitZoneName, HitBoneName, DmgType, bUseHitImpulse);
|
|
}
|
|
}
|
|
|
|
if( !bPlayedDeath )
|
|
{
|
|
bShowHealth = true;
|
|
SetTimer( 2.f, false, nameOf(ResetHealthVisibilty) );
|
|
}
|
|
|
|
Super.PlayTakeHitEffects( HitDirection, HitLocation, bUseHitImpulse );
|
|
}
|
|
|
|
/** Plays hit effects on dead zeds, this includes dismemberment and obliteration */
|
|
simulated function PlayDeadHitEffects(vector HitLocation, vector HitDirection, int HitZoneIndex, name HitZoneName, name HitBoneName, class<KFDamageType> DmgType, optional bool bUseHitImpulse)
|
|
{
|
|
local class<KFProj_PinningBullet> PinProjectileClass;
|
|
local KFPawn DeadPawn;
|
|
local KFGoreManager GoreManager;
|
|
local bool bIsDismemberingHit;
|
|
local bool bWasObliterated;
|
|
|
|
// If ragdoll and gore is not allowed for dead bodies, check the time of death
|
|
// to see when the pawn died, and skip if he has been dead for a while
|
|
if( bAllowRagdollAndGoreOnDeadBodies || `TimeSince(TimeOfDeath) <= 3.f )
|
|
{
|
|
// If this zone is 'injured' try to dismember it
|
|
if ( (InjuredHitZones & (1 << HitZoneIndex)) > 0 && !HitZones[HitZoneIndex].bPlayedInjury )
|
|
{
|
|
bIsDismemberingHit = PlayDismemberment(HitZoneIndex, DmgType, HitDirection );
|
|
|
|
// If there was no dismemberment, explode head instead
|
|
if ( !bIsDismemberingHit && (InjuredHitZones & (1 << HZI_Head)) > 0 )
|
|
{
|
|
PlayHeadAsplode();
|
|
// Set bIsDismemberingHit to true to add an impulse to the neck
|
|
bIsDismemberingHit = true;
|
|
}
|
|
}
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
// If the gore manager doesn't allow mutilation, then we don't allow anything other than basic effects.
|
|
if(GoreManager.AllowMutilation() && HitFxInfo.bObliterated && `TimeSince(TimeOfDeath) < 0.25f && !bUseDamageInflation)
|
|
{
|
|
bWasObliterated = true;
|
|
bIsDismemberingHit = true;
|
|
HandlePartialGoreAndGibs(DmgType, HitLocation, HitDirection, HitBoneName, true);
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
GoreManager.SpawnObliterationBloodEffect(self);
|
|
}
|
|
else
|
|
{
|
|
// Handle damage types from projectiles that can pin zeds to walls
|
|
PinProjectileClass = DmgType.static.GetPinProjectileClass();
|
|
if( PinProjectileClass != none )
|
|
{
|
|
DeadPawn = self;
|
|
PinProjectileClass.static.CreatePin(DeadPawn, HitLocation, HitDirection, HitBoneName);
|
|
}
|
|
|
|
HandlePartialGoreAndGibs(DmgType, HitLocation, HitDirection, HitBoneName, false);
|
|
}
|
|
|
|
// Apply an impulse to attached limbs and ragdoll
|
|
HandleRagdollImpulseEffects( HitLocation, HitDirection, HitZoneName, HitBoneName, DmgType, bIsDismemberingHit, bUseHitImpulse );
|
|
}
|
|
|
|
// Play blood effects. Apply more blood if this was a dismembering hit
|
|
ApplyBloodDecals(HitZoneIndex, HitLocation, HitDirection, HitZoneName, HitBoneName, DmgType, bIsDismemberingHit, bWasObliterated);
|
|
}
|
|
|
|
/** Apply impulse to the dead ragdolls bones */
|
|
simulated function HandleRagdollImpulseEffects( vector HitLocation, vector HitDirection, name HitZoneName, name HitBoneName, class<KFDamageType> DmgType, bool bIsDismemberingHit, optional bool bUseHitImpulse )
|
|
{
|
|
local vector ImpulseDir, ParentImpulseDir;
|
|
local float ImpulseScale, ParentImpulseScale;
|
|
local name RBBoneName;
|
|
local name HitBoneParentName;
|
|
|
|
// Allow damage type to modify the impulse for a dismembering hit
|
|
ImpulseDir = HitDirection;
|
|
ParentImpulseDir = HitDirection;
|
|
ImpulseScale = 1.f;
|
|
ParentImpulseScale = 1.f;
|
|
|
|
if( bIsDismemberingHit )
|
|
{
|
|
DmgType.static.ModifyDismembermentHitImpulse(self, HitZoneName, ImpulseDir, HitDirection, ParentImpulseDir, ImpulseScale, ParentImpulseScale);
|
|
}
|
|
|
|
if( HitBoneName != '')
|
|
{
|
|
// Get the rigidbody bone that matches the hit bone name
|
|
RBBoneName = GetRBBoneFromBoneName(HitBoneName);
|
|
}
|
|
|
|
// Apply impulse to hit bone. This can be for an alive zed (on death shot) or a dismembered limb
|
|
// In case of explosives, this applies an impulse to the body and not the gibs.
|
|
// Impulse to the gibs is handled separately in CauseGibsAndApplyImpulse()
|
|
if (bUseHitImpulse)
|
|
{
|
|
ApplyRagdollImpulse(DmgType, HitLocation, ImpulseDir, RBBoneName, ImpulseScale);
|
|
|
|
// Apply another impulse to parent bone when a limb gets dismembered for the first time. Skip if parent is the root bone.
|
|
if (bIsDismemberingHit && ParentImpulseScale > 0)
|
|
{
|
|
HitBoneParentName = mesh.GetParentBone(HitBoneName);
|
|
|
|
// Add additional force if we blew the head off if needed
|
|
if ((HitBoneName == 'head' || HitBoneParentName == 'neck') && DmgType != none)
|
|
{
|
|
ParentImpulseScale *= DmgType.default.HeadDestructionImpulseForceScale;
|
|
}
|
|
|
|
// Grab the rigid body bone name so we know which one to apply RB force to
|
|
HitBoneParentName = GetRBBoneFromBoneName(HitBoneParentName);
|
|
|
|
// Do not apply an additional impulse to the parent bone on for a dismembering shot if it'st he same as oru RB bone
|
|
if (RBBoneName != HitBoneParentName && Mesh.PhysicsAsset.FindBodyIndex(HitBoneParentName) != INDEX_NONE)
|
|
{
|
|
ApplyRagdollImpulse(DmgType, HitLocation, ParentImpulseDir, HitBoneParentName, ParentImpulseScale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Hit effects for a living zed */
|
|
simulated function PlayLivingHitEffects(vector HitLocation, vector HitDirection, int HitZoneIndex, name HitZoneName, name HitBoneName, class<KFDamageType> DmgType, optional bool bUseHitImpulse)
|
|
{
|
|
if ( !TryPlayHitReactionAnim(HitDirection, DmgType, HitZoneIndex)
|
|
&& PawnAnimInfo.bCanPlayPhysicsHitReactions
|
|
&& ActorEffectIsRelevant(HitFXInstigator, false) )
|
|
{
|
|
// Play Physics Body Impact.
|
|
PlayPhysicsBodyImpact(HitLocation, HitDirection, DmgType, HitBoneName);
|
|
}
|
|
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
if( WorldInfo.Game != none && KFGameInfo(WorldInfo.Game).bNVAlwaysHeadshot )
|
|
{
|
|
HitZoneIndex = HZI_HEAD;
|
|
}
|
|
// NVCHANGE_BEGIN - RLS - Debugging Effects
|
|
|
|
if( HitZoneIndex == HZI_Head )
|
|
{
|
|
// If head is dismembered (InitPartialKinematics was called), do impulse
|
|
if ( bUseHitImpulse && Mesh.PhysicsWeight == 1.f && bIsHeadless )
|
|
{
|
|
ApplyRagdollImpulse(DmgType, HitLocation, HitDirection, HeadBoneName, 1.f);
|
|
}
|
|
|
|
ApplyHeadChunkGore(DmgType, HitLocation, HitDirection);
|
|
}
|
|
|
|
// Play blood effects. Apply more blood if this was a dismembering hit
|
|
ApplyBloodDecals(HitZoneIndex, HitLocation, HitDirection, HitZoneName, HitBoneName, DmgType, false, false);
|
|
}
|
|
|
|
simulated function PlayDyingSound()
|
|
{
|
|
if( ClassIsChildOf(HitDamageType, class'KFDT_Bleeding') )
|
|
{
|
|
SoundGroupArch.PlayBleedoutDyingSound( self );
|
|
}
|
|
else if( bHasBrokenConstraints && !HasMouth() )
|
|
{
|
|
// if we have no head or mouth...
|
|
SoundGroupArch.PlayMouthlessDyingSound( self );
|
|
}
|
|
else
|
|
{
|
|
super.PlayDyingSound();
|
|
}
|
|
}
|
|
|
|
simulated function PlayHitZoneGoreSounds( name HitBoneName, vector HitLocation )
|
|
{
|
|
if( SoundGroupArch.ShouldPlayCleaveSound(HitBoneName) )
|
|
{
|
|
SoundGroupArch.PlayCleaveSound( self, HitLocation );
|
|
}
|
|
else
|
|
{
|
|
SoundGroupArch.PlayDismembermentSounds( self, HitLocation );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if we've already broken the input head bone
|
|
*
|
|
* @param BoneName the head bone to check to see if we've already broken it.
|
|
*/
|
|
simulated function bool HeadBoneAlreadyBroken(name BoneName)
|
|
{
|
|
if( BrokenHeadBones.Find(BoneName) != INDEX_NONE )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return true if we've already broken the input head bone
|
|
*
|
|
* @param BoneName the head bone to add to the broken head bone array.
|
|
*/
|
|
simulated function AddBrokenHeadBone(name BoneName)
|
|
{
|
|
if( BrokenHeadBones.Find(BoneName) == INDEX_NONE )
|
|
{
|
|
BrokenHeadBones.AddItem(BoneName);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Return true if this pawn is ok with having the input head bone be broken
|
|
*
|
|
* @param BoneName the head bone to check to see if we all it to break.
|
|
*/
|
|
simulated function bool ShouldAllowHeadBoneToBreak(name BoneName)
|
|
{
|
|
if( !bPlayedDeath && !bIsHeadless && !bTearOff )
|
|
{
|
|
if ( NumHeadChunksRemoved >= MaxHeadChunkGoreWhileAlive )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Don't allow both sides of the front of the face (both eyes) to be blown
|
|
// off, or both sides of the back of the head and the jaw while the zed is alive and not headless
|
|
if( (BoneName == 'Gore_FrontL' && BrokenHeadBones.Find('Gore_FrontR') != INDEX_NONE)
|
|
|| (BoneName == 'Gore_FrontR' && BrokenHeadBones.Find('Gore_FrontL') != INDEX_NONE)
|
|
|| (BoneName == 'Gore_BackL' && BrokenHeadBones.Find('Gore_Jaw') != INDEX_NONE && BrokenHeadBones.Find('Gore_BackR') != INDEX_NONE)
|
|
|| (BoneName == 'Gore_BackR' && BrokenHeadBones.Find('Gore_Jaw') != INDEX_NONE && BrokenHeadBones.Find('Gore_BackL') != INDEX_NONE)
|
|
|| (BoneName == 'Gore_Jaw' && BrokenHeadBones.Find('Gore_BackR') != INDEX_NONE && BrokenHeadBones.Find('Gore_BackL') != INDEX_NONE) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Update metadata required by gore chunk attachments */
|
|
simulated function UpdateGoreChunkData(name BoneName)
|
|
{
|
|
if( BoneName == 'Gore_FrontL' ||
|
|
BoneName == 'Gore_FrontR' ||
|
|
BoneName == 'Gore_BackL' ||
|
|
BoneName == 'Gore_BackR' /*||
|
|
BoneName == 'Gore_Jaw'*/ )
|
|
{
|
|
NumHeadChunksRemoved++;
|
|
}
|
|
}
|
|
|
|
/** Implementation of delegate function used to determine whether to attach the skull gore chunk.
|
|
The skull gore chunk is used only when one or two head chunks are removed */
|
|
simulated function bool ShouldAttachSkullChunk()
|
|
{
|
|
return NumHeadChunksRemoved < 3;
|
|
}
|
|
|
|
/** Implementation of delegate function used to determine whether to detach the skull gore chunk.
|
|
If attached, the skull gore chunk is detached when more than 2 head chunks are removed */
|
|
simulated function bool ShouldDetachSkullChunk()
|
|
{
|
|
return NumHeadChunksRemoved >= 3 && (bPlayedDeath || bIsHeadless || bTearOff);
|
|
}
|
|
|
|
/** Handles the gore chunks to be attached to the pawn body when a limb is dismembered or gibbed */
|
|
simulated function HandleGoreChunkAttachments(name DismemberedBone)
|
|
{
|
|
local int AttachmentIndex, ChunkRef;
|
|
local StaticMeshComponent AttachComp;
|
|
local KFCharacterInfo_Monster MonsterInfo;
|
|
local AttachedGoreChunkInfo ChunkInfo;
|
|
local KFGoreChunkAttachmentInfo CurrentChunk;
|
|
|
|
// Update information that is required by the attachment/detachment code
|
|
UpdateGoreChunkData(DismemberedBone);
|
|
|
|
MonsterInfo = GetCharacterMonsterInfo();
|
|
|
|
if( MonsterInfo != none )
|
|
{
|
|
for( AttachmentIndex = 0; AttachmentIndex < MonsterInfo.GoreChunkAttachments.length; AttachmentIndex++ )
|
|
{
|
|
CurrentChunk = MonsterInfo.GoreChunkAttachments[AttachmentIndex];
|
|
|
|
if( CurrentChunk.DismemberedBoneList.Find(DismemberedBone) != INDEX_NONE )
|
|
{
|
|
// Search for it in the currently attached chunks list
|
|
ChunkRef = AttachedGoreChunks.Find('AttachmentIndex', AttachmentIndex);
|
|
|
|
if( CurrentChunk.ShouldAttachGoreChunk(self) && ChunkRef == INDEX_NONE )
|
|
{
|
|
// Create and intialize the attachment component
|
|
AttachComp = new(self) class'StaticMeshComponent';
|
|
AttachComp.SetStaticMesh(CurrentChunk.StaticMesh);
|
|
AttachComp.SetLightingChannels(PawnLightingChannel);
|
|
AttachComp.CastShadow=true;
|
|
AttachComp.bCastDynamicShadow=true;
|
|
AttachComp.bAllowPerObjectShadowBatching=true;
|
|
|
|
//Big head mode specific fix. Increase cull distance for this situation
|
|
if (CurrentChunk.SocketName == 'Head_Attach' && CurrentHeadScale > 1.f)
|
|
{
|
|
AttachComp.SetCullDistance(1000 * CurrentHeadScale);
|
|
}
|
|
else
|
|
{
|
|
AttachComp.SetCullDistance(1000);
|
|
}
|
|
|
|
AttachComp.SetShadowParent(mesh);
|
|
AttachComp.bAllowApproximateOcclusion=True;
|
|
AttachComp.SetTraceBlocking(False,False);
|
|
AttachComp.SetActorCollision(False,False);
|
|
|
|
// Attach chunk to socket in the pawn mesh
|
|
mesh.AttachComponentToSocket(AttachComp, CurrentChunk.SocketName);
|
|
|
|
// Add it to the list of currently active gore attachments
|
|
ChunkInfo.AttachmentIndex = AttachmentIndex;
|
|
ChunkInfo.AttachedComponent = AttachComp;
|
|
AttachedGoreChunks.AddItem(ChunkInfo);
|
|
}
|
|
else if( CurrentChunk.ShouldDetachGoreChunk(self) && ChunkRef != INDEX_NONE )
|
|
{
|
|
AttachComp = AttachedGoreChunks[ChunkRef].AttachedComponent;
|
|
|
|
// Detach the chunk from the mesh
|
|
mesh.DetachComponent(AttachComp);
|
|
|
|
// Remove it from the list of currently active gore attachments
|
|
AttachedGoreChunks.Remove(ChunkRef, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Calculate type of hit reaction animation from damage info */
|
|
simulated function bool TryPlayHitReactionAnim(vector HitDirection, class<KFDamageType> damageType, byte HitZoneIdx)
|
|
{
|
|
local EPawnOctant AnimDir;
|
|
local EHitReactionAnimType HitReactionType;
|
|
local EHitZoneBodyPart BodyPart;
|
|
local bool bOnlyAdditiveHits;
|
|
|
|
if( damageType == none || ActorTimeSince(NextHitReactionAnim_ActorTime) < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// cannot play hit reaction anims during special moves
|
|
if ( IsDoingSpecialMove() )
|
|
{
|
|
bOnlyAdditiveHits = true;
|
|
if ( !SpecialMoves[SpecialMove].bAllowHitReactions )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set the limb used for incap effects
|
|
BodyPart = (HitZoneIdx != 255 && HitZoneIdx < HitZones.Length) ? HitZones[HitZoneIdx].Limb : BP_Torso;
|
|
|
|
HitReactionType = HIT_Light;
|
|
// If we're moving (e.g. DoPauseAI wasn't called because of incap cooldown) don't try hard/medium
|
|
// This is not 100% reliable, but worst case we get a reasonable upper body blend while moving
|
|
bOnlyAdditiveHits = bOnlyAdditiveHits || VSizeSq(Velocity) > 50.f;
|
|
|
|
if ( !bOnlyAdditiveHits )
|
|
{
|
|
HitReactionType = EHitReactionAnimType(AfflictionHandler.GetPredictedHitReaction(DamageType, BodyPart));
|
|
}
|
|
|
|
switch (BodyPart)
|
|
{
|
|
case BP_LeftArm:
|
|
AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
|
|
if ( AnimDir == DIR_Forward || AnimDir == DIR_ForwardLeft || AnimDir == DIR_ForwardRight )
|
|
AnimDir = DIR_ForwardLeft;
|
|
else
|
|
AnimDir = DIR_BackwardLeft;
|
|
break;
|
|
case BP_RightArm:
|
|
AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
|
|
if ( AnimDir == DIR_Forward || AnimDir == DIR_ForwardLeft || AnimDir == DIR_ForwardRight )
|
|
AnimDir = DIR_ForwardRight;
|
|
else
|
|
AnimDir = DIR_BackwardRight;
|
|
break;
|
|
default:
|
|
AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
|
|
break;
|
|
}
|
|
|
|
// Play animation
|
|
return PawnAnimInfo.PlayHitReactionAnim(self, HitReactionType, AnimDir);
|
|
}
|
|
|
|
/** plays clientside hit effects using the data in HitFxInfo */
|
|
simulated function ApplyBloodDecals(int HitZoneIndex, vector HitLocation, vector HitDirection, name HitZoneName, name HitBoneName, class<KFDamageType> DmgType, bool bIsDismemberingHit, bool bWasObliterated)
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
|
|
if( DmgType != none && GoreManager != none )
|
|
{
|
|
// Spawn wound decal on the damaged pawn
|
|
if( !bWasObliterated && !bIsCloaking )
|
|
{
|
|
GoreManager.LeaveABodyWoundDecal(self, HitLocation, HitDirection, HitZoneName, HitBoneName, DmgType);
|
|
}
|
|
|
|
// Spawn blood splatter decal effect behind the damaged pawn
|
|
if ( DmgType.default.bShouldSpawnBloodSplat )
|
|
{
|
|
GoreManager.LeaveABloodSplatterDecal(self, HitLocation, HitDirection);
|
|
}
|
|
|
|
// Spawn persistent blood splatters
|
|
if( DmgType.default.bShouldSpawnPersistentBlood )
|
|
{
|
|
GoreManager.CausePersistentBlood(self, DmgType, HitLocation, HitDirection, HitZoneIndex, bIsDismemberingHit, bWasObliterated);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gibbing, partial gore and gib impusle effects handled here.
|
|
*
|
|
* @param ObliterateGibs If true this function will rip apart all limbs. If false, the default behavior is to select random bones to rip apart.
|
|
*/
|
|
simulated function HandlePartialGoreAndGibs(
|
|
class<KFDamageType> DmgType,
|
|
vector HitLocation,
|
|
vector HitDirection,
|
|
name HitBoneName,
|
|
bool ObliterateGibs)
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
local KFCharacterInfo_Monster MonsterInfo;
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
|
|
if( DmgType != none && GoreManager != none )
|
|
{
|
|
if( GoreManager.AllowMutilation() )
|
|
{
|
|
// Enable alternate bone weighting and gore skeleton
|
|
if( !bIsGoreMesh )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
|
|
// Apply gore only if we were able to successfully switch to the gore mesh
|
|
if( bIsGoreMesh )
|
|
{
|
|
MonsterInfo = GetCharacterMonsterInfo();
|
|
// Try to apply exlosion gore first
|
|
if(ObliterateGibs)
|
|
{
|
|
ApplyObliterationFxGore( GoreManager, MonsterInfo, DmgType );
|
|
}
|
|
else
|
|
if( HitFxInfo.bRadialDamage)
|
|
{
|
|
ApplyRadialFxGore( GoreManager, MonsterInfo, DmgType );
|
|
}
|
|
else
|
|
{
|
|
ApplyTakeHitFxGore( GoreManager, MonsterInfo, DmgType, HitLocation, HitDirection, HitBoneName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply any gibbing from radial damage
|
|
*
|
|
* @param ObliterateGibs If true this function will rip apart all limbs. If false, the default behavior is to select random bones to rip apart.
|
|
*/
|
|
simulated function ApplyRadialFxGore( KFGoreManager GoreManager, KFCharacterInfo_Monster MonsterInfo, class<KFDamageType> DmgType )
|
|
{
|
|
local array<name> OutGibBoneList;
|
|
local float NormalizedDistanceScale;
|
|
local vector ExplosionOrigin;
|
|
local int NumGibs;
|
|
|
|
// Try to apply exlosion gore first
|
|
if( DmgType.default.bCanGib)
|
|
{
|
|
ExplosionOrigin = HitFxRadialInfo.RadiusHurtOrigin;
|
|
|
|
// Use distance from explosion along with some randomization to figure out
|
|
// how many joints to dismember. Distance based calculation is skipped after 10 meters.
|
|
NormalizedDistanceScale = VSize(Location - ExplosionOrigin)/1000.f;
|
|
NumGibs = FCeil(FMax(1.f - NormalizedDistanceScale, 0.f) * rand(10)) + rand(3);
|
|
NumGibs *= MonsterInfo.ExplosionGibScale;
|
|
|
|
GetClosestHitBones(NumGibs, ExplosionOrigin, OutGibBoneList);
|
|
|
|
// Gib-ify!
|
|
// Spawn exlosion PS at the root bone
|
|
GoreManager.CauseGibsAndApplyImpulse(
|
|
self,
|
|
DmgType,
|
|
ExplosionOrigin,
|
|
OutGibBoneList,
|
|
MonsterInfo.ExplosionEffectTemplate,
|
|
mesh.GetBoneLocation(mesh.GetBoneName(0)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function will dismember all rigid body bones in the mesh.
|
|
*/
|
|
simulated function ApplyObliterationFxGore( KFGoreManager GoreManager, KFCharacterInfo_Monster MonsterInfo, class<KFDamageType> DmgType )
|
|
{
|
|
local array<name> OutGibBoneList;
|
|
local vector ObliterationLocation;
|
|
local int MaxNumGibs;
|
|
|
|
if( DmgType.default.MaxObliterationGibs > 0 )
|
|
{
|
|
MaxNumGibs = DmgType.default.MaxObliterationGibs;
|
|
}
|
|
else
|
|
{
|
|
// Allowed to disconnect 100 bones (basically, all bones.)
|
|
MaxNumGibs = 100;
|
|
}
|
|
|
|
// Try to apply exlosion gore first
|
|
if( DmgType.default.bCanGib)
|
|
{
|
|
// Origin is the point where the explosive went off
|
|
if(HitFxInfo.bRadialDamage == true)
|
|
{
|
|
ObliterationLocation = HitFxRadialInfo.RadiusHurtOrigin;
|
|
}
|
|
else if( DmgType.default.bUseHitLocationForGibImpulses )
|
|
{
|
|
ObliterationLocation = HitFxInfo.HitLocation;
|
|
if( DmgType.default.bPointImpulseTowardsOrigin )
|
|
{
|
|
ObliterationLocation -= DecodeUnitVector(HitFxInfo.EncodedHitDirection) * DmgType.default.ImpulseOriginScale;
|
|
}
|
|
else
|
|
{
|
|
ObliterationLocation.Z = Location.Z - CylinderComponent.CollisionHeight * 0.25f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ObliterationLocation = self.Location;
|
|
}
|
|
|
|
GetClosestHitBones(MaxNumGibs, ObliterationLocation, OutGibBoneList);
|
|
|
|
// Gib-ify!
|
|
// Spawn exlosion PS at the root bone
|
|
GoreManager.CauseGibsAndApplyImpulse(
|
|
self,
|
|
DmgType,
|
|
ObliterationLocation,
|
|
OutGibBoneList,
|
|
MonsterInfo.ExplosionEffectTemplate,
|
|
mesh.GetBoneLocation(mesh.GetBoneName(0)));
|
|
}
|
|
}
|
|
|
|
/** Apply any gore or breaks from non radial damage */
|
|
simulated function ApplyTakeHitFxGore(
|
|
KFGoreManager GoreManager,
|
|
KFCharacterInfo_Monster MonsterInfo,
|
|
class<KFDamageType> DmgType,
|
|
vector HitLocation,
|
|
vector HitDirection,
|
|
name HitBoneName)
|
|
{
|
|
local array<name> OutGibBoneList;
|
|
local int JointIndex, ExplosionBreakIdx, BoneIdx;
|
|
local vector ExplosionOrigin;
|
|
local name GibBone;
|
|
|
|
// Apply partial gore (if supported)
|
|
GoreManager.ConditionalApplyPartialGore(self, DmgType, HitLocation, HitDirection, HitBoneName);
|
|
|
|
// Apply any hit explosion gore specified for this bone
|
|
for( JointIndex = 0; JointIndex < MonsterInfo.GoreJointSettings.length; JointIndex++ )
|
|
{
|
|
if( MonsterInfo.GoreJointSettings[JointIndex].HitBoneName == HitBoneName )
|
|
{
|
|
for( ExplosionBreakIdx = 0;
|
|
ExplosionBreakIdx < MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore.length;
|
|
ExplosionBreakIdx++ )
|
|
{
|
|
if( MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].ConstrainToDamageGroups.length == 0 ||
|
|
MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].ConstrainToDamageGroups.Find(DGT_None) != INDEX_None ||
|
|
MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].ConstrainToDamageGroups.Find(DmgType.default.GoreDamageGroup) != INDEX_None)
|
|
{
|
|
// Origin is the place of hit
|
|
ExplosionOrigin = mesh.GetBoneLocation(HitBoneName);
|
|
|
|
// Only include bones that haven't been gibbed/broken already
|
|
for( BoneIdx = 0;
|
|
BoneIdx < MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].BreakBones.length;
|
|
BoneIdx++ )
|
|
{
|
|
GibBone = MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].BreakBones[BoneIdx].BoneName;
|
|
if( !mesh.IsBrokenConstraint(GibBone) )
|
|
{
|
|
OutGibBoneList.AddItem(GibBone);
|
|
}
|
|
}
|
|
|
|
// Gib-ify!
|
|
GoreManager.CauseGibsAndApplyImpulse(
|
|
self,
|
|
DmgType,
|
|
ExplosionOrigin,
|
|
OutGibBoneList,
|
|
MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore[ExplosionBreakIdx].ParticleSystemTemplate,
|
|
ExplosionOrigin,
|
|
HitBoneName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Gibbing and partial gore effects handled here */
|
|
simulated function ApplyHeadChunkGore(class<KFDamageType> DmgType, vector HitLocation, vector HitDirection)
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
|
|
if( bCanCloak && IsAliveAndWell() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
|
|
if( DmgType != none && GoreManager != none )
|
|
{
|
|
if( GoreManager.AllowMutilation() )
|
|
{
|
|
// Enable alternate bone weighting and gore skeleton
|
|
if( !bIsGoreMesh )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
|
|
if ( !bPlayedDeath )
|
|
{
|
|
// @hack: force all gore as if it was a handgun so we only get head chunks
|
|
DmgType = class'KFDT_Ballistic';
|
|
GoreManager.ConditionalApplyPartialGore(self, DmgType, HitLocation, HitDirection, HitZones[HZI_HEAD].BoneName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Reliably play any gore effects related to a zone/limb being dismembered */
|
|
simulated function HitZoneInjured(optional int HitZoneIdx=INDEX_None)
|
|
{
|
|
// --------------------------------------------------------------
|
|
// Network: Server (Also called on clients after TearOff)
|
|
// --------------------------------------------------------------
|
|
if ( Role == ROLE_Authority && HitZoneIdx != INDEX_None )
|
|
{
|
|
if ( HitZoneIdx == HZI_Head )
|
|
{
|
|
CauseHeadTrauma();
|
|
}
|
|
|
|
// replicate to all clients
|
|
InjuredHitZones = InjuredHitZones | (1 << HitZoneIdx);
|
|
}
|
|
|
|
// Play living head explosion effects. Some effects use the clientside HitFX system (e.g. PlayDismemberment),
|
|
// but this should be used when we need a reliably replicated effect.
|
|
if (WorldInfo.NetMode != NM_DedicatedServer && (!bTearOff && !bPlayedDeath))
|
|
{
|
|
if ( (InjuredHitZones & (1 << HZI_Head)) > 0 && !bDisableHeadless)
|
|
{
|
|
// Use HitFxInfo for "best guess" DT and CreationTime to fallback
|
|
// on HeadAsplode when coming into relevancy.
|
|
if ( `TimeSince(CreationTime) > 1.f )
|
|
{
|
|
PlayDismemberment(HZI_Head, HitFxInfo.DamageType);
|
|
}
|
|
|
|
// If there was no dismemberment, explode head instead
|
|
if ( !HitZones[HZI_Head].bPlayedInjury )
|
|
{
|
|
PlayHeadAsplode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function PlayHeadAsplode()
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
local name BoneName;
|
|
|
|
if( HitZones[HZI_Head].bPlayedInjury )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// make sure this doesn't happen after death so that normal HitFX/Gore path is followed. Using
|
|
// bTearOff since bPlayDying may not be set yet on the client
|
|
// Let the head be blown off for a short time still after death
|
|
if ( (bTearOff || bPlayedDeath) && TimeOfDeath > 0 && `TimeSince(TimeOfDeath) > 0.75 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Enable alternate bone weighting and gore skeleton
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
if( GoreManager != none && GoreManager.AllowHeadless() )
|
|
{
|
|
if( !bIsGoreMesh && !bDisableHeadless )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
}
|
|
|
|
// Apply mutilation gore only if we were able to successfully switch to the gore mesh
|
|
if( bIsGoreMesh && GoreManager != none )
|
|
{
|
|
BoneName = HitZones[HZI_Head].BoneName;
|
|
GoreManager.CrushBone( self, BoneName );
|
|
SoundGroupArch.PlayHeadPopSounds( self, mesh.GetBoneLocation(BoneName) );
|
|
HitZones[HZI_Head].bPlayedInjury = true;
|
|
}
|
|
|
|
// Play headshot effects regardless of dismemberment, because the user deserves to see
|
|
// their FX even if their gore settings do not allow dismemberment.
|
|
SpawnHeadShotFX(KFPlayerReplicationInfo(HitFxInfo.DamagerPRI));
|
|
}
|
|
|
|
/** Dismember this hit zone if it's not dismembered already
|
|
* InDmgType and HitDirection are only ever used on the client after a death shot
|
|
*/
|
|
simulated function bool PlayDismemberment(int InHitZoneIndex, class<KFDamageType> InDmgType, optional vector HitDirection)
|
|
{
|
|
local KFGoreManager GoreManager;
|
|
local name BreakBoneName;
|
|
|
|
// Bail out if the hit zone has already been dismembered
|
|
if( HitZones[InHitZoneIndex].bPlayedInjury )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// This hit zone was injured (see CanInjureHitZone), but shouldn't be dismembered
|
|
if ( !InDmgType.static.CanDismemberHitZone( HitZones[InHitZoneIndex].ZoneName ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Enable alternate bone weighting and gore skeleton
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
if( GoreManager != none && GoreManager.AllowMutilation() )
|
|
{
|
|
if( !bIsGoreMesh )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
|
|
// Apply mutilation gore only if we were able to successfully switch to the gore mesh
|
|
if( bIsGoreMesh )
|
|
{
|
|
// Get the bone to dismember from the hit zone
|
|
BreakBoneName = HitZones[InHitZoneIndex].BoneName;
|
|
// If we're dead, allow damage type to override the bone
|
|
if ( Health <= 0 && !IsZero(HitDirection) )
|
|
{
|
|
InDmgType.static.GetBoneToDismember(self, HitDirection, HitZones[InHitZoneIndex].ZoneName, BreakBoneName);
|
|
}
|
|
// Dismember
|
|
|
|
GoreManager.CauseDismemberment(self, BreakBoneName, InDmgType);
|
|
if (InHitZoneIndex == HZI_HEAD)
|
|
{
|
|
SpawnHeadShotFX(KFPlayerReplicationInfo(HitFxInfo.DamagerPRI));
|
|
}
|
|
PlayHitZoneGoreSounds(BreakBoneName, mesh.GetBoneLocation(BreakBoneName));
|
|
HitZones[InHitZoneIndex].bPlayedInjury = true;
|
|
|
|
// If we're still alive (non-ragdoll), start partial physics ragdoll
|
|
// Note: bPlayedDeath may not be set yet on client, so use Health
|
|
if ( Health > 0 && bHasBrokenConstraints )
|
|
{
|
|
InitPartialKinematics();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Apply damage to a specific zone (useful for gore effects) */
|
|
function TakeHitZoneDamage(float Damage, class<DamageType> DamageType, int HitZoneIdx, vector InstigatorLocation)
|
|
{
|
|
local float HeadHealthPercentage;
|
|
|
|
if (HitZoneIdx > HitZones.Length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Super.TakeHitZoneDamage(Damage, DamageType, HitZoneIdx, InstigatorLocation);
|
|
|
|
// When GoreHealth <= 0, check to see if this weapon can dismember limbs
|
|
if ( HitZones[HitZoneIdx].GoreHealth <= 0 && CanInjureHitZone(DamageType, HitZoneIdx) )
|
|
{
|
|
HitZoneInjured(HitZoneIdx);
|
|
}
|
|
|
|
// Handle head injuries
|
|
if ( HitZoneIdx == HZI_Head )
|
|
{
|
|
// Based on head health, calculate number of head chunks we're allowed to remove
|
|
if( !bPlayedDeath && !bIsHeadless && !bTearOff )
|
|
{
|
|
HeadHealthPercentage = GetHeadHealthPercent();
|
|
if( HeadHealthPercentage > 0.5 )
|
|
{
|
|
MaxHeadChunkGoreWhileAlive = 1;
|
|
}
|
|
else if ( HeadHealthPercentage > 0.25 )
|
|
{
|
|
MaxHeadChunkGoreWhileAlive = 2;
|
|
}
|
|
else if ( HeadHealthPercentage > 0.0 )
|
|
{
|
|
MaxHeadChunkGoreWhileAlive = 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns the percentage of head health remaining on this zed */
|
|
function float GetHeadHealthPercent()
|
|
{
|
|
local float HeadHealth, HeadHealthMax;
|
|
|
|
HeadHealth = float(HitZones[HZI_Head].GoreHealth);
|
|
HeadHealthMax = float(HitZones[HZI_Head].MaxGoreHealth);
|
|
|
|
return HeadHealth / HeadHealthMax;
|
|
}
|
|
|
|
/** Called by KFPawnAnimInfo when determining whether an attack can be performed */
|
|
simulated function bool ShouldPlayHeadlessMeleeAnims()
|
|
{
|
|
return bIsHeadless || bEmpPanicked;
|
|
}
|
|
|
|
/** Generally used to determine which/whether dialog/voice events should be played */
|
|
simulated event bool HasMouth()
|
|
{
|
|
if( !bHasBrokenConstraints )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return !Mesh.IsBrokenConstraint( 'head' ) && !Mesh.IsBoneHidden( Mesh.MatchRefBone('gore_jaw') );
|
|
}
|
|
|
|
/** Breaks all joint constraints using dependent gore system */
|
|
simulated function ForceBreakAllConstraints()
|
|
{
|
|
local int i;
|
|
local KFGoreManager GoreManager;
|
|
|
|
bHasBrokenConstraints = TRUE;
|
|
|
|
// Enable alternate bone weighting and gore skeleton
|
|
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
|
if( GoreManager == none )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( GoreManager.AllowMutilation() )
|
|
{
|
|
if( !bIsGoreMesh )
|
|
{
|
|
SwitchToGoreMesh();
|
|
}
|
|
|
|
for( i = 0; i < CharacterMonsterArch.GoreJointSettings.length; i++ )
|
|
{
|
|
if ( !CharacterMonsterArch.GoreJointSettings[i].bNonBreakableJoint )
|
|
{
|
|
GoreManager.BreakConstraint( self, CharacterMonsterArch.GoreJointSettings[i].HitBoneName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function NotifyMeleeAttackFinished();
|
|
|
|
/** Checks if we're able to reduce or restore our collision size if we're going through
|
|
* a choke point and we have a large collision cylinder */
|
|
simulated function ChokePointTimer()
|
|
{
|
|
if( !IsTimerActive(nameof(ChokePointTimer)) )
|
|
{
|
|
SetTimer(0.3f, true, nameof(ChokePointTimer), self);
|
|
}
|
|
|
|
if( CurrentChokePointTrigger != none )
|
|
{
|
|
// Check if we want to restore our collision
|
|
if( CurrentChokePointTrigger.CanRestoreChokeCollision(self) )
|
|
{
|
|
// If our enemy has entered the choke point and we can fit in the geometry, retore our collision
|
|
if( CylinderComponent.CollisionRadius < CylinderComponent.default.CollisionRadius
|
|
&& !CheckEncroachingWorldGeometry() )
|
|
{
|
|
SetChokePointCollision(false);
|
|
}
|
|
}
|
|
// if our collision is too large to fit through this chokepiont, reduce it
|
|
else if( CylinderComponent.CollisionRadius > CurrentChokePointTrigger.MaxCollisionRadius )
|
|
{
|
|
SetChokePointCollision(true);
|
|
}
|
|
}
|
|
// if we are out of a chokepoint and can restore our collision, do so
|
|
else if( !CheckEncroachingWorldGeometry() )
|
|
{
|
|
// Only restore collision if it's been reduced
|
|
if( CylinderComponent.CollisionRadius < CylinderComponent.default.CollisionRadius )
|
|
{
|
|
SetChokePointCollision(false);
|
|
}
|
|
ClearTimer(nameof(ChokePointTimer), self);
|
|
}
|
|
}
|
|
|
|
event SetDamageInflation(float NewInflation)
|
|
{
|
|
RepDamageInflateParam = FloatToByte(NewInflation);
|
|
SetHeadScale(1.0 + (GetCurrentInflation() / 2.0), CurrentHeadScale);
|
|
HandleDamageInflation();
|
|
}
|
|
|
|
simulated function float GetCurrentInflation()
|
|
{
|
|
local float CurrentInflation;
|
|
CurrentInflation = FClamp(ByteToFloat(RepInflateMatParams.RepInflateMatParam) + ByteToFloat(RepInflateMatParams.RepInflateMatParam) - ByteToFloat(RepBleedInflateMatParam), -1.0, 1.0);
|
|
//`log("*** Inflation" @ CurrentInflation);
|
|
//RepInflateMatParam - From microwave affliction
|
|
//RepDamageInflateParam - From modes where gametype modifies size (Shrinky Dinky, Beefcake, etc)
|
|
//RepBleedInflateMatParam - From bleed affliction, set to negative here so we don't lose precision in byte conversion
|
|
return CurrentInflation;
|
|
}
|
|
|
|
simulated function HandleDamageInflation()
|
|
{
|
|
if ( WorldInfo.NetMode != NM_DedicatedServer )
|
|
{
|
|
UpdateVisualInflation(GetCurrentInflation() * 2.0);
|
|
}
|
|
}
|
|
|
|
simulated function UpdateBleedIncapFX()
|
|
{
|
|
local float CurrentStrength;
|
|
if (WorldInfo.NetMode != NM_DedicatedServer)
|
|
{
|
|
CurrentStrength = ByteToFloat(RepBleedInflateMatParam);
|
|
//We've gone past the threshold, create a PSC
|
|
if ((CurrentStrength != 0 && IsAliveAndWell()) && BleedIncapPSC == none)
|
|
{
|
|
BleedIncapPSC = WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(BleedIncapFX, Mesh, class'KFSM_Stunned'.default.DazedFXSocketName, true);
|
|
if (BleedIncapPSC != none)
|
|
{
|
|
BleedIncapPSC.SetAbsolute(false, true, false);
|
|
BleedIncapPSC.SetRotation(rotator(vect(0, 0, 1)) + class'KFSM_Stunned'.default.DazedFXRelativeRotation);
|
|
}
|
|
}
|
|
//We've underrun the threshold, remove PSC
|
|
else if ((CurrentStrength == 0 || !IsAliveAndWell()) && BleedIncapPSC != none)
|
|
{
|
|
BleedIncapPSC.DeactivateSystem();
|
|
DetachComponent(BleedIncapPSC);
|
|
BleedIncapPSC = none;
|
|
}
|
|
}
|
|
}
|
|
|
|
private final simulated function SpawnHeadShotFX(KFPlayerReplicationInfo DamagerPRI)
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local HeadshotEffect SHeadshotEffect;
|
|
local vector SpawnVector;
|
|
|
|
if (DamagerPRI != none)
|
|
{
|
|
if (WorldInfo.NetMode != NM_DedicatedServer)
|
|
{
|
|
KFPC = KFPlayerController(WorldInfo.GetALocalPlayerController());
|
|
|
|
if (KFPC == none || (KFPC.bHideRemotePlayerHeadshotEffects && DamagerPRI != KFPC.PlayerReplicationInfo))
|
|
{
|
|
return;
|
|
}
|
|
SHeadshotEffect = class'KFHeadShotEffectList'.static.GetUnlockedHeadshotEffect(DamagerPRI.GetHeadShotEffectID());
|
|
if (SHeadshotEffect.Id != INDEX_NONE && SHeadshotEffect.EffectPS != none)
|
|
{
|
|
Mesh.GetSocketWorldLocationAndRotation(class'KFSM_Stunned'.default.DazedFXSocketName, SpawnVector);
|
|
WorldInfo.MyEmitterPool.SpawnEmitter(SHeadshotEffect.EffectPS, SpawnVector);
|
|
HeadShotAkComponent.PlayEvent(SHeadshotEffect.HeadshotSoundEffect, true, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Ephemeral Stats tracking
|
|
********************************************************************************************* */
|
|
static function bool IsLargeZed(){ return default.bLargeZed; }
|
|
|
|
static event bool IsABoss(){ return false; }
|
|
|
|
/*********************************************************************************************
|
|
* @name Perk related
|
|
********************************************************************************************* */
|
|
simulated event UpdateSpottedStatus();
|
|
static function bool IsStalkerClass(){ return default.bIsStalkerClass; }
|
|
static function bool IsCrawlerClass(){ return default.bIsCrawlerClass; }
|
|
static function bool IsFleshpoundClass(){ return default.bIsFleshpoundClass; }
|
|
static function bool IsClotClass(){ return default.bIsClotClass; }
|
|
static function bool IsBloatClass(){ return default.bIsBloatClass; }
|
|
|
|
function float GetPerkDoTScaler( optional Controller InstigatedBy, optional class<KFDamageType> KFDT )
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local KFPerk InstigatorPerk;
|
|
local float DoTScaler;
|
|
|
|
DoTScaler = 1.f;
|
|
|
|
if( InstigatedBy != none )
|
|
{
|
|
KFPC = KFPlayerController(InstigatedBy);
|
|
if( KFPC != none )
|
|
{
|
|
InstigatorPerk = KFPC.GetPerk();
|
|
if( InstigatorPerk != none )
|
|
{
|
|
DoTScaler += InstigatorPerk.GetDoTScalerAdditions(KFDT);
|
|
}
|
|
}
|
|
}
|
|
|
|
return DoTScaler;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Overhead Debug Text
|
|
********************************************************************************************* */
|
|
|
|
/**
|
|
* Sets debug text rendering
|
|
* @param bTurnOn true to enable, false to disable over-NPC-head debug text rendering
|
|
*/
|
|
function SetDebugTextRendering( bool bTurnOn )
|
|
{
|
|
local PlayerController PC;
|
|
local KFHUDBase KFHud;
|
|
|
|
bDebug_DrawOverheadInfo = bTurnOn;
|
|
|
|
// final local player's hud object
|
|
ForEach LocalPlayerControllers(class'PlayerController', PC)
|
|
{
|
|
KFHud = KFHUDBase(PC.MyHUD);
|
|
}
|
|
|
|
if ( KFHud != None )
|
|
{
|
|
// Add self to player HUD PostRenderedActor list
|
|
KFHud.SetPostRenderingFor(bTurnOn, self);
|
|
}
|
|
}
|
|
|
|
/** Called by HUD for actors in HUD's PostRenderedActor list */
|
|
simulated event PostRenderFor( PlayerController PC, Canvas Canvas, vector CameraPosition, vector CameraDir )
|
|
{
|
|
local KFHUDBase PCHUD;
|
|
local bool bShowAllCategories;
|
|
local vector2d ScreenPos;
|
|
|
|
PCHUD = KFHUDBase(PC.myHUD );
|
|
|
|
if( PCHUD.ShouldDisplayDebug('All') || PCHUD.ShouldDisplayDebug('AllVerbose') )
|
|
{
|
|
bShowAllCategories = true;
|
|
}
|
|
|
|
if( MyKFAIC != none )
|
|
{
|
|
if( bShowAllCategories || PCHUD.ShouldDisplayDebug('AIMovement') || PCHUD.ShouldDisplayDebug('AIPathing') )
|
|
{
|
|
MyKFAIC.DrawDebugOverheadMovementPhaseData( PCHUD, ScreenPos );
|
|
}
|
|
if( bShowAllCategories || PCHUD.ShouldDisplayDebug('BehaviorTree') )
|
|
{
|
|
MyKFAIC.DrawBehaviorTreeIconOverhead( PCHUD );
|
|
}
|
|
}
|
|
|
|
if( PC != none && PC.myHUD != none && bDebug_DrawOverheadInfo )
|
|
{
|
|
// Note: HUD type will either be KFHUDBase or KFDebugCameraHUD (for ToggleDebugCamera mode) which is a KFHDUBase subclass.
|
|
// ...will need to make sure any other similar code works for both types.
|
|
PCHUD = KFHUDBase(PC.myHUD );
|
|
if( PCHUD != none )
|
|
{
|
|
DrawDebugOverheadText( PCHUD, ScreenPos );
|
|
}
|
|
}
|
|
else if( PC != none && PC.myHUD != none && bDebug_DrawSprintingOverheadInfo )
|
|
{
|
|
PCHUD = KFHUDBase(PC.myHUD );
|
|
if( PCHUD != none )
|
|
{
|
|
DrawDebugOverheadSprintingText( PCHUD );
|
|
}
|
|
}
|
|
}
|
|
|
|
function DrawDebugOverheadText( KFHUDBase HUD, Out Vector2d ScreenPos )
|
|
{
|
|
local Texture2D Icon;
|
|
local PlayerController PC;
|
|
local Canvas Canvas;
|
|
local Vector CameraLoc, ScreenLoc;
|
|
local Rotator CameraRot;
|
|
local float X, Y;
|
|
local float DOT;
|
|
local array<string> OverheadTexts;
|
|
local array<Color> OverheadColors;
|
|
local int i;
|
|
|
|
if( !IsAliveAndWell() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Canvas = HUD.Canvas;
|
|
ScreenLoc = Canvas.Project( Location + vect(0,0,1) * GetCollisionHeight() * 1.5f );
|
|
if( ScreenLoc.X < 0 || ScreenLoc.X >= HUD.Canvas.ClipX || ScreenLoc.Y < 0 && ScreenLoc.Y >= HUD.Canvas.ClipY )
|
|
{
|
|
return;
|
|
}
|
|
|
|
PC = HUD.PlayerOwner;
|
|
Canvas.SetDrawColor(0,255,64);
|
|
OverheadColors[OverheadColors.Length] = MakeColor(0,255,64);
|
|
|
|
PC.GetPlayerViewPoint( CameraLoc, CameraRot );
|
|
Dot = vector( CameraRot ) dot ( Location - CameraLoc );
|
|
if( Dot < 0.5f )
|
|
{
|
|
return;
|
|
}
|
|
// Draw Icon (need some unique Zed icons)
|
|
Icon = Texture2D'ENG_EditorResources_TEX.AI.S_AI';
|
|
if (Icon != None)
|
|
{
|
|
Canvas.SetPos( ScreenLoc.X - Icon.SizeX / 2, ScreenLoc.Y - Icon.SizeY / 2, ScreenLoc.Z );
|
|
Canvas.DrawTexture( Icon, 1.f );
|
|
X = ScreenLoc.X + Icon.SizeX/2 + 5;
|
|
Y = ScreenLoc.Y - Icon.SizeY/2;
|
|
}
|
|
else
|
|
{
|
|
X = ScreenLoc.X;
|
|
Y = ScreenLoc.Y;
|
|
}
|
|
|
|
if( ScreenPos.X == 0 && ScreenPos.Y == 0 )
|
|
{
|
|
Canvas.SetPos( X, Y );
|
|
}
|
|
else
|
|
{
|
|
Canvas.SetPos( ScreenPos.X, ScreenPos.Y );
|
|
}
|
|
|
|
Canvas.Font = class'Engine'.Static.GetSmallFont();
|
|
|
|
GetOverheadDebugText( HUD, OverheadTexts, OverheadColors );
|
|
|
|
// Draw the AI Command info, etc
|
|
if( MyKFAIC != none )
|
|
{
|
|
MyKFAIC.GetCommandStack( HUD, OverheadTexts, OverheadColors );
|
|
}
|
|
|
|
for (i = 0; i < OverheadTexts.length; i++)
|
|
{
|
|
// Set custom color if it exists
|
|
if( OverheadColors[i] != MakeColor(0,0,0,0) )
|
|
{
|
|
Canvas.SetDrawColor(OverheadColors[i].R, OverheadColors[i].G, OverheadColors[i].B, 255);
|
|
}
|
|
else
|
|
{
|
|
Canvas.SetDrawColor(0, 255, 64, 255);
|
|
}
|
|
|
|
Canvas.DrawText(OverheadTexts[i]);
|
|
}
|
|
|
|
ScreenPos.X = Canvas.CurX;
|
|
ScreenPos.Y = Canvas.CurY;
|
|
|
|
if( HUD.ShouldDisplayDebug('AIMovement') )
|
|
{
|
|
DrawDebugSphere( Location - vect(0,0,1) * (GetCollisionHeight() - MaxStepHeight), 8, 10, 255, 255, 0, FALSE); // Yellow = MaxStepHeight
|
|
DrawDebugSphere( Location + vect(0,0,1) * MaxJumpHeight, 15, 10, 0, 255, 0, FALSE); // Green = MaxJumpHeight
|
|
}
|
|
|
|
}
|
|
|
|
function DrawDebugOverheadSprintingText( KFHUDBase HUD )
|
|
{
|
|
local Texture2D moveTypeIcon;
|
|
local PlayerController plyCtrl;
|
|
local Canvas displayCanvas;
|
|
local Vector plyCameraLoc, plyScreenLoc;
|
|
local Rotator plyCameraRot;
|
|
local String displayStr;
|
|
local float displayX, displayY;
|
|
local float infrontDOT;
|
|
local Color newTextColor;
|
|
|
|
if( !IsAliveAndWell() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
displayCanvas = HUD.Canvas;
|
|
plyScreenLoc = displayCanvas.Project( Location + vect(0,0,1) * GetCollisionHeight() * 1.5f );
|
|
if( plyScreenLoc.X < 0 || plyScreenLoc.X >= HUD.Canvas.ClipX || plyScreenLoc.Y < 0 && plyScreenLoc.Y >= HUD.Canvas.ClipY )
|
|
{
|
|
return;
|
|
}
|
|
|
|
plyCtrl = HUD.PlayerOwner;
|
|
displayCanvas.SetDrawColor(255,255,255);
|
|
|
|
plyCtrl.GetPlayerViewPoint( plyCameraLoc, plyCameraRot );
|
|
infrontDOT = vector( plyCameraRot ) dot ( Location - plyCameraLoc );
|
|
if( infrontDOT < 0.5f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( bDebug_UseIconForShowingSprintingOverheadInfo )
|
|
{
|
|
if( bIsSprinting )
|
|
{
|
|
moveTypeIcon = MyKFAIC.MyAIDirector.GetDebugIsSprintingIcon();
|
|
}
|
|
else
|
|
{
|
|
moveTypeIcon = MyKFAIC.MyAIDirector.GetDebugIsWalkingIcon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
moveTypeIcon = none;
|
|
if( bIsSprinting )
|
|
{
|
|
displayStr = "S";
|
|
newTextColor = class'HUD'.default.RedColor;
|
|
}
|
|
else
|
|
{
|
|
displayStr = "W";
|
|
newTextColor = class'HUD'.default.GreenColor;
|
|
}
|
|
}
|
|
|
|
|
|
if (moveTypeIcon != None)
|
|
{
|
|
displayCanvas.SetPos( plyScreenLoc.X - moveTypeIcon.SizeX / 2, plyScreenLoc.Y - moveTypeIcon.SizeY / 2, plyScreenLoc.Z );
|
|
displayCanvas.DrawTexture( moveTypeIcon, 1.f );
|
|
displayX = plyScreenLoc.X + moveTypeIcon.SizeX/2 + 5;
|
|
displayY = plyScreenLoc.Y - moveTypeIcon.SizeY/2;
|
|
}
|
|
else
|
|
{
|
|
displayX = plyScreenLoc.X - GetCollisionRadius();
|
|
displayY = plyScreenLoc.Y;
|
|
}
|
|
displayCanvas.SetPos( displayX, displayY );
|
|
|
|
|
|
if( Len(displayStr) > 0 )
|
|
{
|
|
displayCanvas.Font = MyKFAIC.MyAIDirector.GetAiDebugScreenLargeFont();
|
|
class'KFAIController'.static.DrawDebugText( HUD, displayStr, newTextColor );
|
|
}
|
|
}
|
|
|
|
simulated function GetOverheadDebugText( KFHUDBase HUD, out array<string> OverheadTexts, out array<Color> OverheadColors )
|
|
{
|
|
local string DebugText;
|
|
local KFGameInfo KFGI;
|
|
local float HealthMod;
|
|
local float HeadHealthMod;
|
|
local bool bShowAll, bShowAllVerbose;
|
|
|
|
if( HUD.ShouldDisplayDebug('All') )
|
|
{
|
|
bShowAll = true;
|
|
}
|
|
|
|
if( HUD.ShouldDisplayDebug('AllVerbose') )
|
|
{
|
|
bShowAll = true;
|
|
bShowAllVerbose = true;
|
|
}
|
|
|
|
KFGI = KFGameInfo(WorldInfo.Game);
|
|
if ( KFGI != none )
|
|
{
|
|
KFGI.DifficultyInfo.GetAIHealthModifier(self, KFGI.GetModifiedGameDifficulty(), KFGI.GetLivingPlayerCount(), HealthMod, HeadHealthMod);
|
|
if( bShowAllVerbose || HUD.ShouldDisplayDebug('ZedHealthVerbose') )
|
|
{
|
|
DebugText = " Health: "$Health
|
|
$" HeadHealth: "$HitZones[HZI_HEAD].GoreHealth$"\n"
|
|
$" Starting Health: "$HealthMod*default.Health
|
|
$" Starting HeadHealth: "$HeadHealthMod*Default.HitZones[HZI_HEAD].GoreHealth$"\n"
|
|
$" Health Modifier: "$HealthMod
|
|
$" Default Health: "$default.Health
|
|
$" Default HeadHealth: "$Default.HitZones[HZI_HEAD].GoreHealth;
|
|
}
|
|
else if( bShowAll || HUD.ShouldDisplayDebug('ZedHealth') )
|
|
{
|
|
DebugText = " Health: "$Health
|
|
$" HeadHealth: "$HitZones[HZI_HEAD].GoreHealth
|
|
$" HeadHealth %: "$(HitZones[HZI_HEAD].GoreHealth/(HeadHealthMod*Default.HitZones[HZI_HEAD].GoreHealth) * 100);
|
|
}
|
|
}
|
|
|
|
OverheadTexts[OverheadTexts.Length] = DebugText;
|
|
|
|
if( bShowAll || HUD.ShouldDisplayDebug('AITargeting') )
|
|
{
|
|
if( MyKFAIC != none )
|
|
{
|
|
DebugText = "---------- AI Targeting ----------\n";
|
|
|
|
if ( MyKFAIC.Enemy != None )
|
|
{
|
|
DebugText = DebugText@"ENEMY: "$MyKFAIC.Enemy.GetHumanReadableName()$" Enemy Dist: "$VSize(MyKFAIC.Enemy.Location - Location)$"\n";
|
|
}
|
|
else
|
|
{
|
|
DebugText = DebugText@"ENEMY: NO Enemy "$"\n";
|
|
}
|
|
|
|
|
|
if( MyKFAIC.Focus != none )
|
|
{
|
|
DebugText = DebugText@"FOCUS: "$MyKFAIC.Focus$"\n";
|
|
DrawDebugLine(Mesh.GetBoneLocation(HeadBoneName), MyKFAIC.Focus.Location, 255, 255, 0, FALSE); // Yellow = focus
|
|
}
|
|
if( MyKFAIC.GetFocalPoint() != vect(0,0,0) )
|
|
{
|
|
DrawDebugLine(Mesh.GetBoneLocation(HeadBoneName), MyKFAIC.GetFocalPoint(), 255, 255, 0, FALSE); // Yellow = focal point
|
|
}
|
|
OverheadTexts[OverheadTexts.Length] = DebugText;
|
|
}
|
|
}
|
|
|
|
if( bShowAll || HUD.ShouldDisplayDebug('AIMovement') )
|
|
{
|
|
DebugText = "---------- AI MOVEMENT ----------\n";
|
|
DebugText = DebugText$"Velocity: "$VSize(Velocity)$" X: "$Velocity.X$" Y: "$Velocity.Y$" Z: "$Velocity.Z$" UU/S, "$VSize(Velocity)/100$"\n";
|
|
DebugText = DebugText$"Acceleration: "$VSize(Acceleration)$" X: "$Acceleration.X$" Y: "$Acceleration.Y$" Z: "$Acceleration.Z$" Physics: "$GetPhysicsName()$"\n";
|
|
//DebugText = DebugText$"SuperSpeed: "$IsUsingSuperSpeed()$" LastLOSOrRelevantTime: "$`TimeSince(LastLOSOrRelevantTime)$" LastRenderTime: "$`TimeSince(Mesh.LastRenderTime)$"\n";
|
|
DebugText = DebugText$"SuperSpeed: "$IsUsingSuperSpeed()$" LastLOSOrRelevantTime: "$`TimeSince(LastLOSOrRelevantTime)$" LastRenderTime: "$`TimeSince(LastRenderTime)$"\n";
|
|
if( MyKFAIC != none )
|
|
{
|
|
if( MyKFAIC.bPreparingMove )
|
|
{
|
|
DebugText = DebugText@"bPreparingMove:"$MyKFAIC.bPreparingMove;
|
|
}
|
|
|
|
//DebugText = DebugText@"AICommand: "$MyKFAIC.GetActiveCommand();
|
|
}
|
|
OverheadTexts[OverheadTexts.Length] = DebugText;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog
|
|
**/
|
|
|
|
/** Returns (hardcoded) dialog event ID for when players kills this zed type */
|
|
function int GetKillerDialogID()
|
|
{
|
|
return 65;//KILL_Generic
|
|
}
|
|
|
|
/** Returns (hardcoded) dialog event ID for when players spots this zed type */
|
|
function int GetSpotterDialogID()
|
|
{
|
|
return 125;//SPOTZ_Generic
|
|
}
|
|
|
|
function UpdateDeadHorseStreak( bool bStillActive)
|
|
{
|
|
if( bStillActive )
|
|
{
|
|
DeadHorseHitStreakAmt++;
|
|
}
|
|
else
|
|
{
|
|
DeadHorseHitStreakAmt = 1;
|
|
}
|
|
|
|
LastDeadHorseHitTime = WorldInfo.TimeSeconds;
|
|
}
|
|
|
|
/** Returns (hardcoded) trader advice dialog ID */
|
|
static function int GetTraderAdviceID()
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/** Play sounds when jumping and landing */
|
|
function PlayLeapedDialog();
|
|
function PlayLandedDialog();
|
|
|
|
function MotivatePlayerToAttack( float Percentage, class<DamageType> AntiGriefDamageTypeClass )
|
|
{
|
|
local PlayerController PC;
|
|
|
|
PC = PlayerController(Controller);
|
|
if( PC != none && WorldInfo.TimeSeconds - LastAttackHumanWarningTime > 9 )
|
|
{
|
|
PC.ReceiveLocalizedMessage( class'KFLocalMessage_Priority', GMT_AttackHumanPlayers );
|
|
LastAttackHumanWarningTime = WorldInfo.TimeSeconds;
|
|
}
|
|
|
|
TakeDamage( int(float(HealthMax) * 0.05f), none, Location + vRand()*5.f, vRand(), AntiGriefDamageTypeClass );
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name UI / Localization
|
|
********************************************************************************************* */
|
|
/**Looks up and returns localized name */
|
|
static function string GetLocalizedName()
|
|
{
|
|
local string MonsterName;
|
|
|
|
//check if we have a seasonal variant
|
|
MonsterName = Localize("Zeds", String(default.LocalizationKey) $ GetSeasonalLocalizationSuffix(), "KFGame");
|
|
|
|
//Has a question mark in it, which would indicate failure. return default.
|
|
if (InStr(MonsterName, "?") >= 0)
|
|
{
|
|
MonsterName = Localize("Zeds", String(default.LocalizationKey), "KFGame");
|
|
}
|
|
|
|
return MonsterName;
|
|
}
|
|
|
|
static function string GetSeasonalLocalizationSuffix()
|
|
{
|
|
//Remove any year information, just get 1s digit
|
|
switch (class'KFGameEngine'.static.GetSeasonalEventID() % 10)
|
|
{
|
|
case SEI_Spring:
|
|
return "_Spring";
|
|
case SEI_Summer:
|
|
return "_Summer";
|
|
case SEI_Fall:
|
|
return "_Fall";
|
|
case SEI_Winter:
|
|
return "_Winter";
|
|
default:
|
|
return "";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Armor
|
|
********************************************************************************************* */
|
|
|
|
function ZedExplodeArmor(int ArmorZoneIdx, name ArmorZoneName)
|
|
{
|
|
if (ArmorInfo != none)
|
|
{
|
|
ArmorInfo.ExplodeArmor(ArmorZoneIdx);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Achievements
|
|
********************************************************************************************* */
|
|
|
|
native protected function bool ShouldGrandOnDeathAchievement();
|
|
native protected function int GetZedOnDeathAchievement();
|
|
native function DisablebOnDeathAchivement();
|
|
|
|
DefaultProperties
|
|
{
|
|
// ---------------------------------------------
|
|
// AI / Navigation
|
|
PeripheralVision=-1.f // 360
|
|
Alertness=1.f
|
|
SightRadius=16384.f
|
|
ControllerClass=class'KFGame.KFAIController_Monster'
|
|
PursuitSpeedScale=1.f
|
|
PathSearchType=PST_Constraint
|
|
AccelConvergeFalloffDistance=400.f
|
|
bAllowAccelSmoothing=false
|
|
MatchEnemySpeedAtDistance=200.f
|
|
MinimumEnemySpeedToMatch=280.f
|
|
bModifyReachSpecCost=true
|
|
bDebug_DrawSprintingOverheadInfo=false
|
|
|
|
LedgeCheckThreshold=350.0f
|
|
bDebug_UseIconForShowingSprintingOverheadInfo=true
|
|
CollisionRadiusForReducedZedOnZedPinchPointCollisionState=1
|
|
|
|
Begin Object Name=KFPawnSkeletalMeshComponent
|
|
WireframeColor=(R=255,G=255,B=0,A=255)
|
|
bUseAsOccluder=TRUE
|
|
End Object
|
|
|
|
// ---------------------------------------------
|
|
// Special Moves
|
|
Begin Object Name=SpecialMoveHandler_0
|
|
SpecialMoveClasses(SM_MeleeAttack) =class'KFGame.KFSM_MeleeAttack'
|
|
SpecialMoveClasses(SM_MeleeAttackDoor) =class'KFSM_DoorMeleeAttack'
|
|
SpecialMoveClasses(SM_GrappleAttack) =class'KFGame.KFSM_GrappleCombined'
|
|
SpecialMoveClasses(SM_Stumble) =class'KFGame.KFSM_Stumble'
|
|
SpecialMoveClasses(SM_RecoverFromRagdoll)=class'KFGame.KFSM_RecoverFromRagdoll'
|
|
SpecialMoveClasses(SM_Knockdown) =class'KFSM_RagdollKnockdown'
|
|
SpecialMoveClasses(SM_DeathAnim) =class'KFSM_DeathAnim'
|
|
SpecialMoveClasses(SM_Stunned) =class'KFSM_Stunned'
|
|
SpecialMoveClasses(SM_Frozen) =class'KFSM_Frozen'
|
|
SpecialMoveClasses(SM_Taunt) =class'KFGame.KFSM_Zed_Taunt'
|
|
SpecialMoveClasses(SM_WalkingTaunt) =class'KFGame.KFSM_Zed_WalkingTaunt'
|
|
SpecialMoveClasses(SM_BossTheatrics) =class'KFGame.KFSM_Zed_Boss_Theatrics'
|
|
End Object
|
|
|
|
IncapSettings(AF_Poison)=(Cooldown=5.0, Duration=5.0,)
|
|
IncapSettings(AF_Microwave)=(Cooldown=5.0, Duration=5.0,)
|
|
IncapSettings(AF_Freeze)=(Cooldown=5.0)
|
|
IncapSettings(AF_Snare)=(Cooldown=5.0, Duration=5.0,)
|
|
|
|
// ---------------------------------------------
|
|
// Movement / Physics
|
|
bCanCrouch=false
|
|
AccelRate=+02048.000000
|
|
JumpZ=750.f
|
|
HiddenGroundSpeed=600.f // same speed for all monsters
|
|
bCanStrafe=true
|
|
MaxJumpHeight=128.f
|
|
MaxTurningRadius=64.f
|
|
BumpDamageType=class'KFDT_NPCBump'
|
|
BumpFrequency=0.5f
|
|
bLimitFallAccel=TRUE
|
|
SpeedAdjustTransitionRate=200.f
|
|
InitialGroundSpeedModifier=1.0
|
|
|
|
// ---------------------------------------------
|
|
// Spawning
|
|
MinSpawnSquadSizeType=EST_Small
|
|
|
|
// ---------------------------------------------
|
|
// Gameplay
|
|
DifficultySettings=class'KFMonsterDifficultyInfo'
|
|
|
|
Begin Object Class=KFMeleeHelperAI Name=MeleeHelper_0
|
|
MaxHitRange=180.f
|
|
BaseDamage=6.f
|
|
End Object
|
|
MeleeAttackHelper=MeleeHelper_0
|
|
|
|
bHasExtraSprintJumpVelocity=false
|
|
bCanRage=false
|
|
bCanGrabAttack=false
|
|
bCanMeleeAttack=true
|
|
ReachedEnemyThresholdScale=1.f
|
|
ZedBumpDamageScale=1.f
|
|
HeadlessBleedOutTime=5.f
|
|
ParryResistance=1
|
|
DifficultyDamageMod=1.0
|
|
GameResistancePct=1.f
|
|
|
|
//NapalmCheckInterval=0.5f
|
|
|
|
// Blocking
|
|
MinBlockFOV=0.1f
|
|
BlockSprintSpeedModifier=0.75f
|
|
|
|
bIsStalkerClass=false
|
|
bIsCrawlerClass=false
|
|
bIsFleshpoundclass=false
|
|
bIsClotClass=false
|
|
bIsBloatClass=false
|
|
|
|
bDisableGoreMeshWhileAlive=false
|
|
|
|
DamageInflationPercent = 1.0
|
|
IntendedDamageInflationPercent = 1.0
|
|
DamageInflationRate=1.0
|
|
RandomColorIdx=-1
|
|
|
|
bCanBePinned=true
|
|
|
|
// ---------------------------------------------
|
|
// Hit Zones
|
|
HitZones.Empty
|
|
HitZones.Add((ZoneName=head, BoneName=Head, Limb=BP_Head, GoreHealth=20, DmgScale=1.1, SkinID=1))
|
|
HitZones.Add((ZoneName=neck, BoneName=Neck, Limb=BP_Head, GoreHealth=20))
|
|
HitZones.Add((ZoneName=chest, BoneName=Spine2, Limb=BP_Torso, GoreHealth=150))
|
|
HitZones.Add((ZoneName=heart, BoneName=Spine2, Limb=BP_Special, GoreHealth=150))
|
|
HitZones.Add((ZoneName=lupperarm, BoneName=LeftArm, Limb=BP_LeftArm, GoreHealth=50))
|
|
HitZones.Add((ZoneName=lforearm, BoneName=LeftForearm, Limb=BP_LeftArm, GoreHealth=15))
|
|
HitZones.Add((ZoneName=lhand, BoneName=LeftForearm, Limb=BP_LeftArm, GoreHealth=20))
|
|
HitZones.Add((ZoneName=rupperarm, BoneName=RightArm, Limb=BP_RightArm, GoreHealth=50))
|
|
HitZones.Add((ZoneName=rforearm, BoneName=RightForearm, Limb=BP_RightArm, GoreHealth=15))
|
|
HitZones.Add((ZoneName=rhand, BoneName=RightForearm, Limb=BP_RightArm, GoreHealth=20))
|
|
HitZones.Add((ZoneName=stomach, BoneName=Spine1, Limb=BP_Torso, GoreHealth=150))
|
|
HitZones.Add((ZoneName=abdomen, BoneName=Hips, Limb=BP_Torso, GoreHealth=150))
|
|
HitZones.Add((ZoneName=lthigh, BoneName=LeftUpLeg, Limb=BP_LeftLeg, GoreHealth=75))
|
|
HitZones.Add((ZoneName=lcalf, BoneName=LeftLeg, Limb=BP_LeftLeg, GoreHealth=25))
|
|
HitZones.Add((ZoneName=lfoot, BoneName=LeftLeg, Limb=BP_LeftLeg, GoreHealth=15))
|
|
HitZones.Add((ZoneName=rthigh, BoneName=RightUpLeg, Limb=BP_RightLeg, GoreHealth=75))
|
|
HitZones.Add((ZoneName=rcalf, BoneName=RightLeg, Limb=BP_RightLeg, GoreHealth=25))
|
|
HitZones.Add((ZoneName=rfoot, BoneName=RightLeg, Limb=BP_RightLeg, GoreHealth=15))
|
|
|
|
// Pad the arrays so we can modify them with the live update system if needed
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
LiveDamageTypeModifiers.Add(())
|
|
|
|
WeakSpotSocketNames.Add(FX_Dazed) // Head
|
|
|
|
// List of BodySetups turned to phycsi for arm injury
|
|
ArmPhysicsBoneList=("RightShoulder","RightArm","RightForeArm","RightHand")
|
|
|
|
BleedIncapFX=ParticleSystem'FX_Gameplay_EMIT_THREE.FX_Incap_Bleed_01'
|
|
|
|
// ---------------------------------------------
|
|
// Animation
|
|
bCanHeadTrack=true
|
|
|
|
`if(`notdefined(ShippingPC))
|
|
DebugRadarTexture=Texture2D'GP_Debug.SineWaveMarker_TEX';
|
|
`endif
|
|
OnDeathAchievementID = INDEX_NONE
|
|
|
|
bKnockdownWhenJumpedOn = True
|
|
|
|
// ---------------------------------------------
|
|
// sounds
|
|
Begin Object Class=AkComponent name=SprintAkComponent0
|
|
BoneName=dummy
|
|
bStopWhenOwnerDestroyed=true
|
|
bForceOcclusionUpdateInterval=true
|
|
OcclusionUpdateInterval=0.2f
|
|
End Object
|
|
SprintAkComponent=SprintAkComponent0
|
|
Components.Add(SprintAkComponent0)
|
|
|
|
Begin Object Class=AkComponent name=HeadshotAkComponent0
|
|
bStopWhenOwnerDestroyed=false
|
|
End Object
|
|
HeadShotAkComponent=HeadshotAkComponent0
|
|
Components.Add(HeadshotAkComponent0)
|
|
|
|
bSprintOverride=false
|
|
|
|
VortexAttracionModifier=1.0f
|
|
}
|