//=============================================================================
// KFWeap_MeleeBase
//=============================================================================
// Base class used for weapons where melee is the primary firemode
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFWeap_MeleeBase extends KFWeapon
	abstract
	native;

/************************************************************************************
 * @name Firing / Timing / States
 ***********************************************************************************/

/** These override the base firemodes of the same ID (for readability) */
const BLOCK_FIREMODE		= 1; // ALTFIRE_FIREMODE
const HEAVY_ATK_FIREMODE	= 5; // NEW - IronSights Key

/** Set when blood material value is high */
var bool bIsBloody;

/** Maximum number of attacks that can be played without releasing fire */
var byte MaxChainAtkCount;

/** Minimum amount of time to go to the MeleeSustained state for */
var float MinMeleeSustainedTime;
/** Minimum amount of time to wait before dealing damage in the  MeleeSustained state */
var() float MeleeSustainedWarmupTime;
/** Amount of time required between cancelling attacks with reload*/
var private const float ReloadCancelTimeLimit;

/** Whether this can be interrupted by another attack/reload/etc. */
var bool StartFireDisabled;

/** Special flag in order to make weapons like the frost shotgun axe (frostfang) have the access to perk skills */
var bool bHasToBeConsideredAsRangedWeaponForPerks;

/*********************************************************************************************
 * @name Defensive Abilities
 *********************************************************************************************/

struct native BlockEffectInfo
{
	var class<DamageType> DmgType;

	/** If != None, overrides the class default FX */
	var AkEvent 		BlockSound;
	var AkEvent			ParrySound;
	var ParticleSystem	BlockParticleSys;
	var ParticleSystem  ParryParticleSys;
};

var array<BlockEffectInfo> BlockTypes;

/** Damage while blocking will be mitigated by this percentage */
var() float BlockDamageMitigation;

/** Parry damage will be mitigated by this percentage */
var() float ParryDamageMitigationPercent;
/** Hit reaction strength to bypass pawn's ParryStumbleResist */
var() byte ParryStrength;

/** If true, owning pawn moves at a slower (iron sight) walking speed */
var bool bMoveAtWalkingSpeed;

/** Time between block hit reaction anims */
var() protected float BlockHitAnimCooldownTime;

/** The last time we played a block hit reaction anim */
var transient protected float LastBlockHitAnimTime;

/*********************************************************************************************
 * @name Animation
 *********************************************************************************************/

/** 4-way directional melee attack animation names */
const MeleeAttackAnim_F  = 'Atk_F';
const MeleeAttackAnim_B	 = 'Atk_B';
const MeleeAttackAnim_L  = 'Atk_L';
const MeleeAttackAnim_R  = 'Atk_R';

/** 4-way directional heavy attack animation names */
const MeleeHeavyAttackAnim_F = 'Atk_H_F';
const MeleeHeavyAttackAnim_B = 'Atk_H_B';
const MeleeHeavyAttackAnim_L = 'Atk_H_L';
const MeleeHeavyAttackAnim_R = 'Atk_H_R';

/** 8-way directional combo chain animation names */
const MeleeComboChainAnim_F	 = 'Combo_F';
const MeleeComboChainAnim_FL = 'Combo_FL';
const MeleeComboChainAnim_FR = 'Combo_FR';
const MeleeComboChainAnim_B  = 'Combo_B';
const MeleeComboChainAnim_BL = 'Combo_BL';
const MeleeComboChainAnim_BR = 'Combo_BR';
const MeleeComboChainAnim_L  = 'Combo_L';
const MeleeComboChainAnim_R  = 'Combo_R';

/** Special ability attacks */
const MeleeDrawStrikeAnim = 'Atk_Draw';

/** Defensive stance animation names */
const MeleeBlockStartAnim = 'Brace_in';
const MeleeBlockLoopAnim  = 'Brace_loop';
const MeleeBlockEndAnim   = 'Brace_out';

/** Weapon wear/cleaning */
const CleanBloodyAnim     = 'Clean_Blood';
const CleanNonBloodyAnim  = 'Clean_NoBlood';

/** MeleeSustained default anims - To modify this in a subclass override GetLoopingFireAnim, etc... */
const MeleeSustainedLoopAnim	= 'Atk_F_Loop';
const MeleeSustainedStartAnim	= 'Atk_F_In';
const MeleeSUstainedEndAnim		= 'Atk_F_Out';

/** Settle animations played after an attack */
var array<name> MeleeAttackSettleAnims;

/** Animations played on successful block */
var array<name> MeleeBlockHitAnims;

/*********************************************************************************************
 * @name	Effects
 ********************************************************************************************* */

/** (deprecated) particle system templates for trail notifies */
var ParticleSystem DistortTrailParticle;
var ParticleSystem WhiteTrailParticle;
var ParticleSystem BlueTrailParticle;
var ParticleSystem RedTrailParticle;

/** Block / Parry */
var AkBaseSoundObject BlockSound;
var AKBaseSoundObject ParrySound;
var ParticleSystem	  BlockParticleSystem;
var ParticleSystem	  ParryParticleSystem;
var name			  BlockEffectsSocketName;

/*********************************************************************************************
 * @name	Trader
 ********************************************************************************************* */

/** Estimated attack rate for this weapon, taking chaining, etc. into account.
  * Basically, just eyeball the animations and take an average.
 */
var() byte EstimatedFireRate;

cpptext
{
	// custom trail overrides
	virtual class UParticleSystem* GetAnimTrailParticleSystem(const class UAnimNotify_Trails* AnimNotifyData) const;
}

/*********************************************************************************************
 * @name		Instigator Movement Speed
********************************************************************************************* */

