physics: disable gravity when server sends SMSG_MOVE_GRAVITY_DISABLE

SMSG_MOVE_GRAVITY_DISABLE/ENABLE now correctly set/clear the LEVITATING
movement flag instead of passing flag=0. GameHandler::isGravityDisabled()
reads the LEVITATING bit and is synced to CameraController each frame.

When gravity is disabled the physics loop bleeds off downward velocity
and skips gravity accumulation, so Levitate and similar effects actually
float the player rather than letting them fall through the world.
This commit is contained in:
Kelsi 2026-03-10 13:07:34 -07:00
parent dd6f6d1174
commit f2337aeaa7
5 changed files with 19 additions and 4 deletions

View file

@ -1155,6 +1155,9 @@ public:
bool isPlayerRooted() const { bool isPlayerRooted() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::ROOT)) != 0; return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::ROOT)) != 0;
} }
bool isGravityDisabled() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::LEVITATING)) != 0;
}
void dismount(); void dismount();
// Taxi / Flight Paths // Taxi / Flight Paths

View file

@ -94,6 +94,7 @@ public:
void setRunSpeedOverride(float speed) { runSpeedOverride_ = speed; } void setRunSpeedOverride(float speed) { runSpeedOverride_ = speed; }
void setMovementRooted(bool rooted) { movementRooted_ = rooted; } void setMovementRooted(bool rooted) { movementRooted_ = rooted; }
bool isMovementRooted() const { return movementRooted_; } bool isMovementRooted() const { return movementRooted_; }
void setGravityDisabled(bool disabled) { gravityDisabled_ = disabled; }
void setMounted(bool m) { mounted_ = m; } void setMounted(bool m) { mounted_ = m; }
void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; } void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; }
void setExternalFollow(bool enabled) { externalFollow_ = enabled; } void setExternalFollow(bool enabled) { externalFollow_ = enabled; }
@ -272,6 +273,8 @@ private:
float runSpeedOverride_ = 0.0f; float runSpeedOverride_ = 0.0f;
// Server-driven root state: when true, block all horizontal movement input. // Server-driven root state: when true, block all horizontal movement input.
bool movementRooted_ = false; bool movementRooted_ = false;
// Server-driven gravity disable (levitate/hover): skip gravity accumulation.
bool gravityDisabled_ = false;
bool mounted_ = false; bool mounted_ = false;
float mountHeightOffset_ = 0.0f; float mountHeightOffset_ = 0.0f;
bool externalMoving_ = false; bool externalMoving_ = false;

View file

@ -1010,6 +1010,7 @@ void Application::update(float deltaTime) {
if (renderer && gameHandler && renderer->getCameraController()) { if (renderer && gameHandler && renderer->getCameraController()) {
renderer->getCameraController()->setRunSpeedOverride(gameHandler->getServerRunSpeed()); renderer->getCameraController()->setRunSpeedOverride(gameHandler->getServerRunSpeed());
renderer->getCameraController()->setMovementRooted(gameHandler->isPlayerRooted()); renderer->getCameraController()->setMovementRooted(gameHandler->isPlayerRooted());
renderer->getCameraController()->setGravityDisabled(gameHandler->isGravityDisabled());
} }
bool onTaxi = gameHandler && bool onTaxi = gameHandler &&

View file

@ -5579,10 +5579,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
// ---- Player movement flag changes (server-pushed) ---- // ---- Player movement flag changes (server-pushed) ----
case Opcode::SMSG_MOVE_GRAVITY_DISABLE: case Opcode::SMSG_MOVE_GRAVITY_DISABLE:
handleForceMoveFlagChange(packet, "GRAVITY_DISABLE", Opcode::CMSG_MOVE_GRAVITY_DISABLE_ACK, 0, true); handleForceMoveFlagChange(packet, "GRAVITY_DISABLE", Opcode::CMSG_MOVE_GRAVITY_DISABLE_ACK,
static_cast<uint32_t>(MovementFlags::LEVITATING), true);
break; break;
case Opcode::SMSG_MOVE_GRAVITY_ENABLE: case Opcode::SMSG_MOVE_GRAVITY_ENABLE:
handleForceMoveFlagChange(packet, "GRAVITY_ENABLE", Opcode::CMSG_MOVE_GRAVITY_ENABLE_ACK, 0, true); handleForceMoveFlagChange(packet, "GRAVITY_ENABLE", Opcode::CMSG_MOVE_GRAVITY_ENABLE_ACK,
static_cast<uint32_t>(MovementFlags::LEVITATING), false);
break; break;
case Opcode::SMSG_MOVE_LAND_WALK: case Opcode::SMSG_MOVE_LAND_WALK:
handleForceMoveFlagChange(packet, "LAND_WALK", Opcode::CMSG_MOVE_WATER_WALK_ACK, 0, false); handleForceMoveFlagChange(packet, "LAND_WALK", Opcode::CMSG_MOVE_WATER_WALK_ACK, 0, false);

View file

@ -717,8 +717,14 @@ void CameraController::update(float deltaTime) {
jumpBufferTimer -= physicsDeltaTime; jumpBufferTimer -= physicsDeltaTime;
coyoteTimer -= physicsDeltaTime; coyoteTimer -= physicsDeltaTime;
// Apply gravity // Apply gravity (skip when server has disabled gravity, e.g. Levitate spell)
verticalVelocity += gravity * physicsDeltaTime; if (gravityDisabled_) {
// Float in place: bleed off any downward velocity, allow upward to decay slowly
if (verticalVelocity < 0.0f) verticalVelocity = 0.0f;
else verticalVelocity *= std::max(0.0f, 1.0f - 3.0f * physicsDeltaTime);
} else {
verticalVelocity += gravity * physicsDeltaTime;
}
targetPos.z += verticalVelocity * physicsDeltaTime; targetPos.z += verticalVelocity * physicsDeltaTime;
} }
} else { } else {