209 lines
4.9 KiB
C++
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();
|
|
}
|