Initial Commit - Lesson 31 (Commit #1)
This commit is contained in:
15
Source/FPSTemplate.Target.cs
Normal file
15
Source/FPSTemplate.Target.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
35
Source/FPSTemplate/FPSTemplate.Build.cs
Normal file
35
Source/FPSTemplate/FPSTemplate.Build.cs
Normal 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
|
||||
}
|
||||
}
|
||||
6
Source/FPSTemplate/FPSTemplate.cpp
Normal file
6
Source/FPSTemplate/FPSTemplate.cpp
Normal 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" );
|
||||
7
Source/FPSTemplate/FPSTemplate.h
Normal file
7
Source/FPSTemplate/FPSTemplate.h
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
#define ECC_Weapon ECollisionChannel::ECC_GameTraceChannel1
|
||||
415
Source/FPSTemplate/Private/Character/ShooterCharacter.cpp
Normal file
415
Source/FPSTemplate/Private/Character/ShooterCharacter.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
150
Source/FPSTemplate/Private/Character/ShooterHealthComponent.cpp
Normal file
150
Source/FPSTemplate/Private/Character/ShooterHealthComponent.cpp
Normal 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);
|
||||
}
|
||||
|
||||
645
Source/FPSTemplate/Private/Combat/CombatComponent.cpp
Normal file
645
Source/FPSTemplate/Private/Combat/CombatComponent.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
4
Source/FPSTemplate/Private/Data/SpecialElimData.cpp
Normal file
4
Source/FPSTemplate/Private/Data/SpecialElimData.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "Data/SpecialElimData.h"
|
||||
4
Source/FPSTemplate/Private/Data/WeaponData.cpp
Normal file
4
Source/FPSTemplate/Private/Data/WeaponData.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "Data/WeaponData.h"
|
||||
185
Source/FPSTemplate/Private/Elimination/EliminationComponent.cpp
Normal file
185
Source/FPSTemplate/Private/Elimination/EliminationComponent.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
77
Source/FPSTemplate/Private/Game/MatchGameState.cpp
Normal file
77
Source/FPSTemplate/Private/Game/MatchGameState.cpp
Normal 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();
|
||||
|
||||
}
|
||||
244
Source/FPSTemplate/Private/Game/ShooterGameMode.cpp
Normal file
244
Source/FPSTemplate/Private/Game/ShooterGameMode.cpp
Normal 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
|
||||
|
||||
|
||||
};
|
||||
57
Source/FPSTemplate/Private/Game/ShooterGameModeBase.cpp
Normal file
57
Source/FPSTemplate/Private/Game/ShooterGameModeBase.cpp
Normal 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]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "Interfaces/PlayerInterface.h"
|
||||
|
||||
// Add default functionality here for any IPlayerInterface functions that are not pure virtual.
|
||||
224
Source/FPSTemplate/Private/Player/MatchPlayerState.cpp
Normal file
224
Source/FPSTemplate/Private/Player/MatchPlayerState.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
1
Source/FPSTemplate/Private/ShooterTypes/ShooterTypes.cpp
Normal file
1
Source/FPSTemplate/Private/ShooterTypes/ShooterTypes.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "ShooterTypes/ShooterTypes.h"
|
||||
9
Source/FPSTemplate/Private/Tags/ShooterGameplayTags.cpp
Normal file
9
Source/FPSTemplate/Private/Tags/ShooterGameplayTags.cpp
Normal 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")
|
||||
|
||||
}
|
||||
63
Source/FPSTemplate/Private/UI/Elims/ScoreWidget.cpp
Normal file
63
Source/FPSTemplate/Private/UI/Elims/ScoreWidget.cpp
Normal 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;
|
||||
}
|
||||
35
Source/FPSTemplate/Private/UI/Elims/SpecialElimWidget.cpp
Normal file
35
Source/FPSTemplate/Private/UI/Elims/SpecialElimWidget.cpp
Normal 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);
|
||||
}
|
||||
90
Source/FPSTemplate/Private/UI/ShooterAmmoCounter.cpp
Normal file
90
Source/FPSTemplate/Private/UI/ShooterAmmoCounter.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
19
Source/FPSTemplate/Private/UI/ShooterHUD.cpp
Normal file
19
Source/FPSTemplate/Private/UI/ShooterHUD.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
161
Source/FPSTemplate/Private/UI/ShooterReticle.cpp
Normal file
161
Source/FPSTemplate/Private/UI/ShooterReticle.cpp
Normal 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;
|
||||
}
|
||||
|
||||
238
Source/FPSTemplate/Private/Weapon/Weapon.cpp
Normal file
238
Source/FPSTemplate/Private/Weapon/Weapon.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
149
Source/FPSTemplate/Public/Character/ShooterCharacter.h
Normal file
149
Source/FPSTemplate/Public/Character/ShooterCharacter.h
Normal 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;
|
||||
};
|
||||
|
||||
113
Source/FPSTemplate/Public/Character/ShooterHealthComponent.h
Normal file
113
Source/FPSTemplate/Public/Character/ShooterHealthComponent.h
Normal 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);
|
||||
|
||||
};
|
||||
155
Source/FPSTemplate/Public/Combat/CombatComponent.h
Normal file
155
Source/FPSTemplate/Public/Combat/CombatComponent.h
Normal 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;
|
||||
|
||||
};
|
||||
42
Source/FPSTemplate/Public/Data/SpecialElimData.h
Normal file
42
Source/FPSTemplate/Public/Data/SpecialElimData.h
Normal 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;
|
||||
};
|
||||
78
Source/FPSTemplate/Public/Data/WeaponData.h
Normal file
78
Source/FPSTemplate/Public/Data/WeaponData.h
Normal 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;
|
||||
};
|
||||
47
Source/FPSTemplate/Public/Elimination/EliminationComponent.h
Normal file
47
Source/FPSTemplate/Public/Elimination/EliminationComponent.h
Normal 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);
|
||||
};
|
||||
37
Source/FPSTemplate/Public/Game/MatchGameState.h
Normal file
37
Source/FPSTemplate/Public/Game/MatchGameState.h
Normal 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;
|
||||
};
|
||||
44
Source/FPSTemplate/Public/Game/ShooterGameMode.h
Normal file
44
Source/FPSTemplate/Public/Game/ShooterGameMode.h
Normal 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;
|
||||
};
|
||||
30
Source/FPSTemplate/Public/Game/ShooterGameModeBase.h
Normal file
30
Source/FPSTemplate/Public/Game/ShooterGameModeBase.h
Normal 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;
|
||||
};
|
||||
90
Source/FPSTemplate/Public/Interfaces/PlayerInterface.h
Normal file
90
Source/FPSTemplate/Public/Interfaces/PlayerInterface.h
Normal 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();
|
||||
};
|
||||
92
Source/FPSTemplate/Public/Player/MatchPlayerState.h
Normal file
92
Source/FPSTemplate/Public/Player/MatchPlayerState.h
Normal 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);
|
||||
};
|
||||
|
||||
|
||||
58
Source/FPSTemplate/Public/Player/ShooterPlayerController.h
Normal file
58
Source/FPSTemplate/Public/Player/ShooterPlayerController.h
Normal 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();
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
89
Source/FPSTemplate/Public/ShooterTypes/ShooterTypes.h
Normal file
89
Source/FPSTemplate/Public/ShooterTypes/ShooterTypes.h
Normal 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)
|
||||
11
Source/FPSTemplate/Public/Tags/ShooterGameplayTags.h
Normal file
11
Source/FPSTemplate/Public/Tags/ShooterGameplayTags.h
Normal 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);
|
||||
}
|
||||
33
Source/FPSTemplate/Public/UI/Elims/ScoreWidget.h
Normal file
33
Source/FPSTemplate/Public/UI/Elims/ScoreWidget.h
Normal 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;
|
||||
};
|
||||
29
Source/FPSTemplate/Public/UI/Elims/SpecialElimWidget.h
Normal file
29
Source/FPSTemplate/Public/UI/Elims/SpecialElimWidget.h
Normal 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);
|
||||
};
|
||||
49
Source/FPSTemplate/Public/UI/ShooterAmmoCounter.h
Normal file
49
Source/FPSTemplate/Public/UI/ShooterAmmoCounter.h
Normal 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);
|
||||
};
|
||||
31
Source/FPSTemplate/Public/UI/ShooterHUD.h
Normal file
31
Source/FPSTemplate/Public/UI/ShooterHUD.h
Normal 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;
|
||||
};
|
||||
86
Source/FPSTemplate/Public/UI/ShooterReticle.h
Normal file
86
Source/FPSTemplate/Public/UI/ShooterReticle.h
Normal 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);
|
||||
};
|
||||
|
||||
|
||||
164
Source/FPSTemplate/Public/Weapon/Weapon.h
Normal file
164
Source/FPSTemplate/Public/Weapon/Weapon.h
Normal 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;
|
||||
|
||||
};
|
||||
15
Source/FPSTemplateClient.Target.cs
Normal file
15
Source/FPSTemplateClient.Target.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
15
Source/FPSTemplateEditor.Target.cs
Normal file
15
Source/FPSTemplateEditor.Target.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
15
Source/FPSTemplateServer.Target.cs
Normal file
15
Source/FPSTemplateServer.Target.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user