mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Controller toggle-sneak comes back after a screen closes
The pad's toggle-sneak flipped sneaking with a hold-mode Shift press or release, so KeyMapping.releaseAll dropped it when a screen opened and restoreToggleStatesOnScreenClosed had nothing to restore. ToggleKeys now has a pad toggle: it flips the sneak mapping as ToggleKeyMapping.setDown does with its option on, and while it holds sneaking the mapping toggles (released and remembered by a screen, restored when it closes, a shift release behind the screen ignored), as vanilla's toggle crouch does. Part of #3061. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
518ecfb511
commit
a8b5151ce5
3 files changed
+102
-11
No files matched your search
@@ -10,6 +10,10 @@ pub struct KeyState {
|
||||
released_by_screen_when_down: bool,
|
||||
toggle: Option<bool>,
|
||||
keyboard: bool,
|
||||
/// Held down by a controller's toggle (Horizon's pad toggle-sneak, which
|
||||
/// vanilla has no option for): until it lets go, the mapping toggles as
|
||||
/// if its vanilla option were on, so a screen releases and restores it.
|
||||
pad_toggled: bool,
|
||||
}
|
||||
|
||||
impl KeyState {
|
||||
@@ -20,6 +24,7 @@ impl KeyState {
|
||||
released_by_screen_when_down: false,
|
||||
toggle: None,
|
||||
keyboard: true,
|
||||
pad_toggled: false,
|
||||
}
|
||||
}
|
||||
/// A `ToggleKeyMapping`; `keyboard` is whether the bound key is a keyboard key.
|
||||
@@ -43,16 +48,28 @@ impl KeyState {
|
||||
true
|
||||
}
|
||||
}
|
||||
/// The mapping toggles: its option is on, or a pad toggled it down.
|
||||
fn needs_toggle(&self, option: bool) -> bool {
|
||||
option || self.pad_toggled
|
||||
}
|
||||
/// `setDown`; `needs_toggle` is the mapping's option value.
|
||||
pub fn set_down(&mut self, down: bool, needs_toggle: bool) {
|
||||
if self.toggle.is_some() && needs_toggle {
|
||||
if self.toggle.is_some() && self.needs_toggle(needs_toggle) {
|
||||
if down {
|
||||
self.is_down = !self.is_down;
|
||||
// Toggled back up: the pad's toggle is over.
|
||||
self.pad_toggled &= self.is_down;
|
||||
}
|
||||
} else {
|
||||
self.is_down = down;
|
||||
}
|
||||
}
|
||||
/// A controller's toggle button: flips the mapping like `setDown(true)`
|
||||
/// with its option on, whatever the option says.
|
||||
pub fn pad_toggle(&mut self) {
|
||||
self.is_down = !self.is_down;
|
||||
self.pad_toggled = self.is_down;
|
||||
}
|
||||
/// `shouldSetOnIngameFocus` for a keyboard-bound, bound key.
|
||||
pub fn should_set_on_ingame_focus(&self, needs_toggle: bool) -> bool {
|
||||
self.keyboard && (self.toggle.is_none() || !needs_toggle)
|
||||
@@ -61,7 +78,8 @@ impl KeyState {
|
||||
pub fn release(&mut self, needs_toggle: bool) {
|
||||
if self.toggle.is_some() {
|
||||
// ToggleKeyMapping.release does not clear pending clicks.
|
||||
if needs_toggle && self.is_down || self.released_by_screen_when_down {
|
||||
if self.needs_toggle(needs_toggle) && self.is_down || self.released_by_screen_when_down
|
||||
{
|
||||
self.released_by_screen_when_down = true;
|
||||
}
|
||||
self.reset();
|
||||
@@ -70,7 +88,8 @@ impl KeyState {
|
||||
self.set_down(false, needs_toggle);
|
||||
}
|
||||
}
|
||||
/// `ToggleKeyMapping.reset`.
|
||||
/// `ToggleKeyMapping.reset`. A pad toggle stays on record, so the screen
|
||||
/// that released it restores it.
|
||||
pub fn reset(&mut self) {
|
||||
self.is_down = false;
|
||||
}
|
||||
@@ -83,7 +102,10 @@ impl KeyState {
|
||||
let Some(restore) = self.toggle else {
|
||||
return false;
|
||||
};
|
||||
let result = restore && needs_toggle && self.keyboard && self.released_by_screen_when_down;
|
||||
let result = restore
|
||||
&& self.needs_toggle(needs_toggle)
|
||||
&& self.keyboard
|
||||
&& self.released_by_screen_when_down;
|
||||
self.released_by_screen_when_down = false;
|
||||
result
|
||||
}
|
||||
@@ -138,6 +160,15 @@ impl ToggleKeys {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
/// A controller's toggle button bound to `slot` (the pad's toggle-sneak):
|
||||
/// a press with no screen open flips the mapping and clicks it, as a
|
||||
/// key press does with the mapping's toggle option on.
|
||||
pub fn pad_toggle(&mut self, slot: usize, screen_open: bool) {
|
||||
if !screen_open {
|
||||
self.0[slot].pad_toggle();
|
||||
self.0[slot].click();
|
||||
}
|
||||
}
|
||||
/// `MouseHandler.onButton` for a button bound to `slot`: only without a
|
||||
/// screen or overlay.
|
||||
pub fn button(&mut self, slot: usize, pressed: bool, screen_open: bool, needs_toggle: bool) {
|
||||
@@ -167,6 +198,9 @@ impl ToggleKeys {
|
||||
pub fn respawn(&mut self) {
|
||||
for key in &mut self.0 {
|
||||
key.reset();
|
||||
// As with the vanilla option, the death screen closing after
|
||||
// this still restores a toggle it released.
|
||||
key.pad_toggled &= key.released_by_screen_when_down;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +231,57 @@ mod tests {
|
||||
keys.respawn();
|
||||
assert!(!keys.is_down(SPRINT));
|
||||
}
|
||||
/// #3061: the pad's toggle-sneak (vanilla's toggle crouch off) comes
|
||||
/// back after a screen, as the vanilla option's toggle does; a
|
||||
/// keyboard shift-click in the inventory does not drop it.
|
||||
#[test]
|
||||
fn pad_toggle_is_restored_after_a_screen() {
|
||||
let off = [false; 4];
|
||||
let mut keys = ToggleKeys::default();
|
||||
keys.pad_toggle(SNEAK, false);
|
||||
assert!(keys.is_down(SNEAK));
|
||||
keys.screen_opened(off);
|
||||
assert!(!keys.is_down(SNEAK));
|
||||
keys.key(SNEAK, KeyAction::Press, true, false);
|
||||
keys.key(SNEAK, KeyAction::Release, true, false);
|
||||
keys.screen_closed(off);
|
||||
assert!(keys.is_down(SNEAK), "the pad's toggle is restored");
|
||||
// Toggled back up, it stays up through the next screen.
|
||||
keys.pad_toggle(SNEAK, false);
|
||||
assert!(!keys.is_down(SNEAK));
|
||||
keys.screen_opened(off);
|
||||
keys.screen_closed(off);
|
||||
assert!(!keys.is_down(SNEAK));
|
||||
// A held key is not restored, as before.
|
||||
keys.key(SNEAK, KeyAction::Press, false, false);
|
||||
keys.screen_opened(off);
|
||||
keys.key(SNEAK, KeyAction::Release, true, false);
|
||||
keys.screen_closed(off);
|
||||
assert!(!keys.is_down(SNEAK));
|
||||
}
|
||||
/// After the pad's toggle is over (toggled up, or reset by a respawn
|
||||
/// with no screen to restore it), the shift key holds again.
|
||||
#[test]
|
||||
fn pad_toggle_ends_with_its_release() {
|
||||
let off = [false; 4];
|
||||
let mut keys = ToggleKeys::default();
|
||||
keys.pad_toggle(SNEAK, false);
|
||||
keys.respawn();
|
||||
assert!(!keys.is_down(SNEAK));
|
||||
keys.key(SNEAK, KeyAction::Press, false, false);
|
||||
keys.key(SNEAK, KeyAction::Release, false, false);
|
||||
assert!(!keys.is_down(SNEAK), "held, not toggled");
|
||||
// The death screen releases it and closes after the respawn: the
|
||||
// toggle comes back, as with the vanilla option.
|
||||
keys.pad_toggle(SNEAK, false);
|
||||
keys.screen_opened(off);
|
||||
keys.respawn();
|
||||
keys.screen_closed(off);
|
||||
assert!(keys.is_down(SNEAK));
|
||||
// A press with a screen open does nothing.
|
||||
keys.pad_toggle(SNEAK, true);
|
||||
assert!(keys.is_down(SNEAK));
|
||||
}
|
||||
#[test]
|
||||
fn screen_release_restores_toggled_state() {
|
||||
let mut k = KeyState::toggle(true, true);
|
||||
|
||||
@@ -8,6 +8,7 @@ use horizon_client::{
|
||||
Result,
|
||||
gamepad::{self, Button, Edges, GlyphStyle, HealthWatch, PadState, Rumble},
|
||||
keybinds::{KeyBinds, key_code, mouse_button},
|
||||
keymapping::SNEAK,
|
||||
mouse::Rotation,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
@@ -214,12 +215,16 @@ impl Game<'_> {
|
||||
"key.sneak"
|
||||
if self.settings.controller.toggle_sneak && !self.settings.toggle_crouch =>
|
||||
{
|
||||
// A stick click flips sneaking instead of holding it.
|
||||
if down {
|
||||
let sneaking = self.keys.contains(&KeyCode::ShiftLeft);
|
||||
self.mapped_key(KeyCode::ShiftLeft, !sneaking, false)?;
|
||||
// A stick click flips sneaking instead of holding it, as
|
||||
// vanilla's toggle crouch does: a screen opening releases it
|
||||
// and closing restores it (ToggleKeyMapping).
|
||||
if !down {
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
let screen_open = self.screen != Screen::Playing || self.title.is_some();
|
||||
self.toggles.pad_toggle(SNEAK, screen_open);
|
||||
self.sync_toggle(SNEAK);
|
||||
self.key_pressed(KeyCode::ShiftLeft)
|
||||
}
|
||||
_ => {
|
||||
let default = KeyBinds::default_of(action);
|
||||
|
||||
+3
-2
@@ -65,8 +65,9 @@ loop iteration next to winit's events.
|
||||
|
||||
The Guide / PS / Steam button is left to the system. Sneak on a stick click
|
||||
toggles by default (holding a stick in while steering the camera is
|
||||
awkward, which is why Bedrock does the same); the controls options can make it
|
||||
hold-to-sneak.
|
||||
awkward, which is why Bedrock does the same) and acts like vanilla's toggle
|
||||
crouch: a screen stands you up while it is open and closing it crouches you
|
||||
again. The controls options can make it hold-to-sneak.
|
||||
|
||||
## Menus and inventories
|
||||
|
||||
|
||||
Reference in new issue
Block a user