//=============================================================================
// KFSM_Matriarch_PlasmaCannon
//=============================================================================
// Flamethrower-style attack, lasts 5 seconds, interruptable with big damage.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
// - Dan Weiss
//=============================================================================

class KFSM_Matriarch_PlasmaCannon extends KFSM_PlaySingleAnim;

/** Max distance between the Matriarch and the current victim target */
var float MaxVictimDistance;

/** Name of socket that shooting originates from.  Used for both visual attach and damage trace */
var name ShootingSocketName;

/** Size of the beam being fired */
var Vector BeamSize;

/** Amount of time between attempted damage ticks */
var float DamageTickRate;

/** Per-tick damage amount.  Done to each target in the beam */
var float DamagePerTick;

/** Damage type class */
var class<DamageType> CannonDamageType;

/** Template for the plasma beam */
var ParticleSystem BeamPSCTemplate;

/** PSC generated from the beam template.  Gets recycled in and out of the emitter pool as needed. */
var ParticleSystemComponent BeamPSC;

/** Template for beam hit location FX */
var const ParticleSystem BeamHitPSCTemplate;

/** PSC for the hit location. On when the beam is hitting anything, off when missing everything */
var ParticleSystemComponent BeamHitPSC;

var ParticleSystem MuzzleFlashPSCTemplate;
var ParticleSystemComponent MuzzleFlashPSC;

/** Amount of time that's passed since the last damage tick occurred */
var float SinceLastDamage;

/** AK Event for start of beam charge */
var AkEvent BeamStartSFX;

/** AK Event for end of special move */
var AkEvent BeamEndSFX;

/** Looping sound for beam hit */
var AkEvent BeamHitSFX;
var AkEvent BeamHitStopSFX;


/** Wind Up animation name */
var Name WindUpAnimName;

/** Wind Down animation name */
var Name WindDownAnimName;

var bool bTickDamage;

var KFPawn_ZedMatriarch MyMatPawn;

/*
	Targeting
*/

/** Time to check before firing if an enemy is still visible */
var float VisibilityCheckTime, VisibilityCheckTimer;
var float AngleCheckDot;
var bool bLogTargeting;

function SpecialMoveStarted( bool bForced, Name PrevMove )
{
    super.SpecialMoveStarted(bForced, PrevMove);

	PlayWindUpAnimation();

	MyMatPawn = KFPawn_ZedMatriarch(KFPOwner);
	MyMatPawn.PlayPlasmaCannonDialog();
}

/** Play the wind down if we have to */
function SpecialMoveFlagsUpdated()
{
	switch (KFPOwner.SpecialMoveFlags)
	{
	case 1:
        PlayFireAnim();
       	break;

	case 2:
	    PlayWindDownAnim();
	    break;
	}
}

function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
	switch (DeferredSeqName)
	{
	case WindUpAnimName:
		if (KFPOwner.Role == ROLE_Authority)
		{
			// Do the beam check on the authority and then re-do the special move with new flags,
			// which will cause the flags to be replicated and caught by SpecialMoveFlagsUpdated

			if (PrefireBeamCheck())
			{
				KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 1);
			}
			else
			{
				KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 2);
			}
		}
		break;

	case AnimName:
		PlayWindDownAnim();
		break;

	case WindDownAnimName:
		KFPOwner.EndSpecialMove();
		break;
	}
}

function bool PrefireBeamCheck()
{
	local Actor HitActor;
	local vector SocketLocation, HitLocation, HitNormal, ToEnemy, TraceEnd, GunTargetBoneLocation;
	local rotator SocketRotation;

	PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
	GunTargetBoneLocation = KFPOwner.Controller.Enemy.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
	ToEnemy = Normal(GunTargetBoneLocation - SocketLocation);
	TraceEnd = SocketLocation + ToEnemy * MaxVictimDistance;

	HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, SocketLocation, true);
	return KFPawn(HitActor) != none;
}

/** Overridden to do nothing */
function PlayAnimation();

/** Plays our wind up anim, starts the barrel spin skel controller */
function PlayWindUpAnimation()
{
	PlaySpecialMoveAnim(WindUpAnimName, EAS_FullBody, BlendInTime, BlendOutTime, 1.f);

	KFPOwner.ZeroMovementVariables();
}

/** Plays our fire animation, starts weapon fire */
function PlayFireAnim()
{
	MyMatPawn.SetGunTracking(true);

	PlaySpecialMoveAnim(AnimName, EAS_FullBody, 0.1f, 0.2f);

	ActivateFX();
	KFPOwner.ZeroMovementVariables();
	KFPOwner.SetWeaponAmbientSound(BeamStartSFX);
	bTickDamage = true;

	if (KFPOwner.Role == ROLE_Authority)
	{
		VisibilityCheckTimer = VisibilityCheckTime;
	}

	//bUseCustomRotationRate = true;
	PawnOwner.RotationRate = CustomRotationRate;
}

/** Plays our wind down animation, stops firing, disables barrel spin skel controller */
function PlayWindDownAnim()
{
	MyMatPawn.SetGunTracking(false);

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

	KFPOwner.ZeroMovementVariables();
	bTickDamage = false;
	DeactivateFX();
}

function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
    super.SpecialMoveEnded(PrevMove, NextMove);

	KFPOwner.RotationRate = KFPOwner.default.RotationRate;

	bTickDamage = false;
	DeactivateFX();
}

function ActivateFX()
{
	local ParticleSysParam SourceParam;

	if (KFPOwner.WorldInfo.NetMode != NM_DedicatedServer)
	{
		if (BeamPSCTemplate != none)
		{
			BeamPSC = PawnOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(
				BeamPSCTemplate, PawnOwner.Mesh, 'LeftHand');
			if (BeamPSC != none)
			{
				SourceParam.Name = 'SourceActor';
				SourceParam.ParamType = PSPT_Actor;
				SourceParam.Actor = PawnOwner;

				BeamPSC.InstanceParameters.AddItem(SourceParam);
			}
		}

		if (MuzzleFlashPSCTemplate != none)
		{
			MuzzleFlashPSC = PawnOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(
				MuzzleFlashPSCTemplate, PawnOwner.Mesh, 'LeftHand');
			if (MuzzleFlashPSC != none)
			{
				SourceParam.Name = 'SourceActor';
				SourceParam.ParamType = PSPT_Actor;
				SourceParam.Actor = PawnOwner;

				MuzzleFlashPSC.InstanceParameters.AddItem(SourceParam);
			}
		}
	}
}

function DeactivateFX()
{
	if (BeamPSC != none)
    {
        BeamPSC.DeactivateSystem();
        BeamPSC = none;
		KFPOwner.SetWeaponAmbientSound(BeamEndSFX);
    }

	if (BeamHitPSC != none)
    {
        BeamHitPSC.DeactivateSystem();
        BeamHitPSC = none;
		MyMatPawn.BeamHitAC.PlayEvent(BeamHitStopSFX);
    }

	if (MuzzleFlashPSC != none)
	{
		MuzzleFlashPSC.DeactivateSystem();
		MuzzleFlashPSC = none;
	}

    if (MyMatPawn.BeamHitAC != none)
    {
        MyMatPawn.BeamHitAC.StopEvents();
    }
}

function Actor GetBeamTarget(out vector HitLocation)
{
	local Actor HitActor;
	local vector SocketLocation, HitNormal, TraceEnd;
	local rotator SocketRotation;

	// force the beam vfx on the client to hit the pawn that the server is hitting
	if (PawnOwner.Role < ROLE_Authority && MyMatPawn.BeamTarget != none)
	{
		HitLocation = MyMatPawn.BeamTarget.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
		return MyMatPawn.BeamTarget;
	}

	PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
	TraceEnd = SocketLocation + vector(SocketRotation) * MaxVictimDistance;

	foreach PawnOwner.TraceActors(
		class'Actor', HitActor, HitLocation, HitNormal, TraceEnd, SocketLocation, BeamSize)
	{
		if (IsValidBeamTarget(HitActor))
		{
			break;
		}
		else
		{
			HitActor = none;
		}
	}

	return HitActor;
}

function bool IsValidBeamTarget(Actor HitActor)
{
    //If we're a pawn or we're a non-resettable static mesh actor
    return Pawn(HitActor) != none || (StaticMeshActor(HitActor) != none && !StaticMeshActor(HitActor).bResetCapable) || SkeletalMeshActor(HitActor) != none || StaticMeshCollectionActor(HitActor) != none;
}

function Tick(float DeltaTime)
{
	local Actor HitActor;
	local vector SocketLocation, HitLocation;
	local rotator SocketRotation;

    super.Tick(DeltaTime);

	if (bTickDamage)
	{
		HitActor = GetBeamTarget(HitLocation);

		if (PawnOwner.Role == ROLE_Authority)
		{
			SinceLastDamage += DeltaTime;
			while (SinceLastDamage > DamageTickRate)
			{
				if (HitActor != none)
				{
					HitActor.TakeDamage(
						DamagePerTick, PawnOwner.Controller, HitActor.Location, vect(0,0,0), CannonDamageType);

					if (KFPawn(HitActor) != none && !KFPawn(HitActor).IsAliveAndWell())
					{
						HitActor = none;
					}
				}
				MyMatPawn.BeamTarget = Pawn(HitActor);
				SinceLastDamage -= DamageTickRate;
			}

			if (bLogTargeting)
			{
				`log("(PLASMA BEAM TARGETING) "$self$"::Tick - HitActor: "$HitActor$"; VisibilityCheckTimer: "$VisibilityCheckTimer);
			}

			if (CanHitEnemy())
			{
				VisibilityCheckTimer = VisibilityCheckTime;
			}
			else
			{
				VisibilityCheckTimer -= DeltaTime;
				if (VisibilityCheckTimer <= 0)
				{
					FindNewEnemy();
					VisibilityCheckTimer = VisibilityCheckTime;
				}
			}
		}

		if (PawnOwner.WorldInfo.NetMode != NM_DedicatedServer)
		{
			if (HitActor != none)
			{
				if (BeamPSC != none)
				{
					BeamPSC.SetBeamTargetPoint(0, HitLocation, 0);
				}

				if (BeamHitPSC == none)
				{
					BeamHitPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitter(BeamHitPSCTemplate, HitLocation);
					MyMatPawn.BeamHitAC.PlayEvent(BeamHitSFX);
				}

				if (BeamHitPSC != none)
				{
					PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
					BeamHitPSC.SetAbsolute(true, true, false);
					BeamHitPSC.SetTranslation(HitLocation);
					BeamHitPSC.SetRotation(SocketRotation);
				}
			}
			else
			{
				if (BeamHitPSC != none && BeamHitPSC.bIsActive)
				{
					BeamHitPSC.DeactivateSystem();
					BeamHitPSC = none;
					MyMatPawn.BeamHitAC.PlayEvent(BeamHitStopSFX);
				}
			}
		}
	}
}

function bool CanHitEnemy()
{
	local Actor HitActor;
	local vector SocketLocation, HitLocation, HitNormal, ToEnemy, TraceEnd, GunTargetBoneLocation;
	local rotator SocketRotation;

	PawnOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, SocketLocation, SocketRotation);
	GunTargetBoneLocation = KFPOwner.Controller.Enemy.Mesh.GetBoneLocation(MyMatPawn.GunTargetBoneName);
	ToEnemy = Normal(GunTargetBoneLocation - SocketLocation);
	TraceEnd = SocketLocation + ToEnemy * MaxVictimDistance;

	HitActor = PawnOwner.Trace(HitLocation, HitNormal, TraceEnd, SocketLocation, true);
	return HitActor == KFPOwner.Controller.Enemy;
}

function FindNewEnemy()
{
	local KFPawn_Human KFPH, BestTarget;
	local vector StartTrace, EndTrace, ToTarget;
	local rotator SocketRotation;
	local float TargetDot, BestDot, MaxVictimDistanceSq;

	local vector HitLocation, HitNormal;
	local actor HitActor;

	KFPOwner.Mesh.GetSocketWorldLocationAndRotation(ShootingSocketName, StartTrace, SocketRotation);
	MaxVictimDistanceSq = MaxVictimDistance * MaxVictimDistance;

	foreach KFPOwner.WorldInfo.AllPawns(class'KFPawn_Human', KFPH)
	{
		if (bLogTargeting)
		{
			`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - Potential target: "$KFPH);
		}

		ToTarget = KFPH.Location - StartTrace;
		if (VSizeSq(ToTarget) > MaxVictimDistanceSq)
		{
			if (bLogTargeting)
			{
				`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - too far");
			}
			continue;
		}

		ToTarget = Normal(ToTarget);
		TargetDot = ToTarget dot (vector(SocketRotation));
		if (TargetDot < AngleCheckDot || TargetDot < BestDot)
		{
			if (bLogTargeting)
			{
				`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - bad angle");
			}
			continue;
		}

		EndTrace = StartTrace + (ToTarget * MaxVictimDistance);
		HitActor = KFPOwner.Trace(HitLocation, HitNormal, EndTrace, StartTrace, true,,, 1);
		if (bLogTargeting)
		{
			`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - HitActor: "$HitActor);
		}

		if (HitActor != KFPH)
		{
			if (bLogTargeting)
			{
				`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - obstructed");
			}
			continue;
		}

		if (bLogTargeting)
		{
			`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - okay!");
		}

		BestTarget = KFPH;
		BestDot = TargetDot;
	}

	if (bLogTargeting)
	{
		`log("(PLASMA BEAM TARGETING) "$self$"::FindNewEnemy - BestTarget: "$BestTarget);
	}

	if (BestTarget != none)
	{
		if (BestTarget != KFPOwner.Controller.Enemy)
		{
			KFAIController(KFPOwner.Controller).ChangeEnemy(BestTarget, false);
		}
	}
	else
	{
		KFPOwner.DoSpecialMove(SM_HoseWeaponAttack, true,, 2);
	}
}

defaultproperties
{
    //Special Move
    Handle = KFSM_Matriarch_PlasmaCannon
    bDisableSteering = false
    bDisableMovement = true
	bDisableTurnInPlace=true
    bCanBeInterrupted=false
	bShouldDeferToPostTick=true
    bUseCustomRotationRate=false // we will manually turn to true after wind-up
    CustomRotationRate=(Pitch=50000, Yaw=4500, Roll=50000)

    //Plasma Cannon
    MaxVictimDistance = 2500.f
	BeamSize = (X=15.0,Y=15.0,Z=15.0)
	DamageTickRate = 0.1f
    ShootingSocketName = Hand_FX_End_L
    DamagePerTick = 7.0f
    CannonDamageType = class'KFDT_EMP_MatriarchPlasmaCannon'
    BeamPSCTemplate = ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Beam_01'
	BeamHitPSCTemplate=ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Impact_01'
	MuzzleFlashPSCTemplate=ParticleSystem'ZED_Matriarch_EMIT.FX_Plasma_Cannon_Muzzleflash_01'
	VisibilityCheckTime=0.8f
	AngleCheckDot=0.500f // 60 degrees in front, type cos(X degrees) into google, replacing "X" with a number 0-90, lower is narrower FOV
	bLogTargeting=false

	//Sounds
    BeamStartSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_Plasma_Cannon_Loop_01'
    BeamEndSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_Plasma_Cannon_End_01'
	BeamHitSFX=AkEvent'WW_ZED_Matriarch.Play_Matriarch_SFX_Attack_PulseCannon_Beam_Hit_LP'
	BeamHitStopSFX=AkEvent'WW_ZED_Matriarch.Stop_Matriarch_SFX_Attack_PulseCannon_Beam_Hit_LP'

    //Animation
	WindUpAnimName=Plasma_Cannon_TO_Load
    AnimName=Plasma_Cannon_Idle
	WindDownAnimName=Plasma_Cannon_TO_Idle
    AnimStance=EAS_UpperBody
}