mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-07-31 18:00:46 -04:00
feat(movement): make /follow actually walk toward the target
/follow already existed but was camera-only: it set followTargetGuid_/ followRenderPos_ and told the camera controller to track the target's render position each frame, but the character itself never moved. Adds MovementHandler::updateFollowMovement(), called from the same per-frame block that already refreshes followRenderPos_ in GameHandler::update(), so no new per-frame wiring is needed. It's the same straight-line step the headless client's single-shot /movement/goto uses (updateMovementTask() in tools/headless_client/main.cpp), but the target position is read fresh from the live entity every call instead of a stored waypoint (so it re-chases a moving target, and never needs that command's long-distance Z-interpolation guard), and it stops at a fixed distance (1 yard, matching retail's tight follow) instead of snapping onto the target. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,14 @@ public:
|
||||
// Follow target (moved from GameHandler)
|
||||
void followTarget();
|
||||
void cancelFollow();
|
||||
// Per-frame movement toward the followed target's live position, called
|
||||
// from GameHandler::update() alongside the existing followRenderPos_
|
||||
// refresh. A no-op when no follow target is set. Recomputes the target
|
||||
// position fresh every call (from the live entity, not a stored point),
|
||||
// so it doesn't need the long-distance Z-interpolation guard the
|
||||
// headless client's single-shot /movement/goto needs - there's never a
|
||||
// large stale remaining distance to interpolate across.
|
||||
void updateFollowMovement(float deltaTime);
|
||||
|
||||
// Area trigger detection
|
||||
void loadAreaTriggerDbc();
|
||||
@@ -287,6 +295,8 @@ private:
|
||||
uint32_t knownTaxiMask_[12] = {};
|
||||
bool taxiMaskInitialized_ = false;
|
||||
std::unordered_map<uint32_t, uint32_t> taxiCostMap_;
|
||||
|
||||
bool followMoveMoving_ = false; // whether MSG_MOVE_START_FORWARD has been sent for the current follow
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
||||
@@ -813,6 +813,7 @@ void GameHandler::update(float deltaTime) {
|
||||
if (followEnt) {
|
||||
followRenderPos_ = core::coords::canonicalToRender(
|
||||
glm::vec3(followEnt->getX(), followEnt->getY(), followEnt->getZ()));
|
||||
if (movementHandler_) movementHandler_->updateFollowMovement(deltaTime);
|
||||
} else {
|
||||
cancelFollow();
|
||||
}
|
||||
|
||||
@@ -3085,6 +3085,10 @@ void MovementHandler::cancelFollow() {
|
||||
return;
|
||||
}
|
||||
owner_.followTargetGuidRef() = 0;
|
||||
if (followMoveMoving_) {
|
||||
sendMovement(Opcode::MSG_MOVE_STOP);
|
||||
followMoveMoving_ = false;
|
||||
}
|
||||
if (owner_.autoFollowCallbackRef()) {
|
||||
owner_.autoFollowCallbackRef()(nullptr);
|
||||
}
|
||||
@@ -3092,5 +3096,65 @@ void MovementHandler::cancelFollow() {
|
||||
owner_.fireAddonEvent("AUTOFOLLOW_END", {});
|
||||
}
|
||||
|
||||
// Same straight-line step used by the headless client's single-shot
|
||||
// /movement/goto (updateMovementTask() in tools/headless_client/main.cpp),
|
||||
// but the target position is read fresh from the live entity every call
|
||||
// instead of a stored waypoint, and stops at kFollowStopDistance rather
|
||||
// than snapping onto the target.
|
||||
void MovementHandler::updateFollowMovement(float deltaTime) {
|
||||
const uint64_t guid = owner_.followTargetGuidRef();
|
||||
if (guid == 0) {
|
||||
return;
|
||||
}
|
||||
if (owner_.getState() != WorldState::IN_WORLD) {
|
||||
return;
|
||||
}
|
||||
if (isPlayerRooted() || !isServerMovementAllowed() || isOnTaxiFlight()) {
|
||||
if (followMoveMoving_) {
|
||||
sendMovement(Opcode::MSG_MOVE_STOP);
|
||||
followMoveMoving_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto target = owner_.getEntityManager().getEntity(guid);
|
||||
if (!target) {
|
||||
// cancelFollow() (called from GameHandler::update()'s
|
||||
// followRenderPos_ refresh) handles the disappeared-target case;
|
||||
// just stay quiet here rather than duplicate that check.
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr float kFollowStopDistance = 1.0f;
|
||||
const float dx = target->getX() - movementInfo.x;
|
||||
const float dy = target->getY() - movementInfo.y;
|
||||
const float dz = target->getZ() - movementInfo.z;
|
||||
const float horizontalDist = std::sqrt(dx * dx + dy * dy);
|
||||
const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (distance <= kFollowStopDistance) {
|
||||
if (followMoveMoving_) {
|
||||
sendMovement(Opcode::MSG_MOVE_STOP);
|
||||
followMoveMoving_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (horizontalDist > 0.001f) {
|
||||
setOrientation(std::atan2(-dy, dx));
|
||||
sendMovement(Opcode::MSG_MOVE_SET_FACING);
|
||||
}
|
||||
if (!followMoveMoving_) {
|
||||
sendMovement(Opcode::MSG_MOVE_START_FORWARD);
|
||||
followMoveMoving_ = true;
|
||||
}
|
||||
|
||||
const float speed = std::max(0.1f, getServerRunSpeed());
|
||||
const float step = std::min(distance - kFollowStopDistance, speed * std::max(0.0f, deltaTime));
|
||||
const float t = distance > 0.001f ? (step / distance) : 0.0f;
|
||||
setPosition(movementInfo.x + dx * t, movementInfo.y + dy * t, movementInfo.z + dz * t);
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
||||
@@ -189,7 +189,7 @@ public:
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"follow", "f"}; }
|
||||
std::string helpText() const override { return "Follow target"; }
|
||||
std::string helpText() const override { return "Walk toward and camera-follow your current target"; }
|
||||
};
|
||||
|
||||
// --- /stopfollow ---
|
||||
|
||||
Reference in New Issue
Block a user