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.
220 lines
6.7 KiB
220 lines
6.7 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();
|
|
}
|
|
|
|
if(ActorHasTag("Player"))
|
|
{
|
|
Cast<ASPGameState>(GetWorld()->GetGameState())->PlayerDead();
|
|
}
|
|
}
|
|
|
|
// 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 + 50.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);
|
|
}
|
|
|