feat(runtime): pick up WHM/WOT/WOC sidecars from asset tree

Closes the loop on the asset_extract --emit-terrain pipeline. The
runtime terrain loader now probes for a .whm/.wot/.woc trio in the
same directory as the resolved ADT (e.g. <data>/world/maps/foo/
foo_30_30.{whm,wot,woc}) before falling back to ADTLoader.

Hits the open-format path when:
  custom_zones/<map>/<map>_X_Y.{whm,wot} exists  (zone author override)
  output/<map>/<map>_X_Y.{whm,wot} exists        (editor export)
  <data>/world/maps/<map>/<map>_X_Y.{whm,wot}    (asset extractor)
  -- otherwise falls through to ADTLoader::load(adtData)

Promotes AssetManager::resolveFile to public so callers (terrain
sidecar probe here, anything else later) can locate an extracted
file's directory without reading the bytes.

Servers/private servers continue to read .adt via manifest paths
unchanged. Runtime sidecar coverage now matches the extractor's
emit set across all five binary open formats.
This commit is contained in:
Kelsi 2026-05-06 10:48:40 -07:00
parent b5ff9eb2a2
commit ea745005ce
2 changed files with 42 additions and 5 deletions

View file

@ -368,6 +368,37 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
}
}
// Try WHM/WOT sidecar from the asset tree (asset_extract --emit-terrain
// writes one alongside the ADT). This lets the runtime use the open
// format without copying anything into custom_zones/.
if (!loadedFromWot) {
std::string adtPath = getADTPath(coord);
std::string adtFsPath = assetManager->resolveFile(adtPath);
if (!adtFsPath.empty() && adtFsPath.size() >= 4) {
std::string sidecarBase = adtFsPath.substr(0, adtFsPath.size() - 4);
if (pipeline::WoweeTerrainLoader::exists(sidecarBase) &&
pipeline::WoweeTerrainLoader::load(sidecarBase, *terrainPtr)) {
loadedFromWot = true;
LOG_INFO("Loaded asset-tree WHM/WOT sidecar: ", sidecarBase);
if (pipeline::WoweeCollisionBuilder::exists(sidecarBase)) {
auto woc = pipeline::WoweeCollisionBuilder::load(sidecarBase + ".woc");
if (woc.isValid()) {
CollisionData cd;
cd.triangles.reserve(woc.triangles.size());
for (const auto& t : woc.triangles)
cd.triangles.push_back({t.v0, t.v1, t.v2, t.flags});
cd.boundsMin = woc.bounds.min;
cd.boundsMax = woc.bounds.max;
cd.loaded = true;
collisionTiles_[tileKey(coord.x, coord.y)] = std::move(cd);
LOG_INFO("Loaded sidecar WOC collision: ",
woc.triangles.size(), " triangles");
}
}
}
}
}
// Fall back to ADT format
if (!loadedFromWot) {
std::string adtPath = getADTPath(coord);