// 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 LogInfo(Msg) Util::log_info(TEXT("ABasePawn"), Msg) #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(this, FName("Root")); RootComponent = root_component; collision = object_initializer.CreateDefaultSubobject(this, FName("Collision")); collision->SetupAttachment(RootComponent); collision->SetCollisionProfileName(FName("Pawn")); collision->SetCanEverAffectNavigation(false); player_mesh = object_initializer.CreateDefaultSubobject(this, FName("PlayerMesh")); player_mesh->SetupAttachment(collision); player_mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); player_mesh->SetCanEverAffectNavigation(false); movement_component = object_initializer.CreateDefaultSubobject(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]; LogInfo(FString::Printf(TEXT("Total health %d"), current_health)); } } 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) { handle_death(); current_health = max_health; } } 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 succeess = aim_assist(player_location, rotator, assist_yaw); if (assist_yaw) { LogInfo("Aim assist activated!"); 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( 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(), EDrawDebugTrace::Type::None, out_hit, true, FLinearColor::Green, FLinearColor::Green, 0 ); /* * No enemies were within range. */ if (!succeeded) { return false; } ABasePawn* enemy_pawn = Cast(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( death_class, player_location, FRotator(0,0,0), spawnParameters ); destroy_self(); } void ABasePawn::destroy_self() { /* * Remove thyself from this world. */ Destroy(); }