mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-20 13:44:21 +00:00
Compare commits
1 Commits
leave-team
...
shrink-ran
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
855864c536 |
@@ -105,7 +105,15 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
|||||||
auto* team = TeamContainer::GetTeam(playerID);
|
auto* team = TeamContainer::GetTeam(playerID);
|
||||||
|
|
||||||
if (team != nullptr) {
|
if (team != nullptr) {
|
||||||
TeamContainer::RemoveMember(team, playerID, false, false, true);
|
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
|
||||||
|
|
||||||
|
for (const auto memberId : team->memberIDs) {
|
||||||
|
const auto& otherMember = GetPlayerData(memberId);
|
||||||
|
|
||||||
|
if (!otherMember) continue;
|
||||||
|
|
||||||
|
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
|
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
|
||||||
|
|||||||
@@ -289,14 +289,6 @@ struct LwoNameValue {
|
|||||||
this->Erase(GeneralUtils::ASCIIToUTF16(key));
|
this->Erase(GeneralUtils::ASCIIToUTF16(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueType::iterator find(const ValueType::key_type& key) {
|
|
||||||
return this->values.find(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueType::const_iterator find(const ValueType::key_type& key) const {
|
|
||||||
return this->values.find(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
LwoNameValue() = default;
|
LwoNameValue() = default;
|
||||||
|
|
||||||
LwoNameValue(const LwoNameValue& other) {
|
LwoNameValue(const LwoNameValue& other) {
|
||||||
|
|||||||
@@ -38,11 +38,3 @@ std::vector<CDObjectSkills> CDObjectSkillsTable::Query(std::function<bool(CDObje
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CDObjectSkills> CDObjectSkillsTable::Get(const LOT lot) const {
|
|
||||||
std::vector<CDObjectSkills> toReturn;
|
|
||||||
for (const auto& entry : GetEntries()) {
|
|
||||||
if (entry.objectTemplate == lot) toReturn.push_back(entry);
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
#include "CDTable.h"
|
#include "CDTable.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct CDObjectSkills {
|
struct CDObjectSkills {
|
||||||
uint32_t objectTemplate; //!< The LOT of the item
|
uint32_t objectTemplate; //!< The LOT of the item
|
||||||
uint32_t skillID; //!< The Skill ID of the object
|
uint32_t skillID; //!< The Skill ID of the object
|
||||||
uint32_t castOnType; //!< ???
|
uint32_t castOnType; //!< ???
|
||||||
int32_t AICombatWeight; //!< ???
|
uint32_t AICombatWeight; //!< ???
|
||||||
};
|
};
|
||||||
|
|
||||||
class CDObjectSkillsTable : public CDTable<CDObjectSkillsTable, std::vector<CDObjectSkills>> {
|
class CDObjectSkillsTable : public CDTable<CDObjectSkillsTable, std::vector<CDObjectSkills>> {
|
||||||
@@ -18,6 +17,5 @@ public:
|
|||||||
void LoadValuesFromDatabase();
|
void LoadValuesFromDatabase();
|
||||||
// Queries the table with a custom "where" clause
|
// Queries the table with a custom "where" clause
|
||||||
std::vector<CDObjectSkills> Query(std::function<bool(CDObjectSkills)> predicate);
|
std::vector<CDObjectSkills> Query(std::function<bool(CDObjectSkills)> predicate);
|
||||||
std::vector<CDObjectSkills> Get(const LOT lot) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,40 +5,20 @@
|
|||||||
|
|
||||||
void NpcCombatSkillBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bit_stream, BehaviorBranchContext branch) {
|
void NpcCombatSkillBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bit_stream, BehaviorBranchContext branch) {
|
||||||
context->skillTime = this->m_npcSkillTime;
|
context->skillTime = this->m_npcSkillTime;
|
||||||
const auto* const targetEntity = Game::entityManager->GetEntity(branch.target);
|
|
||||||
const auto* const sourceEntity = Game::entityManager->GetEntity(context->caster);
|
|
||||||
|
|
||||||
bool cast = true;
|
for (auto* behavior : this->m_behaviors) {
|
||||||
// Check that the target is within the cast range
|
behavior->Calculate(context, bit_stream, branch);
|
||||||
if (targetEntity && sourceEntity && this->m_maxRange != 0.0f) {
|
|
||||||
const auto targetPos = targetEntity->GetPosition();
|
|
||||||
const auto sourcePos = sourceEntity->GetPosition();
|
|
||||||
const auto distance = NiPoint3::DistanceSquared(targetPos, sourcePos);
|
|
||||||
cast = distance >= this->m_minRange && distance <= this->m_maxRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cast) {
|
|
||||||
for (auto* behavior : this->m_behaviors) {
|
|
||||||
behavior->Calculate(context, bit_stream, branch);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We failed to find a valid target, do not continue the behavior
|
|
||||||
context->foundTarget = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcCombatSkillBehavior::Load() {
|
void NpcCombatSkillBehavior::Load() {
|
||||||
this->m_npcSkillTime = GetFloat("npc skill time");
|
this->m_npcSkillTime = GetFloat("npc skill time");
|
||||||
this->m_minRange = GetFloat("min range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements
|
|
||||||
this->m_minRange *= this->m_minRange;
|
|
||||||
this->m_maxRange = GetFloat("max range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements
|
|
||||||
this->m_maxRange *= this->m_maxRange;
|
|
||||||
|
|
||||||
const auto parameters = GetParameterNames();
|
const auto parameters = GetParameterNames();
|
||||||
|
|
||||||
for (const auto& [parameter, value] : parameters) {
|
for (const auto& parameter : parameters) {
|
||||||
if (parameter.rfind("behavior", 0) == 0) {
|
if (parameter.first.rfind("behavior", 0) == 0) {
|
||||||
auto* action = GetAction(value);
|
auto* action = GetAction(parameter.second);
|
||||||
|
|
||||||
this->m_behaviors.push_back(action);
|
this->m_behaviors.push_back(action);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ public:
|
|||||||
float m_npcSkillTime;
|
float m_npcSkillTime;
|
||||||
|
|
||||||
float m_maxRange{};
|
float m_maxRange{};
|
||||||
float m_minRange{};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Inherited
|
* Inherited
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
|
|
||||||
#include "CDClientDatabase.h"
|
#include "CDClientDatabase.h"
|
||||||
#include "CDClientManager.h"
|
#include "CDClientManager.h"
|
||||||
#include "CDObjectSkillsTable.h"
|
|
||||||
#include "CDSkillBehaviorTable.h"
|
|
||||||
#include "DestroyableComponent.h"
|
#include "DestroyableComponent.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -45,7 +43,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
|
|||||||
|
|
||||||
//Grab the aggro information from BaseCombatAI:
|
//Grab the aggro information from BaseCombatAI:
|
||||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||||
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius, minRoundLength, maxRoundLength, combatRoundLength FROM BaseCombatAIComponent WHERE id = ?;");
|
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
|
||||||
componentQuery.bind(1, static_cast<int>(componentID));
|
componentQuery.bind(1, static_cast<int>(componentID));
|
||||||
|
|
||||||
auto componentResult = componentQuery.execQuery();
|
auto componentResult = componentQuery.execQuery();
|
||||||
@@ -65,37 +63,44 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
|
|||||||
|
|
||||||
if (!componentResult.fieldIsNull("hardTetherRadius"))
|
if (!componentResult.fieldIsNull("hardTetherRadius"))
|
||||||
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
|
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
|
||||||
|
|
||||||
m_MinRoundLength = componentResult.getFloatField("minRoundLength");
|
|
||||||
m_MaxRoundLength = componentResult.getFloatField("maxRoundLength");
|
|
||||||
m_CombatRoundLength = componentResult.getFloatField("combatRoundLength");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentResult.finalize();
|
||||||
|
|
||||||
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
|
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
|
||||||
// radii if it is greater than the one in the database.
|
// radii if it is greater than the one in the database.
|
||||||
m_AggroRadius = m_Parent->HasVar(u"aggroRadius") ? m_Parent->GetVar<float>(u"aggroRadius") : m_AggroRadius;
|
if (m_Parent) {
|
||||||
m_HardTetherRadius = m_Parent->HasVar(u"tetherRadius") ? m_Parent->GetVar<float>(u"tetherRadius") : m_HardTetherRadius;
|
auto aggroRadius = m_Parent->GetVar<float>(u"aggroRadius");
|
||||||
|
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
|
||||||
|
auto tetherRadius = m_Parent->GetVar<float>(u"tetherRadius");
|
||||||
|
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find skills
|
* Find skills
|
||||||
*/
|
*/
|
||||||
for (const auto objectSkill : CDClientManager::GetTable<CDObjectSkillsTable>()->Get(parent->GetLOT())) {
|
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
|
||||||
const auto skillBehavior = CDClientManager::GetTable<CDSkillBehaviorTable>()->GetSkillByID(objectSkill.skillID);
|
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
|
||||||
if (skillBehavior.skillID == objectSkill.skillID) {
|
skillQuery.bind(1, static_cast<int>(parent->GetLOT()));
|
||||||
const auto skillId = skillBehavior.skillID;
|
|
||||||
|
|
||||||
const auto abilityCooldown = skillBehavior.cooldown;
|
auto result = skillQuery.execQuery();
|
||||||
|
|
||||||
const auto behaviorId = skillBehavior.behaviorID;
|
while (!result.eof()) {
|
||||||
|
const auto skillId = static_cast<uint32_t>(result.getIntField("skillID"));
|
||||||
|
|
||||||
const auto combatWeight = objectSkill.AICombatWeight;
|
const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown"));
|
||||||
|
|
||||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
|
||||||
|
|
||||||
AiSkillEntry entry = { .skillId = skillId, .cooldown = 0.0f, .abilityCooldown = abilityCooldown, .behavior = behavior, .combatWeight = combatWeight };
|
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||||
|
|
||||||
m_SkillEntries.push_back(entry);
|
std::stringstream behaviorQuery;
|
||||||
}
|
|
||||||
|
AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior };
|
||||||
|
|
||||||
|
m_SkillEntries.push_back(entry);
|
||||||
|
|
||||||
|
result.nextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stun(1.0f);
|
Stun(1.0f);
|
||||||
@@ -205,10 +210,8 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stunnedThisFrame) {
|
if (stunnedThisFrame) {
|
||||||
if (!m_MovementAI->IsPaused()) m_MovementAI->Pause();
|
m_MovementAI->Stop();
|
||||||
|
|
||||||
// in this case we just become unstunned so check if we paused and resume if we did
|
|
||||||
if (!m_Stunned && m_MovementAI->IsPaused()) m_MovementAI->Resume();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,12 +246,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
|||||||
|
|
||||||
void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||||
bool hasSkillToCast = false;
|
bool hasSkillToCast = false;
|
||||||
int32_t maxSkillWeights = 0;
|
|
||||||
for (auto& entry : m_SkillEntries) {
|
for (auto& entry : m_SkillEntries) {
|
||||||
if (entry.cooldown > 0.0f) {
|
if (entry.cooldown > 0.0f) {
|
||||||
entry.cooldown -= deltaTime;
|
entry.cooldown -= deltaTime;
|
||||||
} else {
|
} else {
|
||||||
maxSkillWeights += entry.combatWeight;
|
|
||||||
hasSkillToCast = true;
|
hasSkillToCast = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,14 +317,12 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
SetAiState(AiState::aggro);
|
SetAiState(AiState::aggro);
|
||||||
} else {
|
} else {
|
||||||
SetAiState(AiState::idle);
|
SetAiState(AiState::idle);
|
||||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasSkillToCast) return;
|
if (!hasSkillToCast) return;
|
||||||
|
|
||||||
if (m_Target == LWOOBJID_EMPTY) {
|
if (m_Target == LWOOBJID_EMPTY) {
|
||||||
SetAiState(AiState::idle);
|
SetAiState(AiState::idle);
|
||||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -334,23 +333,14 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
LookAt(target->GetPosition());
|
LookAt(target->GetPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roll to find which skill we'll try to cast
|
for (auto i = 0; i < m_SkillEntries.size(); ++i) {
|
||||||
auto randomizedWeight = GeneralUtils::GenerateRandomNumber<int32_t>(0, maxSkillWeights);
|
auto entry = m_SkillEntries.at(i);
|
||||||
|
|
||||||
for (auto& entry : m_SkillEntries) {
|
if (entry.cooldown > 0) {
|
||||||
// Skill isn't cooled off yet
|
|
||||||
if (entry.cooldown > 0.0f) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomizedWeight -= entry.combatWeight;
|
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY);
|
||||||
|
|
||||||
// if the weight is still greater than 0 continue to the next rolled skill
|
|
||||||
if (randomizedWeight > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget());
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (m_MovementAI != nullptr) {
|
if (m_MovementAI != nullptr) {
|
||||||
@@ -365,6 +355,8 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
|
|
||||||
entry.cooldown = entry.abilityCooldown + m_SkillTime;
|
entry.cooldown = entry.abilityCooldown + m_SkillTime;
|
||||||
|
|
||||||
|
m_SkillEntries[i] = entry;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,11 +618,6 @@ void BaseCombatAIComponent::Wander() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a path to follow we should almost certainly do that instead of wandering.
|
|
||||||
if (m_MovementAI->HasPath()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_MovementAI->SetHaltDistance(0);
|
m_MovementAI->SetHaltDistance(0);
|
||||||
|
|
||||||
const auto& info = m_MovementAI->GetInfo();
|
const auto& info = m_MovementAI->GetInfo();
|
||||||
@@ -759,8 +746,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
|
|||||||
m_TetherSpeed = value;
|
m_TetherSpeed = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseCombatAIComponent::Stun(const float time, const bool force) {
|
void BaseCombatAIComponent::Stun(const float time) {
|
||||||
if (!force && (m_StunImmune || m_StunTime > time)) {
|
if (m_StunImmune || m_StunTime > time) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,12 +862,12 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
|
|||||||
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
||||||
std::string curState;
|
std::string curState;
|
||||||
switch (m_State) {
|
switch (m_State) {
|
||||||
case idle: curState = "Idling"; break;
|
case idle: curState = "Idling"; break;
|
||||||
case aggro: curState = "Aggroed"; break;
|
case aggro: curState = "Aggroed"; break;
|
||||||
case tether: curState = "Returning to Tether"; break;
|
case tether: curState = "Returning to Tether"; break;
|
||||||
case spawn: curState = "Spawn"; break;
|
case spawn: curState = "Spawn"; break;
|
||||||
case dead: curState = "Dead"; break;
|
case dead: curState = "Dead"; break;
|
||||||
default: curState = "Unknown or Undefined"; break;
|
default: curState = "Unknown or Undefined"; break;
|
||||||
}
|
}
|
||||||
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
||||||
|
|
||||||
@@ -918,16 +905,8 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
|
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
|
||||||
for (const auto& [id, threat] : m_RemovedThreatList) {
|
for (const auto& [id, threat] : m_ThreatEntries) {
|
||||||
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
|
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
|
||||||
}
|
}
|
||||||
auto& skillInfo = cmptType.PushDebug("Skill Info");
|
|
||||||
for (const auto& skill : m_SkillEntries) {
|
|
||||||
auto& skillDebug = skillInfo.PushDebug("Skill ID " + std::to_string(skill.skillId));
|
|
||||||
skillDebug.PushDebug<AMFDoubleValue>("Cooldown") = skill.cooldown;
|
|
||||||
skillDebug.PushDebug<AMFDoubleValue>("Ability Cooldown") = skill.abilityCooldown;
|
|
||||||
skillDebug.PushDebug<AMFIntValue>("AI Combat Weight") = skill.combatWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,13 @@ enum class AiState : uint32_t {
|
|||||||
*/
|
*/
|
||||||
struct AiSkillEntry
|
struct AiSkillEntry
|
||||||
{
|
{
|
||||||
uint32_t skillId{};
|
uint32_t skillId;
|
||||||
|
|
||||||
float cooldown{};
|
float cooldown;
|
||||||
|
|
||||||
float abilityCooldown{};
|
float abilityCooldown;
|
||||||
|
|
||||||
Behavior* behavior{};
|
Behavior* behavior;
|
||||||
|
|
||||||
int32_t combatWeight{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,9 +181,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
|
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
|
||||||
* @param time the time to stun the entity, if stunnable
|
* @param time the time to stun the entity, if stunnable
|
||||||
* @param force whether or not to force the stun and ignore checks
|
|
||||||
*/
|
*/
|
||||||
void Stun(float time, const bool force = false);
|
void Stun(float time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
|
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
|
||||||
@@ -239,8 +236,6 @@ public:
|
|||||||
|
|
||||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||||
|
|
||||||
void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Returns the current target or the target that currently is the largest threat to this entity
|
* Returns the current target or the target that currently is the largest threat to this entity
|
||||||
@@ -399,17 +394,9 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool m_DirtyStateOrTarget = false;
|
bool m_DirtyStateOrTarget = false;
|
||||||
|
|
||||||
// Min amount of time to remain as in combat after casting a skill
|
|
||||||
float m_MinRoundLength = 0.0f;
|
|
||||||
|
|
||||||
// max amount of time to remain as in combat after casting a skill
|
|
||||||
float m_MaxRoundLength = 0.0f;
|
|
||||||
|
|
||||||
// The amount of time the entity will be forced to tether for
|
// The amount of time the entity will be forced to tether for
|
||||||
float m_ForcedTetherTime = 0.0f;
|
float m_ForcedTetherTime = 0.0f;
|
||||||
|
|
||||||
float m_CombatRoundLength = 0.0f;
|
|
||||||
|
|
||||||
// The amount of time a removed threat will be ignored for.
|
// The amount of time a removed threat will be ignored for.
|
||||||
std::map<LWOOBJID, float> m_RemovedThreatList;
|
std::map<LWOOBJID, float> m_RemovedThreatList;
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,6 @@
|
|||||||
#include "Amf3.h"
|
#include "Amf3.h"
|
||||||
|
|
||||||
#include "dNavMesh.h"
|
#include "dNavMesh.h"
|
||||||
#include "eWaypointCommandType.h"
|
|
||||||
#include "StringifiedEnum.h"
|
|
||||||
#include "SkillComponent.h"
|
|
||||||
#include "GeneralUtils.h"
|
|
||||||
#include "RenderComponent.h"
|
|
||||||
#include "InventoryComponent.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +31,8 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
|
|||||||
m_Info = info;
|
m_Info = info;
|
||||||
m_AtFinalWaypoint = true;
|
m_AtFinalWaypoint = true;
|
||||||
|
|
||||||
|
m_BaseCombatAI = nullptr;
|
||||||
|
|
||||||
m_BaseCombatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
|
m_BaseCombatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||||
|
|
||||||
//Try and fix the insane values:
|
//Try and fix the insane values:
|
||||||
@@ -64,7 +60,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
|
|||||||
|
|
||||||
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
|
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
|
||||||
|
|
||||||
SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovementAIComponent::SetPath(const std::string pathName) {
|
void MovementAIComponent::SetPath(const std::string pathName) {
|
||||||
@@ -129,11 +125,7 @@ void MovementAIComponent::Update(const float deltaTime) {
|
|||||||
|
|
||||||
m_TimeTravelled += deltaTime;
|
m_TimeTravelled += deltaTime;
|
||||||
|
|
||||||
const auto approxPos = ApproximateLocation();
|
SetPosition(ApproximateLocation());
|
||||||
SetPosition(approxPos);
|
|
||||||
// Set the AIs new home based on where our current waypoint is IF we're idle, that way we can return to this
|
|
||||||
// when resuming the pathing after losing aggro while moving the aggro hitbox with us
|
|
||||||
if (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle) m_BaseCombatAI->SetStartingPosition(approxPos);
|
|
||||||
|
|
||||||
if (m_TimeTravelled < m_TimeToTravel) return;
|
if (m_TimeTravelled < m_TimeToTravel) return;
|
||||||
m_TimeTravelled = 0.0f;
|
m_TimeTravelled = 0.0f;
|
||||||
@@ -168,34 +160,32 @@ void MovementAIComponent::Update(const float deltaTime) {
|
|||||||
SetRotation(QuatUtils::LookAt(source, m_NextWaypoint));
|
SetRotation(QuatUtils::LookAt(source, m_NextWaypoint));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only try to renew or continue the path if we're in the idle or spawn state and we actually have a combatAI component
|
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
||||||
if (!m_BaseCombatAI || (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle)) {
|
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
||||||
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
if (m_CurrentPath.empty()) {
|
||||||
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
if (m_Path) {
|
||||||
RunWaypointCommands(waypointNum);
|
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
||||||
if (m_CurrentPath.empty()) {
|
SetPath(m_Path->pathWaypoints);
|
||||||
if (m_Path) {
|
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
|
||||||
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
m_IsBounced = !m_IsBounced;
|
||||||
SetPath(m_Path->pathWaypoints);
|
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
|
||||||
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
|
if (m_IsBounced) std::ranges::reverse(waypoints);
|
||||||
m_IsBounced = !m_IsBounced;
|
SetPath(waypoints);
|
||||||
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
|
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
||||||
if (m_IsBounced) std::ranges::reverse(waypoints);
|
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||||
SetPath(waypoints);
|
|
||||||
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
|
||||||
// In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again.
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Stop();
|
Stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SetDestination(m_CurrentPath.top().position);
|
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||||
|
Stop();
|
||||||
m_CurrentPath.pop();
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||||
|
SetDestination(m_CurrentPath.top().position);
|
||||||
|
|
||||||
|
m_CurrentPath.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +212,8 @@ NiPoint3 MovementAIComponent::GetCurrentWaypoint() const {
|
|||||||
|
|
||||||
NiPoint3 MovementAIComponent::ApproximateLocation() const {
|
NiPoint3 MovementAIComponent::ApproximateLocation() const {
|
||||||
auto source = m_SourcePosition;
|
auto source = m_SourcePosition;
|
||||||
if (AtFinalWaypoint()) return m_Parent->GetPosition();
|
|
||||||
|
if (AtFinalWaypoint()) return source;
|
||||||
|
|
||||||
auto destination = m_NextWaypoint;
|
auto destination = m_NextWaypoint;
|
||||||
|
|
||||||
@@ -432,69 +423,7 @@ NiPoint3 MovementAIComponent::GetDestination() const {
|
|||||||
void MovementAIComponent::SetMaxSpeed(const float value) {
|
void MovementAIComponent::SetMaxSpeed(const float value) {
|
||||||
if (value == m_MaxSpeed) return;
|
if (value == m_MaxSpeed) return;
|
||||||
m_MaxSpeed = value;
|
m_MaxSpeed = value;
|
||||||
m_Acceleration = value / 5.0f;
|
m_Acceleration = value / 5;
|
||||||
}
|
|
||||||
|
|
||||||
void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) {
|
|
||||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
|
||||||
|
|
||||||
if (!m_Path || waypointNum >= m_Path->pathWaypoints.size()) return;
|
|
||||||
const auto& commands = m_Path->pathWaypoints[waypointNum].commands;
|
|
||||||
for (const auto& [command, data] : commands) {
|
|
||||||
LOG_DEBUG("%s %s %s", StringifiedEnum::ToString(command).data(), m_Path->pathName.c_str(), data.c_str());
|
|
||||||
const auto dataSplit = GeneralUtils::SplitString(data, ',');
|
|
||||||
switch (command) {
|
|
||||||
case eWaypointCommandType::INVALID: break;
|
|
||||||
case eWaypointCommandType::BOUNCE: break;
|
|
||||||
case eWaypointCommandType::STOP: Pause(); break;
|
|
||||||
case eWaypointCommandType::GROUP_EMOTE: break;
|
|
||||||
case eWaypointCommandType::SET_VARIABLE: break; // Empty in the client
|
|
||||||
case eWaypointCommandType::CAST_SKILL: {
|
|
||||||
const auto skill = GeneralUtils::TryParse<uint32_t>(data);
|
|
||||||
if (skill) {
|
|
||||||
auto* const skillComponent = m_Parent->GetComponent<SkillComponent>();
|
|
||||||
if (skillComponent) skillComponent->CastSkill(skill.value());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eWaypointCommandType::EQUIP_INVENTORY: {
|
|
||||||
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
|
||||||
if (inventoryComponent) {
|
|
||||||
// items should always exist
|
|
||||||
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
|
||||||
if (item) inventoryComponent->EquipItem(item);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eWaypointCommandType::UNEQUIP_INVENTORY: {
|
|
||||||
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
|
||||||
if (inventoryComponent) {
|
|
||||||
// items should always exist
|
|
||||||
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
|
||||||
if (item) inventoryComponent->UnEquipItem(item);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eWaypointCommandType::DELAY: {
|
|
||||||
// Pause(GeneralUtils::TryParse<float>(data).value_or(0.0f));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eWaypointCommandType::EMOTE: {
|
|
||||||
// m_Delay = RenderComponent::GetAnimationTime(m_Parent, data);
|
|
||||||
// const auto emoteID = GeneralUtils::TryParse<uint32_t>(data);
|
|
||||||
// if (emoteID) GameMessages::SendPlayEmote(m_Parent->GetObjectID(), emoteID.value(), LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eWaypointCommandType::TELEPORT: break;
|
|
||||||
case eWaypointCommandType::PATH_SPEED: m_BaseSpeed = GetBaseSpeed(m_Parent->GetLOT()) * GeneralUtils::TryParse<float>(data).value_or(1.0f); break;
|
|
||||||
case eWaypointCommandType::REMOVE_NPC: break;
|
|
||||||
case eWaypointCommandType::CHANGE_WAYPOINT: SetPath(dataSplit[0]); break;
|
|
||||||
case eWaypointCommandType::DELETE_SELF: break;
|
|
||||||
case eWaypointCommandType::KILL_SELF: m_Parent->Smash(); break;
|
|
||||||
case eWaypointCommandType::SPAWN_OBJECT: break;
|
|
||||||
case eWaypointCommandType::PLAY_SOUND: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||||
@@ -524,7 +453,6 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
|
|||||||
movementInfo.PushDebug<AMFBoolValue>("Lock Rotation") = m_LockRotation;
|
movementInfo.PushDebug<AMFBoolValue>("Lock Rotation") = m_LockRotation;
|
||||||
movementInfo.PushDebug<AMFBoolValue>("Paused") = m_Paused;
|
movementInfo.PushDebug<AMFBoolValue>("Paused") = m_Paused;
|
||||||
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
|
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
|
||||||
movementInfo.PushDebug<AMFBoolValue>("At Final Waypoint") = m_AtFinalWaypoint;
|
|
||||||
|
|
||||||
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
|
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
|
||||||
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
|
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
|
||||||
|
|||||||
@@ -212,16 +212,8 @@ public:
|
|||||||
bool IsPaused() const { return m_Paused; }
|
bool IsPaused() const { return m_Paused; }
|
||||||
|
|
||||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||||
|
|
||||||
bool HasPath() const { return m_Path != nullptr; }
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief
|
|
||||||
* Runs the commands on a waypoint if a path exists
|
|
||||||
*/
|
|
||||||
void RunWaypointCommands(uint32_t waypointNum);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current position of the entity
|
* Sets the current position of the entity
|
||||||
* @param value the position to set
|
* @param value the position to set
|
||||||
|
|||||||
@@ -962,12 +962,5 @@ namespace GameMessages {
|
|||||||
|
|
||||||
LWOOBJID childID{};
|
LWOOBJID childID{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ObjectLoaded : public GameMsg {
|
|
||||||
ObjectLoaded() : GameMsg(MessageType::Game::OBJECT_LOADED) {}
|
|
||||||
|
|
||||||
LWOOBJID objectID{};
|
|
||||||
LOT lot{};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
#endif // GAMEMESSAGES_H
|
#endif // GAMEMESSAGES_H
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
set(DSCRIPTS_SOURCES_02_SERVER_DLU
|
set(DSCRIPTS_SOURCES_02_SERVER_DLU
|
||||||
"DLUVanityTeleportingObject.cpp"
|
"DLUVanityTeleportingObject.cpp"
|
||||||
"RegisterWithZoneControl.cpp"
|
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
#include "RegisterWithZoneControl.h"
|
|
||||||
|
|
||||||
#include "Entity.h"
|
|
||||||
#include "EntityManager.h"
|
|
||||||
#include "GameMessages.h"
|
|
||||||
|
|
||||||
void RegisterWithZoneControl::OnStartup(Entity* self) {
|
|
||||||
GameMessages::ObjectLoaded objLoaded;
|
|
||||||
objLoaded.objectID = self->GetObjectID();
|
|
||||||
objLoaded.lot = self->GetLOT();
|
|
||||||
objLoaded.Send(Game::entityManager->GetZoneControlEntity()->GetObjectID());
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// Darkflame Universe
|
|
||||||
// Copyright 2026
|
|
||||||
|
|
||||||
#ifndef REGISTERWITHZONECONTROL_H
|
|
||||||
#define REGISTERWITHZONECONTROL_H
|
|
||||||
|
|
||||||
#include "CppScripts.h"
|
|
||||||
|
|
||||||
class RegisterWithZoneControl : public CppScripts::Script {
|
|
||||||
public:
|
|
||||||
void OnStartup(Entity* self) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //!REGISTERWITHZONECONTROL_H
|
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
#include "RenderComponent.h"
|
#include "RenderComponent.h"
|
||||||
#include "PlayerManager.h"
|
#include "PlayerManager.h"
|
||||||
#include "eStateChangeType.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -49,30 +48,10 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) {
|
|||||||
combat->SetStunImmune(true);
|
combat->SetStunImmune(true);
|
||||||
|
|
||||||
m_CurrentBossStage = 1;
|
m_CurrentBossStage = 1;
|
||||||
ToggleAttacking(*self, false);
|
|
||||||
self->SetProximityRadius(65.0f, "AggroRadius");
|
|
||||||
// Obtain faction and collision group to save for subsequent resets
|
// Obtain faction and collision group to save for subsequent resets
|
||||||
}
|
}
|
||||||
|
|
||||||
void BossSpiderQueenEnemyServer::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) {
|
|
||||||
if (name != "AggroRadius" || !entering || !entering->IsPlayer()) return;
|
|
||||||
|
|
||||||
auto playerCount = self->GetVar<int32_t>(u"player_count");
|
|
||||||
|
|
||||||
if (status == "ENTER") {
|
|
||||||
if (playerCount == 0) {
|
|
||||||
ToggleAttacking(*self, true);
|
|
||||||
}
|
|
||||||
playerCount++;
|
|
||||||
} else if (status == "LEAVE") {
|
|
||||||
playerCount--;
|
|
||||||
if (playerCount == 0) {
|
|
||||||
ToggleAttacking(*self, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self->SetVar<int32_t>(u"player_count", playerCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) {
|
void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) {
|
||||||
if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) {
|
if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) {
|
||||||
for (const auto& player : PlayerManager::GetAllPlayers()) {
|
for (const auto& player : PlayerManager::GetAllPlayers()) {
|
||||||
@@ -92,7 +71,6 @@ void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) {
|
|||||||
self->SetPosition({ 10000, 0, 10000 });
|
self->SetPosition({ 10000, 0, 10000 });
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(self);
|
Game::entityManager->SerializeEntity(self);
|
||||||
ToggleAttacking(*self, false);
|
|
||||||
|
|
||||||
controller->OnFireEventServerSide(self, "ClearProperty");
|
controller->OnFireEventServerSide(self, "ClearProperty");
|
||||||
}
|
}
|
||||||
@@ -656,19 +634,3 @@ float BossSpiderQueenEnemyServer::PlayAnimAndReturnTime(Entity* self, const std:
|
|||||||
|
|
||||||
return animTimer;
|
return animTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BossSpiderQueenEnemyServer::ToggleAttacking(Entity& self, bool on) {
|
|
||||||
const auto stoppedFlag = self.GetVarAs<bool>(u"stoppedFlag");
|
|
||||||
|
|
||||||
if (!on) {
|
|
||||||
if (stoppedFlag) return;
|
|
||||||
|
|
||||||
self.SetVar(u"stoppedFlag", true);
|
|
||||||
combat->Stun(100000.0f, true); // forcibly stun so we stop attacking people trying to put on armor
|
|
||||||
} else {
|
|
||||||
if (!stoppedFlag) return;
|
|
||||||
|
|
||||||
self.SetVar(u"stoppedFlag", false);
|
|
||||||
combat->Stun(0.0f, true); // forcibly turn off the stun we put on above
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -46,10 +46,7 @@ public:
|
|||||||
|
|
||||||
void OnTimerDone(Entity* self, std::string timerName) override;
|
void OnTimerDone(Entity* self, std::string timerName) override;
|
||||||
|
|
||||||
void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ToggleAttacking(Entity& self, bool on);
|
|
||||||
//Regular variables:
|
//Regular variables:
|
||||||
DestroyableComponent* destroyable = nullptr;
|
DestroyableComponent* destroyable = nullptr;
|
||||||
ControllablePhysicsComponent* controllable = nullptr;
|
ControllablePhysicsComponent* controllable = nullptr;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ void VisToggleNotifierServer::OnMissionDialogueOK(Entity* self, Entity* target,
|
|||||||
auto spawners = Game::zoneManager->GetSpawnersByName(itr->second);
|
auto spawners = Game::zoneManager->GetSpawnersByName(itr->second);
|
||||||
if (spawners.empty()) return;
|
if (spawners.empty()) return;
|
||||||
for (const auto spawner : spawners) {
|
for (const auto spawner : spawners) {
|
||||||
const auto& spawnedObjIds = spawner->GetSpawnedObjectIDs();
|
auto spawnedObjIds = spawner->GetSpawnedObjectIDs();
|
||||||
for (const auto& objId : spawnedObjIds) {
|
for (const auto& objId : spawnedObjIds) {
|
||||||
GameMessages::SendNotifyClientObject(objId, u"SetVisibility", visible);
|
GameMessages::SendNotifyClientObject(objId, u"SetVisibility", visible);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ void ResetMissions(Entity& user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OldManNPC::OnUse(Entity* self, Entity* user) {
|
void OldManNPC::OnUse(Entity* self, Entity* user) {
|
||||||
|
LOG("");
|
||||||
const auto* const missionComponent = user->GetComponent<MissionComponent>();
|
const auto* const missionComponent = user->GetComponent<MissionComponent>();
|
||||||
if (!missionComponent) return;
|
if (!missionComponent) return;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ void OldManNPC::OnUse(Entity* self, Entity* user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto missionState = mission->GetMissionState();
|
const auto missionState = mission->GetMissionState();
|
||||||
|
LOG("mission state %i", missionState);
|
||||||
if (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE) {
|
if (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE) {
|
||||||
ResetMissions(*user);
|
ResetMissions(*user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,8 +339,6 @@
|
|||||||
#include "ImaginationBackPack.h"
|
#include "ImaginationBackPack.h"
|
||||||
#include "NsWinterRaceServer.h"
|
#include "NsWinterRaceServer.h"
|
||||||
|
|
||||||
#include "RegisterWithZoneControl.h"
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -665,7 +663,6 @@ namespace {
|
|||||||
|
|
||||||
//WBL
|
//WBL
|
||||||
{"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}},
|
{"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}},
|
||||||
{"scripts\\zone\\LUPs\\Moonbase Intro\\MOONBASE-INTRO_INTRO_CINEMATIC.lua", []() {return new WblGenericZone();}},
|
|
||||||
|
|
||||||
//Alpha
|
//Alpha
|
||||||
{"scripts\\ai\\FV\\L_TRIGGER_GAS.lua", []() {return new TriggerGas();}},
|
{"scripts\\ai\\FV\\L_TRIGGER_GAS.lua", []() {return new TriggerGas();}},
|
||||||
@@ -711,8 +708,7 @@ namespace {
|
|||||||
{"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}},
|
{"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}},
|
||||||
{"scripts\\equipmenttriggers\\L_TRIAL_FACTION_ARMOR_SERVER.lua", []() {return new TrialFactionArmorServer();}},
|
{"scripts\\equipmenttriggers\\L_TRIAL_FACTION_ARMOR_SERVER.lua", []() {return new TrialFactionArmorServer();}},
|
||||||
{"scripts\\equipmenttriggers\\ImaginationBackPack.lua", []() {return new ImaginationBackPack();}},
|
{"scripts\\equipmenttriggers\\ImaginationBackPack.lua", []() {return new ImaginationBackPack();}},
|
||||||
{"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON_INSTANCE_ACTOR.lua", [](){return new RegisterWithZoneControl();}},
|
|
||||||
{"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON_INSTANCE_EFFECT.lua", [](){return new RegisterWithZoneControl();}},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::set<std::string> g_ExcludedScripts = {
|
std::set<std::string> g_ExcludedScripts = {
|
||||||
@@ -736,11 +732,6 @@ namespace {
|
|||||||
"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_InfectedCitizen.lua",
|
"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_InfectedCitizen.lua",
|
||||||
"scripts\\ai\\MINIGAME\\SIEGE\\OBJECTS\\ATTACKER_BOUNCER_SERVER.lua",
|
"scripts\\ai\\MINIGAME\\SIEGE\\OBJECTS\\ATTACKER_BOUNCER_SERVER.lua",
|
||||||
"scripts\\ai\\AG\\L_AG_ZONE_PLAYER.lua",
|
"scripts\\ai\\AG\\L_AG_ZONE_PLAYER.lua",
|
||||||
"scripts\\ai\\GENERAL\\L_NPC_GENERIC_MOVEMENT.lua", // Really old alpha script
|
|
||||||
"scripts\\zone\\LUPs\\DeepFreeze Intro\\WBL_Enemy_Beaver.lua", // Really old alpha script
|
|
||||||
"scripts\\ai\\GENERAL\\L_NPC_GENERIC_WANDER_SMALL.lua", // Really old alpha script
|
|
||||||
"scripts\\ai\\NP\\L_NPC_NP_OLD_MAN_SHERLAND.lua", // This NPC doesn't even exist in modern crux, the only place this is used...
|
|
||||||
"scripts\\02_server\\Map\\General\\L_SIMPLE_MOVER_SWITCH.lua", // This platform does not exist even when moved manually on a client
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -754,8 +745,7 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin
|
|||||||
Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn;
|
Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn;
|
||||||
|
|
||||||
if (script == &InvalidToReturn && !scriptName.empty() && !g_ExcludedScripts.contains(scriptName)) {
|
if (script == &InvalidToReturn && !scriptName.empty() && !g_ExcludedScripts.contains(scriptName)) {
|
||||||
const auto [x, y, z] = parent->GetPosition();
|
LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str());
|
||||||
LOG_DEBUG("LOT %i at %f %f %f attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), x, y, z, scriptName.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_Scripts[scriptName] = script;
|
g_Scripts[scriptName] = script;
|
||||||
|
|||||||
@@ -1019,7 +1019,6 @@ void HandlePacket(Packet* packet) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
Character* c = user->GetLastUsedChar();
|
Character* c = user->GetLastUsedChar();
|
||||||
if (c != nullptr) {
|
if (c != nullptr) {
|
||||||
if (Game::entityManager->GetEntity(c->GetObjectID())) return;
|
|
||||||
std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName());
|
std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName());
|
||||||
Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress);
|
Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include "GeneralUtils.h"
|
#include "GeneralUtils.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include <algorithm>
|
|
||||||
#include <ranges>
|
|
||||||
|
|
||||||
Spawner::Spawner(const SpawnerInfo& info) {
|
Spawner::Spawner(const SpawnerInfo info) {
|
||||||
m_Info = info;
|
m_Info = info;
|
||||||
m_Active = m_Info.activeOnLoad && info.spawnActivator;
|
m_Active = m_Info.activeOnLoad && info.spawnActivator;
|
||||||
m_EntityInfo = EntityInfo();
|
m_EntityInfo = EntityInfo();
|
||||||
@@ -64,6 +62,10 @@ Spawner::Spawner(const SpawnerInfo& info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spawner::~Spawner() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Entity* Spawner::Spawn() {
|
Entity* Spawner::Spawn() {
|
||||||
std::vector<SpawnerNode*> freeNodes;
|
std::vector<SpawnerNode*> freeNodes;
|
||||||
for (SpawnerNode* node : m_Info.nodes) {
|
for (SpawnerNode* node : m_Info.nodes) {
|
||||||
@@ -75,25 +77,9 @@ Entity* Spawner::Spawn() {
|
|||||||
return Spawn(freeNodes);
|
return Spawn(freeNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity* Spawner::Spawn(const std::vector<SpawnerNode*>& freeNodes, const bool force) {
|
Entity* Spawner::Spawn(std::vector<SpawnerNode*> freeNodes, const bool force) {
|
||||||
Entity* spawnedEntity = nullptr;
|
if (force || ((m_Entities.size() < m_Info.amountMaintained) && (freeNodes.size() > 0) && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -1))) {
|
||||||
if (force || ((m_Entities.size() < m_Info.amountMaintained) && !freeNodes.empty() && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -1))) {
|
SpawnerNode* spawnNode = freeNodes[GeneralUtils::GenerateRandomNumber<int>(0, freeNodes.size() - 1)];
|
||||||
// first sum the weights we were provided
|
|
||||||
int32_t spawnWeight = 0;
|
|
||||||
for (const auto* const node : freeNodes) spawnWeight += node->weight;
|
|
||||||
auto chosenWeight = GeneralUtils::GenerateRandomNumber<int32_t>(1, spawnWeight);
|
|
||||||
|
|
||||||
// Default to 0 incase something goes wrong in this calc
|
|
||||||
// Roll the spawner nodes based on their weights, higher weights = more likely to spawn
|
|
||||||
SpawnerNode* spawnNode = freeNodes[0];
|
|
||||||
for (auto* const node : freeNodes) {
|
|
||||||
chosenWeight -= node->weight;
|
|
||||||
if (chosenWeight <= 0) {
|
|
||||||
spawnNode = node;
|
|
||||||
break; // we rolled a spawner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++m_AmountSpawned;
|
++m_AmountSpawned;
|
||||||
m_EntityInfo.pos = spawnNode->position;
|
m_EntityInfo.pos = spawnNode->position;
|
||||||
m_EntityInfo.rot = spawnNode->rotation;
|
m_EntityInfo.rot = spawnNode->rotation;
|
||||||
@@ -107,24 +93,26 @@ Entity* Spawner::Spawn(const std::vector<SpawnerNode*>& freeNodes, const bool fo
|
|||||||
m_EntityInfo.spawnerID = m_Info.spawnerID;
|
m_EntityInfo.spawnerID = m_Info.spawnerID;
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnedEntity = Game::entityManager->CreateEntity(m_EntityInfo, nullptr);
|
Entity* rezdE = Game::entityManager->CreateEntity(m_EntityInfo, nullptr);
|
||||||
|
|
||||||
spawnedEntity->GetGroups() = m_Info.groups;
|
rezdE->GetGroups() = m_Info.groups;
|
||||||
|
|
||||||
Game::entityManager->ConstructEntity(spawnedEntity);
|
Game::entityManager->ConstructEntity(rezdE);
|
||||||
|
|
||||||
m_Entities[spawnedEntity->GetObjectID()] = spawnNode;
|
m_Entities.insert({ rezdE->GetObjectID(), spawnNode });
|
||||||
spawnNode->entities.push_back(spawnedEntity->GetObjectID());
|
spawnNode->entities.push_back(rezdE->GetObjectID());
|
||||||
if (m_Entities.size() == m_Info.amountMaintained) {
|
if (m_Entities.size() == m_Info.amountMaintained) {
|
||||||
m_NeedsUpdate = false;
|
m_NeedsUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& cb : m_EntitySpawnedCallbacks) {
|
for (const auto& cb : m_EntitySpawnedCallbacks) {
|
||||||
cb(spawnedEntity);
|
cb(rezdE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rezdE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return spawnedEntity;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawner::AddSpawnedEntityDieCallback(std::function<void()> callback) {
|
void Spawner::AddSpawnedEntityDieCallback(std::function<void()> callback) {
|
||||||
@@ -160,18 +148,18 @@ void Spawner::SoftReset() {
|
|||||||
m_NeedsUpdate = true;
|
m_NeedsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawner::SetRespawnTime(const float time) {
|
void Spawner::SetRespawnTime(float time) {
|
||||||
m_Info.respawnTime = time;
|
m_Info.respawnTime = time;
|
||||||
|
|
||||||
for (size_t i = 0; i < m_WaitTimes.size(); ++i) {
|
for (size_t i = 0; i < m_WaitTimes.size(); ++i) {
|
||||||
m_WaitTimes[i] = 0;
|
m_WaitTimes[i] = 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
m_Start = true;
|
m_Start = true;
|
||||||
m_NeedsUpdate = true;
|
m_NeedsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawner::SetNumToMaintain(const int32_t value) {
|
void Spawner::SetNumToMaintain(int32_t value) {
|
||||||
m_Info.amountMaintained = value;
|
m_Info.amountMaintained = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +177,15 @@ void Spawner::Update(const float deltaTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_NeedsUpdate || !m_Active || m_Info.spawnsOnSmash) return;
|
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(); ) {
|
for (size_t i = 0; i < m_WaitTimes.size(); ) {
|
||||||
m_WaitTimes[i] += deltaTime;
|
m_WaitTimes[i] += deltaTime;
|
||||||
if (m_WaitTimes[i] >= m_Info.respawnTime) {
|
if (m_WaitTimes[i] >= m_Info.respawnTime) {
|
||||||
@@ -203,21 +198,22 @@ void Spawner::Update(const float deltaTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<LWOOBJID> Spawner::GetSpawnedObjectIDs() const {
|
std::vector<LWOOBJID> Spawner::GetSpawnedObjectIDs() const {
|
||||||
std::vector<LWOOBJID> ids;
|
std::vector<LWOOBJID> ids;
|
||||||
ids.reserve(m_Entities.size());
|
ids.reserve(m_Entities.size());
|
||||||
for (const auto objId : m_Entities | std::views::keys) {
|
for (const auto& [objId, spawnerNode] : m_Entities) {
|
||||||
ids.push_back(objId);
|
ids.push_back(objId);
|
||||||
}
|
}
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
|
void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
|
||||||
for (const auto& cb : m_SpawnedEntityDieCallbacks) {
|
for (std::function<void()> cb : m_SpawnedEntityDieCallbacks) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_NeedsUpdate = true;
|
m_NeedsUpdate = true;
|
||||||
|
//m_RespawnTime = 10.0f;
|
||||||
m_WaitTimes.push_back(0.0f);
|
m_WaitTimes.push_back(0.0f);
|
||||||
SpawnerNode* node;
|
SpawnerNode* node;
|
||||||
|
|
||||||
@@ -225,7 +221,9 @@ void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
|
|||||||
if (it != m_Entities.end()) node = it->second;
|
if (it != m_Entities.end()) node = it->second;
|
||||||
else return;
|
else return;
|
||||||
|
|
||||||
if (!node) return;
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < node->entities.size();) {
|
for (size_t i = 0; i < node->entities.size();) {
|
||||||
if (node->entities[i] && node->entities[i] == objectID)
|
if (node->entities[i] && node->entities[i] == objectID)
|
||||||
@@ -251,6 +249,6 @@ void Spawner::Activate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spawner::SetSpawnLot(const LOT lot) {
|
void Spawner::SetSpawnLot(LOT lot) {
|
||||||
m_EntityInfo.lot = lot;
|
m_EntityInfo.lot = lot;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,41 +11,12 @@
|
|||||||
#include "LDFFormat.h"
|
#include "LDFFormat.h"
|
||||||
#include "EntityInfo.h"
|
#include "EntityInfo.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* Any given spawner owns a certain number of spawner nodes
|
|
||||||
* these nodes are where entities are actually spawned
|
|
||||||
* The first spawner nodes waypoint in any given network contains the base config for all the spawner nodes
|
|
||||||
* Then each spawner node after the first may contain duplicate settings which override the base ones
|
|
||||||
* If spawner node 1 has an attached_path of "1", then all spawner nodes in this spawner network will have
|
|
||||||
* an attached_path of "1".
|
|
||||||
* Each spawner node can also specify attached_path of any other value and it will override the one provided by node 1.
|
|
||||||
* If a spawner node does NOT provide an override, the first one will be used
|
|
||||||
* I have no clue why the nodes are pointers, beats me
|
|
||||||
* sn = SpawnerNode
|
|
||||||
* Spawner
|
|
||||||
* ----------------
|
|
||||||
* | sn |
|
|
||||||
* | sn |
|
|
||||||
* | sn |
|
|
||||||
* | |
|
|
||||||
* | sn |
|
|
||||||
* | sn |
|
|
||||||
* -----------------
|
|
||||||
*/
|
|
||||||
struct SpawnerNode {
|
struct SpawnerNode {
|
||||||
// This spawner nodes position in the world
|
|
||||||
NiPoint3 position = NiPoint3Constant::ZERO;
|
NiPoint3 position = NiPoint3Constant::ZERO;
|
||||||
// The rotation of this spawner in the world
|
|
||||||
NiQuaternion rotation = QuatUtils::IDENTITY;
|
NiQuaternion rotation = QuatUtils::IDENTITY;
|
||||||
// This spawners nodes ID in this spawner network
|
|
||||||
uint32_t nodeID = 0;
|
uint32_t nodeID = 0;
|
||||||
// The max number of entities that can be spawned by this node
|
|
||||||
uint32_t nodeMax = 1;
|
uint32_t nodeMax = 1;
|
||||||
// The weight (chance) this spawner node has. Higher is more common
|
|
||||||
int32_t weight = 1;
|
|
||||||
// The IDs of entities spawned by this spawner node
|
|
||||||
std::vector<LWOOBJID> entities;
|
std::vector<LWOOBJID> entities;
|
||||||
// The config of all entities spawned by this node
|
|
||||||
LwoNameValue config;
|
LwoNameValue config;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,10 +45,11 @@ struct SpawnerInfo {
|
|||||||
|
|
||||||
class Spawner {
|
class Spawner {
|
||||||
public:
|
public:
|
||||||
Spawner(const SpawnerInfo& info);
|
Spawner(SpawnerInfo info);
|
||||||
|
~Spawner();
|
||||||
|
|
||||||
Entity* Spawn();
|
Entity* Spawn();
|
||||||
Entity* Spawn(const std::vector<SpawnerNode*>& freeNodes, bool force = false);
|
Entity* Spawn(std::vector<SpawnerNode*> freeNodes, bool force = false);
|
||||||
void Update(float deltaTime);
|
void Update(float deltaTime);
|
||||||
void NotifyOfEntityDeath(const LWOOBJID& objectID);
|
void NotifyOfEntityDeath(const LWOOBJID& objectID);
|
||||||
void Activate();
|
void Activate();
|
||||||
@@ -85,16 +57,16 @@ public:
|
|||||||
int32_t GetAmountSpawned() { return m_AmountSpawned; };
|
int32_t GetAmountSpawned() { return m_AmountSpawned; };
|
||||||
std::string GetName() { return m_Info.name; };
|
std::string GetName() { return m_Info.name; };
|
||||||
std::vector<std::string> GetGroups() { return m_Info.groups; };
|
std::vector<std::string> GetGroups() { return m_Info.groups; };
|
||||||
void AddSpawnedEntityDieCallback(const std::function<void()> callback);
|
void AddSpawnedEntityDieCallback(std::function<void()> callback);
|
||||||
void AddEntitySpawnedCallback(const std::function<void(Entity*)> callback);
|
void AddEntitySpawnedCallback(std::function<void(Entity*)> callback);
|
||||||
void SetSpawnLot(const LOT lot);
|
void SetSpawnLot(LOT lot);
|
||||||
void Reset();
|
void Reset();
|
||||||
void DestroyAllEntities();
|
void DestroyAllEntities();
|
||||||
void SoftReset();
|
void SoftReset();
|
||||||
void SetRespawnTime(const float time);
|
void SetRespawnTime(float time);
|
||||||
void SetNumToMaintain(const int32_t value);
|
void SetNumToMaintain(int32_t value);
|
||||||
bool GetIsSpawnSmashGroup() const { return m_SpawnSmashFoundGroup; };
|
bool GetIsSpawnSmashGroup() const { return m_SpawnSmashFoundGroup; };
|
||||||
const std::vector<LWOOBJID> GetSpawnedObjectIDs() const;
|
std::vector<LWOOBJID> GetSpawnedObjectIDs() const;
|
||||||
|
|
||||||
SpawnerInfo m_Info;
|
SpawnerInfo m_Info;
|
||||||
bool m_Active = true;
|
bool m_Active = true;
|
||||||
|
|||||||
@@ -101,47 +101,36 @@ void Zone::LoadZoneIntoMemory() {
|
|||||||
m_Paths.reserve(pathCount);
|
m_Paths.reserve(pathCount);
|
||||||
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
||||||
|
|
||||||
for (const Path& path : m_Paths) {
|
for (Path path : m_Paths) {
|
||||||
if (path.pathType != PathType::Spawner) continue;
|
if (path.pathType != PathType::Spawner) continue;
|
||||||
SpawnerInfo info{};
|
SpawnerInfo info = SpawnerInfo();
|
||||||
for (size_t i = 0; i < path.pathWaypoints.size(); i++) {
|
for (PathWaypoint waypoint : path.pathWaypoints) {
|
||||||
const auto& waypoint = path.pathWaypoints[i];
|
|
||||||
SpawnerNode* node = new SpawnerNode();
|
SpawnerNode* node = new SpawnerNode();
|
||||||
node->position = waypoint.position;
|
node->position = waypoint.position;
|
||||||
node->rotation = waypoint.rotation;
|
node->rotation = waypoint.rotation;
|
||||||
node->nodeID = 0;
|
node->nodeID = 0;
|
||||||
node->config = path.pathWaypoints[0].config;
|
node->config = waypoint.config;
|
||||||
// All spawner waypoints get the config data of the first waypoint, but then we
|
|
||||||
// overwrite settings on this waypoint if we have another one defined of the same name
|
|
||||||
if (i != 0) {
|
|
||||||
for (const auto& [key, value] : waypoint.config) {
|
|
||||||
node->config.ParseInsert(value->GetString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& data : waypoint.config | std::views::values) {
|
for (const auto& data : waypoint.config | std::views::values) {
|
||||||
if (!data) continue;
|
if (!data) continue;
|
||||||
|
|
||||||
if (data->GetKey() == u"spawner_node_id") {
|
if (data->GetKey() == u"spawner_node_id") {
|
||||||
node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0);
|
node->nodeID = std::stoi(data->GetValueAsString());
|
||||||
} else if (data->GetKey() == u"spawner_max_per_node") {
|
} else if (data->GetKey() == u"spawner_max_per_node") {
|
||||||
node->nodeMax = GeneralUtils::TryParse(data->GetValueAsString(), 0);
|
node->nodeMax = std::stoi(data->GetValueAsString());
|
||||||
} else if (data->GetKey() == u"groupID") { // Load object group
|
} else if (data->GetKey() == u"groupID") { // Load object group
|
||||||
info.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';');
|
std::string groupStr = data->GetValueAsString();
|
||||||
|
info.groups = GeneralUtils::SplitString(groupStr, ';');
|
||||||
if (info.groups.back().empty()) info.groups.erase(info.groups.end() - 1);
|
if (info.groups.back().empty()) info.groups.erase(info.groups.end() - 1);
|
||||||
} else if (data->GetKey() == u"grpNameQBShowBricks") {
|
} else if (data->GetKey() == u"grpNameQBShowBricks") {
|
||||||
|
if (data->GetValueAsString().empty()) continue;
|
||||||
|
/*std::string groupStr = data->GetValueAsString();
|
||||||
|
info.groups.push_back(groupStr);*/
|
||||||
info.grpNameQBShowBricks = data->GetValueAsString();
|
info.grpNameQBShowBricks = data->GetValueAsString();
|
||||||
} else if (data->GetKey() == u"spawner_name") {
|
} else if (data->GetKey() == u"spawner_name") {
|
||||||
info.name = data->GetValueAsString();
|
info.name = data->GetValueAsString();
|
||||||
} else if (data->GetKey() == u"weight") {
|
|
||||||
node->weight = GeneralUtils::TryParse(data->GetValueAsString(), 1);
|
|
||||||
if (node->weight <= 0) {
|
|
||||||
LOG("Found a spawner with a weight of <= 0, is this intentional? %s:%i", info.name.c_str(), node->nodeID);
|
|
||||||
node->weight = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.nodes.push_back(node);
|
info.nodes.push_back(node);
|
||||||
}
|
}
|
||||||
info.templateID = path.spawner.spawnedLOT;
|
info.templateID = path.spawner.spawnedLOT;
|
||||||
|
|||||||
Reference in New Issue
Block a user