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.
227 lines
7.2 KiB
227 lines
7.2 KiB
2 years ago
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||
|
|
||
|
#include "EnemyAIController.h"
|
||
|
#include "SinglePlayerGS.h"
|
||
|
#include "Kismet/GameplayStatics.h"
|
||
|
#include "GenericPlatform/GenericPlatformMath.h"
|
||
|
|
||
|
AEnemyAIController::AEnemyAIController()
|
||
|
: Super() {
|
||
|
PrimaryActorTick.bCanEverTick = true;
|
||
2 years ago
|
SetActorTickInterval(2.0f);
|
||
2 years ago
|
}
|
||
|
|
||
|
void AEnemyAIController::Tick(float DeltaSeconds) {
|
||
|
Super::Tick(DeltaSeconds);
|
||
|
if (bIsAITurn && !bIsActing) {
|
||
|
if (TroopersCursor >= PossessedTroopers.Num()) {
|
||
|
EndTurn();
|
||
|
} else {
|
||
|
MakeMove();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void AEnemyAIController::StartTurn() {
|
||
|
UE_LOG(LogTemp, Warning, TEXT("Enemy AI StartTurn"));
|
||
|
bIsAITurn = true;
|
||
|
bIsActing = false;
|
||
|
// MakeMove();
|
||
|
}
|
||
|
|
||
|
void AEnemyAIController::EndTurn() {
|
||
|
bIsAITurn = false;
|
||
|
TroopersCursor = 0;
|
||
|
bIsEnded = true;
|
||
|
}
|
||
|
|
||
|
void AEnemyAIController::ActionDone() {
|
||
|
bIsActing = false;
|
||
|
}
|
||
|
|
||
|
bool AEnemyAIController::IsAITurn() const {
|
||
|
return bIsAITurn;
|
||
|
}
|
||
|
|
||
2 years ago
|
void AEnemyAIController::SpawnIfNeeded() {
|
||
|
RemoveDeadTroopers();
|
||
|
while (PossessedTroopers.Num() < MAX_TROOPERS_COUNT) {
|
||
|
const FVector Location = GetFreeLocation();
|
||
|
const FVector Rotation = {0.0f, 0.0f, 0.0f};
|
||
|
FTransform SpawnLocationAndRotation(Rotation);
|
||
|
SpawnLocationAndRotation.SetLocation(Location);
|
||
|
const TArray<TSubclassOf<ATrooper>> &LoadedTroopersAssets = GetWorld()->
|
||
|
GetGameState<ASinglePlayerGS>()->GetTroopersAssets();
|
||
|
AActor *Spawned = GetWorld()->SpawnActorDeferred<ATrooper>(
|
||
|
LoadedTroopersAssets[FMath::RandRange(
|
||
|
0, LoadedTroopersAssets.Num() - 1)],
|
||
|
SpawnLocationAndRotation);
|
||
|
Cast<ATrooper>(Spawned)->Initialize(
|
||
|
1, Location, TroopersCount++);
|
||
|
Spawned->FinishSpawning(SpawnLocationAndRotation);
|
||
|
Spawned->SetActorLocation(Location);
|
||
|
ATrooper *Trooper = Cast<ATrooper>(Spawned);
|
||
|
GetWorld()->GetGameState<ASinglePlayerGS>()->AddTrooper(Trooper);
|
||
|
PossessedTroopers.Add(Trooper);
|
||
|
Trooper->SetAIPossession(this);
|
||
|
Trooper->HighlightAsEnemy(PLAYER_INDEX);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// void AEnemyAIController::SetTrooperAssetsAndSpawn(
|
||
|
// TArray<UClass *> TrooperAssets,
|
||
|
// int TrooperCount) {
|
||
|
// LoadedTrooperAssets = MoveTemp(TrooperAssets);
|
||
|
// TroopersCount = TrooperCount;
|
||
|
// SpawnIfNeeded();
|
||
|
// }
|
||
|
|
||
|
void AEnemyAIController::RemoveDeadTroopers() {
|
||
|
for (int index = 0; index < PossessedTroopers.Num(); ++index) {
|
||
|
if (!PossessedTroopers[index] || !PossessedTroopers[index]->
|
||
|
IsValidLowLevel() ||
|
||
|
PossessedTroopers[index]->IsDead()) {
|
||
|
PossessedTroopers.RemoveAtSwap(index);
|
||
|
index--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FVector AEnemyAIController::GetFreeLocation() const {
|
||
|
for (const auto Location : SpawnPoints) {
|
||
|
bool bIsClose = false;
|
||
|
for (const auto Trooper : PossessedTroopers) {
|
||
|
if ((Location - Trooper->GetLocation()).Size() < 100.0f) {
|
||
|
bIsClose = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (bIsClose) {
|
||
|
continue;
|
||
|
}
|
||
|
return Location;
|
||
|
}
|
||
|
return {-2000.0f, FMath::RandRange(-2000.0f, 2000.0f), 0.0f};
|
||
|
}
|
||
|
|
||
2 years ago
|
void AEnemyAIController::MakeMove() {
|
||
|
while (TroopersCursor < PossessedTroopers.Num()) {
|
||
|
while (TroopersCursor < PossessedTroopers.Num() && (
|
||
|
!PossessedTroopers[TroopersCursor] ||
|
||
|
!PossessedTroopers[TroopersCursor]->IsValidLowLevel()
|
||
|
|| PossessedTroopers[TroopersCursor]->IsDead())) {
|
||
|
++TroopersCursor;
|
||
|
}
|
||
|
if (TroopersCursor >= PossessedTroopers.Num()) {
|
||
|
return;
|
||
|
}
|
||
|
const int Index = GetClosestTrooper();
|
||
2 years ago
|
if (Index == -1) {
|
||
|
return;
|
||
|
}
|
||
2 years ago
|
bool failed;
|
||
|
if (!IsCloseEnough(Index)) {
|
||
|
failed = MoveTo(Index);
|
||
|
} else {
|
||
|
failed = TryAttack(Index);
|
||
|
}
|
||
|
if (failed) {
|
||
|
TroopersCursor++;
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool AEnemyAIController::IsCloseEnough(int TrooperIndex) const {
|
||
|
const ATrooper *CurrentTrooper = PossessedTroopers[TroopersCursor];
|
||
|
const ATrooper *OtherTrooper = PlayerTroopers[TrooperIndex];
|
||
|
return (CurrentTrooper->GetLocation() - OtherTrooper->GetLocation()).Size()
|
||
|
<= CurrentTrooper->GetRealActionRadius(1);
|
||
|
}
|
||
|
|
||
|
bool AEnemyAIController::TryAttack(int TrooperIndex) {
|
||
|
ATrooper *Attacker = PossessedTroopers[TroopersCursor];
|
||
|
const auto Location = PlayerTroopers[TrooperIndex]->GetLocation();
|
||
|
constexpr int ActionIndex = 1;
|
||
|
if (Attacker->CheckAttackCorrectness(Location, ActionIndex)) {
|
||
|
bIsActing = true;
|
||
|
Attacker->Attack(ActionIndex, Location);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool AEnemyAIController::MoveTo(int TrooperIndex) {
|
||
|
ATrooper *Trooper = PossessedTroopers[TroopersCursor];
|
||
|
const FVector CurrentLocation = Trooper->GetLocation();
|
||
|
const FVector Destination = PlayerTroopers[TrooperIndex]->GetLocation();
|
||
|
constexpr int ActionIndex = 1;
|
||
|
constexpr int MoveActionIndex = 0;
|
||
|
const float AttackRadius = Trooper->GetRealActionRadius(ActionIndex);
|
||
|
const FVector Vector = Destination - CurrentLocation;
|
||
|
const float MaxLength = Trooper->GetRealActionRadius(MoveActionIndex);
|
||
|
float PathLength = Vector.Size() - AttackRadius + 10.f;
|
||
|
PathLength = FMath::Min(PathLength, MaxLength);
|
||
|
FVector Location = CurrentLocation + Vector.GetSafeNormal2D() *
|
||
|
PathLength;
|
||
|
Location.Z = 0.0f;
|
||
|
if (PathLength > 1.0f && Trooper->CheckMoveCorrectness(Location)) {
|
||
|
bIsActing = true;
|
||
|
Trooper->MoveTrooper(Location);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
2 years ago
|
void AEnemyAIController::InitializeSpawnPoints() {
|
||
|
SpawnPoints.Add(FVector{
|
||
|
-2000.0f,
|
||
|
-(MAX_TROOPERS_COUNT - 1) * TROOPERS_DISTANCE.Y / 2,
|
||
|
0.0f
|
||
|
});
|
||
|
for (int index = 1; index < MAX_TROOPERS_COUNT; ++index) {
|
||
|
SpawnPoints.Add(SpawnPoints[SpawnPoints.Num() - 1] + TROOPERS_DISTANCE);
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
int AEnemyAIController::GetClosestTrooper() const {
|
||
|
float minDistance = 1000000.0f;
|
||
2 years ago
|
int minIndex = -1;
|
||
2 years ago
|
const ATrooper *CurrentTrooper = PossessedTroopers[TroopersCursor];
|
||
|
for (int index = 0; index < PlayerTroopers.Num(); ++index) {
|
||
|
const ATrooper *OtherTrooper = PlayerTroopers[index];
|
||
|
if (OtherTrooper->IsValidLowLevel() && !OtherTrooper->IsDead()) {
|
||
|
const float distance = (
|
||
|
CurrentTrooper->GetLocation() - OtherTrooper->
|
||
|
GetLocation()).Size();
|
||
|
if (distance < minDistance) {
|
||
|
minDistance = distance;
|
||
|
minIndex = index;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return minIndex;
|
||
|
}
|
||
|
|
||
|
void AEnemyAIController::InitializeTroopers(
|
||
|
const TArray<ATrooper *> &Troopers) {
|
||
|
for (const auto Trooper : Troopers) {
|
||
|
if (Trooper != nullptr) {
|
||
|
if (Trooper->GetPlayerIndex() == AI_INDEX) {
|
||
|
Trooper->SetAIPossession(this);
|
||
|
PossessedTroopers.Add(Trooper);
|
||
|
} else {
|
||
|
PlayerTroopers.Add(Trooper);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
2 years ago
|
InitializeSpawnPoints();
|
||
|
SpawnIfNeeded();
|
||
2 years ago
|
}
|
||
|
|
||
|
void AEnemyAIController::BeginPlay() {
|
||
|
Super::BeginPlay();
|
||
|
}
|