mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Keep a move behind a dig and its arm swing for the next tick
The hub hands a dig (handlePlayerAction) to the game after its pass, and a move queued right behind it waited for the next tick so it was not checked against the block the player had just broken. A creative break sends the arm swing (Punch) between the dig and the move, so the move was handled in the same pass: the block was still there, the move was refused and the player sent back. Digging straight down, the player sank into the block, was put back on top of it a tick later, then fell. Any move left in the queue now waits, with what is queued before it, as vanilla handles the packets in order. Traced with horizon-ctl in a creative world: the player rose back after 6 to 7 of 8 breaks before, after none now. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
27c3559421
commit
32a0a11fed
2 files changed
+56
-5
No files matched your search
@@ -2216,13 +2216,16 @@ impl Hub {
|
||||
// game's, run after this pass. A move queued behind it is checked
|
||||
// against the block the player just broke and falls through, so it
|
||||
// would be sent back; it waits for the next tick, when the block is
|
||||
// gone, as vanilla handles the two packets in order.
|
||||
// gone, as vanilla handles the packets in order. So does everything
|
||||
// queued before that move: the arm swing (Punch) comes between the dig
|
||||
// and the move.
|
||||
S::PlayerAction(p) if p.action <= 3 => {
|
||||
events.push(HostEvent::Packet(conn, Box::new(S::PlayerAction(p))));
|
||||
if matches!(
|
||||
self.guests[i].inbox.front(),
|
||||
Some(S::MovePlayerPos(_) | S::MovePlayerPosRot(_))
|
||||
) {
|
||||
if self.guests[i]
|
||||
.inbox
|
||||
.iter()
|
||||
.any(|p| matches!(p, S::MovePlayerPos(_) | S::MovePlayerPosRot(_)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +407,54 @@ fn sprint_commands_survive_keyboard_input_changes() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_move_behind_a_dig_and_its_swing_waits_for_the_next_tick() {
|
||||
let Some(resources) = resources() else {
|
||||
return;
|
||||
};
|
||||
let mut s = server(&resources);
|
||||
let port = s.hub.lan.port;
|
||||
let _bot = run(&mut s, move || connect(port, "Digger", true));
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while s.hub.guests.is_empty() {
|
||||
s.tick(&[]);
|
||||
assert!(Instant::now() < deadline, "guest did not join");
|
||||
thread::yield_now();
|
||||
}
|
||||
s.hub.guests[0].loaded = true;
|
||||
let at = s.hub.guests[0].position;
|
||||
let below = [
|
||||
at[0].floor() as i32,
|
||||
at[1].floor() as i32 - 1,
|
||||
at[2].floor() as i32,
|
||||
];
|
||||
// A creative break as the client sends it in one tick: the dig, the arm swing,
|
||||
// then the move into the block it predicted gone.
|
||||
let inbox = &mut s.hub.guests[0].inbox;
|
||||
inbox.push_back(S::PlayerAction(play::PlayerAction {
|
||||
action: 0,
|
||||
pos: below,
|
||||
direction: 1,
|
||||
sequence: 1,
|
||||
}));
|
||||
inbox.push_back(S::Punch(Empty));
|
||||
inbox.push_back(S::MovePlayerPos(play::MovePlayerPos {
|
||||
x: at[0],
|
||||
y: at[1] - 0.0784,
|
||||
z: at[2],
|
||||
flags: 0,
|
||||
}));
|
||||
s.tick(&[]);
|
||||
// The game breaks the block after the hub's pass: the move waits, or it would be
|
||||
// checked against the block and sent back.
|
||||
let g = &s.hub.guests[0];
|
||||
assert_eq!(g.position, at, "the move was handled before the dig");
|
||||
assert!(
|
||||
matches!(g.inbox.back(), Some(S::MovePlayerPos(_))),
|
||||
"the move left the queue"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_and_join() {
|
||||
let Some(resources) = resources() else {
|
||||
|
||||
Reference in new issue
Block a user