//=============================================================================
// 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
}