//============================================================================= // KFSM_FleshpoundKing_ChestBeam //============================================================================= // Fleshpound king's chest beam attach. Beam particle code from // KFSM_Matriarch_PlasmaCannon //============================================================================= // Killing Floor 2 // Copyright (C) 2017 Tripwire Interactive LLC // - Matt 'Squirrlz' Farber //============================================================================= class KFSM_FleshpoundKing_ChestBeam extends KFSM_PlaySingleAnim; /** Cached king pawn owner */ var KFPawn_ZedFleshpoundKing KingPawnOwner; /** Template for the plasma beam */ var const 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; /** 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; /** Camera shake impact */ var CameraShake BeamHitShake; /** Socket to attach origin of beam */ var const Name ChestBeamSocketName; /** How much time to wait before attempting to sweep to a new target */ var const float TimeUntilTargetChange; /** Damagetype used for beam attack */ var const class BeamDamageType; /** The maximum length the beam can be (used both for FX and damage) */ var const float MaxBeamLength; /** The width, height, and depth of the beam, used for damage traces */ var const vector BeamExtent; /** How much time to wait between damage ticks */ var const float DamageInterval; /** How much damage to deal per damage tick */ var const int DamagePerTick; /** How much of a momentum impulse to apply each damage tick */ var const float DamageMomentumImpulse; /** Draws a debug line between the beam's start and end points */ var const bool bDrawDebugBeam; function SpecialMoveStarted( bool bForced, Name PrevMove ) { super.SpecialMoveStarted(bForced, PrevMove); // Attempt a sweep attack if we have multiple targets in front of us if( KFPOwner.Role == ROLE_Authority ) { KFPOwner.SetTimer( TimeUntilTargetChange, false, nameOf(Timer_AttemptTargetChange), self ); } // Use custom FX/lights KFPOwner.UpdateGameplayMICParams(); //Start KFP beam SFX KFPOwner.SetWeaponAmbientSound(BeamStartSFX); KingPawnOwner = KFPawn_ZedFleshpoundKing(KFPOwner); } /** Updates beam PSC */ function Tick( float DeltaTime ) { super.Tick( DeltaTime ); if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer && BeamPSC != none && BeamPSC.bIsActive ) { SetBeamTarget(); } } 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 SetBeamTarget() { local vector SocketLoc, BeamEnd, HitLocation, HitNormal; local rotator SocketRot; local Actor HitActor; local bool bShouldActivateHit; KFPOwner.Mesh.GetSocketWorldLocationAndRotation( ChestBeamSocketName, SocketLoc, SocketRot ); BeamEnd = SocketLoc + vector(KFPOwner.Rotation) * MaxBeamLength; // for now use Pawn's rotation, I don't know why but this socket's rotation freaks out during the animation bShouldActivateHit = false; foreach KFPOwner.TraceActors(class'Actor', HitActor, HitLocation, HitNormal, BeamEnd, SocketLoc, BeamExtent, , KFPOwner.TRACEFLAG_Bullet) { //Beam will be stopped on all valid targets if (IsValidBeamTarget(HitActor)) { if (HitActor != none && HitActor.bCanBeDamaged) { BeamEnd = HitLocation - vect(0, 0, 64); } else { BeamEnd = HitLocation; } bShouldActivateHit = HitActor != none; break; } } BeamPSC.SetBeamTargetPoint( 0, BeamEnd, 0 ); //Setup impact sound/fx if valid if (bShouldActivateHit) { //Trigger sound and VFX at the same time, use the more reliable SFX if (BeamHitPSC == none) { BeamHitPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitter(BeamHitPSCTemplate, BeamEnd); KingPawnOwner.BeamHitAC.PlayEvent(BeamHitSFX); } } else { if (BeamHitPSC != none && BeamHitPSC.bIsActive) { BeamHitPSC.DeactivateSystem(); BeamHitPSC = none; KingPawnOwner.BeamHitAC.PlayEvent(BeamHitStopSFX); } } //Update location of sound/fx if (BeamHitPSC != none) { BeamHitPSC.SetAbsolute(true, true, false); BeamHitPSC.SetTranslation(BeamEnd); BeamHitPSC.SetRotation(KFPOwner.Rotation); } if( bDrawDebugBeam ) { KFPOwner.FlushPersistentDebugLines(); KFPOwner.DrawDebugLine( SocketLoc, BeamEnd, 100, 128, 255, true ); } } function ToggleBeam( bool bEnable ) { local ParticleSysParam SourceParam; if( bEnable ) { //Startup visual FX if( BeamPSCTemplate != none) { BeamPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment( BeamPSCTemplate, KFPOwner.Mesh, ChestBeamSocketName, true ); if( BeamPSC != none) { SourceParam.Name = 'SourceActor'; SourceParam.ParamType = PSPT_Actor; SourceParam.Actor = KFPOwner; BeamPSC.InstanceParameters.AddItem( SourceParam ); SetBeamTarget(); } } // Start damage timer. We'll do damage on the client too so we affect ragdolls! KFPOwner.SetTimer( DamageInterval, true, nameOf(Timer_TickDamage), self ); } else { DisableBeamFX(); // Stop dealing damage KFPOwner.ClearTimer( nameOf(Timer_TickDamage), self ); } } /** Turns the beam particle system/sound OFF */ function DisableBeamFX() { if( BeamPSC != none && BeamPSC.bIsActive ) { BeamPSC.DeactivateSystem(); BeamPSC = none; } if (BeamHitPSC != none) { BeamHitPSC.DeactivateSystem(); BeamHitPSC = none; } if (KingPawnOwner.BeamHitAC != none) { KingPawnOwner.BeamHitAC.StopEvents(); } } function Timer_AttemptTargetChange() { local Pawn P, BestTarget; local KFAIController_ZedFleshpoundKing KingFPController; local vector PawnDir; local float DotAngle, BestAngle; PawnDir = vector( KFPOwner.Rotation ); foreach KFPOwner.WorldInfo.AllPawns( class'Pawn', P ) { if( P != KFPOwner && P != AIOwner.Enemy && P.GetTeamNum() != KFPOwner.GetTeamNum() && P.IsAliveAndWell() && P.CanAITargetThisPawn(KFPOwner.Controller)) { DotAngle = PawnDir dot Normal( P.Location - KFPOwner.Location ); if( DotAngle < 0.2f ) { continue; } // Choose the target furthest from the center to create the widest sweep if( BestAngle == 0.f || DotAngle < BestAngle ) { BestAngle = DotAngle; BestTarget = P; } } } // Set our new target if we can if( BestTarget != none ) { KingFPController = KFAIController_ZedFleshpoundKing( AIOwner ); if( KingFPController != none ) { KingFPController.ForceTargetChange( BestTarget ); } } } function Timer_TickDamage() { local vector SocketLoc, BeamDir, EndTrace, HitLocation, HitNormal; local rotator SocketRot; local Actor HitActor; local TraceHitInfo HitInfo; KFPOwner.Mesh.GetSocketWorldLocationAndRotation( ChestBeamSocketName, SocketLoc, SocketRot ); BeamDir = vector( KFPOwner.Rotation ); // for now use Pawn's rotation, I don't know why but this socket's rotation freaks out during the animation EndTrace = SocketLoc + BeamDir * MaxBeamLength; foreach KFPOwner.TraceActors(class'Actor', HitActor, HitLocation, HitNormal, EndTrace, SocketLoc, BeamExtent, , KFPOwner.TRACEFLAG_Bullet) { //Beam will be stopped on all valid targets if (IsValidBeamTarget(HitActor)) { if (HitActor.bCanBeDamaged) { HitActor.TakeDamage(DamagePerTick, KFPOwner.Controller, HitLocation, BeamDir*DamageMomentumImpulse, BeamDamageType, HitInfo, KFPOwner); if (Pawn(HitActor) != none && PlayerController(Pawn(HitActor).Controller) != none) { PlayerController(Pawn(HitActor).Controller).ClientPlayCameraShake(BeamHitShake, 1.f, true); } } return; } } } function SpecialMoveEnded( Name PrevMove, Name NextMove ) { super.SpecialMoveEnded(PrevMove, NextMove); ToggleBeam(false); //Start KFP beam SFX KFPOwner.SetWeaponAmbientSound(BeamEndSFX); // Restore FX/lights if( KFPOwner != none && KFPOwner.IsAliveAndWell() ) { KFPOwner.UpdateGameplayMICParams(); } KFPOwner.FlushPersistentDebugLines(); // temp, @todo: remove } DefaultProperties { // --------------------------------------------- // SpecialMove Handle=KFSM_FleshpoundKing_ChestBeam AnimName=Atk_ChestBeam AnimStance=EAS_FullBody bDisableSteering=false bDisableMovement=true bDisableTurnInPlace=true bCanBeInterrupted=false bUseCustomRotationRate=true CustomRotationRate=(Pitch=50000, Yaw=25000, Roll=50000) // --------------------------------------------- // Effects BeamPSCTemplate=ParticleSystem'ZED_Fleshpound_King_EMIT.FX_ChestBeam' BeamHitPSCTemplate=ParticleSystem'ZED_Fleshpound_King_EMIT.FX_ChestBeam_Impact' ChestBeamSocketName=ChestBeamSocket BeamStartSFX=AkEvent'ww_zed_fleshpound_2.Play_King_FP_Beam_Start_LP' BeamEndSFX=AkEvent'ww_zed_fleshpound_2.Play_King_FP_Beam_End' BeamHitSFX=AkEvent'ww_zed_fleshpound_2.Play_FP_Beam_Hit_LP' BeamHitStopSFX=AkEvent'ww_zed_fleshpound_2.Stop_FP_Beam_Hit_LP' Begin Object Class=CameraShake Name=BeamHitShake0 bSingleInstance=true OscillationDuration=0.35f RotOscillation={(Pitch=(Amplitude=250.f,Frequency=60.f), Yaw=(Amplitude=150.f,Frequency=70.f), Roll=(Amplitude=150.f,Frequency=100.f))} End Object BeamHitShake=BeamHitShake0 // --------------------------------------------- // KFSM_FleshpoundKing_ChestBeam BeamDamageType=class'KFDT_FleshpoundKing_ChestBeam' BeamExtent=(X=15.0f, Y=15.0f, Z=15.0f) MaxBeamLength=3000.0f //2500.0f //1300 DamageInterval=0.1f DamagePerTick=12 //10 //15 //7 DamageMomentumImpulse=100.0f TimeUntilTargetChange=0.75f // --------------------------------------------- // Debug bDrawDebugBeam=false }