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