//=============================================================================
// KFWeap_Pistol_Blunderbuss
//=============================================================================
// A pistol that can shoot explosive cannonballs and shard shotgun projectiles.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2020 Tripwire Interactive LLC
//	- Tulio Beloqui (Saber Interactive)
//=============================================================================

class KFWeap_Pistol_Blunderbuss extends KFWeap_PistolBase;

/** List of spawned cannon balls */
var array<KFProj_Cannonball_Blunderbuss> DeployedCannonballs;

/** Same as DeployedCannonballs.Length, but replicated because cannon balls are only tracked on server */
var int NumDeployedCannonballs;

/** set in the state to control the spawned projectiles **/
var bool bDeployedCannonball;

/** flag indicating that the cannonball was detonated **/
var bool bCannonballWasDetonated;

/** flag indicating that the cannonbal was converted to a time bomb and should not be converted again if it is in mid air **/
var bool bCannonballConvertedToTimeBomb;

/** flag indicating that the player released the button and the cannonbal can't be configured as a timed bomb **/
var bool bForceStandardCannonbal;

/** Reduction for the amount of damage dealt to the weapon owner (including damage by the explosion) */
var float SelfDamageReductionValue;

/** Amount of time we hold the fire button on this fire state, used in BlunderbussDeployAndDetonate **/
var transient float FireHoldTime;

/** Amount of time we should pass with the fire button on hold to trigger a timed explosion, used in BlunderbussDeployAndDetonate **/
var transient float TimedDetonationThresholdTime;

/** The server can block client shots in some cases **/
var transient bool bWaitingForServer;

var(Animations) const editconst	name		FireLoopStartLastSightedAnim;
var(Animations) const editconst	name		FireLoopStartLastAnim;
var(Animations) const editconst	name		FireLoopLastSightedAnim;

var bool bLastAnim;

replication
{
	if (bNetDirty)
		bDeployedCannonball, NumDeployedCannonballs, bCannonballWasDetonated;

}

simulated function AltFireMode()
{
	if ( !Instigator.IsLocallyControlled() )
	{
		return;
	}

	StartFire(ALTFIRE_FIREMODE);
}

simulated function Projectile ProjectileFire()
{
	local Projectile P;
	local KFProj_Cannonball_Blunderbuss Cannonball;
	
	P = super.ProjectileFire();
	Cannonball = KFProj_Cannonball_Blunderbuss(P);

	if (Cannonball != none)
	{
		DeployedCannonballs.AddItem(Cannonball);
		NumDeployedCannonballs = DeployedCannonballs.Length;
		bForceNetUpdate = true;
	}

	return P;
}

/** Removes a charge from the list using either an index or an actor and updates NumDeployedCannonballs */
function RemoveDeployedCannonball(optional int CannonballIndex = INDEX_NONE, optional Actor CannonballActor)
{
	if (CannonballIndex == INDEX_NONE)
	{
		if (CannonballActor != none)
		{
			CannonballIndex = DeployedCannonballs.Find(CannonballActor);
		}
	}

	if (CannonballIndex != INDEX_NONE)
	{
		DeployedCannonballs.Remove(CannonballIndex, 1);
		NumDeployedCannonballs = DeployedCannonballs.Length;
		bForceNetUpdate = true;
	}
}

simulated function StartFire(byte FireModeNum)
{
	if(bWaitingForServer && FireModeNum <= 1)
	{
		return;
	}
	super.StartFire(FireModeNum);
}

simulated function EndFire(byte FireModeNum)
{
	//if(PendingFire(DEFAULT_FIREMODE)) return;
	bForceStandardCannonbal = true;
	super.EndFire(FireModeNum);
}

simulated function ResetFireState()
{
	FireHoldTime = 0;
	bForceStandardCannonbal = false;
	bCannonballWasDetonated = false;
	bCannonballConvertedToTimeBomb = false;
}

