369 lines
9.0 KiB
Ucode
369 lines
9.0 KiB
Ucode
//=============================================================================
|
|
// 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<KFDamageType> 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<EMPBlastHitInfo> 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<EMPBlastHitInfo> 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<EMPBlastHitInfo> 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)
|
|
}
|