Initial Commit - Lesson 31 (Commit #1)

This commit is contained in:
Norman Lansing
2026-02-24 22:39:26 -05:00
commit 9591e7f503
4631 changed files with 1019212 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class FPSTemplateTarget : TargetRules
{
public FPSTemplateTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("FPSTemplate");
}
}

View File

@@ -0,0 +1,35 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class FPSTemplate : ModuleRules
{
public FPSTemplate(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "PhysicsCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "GameplayTags", "Slate", "SlateCore" });
// Adds in the plugin for GameLiftServerSDK if it is the server build.
if (Target.Type == TargetType.Server)
{
PublicDependencyModuleNames.Add("GameLiftServerSDK");
}
else
{
PublicDefinitions.Add("WITH_GAMELIFT=0");
}
bEnableExceptions = true;
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FPSTemplate.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, FPSTemplate, "FPSTemplate" );

View File

@@ -0,0 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#define ECC_Weapon ECollisionChannel::ECC_GameTraceChannel1

View File

@@ -0,0 +1,415 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ShooterCharacter.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "Character/ShooterHealthComponent.h"
#include "Data/WeaponData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Combat/CombatComponent.h"
#include "Components/CapsuleComponent.h"
#include "Elimination/EliminationComponent.h"
#include "FPSTemplate/FPSTemplate.h"
#include "Game/ShooterGameModeBase.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "Player/ShooterPlayerController.h"
#include "Weapon/Weapon.h"
AShooterCharacter::AShooterCharacter()
{
PrimaryActorTick.bCanEverTick = true;
SpringArm = CreateDefaultSubobject<USpringArmComponent>("SpringArm");
SpringArm->SetupAttachment(GetRootComponent());
SpringArm->TargetArmLength = 0.f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 15.f;
SpringArm->bUsePawnControlRotation = true;
FirstPersonCamera = CreateDefaultSubobject<UCameraComponent>("FirstPersonCamera");
FirstPersonCamera->SetupAttachment(SpringArm);
FirstPersonCamera->bUsePawnControlRotation = false;
Mesh1P = CreateDefaultSubobject<USkeletalMeshComponent>("Mesh1P");
Mesh1P->SetupAttachment(FirstPersonCamera);
Mesh1P->bOnlyOwnerSee = true;
Mesh1P->bOwnerNoSee = false;
Mesh1P->bCastDynamicShadow = false;
Mesh1P->bReceivesDecals = false;
Mesh1P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
Mesh1P->PrimaryComponentTick.TickGroup = TG_PrePhysics;
GetMesh()->bOnlyOwnerSee = false;
GetMesh()->bOwnerNoSee = true;
GetMesh()->bReceivesDecals = false;
Combat = CreateDefaultSubobject<UCombatComponent>("Combat");
Combat->SetIsReplicated(true);
HealthComponent = CreateDefaultSubobject<UShooterHealthComponent>("Health");
HealthComponent->SetIsReplicated(true);
EliminationComponent = CreateDefaultSubobject<UEliminationComponent>("EliminationComponent");
EliminationComponent->SetIsReplicated(false);
DefaultFieldOfView = 90.f;
TurningStatus = ETurningInPlace::NotTurning;
bWeaponFirstReplicated = false;
}
void AShooterCharacter::OnDeathStarted(AActor* DyingActor, AActor* DeathInstigator)
{
int32 Index = FMath::RandRange(0, DeathMontages.Num() - 1);
UAnimMontage* DeathMontage = DeathMontages[Index];
AShooterPlayerController* VictimController = Cast<AShooterPlayerController>(GetController());
if (HasAuthority() && IsValid(VictimController))
{
if (IsValid(Combat))
{
Combat->DestroyInventory();
}
if (AShooterGameModeBase* GameMode = Cast<AShooterGameModeBase>(UGameplayStatics::GetGameMode(this)))
{
ACharacter* InstigatorCharacter = Cast<ACharacter>(DeathInstigator);
if (IsValid(InstigatorCharacter))
{
APlayerController* InstigatorController = Cast<APlayerController>(InstigatorCharacter->GetController());
if (IsValid(InstigatorController))
{
GameMode->StartPlayerElimination(DeathMontage->GetPlayLength(), this, VictimController, InstigatorController);
}
}
}
}
if (GetNetMode() != NM_DedicatedServer)
{
DeathEffects(DeathInstigator, DeathMontage);
if (IsValid(VictimController))
{
DisableInput(VictimController);
if (IsLocallyControlled())
{
VictimController->bPawnAlive = false;
}
}
}
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Weapon, ECR_Ignore);
GetMesh()->SetCollisionResponseToChannel(ECC_Weapon, ECR_Ignore);
}
void AShooterCharacter::BeginPlay()
{
Super::BeginPlay();
if (IsValid(HealthComponent))
{
HealthComponent->OnDeathStarted.AddDynamic(this, &AShooterCharacter::OnDeathStarted);
}
AShooterPlayerController* VictimController = Cast<AShooterPlayerController>(GetController());
if (IsLocallyControlled() && IsValid(VictimController))
{
VictimController->bPawnAlive = true;
}
StartingAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f);
if (IsValid(Combat) && IsValid(EliminationComponent) && HasAuthority())
{
Combat->OnRoundReported.AddDynamic(EliminationComponent, &UEliminationComponent::OnRoundReported);
}
FTimerDelegate InitializeWidgetsDelegate;
InitializeWidgetsDelegate.BindLambda([&]
{
if (IsValid(Combat) && IsLocallyControlled())
{
Combat->InitializeWeaponWidgets();
}
});
GetWorldTimerManager().SetTimer(InitiializeWidgets_Timer, InitializeWidgetsDelegate, 0.5f, false);
}
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* ShooterInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
ShooterInputComponent->BindAction(CycleWeaponAction, ETriggerEvent::Started, this, &AShooterCharacter::Input_CycleWeapon);
ShooterInputComponent->BindAction(FireWeaponAction, ETriggerEvent::Started, this, &AShooterCharacter::Input_FireWeapon_Pressed);
ShooterInputComponent->BindAction(FireWeaponAction, ETriggerEvent::Completed, this, &AShooterCharacter::Input_FireWeapon_Released);
ShooterInputComponent->BindAction(ReloadWeaponAction, ETriggerEvent::Started, this, &AShooterCharacter::Input_ReloadWeapon);
ShooterInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &AShooterCharacter::Input_Aim_Pressed);
ShooterInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &AShooterCharacter::Input_Aim_Released);
}
void AShooterCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (IsValid(Combat))
{
Combat->SpawnDefaultInventory();
}
APlayerController* PlayerController = Cast<APlayerController>(NewController);
if (IsValid(PlayerController))
{
EnableInput(PlayerController);
}
}
void AShooterCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FVector Velocity = GetVelocity();
Velocity.Z = 0.f;
float Speed = Velocity.Size();
bool bIsInAir = GetCharacterMovement()->IsFalling();
if (Speed == 0.f && !bIsInAir) // standing still, not jumping
{
FRotator CurrentAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f);
FRotator DeltaAimRotation = UKismetMathLibrary::NormalizedDeltaRotator(CurrentAimRotation, StartingAimRotation);
AO_Yaw = DeltaAimRotation.Yaw;
if (TurningStatus == ETurningInPlace::NotTurning)
{
InterpAO_Yaw = AO_Yaw;
}
TurnInPlace(DeltaTime);
}
if (Speed > 0.f || bIsInAir) // running, or jumping
{
StartingAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f);
AO_Yaw = 0.f;
bUseControllerRotationYaw = true;
FRotator AimRotation = GetBaseAimRotation();
FRotator MovementRotation = UKismetMathLibrary::MakeRotFromX(GetVelocity());
MovementOffsetYaw = UKismetMathLibrary::NormalizedDeltaRotator(MovementRotation,AimRotation).Yaw;
TurningStatus = ETurningInPlace::NotTurning;
}
if (IsValid(Combat) && IsValid(Combat->CurrentWeapon) && IsValid(Combat->CurrentWeapon->GetMesh3P()))
{
FABRIK_SocketTransform = Combat->CurrentWeapon->GetMesh3P()->GetSocketTransform(FName("FABRIK_Socket"), RTS_World);
FVector OutLocation;
FRotator OutRotation;
GetMesh()->TransformToBoneSpace(FName("hand_r"), FABRIK_SocketTransform.GetLocation(), FABRIK_SocketTransform.GetRotation().Rotator(), OutLocation, OutRotation);
FABRIK_SocketTransform.SetLocation(OutLocation);
FABRIK_SocketTransform.SetRotation(OutRotation.Quaternion());
}
AO_Yaw *= -1.f;
}
void AShooterCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
}
void AShooterCharacter::BeginDestroy()
{
Super::BeginDestroy();
if (IsValid(Combat))
{
Combat->DestroyInventory();
}
}
void AShooterCharacter::UnPossessed()
{
if (IsValid(Combat))
{
Combat->DestroyInventory();
}
Super::UnPossessed();
}
void AShooterCharacter::TurnInPlace(float DeltaTime)
{
if (AO_Yaw > 90.f)
{
TurningStatus = ETurningInPlace::Right;
}
else if (AO_Yaw < -90.f)
{
TurningStatus = ETurningInPlace::Left;
}
if (TurningStatus != ETurningInPlace::NotTurning)
{
InterpAO_Yaw = FMath::FInterpTo(InterpAO_Yaw, 0.f, DeltaTime, 4.f);
AO_Yaw = InterpAO_Yaw;
if (FMath::Abs(AO_Yaw) < 5.f)
{
TurningStatus = ETurningInPlace::NotTurning;
StartingAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f);
}
}
}
FName AShooterCharacter::GetWeaponAttachPoint_Implementation(const FGameplayTag& WeaponType) const
{
checkf(Combat->WeaponData, TEXT("No Weapon Data Asset - Please fill out BP_ShooterCharacter"));
return Combat->WeaponData->GripPoints.FindChecked(WeaponType);
}
USkeletalMeshComponent* AShooterCharacter::GetSpecifcPawnMesh_Implementation(bool WantFirstPerson) const
{
return WantFirstPerson == true ? Mesh1P.Get() : GetMesh();
}
USkeletalMeshComponent* AShooterCharacter::GetPawnMesh_Implementation() const
{
return IsLocalFirstPerson() ? Mesh1P.Get() : GetMesh();
}
bool AShooterCharacter::IsFirstPerson_Implementation() const
{
return IsLocalFirstPerson();
}
bool AShooterCharacter::DoDamage_Implementation(float DamageAmount, AActor* DamageInstigator)
{
if (!IsValid(HealthComponent)) return false;
const bool bLethal = HealthComponent->ChangeHealthByAmount(-DamageAmount, DamageInstigator);
const int32 MontageSelection = FMath::RandRange(0, HitReacts.Num() - 1);
Multicast_HitReact(MontageSelection);
return bLethal;
}
void AShooterCharacter::AddAmmo_Implementation(const FGameplayTag& WeaponType, int32 AmmoAmount)
{
if (HasAuthority() && IsValid(Combat))
{
Combat->AddAmmo(WeaponType, AmmoAmount);
}
}
AWeapon* AShooterCharacter::GetCurrentWeapon_Implementation()
{
if (!IsValid(Combat)) return nullptr;
return Combat->CurrentWeapon;
}
int32 AShooterCharacter::GetCarriedAmmo_Implementation()
{
if (!IsValid(Combat)) return -1;
return Combat->CarriedAmmo;
}
void AShooterCharacter::InitializeWidgets_Implementation()
{
if (!IsValid(Combat)) return;
return Combat->InitializeWeaponWidgets();
}
void AShooterCharacter::Multicast_HitReact_Implementation(int32 MontageIndex)
{
if (GetNetMode() != NM_DedicatedServer && !IsLocallyControlled())
{
GetMesh()->GetAnimInstance()->Montage_Play(HitReacts[MontageIndex]);
}
}
FRotator AShooterCharacter::GetFixedAimRotation() const
{
FRotator AimRotation = GetBaseAimRotation();
if (AimRotation.Pitch > 90.f && !IsLocallyControlled())
{
// map pitch from [270, 360) to [-90, 0)
const FVector2D InRange(270.f, 360.f);
const FVector2D OutRange(-90.f, 0.f);
AimRotation.Pitch = FMath::GetMappedRangeValueClamped(InRange, OutRange, AimRotation.Pitch);
}
return AimRotation;
}
bool AShooterCharacter::IsLocalFirstPerson() const
{
return IsValid(Controller) && Controller->IsLocalPlayerController();
}
void AShooterCharacter::Input_CycleWeapon()
{
Combat->Initiate_CycleWeapon();
}
void AShooterCharacter::Notify_CycleWeapon_Implementation()
{
Combat->Notify_CycleWeapon();
}
void AShooterCharacter::Notify_ReloadWeapon_Implementation()
{
Combat->Notify_ReloadWeapon();
}
void AShooterCharacter::Input_FireWeapon_Pressed()
{
Combat->Initiate_FireWeapon_Pressed();
}
void AShooterCharacter::Input_FireWeapon_Released()
{
Combat->Initiate_FireWeapon_Released();
}
void AShooterCharacter::Input_ReloadWeapon()
{
Combat->Initiate_ReloadWeapon();
}
void AShooterCharacter::Input_Aim_Pressed()
{
Combat->Initiate_AimPressed();
OnAim(true);
}
void AShooterCharacter::Input_Aim_Released()
{
Combat->Initiate_AimReleased();
OnAim(false);
}
void AShooterCharacter::Initiate_Crouch_Implementation()
{
GetCharacterMovement()->bWantsToCrouch = !GetCharacterMovement()->bWantsToCrouch;
}
void AShooterCharacter::Initiate_Jump_Implementation()
{
if (GetCharacterMovement()->bWantsToCrouch)
{
GetCharacterMovement()->bWantsToCrouch = false;
}
else
{
Jump();
}
}
bool AShooterCharacter::IsDeadOrDying_Implementation()
{
if (IsValid(HealthComponent))
{
return HealthComponent->IsDeadOrDying();
}
return true;
}
void AShooterCharacter::WeaponReplicated_Implementation()
{
if (!bWeaponFirstReplicated)
{
bWeaponFirstReplicated = true;
OnWeaponFirstReplicated.Broadcast(Combat->CurrentWeapon);
}
}

View File

@@ -0,0 +1,150 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ShooterHealthComponent.h"
#include "Net/UnrealNetwork.h"
UShooterHealthComponent::UShooterHealthComponent()
{
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
Health = 100.f;
MaxHealth = 100.f;
DeathState = EDeathState::NotDead;
}
void UShooterHealthComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UShooterHealthComponent, DeathState);
DOREPLIFETIME_CONDITION(UShooterHealthComponent, Health, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(UShooterHealthComponent, MaxHealth, COND_OwnerOnly);
}
float UShooterHealthComponent::GetHealthNormalized() const
{
return ((MaxHealth > 0.0f) ? (Health / MaxHealth) : 0.0f);
}
void UShooterHealthComponent::StartDeath(AActor* Instigator)
{
if (DeathState != EDeathState::NotDead)
{
return;
}
DeathState = EDeathState::DeathStarted;
AActor* Owner = GetOwner();
check(Owner);
OnDeathStarted.Broadcast(Owner, Instigator);
Owner->ForceNetUpdate();
}
void UShooterHealthComponent::FinishDeath(AActor* Instigator)
{
if (DeathState != EDeathState::DeathStarted)
{
return;
}
DeathState = EDeathState::DeathFinished;
AActor* Owner = GetOwner();
check(Owner);
OnDeathFinished.Broadcast(Owner, Instigator);
Owner->ForceNetUpdate();
}
void UShooterHealthComponent::BeginPlay()
{
Super::BeginPlay();
}
bool UShooterHealthComponent::ChangeHealthByAmount(float Amount, AActor* Instigator)
{
float OldValue = Health;
Health = FMath::Clamp(Health + Amount, 0.f, MaxHealth);
OnHealthChanged.Broadcast(this, OldValue, Health, Instigator);
if (Health <= 0.f)
{
StartDeath(Instigator);
return true;
}
return false;
}
void UShooterHealthComponent::ChangeMaxHealthByAmount(float Amount, AActor* Instigator)
{
float OldValue = MaxHealth;
MaxHealth += Amount;
OnHealthChanged.Broadcast(this, OldValue, MaxHealth, Instigator);
}
void UShooterHealthComponent::HandleOutOfHealth()
{
}
void UShooterHealthComponent::OnRep_DeathState(EDeathState OldDeathState)
{
const EDeathState NewDeathState = DeathState;
// Revert the death state for now since we rely on StartDeath and FinishDeath to change it.
DeathState = OldDeathState;
if (OldDeathState > NewDeathState)
{
// The server is trying to set us back but we've already predicted past the server state.
UE_LOG(LogTemp, Warning, TEXT("HealthComponent: Predicted past server death state [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
return;
}
if (OldDeathState == EDeathState::NotDead)
{
if (NewDeathState == EDeathState::DeathStarted)
{
StartDeath(nullptr);
}
else if (NewDeathState == EDeathState::DeathFinished)
{
StartDeath(nullptr);
FinishDeath(nullptr);
}
else
{
UE_LOG(LogTemp, Error, TEXT("HealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
}
}
else if (OldDeathState == EDeathState::DeathStarted)
{
if (NewDeathState == EDeathState::DeathFinished)
{
FinishDeath(nullptr);
}
else
{
UE_LOG(LogTemp, Error, TEXT("HealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
}
}
}
void UShooterHealthComponent::OnRep_Health(float OldValue)
{
OnHealthChanged.Broadcast(this, OldValue, Health, nullptr);
}
void UShooterHealthComponent::OnRep_MaxHealth(float OldValue)
{
OnMaxHealthChanged.Broadcast(this, OldValue, MaxHealth, nullptr);
}

View File

@@ -0,0 +1,645 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Combat/CombatComponent.h"
#include "Character/ShooterCharacter.h"
#include "Data/WeaponData.h"
#include "Net/UnrealNetwork.h"
#include "Weapon/Weapon.h"
#include "TimerManager.h"
#include "FPSTemplate/FPSTemplate.h"
#include "Kismet/GameplayStatics.h"
UCombatComponent::UCombatComponent()
{
PrimaryComponentTick.bCanEverTick = true;
CarriedAmmo = 0;
Local_WeaponIndex = 0;
bTriggerPressed = false;
bAiming = false;
TraceLength = 20'000.f;
}
void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(UCombatComponent, CarriedAmmo, COND_OwnerOnly);
DOREPLIFETIME(UCombatComponent, Inventory);
DOREPLIFETIME(UCombatComponent, CurrentWeapon);
DOREPLIFETIME_CONDITION(UCombatComponent, bAiming, COND_SkipOwner);
}
void UCombatComponent::AddAmmo(FGameplayTag WeaponType, int32 AmmoAmount)
{
if (GetOwner()->HasAuthority() && IsValid(CurrentWeapon))
{
int32 NewAmmo = CarriedAmmoMap.FindChecked(WeaponType) + AmmoAmount;
CarriedAmmoMap[WeaponType] = NewAmmo;
if (CurrentWeapon->WeaponType.MatchesTagExact(WeaponType))
{
CarriedAmmo = NewAmmo;
if (CurrentWeapon->Ammo == 0 && NewAmmo > 0)
{
Server_ReloadWeapon(true);
}
}
OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo);
}
}
void UCombatComponent::InitializeWeaponWidgets() const
{
if (IsValid(CurrentWeapon))
{
OnReticleChanged.Broadcast(CurrentWeapon->GetReticleDynamicMaterialInstance(), CurrentWeapon->ReticleParams, Local_PlayerHitResult.bHitPlayer);
OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo);
}
}
void UCombatComponent::SpawnDefaultInventory()
{
AActor* OwningActor = GetOwner();
if (!IsValid(OwningActor)) return;
if (OwningActor->GetLocalRole() < ROLE_Authority)
{
return;
}
for (TSubclassOf<AWeapon>& WeaponClass : DefaultInventoryClasses)
{
FActorSpawnParameters SpawnInfo;
SpawnInfo.Instigator = Cast<APawn>(OwningActor);
SpawnInfo.Owner = OwningActor;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AWeapon* NewWeapon = GetWorld()->SpawnActor<AWeapon>(WeaponClass, SpawnInfo);
CarriedAmmoMap.Add(NewWeapon->WeaponType, NewWeapon->StartingCarriedAmmo);
AddWeapon(NewWeapon);
}
// equip first weapon in inventory
if (Inventory.Num() > 0)
{
EquipWeapon(Inventory[0]);
CarriedAmmo = CarriedAmmoMap.FindChecked(Inventory[0]->WeaponType);
}
}
void UCombatComponent::DestroyInventory()
{
for (AWeapon* Weapon : Inventory)
{
if (IsValid(Weapon))
{
Weapon->Destroy();
}
}
}
void UCombatComponent::OnRep_CurrentWeapon(AWeapon* LastWeapon)
{
SetCurrentWeapon(CurrentWeapon, LastWeapon);
check(GetOwner());
check(GetOwner()->Implements<UPlayerInterface>());
IPlayerInterface::Execute_WeaponReplicated(GetOwner());
}
void UCombatComponent::ServerEquipWeapon_Implementation(AWeapon* NewWeapon)
{
EquipWeapon(NewWeapon);
}
void UCombatComponent::BeginPlay()
{
Super::BeginPlay();
}
void UCombatComponent::OnRep_CarriedAmmo()
{
if (IsValid(CurrentWeapon))
{
OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo);
}
}
void UCombatComponent::SetCurrentWeapon(AWeapon* NewWeapon, AWeapon* LastWeapon)
{
AWeapon* LocalLastWeapon = nullptr;
if (IsValid(LastWeapon))
{
LocalLastWeapon = LastWeapon;
}
else if (NewWeapon != CurrentWeapon)
{
LocalLastWeapon = CurrentWeapon;
}
// unequip previous
if (IsValid(LocalLastWeapon))
{
LocalLastWeapon->OnUnEquip();
}
CurrentWeapon = NewWeapon;
APawn* OwningPawn = Cast<APawn>(GetOwner());
if (IsValid(OwningPawn) && OwningPawn->HasAuthority() && IsValid(CurrentWeapon))
{
CarriedAmmo = CarriedAmmoMap.FindChecked(CurrentWeapon->WeaponType);
}
// equip new one
if (IsValid(NewWeapon))
{
NewWeapon->SetOwningPawn(Cast<APawn>(GetOwner()));
NewWeapon->OnEquip(LastWeapon);
}
if (IsValid(CurrentWeapon) && CurrentWeapon->Ammo == 0 && CarriedAmmo > 0 && IsValid(OwningPawn) && OwningPawn->IsLocallyControlled())
{
Local_ReloadWeapon();
Server_ReloadWeapon();
}
}
void UCombatComponent::AddWeapon(AWeapon* Weapon)
{
if (IsValid(Weapon) && IsValid(GetOwner()) && GetOwner()->GetLocalRole() == ROLE_Authority)
{
Weapon->OnEnterInventory(Cast<APawn>(GetOwner()));
Inventory.AddUnique(Weapon);
}
}
void UCombatComponent::EquipWeapon(AWeapon* Weapon)
{
if (!IsValid(Weapon) || !IsValid(GetOwner())) return;
if (GetOwner()->GetLocalRole() == ROLE_Authority)
{
SetCurrentWeapon(Weapon, CurrentWeapon);
}
else
{
ServerEquipWeapon(Weapon);
}
}
void UCombatComponent::Initiate_CycleWeapon()
{
if (!IsValid(CurrentWeapon)) return;
if (CurrentWeapon->GetWeaponState() == EWeaponState::Equipping) return;
if (!IsValid(GetOwner())) return;
if (GetOwner()->Implements<UPlayerInterface>() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return;
AdvanceWeaponIndex();
Local_CycleWeapon(Local_WeaponIndex);
Server_CycleWeapon(Local_WeaponIndex);
}
void UCombatComponent::Notify_CycleWeapon()
{
if (!IsValid(CurrentWeapon)) return;
CurrentWeapon->SetWeaponState(EWeaponState::Idle);
AWeapon* NextWeapon = Inventory[Local_WeaponIndex];
if (IsValid(NextWeapon))
{
EquipWeapon(NextWeapon);
}
CurrentWeapon->SetWeaponState(EWeaponState::Equipping);
}
void UCombatComponent::Local_CycleWeapon(const int32 WeaponIndex)
{
if (!IsValid(GetOwner()) || !GetOwner()->Implements<UPlayerInterface>()) return;
const AWeapon* NextWeapon = Inventory[WeaponIndex];
if (!IsValid(NextWeapon) || !IsValid(WeaponData)) return;
const FMontageData& FirstPersonMontages = WeaponData->FirstPersonMontages.FindChecked(NextWeapon->WeaponType);
USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true);
if (IsValid(Mesh1P))
{
Mesh1P->GetAnimInstance()->Montage_Play(FirstPersonMontages.EquipMontage);
Mesh1P->GetAnimInstance()->OnMontageBlendingOut.AddDynamic(this, &UCombatComponent::BlendOut_CycleWeapon);
}
const FMontageData& ThirdPersonMontages = WeaponData->ThirdPersonMontages.FindChecked(NextWeapon->WeaponType);
USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false);
if (IsValid(Mesh3P))
{
Mesh3P->GetAnimInstance()->Montage_Play(ThirdPersonMontages.EquipMontage);
}
if (IsValid(CurrentWeapon))
{
CurrentWeapon->SetWeaponState(EWeaponState::Equipping);
}
}
void UCombatComponent::Multicast_CycleWeapon_Implementation(const int32 WeaponIndex)
{
if (!IsValid(Cast<APawn>(GetOwner())) || Cast<APawn>(GetOwner())->IsLocallyControlled()) return;
Local_WeaponIndex = WeaponIndex;
Local_CycleWeapon(WeaponIndex);
}
void UCombatComponent::Server_CycleWeapon_Implementation(const int32 WeaponIndex)
{
Local_WeaponIndex = WeaponIndex;
Multicast_CycleWeapon(WeaponIndex);
}
int32 UCombatComponent::AdvanceWeaponIndex()
{
if (Inventory.Num() >= 2)
{
Local_WeaponIndex = (Local_WeaponIndex + 1) % Inventory.Num();
}
return Local_WeaponIndex;
}
void UCombatComponent::BlendOut_CycleWeapon(UAnimMontage* Montage, bool bInterrupted)
{
UAnimInstance* AnimInstance = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true)->GetAnimInstance();
if (IsValid(AnimInstance) && AnimInstance->OnMontageBlendingOut.IsAlreadyBound(this, &UCombatComponent::BlendOut_CycleWeapon))
{
AnimInstance->OnMontageBlendingOut.RemoveDynamic(this, &UCombatComponent::BlendOut_CycleWeapon);
}
if (!IsValid(CurrentWeapon)) return;
CurrentWeapon->SetWeaponState(EWeaponState::Idle);
OnReticleChanged.Broadcast(CurrentWeapon->GetReticleDynamicMaterialInstance(), CurrentWeapon->ReticleParams, Local_PlayerHitResult.bHitPlayer);
OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo);
if (bTriggerPressed && CurrentWeapon->FireType == EFireType::Auto && CurrentWeapon->Ammo > 0)
{
Local_FireWeapon();
}
}
void UCombatComponent::Initiate_FireWeapon_Pressed()
{
bTriggerPressed = true;
if (!IsValid(GetOwner())) return;
if (GetOwner()->Implements<UPlayerInterface>() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner()))
{
bTriggerPressed = false;
return;
}
if (!IsValid(CurrentWeapon)) return;
if (CurrentWeapon->GetWeaponState() == EWeaponState::Idle && CurrentWeapon->Ammo > 0)
{
Local_FireWeapon();
}
}
void UCombatComponent::Initiate_FireWeapon_Released()
{
bTriggerPressed = false;
}
void UCombatComponent::Local_FireWeapon()
{
if (!IsValid(WeaponData) || !IsValid(CurrentWeapon)) return;
UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage;
USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true);
if (IsValid(Montage1P) && IsValid(Mesh1P))
{
Mesh1P->GetAnimInstance()->Montage_Play(Montage1P);
}
UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage;
USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false);
if (IsValid(Montage3P) && IsValid(Mesh3P))
{
Mesh3P->GetAnimInstance()->Montage_Play(Montage3P);
}
APawn* OwningPawn = Cast<APawn>(GetOwner());
if (IsValid(OwningPawn) && OwningPawn->IsLocallyControlled())
{
GetWorld()->GetTimerManager().SetTimer(FireTimer, this, &UCombatComponent::FireTimerFinished, CurrentWeapon->FireTime);
FHitResult Hit;
FVector TraceStart = HitScanTrace(5.f, Hit);
// Tell the weapon the impact point, normal, and physical material
EPhysicalSurface SurfaceType = Hit.PhysMaterial.IsValid(false) ? Hit.PhysMaterial->SurfaceType.GetValue() : EPhysicalSurface::SurfaceType1;
CurrentWeapon->Local_Fire(Hit.ImpactPoint, Hit.ImpactNormal, SurfaceType, true);
// Send the server the hit info.
const bool bHitPlayer = IsValid(Hit.GetActor()) ? Hit.GetActor()->Implements<UPlayerInterface>() : false;
const bool bHeadShot = Hit.BoneName == "head";
OnRoundFired.Broadcast(CurrentWeapon->Ammo, CurrentWeapon->MagCapacity, CarriedAmmo);
if (GetNetMode() == NM_Standalone) return;
Server_FireWeapon(TraceStart, Hit, bHitPlayer, bHeadShot);
}
}
void UCombatComponent::Server_FireWeapon_Implementation(const FVector_NetQuantize& TraceStart, const FHitResult& Impact, bool bScoredHit, bool bHeadShot)
{
// Do your server-side rewind validation here...
if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return;
float Damage = bHeadShot ? CurrentWeapon->HeadShotDamage : CurrentWeapon->Damage;
bool bLethal = false;
bool bHit = false;
if (IsValid(Impact.GetActor()) && Impact.GetActor()->Implements<UPlayerInterface>())
{
bLethal = IPlayerInterface::Execute_DoDamage(Impact.GetActor(), Damage, GetOwner());
bHit = true;
}
else
{
bHit = false;
}
OnRoundReported.Broadcast(GetOwner(), Impact.GetActor(), bHit, bHeadShot, bLethal);
if (GetNetMode() != NM_ListenServer || !Cast<APawn>(GetOwner())->IsLocallyControlled())
{
// We still need to update ammo server-side for non-hosting player-controlled proxies on a listen server
CurrentWeapon->Auth_Fire();
}
Multicast_FireWeapon(Impact, CurrentWeapon->Ammo);
}
void UCombatComponent::Multicast_FireWeapon_Implementation(const FHitResult& Impact, int32 AuthAmmo)
{
if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return;
if (Cast<APawn>(GetOwner())->IsLocallyControlled())
{
CurrentWeapon->Rep_Fire(AuthAmmo);
}
else
{
EPhysicalSurface SurfaceType = Impact.PhysMaterial.IsValid(false) ? Impact.PhysMaterial->SurfaceType.GetValue() : SurfaceType1;
if (IsValid(CurrentWeapon))
{
CurrentWeapon->Local_Fire(Impact.ImpactPoint, Impact.ImpactNormal, SurfaceType, false);
if (IsValid(WeaponData))
{
UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage;
USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true);
if (IsValid(Mesh1P) && IsValid(Montage1P))
{
Mesh1P->GetAnimInstance()->Montage_Play(Montage1P);
}
UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage;
USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false);
if (IsValid(Mesh3P) && IsValid(Montage3P))
{
Mesh3P->GetAnimInstance()->Montage_Play(Montage3P);
}
}
}
}
}
void UCombatComponent::FireTimerFinished()
{
if (!IsValid(GetOwner())) return;
if (GetOwner()->Implements<UPlayerInterface>() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner()))
{
bTriggerPressed = false;
return;
}
if (!IsValid(CurrentWeapon)) return;
if (CurrentWeapon->Ammo == 0 && CarriedAmmo > 0 && Cast<APawn>(GetOwner())->IsLocallyControlled())
{
Local_ReloadWeapon();
Server_ReloadWeapon();
return;
}
if (CurrentWeapon->FireType == EFireType::Auto && bTriggerPressed && CurrentWeapon->Ammo > 0)
{
Local_FireWeapon();
return;
}
CurrentWeapon->SetWeaponState(EWeaponState::Idle);
}
void UCombatComponent::Initiate_ReloadWeapon()
{
if (!IsValid(CurrentWeapon)) return;
if (CurrentWeapon->GetWeaponState() == EWeaponState::Equipping || CurrentWeapon->GetWeaponState() == EWeaponState::Reloading) return;
if (CurrentWeapon->Ammo == CurrentWeapon->MagCapacity || CarriedAmmo == 0) return;
if (!IsValid(GetOwner())) return;
if (GetOwner()->Implements<UPlayerInterface>() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return;
Local_ReloadWeapon();
Server_ReloadWeapon();
}
void UCombatComponent::Local_ReloadWeapon()
{
if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return;
UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage;
USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true);
if (IsValid(Montage1P) && IsValid(Mesh1P))
{
Mesh1P->GetAnimInstance()->Montage_Play(Montage1P);
}
UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage;
USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false);
if (IsValid(Montage3P) && IsValid(Mesh3P))
{
Mesh3P->GetAnimInstance()->Montage_Play(Montage3P);
}
UAnimMontage* WeaponMontage = WeaponData->WeaponMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage;
if (IsValid(CurrentWeapon->GetMesh1P()) && IsValid(CurrentWeapon->GetMesh3P()))
{
CurrentWeapon->GetMesh1P()->GetAnimInstance()->Montage_Play(WeaponMontage);
CurrentWeapon->GetMesh3P()->GetAnimInstance()->Montage_Play(WeaponMontage);
}
CurrentWeapon->SetWeaponState(EWeaponState::Reloading);
}
void UCombatComponent::Server_ReloadWeapon_Implementation(bool bLocalOwnerReload)
{
Multicast_ReloadWeapon(CurrentWeapon->Ammo, CarriedAmmo, bLocalOwnerReload);
}
void UCombatComponent::Multicast_ReloadWeapon_Implementation(int32 NewWeaponAmmo, int32 NewCarriedAmmo, bool bLocalOwnerReload)
{
Local_ReloadWeapon();
}
void UCombatComponent::Client_ReloadWeapon_Implementation(int32 NewWeaponAmmo, int32 NewCarriedAmmo)
{
if (!IsValid(GetOwner()) || !IsValid(CurrentWeapon)) return;
if (Cast<APawn>(GetOwner())->IsLocallyControlled())
{
CurrentWeapon->Ammo = NewWeaponAmmo;
CarriedAmmo = NewCarriedAmmo;
OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo);
}
}
void UCombatComponent::Notify_ReloadWeapon()
{
if (!IsValid(CurrentWeapon)) return;
if (GetNetMode() == NM_ListenServer || GetNetMode() == NM_DedicatedServer || GetNetMode() == NM_Standalone)
{
int32 EmptySpace = CurrentWeapon->MagCapacity - CurrentWeapon->Ammo;
int32 AmountToRefill = FMath::Min(EmptySpace, CarriedAmmo);
CurrentWeapon->Ammo += AmountToRefill;
CarriedAmmoMap[CurrentWeapon->WeaponType] = CarriedAmmoMap[CurrentWeapon->WeaponType] - AmountToRefill;
CarriedAmmo = CarriedAmmoMap[CurrentWeapon->WeaponType];
Client_ReloadWeapon(CurrentWeapon->Ammo, CarriedAmmo);
}
CurrentWeapon->SetWeaponState(EWeaponState::Idle);
if (bTriggerPressed && CurrentWeapon->FireType == EFireType::Auto && CurrentWeapon->Ammo > 0)
{
Local_FireWeapon();
}
}
void UCombatComponent::Initiate_AimPressed()
{
if (!IsValid(GetOwner())) return;
if (GetOwner()->Implements<UPlayerInterface>() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return;
Local_Aim(true);
Server_Aim(true);
}
void UCombatComponent::Server_Aim_Implementation(bool bPressed)
{
Local_Aim(bPressed);
}
void UCombatComponent::Initiate_AimReleased()
{
Local_Aim(false);
Server_Aim(false);
}
void UCombatComponent::Local_Aim(bool bPressed)
{
bAiming = bPressed;
OnAimingStatusChanged.Broadcast(bAiming);
OnAim(bPressed);
}
void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
APawn* OwningPawn = Cast<APawn>(GetOwner());
if (!IsValid(OwningPawn) || !OwningPawn->IsLocallyControlled()) return;
if (APlayerController* PC = Cast<APlayerController>(OwningPawn->GetController()); IsValid(PC))
{
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
FVector2D ReticleLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);
FVector ReticleWorldLocation;
FVector ReticleWorlDirection;
UGameplayStatics::DeprojectScreenToWorld(PC, ReticleLocation, ReticleWorldLocation, ReticleWorlDirection);
Local_PlayerHitResult.Start = ReticleWorldLocation;
Local_PlayerHitResult.End = Local_PlayerHitResult.Start + ReticleWorlDirection * TraceLength;
Local_PlayerHitResult.bHitPlayerLastFrame = Local_PlayerHitResult.bHitPlayer;
FHitResult TraceHit;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(GetOwner());
for (AWeapon* Weapon : Inventory)
{
QueryParams.AddIgnoredActor(Weapon);
}
FCollisionResponseParams ResponseParams;
ResponseParams.CollisionResponse.SetAllChannels(ECR_Ignore);
ResponseParams.CollisionResponse.SetResponse(ECC_WorldStatic, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_WorldDynamic, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_PhysicsBody, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_Pawn, ECR_Block);
GetWorld()->LineTraceSingleByChannel(TraceHit, Local_PlayerHitResult.Start, Local_PlayerHitResult.End, ECC_Weapon, QueryParams, ResponseParams);
Local_PlayerHitResult.End = TraceHit.bBlockingHit ? TraceHit.ImpactPoint : Local_PlayerHitResult.End;
if (IsValid(TraceHit.GetActor()) && TraceHit.GetActor()->Implements<UPlayerInterface>())
{
Local_PlayerHitResult.bHitPlayer = true;
}
else
{
Local_PlayerHitResult.bHitPlayer = false;
}
Local_PlayerHitResult.bHeadShot = TraceHit.bBlockingHit && TraceHit.BoneName == "head";
if (Local_PlayerHitResult.bHitPlayer != Local_PlayerHitResult.bHitPlayerLastFrame)
{
OnTargetingPlayerStatusChanged.Broadcast(Local_PlayerHitResult.bHitPlayer);
}
}
}
FVector UCombatComponent::HitScanTrace(float SweepRadius, FHitResult& OutHit)
{
FVector Start = GetOwner()->GetActorLocation();
FCollisionQueryParams QueryParams;
QueryParams.bReturnPhysicalMaterial = true;
QueryParams.AddIgnoredActor(GetOwner());
for (AWeapon* Weapon : Inventory)
{
QueryParams.AddIgnoredActor(Weapon);
}
FCollisionResponseParams ResponseParams;
ResponseParams.CollisionResponse.SetAllChannels(ECR_Ignore);
ResponseParams.CollisionResponse.SetResponse(ECC_Pawn, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_WorldStatic, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_WorldDynamic, ECR_Block);
ResponseParams.CollisionResponse.SetResponse(ECC_PhysicsBody, ECR_Block);
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
if (APlayerController* PC = Cast<APlayerController>(Cast<APawn>(GetOwner())->GetController()); IsValid(PC))
{
FVector2D ReticleLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);
FVector ReticleWorldLocation;
FVector ReticleWorlDirection;
UGameplayStatics::DeprojectScreenToWorld(PC, ReticleLocation, ReticleWorldLocation, ReticleWorlDirection);
Start = ReticleWorldLocation;
FVector End = Start + ReticleWorlDirection * TraceLength;
const bool bHit = GetWorld()->SweepSingleByChannel(OutHit, Start, End, FQuat::Identity, ECC_Weapon, FCollisionShape::MakeSphere(SweepRadius), QueryParams, ResponseParams);
if (!bHit)
{
OutHit.ImpactPoint = End;
OutHit.ImpactNormal = (Start - End).GetSafeNormal();
}
}
return Start;
}

