1
0
KF2-Dev-Scripts/KFGame/Classes/KFPawn_Human.uc
2021-03-23 19:19:31 +03:00

2213 lines
62 KiB
Ucode

//=============================================================================
// KFPawn_Human
//=============================================================================
// KF Pawn for human characters
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFPawn_Human extends KFPawn
nativereplication
native(Pawn);
`include(KFGame\KFGameAnalytics.uci);
`include(KFGame\KFMatchStats.uci);
/** Current movement speed penalty based on health */
var float LowHealthSpeedPenalty;
/*********************************************************************************************
* @name Animation
********************************************************************************************* */
/** Replicated state of 1st person for 3rd person animations */
var repnotify byte CurrentWeaponState;
/** Replicated 3rd person attachment animation rate value, for matching up to 1st person anims */
var protected byte WeaponAttachmentAnimRateByte;
/** Item Id for 3rd person weapon skin */
var const repnotify int WeaponSkinItemId;
/** Random face expression poses when dead */
var array<name> DeathFaceAnims;
/*********************************************************************************************
* @name Effects
********************************************************************************************* */
/** How long to wait after a pawn is dead before spawning the blood pool */
var globalconfig float BloodPoolDelay;
/** chance to play a pain sound when hit */
var float PainSoundChanceOnHit;
/** how long before another pain sound can be played when hit */
var float PainSoundCoolDown;
/** the last time a pain sound was played */
var float PainSoundLastPlayedTime;
var string PerkFXEmitterPoolClassPath;
var transient EmitterPool PerkFXEmitterPool;
/** Default values for material based damage effects on death */
var const name DefaultDeathMaterialEffectParamName;
var const float DefaultDeathMaterialEffectDuration;
/**
* Book keeping variables for material based damage effects.
* Note: Although multiple effects can coexist, only single effect interpolation is supported at any given time.
* This works as long as the interp time is not too long. Consider making this an array if that does not hold true.
*/
var transient float DeathMaterialEffectDuration;
var transient float DeathMaterialEffectTimeRemaining;
var transient name DeathMaterialEffectParamName;
struct native KFPowerUpFxInfo
{
/** the power up type the player has */
var class<KFPowerUp> PowerUpType;
var byte Count;
};
struct native KFPowerUpCurrentFxInfo
{
/** the power up type the player has */
var class<KFPowerUp> PowerUpType;
/** the particle component associated with the effect */
var ParticleSystemComponent ParticleEffect;
};
/** replicated information on a powerup effect */
var repnotify KFPowerUpFxInfo PowerUpFxInfo;
/** replicated information on a powerup effect */
var repnotify KFPowerUpFxInfo PowerUpFxStopInfo;
var KFPowerUpCurrentFxInfo CurrentPowerUpEffect;
/*********************************************************************************************
* @name Flashlight (aka Torch)
********************************************************************************************* */
/** A flashlight flash instance */
var transient KFFlashlightAttachment FlashLight;
/** A reference to the muzzle flash template */
var const KFFlashlightAttachment FlashLightTemplate;
/** Toggles a clients flashlight for all other clients */
var repnotify bool bFlashlightOn;
/** Toggles a client iron sight for all other clients */
var repnotify bool bUsingIronSights;
/** Toggles a client iron sight for all other clients */
var repnotify bool bUsingAltFireMode;
var() float BatteryDrainRate;
var() float BatteryRechargeRate;
var float BatteryCharge;
/** Night vision active (local player only) */
var float NVGBatteryDrainRate;
/*********************************************************************************************
* @name Night Vision
********************************************************************************************* */
var() PointLightComponent NightVisionLightTemplate;
var transient PointLightComponent NightVisionLight;
/*********************************************************************************************
* @name HUD
********************************************************************************************* */
/** Needed to render the players health etc **/
var KFGFxMoviePlayer_PlayerInfo PlayerPartyInfo;
var byte ActivePerkMessageIdx;
/*********************************************************************************************
* @name Health & Armor
********************************************************************************************* */
var byte HealthToRegen;
var float HealthRegenRate;
var float HealerRewardScaler;
var byte MaxArmor;
var byte Armor;
var byte IntegrityLevel_High;
var byte IntegrityLevel_Medium;
var byte IntegrityLevel_Low;
var float ArmorAbsorbModifier_High;
var float ArmorAbsorbModifier_Medium;
var float ArmorAbsorbModifier_Low;
/*********************************************************************************************
* @name Solo Surrounded Difficulty Reduction
********************************************************************************************* */
/** Minimum number of enemies to trigger surrounded difficulty reduction when playing solo */
var byte MinEnemiesToTriggerSurrounded;
/** Health percent threshold that must be reached to trigger surrounded when solo */
var float MinHealthPctToTriggerSurrounded;
/*********************************************************************************************
* @name Perk @ToDo: Move stuff to PRI and combine in a byte/INT
********************************************************************************************* */
var array<string> ActiveSkillIconPaths;
var repnotify private byte HealingSpeedBoost;
var repnotify private byte HealingDamageBoost;
var repnotify private byte HealingShield;
var transient KFExplosion_AirborneAgent AAExplosionActor;
/*********************************************************************************************
* @name Objectives
********************************************************************************************* */
var bool bObjectivePlayer;
/*********************************************************************************************
* @name Dialog
********************************************************************************************* */
var transient int DoshCaughtStreakAmt;
var transient float LastDoshCaughtTime;
var transient PlayerReplicationInfo LastDoshCaughtGiver;
var transient int ZedsKilledStreakAmt;
var transient float LastZedKilledTime;
var transient int DamageTakenStreakAmt;
var transient float LastDamageTakenStreakStartTime;
var transient float InitialContinousDamageTime;
var transient float IdleStartTime;
var transient int EnvironmentDialogEventID;
var transient float SprintTowardZedStartTime;
var transient float SprintStartTime;
var protected AkComponent TraderDialogAkComponent;
/** Game info set variable to disable dialog on clients for specific game modes */
var bool bDisableTraderDialog;
struct native DialogResponseInfo
{
var KFPawn Speaker;
var KFPawn RespondingToPawn;
var int EventID;
var int RespondingToID;
};
var DialogResponseInfo DlgRespInfo;
replication
{
// Replicated to ALL
if(bNetDirty)
Armor, MaxArmor, bObjectivePlayer, WeaponSkinItemId, HealingSpeedBoost,
HealingDamageBoost, HealingShield, HealthToRegen, PowerUpFxInfo, PowerUpFxStopInfo;
// Replicated to owning client
if (bNetDirty && bNetOwner)
bDisableTraderDialog;
// Replicated to all but the owning client
if(bNetDirty && (!bNetOwner || bDemoRecording))
CurrentWeaponState, WeaponAttachmentAnimRateByte, bFlashlightOn, bUsingIronSights, bUsingAltFireMode;
}
cpptext
{
// Actor
INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
virtual void TickAuthoritative( FLOAT DeltaSeconds );
virtual FLOAT MaxSpeedModifier();
virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive,AActor *SourceActor, DWORD TraceFlags);
}
/*********************************************************************************************
* @name General
********************************************************************************************* */
simulated event Tick( float DeltaTime )
{
local float NewSpeedPenalty;
super.Tick( DeltaTime );
if ( Role == ROLE_Authority )
{
// Update movement speed based on health
if ( Health < HealthMax )
{
// Use 100 instead of HealthMax so that perk health bonuses do not penalize movement
NewSpeedPenalty = Lerp(0.15f, 0.f, FMin(float(Health) / 100, 1.f));
}
else
{
NewSpeedPenalty = 0.f;
}
if ( NewSpeedPenalty != LowHealthSpeedPenalty )
{
LowHealthSpeedPenalty = NewSpeedPenalty;
UpdateGroundSpeed();
}
}
if( WorldInfo.NetMode != NM_DedicatedServer )
{
if( DeathMaterialEffectTimeRemaining > 0 )
{
UpdateDeathMaterialEffect( DeltaTime );
}
}
}
/*********************************************************************************************
* @name Constructors, Destructors, and Loading
********************************************************************************************* */
// Called immediately after gameplay begins.
simulated event PreBeginPlay()
{
local class<EmitterPool> PoolClass;
super.PreBeginPlay();
if( WorldInfo.NetMode != NM_DedicatedServer )
{
if( PerkFXEmitterPoolClassPath != "" )
{
PoolClass = class<EmitterPool>(DynamicLoadObject(PerkFXEmitterPoolClassPath, class'Class'));
if( PoolClass != None )
{
PerkFXEmitterPool = Spawn(PoolClass, self,, vect(0,0,0), rot(0,0,0));
}
}
}
}
function PossessedBy(Controller C, bool bVehicleTransition)
{
Super.PossessedBy(C, bVehicleTransition);
// Reset perk buffs
ResetHealingSpeedBoost();
ResetHealingDamageBoost();
ResetHealingShield();
ResetIdleStartTime();
// See if we should start our surrounded timer
if( IsAliveAndWell() && WorldInfo.Game != none && WorldInfo.Game.NumPlayers == 1 && KFGameInfo(WorldInfo.Game).bOnePlayerAtStart )
{
SetTimer( 1.f, true, nameOf(Timer_CheckSurrounded) );
}
}
simulated function NotifyTeamChanged()
{
local KFPerk InstigatorPerk;
// Applies Character Info for < ROLE_Authority
if( PlayerReplicationInfo != None )
{
SetCharacterArch(GetCharacterInfo());
}
InstigatorPerk = GetPerk();
if( InstigatorPerk != none )
{
InstigatorPerk.NotifyPawnTeamChanged();
}
}
/**
* Check on various replicated data and act accordingly.
*/
simulated event ReplicatedEvent(name VarName)
{
switch( VarName )
{
case nameof(CurrentWeaponState):
WeaponStateChanged(CurrentWeaponState, true);
break;
case nameof(WeaponSkinItemId):
if ( WeaponAttachment != None && WeaponSkinItemId > 0 )
{
WeaponAttachment.SetWeaponSkin(WeaponSkinItemId);
}
break;
case nameof(bFlashlightOn):
SetFlashlight(bFlashlightOn, false);
break;
case nameof(bUsingIronSights):
SetIronSights(bUsingIronSights, false);
break;
case nameof(bUsingAltFireMode):
SetUsingAltFireMode (bUsingAltFireMode, false);
break;
case nameof(PowerUpFxInfo):
PlayPowerUpEffect(PowerUpFxInfo);
break;
case nameof(PowerUpFxStopInfo):
StopPowerUpEffect(PowerUpFxStopInfo);
break;
case nameof(HealingSpeedBoost):
NotifyHealingSpeedBoostBuff(HealingSpeedBoost);
break;
case nameof(HealingDamageBoost):
NotifyHealingDamageBoostBuff(HealingDamageBoost);
break;
case nameof(HealingShield):
NotifyHealingShieldBoostBuff(HealingShield);
break;
}
Super.ReplicatedEvent(VarName);
}
simulated event Destroyed()
{
if( PlayerPartyInfo != none )
{
PlayerPartyInfo.SetVisible( false );
PlayerPartyInfo.Close();
}
if ( Flashlight != None )
{
Flashlight.DetachFlashlight();
}
if( AAExplosionActor != none )
{
AAExplosionActor.Destroy();
}
super.Destroyed();
}
/** Don't want to set this in the character arch, will do it later OnCharacterMeshChanged*/
simulated function SetCharacterArchAnimationInfo() {}
/** Set various basic properties for this KFPawn based on the character class metadata */
simulated function SetCharacterArch(KFCharacterInfoBase Info, optional bool bForce )
{
Super.SetCharacterArch(Info);
if( WorldInfo.NetMode != NM_DedicatedServer )
{
// Attach/Reattach flashlight components when mesh is set
if ( Flashlight == None && FlashLightTemplate != None )
{
Flashlight = new(self) Class'KFFlashlightAttachment' (FlashLightTemplate);
Flashlight.AttachFlashlight(Mesh);
}
else if ( FlashLight != None )
{
Flashlight.Reattach();
}
}
}
/** Notify pawn whenever mesh is swapped (e.g. new character or new outfit) */
simulated function OnCharacterMeshChanged()
{
if ( FlashLight != None )
{
Flashlight.Reattach();
}
// If the character mesh was async loaded, the call to SetCharacterAnimationInfo in
// KFPawn::SetCharacterArch will not do what it's supposed to do because it needs a skeletal mesh.
// So, call it here, once the skeletal mesh is loaded.
SetCharacterAnimationInfo();
}
/*********************************************************************************************
* @name Inventory
********************************************************************************************* */
/**
* Overridden to iterate through the DefaultInventory array and
* give each item to this Pawn.
*
* @see GameInfo.AddDefaultInventory
*/
function AddDefaultInventory()
{
local KFPerk MyPerk;
local KFGameInfo GameInfo;
MyPerk = GetPerk();
if( MyPerk != none )
{
MyPerk.AddDefaultInventory(self);
}
/** DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject("KFGameContent.KFWeap_Pistol_9mm", class'Class')));
Loading the secondary weapon in the perk again */
GameInfo = KFGameInfo(WorldInfo.Game);
if(GameInfo.OutbreakEvent == none || !GameInfo.OutbreakEvent.ActiveEvent.bCannotBeHealed)
{
DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject("KFGameContent.KFWeap_Healer_Syringe", class'Class')));
}
DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject("KFGameContent.KFWeap_Welder", class'Class')));
DefaultInventory.AddItem(class<Inventory>(DynamicLoadObject("KFGameContent.KFInventory_Money", class'Class')));
Super.AddDefaultInventory();
}
/** When switching weapon modify GroundSpeed by encumbrance level */
simulated function PlayWeaponSwitch(Weapon OldWeapon, Weapon NewWeapon)
{
Super.PlayWeaponSwitch(OldWeapon, NewWeapon);
if( Role == ROLE_Authority )
{
// Update GroundSpeed on weapon switch in case our perk
// has a weapon specific movement bonus
UpdateGroundSpeed();
`DialogManager.PlaySwitchToFavoriteWeaponDialog( self );
}
}
simulated function bool CanThrowWeapon()
{
local KFPlayerController KFPC;
KFPC = KFPlayerController(Controller);
if (KFPC != none && KFPC.MyGFxManager != none && KFPC.MyGFxManager.TraderMenu != none && KFPC.MyGFxManager.CurrentMenu == KFPC.MyGFxManager.TraderMenu)
{
return false;
}
return super.CanThrowWeapon();
}
/**
* Event called from native code when Pawn starts crouching.
* Called on non owned Pawns through bIsCrouched replication.
* Network: ALL
*
* @param HeightAdjust height difference in unreal units between default collision height, and actual crouched cylinder height.
*/
simulated event StartCrouch( float HeightAdjust )
{
super.StartCrouch( HeightAdjust );
UpdateGroundSpeed();
if (WeaponAttachment != none)
{
WeaponAttachment.StartPawnCrouch ();
}
}
/**
* Event called from native code when Pawn stops crouching.
* Called on non owned Pawns through bIsCrouched replication.
* Network: ALL
*
* @param HeightAdjust height difference in unreal units between default collision height, and actual crouched cylinder height.
*/
simulated event EndCrouch( float HeightAdjust )
{
Super.EndCrouch( HeightAdjust );
UpdateGroundSpeed();
if (WeaponAttachment != none)
{
WeaponAttachment.EndPawnCrouch ();
}
}
/**
* Reset/update GroundSpeed based on perk/weapon selection. GroundSpeed is used instead of
* MaxSpeedModifier() so that other physics code reacts properly (e.g. bLimitFallAccel)
* Network: Server Only
*/
function UpdateGroundSpeed()
{
local KFInventoryManager InvM;
local float WeightMod, HealthMod, WeaponMod;
local KFGameInfo KFGI;
local KFWeapon CurrentWeapon;
local KFPlayerController KFPC;
if ( Role < ROLE_Authority )
return;
CurrentWeapon = KFWeapon(Weapon);
InvM = KFInventoryManager(InvManager);
WeightMod = (InvM != None) ? InvM.GetEncumbranceSpeedMod() : 1.f;
HealthMod = GetHealthMod();
// some weapons can change a player's movement speed during certain states
WeaponMod = (CurrentWeapon != None) ? CurrentWeapon.MovementSpeedMod : 1.f;
//Grab new defaults
GroundSpeed = default.GroundSpeed;
SprintSpeed = default.SprintSpeed;
//Allow game info modifiers
KFGI = KFGameInfo(WorldInfo.Game);
if (KFGI != none)
{
KFGI.ModifyGroundSpeed(self, GroundSpeed);
KFGI.ModifySprintSpeed(self, SprintSpeed);
}
//Add pawn modifiers
GroundSpeed = GroundSpeed * WeightMod * HealthMod * WeaponMod;
SprintSpeed = SprintSpeed * WeightMod * HealthMod * WeaponMod;
// Ask our perk to set the new ground speed based on weapon type
if( GetPerk() != none )
{
GetPerk().ModifySpeed( GroundSpeed );
GetPerk().ModifySprintSpeed( SprintSpeed );
GetPerk().FinalizeSpeedVariables();
}
// Ask the current power up to set new ground speed
KFPC = KFPlayerController(Controller);
if( KFPC != none && KFPC.GetPowerUp() != none )
{
KFPC.GetPowerUp().ModifySpeed( GroundSpeed );
KFPC.GetPowerUp().ModifySprintSpeed( SprintSpeed );
KFPC.GetPowerUp().FinalizeSpeedVariables();
}
if (CurrentWeapon != None)
{
if (CurrentWeapon.OverrideGroundSpeed >= 0.0f)
{
GroundSpeed = CurrentWeapon.OverrideGroundSpeed;
}
if (CurrentWeapon.OverrideSprintSpeed >= 0.0f)
{
SprintSpeed = CurrentWeapon.OverrideSprintSpeed;
}
}
}
function float GetHealthMod()
{
return 1.f - LowHealthSpeedPenalty;
}
/*********************************************************************************************
* @name Animation
********************************************************************************************* */
/** Called when a pawn's weapon changes state. */
simulated function WeaponStateChanged(byte NewState, optional bool bViaReplication)
{
CurrentWeaponState = NewState;
// skip if this pawn was recently spawned, so we don't play out-of-date anims when pawns become relevant
if( `TimeSince(CreationTime) < 1.f )
{
return;
}
// skip weapon anims while in a special move
if( IsDoingSpecialMove() && !SpecialMoves[SpecialMove].bAllowThirdPersonWeaponAnims )
{
return;
}
if( WeaponAttachment != None )
{
WeaponAttachment.UpdateThirdPersonWeaponAction( EWeaponState(CurrentWeaponState), self, GetWeaponAttachmentAnimRateByte() );
}
}
/** Sets the current weapon animation rate to synchronize 3rd person animations with 1st person */
function SetWeaponAttachmentAnimRateByte( float NewAnimRate )
{
WeaponAttachmentAnimRateByte = FloatToByte( fClamp(NewAnimRate - 1.f, 0.f, 1.f) );
}
/** Returns the animation rate to scale all animations in the WeaponAttachment by */
simulated function byte GetWeaponAttachmentAnimRateByte()
{
return WeaponAttachmentAnimRateByte;
}
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
Super.PostInitAnimTree(SkelComp);
BodyStanceNodes[EAS_CH_UpperBody] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_CH_Upper'));
BodyStanceNodes[EAS_CH_LowerBody] = AnimNodeSlot(SkelComp.FindAnimNode('Custom_CH_Lower'));
RecoilNodes[0] = GameSkelCtrl_Recoil(SkelComp.FindSkelControl('SpineRecoil'));
//RecoilNodes[1] = GameSkelCtrl_Recoil(SkelComp.FindSkelControl('Recoil_Hand'));
}
/** Stops all animations on character */
simulated function StopAllAnimations()
{
local name FacePose;
FacePose = DeathFaceAnims[Rand(DeathFaceAnims.Length)];
PlayBodyAnim(FacePose, EAS_Face,,,, true);
Super.StopAllAnimations();
}
simulated function CheckAndEndActiveEMoteSpecialMove()
{
if( IsDoingSpecialMove() && SpecialMove == SM_Emote )
{
SpecialMoveHandler.EndSpecialMove( SM_EMote );
}
}
/*********************************************************************************************
* @name Health
********************************************************************************************* */
simulated function bool CanBeHealed()
{
return true;
}
`if(`__TW_)
event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bCanRepairArmor=true, optional bool bMessageHealer=true)
`else
event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType)
`endif
{
local KFPlayerController KFPC;
local KFPowerUp KFPowerUp;
local KFGameInfo GameInfo;
KFPC = KFPlayerController(Controller);
if ( KFPC != none )
{
KFPowerUp = KFPC.GetPowerUp();
if( KFPowerUp != none && !KFPowerUp.CanBeHealed())
{
return false;
}
}
GameInfo = KFGameInfo(WorldInfo.Game);
if (GameInfo.OutbreakEvent != none && GameInfo.OutbreakEvent.ActiveEvent.bCannotBeHealed)
{
return false;
}
return HealDamageForce(Amount, Healer, DamageType, bCanRepairArmor, bMessageHealer);
}
event bool HealDamageForce(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bCanRepairArmor=true, optional bool bMessageHealer=true)
{
local int DoshEarned;
local float UsedHealAmount;
local KFPlayerReplicationInfo InstigatorPRI;
local KFPlayerController InstigatorPC, KFPC;
local KFPerk InstigatorPerk;
local class<KFDamageType> KFDT;
local int i;
local bool bRepairedArmor;
local int OldHealth;
OldHealth = Health;
InstigatorPC = KFPlayerController(Healer);
InstigatorPerk = InstigatorPC != None ? InstigatorPC.GetPerk() : None;
if( InstigatorPerk != None )
{
if( bCanRepairArmor )
{
// Instigator might be able to repair some armomr
bRepairedArmor = InstigatorPerk.RepairArmor( self );
}
if( InstigatorPerk.GetHealingSpeedBoostActive() )
{
UpdateHealingSpeedBoost();
}
if( InstigatorPerk.GetHealingDamageBoostActive() )
{
UpdateHealingDamageBoost();
}
if( InstigatorPerk.GetHealingShieldActive() )
{
UpdateHealingShield();
}
}
if( Amount > 0 && IsAliveAndWell() && Health < HealthMax )
{
// Play any healing effects attached to this damage type
KFDT = class<KFDamageType>(DamageType);
if( KFDT != none && KFDT.default.bNoPain )
{
PlayHeal( KFDT );
}
else
{
`warn("No hit effects et for damagetype:" @DamageType);
}
if( Role == ROLE_Authority )
{
if( InstigatorPC == None || InstigatorPC.PlayerReplicationInfo == None )
{
return false;
}
InstigatorPRI = KFPlayerReplicationInfo(InstigatorPC.PlayerReplicationInfo);
UsedHealAmount = Amount;
if( InstigatorPerk != none )
{
if( InstigatorPerk.ModifyHealAmount( UsedHealAmount ) )
{
if( Controller != Healer && InstigatorPerk.IsHealingSurgeActive() )
{
if( InstigatorPC.Pawn != none )
{
InstigatorPC.Pawn.HealDamage(InstigatorPC.Pawn.HealthMax * InstigatorPerk.GetSelfHealingSurgePct(), InstigatorPC, class'KFDT_Healing');
}
}
}
}
// You can never have a HealthToRegen value that's greater than HealthMax
if( Health + HealthToRegen + UsedHealAmount > HealthMax )
{
UsedHealAmount = HealthMax - (Health + HealthToRegen);
}
HealthToRegen += UsedHealAmount;
SetTimer(HealthRegenRate, true, nameof( GiveHealthOverTime ));
// Give the healer money/XP for helping a teammate
if( InstigatorPC.Pawn != none && InstigatorPC.Pawn != self )
{
DoshEarned = ( UsedHealAmount / float(HealthMax) ) * HealerRewardScaler;
InstigatorPRI.AddDosh(Max(DoshEarned, 0), true);
InstigatorPC.AddHealPoints( UsedHealAmount );
}
if( Healer.bIsPlayer )
{
if( Healer != Controller )
{
InstigatorPC.ReceiveLocalizedMessage( class'KFLocalMessage_Game', GMT_HealedPlayer, PlayerReplicationInfo );
KFPC = KFPlayerController(Controller);
KFPC.ReceiveLocalizedMessage( class'KFLocalMessage_Game', GMT_HealedBy, Healer.PlayerReplicationInfo );
`RecordAARIntStat(KFPC, HEAL_RECEIVED, UsedHealAmount);
`RecordAARIntStat(InstigatorPC, HEAL_GIVEN, UsedHealAmount);
}
else
{
if( bMessageHealer )
{
InstigatorPC.ReceiveLocalizedMessage( class'KFLocalMessage_Game', GMT_HealedSelf, PlayerReplicationInfo );
}
}
}
// don't play dialog for healing done through perk skills (e.g. berserker vampire skill)
if( bMessageHealer )
{
`DialogManager.PlayHealingDialog( KFPawn(Healer.Pawn), self, float(Health + HealthToRegen) / float(HealthMax) );
}
// Reduce burn duration and damage in half if you heal while burning
for( i = 0; i < DamageOverTimeArray.Length; ++i )
{
if( DamageOverTimeArray[i].DoT_Type == DOT_Fire )
{
DamageOverTimeArray[i].Duration *= 0.5;
DamageOverTimeArray[i].Damage *= 0.5;
break;
}
}
if (Health - OldHealth > 0)
{
WorldInfo.Game.ScoreHeal(Health - OldHealth, OldHealth, Healer, self, DamageType);
}
return true;
}
}
if (Health - OldHealth > 0)
{
WorldInfo.Game.ScoreHeal(Health - OldHealth, OldHealth, Healer, self, DamageType);
}
return bRepairedArmor;
}
/** Network: Server only */
function GiveHealthOverTime()
{
local KFPlayerReplicationInfo KFPRI;
if( HealthToRegen > 0 && Health < HealthMax )
{
Health++;
HealthToRegen--;
WorldInfo.Game.ScoreHeal(1, Health - 1, Controller, self, none);
KFPRI = KFPlayerReplicationInfo( PlayerReplicationInfo );
if( KFPRI != none )
{
KFPRI.PlayerHealth = Health;
KFPRI.PlayerHealthPercent = FloatToByte( float(Health) / float(HealthMax) );
}
}
else
{
HealthToRegen = 0;
ClearTimer( nameof( GiveHealthOverTime ) );
}
}
/*********************************************************************************************
* @name Armor
********************************************************************************************* */
function AddArmor( int Amount )
{
Armor = Min( Armor + Amount, GetMaxArmor() );
}
function GiveMaxArmor()
{
Armor = GetMaxArmor();
}
function int GetMaxArmor()
{
return MaxArmor; //Perk might adjust that later
}
function ShieldAbsorb( out int InDamage )
{
local float AbsorbedPct;
local int AbsorbedDmg;
local KFPerk MyPerk;
MyPerk = GetPerk();
if( MyPerk != none && MyPerk.HasHeavyArmor() )
{
AbsorbedDmg = Min(InDamage, Armor);
Armor -= MyPerk.GetArmorDamageAmount( AbsorbedDmg );
InDamage -= AbsorbedDmg;
return;
}
// Three levels of armor integrity
if( Armor >= IntegrityLevel_High )
{
AbsorbedPct = ArmorAbsorbModifier_High;
}
else if( Armor >= IntegrityLevel_Medium )
{
AbsorbedPct = ArmorAbsorbModifier_Medium;
}
else
{
AbsorbedPct = ArmorAbsorbModifier_Low;
}
AbsorbedDmg = Min(Round(AbsorbedPct * InDamage), Armor);
// reduce damage and armor
Armor -= AbsorbedDmg;
InDamage -= AbsorbedDmg;
}
/*********************************************************************************************
* @name Effects / Gore
********************************************************************************************* */
/**
* Spawn a blood pool decal effect using the GoreManager
*/
simulated function LeaveBloodPool()
{
local KFGoreManager GoreManager;
GoreManager = KFGoreManager(WorldInfo.MyGoreEffectManager);
if( GoreManager != none )
{
// Spawn a blood pool
GoreManager.LeaveABloodPoolDecal(self);
}
}
simulated function PlayTakeHitEffects( vector HitDirection, vector HitLocation, optional bool bUseHitImpulse = true)
{
local class<KFDamageType> DmgType;
local name HitBoneName, RBBoneName;
local int HitZoneIndex;
DmgType = HitFxInfo.DamageType;
if( WorldInfo.TimeSeconds > PainSoundLastPlayedTime + PainSoundCoolDown )
{
if( PainSoundChanceOnHit >= (1.f - FRand()) )
{
SoundGroupArch.PlayPainSound( self );
PainSoundLastPlayedTime = WorldInfo.TimeSeconds;
}
}
Super.PlayTakeHitEffects( HitDirection, HitLocation, bUseHitImpulse );
// @TODO Move to PlayDying()
// Add some death ragdoll velocity
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 )
{
HitZoneIndex = HitFxInfo.HitBoneIndex;
if ( HitZoneIndex != 255 ) // INDEX_None -> 255 after byte conversion
{
HitBoneName = HitZones[HitZoneIndex].BoneName;
}
if( HitBoneName != '' )
{
RBBoneName = GetRBBoneFromBoneName( HitBoneName );
}
if (bUseHitImpulse)
{
ApplyRagdollImpulse(DmgType, HitLocation, HitDirection, RBBoneName, 1.f);
}
}
}
}
/** Makes an impact sound and leaves a blood splat upon body impact */
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);
}
}
}
}
/** Called clientside by PlayTakeHitEffects on the Instigating Pawn */
simulated function PlayDamageInstigatorHitEffects(KFPawn Victim)
{
local float BloodParamIncrementValue;
Super.PlayDamageInstigatorHitEffects(Victim);
if ( WeaponAttachment != None )
{
if ( WorldInfo.NetMode != NM_DedicatedServer &&
VSizeSq(Victim.Location - Location) < BattleBloodRangeSq )
{
BloodParamIncrementValue = RandRange(0.01, 0.05);
// Amplify blood for killing blows
if ( WorldInfo.TimeSeconds == Victim.TimeOfDeath )
{
BloodParamIncrementValue *= 2.f;
}
// Apply blood to body and face
AddBattleBlood(BloodParamIncrementValue);
// Apply blood to weapon attachment
WeaponAttachment.AddBattleBlood(BloodParamIncrementValue);
}
}
}
/** Ambient battle blood added to chracter body and face as they kill Zeds */
simulated function AddBattleBlood(float InBattleBloodIncrementvalue)
{
local MaterialInstanceConstant MIC;
// Accumulate the blood param value of the pawn
BattleBloodParamValue = FMax(BattleBloodParamValue + InBattleBloodIncrementvalue, MinBattleBloodValue);
foreach CharacterMICs(MIC)
{
MIC.SetScalarParameterValue(BattleBloodParamName, BattleBloodParamValue);
}
}
simulated function SetNightVisionLight(bool bEnabled)
{
}
/** Clean up function to terminate any effects on death */
simulated function TerminateEffectsOnDeath()
{
Super.TerminateEffectsOnDeath();
if ( Flashlight != None )
{
Flashlight.OwnerDied();
}
}
function PlayPowerUp( class<KFPowerUp> PowerUpType )
{
PowerUpFxInfo.PowerUpType = PowerUpType;
PowerUpFxInfo.Count++;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
PlayPowerUpEffect(PowerUpFxInfo);
}
}
simulated function PlayPowerUpEffect( KFPowerUpFxInfo PUpFxInfo )
{
local KFPlayerController KFPC;
KFPC = KFPlayerController(Controller);
if( KFPC != none && PUpFxInfo.PowerUpType != none )
{
KFPC.PlayPowerUpEffect(PUpFxInfo.PowerUpType);
}
if ( PUpFxInfo.PowerUpType != None )
{
PUpFxInfo.PowerUpType.static.PlayEffects(self);
}
}
function StopPowerUp( class<KFPowerUp> PowerUpType )
{
PowerUpFxStopInfo.PowerUpType = PowerUpType;
PowerUpFxStopInfo.Count++;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
StopPowerUpEffect(PowerUpFxStopInfo);
}
}
simulated function StopPowerUpEffect( KFPowerUpFxInfo PUpFxInfo )
{
local KFPlayerController KFPC;
KFPC = KFPlayerController(Controller);
if( KFPC != none )
{
KFPC.StopPowerUpEffect(PUpFxInfo.PowerUpType);
}
if ( PUpFxInfo.PowerUpType != None && PUpFxInfo.PowerUpType == CurrentPowerUpEffect.PowerUpType && CurrentPowerUpEffect.ParticleEffect != none )
{
CurrentPowerUpEffect.ParticleEffect.DeactivateSystem();
CurrentPowerUpEffect.ParticleEffect = none;
}
}
/*********************************************************************************************
* @name Damage/Death Methods
********************************************************************************************* */
/* PlayDying() is called on server/standalone game when killed
and also on net client when pawn gets bTearOff set to true (and bPlayedDeath is false)
*/
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
local class<KFDamageType> KFDT;
super.PlayDying(DamageType, HitLoc);
if( AAExplosionActor != none )
{
AAExplosionActor.Destroy();
}
// If ragdoll was successful
if ( Physics == PHYS_RigidBody && !Mesh.HiddenGame )
{
KFDT = class<KFDamageType>(DamageType);
// Spawn a blood pool
SetTimer(BloodPoolDelay,false,nameof(LeaveBloodPool));
// Death material effect (damage type based)
PlayDeathMaterialEffects(
(KFDT != None && KFDT.default.DeathMaterialEffectParamName != '') ? KFDT.default.DeathMaterialEffectParamName : DefaultDeathMaterialEffectParamName,
(KFDT != None && KFDT.default.DeathMaterialEffectDuration != 0.f) ? KFDT.default.DeathMaterialEffectDuration : DefaultDeathMaterialEffectDuration
);
}
// Kill our world health indicator when we die
if( PlayerPartyInfo != none )
{
PlayerPartyInfo.Close(true);
PlayerPartyInfo = none;
}
}
function bool Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
// Don't check if we're surrounded when we're dead
ClearTimer( nameOf(Timer_CheckSurrounded) );
if( Super.Died( Killer, DamageType, HitLocation ) )
{
`DialogManager.PlayPlayerDeathDialog( self );
//ProTip: No, you do not have a PRI anymore.
return true;
}
return false;
}
/* AdjustDamage()
adjust damage based on inventory, other attributes
*/
function AdjustDamage(out int InDamage, out vector Momentum, Controller InstigatedBy, vector HitLocation, class<DamageType> DamageType, TraceHitInfo HitInfo, Actor DamageCauser)
{
local KFPerk MyKFPerk;
local float TempDamage;
local bool bHasSacrificeSkill;
`log(self @ GetFuncName()@"Adjusted Damage BEFORE =" @ InDamage, bLogTakeDamage);
super.AdjustDamage(InDamage, Momentum, InstigatedBy, HitLocation, DamageType, HitInfo, DamageCauser);
// nullify damage during trader time
if (KFGameReplicationInfo(KFGameInfo(WorldInfo.Game).GameReplicationInfo).bTraderIsOpen)
{
InDamage = 0;
return;
}
MyKFPerk = GetPerk();
if( MyKFPerk != none )
{
MyKFPerk.ModifyDamageTaken( InDamage, DamageType, InstigatedBy );
bHasSacrificeSkill = MyKFPerk.ShouldSacrifice();
}
TempDamage = InDamage;
if( TempDamage > 0 && class'KFPerk_Demolitionist'.static.IsDmgTypeExplosiveResistable( DamageType ) && HasExplosiveResistance() )
{
class'KFPerk_Demolitionist'.static.ModifyExplosiveDamage( TempDamage );
TempDamage = TempDamage < 1.f ? 1.f : TempDamage;
}
TempDamage *= GetHealingShieldModifier();
InDamage = Round( TempDamage );
// Reduce damage based on you current armor integrity
if( InDamage > 0 && Armor > 0 && DamageType.default.bArmorStops )
{
ShieldAbsorb( InDamage );
//Shield has taken all the damage. Setup the HitFXInfo for replication so we can
// respond to hit through the normal hit FX chain.
if (InDamage <= 0)
{
AddHitFX(InDamage, InstigatedBy, GetHitZoneIndex(HitInfo.BoneName), HitLocation, Momentum, class<KFDamageType>(DamageType));
}
}
if( bHasSacrificeSkill && Health >= 5 && Health - InDamage < 5 )
{
Health = InDamage + 5;
SacrificeExplode();
}
// register damage to divide up score
if( InstigatedBy != none )
{
AddTakenDamage( InstigatedBy, FMin(Health, InDamage), DamageCauser, class<KFDamageType>(DamageType) );
}
`log(self @ GetFuncName()@"Adjusted Damage AFTER =" @ InDamage, bLogTakeDamage);
// (Cheats) Dont allow dying if demigod mode is enabled
`if(`__TW_SDK_)
if ( Controller != none && Controller.bDemiGodMode && InDamage >= Health )
{
// Increase your health when you are going to get killed... so the amount of damage in semigod is not always just 1...
// Some ais do different reactions depending on the amount of damaged caused in the last x seconds...
if ( Health == 1 )
{
Health = HealthMax * 0.25f;
}
if( InDamage >= Health )
{
InDamage = Health - 1;
}
}
`endif
}
event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
local int ActualDamageTaken, OldHealth, OldArmor;
//local KFGameInfo KFGI;
local KFGameReplicationInfo KFGRI;
local KFPlayerReplicationInfo KFPRI;
local KFAIController_ZedBoss InstigatedByBoss;
OldHealth = Health;
OldArmor = Armor;
`log(GetFuncName()@"Damage BEFORE ="$Damage$" DamageType: "$DamageType$" DamageCauser: "$DamageCauser, bLogTakeDamage);
Super.TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
ActualDamageTaken = OldHealth - Health;
`log(GetFuncName()@"Damage AFTER ="$ActualDamageTaken$" DamageType: "$DamageType$" DamageCauser: "$DamageCauser, bLogTakeDamage);
KFGRI = KFGameReplicationInfo(KFGameInfo(WorldInfo.Game).GameReplicationInfo);
if( (ActualDamageTaken > 0 || OldArmor - Armor > 0) && IsAliveAndWell() && !KFGRI.bTraderIsOpen )
{
KFPlayerController(Controller).NotifyHitTaken();
}
if( ActualDamageTaken > 0 && IsAliveAndWell() )
{
CheckAndEndActiveEMoteSpecialMove();
`DialogManager.PlayPlayerDamageDialog( self, DamageType, ActualDamageTaken );
InstigatedByBoss = KFAIController_ZedBoss( InstigatedBy );
if( InstigatedByBoss != none )
{
InstigatedByBoss.PlayDamagePlayerDialog( DamageType );
}
`RecordAARIntStat(KFPlayerController(Controller), DAMAGE_TAKEN, ActualDamageTaken);
}
KFPRI = KFPlayerReplicationInfo( PlayerReplicationInfo );
if( KFPRI != none )
{
KFPRI.PlayerHealth = Health;
KFPRI.PlayerHealthPercent = FloatToByte( float(Health) / float(HealthMax) );
}
ResetIdleStartTime();
}
/** Plays damagetype-specific material effects upon death */
simulated function PlayDeathMaterialEffects(name DamageMICParamName, float Duration)
{
DeathMaterialEffectTimeRemaining = Duration;
DeathMaterialEffectDuration = Duration;
DeathMaterialEffectParamName = DamageMICParamName;
}
/** Update any material effects */
function UpdateDeathMaterialEffect(float DeltaTime)
{
local float Intensity;
local MaterialInstanceConstant MIC;
if( DeathMaterialEffectTimeRemaining > 0.f )
{
if( DeathMaterialEffectTimeRemaining > DeltaTime )
{
DeathMaterialEffectTimeRemaining -= DeltaTime;
Intensity = 1.f - fClamp( DeathMaterialEffectTimeRemaining/DeathMaterialEffectDuration, 0.f, 1.f );
}
else
{
DeathMaterialEffectTimeRemaining = 0.f;
Intensity = 1.f;
}
// Update the materials
foreach CharacterMICs( MIC )
{
MIC.SetScalarParameterValue( DeathMaterialEffectParamName, Intensity );
}
}
}
/*********************************************************************************************
* @name Perks
********************************************************************************************* */
function SacrificeExplode()
{
local KFExplosionActorReplicated ExploActor;
local GameExplosion ExplosionTemplate;
local KFPerk_Demolitionist DemoPerk;
if ( Role < ROLE_Authority )
{
return;
}
DemoPerk = KFPerk_Demolitionist(GetPerk());
// explode using the given template
ExploActor = Spawn(class'KFExplosionActorReplicated', self,, Location,,, true);
if( ExploActor != None )
{
ExploActor.InstigatorController = Controller;
ExploActor.Instigator = self;
ExplosionTemplate = class'KFPerk_Demolitionist'.static.GetSacrificeExplosionTemplate();
ExplosionTemplate.bIgnoreInstigator = true;
ExploActor.Explode( ExplosionTemplate );
if( DemoPerk != none )
{
DemoPerk.NotifyPerkSacrificeExploded();
}
}
}
function StartAirBorneAgentEvent()
{
local KFGameExplosion AAExplosionTemplate;
local class<KFExplosion_AirborneAgent> AAExplosionActorClass;
if( AAExplosionActor != none )
{
AAExplosionActor.Destroy();
}
AAExplosionTemplate = class'KFPerk_FieldMedic'.static.GetAAExplosionTemplate();
AAExplosionTemplate.MyDamageType = class'KFPerk_FieldMedic'.static.GetAADamageTypeClass();
AAExplosionActorClass = class'KFPerk_FieldMedic'.static.GetAAExplosionActorClass();
AAExplosionActor = Spawn( AAExplosionActorClass, Self,, Location );
if( AAExplosionActor != None )
{
AAExplosionActor.Instigator = self;
AAExplosionActor.InstigatorController = Controller;
AAExplosionActor.MyPawn = self;
AAExplosionActor.SetPhysics( PHYS_NONE );
AAExplosionActor.SetBase( self,, Mesh );
AAExplosionActor.Explode( AAExplosionTemplate );
}
}
simulated function UpdateHealingSpeedBoost()
{
HealingSpeedBoost = Min( HealingSpeedBoost + class'KFPerk_FieldMedic'.static.GetHealingSpeedBoost(), class'KFPerk_FieldMedic'.static.GetMaxHealingSpeedBoost() );
SetTimer( class'KFPerk_FieldMedic'.static.GetHealingSpeedBoostDuration(),, nameOf(ResetHealingSpeedBoost) );
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingSpeedBoostBuff(HealingSpeedBoost);
}
}
simulated function ResetHealingSpeedBoost()
{
HealingSpeedBoost = 0;
if( IsTimerActive( nameOf( ResetHealingSpeedBoost ) ) )
{
ClearTimer( nameOf( ResetHealingSpeedBoost ) );
}
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingSpeedBoostBuff(HealingSpeedBoost);
}
}
simulated function float GetHealingDamageBoostModifier()
{
return 1 + (float(HealingDamageBoost) / 100);
}
simulated function UpdateHealingDamageBoost()
{
HealingDamageBoost = Min( HealingDamageBoost + class'KFPerk_FieldMedic'.static.GetHealingDamageBoost(), class'KFPerk_FieldMedic'.static.GetMaxHealingDamageBoost() );
SetTimer( class'KFPerk_FieldMedic'.static.GetHealingDamageBoostDuration(),, nameOf(ResetHealingDamageBoost) );
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingDamageBoostBuff(HealingDamageBoost);
}
}
simulated function ResetHealingDamageBoost()
{
HealingDamageBoost = 0;
if( IsTimerActive( nameOf( ResetHealingDamageBoost ) ) )
{
ClearTimer( nameOf( ResetHealingDamageBoost ) );
}
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingDamageBoostBuff(HealingDamageBoost);
}
}
simulated function float GetHealingShieldModifier()
{
return 1 - (float(HealingShield) / 100);
}
simulated function UpdateHealingShield()
{
HealingShield = Min( HealingShield + class'KFPerk_FieldMedic'.static.GetHealingShield(), class'KFPerk_FieldMedic'.static.GetMaxHealingShield() );
SetTimer( class'KFPerk_FieldMedic'.static.GetHealingShieldDuration(),, nameOf(ResetHealingShield) );
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingShieldBoostBuff(HealingShield);
}
}
simulated function ResetHealingShield()
{
HealingShield = 0;
if( IsTimerActive( nameOf( ResetHealingShield ) ) )
{
ClearTimer( nameOf( ResetHealingShield ) );
}
if ( WorldInfo.NetMode == NM_STANDALONE)
{
NotifyHealingShieldBoostBuff(HealingShield);
}
}
/**
* @brief Checks if we are close to a demo player with explosivbe resistance skill enabled
*
* @return true if close and enabled
*/
protected function bool HasExplosiveResistance()
{
local KFPawn_Human TestPawn;
local KFPerk TestPawnPerk;
foreach WorldInfo.Allpawns( class'KFPawn_Human', TestPawn, Location, class'KFPerk_Demolitionist'.static.GetExplosiveResistanceRadius() )
{
TestPawnPerk = TestPawn.GetPerk();
if( TestPawnPerk != none && TestPawnPerk.IsSharedExplosiveResistaneActive() )
{
return true;
}
}
return false;
}
function float GetPerkDoTScaler( optional Controller InstigatedBy, optional class<KFDamageType> KFDT )
{
local KFPerk MyPerk;
local float DoTScaler;
DoTScaler = 1.f;
MyPerk = GetPerk();
if( MyPerk != none )
{
MyPerk.ModifyBloatBileDoT( DoTScaler );
}
return DoTScaler;
}
function array<string> GetUpdatedSkillIndicators()
{
return ActiveSkillIconPaths;
}
/*********************************************************************************************
* @name Dialog
********************************************************************************************* */
delegate OnFinishedDialog( const out DialogResponseInfo ResponseInfo );
// tip: HandleDialogResponse gets called whenever a dialog AkEvent is stopped, even if it's stopped early (interrupted)
function HandleDialogResponse()
{
if( Role == ROLE_Authority )
{
if( OnFinishedDialog != none )
{
if( DlgRespInfo.RespondingToID < 0 || DlgRespInfo.RespondingToID == CurrDialogEventID )
{
OnFinishedDialog( DlgRespInfo );
}
OnFinishedDialog = none;
}
}
}
function SetDialogResponseDelegate( KFPawn Responder, delegate<OnFinishedDialog> ResponseDelegate, optional int ResponseID = -1, optional int RespondingToID = -1 )
{
DlgRespInfo.Speaker = Responder;
DlgRespInfo.RespondingToPawn = self;
DlgRespInfo.EventID = ResponseID;
DlgRespInfo.RespondingToID = RespondingToID;
OnFinishedDialog = ResponseDelegate;
}
function UpdateDoshCaught( int Amount, PlayerReplicationInfo Tosser )
{
if( `TimeSince(LastDoshCaughtTime) < 0.75 && LastDoshCaughtGiver == Tosser ) // @todo: make interval editable
{
DoshCaughtStreakAmt += Amount;
}
else
{
DoshCaughtStreakAmt = Amount;
LastDoshCaughtGiver = Tosser;
}
LastDoshCaughtTime = WorldInfo.TimeSeconds;
SetTimer( 0.75, false, nameof(CaughtDoshDialogTimer) ); // @todo: make interval editable
}
function CaughtDoshDialogTimer()
{
`DialogManager.PlayDoshCaughtDialog( self );
ClearTimer( nameof(CaughtDoshDialogTimer) );
}
function UpdateKillStreak()
{
if( LastPainTime < LastZedKilledTime )
{
ZedsKilledStreakAmt++;
}
else
{
ZedsKilledStreakAmt = 1;
}
LastZedKilledTime = WorldInfo.TimeSeconds;
}
function UpdateDamageTakenStreak( int Amount, float Interval )
{
if( `TimeSince(LastDamageTakenStreakStartTime) < Interval )
{
DamageTakenStreakAmt += Amount;
}
else
{
DamageTakenStreakAmt = Amount;
LastDamageTakenStreakStartTime = WorldInfo.TimeSeconds;
}
}
function UpdateContinuousDamage( KFPawn_Monster DamagedZed, float MaxHitInterval )
{
if( DamagedZed.LastHitBy != Controller || `TimeSince(DamagedZed.LastPainTime) > MaxHitInterval )
{
InitialContinousDamageTime = WorldInfo.TimeSeconds;
}
}
function ResetIdleStartTime()
{
local PlayerController PC;
local KFPawn_Human KFPH;
local float DistanceToTeammateSq, MaxResetDistanceSq;
IdleStartTime = WorldInfo.TimeSeconds;
// reset idle start time for nearby teammates
// (i.e. consider them no longer idle if they are close enough to you to need to pay attention and not ramble on about the weather)
MaxResetDistanceSq = 3000 * 3000;
foreach WorldInfo.AllControllers(Class'PlayerController', PC)
{
if( PC == Controller )
{
continue;
}
KFPH = KFPawn_Human( PC.Pawn );
if( KFPH == none || !KFPH.IsAliveAndWell() )
{
continue;
}
DistanceToTeammateSq = VSizeSq( KFPH.Location - Location );
if( DistanceToTeammateSq <= MaxResetDistanceSq )
{
KFPH.IdleStartTime = WorldInfo.TimeSeconds;
}
}
}
function float TimeSpentIdling()
{
return `TimeSince(IdleStartTime);
}
function PlayTraderDialog( AkEvent DialogEvent )
{
if (bDisableTraderDialog)
{
return;
}
TraderDialogAkComponent.PlayEvent( DialogEvent );
}
function StopTraderDialog()
{
if( TraderDialogAkComponent == none )
{
return;
}
TraderDialogAkComponent.StopEvents();
}
/*********************************************************************************************
* @name Movement Methods
********************************************************************************************* */
function SetSprinting(bool bNewSprintStatus)
{
if( bIsSprinting || bNewSprintStatus )
{
`DialogManager.PlaySprintPantingDialog( self, bNewSprintStatus );
}
super.SetSprinting( bNewSprintStatus );
}
function bool DoJump( bool bUpdating )
{
if( super.DoJump(bUpdating) )
{
`DialogManager.PlayJumpDialog( self );
return true;
}
return false;
}
simulated event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
{
local KFPerk MyPerk;
if( WorldInfo.TimeDilation < 1.f && !IsZero(Velocity) && Other.GetTeamNum() != GetTeamNum() )
{
MyPerk = GetPerk();
if (MyPerk != none)
{
MyPerk.OnBump(Other, self, Velocity, Rotation);
}
}
}
/** Checks if we are surrounded and notifies the game conductor */
function Timer_CheckSurrounded()
{
local KFGameInfo KFGI;
// Only check surrounded if we still have only one player and if we are below the health threshold
if( WorldInfo.Game.NumPlayers == 1 && GetHealthPercentage() < MinHealthPctToTriggerSurrounded && IsSurrounded(true, MinEnemiesToTriggerSurrounded, 250.f) )
{
KFGI = KFGameInfo( WorldInfo.Game );
if( KFGI != none && KFGI.GameConductor != none )
{
KFGI.GameConductor.NotifySoloPlayerSurrounded();
}
}
}
/*********************************************************************************************
* @name Torch (aka Flashlight)
********************************************************************************************* */
/**
* Toggle the flashlight on and off
*/
simulated function ToggleEquipment()
{
if( IsLocallyControlled() && !bPlayedDeath )
{
SetFlashlight(!bFlashlightOn, true);
}
}
/**
* Called when there is a need to change the weapon attachment (either via
* replication or locally if controlled.
*/
simulated function SetFlashlight(bool bEnabled, optional bool bReplicate)
{
bFlashlightOn = bEnabled;
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
// So that we don't have to handle viewmode switching just
// always use the 1st person flashlight for the local player
if ( !bPlayedDeath )
{
Flashlight.UpdateFlashlightFor(self);
}
// replicate for third person flashlight
if( bReplicate && Role == ROLE_AutonomousProxy )
{
ServerSetFlashlight(bFlashlightOn);
}
}
/**
* Communicates the stage of the third person flashlight is on for clients
* and servers
*
* @param bFlashlightOn whether the flashlight is on or off
*/
reliable server private function ServerSetFlashlight(bool bEnabled)
{
bFlashlightOn = bEnabled;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
SetFlashlight(bEnabled, false);
}
}
simulated function SetIronSights (bool bEnabled, optional bool bReplicate)
{
bUsingIronSights = bEnabled;
if (WeaponAttachment != none)
{
WeaponAttachment.SetWeaponUsingIronSights (bEnabled);
}
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
// replicate for third person Iron Sight
if( bReplicate && Role == ROLE_AutonomousProxy)
{
ServerSetIronSights(bUsingIronSights);
}
}
reliable server private function ServerSetIronSights(bool bEnabled)
{
bUsingIronSights = bEnabled;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
SetIronSights(bEnabled, false);
}
}
simulated function SetUsingAltFireMode(bool bEnabled, optional bool bReplicate)
{
bUsingAltFireMode = bEnabled;
if (WeaponAttachment != none)
{
WeaponAttachment.SetWeaponAltFireMode (bEnabled);
}
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
// replicate for third person Iron Sight
if( bReplicate && Role == ROLE_AutonomousProxy)
{
ServerSetUsingAltFireMode(bUsingAltFireMode);
}
}
reliable server private function ServerSetUsingAltFireMode(bool bEnabled)
{
bUsingAltFireMode = bEnabled;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
SetUsingAltFireMode(bEnabled, false);
}
}
/** Called when BatteryCharge hits zero */
simulated event NotifyOutOfBattery()
{
local KFPlayerController KFPC;
if( IsLocallyControlled() )
{
KFPC = KFPlayerController(Controller);
if( KFPC.bNightVisionActive )
{
KFPC.SetNightVision(false);
}
if( bFlashlightOn )
{
SetFlashlight(false, true);
}
}
}
/** First person weapon visility */
simulated function SetFirstPersonVisibility(bool bWeaponVisible)
{
Super.SetFirstPersonVisibility(bWeaponVisible);
if ( Flashlight != None )
{
Flashlight.SetFirstPersonVisibility(bWeaponVisible);
}
}
/** Set the lighting channels on all the appropriate pawn meshes */
simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
{
Super.SetMeshLightingChannels(NewLightingChannels);
if ( Flashlight != None )
{
Flashlight.SetLightingChannels(NewLightingChannels);
}
}
/*********************************************************************************************
* @state Dying
********************************************************************************************* */
State Dying
{
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.UsingFirstPersonCamera() && !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, HeadRot, fDeltaTime, 10.0 );
return true;
}
return Global.CalcCamera( fDeltaTime, out_CamLoc, out_CamRot, out_FOV );
}
}
/*********************************************************************************************
AEWESOMEHUD(TM)
********************************************************************************************* */
native simulated function DrawDoors(Canvas Canvas);
/** Hook called from HUD actor. Gives access to HUD and Canvas */
simulated function DrawHUD( HUD H )
{
local Canvas Canvas;
local KFPlayerController KFPC;
Super.DrawHUD(H);
KFPC = KFPlayerController(Controller);
if( !H.bShowHUD )
{
return;
}
if( KFPC != none && KFPC.IsBossCameraMode())
{
return;
}
// Slightly AWESOMEHUD(TM)
Canvas = H.Canvas;
if( Canvas != none )
{
DrawDoors(Canvas);
Canvas.EnableStencilTest(true);
DrawPerkHUD(Canvas);
Canvas.EnableStencilTest(false);
}
}
function DrawPerkHUD(Canvas C)
{
local KFPerk Perk;
Perk = GetPerk();
if( Perk != None )
{
Perk.DrawSpecialPerkHUD(C);
}
}
/**
* list important Pawn variables on canvas. HUD will call DisplayDebug() on the current ViewTarget when
* the ShowDebug exec is used
*
* @param HUD - HUD with canvas to draw on
* @input out_YL - Height of the current font
* @input out_YPos - Y position on Canvas. out_YPos += out_YL, gives position to draw text for next debug line.
*/
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
local Canvas Canvas;
Super.DisplayDebug(HUD, out_YL, out_YPos);
Canvas = HUD.Canvas;
if (HUD.ShouldDisplayDebug('movement'))
{
Canvas.SetDrawColor(0,255,255);
Canvas.DrawText("EncumbranceMod:" @ KFInventoryManager(InvManager).GetEncumbranceSpeedMod());
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("HealthSpeedMod:" @ 1.f - LowHealthSpeedPenalty, FALSE);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
}
simulated function NotifyHealingSpeedBoostBuff(byte Speed)
{
if( Role == ROLE_Authority )
{
HealingSpeedBoost = Speed;
bForceNetUpdate = true;
}
if( IsLocallyControlled() )
{
UpdateActiveSkillsPath(class'KFPerk_FieldMedic'.default.PerkSkills[EMedicHealingSpeedBoost].IconPath, Speed > 0.0f);
}
}
simulated function NotifyHealingDamageBoostBuff(byte Damage)
{
if( Role == ROLE_Authority )
{
HealingSpeedBoost = Damage;
bForceNetUpdate = true;
}
if( IsLocallyControlled() )
{
UpdateActiveSkillsPath(class'KFPerk_FieldMedic'.default.PerkSkills[EMedicHealingDamageBoost].IconPath, Damage > 0.0f);
}
}
simulated function NotifyHealingShieldBoostBuff(byte Shield)
{
if( Role == ROLE_Authority )
{
HealingSpeedBoost = Shield;
bForceNetUpdate = true;
}
if( IsLocallyControlled() )
{
UpdateActiveSkillsPath(class'KFPerk_FieldMedic'.default.PerkSkills[EMedicHealingShield].IconPath, Shield > 0.0f);
}
}
function UpdateActiveSkillsPath(string IconPath, bool Active)
{
local KFPlayerController KFPC;
if(Active)
{
if (ActiveSkillIconPaths.Find(IconPath) == INDEX_NONE)
{
ActiveSkillIconPaths.AddItem(IconPath);
}
}
else
{
ActiveSkillIconPaths.RemoveItem(IconPath);
}
KFPC = KFPlayerController(Controller);
KFPC.MyGFxHUD.PlayerStatusContainer.ShowActiveIndicators(ActiveSkillIconPaths);
}
defaultproperties
{
Begin Object Class=KFFlashlightAttachment name=Flashlight_0
LightConeMesh=StaticMesh'wep_flashlights_mesh.WEP_3P_Lightcone'
AttachmentMesh=StaticMesh'wep_flashlights_mesh.PlayerLight_MESH'
End Object
FlashLightTemplate=Flashlight_0
// ---------------------------------------------
// Hit Zones
HitZones.Add((ZoneName=head,BoneName=Head))
HitZones.Add((ZoneName=neck,BoneName=Neck))
HitZones.Add((ZoneName=chest,BoneName=Spine2))
HitZones.Add((ZoneName=heart,BoneName=Spine2))
HitZones.Add((ZoneName=lupperarm,BoneName=LeftArm))
HitZones.Add((ZoneName=lforearm,BoneName=LeftForearm))
HitZones.Add((ZoneName=lhand,BoneName=LeftForearm))
HitZones.Add((ZoneName=rupperarm,BoneName=RightArm))
HitZones.Add((ZoneName=rforearm,BoneName=RightForearm))
HitZones.Add((ZoneName=rhand,BoneName=RightForearm))
HitZones.Add((ZoneName=stomach,BoneName=Spine1))
HitZones.Add((ZoneName=abdomen,BoneName=Hips))
HitZones.Add((ZoneName=lthigh,BoneName=LeftUpLeg))
HitZones.Add((ZoneName=lcalf,BoneName=LeftLeg))
HitZones.Add((ZoneName=lfoot,BoneName=LeftLeg))
HitZones.Add((ZoneName=rthigh,BoneName=RightUpLeg))
HitZones.Add((ZoneName=rcalf,BoneName=RightLeg))
HitZones.Add((ZoneName=rfoot,BoneName=RightLeg))
Begin Object Name=KFPawnSkeletalMeshComponent
bPerBoneMotionBlur=false
End Object
// Gore
BattleBloodParamName=Scalar_Blood_Contrast
MinBattleBloodValue=0.20f
BattleBloodRangeSq=40000.f /* 2 sq. m */
DeathMaterialEffectDuration=0.1f
DeathMaterialEffectParamName=scalar_dead
// ---------------------------------------------
// Movement / Physics
GroundSpeed=383.f
SprintSpeed=460.f
bCanCrouch=true
// PHYS_Falling
JumpZ=650.f
AirControl=+0.15
bEnableAimOffset=true
CrouchRadius=+40.0
Begin Object Name=CollisionCylinder
CollisionRadius=+0040.000000
End Object
TeammateCollisionRadiusPercent=0.50
// ---------------------------------------------
// Sounds
Begin Object Class=AkComponent name=TraderDialogAkSoundComponent
BoneName=Root
bForceOcclusionUpdateInterval=true
OcclusionUpdateInterval=0.f // never update occlusion for trader dialog
End Object
TraderDialogAkComponent=TraderDialogAkSoundComponent
Components.Add(TraderDialogAkSoundComponent)
PainSoundChanceOnHit=1.f
PainSoundCoolDown=1.f
// ---------------------------------------------
// Health
HealthRegenRate=0.1
HealerRewardScaler=60.0f
// ---------------------------------------------
// Solo Surrounded Difficulty Reduction
MinEnemiesToTriggerSurrounded=2
MinHealthPctToTriggerSurrounded=0.95
// ---------------------------------------------
// Armor
IntegrityLevel_High=75
IntegrityLevel_Medium=50
IntegrityLevel_Low=25
MaxArmor=100
ArmorAbsorbModifier_High=0.75
ArmorAbsorbModifier_Medium=0.65
ArmorAbsorbModifier_Low=0.55
bCanPickupInventory=true
// Flashlight Battery
BatteryCharge=100
BatteryDrainRate=0.8f //2.
BatteryRechargeRate=6.f
NVGBatteryDrainRate=0.8f
IncapSettings(AF_FirePanic)=(Vulnerability=(50.f), Cooldown=0.0, Duration=1.0)
DeathFaceAnims=(Death_V1, Death_V2, Death_V3)
PerkFXEmitterPoolClassPath="KFGame.KFPerkFXEmitterPool"
// ---------------------------------------------
// Special moves
Begin Object Name=SpecialMoveHandler_0
SpecialMoveClasses(SM_GrappleVictim)=class'KFGame.KFSM_GrappleVictim'
SpecialMoveClasses(SM_DisabledGrappleVictim)=class'KFGame.KFSM_DisabledGrappleVictim'
SpecialMoveClasses(SM_HansGrappleVictim)=class'KFGame.KFSM_HansGrappleVictim'
SpecialMoveClasses(SM_Emote)=class'KFGame.KFSM_Player_Emote'
SpecialMoveClasses(SM_DARGrappleVictim)=class'KFGame.KFSM_EvilDAR_EMPGrapple'
SpecialMoveClasses(SM_BloatKingGorgeVictim)=class'KFGame.KFSM_BloatKingGorgeVictim'
End Object
}