//=============================================================================
// KFSM_InteractionPawnLeader
//=============================================================================
// Base class for Pawn to Pawn Interactions.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
// Based on GSM_InteractionPawnLeader_Base
// Copyright 1998-2011 Epic Games, Inc. All Rights Reserved.
//=============================================================================

class KFSM_InteractionPawnLeader extends KFSpecialMove
	native(SpecialMoves)
	abstract;

var transient protected CameraAnimInst ExecutionCameraAnimInst_Leader;
var transient protected CameraAnimInst ExecutionCameraAnimInst_Follower;

/** Pointer to Follower. */
var KFPawn			Follower;
/** if other than SM_None, then force InteractionPawn into this special move. */
var ESpecialMove	FollowerSpecialMove;
/** Max time to wait for Interaction to start. If it can't be made, special move will be aborted. */
var float			InteractionStartTimeOut;

/** If TRUE, Pawns will be aligned with each other. */
var bool			bAlignPawns;
/** Desired distance to align both pawns. */
var	float			AlignDistance;
/** Checks 2D dist between the pawns versus this threshold when determining if they are close enough */
var float			AlignDistanceThreshold;
/** Should leader be aligned as well as follower */
var bool 			bAlignLeaderLocation;
/** If TRUE, align the Z position of follower as well */
var bool 			bAlignFollowerZ;
/** Should Follower look in same dir as me? */
var bool			bAlignFollowerLookSameDirAsMe;
/** If Rotations should automatically be aligned */
var bool            bAlignLeaderRotation;
var bool            bAlignFollowerRotation;
var bool			bStopAlignFollowerRotationAtGoal;
/** If human controlled controller's Rotations should automatically be aligned as well */
var bool            bAlignHumanFollowerControllerRotation;
/** Used by RInterpTo, multiplied by 4096.f (see GOW3 UGearSpecialMove::MoveTo which is similar) */
var float			AlignFollowerInterpSpeed;
var INT             LeaderRelativeYawOffset;
/** Time it takes to pull the pawns together */
var float			AlignSpeedModifier;

/** (NEW) Test interaction collision to detect when pawns get too far apart. */
var bool			bRetryCollisionCheck;

// C++ functions
cpptext
{
	virtual void PrePerformPhysics(FLOAT DeltaTime);
}

/**
 * Trace to see if it will hit from current location to +targetrelativeoffset
 * If so, it will return vector from target location to hit location
 */
native function vector GetClipSafeMeshTranslation(out vector TargetRelativeOffset);

function SpecialMoveStarted(bool bForced, Name PrevMove)
{
	Super.SpecialMoveStarted(bForced, PrevMove);

	// Reset variables
	Follower = None;
	bAlignFollowerLookSameDirAsMe = default.bAlignFollowerLookSameDirAsMe;
	bAlignPawns = default.bAlignPawns;
	bAlignFollowerRotation = default.bAlignFollowerRotation;

	// Set up a safety net in case interaction cannot be started
	PawnOwner.SetTimer( InteractionStartTimeOut, FALSE, nameof(self.InteractionStartTimedOut), self );

	// See if we can start interaction right now. If we can't, keep trying until we can.
	CheckReadyToStartInteraction();
}

function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
	local KFSM_InteractionPawnFollower FollowerSM;

	// Clear timers
	PawnOwner.ClearTimer(nameof(CheckReadyToStartInteraction), Self);
	PawnOwner.ClearTimer(nameof(InteractionStartTimedOut), self);
	PawnOwner.ClearTimer(nameof(RetryCollisionTimer), self);

	// If the special move was ended while bAlignPawns was on, velocity may get stuck on clients
	if( bAlignPawns && !KFPOwner.IsHumanControlled() )
	{
		PawnOwner.ZeroMovementVariables();
		if( Follower != None)
		{
			Follower.ZeroMovementVariables();
		}
	}

	// If the leader is leaving the special move, notify the follower
	if( Follower != None && Follower.IsDoingSpecialMove(FollowerSpecialMove) )
	{
		FollowerSM = KFSM_InteractionPawnFollower(Follower.SpecialMoves[FollowerSpecialMove]);
		FollowerSM.OnLeaderLeavingSpecialMove();
	}

	// Clear reference to Interaction Pawn.
	Follower = None;

	if (ExecutionCameraAnimInst_Leader != None)
	{
		if (PCOwner != None)
		{
			PCOwner.PlayerCamera.StopCameraAnim(ExecutionCameraAnimInst_Leader);
		}
		ExecutionCameraAnimInst_Leader = None;
	}
	if (ExecutionCameraAnimInst_Follower != None)
	{
		if (PCOwner != None)
		{
			PCOwner.PlayerCamera.StopCameraAnim(ExecutionCameraAnimInst_Follower);
		}
		ExecutionCameraAnimInst_Follower = None;
	}

	Super.SpecialMoveEnded(PrevMove, NextMove);
}