/** If TRUE, Instigator/Owner moves at walking speed when this weapon is equipped */
simulated function bool ShouldOwnerWalk()
{
	return bMoveAtWalkingSpeed;
}

/** Used to toggle bMoveAtWalkingSpeed */
simulated function SetSlowMovement(bool bEnabled)
{
	if ( Instigator.IsLocallyControlled() )
	{
		bMoveAtWalkingSpeed = bEnabled;

		if ( Role < ROLE_Authority )
		{
			ServerSetSlowMovement(bEnabled);
		}
	}
}

/** Called by local player to synchronize movement the same way ServerZoomIn/ServerZoomOut does */
reliable server function ServerSetSlowMovement(bool bEnabled)
{
	bMoveAtWalkingSpeed = bEnabled;
}

/*********************************************************************************************
 * @name		Firing / Projectile
********************************************************************************************* */

/**
 * @see Weapon::StartFire
 */
simulated function StartFire(byte FireModeNum)
{
	// can't start fire because it's in an uninterruptible state
	if (StartFireDisabled)
	{
		return;
	}

	// if the weapon is currently attacking
	if (CurrentFireMode == DEFAULT_FIREMODE || CurrentFireMode == ALTFIRE_FIREMODE || CurrentFireMode == BASH_FIREMODE)
	{
		// and the player tries to cancel with a reload action
		if(FireModeNum == RELOAD_FIREMODE)
		{
			// stop them from reload cancelling if it has already happened too recently
			if (IsTimerActive(nameof(Timer_FireCancel)))
			{
				return;
			}
			else
			{
				SetTimer(ReloadCancelTimeLimit, false, nameof(Timer_FireCancel));
			}
		}
	}
	// Get attack type and send to server
	if( FireModeNum == DEFAULT_FIREMODE || FireModeNum == HEAVY_ATK_FIREMODE )
	{
		StartMeleeFire(FireModeNum, MeleeAttackHelper.ChooseAttackDir(), ATK_Normal);
		return;
	}

	// If we're going to pretend bash is primary (see SendToFiringState) need to init directional melee
	if( FireModeNum == BASH_FIREMODE && WeaponFireTypes[FireModeNum] == EWFT_None )
	{
		StartMeleeFire(FireModeNum, MeleeAttackHelper.ChooseAttackDir(), ATK_Normal);
		return;
	}

	Super.StartFire(FireModeNum);
}

simulated function Timer_FireCancel() {}

/**
 * Like StartFire, but replicates a attack type
 * Network: LocalPlayer
 */
simulated function StartMeleeFire(byte FireModeNum, EPawnOctant AttackDir, EMeleeAttackType AtkType)
{
	MeleeAttackHelper.InitAttackSequence(AttackDir, AtkType);

	// derived from StartFire()
	if( Instigator == None || !Instigator.bNoWeaponFiring )
	{
		if( Role < Role_Authority )
		{
			// if we're a client, synchronize server
			ServerStartMeleeFire(FireModeNum, AttackDir, AtkType);
		}

		// Start fire locally
		BeginFire(FireModeNum);
	}
}

/**
 * Sets the desired attack type for an incoming melee fire mode
 * Network: Dedicated Server only, or Listen Server for remote clients.
 */
reliable server private function ServerStartMeleeFire(byte FireModeNum, EPawnOctant AttackDir, EMeleeAttackType AtkType)
{
	MeleeAttackHelper.InitAttackSequence(AttackDir, AtkType);
	Super.ServerStartFire(FireModeNum);
}

/** Instead of switch fire mode use as immediate alt fire */
simulated function AltFireMode()
{
	if ( !Instigator.IsLocallyControlled() )
	{
		return;
	}

	// StartFire - StopFire called from KFPlayerInput
	StartFire(BLOCK_FIREMODE);
}

/** Route ironsight player input to heavy attack */
simulated function SetIronSights(bool bNewIronSights)
{
	if ( !Instigator.IsLocallyControlled()  )
	{
		return;
	}

	if ( bNewIronSights )
	{
		StartFire(HEAVY_ATK_FIREMODE);
	}
	else
	{
		StopFire(HEAVY_ATK_FIREMODE);
	}
}

/**
 * Send weapon to proper firing state
 */
simulated function SendToFiringState(byte FireModeNum)
{
	// If we don't have anything assigned to BASH_FIREMODE (V) route to primary attack (LMB)
	if( FireModeNum == BASH_FIREMODE && WeaponFireTypes[FireModeNum] == EWFT_None )
	{
		Super.SendToFiringState(DEFAULT_FIREMODE);
		ClearPendingFire(BASH_FIREMODE);
	}

	Super.SendToFiringState(FireModeNum);
}

/*********************************************************************************************
 * @name		Damage
********************************************************************************************* */

/** process local player impact for clientside hit detection */
event RecieveClientImpact(byte FiringMode, const out ImpactInfo Impact, optional out float PenetrationValue, optional int ImpactNum)
{
	MeleeAttackHelper.ProcessMeleeHit(FiringMode, Impact);
}

/** returns the damage amount for this attack */
simulated function int GetMeleeDamage(byte FireModeNum, optional vector RayDir)
{
	local int Damage;
	local KFPerk InstigatorPerk;

	Damage = GetModifiedDamage(FireModeNum, RayDir);
	// decode damage scale (see GetDamageScaleByAngle) from the RayDir
	if ( !IsZero(RayDir) )
	{
		Damage = Round(float(Damage) * FMin(VSize(RayDir), 1.f));
	}

	InstigatorPerk = GetPerk();
	if( InstigatorPerk != none )
	{
		if( IsHeavyAttack( FireModeNum ) )
		{
			InstigatorPerk.ModifyHardAttackDamage( Damage );
		}
		else if( IsLightAttack(FireModeNum) )
		{
			InstigatorPerk.ModifyLightAttackDamage( Damage );
		}
	}

	return Damage;
}

/*********************************************************************************************
 * @name		Blood/Wear Effects
 *********************************************************************************************/

/** Increment bloody material parameter --- Network: Local Player */
simulated function AddBlood(float MinAmount, float MaxAmount)
{
	Super.AddBlood(MinAmount, MaxAmount);

	if ( !bIsBloody )
	{
		bIsBloody = true;
		ServerSetBloody(true);
	}
}

/** Syncronize this with the server for reloading state */
reliable server private function ServerSetBloody(bool bNewIsBloody)
{
	bIsBloody = bNewIsBloody;
}

/** Remove blood material parameter from this weapon */
simulated function ANIMNOTIFY_CleanBlood()
{
	local int i;

	bIsBloody = false;

	if ( WorldInfo.NetMode != NM_DedicatedServer && WeaponMICs.Length > 0 )
	{
		BloodParamValue = 0.f;

		for( i = 0; i < WeaponMICs.Length; ++i )
		{
			if( WeaponMICs[i] != none )
			{
				WeaponMICs[i].SetScalarParameterValue( BloodParamName, BloodParamValue );
			}
		}
	}
}

/*********************************************************************************************
 * @name		Animation
********************************************************************************************* */

/** Overriden to use WeaponAnimNodeSeq */
simulated function PlayWeaponAnimation(name Sequence, float fDesiredDuration, optional bool bLoop, optional SkeletalMeshComponent SkelMesh)
{
	Super.PlayWeaponAnimation(Sequence, fDesiredDuration, bLoop, SkelMesh);

	// Reset CameraAnim following
	bFollowAnimSeqCamera = default.bFollowAnimSeqCamera;
}

/** Called from the MeleeHelper class to allow for the weapon to override settings */
simulated function PlayMeleeAnimation(name AnimName, out float out_Rate, float BlendTime)
{
	Super.PlayMeleeAnimation(AnimName, out_Rate, BlendTime);

	// Enable CameraAnim following
	bFollowAnimSeqCamera = true;

	`DialogManager.PlayMeleeAttackDialog( KFPawn(Instigator), IsHeavyAttack(CurrentFireMode) );
}

/*********************************************************************************************
 * State WeaponUpkeep
 * A firemode that replaces reload where the user cleans or sharpens the weapon
 *********************************************************************************************/

simulated function UpkeepComplete();

/** Returns true if weapon can potentially be reloaded */
simulated function bool CanReload(optional byte FireModeNum)
{
	if ( FiringStatesArray[RELOAD_FIREMODE] == 'WeaponUpkeep' )
	{
		return true;
	}

	return Super.CanReload(FireModeNum);
}

simulated state WeaponUpkeep
{
	simulated function byte GetWeaponStateId()
	{
		return WEP_Cleaning;
	}

	simulated function BeginState(name PreviousStateName)
	{
		local name AnimName;
		local float Duration;

		// Calc Reload Duration
		AnimName = (bIsBloody) ? CleanBloodyAnim : CleanNonBloodyAnim;
		Duration = MySkelMesh.GetAnimInterruptTime(AnimName);

		if ( Duration > 0.f )
		{
			if ( Instigator.IsFirstPerson() )
			{
				PlayAnimation(AnimName);
			}

			SetTimer(Duration, FALSE, nameof(UpkeepComplete));
		}
		else
		{
			`warn("Duration is zero!!!"@AnimName);
			SetTimer(0.001, FALSE, nameof(UpkeepComplete));
		}

		NotifyBeginState();
	}

	// Allow for players to skip cleaning the weapon and go right into attacking.
	simulated function BeginFire(byte FireModeNum)
	{
		Global.BeginFire(FireModeNum);

		// handle reload interrupts
		if ( FireModeNum != RELOAD_FIREMODE )
		{
			// if able, immediately interupt/abort the reload state
			if( PendingFire(FireModeNum) && HasAmmo(FireModeNum) )
			{
                ClearPendingFire(RELOAD_FIREMODE);
				GotoState('Active');
			}
		}
	}

	simulated event EndState(Name NextStateName)
	{
		NotifyEndState();
	}

	/** Cleanup state */
	simulated function UpkeepComplete()
	{
		// we're done, leave state and go back to active
		GotoState('Active');
	}
}

simulated function bool CanOverrideMagReload(byte FireModeNum)
{
	return Super.CanOverrideMagReload(FireModeNum) || FireModeNum == GRENADE_FIREMODE;
}

/*********************************************************************************************
 * State MeleeChainAttacking
 * A melee firemode that chains together a sequence of attacks
 *********************************************************************************************/

simulated function bool IsLightAttack( byte FireMode );

simulated state MeleeChainAttacking extends MeleeAttackBasic
{
	simulated function BeginState(Name PrevStateName)
	{
		super.BeginState( PrevStateName );
	}

	simulated function byte GetWeaponStateId()
	{
		switch (MeleeAttackHelper.CurrentAttackDir)
		{
		case DIR_Forward:		return WEP_Melee_F;
		case DIR_ForwardLeft:	return WEP_Melee_F;
		case DIR_ForwardRight:	return WEP_Melee_F;
		case DIR_Backward:		return WEP_Melee_B;
		case DIR_BackwardLeft:	return WEP_Melee_B;
		case DIR_BackwardRight: return WEP_Melee_B;
		case DIR_Left:			return WEP_Melee_L;
		case DIR_Right:			return WEP_Melee_R;
		}

		return WEP_MeleeChain;
	}

	/** Called when primary fire is let go, or when ShouldContinueMelee returns false */
	simulated function EndState(Name NextStateName)
	{
		Super.EndState(NextStateName);

		PlayMeleeSettleAnim();

		// If both melee attacks are pressed, give priority to heavy attack.  Need
		// to do this because BeginFire is called in numeric order.
		if ( PendingFire(DEFAULT_FIREMODE) && PendingFire(HEAVY_ATK_FIREMODE) )
		{
			ClearPendingFire(DEFAULT_FIREMODE);
		}
	}

	/** Checks to see if we can perform another melee strike without changing states */
	simulated function bool ShouldContinueMelee(optional int ChainCount)
	{
		// If next attack is of a different fire mode we want leave the attacking state so
		// that the FiringState and FiringMode are properly updated for the new attack.
		if ( PendingFire(HEAVY_ATK_FIREMODE) )
		{
			return false;
		}

		// normal weapon path (pending fire, etc...)
		if ( !ShouldRefire() )
		{
			return false;
		}

		// make sure our firemode handler supports chain attacks
		if ( !MeleeAttackHelper.bHasChainAttacks )
		{
			return false;
		}

		return (ChainCount + 1) < MaxChainAtkCount;
	}

	/** Get name of the animation to play for PlayFireEffects */
	simulated function name GetMeleeAnimName(EPawnOctant AtkDir, EMeleeAttackType AtkType)
	{
		// Update our animation rate before changing weapon state to stay synced
		UpdateWeaponAttachmentAnimRate( GetThirdPersonAnimRate() );

		// update state id to match new attack direction
		KFPawn(Instigator).WeaponStateChanged(GetWeaponStateId());

		// primary / normal strikes and chain attacks
		if ( AtkType == ATK_Combo )
		{
			switch (AtkDir)
			{
			case DIR_Forward:		return MeleeComboChainAnim_F;
			case DIR_ForwardLeft:	return MeleeComboChainAnim_FL;
			case DIR_ForwardRight:	return MeleeComboChainAnim_FR;
			case DIR_Backward:		return FRand() < 0.5f ? MeleeComboChainAnim_BL : MeleeComboChainAnim_BR;
			case DIR_BackwardLeft:	return MeleeComboChainAnim_BL;
			case DIR_BackwardRight: return MeleeComboChainAnim_BR;
			case DIR_Left:			return MeleeComboChainAnim_L;
			case DIR_Right:			return MeleeComboChainAnim_R;
			}
		}

		switch (AtkDir)
		{
		case DIR_Forward:		return MeleeAttackAnim_F;
		case DIR_ForwardLeft:	return MeleeAttackAnim_F;
		case DIR_ForwardRight:	return MeleeAttackAnim_F;
		case DIR_Backward:		return MeleeAttackAnim_B;
		case DIR_BackwardLeft:	return MeleeAttackAnim_B;
		case DIR_BackwardRight: return MeleeAttackAnim_B;
		case DIR_Left:			return MeleeAttackAnim_L;
		case DIR_Right:			return MeleeAttackAnim_R;
		}
	}

	simulated function bool IsLightAttack( byte FireMode )
	{
		return true;
	}

	/** Gets the current animation rate, scaled or not */
	simulated function float GetThirdPersonAnimRate()
	{
		local float ScaledRate;

		ScaledRate = EvalInterpCurveFloat( MeleeAttackHelper.FatigueCurve, MeleeAttackHelper.NumChainedAttacks );
		ModifyMeleeAttackSpeed(ScaledRate, CurrentFireMode);

		return 1.f / ScaledRate;
	}
}

/** Plays a 'settle' animation after a melee attack is finished */
simulated function PlayMeleeSettleAnim()
{
	local int AnimIdx;

	if( MeleeAttackSettleAnims.Length > 0 )
	{
		AnimIdx = Rand(MeleeAttackSettleAnims.Length);
		PlayAnimation(MeleeAttackSettleAnims[AnimIdx], 0.0, false, 0.1);
	}
}

/*********************************************************************************************
 * State MeleeHeavyAttacking
 * This is the alt-fire Melee State.
 *********************************************************************************************/

simulated function bool IsHeavyAttack(byte FireMode);

simulated state MeleeHeavyAttacking extends MeleeAttackBasic
{
	simulated function byte GetWeaponStateId()
	{
		switch (MeleeAttackHelper.CurrentAttackDir)
		{
		case DIR_Forward:		return WEP_MeleeHeavy_F;
		case DIR_ForwardLeft:	return WEP_MeleeHeavy_F;
		case DIR_ForwardRight:	return WEP_MeleeHeavy_F;
		case DIR_Backward:		return WEP_MeleeHeavy_B;
		case DIR_BackwardLeft:	return WEP_MeleeHeavy_B;
		case DIR_BackwardRight: return WEP_MeleeHeavy_B;
		case DIR_Left:			return WEP_MeleeHeavy_L;
		case DIR_Right:			return WEP_MeleeHeavy_R;
		}

		return WEP_Idle;
	}

	simulated function BeginState(name PreviousStateName)
    {
   		Super.BeginState(PreviousStateName);

		// freeze the player for a short time for heavy damage strikes
		if ( Instigator.IsLocallyControlled() )
		{
			KFPlayerController(Instigator.Controller).PauseMoveInput(0.1f);
		}
	}

	simulated function EndState(Name NextStateName)
	{
		Super.EndState(NextStateName);
		PlayMeleeSettleAnim();
	}

	/** heavy damage attack anims */
	simulated function name GetMeleeAnimName(EPawnOctant AtkDir, EMeleeAttackType AtkType)
	{
		// heavy damage attacks
		if ( AtkType == ATK_DrawStrike )
		{
			return MeleeDrawStrikeAnim;
		}

		switch (AtkDir)
		{
		case DIR_Forward:		return MeleeHeavyAttackAnim_F;
		case DIR_ForwardLeft:	return MeleeHeavyAttackAnim_F;
		case DIR_ForwardRight:	return MeleeHeavyAttackAnim_F;
		case DIR_Backward:		return MeleeHeavyAttackAnim_B;
		case DIR_BackwardLeft:	return MeleeHeavyAttackAnim_B;
		case DIR_BackwardRight: return MeleeHeavyAttackAnim_B;
		case DIR_Left:			return MeleeHeavyAttackAnim_L;
		case DIR_Right:			return MeleeHeavyAttackAnim_R;
		}
	}

	simulated function bool IsHeavyAttack(byte FireMode)
	{
		return true;
	}
}

/*********************************************************************************************
 * State MeleeSustained
  * A special melee state used for chainsaw like weapons.  Extends WeaponFiring which is a
  * bit odd, but it shares more in common with it than it does with MeleeAttackBasic
 *********************************************************************************************/

// Global declarations for this state
function SustainedMinFireTimer();
function SustainedWarmupEndTimer();

simulated state MeleeSustained extends WeaponFiring
{
	ignores AllowSprinting, AllowIronSights;

	simulated function bool IsMeleeing()
	{
		return TRUE;
	}

	simulated function BeginState(Name PreviousStateName)
	{
		// @note: No super because we don't want to FireAmmunition() right away
		local KFPerk InstigatorPerk;

		InstigatorPerk = GetPerk();
		if( InstigatorPerk != none )
		{
			SetZedTimeResist( InstigatorPerk.GetZedTimeModifier(self) );
		}

		if ( MeleeSustainedWarmupTime > 0.f )
		{
			SetTimer(MeleeSustainedWarmupTime, false, nameof(SustainedWarmupEndTimer));
		}
		else
		{
			SustainedWarmupEndTimer();
		}

		StartLoopingFireEffects(CurrentFireMode, true);
		NotifyBeginState();
	}

	simulated function EndState(Name NextStateName)
	{
		Super.EndState(NextStateName);
		NotifyEndState();
	}

	/** Handle MinMeleeSustainedTime */
	simulated function bool StillFiring(byte FireMode)
	{
		if ( Global.StillFiring(FireMode) || IsTimerActive(nameof(SustainedMinFireTimer)) )
		{
			return true;
		}
		return false;
	}

	/** Called after warmup when weapon is ready to fire */
	simulated function SustainedWarmupEndTimer()
	{
		// Do the first damage right away, we've already waited for the warmup time
		FireAmmunition();
		TimeWeaponFiring(CurrentFireMode);

		if ( MinMeleeSustainedTime > 0.f )
		{
			SetTimer(MinMeleeSustainedTime, false, nameof(SustainedMinFireTimer));
		}
	}

	/** Performs melee hit detection and does damage */
    simulated function FireAmmunition()
	{
		HandleWeaponShotTaken( CurrentFireMode );
        MeleeAttackHelper.bHitEnemyThisAttack = false;
        MeleeAttackHelper.MeleeAttackImpact();

    	// Use ammunition to fire
    	ConsumeAmmo( CurrentFireMode );
	}

	/** Get name of the animation to play for PlayFireEffects */
	simulated function name GetLoopingFireAnim(byte FireModeNum)
	{
		return MeleeSustainedLoopAnim;
	}

	/** Get name of the animation to play for PlayFireEffects */
	simulated function name GetLoopStartFireAnim(byte FireModeNum)
	{
		return MeleeSustainedStartAnim;
	}

	/** Get name of the animation to play for PlayFireEffects */
	simulated function name GetLoopEndFireAnim(byte FireModeNum)
	{
		return MeleeSustainedEndAnim;
	}

	simulated function byte GetWeaponStateId()
	{
		return WEP_MeleeSustained;
	}
}

/*********************************************************************************************
 * State MeleeBlocking
  * This is the default Blocking State.  It's performed on both the client and the server.
 *********************************************************************************************/

// Global declarations for blocking state
simulated function BlockLoopTimer();
simulated function ParryCheckTimer();

/** Called on the server when successfully block/parry an attack */
unreliable client function ClientPlayBlockEffects(optional byte BlockTypeIndex=255)
{
	local AkBaseSoundObject Sound;
	local ParticleSystem PSTemplate;

	GetBlockEffects(BlockTypeIndex, Sound, PSTemplate);
	PlayLocalBlockEffects(Sound, PSTemplate);
}

/** Called on the server when successfully block/parry an attack */
reliable client function ClientPlayParryEffects(optional byte BlockTypeIndex=255)
{
	local AkBaseSoundObject Sound;
	local ParticleSystem PSTemplate;
	local KFPerk InstigatorPerk;

	InstigatorPerk = GetPerk();
	if( InstigatorPerk != none )
	{
		InstigatorPerk.SetSuccessfullParry();
	}

	GetParryEffects(BlockTypeIndex, Sound, PSTemplate);
	PlayLocalBlockEffects(Sound, PSTemplate);
}

simulated function NotifyAttackParried();
simulated function NotifyAttackBlocked();

simulated state MeleeBlocking
{
	ignores ForceReload, ShouldAutoReload;

	simulated function byte GetWeaponStateId()
	{
		return WEP_MeleeBlock;
	}

	simulated function BeginState(name PreviousStateName)
    {
    	local float ParryDuration;

   		ParryDuration = PlayBlockStart();

		// Set the duration of the window to parry incoming attacks
		if ( ParryDuration > 0.f )
		{
			SetTimer( ParryDuration, false, nameof(ParryCheckTimer) );
		}

		NotifyBeginState();
	}

	simulated function EndState(Name NextStateName)
	{
		if ( Instigator.IsLocallyControlled() )
		{
			PlayAnimation(MeleeBlockEndAnim);
		}

		//SetSlowMovement(false);
		NotifyEndState();
	}

	/** Return to active state if we're done blocking */
	simulated function EndFire(byte FireModeNum)
	{
		Global.EndFire(FireModeNum);

		// Wait until parry is finished, then check PendingFire to stop blocking
		if ( !StillFiring(CurrentFireMode) && !IsTimerActive(nameof(ParryCheckTimer)) )
		{
			GotoState('BlockingCooldown');
		}
	}

	/** After the parry window is finished, check PendingFire to see if we're still blocking */
	simulated function ParryCheckTimer()
	{
		// Check PendingFire to stop blocking
		if ( !StillFiring(CurrentFireMode) )
		{
			GotoState('BlockingCooldown');
		}
	}

	/** Grab/Grapple attacks can be parried */
	function bool IsGrappleBlocked(Pawn InstigatedBy)
	{
		local float FacingDot;
		local vector Dir2d;

		// zero Z to give us a 2d dot product
		Dir2d = Normal2d(InstigatedBy.Location - Instigator.Location);
		FacingDot = vector(Instigator.Rotation) dot (Dir2d);

		// Cos(85)
		if ( FacingDot > 0.087f )
		{
			if ( IsTimerActive(nameof(ParryCheckTimer)) )
			{
				KFPawn(InstigatedBy).NotifyAttackParried(Instigator, 255);
				ClientPlayParryEffects();
				NotifyAttackParried();
			}
			else
			{
				ClientPlayBlockEffects();
				NotifyAttackBlocked();
			}

			return TRUE;
		}
		return FALSE;
	}

	/** While holding a melee weapon reduce some incoming damage */
	function AdjustDamage(out int InDamage, class<DamageType> DamageType, Actor DamageCauser)
	{
		local float FacingDot;
		local vector Dir2d;
		local KFPerk InstigatorPerk;
		local byte BlockTypeIndex;

		// don't apply block/parry effects for teammates
		if (Instigator.IsSameTeam(DamageCauser.Instigator))
		{
			return;
		}

		// zero Z to give us a 2d dot product
		Dir2d = Normal2d(DamageCauser.Location - Instigator.Location);
		FacingDot = vector(Instigator.Rotation) dot (Dir2d);

		// Cos(85)
		if ( FacingDot > 0.087f && CanBlockDamageType(DamageType, BlockTypeIndex) )
		{
			InstigatorPerk = GetPerk();

			if ( IsTimerActive(nameof(ParryCheckTimer)) )
			{
				InDamage *= GetUpgradedParryDamageMitigation(CurrentWeaponUpgradeIndex);
				// Notify attacking pawn for effects / animations
				if ( KFPawn(DamageCauser) != None )
				{
					KFPawn(DamageCauser).NotifyAttackParried(Instigator, ParryStrength);
				}

				// @NOTE: This is now always true per discussion with AndrewL on KFII-29686. Since we always
				// do the damage mitigation, we should always play the effect regardless of whether the
				// zed was stumbled or knocked down. -MattF
				ClientPlayParryEffects(BlockTypeIndex);

				NotifyAttackParried();

				if( InstigatorPerk != none )
				{
					InstigatorPerk.SetSuccessfullParry();
				}
			}
			else
			{
				InDamage *= GetUpgradedBlockDamageMitigation(CurrentWeaponUpgradeIndex);
				ClientPlayBlockEffects(BlockTypeIndex);

				NotifyAttackBlocked();

				if( InstigatorPerk != none )
				{
					InstigatorPerk.SetSuccessfullBlock();
				}
			}
		}
	}

	simulated function BlockLoopTimer()
	{
		if( Instigator.IsLocallyControlled() )
		{
			PlayAnimation(MeleeBlockLoopAnim, , true);
		}
	}

	/** State override for Block_Hit animations */
	unreliable client function ClientPlayBlockEffects(optional byte BlockTypeIndex=255)
	{
		local int AnimIdx;
		local float Duration;
		local KFPerk InstigatorPerk;

		Global.ClientPlayBlockEffects(BlockTypeIndex);

		InstigatorPerk = GetPerk();
		if( InstigatorPerk != none )
		{
			InstigatorPerk.SetSuccessfullBlock();
		}

		if( MeleeBlockHitAnims.Length > 0 && `TimeSince(LastBlockHitAnimTime) > BlockHitAnimCooldownTime && !IsTimerActive(nameof(ParryCheckTimer)) )
		{
			AnimIdx = Rand(MeleeBlockHitAnims.Length);
			Duration = MySkelMesh.GetAnimLength(MeleeBlockHitAnims[AnimIdx]);

			if ( Duration > 0 )
			{
				LastBlockHitAnimTime = WorldInfo.TimeSeconds;
				PlayAnimation(MeleeBlockHitAnims[AnimIdx]);
				SetTimer(Duration, false, nameof(BlockLoopTimer));
			}
		}
	}
}

simulated function float PlayBlockStart()
{
	local float AnimDuration;

	if( Instigator.IsLocallyControlled() )
	{
		PlayAnimation(MeleeBlockStartAnim);
	}

	// set when to start playing the looping anim
	AnimDuration = MySkelMesh.GetAnimLength(MeleeBlockStartAnim);
	if ( AnimDuration > 0.f )
	{
		SetTimer(AnimDuration, false, nameof(BlockLoopTimer));
	}
	else
	{
		BlockLoopTimer();
	}

	// set the parry duration to the same as the block start anim
	return AnimDuration;
}

/** Called on the client when successfully block/parry an attack */
simulated function PlayLocalBlockEffects(AKBaseSoundObject Sound, ParticleSystem PSTemplate)
{
	local vector Loc;
	local rotator Rot;
	local ParticleSystemComponent PSC;

	if ( Sound != None )
	{
		PlaySoundBase(Sound, true);
	}

	if ( PSTemplate != None )
	{
		if ( MySkelMesh.GetSocketWorldLocationAndRotation(BlockEffectsSocketName, Loc, Rot) )
		{
			PSC = WorldInfo.MyEmitterPool.SpawnEmitter(PSTemplate, Loc,  Rot);
			PSC.SetDepthPriorityGroup(SDPG_Foreground);
		}
		else
		{
			`log(self@GetFuncName()@"missing BlockEffects Socket!");
		}
	}
}

