//=============================================================================
// KFSpecialMoveHandler
//=============================================================================
// Manages starting and stopping special moves for pawns
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFSpecialMove extends GameSpecialMove
	native(SpecialMoves)
	abstract;

/** Owner of this special move */
var KFPawn				KFPOwner;
var KFPlayerController	PCOwner;
var KFAIController		AIOwner;
var transient Object AISpecialOwner;
var transient ESpecialMove SMIndex;

/*********************************************************************************************
 * Animation
 *********************************************************************************************/

/** Cached reference to the current AnimNodeSlot used for cleanup */
var AnimNodeSlot ActiveSlotNode;

/** If set, this special move will override the value for Pawn::MaxSpeedModifier() */
var bool bOverridePawnSpeedModifier;

/** If set, hit reactions will still play while special move is active */
var bool bAllowHitReactions;
/** If set, HandleMomentum is skipped when taking damage */
var bool bAllowMomentumPush;
/** If set, never interrupt this move for a wander. Instead try to wander at the end of the move 
*   NOTE: this variable could potentially be replaced with bDisableMovement. This is not currently
*   done in case it breaks other special moves */
var bool bCanOnlyWanderAtEnd;
/** If set, allow weapon anims to be played for third-person pawns (weapon attachment) */
var bool bAllowThirdPersonWeaponAnims;
/** If set, sets Pawn.RotationRate to CustomRotationRate for the duration of the move */
var bool bUseCustomRotationRate;
var rotator CustomRotationRate;
/** Used when deferring special move animations till post sync due to physics calls. */
var name DeferredSeqName;

/** Default root motion mode to be used for this move */
var ERootMotionMode SMRootMotionMode;

/** When TRUE, tells our physics smoothing code to use a higher threshold when in root motion. */
var bool bUseHigherMeshSmoothingThreshold;

/*********************************************************************************************
 * Weapon firing
 *********************************************************************************************/

/** Weapon firing should cease/be disabled while this special move is being performed */
var bool bDisablesWeaponFiring;

/**
* For SpecialMove interactions. Only InteractionPawn allowed to damage me.
* Note: this is reset to FALSE in SpecialMoveStarted.
*/
var bool bOnlyInteractionPawnCanDamageMe;
/** Can change InteractionPawn before execution (InternalCanDoSpecialMove) */
var bool bCanModifyInteractionPawn;

/** Can be used by input functions */
var bool bPendingStopFire;

/** Flags used by button-activated special moves */
const FLAG_SpecialMoveButtonPressed = 253;
const FLAG_SpecialMoveButtonReleased = 254;

/** Sets a custom interpolated 3rd person view offset if we've got a third person camera */
var bool bUseCustomThirdPersonViewOffset;
var ViewOffsetData CustomThirdPersonViewOffset;
var float ViewOffsetInterpTime;
var bool bRestoredCameraDefaults;

/** Custom interpolated camera FOV */
var float CustomCameraFOV;
var float CameraFOVTransitionTime;

/*********************************************************************************************
 * GearSpecialMove
 *********************************************************************************************/

/** Flag to allow/disallow headtracking while in this special move. */
var const           bool    bDisableHeadTracking;
/** AI should ignore notifications */
var	const			bool	bDisableAI;
/** Disable movement (but doesn't necessarily stop physics, just blocks inputs). */
var					bool	bDisableMovement;
var             	bool	bMovementDisabled;
/** Lock Pawn Rotation, prevent it from being affects by Controller.Rotation */
var	const			bool	bLockPawnRotation;
var     			bool	bPawnRotationLocked;
var                 bool    bDisableLook;
/** Weapon goes down and can't shoot, be switched, can't quick heal, etc */
var                 bool    bDisableWeaponInteraction;
/** if > 0.0 after this many seconds the AI's command is popped and the special move is aborted */
var					float	AITimeout;
var					bool	bDisableAIAttackRangeChecks;
var					bool	bDisableSteering;
/** If TRUE, disables turning in place animations & code. Pawn will always face his rotation. */
var const			bool	bDisableTurnInPlace;
/** If set to anything above 0, scales the turn in place anim rate by its value */
var const 			float 	CustomTurnInPlaceAnimRate;
var	const			bool	bDisablePhysics;
/** If TRUE, disables physics adjustments on the clients. Where drastic velocity changes are needed, this can help with rubberbanding */
var const 			bool 	bServerOnlyPhysics;
var 				bool 	bAllowFireAnims;
var					bool	bShouldDeferToPostTick;
var	AICommand_PushedBySM	AICommand;
/** Default AICommand to push on AI when SpecialMove starts */
var class<AICommand_PushedBySM>	DefaultAICommandClass;

cpptext
{
	/** Affect pawn owner movement speed */
	virtual FLOAT GetMovementSpeedModifier() {return 1.f;}
	/** Using cpptext since GameSpecialMove's version is a final function. */
	virtual void ForcePawnRotation( APawn* P, FRotator NewRotation,UBOOL bIgnoreHumanController=FALSE  );
}

/*********************************************************************************************
 * Functions
 *********************************************************************************************/

/** Uses traceflags identical to pawn moves, checks to see if the path is clear for pawns to move through */
native static function bool IsPawnPathClear( Actor TraceInstigator, Pawn TraceToPawn, vector TraceEnd, vector TraceStart, optional vector TraceExtent, optional bool bIgnorePawns, optional bool bTraceComplex );

/** Generic PackFlags to be implemented by subclasses.  (see also PackSMFlags for advanced moves) */
static function byte PackFlagsBase(KFPawn P);

/** Overridden to lock desired rotation (see GOW3) */
//native function ForcePawnRotation(Pawn P, Rotator NewRotation);

function bool NotifyBump(Actor Other, vector HitNormal);
/** Called when the interaction pawn is updated after the special move has started */
function InteractionPawnUpdated(); 
/** Called on some player-controlled moves when a firemode input has been pressed */
function SpecialMoveButtonRetriggered();
/** Called on some player-controlled moves when a firemode input has been released */
function SpecialMoveButtonReleased();

/**
 * Checks to see if this Special Move can be done.
 */
protected function bool InternalCanDoSpecialMove()
{
	return (PawnOwner.Physics != PHYS_RigidBody);
}

function InitSpecialMove( Pawn InPawn, Name InHandle )
{
	PawnOwner = InPawn;

	KFPOwner = KFPawn(InPawn);
	if ( PawnOwner.Controller != none )
	{
		PCOwner = KFPlayerController(PawnOwner.Controller);
		if( PCOwner == none )
		{
			AIOwner = KFAIController(PawnOwner.Controller);
		}
	}

	bPendingStopFire = false;
}

function SpecialMoveStarted( bool bForced, Name PrevMove )
{
	local AICommand AIOwnerActiveCommand;
	local KFWeapon KFW;

	// Make sure we're taken out of ironsights
	if( PCOwner != none && KFPOwner.Weapon != none )
	{
		KFW = KFWeapon( KFPOwner.Weapon );
		if( KFW != none )
		{
			KFW.SetIronSights( false );
		}
	}

	SMIndex = KFPOwner.SpecialMove;

	// Push AICommand if it is defined.
	if( AIOwner != None )
	{
		if( DefaultAICommandClass != None && AIOwner.MyKFPawn != None )
		{
			AIOwnerActiveCommand == AICommand(AIOwner.GetActiveCommand());

			if(( AIOwnerActiveCommand == none || AIOwnerActiveCommand.AllowPushOfDefaultCommandForSpecialMove(KFPOwner.SpecialMove)))
			{
				AICommand = DefaultAICommandClass.static.PushSpecialMoveCommand(AIOwner);
			}
		}

		AIOwner.NotifySpecialMoveStarted( self );
	}

	if( PCOwner != None )
	{
		if( bDisableLook )
		{
			PCOwner.IgnoreLookInput(TRUE);
		}

		// Set camera offset
		if( bUseCustomThirdPersonViewOffset && PCOwner.PlayerCamera != none && PCOwner.PlayerCamera.CameraStyle == 'ThirdPerson' )
		{
			KFThirdPersonCamera(KFPlayerCamera(PCOwner.PlayerCamera).ThirdPersonCam).SetViewOffset( CustomThirdPersonViewOffset, true, ViewOffsetInterpTime );
		}

		// Set camera FOV
		if( CustomCameraFOV > 0.f )
		{
			PCOwner.HandleTransitionFOV( CustomCameraFOV, CameraFOVTransitionTime );
		}

		bRestoredCameraDefaults = false;
	}

	if( bDisableWeaponInteraction && KFPOwner.Weapon != none && KFWeapon(KFPOwner.Weapon) != none )
	{
        KFWeapon(KFPOwner.Weapon).SetSimplePutDown(true);
	}

	if( bDisableTurnInPlace )
	{
		KFPOwner.bDisableTurnInPlace = TRUE;
	}

	if( bDisableMovement )
	{
		SetMovementLock(TRUE);
	}

	// If Pawn rotation should be locked, make sure it is.
	if( bLockPawnRotation )
	{
		SetLockPawnRotation(TRUE);
	}

	if( bDisablePhysics )
	{
		PawnOwner.ZeroMovementVariables();
		if (PawnOwner.Role == ROLE_Authority || PawnOwner.IsLocallyControlled())
		{
			PawnOwner.SetPhysics(PHYS_None);
		}
	}

	if( bUseCustomRotationRate )
	{
		PawnOwner.RotationRate = CustomRotationRate;
	}

	if( CustomTurnInPlaceAnimRate > 0.f )
	{
		KFPOwner.TurnInPlaceAnimRate = CustomTurnInPlaceAnimRate;
	}
}

