Fix WMO spawn presets to avoid terrain snapping

This commit is contained in:
Kelsi 2026-02-04 23:30:03 -08:00
parent ae37e5592e
commit 28a8e806e1
3 changed files with 28 additions and 18 deletions

View file

@ -92,6 +92,7 @@ private:
bool singlePlayerMode = false;
bool playerCharacterSpawned = false;
bool npcsSpawned = false;
bool spawnSnapToGround = true;
float lastFrameTime = 0.0f;
float movementHeartbeatTimer = 0.0f;

View file

@ -11,17 +11,20 @@ struct SpawnPreset {
glm::vec3 spawnCanonical; // Canonical WoW coords: +X=North, +Y=West, +Z=Up
float yawDeg;
float pitchDeg;
bool snapToGround; // true=terrain/WMO floor search, false=preserve explicit Z
};
// Spawn positions in canonical WoW world coordinates (X=north, Y=west, Z=up).
// Tile is computed from position via: tileN = floor(32 - wowN / 533.33333)
inline const SpawnPreset SPAWN_PRESETS[] = {
{"goldshire", "Goldshire", "Azeroth", glm::vec3( 62.0f, -9464.0f, 200.0f), 0.0f, -5.0f},
{"stormwind", "Stormwind Gate", "Azeroth", glm::vec3( 425.0f, -9176.0f, 120.0f), 35.0f, -8.0f},
{"ironforge", "Ironforge", "Azeroth", glm::vec3( -882.0f, -4981.0f, 510.0f), -20.0f, -8.0f},
{"westfall", "Westfall", "Azeroth", glm::vec3( 1215.0f,-10440.0f, 80.0f), 10.0f, -8.0f},
{"goldshire", "Goldshire", "Azeroth", glm::vec3( 62.0f, -9464.0f, 200.0f), 0.0f, -5.0f, true},
{"stormwind", "Stormwind Gate", "Azeroth", glm::vec3( 425.0f, -9176.0f, 120.0f), 35.0f, -8.0f, true},
// Stormwind city center plaza on WMO floor (not terrain intersection).
{"sw_plaza", "Stormwind Plaza","Azeroth", glm::vec3( 620.0f, -8830.0f, 95.0f), 180.0f, -8.0f, false},
{"ironforge", "Ironforge", "Azeroth", glm::vec3( -882.0f, -4981.0f, 510.0f), -20.0f, -8.0f, true},
{"westfall", "Westfall", "Azeroth", glm::vec3( 1215.0f,-10440.0f, 80.0f), 10.0f, -8.0f, true},
};
inline constexpr int SPAWN_PRESET_COUNT = 4;
inline constexpr int SPAWN_PRESET_COUNT = static_cast<int>(sizeof(SPAWN_PRESETS) / sizeof(SPAWN_PRESETS[0]));
} // namespace wowee::core

View file

@ -59,7 +59,7 @@ const SpawnPreset* selectSpawnPreset(const char* envValue) {
}
LOG_WARNING("Unknown WOW_SPAWN='", key, "', falling back to goldshire");
LOG_INFO("Available WOW_SPAWN presets: goldshire, stormwind, ironforge, westfall");
LOG_INFO("Available WOW_SPAWN presets: goldshire, stormwind, sw_plaza, ironforge, westfall");
return &SPAWN_PRESETS[0];
}
@ -676,12 +676,12 @@ void Application::spawnPlayerCharacter() {
LOG_INFO("Loaded fallback cube model (no MPQ data)");
}
// Spawn character at the camera controller's default position (matches hearthstone),
// but snap Z to actual terrain height so the character doesn't float.
// Spawn character at the camera controller's default position (matches hearthstone).
// Most presets snap to floor; explicit WMO-floor presets keep their authored Z.
auto* camCtrl = renderer->getCameraController();
glm::vec3 spawnPos = camCtrl ? camCtrl->getDefaultPosition()
: (camera->getPosition() - glm::vec3(0.0f, 0.0f, 5.0f));
if (renderer->getTerrainManager()) {
if (spawnSnapToGround && renderer->getTerrainManager()) {
auto terrainH = renderer->getTerrainManager()->getHeightAt(spawnPos.x, spawnPos.y);
if (terrainH) {
spawnPos.z = *terrainH + 0.1f;
@ -913,6 +913,7 @@ void Application::startSinglePlayer() {
std::string mapName = spawnPreset ? spawnPreset->mapName : "Azeroth";
float spawnYaw = spawnPreset ? spawnPreset->yawDeg : 0.0f;
float spawnPitch = spawnPreset ? spawnPreset->pitchDeg : -5.0f;
spawnSnapToGround = spawnPreset ? spawnPreset->snapToGround : true;
if (auto envSpawnPos = parseVec3Csv(std::getenv("WOW_SPAWN_POS"))) {
spawnCanonical = *envSpawnPos;
@ -1014,8 +1015,8 @@ void Application::startSinglePlayer() {
LOG_INFO("Terrain streaming complete: ", terrainMgr->getLoadedTileCount(), " tiles loaded");
// Re-snap camera to ground now that all surrounding tiles are loaded
// (the initial reset inside loadTestTerrain only had 1 tile)
if (renderer->getCameraController()) {
// (the initial reset inside loadTestTerrain only had 1 tile).
if (spawnSnapToGround && renderer->getCameraController()) {
renderer->getCameraController()->reset();
}
}
@ -1027,7 +1028,7 @@ void Application::startSinglePlayer() {
// Final camera reset: now that follow target exists and terrain is loaded,
// snap the third-person camera into the correct orbit position.
if (renderer && renderer->getCameraController()) {
if (spawnSnapToGround && renderer && renderer->getCameraController()) {
renderer->getCameraController()->reset();
}
@ -1055,6 +1056,7 @@ void Application::teleportTo(int presetIndex) {
const auto& preset = SPAWN_PRESETS[presetIndex];
LOG_INFO("Teleporting to: ", preset.label);
spawnSnapToGround = preset.snapToGround;
// Convert canonical WoW → engine rendering coordinates (swap X/Y)
glm::vec3 spawnRender = core::coords::canonicalToRender(preset.spawnCanonical);
@ -1120,16 +1122,20 @@ void Application::teleportTo(int presetIndex) {
LOG_INFO("Teleport terrain streaming complete: ", terrainMgr->getLoadedTileCount(), " tiles loaded");
}
// Reset camera — this snaps character position to terrain via followTarget
if (renderer && renderer->getCameraController()) {
// Floor-snapping presets use camera reset. WMO-floor presets keep explicit Z.
if (spawnSnapToGround && renderer && renderer->getCameraController()) {
renderer->getCameraController()->reset();
}
// Sync the terrain-snapped character position to game handler
if (!spawnSnapToGround && renderer) {
renderer->getCharacterPosition() = spawnRender;
}
// Sync final character position to game handler
if (renderer && gameHandler) {
glm::vec3 snappedRender = renderer->getCharacterPosition();
glm::vec3 snappedCanonical = core::coords::renderToCanonical(snappedRender);
gameHandler->setPosition(snappedCanonical.x, snappedCanonical.y, snappedCanonical.z);
glm::vec3 finalRender = renderer->getCharacterPosition();
glm::vec3 finalCanonical = core::coords::renderToCanonical(finalRender);
gameHandler->setPosition(finalCanonical.x, finalCanonical.y, finalCanonical.z);
}
LOG_INFO("Teleport to ", preset.label, " complete");