//=============================================================================
// KFPawn_Scripted
//=============================================================================
// Pawn for scripted characters
//=============================================================================
// Killing Floor 2
// Copyright (C) 2018 Tripwire Interactive LLC
//=============================================================================
class KFPawn_Scripted extends KFPawn
	native(Pawn)
	implements(KFInterface_TriggerOwner);

var localized string ScriptedPawnString;

// Character archetype for the pawn
var repnotify KFCharacterInfo_ScriptedPawn ScriptedCharArchTemplate;
var KFCharacterInfo_ScriptedPawn ScriptedCharArch;
var bool bCanCurrentlyBeTargetedByZeds;
var repnotify transient KFWeldableComponent WeldableComponent;
var repnotify transient KFWeldableTrigger WeldableTrigger;

var transient KFTrigger_NotifyOwner ZedProximityTrigger, PlayerProximityTrigger;
var transient bool bZedInProximity, bPlayerInProximity;

var repnotify byte CurrentState;
var byte PreviousState;

var transient repnotify bool bActive;
var transient bool bStartInactive;

var string IconPath;

var KFDoorActor BlockingDoor;
var float SpeedScalarForObstacles;

var array<ExtraVFXInfo> ScriptedStateVFX;

/** Amount of timer since last damage to consider "recent" in terms of UI messaging to the player*/
var float RecentDamageTimerLength;

/** Default material for this scripted pawn's mesh*/
var MaterialInterface DefaultMaterial;

/** Whether to block PCs */
var() bool bBlockPlayers<DisplayName = Block Human Players>;
/** Whether to block PC controlled zeds (team == 255) */
var() bool bBlockZedPlayers<DisplayName = Block Zed Players>;
/** Whether to block AI Zeds */
var() bool bBlockMonsters<DisplayName = Block AI>;
/** Whether to block anything when at zero health or marked dead */
var() bool bBlockInStoppedState<DisplayName = Block When In Stopped State>;
/** If the current scripted pawn state doesn't allow movement */
var bool bIsInStoppedState;

delegate Delegate_OnReachedRouteMarker(int MarkerIdx, SplineActor Marker, int SubIdx, float DistSinceLastMarker);
delegate Delegate_OnEndedRoute(bool bSuccess);
delegate Delegate_OnTakeDamage(int Damage);
delegate Delegate_OnHealDamage(int HealAmount);
delegate Delegate_OnChangeState(int CurrState, int PrevState);

replication
{
	if (bNetDirty)
		ScriptedCharArchTemplate, CurrentState, WeldableComponent, WeldableTrigger, bActive;
}

cpptext
{
	virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive,AActor *SourceActor, DWORD TraceFlags);
	virtual UBOOL IgnoreBlockingBy(const AActor* Other) const;
	// also checks whether the pawn is active
	virtual UBOOL IsAliveAndWell() const;
}

simulated event ReplicatedEvent(name VarName)
{
	switch (VarName)
	{
	case nameof(ScriptedCharArchTemplate):
		SetCharacterArch(ScriptedCharArchTemplate, true);

		// attempts to initialize weldable component will fail before we get our archetype
		InitializeWeldableComponent();
		break;

	case nameof(WeldableComponent):
		InitializeWeldableComponent();
		break;

	case nameof(WeldableTrigger):
		InitializeWeldableTrigger();
		break;

	case nameof(CurrentState):
		SetPawnState(CurrentState);
		break;

	case nameof(bActive):
		SetActive(bActive);
		break;

	default:
		super.ReplicatedEvent(VarName);
		break;
	}
}

function Initialize(KFCharacterInfo_ScriptedPawn InCharInfo, int InHealth, int InHealthMax)
{
	local int i;

	if (Role < ROLE_Authority)
	{
		return;
	}

	SetCharacterArch(InCharInfo, true);

	if (ScriptedCharArch.bDisableCollisionOnStart)
	{
		SetCollision(false, false);
	}

	Health = Clamp(InHealth, 1, InHealthMax);
	HealthMax = InHealthMax;

	if (ScriptedCharArch.bPawnCanBeWelded || ScriptedCharArch.bPawnCanBeUnwelded)
	{
		InitializeWeldable();
	}

	// if pawn isn't allowed to die, then make sure any state set to a health percentage less than 1
	// gets set to something a little bit more than 1
	if (!ScriptedCharArch.bPawnCanBeKilled)
	{
		for (i = 0; i < ScriptedCharArch.States.Length; ++i)
		{
			ScriptedCharArch.States[i].HealthPctThreshold = FMax(ScriptedCharArch.States[i].HealthPctThreshold, (1.1f / float(HealthMax)));
		}
	}

	if (ScriptedCharArch.bUseZedProximityTrigger || ScriptedCharArch.bUsePlayerProximityTrigger)
	{
		InitializeProximityTriggers();
	}

	// save off the default material in case a specific state does not specify a material
	DefaultMaterial = Mesh.GetMaterial(0);

	SetPawnState(0);
	UpdatePawnState();
	SetCanBeTargeted(false);

	if (ScriptedCharArch.bIsFlyingPawn)
	{
		SetPhysics(PHYS_Flying);
		bCollideWorld = false;
	}
	else
	{
		SetPhysics(PHYS_Walking);
		bCollideWorld = true; // must collide with world until it lands
	}
}

function InitializeWeldable()
{
	WeldableComponent = Spawn(class'KFWeldableComponent', self);
	InitializeWeldableComponent();

	WeldableTrigger = Spawn(class'KFWeldableTrigger', self);
	InitializeWeldableTrigger();

	WeldableTrigger.SetCollision(false, false);
	UpdateWeldIntegrity();
}

simulated function InitializeWeldableComponent()
{
	local float WeldCompRadius, WeldCompHeight;
	local KFGameReplicationInfo KFGRI;

	// If we don't have an archetype, wait for it
	// (InitializeWeldableComponent will be called from ReplicatedEvent)
	if (ScriptedCharArch != none && WeldableComponent != none)
	{
		KFGRI = KFGameReplicationInfo(WorldInfo.GRI);

		WeldableComponent.SetOwner(self);
		WeldableComponent.SetBase(self);
		WeldableComponent.MaxWeldIntegrity = ScriptedCharArch.PawnMaxWeldIntegrityPerPlayer[KFGRI.GetNumPlayers()-1];
		if (Role == ROLE_Authority)
		{
			WeldableComponent.WeldIntegrity = WeldableComponent.MaxWeldIntegrity;
		}
		WeldableComponent.bWeldable = ScriptedCharArch.bPawnCanBeWelded;
		WeldableComponent.bUnweldable = ScriptedCharArch.bPawnCanBeUnwelded;
		WeldableComponent.Delegate_OnWeldIntegrityChanged = OnWelded;

		WeldCompRadius = WeldableComponent.GetCollisionCylinderRadius();
		WeldCompHeight = WeldableComponent.GetCollisionCylinderHeight();
		WeldableComponent.SetCollisionCylinderSize(WeldCompRadius * ScriptedCharArch.PawnWeldableComponentScale, WeldCompHeight);
		WeldableComponent.SetCollision(bActive, false);
	}
}

simulated function InitializeWeldableTrigger()
{
	if (WeldableTrigger != none)
	{
		WeldableTrigger.SetOwner(self);
		WeldableTrigger.SetBase(self);
		WeldableTrigger.WeldableComponent = WeldableComponent;
	}
}

simulated function InitializeProximityTriggers()
{
	local float TrigRadius, TrigHeight;

	PlayerProximityTrigger = Spawn(class'KFTrigger_NotifyOwner', self);
	PlayerProximityTrigger.SetOwner(self);
	PlayerProximityTrigger.SetBase(self);

	TrigRadius = CylinderComponent(PlayerProximityTrigger.CollisionComponent).CollisionRadius;
	TrigHeight = CylinderComponent(PlayerProximityTrigger.CollisionComponent).CollisionHeight;
	CylinderComponent(PlayerProximityTrigger.CollisionComponent).SetCylinderSize(TrigRadius * ScriptedCharArch.PlayerProximityTriggerScale, TrigHeight);

	ZedProximityTrigger = Spawn(class'KFTrigger_NotifyOwner', self);
	ZedProximityTrigger.SetOwner(self);
	ZedProximityTrigger.SetBase(self);

	TrigRadius = CylinderComponent(ZedProximityTrigger.CollisionComponent).CollisionRadius;
	TrigHeight = CylinderComponent(ZedProximityTrigger.CollisionComponent).CollisionHeight;
	CylinderComponent(ZedProximityTrigger.CollisionComponent).SetCylinderSize(TrigRadius * ScriptedCharArch.ZedProximityTriggerScale, TrigHeight);
}

simulated function OnWelded(int Amount, KFPawn Welder)
{
	local float WeldAmountPct;
	local float HealAmount;

	if (Role == ROLE_Authority && WeldableComponent != none)
	{
		WeldAmountPct = float(Amount) / float(WeldableComponent.MaxWeldIntegrity);
		HealAmount = WeldAmountPct * float(HealthMax);
		Health += HealAmount;

		UpdateWeldIntegrity();
		UpdatePawnState(1);

		if (Delegate_OnHealDamage != none)
		{
			Delegate_OnHealDamage(HealAmount);
		}
	}
}

