Merge remote-tracking branch 'origin/main' into raw-parsing-for-scene-data

# Conflicts:
#	dNavigation/dTerrain/RawChunk.cpp
This commit is contained in:
Aaron Kimbrell
2026-06-21 01:13:34 -05:00
274 changed files with 4280 additions and 2608 deletions

View File

@@ -16,6 +16,7 @@
#include "AssetManager.h"
#include "ClientVersion.h"
#include "dConfig.h"
#include <ranges>
Level::Level(Zone* parentZone, const std::string& filepath) {
m_ParentZone = parentZone;
@@ -30,7 +31,7 @@ Level::Level(Zone* parentZone, const std::string& filepath) {
ReadChunks(stream);
}
void Level::MakeSpawner(SceneObject obj) {
void Level::MakeSpawner(const SceneObject& obj) {
SpawnerInfo spawnInfo = SpawnerInfo();
SpawnerNode* node = new SpawnerNode();
spawnInfo.templateID = obj.lot;
@@ -40,14 +41,14 @@ void Level::MakeSpawner(SceneObject obj) {
node->rotation = obj.rotation;
node->config = obj.settings;
spawnInfo.nodes.push_back(node);
for (LDFBaseData* data : obj.settings) {
for (const auto& data : obj.settings.values | std::views::values) {
if (!data) continue;
if (data->GetKey() == u"spawntemplate") {
spawnInfo.templateID = std::stoi(data->GetValueAsString());
spawnInfo.templateID = GeneralUtils::TryParse(data->GetValueAsString(), 0);
}
if (data->GetKey() == u"spawner_node_id") {
node->nodeID = std::stoi(data->GetValueAsString());
node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0u);
}
if (data->GetKey() == u"spawner_name") {
@@ -55,45 +56,44 @@ void Level::MakeSpawner(SceneObject obj) {
}
if (data->GetKey() == u"max_to_spawn") {
spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString());
spawnInfo.maxToSpawn = GeneralUtils::TryParse(data->GetValueAsString(), 0);
}
if (data->GetKey() == u"spawner_active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
spawnInfo.activeOnLoad = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
if (data->GetKey() == u"active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
spawnInfo.activeOnLoad = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
if (data->GetKey() == u"respawn") {
if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds
{
spawnInfo.respawnTime = std::stof(data->GetValueAsString());
} else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms?
spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0.0f);
} else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms
{
spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000;
spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0) / 1000;
}
}
if (data->GetKey() == u"spawnsGroupOnSmash") {
spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString());
spawnInfo.spawnsOnSmash = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") {
spawnInfo.spawnOnSmashGroupName = data->GetValueAsString();
}
if (data->GetKey() == u"groupID") { // Load object groups
std::string groupStr = data->GetValueAsString();
spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';');
spawnInfo.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';');
if (spawnInfo.groups.back().empty()) spawnInfo.groups.erase(spawnInfo.groups.end() - 1);
}
if (data->GetKey() == u"no_auto_spawn") {
spawnInfo.noAutoSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
spawnInfo.noAutoSpawn = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
if (data->GetKey() == u"no_timed_spawn") {
spawnInfo.noTimedSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
spawnInfo.noTimedSpawn = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
if (data->GetKey() == u"spawnActivator") {
spawnInfo.spawnActivator = static_cast<LDFData<bool>*>(data)->GetValue();
spawnInfo.spawnActivator = GeneralUtils::TryParse(data->GetValueAsString(), false);
}
}
@@ -236,10 +236,11 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, obj.lot);
if (header.fileInfo.version >= 38) {
uint32_t tmp = 1;
int32_t tmp = 1;
BinaryIO::BinaryRead(file, tmp);
if (tmp > -1 && tmp < 11) obj.nodeType = tmp;
}
if (header.fileInfo.version >= 32) {
BinaryIO::BinaryRead(file, obj.glomId);
}
@@ -257,20 +258,14 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
Game::zoneManager->GetZoneMut()->SetSpawnRot(obj.rotation);
}
std::string sData = GeneralUtils::UTF16ToWTF8(ldfString);
std::stringstream ssData(sData);
std::string token;
char deliminator = '\n';
while (std::getline(ssData, token, deliminator)) {
LDFBaseData* ldfData = LDFBaseData::DataFromString(token);
obj.settings.push_back(ldfData);
for (const auto& token : GeneralUtils::SplitString(GeneralUtils::UTF16ToWTF8(ldfString), '\n')) {
obj.settings.ParseInsert(token);
}
// We should never have more than 1 zone control object
bool skipLoadingObject = obj.lot == zoneControlObject->GetLOT();
for (LDFBaseData* data : obj.settings) {
for (const auto& data : obj.settings | std::views::values) {
if (!data) continue;
if (data->GetKey() == u"gatingOnFeature") {
gating.featureName = data->GetValueAsString();
@@ -290,17 +285,12 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
}
// If this is a client only object, we can skip loading it
if (data->GetKey() == u"loadOnClientOnly") {
skipLoadingObject |= static_cast<bool>(std::stoi(data->GetValueAsString()));
skipLoadingObject |= GeneralUtils::TryParse(data->GetValueAsString(), false);
break;
}
}
if (skipLoadingObject) {
for (auto* setting : obj.settings) {
delete setting;
setting = nullptr;
}
continue;
}

View File

@@ -40,7 +40,7 @@ public:
public:
Level(Zone* parentZone, const std::string& filepath);
static void MakeSpawner(SceneObject obj);
static void MakeSpawner(const SceneObject& obj);
std::map<uint32_t, Header> m_ChunkHeaders;
private:

View File

@@ -6,8 +6,10 @@
#include <functional>
#include "GeneralUtils.h"
#include "dZoneManager.h"
#include <algorithm>
#include <ranges>
Spawner::Spawner(const SpawnerInfo info) {
Spawner::Spawner(const SpawnerInfo& info) {
m_Info = info;
m_Active = m_Info.activeOnLoad && info.spawnActivator;
m_EntityInfo = EntityInfo();
@@ -53,18 +55,15 @@ Spawner::Spawner(const SpawnerInfo info) {
}
for (Spawner* ssSpawner : spawnSmashSpawnersN) {
m_SpawnSmashFoundGroup = true;
m_SpawnOnSmash = ssSpawner;
m_SpawnOnSmashID = ssSpawner ? ssSpawner->m_Info.spawnerID : LWOOBJID_EMPTY;
ssSpawner->AddSpawnedEntityDieCallback([=, this]() {
Spawn();
// Intentionally left as a non debug log since i have no idea how much stuff this would affect
LOG("WOULD HAVE SPAWNED %i", m_EntityInfo.lot);
});
}
}
}
Spawner::~Spawner() {
}
Entity* Spawner::Spawn() {
std::vector<SpawnerNode*> freeNodes;
for (SpawnerNode* node : m_Info.nodes) {
@@ -76,9 +75,25 @@ Entity* Spawner::Spawn() {
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)];
Entity* Spawner::Spawn(const std::vector<SpawnerNode*>& freeNodes, const bool force) {
Entity* spawnedEntity = nullptr;
if (force || ((m_Entities.size() < m_Info.amountMaintained) && !freeNodes.empty() && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -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_EntityInfo.pos = spawnNode->position;
m_EntityInfo.rot = spawnNode->rotation;
@@ -92,26 +107,24 @@ Entity* Spawner::Spawn(std::vector<SpawnerNode*> freeNodes, const bool force) {
m_EntityInfo.spawnerID = m_Info.spawnerID;
}
Entity* rezdE = Game::entityManager->CreateEntity(m_EntityInfo, nullptr);
spawnedEntity = Game::entityManager->CreateEntity(m_EntityInfo, nullptr);
rezdE->GetGroups() = m_Info.groups;
spawnedEntity->GetGroups() = m_Info.groups;
Game::entityManager->ConstructEntity(rezdE);
Game::entityManager->ConstructEntity(spawnedEntity);
m_Entities.insert({ rezdE->GetObjectID(), spawnNode });
spawnNode->entities.push_back(rezdE->GetObjectID());
m_Entities[spawnedEntity->GetObjectID()] = spawnNode;
spawnNode->entities.push_back(spawnedEntity->GetObjectID());
if (m_Entities.size() == m_Info.amountMaintained) {
m_NeedsUpdate = false;
}
for (const auto& cb : m_EntitySpawnedCallbacks) {
cb(rezdE);
cb(spawnedEntity);
}
return rezdE;
}
return nullptr;
return spawnedEntity;
}
void Spawner::AddSpawnedEntityDieCallback(std::function<void()> callback) {
@@ -147,18 +160,18 @@ void Spawner::SoftReset() {
m_NeedsUpdate = true;
}
void Spawner::SetRespawnTime(float time) {
void Spawner::SetRespawnTime(const 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) {
void Spawner::SetNumToMaintain(const int32_t value) {
m_Info.amountMaintained = value;
}
@@ -176,41 +189,35 @@ void Spawner::Update(const float deltaTime) {
return;
}
if (!m_NeedsUpdate) return;
if (!m_Active) return;
//if (m_Info.noTimedSpawn) return;
if (m_Info.spawnsOnSmash) {
if (!m_SpawnSmashFoundGroup) {
if (!m_NeedsUpdate || !m_Active || m_Info.spawnsOnSmash) return;
}
return;
}
for (size_t i = 0; i < m_WaitTimes.size(); ++i) {
for (size_t i = 0; i < m_WaitTimes.size(); ) {
m_WaitTimes[i] += deltaTime;
if (m_WaitTimes[i] >= m_Info.respawnTime) {
m_WaitTimes.erase(m_WaitTimes.begin() + i);
Spawn();
} else {
i++;
}
}
}
std::vector<LWOOBJID> Spawner::GetSpawnedObjectIDs() const {
const std::vector<LWOOBJID> Spawner::GetSpawnedObjectIDs() const {
std::vector<LWOOBJID> ids;
ids.reserve(m_Entities.size());
for (const auto& [objId, spawnerNode] : m_Entities) {
for (const auto objId : m_Entities | std::views::keys) {
ids.push_back(objId);
}
return ids;
}
void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
for (std::function<void()> cb : m_SpawnedEntityDieCallbacks) {
for (const auto& cb : m_SpawnedEntityDieCallbacks) {
cb();
}
m_NeedsUpdate = true;
//m_RespawnTime = 10.0f;
m_WaitTimes.push_back(0.0f);
SpawnerNode* node;
@@ -218,19 +225,20 @@ void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) {
if (it != m_Entities.end()) node = it->second;
else return;
if (!node) {
return;
}
if (!node) return;
for (size_t i = 0; i < node->entities.size(); ++i) {
for (size_t i = 0; i < node->entities.size();) {
if (node->entities[i] && node->entities[i] == objectID)
node->entities.erase(node->entities.begin() + i);
else
i++;
}
m_Entities.erase(objectID);
if (m_SpawnOnSmash != nullptr) {
m_SpawnOnSmash->Reset();
auto* const spawnOnSmash = Game::zoneManager->GetSpawner(m_SpawnOnSmashID);
if (spawnOnSmash) {
spawnOnSmash->Reset();
}
}
@@ -243,6 +251,6 @@ void Spawner::Activate() {
}
}
void Spawner::SetSpawnLot(LOT lot) {
void Spawner::SetSpawnLot(const LOT lot) {
m_EntityInfo.lot = lot;
}

View File

@@ -11,13 +11,42 @@
#include "LDFFormat.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 {
// This spawner nodes position in the world
NiPoint3 position = NiPoint3Constant::ZERO;
// The rotation of this spawner in the world
NiQuaternion rotation = QuatUtils::IDENTITY;
// This spawners nodes ID in this spawner network
uint32_t nodeID = 0;
// The max number of entities that can be spawned by this node
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<LDFBaseData*> config;
// The config of all entities spawned by this node
LwoNameValue config;
};
struct SpawnerInfo {
@@ -45,11 +74,10 @@ struct SpawnerInfo {
class Spawner {
public:
Spawner(SpawnerInfo info);
~Spawner();
Spawner(const SpawnerInfo& info);
Entity* Spawn();
Entity* Spawn(std::vector<SpawnerNode*> freeNodes, bool force = false);
Entity* Spawn(const std::vector<SpawnerNode*>& freeNodes, bool force = false);
void Update(float deltaTime);
void NotifyOfEntityDeath(const LWOOBJID& objectID);
void Activate();
@@ -57,16 +85,16 @@ public:
int32_t GetAmountSpawned() { return m_AmountSpawned; };
std::string GetName() { return m_Info.name; };
std::vector<std::string> GetGroups() { return m_Info.groups; };
void AddSpawnedEntityDieCallback(std::function<void()> callback);
void AddEntitySpawnedCallback(std::function<void(Entity*)> callback);
void SetSpawnLot(LOT lot);
void AddSpawnedEntityDieCallback(const std::function<void()> callback);
void AddEntitySpawnedCallback(const std::function<void(Entity*)> callback);
void SetSpawnLot(const LOT lot);
void Reset();
void DestroyAllEntities();
void SoftReset();
void SetRespawnTime(float time);
void SetNumToMaintain(int32_t value);
void SetRespawnTime(const float time);
void SetNumToMaintain(const int32_t value);
bool GetIsSpawnSmashGroup() const { return m_SpawnSmashFoundGroup; };
std::vector<LWOOBJID> GetSpawnedObjectIDs() const;
const std::vector<LWOOBJID> GetSpawnedObjectIDs() const;
SpawnerInfo m_Info;
bool m_Active = true;
@@ -82,7 +110,7 @@ private:
EntityInfo m_EntityInfo;
int32_t m_AmountSpawned = 0;
bool m_Start = false;
Spawner* m_SpawnOnSmash = nullptr;
LWOOBJID m_SpawnOnSmashID = LWOOBJID_EMPTY;
};
#endif // SPAWNER_H

View File

@@ -136,36 +136,47 @@ void Zone::LoadZoneIntoMemory() {
m_Paths.reserve(pathCount);
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
for (Path path : m_Paths) {
for (const Path& path : m_Paths) {
if (path.pathType != PathType::Spawner) continue;
SpawnerInfo info = SpawnerInfo();
for (PathWaypoint waypoint : path.pathWaypoints) {
SpawnerInfo info{};
for (size_t i = 0; i < path.pathWaypoints.size(); i++) {
const auto& waypoint = path.pathWaypoints[i];
SpawnerNode* node = new SpawnerNode();
node->position = waypoint.position;
node->rotation = waypoint.rotation;
node->nodeID = 0;
node->config = waypoint.config;
node->config = path.pathWaypoints[0].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 (LDFBaseData* data : waypoint.config) {
for (const auto& data : waypoint.config | std::views::values) {
if (!data) continue;
if (data->GetKey() == u"spawner_node_id") {
node->nodeID = std::stoi(data->GetValueAsString());
node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0);
} else if (data->GetKey() == u"spawner_max_per_node") {
node->nodeMax = std::stoi(data->GetValueAsString());
node->nodeMax = GeneralUtils::TryParse(data->GetValueAsString(), 0);
} else if (data->GetKey() == u"groupID") { // Load object group
std::string groupStr = data->GetValueAsString();
info.groups = GeneralUtils::SplitString(groupStr, ';');
info.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';');
if (info.groups.back().empty()) info.groups.erase(info.groups.end() - 1);
} 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();
} else if (data->GetKey() == u"spawner_name") {
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.templateID = path.spawner.spawnedLOT;
@@ -500,7 +511,7 @@ void Zone::LoadPath(std::istream& file) {
command.data = value;
} else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str());
} else {
waypoint.config.emplace_back(LDFBaseData::DataFromString(parameter + "=" + value));
waypoint.config.ParseInsert(parameter + "=" + value);
}
}

View File

@@ -77,7 +77,7 @@ struct PathWaypoint {
CameraPathWaypoint camera;
RacingPathWaypoint racing;
float speed{};
std::vector<LDFBaseData*> config;
LwoNameValue config;
std::vector<WaypointCommand> commands;
};

View File

@@ -13,9 +13,8 @@ struct SceneObject {
NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY;
float scale = 1.0f;
//std::string settings;
uint32_t value3;
std::vector<LDFBaseData*> settings;
LwoNameValue settings;
};
#define LOT_MARKER_PLAYER_START 1931