//=============================================================================
// KFSM_RecoverFromRagdoll
//=============================================================================
// Recover from rigid body physics knockdown effects
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
// Based on GSM_RecoverFromRagdoll
// Copyright 1998-2011 Epic Games, Inc. All Rights Reserved.
//=============================================================================

class KFSM_RecoverFromRagdoll extends KFSpecialMove
	native(SpecialMoves);
	
// Recovery anims
var() Name GetUpFrontAnim;
var() Name GetUpBackAnim;

// Blend out physics
var		bool	bBlendToGetUp;
var		float	GetUpBlendStartTime;
var()	float	GetUpBlendTime;

// GetUp orientation
var()	Name	UpDownBoneName;
var()	EAxis	UpDownAxis;
var()	bool	bInvertUpDownBoneAxis;
var()	Name	OrientationBoneName;
var()	EAxis	OrientationAxis;
var()	INT		GetUpFromBackYawOffset;

/** If TRUE, ignore pawns when looking for a spot to get up */
var()	bool	bIgnorePawnsOnRecover;

/** enable debugging */
var()   bool	bDebugSpecialMove;

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

protected function bool InternalCanDoSpecialMove()
{
	return (PawnOwner.Physics == PHYS_RigidBody);
}

function SpecialMoveStarted(bool bForced, Name PrevMove)
{
	local vector	HitLocation, HitNormal, TraceStart, TraceEnd, Extent;
	local vector	HeightVec;
	local Actor		HitActor;

	Super.SpecialMoveStarted(bForced,PrevMove);

	// Stop updating the physics bones to match the animation
	PawnOwner.Mesh.bUpdateKinematicBonesFromAnimation = FALSE;
	PawnOwner.Mesh.PhysicsWeight = 1.f;

	// Restore physics update settings
	PawnOwner.Mesh.MinDistFactorForKinematicUpdate = PawnOwner.default.Mesh.MinDistFactorForKinematicUpdate;

	// Restore tick group
	PawnOwner.Mesh.SetTickGroup(PawnOwner.Default.Mesh.TickGroup);
	PawnOwner.SetTickGroup(TG_PreAsyncWork);

	// This will fix all bones, and stop Actor location being changed.
	PawnOwner.SetPhysics(PHYS_None);

	// Figure out Actor Rotation and Recovery animation to play
	PlayRecoveryAnim();

	// Move Pawn off walls
	Extent = vect(0,0,0);
	// Trace down to floor level
	HeightVec	= vect(0,0,1) * PawnOwner.CylinderComponent.CollisionHeight;
	TraceStart	= PawnOwner.Location + HeightVec * 1.1f;	// Don't go to high, so we don't start trace inside a ceiling.
	TraceEnd	= PawnOwner.Location - HeightVec * 3.f;

	if(bIgnorePawnsOnRecover)
	{
		//HitActor = TraceNoPawns(PawnOwner, TraceEnd, TraceStart, HitLocation);
		HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, TraceStart, FALSE, Extent);
	}
	else
	{
		HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, TraceStart, TRUE, Extent);
	}

	if( HitActor != None )
	{
		MoveOffGeometry(HitLocation, HeightVec, Extent);
	}
	else if ( bDebugSpecialMove )
	{
		`log(class @ "Failed trace");
		PawnOwner.DrawDebugCylinder(TraceStart + vect(0,0,1) * Extent.Z, TraceEnd - vect(0,0,1) * Extent.Z, Extent.X, 4, 0, 255, 0, TRUE);
		PawnOwner.DrawDebugLine(TraceStart, TraceEnd, 0, 0, 255, TRUE);
	}

	// Start changing PhysicsWeight from 1.f (showing ragdoll position) to 0.f (showing first frame of get up anim)
	bBlendToGetUp = TRUE;
	GetUpBlendStartTime = PawnOwner.WorldInfo.TimeSeconds;

	// Skip BlendToGetUp if Pawn was using partial kinamatics for gore (e.g. Headless)
	if ( KFPOwner.bHasBrokenConstraints && KFPOwner.HasInjuredHitZones() )
	{
		PawnOwner.Mesh.PhysicsWeight = 0.f;
		FinishedBlendToGetUp();
	}
}

/** Trace, but ignore volumes and triggers */
simulated function Actor TraceNoPawns(Pawn TraceOwner, vector End, vector Start, out vector HitLocation)
{
	local Actor HitActor;
	local vector HitLoc, HitNorm;

	// Iterate over each actor along trace...
	foreach TraceOwner.TraceActors(class'Actor', HitActor, HitLoc, HitNorm, End, Start, vect(0,0,0))
	{
		// .. if it's not a pawn, use it!
		if(Pawn(HitActor) == None)
		{
			HitLocation = HitLoc;
			return HitActor;
		}
	}
	return None;
}

// Move off geometry by collision extent.
simulated function MoveOffGeometry(vector HitLocation, vector HeightVec, vector Extent)
{
	local PrimitiveComponent    BackupCollisionComponent;

	HitLocation += HeightVec - vect(0,0,1) * Extent.Z + vect(0,0,1); // nudge by one unit up.
	Extent = Vect(1,1,0) * PawnOwner.CylinderComponent.CollisionRadius + HeightVec;

	if ( bDebugSpecialMove )
	{
		PawnOwner.DrawDebugSphere(HitLocation, 16, 8, 255, 0, 0, TRUE);
		PawnOwner.DrawDebugBox(HitLocation, Extent, 255, 0, 0, TRUE);
	}

	if( PawnOwner.FindSpot(Extent, HitLocation) )
	{
		if ( bDebugSpecialMove )
		{
			PawnOwner.DrawDebugSphere(HitLocation, 16, 8, 0, 255, 0, TRUE);
			PawnOwner.DrawDebugBox(HitLocation, Extent, 0, 255, 0, TRUE);
		}

		// Restore collision component just for the SetLocation call.
		BackupCollisionComponent = PawnOwner.CollisionComponent;
		PawnOwner.CollisionComponent = PawnOwner.CylinderComponent;
		if( !PawnOwner.SetLocation(HitLocation) )
		{
			`warn(class @ "SetLocation failed!!!!!!!!!!", bDebugSpecialMove);
		}
		PawnOwner.CollisionComponent = BackupCollisionComponent;

		if ( bDebugSpecialMove )
		{
			PawnOwner.DrawDebugSphere(PawnOwner.Location, 16, 8, 0, 0, 255, TRUE);
			PawnOwner.DrawDebugBox(PawnOwner.Location, Extent, 0, 0, 255, TRUE);
		}
	}
	else
	{
		PawnOwner.SetLocation(HitLocation);
		if ( bDebugSpecialMove )
		{
			`log(class @ "Failed findspot");
			PawnOwner.DrawDebugCylinder(PawnOwner.Location - HeightVec, PawnOwner.Location + HeightVec, PawnOwner.CylinderComponent.CollisionRadius, 12, 255, 0, 0, TRUE);
		}
	}
}

/**
 * Figure out what the Actor's Rotation should be.
 * And which recovery animation should be played.
 */
function PlayRecoveryAnim()
{
	local Rotator NewRotation;
	local int OrientationBodyIndex, UpDownBodyIndex;
	local matrix BodyTM;
	local bool bGetUpFromBack;

	bGetUpFromBack = FALSE;

	// Make sure we have a bone set to do these things.
	OrientationBodyIndex = PawnOwner.Mesh.PhysicsAsset.FindBodyIndex(OrientationBoneName);
	if( OrientationBodyIndex != INDEX_NONE )
	{
		// force rotation to match the body's direction so the blend to the getup animation looks more natural
		BodyTM = PawnOwner.Mesh.PhysicsAssetInstance.Bodies[OrientationBodyIndex].GetUnrealWorldTM();
		NewRotation.Yaw = rotator(MatrixGetAxis(BodyTM, OrientationAxis)).Yaw;
		//PawnOwner.DrawDebugCoordinateSystem(MatrixGetOrigin(BodyTM), MatrixGetRotator(BodyTM), 5.f, TRUE);

		UpDownBodyIndex = PawnOwner.Mesh.PhysicsAsset.FindBodyIndex(UpDownBoneName);

		if( UpDownBodyIndex != INDEX_NONE )
		{
			BodyTM = PawnOwner.Mesh.PhysicsAssetInstance.Bodies[UpDownBodyIndex].GetUnrealWorldTM();
			bGetUpFromBack = (MatrixGetAxis(BodyTM, UpDownAxis).Z > 0.f);
			//PawnOwner.DrawDebugCoordinateSystem(MatrixGetOrigin(BodyTM), MatrixGetRotator(BodyTM), 5.f, TRUE);
			if( bInvertUpDownBoneAxis )
			{
				bGetUpFromBack = !bGetUpFromBack;
			}
		}

		// flip it around if the head is facing upwards, since the animation for that makes the character
		// end up facing in the opposite direction that its body is pointing on the ground
		if( bGetUpFromBack )
		{
			NewRotation.Yaw += GetUpFromBackYawOffset;
			NewRotation = Normalize(NewRotation);
		}
		PawnOwner.SetRotation(NewRotation);
 		//PawnOwner.DrawDebugCoordinateSystem(PawnOwner.Location, PawnOwner.Rotation, 50.f, TRUE);
	}
	else
	{
		NewRotation.Yaw = PawnOwner.Rotation.Yaw;
		PawnOwner.SetRotation(NewRotation);
	}

	// Choose correct get-up animation and rewind get up animation to start
	// The play rate is zero - we just want it frozen on the first frame.
	PlaySpecialMoveAnim(GetRecoveryAnim(bGetUpFromBack), EAS_FullBody, 0.f, 0.33f, 1.f);

	// Enable Root Motion
	EnableRootMotion();
}

/** Helper for PlayRecoveryAnim */
function name GetRecoveryAnim(bool bGetUpFromBack)
{
	return (bGetUpFromBack) ? GetUpBackAnim : GetUpFrontAnim;
}

/**
 *	Done with blend, start updating physics bones again, and show result.
 *	Bones should all be fixed at this point.
 */
simulated event FinishedBlendToGetUp()
{
	bBlendToGetUp = FALSE;
	class'KFSM_RagdollKnockdown'.static.TermKnockdownRagdoll(KFPOwner);
}

function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
	if (bBlendToGetUp)
	{
		FinishedBlendToGetUp();
	}

	bBlendToGetUp = FALSE;

	DisableRootMotion();

	Super.SpecialMoveEnded(PrevMove, NextMove);
}

defaultproperties
{
	Handle=KFSM_RecoverFromRagdoll

	// ---------------------------------------------
	// Animations
	GetUpFrontAnim=Getup_F_V1
	GetUpBackAnim=Getup_B_V1

	UpDownBoneName="Spine1"
	UpDownAxis=AXIS_Y
	bInvertUpDownBoneAxis=TRUE
	OrientationBoneName="Spine1"
	OrientationAxis=AXIS_X
	GetUpFromBackYawOffset=+32768
	GetUpBlendTime=0.42f
	bIgnorePawnsOnRecover=TRUE

	// ---------------------------------------------
	// SpecialMove
	bDisableMovement=TRUE
	bLockPawnRotation=TRUE
    bDisablesWeaponFiring=true
	bCanOnlyWanderAtEnd=true    
}