worldport: re-claim our own body after a transfer

Leaving a battleground left the character unable to walk for the rest of the session. The
`settle released` line named it: `WASD held by: control-lost`.

vmangos takes control for the teleport (`SetClientControl(player, 0)`), so `Revoked` sets
`control_lost` and parks the mover, and only a later `SMSG_CLIENT_CONTROL_UPDATE` with
`allow_move = true` clears it. Nothing guarantees that packet, and nothing else clears the flag:
the worldport arm replaces the pose and the settle releases, neither asks, and `seizing` at
`wire_in.rs` wants `reseat`, which a worldport never sets. So the lock on the body we just left
parked the body we came back as.

A transfer is a mover change, so re-claim at the transfer's completion, with the ack, the way
login does — the same teardown as the reference's `SetActiveMover` reset. `reseat` rides along so
the existing seize path resets the outgoing mover's state. A possession is left alone: there the
lock is the point.

Compile-verified only — the live run is the test.
This commit is contained in:
chairmanpow
2026-10-02 18:38:56 +01:00
parent bf52a0abb1
commit 3b068b15c2
+23 -4
View File
@@ -105,6 +105,7 @@ pub(super) fn release_post_snap_hold(
time: Res<Time>,
mut last_counters: Local<Option<[usize; 6]>>,
net_cmds: Option<Res<crate::net::NetCommands>>,
self_guid: Option<Res<crate::net::SelfGuid>>,
) {
let Some(p) = progress else { return };
// Residency counts only for the tile under the body. On a mismatch (the teleport's snap frame,
@@ -149,10 +150,28 @@ pub(super) fn release_post_snap_hold(
// this a character that comes out of a worldport unable to walk is a story, because nothing
// else in the log says whether the hold is the settle, a lost mover, or a pending reseat.
if was_settling && !player.settling {
info!(
"worldport: settle released — WASD held by: {}",
player.movement_suppressors()
);
let held = player.movement_suppressors();
info!("worldport: settle released — WASD held by: {held}");
// A transfer is a mover change, and the server's revoke belonged to the body on the map we
// left: vmangos takes control for the teleport (`SetClientControl(player, 0)`) and does not
// promise the restore. Left set, `control_lost` parks the body for the rest of the session,
// because nothing else clears it — the worldport arm replaces the pose and the settle
// releases, and neither asks. Re-claim our own body here, at the transfer's completion and
// with the ack, the way login does; the reference's `SetActiveMover` reset (`wire_in.rs`)
// is the same teardown. A possession is left alone: there the lock is the point.
if player.control_lost && player.foreign_mover.is_none() {
if let (Some(net), Some(guid)) =
(net_cmds.as_deref(), self_guid.as_deref().and_then(|g| g.0))
{
player.control_lost = false;
player.reseat = true;
super::move_trace::mover_claim("WORLDPORT", guid);
let _ = net
.0
.send(crate::net::ClientCommand::SetActiveMover { guid });
info!("worldport: re-claimed our own body after the transfer");
}
}
}
// The worldport ack, the reference's post-load `0xDC`, goes at either end of the hold: the
// server keeps an unacked transfer out of world until logout.