function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
	// Clear pending stop fire flag
	bPendingStopFire = false;

	// Clear timers.
	PawnOwner.ClearTimer(nameof(AbortSpecialMove), Self);

	// AI set special move command.
	if( AIOwner != None && DefaultAICommandClass != None && AICommand != None )
	{
		if( AIOwner.GetActiveCommand() == AICommand )
		{
			AICommand.Status = 'Success';
			AIOwner.PopCommand(AICommand);
		}
		// Clear reference.
		AICommand = None;
	}

	if( PCOwner != None )
	{
		if( bDisableLook )
		{
			PCOwner.IgnoreLookInput(FALSE);
		}

		RestoreCameraDefaults();
	}

	if( KFPOwner != none )
	{
		KFPOwner.NotifySpecialMoveEnded( self, SMIndex );
	
		if( bUseCustomRotationRate )
		{
			KFPOwner.RotationRate = KFPOwner.default.RotationRate;
		}

		if( KFPOwner.TurnInPlaceAnimRate != KFPOwner.default.TurnInPlaceAnimRate )
		{
			KFPOwner.TurnInPlaceAnimRate = KFPOwner.default.TurnInPlaceAnimRate;
		}

		if( bDisableTurnInPlace )
		{
			KFPOwner.bDisableTurnInPlace = KFPOwner.default.bDisableTurnInPlace;
		}

		if( bDisableWeaponInteraction && KFPOwner.Weapon != none && KFWeapon(KFPOwner.Weapon) != none )
		{
	        KFWeapon(KFPOwner.Weapon).SetSimplePutDown(false);
		}

		// If we are in a state that should have left us wandering, begin wandering at the end of this move
		if( bCanOnlyWanderAtEnd && KFPOwner != none )
		{
			if( AIOwner != none && KFPOwner.IsHeadless() )
			{
				AIOwner.DoHeadlessWander();
			}
			else if(KFPOwner.ShouldBeWandering())
			{
				KFPOwner.CausePanicWander();	
			}
		}		
	}

	if ( bDisablePhysics && (PawnOwner.Role == ROLE_Authority || PawnOwner.IsLocallyControlled()) )
	{
		PawnOwner.SetPhysics(PHYS_Falling);
	}

	// If movement was disabled, toggle it back on
	if( bMovementDisabled )
	{
		SetMovementLock(FALSE);
	}

	if (AIOwner != none)
	{
		AIOwner.NotifySpecialMoveEnded(self);
	}
	AISpecialOwner = none;

	// If Pawn rotation was locked in default properties, unlock it.
	// Otherwise we're in manual mode, so let the special subclass handle everything.
	if( bPawnRotationLocked )
	{
		SetLockPawnRotation(FALSE);
	}

	// Disable end of animation notification, so it doesn't interfere with future special moves
	// using that same animation channel.
	if ( ActiveSlotNode != None )
	{
		ActiveSlotNode.SetActorAnimEndNotification( FALSE );
		ActiveSlotNode = None;
	}
}

/** Restores camera to its previous values */
function RestoreCameraDefaults()
{
	if( bRestoredCameraDefaults || PCOwner == none )
	{
		return;
	}

	if( bUseCustomThirdPersonViewOffset && PCOwner.PlayerCamera != none && PCOwner.PlayerCamera.CameraStyle == 'ThirdPerson' )
	{
		if( KFPawn_Monster(KFPOwner) != none )
		{
			KFThirdPersonCamera(KFPlayerCamera(PCOwner.PlayerCamera).ThirdPersonCam).SetViewOffset( KFPawn_Monster(KFPOwner).ThirdPersonViewOffset, true, ViewOffsetInterpTime );
		}
	}

	// Return camera fov to default
	if( CustomCameraFOV > 0.f )
	{
		PCOwner.HandleTransitionFOV( PCOwner.DefaultFOV, CameraFOVTransitionTime );
	}

	bRestoredCameraDefaults = true;
}

