//=============================================================================
// KFWeap_PistolBase
//=============================================================================
// Generic pistol class
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// Chris "schneidzekk" Schneider
//=============================================================================
class KFWeap_PistolBase extends KFWeapon
	native;

/*********************************************************************************************
 * @name	Revolver vars
********************************************************************************************* */

// Whether this weapon is a revolver. Enables revolver functionality like rotating cylinder and noticeably-used shells
var bool bRevolver;

// Whether this revolver resets its rotation at the start of the reload state
var bool bUseDefaultResetOnReload;

// Meshes to indicate which rounds have been fired or not
var SkeletalMesh UnusedBulletMeshTemplate;
var SkeletalMesh UsedBulletMeshTemplate;
var array<name> BulletFXSocketNames;
var array<KFSkeletalMeshComponent> BulletMeshComponents;

const CYLINDERSTATE_READY = 0;
const CYLINDERSTATE_PENDING = 1;
const CYLINDERSTATE_ROTATING = 2;

var CylinderRotationInfo CylinderRotInfo;

cpptext
{
	// cylinder is incrementally rotated
	virtual void TickSpecial( FLOAT DeltaSeconds );
};

/** Cache Anim Nodes from the tree
* 	@note: skipped on server because AttachComponent/AttachWeaponTo is not called
*/
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
	super.PostInitAnimTree( SkelComp );

	if( bRevolver )
	{
		PostInitAnimTreeRevolver( SkelComp );
	}
}

/*********************************************************************************************
 * @name	Ammo
********************************************************************************************* */

// /**
//  * @see Weapon::ConsumeAmmo
//  */
simulated function ConsumeAmmo( byte FireModeNum )
{
	local int BulletIndex;

	BulletIndex = MagazineCapacity[DEFAULT_FIREMODE] - AmmoCount[DEFAULT_FIREMODE];

	super.ConsumeAmmo( FireModeNum );

	// update bullet mesh
	if( BulletIndex < BulletMeshComponents.Length )
	{
		BulletMeshComponents[BulletIndex].SetSkeletalMesh( UsedBulletMeshTemplate );
	}

	// do revovler ammo before actually consuming ammo
	if( bRevolver && Instigator != none && Instigator.IsLocallyControlled() )
	{
		ConsumeAmmoRevolver();
	}
}

/**
 * State Reloading
 * State the weapon is in when it is being reloaded (current magazine replaced with a new one, related animations and effects played).
 */
simulated state Reloading
{
	ignores ForceReload, ShouldAutoReload, AllowSprinting;

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

		if( bRevolver && bUseDefaultResetOnReload )
		{
			ResetCylinder();
		}
	}
}

/*********************************************************************************************
 * @name	Revolver
********************************************************************************************* */

simulated event PostInitAnimTreeRevolver( SkeletalMeshComponent SkelComp )
{
	local int i;

	CylinderRotInfo.Control = SkelControlSingleBone( SkelComp.FindSkelControl('CylinderControl') );
	if( CylinderRotInfo.Control != none )
	{
		CylinderRotInfo.Control.SetSkelControlActive( true );
	}

	// attach bullet meshes to correct sockets
	for( i = 0; i < BulletMeshComponents.Length; ++i )
	{
		BulletMeshComponents[i].SetHidden( false );
		MySkelMesh.AttachComponentToSocket( BulletMeshComponents[i], BulletFXSocketNames[i] );
	}
}

simulated function ConsumeAmmoRevolver()
{
	if(bUseDefaultResetOnReload)
	{
		CheckCylinderRotation( CylinderRotInfo );
		CylinderRotInfo.State = CYLINDERSTATE_PENDING;
	}
}

simulated function CheckCylinderRotation( out CylinderRotationInfo RotInfo, optional bool bResetState )
{
	if( RotInfo.State != CYLINDERSTATE_READY )
	{
		RotateCylinder( RotInfo, true );

		if( bResetState )
		{
			RotInfo.State = CYLINDERSTATE_READY;
		}
	}
}

/** Initiate cylinder rotation (rotation occurs in tickspecial in native) */
simulated function ANIMNOTIFY_RotateCylinder()
{
	RotateCylinder( CylinderRotInfo );
}

/** Reset the rotation of the cylinder via notification, ddefault revolvers do not need this */
simulated function ANIMNOTIFY_ResetCylinder()
{
	ResetCylinder();
}

/** Initiates cylinder rotation process */
simulated function RotateCylinder( out CylinderRotationInfo RotInfo, optional bool bInstant )
{
	if( bInstant )
	{
		// don't use timer, just set rotation

		if( RotInfo.State == CYLINDERSTATE_PENDING )
		{
			// if we're pending, we don't have an updated rotation yet, so get a rotation
			IncrementCylinderRotation( RotInfo );
		}
		else
		{
			// if we're rotating already, we're going to go to a pending state after forcing rotation
			RotInfo.State = CYLINDERSTATE_PENDING;
		}

		SetCylinderRotation( RotInfo, RotInfo.NextDegrees );
		RotInfo.Timer = 0.f;
	}
	else
	{
		// use timer, rotate over time in native tickspecial

		RotInfo.State = CYLINDERSTATE_ROTATING;
		IncrementCylinderRotation( RotInfo );
		RotInfo.Timer = RotInfo.Time;
	}
}

