From 21604461fc0e9b7081c7a5a9996d1e67d598b9df Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 13:01:44 -0700 Subject: [PATCH] physics: block client-side movement when server roots the player When SMSG_FORCE_MOVE_ROOT sets ROOT in movementInfo.flags, the camera controller was not aware and continued to accept directional input. This caused position desync (client moves, server sees player as rooted). - Add movementRooted_ flag to CameraController with setter/getter. - Block nowForward/nowBackward/nowStrafe when movementRooted_ is set. - Sync isPlayerRooted() from GameHandler to CameraController each frame alongside the existing run-speed sync in application.cpp. - Add GameHandler::isPlayerRooted() convenience accessor. --- include/game/game_handler.hpp | 3 +++ include/rendering/camera_controller.hpp | 4 ++++ src/core/application.cpp | 1 + src/rendering/camera_controller.cpp | 6 ++++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index c333676a..8b39d3d9 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1152,6 +1152,9 @@ public: bool isMounted() const { return currentMountDisplayId_ != 0; } bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; } float getServerRunSpeed() const { return serverRunSpeed_; } + bool isPlayerRooted() const { + return (movementInfo.flags & static_cast(MovementFlags::ROOT)) != 0; + } void dismount(); // Taxi / Flight Paths diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index 679b2fa4..2c8baf3a 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -92,6 +92,8 @@ public: void setMovementCallback(MovementCallback cb) { movementCallback = std::move(cb); } void setUseWoWSpeed(bool use) { useWoWSpeed = use; } void setRunSpeedOverride(float speed) { runSpeedOverride_ = speed; } + void setMovementRooted(bool rooted) { movementRooted_ = rooted; } + bool isMovementRooted() const { return movementRooted_; } void setMounted(bool m) { mounted_ = m; } void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; } void setExternalFollow(bool enabled) { externalFollow_ = enabled; } @@ -268,6 +270,8 @@ private: // Server-driven run speed override (0 = use default WOW_RUN_SPEED) float runSpeedOverride_ = 0.0f; + // Server-driven root state: when true, block all horizontal movement input. + bool movementRooted_ = false; bool mounted_ = false; float mountHeightOffset_ = 0.0f; bool externalMoving_ = false; diff --git a/src/core/application.cpp b/src/core/application.cpp index 065912dc..7de5e2d9 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1009,6 +1009,7 @@ void Application::update(float deltaTime) { runInGameStage("post-update sync", [&] { if (renderer && gameHandler && renderer->getCameraController()) { renderer->getCameraController()->setRunSpeedOverride(gameHandler->getServerRunSpeed()); + renderer->getCameraController()->setMovementRooted(gameHandler->isPlayerRooted()); } bool onTaxi = gameHandler && diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 0e6f9f43..63935117 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -275,8 +275,10 @@ void CameraController::update(float deltaTime) { if (mouseAutorun) { autoRunning = false; } - bool nowForward = keyW || mouseAutorun || autoRunning; - bool nowBackward = keyS; + // When the server has rooted the player, suppress all horizontal movement input. + const bool movBlocked = movementRooted_; + bool nowForward = !movBlocked && (keyW || mouseAutorun || autoRunning); + bool nowBackward = !movBlocked && keyS; bool nowStrafeLeft = false; bool nowStrafeRight = false; bool nowTurnLeft = false;