/** Locks or Unlocks Pawn movement */
final function SetMovementLock(bool bEnable)
{
	if( bMovementDisabled != bEnable )
	{
		bMovementDisabled = bEnable;

		if (PCOwner != None)
		{
			PCOwner.IgnoreMoveInput(bEnable);
			// we might be in between the input reading and PlayerMove(), so we need to zero the inputs
			// to make sure a stray tick of motion doesn't get in
			if (PCOwner.PlayerInput != None)
			{
				PCOwner.PlayerInput.aForward = 0.f;
				PCOwner.PlayerInput.aStrafe = 0.f;
				PCOwner.PlayerInput.aUp = 0.f;
			}
		}
		else if( AIOwner != none )
		{
			AIOwner.bPreparingMove = bEnable;
		}

		// Set acceleration to zero
		if( bEnable )
		{
			PawnOwner.Acceleration = Vect(0,0,0);
		}
	}
}

/**
 * Can be overridden to modify sprint speed when the move is running. Must be implemented in subclasses (see KFSM_Block)!
 * Network: SERVER
 */
function float GetSprintSpeedModifier() 
{
	return 1.0f;
};

function EnableRootMotion()
{
	// Turn on root motion on animation node
	KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneAxisOption( RBA_Translate, RBA_Translate, RBA_Translate );

	// Turn on Root motion on mesh.
	KFPOwner.Mesh.RootMotionMode = SMRootMotionMode;
}

function DisableRootMotion()
{
	// Restore default root motion mode
	KFPOwner.Mesh.RootMotionMode = PawnOwner.Mesh.default.RootMotionMode;

	// Turn off Root motion on animation node
	KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneAxisOption(RBA_Discard, RBA_Discard, RBA_Discard);
}

/** Overridden to restrict movement when on the ground */
function Tick( float DeltaTime )
{
	super.Tick( DeltaTime );

	if( bMovementDisabled && KFPOwner != none && KFPOwner.Role == ROLE_Authority && KFPOwner.Physics == PHYS_Walking )
	{
		KFPOwner.Velocity.X = 0.f;
		KFPOwner.Velocity.Y = 0.f;
		KFPOwner.Acceleration = vect(0,0,0);
	}
}

/**
 * Locks or UnLocks Pawn rotation.
 * When Pawn rotation is locked, Pawn will stop rotating to match the controller's Yaw.
 */
final function SetLockPawnRotation(bool bLock)
{
	if( bPawnRotationLocked != bLock )
	{
		bPawnRotationLocked = bLock;

		// If we were locked, then interpolate back to new (unlocked) rotation
		if( !bLock )
		{
			//PawnOwner.InterpolatePawnRotation();
		}
	}
}

/** Allows special moves to alter view rotation */
function ProcessViewRotation( float DeltaTime, out rotator out_ViewRotation, out Rotator out_DeltaRot );

