mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
physics: implement knockback simulation from SMSG_MOVE_KNOCK_BACK
Previously the handler ACKed with current position and ignored the velocity fields entirely (vcos/vsin/hspeed/vspeed were [[maybe_unused]]). The server expects the client to fly through the air on knockback — without simulation the player stays in place while the server models them as airborne, causing position desync and rubberbanding. Changes: - CameraController: add applyKnockBack(vcos, vsin, hspeed, vspeed) that sets knockbackHorizVel_ and launches verticalVelocity = -vspeed (server sends vspeed as negative for upward launches, matching TrinityCore) - Physics loop: each tick adds knockbackHorizVel_ to targetPos then applies exponential drag (KNOCKBACK_HORIZ_DRAG=4.5/s) until velocity < 0.05 u/s - GameHandler: parse all four fields, add KnockBackCallback, call it for the local player so the camera controller receives the impulse - Application: register the callback — routes server knockback to physics The existing ACK path is unchanged; the server gets position confirmation as before while the client now actually simulates the trajectory.
This commit is contained in:
parent
dd3f9e5b9e
commit
c622fde7be
5 changed files with 67 additions and 5 deletions
|
|
@ -693,6 +693,11 @@ public:
|
||||||
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z, bool isInitialEntry)>;
|
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z, bool isInitialEntry)>;
|
||||||
void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); }
|
void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Knockback callback: called when server sends SMSG_MOVE_KNOCK_BACK for the player.
|
||||||
|
// Parameters: vcos, vsin (render-space direction), hspeed, vspeed (raw from packet).
|
||||||
|
using KnockBackCallback = std::function<void(float vcos, float vsin, float hspeed, float vspeed)>;
|
||||||
|
void setKnockBackCallback(KnockBackCallback cb) { knockBackCallback_ = std::move(cb); }
|
||||||
|
|
||||||
// Unstuck callback (resets player Z to floor height)
|
// Unstuck callback (resets player Z to floor height)
|
||||||
using UnstuckCallback = std::function<void()>;
|
using UnstuckCallback = std::function<void()>;
|
||||||
void setUnstuckCallback(UnstuckCallback cb) { unstuckCallback_ = std::move(cb); }
|
void setUnstuckCallback(UnstuckCallback cb) { unstuckCallback_ = std::move(cb); }
|
||||||
|
|
@ -1812,6 +1817,7 @@ private:
|
||||||
|
|
||||||
// ---- Phase 3: Spells ----
|
// ---- Phase 3: Spells ----
|
||||||
WorldEntryCallback worldEntryCallback_;
|
WorldEntryCallback worldEntryCallback_;
|
||||||
|
KnockBackCallback knockBackCallback_;
|
||||||
UnstuckCallback unstuckCallback_;
|
UnstuckCallback unstuckCallback_;
|
||||||
UnstuckCallback unstuckGyCallback_;
|
UnstuckCallback unstuckGyCallback_;
|
||||||
UnstuckCallback unstuckHearthCallback_;
|
UnstuckCallback unstuckHearthCallback_;
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,12 @@ public:
|
||||||
// Trigger mount jump (applies vertical velocity for physics hop)
|
// Trigger mount jump (applies vertical velocity for physics hop)
|
||||||
void triggerMountJump();
|
void triggerMountJump();
|
||||||
|
|
||||||
|
// Apply server-driven knockback impulse.
|
||||||
|
// dir: render-space 2D direction unit vector (from vcos/vsin in packet)
|
||||||
|
// hspeed: horizontal speed magnitude (units/s)
|
||||||
|
// vspeed: raw packet vspeed field (server sends negative for upward launch)
|
||||||
|
void applyKnockBack(float vcos, float vsin, float hspeed, float vspeed);
|
||||||
|
|
||||||
// For first-person player hiding
|
// For first-person player hiding
|
||||||
void setCharacterRenderer(class CharacterRenderer* cr, uint32_t playerId) {
|
void setCharacterRenderer(class CharacterRenderer* cr, uint32_t playerId) {
|
||||||
characterRenderer = cr;
|
characterRenderer = cr;
|
||||||
|
|
@ -313,6 +319,14 @@ private:
|
||||||
float cachedFloorHeight_ = 0.0f;
|
float cachedFloorHeight_ = 0.0f;
|
||||||
bool hasCachedFloor_ = false;
|
bool hasCachedFloor_ = false;
|
||||||
static constexpr float COLLISION_CACHE_DISTANCE = 0.15f; // Re-check every 15cm
|
static constexpr float COLLISION_CACHE_DISTANCE = 0.15f; // Re-check every 15cm
|
||||||
|
|
||||||
|
// Server-driven knockback state.
|
||||||
|
// When the server sends SMSG_MOVE_KNOCK_BACK, we apply horizontal + vertical
|
||||||
|
// impulse here and let the normal physics loop (gravity, collision) resolve it.
|
||||||
|
bool knockbackActive_ = false;
|
||||||
|
glm::vec2 knockbackHorizVel_ = glm::vec2(0.0f); // render-space horizontal velocity (units/s)
|
||||||
|
// Horizontal velocity decays via WoW-like drag so the player doesn't slide forever.
|
||||||
|
static constexpr float KNOCKBACK_HORIZ_DRAG = 4.5f; // exponential decay rate (1/s)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rendering
|
} // namespace rendering
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,11 @@ void Application::setState(AppState newState) {
|
||||||
renderer->triggerMeleeSwing();
|
renderer->triggerMeleeSwing();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gameHandler->setKnockBackCallback([this](float vcos, float vsin, float hspeed, float vspeed) {
|
||||||
|
if (renderer && renderer->getCameraController()) {
|
||||||
|
renderer->getCameraController()->applyKnockBack(vcos, vsin, hspeed, vspeed);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Load quest marker models
|
// Load quest marker models
|
||||||
loadQuestMarkerModels();
|
loadQuestMarkerModels();
|
||||||
|
|
|
||||||
|
|
@ -11491,16 +11491,23 @@ void GameHandler::handleMoveKnockBack(network::Packet& packet) {
|
||||||
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
||||||
if (packet.getSize() - packet.getReadPos() < 20) return; // counter(4) + vcos(4) + vsin(4) + hspeed(4) + vspeed(4)
|
if (packet.getSize() - packet.getReadPos() < 20) return; // counter(4) + vcos(4) + vsin(4) + hspeed(4) + vspeed(4)
|
||||||
uint32_t counter = packet.readUInt32();
|
uint32_t counter = packet.readUInt32();
|
||||||
[[maybe_unused]] float vcos = packet.readFloat();
|
float vcos = packet.readFloat();
|
||||||
[[maybe_unused]] float vsin = packet.readFloat();
|
float vsin = packet.readFloat();
|
||||||
[[maybe_unused]] float hspeed = packet.readFloat();
|
float hspeed = packet.readFloat();
|
||||||
[[maybe_unused]] float vspeed = packet.readFloat();
|
float vspeed = packet.readFloat();
|
||||||
|
|
||||||
LOG_INFO("SMSG_MOVE_KNOCK_BACK: guid=0x", std::hex, guid, std::dec,
|
LOG_INFO("SMSG_MOVE_KNOCK_BACK: guid=0x", std::hex, guid, std::dec,
|
||||||
" counter=", counter, " hspeed=", hspeed, " vspeed=", vspeed);
|
" counter=", counter, " vcos=", vcos, " vsin=", vsin,
|
||||||
|
" hspeed=", hspeed, " vspeed=", vspeed);
|
||||||
|
|
||||||
if (guid != playerGuid) return;
|
if (guid != playerGuid) return;
|
||||||
|
|
||||||
|
// Apply knockback physics locally so the player visually flies through the air.
|
||||||
|
// The callback forwards to CameraController::applyKnockBack().
|
||||||
|
if (knockBackCallback_) {
|
||||||
|
knockBackCallback_(vcos, vsin, hspeed, vspeed);
|
||||||
|
}
|
||||||
|
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
uint16_t ackWire = wireOpcode(Opcode::CMSG_MOVE_KNOCK_BACK_ACK);
|
uint16_t ackWire = wireOpcode(Opcode::CMSG_MOVE_KNOCK_BACK_ACK);
|
||||||
if (ackWire == 0xFFFF) return;
|
if (ackWire == 0xFFFF) return;
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,20 @@ void CameraController::update(float deltaTime) {
|
||||||
targetPos += movement * speed * physicsDeltaTime;
|
targetPos += movement * speed * physicsDeltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply server-driven knockback horizontal velocity (decays over time).
|
||||||
|
if (knockbackActive_) {
|
||||||
|
targetPos.x += knockbackHorizVel_.x * physicsDeltaTime;
|
||||||
|
targetPos.y += knockbackHorizVel_.y * physicsDeltaTime;
|
||||||
|
// Exponential drag: reduce each frame so the player decelerates naturally.
|
||||||
|
float drag = std::exp(-KNOCKBACK_HORIZ_DRAG * physicsDeltaTime);
|
||||||
|
knockbackHorizVel_ *= drag;
|
||||||
|
// Once negligible, clear the flag so collision/grounding work normally.
|
||||||
|
if (glm::length(knockbackHorizVel_) < 0.05f) {
|
||||||
|
knockbackActive_ = false;
|
||||||
|
knockbackHorizVel_ = glm::vec2(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Jump with input buffering and coyote time
|
// Jump with input buffering and coyote time
|
||||||
if (nowJump) jumpBufferTimer = JUMP_BUFFER_TIME;
|
if (nowJump) jumpBufferTimer = JUMP_BUFFER_TIME;
|
||||||
if (grounded) coyoteTimer = COYOTE_TIME;
|
if (grounded) coyoteTimer = COYOTE_TIME;
|
||||||
|
|
@ -2096,5 +2110,21 @@ void CameraController::triggerMountJump() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CameraController::applyKnockBack(float vcos, float vsin, float hspeed, float vspeed) {
|
||||||
|
// The server sends (vcos, vsin) as the 2D direction vector in server/wire
|
||||||
|
// coordinate space. After the server→canonical→render swaps, the direction
|
||||||
|
// in render space is simply (vcos, vsin) — the two swaps cancel each other.
|
||||||
|
knockbackHorizVel_ = glm::vec2(vcos, vsin) * hspeed;
|
||||||
|
knockbackActive_ = true;
|
||||||
|
|
||||||
|
// vspeed in the wire packet is negative when the server wants to launch the
|
||||||
|
// player upward (matches TrinityCore: data << float(-speedZ)). Negate it
|
||||||
|
// here to obtain the correct upward initial velocity.
|
||||||
|
verticalVelocity = -vspeed;
|
||||||
|
grounded = false;
|
||||||
|
coyoteTimer = 0.0f;
|
||||||
|
jumpBufferTimer = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rendering
|
} // namespace rendering
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue