//============================================================================= // 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 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 PowerUpType; var byte Count; }; struct native KFPowerUpCurrentFxInfo { /** the power up type the player has */ var class 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 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 PoolClass; super.PreBeginPlay(); if( WorldInfo.NetMode != NM_DedicatedServer ) { if( PerkFXEmitterPoolClassPath != "" ) { PoolClass = class(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(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(DynamicLoadObject("KFGameContent.KFWeap_Healer_Syringe", class'Class'))); } DefaultInventory.AddItem(class(DynamicLoadObject("KFGameContent.KFWeap_Welder", class'Class'))); DefaultInventory.AddItem(class(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, optional bool bCanRepairArmor=true, optional bool bMessageHealer=true) `else event bool HealDamage(int Amount, Controller Healer, class 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, 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 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(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 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 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 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, vector HitLoc) { local class KFDT; super.PlayDying(DamageType, HitLoc); if( AAExplosionActor != none ) { AAExplosionActor.Destroy(); } // If ragdoll was successful if ( Physics == PHYS_RigidBody && !Mesh.HiddenGame ) { KFDT = class(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, 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, 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(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(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, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { local int ActualDamageTaken, OldHealth; //local KFGameInfo KFGI; local KFGameReplicationInfo KFGRI; local KFPlayerReplicationInfo KFPRI; local KFAIController_ZedBoss InstigatedByBoss; OldHealth = Health; `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( Damage > 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 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 KFDT ) { local KFPerk MyPerk; local float DoTScaler; DoTScaler = 1.f; MyPerk = GetPerk(); if( MyPerk != none ) { MyPerk.ModifyBloatBileDoT( DoTScaler ); } return DoTScaler; } function array 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 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 }