/** Play a CameraAnim */
function CameraAnimInst PlayCameraAnim
(
	Pawn			PawnToPlay,
	CameraAnim		InCameraAnim,
	optional float	Rate=1.f,
	optional float	Scale=1.f,
	optional float	BlendInTime,
	optional float	BlendOutTime,
	optional bool	bLoop,
	optional bool	bRandomStartTime,
	optional float	Duration,
	optional bool	bSingleInstance,
	optional bool	bSkipSpaceCheck
)
{
	local KFPlayerController	KFPC;

	KFPC = KFPlayerController(PawnToPlay.Controller);

	// Only play camera animations on local human players
	if (KFPC == None || !PawnToPlay.IsLocallyControlled() || KFPC.PlayerCamera == None || InCameraAnim == None)
	{
		return None;
	}

	if (!bSkipSpaceCheck && !KFPC.CameraAnimHasEnoughSpace(InCameraAnim, Scale))
	{
		`log("PlayCameraAnim failed CameraAnimHasEnoughSpace:" @ KFPC.CameraAnimHasEnoughSpace(InCameraAnim, Scale));
		return None;
	}

	if (PawnOwner != None && PawnOwner.WorldInfo.GRI.bMatchIsOver)
	{
		return None;
	}

	// Play camera animation
	return KFPC.PlayerCamera.PlayCameraAnim(InCameraAnim, Rate, Scale, BlendInTime, BlendOutTime, bLoop, bRandomStartTime, Duration, bSingleInstance);
}

/* Play a custom animation by name */
simulated function float PlaySpecialMoveAnim( name AnimName,
											  EAnimSlotStance AnimStance,
											  optional float BlendInTime=0.2f,
											  optional float BlendOutTime=0.2f,
											  optional float Rate=1.f,
											  optional bool	bLooping )
{
	local float Duration;

	`log("PlaySpecialMoveAnim '"$AnimName$"' on"@KFPOwner@" SM="$self, KFPOwner.bLogCustomAnim);

	Duration = KFPOwner.PlayBodyAnim(AnimName, AnimStance, Rate, BlendInTime, BlendOutTime, bLooping);

	// Make sure animation is valid (otherwise this move could break)
	if ( Duration <= 0 && KFPOwner.IsDoingSpecialMove() )
	{
		// Failed to play animation, fall back to just setting a timer to exit the special move.
		`warn("Failed to play" @ AnimName @ "on special move" @ Self @ "on Pawn" @ PawnOwner);
		PawnOwner.SetTimer(0.25f, FALSE, nameof(AbortSpecialMove), Self);
		return 0;
	}

	// Enable OnAnimEnd notification (auto-reset by the slot node)
	if ( !bLooping )
	{
		ActiveSlotNode = KFPOwner.BodyStanceNodes[AnimStance];
		ActiveSlotNode.SetActorAnimEndNotification( TRUE );
	}

	return Duration;
}

/** Timer used to exist gracefully if something has gone wrong */
function AbortSpecialMove()
{
	KFPOwner.EndSpecialMove();
}

/**
 * Notification called when body stance animation finished playing.
 * @param	SeqNode		- Node that finished playing. You can get to the SkeletalMeshComponent by looking at SeqNode->SkelComponent
 * @param	PlayedTime	- Time played on this animation. (play rate independant).
 * @param	ExcessTime	- how much time overlapped beyond end of animation. (play rate independant).
 */
function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
	// By default end this special move.
	KFPOwner.EndSpecialMove();
}

/**
 * This is a callback function that will be kicked off from KFPawn.uc OnAnimEnd. Some animations cause
 * physics to happen when they end so we defer certain end animations till post tick.
 */
function DeferOnAnimEnd()
{
	if( KFPOwner.SpecialMoves[KFPOwner.SpecialMove] == self)
	{
		AnimEndNotify(None, 0, 0);
	}
}

/** Called from KFPawn::OnAnimNotifyParticleSystemSpawned() */
function OnAnimNotifyParticleSystemSpawned( const AnimNotify_PlayParticleEffect AnimNotifyData, ParticleSystemComponent PSC );

/*********************************************************************************************
 * Gameplay notications/hooks
 *********************************************************************************************/

/** Called by owning AI Command when AI behavior decides to stop this attack */
function AbortedByAICommand();

/** Notification from the pawn that damage was taken during move */
function NotifyOwnerTakeHit(class<KFDamageType> DamageType, vector HitLoc, vector HitDir, Controller InstigatedBy);

/** Notification from the pawn that a medium (aka gun) or heavy (aka melee) affliction has been activated */
function NotifyHitReactionInterrupt();

/** Called when owning pawn has damage parried during this move */
function bool CanInterruptWithParry();

/** Called when owner swaps to gore mesh during this move */
function OnGoreMeshSwap();

/** Server notification that the pawn has been EMP disrupted */
function OnEMPDisrupted()
{
	if( KFPOwner != none && KFPOwner.IsHumanControlled() )
	{
		KFPOwner.EndSpecialMove();
	}
}

/** Used for changing attack target (non claimable, switch to other enemy) */
event ModifyInteractionPawn(out KFPawn OtherPawn);

/** Allows special moves to control the crosshair */
function bool CanDrawCrosshair() { return true; }

/** Allows special move to alter aim rotation */
function bool GetSMAimRotation( out rotator AimRot );

defaultproperties
{
	Handle=KFSpecialMove
	SMRootMotionMode=RMM_Accel
	bDisableSteering=true
	bDisableAIAttackRangeChecks=true
	bAllowHitReactions=false
	bCanOnlyWanderAtEnd=false
	bShouldDeferToPostTick=false
	bUseHigherMeshSmoothingThreshold=false
	CustomTurnInPlaceAnimRate=0.f

	// Camera view offset/FOV
	bUseCustomThirdPersonViewOffset=false
	CustomCameraFOV=0.f
	CameraFOVTransitionTime=1.f
}