simulated function UpdateWeldIntegrity()
{
	if (Role == ROLE_Authority && WeldableComponent != none)
	{
		// set weldintegrity based on current health, because health and weldintegrity can get out of sync due to rounding error
		WeldableComponent.SetWeldIntegrity((float(Health) / float(HealthMax)) * WeldableComponent.MaxWeldIntegrity);
	}
}

simulated function SetEscortPawnOnHud()
{
	local KFPlayerController KFPC;

	ForEach LocalPlayerControllers(class'KFPlayerController', KFPC)
	{
		if (KFPC.MyGFxHUD != None && KFPC.MyGFxHUD.BossHealthBar != none)
		{
			KFPC.MyGFxHUD.BossHealthBar.SetEscortPawn(self);
		}
	}
}


simulated function RemoveEscortPawnOnHud()
{
	local KFPlayerController KFPC;

	ForEach LocalPlayerControllers(class'KFPlayerController', KFPC)
	{
		if (KFPC.MyGFxHUD != None && KFPC.MyGFxHUD.BossHealthBar != none)
		{
			KFPC.MyGFxHUD.BossHealthBar.Deactivate();
		}
	}
}

/** Called from Possessed event when this controller has taken control of a Pawn */
function PossessedBy(Controller C, bool bVehicleTransition)
{
	Super.PossessedBy(C, bVehicleTransition);

	/** Set MyKFAIC for convenience to avoid casting */
	if (KFAIController(C) != none)
	{
		MyKFAIC = KFAIController(C);
	}

	KFGameInfo(WorldInfo.Game).Teams[0].AddToTeam(C);
}

simulated event byte ScriptGetTeamNum()
{
	return 0;
}

function AdjustDamage(out int InDamage, out vector Momentum, Controller InstigatedBy, vector HitLocation, class<DamageType> DamageType, TraceHitInfo HitInfo, Actor DamageCauser)
{
	if (InstigatedBy.GetTeamNum() == GetTeamNum() || !bCanCurrentlyBeTargetedByZeds || !bActive)
	{
		InDamage = 0;
	}
	else
	{
		super.AdjustDamage(InDamage, Momentum, InstigatedBy, HitLocation, DamageType, HitInfo, DamageCauser);

		if (InDamage >= Health && !ScriptedCharArch.bPawnCanBeKilled)
		{
			InDamage = Max(Health - 1, 0);
		}
	}
}

function Timer_RecentDamage() {}
function bool WasAttackedRecently()
{
	return IsTimerActive(nameof(Timer_RecentDamage));
}


event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
	local int OldHealth, ActualDamage;

	OldHealth = Health;
	Super.TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
	ActualDamage = OldHealth - Health;

	if (ActualDamage > 0)
	{
		SetTimer(RecentDamageTimerLength, false, nameof(Timer_RecentDamage), self);

		if (WeldableComponent != none)
		{
			UpdateWeldIntegrity();
		}

		UpdatePawnState(-1);

		if (Delegate_OnTakeDamage != none)
		{
			Delegate_OnTakeDamage(ActualDamage);
		}
	}
}

simulated function PlayTakeHitEffects(vector HitDirection, vector HitLocation, optional bool bUseHitImpulse = true)
{
	TryPlayHitReactionAnim(HitDirection, HitFxInfo.DamageType, HitFxInfo.HitBoneIndex);
}

/** Calculate type of hit reaction animation from damage info */
simulated function bool TryPlayHitReactionAnim(vector HitDirection, class<KFDamageType> damageType, byte HitZoneIdx)
{
	local EPawnOctant AnimDir;
	local EHitReactionAnimType HitReactionType;
	local EHitZoneBodyPart BodyPart;
	local bool bOnlyAdditiveHits;

	if( damageType == none || ActorTimeSince(NextHitReactionAnim_ActorTime) < 0 )
	{
		return false;
	}

	// cannot play hit reaction anims during special moves
	if ( IsDoingSpecialMove() )
	{
		bOnlyAdditiveHits = true;
		if ( !SpecialMoves[SpecialMove].bAllowHitReactions )
		{
			return false;
		}
	}

	// Set the limb used for incap effects
	BodyPart = (HitZoneIdx != 255 && HitZoneIdx < HitZones.Length) ? HitZones[HitZoneIdx].Limb : BP_Torso;

	HitReactionType = HIT_Light;
	// If we're moving (e.g. DoPauseAI wasn't called because of incap cooldown) don't try hard/medium
	// This is not 100% reliable, but worst case we get a reasonable upper body blend while moving
	bOnlyAdditiveHits = bOnlyAdditiveHits || VSizeSq(Velocity) > 50.f;

	if ( !bOnlyAdditiveHits )
	{
		HitReactionType = EHitReactionAnimType(AfflictionHandler.GetPredictedHitReaction(DamageType, BodyPart));
	}

	switch (BodyPart)
	{
	case BP_LeftArm:
		AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
		if ( AnimDir == DIR_Forward || AnimDir == DIR_ForwardLeft || AnimDir == DIR_ForwardRight )
			AnimDir = DIR_ForwardLeft;
		else
			AnimDir = DIR_BackwardLeft;
		break;
	case BP_RightArm:
		AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
		if ( AnimDir == DIR_Forward || AnimDir == DIR_ForwardLeft || AnimDir == DIR_ForwardRight )
			AnimDir = DIR_ForwardRight;
		else
			AnimDir = DIR_BackwardRight;
		break;
	default:
		AnimDir = CalcOctagonRegion(Rotation, -HitDirection);
		break;
	}

	// Play animation
	return PawnAnimInfo.PlayHitReactionAnim(self, HitReactionType, AnimDir);
}

simulated function bool CanBeHealed()
{
	return ScriptedCharArch.bPawnCanBeHealed;
}

event bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType, optional bool bRepairArmor=true, optional bool bMessageHealer=true)
{
	local bool result;
	local int PrevHealth;

	if (!bActive || !ScriptedCharArch.bPawnCanBeHealed)
	{
		return false;
	}

	PrevHealth = Health;
	result = Super.HealDamage(Amount, Healer, DamageType, bRepairArmor, bMessageHealer);

	UpdateWeldIntegrity();
	UpdatePawnState(1);

	if (Delegate_OnHealDamage != none)
	{
		Delegate_OnHealDamage(Health - PrevHealth);
	}

	return result;
}

function UpdatePawnState(optional int HealthDelta = 0)
{
	local int i;
	local float HealthPct;

	HealthPct = float(Health) / float(HealthMax);

	if (HealthDelta > 0)
	{
		for (i = 0; i < CurrentState; ++i)
		{
			if (HealthPct >= ScriptedCharArch.States[i].HealthPctThreshold)
			{
				break;
			}
		}
	}
	else
	{
		for (i = CurrentState; i < ScriptedCharArch.States.Length - 1; ++i)
		{
			if (HealthPct >= ScriptedCharArch.States[i + 1].HealthPctThreshold)
			{
				break;
			}
		}
	}

	SetPawnState(i);
}

simulated function SetPawnState(int InState)
{
	local int ExitTransitionType, EnterTransitionType;

	if (Role == ROLE_Authority && InState == CurrentState)
	{
		return;
	}

	if (ScriptedCharArchTemplate != none)
	{
		if (CharacterArch == none)
		{
			// Make sure arch is set because this function could be called after ScriptedCharArchTemplate is
			// replicated but before ScriptedCharArchTemplate is repnotified
			SetCharacterArch(ScriptedCharArchTemplate, true);
		}

		if (Role == ROLE_Authority)
		{
			CurrentState = InState;
			SetCanBeTargeted(ScriptedCharArch.States[InState].bCanBeTargetedByZeds);
		}

		if (CurrentState != PreviousState)
		{
			UpdatePawnSpeed();

			// set whether this current state allows movement
			bIsInStoppedState = ScriptedCharArch.States[CurrentState].SpeedScalar <= 0.0f;

			if (PreviousState < CurrentState)
			{
				ExitTransitionType = FXTransitionType_PlayGreaterStateTransition;
				EnterTransitionType = FXTransitionType_PlayLesserStateTransition;
			}
			else if (PreviousState > CurrentState)
			{
				ExitTransitionType = FXTransitionType_PlayLesserStateTransition;
				EnterTransitionType = FXTransitionType_PlayGreaterStateTransition;
			}

			StopExtraVFX(Name("EnterState"$PreviousState));

			PlayExtraVFX(Name("ExitState"$PreviousState$"-"$ExitTransitionType));
			PlayExtraVFX(Name("EnterState"$CurrentState$"-"$EnterTransitionType));

			PlayExtraVFX(Name("ExitState"$PreviousState));
			PlayExtraVFX(Name("EnterState"$CurrentState));

			CheckScriptedPawnMaterial();
			DoSpecialMove(SM_ScriptedPawnStateChange, true);
		}

		if (Delegate_OnChangeState != none)
		{
			Delegate_OnChangeState(CurrentState, PreviousState);
		}

		PreviousState = CurrentState;
	}
}

simulated function CheckScriptedPawnMaterial()
{
	//change the material based on the new state
	if (bActive && ScriptedCharArch.States[CurrentState].HighlightedStateMaterial != none)
	{
		Mesh.SetMaterial(0, ScriptedCharArch.States[CurrentState].HighlightedStateMaterial);
	}
	else if (ScriptedCharArch.States[CurrentState].DefaultStateMaterial != none)
	{
		Mesh.SetMaterial(0, ScriptedCharArch.States[CurrentState].DefaultStateMaterial);
	}
	else
	{
		// if there isn't a material set, use the default material
		Mesh.SetMaterial(0, DefaultMaterial);
	}
}

function bool IsBlockedByZed()
{
	return ScriptedCharArch.bUseZedProximityTrigger && bZedInProximity;
}

function UpdatePawnSpeed()
{
	GroundSpeed = ScriptedCharArch.PawnSpeed * ScriptedCharArch.States[CurrentState].SpeedScalar;

	if (IsBlockedByZed())
	{
		GroundSpeed *= ScriptedCharArch.SpeedScalarForZedProximity;
	}

	if (ScriptedCharArch.bUsePlayerProximityTrigger && bPlayerInProximity)
	{
		GroundSpeed *= ScriptedCharArch.SpeedScalarForPlayerProximity;
	}

	// used to pause the scripted pawn in front of closed doors and other obstacles
	GroundSpeed *= SpeedScalarForObstacles;
}

simulated function bool IsInCriticalCondition()
{
	return CurrentState == ScriptedCharArch.States.Length - 1;
}

simulated function name GetStateTransitionAnim()
{
	if (CurrentState != PreviousState)
	{
		if (ScriptedCharArch.States[CurrentState].EnterAnim != `NAME_None)
		{
			return ScriptedCharArch.States[CurrentState].EnterAnim;
		}
		else if (PreviousState != 255 && ScriptedCharArch.States[PreviousState].ExitAnim != `NAME_None)
		{
			return ScriptedCharArch.States[PreviousState].ExitAnim;
		}
	}

	return `NAME_None;
}

simulated function SetCharacterArch(KFCharacterInfoBase Info, optional bool bForce)
{
	local int i, j;
	local ExtraVFXInfo FX;

	super.SetCharacterArch(Info, bForce);

	// SetCharacterArch sets CharacterArch, which is an instance of Info and can't be replicated,
	// so replicate the template (Info) instead
	if (Role == ROLE_Authority)
	{
		ScriptedCharArchTemplate = KFCharacterInfo_ScriptedPawn(Info);
	}

	ScriptedCharArch = KFCharacterInfo_ScriptedPawn(CharacterArch);

	// utilized by the PlayExtraVFX system similar to the archetype's ExtraVFX array
	// putting these effects in a separate array so that the archetype asset storing ExtraVFX won't be modified
	// must be setup before updating pawn state

	// normally ExtraVFX has start and stop SFX events, but EnterFX will handle the start and ExitFX
	// will handle the stop
	for (i = 0; i < ScriptedCharArch.States.Length; ++i)
	{
		for (j = 0; j < ScriptedCharArch.States[i].EnterFX.Length; ++j)
		{
			FX.VFX = ScriptedCharArch.States[i].EnterFX[j].VFX;
			FX.SFXStartEvent = ScriptedCharArch.States[i].EnterFX[j].SFX;
			FX.SocketName = ScriptedCharArch.States[i].EnterFX[j].SocketName;
			FX.Label = Name("EnterState"$i);
			if (ScriptedCharArch.States[i].EnterFX[j].TransitionType != FXTransitionType_PlayAlways)
			{
				FX.Label = Name(FX.Label $ "-" $ int(ScriptedCharArch.States[i].EnterFX[j].TransitionType));
			}
			ScriptedStateVFX.AddItem(FX);
		}

		for (j = 0; j < ScriptedCharArch.States[i].ExitFX.Length; ++j)
		{
			FX.VFX = ScriptedCharArch.States[i].ExitFX[j].VFX;
			FX.SFXStartEvent = ScriptedCharArch.States[i].ExitFX[j].SFX;
			FX.SocketName = ScriptedCharArch.States[i].ExitFX[j].SocketName;
			FX.Label = Name("ExitState"$i);
			if (ScriptedCharArch.States[i].ExitFX[j].TransitionType != FXTransitionType_PlayAlways)
			{
				FX.Label = Name(FX.Label $ "-" $ int(ScriptedCharArch.States[i].ExitFX[j].TransitionType));
			}
			ScriptedStateVFX.AddItem(FX);
		}
	}
}

function bool Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
	local KFAIController KFAIC;

	KFAIC = KFAIController(Controller);
	if (KFAIC != none && KFAIC.CommandList != none)
	{
		KFAIC.AbortCommand(KFAIC.CommandList);
	}

	return super.Died(Killer, damageType, HitLocation);
}

function HandleMomentum(vector Momentum, Vector HitLocation, class<DamageType> DamageType, optional TraceHitInfo HitInfo)
{
	if (ScriptedCharArch.bPawnHandlesMomentum)
	{
		super.HandleMomentum(Momentum, HitLocation, DamageType, HitInfo);
	}
}

function bool CanBeGrabbed(KFPawn GrabbingPawn, optional bool bIgnoreFalling, optional bool bAllowSameTeamGrab)
{
	return ScriptedCharArch.bPawnCanBeGrabbed;
}

event Landed(vector HitNormal, Actor FloorActor)
{
	super.Landed(HitNormal, FloorActor);
	bCollideWorld = false;
}

function StartRoute(SplineActor SplineStart, SplineActor SplineEnd, int SegmentGranularity)
{
	class'AICommand_ScriptedPawn_TraverseSpline'.static.TraverseSpline(MyKFAIC, SplineStart, SplineEnd, SegmentGranularity);
}

function ReachedRouteMarker(int MarkerIdx, SplineActor Marker, int SubIdx, float DistSinceLastMarker)
{
	if (Delegate_OnReachedRouteMarker != none)
	{
		Delegate_OnReachedRouteMarker(MarkerIdx, Marker, SubIdx, DistSinceLastMarker);
	}
}

// ReachedGoal is called when the pawn successfully completes its scripted route
function ReachedGoal()
{
	FinishSequence();
	Finish(false);
}

// EndedRoute is called when the pawn either reaches the last node in the route (and should disappear)
// or dies and does not reach its goal
function EndedRoute(bool bSuccess)
{
	FinishSequence();

	if (bSuccess)
	{
		Finish(true);
	}

	if (Delegate_OnEndedRoute != none)
	{
		Delegate_OnEndedRoute(bSuccess);
	}
}

// Allow the sequence action to resolve itself
// NOTE: Must be called before "Finish"
function FinishSequence()
{
	local int i;
	local Sequence GameSeq;
	local array<SequenceObject> AllPawnStartActions;

	GameSeq = WorldInfo.GetGameSequence();
	if (GameSeq != None)
	{
		// find any matinee actions that exist
		GameSeq.FindSeqObjectsByClass(class'KFSeqAct_StartScriptedPawn', true, AllPawnStartActions);
		for (i = 0; i < AllPawnStartActions.Length; i++)
		{
			KFSeqAct_StartScriptedPawn(AllPawnStartActions[i]).CheckPawnFinished(self);
		}

		AllPawnStartActions.length = 0;
		GameSeq.FindSeqObjectsByClass(class'KFSeqAct_RestartScriptedPawn', true, AllPawnStartActions);
		for (i = 0; i < AllPawnStartActions.Length; i++)
		{
			KFSeqAct_RestartScriptedPawn(AllPawnStartActions[i]).CheckPawnFinished(self);
		}
	}
}

// Stuff that happens when the pawn starts its route
function Start()
{
	SetCollision(true, true);

	if (WeldableComponent != none)
	{
		WeldableComponent.SetCollision(bActive, false);
	}

	if (WeldableTrigger != none)
	{
		WeldableTrigger.SetCollision(bActive, false);
	}

	UpdatePawnState();

	if (bStartInactive)
	{
		SetActive(false);
	}
}

simulated function PlayFinishedSounds()
{
	local int i;

	// generally used to stop the looping sounds
	for (i = 0; i < ScriptedCharArch.FinishSoundEvents.length; i++)
	{
		PlaySoundBase(ScriptedCharArch.FinishSoundEvents[i], false, WorldInfo.NetMode == NM_DedicatedServer);
	}
}

// Stuff that happens when the pawn ends its route
function Finish(bool bHide)
{
	if (ScriptedCharArch.bHideOnFinish)
	{
		if (bHide)
		{
			SetPhysics(PHYS_None);
			SetHidden(true);

			// only used when hiding the pawn, otherwise want to keep playing its normal sounds
			PlayFinishedSounds();
		}

		SetCollision(false, false);
	}
	else
	{
		// manually stop the cart at the end since physics are still enabled if the pawn doesn't get hidden
		if (bHide)
		{
			GroundSpeed = 0.0f;
			Velocity = vect(0, 0, 0);
		}
	}

	DamageOverTimeArray.length = 0; // remove damage over time effects
	//bPlayedDeath = true; // forces zeds to find a new target // Now accomplished in ZedBaseCommand with CanAITargetThisPawn
	SetCanBeTargeted(false); // prevents zeds from re-targeting

	if (WeldableComponent != none)
	{
		WeldableComponent.SetCollision(false, false);
	}

	if (WeldableTrigger != none)
	{
		WeldableTrigger.SetCollision(false, false);
	}

	SetActive(false);
}

simulated event Bump(Actor Other, PrimitiveComponent OtherComp, Vector HitNormal)
{
	local vector ToOther;
	if (KFPawn(Other) != none)
	{
		ToOther = Normal(Other.Location - Location);
		if ((ToOther Dot HitNormal) < 0)
		{
			// This is the case where the scripted pawn bumps into another pawn.
			// Move the pawn.
			KFPawn(Other).AddVelocity(HitNormal*(-ScriptedCharArch.PawnBumpImpulse), vect(0,0,0), none);
		}
		else
		{
			// This is the case where another pawn bumps into the scripted pawn.
			// Probably don't need to do anything.
		}
	}
}

function bool CanAITargetThisPawn(Controller TargetingController)
{
    return bCanCurrentlyBeTargetedByZeds;
}

function SetCanBeTargeted(bool InCanBeTargeted)
{
	local bool bCouldPreviouslyBeTargetedByZeds;
	local KFPawn_Monster KFPM;

	bCouldPreviouslyBeTargetedByZeds = bCanCurrentlyBeTargetedByZeds;
	bCanCurrentlyBeTargetedByZeds = ScriptedCharArch != none && ScriptedCharArch.States[CurrentState].bCanBeTargetedByZeds && InCanBeTargeted;
	if (bCouldPreviouslyBeTargetedByZeds && !bCanCurrentlyBeTargetedByZeds)
	{
		foreach WorldInfo.AllPawns(class'KFPawn_Monster', KFPM)
		{
			if (KFPM.GetEnemy() == self)
			{
				KFAIController(KFPM.Controller).FindNewEnemy();
			}
		}
	}
}

simulated function Texture2D GetStateIconTexture()
{
	if (CurrentState == 255)
	{
		return none;
	}

	return ScriptedCharArch.States[CurrentState].Icon;
}

function NotifyTriggerTouch(KFTrigger_NotifyOwner Sender, Actor Toucher, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal)
{
	local KFPawn_Monster Zed;
	local KFPawn_Human Player;

	if (Sender == ZedProximityTrigger)
	{
		if (ScriptedCharArch.bUseZedProximityTrigger)
		{
			Zed = KFPawn_Monster(Toucher);
			if (Zed != none)
			{
				bZedInProximity = true;

				// aggro
				if (bCanCurrentlyBeTargetedByZeds && KFAIController(Zed.Controller) != none)
				{
					KFAIController(Zed.Controller).SetEnemy(self);
				}
			}
		}

		UpdatePawnSpeed();
	}

	if (Sender == PlayerProximityTrigger)
	{
		if (ScriptedCharArch.bUsePlayerProximityTrigger)
		{
			Player = KFPawn_Human(Toucher);
			if (Player != none)
			{
				bPlayerInProximity = true;
			}
		}

		UpdatePawnSpeed();
	}
}

function NotifyTriggerUnTouch(KFTrigger_NotifyOwner Sender, Actor UnToucher)
{
	local KFPawn_Monster Zed;
	local KFPawn_Human Player;

	if (Sender == ZedProximityTrigger)
	{
		if (ScriptedCharArch.bUseZedProximityTrigger)
		{
			Zed = KFPawn_Monster(UnToucher);
			if (Zed != none)
			{
				bZedInProximity = false;
				foreach ZedProximityTrigger.TouchingActors(class'KFPawn_Monster', Zed)
				{
					if (Zed != UnToucher)
					{
						bZedInProximity = true;
						break;
					}
				}
			}
		}

		UpdatePawnSpeed();
	}

	if (Sender == PlayerProximityTrigger)
	{
		if (ScriptedCharArch.bUsePlayerProximityTrigger)
		{
			Player = KFPawn_Human(UnToucher);
			if (Player != none)
			{
				bPlayerInProximity = false;
				foreach PlayerProximityTrigger.TouchingActors(class'KFPawn_Human', Player)
				{
					if (Player != UnToucher)
					{
						bPlayerInProximity = true;
						break;
					}
				}
			}
		}

		UpdatePawnSpeed();
	}
}

simulated function SetActive(bool InActive)
{
	if (Role == ROLE_Authority)
	{
		if (bActive == InActive)
		{
			// Don't retrigger activation events if we are already active/unactive.
			return;
		}

		bActive = InActive;
		SetCanBeTargeted(bActive);
		UpdateWeldIntegrity();
		UpdatePawnSpeed();
	}

	if (WeldableComponent != none)
	{
		WeldableComponent.SetCollision(bActive, false);
	}

	if (WeldableTrigger != none)
	{
		WeldableTrigger.SetCollision(bActive, false);
	}

	if (InActive)
	{
		SetEscortPawnOnHud();
	}
	else
	{
		//hide boss healthbar
		RemoveEscortPawnOnHud();
	}

	CheckScriptedPawnMaterial();
}

simulated function bool ShouldShowOnHUD()
{
	return bActive;
}

simulated function float GetHealthPercent()
{
	return fclamp(float(Health) / float(HealthMax), 0.0f, 1.0f);
}

static function string GetLocalizedName()
{
	return default.ScriptedPawnString;
}

simulated function string GetIconPath()
{
	return IconPath;
}

simulated function StartDoorWait(KFDoorActor Door)
{
	BlockingDoor = Door;
	SpeedScalarForObstacles = 0.0f;
	UpdatePawnSpeed();
	SetTimer(0.5f, true, nameof(Timer_DoorWait), self);
}

simulated function PlayExtraVFX(Name FXLabel)
{
	local int i;
	local ExtraVFXAttachmentInfo VFXAttachment;
	local bool bActivatedExistingSystem;

	if (WorldInfo.NetMode != NM_DedicatedServer && FXLabel != `NAME_NONE)
	{
		// if this has already been spawned before, play the existing system in the parent instead
		for (i = 0; i < ExtraVFXAttachments.Length; ++i)
		{
			if (ExtraVFXAttachments[i].Info.Label == FXLabel)
			{
				bActivatedExistingSystem = true;
			}
		}

		if (!bActivatedExistingSystem)
		{
			for (i = 0; i < ScriptedStateVFX.Length; ++i)
			{
				if (ScriptedStateVFX[i].Label == FXLabel)
				{
					VFXAttachment.VFXComponent = SpawnExtraVFX(ScriptedStateVFX[i]);
					VFXAttachment.Info = ScriptedStateVFX[i];
					ExtraVFXAttachments.AddItem(VFXAttachment);
				}
			}
		}
	}

	super.PlayExtraVFX(FXLabel);
}

