// 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(TEXT("Projectile Spawn Point")); ProjectileSpawnPoint->SetupAttachment(RootComponent); DamageNumberWidget = CreateDefaultSubobject(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(StaticLoadObject(UAnimMontage::StaticClass(), nullptr, *MeleeAttack1Name)); FString MeleeAttack2Name = "/Game/TopDown/Blueprints/Characters/Main/M_Melee2.M_Melee2"; UAnimMontage* MontageMelee2 = Cast(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 GameInstance = GetGameInstance()->GetSubsystem(); GameInstance->XP += XP; GameInstance->Money += Money; GetMesh()->SetCollisionResponseToChannel(ECC_EngineTraceChannel1, ECR_Ignore); GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_EngineTraceChannel1, ECR_Ignore); GetWorld()->GetAuthGameMode()->GetGameState()->DecrementEnemies(); } if(ActorHasTag("Player")) { Cast(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 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(P, SpawnPoint, ActorRotation, SpawnOptions); ABasicProjectile* AsProjectile = Cast(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(); 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 FoundActors; UGameplayStatics::GetAllActorsWithTag(GetWorld(), TagToSearch, FoundActors); AActor* NearestActor = nullptr; float NearestDistance = MAX_FLT; for(AActor* CurrentActor : FoundActors) { if(Cast(CurrentActor)->IsDead()) { continue; } const float Distance = FVector::DistSquared(CurrentActor->GetActorLocation(), GetActorLocation()); if(Distance < NearestDistance) { NearestActor = CurrentActor; NearestDistance = Distance; } } auto NewTarget = Cast(NearestActor); TargetUnit = NewTarget; return NewTarget; } float AUnit::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) { AUnit* Attacker = Cast(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); }