mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33: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
|
|
@ -1,7 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
|
@ -76,14 +79,70 @@ public:
|
|||
z = pz;
|
||||
orientation = o;
|
||||
isMoving_ = false; // Instant position set cancels interpolation
|
||||
usePathMode_ = false;
|
||||
}
|
||||
|
||||
// Multi-segment path movement
|
||||
void startMoveAlongPath(const std::vector<std::array<float, 3>>& path, float destO, float totalDuration) {
|
||||
if (path.empty()) return;
|
||||
if (path.size() == 1 || totalDuration <= 0.0f) {
|
||||
startMoveTo(path.back()[0], path.back()[1], path.back()[2], destO, totalDuration);
|
||||
return;
|
||||
}
|
||||
// Compute cumulative distances for proportional segment timing
|
||||
pathPoints_ = path;
|
||||
pathSegDists_.resize(path.size());
|
||||
pathSegDists_[0] = 0.0f;
|
||||
float totalDist = 0.0f;
|
||||
for (size_t i = 1; i < path.size(); i++) {
|
||||
float dx = path[i][0] - path[i - 1][0];
|
||||
float dy = path[i][1] - path[i - 1][1];
|
||||
float dz = path[i][2] - path[i - 1][2];
|
||||
totalDist += std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
pathSegDists_[i] = totalDist;
|
||||
}
|
||||
if (totalDist < 0.001f) {
|
||||
startMoveTo(path.back()[0], path.back()[1], path.back()[2], destO, totalDuration);
|
||||
return;
|
||||
}
|
||||
// Snap position if in overrun phase
|
||||
if (isMoving_ && moveElapsed_ >= moveDuration_) {
|
||||
x = moveEndX_; y = moveEndY_; z = moveEndZ_;
|
||||
}
|
||||
moveEndX_ = path.back()[0]; moveEndY_ = path.back()[1]; moveEndZ_ = path.back()[2];
|
||||
moveDuration_ = totalDuration;
|
||||
moveElapsed_ = 0.0f;
|
||||
orientation = destO;
|
||||
isMoving_ = true;
|
||||
usePathMode_ = true;
|
||||
// Velocity for dead-reckoning after path completes
|
||||
float fromX = isMoving_ ? moveEndX_ : x;
|
||||
float fromY = isMoving_ ? moveEndY_ : y;
|
||||
float impliedVX = (path.back()[0] - fromX) / totalDuration;
|
||||
float impliedVY = (path.back()[1] - fromY) / totalDuration;
|
||||
float impliedVZ = (path.back()[2] - path[0][2]) / totalDuration;
|
||||
const float alpha = 0.65f;
|
||||
velX_ = alpha * impliedVX + (1.0f - alpha) * velX_;
|
||||
velY_ = alpha * impliedVY + (1.0f - alpha) * velY_;
|
||||
velZ_ = alpha * impliedVZ + (1.0f - alpha) * velZ_;
|
||||
}
|
||||
|
||||
// Movement interpolation (syncs entity position with renderer during movement)
|
||||
void startMoveTo(float destX, float destY, float destZ, float destO, float durationSec) {
|
||||
usePathMode_ = false;
|
||||
if (durationSec <= 0.0f) {
|
||||
setPosition(destX, destY, destZ, destO);
|
||||
return;
|
||||
}
|
||||
// If we're in the dead-reckoning overrun phase, snap x/y/z back to the
|
||||
// destination before using them as the new start. The renderer was showing
|
||||
// the entity at moveEnd (via getLatest) during overrun, so the new
|
||||
// interpolation must start there to avoid a visible teleport.
|
||||
if (isMoving_ && moveElapsed_ >= moveDuration_) {
|
||||
x = moveEndX_;
|
||||
y = moveEndY_;
|
||||
z = moveEndZ_;
|
||||
}
|
||||
// Derive velocity from the displacement this packet implies.
|
||||
// Use the previous destination (not current lerped pos) as the "from" so
|
||||
// variable network timing doesn't inflate/shrink the implied speed.
|
||||
|
|
@ -113,11 +172,31 @@ public:
|
|||
if (!isMoving_) return;
|
||||
moveElapsed_ += deltaTime;
|
||||
if (moveElapsed_ < moveDuration_) {
|
||||
// Linear interpolation within the packet window
|
||||
float t = moveElapsed_ / moveDuration_;
|
||||
x = moveStartX_ + (moveEndX_ - moveStartX_) * t;
|
||||
y = moveStartY_ + (moveEndY_ - moveStartY_) * t;
|
||||
z = moveStartZ_ + (moveEndZ_ - moveStartZ_) * t;
|
||||
if (usePathMode_ && pathPoints_.size() > 1) {
|
||||
// Multi-segment path interpolation
|
||||
float totalDist = pathSegDists_.back();
|
||||
float t = moveElapsed_ / moveDuration_;
|
||||
float targetDist = t * totalDist;
|
||||
// Find the segment containing targetDist
|
||||
size_t seg = 1;
|
||||
while (seg < pathSegDists_.size() - 1 && pathSegDists_[seg] < targetDist)
|
||||
seg++;
|
||||
float segStart = pathSegDists_[seg - 1];
|
||||
float segEnd = pathSegDists_[seg];
|
||||
float segLen = segEnd - segStart;
|
||||
float segT = (segLen > 0.001f) ? (targetDist - segStart) / segLen : 0.0f;
|
||||
const auto& p0 = pathPoints_[seg - 1];
|
||||
const auto& p1 = pathPoints_[seg];
|
||||
x = p0[0] + (p1[0] - p0[0]) * segT;
|
||||
y = p0[1] + (p1[1] - p0[1]) * segT;
|
||||
z = p0[2] + (p1[2] - p0[2]) * segT;
|
||||
} else {
|
||||
// Single-segment linear interpolation
|
||||
float t = moveElapsed_ / moveDuration_;
|
||||
x = moveStartX_ + (moveEndX_ - moveStartX_) * t;
|
||||
y = moveStartY_ + (moveEndY_ - moveStartY_) * t;
|
||||
z = moveStartZ_ + (moveEndZ_ - moveStartZ_) * t;
|
||||
}
|
||||
} else {
|
||||
// Past the interpolation window: dead-reckon at the smoothed velocity
|
||||
// rather than freezing in place. Cap to one extra interval so we don't
|
||||
|
|
@ -192,11 +271,15 @@ protected:
|
|||
|
||||
// Movement interpolation state
|
||||
bool isMoving_ = false;
|
||||
bool usePathMode_ = false;
|
||||
float moveStartX_ = 0, moveStartY_ = 0, moveStartZ_ = 0;
|
||||
float moveEndX_ = 0, moveEndY_ = 0, moveEndZ_ = 0;
|
||||
float moveDuration_ = 0;
|
||||
float moveElapsed_ = 0;
|
||||
float velX_ = 0, velY_ = 0, velZ_ = 0; // Smoothed velocity for dead reckoning
|
||||
// Multi-segment path data
|
||||
std::vector<std::array<float, 3>> pathPoints_;
|
||||
std::vector<float> pathSegDists_; // Cumulative distances for each waypoint
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1678,6 +1678,9 @@ struct MonsterMoveData {
|
|||
// Destination (final point of the spline, server coords)
|
||||
float destX = 0, destY = 0, destZ = 0;
|
||||
bool hasDest = false;
|
||||
// Intermediate waypoints along the path (server coords, excludes start & final dest)
|
||||
struct Point { float x, y, z; };
|
||||
std::vector<Point> waypoints;
|
||||
};
|
||||
|
||||
class MonsterMoveParser {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue