- A creature that was already walking when it came into view stood frozen at its spawn point, then teleported — up to a full wander leg, 30 yards in the worst measured case — when its next movement packet arrived. The create packet carries the live path (where the unit is along it, how much is left) and we parsed all of it into the void. The unit now joins its walk exactly where the server has it: measured before/after, 13% of movement starts were over-a-yard teleports, now effectively zero. - Walking into Zul'Gurub or Stratholme through the portal dropped you straight through the world. The landing check asks "is there a floor under me?" — but on the arrival frame the loaded world is still the map you just left, and an instance entrance is a copy of its outdoor ground at the same coordinates, 0.23 yards under your feet. The old floor said yes, then despawned. The hold now waits for the destination's own ground to arrive, and a world that never arrives keeps the loading screen instead of dropping you into the void. - Same fix covers a decoder hazard found on the way: a plain two-point path announces two points but ships only the destination, and trusting the count over-ran the packet — it never fires on our server's pathfinder, but now it can't. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.6 KiB
Rust
75 lines
3.6 KiB
Rust
//! Per-frame motion model for streamed units: server splines + remote-player dead-reckoning.
|
|
//!
|
|
//! Two motion sources drive an entity's [`Transform`] between the sparse server packets (decisions 0052
|
|
//! + 0053):
|
|
//! - **[`Spline`]** — a server-authored path, sampled at constant speed by
|
|
//! [`spline::sample_splines`]. Creatures ride these. Two sources, one component: a fresh
|
|
//! `SMSG_MONSTER_MOVE` ([`spline::monster_move_spline`]), and the walk a unit was **already** on when
|
|
//! it streamed into view, joined mid-path from its create block ([`spline::create_spline`], 0708).
|
|
//! - **[`RemoteMotion`]** — another player's flag-driven locomotion, integrated by
|
|
//! [`remote::extrapolate_remote_units`]: the client's own dead-reckoning in miniature (extrapolate
|
|
//! from the last reported state, snap to the truth on the next packet). A jump is a **ballistic
|
|
//! event** — the launch played out locally under gravity — not flag-driven walking (decision 0053).
|
|
//!
|
|
//! The parent [`super`] module owns the wire→ECS bridge that seeds + corrects these from the packet
|
|
//! stream; this module owns the integration, split by concern: [`spline`] (the path walk + its
|
|
//! terrain re-ground), [`remote`] (dead-reckoning + jump ballistics), [`relay`] (***when*** a relayed
|
|
//! move replays — the per-unit fire-time chain), [`facing`] (wire facing
|
|
//! resolution + the client-local idle re-face and its [`FacingStep`] shuffle latch). This face keeps
|
|
//! the shared pose glue. Poses live canonically in raw WoW space on the components; the `Transform`
|
|
//! is derived (translation + facing only — scale, baked by the renderer, is preserved).
|
|
|
|
use benilla_assets::coords::wow_to_bevy;
|
|
use bevy::prelude::*;
|
|
|
|
mod facing;
|
|
mod relay;
|
|
mod remote;
|
|
mod spline;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
pub(crate) use facing::FacingStep;
|
|
pub(super) use facing::{face_target, resolve_facing};
|
|
pub(crate) use relay::{PendingMove, RelayMove};
|
|
pub(crate) use remote::RemoteMotion;
|
|
pub(in crate::net) use remote::{apply_move, arrival_snap, trace_relay, RelayOutcome};
|
|
pub(super) use remote::{drain_pending_moves, extrapolate_remote_units};
|
|
pub(super) use spline::{
|
|
create_spline, ground_clamp_creatures, mark_swimming_creatures, monster_move_spline,
|
|
sample_splines, trace_create_spline, trace_move_snap,
|
|
};
|
|
pub(crate) use spline::{CreatureSwimming, Spline};
|
|
|
|
/// A full [`Transform`] for a freshly-spawned entity from a raw-WoW pose. WoW orientation (yaw about
|
|
/// +Z) maps straight to a Bevy +Y yaw under `wow_to_bevy` — no 180° offset (unlike doodad/WMO
|
|
/// *placement* rotation).
|
|
pub(super) fn pose_transform(position: [f32; 3], orientation: f32) -> Transform {
|
|
Transform {
|
|
translation: wow_to_bevy(position),
|
|
rotation: Quat::from_rotation_y(orientation),
|
|
..default()
|
|
}
|
|
}
|
|
|
|
/// Update an existing entity's translation + rotation from a raw-WoW pose, **preserving its scale**
|
|
/// (the renderer bakes per-display model scale into the transform; a move must not reset it). The
|
|
/// entity may not be query-visible the very frame it spawned — then the move is simply skipped (the
|
|
/// spawn pose already placed it, and the next move lands once commands flush).
|
|
pub(super) fn write_pose(
|
|
transforms: &mut Query<&mut Transform>,
|
|
e: Entity,
|
|
position: [f32; 3],
|
|
orientation: f32,
|
|
) {
|
|
if let Ok(mut t) = transforms.get_mut(e) {
|
|
t.translation = wow_to_bevy(position);
|
|
t.rotation = Quat::from_rotation_y(orientation);
|
|
}
|
|
}
|
|
|
|
/// Recover the WoW yaw from a unit [`Transform`] rotation (always built as [`Quat::from_rotation_y`]).
|
|
fn yaw_of(rotation: Quat) -> f32 {
|
|
rotation.to_euler(EulerRot::YXZ).0
|
|
}
|