/** If true, this damage type can be blocked by the MeleeBlocking state */
function bool CanBlockDamageType(class<DamageType> DamageType, optional out byte out_Idx)
{
	local int Idx;

	// Check if this damage should be ignored completely
	for (Idx = 0; Idx < BlockTypes.length; ++Idx)
	{
		if ( ClassIsChildOf(DamageType, BlockTypes[Idx].DmgType) )
		{
			out_Idx = Idx;
			return true;
		}
	}

	out_Idx = INDEX_NONE;
	return false;
}

/** Returns sound and particle system overrides using index into BlockTypes array */
simulated function GetBlockEffects(byte BlockIndex, out AKBaseSoundObject outSound, out ParticleSystem outParticleSys)
{
	outSound = BlockSound;
	outParticleSys = BlockParticleSystem;

	if ( BlockIndex != 255 )
	{
		if ( BlockTypes[BlockIndex].BlockSound != None )
		{
			outSound = BlockTypes[BlockIndex].BlockSound;
		}
		if ( BlockTypes[BlockIndex].BlockParticleSys != None )
		{
			outParticleSys = BlockTypes[BlockIndex].BlockParticleSys;
		}
	}
}

/** Returns sound and particle system overrides using index into BlockTypes array */
simulated function GetParryEffects(byte BlockIndex, out AKBaseSoundObject outSound, out ParticleSystem outParticleSys)
{
	outSound = ParrySound;
	outParticleSys = ParryParticleSystem;

	if ( BlockIndex != 255 )
	{
		if ( BlockTypes[BlockIndex].ParrySound != None )
		{
			outSound = BlockTypes[BlockIndex].ParrySound;
		}
		if ( BlockTypes[BlockIndex].ParryParticleSys != None )
		{
			outParticleSys = BlockTypes[BlockIndex].ParryParticleSys;
		}
	}
}

/*********************************************************************************************
 * State BlockingCooldown
  * A short cooldown state to prevent spamming block while still allowing pendingfire to be set
 *********************************************************************************************/

// Global declarations for this state
simulated function BlockCooldownTimer();

simulated state BlockingCooldown extends Active
{
	ignores AllowSprinting;

	/** Set cooldown duration */
	simulated function BeginState( Name PreviousStateName )
	{
		SetTimer(0.5, false, nameof(BlockCooldownTimer));
		Super.BeginState(PreviousStateName);
	}

	// prevent going to block/parry state
	simulated function bool HasAmmo( byte FireModeNum, optional int Amount )
	{
		if ( FireModeNum == BLOCK_FIREMODE )
		{
			return false;
		}

		return Global.HasAmmo(FireModeNum, Amount);
	}

	// prevent HasAmmo (above) from causing an auto reload
	simulated function bool ShouldAutoReload(byte FireModeNum)
	{
		if ( FireModeNum == BLOCK_FIREMODE )
		{
			return false;
		}

		return Global.ShouldAutoReload(FireModeNum);
	}

	simulated function BlockCooldownTimer()
	{
		GotoState('Active');
	}
}

/*********************************************************************************************
 * State WeaponEquipping
 * Overridden for DrawStrike ability
*********************************************************************************************/

/** Perform a special attack animation while equipping */
simulated function ANIMNOTIFY_DrawAtk();
simulated function AttemptDrawStrike();

simulated state WeaponEquipping
{
	/** Override BeginFire so that it will enter the firing state right away. */
	simulated function ANIMNOTIFY_DrawAtk()
	{
		// wait a short time because we can't call SetAnim during a notify
		SetTimer(0.001f, false, nameof(AttemptDrawStrike));
	}

	/** If melee attack fire modes are pending, perform draw strike */
	simulated function AttemptDrawStrike()
	{
	    // if either of the fire modes are pending, perform them
		if ( PendingFire(DEFAULT_FIREMODE) || PendingFire(HEAVY_ATK_FIREMODE) )
		{
			MeleeAttackHelper.InitAttackSequence(DIR_Right, ATK_DrawStrike);
			SendToFiringState(HEAVY_ATK_FIREMODE);
		}
	}
}

/*********************************************************************************************
 * Trader
 ********************************************************************************************/

/** Allows weapon to set its own trader stats (can set number of stats, names and values of stats) */
static simulated event SetTraderWeaponStats( out array<STraderItemWeaponStats> WeaponStats )
{
	super.SetTraderWeaponStats( WeaponStats );

	WeaponStats.Length = WeaponStats.Length + 1;
	WeaponStats[WeaponStats.Length-1].StatType = TWS_Block;
	WeaponStats[WeaponStats.Length-1].StatValue = GetUpgradedBlockDamageMitigation(0);

	WeaponStats.Length = WeaponStats.Length + 1;
	WeaponStats[WeaponStats.Length-1].StatType = TWS_Parry;
	WeaponStats[WeaponStats.Length-1].StatValue = GetUpgradedParryDamageMitigation(0);
}

/** Allows weapon to calculate its own damage for display in trader
  * In general, melee weapons should use heavy attack to determine damage
  */