/*********************************************************************************************
 * State WeaponSingleFiring (Alt Fire)
 * Fire must be released between every shot.
 *********************************************************************************************/

simulated state WeaponSingleFiring
{
	simulated function FireAmmunition()
	{
		Super.FireAmmunition();
		if(Role != Role_Authority)
		{
			bWaitingForServer = true;
		}
	}

	simulated function bool ShouldRefire()
	{
		return Super.ShouldRefire() && !bWaitingForServer;
	}
}

/*********************************************************************************************
 * State Reloading
 * State the weapon is in when it is being reloaded
*********************************************************************************************/
simulated state Active
{
	simulated function BeginState( name PreviousStateName )
	{
		super.BeginState( PreviousStateName );
		
		if (Role == ROLE_Authority)
		{
			ClientResetFire();
		}
	}
}

/*********************************************************************************************
 * State BlunderbussDeployAndDetonate
 * The weapon is in this state while holding fire button for the CannonBall fire
*********************************************************************************************/

simulated state BlunderbussDeployAndDetonate extends WeaponSingleFiring
{
    simulated event BeginState(Name PreviousStateName)
    { 
		if( !IsTimerActive('TryDetonateCannonBall') )
		{
			SetTimer( 0.05f, true, nameof(TryDetonateCannonBall) );
		}

		Super.BeginState(PreviousStateName);
		ResetFireState();
	}

    simulated event EndState(Name NextStateName)
    {
		local int iNumOfCannonballs, i;
		
		if (Role == ROLE_Authority)
		{
			iNumOfCannonballs = DeployedCannonballs.Length;
			for(i=0; i < iNumOfCannonballs; i++)
			{
				DeployedCannonballs[0].Detonate();
			}
		}
		
		bDeployedCannonball = false;

		ClearTimer( nameof(TryDetonateCannonBall) );
		Super.EndState(NextStateName);
	}

	simulated function PutDownWeapon()
	{
		local KFProj_Cannonball_Blunderbuss Cannonball;
 
		if (Role == ROLE_Authority && bDeployedCannonball && DeployedCannonballs.Length > 0)
		{
			Cannonball = DeployedCannonballs[DeployedCannonballs.Length - 1];
			if (Cannonball.bIsTimedExplosive)
			{
				Cannonball.Detonate();
				bCannonballWasDetonated = true;
			}
		}

		if(Role == ROLE_Authority)
		{
			ClientResetFire();
			bDeployedCannonball = false;
		}

		global.PutDownWeapon();
	}
	
	simulated function TryDetonateCannonBall()
	{
		if( bCannonballWasDetonated || bWeaponPutDown || ShouldRefire() )
		{
			return;
		}

		DetonateCannonball();
	}

    simulated event Tick(float DeltaTime)
    {
		local KFProj_Cannonball_Blunderbuss Cannonball;

		global.Tick(DeltaTime);

		// Timed bomb is not allowed if we press the button after releasing it
		if(Role == ROLE_Authority && bForceStandardCannonbal && !bCannonballConvertedToTimeBomb)
		{
			bDeployedCannonball = false;
			bNetDirty=true;
			return;
		}

		// Don't charge unless we're holding down the button
		if (PendingFire(CurrentFireMode) && FireHoldTime < TimedDetonationThresholdTime)
		{
			FireHoldTime += DeltaTime;
		}

		if (Role == ROLE_Authority)
		{
			// Double check, this should not be empty:
			if (DeployedCannonballs.Length > 0)
			{
				Cannonball = DeployedCannonballs[DeployedCannonballs.Length - 1];
				
				// make sure we mark it as timed exploside only once!
				if (!Cannonball.bIsTimedExplosive && FireHoldTime >= TimedDetonationThresholdTime && !bCannonballConvertedToTimeBomb)
				{
					bCannonballConvertedToTimeBomb = true;
					Cannonball.bIsTimedExplosive = true;
					Cannonball.bNetDirty = true;
				}
			}
		}
    }
	
    simulated function FireAmmunition()
    {
		if (!bDeployedCannonball)
		{
			if(Role != Role_Authority)
			{
				bWaitingForServer = true;
			}

			super.FireAmmunition();
			ResetFireState();
			bNetDirty=true;
		}
		
		bDeployedCannonball = true;

		// re-set the pending fire, so we can still be in this state until release the fire button
		SetPendingFire(CurrentFireMode);
	}
	
	simulated function bool ShouldRefire()
	{
		return !bCannonballWasDetonated && StillFiring(CurrentFireMode);
	}

	simulated function DetonateCannonball ()
	{
		local KFProj_Cannonball_Blunderbuss Cannonball;

		if (Role == ROLE_Authority)
		{
			// Double check, this should be not empty:
			if (bDeployedCannonball && DeployedCannonballs.Length > 0)
			{
				Cannonball = DeployedCannonballs[DeployedCannonballs.Length - 1];
				if (Cannonball.bIsTimedExplosive)
				{
					Cannonball.Detonate();
					bCannonballWasDetonated = true;
					
					ClientResetFireInterval();
					if( IsTimerActive('RefireCheckTimer') )
					{
						ClearTimer( nameof(RefireCheckTimer) );
					}
					SetTimer( GetFireInterval(0), true, nameof(RefireCheckTimer) );
					bDeployedCannonball = false;
				}
			}
		}
	}

	simulated function HandleFinishedFiring ()
	{
		if (Role == ROLE_Authority)
		{
			// auto switch weapon when out of ammo and after detonating the last deployed ball
			if (!HasAnyAmmo() && NumDeployedCannonballs == 0)
			{
				if (CanSwitchWeapons())
				{
					Instigator.Controller.ClientSwitchToBestWeapon(false);
				}
			}
		}

		super.HandleFinishedFiring();
	}
}

