steel_core/inventory/menu/kinds/
inventory_menu.rs1use steel_registry::item_stack::ItemStack;
12use steel_utils::locks::{IntoShared, Shared};
13
14use crate::inventory::container::{CraftingContainer, ResultContainer};
15use crate::inventory::prelude::*;
16use crate::inventory::slots::{ArmorSlot, CraftingHandler};
17use crate::player::Player;
18use crate::player::player_inventory::{PlayerInventory, armor_equipment};
19
20pub const INVENTORY_MENU_CONTAINER_ID: u8 = 0;
22
23#[must_use]
30pub fn inventory_menu(inventory: Shared<PlayerInventory>) -> Menu {
31 let crafting_container = CraftingContainer::new(2, 2).into_shared();
32 let result_container = ResultContainer::new().into_shared();
33
34 let handler = CraftingHandler::new(crafting_container.clone(), result_container.clone(), 2);
35
36 let mut builder = MenuBuilder::new(None, INVENTORY_MENU_CONTAINER_ID);
37
38 let result = builder.result_slot(handler.clone());
39 let grid = builder.section_all(crafting_container);
40 let armor = builder.section_at(
41 &inventory,
42 PlayerInventory::ARMOR_TOP_DOWN,
43 SectionKind::custom(|container, index| {
44 Box::new(ArmorSlot::new(
45 container.clone(),
46 index,
47 armor_equipment(index),
48 ))
49 }),
50 );
51 let player = builder.player_inventory(&inventory);
52 let offhand = builder.section_at(
53 &inventory,
54 [PlayerInventory::SLOT_OFFHAND],
55 SectionKind::Normal,
56 );
57
58 builder.drain(grid);
60
61 builder.build(InventoryKind {
62 result_container,
63 handler,
64 result,
65 grid,
66 armor,
67 inv: player.all(),
68 main: player.main(),
69 hotbar: player.hotbar(),
70 offhand,
71 })
72}
73
74pub struct InventoryKind {
77 result_container: Shared<ResultContainer>,
79 handler: CraftingHandler,
80 result: Section,
82 grid: Section,
84 armor: Section,
86 inv: Section,
88 main: Section,
90 hotbar: Section,
92 offhand: Section,
94}
95
96unsafe impl steel_utils::DowncastType for InventoryKind {
99 const TYPE_KEY: steel_utils::DowncastTypeKey =
100 steel_utils::DowncastTypeKey::new("steel:menu/inventory");
101}
102
103impl InventoryKind {
104 pub(crate) fn crafting_id(&self) -> ContainerId {
106 self.handler.crafting_id()
107 }
108
109 pub(crate) fn crafting_container(&self) -> Shared<CraftingContainer> {
111 self.handler.crafting_container()
112 }
113
114 pub(crate) fn crafting_handler(&self) -> CraftingHandler {
116 self.handler.clone()
117 }
118
119 pub(crate) fn update_result(&self, guard: &mut ContainerLockGuard) {
121 self.handler.update_result(guard);
122 }
123
124 fn move_between_inventory_and_hotbar(
126 &self,
127 behavior: &MenuBehavior,
128 guard: &mut ContainerLockGuard,
129 slot_index: usize,
130 stack: &mut ItemStack,
131 ) -> bool {
132 if self.main.contains(slot_index) {
133 behavior.move_item_stack_to(
134 guard,
135 slot_index,
136 stack,
137 self.hotbar.start(),
138 self.hotbar.end(),
139 FillDirection::Forward,
140 )
141 } else if self.hotbar.contains(slot_index) {
142 behavior.move_item_stack_to(
143 guard,
144 slot_index,
145 stack,
146 self.main.start(),
147 self.main.end(),
148 FillDirection::Forward,
149 )
150 } else {
151 behavior.move_item_stack_to(
152 guard,
153 slot_index,
154 stack,
155 self.inv.start(),
156 self.inv.end(),
157 FillDirection::Forward,
158 )
159 }
160 }
161}
162
163impl MenuKind for InventoryKind {
164 #[expect(
169 clippy::too_many_lines,
170 reason = "mirrors Java's InventoryMenu::quickMoveStack branch structure"
171 )]
172 fn quick_move(
173 &mut self,
174 behavior: &mut MenuBehavior,
175 guard: &mut ContainerLockGuard,
176 slot_index: usize,
177 player: &Player,
178 ) -> Option<ItemStack> {
179 if slot_index >= behavior.slots().len() {
180 return Some(ItemStack::empty());
181 }
182
183 let stack = behavior.slots()[slot_index].get_item(guard).clone();
184 if stack.is_empty() {
185 return Some(ItemStack::empty());
186 }
187 if self.result.contains(slot_index)
188 && !behavior.slots()[slot_index].may_pickup(guard, player)
189 {
190 return Some(ItemStack::empty());
191 }
192
193 let clicked = stack.clone();
194 let mut stack_mut = stack;
195
196 let moved = if self.result.contains(slot_index) {
198 behavior.move_item_stack_to(
200 guard,
201 slot_index,
202 &mut stack_mut,
203 self.inv.start(),
204 self.inv.end(),
205 FillDirection::Backward,
206 )
207 } else if self.grid.contains(slot_index) || self.armor.contains(slot_index) {
208 behavior.move_item_stack_to(
210 guard,
211 slot_index,
212 &mut stack_mut,
213 self.inv.start(),
214 self.inv.end(),
215 FillDirection::Forward,
216 )
217 } else {
218 let equippable_slot = clicked.get_equippable_slot();
220
221 if let Some(eq_slot) = equippable_slot {
222 if eq_slot.slot_type() == EquipmentSlotType::HumanoidArmor {
223 let armor_slot_index = self.armor.start()
225 + match eq_slot {
226 EquipmentSlot::Head => 0,
227 EquipmentSlot::Chest => 1,
228 EquipmentSlot::Legs => 2,
229 EquipmentSlot::Feet => 3,
230 _ => unreachable!(),
231 };
232
233 if behavior.slots()[armor_slot_index].has_item(guard) {
234 self.move_between_inventory_and_hotbar(
235 behavior,
236 guard,
237 slot_index,
238 &mut stack_mut,
239 )
240 } else {
241 behavior.move_item_stack_to(
242 guard,
243 slot_index,
244 &mut stack_mut,
245 armor_slot_index,
246 armor_slot_index + 1,
247 FillDirection::Forward,
248 )
249 }
250 } else if eq_slot == EquipmentSlot::OffHand {
251 if behavior.slots()[self.offhand.start()].has_item(guard) {
252 self.move_between_inventory_and_hotbar(
253 behavior,
254 guard,
255 slot_index,
256 &mut stack_mut,
257 )
258 } else {
259 behavior.move_item_stack_to(
260 guard,
261 slot_index,
262 &mut stack_mut,
263 self.offhand.start(),
264 self.offhand.end(),
265 FillDirection::Forward,
266 )
267 }
268 } else {
269 self.move_between_inventory_and_hotbar(
270 behavior,
271 guard,
272 slot_index,
273 &mut stack_mut,
274 )
275 }
276 } else {
277 self.move_between_inventory_and_hotbar(behavior, guard, slot_index, &mut stack_mut)
278 }
279 };
280
281 if !moved {
282 return Some(ItemStack::empty());
283 }
284
285 behavior.update_quick_move_source(guard, slot_index, &stack_mut, &clicked);
286
287 if stack_mut.count == clicked.count {
288 return Some(ItemStack::empty());
289 }
290
291 if let Some(remainder) = behavior.slots()[slot_index].on_take(guard, &stack_mut, player) {
292 player.add_item_or_drop_with_guard(guard, remainder);
294 }
295
296 if self.result.contains(slot_index) {
297 if !stack_mut.is_empty() {
299 let _ = guard.run_unlocked(|| player.drop_item(stack_mut, false, false));
300 }
301 }
302
303 Some(clicked)
304 }
305
306 fn can_take_item_for_pick_all(&self, _carried: &ItemStack, slot_index: usize) -> bool {
308 !self.result.contains(slot_index)
309 }
310
311 fn removed(&mut self, _behavior: &mut MenuBehavior, _player: &Player) {
313 self.result_container.lock().set_item(0, ItemStack::empty());
314 }
315
316 fn slots_changed(
317 &mut self,
318 _behavior: &mut MenuBehavior,
319 guard: &mut ContainerLockGuard,
320 _player: &Player,
321 ) {
322 self.handler.update_result(guard);
323 }
324}
325
326#[cfg(test)]
327mod tests;