View File

@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Data/SpecialElimData.h"

View File

@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Data/WeaponData.h"

View File

@@ -0,0 +1,185 @@
#include "Elimination/EliminationComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet/GameplayStatics.h"
#include "Player/MatchPlayerState.h"
#include "Game/MatchGameState.h"
UEliminationComponent::UEliminationComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SequentialElimInterval = 2.f;
ElimsNeededForStreak = 5;
LastElimTime = 0.f;
SequentialElims = 0;
Streak = 0;
}
void UEliminationComponent::BeginPlay()
{
Super::BeginPlay();
}
AMatchPlayerState* UEliminationComponent::GetPlayerStateFromActor(AActor* Actor)
{
APawn* Pawn = Cast<APawn>(Actor);
if (IsValid(Pawn))
{
return Pawn->GetPlayerState<AMatchPlayerState>();
}
return nullptr;
}
void UEliminationComponent::OnRoundReported(AActor* Attacker, AActor* Victim, bool bHit, bool bHeadShot, bool bLethal)
{
AMatchPlayerState* AttackerPS = GetPlayerStateFromActor(Attacker);
if (!IsValid(AttackerPS)) return;
ProcessHitOrMiss(bHit, AttackerPS);
if (!bHit) return; // Early return if it was a miss
AMatchPlayerState* VictimPS = GetPlayerStateFromActor(Victim);
if (!IsValid(VictimPS)) return;
if (bLethal) ProcessElimination(bHeadShot, AttackerPS, VictimPS);
}
void UEliminationComponent::ProcessHitOrMiss(bool bHit, AMatchPlayerState* AttackerPS)
{
if (bHit)
{
AttackerPS->AddHit();
}
else
{
AttackerPS->AddMiss();
}
}
void UEliminationComponent::ProcessHeadshot(bool bHeadShot, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS)
{
if (bHeadShot)
{
SpecialElimType |= ESpecialElimType::Headshot;
AttackerPS->AddHeadShotElim();
}
}
void UEliminationComponent::ProcessSequentialEliminations(AMatchPlayerState* AttackerPS, ESpecialElimType& SpecialElimType)
{
float CurrentTime = GetWorld()->GetTimeSeconds();
if (CurrentTime - LastElimTime <= SequentialElimInterval)
{
SequentialElims++;
}
else
{
SequentialElims = 1;
}
LastElimTime = CurrentTime;
if (SequentialElims > 1)
{
SpecialElimType |= ESpecialElimType::Sequential;
AttackerPS->AddSequentialElim(SequentialElims);
}
}
void UEliminationComponent::ProcessStreaks(AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS, ESpecialElimType& SpecialElimType)
{
++Streak;
if (Streak >= ElimsNeededForStreak)
{
SpecialElimType |= ESpecialElimType::Streak;
AttackerPS->SetOnStreak(true);
AttackerPS->UpdateHighestStreak(Streak);
}
if (VictimPS->IsOnStreak())
{
SpecialElimType |= ESpecialElimType::Showstopper;
AttackerPS->AddShowStopperElim();
}
VictimPS->SetOnStreak(false);
if (AttackerPS->GetLastAttacker() == VictimPS)
{
SpecialElimType |= ESpecialElimType::Revenge;
AttackerPS->AddRevengeElim();
AttackerPS->SetLastAttacker(nullptr);
}
VictimPS->SetLastAttacker(AttackerPS);
}
void UEliminationComponent::HandleFirstBlood(AMatchGameState* GameState, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS)
{
if (!GameState->HasFirstBloodBeenHad())
{
SpecialElimType |= ESpecialElimType::FirstBlood;
AttackerPS->GotFirstBlood();
}
}
void UEliminationComponent::UpdateLeaderStatus(AMatchGameState* GameState, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS)
{
AMatchPlayerState* LastLeader = GameState->GetLeader();
const bool bAttackerWasTiedForTheLead = GameState->IsTiedForTheLead(AttackerPS);
GameState->UpdateLeader();
if (!bAttackerWasTiedForTheLead && GameState->IsTiedForTheLead(AttackerPS))
{
SpecialElimType |= ESpecialElimType::TiedTheLeader;
}
if (IsValid(LastLeader) && LastLeader != GameState->GetLeader())
{
// LastLeader has now lost the lead
LastLeader->Client_LostTheLead();
if (VictimPS == LastLeader)
{
SpecialElimType |= ESpecialElimType::Dethrone;
AttackerPS->AddDethroneElim();
}
}
if (AttackerPS != LastLeader && AttackerPS == GameState->GetLeader())
{
SpecialElimType |= ESpecialElimType::GainedTheLead;
}
}
bool UEliminationComponent::HasSpecialElimTypes(const ESpecialElimType& SpecialElimType)
{
return static_cast<uint8>(SpecialElimType) != 0;
}
void UEliminationComponent::ProcessElimination(bool bHeadShot, AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS)
{
AttackerPS->AddScoredElim();
VictimPS->AddDefeat();
ESpecialElimType SpecialElimType{};
ProcessHeadshot(bHeadShot, SpecialElimType, AttackerPS);
ProcessSequentialEliminations(AttackerPS, SpecialElimType);
ProcessStreaks(AttackerPS, VictimPS, SpecialElimType);
AMatchGameState* GameState = Cast<AMatchGameState>(UGameplayStatics::GetGameState(AttackerPS));
if (IsValid(GameState))
{
HandleFirstBlood(GameState, SpecialElimType, AttackerPS);
UpdateLeaderStatus(GameState, SpecialElimType, AttackerPS, VictimPS);
}
if (HasSpecialElimTypes(SpecialElimType))
{
// inform the client of a special elim.
AttackerPS->Client_SpecialElim(SpecialElimType, SequentialElims, Streak, AttackerPS->GetScoredElims());
}
else
{
// no special elims; save some bandwidth.
AttackerPS->Client_ScoredElim(AttackerPS->GetScoredElims());
}
}

View File

@@ -0,0 +1,77 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/MatchGameState.h"
#include "Player/MatchPlayerState.h"
AMatchGameState::AMatchGameState()
{
Leaders = TArray<TObjectPtr<AMatchPlayerState>>();
bHasFirstBloodBeenHad = false;
}
AMatchPlayerState* AMatchGameState::GetLeader() const
{
if (Leaders.Num() == 1)
{
return Leaders[0];
}
return nullptr;
}
void AMatchGameState::UpdateLeader()
{
TArray<APlayerState*> SortedPlayers = PlayerArray;
SortedPlayers.Sort([](const APlayerState& A, const APlayerState& B)
{
const AMatchPlayerState* PlayerA = Cast<AMatchPlayerState>(&A);
const AMatchPlayerState* PlayerB = Cast<AMatchPlayerState>(&B);
return PlayerA->GetScoredElims() > PlayerB->GetScoredElims();
});
Leaders.Empty();
if (SortedPlayers.Num() > 0)
{
int32 HighestScore = 0;
for (APlayerState* PlayerState : SortedPlayers)
{
AMatchPlayerState* Player = Cast<AMatchPlayerState>(PlayerState);
if (IsValid(Player))
{
int32 PlayerScore = Player->GetScoredElims();
// On the first iteration, set the highest score
if (Leaders.Num() == 0)
{
HighestScore = PlayerScore;
Leaders.Add(Player);
}
else if (PlayerScore == HighestScore)
{
Leaders.Add(Player); // Add to leaders if scores are tied
}
else
{
break; // As it's sorted, no need to check further
}
}
}
}
bHasFirstBloodBeenHad = true;
}
bool AMatchGameState::IsTiedForTheLead(AMatchPlayerState* PlayerState)
{
if (Leaders.Contains(PlayerState)) return true;
return false;
}
void AMatchGameState::BeginPlay()
{
Super::BeginPlay();
}