simulated function Timer_DoorWait()
{
	if (BlockingDoor == none || BlockingDoor.bIsDoorOpen || BlockingDoor.bIsDestroyed)
	{
		BlockingDoor = none;
		SpeedScalarForObstacles = 1.0f;
		UpdatePawnSpeed();
		ClearTimer(nameof(Timer_DoorWait), self);
	}
}

defaultproperties
{
	ControllerClass=class'KFGame.KFAIController_ScriptedPawn'

	CurrentState=255
	PreviousState=255

	AccelRate=0

	bAlwaysRelevant=true

	// Bone names (don't need 'em, having them causes warnings)
	LeftFootBoneName=""
	RightFootBoneName=""
	LeftHandBoneName=""
	RightHandBoneName=""
	HeadBoneName=""
	TorsoBoneName=""
	PelvisBoneName=""

	// ---------------------------------------------
	// Special moves
	Begin Object Name=SpecialMoveHandler_0
		SpecialMoveClasses(SM_ScriptedPawnStateChange)=class'KFGame.KFSM_PlaySingleAnim_ScriptedPawn'
	End Object

	IconPath="ZED_Patriarch_UI.ZED-VS_Icon_Boss"

	SpeedScalarForObstacles=1.0f;
	RecentDamageTimerLength=5.0f;

	Begin Object Name=CollisionCylinder
		CollisionRadius=+0108.000000
		CollisionHeight=+0086.000000
		BlockZeroExtent=false // blocked by mesh
	End Object

	bBlockPlayers=false
	bBlockZedPlayers=true
	bBlockMonsters=true

	bCanBeAdheredTo=false
	bCanBeFrictionedTo=false

	bBlockInStoppedState=false
	bIsInStoppedState=false
}
