280 lines
6.1 KiB
C++
Executable File
280 lines
6.1 KiB
C++
Executable File
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "BasePawn.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "GameFramework/FloatingPawnMovement.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Kismet/KismetSystemLibrary.h"
|
|
#include "Util.h"
|
|
|
|
#define LogError(Msg) Util::log_error(TEXT("ABasePawn"), Msg)
|
|
|
|
// Sets default values
|
|
ABasePawn::ABasePawn(const FObjectInitializer &object_initializer):
|
|
APawn(object_initializer)
|
|
{
|
|
// Create default components.
|
|
root_component = object_initializer.CreateDefaultSubobject<USceneComponent>(this, FName("Root"));
|
|
RootComponent = root_component;
|
|
|
|
collision = object_initializer.CreateDefaultSubobject<UBoxComponent>(this, FName("Collision"));
|
|
collision->SetupAttachment(RootComponent);
|
|
collision->SetCollisionProfileName(FName("Pawn"));
|
|
collision->SetCanEverAffectNavigation(false);
|
|
|
|
player_mesh = object_initializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, FName("PlayerMesh"));
|
|
player_mesh->SetupAttachment(collision);
|
|
player_mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
player_mesh->SetCanEverAffectNavigation(false);
|
|
|
|
movement_component = object_initializer.CreateDefaultSubobject<UFloatingPawnMovement>(this, FName("MovementComponent"));
|
|
movement_component->UpdatedComponent = collision;
|
|
|
|
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
// Set defaults.
|
|
max_health = 100;
|
|
max_ammo = 25;
|
|
}
|
|
|
|
// Called when the game starts or when spawned
|
|
void ABasePawn::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
/*
|
|
* Setup collision handler.
|
|
*/
|
|
collision->OnComponentHit.AddDynamic(this, &ABasePawn::handle_hit);
|
|
|
|
|
|
/*
|
|
* Set base state.
|
|
*/
|
|
current_health = max_health;
|
|
current_ammo = max_ammo;
|
|
}
|
|
|
|
void ABasePawn::handle_hit(
|
|
UPrimitiveComponent* hit_component,
|
|
AActor* other_actor,
|
|
UPrimitiveComponent* other_component,
|
|
FVector normal_impulse,
|
|
const FHitResult& hit
|
|
)
|
|
{
|
|
UClass* other_class = other_actor->GetClass();
|
|
if (damage_class_map.Contains(other_class))
|
|
{
|
|
// Update current health.
|
|
current_health -= damage_class_map[other_class];
|
|
}
|
|
|
|
handle_hit_internal(other_actor);
|
|
}
|
|
|
|
void ABasePawn::handle_hit_internal(AActor* other_actor)
|
|
{
|
|
// do nothing by default;
|
|
}
|
|
|
|
UPawnMovementComponent* ABasePawn::GetMovementComponent() const
|
|
{
|
|
return movement_component;
|
|
}
|
|
|
|
FVector ABasePawn::get_location() const
|
|
{
|
|
return collision->GetComponentLocation();
|
|
}
|
|
|
|
// Called every frame
|
|
void ABasePawn::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
/*
|
|
* Auto-fire.
|
|
*/
|
|
shoot_time_elapsed += DeltaTime;
|
|
if (shooting_enabled && shoot_time_elapsed > fire_rate_s)
|
|
{
|
|
shoot();
|
|
}
|
|
|
|
/*
|
|
* Handle death.
|
|
*/
|
|
if (current_health <= 0 && !death_handled)
|
|
{
|
|
handle_death();
|
|
death_handled = true;
|
|
}
|
|
}
|
|
|
|
void ABasePawn::handle_move_forward(float axis)
|
|
{
|
|
if (axis != 0)
|
|
{
|
|
AddMovementInput(FVector(1,0,0), axis);
|
|
}
|
|
}
|
|
|
|
void ABasePawn::handle_move_right(float axis)
|
|
{
|
|
if (axis != 0)
|
|
{
|
|
AddMovementInput(FVector(0, 1, 0), axis);
|
|
}
|
|
}
|
|
|
|
void ABasePawn::update_yaw_x(float x_component)
|
|
{
|
|
yaw.x_component = x_component;
|
|
}
|
|
|
|
void ABasePawn::update_yaw_y(float y_component)
|
|
{
|
|
yaw.y_component = y_component;
|
|
}
|
|
|
|
void ABasePawn::start_shooting()
|
|
{
|
|
shooting_enabled = true;
|
|
shoot();
|
|
}
|
|
|
|
void ABasePawn::stop_shooting()
|
|
{
|
|
shooting_enabled = false;
|
|
}
|
|
|
|
void ABasePawn::shoot()
|
|
{
|
|
if (current_ammo > 0)
|
|
{
|
|
const FVector player_location = get_location();
|
|
FRotator rotator(0, yaw.calculate_yaw(), 0);
|
|
if (use_aim_assist)
|
|
{
|
|
float assist_yaw;
|
|
const bool success = aim_assist(player_location, rotator, assist_yaw);
|
|
if (success)
|
|
{
|
|
rotator.Yaw = assist_yaw;
|
|
}
|
|
}
|
|
|
|
FVector forward = FRotationMatrix(rotator).GetUnitAxis(EAxis::X);
|
|
FVector spawn_location = player_location + (forward * 100);
|
|
FActorSpawnParameters spawnParameters;
|
|
spawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
spawnParameters.Owner = this;
|
|
spawnParameters.Instigator = this;
|
|
AActor* bullet = GetWorld()->SpawnActor<AActor>(
|
|
bullet_actor_class,
|
|
spawn_location,
|
|
rotator,
|
|
spawnParameters
|
|
);
|
|
|
|
shoot_time_elapsed = 0;
|
|
current_ammo -= 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the yaw angle needed to hit the nearest enemy target.
|
|
*
|
|
* @return Yaw angle offset.
|
|
*/
|
|
bool ABasePawn::aim_assist(const FVector start, const FRotator rotator, float &yaw_output)
|
|
{
|
|
/*
|
|
* Create trace end based on start location and given rotator.
|
|
*/
|
|
FVector trace = start + FVector(10000, 0, 0);
|
|
trace = rotator.RotateVector(trace);
|
|
|
|
/*
|
|
* Run box trace for enemy targets.
|
|
*/
|
|
FHitResult out_hit;
|
|
const bool succeeded = UKismetSystemLibrary::SphereTraceSingle(
|
|
GetWorld(),
|
|
start,
|
|
trace,
|
|
aim_assist_distance,
|
|
ETraceTypeQuery::TraceTypeQuery3, // "Enemy" trace channel
|
|
false,
|
|
TArray<AActor*>(),
|
|
EDrawDebugTrace::Type::None,
|
|
out_hit,
|
|
true,
|
|
FLinearColor::Green,
|
|
FLinearColor::Green,
|
|
0
|
|
);
|
|
|
|
/*
|
|
* No enemies were within range.
|
|
*/
|
|
if (!succeeded)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ABasePawn* enemy_pawn = Cast<ABasePawn>(out_hit.Actor);
|
|
/*
|
|
* Bad setup. We should only be hitting pawns.
|
|
*/
|
|
if (!enemy_pawn)
|
|
{
|
|
LogError("Aim assist trace hit non-pawn");
|
|
return false;
|
|
}
|
|
|
|
const FVector enemy_vector = enemy_pawn->get_location() - start;
|
|
yaw_output = enemy_vector.Rotation().Yaw;
|
|
return true;
|
|
}
|
|
|
|
void ABasePawn::handle_death()
|
|
{
|
|
/*
|
|
* Disable collision.
|
|
*/
|
|
collision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
|
|
if (death_effect)
|
|
{
|
|
death_effect->Destroy();
|
|
death_effect = nullptr;
|
|
}
|
|
|
|
/*
|
|
* Spawn particle effect.
|
|
*/
|
|
FVector player_location = player_mesh->GetComponentLocation();
|
|
FActorSpawnParameters spawnParameters;
|
|
spawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
death_effect = GetWorld()->SpawnActor<AActor>(
|
|
death_class,
|
|
player_location,
|
|
FRotator(0,0,0),
|
|
spawnParameters
|
|
);
|
|
|
|
destroy_self();
|
|
}
|
|
|
|
void ABasePawn::destroy_self()
|
|
{
|
|
/*
|
|
* Remove thyself from this world.
|
|
*/
|
|
Destroy();
|
|
}
|