// Project Sword & Gun Copyright © 2021 Kevin Poretti


#include "Weapons/SNGRangedWeaponBase.h"

#include "Kismet/GameplayStatics.h"

/** Sets default values for this actor's properties. */
ASNGRangedWeaponBase::ASNGRangedWeaponBase()
{
	MuzzleSocketName = "Muzzle";
	TracerTargetName = "TraceEnd";
	
	RateOfFire = 60.0f;
	MaxAmmoCount = 6;

	VulnerableDamageMultiplier = 2.0f;
}

/**
 *	Function that sets up the weapon for firing. This sets up a timer which periodically calls the actual weapon
 *	firing functionality.
 */
void ASNGRangedWeaponBase::StartFire()
{
	// see if weapon is still in cooldown based on last time weapon was fired
	float FirstDelay = FMath::Max((LastFireTime + TimeBetweenShots) - GetWorld()->TimeSeconds, 0.0f);
	GetWorldTimerManager().SetTimer(TimerHandle_TimeBetweenShots, this, &ASNGRangedWeaponBase::Fire,
		TimeBetweenShots, true, FirstDelay);
}

/**
 *	Function that sets up the weapon for firing. This clears the timer previously set up by the StartFire function.
 */
void ASNGRangedWeaponBase::StopFire()
{
	GetWorldTimerManager().ClearTimer(TimerHandle_TimeBetweenShots);
}

/**
 *	Checks to see if this weapon has enough ammo in its "clip" to actually fire.
 *
 *	@returns true if the weapon can be fired and false otherwise
 */
bool ASNGRangedWeaponBase::CanFire() const
{
	return CurrentAmmoCount > 0;
}

/**
 *	Reloads this weapon.
 *
 *	NOTE(kevin): This description is supposed to be how this function behaves, however the concept of an ammo
 *	inventory does not exists yet. All weapons are treated as having infinite ammo in their reserves, so this always
 *	sets CurrentAmmoCount to MaxAmmoCount.
 *
 *	This sets the CurrentAmmoCount to either MaxAmmoCount or the remaining ammo count in the weapons owner's ammo
 *	inventory, whichever is smaller. This also subtracts the amount of ammo loaded into this weapon's clip from whatever
 *	was in the weapon owner's ammo inventory.
 */
void ASNGRangedWeaponBase::Reload()
{
	CurrentAmmoCount = MaxAmmoCount;
	OnReloaded.Broadcast(CurrentAmmoCount);
}

/**
 *	Checks to see if the weapon "clip" is not full and can actually be reloaded.
 *
 *	@returns true if the weapon can be reloaded and false otherwise 
 */
bool ASNGRangedWeaponBase::CanReload() const
{
	return CurrentAmmoCount < MaxAmmoCount;
}

/**
 *	Gets the current ammo count in this weapon's "clip"
 */
int32 ASNGRangedWeaponBase::GetCurrentAmmoCount() const
{
	return CurrentAmmoCount;
}

/**
 *	Plays impacts when this weapon successfully hits something.
 *
 *	This function is overriden from the SNGWeaponBase class to modify the ImpactTransform's rotation so that the
 *	impact effect is spawned with a rotation determined by the direction the weapon was fired from rather than the
 *	impact point's normal.
 *
 *	@param SurfaceType the type of the surface that was hit by this weapon
 *	@param ImpactTransform the location and rotation of the impact (rotation is usually the impacted normal direction)
 */
void ASNGRangedWeaponBase::PlayImpactEffects(EPhysicalSurface SurfaceType, FTransform ImpactTransform)
{
	FVector MuzzleLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName);
	FVector ShotDirection = ImpactTransform.GetLocation() - MuzzleLocation;
	ShotDirection.Normalize();
	FTransform NewImpactTransform(ShotDirection.Rotation(), ImpactTransform.GetLocation());
	
	Super::PlayImpactEffects(SurfaceType, NewImpactTransform);
}

/**
 *	Plays the weapon firing effects (muzzle flash and the tracer effect).
 *
 *	@param TraceEnd where the tracer effect should end. This is either the location this weapon impacted when it was
 *	fired or the end of this weapon's range if nothing was hit.
 */
void ASNGRangedWeaponBase::PlayFireEffect()
{
	if(MuzzleEffect)
	{
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleEffect, WeaponMesh->GetSocketLocation(MuzzleSocketName));
	}
}

/**
 *	Calls actual firing logic if this weapon is able to be fired and handles ammo subtraction.
 *
 *	This function is called via a timer periodically. The rate at which this function is called is determined by
 *	whatever RateOfFire variable is set to.
 *
 *	This function is not meant to be modified on a per class basis. If you want to modify the actual firing logic
 *	you should either override the DoFire_Implementation function in C++ or implement the DoFire event in Blueprints.
 */
void ASNGRangedWeaponBase::Fire()
{
	if(CanFire())
	{
		DoFire();
		
		LastFireTime = GetWorld()->TimeSeconds;
		CurrentAmmoCount = FMath::Clamp(--CurrentAmmoCount, 0, MaxAmmoCount);
		OnFired.Broadcast(CurrentAmmoCount);
	}
}

/**
 *	C++ implementation of the DoFire function, which is called in Fire if this weapon is actually able to be fired.
 *
 *	By default, this just plays this weapon's firing sound. You must override this function to do specific firing
 *	logic (line cast or spawning a projectile). 
 */
void ASNGRangedWeaponBase::DoFire_Implementation()
{
	if(FireSound)
	{
		UGameplayStatics::PlaySound2D(GetWorld(), FireSound);
	}
}

/** Called when the game starts or when spawned */
void ASNGRangedWeaponBase::BeginPlay()
{
	Super::BeginPlay();

	TimeBetweenShots = 60 / RateOfFire;
	CurrentAmmoCount = MaxAmmoCount;
}
