//=============================================================================
// KFSM_Emerge
//=============================================================================
// Class Description
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//  - Author 3/14/2014
//=============================================================================

class KFSM_Emerge extends KFSpecialMove;

var()	array<name>	FloorEmerge;
var()	array<name>	Wall248Emerge;
var()	array<name>	WallHighEmerge;
var()	array<name>	CeilingEmerge;

var()	float BlendOutTime;

/** If set, triggers a SM_RagdollKnockdown move at the end */
var bool bDoKnockdown;

/** Pack the animation index we'll be using for the zed to emerge */
static function byte PackAnimFlag( EEmergeAnim EmergeType, optional out byte LastAnimVariant )
{
	local byte Variant;
	local int NumAnims;

	NumAnims = GetEmergeAnimNum(EmergeType);

	if ( ( EmergeType == EMERGE_Wall248UU || EmergeType == EMERGE_WallHigh ) )
    {
    	Variant = 0;
		if ( NumAnims > 1 )
		{
			while ( Variant == LastAnimVariant )
			{
				Variant = Rand(NumAnims);
			}
		}

		LastAnimVariant = Variant;
    }
    else
    {
		Variant = Rand(NumAnims);
	}
	// pack into two nibbles
	return EmergeType + (Variant << 4);
}

static function int GetEmergeAnimNum( byte EmergeType )
{
	switch( EmergeType )
	{
	 	case EMERGE_Floor:
	 		 return default.FloorEmerge.Length;
	 	case EMERGE_Wall248UU:
	 		 return default.Wall248Emerge.Length;
 		case EMERGE_WallHigh:
	 		 return default.WallHighEmerge.Length;
		case EMERGE_Ceiling:
	 		 return default.CeilingEmerge.Length;
	}

	`log(GetFuncName()@"Failed to find valid anim for:"$EmergeType);
}

function name GetEmergeAnim( byte EmergeType, int Variant )
{
	switch( EmergeType )
	{
	 	case EMERGE_Floor:
	 		 return FloorEmerge[Variant];
	 	case EMERGE_Wall248UU:
	 		 return Wall248Emerge[Variant];
 		case EMERGE_WallHigh:
	 		 return WallHighEmerge[Variant];
		case EMERGE_Ceiling:
	 		 return CeilingEmerge[Variant];
	}

	`log(GetFuncName()@"Failed to find valid anim for:"$EmergeType@"variant:"$Variant);
}

/** Allow override from Knockdown (see ANIMNOTIFY_Knockdown) */
function bool CanOverrideMoveWith( Name NewMove )
{
	if ( NewMove == 'KFSM_Knockdown' )
		return true;

	return Super.CanOverrideMoveWith(NewMove);;
}

/** Notification called when Special Move starts */
function SpecialMoveStarted(bool bForced, Name PrevMove )
{
	super.SpecialMoveStarted( bForced, PrevMove );
	
	PlayEmerge();
	bDoKnockdown = false;
}

/** Stop the movement and play the stun animation for all clients */
function name PlayEmerge()
{
	local byte Type, Variant;
	local name EmergeAnim;
	
	Type = KFPOwner.SpecialMoveFlags & 15;
	Variant = KFPOwner.SpecialMoveFlags >> 4;
	EmergeAnim = GetEmergeAnim(Type, Variant);
	
	PlaySpecialMoveAnim( EmergeAnim, EAS_FullBody, 0.0f, BlendOutTime, 1.f, false );
	
	// Turn on Root translation
	EnableRootMotion();

	// Use root rotation to rotate the actor.
	KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneRotationOption(RRO_Extract, RRO_Extract, RRO_Extract);
	KFPOwner.Mesh.RootMotionRotationMode = RMRM_RotateActor;

	KFPOwner.SetCollision(KFPOwner.bCollideActors, FALSE);
	KFPOwner.bCollideWorld = FALSE;

	// Custom handler for destructibles
	if( KFPOwner.WorldInfo.NetMode != NM_Client )
	{
		KFPOwner.SetTimer( 0.25f, true, nameOf(Timer_CheckForPortalDestructibles), self );
	}

	// Force always relevant to fix a bug where the actor becomes relevant midway through his root motion 
	// animation. bUpdateSimulatedPosition might be a alternative?
	KFPOwner.bAlwaysRelevant = true;

	return EmergeAnim;
}

/** Notification called when Special Move starts when ReplicatedSpecialMove changes*/
function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
	Super.SpecialMoveEnded(PrevMove, NextMove);

	// Reset root rotation
	KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneRotationOption(RRO_Discard, RRO_Discard, RRO_Discard);
	KFPOwner.Mesh.RootMotionRotationMode = RMRM_Ignore;

	// Reset root translation
	DisableRootMotion();

	KFPOwner.bAlwaysRelevant = PawnOwner.default.bAlwaysRelevant;

	RestoreCollision();

	if( KFPOwner.WorldInfo.NetMode != NM_Client )
	{
		KFPOwner.ClearTimer( nameOf(Timer_CheckForPortalDestructibles), self );
	}

	// Sometime after the move is over make sure our new AI was able to generate a valid path
	if ( PawnOwner.Role == ROLE_Authority && !PawnOwner.IsHumanControlled() )
	{
		PawnOwner.SetTimer(5.f, false, nameof(FindAnchorFailsafe), self);
	}
}

