mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
The client never loads a level object whose config has carver_only set (its resource manager skips the load; LWOBasePhysComponent::LoadConfigData 0x00c495c9 reads the flag next to navmesh_carver), and live never sent one: none of the 331 carver_only placements (mostly Crux Prime's trigger boxes) shows up among the live constructions, where their zones' other objects do. DLU spawned them as objects. They are skipped now. The ones that carve the navmesh (53, in six zones, among them the Avant Gardens Sentinel camp walls) still stop the server's movers: their shape goes into the world's movement blockers without an object, owned by the physics world (dpWorld::AddOwnedMovementBlocker) and freed when it shuts down. None of them has a script, trigger or group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
220 lines
5.5 KiB
C++
220 lines
5.5 KiB
C++
#include "dpWorld.h"
|
|
#include "dpEntity.h"
|
|
#include "dpGrid.h"
|
|
#include "DetourCommon.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "dConfig.h"
|
|
|
|
#include "dNavMesh.h"
|
|
|
|
namespace {
|
|
dpGrid* m_Grid = nullptr;
|
|
dNavMesh* m_NavMesh = nullptr;
|
|
int32_t phys_sp_tilesize = 205;
|
|
int32_t phys_sp_tilecount = 12;
|
|
|
|
uint32_t m_ZoneID = 0;
|
|
|
|
std::vector<dpEntity*> m_StaticEntities;
|
|
std::vector<dpEntity*> m_DynamicEntites;
|
|
bool phys_spatial_partitioning = true;
|
|
|
|
std::vector<dpMovementBlocker> m_MovementBlockers;
|
|
std::vector<std::unique_ptr<dpEntity>> m_OwnedMovementBlockers;
|
|
};
|
|
|
|
void dpWorld::Initialize(unsigned int zoneID, bool generateNewNavMesh) {
|
|
const auto physSpTilecount = Game::config->GetValue("phys_sp_tilecount");
|
|
if (!physSpTilecount.empty()) {
|
|
phys_sp_tilecount = GeneralUtils::TryParse<int32_t>(physSpTilecount).value_or(phys_sp_tilecount);
|
|
}
|
|
|
|
const auto physSpTilesize = Game::config->GetValue("phys_sp_tilesize");
|
|
if (!physSpTilesize.empty()) {
|
|
phys_sp_tilesize = GeneralUtils::TryParse<int32_t>(physSpTilesize).value_or(phys_sp_tilesize);
|
|
}
|
|
|
|
const auto physSpatialPartitioning = Game::config->GetValue("phys_spatial_partitioning");
|
|
if (!physSpatialPartitioning.empty()) phys_spatial_partitioning = physSpatialPartitioning == "1";
|
|
|
|
//If spatial partitioning is enabled, then we need to create the m_Grid.
|
|
//if m_Grid exists, then the old method will be used.
|
|
//SP will NOT be used unless it is added to ShouldUseSP();
|
|
if (ShouldUseSP(zoneID)) {
|
|
m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize);
|
|
}
|
|
|
|
if (generateNewNavMesh) m_NavMesh = new dNavMesh(zoneID);
|
|
|
|
LOG("Physics world initialized!");
|
|
m_ZoneID = zoneID;
|
|
}
|
|
|
|
void dpWorld::Reload() {
|
|
if (m_Grid) {
|
|
m_Grid->SetDeleteGrid(false);
|
|
auto oldGridCells = m_Grid->GetCells();
|
|
delete m_Grid;
|
|
m_Grid = nullptr;
|
|
|
|
Initialize(m_ZoneID, false);
|
|
for (auto column : oldGridCells) {
|
|
for (auto row : column) {
|
|
for (auto entity : row) {
|
|
AddEntity(entity);
|
|
}
|
|
}
|
|
}
|
|
LOG("Successfully reloaded physics world!");
|
|
} else {
|
|
LOG("No physics world to reload!");
|
|
}
|
|
}
|
|
|
|
void dpWorld::Shutdown() {
|
|
if (m_Grid) {
|
|
// Triple check this is true
|
|
m_Grid->SetDeleteGrid(true);
|
|
delete m_Grid;
|
|
m_Grid = nullptr;
|
|
}
|
|
|
|
if (m_NavMesh) {
|
|
delete m_NavMesh;
|
|
m_NavMesh = nullptr;
|
|
}
|
|
|
|
m_MovementBlockers.clear();
|
|
m_OwnedMovementBlockers.clear();
|
|
}
|
|
|
|
bool dpWorld::IsLoaded() {
|
|
return m_NavMesh && m_NavMesh->IsNavmeshLoaded();
|
|
}
|
|
|
|
void dpWorld::StepWorld(float deltaTime) {
|
|
if (m_Grid) {
|
|
m_Grid->Update(deltaTime);
|
|
return;
|
|
}
|
|
|
|
//Pre update:
|
|
for (auto entity : m_StaticEntities) {
|
|
if (!entity || entity->GetSleeping()) continue;
|
|
entity->PreUpdate();
|
|
}
|
|
|
|
//Do actual update:
|
|
for (auto entity : m_DynamicEntites) {
|
|
if (!entity || entity->GetSleeping()) continue;
|
|
|
|
entity->Update(deltaTime);
|
|
|
|
for (auto other : m_StaticEntities) {
|
|
if (!other || other->GetSleeping() || entity->GetObjectID() == other->GetObjectID()) continue;
|
|
|
|
other->CheckCollision(entity); //swap "other" and "entity" if you want dyn objs to handle collisions.
|
|
}
|
|
}
|
|
}
|
|
|
|
dNavMesh* dpWorld::GetNavMesh() {
|
|
return m_NavMesh;
|
|
}
|
|
|
|
void dpWorld::AddEntity(dpEntity* entity) {
|
|
if (m_Grid) entity->SetGrid(m_Grid); //This sorts this entity into the right cell
|
|
else { //old method, slow
|
|
if (entity->GetIsStatic()) m_StaticEntities.push_back(entity);
|
|
else m_DynamicEntites.push_back(entity);
|
|
}
|
|
}
|
|
|
|
void dpWorld::DetachEntity(dpEntity* entity) {
|
|
if (!entity) return;
|
|
|
|
if (m_Grid) {
|
|
m_Grid->Remove(entity);
|
|
return;
|
|
}
|
|
|
|
auto& entities = entity->GetIsStatic() ? m_StaticEntities : m_DynamicEntites;
|
|
for (auto& other : entities) {
|
|
if (other == entity) other = nullptr;
|
|
}
|
|
}
|
|
|
|
void dpWorld::RemoveEntity(dpEntity* entity) {
|
|
if (!entity) return;
|
|
|
|
if (m_Grid) {
|
|
m_Grid->Delete(entity);
|
|
} else {
|
|
if (entity->GetIsStatic()) {
|
|
for (size_t i = 0; i < m_StaticEntities.size(); ++i) {
|
|
if (m_StaticEntities[i] == entity) {
|
|
delete m_StaticEntities[i];
|
|
m_StaticEntities[i] = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < m_DynamicEntites.size(); ++i) {
|
|
if (m_DynamicEntites[i] == entity) {
|
|
delete m_DynamicEntites[i];
|
|
m_DynamicEntites[i] = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool dpWorld::ShouldUseSP(uint32_t zoneID) {
|
|
if (!phys_spatial_partitioning) return false;
|
|
|
|
// TODO: Add to this list as needed.
|
|
// Only large maps should be added as tiling likely makes little difference on small maps.
|
|
|
|
switch (zoneID) {
|
|
case 1100: // Avant Gardens
|
|
case 1200: // Nimbus Station
|
|
case 1300: // Gnarled Forest
|
|
case 1400: // Forbidden Valley
|
|
case 1800: // Crux Prime
|
|
case 1900: // Nexus Tower
|
|
case 2000: // Ninjago
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void dpWorld::AddMovementBlocker(const dpEntity* entity, const uint32_t filter) {
|
|
if (!entity) return;
|
|
m_MovementBlockers.push_back({ entity, filter });
|
|
}
|
|
|
|
void dpWorld::AddOwnedMovementBlocker(dpEntity* entity, const uint32_t filter) {
|
|
if (!entity) return;
|
|
m_OwnedMovementBlockers.emplace_back(entity);
|
|
AddMovementBlocker(entity, filter);
|
|
}
|
|
|
|
void dpWorld::RemoveMovementBlocker(const dpEntity* entity) {
|
|
std::erase_if(m_MovementBlockers, [entity](const dpMovementBlocker& blocker) { return blocker.entity == entity; });
|
|
}
|
|
|
|
std::span<const dpMovementBlocker> dpWorld::GetMovementBlockers() {
|
|
return m_MovementBlockers;
|
|
}
|
|
|
|
std::vector<NiPoint3> dpWorld::ClampPath(const NiPoint3& start, std::vector<NiPoint3> path, const uint32_t moverFilter) {
|
|
return dpMovementBlockers::ClampPath(m_MovementBlockers, start, std::move(path), moverFilter);
|
|
}
|