reliable client function ClientResetFire()
{
	bWaitingForServer = false;
}

reliable client function ClientResetFireInterval()
{
	if( IsTimerActive('RefireCheckTimer') )
	{
		ClearTimer( nameof(RefireCheckTimer) );
	}
	SetTimer( GetFireInterval(0), true, nameof(RefireCheckTimer) );
}

simulated function HandleProjectileImpact(byte ProjectileFireMode, ImpactInfo Impact, optional float PenetrationValue)
{
	// Blunderbuss projectile detection is handled in the server to avoid collision sync problems
	if(Instigator != None && Instigator.Role == ROLE_Authority)
	{
		ProcessInstantHitEx(ProjectileFireMode, Impact,, PenetrationValue, 0);
	}
}

function AdjustDamage(out int InDamage, class<DamageType> DamageType, Actor DamageCauser)
{
	super.AdjustDamage(InDamage, DamageType, DamageCauser);

	if (Instigator != none && DamageCauser != none && DamageCauser.Instigator == Instigator)
	{
		InDamage *= SelfDamageReductionValue;
	}
}

simulated function KFProjectile SpawnAllProjectiles(class<KFProjectile> KFProjClass, vector RealStartLoc, vector AimDir)
{
	local KFPerk InstigatorPerk;

	if (CurrentFireMode == ALTFIRE_FIREMODE)
	{
		InstigatorPerk = GetPerk();
		if (InstigatorPerk != none)
		{
			Spread[CurrentFireMode] = default.Spread[CurrentFireMode] * InstigatorPerk.GetTightChokeModifier();
		}
	}

	return super.SpawnAllProjectiles(KFProjClass, RealStartLoc, AimDir);
}

/** Get name of the animation to play for PlayFireEffects */
simulated function name GetLoopStartFireAnim(byte FireModeNum)
{
	if ( bUsingSights )
	{
		if(AmmoCount[GetAmmoType(FireModeNum)] <= 1 && FireModeNum == DEFAULT_FIREMODE)
		{
			return FireLoopStartLastSightedAnim;
		}
		else
		{
			return FireLoopStartSightedAnim;
		}
	}

	if(AmmoCount[GetAmmoType(FireModeNum)] <= 1 && FireModeNum == DEFAULT_FIREMODE)
	{
		return FireLoopStartLastAnim;
	}
	else
	{ 
		return FireLoopStartAnim;
	}
}

/** Get name of the animation to play for PlayFireEffects */
simulated function name GetLoopingFireAnim(byte FireModeNum)
{
	// scoped-sight anims
	if( bUsingScopePosition )
	{
		return FireLoopScopedAnim;
	}
	// ironsights animations
	else if ( bUsingSights )
	{
		if(AmmoCount[GetAmmoType(FireModeNum)] < 1 && FireModeNum == DEFAULT_FIREMODE)
		{
			return FireLoopLastSightedAnim;
		}
		else
		{
			return FireLoopSightedAnim;
		}
	}

	return FireLoopAnim;
}

defaultproperties
{

	//Custom Anims
	FireLoopStartLastSightedAnim=ShootLoop_Iron_Start_Last;
	FireLoopStartLastAnim=ShootLoop_Start_Last;
	FireLoopLastSightedAnim=ShootLoop_Iron_Last;

    // Revolver
	bRevolver=true
	bUseDefaultResetOnReload=false
	CylinderRotInfo=(Inc=120.0, Time=0.2)

	// Inventory
	InventoryGroup=IG_Primary
	InventorySize=7
	GroupPriority=100
	bCanThrow=true
	bDropOnDeath=true
	WeaponSelectTexture=Texture2D'WEP_UI_Blunderbuss_TEX.UI_WeaponSelect_BlunderBluss'
	bIsBackupWeapon=false

	// Gameplay
	SelfDamageReductionValue=0.5f //0.75f
	TimedDetonationThresholdTime=0.01f

	// FOV
	MeshFOV=86
	MeshIronSightFOV=75
	PlayerIronSightFOV=75
	PlayerSprintFOV=95

	// Depth of field
	DOF_bOverrideEnvironmentDOF=true
	DOF_SharpRadius=500.0
	DOF_FocalRadius=1000.0
	DOF_MinBlurSize=0.0
	DOF_MaxNearBlurSize=2.0
	DOF_MaxFarBlurSize=0.0
	DOF_ExpFalloff=1.0
	DOF_MaxFocalDistance=2000.0

	DOF_BlendInSpeed=1.0
	DOF_BlendOutSpeed=1.0

	DOF_FG_FocalRadius=50
	DOF_FG_SharpRadius=0
	DOF_FG_MinBlurSize=0
	DOF_FG_MaxNearBlurSize=3
	DOF_FG_ExpFalloff=1

	// Zooming/Position
	PlayerViewOffset=(X=-15,Y=12,Z=-6)
	IronSightPosition=(X=-3,Y=0,Z=0)

	// Content
	PackageKey="Blunderbuss"
    FirstPersonMeshName="WEP_1P_Blunderbuss_MESH.Wep_1stP_Blunderbuss_Rig"
	FirstPersonAnimSetNames(0)="WEP_1P_Blunderbuss_ANIM.Wep_1stP_Blunderbuss_Anim"
	PickupMeshName="WEP_3P_Blunderbuss_MESH.Wep_3rdP_Blunderbuss_Pickup"
	AttachmentArchetypeName="WEP_Blunderbuss_ARCH.Wep_Blunderbuss_3P"
	MuzzleFlashTemplateName="WEP_Blunderbuss_ARCH.Wep_Blunderbuss_MuzzleFlash"
	Begin Object Name=FirstPersonMesh
		// new anim tree with skelcontrol to rotate cylinders
		AnimTreeTemplate=AnimTree'CHR_1P_Arms_ARCH.WEP_1stP_Animtree_Master_Revolver'
	End Object

	// Ammo
	MagazineCapacity[0]=3
	SpareAmmoCapacity[0]=39
	InitialSpareMags[0]=4
	bCanBeReloaded=true
	bReloadFromMagazine=true

	// Recoil
	maxRecoilPitch=900
	minRecoilPitch=775
	maxRecoilYaw=500
	minRecoilYaw=-500
	RecoilRate=0.085
	RecoilBlendOutRatio=1.1
	RecoilMaxYawLimit=500
	RecoilMinYawLimit=65035
	RecoilMaxPitchLimit=1500
	RecoilMinPitchLimit=64785
	RecoilISMaxYawLimit=50
	RecoilISMinYawLimit=65485
	RecoilISMaxPitchLimit=500
	RecoilISMinPitchLimit=65485

	// DEFAULT_FIREMODE
	FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Grenade'
	FiringStatesArray(DEFAULT_FIREMODE)=BlunderbussDeployAndDetonate
	WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Projectile
	WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_Cannonball_Blunderbuss'
	FireInterval(DEFAULT_FIREMODE)=+0.69 // 86 RPM
	InstantHitDamage(DEFAULT_FIREMODE)=300.0
	InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Ballistic_BlunderbussImpact'
	PenetrationPower(DEFAULT_FIREMODE)=0
	Spread(DEFAULT_FIREMODE)=0.015
	bLoopingFireAnim(DEFAULT_FIREMODE)=true
	FireOffset=(X=39,Y=5,Z=-5)

	// ALT_FIREMODE
	FireModeIconPaths(ALTFIRE_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletSingle'
	FiringStatesArray(ALTFIRE_FIREMODE)=WeaponSingleFiring
	WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_Projectile
	WeaponProjectiles(ALTFIRE_FIREMODE)=class'KFProj_Nail_Blunderbuss'
	FireInterval(ALTFIRE_FIREMODE)=+0.69 // 86 RPM
	InstantHitDamage(ALTFIRE_FIREMODE)=50.0
	InstantHitDamageTypes(ALTFIRE_FIREMODE)=class'KFDT_Ballistic_BlunderbussShards'
	PenetrationPower(ALTFIRE_FIREMODE)=2.0
	Spread(ALTFIRE_FIREMODE)=0.175
	NumPellets(ALTFIRE_FIREMODE)=10

	// BASH_FIREMODE
	InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_Blunderbuss'
	InstantHitDamage(BASH_FIREMODE)=26

	// Fire Effects
	WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_Blunderbuss.Play_WEP_Blunderbuss_Fire_3P_01', FirstPersonCue=AkEvent'WW_WEP_Blunderbuss.Play_WEP_Blunderbuss_Fire_1P_01')
	WeaponDryFireSnd(DEFAULT_FIREMODE)=AkEvent'WW_WEP_SA_9mm.Play_WEP_SA_9mm_Handling_DryFire'

	WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_Blunderbuss.Play_WEP_Blunderbuss_Fire_3P_01', FirstPersonCue=AkEvent'WW_WEP_Blunderbuss.Play_WEP_Blunderbuss_Fire_1P_01')
	WeaponDryFireSnd(ALTFIRE_FIREMODE)=AkEvent'WW_WEP_SA_9mm.Play_WEP_SA_9mm_Handling_DryFire'

	// Attachments
	bHasIronSights=true
	bHasFlashlight=true

    AssociatedPerkClasses(0)=class'KFPerk_Demolitionist'
    AssociatedPerkClasses(1)=class'KFPerk_Support'

	// Custom animations
	FireSightedAnims=(Shoot_Iron)
	IdleFidgetAnims=(Guncheck_v1, Guncheck_v2, Guncheck_v3)

	bHasFireLastAnims=true
	BonesToLockOnEmpty=(RW_Hammer, RW_Frizzen)

	WeaponUpgrades[1]=(Stats=((Stat=EWUS_Damage0, Scale=1.15f), (Stat=EWUS_Damage1, Scale=1.15f), (Stat=EWUS_Weight, Add=1)))
}