feat: scene ghosting, as the client streams a zone's scenes

The client keeps the scene under the player loaded, the scenes the zone
file's transitions connect to it, and the global scene
(Zone::StreamScenesAroundPosition 0x0108a3f0, TerrainManager::GetSceneAtPos
0x01069010, the connected scenes at 0x01066500; transitions naming a
missing scene dropped as Zone::FixupInvalidTransitions 0x010842e0 does).

- ZoneScenes (dCommon): the terrain's scene map lookup and the scene graph,
  shared by the world server and the dashboard.
- Objects remember the scene they were placed in (spawners pass theirs on).
- ghosting_scenes=1 (world config, off by default): players get the objects
  of their loaded scenes instead of the ones within the ghosting distances;
  objects from no scene go by the scene under them. Zones without a scene
  map keep distance ghosting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 22:02:20 -05:00
parent 696580cce5
commit 1b9724a7ec
20 changed files with 363 additions and 19 deletions

View File

@@ -66,6 +66,7 @@ void Zone::LoadZoneIntoMemory() {
m_SpawnpointRotation = zoneFile.spawnpointRotation;
m_SceneCount = zoneFile.scenes.size();
m_SceneGraph = ZoneScenes::SceneGraph(zoneFile.scenes, zoneFile.sceneTransitions);
for (auto& scene : zoneFile.scenes) {
LoadScene(std::move(scene));
}
@@ -198,7 +199,7 @@ uint32_t Zone::CalculateChecksum() const {
void Zone::LoadLevelsIntoMemory() {
for (auto& [sceneID, scene] : m_Scenes) {
if (scene.level) continue;
scene.level = std::make_unique<Level>(this, m_ZonePath + scene.filename);
scene.level = std::make_unique<Level>(this, m_ZonePath + scene.filename, sceneID.GetSceneID());
if (scene.level->m_ChunkHeaders.empty()) continue;
@@ -207,6 +208,22 @@ void Zone::LoadLevelsIntoMemory() {
}
}
bool Zone::LoadSceneMap() {
if (m_ZoneRawPath.empty()) return false;
auto file = Game::assetManager->GetFile((m_ZonePath + m_ZoneRawPath).c_str());
if (!file) {
LOG("Could not open the terrain file %s for its scene map", (m_ZonePath + m_ZoneRawPath).c_str());
return false;
}
Raw::Raw raw;
if (!Raw::ReadRaw(file, raw)) {
LOG("Could not read the terrain file %s for its scene map", (m_ZonePath + m_ZoneRawPath).c_str());
return false;
}
m_SceneMap = ZoneScenes::SceneMap(raw);
return !m_SceneMap.Empty();
}
void Zone::AddRevision(LWOSCENEID sceneID, uint32_t revision) {
if (m_MapRevisions.find(sceneID) == m_MapRevisions.end()) {
m_MapRevisions.insert(std::make_pair(sceneID, revision));