Files
2026-08-02 22:34:52 +03:00

209 lines
4.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Variant_Shooter/AI/ShooterNPC.h"
#include "ShooterWeapon.h"
#include "Components/SkeletalMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "Engine/World.h"
#include "ShooterGameMode.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "TimerManager.h"
void AShooterNPC::BeginPlay()
{
Super::BeginPlay();
// spawn the weapon
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = this;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
Weapon = GetWorld()->SpawnActor<AShooterWeapon>(WeaponClass, GetActorTransform(), SpawnParams);
}
void AShooterNPC::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// clear the death timer
GetWorld()->GetTimerManager().ClearTimer(DeathTimer);
}
float AShooterNPC::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
// ignore if already dead
if (bIsDead)
{
return 0.0f;
}
// Reduce HP
CurrentHP -= Damage;
// Have we depleted HP?
if (CurrentHP <= 0.0f)
{
Die();
}
return Damage;
}
void AShooterNPC::AttachWeaponMeshes(AShooterWeapon* WeaponToAttach)
{
const FAttachmentTransformRules AttachmentRule(EAttachmentRule::SnapToTarget, false);
// attach the weapon actor
WeaponToAttach->AttachToActor(this, AttachmentRule);
// attach the weapon meshes
WeaponToAttach->GetFirstPersonMesh()->AttachToComponent(GetFirstPersonMesh(), AttachmentRule, FirstPersonWeaponSocket);
WeaponToAttach->GetThirdPersonMesh()->AttachToComponent(GetMesh(), AttachmentRule, FirstPersonWeaponSocket);
}
void AShooterNPC::PlayFiringMontage(UAnimMontage* Montage)
{
// unused
}
void AShooterNPC::AddWeaponRecoil(float Recoil)
{
// unused
}
void AShooterNPC::UpdateWeaponHUD(int32 CurrentAmmo, int32 MagazineSize)
{
// unused
}
FVector AShooterNPC::GetWeaponTargetLocation()
{
// start aiming from the camera location
const FVector AimSource = GetFirstPersonCameraComponent()->GetComponentLocation();
FVector AimDir, AimTarget = FVector::ZeroVector;
// do we have an aim target?
if (CurrentAimTarget)
{
// target the actor location
AimTarget = CurrentAimTarget->GetActorLocation();
// apply a vertical offset to target head/feet
AimTarget.Z += FMath::RandRange(MinAimOffsetZ, MaxAimOffsetZ);
// get the aim direction and apply randomness in a cone
AimDir = (AimTarget - AimSource).GetSafeNormal();
AimDir = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(AimDir, AimVarianceHalfAngle);
} else {
// no aim target, so just use the camera facing
AimDir = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(GetFirstPersonCameraComponent()->GetForwardVector(), AimVarianceHalfAngle);
}
// calculate the unobstructed aim target location
AimTarget = AimSource + (AimDir * AimRange);
// run a visibility trace to see if there's obstructions
FHitResult OutHit;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
GetWorld()->LineTraceSingleByChannel(OutHit, AimSource, AimTarget, ECC_Visibility, QueryParams);
// return either the impact point or the trace end
return OutHit.bBlockingHit ? OutHit.ImpactPoint : OutHit.TraceEnd;
}
void AShooterNPC::AddWeaponClass(const TSubclassOf<AShooterWeapon>& InWeaponClass)
{
// unused
}
void AShooterNPC::OnWeaponActivated(AShooterWeapon* InWeapon)
{
// unused
}
void AShooterNPC::OnWeaponDeactivated(AShooterWeapon* InWeapon)
{
// unused
}
void AShooterNPC::OnSemiWeaponRefire()
{
// are we still shooting?
if (bIsShooting)
{
// fire the weapon
Weapon->StartFiring();
}
}
void AShooterNPC::Die()
{
// ignore if already dead
if (bIsDead)
{
return;
}
// raise the dead flag
bIsDead = true;
// increment the team score
if (AShooterGameMode* GM = Cast<AShooterGameMode>(GetWorld()->GetAuthGameMode()))
{
GM->IncrementTeamScore(TeamByte);
}
// disable capsule collision
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// stop movement
GetCharacterMovement()->StopMovementImmediately();
GetCharacterMovement()->StopActiveMovement();
// enable ragdoll physics on the third person mesh
GetMesh()->SetCollisionProfileName(RagdollCollisionProfile);
GetMesh()->SetSimulatePhysics(true);
GetMesh()->SetPhysicsBlendWeight(1.0f);
// schedule actor destruction
GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &AShooterNPC::DeferredDestruction, DeferredDestructionTime, false);
}
void AShooterNPC::DeferredDestruction()
{
Destroy();
}
void AShooterNPC::StartShooting(AActor* ActorToShoot)
{
// save the aim target
CurrentAimTarget = ActorToShoot;
// raise the flag
bIsShooting = true;
// signal the weapon
Weapon->StartFiring();
}
void AShooterNPC::StopShooting()
{
// lower the flag
bIsShooting = false;
// signal the weapon
Weapon->StopFiring();
}