USWeaponHandlerComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SWeaponHandlerComponent.generated.h"

class ASWeapon;

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEquippedWeaponChangedSignature,  ASWeapon*, NewWeapon);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEquippedWeaponAmmoChangedSignature,  int, AmmoAmount);

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class COOPHORDEGAME_API USWeaponHandlerComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	USWeaponHandlerComponent();

private:
	void EquipWeapon(ASWeapon* NextWeapon);
	
protected:
	virtual void BeginPlay() override;
	
	UPROPERTY(ReplicatedUsing=OnRep_EquippedWeapon, BlueprintReadOnly);
	ASWeapon* EquippedWeapon;
	
	UPROPERTY(BlueprintReadOnly)
	TArray<ASWeapon*> OwnedWeapons;

	UPROPERTY(EditDefaultsOnly, Category="Weapons")
	int MaxNumOfWeaponsHeld;

	// Whether the EquippedWeapon should be automatically removed when picking up a new weapon with a full inventory
	UPROPERTY(EditDefaultsOnly, Category="Weapons")
	bool bRemoveWeaponIfInventoryFull;

	UFUNCTION()
	void OnRep_EquippedWeapon();
	
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	//Weapon switching has to happen on the server because OwnedWeapons is empty on clients
	UFUNCTION(Server, Reliable)
	void ServerSwitchWeapon(bool SwitchToNextWeapon);
	UFUNCTION(Server, Reliable)
	void ServerEquipWeapon(ASWeapon* Weapon);

public:
	// Used to update UI
	UPROPERTY(BlueprintAssignable, Category="Events")
	FOnEquippedWeaponChangedSignature OnWeaponChanged;
	// Used to update UI
	UPROPERTY(BlueprintAssignable, Category="Events")
	FOnEquippedWeaponAmmoChangedSignature OnAmmoAmountChanged;

	// Switch between weapon in OwnedWeapons 
	UFUNCTION(BlueprintCallable)
	void SwitchWeapon(bool SwitchToNextWeapon);
	UFUNCTION(BlueprintCallable, Category="Weapons")
	void AddWeapon(TSubclassOf<ASWeapon> NewWeapon);
	void RemoveWeapon(ASWeapon* WeaponToRemove);
	
	void StartFire();
	void StopFire();	
};

USWeaponHandlerComponent.cpp

#include "SWeaponHandlerComponent.h"
#include "SWeapon.h"
#include "Net/UnrealNetwork.h"


USWeaponHandlerComponent::USWeaponHandlerComponent()
{
	MaxNumOfWeaponsHeld = 2;
	bRemoveWeaponIfInventoryFull = true;
	SetIsReplicated(true);
}


void USWeaponHandlerComponent::BeginPlay()
{
	Super::BeginPlay();
}

void USWeaponHandlerComponent::OnRep_EquippedWeapon()
{
	OnWeaponChanged.Broadcast(EquippedWeapon);
}


void USWeaponHandlerComponent::AddWeapon(TSubclassOf<ASWeapon> NewWeapon)
{
	// NewWeapon is null, nothing to add
	if(NewWeapon == nullptr)
	{
		UE_LOG(LogTemp,Log,TEXT("NewWeapon is null"));
		return;
	}

	// Check if the player already has the weapon
	for (ASWeapon* Weapon : OwnedWeapons)
	{
		// Character already has weapon so give ammo instead
		if(Weapon->GetName().Contains(NewWeapon->GetName()))
		{
			Weapon->ReceiveAmmo(100);
			return;
		}
	}
	
	// Check if inventory is full
	if(OwnedWeapons.Num() >= MaxNumOfWeaponsHeld)
	{
		// Inventory is full, cannot hold anymore weapons 
		UE_LOG(LogTemp,Log,TEXT("Weapon inventory for %s is already full"), *GetOwner()->GetName());
		if(bRemoveWeaponIfInventoryFull)
		{
			RemoveWeapon(EquippedWeapon);
		}
		else
		{
			return;
		}
	}
	
	if(GetOwner()->HasAuthority())
	{
		FActorSpawnParameters SpawnParams;
		SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		
		ASWeapon* InstantiatedWeapon = GetWorld()->SpawnActor<ASWeapon>(NewWeapon, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
		OwnedWeapons.Add(InstantiatedWeapon);
		EquipWeapon(InstantiatedWeapon);
	}
}

void USWeaponHandlerComponent::RemoveWeapon(ASWeapon* WeaponToRemove)
{
	if(WeaponToRemove == nullptr)
		return;

	for (int i = OwnedWeapons.Num() - 1; i >= 0; i--)
	{
		if(OwnedWeapons[i] == WeaponToRemove)
		{
			OwnedWeapons.RemoveAt(i);
			return;
		}
	}

	UE_LOG(LogTemp,Log,TEXT("Player didnt have %s"), *WeaponToRemove->GetName());
}

void USWeaponHandlerComponent::SwitchWeapon(bool SwitchToNextWeapon)
{
	if(!GetOwner()->HasAuthority())
	{
		ServerSwitchWeapon(SwitchToNextWeapon);
		return;
	}

	//Get index of equipped weapon
	int EquippedWeaponIndex = 0;
	for(int i = 0; i < OwnedWeapons.Num(); i++)
	{
		if(OwnedWeapons[i] == EquippedWeapon)
		{
			EquippedWeaponIndex = i;
			break;
		}
	}

	// Validate new weapon index
	int NewWeaponIndex = EquippedWeaponIndex + (SwitchToNextWeapon ? 1 : -1);
	if(NewWeaponIndex < 0)
		NewWeaponIndex = OwnedWeapons.Num() - 1;
	if(NewWeaponIndex >= OwnedWeapons.Num())
		NewWeaponIndex = 0;

	if(OwnedWeapons[NewWeaponIndex])
		EquipWeapon(OwnedWeapons[NewWeaponIndex]);
}

void USWeaponHandlerComponent::EquipWeapon(ASWeapon* NextWeapon)
{
	if(!GetOwner()->HasAuthority())
	{
		ServerEquipWeapon(NextWeapon);
		OnWeaponChanged.Broadcast(NextWeapon);
		return;
	}

	for(int i = 0; i < OwnedWeapons.Num(); i++)
	{
		OwnedWeapons[i]->SetHidden(true);
	}
	if(EquippedWeapon)
	{
		EquippedWeapon->OnUnequip();
	}
	
	EquippedWeapon = NextWeapon;
	if(EquippedWeapon)
	{
		EquippedWeapon->SetHidden(false);
		OnWeaponChanged.Broadcast(EquippedWeapon);
		EquippedWeapon->OnEquip(this);
	}
}

void USWeaponHandlerComponent::ServerSwitchWeapon_Implementation(bool SwitchToNextWeapon)
{
	SwitchWeapon(SwitchToNextWeapon);
}


void USWeaponHandlerComponent::ServerEquipWeapon_Implementation(ASWeapon* Weapon)
{
	EquipWeapon(Weapon);
	OnWeaponChanged.Broadcast(Weapon);
}

void USWeaponHandlerComponent::StartFire()
{
	if(EquippedWeapon)
	{
		EquippedWeapon->StartFire();
	}
}

void USWeaponHandlerComponent::StopFire()
{
	if(EquippedWeapon)
	{
		EquippedWeapon->StopFire();
	}
}

void USWeaponHandlerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(USWeaponHandlerComponent, EquippedWeapon);
}