function Timer_CheckForPortalDestructibles()
{
	local KFPawn_Monster KFPM;
	local KFDestructibleActor KFDA;

	if( KFPOwner.bCollideWorld )
	{
		return;
	}

	KFPM = KFPawn_Monster( KFPOwner );
	if( KFPM != none )
	{
		foreach KFPOwner.OverlappingActors( class'KFDestructibleActor', KFDA, KFPOwner.CylinderComponent.CollisionRadius,, true )
		{
			KFDA.BumpedByMonster( KFPM, Normal(KFDA.Location - KFPOwner.Location) );
		}
	}
}

/**
 * Notification called when body stance animation finished playing.
 */
function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
	// @hack: For knockdown use SM override isntead of ending this move.  Ending it at the
	// same time will trigger a SetPhysics call from AI state Action_Idle and break knockdown

	if ( bDoKnockdown )
	{
		KFPOwner.Knockdown(PawnOwner.Velocity, vect(1,1,1));
	}

	// If we're still in emerge (e.g. no knockdown, knockdown failed) end normally
	if ( KFPOwner.SpecialMove == SM_Emerge  )
	{
		KFPOwner.EndSpecialMove();
	}
}

/** When emerge is complete need to reenable collision settings */
function RestoreCollision()
{
	KFPOwner.SetCollision(PawnOwner.default.bCollideActors, PawnOwner.default.bBlockActors);
 	KFPOwner.bCollideWorld = TRUE;
	KFPOwner.FitCollision();

	PushOverlappingHumans();
}

/** If the pawn is overlapping with another pawn at the end of emerging, give it a push back */
function PushOverlappingHumans()
{
	local Pawn P;

	if ( PawnOwner.Role < ROLE_Authority )
	{
		return;
	}

	foreach PawnOwner.WorldInfo.AllPawns(class'Pawn', P, PawnOwner.Location, PawnOwner.CylinderComponent.CollisionRadius)
	{
		if ( P == KFPOwner || P.Controller == none || !P.Controller.bIsPlayer )
		{
			continue;
		}

		P.Velocity += (100 + KFPOwner.CylinderComponent.CollisionRadius) * vector( KFPOwner.Rotation ) * 2.5f;
		P.Velocity.Z = 200 + KFPOwner.CylinderComponent.CollisionHeight;
		P.SetPhysics(PHYS_Falling);
	}
}

/** Timer used to exist gracefully if something has gone wrong */
function AbortSpecialMove()
{
	super.AbortSpecialMove();
	// Kill the zed if it doesn't have the emerge animation yet
	PawnOwner.Died( None, PawnOwner.WorldInfo.KillzDamageType, PawnOwner.Location );
}

/** Sometime after the move is over make sure our new AI was able to generate a valid path */
function FindAnchorFailsafe()
{
	if ( PawnOwner.Role == ROLE_Authority && !PawnOwner.IsHumanControlled() )
	{
		if ( `TimeSinceEx(PawnOwner, PawnOwner.FindAnchorFailedTime) < 5.f)
		{
			`Warn("Zed unable to resume pathing after SM_Emerge"@PawnOwner@KFPOwner.SpecialMove);
			PawnOwner.Died( None, PawnOwner.WorldInfo.KillzDamageType, PawnOwner.Location );
		}
	}
}

defaultproperties
{
	Handle=SM_Emerge
	SMRootMotionMode=RMM_Translate
	bPawnRotationLocked=true
	bDisablePhysics=true
	bCanOnlyWanderAtEnd=true
	bShouldDeferToPostTick=true

	// animations
	FloorEmerge=(Enter_Floor_V1, Enter_Floor_V2, Enter_Floor_V3)
	Wall248Emerge=(Enter_vent_C_V1, Enter_vent_C_V2, Enter_vent_L_V1, Enter_vent_L_V2, Enter_vent_R_V1, Enter_vent_R_V2)
	WallHighEmerge=(Enter_ventHigh_C_V1, Enter_ventHigh_C_V2, Enter_ventHigh_L_V1, Enter_ventHigh_L_V2, Enter_ventHigh_R_V1, Enter_ventHigh_R_V2)
	CeilingEmerge=(Enter_Ceiling_V1, Enter_Ceiling_V2, Enter_Ceiling_V3, Enter_Ceiling_V4)

	BlendOutTime=0.2

	DefaultAICommandClass=class'KFGame.AICommand_PushedBySM'
}