static simulated function float CalculateTraderWeaponStatDamage()
{
	local float CalculatedDamage;
	local class<KFDamageType> DamageType;

	CalculatedDamage = default.InstantHitDamage[HEAVY_ATK_FIREMODE];

	DamageType = class<KFDamageType>(default.InstantHitDamageTypes[HEAVY_ATK_FIREMODE]);
	if( DamageType != none && DamageType.default.DoT_Type != DOT_None )
	{
		CalculatedDamage += (DamageType.default.DoT_Duration / DamageType.default.DoT_Interval) * (CalculatedDamage * DamageType.default.DoT_DamageScale);
	}

	return CalculatedDamage;
}

/** Allows weapon to calculate its own fire rate for display in trader.
  * Overridden to use estimated fire rate.
  */
static simulated function float CalculateTraderWeaponStatFireRate()
{
	// attack interval in anim-based, so there's no way to compute it programatically from the editor, where
	// this function is called from.

	return default.EstimatedFireRate;
}

/** Returns trader filter index based on weapon type */
static simulated event EFilterTypeUI GetTraderFilter()
{
	return FT_Melee;
}

/*********************************************************************************************
 * Upgrades
 ********************************************************************************************/

static simulated function float GetUpgradedBlockDamageMitigation(int UpgradeIndex)
{
	return GetUpgradedStatValue(default.BlockDamageMitigation, EWUS_BlockDmgMitigation, UpgradeIndex);
}

static simulated function float GetUpgradedParryDamageMitigation(int UpgradeIndex)
{
	return GetUpgradedStatValue(default.ParryDamageMitigationPercent, EWUS_ParryDmgMitigation, UpgradeIndex);
}

defaultproperties
{
	InventoryGroup=IG_Melee
	bMeleeWeapon=true

	Begin Object Name=FirstPersonMesh
		// Set MinTickTimeStep to ensure a high enough tickrate for melee hitbox collision
		MinTickTimeStep=0.025f // 40fps
	End Object

	// DEFAULT_FIREMODE
	FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Melee'
	FiringStatesArray(DEFAULT_FIREMODE)=MeleeChainAttacking
	WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Custom
	InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Slashing'
	InstantHitDamage(DEFAULT_FIREMODE)=20
	AmmoCost(DEFAULT_FIREMODE)=0

	// ALT_FIREMODE
	FireModeIconPaths(HEAVY_ATK_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Melee'
	FiringStatesArray(HEAVY_ATK_FIREMODE)=MeleeHeavyAttacking
	WeaponFireTypes(HEAVY_ATK_FIREMODE)=EWFT_Custom
	InstantHitDamageTypes(HEAVY_ATK_FIREMODE)=class'KFDT_Slashing'
	InstantHitDamage(HEAVY_ATK_FIREMODE)=50
	InstantHitMomentum(HEAVY_ATK_FIREMODE)=1.f
	AmmoCost(HEAVY_ATK_FIREMODE)=0

	// RELOAD_FIREMODE
	FiringStatesArray(RELOAD_FIREMODE)=WeaponUpkeep

	// MELEE_BLOCK_FIREMODE
	FiringStatesArray(BLOCK_FIREMODE)=MeleeBlocking
	WeaponFireTypes(BLOCK_FIREMODE)=EWFT_Custom
	FireInterval(BLOCK_FIREMODE)=1.f
	AmmoCost(BLOCK_FIREMODE)=0

	Begin Object Name=MeleeHelper_0
		bUseDirectionalMelee=true
		bHasChainAttacks=true
		bUseMeleeHitTimer=false
		MaxHitRange=150
	End Object

	// default MIC param names
	BlockEffectsSocketName=BlockEffect

	// Attachments
	bHasIronSights=false
	bHasFlashlight=false

	// Aim Assist
	AimCorrectionSize=0.f
	bTargetAdhesionEnabled=false

	//--------------------------------
	// Defensive
	BlockDamageMitigation=0.50f
	ParryDamageMitigationPercent=0.2
	ParryStrength=4
	BlockHitAnimCooldownTime=0.5f
	BlockTypes.Add((DmgType=class'KFDT_Bludgeon'))
	BlockTypes.Add((DmgType=class'KFDT_Slashing'))

	BlockSound=none
	ParrySound=none
	BlockParticleSystem=ParticleSystem'FX_Impacts_EMIT.FX_Block_melee_01'
	ParryParticleSystem=ParticleSystem'FX_Impacts_EMIT.FX_Parry_melee_01'
	MeleeBlockHitAnims=(Block_Hit_V1, Block_Hit_V2, Block_Hit_V3);

	DistortTrailParticle = ParticleSystem'FX_Gameplay_EMIT_THREE.Trails.FX_Trail_Distort_R_01'
	WhiteTrailParticle = ParticleSystem'FX_Gameplay_EMIT_THREE.Trails.FX_Trail_White_R_01'
	BlueTrailParticle = ParticleSystem'FX_Gameplay_EMIT_THREE.Trails.FX_Trail_Blue_R_01'
	RedTrailParticle = ParticleSystem'FX_Gameplay_EMIT_THREE.Trails.FX_Trail_Red_R_01'

	//--------------------------------
	// Animations
	MaxChainAtkCount = 3
	MeleeAttackSettleAnims.Add(Settle_V1)
	MinMeleeSustainedTime = 0.5f
	MeleeSustainedWarmupTime = 0.25f

	// Trader
	EstimatedFireRate = 100

	// Upgrades
	UpgradeFireModes(BLOCK_FIREMODE) = 0
	UpgradeFireModes(HEAVY_ATK_FIREMODE) = 1
	UpgradeFireModes(CUSTOM_FIREMODE) = 1

	ReloadCancelTimeLimit = 0.5f;

	bHasToBeConsideredAsRangedWeaponForPerks=false;
}