/** Safety net in case Interaction cannot be started. Abort special move. */
function InteractionStartTimedOut()
{
	local KFPawn KFP;

	KFP = KFPawn(PawnOwner);

	if (KFP.SpecialMoves[KFP.SpecialMove] == self)
	{
		`Warn(KFP.WorldInfo.TimeSeconds @ KFP @ class @ GetFuncName() @ "InteractionStartTimeOut hit!! Aborting move." @ Follower @ Follower.SpecialMove );

		KFP.EndSpecialMove(, true);
	}
}

/** Checks if Interaction is ready to be started, and starts if it is. */
final function CheckReadyToStartInteraction()
{
	local KFSM_InteractionPawnFollower FollowerSM;

	// Make sure we have an InteractionPawn, this is a requirement for these types of special moves
	if( KFPOwner.InteractionPawn != None )
	{
		// Save variable here for ease of use.
		Follower = KFPOwner.InteractionPawn;
		// If net owner, start Follower special move if not already done.
		if( PawnOwner.WorldInfo.NetMode != NM_Client )
		{
			// Make sure we have a valid Pawn to work with
			if( Follower != None && Follower.Health > 0 )
			{
				if( !Follower.IsDoingSpecialMove(FollowerSpecialMove) )
				{
					Follower.DoSpecialMove(FollowerSpecialMove, TRUE, PawnOwner);
				}
			}
			else
			{
				// Our Pawn is never going to go in his special move... pop an error, and exit move.
				`Warn(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Follower not gameplay relevant, Interaction cannot be started!!!" @ Follower @ Follower.SpecialMove, KFPOwner.bLogSpecialMove);
				KFPOwner.EndSpecialMove(KFPOwner.SpecialMove);
				return;
			}
		}
	}

	// If not ready, then set timer to try again next tick.
	if( !IsReadyToStartInteraction() )
	{
		if( Follower == None )
		{
			`log(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Not ready to StartInteraction, delay... Follower:" @ Follower, KFPOwner.bLogSpecialMove,'SpecialMoves');
		}
		else
		{
			`log(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Not ready to StartInteraction, delay... Follower:" @ Follower @ "Follower.SpecialMove:" @ Follower.SpecialMove, KFPOwner.bLogSpecialMove ,'SpecialMoves');
		}

		// Retry next frame...
		PawnOwner.SetTimer( PawnOwner.WorldInfo.DeltaSeconds, FALSE, nameof(self.CheckReadyToStartInteraction), self );
	}
	// otherwise, start interaction now!
	else
	{
		`log(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "StartInteraction. Follower:" @ Follower @ "Follower.SpecialMove:" @ Follower.SpecialMove, KFPOwner.bLogSpecialMove ,'SpecialMoves');

		PawnOwner.ClearTimer(nameof(CheckReadyToStartInteraction), Self);
		// Clear timeout timer
		PawnOwner.ClearTimer(nameof(InteractionStartTimedOut), Self);
		// Notify follower that we're about to start.
		FollowerSM = KFSM_InteractionPawnFollower(Follower.SpecialMoves[FollowerSpecialMove]);
		FollowerSM.InteractionStarted();
		// And start interaction!
		StartInteraction();
	}
}

/**
 * Conditions to determine if interaction is ready to be started.
 * - Need an InteractionPawn
 * - InteractionPawn needs to be doing his InteractionPawnSpecialMove if it is other than SM_None.
 */
function bool IsReadyToStartInteraction()
{
	return (Follower != None && Follower.IsDoingSpecialMove(FollowerSpecialMove));
}

/** StartInteraction */
function StartInteraction()
{
	if ( bRetryCollisionCheck && PawnOwner.Role == ROLE_Authority )
	{
		PawnOwner.SetTimer(0.5f, true, nameof(RetryCollisionTimer), self);
	}
}

/** Called on an interval to detect when pawns get too far apart or are seperated by a door */
function RetryCollisionTimer()
{
	// Test for vertical melee range (e.g. Follower fell off a ledge) & reachabilty (doors etc.)
	// This started happening after PHYS_Falling handling was added to PrePerformPhysics
	if ( (Abs(Follower.Location.Z - PawnOwner.Location.Z) > PawnOwner.CylinderComponent.CollisionHeight * 1.5) ||
	 	 !IsFollowerReachable() )
	{
		//`log("rejected outside of vertical melee range");
		KFPOwner.EndSpecialMove();
	}
}

/**
 * @brief Checks if the "follower" can stil be reached, mostly for doors
 *
 * @return true if we can physically reach the "follower"
 */
