1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFSM_Matriarch_PlasmaCannon.uc

529 lines
14 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// KFSM_Matriarch_PlasmaCannon
//=============================================================================
// Flamethrower-style attack, lasts 5 seconds, interruptable with big damage.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
// - Dan Weiss
//=============================================================================
class KFSM_Matriarch_PlasmaCannon extends KFSM_PlaySingleAnim;
/** Max distance between the Matriarch and the current victim target */
var float MaxVictimDistance;
/** Name of socket that shooting originates from. Used for both visual attach and damage trace */
var name ShootingSocketName;
/** Size of the beam being fired */
var Vector BeamSize;
/** Amount of time between attempted damage ticks */
var float DamageTickRate;
/** Per-tick damage amount. Done to each target in the beam */
var float DamagePerTick;
/** Damage type class */
var class<DamageType> CannonDamageType;
/** Template for the plasma beam */
var ParticleSystem BeamPSCTemplate;
/** PSC generated from the beam template. Gets recycled in and out of the emitter pool as needed. */
var ParticleSystemComponent BeamPSC;
/** Template for beam hit location FX */
var const ParticleSystem BeamHitPSCTemplate;
/** PSC for the hit location. On when the beam is hitting anything, off when missing everything */
var ParticleSystemComponent BeamHitPSC;
var ParticleSystem MuzzleFlashPSCTemplate;
var ParticleSystemComponent MuzzleFlashPSC;
/** Amount of time that's passed since the last damage tick occurred */
var float SinceLastDamage;
/** AK Event for start of beam charge */
var AkEvent BeamStartSFX;
/** AK Event for end of special move */
var AkEvent BeamEndSFX;
/** Looping sound for beam hit */
var AkEvent BeamHitSFX;
var AkEvent BeamHitStopSFX;
/** Wind Up animation name */
var Name WindUpAnimName;
/** Wind Down animation name */
var Name WindDownAnimName;
var bool bTickDamage;
var KFPawn_ZedMatriarch MyMatPawn;
/*
Targeting
*/
/** Time to check before firing if an enemy is still visible */
var float VisibilityCheckTime, VisibilityCheckTimer;
var float AngleCheckDot;
var bool bLogTargeting;
function SpecialMoveStarted( bool bForced, Name PrevMove )
{
super.SpecialMoveStarted(bForced, PrevMove);
PlayWindUpAnimation();
MyMatPawn = KFPawn_ZedMatriarch(KFPOwner);
MyMatPawn.PlayPlasmaCannonDialog();
}
/** Play the wind down if we have to */
function SpecialMoveFlagsUpdated()
{
switch (KFPOwner.SpecialMoveFlags)
{
case 1:
PlayFireAnim();
break;
case 2:
PlayWindDownAnim();
break;
}
}
function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
switch (DeferredSeqName)
{
case WindUpAnimName:
if (KFPOwner.Role == ROLE_Authority)
{
// Do the beam check on the authority and then re-do the special move with new flags,
// which will cause the flags to be replicated and caught by SpecialMoveFlagsUpdated
if (PrefireBeamCheck())
{
KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 1);
}
else
{
KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 2);
}
}
break;
case AnimName:
PlayWindDownAnim();
break;
case WindDownAnimName:
KFPOwner.EndSpecialMove();
break;
}
}
function bool PrefireBeamCheck()
{
local Actor HitActor;
local vector SocketLocation, HitLocation, HitNormal, ToEnemy, TraceEnd, GunTargetBoneLocation;
local rotator SocketRotation;
PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
GunTargetBoneLocation = KFPOwner.Controller.Enemy.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
ToEnemy = Normal(GunTargetBoneLocation - SocketLocation);
TraceEnd = SocketLocation + ToEnemy * MaxVictimDistance;
HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, SocketLocation, true);
return KFPawn(HitActor) != none;
}
/** Overridden to do nothing */
function PlayAnimation();
/** Plays our wind up anim, starts the barrel spin skel controller */
function PlayWindUpAnimation()
{
PlaySpecialMoveAnim(WindUpAnimName, EAS_FullBody, BlendInTime, BlendOutTime, 1.f);
KFPOwner.ZeroMovementVariables();
}
/** Plays our fire animation, starts weapon fire */
function PlayFireAnim()
{
MyMatPawn.SetGunTracking(true);
PlaySpecialMoveAnim(AnimName, EAS_FullBody, 0.1f, 0.2f);
ActivateFX();
KFPOwner.ZeroMovementVariables();
KFPOwner.SetWeaponAmbientSound(BeamStartSFX);
bTickDamage = true;
if (KFPOwner.Role == ROLE_Authority)
{
VisibilityCheckTimer = VisibilityCheckTime;
}
//bUseCustomRotationRate = true;
PawnOwner.RotationRate = CustomRotationRate;
}
/** Plays our wind down animation, stops firing, disables barrel spin skel controller */
function PlayWindDownAnim()
{
MyMatPawn.SetGunTracking(false);
PlaySpecialMoveAnim(WindDownAnimName, EAS_FullBody, BlendInTime, BlendOutTime, 1.f);
KFPOwner.ZeroMovementVariables();
bTickDamage = false;
DeactivateFX();
}
function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
super.SpecialMoveEnded(PrevMove, NextMove);
KFPOwner.RotationRate = KFPOwner.default.RotationRate;
bTickDamage = false;
DeactivateFX();
}
function ActivateFX()
{
local ParticleSysParam SourceParam;
if (KFPOwner.WorldInfo.NetMode != NM_DedicatedServer)
{
if (BeamPSCTemplate != none)
{
BeamPSC = PawnOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(
BeamPSCTemplate, PawnOwner.Mesh, 'LeftHand');
if (BeamPSC != none)
{
SourceParam.Name = 'SourceActor';
SourceParam.ParamType = PSPT_Actor;
SourceParam.Actor = PawnOwner;
BeamPSC.InstanceParameters.AddItem(SourceParam);
}
}
if (MuzzleFlashPSCTemplate != none)
{
MuzzleFlashPSC = PawnOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(
MuzzleFlashPSCTemplate, PawnOwner.Mesh, 'LeftHand');
if (MuzzleFlashPSC != none)
{
SourceParam.Name = 'SourceActor';
SourceParam.ParamType = PSPT_Actor;
SourceParam.Actor = PawnOwner;
MuzzleFlashPSC.InstanceParameters.AddItem(SourceParam);
}
}
}
}
function DeactivateFX()
{
if (BeamPSC != none)
{
BeamPSC.DeactivateSystem();
BeamPSC = none;
KFPOwner.SetWeaponAmbientSound(BeamEndSFX);
}
if (BeamHitPSC != none)
{
BeamHitPSC.DeactivateSystem();
BeamHitPSC = none;
MyMatPawn.BeamHitAC.PlayEvent(BeamHitStopSFX);
}
if (MuzzleFlashPSC != none)
{
MuzzleFlashPSC.DeactivateSystem();
MuzzleFlashPSC = none;
}
if (MyMatPawn.BeamHitAC != none)
{
MyMatPawn.BeamHitAC.StopEvents();
}
}
function Actor GetBeamTarget(out vector HitLocation)
{
local Actor HitActor;
local vector SocketLocation, HitNormal, TraceEnd;
local rotator SocketRotation;
// force the beam vfx on the client to hit the pawn that the server is hitting
if (PawnOwner.Role < ROLE_Authority && MyMatPawn.BeamTarget != none)
{
HitLocation = MyMatPawn.BeamTarget.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
return MyMatPawn.BeamTarget;
}
PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
TraceEnd = SocketLocation + vector(SocketRotation) * MaxVictimDistance;
foreach PawnOwner.TraceActors(
class'Actor', HitActor, HitLocation, HitNormal, TraceEnd, SocketLocation, BeamSize)
{
if (IsValidBeamTarget(HitActor))
{
break;
}
else
{
HitActor = none;
}
}
return HitActor;
}
function bool IsValidBeamTarget(Actor HitActor)
{
//If we're a pawn or we're a non-resettable static mesh actor
return Pawn(HitActor) != none || (StaticMeshActor(HitActor) != none && !StaticMeshActor(HitActor).bResetCapable) || SkeletalMeshActor(HitActor) != none || StaticMeshCollectionActor(HitActor) != none;
}
function Tick(float DeltaTime)
{
local Actor HitActor;
local vector SocketLocation, HitLocation;
local rotator SocketRotation;
super.Tick(DeltaTime);
if (bTickDamage)
{
HitActor = GetBeamTarget(HitLocation);
if (PawnOwner.Role == ROLE_Authority)
{
SinceLastDamage += DeltaTime;
while (SinceLastDamage > DamageTickRate)
{
if (HitActor != none)
{
HitActor.TakeDamage(
DamagePerTick, PawnOwner.Controller, HitActor.Location, vect(0,0,0), CannonDamageType);
if (KFPawn(HitActor) != none && !KFPawn(HitActor).IsAliveAndWell())
{
HitActor = none;
}
}
MyMatPawn.BeamTarget = Pawn(HitActor);
SinceLastDamage -= DamageTickRate;
}
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::Tick - HitActor: "$HitActor$"; VisibilityCheckTimer: "$VisibilityCheckTimer);
}
if (CanHitEnemy())
{
VisibilityCheckTimer = VisibilityCheckTime;
}
else
{
VisibilityCheckTimer -= DeltaTime;
if (VisibilityCheckTimer <= 0)
{
FindNewEnemy();
VisibilityCheckTimer = VisibilityCheckTime;
}
}
}
if (PawnOwner.WorldInfo.NetMode != NM_DedicatedServer)
{
if (HitActor != none)
{
if (BeamPSC != none)
{
BeamPSC.SetBeamTargetPoint(0, HitLocation, 0);
}
if (BeamHitPSC == none)
{
BeamHitPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitter(BeamHitPSCTemplate, HitLocation);
MyMatPawn.BeamHitAC.PlayEvent(BeamHitSFX);
}
if (BeamHitPSC != none)
{
PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
BeamHitPSC.SetAbsolute(true, true, false);
BeamHitPSC.SetTranslation(HitLocation);
BeamHitPSC.SetRotation(SocketRotation);
}
}
else
{
if (BeamHitPSC != none && BeamHitPSC.bIsActive)
{
BeamHitPSC.DeactivateSystem();
BeamHitPSC = none;
MyMatPawn.BeamHitAC.PlayEvent(BeamHitStopSFX);
}
}
}
}
}
function bool CanHitEnemy()
{
local Actor HitActor;
local vector SocketLocation, HitLocation, HitNormal, ToEnemy, TraceEnd, GunTargetBoneLocation;
local rotator SocketRotation;
PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
GunTargetBoneLocation = KFPOwner.Controller.Enemy.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
ToEnemy = Normal(GunTargetBoneLocation - SocketLocation);
TraceEnd = SocketLocation + ToEnemy * MaxVictimDistance;
HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, SocketLocation, true);
return HitActor == KFPOwner.Controller.Enemy;
}
function FindNewEnemy()
{
local KFPawn_Human KFPH, BestTarget;
local vector StartTrace, EndTrace, ToTarget;
local rotator SocketRotation;
local float TargetDot, BestDot, MaxVictimDistanceSq;
local vector HitLocation, HitNormal;
local actor HitActor;
KFPOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, StartTrace, SocketRotation);
MaxVictimDistanceSq = MaxVictimDistance * MaxVictimDistance;
foreach KFPOwner.WorldInfo.AllPawns(class'KFPawn_Human', KFPH)
{
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - Potential target: "$KFPH);
}
ToTarget = KFPH.Location - StartTrace;
if (VSizeSq(ToTarget) > MaxVictimDistanceSq)
{
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - too far");
}
continue;
}
ToTarget = Normal(ToTarget);
TargetDot = ToTarget dot (vector(SocketRotation));
if (TargetDot < AngleCheckDot || TargetDot < BestDot)
{
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - bad angle");
}
continue;
}
EndTrace = StartTrace + (ToTarget * MaxVictimDistance);
HitActor = KFPOwner.Trace(HitLocation, HitNormal, EndTrace, StartTrace, true,,, 1);
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - HitActor: "$HitActor);
}
if (HitActor != KFPH)
{
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - obstructed");
}
continue;
}
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - okay!");
}
BestTarget = KFPH;
BestDot = TargetDot;
}
if (bLogTargeting)
{
`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - BestTarget: "$BestTarget);
}
if (BestTarget != none)
{
if (BestTarget != KFPOwner.Controller.Enemy)
{
KFAIController(KFPOwner.Controller).ChangeEnemy(BestTarget, false);
}
}
else
{
KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 2);
}
}
defaultproperties
{
//Special Move
Handle = KFSM_Matriarch_PlasmaCannon
bDisableSteering = false
bDisableMovement = true
bDisableTurnInPlace=true
bCanBeInterrupted=false
bShouldDeferToPostTick=true
bUseCustomRotationRate=false // we will manually turn to true after wind-up
CustomRotationRate=(Pitch=50000, Yaw=4500, Roll=50000)
//Plasma Cannon
MaxVictimDistance = 2500.f
BeamSize = (X=15.0,Y=15.0,Z=15.0)
DamageTickRate = 0.1f
ShootingSocketName = Hand_FX_End_L
DamagePerTick = 7.0f
CannonDamageType = class'KFDT_EMP_MatriarchPlasmaCannon'
BeamPSCTemplate = ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Beam_01'
BeamHitPSCTemplate=ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Impact_01'
MuzzleFlashPSCTemplate=ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Muzzleflash_01'
VisibilityCheckTime=0.8f
AngleCheckDot=0.500f // 60 degrees in front, type cos(X degrees) into google, replacing "X" with a number 0-90, lower is narrower FOV
bLogTargeting=false
//Sounds
BeamStartSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_Plasma_Cannon_Loop_01'
BeamEndSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_Plasma_Cannon_End_01'
BeamHitSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_SFX_Attack_PulseCannon_Beam_Hit_LP'
BeamHitStopSFX=AkEvent'WW_ZED_Matriarch.Stop_Matriarch_SFX_Attack_PulseCannon_Beam_Hit_LP'
//Animation
WindUpAnimName=Plasma_Cannon_TO_Load
AnimName=Plasma_Cannon_Idle
WindDownAnimName=Plasma_Cannon_TO_Idle
AnimStance=EAS_UpperBody
}