1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFSM_PlayerSiren_VortexScream.uc
2020-12-13 18:01:13 +03:00

529 lines
15 KiB
Ucode

//=============================================================================
// KFSM_PlayerSiren_VortexScream
//=============================================================================
// Player controlled siren vortex scream
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFSM_PlayerSiren_VortexScream extends KFSM_GrappleCombined;
/** Sounds for vortex */
var const AkEvent VortexLoopAkEvent;
var const AkEvent VortexLoopEndAkEvent;
var const AkEvent VortexGrabAkEvent;
var const AkEvent VortexGrabEndAkEvent;
/** How much the view pitch should be constrained */
var const vector2D ViewPitchConstraints;
/** The amount of time between checks to see if an enemy has been caught in the vortex */
var const float VortexCheckTime;
/** The maximum effective range (squared) of the vortex */
var const float MaxRangeSQ;
/** The minimum FOV required for a target */
var const float MinGrabTargetFOV;
/** The time, worldinfo.timeseconds, when we found a victim */
var protected float FollowerAttachTime;
/** Particle system used for the vortex effect */
var const ParticleSystem VortexEffect;
/** Component for vortex particle effect */
var transient ParticleSystemComponent VortexPSC;
/** How long the special move lasts after pulling someone in */
var const float VortexDuration;
/** How long to force the vortex on for after the button has been released */
var const float MinVortexDuration;
/** Set to TRUE after the minimum duration timer expires */
var protected bool bVortexCanBeInterrupted;
/** Interpolated view pitch on remote clients */
var protected float InterpViewPitch;
/** The speed at which to interpolate our camera rotation when locking pawn rotation */
var const float ViewRotInterpSpeed;
/** Interpolated rotation of pawn after aquiring a target */
var protected float InterpolatedRotation;
/** The overall damage done to a follower if they are held for the full vortex duration */
var const float DamageOverDuration;
/** How much damage to do to the follower per second, calculated at start of move */
var protected int FollowerDamagePerSec;
/** Damagetype to use */
var const class<KFDamageType> VortexDamageType;
/** Restrictions for doing Vortex Scream attack */
protected function bool InternalCanDoSpecialMove()
{
if( KFPOwner.bIsSprinting )
{
KFPOwner.SetSprinting( false );
}
// Not while jumping/flying/etc
if( KFPOwner.Physics != PHYS_Walking )
{
return false;
}
return super.InternalCanDoSpecialMove();
}
function SpecialMoveStarted( bool bForced, Name PrevMove )
{
local KFPawn_Monster MonsterOwner;
Super.SpecialMoveStarted( bForced, PrevMove );
MonsterOwner = KFPawn_Monster( KFPOwner );
bAlignFollowerLookSameDirAsMe = default.bAlignFollowerLookSameDirAsMe;
bAlignFollowerRotation = default.bAlignFollowerRotation;
bAlignPawns = false;
Follower = none;
InterpViewPitch = 0.f;
FollowerAttachTime = 0.f;
// On the server start a timer to check collision
if ( MonsterOwner.Role == ROLE_Authority )
{
MonsterOwner.SetTimer( VortexCheckTime, true, nameOf(Timer_CheckVortex), self );
// Calculate our damage
FollowerDamagePerSec = MonsterOwner.GetRallyBoostDamage( int(DamageOverDuration / VortexDuration) );
}
bVortexCanBeInterrupted = false;
bPendingStopFire = false;
// Set our minimum vortex duration timer
if( MonsterOwner.IsLocallyControlled() )
{
MonsterOwner.SetTimer( MinVortexDuration, false, nameOf(Timer_VortexInterrupt), self );
}
MonsterOwner.BumpFrequency = 0.f;
// View constraints
MonsterOwner.ViewPitchMin = ViewPitchConstraints.X;
MonsterOwner.ViewPitchMax = ViewPitchConstraints.Y;
// Head tracking
if( MonsterOwner.IK_Look_Head == none )
{
MonsterOwner.IK_Look_Head = SkelControlLookAt( MonsterOwner.Mesh.FindSkelControl('HeadLook') );
}
MonsterOwner.bCanHeadTrack = true;
MonsterOwner.bIsHeadTrackingActive = true;
MonsterOwner.MyLookAtInfo.LookAtPct = 1.f;
MonsterOwner.MyLookAtInfo.BlendOut = 0.33f;
MonsterOwner.MyLookAtInfo.BlendIn = 0.2f;
// Spawn particle effect, play sound
if( MonsterOwner.WorldInfo.NetMode != NM_DedicatedServer )
{
VortexPSC = MonsterOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment( VortexEffect, MonsterOwner.Mesh, 'VortexSocket', true );
VortexPSC.SetAbsolute( false, true );
VortexPSC.SetRotation( MonsterOwner.Rotation );
// Post ak event on owner
MonsterOwner.PostAkEvent( VortexLoopAkEvent, true, true, true );
}
}
/** Play an animation and enable the OnAnimEnd notification */
function PlayGrabAnim()
{
PlaySpecialMoveAnim( GrabStartAnimName, EAS_FullBody,,,, true );
}
/** Script Tick function. */
function Tick( float DeltaTime )
{
local vector EffectLoc;
local rotator ViewRot, Projection, DesiredRotation;
// End move if something is blocking the path to the pawn */
/*if( KFPOwner == none
|| (KFPOwner.Role == ROLE_Authority
&& Follower != none
&& !IsPawnPathClear(KFPOwner, Follower, Follower.Location, KFPOwner.Location, vect(1,1,1), true)) )
{
KFPOwner.EndSpecialMove();
}*/
if( KFPOwner != none )
{
if( Follower == none )
{
if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
{
// Head tracking
if( PCOwner != none && PCOwner.PlayerCamera != none )
{
ViewRot = PCOwner.PlayerCamera.CameraCache.POV.Rotation;
KFPOwner.MyLookAtInfo.ForcedLookAtLocation = PCOwner.PlayerCamera.CameraCache.POV.Location + vector( ViewRot ) * 5000.f;
}
else
{
if( InterpViewPitch == 0.f )
{
InterpViewPitch = GetUncompressedViewPitch();
}
else
{
InterpViewPitch = FInterpTo( InterpViewPitch, GetUncompressedViewPitch(), DeltaTime, 15.f );
}
ViewRot = KFPOwner.GetViewRotation();
ViewRot.Pitch = InterpViewPitch;
KFPOwner.MyLookAtInfo.ForcedLookAtLocation = KFPOwner.GetPawnViewLocation() + vector( ViewRot ) * 5000.f;
}
// Vortex effect
VortexPSC.SetRotation( rotator(KFPOwner.MyLookAtInfo.ForcedLookAtLocation - KFPOwner.Location) );
}
}
else
{
KFPOwner.Mesh.GetSocketWorldLocationAndRotation( 'VortexSocket', EffectLoc );
Projection = rotator( Follower.Location - EffectLoc );
// Set effect rotation
if( VortexPSC != none )
{
VortexPSC.SetRotation( RInterpTo(VortexPSC.GetRotation(), Projection, DeltaTime, ViewRotInterpSpeed) );
}
// Set pawn rotation
DesiredRotation = KFPOwner.Rotation;
DesiredRotation.Yaw = FInterpTo( KFPOwner.Rotation.Yaw, Projection.Yaw, DeltaTime, ViewRotInterpSpeed );
ForcePawnRotation( KFPOwner, DesiredRotation, false );
// Set head tracking target
if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
{
KFPOwner.MyLookAtInfo.ForcedLookAtLocation = Follower.Location;
}
}
}
}
/** Returns our uncompressed replicated view pitch */
function float GetUncompressedViewPitch()
{
return float( NormalizeRotAxis(KFPOwner.RemoteViewPitch << 8) );
}
/** Lock view rotation when grabbing */
function ProcessViewRotation( float DeltaTime, out rotator out_ViewRotation, out Rotator out_DeltaRot )
{
if( Follower != none )
{
out_ViewRotation = RInterpTo( out_ViewRotation, KFPOwner.Rotation, DeltaTime, ViewRotInterpSpeed );
out_DeltaRot = rot(0,0,0);
}
}
/** Searches for a valid pawn to interact with */
function Timer_CheckVortex()
{
local KFPawn KFP, BestTarget;
local vector CameraNormal, Projection, TraceStart, GrabLocation;
local float FOV;
local float DistSQ, BestDistSQ;
/** Early out if we have no playercontroller or camera */
if( PCOwner == none || PCOwner.PlayerCamera == none )
{
return;
}
/** Get camera rotation */
CameraNormal = vector( PCOwner.PlayerCamera.CameraCache.POV.Rotation );
// Our trace origin
TraceStart = KFPOwner.Location + ( KFPOwner.BaseEyeHeight * vect(0,0,1) );
foreach KFPOwner.WorldInfo.AllPawns( class'KFPawn', KFP )
{
if( KFP.GetTeamNum() != KFPOwner.GetTeamNum() && CanInteractWithPawn(KFP) )
{
Projection = KFP.Location - TraceStart;
DistSQ = VSizeSQ( Projection );
if( DistSQ <= MaxRangeSQ )
{
FOV = CameraNormal dot Normal( Projection );
if( FOV > MinGrabTargetFOV )
{
// Need both an extent and zero extent trace!
// Note: Unreal 3 is weird. -MattF
GrabLocation = KFP.Location + ( KFP.BaseEyeHeight * vect(0,0,1) );
if( IsPawnPathClear(KFPOwner, KFP, GrabLocation, TraceStart, vect(2,2,2),, true)
&& IsPawnPathClear(KFPOwner, KFP, GrabLocation, TraceStart,,, true) )
{
if( BestTarget == none || DistSQ < BestDistSQ )
{
BestDistSQ = DistSQ;
BestTarget = KFP;
}
}
}
}
}
}
if( BestTarget != none )
{
// Set our attach time
FollowerAttachTime = KFPOwner.WorldInfo.TimeSeconds;
// Add follower to specialmove
KFPOwner.DoSpecialMove( KFPOwner.SpecialMove, true, BestTarget );
// Damage immediately, set damage timer
Timer_DamageFollower();
KFPOwner.SetTimer( 1.f, true, nameOf(Timer_DamageFollower), self );
// Stop trace
KFPOwner.ClearTimer( nameOf(Timer_CheckVortex), self );
}
}
/** We've received an interaction pawn, start the interaction */
function InteractionPawnUpdated()
{
if( KFPOwner.InteractionPawn != none )
{
bAlignPawns = true;
CheckReadyToStartInteraction();
}
else
{
KFPOwner.EndSpecialMove();
}
}
function StartInteraction()
{
if( Follower != none )
{
bAlignPawns = true;
Follower.AirSpeed = 10000.f;
Follower.SetPhysics( PHYS_Flying );
/** Must lock pawn rotation now */
SetLockPawnRotation( true );
// Play a grab sound
if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
{
KFPOwner.PostAkEvent( VortexGrabAkEvent, true, true, true );
}
++KFPlayerReplicationInfoVersus(KFPOwner.PlayerReplicationInfo).ZedGrabs;
}
super.StartInteraction();
}
/** Damage the follower, see if it's time to end the move */
function Timer_DamageFollower()
{
local vector GrabLocation;
local vector GrabDirection;
if( KFPOwner.WorldInfo.TimeSeconds - FollowerAttachTime >= VortexDuration )
{
KFPOwner.EndSpecialMove();
return;
}
if( Follower != none && !Follower.bPlayedDeath )
{
GrabDirection = Normal( KFPOwner.Location - Follower.Location );
GrabLocation = Follower.Location + (GrabDirection * Follower.CylinderComponent.CollisionRadius);
Follower.TakeDamage( FollowerDamagePerSec, KFPOwner.Controller, GrabLocation, GrabDirection, VortexDamageType,, KFPOwner );
// End move if the follower has died
if( Follower.bPlayedDeath || Follower.Health <= 0 )
{
KFPOwner.EndSpecialMove();
return;
}
// Do a camera shake, etc
KFPawn_Monster(KFPOwner).MeleeAttackHelper.PlayMeleeHitEffects( Follower, GrabLocation, GrabDirection, false );
}
}
/** When the grapple animation ends, continue it with a different grapple anim */
function SpecialMoveFlagsUpdated()
{
if( KFPOwner.SpecialMoveFlags == FLAG_SpecialMoveButtonReleased )
{
KFPOwner.EndSpecialMove();
}
else
{
super.SpecialMoveFlagsUpdated();
}
}
/** Skip Super, we control animations here */
function AnimEndNotify( AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime )
{
Super(KFSpecialMove).AnimEndNotify( SeqNode, PlayedTime, ExcessTime );
}
/** Follower has left special move */
function OnFollowerLeavingSpecialMove()
{
super.OnFollowerLeavingSpecialMove();
ResetFollowerPhysics();
}
/** Resets physics values on follower */
function ResetFollowerPhysics()
{
if( Follower != none )
{
Follower.AirSpeed = Follower.default.AirSpeed;
if( Follower.Physics == PHYS_Flying )
{
Follower.SetPhysics( PHYS_Falling );
}
}
}
/** Special move ended */
function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
if( VortexPSC != none )
{
VortexPSC.DeactivateSystem();
VortexPSC = none;
}
ResetFollowerPhysics();
SetLockPawnRotation( false );
if( KFPOwner != none )
{
// Play our vortex end event
if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
{
if( Follower != none )
{
KFPOwner.PostAkEvent( VortexGrabEndAkEvent, true, true, true );
}
else
{
KFPOwner.PostAkEvent( VortexLoopEndAkEvent, true, true, true );
}
}
// Disable head tracking
KFPOwner.bIsHeadTrackingActive = false;
KFPOwner.MyLookAtInfo.ForcedLookAtLocation = vect(0,0,0);
// Restore view pitch limits
KFPOwner.ViewPitchMin = KFPOwner.default.ViewPitchMin;
KFPOwner.ViewPitchMax = KFPOwner.default.ViewPitchMax;
// Clear timers
if( KFPOwner.Role == ROLE_Authority )
{
KFPawn_Monster(PawnOwner).BumpFrequency = KFPawn_Monster(PawnOwner).default.BumpFrequency;
KFPOwner.ClearTimer( nameOf(Timer_CheckVortex), self );
KFPOwner.ClearTimer( nameOf(Timer_DamageFollower), self );
KFPOwner.ClearTimer( nameOf(Timer_VortexInterrupt), self );
}
}
super.SpecialMoveEnded( PrevMove, NextMove );
}
/** Called afte the minimum vortex time has been reached */
function Timer_VortexInterrupt()
{
bVortexCanBeInterrupted = true;
if( bPendingStopFire )
{
SpecialMoveButtonReleased();
}
}
/* Called on some player-controlled moves when a firemode input has been pressed */
function SpecialMoveButtonRetriggered()
{
bPendingStopFire = false;
}
/** Called on some player-controlled moves when a firemode input has been released */
function SpecialMoveButtonReleased()
{
bPendingStopFire = true;
if( !bVortexCanBeInterrupted )
{
return;
}
KFPOwner.DoSpecialMove( KFPOwner.SpecialMove, true,, FLAG_SpecialMoveButtonReleased );
if( KFPOwner.Role < ROLE_Authority && KFPOwner.IsLocallyControlled() )
{
KFPOwner.ServerDoSpecialMove( KFPOwner.SpecialMove, true,, FLAG_SpecialMoveButtonReleased );
}
}
defaultproperties
{
Handle=KFSM_PlayerSiren_VortexScream
FollowerSpecialMove=SM_SirenVortexVictim
VortexEffect=ParticleSystem'VFX_TEX_THREE.FX_Siren_Pull_Long_01'
ViewPitchConstraints=(X=-8192, Y=8192)
VortexCheckTime=0.14f
MaxRangeSQ=1562500.f
MinGrabTargetFOV=0.96f
VortexDuration=5 //6
MinVortexDuration=1.f
AlignSpeedModifier=0.04f // How fast the suction draws in the enemy
AlignFollowerInterpSpeed=22.f // How fast the enemy pawn rotates to face the siren
AlignDistance=360.f // Vortex pulls enemy pawn until it reaches this distance from the siren
ViewRotInterpSpeed=0.5f
AlignDistanceThreshold=4.0f
bStopAlignFollowerRotationAtGoal=false
bAlignFollowerZ=true
bAlignLeaderLocation=false
bServerOnlyPhysics=true
bRetryCollisionCheck=false
GrabStartAnimName=Player_Pull
VortexLoopAkEvent=AkEvent'WW_ZED_Siren.Play_Siren_Pull_Start'
VortexLoopEndAkEvent=AkEvent'WW_ZED_Siren.Stop_Siren_Pull_Start'
VortexGrabAkEvent=AkEvent'WW_ZED_Siren.Play_Siren_Pull_Hit'
VortexGrabEndAkEvent=AkEvent'WW_ZED_Siren.Stop_Siren_Pull_Hit'
VortexDamageType=class'KFDT_Sonic_VortexScream'
DamageOverDuration=24.f
}