//============================================================================= // KFSM_DAR_EMPAttack //============================================================================= // //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class KFSM_DAR_EMPAttack extends KFSM_RangedAttack; struct EMPBlastHitInfo { var Actor HitActor; var vector HitLocation; var TraceHitInfo HitInfo; }; /** Template for the EMP */ var const ParticleSystem EMPPSCTemplate; /** PSC generated from the EMP template. Gets recycled in and out of the emitter pool as needed. */ var ParticleSystemComponent EMPPSC; /** Template for EMP hit location FX */ var const ParticleSystem EMPHitPSCTemplate; /** PSC for the hit location. On when the EMP is hitting anything, off when missing everything */ var ParticleSystemComponent EMPHitPSC; /** AK Event for start of EMP charge */ var AkEvent EMPStartSFX; /** AK Event for end of special move */ var AkEvent EMPEndSFX; /** Looping sound for EMP hit */ var AkEvent EMPHitSFX; var AkEvent EMPHitStopSFX; /** Camera shake impact */ var CameraShake EMPHitShake; /** Socket to attach origin of EMP */ var const name EMPSocketName; /** Damagetype used for beam attack */ var const class EMPDamageType; /** The maximum length the beam can be (used both for FX and damage) */ var const float MaxEMPLength; /** The width, height, and depth of the beam, used for damage traces */ var const vector EMPExtent; /** The total amount of damage an EMP blast will do. */ var int EMPDamage; /** Cached list of all pawns hit by the EMP Blast. */ var array Victims; /** The amount of time after the beam is created before we apply damage. */ var float ApplyDamageDelay; /** Draws a debug line between the beam's start and end points */ var const bool bDrawDebugEMP; /** How long will a victim be grappled. */ var float GrappleTime; function SpecialMoveStarted(bool bForced, name PrevMove) { super(KFSM_PlaySingleAnim).SpecialMoveStarted(bForced, PrevMove); KFPOwner.UpdateGameplayMICParams(); KFPOwner.SetWeaponAmbientSound(EMPStartSFX); Victims.length = 0; } function SpecialMoveEnded(name PrevMove, name NextMove) { super.SpecialMoveEnded(PrevMove, NextMove); DisableEMP(); if (KFPOwner != none && KFPOwner.IsAliveAndWell()) { KFPOwner.UpdateGameplayMICParams(); if( KFPOwner.IsWeaponAmbientSoundPlaying(EMPStartSFX) ) { KFPOwner.SetWeaponAmbientSound(EMPEndSFX); } } KFPOwner.FlushPersistentDebugLines(); } function ToggleEMP(bool bEnabled) { local ParticleSysParam SourceParam; local vector EMPEnd; local int bHitTarget; if (bEnabled) { DoEMPHit(EMPEnd, bHitTarget); if (KFPOwner.WorldInfo.NetMode != NM_DedicatedServer) { if (EMPPSCTemplate != none) { EMPPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(EMPPSCTemplate, KFPOwner.Mesh, EMPSocketName, true); if (EMPPSC != none) { SourceParam.Name = 'SourceActor'; SourceParam.ParamType = PSPT_Actor; SourceParam.Actor = KFPOwner; EMPPSC.InstanceParameters.Length = 0; EMPPSC.InstanceParameters.AddItem(SourceParam); DoEMPEffects(EMPEnd, bool(bHitTarget)); } } } } else { DisableEMP(); KFPOwner.SetWeaponAmbientSound(EMPEndSFX); } } function DisableEMP() { ReleaseVictims(); DeactivatePSC(); DeactivateHitPSC(); } function bool IsValidTarget(Actor HitActor) { return (Pawn(HitActor) != none || (StaticMeshActor(HitActor) != none && !StaticMeshActor(HitActor).bResetCapable) || SkeletalMeshActor(HitActor) != none) && WithinRange(HitActor); } function bool WithinRange(Actor HitActor) { return VSizeSq(HitActor.Location - KFPOwner.Location) <= Square(MaxEMPLength); } function DoEMPHit(out vector out_EMPEnd, out int out_bHitTarget) { local vector SocketLocation; local EMPBlastHitInfo BlastHitInfo; local rotator SocketRot; local array HitList; local vector HitLoc, HitNrm; local Actor HitActor; KFPOwner.Mesh.GetSocketWorldLocationAndRotation(EMPSocketName, SocketLocation, SocketRot); out_bHitTarget = int(false); out_EMPEnd = SocketLocation + vector(SocketRot) * MaxEMPLength; // We set the InteractionPawn for this move specifically, by implementing GetInteractionPawn() in // AICommand_DAR_EMPAttack:Command_SpecialMove if (KFPOwner.InteractionPawn != none) { if (IsValidTarget(KFPOwner.InteractionPawn)) { BlastHitInfo.HitActor = KFPOwner.InteractionPawn; BlastHitInfo.HitLocation = KFPOwner.InteractionPawn.Location; HitList.AddItem(BlastHitInfo); } } else { GetEnemyByTrace(HitList, SocketLocation, out_EMPEnd); } foreach HitList(BlastHitInfo) { if (IsValidTarget(BlastHitInfo.HitActor)) { HitActor = KFPOwner.Trace(HitLoc, HitNrm, BlastHitInfo.HitLocation, SocketLocation); if (HitActor != BlastHitInfo.HitActor) { BlastHitInfo.HitLocation = HitLoc; } else { out_bHitTarget = int(true); Victims.AddItem(BlastHitInfo); } } } if (HitList.length == 1) { out_EMPEnd = HitList[0].HitLocation - vect(0, 0, 64); } if (KFPOwner.Role == ROLE_Authority) { if (Victims.length > 0) { if (ApplyDamageDelay > 0) { KFPOwner.SetTimer(ApplyDamageDelay, false, 'ApplyDamage', self); } else { ApplyDamage(); } } } } function GetEnemyByTrace(out array out_EMPBlastHitInfo, vector StartLocation, vector EndLocation) { local vector HitLocation, HitNormal; local Actor HitActor; local TraceHitInfo HitInfo; local EMPBlastHitInfo BlastHitInfo; out_EMPBlastHitInfo.length = 0; HitActor = KFPOwner.Trace(HitLocation, HitNormal, EndLocation, StartLocation, , EMPExtent, HitInfo, KFPOwner.TRACEFLAG_Bullet); if (HitActor != none) { if (IsValidTarget(HitActor)) { BlastHitInfo.HitActor = HitActor; BlastHitInfo.HitLocation = HitLocation; BlastHitInfo.HitInfo = HitInfo; out_EMPBlastHitInfo.AddItem(BlastHitInfo); } } } function ApplyDamage() { local EMPBlastHitInfo BlastHitInfo; foreach Victims(BlastHitInfo) { if(BlastHitInfo.HitActor != none) { BlastHitInfo.HitActor.TakeDamage(EMPDamage, KFPOwner.Controller, BlastHitInfo.HitLocation, vector(KFPOwner.Rotation), EMPDamageType, BlastHitInfo.HitInfo, KFPOwner); if (KFPawn(BlastHitInfo.HitActor) != none) { KFPawn(BlastHitInfo.HitActor).DoSpecialMove(SM_DARGrappleVictim, true, PawnOwner); } } } if (Victims.length > 0) { KFPOwner.SetTimer(GrappleTime, false, 'ReleaseVictims', self); } } function ReleaseVictims() { local EMPBlastHitInfo BlastHitInfo; foreach Victims(BlastHitInfo) { if (BlastHitInfo.HitActor != none && KFPawn(BlastHitInfo.HitActor) != none) { KFPawn(BlastHitInfo.HitActor).EndSpecialMove(SM_DARGrappleVictim); } } Victims.length = 0; } function DoEMPEffects(vector EMPEnd, bool bHitTarget) { local vector SocketLoc; local rotator SocketRot; EMPPSC.SetBeamTargetPoint(0, EMPEnd, 0); KFPOwner.Mesh.GetSocketWorldLocationAndRotation(EMPSocketName, SocketLoc, SocketRot); if (bHitTarget) { if (EMPHitPSC == none && EMPHitPSCTemplate != none) { EMPHitPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitter(EMPHitPSCTemplate, EMPEnd); } if (EMPHitPSC != none) { EMPHitPSC.SetAbsolute(true, true, false); EMPHitPSC.SetTranslation(EMPEnd); EMPHitPSC.SetRotation(Rotator(EMPEnd - SocketLoc)); } } else { DeactivateHitPSC(); } if (bDrawDebugEMP) { KFPOwner.FlushPersistentDebugLines(); KFPOwner.DrawDebugLine(SocketLoc, EMPEnd, 100, 128, 255, true); } } function bool DeactivatePSC() { if (EMPPSC != none && EMPPSC.bIsActive) { EMPPSC.DeactivateSystem(); EMPPSC = none; return true; } return false; } function bool DeactivateHitPSC() { if (EMPHitPSC != none && EMPHitPSC.bIsActive) { EMPHitPSC.DeactivateSystem(); EMPHitPSC = none; return true; } return false; } defaultproperties { Handle=KFSM_DAR_EMPAttack // Animation AnimNames.Add(Atk_Shoot_EMP_V1) AnimStance=EAS_FullBody bDisableSteering=false bDisableMovement=true bDisableTurnInPlace=true bUseCustomRotationRate=true CustomRotationRate=(Pitch=50000, Yaw=25000, Roll=50000) EMPPSCTemplate=ParticleSystem'ZED_EvilDAR_EMIT.FX_EvilDar_Empblast_Beam' EMPHitPSCTemplate=ParticleSystem'ZED_EvilDAR_EMIT.FX_EvilDar_Empblast_Impact' EMPSocketName=EMP_Blast EMPStartSFX=AkEvent'WW_ZED_Evil_DAR.Play_ZED_EvilDAR_SFX_EMP_LP' EMPEndSFX=AkEvent'WW_ZED_Evil_DAR.Stop_ZED_EvilDAR_SFX_EMP_LP' EMPHitSFX=none EMPHitStopSFX=none EMPDamage=10; EMPDamageType=class'KFDT_DAR_EMPBlast' EMPExtent=(X=15.f, Y=15.f, Z=15.f) MaxEMPLength = 2500.f GrappleTime=2.5f //1.5 FireOffsets(0)=(X=0.f,Y=0,Z=0) }