View File

@@ -0,0 +1,244 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/ShooterGameMode.h"
#include "UObject/ConstructorHelpers.h"
#include "Kismet/GameplayStatics.h"
#if WITH_GAMELIFT
#include "GameLiftServerSDKModels.h"
#endif
#include "GenericPlatform/GenericPlatformOutputDevices.h"
#include "Misc/TypeContainer.h"
DEFINE_LOG_CATEGORY(LogShooterGameMode)
AShooterGameMode::AShooterGameMode()
{
UE_LOG(LogShooterGameMode, Log, TEXT("Initializing ShooterGameMode..."));
}
void AShooterGameMode::BeginPlay()
{
Super::BeginPlay();
#if WITH_GAMELIFT
InitGameLift();
#endif
}
#if WITH_GAMELIFT
void AShooterGameMode::SetServerParameters(TSharedPtr<FServerParameters> OutServerParameters)
{
// AuthToken returned from the "aws gamelift get-compute-auth-token" API. Note this will expire and require a new call to the API after 15 minutes.
FString GameLiftAnywhereAuthToken = "";
if (FParse::Value(FCommandLine::Get(), TEXT("-authtoken="), GameLiftAnywhereAuthToken))
{
OutServerParameters->m_authToken = TCHAR_TO_UTF8(*GameLiftAnywhereAuthToken);
}
else
{
UE_LOG(LogShooterGameMode, Warning, TEXT("-authtoken not found in command line."));
}
// The Host/compute-name of the GameLift Anywhere instance
FString GameLiftAnywhereHostId = "";
if (FParse::Value(FCommandLine::Get(), TEXT("-hostid="), GameLiftAnywhereHostId))
{
OutServerParameters->m_hostId = TCHAR_TO_UTF8(*GameLiftAnywhereHostId);
}
else
{
UE_LOG(LogShooterGameMode, Warning, TEXT("-hostid not found in command line."));
}
// The AnywhereFleet ID.
FString GameLiftAnywhereFleetId = "";
if (FParse::Value(FCommandLine::Get(), TEXT("-fleetid="), GameLiftAnywhereFleetId))
{
OutServerParameters->m_fleetId = TCHAR_TO_UTF8(*GameLiftAnywhereFleetId);
}
else
{
UE_LOG(LogShooterGameMode, Warning, TEXT("-fleetid not found in command line."));
}
// The WebSocket URL (GameLiftServiceSdkEndpoint)
FString GameLiftAnywhereWebSocketUrl = "";
if (FParse::Value(FCommandLine::Get(), TEXT("-websocketurl="), GameLiftAnywhereWebSocketUrl))
{
OutServerParameters->m_webSocketUrl = TCHAR_TO_UTF8(*GameLiftAnywhereWebSocketUrl);
}
else
{
UE_LOG(LogShooterGameMode, Warning, TEXT("-websocketurl not found in command line."));
}
// The PID of the running process
OutServerParameters->m_processId = FString::Printf(TEXT("%d"), GetCurrentProcessId());
}
void AShooterGameMode::LogServerParameters(TSharedPtr<FServerParameters> ServerParameters)
{
// Log Server Parameters
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_YELLOW);
UE_LOG(LogShooterGameMode, Log, TEXT(">>>> WebSocket URL: %s"), *ServerParameters->m_webSocketUrl);
UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Fleet ID: %s"), *ServerParameters->m_fleetId);
UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Process ID: %s"), *ServerParameters->m_processId);
UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Host ID (Compute Name): %s"), *ServerParameters->m_hostId);
UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Auth Token: %s"), *ServerParameters->m_authToken);
// UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Aws Region: %s"), *ServerParameters.m_awsRegion);
// UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Access Key: %s"), *ServerParameters.m_accessKey);
// UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Secret Key: %s"), *ServerParameters.m_secretKey);
// UE_LOG(LogShooterGameMode, Log, TEXT(">>>> Session Token: %s"), *ServerParameters.m_sessionToken);
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE);
}
#endif
void AShooterGameMode::ParseCommandLinePort(int32& OutPort)
{
TArray<FString> CommandLineTokens;
TArray<FString> CommandLineSwitches;
FCommandLine::Parse(FCommandLine::Get(), CommandLineTokens, CommandLineSwitches);
for (FString SwitchStr : CommandLineSwitches)
{
FString Key;
FString Value;
if (SwitchStr.Split("=", &Key, &Value))
{
if (Key.Equals(TEXT("port"), ESearchCase::IgnoreCase))
{
OutPort = FCString::Atoi(*Value);
return;
}
}
}
}
void AShooterGameMode::InitGameLift()
{
#if WITH_GAMELIFT
UE_LOG(LogShooterGameMode, Log, TEXT("Calling InitGameLift"));
// Getting the module first.
FGameLiftServerSDKModule* GameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftServerSDKModule>(FName("GameLiftServerSDK"));
//Define the server parameters for a GameLift Anywhere fleet. These are not needed for a GameLift managed EC2 fleet.
FServerParameters ServerParameters;
bool bIsAnywhereActive = false;
if (FParse::Param(FCommandLine::Get(), TEXT("glAnywhere")))
{
bIsAnywhereActive = true;
}
if (bIsAnywhereActive)
{
UE_LOG(LogShooterGameMode, Log, TEXT("Configuring server parameters for Anywhere..."));
// If GameLift Anywhere is enabled, parse command line arguments and pass them in the ServerParameters object.
SetServerParameters(MakeShared<FServerParameters>(ServerParameters));
LogServerParameters(MakeShared<FServerParameters>(ServerParameters));
}
UE_LOG(LogShooterGameMode, Log, TEXT("Initializing the GameLift Server..."));
// InitSDK will establish a local connection with GameLift's agent to enable futher communication
FGameLiftGenericOutcome InitSdkOutcome = GameLiftSdkModule->InitSDK(ServerParameters);
if (InitSdkOutcome.IsSuccess())
{
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_GREEN);
UE_LOG(LogShooterGameMode, Log, TEXT("GameLift Init SDK Succeeded"));
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE);
}
else
{
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_RED);
UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: InitSDK failed : ("));
FGameLiftError GameLiftError = InitSdkOutcome.GetError();
UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: %s"), *GameLiftError.m_errorMessage);
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE);
return;
}
// Implement callback function onStartGameSession
// GameLift sends a game session activation request to the game server
// and passes a game session object with game properties and other settings.
// Here is where a game server takes action based on the game session object.
// When the game server is ready to receive incoming player connections,
// it invokes the server SDK call ActivateGameSession().
auto OnGameSession = [=](const Aws::GameLift::Server::Model::GameSession& InGameSession)
{
FString GameSessionId = FString(InGameSession.GetGameSessionId());
UE_LOG(LogShooterGameMode, Log, TEXT("GameSession Initializing: %s"), *GameSessionId);
GameLiftSdkModule->ActivateGameSession();
};
ProcessParameters->OnStartGameSession.BindLambda(OnGameSession);
auto OnTerminate = [=]()
{
UE_LOG(LogShooterGameMode, Log, TEXT("Game Server Process is terminating"));
GameLiftSdkModule->ProcessEnding();
};
ProcessParameters->OnTerminate.BindLambda(OnTerminate);
auto OnHealthCheck = [=]()
{
UE_LOG(LogShooterGameMode, Log, TEXT("Performing Health Check"));
return true;
};
ProcessParameters->OnHealthCheck.BindLambda(OnHealthCheck);
int32 Port = FURL::UrlConfig.DefaultPort;
ParseCommandLinePort(Port);
ProcessParameters->port = Port;
// Here, the game server tells GameLift where to find game session Log files.
// At the end of a game session, GameLift uploads everything in the specified
// location and stores it in the cloud for access later.
TArray<FString> LogFiles;
LogFiles.Add(TEXT("FPSTemplate/Saved/Logs/FPSTemplate.log"));
ProcessParameters->logParameters = LogFiles;
// The game server calls ProcessReady() to tell Amazon GameLift Servers it's ready to host game sessions.
UE_LOG(LogShooterGameMode, Log, TEXT("Calling Process Ready..."));
FGameLiftGenericOutcome ProcessReadyOutcome = GameLiftSdkModule->ProcessReady(*ProcessParameters);
if (ProcessReadyOutcome.IsSuccess())
{
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_GREEN);
UE_LOG(LogShooterGameMode, Log, TEXT("Process Ready!"));
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE);
}
else
{
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_RED);
UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: Process Ready Failed!"));
FGameLiftError ProcessReadyError = ProcessReadyOutcome.GetError();
UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: %s"), *ProcessReadyError.m_errorMessage);
UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE);
}
UE_LOG(LogShooterGameMode, Log, TEXT("InitGameLift completed!"));
#endif
};

View File

@@ -0,0 +1,57 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/ShooterGameModeBase.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerStart.h"
#include "Kismet/GameplayStatics.h"
AShooterGameModeBase::AShooterGameModeBase()
{
RespawnTime = 2.f;
}
void AShooterGameModeBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AShooterGameModeBase::StartPlayerElimination(float ElimTime, ACharacter* ElimmedCharacter,
APlayerController* VictimController, APlayerController* AttackerController)
{
FTimerDelegate ElimTimerDelegate;
FTimerHandle TimerHandle;
Timers.Add(VictimController, TimerHandle);
ElimTimerDelegate.BindLambda([this, ElimmedCharacter, VictimController, AttackerController]()
{
PlayerEliminated(ElimmedCharacter, VictimController, AttackerController);
GetWorldTimerManager().ClearTimer(Timers[VictimController]);
Timers.Remove(VictimController);
});
GetWorldTimerManager().SetTimer(TimerHandle, ElimTimerDelegate, ElimTime + RespawnTime, false);
}
void AShooterGameModeBase::PlayerEliminated(ACharacter* ElimmedCharacter, APlayerController* VictimController,
APlayerController* AttackerController)
{
RequestRespawn(ElimmedCharacter, VictimController);
}
void AShooterGameModeBase::RequestRespawn(ACharacter* ElimmedCharacter, AController* ElimmedController)
{
if (ElimmedCharacter)
{
ElimmedCharacter->Reset();
ElimmedCharacter->Destroy();
}
if (ElimmedController)
{
TArray<AActor*> PlayerStarts;
UGameplayStatics::GetAllActorsOfClass(this, APlayerStart::StaticClass(), PlayerStarts);
int32 Selection = FMath::RandRange(0, PlayerStarts.Num() - 1);
RestartPlayerAtPlayerStart(ElimmedController, PlayerStarts[Selection]);
}
}

View File

@@ -0,0 +1,3 @@
#include "Interfaces/PlayerInterface.h"
// Add default functionality here for any IPlayerInterface functions that are not pure virtual.

View File

@@ -0,0 +1,224 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/MatchPlayerState.h"
#include "ShooterTypes/ShooterTypes.h"
#include "UI/Elims/SpecialElimWidget.h"
AMatchPlayerState::AMatchPlayerState()
{
SetNetUpdateFrequency(100.f); // let's not be sluggish, alright?
ScoredElims = 0;
Defeats = 0;
Hits = 0;
Misses = 0;
bOnStreak = false;
HeadShotElims = 0;
SequentialElims = TMap<int32, int32>();
HighestStreak = 0;
RevengeElims = 0;
DethroneElims = 0;
ShowStopperElims = 0;
bFirstBlood = false;
bWinner = false;
}
void AMatchPlayerState::AddScoredElim()
{
++ScoredElims;
}
void AMatchPlayerState::AddDefeat()
{
++Defeats;
}
void AMatchPlayerState::AddHit()
{
++Hits;
}
void AMatchPlayerState::AddMiss()
{
++Misses;
}
void AMatchPlayerState::AddHeadShotElim()
{
++HeadShotElims;
}
void AMatchPlayerState::AddSequentialElim(int32 SequenceCount)
{
if (SequentialElims.Contains(SequenceCount))
{
SequentialElims[SequenceCount]++;
}
else
{
SequentialElims.Add(SequenceCount, 1);
}
// Reduce the count for all lower sequence counts
// This is because a triple elim means a double was scored first,
// But we want to count this as just a triple, i.e:
// elim 1, elim 2, elim 3 = just a triple, not a double and a triple.
for (auto& Elem : SequentialElims)
{
if (Elem.Key < SequenceCount && Elem.Value > 0)
{
Elem.Value--;
}
}
}
void AMatchPlayerState::UpdateHighestStreak(int32 StreakCount)
{
if (StreakCount > HighestStreak)
{
HighestStreak = StreakCount;
}
}
void AMatchPlayerState::AddRevengeElim()
{
++RevengeElims;
}
void AMatchPlayerState::AddDethroneElim()
{
++DethroneElims;
}
void AMatchPlayerState::AddShowStopperElim()
{
++ShowStopperElims;
}
void AMatchPlayerState::GotFirstBlood()
{
bFirstBlood = true;
}
void AMatchPlayerState::IsTheWinner()
{
bWinner = true;
}
TArray<ESpecialElimType> AMatchPlayerState::DecodeElimBitmask(ESpecialElimType ElimTypeBitmask)
{
TArray<ESpecialElimType> ValidElims;
uint8 BitmaskValue = static_cast<uint8>(ElimTypeBitmask);
for(uint8 i = 0; i < 16; i++) // Assuming 8 bits since ESpecialElimType is a uint16
{
if (BitmaskValue & (1 << i))
{
ESpecialElimType EnumValue = static_cast<ESpecialElimType>(1 << i);
ValidElims.Add(EnumValue);
}
}
return ValidElims;
}
void AMatchPlayerState::ProcessNextSpecialElim()
{
FSpecialElimInfo ElimInfo;
if (SpecialElimQueue.Dequeue(ElimInfo))
{
bIsProcessingQueue = true;
ShowSpecialElim(ElimInfo);
// Schedule the next elimination processing after a delay
GetWorldTimerManager().SetTimerForNextTick([this]()
{
constexpr float ElimDisplayTime = 0.5f; // Adjust the time in seconds as needed
FTimerHandle TimerHandle;
GetWorldTimerManager().SetTimer(TimerHandle, this, &AMatchPlayerState::ProcessNextSpecialElim, ElimDisplayTime, false);
});
}
else
{
bIsProcessingQueue = false;
}
}
void AMatchPlayerState::ShowSpecialElim(const FSpecialElimInfo& ElimMessageInfo)
{
FString ElimMessageString = ElimMessageInfo.ElimMessage;
if (ElimMessageInfo.ElimType == ESpecialElimType::Sequential)
{
FString Msg = ElimMessageInfo.ElimMessage;
int32 Seq = ElimMessageInfo.SequentialElimCount;
int32 Streak = ElimMessageInfo.StreakCount;
if (ElimMessageInfo.SequentialElimCount == 2) ElimMessageString = FString("Double Elim!");
else if (ElimMessageInfo.SequentialElimCount == 3) ElimMessageString = FString("Triple Elim!");
else if (ElimMessageInfo.SequentialElimCount == 4) ElimMessageString = FString("Quad Elim!");
else if (ElimMessageInfo.SequentialElimCount > 4) ElimMessageString = FString::Printf(TEXT("Rampage x%d!"), ElimMessageInfo.SequentialElimCount);
}
if (ElimMessageInfo.ElimType == ESpecialElimType::Streak) ElimMessageString = FString::Printf(TEXT("Streak x%d!"), ElimMessageInfo.StreakCount);
if (SpecialElimWidgetClass)
{
USpecialElimWidget* ElimWidget = CreateWidget<USpecialElimWidget>(GetWorld(), SpecialElimWidgetClass);
if (IsValid(ElimWidget))
{
ElimWidget->InitializeWidget(ElimMessageString, ElimMessageInfo.ElimIcon);
ElimWidget->AddToViewport();
}
}
}
void AMatchPlayerState::Client_ScoredElim_Implementation(int32 ElimScore)
{
OnScoreChanged.Broadcast(ElimScore);
}
void AMatchPlayerState::Client_SpecialElim_Implementation(const ESpecialElimType& SpecialElim, int32 SequentialElimCount, int32 StreakCount, int32 ElimScore)
{
OnScoreChanged.Broadcast(ElimScore);
if (!IsValid(SpecialElimData)) return;
TArray<ESpecialElimType> ElimTypes = DecodeElimBitmask(SpecialElim);
for (ESpecialElimType ElimType : ElimTypes)
{
FSpecialElimInfo& ElimInfo = SpecialElimData->SpecialElimInfo.FindChecked(ElimType);
if (ElimType == ESpecialElimType::Sequential)
{
ElimInfo.SequentialElimCount = SequentialElimCount;
}
if (ElimType == ESpecialElimType::Streak)
{
ElimInfo.StreakCount = StreakCount;
}
ElimInfo.ElimType = ElimType;
SpecialElimQueue.Enqueue(ElimInfo);
}
if (!bIsProcessingQueue)
{
ProcessNextSpecialElim();
}
}
void AMatchPlayerState::Client_LostTheLead_Implementation()
{
UE_LOG(LogTemp, Warning, TEXT("%s Lost the Lead"), *GetName());
if (!IsValid(SpecialElimData)) return;
auto& ElimMessageInfo = SpecialElimData->SpecialElimInfo.FindChecked(ESpecialElimType::LostTheLead);
if (SpecialElimWidgetClass)
{
USpecialElimWidget* ElimWidget = CreateWidget<USpecialElimWidget>(GetWorld(), SpecialElimWidgetClass);
if (IsValid(ElimWidget))
{
ElimWidget->InitializeWidget(ElimMessageInfo.ElimMessage, ElimMessageInfo.ElimIcon);
ElimWidget->AddToViewport();
}
}
}

View File

@@ -0,0 +1,90 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/ShooterPlayerController.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"
#include "Interfaces/PlayerInterface.h"
AShooterPlayerController::AShooterPlayerController()
{
bReplicates = true;
bPawnAlive = true;
}
void AShooterPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
bPawnAlive = true;
}
void AShooterPlayerController::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
OnPlayerStateReplicated.Broadcast();
}
void AShooterPlayerController::BeginPlay()
{
Super::BeginPlay();
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
if (Subsystem)
{
Subsystem->AddMappingContext(ShooterIMC, 0);
}
}
void AShooterPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
UEnhancedInputComponent* ShooterInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
ShooterInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AShooterPlayerController::Input_Move);
ShooterInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AShooterPlayerController::Input_Look);
ShooterInputComponent->BindAction(CrouchAction, ETriggerEvent::Started, this, &AShooterPlayerController::Input_Crouch);
ShooterInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AShooterPlayerController::Input_Jump);
}
void AShooterPlayerController::Input_Move(const FInputActionValue& InputActionValue)
{
if (!bPawnAlive) return;
const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
const FRotator Rotation = GetControlRotation();
const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
if (APawn* ControlledPawn = GetPawn<APawn>())
{
ControlledPawn->AddMovementInput(ForwardDirection, InputAxisVector.Y);
ControlledPawn->AddMovementInput(RightDirection, InputAxisVector.X);
}
}
void AShooterPlayerController::Input_Look(const FInputActionValue& InputActionValue)
{
const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
AddYawInput(InputAxisVector.X);
AddPitchInput(InputAxisVector.Y);
}
void AShooterPlayerController::Input_Crouch()
{
if (!bPawnAlive) return;
if (GetPawn() == nullptr || !GetPawn()->Implements<UPlayerInterface>()) return;
IPlayerInterface::Execute_Initiate_Crouch(GetPawn());
}
void AShooterPlayerController::Input_Jump()
{
if (!bPawnAlive) return;
if (GetPawn() == nullptr || !GetPawn()->Implements<UPlayerInterface>()) return;
IPlayerInterface::Execute_Initiate_Jump(GetPawn());
}

View File

@@ -0,0 +1 @@
#include "ShooterTypes/ShooterTypes.h"

View File

@@ -0,0 +1,9 @@
#include "Tags/ShooterGameplayTags.h"
namespace ShooterTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_WeaponType_None, "Weapon.Type.None", "No Weapon")
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_WeaponType_Pistol, "Weapon.Type.Pistol", "Pistol Weapon Type")
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_WeaponType_Rifle, "Weapon.Type.Rifle", "Rifle Weapon Type")
}

View File

@@ -0,0 +1,63 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/Elims/ScoreWidget.h"
#include "Components/TextBlock.h"
#include "Player/MatchPlayerState.h"
#include "Player/ShooterPlayerController.h"
void UScoreWidget::NativeConstruct()
{
Super::NativeConstruct();
AMatchPlayerState* PlayerState = GetPlayerState();
if (IsValid(PlayerState))
{
PlayerState->OnScoreChanged.AddDynamic(this, &UScoreWidget::OnScoreChanged);
OnScoreChanged(PlayerState->GetScoredElims());
}
else
{
AShooterPlayerController* ShooterPlayerController = Cast<AShooterPlayerController>(GetOwningPlayer());
if (IsValid(ShooterPlayerController))
{
ShooterPlayerController->OnPlayerStateReplicated.AddUniqueDynamic(this, &UScoreWidget::OnPlayerStateInitialized);
}
}
}
void UScoreWidget::OnScoreChanged(int32 NewScore)
{
if (IsValid(ScoreText))
{
ScoreText->SetText(FText::AsNumber(NewScore));
}
}
void UScoreWidget::OnPlayerStateInitialized()
{
// Get the PlayerState and bind to the score changed delegate
AMatchPlayerState* PlayerState = GetPlayerState();
if (IsValid(PlayerState))
{
PlayerState->OnScoreChanged.AddDynamic(this, &UScoreWidget::OnScoreChanged);
OnScoreChanged(PlayerState->GetScoredElims());
}
// Unsubscribe from the OnPlayerStateChanged delegate
AShooterPlayerController* ShooterPlayerController = Cast<AShooterPlayerController>(GetOwningPlayer());
if (IsValid(ShooterPlayerController))
{
ShooterPlayerController->OnPlayerStateReplicated.RemoveDynamic(this, &UScoreWidget::OnPlayerStateInitialized);
}
}
AMatchPlayerState* UScoreWidget::GetPlayerState() const
{
APlayerController* PlayerController = GetOwningPlayer();
if (IsValid(PlayerController))
{
return PlayerController->GetPlayerState<AMatchPlayerState>();
}
return nullptr;
}

View File

@@ -0,0 +1,35 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/Elims/SpecialElimWidget.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Components/TextBlock.h"
#include "Components/Image.h"
void USpecialElimWidget::InitializeWidget(const FString& InElimMessage, UTexture2D* InElimTexture)
{
if (IsValid(ElimText))
{
ElimText->SetText(FText::FromString(InElimMessage));
}
if (IsValid(ElimImage) && InElimTexture)
{
ElimImage->SetBrushFromTexture(InElimTexture);
}
}
void USpecialElimWidget::CenterWidget(UUserWidget* Widget, float VerticalRatio)
{
if (!IsValid(Widget))
{
return;
}
FVector2D ViewportSize = UWidgetLayoutLibrary::GetViewportSize(Widget);
const float VerticalFraction = VerticalRatio == 0.f ? 1.f : VerticalRatio * 2.f;
FVector2D CenterPosition(ViewportSize.X / 2.0f, VerticalFraction * ViewportSize.Y / 2.0f);
Widget->SetAlignmentInViewport(FVector2D(0.5f, 0.5f)); // Align widget center to the center of the viewport
Widget->SetPositionInViewport(CenterPosition, true);
}

View File

@@ -0,0 +1,90 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ShooterAmmoCounter.h"
#include "Character/ShooterCharacter.h"
#include "Combat/CombatComponent.h"
#include "Interfaces/PlayerInterface.h"
#include "Weapon/Weapon.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
void UShooterAmmoCounter::NativeConstruct()
{
Super::NativeConstruct();
Image_WeaponIcon->SetRenderOpacity(0.f);
Text_Ammo->SetRenderOpacity(0.f);
GetOwningPlayer()->OnPossessedPawnChanged.AddDynamic(this, &UShooterAmmoCounter::OnPossessedPawnChanged);
AShooterCharacter* ShooterCharacter = Cast<AShooterCharacter>(GetOwningPlayer()->GetPawn());
if (!IsValid(ShooterCharacter)) return;
OnPossessedPawnChanged(nullptr, ShooterCharacter);
if (ShooterCharacter->bWeaponFirstReplicated)
{
AWeapon* CurrentWeapon = IPlayerInterface::Execute_GetCurrentWeapon(ShooterCharacter);
if (IsValid(CurrentWeapon))
{
OnCarriedAmmoChanged(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), IPlayerInterface::Execute_GetCarriedAmmo(ShooterCharacter), CurrentWeapon->Ammo);
}
}
else
{
ShooterCharacter->OnWeaponFirstReplicated.AddDynamic(this, &UShooterAmmoCounter::OnWeaponFirstReplicated);
}
}
void UShooterAmmoCounter::OnWeaponFirstReplicated(AWeapon* Weapon)
{
OnCarriedAmmoChanged(Weapon->GetAmmoCounterDynamicMaterialInstance(), IPlayerInterface::Execute_GetCarriedAmmo(GetOwningPlayer()->GetPawn()), Weapon->Ammo);
}
void UShooterAmmoCounter::OnPossessedPawnChanged(APawn* OldPawn, APawn* NewPawn)
{
UCombatComponent* OldPawnCombat = UCombatComponent::FindCombatComponent(OldPawn);
if (IsValid(OldPawnCombat))
{
OldPawnCombat->OnCarriedAmmoChanged.RemoveDynamic(this, &UShooterAmmoCounter::OnCarriedAmmoChanged);
OldPawnCombat->OnRoundFired.RemoveDynamic(this, &UShooterAmmoCounter::OnRoundFired);
}
UCombatComponent* NewPawnCombat = UCombatComponent::FindCombatComponent(NewPawn);
if (IsValid(NewPawnCombat))
{
Image_WeaponIcon->SetRenderOpacity(1.f);
Text_Ammo->SetRenderOpacity(1.f);
NewPawnCombat->OnCarriedAmmoChanged.AddDynamic(this, &UShooterAmmoCounter::OnCarriedAmmoChanged);
NewPawnCombat->OnRoundFired.AddDynamic(this, &UShooterAmmoCounter::OnRoundFired);
}
}
void UShooterAmmoCounter::OnCarriedAmmoChanged(UMaterialInstanceDynamic* WeaponIconDynMatInst, int32 InCarriedAmmo, int32 RoundsInWeapon)
{
CurrentWeaponIcon_DynMatInst = WeaponIconDynMatInst;
FSlateBrush Brush;
Brush.SetResourceObject(CurrentWeaponIcon_DynMatInst);
if (IsValid(Image_WeaponIcon))
{
Image_WeaponIcon->SetBrush(Brush);
}
TotalAmmo = InCarriedAmmo + RoundsInWeapon;
if (IsValid(Text_Ammo))
{
FText AmmoText = FText::Format(NSLOCTEXT("AmmoText", "AmmoKey", "{0}"), TotalAmmo);
Text_Ammo->SetText(AmmoText);
}
}
void UShooterAmmoCounter::OnRoundFired(int32 RoundsCurrent, int32 RoundsMax, int32 RoundsCarried)
{
TotalAmmo = RoundsCarried + RoundsCurrent;
if (IsValid(Text_Ammo))
{
FText AmmoText = FText::Format(NSLOCTEXT("AmmoText", "AmmoKey", "{0}"), TotalAmmo);
Text_Ammo->SetText(AmmoText);
}
}

View File

@@ -0,0 +1,19 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ShooterHUD.h"
#include "Blueprint/UserWidget.h"
void AShooterHUD::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = GetOwningPlayerController();
if (IsValid(PlayerController) && ShooterOverlayClass)
{
Overlay = CreateWidget<UUserWidget>(PlayerController, ShooterOverlayClass);
Overlay->AddToViewport();
}
}

View File

