// 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) { LogInfo(FString::Printf(TEXT("Yaw %f"), rotator.Yaw)); const float y_offset = aim_assist(player_location, rotator); if (y_offset != 0) { rotator.Yaw = y_offset; } LogInfo(FString::Printf(TEXT("Offset %f"), y_offset)); FVector line_end = FVector(5000, 0, 0); FVector aim_line_end = rotator.RotateVector(line_end); // DrawDebugLine( // GetWorld(), // player_location, // aim_line_end + player_location, // FColor::Red, // true // ); } 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. */ float ABasePawn::aim_assist(const FVector start, const FRotator rotator) { /* * 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 0; } 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 0; } const FVector enemy_vector = enemy_pawn->get_location() - start; const FRotator new_rotator = enemy_vector.Rotation(); //FVector target_vector = FVector(enemy_vector.X, enemy_vector.Y, 0); //target_vector.Normalize(); //FVector actual_vector = FVector(trace.X, trace.Y, 0) - start; //actual_vector.Normalize(); //const float angle_between = UKismetMathLibrary::DegAcos(FVector::DotProduct(actual_vector, target_vector)); //const FVector cross = FVector::CrossProduct(actual_vector, target_vector); //if (cross.Z > 0) //{ // return angle_between; //} //else if (cross.Z < 0) //{ // return angle_between * -1; //} //return 0; return new_rotator.Yaw; } 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(); }