// 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); }