@@ -0,0 +1,161 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ShooterReticle.h"
#include "Character/ShooterCharacter.h"
#include "Components/Image.h"
#include "Combat/CombatComponent.h"
#include "Weapon/Weapon.h"
#include "Interfaces/PlayerInterface.h"
namespace Reticle
{
const FName Inner_RGBA = FName("Inner_RGBA");
const FName RoundedCornerScale = FName("RoundedCornerScale");
const FName ShapeCutThickness = FName("ShapeCutThickness");
}
namespace Ammo
{
const FName Rounds_Current = FName("Rounds_Current");
const FName Rounds_Max = FName("Rounds_Max");
}
void UShooterReticle::NativeConstruct()
{
Super::NativeConstruct();
Image_Reticle->SetRenderOpacity(0.f);
Image_AmmoCounter->SetRenderOpacity(0.f);
GetOwningPlayer()->OnPossessedPawnChanged.AddDynamic(this, &UShooterReticle::OnPossessedPawnChanged);
AShooterCharacter* ShooterCharacter = Cast<AShooterCharacter>(GetOwningPlayer()->GetPawn());
if (!IsValid(ShooterCharacter)) return;
OnPossessedPawnChanged(nullptr, ShooterCharacter);
if (ShooterCharacter->bWeaponFirstReplicated)
{
AWeapon* CurrentWeapon = IPlayerInterface::Execute_GetCurrentWeapon(ShooterCharacter);
if (IsValid(CurrentWeapon))
{
OnReticleChange(CurrentWeapon->GetReticleDynamicMaterialInstance(), CurrentWeapon->ReticleParams);
OnAmmoCounterChange(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity);
}
}
else
{
ShooterCharacter->OnWeaponFirstReplicated.AddDynamic(this, &UShooterReticle::OnWeaponFirstReplicated);
}
}
void UShooterReticle::OnWeaponFirstReplicated(AWeapon* Weapon)
{
OnReticleChange(Weapon->GetReticleDynamicMaterialInstance(), Weapon->ReticleParams);
OnAmmoCounterChange(Weapon->GetAmmoCounterDynamicMaterialInstance(), Weapon->Ammo, Weapon->MagCapacity);
}
void UShooterReticle::OnPossessedPawnChanged(APawn* OldPawn, APawn* NewPawn)
{
UCombatComponent* OldPawnCombat = UCombatComponent::FindCombatComponent(OldPawn);
if (IsValid(OldPawnCombat))
{
OldPawnCombat->OnReticleChanged.RemoveDynamic(this, &UShooterReticle::OnReticleChange);
OldPawnCombat->OnAmmoCounterChanged.RemoveDynamic(this, &UShooterReticle::OnAmmoCounterChange);
OldPawnCombat->OnTargetingPlayerStatusChanged.RemoveDynamic(this, &UShooterReticle::OnTargetingPlayerStatusChanged);
OldPawnCombat->OnRoundFired.RemoveDynamic(this, &UShooterReticle::OnRoundFired);
OldPawnCombat->OnAimingStatusChanged.RemoveDynamic(this, &UShooterReticle::OnAimingStatusChanged);
}
UCombatComponent* NewPawnCombat = UCombatComponent::FindCombatComponent(NewPawn);
if (IsValid(NewPawnCombat))
{
Image_Reticle->SetRenderOpacity(1.f);
Image_AmmoCounter->SetRenderOpacity(1.f);
NewPawnCombat->OnReticleChanged.AddDynamic(this, &UShooterReticle::OnReticleChange);
NewPawnCombat->OnAmmoCounterChanged.AddDynamic(this, &UShooterReticle::OnAmmoCounterChange);
NewPawnCombat->OnTargetingPlayerStatusChanged.AddDynamic(this, &UShooterReticle::OnTargetingPlayerStatusChanged);
NewPawnCombat->OnRoundFired.AddDynamic(this, &UShooterReticle::OnRoundFired);
NewPawnCombat->OnAimingStatusChanged.AddDynamic(this, &UShooterReticle::OnAimingStatusChanged);
}
}
void UShooterReticle::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
_BaseCornerScaleFactor_TargetingPlayer = FMath::FInterpTo(_BaseCornerScaleFactor_TargetingPlayer, bTargetingPlayer ? CurrentReticleParams.ScaleFactor_Targeting : CurrentReticleParams.ScaleFactor_NotTargeting, InDeltaTime, CurrentReticleParams.TargetingPlayerInterpSpeed);
_BaseCornerScaleFactor_Aiming = FMath::FInterpTo(_BaseCornerScaleFactor_Aiming, bAiming ? CurrentReticleParams.ScaleFactor_Aiming : CurrentReticleParams.ScaleFactor_NotAiming, InDeltaTime, CurrentReticleParams.AimingInterpSpeed);
_BaseCornerScaleFactor_RoundFired = FMath::FInterpTo(_BaseCornerScaleFactor_RoundFired, 0.f, InDeltaTime, CurrentReticleParams.RoundFiredInterpSpeed);
_BaseShapeCutFactor_Aiming = FMath::FInterpTo(BaseShapeCutFactor, bAiming ? CurrentReticleParams.ShapeCutFactor_Aiming : CurrentReticleParams.ShapeCutFactor_NotAiming, InDeltaTime, CurrentReticleParams.AimingInterpSpeed);
_BaseShapeCutFactor_RoundFired = FMath::FInterpTo(_BaseShapeCutFactor_RoundFired, 0.f, InDeltaTime, CurrentReticleParams.RoundFiredInterpSpeed);
BaseCornerScaleFactor = _BaseCornerScaleFactor_TargetingPlayer + _BaseCornerScaleFactor_Aiming + _BaseCornerScaleFactor_RoundFired;
BaseShapeCutFactor = _BaseShapeCutFactor_Aiming + _BaseShapeCutFactor_RoundFired;
if (IsValid(CurrentReticle_DynMatInst))
{
CurrentReticle_DynMatInst->SetScalarParameterValue(Reticle::RoundedCornerScale, BaseCornerScaleFactor);
CurrentReticle_DynMatInst->SetScalarParameterValue(Reticle::ShapeCutThickness, BaseShapeCutFactor);
}
}
void UShooterReticle::OnReticleChange(UMaterialInstanceDynamic* ReticleDynMatInst, const FReticleParams& ReticleParams,
bool bCurrentlyTargetingPlayer)
{
CurrentReticleParams = ReticleParams;
CurrentReticle_DynMatInst = ReticleDynMatInst;
FSlateBrush Brush;
Brush.SetResourceObject(ReticleDynMatInst);
if (IsValid(Image_Reticle))
{
Image_Reticle->SetBrush(Brush);
}
OnTargetingPlayerStatusChanged(bCurrentlyTargetingPlayer);
}
void UShooterReticle::OnAmmoCounterChange(UMaterialInstanceDynamic* AmmoCounterDynMatInst, int32 RoundsCurrent,
int32 RoundsMax)
{
CurrentAmmoCounter_DynMatInst = AmmoCounterDynMatInst;
CurrentAmmoCounter_DynMatInst->SetScalarParameterValue(Ammo::Rounds_Current, RoundsCurrent);
CurrentAmmoCounter_DynMatInst->SetScalarParameterValue(Ammo::Rounds_Max, RoundsMax);
FSlateBrush Brush;
Brush.SetResourceObject(CurrentAmmoCounter_DynMatInst);
if (IsValid(Image_AmmoCounter))
{
Image_AmmoCounter->SetBrush(Brush);
}
}
void UShooterReticle::OnTargetingPlayerStatusChanged(bool bIsTargetingPlayer)
{
if (IsValid(CurrentReticle_DynMatInst))
{
FLinearColor ReticleColor = bIsTargetingPlayer ? FLinearColor::Red : FLinearColor::White;
CurrentReticle_DynMatInst->SetVectorParameterValue(Reticle::Inner_RGBA, ReticleColor);
}
bTargetingPlayer = bIsTargetingPlayer;
}
void UShooterReticle::OnRoundFired(int32 RoundsCurrent, int32 RoundsMax, int32 RoundsCarried)
{
_BaseCornerScaleFactor_RoundFired += CurrentReticleParams.ScaleFactor_RoundFired;
_BaseShapeCutFactor_RoundFired += CurrentReticleParams.ShapeCutFactor_RoundFired;
if (IsValid(CurrentAmmoCounter_DynMatInst))
{
CurrentAmmoCounter_DynMatInst->SetScalarParameterValue(Ammo::Rounds_Current, RoundsCurrent);
CurrentAmmoCounter_DynMatInst->SetScalarParameterValue(Ammo::Rounds_Max, RoundsMax);
}
}
void UShooterReticle::OnAimingStatusChanged(bool bIsAiming)
{
bAiming = bIsAiming;
}

View File

@@ -0,0 +1,238 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Weapon/Weapon.h"
#include "Interfaces/PlayerInterface.h"
#include "Kismet/GameplayStatics.h"
AWeapon::AWeapon()
{
Mesh1P = CreateDefaultSubobject<USkeletalMeshComponent>("Mesh1P");
Mesh1P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
Mesh1P->bReceivesDecals = false;
Mesh1P->CastShadow = false;
Mesh1P->SetHiddenInGame(true);
SetRootComponent(Mesh1P);
Mesh3P = CreateDefaultSubobject<USkeletalMeshComponent>("Mesh3P");
Mesh3P->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
Mesh3P->bReceivesDecals = false;
Mesh3P->CastShadow = true;
Mesh3P->SetupAttachment(Mesh1P);
Mesh3P->SetHiddenInGame(true);
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
bReplicates = true;
bNetUseOwnerRelevancy = true;
AimFieldOfView = 65.f;
}
USkeletalMeshComponent* AWeapon::GetMesh1P() const
{
return Mesh1P;
}
USkeletalMeshComponent* AWeapon::GetMesh3P() const
{
return Mesh3P;
}
UMaterialInstanceDynamic* AWeapon::GetReticleDynamicMaterialInstance()
{
if (!IsValid(DynMatInst_Reticle))
{
DynMatInst_Reticle = UMaterialInstanceDynamic::Create(ReticleMaterial, this);
}
return DynMatInst_Reticle;
}
UMaterialInstanceDynamic* AWeapon::GetAmmoCounterDynamicMaterialInstance()
{
if (!IsValid(DynMatInst_AmmoCounter))
{
DynMatInst_AmmoCounter = UMaterialInstanceDynamic::Create(AmmoCounterMaterial, this);
}
return DynMatInst_AmmoCounter;
}
UMaterialInstanceDynamic* AWeapon::GetWeaponIconDynamicMaterialInstance()
{
if (!IsValid(DynMatInst_WeaponIcon))
{
DynMatInst_WeaponIcon = UMaterialInstanceDynamic::Create(WeaponIconMaterial, this);
}
return DynMatInst_WeaponIcon;
}
void AWeapon::BeginPlay()
{
Super::BeginPlay();
}
void AWeapon::AttachMeshToPawn()
{
APawn* MyPawn = GetInstigator();
if (!IsValid(MyPawn) || !MyPawn->Implements<UPlayerInterface>()) return;
// Remove and hide both first and third person meshes
DetachMeshFromPawn();
// For locally controlled players we attach both weapons and let the bOnlyOwnerSee, bOwnerNoSee flags deal with visibility.
const FName AttachPoint = IPlayerInterface::Execute_GetWeaponAttachPoint(MyPawn, WeaponType);
if (MyPawn->IsLocallyControlled())
{
USkeletalMeshComponent* PawnMesh1p = IPlayerInterface::Execute_GetSpecifcPawnMesh(MyPawn, true);
USkeletalMeshComponent* PawnMesh3p = IPlayerInterface::Execute_GetSpecifcPawnMesh(MyPawn, false);
Mesh1P->SetHiddenInGame(false);
Mesh3P->SetHiddenInGame(true);
Mesh1P->AttachToComponent(PawnMesh1p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
Mesh3P->AttachToComponent(PawnMesh3p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
}
else
{
USkeletalMeshComponent* UseWeaponMesh = GetWeaponMesh();
USkeletalMeshComponent* UsePawnMesh = IPlayerInterface::Execute_GetPawnMesh(MyPawn);
UseWeaponMesh->AttachToComponent(UsePawnMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
UseWeaponMesh->SetHiddenInGame(false);
}
}
void AWeapon::OnEnterInventory(APawn* NewOwner)
{
SetOwningPawn(NewOwner);
}
void AWeapon::OnUnEquip()
{
DetachMeshFromPawn();
}
void AWeapon::OnEquip(const AWeapon* LastWeapon)
{
AttachMeshToPawn();
}
void AWeapon::SetOwningPawn(APawn* NewOwningPawn)
{
if (GetOwner() != NewOwningPawn)
{
SetInstigator(NewOwningPawn);
// net owner for RPC calls
SetOwner(NewOwningPawn);
}
if (IsValid(NewOwningPawn))
{
if (NewOwningPawn->IsLocallyControlled())
{
Mesh1P->SetHiddenInGame(false);
Mesh3P->SetHiddenInGame(true);
}
else
{
Mesh1P->SetHiddenInGame(true);
Mesh3P->SetHiddenInGame(false);
}
}
}
USkeletalMeshComponent* AWeapon::GetWeaponMesh() const
{
if (GetOwner() == nullptr) return nullptr;
if (!GetOwner()->Implements<UPlayerInterface>()) return nullptr;
return IPlayerInterface::Execute_IsFirstPerson(GetOwner()) ? Mesh1P : Mesh3P;
}
void AWeapon::SetWeaponState(EWeaponState::Type NewState)
{
CurrentState = NewState;
}
EWeaponState::Type AWeapon::GetWeaponState() const
{
return CurrentState;
}
void AWeapon::DetachMeshFromPawn()
{
Mesh1P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
Mesh1P->SetHiddenInGame(true);
Mesh3P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
Mesh3P->SetHiddenInGame(true);
}
void AWeapon::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AWeapon::OnRep_Instigator()
{
Super::OnRep_Instigator();
APawn* MyPawn = GetInstigator();
if (IsValid(MyPawn))
{
if (MyPawn->IsLocallyControlled())
{
Mesh1P->SetHiddenInGame(false);
Mesh3P->SetHiddenInGame(true);
}
else
{
Mesh1P->SetHiddenInGame(true);
Mesh3P->SetHiddenInGame(false);
}
}
}
void AWeapon::PostInitializeComponents()
{
Super::PostInitializeComponents();
DetachMeshFromPawn();
}
void AWeapon::Local_Fire(const FVector& ImpactPoint, const FVector& ImpactNormal, TEnumAsByte<EPhysicalSurface> SurfaceType, bool bIsFirstPerson)
{
FireEffects(ImpactPoint, ImpactNormal, SurfaceType, bIsFirstPerson);
if (GetInstigator()->IsLocallyControlled())
{
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
++Sequence;
}
}
int32 AWeapon::Auth_Fire()
{
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
return Ammo;
}
void AWeapon::Rep_Fire(int32 AuthAmmo)
{
if (GetInstigator()->IsLocallyControlled())
{
Ammo = AuthAmmo;
--Sequence;
Ammo -= Sequence;
}
}

View File

@@ -0,0 +1,149 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "GameplayTagContainer.h"
#include "Interfaces/PlayerInterface.h"
#include "ShooterTypes/ShooterTypes.h"
#include "ShooterCharacter.generated.h"
class UEliminationComponent;
class UShooterHealthComponent;
class UShooterOverlay;
class UShooterHUDComponent;
class USpringArmComponent;
class UCameraComponent;
class UWeaponData;
class AWeapon;
class UCombatComponent;
class UInputAction;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWeaponFirstReplicated, AWeapon*, Weapon);
UCLASS()
class FPSTEMPLATE_API AShooterCharacter : public ACharacter, public IPlayerInterface
{
GENERATED_BODY()
public:
AShooterCharacter();
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void PossessedBy(AController* NewController) override;
virtual void Tick(float DeltaTime) override;
virtual void OnRep_PlayerState() override;
virtual void BeginDestroy() override;
virtual void UnPossessed() override;
FRotator StartingAimRotation;
UPROPERTY(BlueprintReadOnly)
float AO_Yaw;
float InterpAO_Yaw;
UPROPERTY(BlueprintReadOnly)
float MovementOffsetYaw;
UPROPERTY(BlueprintReadOnly)
FTransform FABRIK_SocketTransform;
UPROPERTY(BlueprintReadOnly)
ETurningInPlace TurningStatus;
void TurnInPlace(float DeltaTime);
/** PlayerInterface */
virtual FName GetWeaponAttachPoint_Implementation(const FGameplayTag& WeaponType) const override;
virtual USkeletalMeshComponent* GetSpecifcPawnMesh_Implementation(bool WantFirstPerson) const override;
virtual USkeletalMeshComponent* GetPawnMesh_Implementation() const override;
virtual bool IsFirstPerson_Implementation() const override;
virtual bool DoDamage_Implementation(float DamageAmount, AActor* DamageInstigator) override;
virtual void AddAmmo_Implementation(const FGameplayTag& WeaponType, int32 AmmoAmount) override;
virtual AWeapon* GetCurrentWeapon_Implementation() override;
virtual int32 GetCarriedAmmo_Implementation() override;
virtual void InitializeWidgets_Implementation() override;
virtual void Notify_CycleWeapon_Implementation() override;
virtual void Notify_ReloadWeapon_Implementation() override;
virtual void Initiate_Crouch_Implementation() override;
virtual void Initiate_Jump_Implementation() override;
virtual bool IsDeadOrDying_Implementation() override;
virtual void WeaponReplicated_Implementation() override;
/** <end> PlayerInterface */
UFUNCTION(BlueprintCallable)
FRotator GetFixedAimRotation() const;
bool bWeaponFirstReplicated;
UPROPERTY(BlueprintAssignable)
FWeaponFirstReplicated OnWeaponFirstReplicated;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Aiming")
float DefaultFieldOfView;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Death")
TArray<TObjectPtr<UAnimMontage>> DeathMontages;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "HitReact")
TArray<TObjectPtr<UAnimMontage>> HitReacts;
UFUNCTION(BlueprintImplementableEvent)
void OnAim(bool bIsAiming);
UFUNCTION(BlueprintImplementableEvent)
void DeathEffects(AActor* DeathInstigator, UAnimMontage* DeathMontage);
UFUNCTION(NetMulticast, Reliable)
void Multicast_HitReact(int32 MontageIndex);
private:
void Input_CycleWeapon();
void Input_FireWeapon_Pressed();
void Input_FireWeapon_Released();
void Input_ReloadWeapon();
void Input_Aim_Pressed();
void Input_Aim_Released();
bool IsLocalFirstPerson() const;
UFUNCTION()
void OnDeathStarted(AActor* DyingActor, AActor* DeathInstigator);
/** 1st person view */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Mesh, meta = (AllowPrivateAccess = "true"))
TObjectPtr<USkeletalMeshComponent> Mesh1P;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
TObjectPtr<UCombatComponent> Combat;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
TObjectPtr<UShooterHealthComponent> HealthComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
TObjectPtr<UEliminationComponent> EliminationComponent;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
TObjectPtr<USpringArmComponent> SpringArm;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
TObjectPtr<UCameraComponent> FirstPersonCamera;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> CycleWeaponAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> FireWeaponAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> ReloadWeaponAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> AimAction;
FTimerHandle InitiializeWidgets_Timer;
};

View File

@@ -0,0 +1,113 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ShooterHealthComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FShooterHealth_DeathEvent, AActor*, OwningActor, AActor*, Instigator);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FShooterHealth_AttributeChanged, UShooterHealthComponent*, HealthComponent, float, OldValue, float, NewValue, AActor*, Instigator);
/**
* EDeathState
*
* Defines current state of death.
*/
UENUM(BlueprintType)
enum class EDeathState : uint8
{
NotDead = 0,
DeathStarted,
DeathFinished
};
/**
* UShooterHealthComponent
*
* An actor component used to handle anything related to health.
*/
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FPSTEMPLATE_API UShooterHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UShooterHealthComponent();
// Returns the health component if one exists on the specified actor.
UFUNCTION(BlueprintPure, Category = "Shooter|Health")
static UShooterHealthComponent* FindHealthComponent(const AActor* Actor) { return (Actor ? Actor->FindComponentByClass<UShooterHealthComponent>() : nullptr); }
// Returns the current health value.
UFUNCTION(BlueprintCallable, Category = "Shooter|Health")
float GetHealth() const { return Health; };
// Returns the current maximum health value.
UFUNCTION(BlueprintCallable, Category = "Shooter|Health")
float GetMaxHealth() const { return MaxHealth; };
// Returns the current health in the range [0.0, 1.0].
UFUNCTION(BlueprintCallable, Category = "Shooter|Health")
float GetHealthNormalized() const;
UFUNCTION(BlueprintCallable, Category = "Shooter|Health")
EDeathState GetDeathState() const { return DeathState; }
UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "Shooter|Health", Meta = (ExpandBoolAsExecs = "ReturnValue"))
bool IsDeadOrDying() const { return (DeathState > EDeathState::NotDead); }
// Begins the death sequence for the owner.
virtual void StartDeath(AActor* Instigator);
// Ends the death sequence for the owner.
virtual void FinishDeath(AActor* Instigator);
public:
// Delegate fired when the health value has changed. This is called on the client but the instigator may not be valid
UPROPERTY(BlueprintAssignable)
FShooterHealth_AttributeChanged OnHealthChanged;
// Delegate fired when the max health value has changed. This is called on the client but the instigator may not be valid
UPROPERTY(BlueprintAssignable)
FShooterHealth_AttributeChanged OnMaxHealthChanged;
// Delegate fired when the death sequence has started.
UPROPERTY(BlueprintAssignable)
FShooterHealth_DeathEvent OnDeathStarted;
// Delegate fired when the death sequence has finished.
UPROPERTY(BlueprintAssignable)
FShooterHealth_DeathEvent OnDeathFinished;
// return true if lethal
virtual bool ChangeHealthByAmount(float Amount, AActor* Instigator);
virtual void ChangeMaxHealthByAmount(float Amount, AActor* Instigator);
protected:
virtual void BeginPlay() override;
virtual void HandleOutOfHealth();
UFUNCTION()
virtual void OnRep_DeathState(EDeathState OldDeathState);
// Replicated state used to handle dying.
UPROPERTY(ReplicatedUsing = OnRep_DeathState)
EDeathState DeathState;
UPROPERTY(ReplicatedUsing=OnRep_Health)
float Health;
UPROPERTY(ReplicatedUsing=OnRep_MaxHealth)
float MaxHealth;
UFUNCTION()
void OnRep_Health(float OldValue);
UFUNCTION()
void OnRep_MaxHealth(float OldValue);
};

View File

@@ -0,0 +1,155 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "ShooterTypes/ShooterTypes.h"
#include "CombatComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FReticleChangedDelegate, UMaterialInstanceDynamic*, ReticleDynMatInst, const FReticleParams&, ReticleParams, bool, bCurrentlyTargetingPlayer);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAmmoCounterChangedDelegate, UMaterialInstanceDynamic*, AmmoCounterDynMatInst, int32, RoundsCurrent, int32, RoundsMax);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTargetingPlayerStatusChangedDelegate, bool, bIsTargetingPlayer);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRoundFiredDelegate, int32, RoundsCurrent, int32, RoundsMax, int32, RoundsCarried);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAimingStatusChangedDelegate, bool, bIsAiming);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCarriedAmmoChangedDelegate, UMaterialInstanceDynamic*, WeaponIconDynMatInst, int32, InCarriedAmmo, int32, RoundsInWeapon);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FRoundReportedDelegate, AActor*, Attacker, AActor*, Victim, bool, bHit, bool, bHeadShot, bool, bLethal);
class UShooterOverlay;
class UWeaponData;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FPSTEMPLATE_API UCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCombatComponent();
void Initiate_CycleWeapon();
void Notify_CycleWeapon();
void Notify_ReloadWeapon();
void Initiate_FireWeapon_Pressed();
void Initiate_FireWeapon_Released();
void Initiate_ReloadWeapon();
void Initiate_AimPressed();
void Initiate_AimReleased();
UFUNCTION(BlueprintImplementableEvent)
void OnAim(bool bIsAiming);
UFUNCTION(BlueprintPure, Category = "Shooter|Health")
static UCombatComponent* FindCombatComponent(const AActor* Actor) { return (Actor ? Actor->FindComponentByClass<UCombatComponent>() : nullptr); }
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
void AddAmmo(FGameplayTag WeaponType, int32 AmmoAmount);
void InitializeWeaponWidgets() const;
void SpawnDefaultInventory();
void DestroyInventory();
UFUNCTION(Reliable, Server)
void ServerEquipWeapon(AWeapon* NewWeapon);
UPROPERTY(BlueprintReadOnly, Replicated)
bool bAiming;
UPROPERTY(BlueprintAssignable, Category=Combat)
FReticleChangedDelegate OnReticleChanged;
UPROPERTY(BlueprintAssignable, Category=Combat)
FAmmoCounterChangedDelegate OnAmmoCounterChanged;
UPROPERTY(BlueprintAssignable, Category=Combat)
FTargetingPlayerStatusChangedDelegate OnTargetingPlayerStatusChanged;
UPROPERTY(BlueprintAssignable, Category=Combat)
FRoundFiredDelegate OnRoundFired;
UPROPERTY(BlueprintAssignable, Category=Combat)
FAimingStatusChangedDelegate OnAimingStatusChanged;
UPROPERTY(BlueprintAssignable, Category=Combat)
FCarriedAmmoChangedDelegate OnCarriedAmmoChanged;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
TObjectPtr<UWeaponData> WeaponData;
UPROPERTY(ReplicatedUsing = OnRep_CarriedAmmo)
int32 CarriedAmmo;
TMap<FGameplayTag, int32> CarriedAmmoMap;
UPROPERTY(EditDefaultsOnly, Category = Inventory)
TArray<TSubclassOf<AWeapon>> DefaultInventoryClasses;
UPROPERTY(Transient, Replicated)
TArray<AWeapon*> Inventory;
UPROPERTY(Transient, ReplicatedUsing = OnRep_CurrentWeapon, BlueprintReadOnly)
TObjectPtr<AWeapon> CurrentWeapon;
UPROPERTY(EditDefaultsOnly)
float TraceLength;
UPROPERTY(BlueprintReadOnly)
FPlayerHitResult Local_PlayerHitResult;
UPROPERTY(BlueprintAssignable)
FRoundReportedDelegate OnRoundReported;
protected:
virtual void BeginPlay() override;
private:
void SetCurrentWeapon(AWeapon* NewWeapon, AWeapon* LastWeapon = nullptr);
void AddWeapon(AWeapon* Weapon);
void EquipWeapon(AWeapon* Weapon);
void Local_CycleWeapon(const int32 WeaponIndex);
void FireTimerFinished();
int32 AdvanceWeaponIndex();
void Local_ReloadWeapon();
void Local_Aim(bool bPressed);
FVector HitScanTrace(float SweepRadius, FHitResult& OutHit);
UFUNCTION(Server, Reliable)
void Server_CycleWeapon(const int32 WeaponIndex);
UFUNCTION(NetMulticast, Reliable)
void Multicast_CycleWeapon(const int32 WeaponIndex);
UFUNCTION()
void BlendOut_CycleWeapon(UAnimMontage* Montage, bool bInterrupted);
void Local_FireWeapon();
UFUNCTION(Server, Reliable)
void Server_FireWeapon(const FVector_NetQuantize& TraceStart, const FHitResult& Impact, bool bScoredHit, bool bHeadShot);
UFUNCTION(NetMulticast, Reliable)
void Multicast_FireWeapon(const FHitResult& Impact, int32 AuthAmmo);
UFUNCTION(Server, Reliable)
void Server_ReloadWeapon(bool bLocalOwnerReload = false);
UFUNCTION(NetMulticast, Reliable)
void Multicast_ReloadWeapon(int32 NewWeaponAmmo, int32 NewCarriedAmmo, bool bLocalOwnerReload = false);
UFUNCTION(Client, Reliable)
void Client_ReloadWeapon(int32 NewWeaponAmmo, int32 NewCarriedAmmo);
UFUNCTION(Server, Reliable)
void Server_Aim(bool bPressed);
UFUNCTION()
void OnRep_CurrentWeapon(AWeapon* LastWeapon);
UFUNCTION()
void OnRep_CarriedAmmo();
int32 Local_WeaponIndex;
bool bTriggerPressed;
FTimerHandle FireTimer;
};

View File

@@ -0,0 +1,42 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ShooterTypes/ShooterTypes.h"
#include "SpecialElimData.generated.h"
USTRUCT(BlueprintType)
struct FSpecialElimInfo
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
ESpecialElimType ElimType = ESpecialElimType::None;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FString ElimMessage = FString();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UTexture2D> ElimIcon = nullptr;
UPROPERTY(BlueprintReadOnly)
int32 SequentialElimCount = 0;
UPROPERTY(BlueprintReadOnly)
int32 StreakCount = 0;
};
/**
*
*/
UCLASS()
class FPSTEMPLATE_API USpecialElimData : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "SpecialEliminations")
TMap<ESpecialElimType, FSpecialElimInfo> SpecialElimInfo;
};

View File

@@ -0,0 +1,78 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Engine/DataAsset.h"
#include "WeaponData.generated.h"
USTRUCT(BlueprintType)
struct FPlayerAnims
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimSequence> IdleAnim = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimSequence> AimIdleAnim = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimSequence> CrouchIdleAnim = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimSequence> SprintAnim = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UBlendSpace> AimOffset_Hip = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UBlendSpace> AimOffset_Aim = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UBlendSpace> Strafe_Standing = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UBlendSpace> Strafe_Crouching = nullptr;
};
USTRUCT(BlueprintType)
struct FMontageData
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimMontage> EquipMontage = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimMontage> ReloadMontage = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UAnimMontage> FireMontage = nullptr;
};
UCLASS()
class FPSTEMPLATE_API UWeaponData : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|FirstPerson")
TMap<FGameplayTag, FPlayerAnims> FirstPersonAnims;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|ThirdPerson")
TMap<FGameplayTag, FPlayerAnims> ThirdPersonAnims;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|Weapons")
TMap<FGameplayTag, FMontageData> WeaponMontages;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|Weapons")
TMap<FGameplayTag, FName> GripPoints;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|FirstPerson")
TMap<FGameplayTag, FMontageData> FirstPersonMontages;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData|ThirdPerson")
TMap<FGameplayTag, FMontageData> ThirdPersonMontages;
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ShooterTypes/ShooterTypes.h"
#include "EliminationComponent.generated.h"
class AMatchPlayerState;
class AMatchGameState;
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class FPSTEMPLATE_API UEliminationComponent : public UActorComponent
{
GENERATED_BODY()
public:
UEliminationComponent();
UFUNCTION()
void OnRoundReported(AActor* Attacker, AActor* Victim, bool bHit, bool bHeadShot, bool bLethal);
protected:
virtual void BeginPlay() override;
private:
// Time frame to determine sequential elims
UPROPERTY(EditDefaultsOnly, Category = Elimination)
float SequentialElimInterval;
UPROPERTY(EditDefaultsOnly, Category = Elimination)
int32 ElimsNeededForStreak;
float LastElimTime;
int32 SequentialElims;
int32 Streak;
AMatchPlayerState* GetPlayerStateFromActor(AActor* Actor);
void ProcessHitOrMiss(bool bHit, AMatchPlayerState* AttackerPS);
void ProcessElimination(bool bHeadShot, AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS);
void ProcessHeadshot(bool bHeadShot, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS);
void ProcessSequentialEliminations(AMatchPlayerState* AttackerPS, ESpecialElimType& SpecialElimType);
void ProcessStreaks(AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS, ESpecialElimType& SpecialElimType);
void HandleFirstBlood(AMatchGameState* GameState, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS);
void UpdateLeaderStatus(AMatchGameState* GameState, ESpecialElimType& SpecialElimType, AMatchPlayerState* AttackerPS, AMatchPlayerState* VictimPS);
bool HasSpecialElimTypes(const ESpecialElimType& SpecialElimType);
};

View File

@@ -0,0 +1,37 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "MatchGameState.generated.h"
class AMatchPlayerState;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AMatchGameState : public AGameState
{
GENERATED_BODY()
public:
AMatchGameState();
AMatchPlayerState* GetLeader() const;
void UpdateLeader();
bool HasFirstBloodBeenHad() const { return bHasFirstBloodBeenHad; }
bool IsTiedForTheLead(AMatchPlayerState* PlayerState);
protected:
virtual void BeginPlay() override;
private:
UPROPERTY()
TArray<TObjectPtr<AMatchPlayerState>> Leaders;
UPROPERTY()
TArray<TObjectPtr<AMatchPlayerState>> SortedPlayerStates;
bool bHasFirstBloodBeenHad;
};

View File

@@ -0,0 +1,44 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ShooterGameModeBase.h"
#if WITH_GAMELIFT
#include "GameLiftServerSDK.h"
#endif
#include "ShooterGameMode.generated.h"
struct FProcessParameters;
DECLARE_LOG_CATEGORY_EXTERN(LogShooterGameMode, Log, All);
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AShooterGameMode : public AShooterGameModeBase
{
GENERATED_BODY()
public:
AShooterGameMode();
protected:
virtual void BeginPlay() override;
private:
void InitGameLift();
#if WITH_GAMELIFT
void SetServerParameters(TSharedPtr<FServerParameters> OutServerParameters);
void LogServerParameters(TSharedPtr<FServerParameters> ServerParameters);
#endif
void ParseCommandLinePort(int32& OutPort);
TSharedPtr<FProcessParameters> ProcessParameters;
};

View File

@@ -0,0 +1,30 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "ShooterGameModeBase.generated.h"
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AShooterGameModeBase : public AGameMode
{
GENERATED_BODY()
public:
AShooterGameModeBase();
virtual void Tick(float DeltaTime) override;
virtual void StartPlayerElimination(float ElimTime, ACharacter* ElimmedCharacter, class APlayerController* VictimController, APlayerController* AttackerController);
UPROPERTY()
TMap<APlayerController*, FTimerHandle> Timers;
virtual void PlayerEliminated(ACharacter* ElimmedCharacter, class APlayerController* VictimController, APlayerController* AttackerController);
virtual void RequestRespawn(ACharacter* ElimmedCharacter, AController* ElimmedController);
UPROPERTY(EditDefaultsOnly, Category="Respawning")
float RespawnTime;
};

