mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 15:53:51 +00:00
fix(movement): multi-segment path interpolation, waypoint parsing & terrain Z clamping
Add proper waypoint support to entity movement: - Parse intermediate waypoints from MonsterMove packets in both WotLK and Vanilla paths. Uncompressed paths store absolute float3 waypoints; compressed paths decode TrinityCore's packed uint32 deltas (11-bit signed x/y, 10-bit signed z, ×0.25 scale, waypoint = midpoint − delta) with correct 2's-complement sign extension. - Entity::startMoveAlongPath() interpolates along cumulative-distance- proportional segments instead of a single straight line. - MovementHandler builds the full path (start → waypoints → destination) in canonical coords and dispatches to startMoveAlongPath() when waypoints are present. - Snap entity x/y/z to moveEnd in the dead-reckoning overrun phase before starting a new movement, preventing visible teleports when the renderer was showing the entity at its destination. - Clamp creature and player entity Z to the terrain surface via TerrainManager::getHeightAt() during active movement. Idle entities keep their server-authoritative Z to avoid breaking flight masters, elevator riders, etc. Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
parent
e07983b7f6
commit
5b47d034c5
5 changed files with 182 additions and 19 deletions
|
|
@ -1756,6 +1756,17 @@ void Application::update(float deltaTime) {
|
|||
inOverrun ? entity->getLatestZ() : entity->getZ());
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(canonical);
|
||||
|
||||
// Clamp creature Z to terrain surface during movement interpolation.
|
||||
// The server sends single-segment moves and expects the client to place
|
||||
// creatures on the ground. Only clamp while actively moving — idle
|
||||
// creatures keep their server-authoritative Z (flight masters, etc.).
|
||||
if (entity->isActivelyMoving() && renderer->getTerrainManager()) {
|
||||
auto terrainZ = renderer->getTerrainManager()->getHeightAt(renderPos.x, renderPos.y);
|
||||
if (terrainZ.has_value()) {
|
||||
renderPos.z = terrainZ.value();
|
||||
}
|
||||
}
|
||||
|
||||
// Visual collision guard: keep hostile melee units from rendering inside the
|
||||
// player's model while attacking. This is client-side only (no server position change).
|
||||
// Only check for creatures within 8 units (melee range) — saves expensive
|
||||
|
|
@ -1959,6 +1970,14 @@ void Application::update(float deltaTime) {
|
|||
inOverrun ? entity->getLatestZ() : entity->getZ());
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(canonical);
|
||||
|
||||
// Clamp other players' Z to terrain surface during movement
|
||||
if (entity->isActivelyMoving() && renderer->getTerrainManager()) {
|
||||
auto terrainZ = renderer->getTerrainManager()->getHeightAt(renderPos.x, renderPos.y);
|
||||
if (terrainZ.has_value()) {
|
||||
renderPos.z = terrainZ.value();
|
||||
}
|
||||
}
|
||||
|
||||
auto posIt = _pCreatureRenderPosCache.find(guid);
|
||||
if (posIt == _pCreatureRenderPosCache.end()) {
|
||||
charRenderer->setInstancePosition(instanceId, renderPos);
|
||||
|
|
|
|||
|
|
@ -1454,8 +1454,23 @@ void MovementHandler::handleMonsterMove(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
entity->startMoveTo(destCanonical.x, destCanonical.y, destCanonical.z,
|
||||
orientation, data.duration / 1000.0f);
|
||||
// Build full path: start → waypoints → destination (all in canonical coords)
|
||||
if (!data.waypoints.empty()) {
|
||||
glm::vec3 startCanonical = core::coords::serverToCanonical(
|
||||
glm::vec3(data.x, data.y, data.z));
|
||||
std::vector<std::array<float, 3>> path;
|
||||
path.push_back({startCanonical.x, startCanonical.y, startCanonical.z});
|
||||
for (const auto& wp : data.waypoints) {
|
||||
glm::vec3 wpCanonical = core::coords::serverToCanonical(
|
||||
glm::vec3(wp.x, wp.y, wp.z));
|
||||
path.push_back({wpCanonical.x, wpCanonical.y, wpCanonical.z});
|
||||
}
|
||||
path.push_back({destCanonical.x, destCanonical.y, destCanonical.z});
|
||||
entity->startMoveAlongPath(path, orientation, data.duration / 1000.0f);
|
||||
} else {
|
||||
entity->startMoveTo(destCanonical.x, destCanonical.y, destCanonical.z,
|
||||
orientation, data.duration / 1000.0f);
|
||||
}
|
||||
|
||||
if (owner_.creatureMoveCallbackRef()) {
|
||||
owner_.creatureMoveCallbackRef()(data.guid,
|
||||
|
|
|
|||
|
|
@ -637,13 +637,15 @@ bool MonsterMoveParser::parse(network::Packet& packet, MonsterMoveData& data) {
|
|||
bool uncompressed = (data.splineFlags & (0x00080000 | 0x00002000)) != 0;
|
||||
|
||||
if (uncompressed) {
|
||||
// Read last point as destination
|
||||
// Skip to last point: each point is 12 bytes
|
||||
if (pointCount > 1) {
|
||||
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
||||
if (!packet.hasRemaining(12)) return true;
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
||||
}
|
||||
// All waypoints stored as absolute float3 (Catmullrom/Flying paths)
|
||||
// Read all intermediate points, then the final destination
|
||||
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
||||
if (!packet.hasRemaining(12)) return true;
|
||||
MonsterMoveData::Point wp;
|
||||
wp.x = packet.readFloat();
|
||||
wp.y = packet.readFloat();
|
||||
wp.z = packet.readFloat();
|
||||
data.waypoints.push_back(wp);
|
||||
}
|
||||
if (!packet.hasRemaining(12)) return true;
|
||||
data.destX = packet.readFloat();
|
||||
|
|
@ -657,6 +659,33 @@ bool MonsterMoveParser::parse(network::Packet& packet, MonsterMoveData& data) {
|
|||
data.destY = packet.readFloat();
|
||||
data.destZ = packet.readFloat();
|
||||
data.hasDest = true;
|
||||
|
||||
// Remaining waypoints are packed as uint32 deltas from the midpoint
|
||||
// between the creature's start position and the destination.
|
||||
// Encoding matches TrinityCore MoveSpline::PackXYZ:
|
||||
// x = 11-bit signed (bits 0-10), y = 11-bit signed (bits 11-21),
|
||||
// z = 10-bit signed (bits 22-31), each scaled by 0.25 units.
|
||||
if (pointCount > 1) {
|
||||
float midX = (data.x + data.destX) * 0.5f;
|
||||
float midY = (data.y + data.destY) * 0.5f;
|
||||
float midZ = (data.z + data.destZ) * 0.5f;
|
||||
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t packed = packet.readUInt32();
|
||||
// Sign-extend 11-bit x and y, 10-bit z (2's complement)
|
||||
int32_t sx = static_cast<int32_t>(packed & 0x7FF);
|
||||
if (sx & 0x400) sx |= static_cast<int32_t>(0xFFFFF800);
|
||||
int32_t sy = static_cast<int32_t>((packed >> 11) & 0x7FF);
|
||||
if (sy & 0x400) sy |= static_cast<int32_t>(0xFFFFF800);
|
||||
int32_t sz = static_cast<int32_t>((packed >> 22) & 0x3FF);
|
||||
if (sz & 0x200) sz |= static_cast<int32_t>(0xFFFFFC00);
|
||||
MonsterMoveData::Point wp;
|
||||
wp.x = midX - static_cast<float>(sx) * 0.25f;
|
||||
wp.y = midY - static_cast<float>(sy) * 0.25f;
|
||||
wp.z = midZ - static_cast<float>(sz) * 0.25f;
|
||||
data.waypoints.push_back(wp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("MonsterMove: guid=0x", std::hex, data.guid, std::dec,
|
||||
|
|
@ -754,12 +783,26 @@ bool MonsterMoveParser::parseVanilla(network::Packet& packet, MonsterMoveData& d
|
|||
data.destZ = packet.readFloat();
|
||||
data.hasDest = true;
|
||||
|
||||
// Remaining waypoints are packed as uint32 deltas.
|
||||
// Remaining waypoints are packed as uint32 deltas from midpoint.
|
||||
if (pointCount > 1) {
|
||||
size_t skipBytes = static_cast<size_t>(pointCount - 1) * 4;
|
||||
size_t newPos = packet.getReadPos() + skipBytes;
|
||||
if (newPos > packet.getSize()) return false;
|
||||
packet.setReadPos(newPos);
|
||||
float midX = (data.x + data.destX) * 0.5f;
|
||||
float midY = (data.y + data.destY) * 0.5f;
|
||||
float midZ = (data.z + data.destZ) * 0.5f;
|
||||
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t packed = packet.readUInt32();
|
||||
int32_t sx = static_cast<int32_t>(packed & 0x7FF);
|
||||
if (sx & 0x400) sx |= static_cast<int32_t>(0xFFFFF800);
|
||||
int32_t sy = static_cast<int32_t>((packed >> 11) & 0x7FF);
|
||||
if (sy & 0x400) sy |= static_cast<int32_t>(0xFFFFF800);
|
||||
int32_t sz = static_cast<int32_t>((packed >> 22) & 0x3FF);
|
||||
if (sz & 0x200) sz |= static_cast<int32_t>(0xFFFFFC00);
|
||||
MonsterMoveData::Point wp;
|
||||
wp.x = midX - static_cast<float>(sx) * 0.25f;
|
||||
wp.y = midY - static_cast<float>(sy) * 0.25f;
|
||||
wp.z = midZ - static_cast<float>(sz) * 0.25f;
|
||||
data.waypoints.push_back(wp);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("MonsterMove(turtle): guid=0x", std::hex, data.guid, std::dec,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue