From 894844c5e951f14697fd187c21b07f68edde63aa Mon Sep 17 00:00:00 2001 From: Shariq Date: Mon, 6 Jun 2022 17:46:11 +0500 Subject: [PATCH] Added Source files from svn repo --- MechDefence.Build.cs | 23 + MechDefence.cpp | 6 + MechDefence.h | 18 + MechDefenceGameModeBase.cpp | 5 + MechDefenceGameModeBase.h | 17 + Private/MAICharacterBase.cpp | 181 +++++++ Private/MAIEnemyShooter.cpp | 49 ++ Private/MCharacter.cpp | 770 +++++++++++++++++++++++++++++ Private/MConstructableBuilding.cpp | 215 ++++++++ Private/MEnemySpawnPoint.cpp | 32 ++ Private/MGameMode.cpp | 171 +++++++ Private/MGameplayActor.cpp | 28 ++ Private/MHealthComponent.cpp | 65 +++ Private/MInteractableActor.cpp | 6 + Private/MPlayerController.cpp | 69 +++ Private/MPlayerMech.cpp | 188 +++++++ Private/MProjectile.cpp | 48 ++ Private/MTargetableActor.cpp | 6 + Private/MWeapon.cpp | 419 ++++++++++++++++ Public/MAICharacterBase.h | 97 ++++ Public/MAIEnemyShooter.h | 44 ++ Public/MCharacter.h | 283 +++++++++++ Public/MConstructableBuilding.h | 117 +++++ Public/MEnemySpawnPoint.h | 31 ++ Public/MGameMode.h | 96 ++++ Public/MGameplayActor.h | 33 ++ Public/MHealthComponent.h | 63 +++ Public/MInteractableActor.h | 26 + Public/MPlayerController.h | 52 ++ Public/MPlayerMech.h | 90 ++++ Public/MProjectile.h | 45 ++ Public/MTargetableActor.h | 27 + Public/MWeapon.h | 204 ++++++++ Test.txt | 1 - 34 files changed, 3524 insertions(+), 1 deletion(-) create mode 100644 MechDefence.Build.cs create mode 100644 MechDefence.cpp create mode 100644 MechDefence.h create mode 100644 MechDefenceGameModeBase.cpp create mode 100644 MechDefenceGameModeBase.h create mode 100644 Private/MAICharacterBase.cpp create mode 100644 Private/MAIEnemyShooter.cpp create mode 100644 Private/MCharacter.cpp create mode 100644 Private/MConstructableBuilding.cpp create mode 100644 Private/MEnemySpawnPoint.cpp create mode 100644 Private/MGameMode.cpp create mode 100644 Private/MGameplayActor.cpp create mode 100644 Private/MHealthComponent.cpp create mode 100644 Private/MInteractableActor.cpp create mode 100644 Private/MPlayerController.cpp create mode 100644 Private/MPlayerMech.cpp create mode 100644 Private/MProjectile.cpp create mode 100644 Private/MTargetableActor.cpp create mode 100644 Private/MWeapon.cpp create mode 100644 Public/MAICharacterBase.h create mode 100644 Public/MAIEnemyShooter.h create mode 100644 Public/MCharacter.h create mode 100644 Public/MConstructableBuilding.h create mode 100644 Public/MEnemySpawnPoint.h create mode 100644 Public/MGameMode.h create mode 100644 Public/MGameplayActor.h create mode 100644 Public/MHealthComponent.h create mode 100644 Public/MInteractableActor.h create mode 100644 Public/MPlayerController.h create mode 100644 Public/MPlayerMech.h create mode 100644 Public/MProjectile.h create mode 100644 Public/MTargetableActor.h create mode 100644 Public/MWeapon.h delete mode 100644 Test.txt diff --git a/MechDefence.Build.cs b/MechDefence.Build.cs new file mode 100644 index 0000000..d51fb69 --- /dev/null +++ b/MechDefence.Build.cs @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MechDefence : ModuleRules +{ + public MechDefence(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GameplayTags", "NavigationSystem", "UMG", "AIModule", "Niagara", "PhysicsCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // 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 + } +} diff --git a/MechDefence.cpp b/MechDefence.cpp new file mode 100644 index 0000000..5776b97 --- /dev/null +++ b/MechDefence.cpp @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MechDefence.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, MechDefence, "MechDefence" ); diff --git a/MechDefence.h b/MechDefence.h new file mode 100644 index 0000000..d67abbd --- /dev/null +++ b/MechDefence.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +#define TRACE_CHANNEL_Interactable ECC_GameTraceChannel1 +#define TRACE_CHANNEL_PlayerTeam ECC_GameTraceChannel2 +#define TRACE_CHANNEL_Weapon ECC_GameTraceChannel3 +#define TRACE_CHANNEL_BuildingPlacement ECC_GameTraceChannel4 +#define TRACE_CHANNEL_ConstructedBuilding ECC_GameTraceChannel5 + +#define SurfaceType_HighFriction SurfaceType1 +#define SurfaceType_MediumFriction SurfaceType2 +#define SurfaceType_Vulnerable SurfaceType3 + +#define COLLISION_OBJECT_UIEnablingVolume ECC_GameTraceChannel6 diff --git a/MechDefenceGameModeBase.cpp b/MechDefenceGameModeBase.cpp new file mode 100644 index 0000000..c2f55b3 --- /dev/null +++ b/MechDefenceGameModeBase.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "MechDefenceGameModeBase.h" + diff --git a/MechDefenceGameModeBase.h b/MechDefenceGameModeBase.h new file mode 100644 index 0000000..5a3abbf --- /dev/null +++ b/MechDefenceGameModeBase.h @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "MechDefenceGameModeBase.generated.h" + +/** + * + */ +UCLASS() +class MECHDEFENCE_API AMechDefenceGameModeBase : public AGameModeBase +{ + GENERATED_BODY() + +}; diff --git a/Private/MAICharacterBase.cpp b/Private/MAICharacterBase.cpp new file mode 100644 index 0000000..de64149 --- /dev/null +++ b/Private/MAICharacterBase.cpp @@ -0,0 +1,181 @@ + + + +#include "MAICharacterBase.h" + + +#include "MCharacter.h" +#include "Components/CapsuleComponent.h" +#include "Components/ShapeComponent.h" +#include "Components/WidgetComponent.h" +#include "MGameplayActor.h" +#include "MHealthComponent.h" +#include "Misc/OutputDeviceDebug.h" + +AMAICharacterBase::AMAICharacterBase() +{ + PrimaryActorTick.bCanEverTick = true; + + HealthComponent = CreateDefaultSubobject(TEXT("HealthComponent")); + HealthComponent->OnActorDeath.AddDynamic(this, &AMAICharacterBase::HandleCharacterDeath); + + static ConstructorHelpers::FClassFinder SafeZoneClassFinder(TEXT("/Game/MechDefence/Blueprints/Buildings/BP_Beacon")); + SafeZoneClass = SafeZoneClassFinder.Class; + SafeZoneTags.AddTag(FGameplayTag::RequestGameplayTag("PlayerTeam.Beacon")); + + StatusIndicatorWidget = CreateDefaultSubobject(TEXT("Status Indicator")); + StatusIndicatorWidget->SetupAttachment(RootComponent); + + bIsInSafeZone = true; +} + +void AMAICharacterBase::BeginPlay() +{ + Super::BeginPlay(); + + UShapeComponent* OverlappingShape = GetSafeZoneOverlappingShape(); + if(OverlappingShape) + { + OverlappingShape->OnComponentBeginOverlap.AddDynamic(this, &AMAICharacterBase::HandleSafeZoneBeginOverlap); + OverlappingShape->OnComponentEndOverlap.AddDynamic(this, &AMAICharacterBase::HandleSafeZoneEndOverlap); + } + + // Perform these in the next tick to make sure everything is spawned and assigned before we go further + GetWorldTimerManager().SetTimerForNextTick(this, &AMAICharacterBase::CheckSafeZone); + GetWorldTimerManager().SetTimerForNextTick(this, &AMAICharacterBase::SetWidgetHealthComponentProperty); +} + +void AMAICharacterBase::SetWidgetHealthComponentProperty() +{ + UUserWidget* IndicatorWidget = StatusIndicatorWidget->GetWidget(); + if(IndicatorWidget) + { + FObjectProperty* ObjProp = FindFProperty(IndicatorWidget->GetClass(), TEXT("HealthComponent")); + if(ObjProp) + { + ObjProp->SetObjectPropertyValue_InContainer(IndicatorWidget, HealthComponent, 0); + //ObjProp->SetPropertyValue_InContainer(IndicatorWidget, HealthComponent, 0); + } + + const FString Command = FString::Printf(TEXT("SetupHealthChangeAnims")); + FOutputDeviceDebug DebugOutputDevice; + if(!IndicatorWidget->CallFunctionByNameWithArguments(*Command, DebugOutputDevice, IndicatorWidget, true)) + { + UE_LOG(LogTemp, Log, TEXT("Failed to setup health change anims")); + } + } +} + +UShapeComponent* AMAICharacterBase::GetSafeZoneOverlappingShape() +{ + return GetCapsuleComponent(); +} + +void AMAICharacterBase::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +void AMAICharacterBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + +} + +void AMAICharacterBase::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer = ActorTags; +} + +void AMAICharacterBase::HandleSafeZoneBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OtherActor); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && !bIsInSafeZone) + { + EnterSafeZone(OtherActor); + } +} + +void AMAICharacterBase::HandleSafeZoneEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OtherActor); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && bIsInSafeZone) + { + // Check again just to make sure + CheckSafeZone(); + } +} + +void AMAICharacterBase::HandleCharacterDeath(UMHealthComponent* ActorHealthComponent, AActor* DeadActor, AActor* KillerActor) +{ + UShapeComponent* ShapeMesh = GetSafeZoneOverlappingShape(); + ShapeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + DeadActor->SetActorEnableCollision(false); +} + + +void AMAICharacterBase::AddGameplayTags(const FGameplayTagContainer& TagContainer) +{ + ActorTags.AppendTags(TagContainer); +} + +void AMAICharacterBase::AddGameplayTag(const FGameplayTag& Tag) +{ + ActorTags.AddTag(Tag); +} + +void AMAICharacterBase::LeaveSafeZone() +{ + bIsInSafeZone = false; + OnAILeaveSafeZone.Broadcast(this); + if(StatusIndicatorWidget) + { + StatusIndicatorWidget->SetVisibility(false, true); + } +} + +void AMAICharacterBase::EnterSafeZone(AActor* SafeZoneActor) +{ + OnAIEnterSafeZone.Broadcast(this, SafeZoneActor); + bIsInSafeZone = true; + if(StatusIndicatorWidget) + { + StatusIndicatorWidget->SetVisibility(true, true); + } +} + +void AMAICharacterBase::CheckSafeZone() +{ + TArray OverlappingActors; + UShapeComponent* SafeZoneOverlappingShape = GetSafeZoneOverlappingShape(); + if(SafeZoneOverlappingShape) + { + SafeZoneOverlappingShape->GetOverlappingActors(OverlappingActors, SafeZoneClass); + if(OverlappingActors.Num() == 0) + { + if(bIsInSafeZone) + LeaveSafeZone(); + } + else if(OverlappingActors.Num() > 0) + { + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OverlappingActors[0]); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags)) + { + if(!bIsInSafeZone) // If we're not in safe zone yet, enter it + { + EnterSafeZone(OverlappingActors[0]); + } + else + { + // If we're already in safe zone, check if we need healing + + + } + + } + } + } +} diff --git a/Private/MAIEnemyShooter.cpp b/Private/MAIEnemyShooter.cpp new file mode 100644 index 0000000..3a172e7 --- /dev/null +++ b/Private/MAIEnemyShooter.cpp @@ -0,0 +1,49 @@ + + + +#include "MAIEnemyShooter.h" +#include "MWeapon.h" +#include "MHealthComponent.h" +#include "NavigationSystem.h" +#include "Components/CapsuleComponent.h" + +AMAIEnemyShooter::AMAIEnemyShooter() +{ + WeaponSocketName = "WeaponSocket"; + + bCanIdle = true; + PatrolRadius = 500; +} + +void AMAIEnemyShooter::BeginPlay() +{ + Super::BeginPlay(); + + StartingLocation = GetActorLocation(); + + //Spawn Default Weapon + FActorSpawnParameters SpawnParameters; + SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + CurrentWeapon = GetWorld()->SpawnActor(StarterWeaponClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters); + if(CurrentWeapon) + { + CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocketName); + CurrentWeapon->SetOwner(this); + } +} + +UShapeComponent* AMAIEnemyShooter::GetSafeZoneOverlappingShape() +{ + return GetCapsuleComponent(); +} + +FVector AMAIEnemyShooter::PickNextPartrolDestination() +{ + FNavLocation ResultLocation; + UNavigationSystemV1* NavigationSystem = UNavigationSystemV1::GetNavigationSystem(GetController()); + if(NavigationSystem && NavigationSystem->GetRandomPointInNavigableRadius(StartingLocation, PatrolRadius, ResultLocation)) + { + return ResultLocation.Location; + } + return FVector(); +} diff --git a/Private/MCharacter.cpp b/Private/MCharacter.cpp new file mode 100644 index 0000000..ca886b4 --- /dev/null +++ b/Private/MCharacter.cpp @@ -0,0 +1,770 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MCharacter.h" + +#include "DrawDebugHelpers.h" +#include "MPlayerMech.h" +#include "Camera/CameraComponent.h" +#include "Components/CapsuleComponent.h" +#include "GameFramework/PawnMovementComponent.h" +#include "MechDefence/MechDefence.h" +#include "MWeapon.h" +#include "MHealthComponent.h" +#include "HAL/IConsoleManager.h" +#include "EngineUtils.h" +#include "MConstructableBuilding.h" +#include "Kismet/GameplayStatics.h" +#include "Components/AudioComponent.h" +#include "MGameplayActor.h" +#include "MInteractableActor.h" +#include "GameFramework/CharacterMovementComponent.h" + +static TAutoConsoleVariable CVAR_DebugPlayerDrawing( + TEXT("MDEF.DebugPlayerDrawing"), + 0, + TEXT("Draw debug visualizations for the player character"), + ECVF_Cheat); + +static TAutoConsoleVariable CVAR_GodMode( + TEXT("MDEF.GodMode"), + 0, + TEXT("Make the player character invincible"), + ECVF_Cheat); + +AMCharacter::AMCharacter() +{ + PrimaryActorTick.bCanEverTick = true; + + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + CameraComponent->SetupAttachment(GetCapsuleComponent()); + CameraComponent->SetRelativeLocation(FVector(0.f, 0.f, BaseEyeHeight)); + CameraComponent->bUsePawnControlRotation = true; + + PlayerArms = CreateDefaultSubobject(TEXT("PlayerArms")); + PlayerArms->SetupAttachment(CameraComponent); + PlayerArms->SetRelativeLocation(FVector(0.f, 0.f, -155.f)); + PlayerArms->SetRelativeRotation(FRotator(2.f, -15.f, 5.f)); + + GetMovementComponent()->GetNavAgentPropertiesRef().bCanCrouch = true; + + HealthComponent = CreateDefaultSubobject(TEXT("HealthComponent")); + + AudioComponent = CreateDefaultSubobject(TEXT("AudioComponent")); + AudioComponent->SetupAttachment(RootComponent); + + MaxInteractableDistance = 1000.f; + HoveredInteractableActor = nullptr; + bDied = false; + CurrentWeapon = nullptr; + bWantsToZoom = false; + TargetFOV = 45.f; + ZoomInterpolateSpeed = 20.f; + DefaultFOV = CameraComponent->FieldOfView; + OutOfSafeZoneDamageInterval = 1.f; + OutOfSafeZoneDamage = 10.f; + SafeZoneHealAmount = 10.f; + SafeZoneHealInterval = 0.5f; + SafeZoneHealDelay = 5.f; + bIsInSafeZone = false; + bIsFirstTimeHeal = true; + MinimumMovementVelocityThreshold = 100.f; + MaxStamina = 100.f; + CurrentStamina = MaxStamina; + StaminaConsumeAmount = 5.f; + StaminaConsumeInterval = 0.5f; + StaminaRestoreAmount = 10.f; + StaminaRestoreInterval = 0.5f; + StaminaRestoreDelay = 2.f; + SprintMultiplier = 2.f; + bIsSprinting = false; + bCanSprint = true; + ArmRotateSpeed = 5.f; + HiddenArmPitch = -60.f; + HiddenArmLocation = FVector(-20, -5, -200); + + FAutoConsoleVariableSink CVarConsoleVarSink(FConsoleCommandDelegate::CreateUObject(this, &AMCharacter::HandleCVARChanged)); +} + +void AMCharacter::BeginPlay() +{ + Super::BeginPlay(); + + DefaultArmPitch = PlayerArms->GetRelativeRotation().Pitch; + DefaultArmLocation = PlayerArms->GetRelativeLocation(); + + HealthComponent->OnHealthChanged.AddDynamic(this, &AMCharacter::HandleHealthChanged); + if(CVAR_GodMode.GetValueOnGameThread() > 0) + HealthComponent->bIsInvincible = true; + + // Spawn weapons + FActorSpawnParameters SpawnParameters; + SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + Weapons.SetNumZeroed((int32)EWeaponType::MaxWeaponType); + + if(PrimaryWeaponClass) + { + AMWeapon* PrimaryWeapon = GetWorld()->SpawnActor(PrimaryWeaponClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters); + if(PrimaryWeapon) + { + PrimaryWeapon->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, PrimaryWeapon->GetPlayerAttachmentSocketName()); + Weapons[(int)EWeaponType::PrimaryWeapon] = PrimaryWeapon; + PrimaryWeapon->SetOwner(this); + PrimaryWeapon->SetActorEnableCollision(false); + OnWeaponEquipped.AddDynamic(PrimaryWeapon, &AMWeapon::HandleWeaponEquipped); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("Primary Weapon Class not specified. Please set primary weapon class")); + } + + if(ScannerClass) + { + AMWeapon* Scanner = GetWorld()->SpawnActor(ScannerClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters); + if(Scanner) + { + Scanner->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, Scanner->GetPlayerAttachmentSocketName()); + Weapons[(int)EWeaponType::Scanner] = Scanner; + Scanner->SetOwner(this); + Scanner->SetActorHiddenInGame(true); + Scanner->SetActorTickEnabled(false); + Scanner->SetActorEnableCollision(false); + OnWeaponEquipped.AddDynamic(Scanner, &AMWeapon::HandleWeaponEquipped); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("Scanner Class not specified. Please set scanner class")); + } + + if(BlowTorchClass) + { + AMWeapon* BlowTorch = GetWorld()->SpawnActor(BlowTorchClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters); + if(BlowTorch) + { + BlowTorch->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, BlowTorch->GetPlayerAttachmentSocketName()); + Weapons[(int)EWeaponType::BlowTorch] = BlowTorch; + BlowTorch->SetOwner(this); + BlowTorch->SetActorHiddenInGame(true); + BlowTorch->SetActorTickEnabled(false); + BlowTorch->SetActorEnableCollision(false); + OnWeaponEquipped.AddDynamic(BlowTorch, &AMWeapon::HandleWeaponEquipped); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("BlowTorch Class not specified. Please set blowtorch class")); + } + + if(TabletClass) + { + AMWeapon* Tablet = GetWorld()->SpawnActor(TabletClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters); + if(Tablet) + { + Tablet->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, Tablet->GetPlayerAttachmentSocketName()); + Weapons[(int)EWeaponType::Tablet] = Tablet; + Tablet->SetOwner(this); + Tablet->SetActorHiddenInGame(true); + Tablet->SetActorTickEnabled(false); + Tablet->SetActorEnableCollision(false); + OnWeaponEquipped.AddDynamic(Tablet, &AMWeapon::HandleWeaponEquipped); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("Tablet Class not specified. Please set tablet class")); + } + + EquipWeapon((uint8)EWeaponType::PrimaryWeapon); + + + // Safezone checks + UCapsuleComponent* PlayerCapsuleComponent = GetCapsuleComponent(); + if(PlayerCapsuleComponent) + { + PlayerCapsuleComponent->OnComponentBeginOverlap.AddDynamic(this, &AMCharacter::HandleCapsuleBeginOverlap); + PlayerCapsuleComponent->OnComponentEndOverlap.AddDynamic(this, &AMCharacter::HandleCapsuleEndOverlap); + } + + // GetWorldTimerManager().SetTimerForNextTick(this, &AMCharacter::CheckIfInSafeZone); + GetWorldTimerManager().SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::CheckIfInSafeZone, 2.f); +} + +void AMCharacter::HandleCVARChanged() +{ + bool GodMode = CVAR_GodMode.GetValueOnGameThread() > 0; + HealthComponent->bIsInvincible = GodMode; + + UWorld* CurrentWorld = GetWorld(); + if(CurrentWorld) + { + for(TActorIterator It(CurrentWorld); It; ++It) + { + AMPlayerMech* PlayerVehicle = *It; + UMHealthComponent* HC = Cast(PlayerVehicle->GetComponentByClass(UMHealthComponent::StaticClass())); + if(HC) + { + HC->bIsInvincible = GodMode; + } + } + } +} + +void AMCharacter::CheckIfInSafeZone() +{ + TArray OverlappingActors; + UCapsuleComponent* ActorCapsule = GetCapsuleComponent(); + if(ActorCapsule) + { + ActorCapsule->GetOverlappingActors(OverlappingActors, SafeZoneClass); + if(OverlappingActors.Num() == 0) + { + if(bIsInSafeZone) + LeaveSafeZone(); + } + else if(OverlappingActors.Num() > 0) + { + for(AActor* OverlappedActor : OverlappingActors) + { + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OverlappingActors[0]); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags)) + { + //Check if the safezone is constructed then determine if we are inside a constructed or underconstruction safezone + AMConstructableBuilding* ConstructableBuilding = Cast(OverlappedActor); + if(ConstructableBuilding) + { + if(ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed) + { + if(!bIsInSafeZone) // If we're not in safe zone yet, enter it + { + EnterSafeZone(OverlappedActor); + } + else + { + // If we're already in safe zone, check if we need healing + FTimerManager& TimerManager = GetWorldTimerManager(); + if(!HealthComponent->IsAtMaxHealth() && !TimerManager.IsTimerActive(TimerHandle_ApplySafeZoneHeal)) + { + TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay); + + } + + } + break; + } + else + { + // We are inside a safezone but it isnt constructed + if(bIsInSafeZone) + LeaveSafeZone(); + } + } + } + } + + } + } +} + +void AMCharacter::MoveForward(float ScaleValue) +{ + AddMovementInput(GetActorForwardVector(), ScaleValue); +} + +void AMCharacter::MoveRight(float ScaleValue) +{ + AddMovementInput(GetActorRightVector(), ScaleValue); +} + +void AMCharacter::StartCrouch() +{ + Crouch(); +} + +void AMCharacter::StopCrouch() +{ + UnCrouch(); +} + +void AMCharacter::StartFire() +{ + if(CurrentWeapon) + { + if(CurrentWeapon->StartFire()) + { + UAnimInstance* AnimInstance = PlayerArms->GetAnimInstance(); + if(AnimInstance && FireAnimation) + { + AnimInstance->PlaySlotAnimationAsDynamicMontage(FireAnimation, "Arms", 0.f); + } + } + } +} + +void AMCharacter::StopFire() +{ + if(CurrentWeapon) + { + CurrentWeapon->StopFire(); + } +} + +void AMCharacter::Reload() +{ + if(CurrentWeapon && !CurrentWeapon->IsReloading()) + { + CurrentWeapon->Reload(); + UAnimInstance* AnimInstance = PlayerArms->GetAnimInstance(); + if(AnimInstance && ReloadAnimation) + { + AnimInstance->PlaySlotAnimationAsDynamicMontage(ReloadAnimation, "Arms", 0.f); + } + } +} + +void AMCharacter::StartZoom() +{ + bWantsToZoom = true; +} + +void AMCharacter::StopZoom() +{ + bWantsToZoom = false; +} + +void AMCharacter::StartSprint() +{ + if(bCanSprint && !bIsSprinting) + { + UCharacterMovementComponent* MovementComponent = GetCharacterMovement(); + if(MovementComponent) + { + MovementComponent->MaxWalkSpeed *= SprintMultiplier; + GetWorldTimerManager().ClearTimer(TimerHandle_RestoreStamina); + GetWorldTimerManager().SetTimer(TimerHandle_ConsumeStamina, this, &AMCharacter::ConsumeStamina, StaminaConsumeInterval); + bIsSprinting = true; + const FVector CurrentLocation = GetActorLocation(); + PreviousLocation = FVector2D(CurrentLocation.X, CurrentLocation.Y); + } + } +} + +void AMCharacter::StopSprint() +{ + if(bIsSprinting) + { + bIsSprinting = false; + bCanSprint = CurrentStamina > 0.f ? true : false; + UCharacterMovementComponent* MovementComponent = GetCharacterMovement(); + if(MovementComponent) + { + MovementComponent->MaxWalkSpeed /= SprintMultiplier; + GetWorldTimerManager().ClearTimer(TimerHandle_ConsumeStamina); + GetWorldTimerManager().SetTimer(TimerHandle_RestoreStamina, this, &AMCharacter::RestoreStamina, StaminaRestoreInterval, false, StaminaRestoreDelay); + } + } +} + +void AMCharacter::ConsumeStamina() +{ + const FVector CurrentLocation = GetActorLocation(); + const FVector2D CurrentLocation2D = FVector2D(CurrentLocation.X, CurrentLocation.Y); + if(PreviousLocation != CurrentLocation2D) + { + CurrentStamina = FMath::Max(0.f, CurrentStamina - StaminaConsumeAmount); + PreviousLocation = CurrentLocation2D; + } + + if(CurrentStamina > 0.f) + { + GetWorldTimerManager().SetTimer(TimerHandle_ConsumeStamina, this, &AMCharacter::ConsumeStamina, StaminaConsumeInterval); + } + else + { + StopSprint(); + } +} + +void AMCharacter::RestoreStamina() +{ + CurrentStamina = FMath::Min(MaxStamina, CurrentStamina + StaminaRestoreAmount); + if(CurrentStamina < MaxStamina) + { + GetWorldTimerManager().SetTimer(TimerHandle_RestoreStamina, this, &AMCharacter::RestoreStamina, StaminaRestoreInterval); + } + else + { + bCanSprint = true; + } +} + +void AMCharacter::EnterSafeZone(AActor* SafeZoneActor) +{ + OnCharacterEnterSafeZone.Broadcast(this, SafeZoneActor); + FTimerManager& TimerManager = GetWorldTimerManager(); + + if(!HealthComponent->IsAtMaxHealth()) + TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay); + + bIsInSafeZone = true; + + if(SafeZoneEnterSound) + UGameplayStatics::PlaySound2D(this, SafeZoneEnterSound); + + if(TimerManager.IsTimerActive(TimerHandle_ApplyOutOfSafeZoneDamage)) + TimerManager.ClearTimer(TimerHandle_ApplyOutOfSafeZoneDamage); +} + +void AMCharacter::LeaveSafeZone() +{ + if(bIsMounted) + return; + + bIsInSafeZone = false; + GetWorldTimerManager().SetTimer(TimerHandle_ApplyOutOfSafeZoneDamage, this, &AMCharacter::ApplyOutOfSafeZoneDamage, OutOfSafeZoneDamageInterval, true); + GetWorldTimerManager().ClearTimer(TimerHandle_ApplySafeZoneHeal); + OnCharacterLeaveSafeZone.Broadcast(this); + + if(SafeZoneLeaveSound) + UGameplayStatics::PlaySound2D(this, SafeZoneLeaveSound); +} + +void AMCharacter::StartHidingEquippedItem() +{ + bWantsToHideEquippedItem = true; +} + +void AMCharacter::StopHidingEquippedItem() +{ + bWantsToHideEquippedItem = false; +} + +void AMCharacter::SetTargetFOV(float NewTargetFOV) +{ + if(NewTargetFOV != DefaultFOV) + { + TargetFOV = NewTargetFOV; + bWantsToZoom = true; + } +} + +void AMCharacter::ResetToDefaultFOV() +{ + TargetFOV = DefaultFOV; + bWantsToZoom = false; +} + +void AMCharacter::ApplyOutOfSafeZoneDamage() +{ + if(!bIsInSafeZone) + { + if(!HealthComponent->IsDead()) + { + UGameplayStatics::ApplyDamage(this, OutOfSafeZoneDamage, GetController(), this, OutOfSafeZoneDamageClass); + GetWorldTimerManager().SetTimer(TimerHandle_ApplyOutOfSafeZoneDamage, this, &AMCharacter::ApplyOutOfSafeZoneDamage, OutOfSafeZoneDamageInterval); + } + else + { + GetWorldTimerManager().ClearTimer(TimerHandle_ApplyOutOfSafeZoneDamage); + } + } +} + +void AMCharacter::ApplySafeZoneHealing() +{ + if(!HealthComponent->IsAtMaxHealth()) + { + HealthComponent->Heal(SafeZoneHealAmount); + if(bIsFirstTimeHeal) + { + if(HealingStartSound) + UGameplayStatics::PlaySound2D(this, HealingStartSound); + bIsFirstTimeHeal = false; + } + else + { + if(HealingInProgressSound) + UGameplayStatics::PlaySound2D(this, HealingInProgressSound); + } + + } + + if(HealthComponent->IsAtMaxHealth()) + { + GetWorldTimerManager().ClearTimer(TimerHandle_ApplySafeZoneHeal); + bIsFirstTimeHeal = true; + if(HealingCompletedSound) + UGameplayStatics::PlaySound2D(this, HealingCompletedSound); + } + else + { + GetWorldTimerManager().SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval); + } +} + +void AMCharacter::Interact() +{ + if(!HoveredInteractableActor) + return; + + IGameplayTagAssetInterface* GameplayTagAsset = Cast(HoveredInteractableActor); + if(GameplayTagAsset && GameplayTagAsset->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Interactable.Mountable"))) + { + APlayerController* PC = Cast(GetController()); + if(PC) + { + PC->Possess(Cast(HoveredInteractableActor)); + AMPlayerMech* Mech = Cast(HoveredInteractableActor); + if(Mech) + { + bIsMounted = true; + EnterSafeZone(Mech); + Mech->MountPlayerCharacter(this); + + // Disable outline + IMInteractableActor* InteractableActor = Cast(HoveredInteractableActor); + if(InteractableActor) + { + UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent(); + if(InteractableActorMesh) + { + InteractableActorMesh->SetRenderCustomDepth(false); + } + } + } + UE_LOG(LogTemp, Log, TEXT("Player Possessed %s"), *HoveredInteractableActor->GetName()); + } + } +} + +void AMCharacter::HandleCapsuleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OtherActor); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && !bIsInSafeZone) + { + AMConstructableBuilding* ConstructableBuilding = Cast(OtherActor); + if(ConstructableBuilding && ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed) + { + EnterSafeZone(OtherActor); + } + } +} + +void AMCharacter::HandleCapsuleEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + IGameplayTagAssetInterface* GameplayTagAsset = Cast(OtherActor); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && bIsInSafeZone) + { + // Check again just to make sure, we might have left one safe zone but might still be inside + // another. This can happen if the perimeters of both safezone overlap. We're still technically + // inside 'A' safe zone even if we might have left this one. This check is to make sure this + // scenario is correctly handled. + AMConstructableBuilding* ConstructableBuilding = Cast(OtherActor); + if(ConstructableBuilding && ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed) + { + CheckIfInSafeZone(); + } + } +} + +void AMCharacter::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Check for hovered interactable objects + FHitResult TraceHit; + FVector TraceStartLocation = CameraComponent->GetComponentLocation(); + FVector TraceEndLocation; + TraceEndLocation = TraceStartLocation + CameraComponent->GetComponentRotation().Vector() * MaxInteractableDistance; + FCollisionQueryParams CollisionParams; + CollisionParams.AddIgnoredActor(this); + + AActor* PreviousHoveredInteractableActor = HoveredInteractableActor; + bool bShouldUnHover = false; + if(GetWorld()->LineTraceSingleByChannel(TraceHit, TraceStartLocation, TraceEndLocation, TRACE_CHANNEL_Interactable, CollisionParams)) + { + AActor* NewHoveredInteractableActor = TraceHit.Actor.Get(); + if(NewHoveredInteractableActor && NewHoveredInteractableActor != HoveredInteractableActor) + { + //Hover new actor + HoveredInteractableActor = NewHoveredInteractableActor; + bShouldUnHover = true; + IMInteractableActor* InteractableActor = Cast(HoveredInteractableActor); + if(InteractableActor) + { + UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent(); + if(InteractableActorMesh) + { + InteractableActorMesh->SetRenderCustomDepth(true); + } + } + } + } + else + { + HoveredInteractableActor = nullptr; + bShouldUnHover = true; + } + + //Unhover previous interactable actor + if(PreviousHoveredInteractableActor && bShouldUnHover) + { + IMInteractableActor* InteractableActor = Cast(PreviousHoveredInteractableActor); + if(InteractableActor) + { + UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent(); + if(InteractableActorMesh) + { + InteractableActorMesh->SetRenderCustomDepth(false); + } + } + } + + if(CVAR_DebugPlayerDrawing.GetValueOnGameThread() > 0) + DrawDebugLine(GetWorld(), TraceStartLocation, TraceEndLocation, FColor::Green, false, 1.f, 0, 1.f); + + + // Zoom in/out + float NewTargetFOV = bWantsToZoom ? TargetFOV : DefaultFOV; + if(CameraComponent->FieldOfView != NewTargetFOV) + { + float NewFOV = FMath::FInterpTo(CameraComponent->FieldOfView, NewTargetFOV, DeltaTime, ZoomInterpolateSpeed); + CameraComponent->SetFieldOfView(NewFOV); + } + + // Hide/Show equipped item + float TargetPitch = bWantsToHideEquippedItem ? HiddenArmPitch : DefaultArmPitch; + float CurrentPitch = PlayerArms->GetRelativeRotation().Pitch; + if(CurrentPitch != TargetPitch) + { + float NewPitch = FMath::FInterpTo(CurrentPitch, TargetPitch, DeltaTime, ArmRotateSpeed); + FRotator NewRotation = PlayerArms->GetRelativeRotation(); + NewRotation.Pitch = NewPitch; + PlayerArms->SetRelativeRotation(NewRotation); + } + + FVector TargetArmLocation = bWantsToHideEquippedItem ? HiddenArmLocation : DefaultArmLocation; + FVector CurrentLocation = PlayerArms->GetRelativeLocation(); + if(CurrentLocation != TargetArmLocation) + { + FVector NewLocation = FMath::VInterpTo(CurrentLocation, TargetArmLocation, DeltaTime, ArmRotateSpeed); + PlayerArms->SetRelativeLocation(NewLocation); + } + + + // Movement Sounds + float CurrentVelocity = GetVelocity().Size(); + UPawnMovementComponent* MC = GetMovementComponent(); + if(MC && CurrentVelocity > MinimumMovementVelocityThreshold && MC->IsMovingOnGround()) + { + if(MC->IsCrouching() && CrouchWalkSound && !AudioComponent->IsPlaying()) + { + AudioComponent->Stop(); + AudioComponent->SetSound(CrouchWalkSound); + AudioComponent->Play(); + } + else if(WalkSound && !AudioComponent->IsPlaying()) + { + AudioComponent->Stop(); + AudioComponent->SetSound(WalkSound); + AudioComponent->Play(); + } + } +} + +void AMCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + PlayerInputComponent->BindAxis("MoveForward", this, &AMCharacter::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &AMCharacter::MoveRight); + + PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); + PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); + + PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump); + + PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &AMCharacter::StartCrouch); + PlayerInputComponent->BindAction("Crouch", IE_Released, this, &AMCharacter::StopCrouch); + + PlayerInputComponent->BindAction("Dismount", IE_Pressed, this, &AMCharacter::Interact); + + PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AMCharacter::StartFire); + PlayerInputComponent->BindAction("Fire", IE_Released, this, &AMCharacter::StopFire); + + // PlayerInputComponent->BindAction("SecondaryFire", IE_Pressed, this, &AMCharacter::StartZoom); + // PlayerInputComponent->BindAction("SecondaryFire", IE_Released, this, &AMCharacter::StopZoom); + + PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMCharacter::StartSprint); + PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMCharacter::StopSprint); + + PlayerInputComponent->BindAction("Reload", IE_Pressed, this, &AMCharacter::Reload); +} + +void AMCharacter::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer = ActorTags; +} + +void AMCharacter::EquipWeapon(uint8 Weapon) +{ + EWeaponType WeaponType = (EWeaponType)Weapon; + if((CurrentWeapon && CurrentWeapon->WeaponType == WeaponType) || WeaponType >= EWeaponType::MaxWeaponType) + return; + + if(Weapons[Weapon]) + { + AMWeapon* PreviousWeapon = nullptr; + if(CurrentWeapon) + { + if(CurrentWeapon->IsReloading()) + CurrentWeapon->CancelReload(); + CurrentWeapon->SetActorHiddenInGame(true); + CurrentWeapon->SetActorTickEnabled(false); + PreviousWeapon = CurrentWeapon; + } + + CurrentWeapon = Weapons[Weapon]; + CurrentWeapon->SetActorHiddenInGame(false); + CurrentWeapon->SetActorTickEnabled(true); + + OnWeaponEquipped.Broadcast(this, CurrentWeapon, PreviousWeapon); + } +} + +void AMCharacter::HandleHealthChanged(UMHealthComponent* HealthComp, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser) +{ + if(Health <= 0.f && !bDied) + { + bDied = true; + GetMovementComponent()->StopMovementImmediately(); + GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision); + DetachFromControllerPendingDestroy(); + SetLifeSpan(10.f); + } + else + { + // If we received damage, reset the healing timer + if(HealthDelta < 0.f) + { + FTimerManager& TimerManager = GetWorldTimerManager(); + if(TimerManager.IsTimerActive(TimerHandle_ApplySafeZoneHeal)) + TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay); + + if(HitByBulletSound) + UGameplayStatics::PlaySound2D(this, HitByBulletSound); + } + + CheckIfInSafeZone(); + } +} + +float AMCharacter::GetStaminaRemappedInRange(float Min, float Max) +{ + const FVector2D InputRange(0.f, MaxStamina); + const FVector2D OutputRange(Min, Max); + return FMath::GetMappedRangeValueClamped(InputRange, OutputRange, CurrentStamina); +} diff --git a/Private/MConstructableBuilding.cpp b/Private/MConstructableBuilding.cpp new file mode 100644 index 0000000..ce38232 --- /dev/null +++ b/Private/MConstructableBuilding.cpp @@ -0,0 +1,215 @@ + + + +#include "MConstructableBuilding.h" + + + +#include "MHealthComponent.h" +#include "Components/BoxComponent.h" +#include "Components/ShapeComponent.h" +#include "../MechDefence.h" +#include "Blueprint/UserWidget.h" +#include "Components/ArrowComponent.h" +#include "Components/WidgetComponent.h" +#include "Misc/OutputDeviceDebug.h" + +AMConstructableBuilding::AMConstructableBuilding() +{ + PrimaryActorTick.bCanEverTick = true; + + BaseBuildingMesh = CreateDefaultSubobject(TEXT("Base Mesh")); + RootComponent = BaseBuildingMesh; + BaseBuildingMesh->SetCollisionResponseToChannel(TRACE_CHANNEL_ConstructedBuilding, ECR_Block); + BaseBuildingMesh->SetCollisionResponseToChannel(TRACE_CHANNEL_Weapon, ECR_Ignore); + BaseBuildingMesh->SetCollisionResponseToChannel(COLLISION_OBJECT_UIEnablingVolume, ECR_Overlap); + BaseBuildingMesh->OnComponentBeginOverlap.AddDynamic(this, &AMConstructableBuilding::HandleMeshComponentBeginOverlap); + BaseBuildingMesh->OnComponentEndOverlap.AddDynamic(this, &AMConstructableBuilding::HandleMeshComponentEndOverlap); + BaseBuildingMesh->SetGenerateOverlapEvents(true); + + PlacementOverlapChecker = CreateDefaultSubobject(TEXT("PlacementOverlapChecker")); + PlacementOverlapChecker->SetupAttachment(RootComponent); + PlacementOverlapChecker->SetCollisionResponseToAllChannels(ECR_Overlap); + PlacementOverlapChecker->SetCollisionResponseToChannel(TRACE_CHANNEL_Weapon, ECR_Ignore); + + ArrowMesh = CreateDefaultSubobject(TEXT("ArrowMesh")); + ArrowMesh->SetupAttachment(RootComponent); + ArrowMesh->SetHiddenInGame(false); + ArrowMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + InvalidActorsFilter.AddUnique(GetClass()); + + HealthComponent = CreateDefaultSubobject(TEXT("HealthComp")); + + StatusIndicatorWidget = CreateDefaultSubobject(TEXT("Health Indicator")); + StatusIndicatorWidget->SetupAttachment(RootComponent); + StatusIndicatorWidget->SetHiddenInGame(true); + + BuildingState = EBuildingState::UnderConstruction; +} + +void AMConstructableBuilding::HandleMeshComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, + AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if(OtherComp->GetCollisionObjectType() == COLLISION_OBJECT_UIEnablingVolume) + { + StatusIndicatorWidget->SetHiddenInGame(true); + } +} + +void AMConstructableBuilding::HandleMeshComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, + AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, + const FHitResult& SweepResult) +{ + if(OtherComp->GetCollisionObjectType() == COLLISION_OBJECT_UIEnablingVolume) + { + StatusIndicatorWidget->SetHiddenInGame(false); + } +} + +void AMConstructableBuilding::SetupHealthIndicator() +{ + UUserWidget* IndicatorWidget = StatusIndicatorWidget->GetWidget(); + if(IndicatorWidget) + { + FObjectProperty* ObjProp = FindFProperty(IndicatorWidget->GetClass(), TEXT("HealthComponent")); + if(ObjProp) + { + ObjProp->SetObjectPropertyValue_InContainer(IndicatorWidget, HealthComponent, 0); + } + + const FString Command = FString::Printf(TEXT("SetupHealthChangeAnims")); + FOutputDeviceDebug DebugOutputDevice; + if(!IndicatorWidget->CallFunctionByNameWithArguments(*Command, DebugOutputDevice, IndicatorWidget, true)) + { + UE_LOG(LogTemp, Log, TEXT("Failed to setup health change anims")); + } + } +} + +void AMConstructableBuilding::BeginPlay() +{ + Super::BeginPlay(); + + DefaultMaterials = BaseBuildingMesh->GetMaterials(); + + if(bNetStartup) + SetBuildingState(EBuildingState::Constructed); + + GetWorldTimerManager().SetTimerForNextTick(this, &AMConstructableBuilding::SetupHealthIndicator); + HealthComponent->Deactivate(); +} + +void AMConstructableBuilding::ConstructionCompleted() +{ + SetBuildingState(EBuildingState::Constructed); +} + +void AMConstructableBuilding::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +void AMConstructableBuilding::SetBuildingState(TEnumAsByte NewState) +{ + // if(NewState == BuildingState) + // return; + + const EBuildingState PreviousState = BuildingState; + BuildingState = NewState; + + if(NewState != EBuildingState::Constructed) + { + CurrentBuildingStateMaterial = UMaterialInstanceDynamic::Create(BuildingDynamicStateMaterial, this); + //Set instance color param according to the building state + FLinearColor ColorValue; + switch(NewState) + { + case EBuildingState::Constructable: ColorValue = ConstructableColor; break; + case EBuildingState::UnConstructable: ColorValue = UnConstructableColor; break; + case EBuildingState::UnderConstruction: ColorValue = UnderConstructionColor; break; + default: ColorValue = FLinearColor(0, 0, 0); break; + } + + CurrentBuildingStateMaterial->SetVectorParameterValue("Color", ColorValue); + + for(int i = 0; i < DefaultMaterials.Num(); i++) + BaseBuildingMesh->SetMaterial(i, CurrentBuildingStateMaterial); + } + + + if(BuildingState == EBuildingState::Constructed) + { + for(int i = 0; i < DefaultMaterials.Num(); i++) + BaseBuildingMesh->SetMaterial(i, DefaultMaterials[i]); + + BaseBuildingMesh->SetCollisionResponseToChannel(TRACE_CHANNEL_Weapon, ECR_Block); + HealthComponent->Activate(); + ArrowMesh->SetHiddenInGame(true); + } + + BuildingStateSet(NewState, PreviousState); + OnBuildingStateChanged.Broadcast(this, NewState, PreviousState); + +} + +void AMConstructableBuilding::StartBuildingConstruction(float TimeToConstruct) +{ + GetWorldTimerManager().SetTimer(TimerHandle_UnderConstructionTimer, this, &AMConstructableBuilding::ConstructionCompleted, TimeToConstruct); +} + +void AMConstructableBuilding::SetHighlighted_Implementation(bool Highlighted) +{ + BaseBuildingMesh->SetRenderCustomDepth(Highlighted); +} + +bool AMConstructableBuilding::IsAtValidLocation_Implementation() +{ + bool PositionIsValid = true; + if(BaseBuildingMesh) + { + TArray OverlappingActors; + for(const TSubclassOf FilteredActorClass : InvalidActorsFilter) + { + PlacementOverlapChecker->GetOverlappingActors(OverlappingActors, FilteredActorClass); + if(FilteredActorClass == GetClass()) + { + // Check overlapping against ourselves + for(const AActor* OverlappingActor : OverlappingActors) + { + if(OverlappingActor != this) + { + PositionIsValid = false; + break; + } + } + } + else if(OverlappingActors.Num() != 0) + { + PositionIsValid = false; + } + + if(!PositionIsValid) + break; + } + + // We are not overlapping with any unwanted actors, now to check if we are overlapping against + // required actors + if(PositionIsValid) + { + for(const TSubclassOf RequiredActorClass : RequiredOverlappingActorsFilter) + { + PlacementOverlapChecker->GetOverlappingActors(OverlappingActors, RequiredActorClass); + if(OverlappingActors.Num() == 0) + { + PositionIsValid = false; + break; + } + } + } + } + + return PositionIsValid; +} + diff --git a/Private/MEnemySpawnPoint.cpp b/Private/MEnemySpawnPoint.cpp new file mode 100644 index 0000000..5fdcfc9 --- /dev/null +++ b/Private/MEnemySpawnPoint.cpp @@ -0,0 +1,32 @@ + + + +#include "MEnemySpawnPoint.h" + +// Sets default values +AMEnemySpawnPoint::AMEnemySpawnPoint() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + +} + +// Called when the game starts or when spawned +void AMEnemySpawnPoint::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void AMEnemySpawnPoint::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +void AMEnemySpawnPoint::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer = ActorTags; +} + diff --git a/Private/MGameMode.cpp b/Private/MGameMode.cpp new file mode 100644 index 0000000..6c102e7 --- /dev/null +++ b/Private/MGameMode.cpp @@ -0,0 +1,171 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MGameMode.h" +#include "Kismet/GameplayStatics.h" +#include "MPlayerController.h" +#include "EngineUtils.h" +#include "GameFramework/Character.h" +#include "MAICharacterBase.h" +#include "GameplayTagsManager.h" +#include "MHealthComponent.h" + +AMGameMode::AMGameMode() +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.TickInterval = 1.f; + WaveCount = 0; + LevelEndViewTargetBlendTime = 2.f; + TimeBetweenWaves = 60.f; + NumEnemiesKilled = 0; + MaxEnemiesInWave = 10; + MinEnemiesInWave = 3; +} + +void AMGameMode::StartPlay() +{ + Super::StartPlay(); + PrepareForNextWave(); +} + +void AMGameMode::HandlePlayerKilled() +{ + GameOver(); +} + +void AMGameMode::HandlePlayerVehicleDestroyed(AActor* Vehicle) +{ + GameOver(); +} + +void AMGameMode::GameOver() +{ + if(SpectatingViewPointClass) + { + TArray ReturnedActors; + UGameplayStatics::GetAllActorsOfClass(this, SpectatingViewPointClass, ReturnedActors); + + AActor* NewViewTarget = nullptr; + if(ReturnedActors.Num() > 0) + { + NewViewTarget = ReturnedActors[0]; + for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; It++) + { + AMPlayerController* PC = Cast(It->Get()); + if(PC) + { + PC->SetViewTargetWithBlend(NewViewTarget, LevelEndViewTargetBlendTime, EViewTargetBlendFunction::VTBlend_Cubic); + PC->OnGameOver(); + SetWaveState(EWaveState::GameOver); + } + } + } + } +} + +void AMGameMode::LevelCompleted() +{ + if(SpectatingViewPointClass) + { + TArray ReturnedActors; + UGameplayStatics::GetAllActorsOfClass(this, SpectatingViewPointClass, ReturnedActors); + + AActor* NewViewTarget = nullptr; + if(ReturnedActors.Num() > 0) + { + NewViewTarget = ReturnedActors[0]; + for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; It++) + { + AMPlayerController* PC = Cast(It->Get()); + if(PC) + { + PC->SetViewTargetWithBlend(NewViewTarget, LevelEndViewTargetBlendTime, EViewTargetBlendFunction::VTBlend_Cubic); + PC->OnLevelComplete(); + SetWaveState(EWaveState::LevelComplete); + } + } + } + } +} + +void AMGameMode::PrepareForNextWave() +{ + GetWorldTimerManager().SetTimer(TimerHandle_NextWaveStart, this, &AMGameMode::StartWave, TimeBetweenWaves, false); + SetWaveState(EWaveState::WaitingToStart); +} + +void AMGameMode::StartWave() +{ + FTimerManager& TimerManager = GetWorldTimerManager(); + + // Cancel Timer if it is still active. This needs to be done if + // the wave is triggered manually by a console command to make sure wave isn't + // triggered twice + if(TimerManager.IsTimerActive(TimerHandle_NextWaveStart)) + TimerManager.ClearTimer(TimerHandle_NextWaveStart); + + WaveCount++; + NumEnemiesToSpawn = FMath::RandRange(MinEnemiesInWave, MaxEnemiesInWave); + NumEnemiesToSpawn = WaveCount * NumEnemiesToSpawn; + TimerManager.SetTimer(TimerHandle_EnemySpawn, this, &AMGameMode::SpawnEnemyTimerElapsed, 1.f, true, 0.f); + SetWaveState(EWaveState::WaveInProgress); + UE_LOG(LogTemp, Log, TEXT("Enemies to spawn %d"), NumEnemiesToSpawn); +} + +void AMGameMode::EndWave() +{ + GetWorldTimerManager().ClearTimer(TimerHandle_EnemySpawn); + SetWaveState(EWaveState::WaitingToComplete); +} + +void AMGameMode::CheckWaveState() +{ + bool IsPreparingForNextWave = GetWorldTimerManager().IsTimerActive(TimerHandle_NextWaveStart); + if(NumEnemiesToSpawn > 0 || IsPreparingForNextWave) + return; + + bool IsAnyEnemyAlive = false; + for(TActorIterator It(GetWorld()); It; ++It) + { + AMAICharacterBase* EnemyChar = *It; + if(EnemyChar->HasAllMatchingGameplayTags(SpawnedEnemyTags)) + { + UMHealthComponent* HealthComponent = Cast(EnemyChar->GetComponentByClass(UMHealthComponent::StaticClass())); + if(HealthComponent && HealthComponent->GetHealth() > 0.f) + { + IsAnyEnemyAlive = true; + break; + } + } + } + + if(!IsAnyEnemyAlive) + { + SetWaveState(EWaveState::WaveComplete); + PrepareForNextWave(); + } +} + +void AMGameMode::SetWaveState(EWaveState NewWaveState) +{ + WaveStateChanged(NewWaveState, WaveState); + WaveState = NewWaveState; +} + +void AMGameMode::SpawnEnemyTimerElapsed() +{ + SpawnNewEnemy(); + NumEnemiesToSpawn--; + + if(NumEnemiesToSpawn <= 0) + { + EndWave(); + } +} + +void AMGameMode::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + CheckWaveState(); +} + diff --git a/Private/MGameplayActor.cpp b/Private/MGameplayActor.cpp new file mode 100644 index 0000000..99813aa --- /dev/null +++ b/Private/MGameplayActor.cpp @@ -0,0 +1,28 @@ + + + +#include "MGameplayActor.h" + +AMGameplayActor::AMGameplayActor() +{ + PrimaryActorTick.bCanEverTick = true; + +} + +void AMGameplayActor::BeginPlay() +{ + Super::BeginPlay(); + +} + +void AMGameplayActor::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +void AMGameplayActor::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer = ActorTags; +} + diff --git a/Private/MHealthComponent.cpp b/Private/MHealthComponent.cpp new file mode 100644 index 0000000..d6fcfa3 --- /dev/null +++ b/Private/MHealthComponent.cpp @@ -0,0 +1,65 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MHealthComponent.h" +#include "MGameMode.h" + +UMHealthComponent::UMHealthComponent() +{ + DefaultHealth = 100.f; + bIsDead = false; + bIsInvincible = false; +} + + +void UMHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* ComponentOwner = GetOwner(); + if(ComponentOwner) + ComponentOwner->OnTakeAnyDamage.AddDynamic(this, &UMHealthComponent::HandleTakeAnyDamage); + + Health = DefaultHealth; +} + + +void UMHealthComponent::HandleTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser) +{ + if(bIsDead) + return; + + if(!bIsInvincible) + Health = FMath::Clamp(Health - Damage, 0.f, DefaultHealth); + bIsDead = Health <= 0.f; + OnHealthChanged.Broadcast(this, Health, -(Health - Damage), DamageType, InstigatedBy, DamageCauser); + + if(bIsDead) + { + AMGameMode* GM = Cast(GetWorld()->GetAuthGameMode()); + if(GM) + { + GM->OnActorKilled.Broadcast(DamagedActor, DamageCauser, InstigatedBy); + } + + OnActorDeath.Broadcast(this, DamagedActor, DamageCauser); + } +} + +float UMHealthComponent::GetHealthRemappedInRange(float Min, float Max) +{ + const FVector2D InputRange(0.f, DefaultHealth); + const FVector2D OutputRange(Min, Max); + return FMath::GetMappedRangeValueClamped(InputRange, OutputRange, Health); +} + + +void UMHealthComponent::Heal(float HealAmount) +{ + if(HealAmount <= 0.f || bIsDead || Health <= 0.f) + return; + + Health = FMath::Clamp(Health + HealAmount, 0.f, DefaultHealth); + OnHealthChanged.Broadcast(this, Health, HealAmount, nullptr, nullptr, nullptr); +} + diff --git a/Private/MInteractableActor.cpp b/Private/MInteractableActor.cpp new file mode 100644 index 0000000..3297d74 --- /dev/null +++ b/Private/MInteractableActor.cpp @@ -0,0 +1,6 @@ + + + +#include "MInteractableActor.h" + +// Add default functionality here for any IMInteractableActor functions that are not pure virtual. \ No newline at end of file diff --git a/Private/MPlayerController.cpp b/Private/MPlayerController.cpp new file mode 100644 index 0000000..a9c9747 --- /dev/null +++ b/Private/MPlayerController.cpp @@ -0,0 +1,69 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MPlayerController.h" +#include "Blueprint/UserWidget.h" +#include "MWeapon.h" + +AMPlayerController::AMPlayerController() +{ + bShouldPerformFullTickWhenPaused = true; +} + +void AMPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + InputComponent->BindAction("Pause", IE_Pressed, this, &layerController::TogglePause); +} + +void AMPlayerController::TogglePause() +{ + //SetPause(IsPaused() ? false : true); + UE_LOG(LogTemp, Log, TEXT("Pause")); + Pause(); + APawn* ControlledPawn = GetPawn(); + if(IsPaused()) + { + ControlledPawn->DisableInput(this); + if(PauseWidget) + PauseWidget->AddToViewport(); + + if(HudWidget) + HudWidget->RemoveFromViewport(); + + FInputModeGameAndUI InputMode; + SetInputMode(InputMode); + bShowMouseCursor = true; + } + else + { + ControlledPawn->EnableInput(this); + if(HudWidget) + HudWidget->AddToViewport(); + + if(PauseWidget) + PauseWidget->RemoveFromViewport(); + FInputModeGameOnly InputMode; + SetInputMode(InputMode); + bShowMouseCursor = false; + } +} + +void AMPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + if(HudWidgetClass) + { + HudWidget = CreateWidget(this, HudWidgetClass, "PlayerHud"); + if(HudWidget) + HudWidget->AddToViewport(); + } + + if(PauseWidgetClass) + { + PauseWidget = CreateWidget(this, PauseWidgetClass, "PauseWidget"); + } +} + diff --git a/Private/MPlayerMech.cpp b/Private/MPlayerMech.cpp new file mode 100644 index 0000000..f3cb564 --- /dev/null +++ b/Private/MPlayerMech.cpp @@ -0,0 +1,188 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MPlayerMech.h" + +#include "DrawDebugHelpers.h" +#include "MCharacter.h" +#include "NavigationSystem.h" +#include "Camera/CameraComponent.h" +#include "Components/CapsuleComponent.h" +#include "Components/BoxComponent.h" +#include "Components/ArrowComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "MHealthComponent.h" +#include "Components/WidgetComponent.h" +#include "EnvironmentQuery/EnvQueryManager.h" +#include "EnvironmentQuery/EnvQueryTypes.h" +#include "Kismet/GameplayStatics.h" +#include "Misc/OutputDeviceDebug.h" + +int32 AMPlayerMech::DebugMechDrawing = 0; + +FAutoConsoleVariableRef CVAR_DebugMechDrawing( + TEXT("MDEF.DebugMechDrawing"), + AMPlayerMech::DebugMechDrawing, + TEXT("Draw debug visualizations for the player mech"), + ECVF_Cheat); + +AMPlayerMech::AMPlayerMech() +{ + PrimaryActorTick.bCanEverTick = true; + + MeshComponent = CreateDefaultSubobject("MeshComponent"); + RootComponent = MeshComponent; + + SpringArmComponent = CreateDefaultSubobject("SpringArmComponent"); + SpringArmComponent->SetupAttachment(MeshComponent); + SpringArmComponent->TargetArmLength = 1500.f; + + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + CameraComponent->SetupAttachment(SpringArmComponent); + CameraComponent->SetRelativeLocation(FVector(0.f, 0.f, BaseEyeHeight)); + + HealthComponent = CreateDefaultSubobject(TEXT("HealthComponent")); + + StatusIndicatorWidget = CreateDefaultSubobject(TEXT("StatusIndicator")); + StatusIndicatorWidget->SetupAttachment(RootComponent); + + PlayerDismountRadius = 500.f; + PlayerDismountGroundOffset = 100.f; + ResetZOffset = 200.f; + MountedPlayerCharacter = nullptr; +} + +void AMPlayerMech::BeginPlay() +{ + Super::BeginPlay(); + + GetWorldTimerManager().SetTimerForNextTick(this, &layerMech::SetupHealthIndicator); +} + +void AMPlayerMech::SetupHealthIndicator() +{ + UUserWidget* IndicatorWidget = StatusIndicatorWidget->GetWidget(); + if(IndicatorWidget) + { + FObjectProperty* ObjProp = FindFProperty(IndicatorWidget->GetClass(), TEXT("HealthComponent")); + if(ObjProp) + { + ObjProp->SetObjectPropertyValue_InContainer(IndicatorWidget, HealthComponent, 0); + } + + const FString Command = FString::Printf(TEXT("SetupHealthChangeAnims")); + FOutputDeviceDebug DebugOutputDevice; + if(!IndicatorWidget->CallFunctionByNameWithArguments(*Command, DebugOutputDevice, IndicatorWidget, true)) + { + UE_LOG(LogTemp, Log, TEXT("Failed to setup health change anims")); + } + } +} + +void AMPlayerMech::MoveForward(float ScaleValue) +{ + //AddMovementInput(GetActorForwardVector(), ScaleValue); +} + +void AMPlayerMech::MoveRight(float ScaleValue) +{ + //AddMovementInput(GetActorRightVector(), ScaleValue); + //AddControllerYawInput(ScaleValue); +} + +void AMPlayerMech::RequestDismount() +{ + FEnvQueryRequest DismountQueryRequest = FEnvQueryRequest(DismountEQS); + DismountQueryRequest.Execute(EEnvQueryRunMode::SingleResult, this, &layerMech::HandleDismountResponse); +} + +void AMPlayerMech::RevertOrientation() +{ + const FRotator CameraOrientation = CameraComponent->GetComponentRotation(); + const FRotator NewRotation(0.f, CameraOrientation.Yaw, 0.f); + const FVector NewLocation = GetActorLocation() + FVector(0.f, 0.f, ResetZOffset); + SetActorLocationAndRotation(NewLocation, NewRotation, false, nullptr, ETeleportType::TeleportPhysics); +} + +void AMPlayerMech::HandleDismountResponse(TSharedPtr QueryResult) +{ + if(QueryResult->IsSuccsessful()) + { + FVector DismountLocation = QueryResult->GetItemAsLocation(0); + if(DebugMechDrawing > 0) + DrawDebugSphere(GetWorld(), DismountLocation, 50.f, 20, FColor::Green, false, 5.f, 0, 1.f); + + if(MountedPlayerCharacter) + { + MountedPlayerCharacter->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + MountedPlayerCharacter->SetActorHiddenInGame(false); + MountedPlayerCharacter->SetActorEnableCollision(true); + MountedPlayerCharacter->SetActorTickEnabled(true); + //DismountLocation.Z += PlayerDismountGroundOffset; // Add a bit of offset from the ground to avoid player falling from the ground when dismounting + MountedPlayerCharacter->SetActorLocationAndRotation(DismountLocation, FRotator(0.f, GetActorRotation().Yaw, 0.f), false, nullptr, ETeleportType::ResetPhysics); + + APlayerController* PC = Cast(GetController()); + if(PC) + { + PC->Possess(Cast(MountedPlayerCharacter)); + } + + MountedPlayerCharacter->SetMounted(false); + MountedPlayerCharacter->CheckIfInSafeZone(); + MountedPlayerCharacter = nullptr; + + if(MountSound) + UGameplayStatics::PlaySound2D(this, MountSound); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("Could not find suitable location to dismount")); + } +} + +void AMPlayerMech::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +void AMPlayerMech::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + PlayerInputComponent->BindAxis("MoveForward", this, &layerMech::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &layerMech::MoveRight); + + //PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); + PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); + + PlayerInputComponent->BindAction("Dismount", IE_Pressed, this, &layerMech::RequestDismount); + + PlayerInputComponent->BindAction("ResetVehicle", IE_Pressed, this, &layerMech::RevertOrientation); +} + +void AMPlayerMech::MountPlayerCharacter(AMCharacter* PlayerCharacter) +{ + if(!PlayerCharacter) + return; + + MountedPlayerCharacter = PlayerCharacter; + MountedPlayerCharacter->SetActorHiddenInGame(true); + MountedPlayerCharacter->SetActorEnableCollision(false); + MountedPlayerCharacter->SetActorTickEnabled(false); + PlayerCharacter->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale, PlayerAttachmentSocket); + if(MountSound) + UGameplayStatics::PlaySound2D(this, MountSound); +} + +void AMPlayerMech::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer = ActorTags; +} + +UMeshComponent* AMPlayerMech::GetHoveredMeshComponent() +{ + return MeshComponent; +} + diff --git a/Private/MProjectile.cpp b/Private/MProjectile.cpp new file mode 100644 index 0000000..0e73ca4 --- /dev/null +++ b/Private/MProjectile.cpp @@ -0,0 +1,48 @@ +#include "MProjectile.h" + +#include "NiagaraComponent.h" +#include "NiagaraFunctionLibrary.h" +#include "Components/CapsuleComponent.h" +#include "GameFramework/ProjectileMovementComponent.h" + +AMProjectile::AMProjectile() +{ + PrimaryActorTick.bCanEverTick = true; + + CapsuleComponent = CreateDefaultSubobject(TEXT("CapsuleComponent")); + RootComponent = CapsuleComponent; + + MeshComponent = CreateDefaultSubobject(TEXT("MeshComponent")); + MeshComponent->SetupAttachment(RootComponent); + MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + ParticleSystem = CreateDefaultSubobject(TEXT("ParticleSystem")); + ParticleSystem->SetupAttachment(RootComponent); + + ProjectileComponent = CreateDefaultSubobject(TEXT("ProjectileMovement")); + ProjectileComponent->InitialSpeed = 12000.f; + ProjectileComponent->MaxSpeed = 18000.f; + ProjectileComponent->ProjectileGravityScale = 0.f; +} + +void AMProjectile::BeginPlay() +{ + Super::BeginPlay(); + + CapsuleComponent->OnComponentBeginOverlap.AddDynamic(this, &rojectile::HandleBeginOverlap); + SetLifeSpan(5.f); +} + +void AMProjectile::HandleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + AActor* ProjectileOwner = GetOwner(); + if(ProjectileOwner && OtherActor != ProjectileOwner && !ActorClassesToIgnore.Contains(OtherActor->GetClass())) + { + if(ImpactEffect) + { + UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), ImpactEffect, SweepResult.ImpactPoint, SweepResult.ImpactNormal.Rotation()); + } + Destroy(); + } +} diff --git a/Private/MTargetableActor.cpp b/Private/MTargetableActor.cpp new file mode 100644 index 0000000..c5ddf39 --- /dev/null +++ b/Private/MTargetableActor.cpp @@ -0,0 +1,6 @@ + + + +#include "MTargetableActor.h" + +// Add default functionality here for any IMTargetableActor functions that are not pure virtual. \ No newline at end of file diff --git a/Private/MWeapon.cpp b/Private/MWeapon.cpp new file mode 100644 index 0000000..2b00e7c --- /dev/null +++ b/Private/MWeapon.cpp @@ -0,0 +1,419 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MWeapon.h" +#include "Kismet/GameplayStatics.h" +#include "MechDefence/MechDefence.h" +#include "DrawDebugHelpers.h" +#include "MAICharacterBase.h" +#include "MCharacter.h" +#include "Particles/ParticleSystemComponent.h" +#include "MPlayerController.h" +#include "NiagaraSystem.h" +#include "NiagaraFunctionLibrary.h" +#include "NiagaraComponent.h" +#include "Components/AudioComponent.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "MProjectile.h" + +static TAutoConsoleVariable CVAR_DebugWeaponDrawing( + TEXT("MDEF.DebugWeaponDraw"), + 0, + TEXT("Draw debug lines for visualizing weapons"), + ECVF_Cheat); + +AMWeapon::AMWeapon() +{ + PrimaryActorTick.bCanEverTick = true; + + WeaponMesh = CreateDefaultSubobject(TEXT("Weapon Mesh")); + RootComponent = WeaponMesh; + + MuzzleSocketName = "MuzzleSocket"; + PlayerAttachmentSocketName = "WeaponSocket"; + BaseDamage = 20.f; + VulnerableDamageMultiplier = 4.f; + RateOfFire = 100.f; + BulletSpread = 0.5f; + bFiring = false; + bReloading = false; + TimeLastFired = 0.f; + StopFireDelay = 0.1f; + MaxWeaponRange = 10000.f; + TracerParticleStartLocationName = "Target"; + ClipSize = 5; + ReloadTime = 2.f; + ProjectileSpawnChance = 0.5f; + bCanZoom = true; + ZoomInFOV = 60.f; + bSecondaryFireBound = false; +} + +void AMWeapon::BeginPlay() +{ + Super::BeginPlay(); + + ClipRemainingBullets = ClipSize; + TimeBetweenShots = 60.f / RateOfFire; +} + +void AMWeapon::Fire() +{ + OnFire(); + + TimeLastFired = GetWorld()->GetTimeSeconds(); + + if(ClipSize != 0) + ClipRemainingBullets--; + bFiring = true; +} + +void AMWeapon::PlayFireEffects(FVector ShotDirection, UParticleSystem* ImpactEffect, FVector TraceEffectEndLocation, const FRotator ImpactParticleRotation) +{ + // Muzzle Particle Effect + if(MuzzleEffect) + UGameplayStatics::SpawnEmitterAttached(MuzzleEffect, WeaponMesh, MuzzleSocketName); + + // Projectile + bool ProjectileSpawned = false; + if(ProjectileClass && ProjectileSpawnChance > 0.f) + { + const float SpawnProbability = FMath::FRand(); + if(SpawnProbability > 0.f && SpawnProbability <= ProjectileSpawnChance) + { + const FVector MuzzleSocketLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName); + FActorSpawnParameters SpawnParameters; + SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + AMProjectile* Projectile = GetWorld()->SpawnActor(ProjectileClass, MuzzleSocketLocation, ShotDirection.Rotation(), SpawnParameters); + Projectile->SetOwner(this); + ProjectileSpawned = true; + } + } + + // Tracer Particle Effect + if(TracerEffect && !ProjectileSpawned) + { + const FVector MuzzleSocketLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName); + //UParticleSystemComponent* TracerComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), TracerEffect, MuzzleSocketLocation); + UNiagaraComponent* TracerComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), TracerEffect, MuzzleSocketLocation); + if(TracerComp) + { + TracerComp->SetVectorParameter(TracerParticleStartLocationName, TraceEffectEndLocation); + } + } + + // Impact Particle + if(ImpactEffect) + UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactEffect, TraceEffectEndLocation, ImpactParticleRotation); +} + +void AMWeapon::OnFire_Implementation() +{ + AActor* WeaponOwner = GetOwner(); + if(!WeaponOwner) + { + UE_LOG(LogTemp, Log, TEXT("No weapon owner for weapon %s. Cannot Fire!"), *GetName()); + return; + } + + // try and play the sound if specified + if(FireSound) + { + UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation()); + } + + APawn* WeaponOwnerPawn = Cast(WeaponOwner); + MakeNoise(0.75f, WeaponOwnerPawn); + + UParticleSystem* ImpactEffect = DefaultImpactEffect; + + FVector EyeLocation; + FRotator EyeRotation; + WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation); + FVector ShotDirection = EyeRotation.Vector(); + + // Bullet Spread if aiming from hip + bool bShouldApplyBulletSpread = true; + AMCharacter* PlayerChar = Cast(WeaponOwner); + if(PlayerChar && PlayerChar->IsZoomedIn()) + { + bShouldApplyBulletSpread = false; + } + + if(bShouldApplyBulletSpread) + { + float HalfAngleRadians = FMath::DegreesToRadians(BulletSpread); + ShotDirection = FMath::VRandCone(ShotDirection, HalfAngleRadians); + } + + FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange); + FVector TraceEffectEndLocation = TraceEndLocation; // Init Location where the Tracer effect should end + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(WeaponOwner); + QueryParams.AddIgnoredActor(this); + QueryParams.bTraceComplex = true; + QueryParams.bReturnPhysicalMaterial = true; + + EPhysicalSurface HitSurfaceType = SurfaceType_Default; + FRotator ImpactRotation; + + FHitResult Hit; + if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TRACE_CHANNEL_Weapon, QueryParams)) + { + AActor* HitActor = Hit.GetActor(); + TraceEffectEndLocation = Hit.ImpactPoint; + ImpactRotation = Hit.Normal.Rotation(); + + // Damage + float ActualDamage = BaseDamage; + HitSurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get()); + if(HitSurfaceType == SurfaceType_Vulnerable) + ActualDamage *= VulnerableDamageMultiplier; + + + // Check tags on hit actor to determine if it was a valid target so that we can apply damage and provide feedback if the owner of this weapon is player + IGameplayTagAssetInterface* GameplayTagAsset = Cast(HitActor); + if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(ValidTargetTags)) + { + //Damage + UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, ShotDirection, Hit, WeaponOwner->GetInstigatorController(), WeaponOwner, DamageType); + + // Notify player controller so that we can show feedback for the hit on the hud + AMPlayerController* PC = WeaponOwner->GetInstigatorController(); + if(PC) + { + OnTargetHit.Broadcast(this, HitActor, WeaponOwner); + PC->HandleTargetHit(this, HitActor, WeaponOwner); + } + + //Determine the impact particle to spawn. If the hit target is an enemy and has the right particle effects, + //use those. Otherwise, we'll use the defaults + AMAICharacterBase* EnemyBaseChar = Cast(HitActor); + if(EnemyBaseChar) + { + if(HitSurfaceType == SurfaceType_Vulnerable && EnemyBaseChar->GetVulnerableHitParticle()) + ImpactEffect = EnemyBaseChar->GetVulnerableHitParticle(); + else if(EnemyBaseChar->GetDefaultHitParticle()) + ImpactEffect = EnemyBaseChar->GetDefaultHitParticle(); + } + } + } + + if(CVAR_DebugWeaponDrawing.GetValueOnGameThread() > 0) + DrawDebugLine(GetWorld(), EyeLocation, TraceEffectEndLocation, FColor::Red, false, 1.f, 0, 1.f); + + // Fire Particle Effects + PlayFireEffects(ShotDirection, ImpactEffect, TraceEffectEndLocation, ImpactRotation); + + // Camera Shake + APawn* OwnerPawn = Cast(WeaponOwner); + if(OwnerPawn && CameraShakeClass) + { + APlayerController* PC = Cast(OwnerPawn->GetController()); + if(PC) + { + PC->ClientStartCameraShake(CameraShakeClass); + } + } +} + +void AMWeapon::StopFire_Implementation() +{ + if(bFiring) + { + bFiring = false; + } + + GetWorldTimerManager().ClearTimer(TimerHandle_TimeBetweenShots); + + if(ClipRemainingBullets == 0 && ClipSize != 0) + Reload(); +} + +void AMWeapon::StartSecondaryFire_Implementation() +{ + if(bCanZoom) + { + AMCharacter* PlayerChar = Cast(GetOwner()); + if(PlayerChar) + { + PlayerChar->SetTargetFOV(ZoomInFOV); + } + } +} + +void AMWeapon::StopSecondaryFire_Implementation() +{ + if(bCanZoom) + { + AMCharacter* PlayerChar = Cast(GetOwner()); + if(PlayerChar) + { + PlayerChar->ResetToDefaultFOV(); + } + } +} + +void AMWeapon::Reload() +{ + if(bReloading) + return; + + bReloading = true; + bFiring = false; + GetWorldTimerManager().SetTimer(TimerHandle_Reload, this, &AMWeapon::HandleReloadComplete, ReloadTime); + OnWeaponReloadStarted.Broadcast(this); + if(ReloadSound) + TempReloadSoundHandle = UGameplayStatics::SpawnSoundAttached(ReloadSound, RootComponent); +} + +void AMWeapon::HandleWeaponEquipped(AMCharacter* Character, AMWeapon* NewWeapon, AMWeapon* PreviousWeapon) +{ + APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0); + if(NewWeapon && NewWeapon == this) + { + if(PC) + { + EnableInput(PC); + if(!bSecondaryFireBound) + { + UInputComponent* IC = this->InputComponent; + if(IC) + { + FInputActionBinding& PressedBinding = IC->BindAction("SecondaryFire", IE_Pressed, this, &AMWeapon::StartSecondaryFire); + FInputActionBinding& ReleasedBinding = IC->BindAction("SecondaryFire", IE_Released, this, &AMWeapon::StopSecondaryFire); + PressedBinding.bConsumeInput = false; + ReleasedBinding.bConsumeInput = false; + bSecondaryFireBound = true; + } + } + } + } + else if(PreviousWeapon && PreviousWeapon == this) + { + if(PC) + { + DisableInput(PC); + } + } +} + +void AMWeapon::HandleReloadComplete() +{ + bReloading = false; + ClipRemainingBullets = ClipSize; + GetWorldTimerManager().ClearTimer(TimerHandle_Reload); + OnWeaponReloadCompleted.Broadcast(this); + TempReloadSoundHandle = nullptr; +} + +void AMWeapon::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +bool AMWeapon::StartFire_Implementation() +{ + if(bReloading) + return false; + + if(ClipRemainingBullets == 0 && ClipSize != 0) + { + Reload(); + return false; + } + + float FirstFireDelay = FMath::Max(TimeLastFired + TimeBetweenShots - GetWorld()->GetTimeSeconds(), 0.f); + if(RateOfFire == 0.f) + { + Fire(); + GetWorldTimerManager().SetTimer(TimerHandle_TimeBetweenShots, this, &AMWeapon::StopFire, StopFireDelay, false, 0.f); + } + else + { + GetWorldTimerManager().SetTimer(TimerHandle_TimeBetweenShots, this, &AMWeapon::Fire, TimeBetweenShots, true, FirstFireDelay); + } + + return true; +} + +float AMWeapon::GetReloadProgress() +{ + float Progress = 1.f; + if(bReloading) + { + float TimeElapsed = GetWorldTimerManager().GetTimerElapsed(TimerHandle_Reload); + Progress = FMath::Clamp(TimeElapsed / ReloadTime, 0.f, 1.f); + } + return Progress; +} + +void AMWeapon::CancelReload() +{ + if(!bReloading) + return; + + GetWorldTimerManager().ClearTimer(TimerHandle_Reload); + OnWeaponReloadCancelled.Broadcast(this); + bReloading = false; + if(TempReloadSoundHandle) + TempReloadSoundHandle->Stop(); + TempReloadSoundHandle = nullptr; +} + +FVector AMWeapon::CalculateTargetedWorldLocation(TSubclassOf ValidTargetClass, ECollisionChannel TraceChannel) const +{ + FVector Location; + + AActor* WeaponOwner = GetOwner(); + if(WeaponOwner) + { + FVector EyeLocation; + FRotator EyeRotation; + WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation); + FVector ShotDirection = EyeRotation.Vector(); + + FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(WeaponOwner); + QueryParams.AddIgnoredActor(this); + QueryParams.bTraceComplex = true; + QueryParams.bReturnPhysicalMaterial = true; + + FHitResult Hit; + if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TraceChannel, QueryParams)) + Location = Hit.Location; + } + + return Location; +} + +AActor* AMWeapon::GetTargetedActor(ECollisionChannel TraceChannel) const +{ + AActor* TargetedActor = nullptr; + + AActor* WeaponOwner = GetOwner(); + if(WeaponOwner) + { + FVector EyeLocation; + FRotator EyeRotation; + WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation); + FVector ShotDirection = EyeRotation.Vector(); + + FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(WeaponOwner); + QueryParams.AddIgnoredActor(this); + QueryParams.bTraceComplex = true; + + FHitResult Hit; + if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TraceChannel, QueryParams)) + TargetedActor = Hit.GetActor(); + } + + return TargetedActor; +} diff --git a/Public/MAICharacterBase.h b/Public/MAICharacterBase.h new file mode 100644 index 0000000..c39deda --- /dev/null +++ b/Public/MAICharacterBase.h @@ -0,0 +1,97 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "GameplayTagAssetInterfacE.h" + +#include "MAICharacterBase.generated.h" + +class UMHealthComponent; +class UWidgetComponent; +class AMGameplayActor; +class FOnCharacterEnterSafeZone; +class AMAICharacterBase; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAIEnterSafeZone, AMAICharacterBase*, AICharacter, AActor*, SafeZoneEnteredActor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAILeaveSafeZone, AMAICharacterBase*, AICharacter); + +UCLASS() +class MECHDEFENCE_API AMAICharacterBase : public ACharacter, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AMAICharacterBase(); + +protected: + + bool bIsInSafeZone; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter SafeZone") + FGameplayTagContainer SafeZoneTags; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter SafeZone") + TSubclassOf SafeZoneClass; + + UPROPERTY(EditDefaultsOnly, Category = "Tags") + FGameplayTagContainer ActorTags; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter Base") + UWidgetComponent* StatusIndicatorWidget; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter Base") + UMHealthComponent* HealthComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter Base") + UParticleSystem* DefaultHitParticle; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AICharacter Base") + UParticleSystem* VulnerableHitParticle; + + UFUNCTION(BlueprintCallable) + virtual UShapeComponent* GetSafeZoneOverlappingShape(); + + virtual void BeginPlay() override; + void SetWidgetHealthComponentProperty(); + +public: + virtual void Tick(float DeltaTime) override; + + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + UPROPERTY(BlueprintAssignable, Category = "AICharacter Events") + FOnAIEnterSafeZone OnAIEnterSafeZone; + + UPROPERTY(BlueprintAssignable, Category = "AICharacter Events") + FOnAILeaveSafeZone OnAILeaveSafeZone; + + UFUNCTION(BlueprintCallable, Category = "AICharacter") + void AddGameplayTags(const FGameplayTagContainer& TagContainer); + + UFUNCTION(BlueprintCallable, Category = "AICharacter") + void AddGameplayTag(const FGameplayTag& Tag); + + void LeaveSafeZone(); + void EnterSafeZone(AActor* SafeZoneActor); + void CheckSafeZone(); + + bool IsInSafeZone() const { return bIsInSafeZone; } + + UParticleSystem* GetVulnerableHitParticle() const { return VulnerableHitParticle; } + UParticleSystem* GetDefaultHitParticle() const { return DefaultHitParticle; } + +private: + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + + UFUNCTION() + void HandleSafeZoneBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + UFUNCTION() + void HandleSafeZoneEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + UFUNCTION() + void HandleCharacterDeath(UMHealthComponent* ActorHealthComponent, AActor* DeadActor, AActor* KillerActor); + +}; diff --git a/Public/MAIEnemyShooter.h b/Public/MAIEnemyShooter.h new file mode 100644 index 0000000..0284df2 --- /dev/null +++ b/Public/MAIEnemyShooter.h @@ -0,0 +1,44 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "MAICharacterBase.h" +#include "MAIEnemyShooter.generated.h" + +class AMWeapon; + +UCLASS() +class MECHDEFENCE_API AMAIEnemyShooter : public AMAICharacterBase +{ + GENERATED_BODY() + +public: + AMAIEnemyShooter(); + +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadonly, Category = "Shooter") + TSubclassOf StarterWeaponClass; + + UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Shooter") + bool bCanIdle; + + UPROPERTY(EditDefaultsOnly, BlueprintReadonly, Category = "Shooter") + float PatrolRadius; + + UPROPERTY(VisibleAnywhere, BlueprintReadonly, Category = "Shooter", meta = (Tooltip = "The place where this actor was when the level began. Used to generate a random point within a radius when in patrol mode")) + FVector StartingLocation; + + UPROPERTY(BlueprintReadonly, Category = "Shooter") + FName WeaponSocketName; + + UPROPERTY(VisibleAnywhere, BlueprintReadonly, Category = "Shooter") + AMWeapon* CurrentWeapon; + + UFUNCTION(BlueprintCallable, Category = "Shooter") + FVector PickNextPartrolDestination(); + + virtual void BeginPlay() override; + + virtual UShapeComponent* GetSafeZoneOverlappingShape() override; +}; diff --git a/Public/MCharacter.h b/Public/MCharacter.h new file mode 100644 index 0000000..785ad18 --- /dev/null +++ b/Public/MCharacter.h @@ -0,0 +1,283 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + + +#include "GameplayTagAssetInterface.h" +#include "GameFramework/Character.h" +#include "MCharacter.generated.h" + +class AMGameplayActor; +class UCameraComponent; +class AMWeapon; +class UMHealthComponent; +class AMCharacter; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnWeaponEquipped, AMCharacter*, Character, AMWeapon*, NewWeapon, AMWeapon*, PreviousWeapon); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCharacterEnterSafeZone, AMCharacter*, Character, AActor*, SafeZoneEnteredActor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCharacterLeaveSafeZone, AMCharacter*, Character); + +UCLASS() +class MECHDEFENCE_API AMCharacter : public ACharacter, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AMCharacter(); + +protected: + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UCameraComponent* CameraComponent; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + USkeletalMeshComponent* PlayerArms; + + UPROPERTY(EditDefaultsOnly, Category = "Player", meta=(Tooltip = "Maximum Distance to Trace for Interactable Objects", ClampMin = 50.f)) + float MaxInteractableDistance; + + UPROPERTY(EditDefaultsOnly, Category = "Tags") + FGameplayTagContainer ActorTags; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + TSubclassOf PrimaryWeaponClass; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + TSubclassOf ScannerClass; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + TSubclassOf BlowTorchClass; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + TSubclassOf TabletClass; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + UAnimSequence* FireAnimation; + + UPROPERTY(EditDefaultsOnly, Category = "Player") + UAnimSequenceBase* ReloadAnimation; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player") + UMHealthComponent* HealthComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + UAudioComponent* AudioComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* HealingStartSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* HealingInProgressSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* HealingCompletedSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* SafeZoneEnterSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* SafeZoneLeaveSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* HitByBulletSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* WalkSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + USoundBase* CrouchWalkSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float DefaultFOV; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float ZoomInterpolateSpeed; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Tooltip = "The interval in seconds after which we check if player is in safe zone or not to determine whether to apply damage")) + float OutOfSafeZoneDamageInterval; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float OutOfSafeZoneDamage; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Tooltip = "The interval in seconds after which healing is applied when the player is inside the safe zone is their health is low")) + float SafeZoneHealInterval; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Tooltip = "The amount of healing applied when the player is inside safe zone")) + float SafeZoneHealAmount; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Tooltip = "Time interval in seconds after which healing within safezone will begin")) + float SafeZoneHealDelay; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Tooltip = "Minimum velocity threshold after which player movement sounds will be played")) + float MinimumMovementVelocityThreshold; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float MaxStamina; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Player") + float CurrentStamina; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float StaminaConsumeAmount; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Min = 0.f)) + float StaminaConsumeInterval; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Min = 0.f, Tooltip = "The amount of time in seconds to wait before we start recovering stamina")) + float StaminaRestoreDelay; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float StaminaRestoreAmount; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Min = 0.f)) + float StaminaRestoreInterval; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player", meta = (Min = 1.f)) + float SprintMultiplier; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float ArmRotateSpeed; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + float HiddenArmPitch; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + FVector HiddenArmLocation; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + TSubclassOf SafeZoneClass; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + TSubclassOf OutOfSafeZoneDamageClass; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Player") + FGameplayTagContainer SafeZoneTags; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player") + TArray Weapons; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player") + AMWeapon* CurrentWeapon; + + FTimerHandle TimerHandle_ApplyOutOfSafeZoneDamage; + FTimerHandle TimerHandle_ApplySafeZoneHeal; + FTimerHandle TimerHandle_RestoreStamina; + FTimerHandle TimerHandle_ConsumeStamina; + + bool bWantsToZoom; + bool bIsInSafeZone; + bool bIsFirstTimeHeal; + bool bIsMounted; + bool bCanSprint; + bool bIsSprinting; + bool bWantsToHideEquippedItem; + + void MoveForward(float ScaleValue); + void MoveRight(float ScaleValue); + + void StartCrouch(); + void StopCrouch(); + + void StartFire(); + void StopFire(); + + void Interact(); + void Reload(); + + void StartZoom(); + void StopZoom(); + + void StartSprint(); + void StopSprint(); + + void ConsumeStamina(); + void RestoreStamina(); + + void EnterSafeZone(AActor* SafeZoneActor); + void LeaveSafeZone(); + + UFUNCTION() + void ApplyOutOfSafeZoneDamage(); + + UFUNCTION() + void ApplySafeZoneHealing(); + + UFUNCTION() + void HandleCapsuleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + UFUNCTION() + void HandleCapsuleEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + virtual void BeginPlay() override; + + void HandleCVARChanged(); + + // The actor over which our cursor is currently hovering + AActor* HoveredInteractableActor; + + // Used when sprinting to check whether the player has moved or not in order to determine if stamina should be consumed or not + // We only check for the x and y positions to not count jumping as change in location + FVector2D PreviousLocation; + + float DefaultArmPitch; + FVector DefaultArmLocation; + + float TargetFOV; + +public: + virtual void Tick(float DeltaTime) override; + + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + + UFUNCTION(BlueprintCallable) + void EquipWeapon(uint8 Weapon); + + UFUNCTION(BlueprintCallable) + void CheckIfInSafeZone(); + + UFUNCTION() + void HandleHealthChanged(UMHealthComponent* HealthComp, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser); + + UFUNCTION(BlueprintCallable) + void SetMounted(bool Mounted) { bIsMounted = Mounted; } + + UPROPERTY(BlueprintAssignable, Category = "Player Events") + FOnWeaponEquipped OnWeaponEquipped; + + UPROPERTY(BlueprintAssignable, Category = "Character Events") + FOnCharacterEnterSafeZone OnCharacterEnterSafeZone; + + UPROPERTY(BlueprintAssignable, Category = "Character Events") + FOnCharacterLeaveSafeZone OnCharacterLeaveSafeZone; + + UFUNCTION(BlueprintCallable) + float GetStaminaRemappedInRange(float Min, float Max); + + UFUNCTION(BlueprintCallable) + bool IsZoomedIn() const { return bWantsToZoom; } + + UFUNCTION(BlueprintCallable) + bool IsInSafeZone() const { return bIsInSafeZone; } + + UFUNCTION(BlueprintCallable) + bool IsMounted() const { return bIsMounted; } + + UFUNCTION(BlueprintCallable) + void StartHidingEquippedItem(); + + UFUNCTION(BlueprintCallable) + void StopHidingEquippedItem(); + + UFUNCTION(BlueprintCallable) + void SetTargetFOV(float NewTargetFOV); + + UFUNCTION(BlueprintCallable) + void ResetToDefaultFOV(); + +private: + bool bDied; +}; diff --git a/Public/MConstructableBuilding.h b/Public/MConstructableBuilding.h new file mode 100644 index 0000000..511d198 --- /dev/null +++ b/Public/MConstructableBuilding.h @@ -0,0 +1,117 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "MGameplayActor.h" +#include "MConstructableBuilding.generated.h" + +class UArrowComponent; +class UMHealthComponent; +class UWidgetComponent; +class AMConstructableBuilding; + +UENUM(BlueprintType) +enum class EBuildingState : uint8 +{ + Constructable UMETA(DisplayName = "Constructable"), + UnConstructable UMETA(DisplayName = "UnConstructable"), + UnderConstruction UMETA(DisplayName = "UnderConstruction"), + Constructed UMETA(DisplayName = "Constructed") +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnBuildingStateChanged, AMConstructableBuilding*, Building, EBuildingState, NewState, EBuildingState, PreviousState); + +UCLASS() +class MECHDEFENCE_API AMConstructableBuilding : public AMGameplayActor +{ + GENERATED_BODY() + +public: + AMConstructableBuilding(); + +protected: + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + USkeletalMeshComponent* BaseBuildingMesh; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "The colour that will be used to show the building when it is at a valid location and can be constructed")) + FLinearColor ConstructableColor; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "The colour that will be used to show the building when it is at a location where it cannot be constructed")) + FLinearColor UnConstructableColor; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "The colour that will be used to show the building when it has been placed in the world and is under construction")) + FLinearColor UnderConstructionColor; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "The material that will be used on all the meshes of this building while it is in an unconstructed state.")) + UMaterialInterface* BuildingDynamicStateMaterial; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building") + EBuildingState BuildingState; + + UPROPERTY(BlueprintReadWrite) + TArray DefaultMaterials; + + UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "Dynamically created material that represents the current state of the building")) + UMaterialInstanceDynamic* CurrentBuildingStateMaterial; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "List of actor types that will be checked against to determine if the building can be placed at a particular location")) + TArray> InvalidActorsFilter; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "List of actor types that required to be overlapping with this building at a particular location if it is to be placed there")) + TArray> RequiredOverlappingActorsFilter; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building", meta = (Tooltip = "This shape is used alongside InvalidActors filter to determine whether the building can be placed at its current location or not")) + UShapeComponent* PlacementOverlapChecker; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building") + UWidgetComponent* StatusIndicatorWidget; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building") + UStaticMeshComponent* ArrowMesh; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Building") + UMHealthComponent* HealthComponent; + + UFUNCTION() + void HandleMeshComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + UFUNCTION() + void HandleMeshComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + UFUNCTION() + void SetupHealthIndicator(); + + virtual void BeginPlay() override; + + UFUNCTION() + void ConstructionCompleted(); + + FTimerHandle TimerHandle_UnderConstructionTimer; + + +public: + virtual void Tick(float DeltaTime) override; + + UFUNCTION(BlueprintCallable) + void SetBuildingState(TEnumAsByte NewState); + + UFUNCTION(BlueprintImplementableEvent, meta = (Tooltip = "When building state is set, the base class only changes the state of the default mesh. Implement this function in blueprint to add other behaviour")) + void BuildingStateSet(const EBuildingState& NewState, const EBuildingState& PreviousState); + + UFUNCTION(BlueprintCallable) + void StartBuildingConstruction(float TimeToConstruct); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + bool IsAtValidLocation(); + + UFUNCTION(BlueprintCallable) + EBuildingState GetBuildingState() const { return BuildingState; }; + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void SetHighlighted(bool Highlighted); + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Events") + FOnBuildingStateChanged OnBuildingStateChanged; +}; diff --git a/Public/MEnemySpawnPoint.h b/Public/MEnemySpawnPoint.h new file mode 100644 index 0000000..be66910 --- /dev/null +++ b/Public/MEnemySpawnPoint.h @@ -0,0 +1,31 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "GameplayTagAssetInterfacE.h" +#include "MEnemySpawnPoint.generated.h" + +UCLASS() +class MECHDEFENCE_API AMEnemySpawnPoint : public AActor, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AMEnemySpawnPoint(); + +protected: + virtual void BeginPlay() override; + + UPROPERTY(EditDefaultsOnly, Category = "Tags") + FGameplayTagContainer ActorTags; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + +private: + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + +}; diff --git a/Public/MGameMode.h b/Public/MGameMode.h new file mode 100644 index 0000000..9fe0f40 --- /dev/null +++ b/Public/MGameMode.h @@ -0,0 +1,96 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "GameplayTagAssetInterface.h" +#include "MGameMode.generated.h" + +UENUM(BlueprintType) +enum class EWaveState : uint8 +{ + WaitingToStart, + WaveInProgress, + WaitingToComplete, // All the enemies have been spawned and we're waiting for them to be destroyed or for the player to die to move to next state + WaveComplete, + GameOver, + LevelComplete +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActorKilledSignature, AActor*, VictimActor, AActor*, KillerActor, AController*, KillerController); + +UCLASS() +class MECHDEFENCE_API AMGameMode : public AGameModeBase +{ + GENERATED_BODY() + +protected: + UPROPERTY(EditDefaultsOnly, Category = "GameMode") + TSubclassOf SpectatingViewPointClass; + + UPROPERTY(EditDefaultsOnly, Category = "GameMode") + float LevelEndViewTargetBlendTime; + + UPROPERTY(EditDefaultsOnly, Category = "GameMode") + float TimeBetweenWaves; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GameMode") + int WaveCount; + + UPROPERTY(EditDefaultsOnly, Category = "GameMode") + int MaxEnemiesInWave; + + UPROPERTY(EditDefaultsOnly, Category = "GameMode") + int MinEnemiesInWave; + + UPROPERTY(EditDefaultsOnly, meta = (Tooltip = "These are the tags that are used to check how many enemies spawned in the current wave remain in the level"), Category = "GameMode") + FGameplayTagContainer SpawnedEnemyTags; + + virtual void StartPlay() override; + + UFUNCTION(BlueprintCallable) + void HandlePlayerKilled(); + + UFUNCTION(BlueprintCallable) + void HandlePlayerVehicleDestroyed(AActor* Vehicle); + + UFUNCTION(BlueprintCallable) + void GameOver(); + + UFUNCTION(BlueprintCallable) + void LevelCompleted(); + + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "GameMode") + void SpawnNewEnemy(); + + UFUNCTION(BlueprintImplementableEvent, Category = "GameMode") + void WaveStateChanged(EWaveState NewState, EWaveState OldState); + + UFUNCTION(BlueprintCallable, Category = "GameMode") + void StartWave(); + + void PrepareForNextWave(); + void EndWave(); + void CheckWaveState(); + void SetWaveState(EWaveState NewWaveState); + void SpawnEnemyTimerElapsed(); + + int NumEnemiesToSpawn; + + FTimerHandle TimerHandle_NextWaveStart; + FTimerHandle TimerHandle_EnemySpawn; + + EWaveState WaveState; + +public: + UPROPERTY(BlueprintAssignable, Category = "GameMode") + FOnActorKilledSignature OnActorKilled; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "GameMode") + int NumEnemiesKilled; + + void Tick(float DeltaSeconds) override; + + AMGameMode(); +}; diff --git a/Public/MGameplayActor.h b/Public/MGameplayActor.h new file mode 100644 index 0000000..197280b --- /dev/null +++ b/Public/MGameplayActor.h @@ -0,0 +1,33 @@ + + +#pragma once + +#include "CoreMinimal.h" + +#include "GameplayTagAssetInterface.h" +#include "GameFramework/Actor.h" +#include "MGameplayActor.generated.h" + +UCLASS() +class MECHDEFENCE_API AMGameplayActor : public APawn, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AMGameplayActor(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Gameplay") + FGameplayTagContainer ActorTags; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + +}; diff --git a/Public/MHealthComponent.h b/Public/MHealthComponent.h new file mode 100644 index 0000000..e45bf5a --- /dev/null +++ b/Public/MHealthComponent.h @@ -0,0 +1,63 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "MHealthComponent.generated.h" + +class UMHealthComponent; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnHealthChangedSignature, UMHealthComponent*, HealthComp, float, Health, float, HealthDelta, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActorDeath, UMHealthComponent*, HealthComponent, AActor*, DeadActor, AActor*, KillerActor); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class MECHDEFENCE_API UMHealthComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UMHealthComponent(); + +protected: + virtual void BeginPlay() override; + + bool bIsDead; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HealthComponent") + float Health; + + UPROPERTY(EditDefaultsOnly, Category = "HealthComponent", meta = (ClampMin = 0.f)) + float DefaultHealth; + + UFUNCTION() + void HandleTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser); + +public: + UFUNCTION(BlueprintCallable) + float GetHealth() const { return Health; } + + UFUNCTION(BlueprintCallable) + float GetHealthRemappedInRange(float Min = 0.f, float Max = 1.f); + + UFUNCTION(BlueprintCallable) + float GetDefaultHealth() const { return DefaultHealth; } + + UFUNCTION(BlueprintCallable) + bool IsAtMaxHealth() const { return Health == DefaultHealth; } + + UFUNCTION(BlueprintCallable) + bool IsDead() const { return bIsDead; }; + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnHealthChangedSignature OnHealthChanged; + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnActorDeath OnActorDeath; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HealthComponent") + bool bIsInvincible; + + UFUNCTION(BlueprintCallable, Category = "HealthComponent") + void Heal(float HealAmount); +}; diff --git a/Public/MInteractableActor.h b/Public/MInteractableActor.h new file mode 100644 index 0000000..486cacb --- /dev/null +++ b/Public/MInteractableActor.h @@ -0,0 +1,26 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "MInteractableActor.generated.h" + +// This class does not need to be modified. +UINTERFACE(MinimalAPI) +class UMInteractableActor : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class MECHDEFENCE_API IMInteractableActor +{ + GENERATED_BODY() + + // Add interface functions to this class. This is the class that will be inherited to implement this interface. +public: + virtual UMeshComponent* GetHoveredMeshComponent() = 0; +}; diff --git a/Public/MPlayerController.h b/Public/MPlayerController.h new file mode 100644 index 0000000..026ff7d --- /dev/null +++ b/Public/MPlayerController.h @@ -0,0 +1,52 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "MPlayerController.generated.h" + +/** + * + */ +class UUserWidget; +class AMWeapon; + +UCLASS() +class MECHDEFENCE_API AMPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + AMPlayerController(); + + UFUNCTION(BlueprintImplementableEvent, Category = "PlayerController") + void OnGameOver(); + + UFUNCTION(BlueprintImplementableEvent, Category = "PlayerController") + void OnLevelComplete(); + + UFUNCTION(BlueprintImplementableEvent) + void HandleTargetHit(AMWeapon* Weapon, AActor* Target, AActor* Shooter); + +protected: + UPROPERTY(EditDefaultsOnly, Category = "UI") + TSubclassOf PauseWidgetClass; + + UPROPERTY(EditDefaultsOnly, Category = "UI") + TSubclassOf HudWidgetClass; + + void SetupInputComponent() override; + + UFUNCTION(BlueprintCallable) + void TogglePause(); + + void BeginPlay() override; + + UPROPERTY(BlueprintReadOnly, Category = "UI") + UUserWidget* PauseWidget; + + UPROPERTY(BlueprintReadOnly, Category = "UI") + UUserWidget* HudWidget; + +}; diff --git a/Public/MPlayerMech.h b/Public/MPlayerMech.h new file mode 100644 index 0000000..850b0d9 --- /dev/null +++ b/Public/MPlayerMech.h @@ -0,0 +1,90 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + + +#include "GameplayTagAssetInterfacE.h" +#include "MInteractableActor.h" +#include "GameFramework/Pawn.h" +#include "MPlayerMech.generated.h" + +class UWidgetComponent; +class AMCharacter; +class UCameraComponent; +class UMHealthComponent; +class UBoxComponent; +class UEnvQuery; +class USpringArmComponent; +struct FEnvQueryResult; + +UCLASS() +class MECHDEFENCE_API AMPlayerMech : public APawn, public IGameplayTagAssetInterface, public IMInteractableActor +{ + GENERATED_BODY() + +public: + AMPlayerMech(); + +protected: + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UCameraComponent* CameraComponent; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + USpringArmComponent* SpringArmComponent; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + USkeletalMeshComponent* MeshComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components") + UWidgetComponent* StatusIndicatorWidget; + + UPROPERTY(EditDefaultsOnly, Category = "Tags") + FGameplayTagContainer ActorTags; + + UPROPERTY(EditDefaultsOnly, Category = "Mech") + float PlayerDismountRadius; + + UPROPERTY(EditDefaultsOnly, Category = "Mech") + FName PlayerAttachmentSocket; + + UPROPERTY(EditDefaultsOnly, Category = "Mech", meta = (Tooltip = "Offset from the ground to add to the dismount location calculated for the player")) + float PlayerDismountGroundOffset; + + UPROPERTY(EditDefaultsOnly, Category = "Mech", meta = (Tooltip = "Offset from the ground to add to the vehicle when it has been reset")); + float ResetZOffset; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Mech") + UMHealthComponent* HealthComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mech") + UEnvQuery* DismountEQS; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mech") + USoundBase* MountSound; + + // Reference to the player that has mounted this mech + AMCharacter* MountedPlayerCharacter; + + virtual void BeginPlay() override; + void SetupHealthIndicator(); + + void MoveForward(float ScaleValue); + void MoveRight(float ScaleValue); + void RequestDismount(); + void RevertOrientation(); + void HandleDismountResponse(TSharedPtr QueryResult); +public: + virtual void Tick(float DeltaTime) override; + + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + void MountPlayerCharacter(AMCharacter* PlayerCharacter); + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + + virtual UMeshComponent* GetHoveredMeshComponent() override; + + static int32 DebugMechDrawing; +}; diff --git a/Public/MProjectile.h b/Public/MProjectile.h new file mode 100644 index 0000000..3a67b6c --- /dev/null +++ b/Public/MProjectile.h @@ -0,0 +1,45 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "MProjectile.generated.h" + +class UCapsuleComponent; +class UNiagaraComponent; +class UNiagaraSystem; +class UProjectileMovementComponent; +UCLASS() +class MECHDEFENCE_API AMProjectile : public AActor +{ + GENERATED_BODY() + +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components") + UProjectileMovementComponent* ProjectileComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components") + UStaticMeshComponent* MeshComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components") + UNiagaraComponent* ParticleSystem; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components"); + UCapsuleComponent* CapsuleComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Projectile"); + UNiagaraSystem* ImpactEffect; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Projectile") + TArray> ActorClassesToIgnore; + +public: + AMProjectile(); + +protected: + virtual void BeginPlay() override; + + UFUNCTION() + void HandleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); +}; diff --git a/Public/MTargetableActor.h b/Public/MTargetableActor.h new file mode 100644 index 0000000..75d7990 --- /dev/null +++ b/Public/MTargetableActor.h @@ -0,0 +1,27 @@ + + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "MTargetableActor.generated.h" + +// This class does not need to be modified. +UINTERFACE(MinimalAPI) +class UMTargetableActor : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class MECHDEFENCE_API IMTargetableActor +{ + GENERATED_BODY() + +public: + // Get the target points that will be used to aim at this actor by AI + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void GetTargetPoints(TArray& TargetPoints); +}; diff --git a/Public/MWeapon.h b/Public/MWeapon.h new file mode 100644 index 0000000..e27ac6b --- /dev/null +++ b/Public/MWeapon.h @@ -0,0 +1,204 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "GameplayTagAssetInterface.h" +#include "MWeapon.generated.h" + +class AMCharacter; +class AMWeapon; + +UENUM(BlueprintType) +enum class EWeaponType : uint8 +{ + PrimaryWeapon = 0, + Scanner, + BlowTorch, + Tablet, + MaxWeaponType +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnTargetHit, AMWeapon*, Weapon, AActor*, TargetActor, AActor*, Shooter); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWeaponReloadStarted, AMWeapon*, Weapon); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWeaponReloadCompleted, AMWeapon*, Weapon); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWeaponReloadCancelled, AMWeapon*, Weapon); + +class USoundBase; +class UMatineeCameraShake; +class UNiagaraSystem; +class AMProjectile; + +UCLASS() +class MECHDEFENCE_API AMWeapon : public AActor +{ + GENERATED_BODY() + +public: + AMWeapon(); + +protected: + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + USkeletalMeshComponent* WeaponMesh; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + TSubclassOf DamageType; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + FName MuzzleSocketName; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta=(Tooltip="Name of socket on the player's skeleton where the weapon should be attached")) + FName PlayerAttachmentSocketName; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta=(Tooltip="Name of the exposed variable in the beam particle system that will be updated to the location of the target when the weapon fires")) + FName TracerParticleStartLocationName; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + UParticleSystem* MuzzleEffect; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + UNiagaraSystem* TracerEffect; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + UParticleSystem* DefaultImpactEffect; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + TSubclassOf CameraShakeClass; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + TSubclassOf ProjectileClass; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Weapon", meta = (Tooltip = "When the bullet from this weapon hits a target, these tags will determine if the target is valid and if damage and other UI effects should be applied")) + FGameplayTagContainer ValidTargetTags; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + float BaseDamage; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + float VulnerableDamageMultiplier; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Bullets fired per minute. Set it to 0 for weapons that should only fire once when fire button is pressed")) + float RateOfFire; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Bullet spread angle in degrees", ClampMin = 0.f)) + float BulletSpread; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Number of seconds after which StopFire is automatically called. Only used when RateOfFire is also set to 0", ClampMin = 0.f)) + float StopFireDelay; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Number of bullets in one clip after which the weapon is going to be reloaded. Set zero to disable reloading altogether")) + uint8 ClipSize; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Number of seconds it takes to reload the weapon")) + float ReloadTime; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon", meta = (Tooltip = "Chance for the projectile particle to spawn. 1 would mean always and 0 would mean never", ClampMin = 0.f, ClampMax = 1.f)) + float ProjectileSpawnChance; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + UAnimSequence* FireAnimation; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + USoundBase* FireSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + USoundBase* ReloadSound; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + float MaxWeaponRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Zoom") + float ZoomInFOV; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Zoom") + bool bCanZoom; + + float TimeBetweenShots; + float TimeLastFired; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon") + uint8 ClipRemainingBullets; + + bool bFiring; + bool bReloading; + FTimerHandle TimerHandle_TimeBetweenShots; + FTimerHandle TimerHandle_Reload; + bool bSecondaryFireBound; + + UFUNCTION() + void HandleReloadComplete(); + + virtual void BeginPlay() override; + + UFUNCTION(BlueprintCallable) + void Fire(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void OnFire(); + + UFUNCTION(BlueprintCallable) + void PlayFireEffects(FVector ShotDirection, UParticleSystem* ImpactEffect, FVector TraceEffectEndLocation, const FRotator ImpactParticleRotation); + + // This will hold the reload sound handle when it is played so that if the reload is cancelled + // we can stop the sound from playing + UAudioComponent* TempReloadSoundHandle; + +public: + virtual void Tick(float DeltaTime) override; + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + bool StartFire(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void StopFire(); + + UFUNCTION(BLueprintCallable, BlueprintNativeEvent) + void StartSecondaryFire(); + + UFUNCTION(BLueprintCallable, BlueprintNativeEvent) + void StopSecondaryFire(); + + UFUNCTION(BlueprintCallable) + virtual void Reload(); + + UFUNCTION(BlueprintCallable) + float GetReloadProgress(); + + UFUNCTION(BlueprintCallable) + bool IsFiring() { return bFiring; } + + UFUNCTION(BlueprintCallable) + bool IsReloading() { return bReloading; } + + UFUNCTION(BlueprintCallable) + void CancelReload(); + + UFUNCTION() + void HandleWeaponEquipped(AMCharacter* Character, AMWeapon* NewWeapon, AMWeapon* PreviousWeapon); + + UFUNCTION(BlueprintCallable, meta = (Tooltip = "Return the world coordinates of the position of the this weapon is aiming at")) + FVector CalculateTargetedWorldLocation(TSubclassOf ValidTargetClass, ECollisionChannel TraceChannel) const; + + UFUNCTION(BlueprintCallable) + AActor* GetTargetedActor(ECollisionChannel TraceChannel) const; + + FName GetPlayerAttachmentSocketName() const { return PlayerAttachmentSocketName; } + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnTargetHit OnTargetHit; + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnWeaponReloadStarted OnWeaponReloadStarted; + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnWeaponReloadCompleted OnWeaponReloadCompleted; + + UPROPERTY(BlueprintAssignable, Category = "Events") + FOnWeaponReloadCancelled OnWeaponReloadCancelled; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + EWeaponType WeaponType; + + +}; diff --git a/Test.txt b/Test.txt deleted file mode 100644 index e385eb5..0000000 --- a/Test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test document to see whether it works or not