2021-12-05 17:54:36 +00:00
|
|
|
#include "Spawner.h"
|
|
|
|
#include "EntityManager.h"
|
|
|
|
#include "dLogger.h"
|
|
|
|
#include "Game.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include <functional>
|
|
|
|
#include "GeneralUtils.h"
|
|
|
|
#include "dZoneManager.h"
|
2023-07-15 08:54:41 +00:00
|
|
|
#include "SpawnPatterns.h"
|
|
|
|
|
|
|
|
std::map<LOT, std::vector<std::pair<NiPoint3, float>>> Spawner::m_Ratings;
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
Spawner::Spawner(const SpawnerInfo info) {
|
|
|
|
m_Info = info;
|
|
|
|
m_Active = m_Info.activeOnLoad && info.spawnActivator;
|
|
|
|
m_EntityInfo = EntityInfo();
|
|
|
|
m_EntityInfo.spawner = this;
|
|
|
|
|
|
|
|
if (!m_Info.emulated) {
|
|
|
|
m_EntityInfo.spawnerID = m_Info.spawnerID;
|
|
|
|
} else {
|
|
|
|
m_EntityInfo.spawnerID = m_Info.emulator;
|
|
|
|
m_Info.isNetwork = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_EntityInfo.lot = m_Info.templateID;
|
|
|
|
m_EntityInfo.scale = m_Info.templateScale;
|
|
|
|
|
|
|
|
m_Start = m_Info.noTimedSpawn;
|
|
|
|
|
|
|
|
//ssssh...
|
|
|
|
if (m_EntityInfo.lot == 14718) { //AG - MAELSTROM SAMPLE
|
|
|
|
m_Info.groups.emplace_back("MaelstromSamples");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_EntityInfo.lot == 14375) //AG - SPIDER BOSS EGG
|
|
|
|
{
|
|
|
|
m_Info.groups.emplace_back("EGG");
|
|
|
|
}
|
|
|
|
|
|
|
|
int timerCount = m_Info.amountMaintained;
|
|
|
|
|
|
|
|
if (m_Info.amountMaintained > m_Info.nodes.size()) {
|
|
|
|
timerCount = m_Info.nodes.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < timerCount; ++i) {
|
|
|
|
m_WaitTimes.push_back(m_Info.respawnTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_Info.spawnOnSmashGroupName != "") {
|
|
|
|
std::vector<Entity*> spawnSmashEntities = EntityManager::Instance()->GetEntitiesInGroup(m_Info.spawnOnSmashGroupName);
|
|
|
|
std::vector<Spawner*> spawnSmashSpawners = dZoneManager::Instance()->GetSpawnersInGroup(m_Info.spawnOnSmashGroupName);
|
|
|
|
std::vector<Spawner*> spawnSmashSpawnersN = dZoneManager::Instance()->GetSpawnersByName(m_Info.spawnOnSmashGroupName);
|
|
|
|
for (Entity* ssEntity : spawnSmashEntities) {
|
|
|
|
m_SpawnSmashFoundGroup = true;
|
|
|
|
ssEntity->AddDieCallback([=]() {
|
|
|
|
Spawn();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (Spawner* ssSpawner : spawnSmashSpawners) {
|
|
|
|
m_SpawnSmashFoundGroup = true;
|
|
|
|
ssSpawner->AddSpawnedEntityDieCallback([=]() {
|
|
|
|
Spawn();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (Spawner* ssSpawner : spawnSmashSpawnersN) {
|
|
|
|
m_SpawnSmashFoundGroup = true;
|
|
|
|
m_SpawnOnSmash = ssSpawner;
|
|
|
|
ssSpawner->AddSpawnedEntityDieCallback([=]() {
|
|
|
|
Spawn();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-07-15 08:54:41 +00:00
|
|
|
|
|
|
|
m_SpawnPattern = SpawnPatterns::FindSpawnPatterns(m_Info.templateID);
|
|
|
|
|
|
|
|
m_LotsToCheck.push_back(m_Info.templateID);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Spawner::~Spawner() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Entity* Spawner::Spawn() {
|
|
|
|
std::vector<SpawnerNode*> freeNodes;
|
|
|
|
for (SpawnerNode* node : m_Info.nodes) {
|
|
|
|
if (node->entities.size() < node->nodeMax) {
|
|
|
|
freeNodes.push_back(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Spawn(freeNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
Entity* Spawner::Spawn(std::vector<SpawnerNode*> freeNodes, const bool force) {
|
|
|
|
if (force || ((m_Entities.size() < m_Info.amountMaintained) && (freeNodes.size() > 0) && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -1))) {
|
|
|
|
SpawnerNode* spawnNode = freeNodes[GeneralUtils::GenerateRandomNumber<int>(0, freeNodes.size() - 1)];
|
|
|
|
++m_AmountSpawned;
|
|
|
|
m_EntityInfo.pos = spawnNode->position;
|
|
|
|
m_EntityInfo.rot = spawnNode->rotation;
|
|
|
|
m_EntityInfo.settings = spawnNode->config;
|
|
|
|
m_EntityInfo.id = 0;
|
|
|
|
m_EntityInfo.spawner = this;
|
|
|
|
|
|
|
|
if (!m_Info.emulated) {
|
|
|
|
m_EntityInfo.spawnerNodeID = spawnNode->nodeID;
|
|
|
|
m_EntityInfo.hasSpawnerNodeID = true;
|
|
|
|
m_EntityInfo.spawnerID = m_Info.spawnerID;
|
|
|
|
}
|
|
|
|
|
2023-07-15 08:54:41 +00:00
|
|
|
bool usedSpawnPattern = false;
|
|
|
|
|
|
|
|
if (m_SpawnPattern != nullptr) {
|
|
|
|
auto pattern = m_SpawnPattern->GetSpawnPatterns();
|
|
|
|
|
|
|
|
// Check the area rating
|
|
|
|
// std::map<LOT, std::vector<std::pair<NiPoint3, float>>> m_Ratings
|
|
|
|
for (const auto& lot : m_LotsToCheck)
|
|
|
|
{
|
|
|
|
const auto& it = m_Ratings.find(lot);
|
|
|
|
|
|
|
|
int32_t rating = 0;
|
|
|
|
|
|
|
|
if (it != m_Ratings.end()) {
|
|
|
|
// Check if we are within 50units of a rating
|
|
|
|
for (const auto& ratingIt : it->second)
|
|
|
|
{
|
|
|
|
if (NiPoint3::DistanceSquared(ratingIt.first, m_EntityInfo.pos) <= 100.0f * 100.0f)
|
|
|
|
{
|
|
|
|
rating = ratingIt.second;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& it : pattern)
|
|
|
|
{
|
|
|
|
if (it.first > rating) continue;
|
|
|
|
|
|
|
|
// Random number between 0 and 1
|
|
|
|
float random = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
|
|
|
|
|
|
|
const auto& change = it.second.first;
|
|
|
|
|
|
|
|
if (random >= change) continue;
|
|
|
|
|
|
|
|
usedSpawnPattern = true;
|
|
|
|
|
|
|
|
Entity* first = nullptr;
|
|
|
|
|
|
|
|
for (const auto& spawn : it.second.second)
|
|
|
|
{
|
|
|
|
float angle = GeneralUtils::GenerateRandomNumber<float>(0, 360) * M_PI / 180.0f;
|
|
|
|
float radius = GeneralUtils::GenerateRandomNumber<float>(0, 6);
|
|
|
|
|
|
|
|
float x = radius * cos(angle);
|
|
|
|
float z = radius * sin(angle);
|
|
|
|
|
|
|
|
auto copy = m_EntityInfo;
|
|
|
|
copy.pos.x += x;
|
|
|
|
copy.pos.z += z;
|
|
|
|
copy.lot = spawn;
|
|
|
|
|
|
|
|
if (std::find(m_LotsToCheck.begin(), m_LotsToCheck.end(), spawn) == m_LotsToCheck.end()) {
|
|
|
|
m_LotsToCheck.push_back(spawn);
|
|
|
|
}
|
|
|
|
|
|
|
|
Entity* rezdE = EntityManager::Instance()->CreateEntity(copy, nullptr);
|
|
|
|
|
|
|
|
rezdE->GetGroups() = m_Info.groups;
|
|
|
|
|
|
|
|
EntityManager::Instance()->ConstructEntity(rezdE);
|
|
|
|
|
|
|
|
m_Entities.insert({ rezdE->GetObjectID(), spawnNode });
|
|
|
|
|
|
|
|
spawnNode->entities.push_back(rezdE->GetObjectID());
|
|
|
|
|
|
|
|
for (const auto& cb : m_EntitySpawnedCallbacks) {
|
|
|
|
cb(rezdE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (first == nullptr) {
|
|
|
|
first = rezdE;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
usedSpawnPattern = true;
|
|
|
|
|
|
|
|
if (m_Entities.size() == m_Info.amountMaintained) {
|
|
|
|
m_NeedsUpdate = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return first;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
Entity* rezdE = EntityManager::Instance()->CreateEntity(m_EntityInfo, nullptr);
|
|
|
|
|
|
|
|
rezdE->GetGroups() = m_Info.groups;
|
|
|
|
|
|
|
|
EntityManager::Instance()->ConstructEntity(rezdE);
|
|
|
|
|
|
|
|
m_Entities.insert({ rezdE->GetObjectID(), spawnNode });
|
|
|
|
spawnNode->entities.push_back(rezdE->GetObjectID());
|
|
|
|
if (m_Entities.size() == m_Info.amountMaintained) {
|
|
|
|
m_NeedsUpdate = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& cb : m_EntitySpawnedCallbacks) {
|
|
|
|
cb(rezdE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rezdE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::AddSpawnedEntityDieCallback(std::function<void()> callback) {
|
|
|
|
m_SpawnedEntityDieCallbacks.push_back(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::AddEntitySpawnedCallback(std::function<void(Entity*)> callback) {
|
|
|
|
m_EntitySpawnedCallbacks.push_back(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::Reset() {
|
|
|
|
m_Start = true;
|
2023-03-25 10:26:39 +00:00
|
|
|
DestroyAllEntities();
|
|
|
|
m_Entities.clear();
|
|
|
|
m_AmountSpawned = 0;
|
|
|
|
m_NeedsUpdate = true;
|
|
|
|
}
|
2022-07-28 13:39:57 +00:00
|
|
|
|
2023-03-25 10:26:39 +00:00
|
|
|
void Spawner::DestroyAllEntities(){
|
2021-12-05 17:54:36 +00:00
|
|
|
for (auto* node : m_Info.nodes) {
|
2023-03-25 10:26:39 +00:00
|
|
|
for (const auto& element : node->entities) {
|
|
|
|
auto* entity = EntityManager::Instance()->GetEntity(element);
|
2021-12-05 17:54:36 +00:00
|
|
|
if (entity == nullptr) continue;
|
|
|
|
entity->Kill();
|
|
|
|
}
|
|
|
|
node->entities.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::SoftReset() {
|
|
|
|
m_Start = true;
|
|
|
|
m_AmountSpawned = 0;
|
|
|
|
m_NeedsUpdate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::SetRespawnTime(float time) {
|
|
|
|
m_Info.respawnTime = time;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m_WaitTimes.size(); ++i) {
|
|
|
|
m_WaitTimes[i] = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
m_Start = true;
|
|
|
|
m_NeedsUpdate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::SetNumToMaintain(int32_t value) {
|
|
|
|
m_Info.amountMaintained = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::Update(const float deltaTime) {
|
|
|
|
if (m_Start && m_Active) {
|
|
|
|
m_Start = false;
|
|
|
|
|
|
|
|
const auto toSpawn = m_Info.amountMaintained - m_AmountSpawned;
|
|
|
|
for (auto i = 0; i < toSpawn; ++i) {
|
|
|
|
Spawn();
|
|
|
|
}
|
2022-07-28 13:39:57 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
m_WaitTimes.clear();
|
2022-07-28 13:39:57 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-07-28 13:39:57 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
if (!m_NeedsUpdate) return;
|
|
|
|
if (!m_Active) return;
|
|
|
|
//if (m_Info.noTimedSpawn) return;
|
|
|
|
if (m_Info.spawnsOnSmash) {
|
|
|
|
if (!m_SpawnSmashFoundGroup) {
|
|
|
|
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (size_t i = 0; i < m_WaitTimes.size(); ++i) {
|
|
|
|
m_WaitTimes[i] += deltaTime;
|
|
|
|
if (m_WaitTimes[i] >= m_Info.respawnTime) {
|
|
|
|
m_WaitTimes.erase(m_WaitTimes.begin() + i);
|
|
|
|
|
|
|
|
Spawn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
|
|
|
|
for (std::function<void()> cb : m_SpawnedEntityDieCallbacks) {
|
|
|
|
cb();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_NeedsUpdate = true;
|
|
|
|
//m_RespawnTime = 10.0f;
|
|
|
|
m_WaitTimes.push_back(0.0f);
|
|
|
|
SpawnerNode* node;
|
2022-07-28 13:39:57 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
auto it = m_Entities.find(objectID);
|
|
|
|
if (it != m_Entities.end()) node = it->second;
|
|
|
|
else return;
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < node->entities.size(); ++i) {
|
|
|
|
if (node->entities[i] && node->entities[i] == objectID)
|
|
|
|
node->entities.erase(node->entities.begin() + i);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_Entities.erase(objectID);
|
|
|
|
|
|
|
|
if (m_SpawnOnSmash != nullptr) {
|
|
|
|
m_SpawnOnSmash->Reset();
|
|
|
|
}
|
2023-07-15 08:54:41 +00:00
|
|
|
|
|
|
|
const auto& lot = m_Info.templateID;
|
|
|
|
|
|
|
|
// Add to area rating
|
|
|
|
// std::map<LOT, std::vector<std::pair<NiPoint3, float>>> m_Ratings
|
|
|
|
// First check if the lot is in the map, if not, add it
|
|
|
|
// Than check if there exist any ratings for that lot within 50units of the spawner
|
|
|
|
// If there is, add 1 to the rating
|
|
|
|
// If there isn't, add a new rating
|
|
|
|
const auto& pos = node->position;
|
|
|
|
|
|
|
|
const auto& it2 = m_Ratings.find(lot);
|
|
|
|
|
|
|
|
if (it2 == m_Ratings.end()) {
|
|
|
|
m_Ratings.insert({ lot, { { pos, 1.0f } } });
|
|
|
|
} else {
|
|
|
|
auto& ratings = it2->second;
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
for (auto& rating : ratings) {
|
|
|
|
if (NiPoint3::DistanceSquared(rating.first, pos) < 100.0f * 100.0f) {
|
|
|
|
rating.second += 1.0f;
|
|
|
|
|
|
|
|
Game::logger->Log("Spawner", "Rating %f", rating.second);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
ratings.push_back({ pos, 1.0f });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::UpdateRatings(float deltaTime) {
|
|
|
|
// Loop through all ratings and decrease them by deltaTime
|
|
|
|
for (auto& rating : m_Ratings) {
|
|
|
|
for (auto& rating2 : rating.second) {
|
|
|
|
rating2.second -= deltaTime * 0.1f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loop through all ratings and remove any that are 0 or less
|
|
|
|
for (auto it = m_Ratings.begin(); it != m_Ratings.end();) {
|
|
|
|
for (auto it2 = it->second.begin(); it2 != it->second.end();) {
|
|
|
|
if (it2->second <= 0.0f) {
|
|
|
|
it2 = it->second.erase(it2);
|
|
|
|
} else {
|
|
|
|
++it2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (it->second.empty()) {
|
|
|
|
it = m_Ratings.erase(it);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::Activate() {
|
|
|
|
m_Active = true;
|
|
|
|
m_NeedsUpdate = true;
|
|
|
|
|
|
|
|
for (auto& time : m_WaitTimes) {
|
|
|
|
time = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Spawner::SetSpawnLot(LOT lot) {
|
|
|
|
m_EntityInfo.lot = lot;
|
|
|
|
}
|