476 lines
11 KiB
C++
476 lines
11 KiB
C++
// Project Lab - NHTV Igad
|
|
|
|
#include "UnrealProject.h"
|
|
#include "NetworkSwitchComponent.h"
|
|
#include "CreatureSpawnComponent.h"
|
|
#include "DefaultGameMode.h"
|
|
#include "SpawnerBase.h"
|
|
#include "NPCBase.h"
|
|
#include "NetworkCharacter.h"
|
|
#include "NetworkPlayer.h"
|
|
#include "EnemyBase.h"
|
|
#include "GeneralEnemy.h"
|
|
#include "MiniBossCreature.h"
|
|
|
|
|
|
ASpawnerBase::ASpawnerBase()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
USceneComponent* spawnerRoot = CreateDefaultSubobject<USceneComponent>("Spawner Root");
|
|
RootComponent = spawnerRoot;
|
|
|
|
displayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
|
displayMesh->bHiddenInGame = true;
|
|
displayMesh->bGenerateOverlapEvents = false;
|
|
displayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
displayMesh->AttachTo(RootComponent);
|
|
|
|
// Kill particle effect
|
|
killParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Kill Particle1"));
|
|
killParticle->AttachTo(RootComponent);
|
|
|
|
displayArrow = CreateDefaultSubobject<UArrowComponent>(TEXT("Arrow"));
|
|
displayArrow->AttachTo(displayMesh);
|
|
|
|
displayDoors = CreateDefaultSubobject<UNetworkSwitchComponent>(TEXT("Visualizer"));
|
|
displayDoors->AttachTo(displayMesh);
|
|
|
|
visualizerComponent = CreateDefaultSubobject<UCreatureSpawnComponent>(TEXT("Control points visualizer"));
|
|
|
|
visualizerComponent->AttachTo(displayMesh);
|
|
|
|
team = NPCTeam::Team1;
|
|
aggroRadius = 800;
|
|
deaggroRadius = aggroRadius;
|
|
m_mobCount = 0;
|
|
formationScale = 1.0f;
|
|
formationDistance = 100;
|
|
formationRadius = 100;
|
|
resetTimer = 30;
|
|
dropKeyFragmentIndex = 0;
|
|
hasDroppedKey = false;
|
|
|
|
bReplicates = true;
|
|
bAlwaysRelevant = true;
|
|
}
|
|
|
|
void ASpawnerBase::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
TArray<AActor*> test;
|
|
GetAttachedActors(test);
|
|
for (int i = 0; i < test.Num(); i++)
|
|
{
|
|
|
|
if (Cast<ANPCBase>(test[i]))
|
|
{
|
|
spawns.Add(test[i]->GetClass());
|
|
formationPoints.Add(FVector2D(test[i]->GetActorLocation() - GetActorLocation()));
|
|
formationRotation.Add(test[i]->GetActorRotation().Yaw);
|
|
//RPRINT("mobs" + spawns[i]->GetName());
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
Cast<ANPCBase>(test[i])->UnsetSpawn();
|
|
}
|
|
(test[i])->Destroy();
|
|
(test[i]) = nullptr;
|
|
}
|
|
else
|
|
{
|
|
SpawnLocation.Add(test[i]);
|
|
|
|
}
|
|
|
|
}
|
|
for (int32 i = 0; i < spawns.Num(); i++)
|
|
m_mobs.Add(nullptr);
|
|
|
|
m_threatMap.SetNum(m_mobs.Num());
|
|
for (int i = 0; i < spawns.Num(); i++)
|
|
{
|
|
if (i != 0 && spawns[i]->IsChildOf(AGeneralEnemy::StaticClass()))
|
|
{
|
|
Swap(spawns[i], spawns[0]);
|
|
Swap(formationPoints[i], formationPoints[0]);
|
|
Swap(formationRotation[i], formationRotation[0]);
|
|
}
|
|
}
|
|
TArray<FHitResult> hitResult;
|
|
GetWorld()->LineTraceMultiByChannel(hitResult, GetActorLocation(), GetActorLocation() + GetActorUpVector()*-10000.0f, ECollisionChannel::ECC_WorldStatic, FCollisionQueryParams(FName(L"spawnerPlacementTrace"),true));
|
|
bool hit = false;
|
|
float distance = BIG_NUMBER;
|
|
FVector impactLocation;
|
|
for (int i = 0; i < hitResult.Num(); i++)
|
|
{
|
|
if (FVector::Dist(FVector(hitResult[i].ImpactPoint), GetActorLocation()) < distance&&FVector(hitResult[i].ImpactPoint)!= GetActorLocation())
|
|
{
|
|
distance = FVector::Dist(FVector(hitResult[i].ImpactPoint), GetActorLocation());
|
|
impactLocation = FVector(hitResult[i].ImpactPoint);
|
|
hit = true;
|
|
}
|
|
}
|
|
|
|
// Correct spawner position when it can move to the ground below it
|
|
if (hit)
|
|
{
|
|
this->SetActorLocation(FVector(GetActorLocation().X, GetActorLocation().Y, impactLocation.Z));
|
|
}
|
|
|
|
for (int i = 0; i < formationPoints.Num(); i++)
|
|
{
|
|
formationPoints[i] = FVector2D(GetActorRotation().RotateVector(FVector(formationPoints[i].X, formationPoints[i].Y, 0)));
|
|
formationPoints[i] *= formationScale;
|
|
}
|
|
if (spawns.Num() > formationPoints.Num())
|
|
{
|
|
int difference = spawns.Num() - formationPoints.Num();
|
|
for (int i = 0; i < difference; i++)
|
|
{
|
|
formationPoints.Add(FVector2D());
|
|
}
|
|
}
|
|
if (spawns.Num() > formationRotation.Num())
|
|
{
|
|
int difference = spawns.Num() - formationRotation.Num();
|
|
for (int i = 0; i < difference; i++)
|
|
{
|
|
formationRotation.Add(0);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ASpawnerBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
Super::EndPlay(EndPlayReason);
|
|
|
|
if (Role != ROLE_Authority)
|
|
return;
|
|
|
|
for (int32 i = 0; i < m_mobs.Num(); i++)
|
|
{
|
|
if (m_mobs[i])
|
|
{
|
|
m_mobs[i]->UnsetSpawn();
|
|
m_mobs[i] = nullptr;
|
|
}
|
|
}
|
|
formationEnemies.Empty();
|
|
m_nearbyPlayers.clear();
|
|
}
|
|
|
|
void ASpawnerBase::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
// if (formationEnemies.Num() > spawns.Num())
|
|
// formationEnemies.Empty();
|
|
bool reset = false;
|
|
m_resetTimer -= DeltaTime;
|
|
if (m_resetTimer <= 0)
|
|
{
|
|
reset = true;
|
|
}
|
|
for (int i = 0; i < m_mobs.Num(); i++)
|
|
{
|
|
if (m_mobs[i] == nullptr)
|
|
continue;
|
|
if (reset)
|
|
{
|
|
ForceResetTarget(m_mobs[i]);
|
|
}
|
|
if (m_mobs[i]->IsA(AEnemyBase::StaticClass()))
|
|
{
|
|
Cast<AEnemyBase>(m_mobs[i])->hasGeneral = hasGeneral;
|
|
}
|
|
if (m_mobs[i]->IsA(AGeneralEnemy::StaticClass()))
|
|
{
|
|
AGeneralEnemy* asGeneral = Cast<AGeneralEnemy>(m_mobs[i]);
|
|
if (formationEnemies != asGeneral->formationEnemies)
|
|
{
|
|
asGeneral->formationEnemies = formationEnemies;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 ASpawnerBase::OnMobDie(class ANPCBase* mob)
|
|
{
|
|
check(m_mobCount > 0);
|
|
check(Role == ROLE_Authority);
|
|
|
|
int32 idx = -1;
|
|
for (int32 i = 0; i < spawns.Num(); i++)
|
|
{
|
|
if (m_mobs[i] == mob)
|
|
{
|
|
if (m_mobs[i]->IsA(AGeneralEnemy::StaticClass()))
|
|
{
|
|
formationEnemies.Empty();
|
|
}
|
|
if (m_mobs[i]->IsA(AEnemyBase::StaticClass()))
|
|
{
|
|
AEnemyBase* asEnemyBase = Cast<AEnemyBase>(m_mobs[i]);
|
|
if (asEnemyBase->hasGeneral)
|
|
formationEnemies.Remove(asEnemyBase);
|
|
}
|
|
|
|
m_mobCount--;
|
|
|
|
if(m_mobCount == 0)
|
|
{
|
|
OnCampCleared();
|
|
}
|
|
|
|
m_mobs[i] = nullptr;
|
|
|
|
idx = i;
|
|
|
|
m_threatMap[i].clear();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
check(idx >= 0);
|
|
return idx;
|
|
}
|
|
|
|
void ASpawnerBase::OnCampCleared_Implementation()
|
|
{
|
|
if(killParticle->Template)
|
|
{
|
|
killParticle->ResetParticles();
|
|
killParticle->ActivateSystem();
|
|
}
|
|
m_OnCampCleared();
|
|
}
|
|
|
|
int32 ASpawnerBase::GetAliveMobCount()
|
|
{
|
|
return m_mobCount;
|
|
}
|
|
|
|
FVector ASpawnerBase::SpawnResetPosition()
|
|
{
|
|
return GetActorLocation();
|
|
}
|
|
|
|
void ASpawnerBase::m_OnPlayerEnterOverlap(class ANetworkCharacter& player)
|
|
{
|
|
auto find = m_nearbyPlayers.find(&player);
|
|
if (find != m_nearbyPlayers.end())
|
|
return;
|
|
|
|
ANetworkPlayer* playerCast = Cast<ANetworkPlayer>(&player);
|
|
if (IsValid(playerCast))
|
|
{
|
|
playerCast->AddThreatLevel_Client(threatLevel);
|
|
}
|
|
|
|
// Player was succesfully added
|
|
m_nearbyPlayers.emplace(&player);
|
|
|
|
//adding the threat to all mobs
|
|
|
|
/* for (int32 i = 0; i < m_mobs.Num(); i++)
|
|
{
|
|
if (m_mobs[i] == nullptr)
|
|
continue;
|
|
// auto ret = m_threatMap[i].emplace(&player, 0);
|
|
if (!ret.second)
|
|
{
|
|
FPRINT("failed to add player to creaturemap ");
|
|
}
|
|
}*/
|
|
}
|
|
void ASpawnerBase::m_OnPlayerExitOverlap(class ANetworkCharacter& player)
|
|
{
|
|
auto find = m_nearbyPlayers.find(&player);
|
|
if (find == m_nearbyPlayers.end())
|
|
{
|
|
FERROR("leaving player was not found in the nearbyPlayers");
|
|
return;
|
|
}
|
|
|
|
ANetworkPlayer* playerCast = Cast<ANetworkPlayer>(&player);
|
|
if (IsValid(playerCast))
|
|
{
|
|
playerCast->RemoveThreatLevel_Client(threatLevel);
|
|
}
|
|
|
|
// Player was succesfully removed
|
|
m_nearbyPlayers.erase(find);
|
|
}
|
|
|
|
class ANetworkPlayer* ASpawnerBase::m_GetBiggestThreat(int32 index)
|
|
{
|
|
ANetworkPlayer* ret = nullptr;
|
|
int32 biggestThreat = -1;
|
|
for (auto cpair : m_threatMap[index])
|
|
{
|
|
if (cpair.second > biggestThreat)
|
|
{
|
|
biggestThreat = cpair.second;
|
|
ret = cpair.first;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
void ASpawnerBase::m_OnMobSpawn(int32 index)
|
|
{
|
|
ANPCBase* creature = m_mobs[index];
|
|
if (creature == nullptr)
|
|
{
|
|
FWARNING("creature is null at spawn");
|
|
return;
|
|
}
|
|
if (creature->IsA(AGeneralEnemy::StaticClass()))
|
|
{
|
|
AGeneralEnemy* asGeneral = Cast<AGeneralEnemy>(creature);
|
|
asGeneral->formationPoints = formationPoints;
|
|
}
|
|
else
|
|
{
|
|
if (creature->IsA(AEnemyBase::StaticClass()))
|
|
formationEnemies.Add(Cast<AEnemyBase>(creature));
|
|
}
|
|
m_threatMap[index].clear();
|
|
|
|
/* for (ANetworkPlayer* player : m_nearbyPlayers)
|
|
{
|
|
auto ret = m_threatMap[index].insert(std::pair<ANetworkPlayer*, int32>(player, 0));
|
|
if (!ret.second)
|
|
{
|
|
FPRINT("adding failed");
|
|
}
|
|
}*/
|
|
|
|
if (creature->IsA(AMiniBossCreature::StaticClass()) && dropKeyFragmentIndex >= keyFragmentMin && dropKeyFragmentIndex <= keyFragmentMax)
|
|
Cast<AMiniBossCreature>(creature)->keyDropped = PlayerKeyType(dropKeyFragmentIndex);
|
|
}
|
|
|
|
class ANetworkPlayer* ASpawnerBase::GetClosestPlayer()
|
|
{
|
|
if (m_nearbyPlayers.empty())
|
|
return nullptr;
|
|
UWorld* const world = GetWorld();
|
|
if (!world)
|
|
return nullptr;
|
|
ADefaultGameMode* const mode = Cast<ADefaultGameMode>(world->GetAuthGameMode());
|
|
if (!mode)
|
|
return nullptr;
|
|
|
|
// Find the closest player (in aggroRadius)
|
|
ANetworkPlayer* closest = nullptr;
|
|
float closestDist = BIG_NUMBER;
|
|
const float aggroSqr = aggroRadius * aggroRadius;
|
|
for (ANetworkCharacter* character :m_nearbyPlayers)
|
|
{
|
|
|
|
ANetworkPlayer *player = Cast<ANetworkPlayer>(character);
|
|
if (player)
|
|
{
|
|
|
|
// ANetworkPlayer* player = Cast<ANetworkPlayer>(*iter);
|
|
const float distSqr = FVector2D::DistSquared(FVector2D(this->GetActorLocation()), FVector2D(player->GetActorLocation()));
|
|
if (distSqr < closestDist && distSqr <= aggroSqr)
|
|
{
|
|
closest = player;
|
|
closestDist = distSqr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
void ASpawnerBase::AddThreat(class ANPCBase* creature, class ANetworkPlayer* character, int32 threat)
|
|
{
|
|
if (creature == nullptr || creature->IsPendingKill() || character == nullptr || character->IsPendingKill())
|
|
return;
|
|
//find the creaturemap
|
|
|
|
int32 index = 0;
|
|
bool found = false;
|
|
for (ANPCBase* mob : m_mobs)
|
|
{
|
|
if (creature == mob)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
if (!found)
|
|
{
|
|
FERROR("creature not part of threatMap");
|
|
return;
|
|
}
|
|
|
|
//find the player
|
|
map<class ANetworkPlayer*, int32>::iterator it = m_threatMap[index].find(character);
|
|
|
|
if (it == m_threatMap[index].end())
|
|
{
|
|
FERROR("character not part of creatureMap");
|
|
return;
|
|
}
|
|
it->second += threat;
|
|
}
|
|
|
|
void ASpawnerBase::GetNewTarget(class ANPCBase* mob)
|
|
{
|
|
return;
|
|
}
|
|
void ASpawnerBase::ForceResetTarget(class ANPCBase* mob)
|
|
{
|
|
mob->target = nullptr;
|
|
}
|
|
void ASpawnerBase::ForceSetTarget(class ANPCBase* mob,class ANetworkPlayer* target)
|
|
{
|
|
mob->target = target;
|
|
}
|
|
void ASpawnerBase::SetTeam(int newTeam)
|
|
{
|
|
team = (NPCTeam)newTeam;
|
|
|
|
for (int i = 0; i < m_mobs.Num(); i++)
|
|
{
|
|
if (!IsValid(m_mobs[i]))
|
|
continue;
|
|
|
|
m_mobs[i]->SetTeam(newTeam);
|
|
|
|
if (!m_mobs[i]->target)
|
|
continue;
|
|
if(m_mobs[i]->target->GetTeam() == newTeam)
|
|
m_mobs[i]->target = nullptr;
|
|
}
|
|
}
|
|
void ASpawnerBase::CaptureCamp(int team)
|
|
{
|
|
SetTeam(team);
|
|
}
|
|
|
|
TArray<ANetworkPlayer*> ASpawnerBase::GetNearbyPlayers()
|
|
{
|
|
TArray<ANetworkPlayer*> players;
|
|
for(ANetworkCharacter* character : m_nearbyPlayers)
|
|
{
|
|
ANetworkPlayer* player = Cast<ANetworkPlayer>(character);
|
|
if(player)
|
|
{
|
|
players.Add(player);
|
|
}
|
|
}
|
|
return players;
|
|
}
|
|
|
|
void ASpawnerBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(ASpawnerBase, team);
|
|
} |