//=============================================================================
// KFWeap_Healer_Syringe
//=============================================================================
// Weapon class used for the healer
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFWeap_Welder extends KFWeapon
	hidedropdown;

/** If set, automatically equip the previous weapon when leaving a door trigger */
var() bool bAutoUnequip;

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

/** Maximum range to find Door */
var() float WeldingRange;
/** How many points to add to a door's weld integrity per use (use rate determined by FireInterval) */
var() float FastenRate;
/** How many points to subtract from a door's weld integrity per use (use rate determined by FireInterval) */
var() float UnFastenRate;
/** Percent repaired per fire interval */
var() float RepairRate;

/** Give the welder extra range when it has a WeldTarget to avoid the ready animation
activating / deactivating on door that is is currently being damaged */
var const float ExtraWeldingRange;

/** How long (in seconds) it takes to recharge ammo by 1 unit */
var() float AmmoRechargeRate;

/*********************************************************************************************
 @name Animations
********************************************************************************************* */

/** animation names */
var name IdleWeldAnim;
var name WeldOpenAnim;
var name WeldCloseAnim;

/** Reference to the door we're pointing at */
var KFWeldableComponent WeldTarget;

/** Store last update to we can prevent idle->ready->idle to quickly */
var float LastTraceHitTime;

/*********************************************************************************************
 @name Screen
********************************************************************************************* */
var class<KFGFxWorld_WelderScreen> ScreenUIClass;
var KFGFxWorld_WelderScreen ScreenUI;

simulated event PreBeginPlay()
{
	local KFGameEngine KFGEngine;

	Super.PreBeginPlay();

	KFGEngine = KFGameEngine( Class'KFGameEngine'.static.GetEngine() );

	if(KFGEngine != none)
	{
		SetShownInInventory(KFGEngine.bShowWelderInInv);
	}
}

simulated function SetShownInInventory(bool bValue)
{
	InventoryGroup= bValue ? IG_Equipment : IG_None ;
	bAutoUnequip = !bValue;
}

/** Turn on the UI screen when we equip the healer */
simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
{
	super.AttachWeaponTo( MeshCpnt, SocketName );

	if( Instigator != none && Instigator.IsLocallyControlled() )
	{
		// Create the screen's UI piece
		if (ScreenUI == none)
		{
			ScreenUI = new( self ) ScreenUIClass;
			ScreenUI.Init();
			ScreenUI.Start(true);
		}

		if ( ScreenUI != none)
		{
			ScreenUI.SetPause(false);
			ScreenUI.SetCharge( AmmoCount[0] );
			ScreenUI.SetIntegrity( 255 );
		}
	}
}

/** Turn off the UI screen when we unequip the healer */
simulated function DetachWeapon()
{
	super.DetachWeapon();
	if ( Instigator != none && Instigator.IsLocallyControlled() && ScreenUI != none )
	{
		ScreenUI.SetPause();
	}
}

/** @see Weapon::Tick */
simulated event Tick(float DeltaTime)
{
	Super.Tick(DeltaTime);

	UpdateScreenUI();
}

simulated event Destroyed()
{
	if ( Instigator != none && Instigator.IsLocallyControlled() && ScreenUI != none)
	{
		ScreenUI.Close();
	}
	super.Destroyed();
}

/** Only update the screen screen if we have the welder equipped and it's screen values have changed */
simulated function UpdateScreenUI()
{
	local float WeldPercentageFloat;
	local byte WeldPercentage;

	if ( Instigator != none && Instigator.IsLocallyControlled() && Instigator.Weapon == self )
	{
		if ( ScreenUI != none )
		{
			// Check if our current ammo reading has changed
			if ( ScreenUI.CurrentCharge != AmmoCount[0] )
			{
				ScreenUI.SetCharge( AmmoCount[0] );
			}

			if ( WeldTarget != none )
			{
				// Address rounding errors in UI
				WeldPercentageFloat = WeldTarget.GetWeldPct();
				if( WeldPercentageFloat < 1.f && WeldPercentageFloat > 0.f )
				{
					WeldPercentageFloat = 1.f;
				}
				else if( WeldPercentageFloat > 99.f && WeldPercentageFloat < 100.f )
				{
					WeldPercentageFloat = 99.f;
				}

				WeldPercentage = byte( WeldPercentageFloat );
				// Check if our weld integrity has changed
				if ( WeldPercentage != ScreenUI.IntegrityPercentage )
				{
		        	ScreenUI.SetIntegrity( WeldPercentage );
		     	}
			}
			// Remove our weld value
			else if ( ScreenUI.IntegrityPercentage != 255 )
			{
		    	ScreenUI.SetIntegrity( 255 );
			}
		}
	}
}