View File

@@ -0,0 +1,90 @@
#pragma once
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "PlayerInterface.generated.h"
class UShooterOverlay;
struct FGameplayTag;
class AWeapon;
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UPlayerInterface : public UInterface
{
GENERATED_BODY()
};
class FPSTEMPLATE_API IPlayerInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
FName GetWeaponAttachPoint(const FGameplayTag& WeaponType) const;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
USkeletalMeshComponent* GetSpecifcPawnMesh(bool WantFirstPerson) const;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
USkeletalMeshComponent* GetPawnMesh() const;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
bool IsFirstPerson() const;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void Initiate_CycleWeapon();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void Notify_CycleWeapon();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void Notify_ReloadWeapon();
UFUNCTION(BlueprintNativeEvent)
void Initiate_FireWeapon_Pressed();
UFUNCTION(BlueprintNativeEvent)
void Initiate_FireWeapon_Released();
UFUNCTION(BlueprintNativeEvent)
void Initiate_ReloadWeapon();
UFUNCTION(BlueprintNativeEvent)
void Initiate_Aim_Pressed();
UFUNCTION(BlueprintNativeEvent)
void Initiate_Aim_Released();
UFUNCTION(BlueprintNativeEvent)
void Initiate_Crouch();
UFUNCTION(BlueprintNativeEvent)
void Initiate_Jump();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
bool DoDamage(float DamageAmount, AActor* DamageInstigator);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void AddAmmo(const FGameplayTag& WeaponType, int32 AmmoAmount);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
AWeapon* GetCurrentWeapon();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
int32 GetCarriedAmmo();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void InitializeWidgets();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
bool IsDeadOrDying();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void WeaponReplicated();
};

View File

@@ -0,0 +1,92 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "Data/SpecialElimData.h"
#include "MatchPlayerState.generated.h"
enum class ESpecialElimType : uint16;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32, NewScore);
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AMatchPlayerState : public APlayerState
{
GENERATED_BODY()
public:
AMatchPlayerState();
void AddScoredElim();
void AddDefeat();
void AddHit();
void AddMiss();
APlayerState* GetLastAttacker() const { return LastAttacker; }
void SetLastAttacker(APlayerState* Attacker) { LastAttacker = Attacker; }
bool IsOnStreak() const { return bOnStreak; }
void SetOnStreak(bool bIsOnStreak) { bOnStreak = bIsOnStreak; }
int32 GetScoredElims() const { return ScoredElims; }
void AddHeadShotElim();
void AddSequentialElim(int32 SequenceCount);
void UpdateHighestStreak(int32 StreakCount);
void AddRevengeElim();
void AddDethroneElim();
void AddShowStopperElim();
void GotFirstBlood();
void IsTheWinner();
UFUNCTION(Client, Reliable)
void Client_SpecialElim(const ESpecialElimType& SpecialElim, int32 SequentialElimCount, int32 StreakCount, int32 ElimScore);
UFUNCTION(Client, Reliable)
void Client_ScoredElim(int32 ElimScore);
UFUNCTION(Client, Reliable)
void Client_LostTheLead();
UPROPERTY(BlueprintAssignable)
FOnScoreChanged OnScoreChanged;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<USpecialElimData> SpecialElimData;
TArray<ESpecialElimType> DecodeElimBitmask(ESpecialElimType ElimTypeBitmask);
protected:
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UUserWidget> SpecialElimWidgetClass;
private:
int32 ScoredElims;
int32 Defeats;
int32 Hits;
int32 Misses;
bool bOnStreak;
int32 HeadShotElims;
TMap<int32, int32> SequentialElims;
int32 HighestStreak;
int32 RevengeElims;
int32 DethroneElims;
int32 ShowStopperElims;
bool bFirstBlood;
bool bWinner;
UPROPERTY()
TObjectPtr<APlayerState> LastAttacker;
TQueue<FSpecialElimInfo> SpecialElimQueue;
bool bIsProcessingQueue;
void ProcessNextSpecialElim();
void ShowSpecialElim(const FSpecialElimInfo& ElimMessageInfo);
};

View File

@@ -0,0 +1,58 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ShooterPlayerController.generated.h"
struct FInputActionValue;
class UInputMappingContext;
class UInputAction;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPlayerStateReplicated);
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AShooterPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AShooterPlayerController();
bool bPawnAlive;
virtual void OnPossess(APawn* InPawn) override;
virtual void OnRep_PlayerState() override;
UPROPERTY(BlueprintAssignable)
FOnPlayerStateReplicated OnPlayerStateReplicated;
protected:
virtual void BeginPlay() override;
virtual void SetupInputComponent() override;
private:
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputMappingContext> ShooterIMC;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> LookAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> CrouchAction;
UPROPERTY(EditAnywhere, Category="Input")
TObjectPtr<UInputAction> JumpAction;
void Input_Move(const FInputActionValue& InputActionValue);
void Input_Look(const FInputActionValue& InputActionValue);
void Input_Crouch();
void Input_Jump();
};

View File

@@ -0,0 +1,89 @@
#pragma once
#include "ShooterTypes.generated.h"
USTRUCT(BlueprintType)
struct FPlayerHitResult
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
FVector_NetQuantize Start = FVector_NetQuantize::ZeroVector;
UPROPERTY(BlueprintReadWrite)
FVector_NetQuantize End = FVector_NetQuantize::ZeroVector;
UPROPERTY(BlueprintReadWrite)
bool bHitPlayer = false;
UPROPERTY(BlueprintReadWrite)
bool bHitPlayerLastFrame = false;
UPROPERTY(BlueprintReadWrite)
bool bHeadShot = false;
};
USTRUCT(BlueprintType)
struct FReticleParams
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleFactor_Targeting = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleFactor_NotTargeting = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleFactor_Aiming = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleFactor_NotAiming = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ShapeCutFactor_Aiming = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ShapeCutFactor_NotAiming = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ShapeCutFactor_RoundFired = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleFactor_RoundFired = 0.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float AimingInterpSpeed = 15.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float TargetingPlayerInterpSpeed = 10.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float RoundFiredInterpSpeed = 20.f;
};
UENUM(BlueprintType)
enum class ETurningInPlace : uint8
{
Left UMETA(DisplayName = "TurningLeft"),
Right UMETA(DisplayName = "TurningRight"),
NotTurning UMETA(DisplayName = "NotTurning")
};
UENUM(meta = (Bitflags))
enum class ESpecialElimType : uint16
{
None = 0,
Headshot = 1 << 0, // 00000000 00000001
Sequential = 1 << 1, // 00000000 00000010
Streak = 1 << 2, // 00000000 00000100
Revenge = 1 << 3, // 00000000 00001000
Dethrone = 1 << 4, // 00000000 00010000
Showstopper = 1 << 5, // 00000000 00100000
FirstBlood = 1 << 6, // 00000000 01000000
GainedTheLead = 1 << 7, // 00000000 10000000
TiedTheLeader = 1 << 8, // 00000001 00000000
LostTheLead = 1 << 9 // 00000010 00000000
};
ENUM_CLASS_FLAGS(ESpecialElimType)

View File

@@ -0,0 +1,11 @@
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
namespace ShooterTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_WeaponType_None);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_WeaponType_Pistol);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_WeaponType_Rifle);
}

View File

@@ -0,0 +1,33 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ScoreWidget.generated.h"
class UTextBlock;
class AMatchPlayerState;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API UScoreWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UPROPERTY(meta = (BindWidget))
UTextBlock* ScoreText;
protected:
UFUNCTION()
void OnScoreChanged(int32 NewScore);
UFUNCTION()
void OnPlayerStateInitialized();
AMatchPlayerState* GetPlayerState() const;
};

View File

@@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "SpecialElimWidget.generated.h"
class UTextBlock;
class UImage;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API USpecialElimWidget : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> ElimText;
UPROPERTY(meta = (BindWidget), BlueprintReadOnly, EditDefaultsOnly)
TObjectPtr<UImage> ElimImage;
void InitializeWidget(const FString& InElimMessage, UTexture2D* InElimTexture);
UFUNCTION(BlueprintCallable)
static void CenterWidget(UUserWidget* Widget, float VerticalRatio = 0.f);
};

View File

@@ -0,0 +1,49 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ShooterAmmoCounter.generated.h"
class UImage;
class UTextBlock;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API UShooterAmmoCounter : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> Image_WeaponIcon;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> Text_Ammo;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> CurrentWeaponIcon_DynMatInst;
private:
int32 TotalAmmo;
UFUNCTION()
void OnPossessedPawnChanged(APawn* OldPawn, APawn* NewPawn);
UFUNCTION()
void OnCarriedAmmoChanged(UMaterialInstanceDynamic* WeaponIconDynMatInst, int32 InCarriedAmmo, int32 RoundsInWeapon);
UFUNCTION()
void OnRoundFired(int32 RoundsCurrent, int32 RoundsMax, int32 RoundsCarried);
UFUNCTION()
void OnWeaponFirstReplicated(AWeapon* Weapon);
};

View File

@@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShooterHUD.generated.h"
class UUserWidget;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API AShooterHUD : public AHUD
{
GENERATED_BODY()
public:
UUserWidget* GetShooterOverlay() {return Overlay;}
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(EditDefaultsOnly, Category = "Overlay")
TSubclassOf<UUserWidget> ShooterOverlayClass;
UPROPERTY()
TObjectPtr<UUserWidget> Overlay;
};

View File

@@ -0,0 +1,86 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ShooterTypes/ShooterTypes.h"
#include "ShooterReticle.generated.h"
namespace Reticle
{
extern FPSTEMPLATE_API const FName Inner_RGBA;
extern FPSTEMPLATE_API const FName RoundedCornerScale;
extern FPSTEMPLATE_API const FName ShapeCutThickness;
}
namespace Ammo
{
extern FPSTEMPLATE_API const FName Rounds_Current;
extern FPSTEMPLATE_API const FName Rounds_Max;
}
class UImage;
class AWeapon;
/**
*
*/
UCLASS()
class FPSTEMPLATE_API UShooterReticle : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> Image_Reticle;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> Image_AmmoCounter;
private:
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> CurrentReticle_DynMatInst;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> CurrentAmmoCounter_DynMatInst;
FReticleParams CurrentReticleParams;
float BaseCornerScaleFactor;
float _BaseCornerScaleFactor_TargetingPlayer;
float _BaseCornerScaleFactor_Aiming;
float _BaseCornerScaleFactor_RoundFired;
float BaseShapeCutFactor;
float _BaseShapeCutFactor_Aiming;
float _BaseShapeCutFactor_RoundFired;
bool bTargetingPlayer;
bool bAiming;
UFUNCTION()
void OnPossessedPawnChanged(APawn* OldPawn, APawn* NewPawn);
UFUNCTION()
void OnReticleChange(UMaterialInstanceDynamic* ReticleDynMatInst, const FReticleParams& ReticleParams, bool bCurrentlyTargetingPlayer = false);
UFUNCTION()
void OnAmmoCounterChange(UMaterialInstanceDynamic* AmmoCounterDynMatInst, int32 RoundsCurrent, int32 RoundsMax);
UFUNCTION()
void OnTargetingPlayerStatusChanged(bool bIsTargetingPlayer);
UFUNCTION()
void OnRoundFired(int32 RoundsCurrent, int32 RoundsMax, int32 RoundsCarried);
UFUNCTION()
void OnAimingStatusChanged(bool bIsAiming);
UFUNCTION()
void OnWeaponFirstReplicated(AWeapon* Weapon);
};

View File

@@ -0,0 +1,164 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameplayTagContainer.h"
#include "ShooterTypes/ShooterTypes.h"
#include "Weapon.generated.h"
struct FPlayerHitResult;
/** Enum used to describe what state the weapon is currently in. */
UENUM(BlueprintType)
namespace EWeaponState
{
enum Type
{
Idle,
Firing,
Reloading,
Equipping,
};
}
UENUM(BlueprintType)
namespace EFireType
{
enum Type
{
Auto,
SemiAuto
};
}
UCLASS()
class FPSTEMPLATE_API AWeapon : public AActor
{
GENERATED_BODY()
public:
AWeapon();
virtual void Tick(float DeltaTime) override;
virtual void OnRep_Instigator() override;
/** perform initial setup */
virtual void PostInitializeComponents() override;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
FGameplayTag WeaponType;
//////////////////////////////////////////////////////////////////////////
// Firing the Weapon
void Local_Fire(const FVector& ImpactPoint, const FVector& ImpactNormal, TEnumAsByte<EPhysicalSurface> SurfaceType, bool bIsFirstPerson);
int32 Auth_Fire();
void Rep_Fire(int32 AuthAmmo);
UFUNCTION(BlueprintImplementableEvent)
void FireEffects(const FVector& ImpactPoint, const FVector& ImpactNormal, EPhysicalSurface SurfaceType, bool bIsFirstPerson);
//////////////////////////////////////////////////////////////////////////
// Inventory
/** [server] weapon was added to pawn's inventory */
virtual void OnEnterInventory(APawn* NewOwner);
/** weapon is holstered by owner pawn */
virtual void OnUnEquip();
/** weapon is being equipped by owner pawn */
virtual void OnEquip(const AWeapon* LastWeapon);
/** set the weapon's owning pawn */
void SetOwningPawn(APawn* NewOwningPawn);
/** get weapon mesh (needs pawn owner to determine variant) */
USkeletalMeshComponent* GetWeaponMesh() const;
/** update weapon state */
void SetWeaponState(EWeaponState::Type NewState);
EWeaponState::Type GetWeaponState() const;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MagCapacity;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 Ammo;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 Sequence;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 StartingCarriedAmmo;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Damage;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float AimFieldOfView;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float HeadShotDamage;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float FireTime;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TEnumAsByte<EFireType::Type> FireType;
USkeletalMeshComponent* GetMesh1P() const;
USkeletalMeshComponent* GetMesh3P() const;
UMaterialInstanceDynamic* GetReticleDynamicMaterialInstance();
UMaterialInstanceDynamic* GetAmmoCounterDynamicMaterialInstance();
UMaterialInstanceDynamic* GetWeaponIconDynamicMaterialInstance();
UPROPERTY(EditDefaultsOnly, Category=Reticle)
FReticleParams ReticleParams;
protected:
virtual void BeginPlay() override;
//////////////////////////////////////////////////////////////////////////
// Inventory
/** attaches weapon mesh to pawn's mesh */
void AttachMeshToPawn();
/** detaches weapon mesh from pawn */
void DetachMeshFromPawn();
private:
/** weapon mesh: 1st person view */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta = (AllowPrivateAccess = "true"))
TObjectPtr<USkeletalMeshComponent> Mesh1P;
/** weapon mesh: 3rd person view */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta = (AllowPrivateAccess = "true"))
TObjectPtr<USkeletalMeshComponent> Mesh3P;
/** current weapon state */
EWeaponState::Type CurrentState;
UPROPERTY(EditDefaultsOnly, Category=Weapon)
TObjectPtr<UMaterialInterface> ReticleMaterial;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> DynMatInst_Reticle;
UPROPERTY(EditDefaultsOnly, Category=Weapon)
TObjectPtr<UMaterialInterface> AmmoCounterMaterial;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> DynMatInst_AmmoCounter;
UPROPERTY(EditDefaultsOnly, Category=Weapon)
TObjectPtr<UMaterialInterface> WeaponIconMaterial;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> DynMatInst_WeaponIcon;
};

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class FPSTemplateClientTarget : TargetRules
{
public FPSTemplateClientTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Client;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("FPSTemplate");
}
}

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class FPSTemplateEditorTarget : TargetRules
{
public FPSTemplateEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("FPSTemplate");
}
}

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class FPSTemplateServerTarget : TargetRules
{
public FPSTemplateServerTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Server;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("FPSTemplate");
}
}