You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
6.6 KiB

// Fill out your copyright notice in the Description page of Project Settings.
#include "Unit.h"
#include "SPGameState.h"
#include "StatsSubsystem.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/GameModeBase.h"
#include "Kismet/GameplayStatics.h"
class ASPGameState;
// Sets default values
AUnit::AUnit()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
ProjectileSpawnPoint = CreateDefaultSubobject<UArrowComponent>(TEXT("Projectile Spawn Point"));
ProjectileSpawnPoint->SetupAttachment(RootComponent);
DamageNumberWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("Damage Number Widget"));
DamageNumberWidget->SetWidgetSpace(EWidgetSpace::Screen);
DamageNumberWidget->SetVisibility(false, true);
DamageNumberWidget->SetupAttachment(RootComponent);
FString MeleeAttack1Name = "/Game/TopDown/Blueprints/Characters/Main/M_Melee.M_Melee";
UAnimMontage* MontageMelee1 = Cast<UAnimMontage>(StaticLoadObject(UAnimMontage::StaticClass(), nullptr, *MeleeAttack1Name));
FString MeleeAttack2Name = "/Game/TopDown/Blueprints/Characters/Main/M_Melee2.M_Melee2";
UAnimMontage* MontageMelee2 = Cast<UAnimMontage>(StaticLoadObject(UAnimMontage::StaticClass(), nullptr, *MeleeAttack2Name));
MeleeMontage.Add(MontageMelee1);
MeleeMontage.Add(MontageMelee2);
GetCharacterMovement()->SetWalkableFloorAngle(80);
GetCharacterMovement()->MaxStepHeight = 100;
}
// Called when the game starts or when spawned
void AUnit::BeginPlay()
{
Super::BeginPlay();
ApplyStats();
DamageNumbersOriginalPosition = DamageNumberWidget->GetRelativeLocation();
}
void AUnit::OnDeath_Implementation()
{
GetCharacterMovement()->SetMovementMode(MOVE_None);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCharacterMovement()->SetComponentTickEnabled(false);
SetActorTickEnabled(false);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
GetMesh()->SetSimulatePhysics(true);
if(ActorHasTag("Enemy"))
{
TObjectPtr<UStatsSubsystem> GameInstance = GetGameInstance()->GetSubsystem<UStatsSubsystem>();
GameInstance->XP += XP;
GameInstance->Money += Money;
GetMesh()->SetCollisionResponseToChannel(ECC_EngineTraceChannel1, ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_EngineTraceChannel1, ECR_Ignore);
GetWorld()->GetAuthGameMode()->GetGameState<ASPGameState>()->DecrementEnemies();
}
}
// Called every frame
void AUnit::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
CooldownTimer += DeltaTime;
if(IsDead())
{
OnDeath();
}
}
// Called to bind functionality to input
void AUnit::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void AUnit::ApplyMeleeDamage()
{
if(TargetUnit != nullptr)
{
UGameplayStatics::ApplyDamage(TargetUnit, Attack, GetController(), this, nullptr);
} else
{
UE_LOG(LogTemp, Warning, TEXT("Attempt to call Apply Melee Damage with no TargetUnit set"));
}
}
void AUnit::LaunchProjectileSignal()
{
if(TargetUnit && ProjectileClasses.Num() >= 1)
{
TSubclassOf<ABasicProjectile> P = ProjectileClasses[FMath::RandRange(0, ProjectileClasses.Num() - 1)];
FVector SpawnPoint = ProjectileSpawnPoint->GetComponentLocation();
FRotator ActorRotation = GetActorRotation();
FActorSpawnParameters SpawnOptions;
SpawnOptions.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ABasicProjectile* NewProjectile = GetWorld()->SpawnActor<ABasicProjectile>(P, SpawnPoint, ActorRotation, SpawnOptions);
ABasicProjectile* AsProjectile = Cast<ABasicProjectile>(NewProjectile);
NewProjectile->SetLifeSpan(5.0f);
AsProjectile->Damage = Attack;
AsProjectile->OwningController = GetController();
if(ActorHasTag("Enemy"))
{
AsProjectile->SphereComponent->SetCollisionProfileName("EnemyProjectile");
} else
{
AsProjectile->SphereComponent->SetCollisionProfileName("PlayerProjectile");
}
AsProjectile->SphereComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
UProjectileMovementComponent* ProjectileMovement = NewProjectile->GetComponentByClass<UProjectileMovementComponent>();
FVector UnitDirection = TargetUnit->GetActorLocation() - GetActorLocation();
UnitDirection.Normalize();
ProjectileMovement->Velocity = (UnitDirection * ProjectileSpeed);
}
}
void AUnit::SetTarget(AUnit* InTarget)
{
TargetUnit = InTarget;
}
AUnit* AUnit::FindNearestEnemy()
{
if(TargetUnit && !TargetUnit->IsDead() && FVector::Distance(GetActorLocation(), TargetUnit->GetActorLocation()) <= Range + 300)
{
return TargetUnit;
}
const bool IsEnemy = ActorHasTag("Enemy");
const FName TagToSearch = IsEnemy ? "Player" : "Enemy";
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsWithTag(GetWorld(), TagToSearch, FoundActors);
AActor* NearestActor = nullptr;
float NearestDistance = MAX_FLT;
for(AActor* CurrentActor : FoundActors)
{
if(Cast<AUnit>(CurrentActor)->IsDead())
{
continue;
}
const float Distance = FVector::DistSquared(CurrentActor->GetActorLocation(), GetActorLocation());
if(Distance < NearestDistance)
{
NearestActor = CurrentActor;
NearestDistance = Distance;
}
}
auto NewTarget = Cast<AUnit>(NearestActor);
TargetUnit = NewTarget;
return NewTarget;
}
float AUnit::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator,
AActor* DamageCauser)
{
AUnit* Attacker = Cast<AUnit>(EventInstigator->GetPawn());
if(Attacker)
{
float DodgeChance = 0.05 + (Dexterity * 0.008f) - (Attacker->Dexterity * 0.008f);
float DodgeRoll = FMath::RandRange(0.0f, 1.0f);
if(DodgeRoll <= DodgeChance)
{
EventMiss();
return 0;
}
}
FVector HitFromDirection = GetActorLocation() - DamageCauser->GetActorLocation();
HitFromDirection.Z = 0;
HitFromDirection.Normalize();
const float ActualDamage = DamageAmount * (1 - Defense / (Defense + 5.0f)) + 1;
HP -= FMath::RoundToInt(ActualDamage);
HP = FMath::Max(0, HP);
DamageNumberValue = FMath::RoundToInt(ActualDamage);
LaunchCharacter(HitFromDirection * 200.0f, true, false);
Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
return FMath::RoundToInt(ActualDamage);
}
bool AUnit::IsDead() const
{
return HP <= 0;
}
void AUnit::ApplyStats()
{
GetCharacterMovement()->RotationRate.Yaw = 180 + Dexterity * 2;
GetCharacterMovement()->MaxWalkSpeed = 600 + Dexterity * 15;
}
void AUnit::HideDamageNumbers() const
{
DamageNumberWidget->SetVisibility(false, true);
}