mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
Add multi-tier unstuck system with void fall detection
Replace broken hardcoded-coordinate unstuck with tiered fallbacks: last safe position > hearth bind > map spawn. Track safe positions only on real geometry, let player fall after 500ms with no ground, and auto-trigger unstuck after 5s of continuous falling.
This commit is contained in:
parent
8fee55f99f
commit
ef54f62df0
4 changed files with 102 additions and 33 deletions
|
|
@ -44,6 +44,15 @@ public:
|
||||||
void reset();
|
void reset();
|
||||||
void teleportTo(const glm::vec3& pos);
|
void teleportTo(const glm::vec3& pos);
|
||||||
void setOnlineMode(bool online) { onlineMode = online; }
|
void setOnlineMode(bool online) { onlineMode = online; }
|
||||||
|
|
||||||
|
// Last known safe position (grounded, not falling)
|
||||||
|
bool hasLastSafePosition() const { return hasLastSafe_; }
|
||||||
|
const glm::vec3& getLastSafePosition() const { return lastSafePos_; }
|
||||||
|
float getContinuousFallTime() const { return continuousFallTime_; }
|
||||||
|
|
||||||
|
// Auto-unstuck callback (triggered when falling too long)
|
||||||
|
using AutoUnstuckCallback = std::function<void()>;
|
||||||
|
void setAutoUnstuckCallback(AutoUnstuckCallback cb) { autoUnstuckCallback_ = std::move(cb); }
|
||||||
void startIntroPan(float durationSec = 2.8f, float orbitDegrees = 140.0f);
|
void startIntroPan(float durationSec = 2.8f, float orbitDegrees = 140.0f);
|
||||||
bool isIntroActive() const { return introActive; }
|
bool isIntroActive() const { return introActive; }
|
||||||
bool isIdleOrbit() const { return idleOrbit_; }
|
bool isIdleOrbit() const { return idleOrbit_; }
|
||||||
|
|
@ -227,6 +236,23 @@ private:
|
||||||
float idleTimer_ = 0.0f;
|
float idleTimer_ = 0.0f;
|
||||||
bool idleOrbit_ = false; // true when current intro pan is an idle orbit (loops)
|
bool idleOrbit_ = false; // true when current intro pan is an idle orbit (loops)
|
||||||
static constexpr float IDLE_TIMEOUT = 120.0f; // 2 minutes
|
static constexpr float IDLE_TIMEOUT = 120.0f; // 2 minutes
|
||||||
|
|
||||||
|
// Last known safe position (saved periodically when grounded on real geometry)
|
||||||
|
bool hasLastSafe_ = false;
|
||||||
|
glm::vec3 lastSafePos_ = glm::vec3(0.0f);
|
||||||
|
float safePosSaveTimer_ = 0.0f;
|
||||||
|
bool hasRealGround_ = false; // True only when terrain/WMO/M2 floor is detected
|
||||||
|
static constexpr float SAFE_POS_SAVE_INTERVAL = 2.0f; // Save every 2 seconds
|
||||||
|
|
||||||
|
// No-ground timer: after grace period, let the player fall instead of hovering
|
||||||
|
float noGroundTimer_ = 0.0f;
|
||||||
|
static constexpr float NO_GROUND_GRACE = 0.5f; // 500ms grace for terrain streaming
|
||||||
|
|
||||||
|
// Continuous fall time (for auto-unstuck detection)
|
||||||
|
float continuousFallTime_ = 0.0f;
|
||||||
|
bool autoUnstuckFired_ = false;
|
||||||
|
AutoUnstuckCallback autoUnstuckCallback_;
|
||||||
|
static constexpr float AUTO_UNSTUCK_FALL_TIME = 5.0f; // 5 seconds of falling
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rendering
|
} // namespace rendering
|
||||||
|
|
|
||||||
|
|
@ -593,50 +593,56 @@ void Application::setupUICallbacks() {
|
||||||
loadOnlineWorldTerrain(mapId, x, y, z);
|
loadOnlineWorldTerrain(mapId, x, y, z);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unstuck callback — move 5 units forward
|
// /unstuck — snap upward 10m to escape minor WMO cracks
|
||||||
gameHandler->setUnstuckCallback([this]() {
|
gameHandler->setUnstuckCallback([this]() {
|
||||||
if (!renderer || !renderer->getCameraController()) return;
|
if (!renderer || !renderer->getCameraController()) return;
|
||||||
auto* cc = renderer->getCameraController();
|
auto* cc = renderer->getCameraController();
|
||||||
auto* ft = cc->getFollowTargetMutable();
|
auto* ft = cc->getFollowTargetMutable();
|
||||||
if (!ft) return;
|
if (!ft) return;
|
||||||
float yaw = cc->getYaw();
|
glm::vec3 pos = *ft;
|
||||||
ft->x += 5.0f * std::sin(yaw);
|
pos.z += 10.0f;
|
||||||
ft->y += 5.0f * std::cos(yaw);
|
cc->teleportTo(pos);
|
||||||
cc->setDefaultSpawn(*ft, yaw, cc->getPitch());
|
|
||||||
cc->reset();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unstuck to nearest graveyard (WorldSafeLocs.dbc)
|
// /unstuckgy — snap upward 50m to clear all WMO geometry, gravity re-settles onto terrain
|
||||||
gameHandler->setUnstuckGyCallback([this]() {
|
gameHandler->setUnstuckGyCallback([this]() {
|
||||||
if (!renderer || !renderer->getCameraController() || !assetManager) return;
|
if (!renderer || !renderer->getCameraController()) return;
|
||||||
auto* cc = renderer->getCameraController();
|
auto* cc = renderer->getCameraController();
|
||||||
auto* ft = cc->getFollowTargetMutable();
|
auto* ft = cc->getFollowTargetMutable();
|
||||||
if (!ft) return;
|
if (!ft) return;
|
||||||
|
|
||||||
// Hardcoded safe locations per map (canonical WoW coords)
|
// Try last safe position first (nearby, terrain already loaded)
|
||||||
uint32_t mapId = gameHandler ? gameHandler->getCurrentMapId() : 0;
|
if (cc->hasLastSafePosition()) {
|
||||||
glm::vec3 safeCanonical;
|
glm::vec3 safePos = cc->getLastSafePosition();
|
||||||
switch (mapId) {
|
safePos.z += 5.0f;
|
||||||
case 0: safeCanonical = glm::vec3(-8833.38f, 628.63f, 94.0f); break; // Stormwind Trade District
|
cc->teleportTo(safePos);
|
||||||
case 1: safeCanonical = glm::vec3(1629.36f, -4373.34f, 31.2f); break; // Orgrimmar
|
LOG_INFO("Unstuck: teleported to last safe position");
|
||||||
case 530: safeCanonical = glm::vec3(-3961.64f, -13931.2f, 100.6f); break; // Shattrath
|
|
||||||
case 571: safeCanonical = glm::vec3(5804.14f, 624.77f, 647.8f); break; // Dalaran
|
|
||||||
default:
|
|
||||||
LOG_WARNING("No hardcoded safe location for map ", mapId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 safePos = core::coords::canonicalToRender(safeCanonical);
|
// No safe position — snap 50m upward to clear all WMO geometry
|
||||||
cc->setDefaultSpawn(safePos, cc->getYaw(), cc->getPitch());
|
glm::vec3 pos = *ft;
|
||||||
cc->teleportTo(safePos);
|
pos.z += 50.0f;
|
||||||
|
cc->teleportTo(pos);
|
||||||
|
LOG_INFO("Unstuck: snapped 50m upward");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bind point update (innkeeper)
|
// Auto-unstuck: falling for > 5 seconds = void fall, teleport to map entry
|
||||||
gameHandler->setBindPointCallback([this](uint32_t mapId, float x, float y, float z) {
|
if (renderer->getCameraController()) {
|
||||||
|
renderer->getCameraController()->setAutoUnstuckCallback([this]() {
|
||||||
if (!renderer || !renderer->getCameraController()) return;
|
if (!renderer || !renderer->getCameraController()) return;
|
||||||
glm::vec3 canonical(x, y, z);
|
auto* cc = renderer->getCameraController();
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(canonical);
|
|
||||||
renderer->getCameraController()->setDefaultSpawn(renderPos, 0.0f, 15.0f);
|
// Last resort: teleport to map entry point (terrain guaranteed loaded here)
|
||||||
|
glm::vec3 spawnPos = cc->getDefaultPosition();
|
||||||
|
spawnPos.z += 5.0f;
|
||||||
|
cc->teleportTo(spawnPos);
|
||||||
|
LOG_INFO("Auto-unstuck: teleported to map entry point");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind point update (innkeeper) — position stored in gameHandler->getHomeBind()
|
||||||
|
gameHandler->setBindPointCallback([this](uint32_t mapId, float x, float y, float z) {
|
||||||
LOG_INFO("Bindpoint set: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")");
|
LOG_INFO("Bindpoint set: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4494,14 +4494,14 @@ void GameHandler::useItemById(uint32_t itemId) {
|
||||||
void GameHandler::unstuck() {
|
void GameHandler::unstuck() {
|
||||||
if (unstuckCallback_) {
|
if (unstuckCallback_) {
|
||||||
unstuckCallback_();
|
unstuckCallback_();
|
||||||
addSystemChatMessage("Unstuck: moved 5 units forward.");
|
addSystemChatMessage("Unstuck: snapped upward. Use /unstuckgy for full teleport.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::unstuckGy() {
|
void GameHandler::unstuckGy() {
|
||||||
if (unstuckGyCallback_) {
|
if (unstuckGyCallback_) {
|
||||||
unstuckGyCallback_();
|
unstuckGyCallback_();
|
||||||
addSystemChatMessage("Unstuck: moved to nearest graveyard.");
|
addSystemChatMessage("Unstuck: teleported to safe location.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -654,6 +654,8 @@ void CameraController::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groundH) {
|
if (groundH) {
|
||||||
|
hasRealGround_ = true;
|
||||||
|
noGroundTimer_ = 0.0f;
|
||||||
float groundDiff = *groundH - lastGroundZ;
|
float groundDiff = *groundH - lastGroundZ;
|
||||||
if (groundDiff > 2.0f) {
|
if (groundDiff > 2.0f) {
|
||||||
// Landing on a higher ledge - snap up
|
// Landing on a higher ledge - snap up
|
||||||
|
|
@ -674,16 +676,45 @@ void CameraController::update(float deltaTime) {
|
||||||
grounded = false;
|
grounded = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No terrain found — hold at last known ground
|
hasRealGround_ = false;
|
||||||
|
noGroundTimer_ += deltaTime;
|
||||||
|
if (noGroundTimer_ < NO_GROUND_GRACE) {
|
||||||
|
// Brief grace period for terrain streaming — hold position
|
||||||
targetPos.z = lastGroundZ;
|
targetPos.z = lastGroundZ;
|
||||||
verticalVelocity = 0.0f;
|
verticalVelocity = 0.0f;
|
||||||
grounded = true;
|
grounded = true;
|
||||||
|
} else {
|
||||||
|
// No geometry found for too long — let player fall
|
||||||
|
grounded = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update follow target position
|
// Update follow target position
|
||||||
*followTarget = targetPos;
|
*followTarget = targetPos;
|
||||||
|
|
||||||
|
// --- Safe position caching + void fall detection ---
|
||||||
|
if (grounded && hasRealGround_ && !swimming && verticalVelocity >= 0.0f) {
|
||||||
|
// Player is safely on real geometry — save periodically
|
||||||
|
continuousFallTime_ = 0.0f;
|
||||||
|
autoUnstuckFired_ = false;
|
||||||
|
safePosSaveTimer_ += deltaTime;
|
||||||
|
if (safePosSaveTimer_ >= SAFE_POS_SAVE_INTERVAL) {
|
||||||
|
safePosSaveTimer_ = 0.0f;
|
||||||
|
lastSafePos_ = targetPos;
|
||||||
|
hasLastSafe_ = true;
|
||||||
|
}
|
||||||
|
} else if (!grounded && !swimming && !externalFollow_) {
|
||||||
|
// Falling (or standing on nothing past grace period) — accumulate fall time
|
||||||
|
continuousFallTime_ += deltaTime;
|
||||||
|
if (continuousFallTime_ >= AUTO_UNSTUCK_FALL_TIME && !autoUnstuckFired_) {
|
||||||
|
autoUnstuckFired_ = true;
|
||||||
|
if (autoUnstuckCallback_) {
|
||||||
|
autoUnstuckCallback_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== WoW-style orbit camera =====
|
// ===== WoW-style orbit camera =====
|
||||||
// Pivot point at upper chest/neck
|
// Pivot point at upper chest/neck
|
||||||
float mountedOffset = mounted_ ? mountHeightOffset_ : 0.0f;
|
float mountedOffset = mounted_ ? mountHeightOffset_ : 0.0f;
|
||||||
|
|
@ -1100,6 +1131,8 @@ void CameraController::reset() {
|
||||||
swimming = false;
|
swimming = false;
|
||||||
sitting = false;
|
sitting = false;
|
||||||
autoRunning = false;
|
autoRunning = false;
|
||||||
|
noGroundTimer_ = 0.0f;
|
||||||
|
autoUnstuckFired_ = false;
|
||||||
|
|
||||||
// Clear edge-state so movement packets can re-start cleanly after respawn.
|
// Clear edge-state so movement packets can re-start cleanly after respawn.
|
||||||
wasMovingForward = false;
|
wasMovingForward = false;
|
||||||
|
|
@ -1293,7 +1326,11 @@ void CameraController::teleportTo(const glm::vec3& pos) {
|
||||||
verticalVelocity = 0.0f;
|
verticalVelocity = 0.0f;
|
||||||
grounded = true;
|
grounded = true;
|
||||||
swimming = false;
|
swimming = false;
|
||||||
|
sitting = false;
|
||||||
lastGroundZ = pos.z;
|
lastGroundZ = pos.z;
|
||||||
|
noGroundTimer_ = 0.0f; // Reset grace period so terrain has time to stream
|
||||||
|
autoUnstuckFired_ = false;
|
||||||
|
continuousFallTime_ = 0.0f;
|
||||||
|
|
||||||
if (thirdPerson && followTarget) {
|
if (thirdPerson && followTarget) {
|
||||||
*followTarget = pos;
|
*followTarget = pos;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue