Skip to main content

steel_core/inventory/menu/
kind.rs

1//! Per-menu behavior hooks.
2
3use steel_registry::blocks::{BlockRef, block_state_ext::BlockStateExt as _};
4use steel_registry::item_stack::ItemStack;
5use steel_registry::mob_effect::MobEffectRef;
6use steel_utils::{BlockPos, ErasedType};
7
8use crate::inventory::container::DEFAULT_DISTANCE_BUFFER;
9use crate::inventory::menu::behavior::MenuBehavior;
10use crate::world::World;
11use crate::{inventory::lock::ContainerLockGuard, player::Player};
12
13use crate::inventory::click::{Click, ClickOutcome, QuickCraft};
14
15/// Per-menu behavior that isn't shared: recompute-on-change, validity, close
16/// cleanup, and the optional shift-click override.
17///
18/// Menu transitions requested while a hook owns mutable access to the current
19/// menu are applied after that hook returns.
20///
21/// Concrete implementations must claim a unique
22/// [`steel_utils::DowncastTypeKey`] through [`steel_utils::DowncastType`].
23pub trait MenuKind: ErasedType + Send + Sync {
24    /// Recompute recipe-driven slots after a click touched a real slot.
25    fn slots_changed(
26        &mut self,
27        _behavior: &mut MenuBehavior,
28        _guard: &mut ContainerLockGuard,
29        _player: &Player,
30    ) {
31    }
32
33    /// Extra cleanup on close beyond [`Menu::removed`].
34    fn removed(&mut self, _behavior: &mut MenuBehavior, _player: &Player) {}
35
36    /// Applies a rename from the client (anvil-style text input) and recomputes
37    /// any result. No-op for kinds without a rename input.
38    fn on_rename(&mut self, _behavior: &mut MenuBehavior, _name: String, _player: &Player) {}
39
40    /// Handles a client button press (vanilla `clickMenuButton`). Returns true
41    /// when the press was handled, whether or not it changed anything; the
42    /// caller then re-syncs the menu. No-op for kinds without buttons.
43    fn click_menu_button(
44        &mut self,
45        _behavior: &mut MenuBehavior,
46        _button_id: i32,
47        _player: &Player,
48    ) -> bool {
49        false
50    }
51
52    /// Runs after initial contents are built but before they're sent, so
53    /// anything populated here appears in the first render.
54    fn on_open(
55        &mut self,
56        _behavior: &mut MenuBehavior,
57        _guard: &mut ContainerLockGuard,
58        _player: &Player,
59    ) {
60    }
61
62    /// Runs once per tick per viewer while open, before changes are synced.
63    fn on_tick(
64        &mut self,
65        _behavior: &mut MenuBehavior,
66        _guard: &mut ContainerLockGuard,
67        _player: &Player,
68    ) {
69    }
70
71    /// Runs for every non-drag click before default handling. Return
72    /// [`ClickOutcome::Consume`] to treat the slot as a button, or
73    /// [`ClickOutcome::Fallthrough`] for default handling.
74    fn on_slot_clicked(
75        &mut self,
76        _behavior: &mut MenuBehavior,
77        _guard: &mut ContainerLockGuard,
78        _click: Click,
79        _player: &Player,
80    ) -> ClickOutcome {
81        ClickOutcome::Fallthrough
82    }
83
84    /// Runs for each drag phase before default handling. Return
85    /// [`ClickOutcome::Consume`] to cancel the drag.
86    fn on_drag(
87        &mut self,
88        _behavior: &mut MenuBehavior,
89        _guard: &mut ContainerLockGuard,
90        _action: QuickCraft,
91        _player: &Player,
92    ) -> ClickOutcome {
93        ClickOutcome::Fallthrough
94    }
95
96    /// Returns true if a drag may distribute items into `slot_index`.
97    fn can_drag_to(&self, _slot_index: usize) -> bool {
98        true
99    }
100
101    /// Returns true if this menu is still valid for the player.
102    fn still_valid(&self, _behavior: &MenuBehavior, _player: &Player) -> bool {
103        true
104    }
105
106    /// Returns true if an item may be taken from `slot_index` during pickup-all.
107    fn can_take_item_for_pick_all(&self, _carried: &ItemStack, _slot_index: usize) -> bool {
108        true
109    }
110
111    /// Handles a beacon effect update from the client's set-beacon packet.
112    /// Returns `false` when the selection or payment is invalid.
113    fn on_update_effects(
114        &mut self,
115        _behavior: &mut MenuBehavior,
116        _guard: &mut ContainerLockGuard,
117        _primary: Option<MobEffectRef>,
118        _secondary: Option<MobEffectRef>,
119    ) -> bool {
120        true
121    }
122
123    /// Shift-click override. Return `Some` to fully handle the quick-move, or
124    /// `None` to fall back to the route table.
125    fn quick_move(
126        &mut self,
127        _behavior: &mut MenuBehavior,
128        _guard: &mut ContainerLockGuard,
129        _slot_index: usize,
130        _player: &Player,
131    ) -> Option<ItemStack> {
132        None
133    }
134}
135
136/// Vanilla's static `AbstractContainerMenu.stillValid(access, player, block)`, renamed
137/// so it does not read as [`MenuKind::still_valid`]: the block at `pos` is still
138/// `block` and the player is within reach.
139#[must_use]
140pub fn still_valid_block(world: &World, pos: BlockPos, block: BlockRef, player: &Player) -> bool {
141    world.get_block_state(pos).get_block() == block
142        && player
143            .is_within_block_interaction_range_with_buffer(pos, f64::from(DEFAULT_DISTANCE_BUFFER))
144}