function bool IsFollowerReachable()
{
	// Trace from the WorldInfo, since open doors can ignore traces from zeds
	return ( IsPawnPathClear(KFPOwner.WorldInfo, Follower, Follower.Location, KFPOwner.Location, vect(2,2,2), true, true)
			&& IsPawnPathClear(KFPOwner.WorldInfo, Follower, Follower.Location, KFPOwner.Location,, true, true) );
}

/** Messages sent to this special move */
function bool MessageEvent(Name EventName, Object Sender)
{
	if( EventName == 'FollowerLeavingSpecialMove' )
	{
		OnFollowerLeavingSpecialMove();
		return TRUE;
	}

	return Super.MessageEvent(EventName, Sender);
}

/** Notification when Follower is leaving his FollowerSpecialMove */
function OnFollowerLeavingSpecialMove()
{
	if( KFPOwner != none && KFPOwner.Role == ROLE_Authority )
	{
		KFPOwner.EndSpecialMove();
	}
}

/**
 * Dump relative location of a Bone to PawnOwner
 * For debugging purposes
 */
function DebugSocketRelativeLocation(name InSocketName)
{
	local Vector MarkerLoc;
`if(`notdefined(ShippingPC))
	local Vector RelativeLoc;
`endif
	local Rotator MarkerRot;

	// Get position of marker
	//MarkerLoc = PawnOwner.Mesh.GetBoneLocation(BoneName);
	PawnOwner.Mesh.GetSocketWorldLocationAndRotation(InSocketName, MarkerLoc, MarkerRot);

	// Dump a little debug info to display the location we got:
	PawnOwner.DrawDebugSphere(MarkerLoc, 4, 8, 255, 0, 255, TRUE);

`if(`notdefined(ShippingPC))
	// Transform marker location to be relative to Leader location and rotation.
	RelativeLoc = WorldToRelativeOffset(PawnOwner.Rotation, MarkerLoc - PawnOwner.Location);

	`log(GetFuncName() @ "RelativeLoc:" @ RelativeLoc);
`endif
}

/**
 * Special Execution version.
 * Test to play same animation on victim.
 * WARNING: * ForcePitchCentering not reset for Follower
 *			* Follower's ViewTarget is set to the Leader.
 *
 * @param InCameraAnims - the list of anims to choose from
 *
 * @param LeaderAnimInst - a reference to the anim played on the leader
 *
 * @param bViewFromLeaderPOV - true to make followers see the execution from the leader's perspective, false otherwise
 *
 * @return True if it played an anim, false otherwise.
 */
function bool PlayExecutionCameraAnim(out const array<CameraAnim> InCameraAnims, optional out CameraAnimInst LeaderAnimInst, optional float BlendInTime=0.25f, optional float BlendOutTime=0.25f)
{
	local KFPlayerController	FollowerPC;
	local int					AnimIdx;
	local CameraAnimInst		FollowerAnimInst;
	local ViewTargetTransitionParams TransitionParams;

	FollowerPC = KFPlayerController(Follower.Controller);

	if( PCOwner != None )
	{
		AnimIdx = FollowerPC.ChooseRandomCameraAnim(InCameraAnims);
	}

	if (PawnOwner.IsLocallyControlled() && AnimIdx != INDEX_NONE)
	{
		LeaderAnimInst = PlayCameraAnim(PawnOwner, InCameraAnims[AnimIdx],,, BlendInTime, BlendOutTime);
		ExecutionCameraAnimInst_Leader = LeaderAnimInst;
	}

	// If Follower is human controlled, have him spectate his death from the killer's perspective
	if( FollowerPC != None && Follower.IsHumanControlled() )
	{
		if (FollowerPC.GetViewTarget() != PawnOwner)
		{
			TransitionParams.BlendTime = 0;
			FollowerPC.SetViewTarget(PawnOwner, TransitionParams);
		}

		AnimIdx = FollowerPC.ChooseRandomCameraAnim(InCameraAnims);

		// Also play the death camera
		if( Follower.IsLocallyControlled() )
		{
			FollowerAnimInst = PlayCameraAnim(Follower, InCameraAnims[AnimIdx],,, BlendInTime, BlendOutTime);
			ExecutionCameraAnimInst_Follower = FollowerAnimInst;
		}
	}

	return (FollowerAnimInst != None) || (LeaderAnimInst != None);
}

/** Attach Follower to Leader */
function AttachFollowerToLeader(Name SocketName, optional Vector AttachLocation, optional Rotator AttachRotation)
{
	local SkeletalMeshSocket	Socket;

	// Force replication update.
	Follower.bForceNetUpdate = TRUE;

	Follower.SetBase(None);
	Follower.SetPhysics(PHYS_None);
	Follower.SetHardAttach(TRUE);

	Follower.SetCollision(TRUE, FALSE);
	Follower.bCollideWorld = FALSE;

	// Make sure kidnapper has his attachment bone defined.
	Socket = PawnOwner.Mesh.GetSocketByName(SocketName);
	if( Socket != None )
	{
		Follower.SetBase(PawnOwner,, PawnOwner.Mesh, Socket.BoneName);
		Follower.SetRelativeLocation( Socket.RelativeLocation - Follower.default.Mesh.Translation );
		//Follower.SetRelativeRotation( Socket.RelativeRotation );
		//Follower.UpdateMeshTranslationOffset( vect(0,0,0), true );
		//Follower.Mesh.SetTranslation( vect(0,0,0) );
	}
	// check if bone exists with tht name
	else if ( PawnOwner.Mesh.MatchRefBone(SocketName) != INDEX_NONE )
	{
		Follower.SetBase(PawnOwner,, PawnOwner.Mesh, SocketName);
		Follower.SetRelativeLocation( AttachLocation );
		Follower.SetRelativeRotation( AttachRotation );
		//Follower.UpdateMeshTranslationOffset( vect(0,0,0) );
	}
	else
	{
		`Warn(PawnOwner.WorldInfo.TimeSeconds @ class @ GetFuncName() @ "Leader" @ PawnOwner.class @ "Has no attachment socket" @ SocketName @ "!!!!");

		if( AttachRotation != rot(0,0,0) )
		{
			Follower.SetRotation(AttachRotation);
		}
		else
		{
			Follower.SetRotation(PawnOwner.Rotation);
		}

		// b_MF_Attach doesn't exist on all Pawns!!
		if( AttachLocation != vect(0,0,0) )
		{
			Follower.SetLocation(AttachLocation);
		}
		else
		{
			Follower.SetLocation(PawnOwner.Location);
		}
		Follower.SetBase(PawnOwner);
	}

	// need to set PHYS_None again, because SetBase() changes physics to PHYS_Falling
	Follower.SetPhysics(PHYS_None);

	// This needs to be set AFTER Follower.UpdateMeshTranslationOffset( vect(0,0,0) );
	//Follower.bDisableMeshTranslationChanges = TRUE;

	// Log all debug information.
	`log(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Attached Follower:" @ Follower
		@ "BaseSkelComponent:" @ Follower.BaseSkelComponent
		@ "SocketName:" @ SocketName
		@ "BaseBoneName:" @ Follower.BaseBoneName
		@ "RelativeLocation:" @ Follower.RelativeLocation
		@ "RelativeRotation:" @ Follower.RelativeRotation
		@ "bHardAttach:" @ Follower.bHardAttach
		@ "bIgnoreBaseRotation:" @ Follower.bIgnoreBaseRotation, KFPOwner.bLogSpecialMove,'SpecialMoves');

	//REMOVED - Perf difference should be minimal and there is still an issue when the parent is not in shadow, but the child should be.
	//Shadow parent, to render only one shadow for both.
	//Follower.Mesh.SetShadowParent(PawnOwner.Mesh);
}

/**
 * Detaches a based Pawn from the Leader.
 *
 * @param APawn - the Pawn to detach
 */
function DetachPawn(Pawn APawn)
{
	DetachPawnHelper(APawn);
}

/**
 * Detach the pawn and turn collision back on.
 * Static so other classes (like GSM_Hostage) can reuse this detach code.
 *
 * @param APawn - the Pawn to detach
 */
static function DetachPawnHelper(Pawn APawn)
{
	APawn.SetBase(None);
	APawn.SetHardAttach(false);
	APawn.SetPhysics(PHYS_Falling);
	//APawn.bDisableMeshTranslationChanges = FALSE;

	if (!APawn.bTearOff)
	{
		APawn.SetCollision(true, true);
	}
	APawn.bCollideWorld = true;

	// Clear Shadow parent
	APawn.Mesh.SetShadowParent(None);
}

/**
 * Used for Pawn to Pawn interactions.
 * Return TRUE if we can perform an Interaction with this Pawn.
 */
function bool CanInteractWithPawn(KFPawn OtherPawn)
{
	return TRUE;
}

DefaultProperties
{
	bAlignLeaderLocation=true
	bAlignLeaderRotation=TRUE
	bAlignFollowerRotation=true
	bAlignFollowerZ=false
	AlignFollowerInterpSpeed=8.f
	AlignDistanceThreshold=0.3f
	InteractionStartTimeOut=4.f
	FollowerSpecialMove=SM_None

	bRetryCollisionCheck=true
	AlignSpeedModifier=0.5f
}