simulated function IncrementCylinderRotation( out CylinderRotationInfo RotInfo )
{
	RotInfo.PrevDegrees = RotInfo.NextDegrees;
	RotInfo.NextDegrees += RotInfo.Inc;
}

simulated function ResetCylinderInfo( out CylinderRotationInfo RotInfo )
{
	RotInfo.PrevDegrees = 0;
	RotInfo.NextDegrees = 0;
	RotInfo.State = CYLINDERSTATE_READY;
}

simulated event OnCylinderRotationFinished( out CylinderRotationInfo RotInfo )
{
	RotInfo.State = CYLINDERSTATE_READY;
}

// directly sets cylinder rotation, instead of allowing tickspecial to increment rotation
native simulated function SetCylinderRotation( out CylinderRotationInfo RotInfo, float Degrees );

simulated function InitializeReload()
{
	super.InitializeReload();
	CheckCylinderRotation( CylinderRotInfo, true );
}

simulated function ANIMNOTIFY_ResetBulletMeshes()
{
	ResetBulletMeshes();
}

/** Resets cylinder orientation to initial state and repositions bullet meshes to line up with their pre-reset locations */
simulated function ResetCylinder()
{
	local int i, UsedStartIdx, UsedEndIdx;

	// reset cylinder rotation (skel control needs to be reset to initial state while bullet casings are not on screen)
	SetCylinderRotation( CylinderRotInfo, 0 );
	ResetCylinderInfo( CylinderRotInfo );

	// now, we need to make sure the used bullets are still in the correct positions

	// if we fired all our ammo, it doesn't matter where the bullets are, because they're all the same (used)
	if( AmmoCount[DEFAULT_FIREMODE] == 0 )
	{
		return;
	}

	if (BulletMeshComponents.Length > 0)
	{
		// after cylinder reset, top-most bullet will be unused
		BulletMeshComponents[0].SetSkeletalMesh( UnusedBulletMeshTemplate );

		// find the range of bullets that have been used
		UsedStartIdx = BulletMeshComponents.Length - 1;
		UsedEndIdx = UsedStartIdx - (MagazineCapacity[DEFAULT_FIREMODE] - AmmoCount[DEFAULT_FIREMODE]);

		// set used bullets to used mesh
		for( i = UsedStartIdx; i > UsedEndIdx; --i )
		{
			BulletMeshComponents[i].SetSkeletalMesh( UsedBulletMeshTemplate );
		}

		// set the rest of the bullets to unused
		for( i = UsedEndIdx; i > 0; --i )
		{
			BulletMeshComponents[i].SetSkeletalMesh( UnusedBulletMeshTemplate );
		}
	}
}

/** Sets all bullet casing meshes back to unused state */
simulated function ResetBulletMeshes()
{
	local int i;

	for( i = 0; i < BulletMeshComponents.Length; ++i )
	{
		BulletMeshComponents[i].SetSkeletalMesh( UnusedBulletMeshTemplate );
	}
}

/*********************************************************************************************
 * State WeaponPuttingDown
 * Putting down weapon in favor of a new one.
 * Weapon is transitioning to the Inactive state.
*********************************************************************************************/

simulated state WeaponPuttingDown
{
	simulated function EndState(Name NextStateName)
	{
		super.EndState( NextStateName );
		CheckCylinderRotation( CylinderRotInfo, true );
	}
}

/*********************************************************************************************
 * @name	Firing / Projectile
********************************************************************************************* */
/**
 * See Pawn.ProcessInstantHit
 * @param DamageReduction: Custom KF parameter to handle penetration damage reduction
 */
simulated function ProcessInstantHitEx(byte FiringMode, ImpactInfo Impact, optional int NumHits, optional out float out_PenetrationVal, optional int ImpactNum )
{
	local KFPerk InstigatorPerk;

	InstigatorPerk = GetPerk();
	if( InstigatorPerk != none )
	{
		InstigatorPerk.UpdatePerkHeadShots( Impact, InstantHitDamageTypes[FiringMode], ImpactNum );
	}

	super.ProcessInstantHitEx( FiringMode, Impact, NumHits, out_PenetrationVal, ImpactNum );
}

/**
 * @brief Checks if weapon should be auto-reloaded - overwritten to allow gunslinger insta switch
 *
 * @param FireModeNum Current fire mode
 * @return auto reload or not
 */
simulated function bool ShouldAutoReload( byte FireModeNum )
{
	return ShouldAutoReloadGunslinger( FireModeNum );
}

/*********************************************************************************************
 * @name	Iron Sights / Zoom
 *********************************************************************************************/

/**
 * Adjust the FOV for the first person weapon and arms.
 */
simulated event SetFOV( float NewFOV )
{
	local int i;

	super.SetFOV( NewFOV );

	for( i = 0; i < BulletMeshComponents.Length; ++i )
	{
		BulletMeshComponents[i].SetFOV( NewFOV );
	}
}

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

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

DefaultProperties
{
	InventoryGroup=IG_Secondary

	FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletSingle'

	// BASH_FIREMODE
	InstantHitDamage(BASH_FIREMODE)=20.0 //10

	// Aim Assist
	AimCorrectionSize=40.f

	// All revolvers resets their rotation by default
	bUseDefaultResetOnReload=true
}