/*********************************************************************************************
 * @name Ammunition
 *********************************************************************************************/

simulated function bool HasAnyAmmo()
{
	return true;
}

simulated function bool HasAmmo( byte FireModeNum, optional int Amount )
{
	if ( FireModeNum == DEFAULT_FIREMODE || FireModeNum == ALTFIRE_FIREMODE )
	{
		if ( AmmoCount[0] >= AmmoCost[FireModeNum])
		{
			// Requires a valid WeldTarget (see ServerSetWeldTarget)
			return ( WeldTarget != None && CanWeldTarget(FireModeNum) );
		}
		return false;
	}

	return Super.HasAmmo(FireModeNum, Amount);
}

/** Returns true if weapon should be auto-reloaded */
simulated function bool ShouldAutoReload(byte FireModeNum)
{
	return false;
}

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

/** Unweld with RM/L2 */
simulated function SetIronSights(bool bNewIronSights)
{
	if ( !Instigator.IsLocallyControlled()  )
	{
		return;
	}

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

/** Also unweld with MM/R1 */
simulated function AltFireMode()
{
	if ( !Instigator.IsLocallyControlled() )
	{
		return;
	}

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

/**
 * @see Weapon::StartFire
 */
simulated function StartFire(byte FireModeNum)
{
	// Notify server of the weld target we plan to use
	if ( FireModeNum == DEFAULT_FIREMODE || FireModeNum == ALTFIRE_FIREMODE )
	{
		if ( Role < ROLE_Authority )
		{
			ServerSetWeldTarget(WeldTarget, false);
		}
	}

	Super.StartFire(FireModeNum);
}

/**
 * If the weapon isn't an instant hit, or a simple projectile, it should use the type EWFT_Custom.  In those cases
 * this function will be called.  It should be subclassed by the custom weapon.
 */
simulated function CustomFire()
{
	local float CurrentFastenRate, CurrentUnfastenRate;
	WeldTarget = TraceWeldables();
	// Fasten/Unfasten the door
	if ( Role == ROLE_Authority )
	{
		if( !IsTimerActive(nameof(RechargeAmmo)) )
		{
			SetTimer(AmmoRechargeRate, true, nameof(RechargeAmmo));
		}

		if(WeldTarget != None)
		{
			CurrentFastenRate = FastenRate;
			CurrentUnfastenRate = UnFastenRate;

			GetPerk().ModifyWeldingRate(CurrentFastenRate, CurrentUnfastenRate);

			if ( WeldTarget.bIsDestroyed && !WeldTarget.Owner.IsA('KFRepairableActor') )
			{
				WeldTarget.Repair(RepairRate, KFPawn(Instigator));
			}
			else if ( CurrentFireMode == DEFAULT_FIREMODE )
			{
				WeldTarget.Weld(CurrentFastenRate, KFPawn(Instigator));
			}
			else
			{
				WeldTarget.Weld(CurrentUnfastenRate, KFPawn(Instigator));
			}
		}
	}

	// On the local player check to see if we should stop firing
	// It makes sense to do this in ShouldRefire(), but there is no guarantee
	// that it will sync on server/client.  So, use StopFire() instead.
	if ( Instigator.IsLocallyControlled() )
	{
		if ( WeldTarget == none || !CanWeldTarget() )
		{
			// TargetDoor is no longer valid
			StopFire(CurrentFireMode);
		}
	}
}

simulated function bool CanWeldTarget( optional int FireModeNum=CurrentFireMode )
{
	local KFPerk WelderPerk;

	WelderPerk = GetPerk();

	if ( FireModeNum == DEFAULT_FIREMODE && !WeldTarget.CanBeWelded() )
	{
		if( WelderPerk != none && WelderPerk.CanExplosiveWeld() )
		{
			return WeldTarget.CanBeDemoWelded();
		}

		return false;
	}
	else if ( FireModeNum == ALTFIRE_FIREMODE && !WeldTarget.CanBeUnwelded() )
	{
		return false;
	}
	return true;
}

/** Ammo regen timer */
function RechargeAmmo()
{
	if ( AmmoCount[0] < MagazineCapacity[0] )
	{
		AmmoCount[0]++;

		// If we are holding down the mouse when we regen enough ammo, fire again
		if ( AmmoCount[0] == AmmoCost[0] )
		{
		 	Refire();
			if ( !Instigator.IsLocallyControlled() )
			{
				ClientRefire();
			}
		}
	}
	else
	{
	 	ClearTimer(nameof(RechargeAmmo));
	}
}

/**	If we are holding down the mouse when we regen enough ammo, fire again */
simulated function Refire()
{
	local byte i;
    // if either of the fire modes are pending, perform them
	for( i=0; i<=ALTFIRE_FIREMODE; i++ )
	{
		if( PendingFire(i) )
		{
			SendToFiringState(i);
			break;
		}
	}
}

/** RechargeAmmo is called on authority only. When we have enough ammo to fire again
*	we want to make sure that the client plays his animations and fire effects */
reliable client function ClientRefire()
{
 	Refire();
}

/** If we recieve a valid door target after the fire key was already pressed down */
simulated function CheckDelayedStartFire()
{
	local bool bNotifyServer;

	if ( WeldTarget != None )
	{
		if( PendingFire(DEFAULT_FIREMODE) )
		{
			BeginFire(DEFAULT_FIREMODE);
			bNotifyServer = true;
		}
		else if( PendingFire(ALTFIRE_FIREMODE) )
		{
			BeginFire(ALTFIRE_FIREMODE);
			bNotifyServer = true;
		}

		if ( bNotifyServer && Role < ROLE_Authority )
		{
			ServerSetWeldTarget(WeldTarget, true);
		}
	}
}

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

/*********************************************************************************************
 @name WeldTarget
********************************************************************************************* */

/**
 * Find doors within welding range
 * Network: Local Player
 */
simulated function bool TickWeldTarget()
{
	local KFWeldableComponent PreviousTarget;

	// prevent state from changing too often (also a optimization)
	if ( `TimeSince(LastTraceHitTime) < 0.2f )
	{
		return false;
	}

	PreviousTarget = WeldTarget;
	WeldTarget = TraceWeldables();

	// refresh idle anim if TargetDoor has changed
	if ( PreviousTarget != WeldTarget )
	{
		return PlayReadyTransition(PreviousTarget);
	}

	return false;
}

/** Network: All */
simulated function KFWeldableComponent TraceWeldables()
{
	local KFWeldableComponent WeldableComponent;
	local vector HitLoc, HitNorm, StartTrace, EndTrace, AdjustedAim;
	local bool bIsRepairableActor;
	local KFDoorActor WeldableDoor;

	// define range to use for CalcWeaponFire()
	StartTrace = Instigator.GetWeaponStartTraceLocation();
	AdjustedAim = vector(GetAdjustedAim(StartTrace));
	EndTrace = StartTrace + AdjustedAim * WeldingRange;

	// Give the welder extra range when it has a WeldTarget to avoid the ready animation
	// activating / deactivating on door that is is currently being damaged
	if( WeldTarget != none )
	{
		EndTrace += AdjustedAim * ExtraWeldingRange;
	}

	// find weldable (trace against basic cylinder)
	foreach GetTraceOwner().TraceActors(class'KFWeldableComponent', WeldableComponent, HitLoc, HitNorm, EndTrace, StartTrace)
	{
		if (WeldableComponent.Owner == none)
		{
			// should never happen, right?
			continue;
		}

		// trace against owner for more accuracy
		if (Trace(HitLoc, HitNorm, EndTrace, StartTrace, true) != WeldableComponent.Owner)
		{
			continue;
		}

		bIsRepairableActor = WeldableComponent.Owner.IsA('KFRepairableActor');

		if (!bIsRepairableActor && WeldableComponent.bIsDestroyed )
		{
			continue;
		}

		if (bIsRepairableActor && !WeldableComponent.CanBeWelded())
		{
			continue;
		}

		LastTraceHitTime = WorldInfo.TimeSeconds;
		return WeldableComponent;
	}

	WeldableDoor = FindRepairableDoor();
	return WeldableDoor != none ? WeldableDoor.WeldableComponent : none;
}

/** Try to find a nearby destroyed door.  Can't use TraceActors because the door geometry is gone! */
simulated function KFDoorActor FindRepairableDoor()
{
	local KFDoorTrigger DoorTrigger;
	local KFInterface_Usable UsableTrigger;
	local float FacingDot;
	local vector Dir2d;
	local KFPlayerController KFPC;

	// On local player we can detect the interaction message and early out for perf
	// On the server this is only called when firing (instead of on tick) so it's not as bad,
	// but if needed we could add a LastInteractionMessage var to UpdateInteractionMessages()
	if ( Instigator.IsLocallyControlled() )
	{
		KFPC = KFPlayerController(Instigator.Controller);
		if ( KFPC.MyGFxHUD.CurrentInteractionIndex != IMT_RepairDoor )
		{
			return None;
		}
	}

	UsableTrigger = class'KFPlayerController'.static.GetCurrentUsableActor(Instigator);
	DoorTrigger = KFDoorTrigger(UsableTrigger);

	// if we didn't find a door that could be welded, maybe there is one that can be repaired?
	if ( DoorTrigger != None && DoorTrigger.DoorActor != None && DoorTrigger.DoorActor.bIsDestroyed )
	{
		// zero Z to give us a 2d dot product
		Dir2d = Normal2d(DoorTrigger.DoorActor.Location - Instigator.Location);
		FacingDot = vector(Instigator.Rotation) dot (Dir2d);
		if ( FacingDot > 0.87 )
		{
			return DoorTrigger.DoorActor;
		}
	}

	return None;
}

/** Notify server of new WeldTarget for 'HasAmmo' */
reliable server private function ServerSetWeldTarget(KFWeldableComponent NewTarget, bool bDelayedStart)
{
	WeldTarget = NewTarget;
	
	if ( bDelayedStart )
	{
		CheckDelayedStartFire();
	}
}

/** Play a transition animation between idle ready states */
simulated function bool PlayReadyTransition(KFWeldableComponent PreviousTarget)
{
	local name AnimName;
	local float Duration;

	if( WeldTarget != None )
	{
		AnimName = WeldOpenAnim;
		// set timer to begin firing if PendingFire is already set
		if ( PreviousTarget == None )
		{
			Duration = MySkelMesh.GetAnimLength(AnimName);
			SetTimer(FMax(Duration - 0.2f, 0.01f), false, nameof(CheckDelayedStartFire));
		}
	}
	else if( PreviousTarget != None )
	{
		AnimName = WeldCloseAnim;
	}

	if ( AnimName != '' )
	{
		PlayAnimation(AnimName);
		return true;
	}

	return false;
}

/*********************************************************************************************
 * state Inactive
 * This state is the default state.  It needs to make sure Zooming is reset when entering/leaving
 *********************************************************************************************/

auto state Inactive
{
	simulated function BeginState(name PreviousStateName)
	{
		Super.BeginState(PreviousStateName);
		WeldTarget = none;
	}
}

/*********************************************************************************************
 * State Active
 * A Weapon this is being held by a pawn should be in the active state.  In this state,
 * a weapon should loop any number of idle animations, as well as check the PendingFire flags
 * to see if a shot has been fired.
 *********************************************************************************************/

simulated state Active
{
	simulated event Tick(float DeltaTime)
	{
		// Caution - Super will skip our global, but global will skip super's state function!
		Global.Tick(DeltaTime);
		
		if ( Instigator != none && Instigator.IsLocallyControlled() )
		{
			// local player - find nearbydoors
			TickWeldTarget();	// will trace each call, but it's decently fast (zero-extent)
			UpdateScreenUI();

			if (bAutoUnequip)
			{
				TickAutoUnequip();
			}
		}
	}

	simulated event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
	{
		local bool bPlayingAnim;

		if ( Instigator != none && Instigator.IsLocallyControlled() )
		{
			// Update target immediately and enable tick (first time)
			bPlayingAnim = TickWeldTarget();

			// if animation didn't play in UpdateWeldTarget, play idle normally
			if ( !bPlayingAnim )
			{
				PlayIdleAnim();
			}
		}
	}

	simulated function PlayIdleAnim()
	{
		local int IdleIndex;

		if ( Instigator != none && Instigator.IsLocallyControlled() )
		{
			if( WeldTarget != None )
			{
				PlayAnimation(IdleWeldAnim, 0.0, true, 0.2);
			}
			else
			{
				IdleIndex = Rand(IdleAnims.Length);
				PlayAnimation(IdleAnims[IdleIndex], 0.0, true, 0.2);
			}
		}
	}
}

/** Automatically equip previous weapon when leaving a door trigger */
simulated function bool TickAutoUnequip()
{
	local Trigger_PawnsOnly Trigger;
	local KFInventoryManager KFIM;

	ForEach Instigator.TouchingActors(class'Trigger_PawnsOnly', Trigger)
	{
		if(Trigger.IsA('KFDoorTrigger') || Trigger.IsA('KFRepairableActorTrigger') || Trigger.IsA('KFWeldableTrigger'))
		{
			return FALSE;
		}
	}

	KFIM = KFInventoryManager( Instigator.InvManager );
	if ( KFIM != None )
	{
		KFIM.SwitchToLastWeapon();
		return TRUE;
	}

	return FALSE;
}

/*********************************************************************************************
 * state WeaponWelding
 * This is the default Firing State.  It's performed on both the client and the server.
 *********************************************************************************************/

simulated state WeaponWelding extends WeaponFiring
{
	simulated function byte GetWeaponStateId()
	{
		return WEP_Weld;
	}

	simulated function BeginState(Name PrevStateName)
	{
		super.BeginState(PrevStateName);
		NotifyBeginState();
	}

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

/*********************************************************************************************
* state WeaponSprinting
* When the pawn is sprinting, they need to check if they should auto unequip the welder.
*********************************************************************************************/

simulated state WeaponSprinting
{
	simulated event Tick(float DeltaTime)
	{
		Global.Tick(DeltaTime);

		if (Instigator != none && Instigator.IsLocallyControlled())
		{
			if (bAutoUnequip)
			{
				TickAutoUnequip();
			}
		}
	}
}

defaultproperties
{
	InventoryGroup=IG_None
	bCanThrow=false
	bDropOnDeath=false
	bAutoUnequip=true

	PlayerViewOffset=(X=20.0,Y=10,Z=-10)

	FireTweenTime=0.2f
	WeldingRange=150.f
	ExtraWeldingRange=10
	FastenRate=68.f
	UnFastenRate=-110.f
	RepairRate=0.03f  //0.05f
	IdleWeldAnim=Idle_Weld
	WeldOpenAnim=Weld_On
	WeldCloseAnim=Weld_Off

	// Content
	PackageKey="Welder"
	FirstPersonMeshName="WEP_1P_Welder_MESH.Wep_1stP_Welder_Rig"
	FirstPersonAnimSetNames(0)="WEP_1P_Welder_ANIM.Wep_1st_Welder_Anim"
	AttachmentArchetypeName="WEP_Welder_ARCH.Welder_3P"
	MuzzleFlashTemplateName="WEP_Welder_ARCH.Wep_Welder_MuzzleFlash"

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

	// Ammo
	MagazineCapacity[0]=100
	SpareAmmoCapacity[0]=0
	bInfiniteSpareAmmo=true
	AmmoRechargeRate=0.08f
	bAllowClientAmmoTracking=false

	// Grouping
	GroupPriority=5
	WeaponSelectTexture=Texture2D'ui_weaponselect_tex.UI_WeaponSelect_Welder'

	// Weld
	FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Electricity'
	FiringStatesArray(DEFAULT_FIREMODE)=WeaponWelding
	WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Custom
	FireInterval(DEFAULT_FIREMODE)=+0.2
	AmmoCost(DEFAULT_FIREMODE)=7

	// Un-Weld
	FireModeIconPaths(ALTFIRE_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Electricity'
	FiringStatesArray(ALTFIRE_FIREMODE)=WeaponWelding
	WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_Custom
	FireInterval(ALTFIRE_FIREMODE)=+0.2
	AmmoCost(ALTFIRE_FIREMODE)=7

	// Fire Effects
	WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_Welder.Play_WEP_SA_Welder_Fire_Loop_M', FirstPersonCue=AkEvent'WW_WEP_SA_Welder.Play_WEP_SA_Welder_Fire_Loop_S')
	WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_Welder.Play_WEP_SA_Welder_Fire_Loop_M', FirstPersonCue=AkEvent'WW_WEP_SA_Welder.Play_WEP_SA_Welder_Fire_Loop_S')

	// BASH_FIREMODE
	InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_Welder'
	InstantHitDamage(BASH_FIREMODE)=20

	// Advanced (High RPM) Fire Effects
	bLoopingFireAnim(DEFAULT_FIREMODE)=true
	bLoopingFireAnim(ALTFIRE_FIREMODE)=true
	bLoopingFireSnd(DEFAULT_FIREMODE)=true
	bLoopingFireSnd(ALTFIRE_FIREMODE)=true
	WeaponFireLoopEndSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_Welder.Stop_WEP_SA_Welder_Fire_Loop_M', FirstPersonCue=AkEvent'WW_WEP_SA_Welder.Stop_WEP_SA_Welder_Fire_Loop_S')

    ScreenUIClass=class'KFGFxWorld_WelderScreen'

	AssociatedPerkClasses(0)=none

	bStorePreviouslyEquipped=false
}
