//=============================================================================
// KFSM_Stumble
//=============================================================================
// Stumble (aka Stagger, Knockback) on damage
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================

class KFSM_Stumble extends KFSM_PlaySingleAnim;

/** Anims are pulled from the pawn's AnimInfo class (see KFPawnAnimInfo) */
enum EStubmleAnim
{
	ESA_Body_Forward,
	ESA_Body_Backward,
	ESA_Body_Left,
	ESA_Body_Right,
	ESA_Leg_L_Forward,
	ESA_Leg_L_Backward,
	ESA_Leg_L_Left,
	ESA_Leg_L_Right,
	ESA_Leg_R_Forward,
	ESA_Leg_R_Backward,
	ESA_Leg_R_Left,
	ESA_Leg_R_Right,
	ESA_Parry,
};

/** If true, try to perform a leg stumble */
static function bool ShouldDoLegStumble(KFPawn P, EHitZoneBodyPart HitZoneLimb)
{
	if ( HitZoneLimb == BP_LeftLeg || HitZoneLimb == BP_RightLeg )
	{
		return (VSizeSq(P.Velocity) > 100.f);
	}
	return false;
}

static function byte PackBodyHitSMFlags(KFPawn P, vector HitDir)
{
	local EPawnOctant HitRegion;
	local byte Type;

	HitRegion = P.CalcOctagonRegion(P.Rotation, -Normal(HitDir));
	Type = GetStumbleTypeFromRegion(HitRegion);

	// pack into two nibbles
	return Type + (GetRandomVariant(P, Type) << 4);
}

static function byte PackLegHitSMFlags(KFPawn P, vector HitDir, bool bLeftLeg)
{
	local EPawnOctant HitRegion;
	local byte Type;

	if ( P.Floor.Z < 0.98f )
	{
		// get direction based on footing slope
		HitRegion = P.CalcOctagonRegion(P.Rotation, Normal2D(P.Floor));
		Type = GetStumbleTypeFromRegion(HitRegion);
	}
	else
	{
		// get 4-way direction from hit (random reverse)
		HitRegion = P.CalcOctagonRegion(P.Rotation, -Normal(HitDir));
		Type = GetStumbleTypeFromRegion(HitRegion, true);
	}

	// offset anim index by leg
	if ( bLeftLeg )
	{
		Type += ESA_Leg_L_Forward;
	}
	else
	{
		Type += ESA_Leg_R_Forward;
	}

	return Type + (GetRandomVariant(P, Type) << 4);
}

/** Initialize flags for parry animation type */
static function byte PackParrySMFlags(KFPawn P, vector HitDir)
{
	return ESA_Parry + (GetRandomVariant(P, ESA_Parry) << 4);
}

/** Debugging */
static function byte PackRandomSMFlags(KFPawn P)
{
	local byte Type;
	Type = Rand(ESA_MAX);
	return Type + (GetRandomVariant(P, Type) << 4);
}

/** Helper for PackSMFlags */
static function byte GetRandomVariant(KFPawn P, byte Type)
{
	local int NumVariants;
	NumVariants = P.PawnAnimInfo.StumbleAnims[Type].Anims.Length;
	return Rand(NumVariants);
}

/** Helper for PackSMFlags */
static function byte GetStumbleTypeFromRegion(EPawnOctant HitRegion, optional bool bRandomReverse)
{
	if ( bRandomReverse )
	{
		switch (HitRegion)
		{
		case DIR_Forward:
		case DIR_Backward:
			return (FRand() > 0.5) ? ESA_Body_Forward : ESA_Body_Backward;
		case DIR_Left:
		case DIR_Right:
			return (FRand() > 0.5) ? ESA_Body_Left : ESA_Body_Right;
		case DIR_ForwardLeft:
		case DIR_ForwardRight:
		case DIR_BackwardLeft:
		case DIR_BackwardRight:
			return ESA_Body_Forward + Rand(4);
		}
	}
	else
	{
		switch (HitRegion)
		{
		case DIR_Forward:
			return ESA_Body_Forward;
		case DIR_ForwardLeft:
			return (FRand() > 0.5) ? ESA_Body_Forward : ESA_Body_Left;
		case DIR_ForwardRight:
			return (FRand() > 0.5) ? ESA_Body_Forward : ESA_Body_Right;
		case DIR_Backward:
			return ESA_Body_Backward;
		case DIR_BackwardLeft:
			return (FRand() > 0.5) ? ESA_Body_Backward : ESA_Body_Left;
		case DIR_BackwardRight:
			return (FRand() > 0.5) ? ESA_Body_Backward : ESA_Body_Right;
		case DIR_Left:
			return ESA_Body_Left;
		case DIR_Right:
			return ESA_Body_Right;
		}
	}

	return ESA_Body_Forward;
}

/**
 * Checks to see if this Special Move can be done.
 */
protected function bool InternalCanDoSpecialMove()
{
	// Only valid while walking or falling slowly.  This makes stumble exclusive to HandleMomentum()
	return (!KFPOwner.IsImpaired() || KFPOwner.IsHeadless()) && (PawnOwner.Physics == PHYS_Walking || (PawnOwner.Physics == PHYS_Falling &&
        Abs(PawnOwner.Velocity.Z) < 250 && VSize2d(PawnOwner.Velocity) < 600));
}

function PlayAnimation()
{
	local byte Type, Variant;

	Type = KFPOwner.SpecialMoveFlags & 15;
	Variant = KFPOwner.SpecialMoveFlags >> 4;
	AnimName = KFPOwner.PawnAnimInfo.StumbleAnims[Type].Anims[Variant];

	PlaySpecialMoveAnim(AnimName, EAS_FullBody, BlendInTime, BlendOutTime, 1.f);

	// Clear the special move flags now so that SpecialMoveFlagsUpdated never fails
	KFPOwner.SpecialMoveFlags = 255;

	if ( KFPOwner.Role == ROLE_Authority )
	{
		bCanBeInterrupted = false;
		KFPOwner.SetTimer(RandRange(0.5f, 0.75f), false, nameof(EnableInterrupt), Self);
	}
}

function EnableInterrupt()
{
	bCanBeInterrupted = true;
}

/** called when DoSpecialMove() is called again with this special move, but the special move flags have changed */
function SpecialMoveFlagsUpdated()
{
	// If the move flags are updated play a new animation (chain stumble)
	// This can happen on a client if the server interrupted a stumble and
	// then started a new one on the same frame.
	PlayAnimation();
}

DefaultProperties
{
	Handle=KFSM_Stumble

	bUseRootMotion=true
	BlendOutTime=0.2f
	AbortBlendOutTime=0.1f

	// ---------------------------------------------
	// AI/Navigation
	DefaultAICommandClass=class'KFGame.AICommand_Stumble'
	AITimeout=5.f

	bDisablesWeaponFiring=true
	bCanOnlyWanderAtEnd=true
}
