diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs index 4fd2fa6218..4d066afd6c 100644 --- a/crates/horizon-client/src/game.rs +++ b/crates/horizon-client/src/game.rs @@ -8312,7 +8312,7 @@ impl Game<'_> { gameplay_has_keyboard( self.screen, self.title.is_some(), - self.dialog_open(), + self.screen_over_playing(), ), action, ) { @@ -8357,8 +8357,11 @@ impl Game<'_> { (_, false) => KeyAction::Press, (_, true) => KeyAction::Repeat, }; - let screen_open = - !gameplay_has_keyboard(self.screen, self.title.is_some(), self.dialog_open()); + let screen_open = !gameplay_has_keyboard( + self.screen, + self.title.is_some(), + self.screen_over_playing(), + ); self.toggles.key(slot, action, screen_open, option); self.sync_toggle(slot); if !pressed { @@ -8386,7 +8389,11 @@ impl Game<'_> { fn track_toggle_screens(&mut self) { let dialog = self.dialog_open(); drop_held_behind_dialog(dialog, &mut self.keys, &mut self.left, &mut self.right); - let playing = gameplay_has_keyboard(self.screen, self.title.is_some(), dialog); + let playing = gameplay_has_keyboard( + self.screen, + self.title.is_some(), + self.screen_over_playing(), + ); if playing == self.was_playing { return; } @@ -22896,10 +22903,13 @@ fn keyboard_button(playing: bool, action: KeyAction) -> Option { playing.then_some(action != KeyAction::Release) } -/// Gameplay owns the keyboard: `Screen::Playing`, no title, no server dialog. -/// Vanilla's `DialogScreen` is a screen, so `KeyMapping.releaseAll` runs. -fn gameplay_has_keyboard(screen: Screen, title: bool, dialog: bool) -> bool { - screen == Screen::Playing && !title && !dialog +/// Gameplay owns the keyboard: `Screen::Playing`, no title, and nothing open +/// over the world (`screen_over_playing`: a server dialog, a book or lectern, +/// a sign editor, a prompt). Each is a `Screen` in vanilla, so `Gui.setScreen` +/// runs `KeyMapping.releaseAll` and `Minecraft.tick` skips `handleKeybinds` +/// while it is up: a mouse button held into it is let go of (#9729). +fn gameplay_has_keyboard(screen: Screen, title: bool, screen_over: bool) -> bool { + screen == Screen::Playing && !title && !screen_over } /// A dialog drops held keys and mouse buttons it does not see (#4974). @@ -23636,8 +23646,10 @@ mod tests { } } - /// A server dialog is a screen: held keys and the use button are released - /// (`KeyMapping.releaseAll`), and toggled sneak comes back when it closes (#4974). + /// A server dialog, a book, a sign editor or a prompt over the world is a + /// screen: held keys and the mouse buttons are released + /// (`KeyMapping.releaseAll`), and toggled sneak comes back when it closes + /// (#4974, #9729). #[test] fn a_dialog_releases_held_keys_like_a_screen() { assert!(gameplay_has_keyboard(Screen::Playing, false, false)); diff --git a/crates/horizon-client/src/game/remote.rs b/crates/horizon-client/src/game/remote.rs index ee18246099..15bee9fa56 100644 --- a/crates/horizon-client/src/game/remote.rs +++ b/crates/horizon-client/src/game/remote.rs @@ -9046,10 +9046,15 @@ impl Game<'_> { }; let mut input = std::mem::take(&mut r.input); let stop = std::mem::take(&mut r.stop_mining); + // Minecraft.tick runs handleKeybinds only while `gui.screen() == null`: a + // book, a sign editor, a dialog or a prompt over the world (each a + // `Screen` in vanilla) stops the attack and use mappings from acting, + // and `KeyMapping.releaseAll` let go of the buttons when it opened + // (`track_toggle_screens`), so a dig held into a book ends (#9729). let ready = r.loaded_sent && self.screen == Screen::Playing && self.sleeping.is_none() - && !self.dialog_open(); + && !self.screen_over_playing(); // MultiPlayerGameMode.tick, first in Minecraft.tick, screen or not: the slot // chosen since the last tick (the wheel, the last tick's number keys, the // server's SetHeldSlot) goes out before anything else of this tick. diff --git a/tests/test_screen_key_released_hook.py b/tests/test_screen_key_released_hook.py index 66f893989a..5e8f984a00 100644 --- a/tests/test_screen_key_released_hook.py +++ b/tests/test_screen_key_released_hook.py @@ -47,7 +47,7 @@ struct Settings { keys: Bindings } struct Game { keys: HashSet, settings: Settings, screen: bool, title: Option<()>, modifiers: u32, open: bool, cancel: bool, calls: Cell, handled: usize, released: Vec<(bool, i32)> } impl Game { - fn dialog_open(&self) -> bool { false } + fn screen_over_playing(&self) -> bool { false } fn screen_view_now(&self) -> Option<&'static str> { self.open.then_some("options") } fn native_screen_key_released_pre(&self, screen: &&str, name: &str, modifiers: u32, canceled: &mut bool) { assert_eq!((*screen, name, modifiers), ("options", "key.keyboard.a", 5));