# Ein benutzerdefiniertes Inventar öffnen ## Überblick Das Inventarsystem in Pumpkin bietet einen flexiblen Weg zur Verwaltung und Manipulation von Items. Dieses Dokument erklärt, wie du eigene Inventare erstellst und implementierst. ## Inhaltsverzeichnis - [Ein benutzerdefiniertes Inventar öffnen](#ein-benutzerdefiniertes-inventar-öffnen) - [Überblick](#überblick) - [Inhaltsverzeichnis](#inhaltsverzeichnis) - [Einfache Inventar-Implementierung](#einfache-inventar-implementierung) - [Erforderliche Traits](#erforderliche-traits) - [Inventory Trait](#inventory-trait) - [Clearable Trait](#clearable-trait) - [Screen Handler](#screen-handler) - [Generische Screen Handler verwenden](#generische-screen-handler-verwenden) - [Eigene Screen Handler erstellen](#eigene-screen-handler-erstellen) - [Best Practices](#best-practices) - [Beispiele](#beispiele) - [Einfacher Inventar-Einsatz](#einfacher-inventar-einsatz) ## Einfache Inventar-Implementierung Die Struktur `BasicInventory` bietet eine Standard‑Implementierung mit 27 Slots. So implementieren Sie Ihr eigenes Inventar: ```rust use pumpkin_world::{ inventory::{ split_stack, {Clearable, Inventory}, }, item::ItemStack, }; #[derive(Debug)] pub struct BasicInventory { pub items: [Arc>; 27], } ``` ## Erforderliche Traits ### Inventory Trait Das Trait `Inventory` definiert Kernfunktionalitäten die alle Inventare implementieren müssen: ```rust impl Inventory for BasicInventory { // Get the total number of slots in the inventory fn size(&self) -> usize { self.items.len() } // Check if the inventory is completely empty fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { for slot in self.items.iter() { if !slot.lock().await.is_empty() { return false; } } true }) } // Get a reference to an item stack in a specific slot fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { Box::pin(async move { self.items[slot].clone() }) } // Remove and return the entire stack from a slot fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { let mut removed = ItemStack::EMPTY.clone(); let mut guard = self.items[slot].lock().await; std::mem::swap(&mut removed, &mut *guard); removed }) } // Remove a specific amount of items from a stack fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { split_stack(&self.items, slot, amount).await }) } // Set the contents of a specific slot fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { *self.items[slot].lock().await = stack; }) } } ``` ### Clearable Trait Das `Clearable`-Trait bietet die Möglichkeit, ein Inventar zu leeren. Dies muss für das `Inventory`-Trait implementiert werden: ```rust impl Clearable for YourInventory { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { for item in self.items.iter() { *item.lock().await = ItemStack::EMPTY.clone(); } }) } } ``` ## Screen Handler Bildschirmhandler dienen der Erstellung und Verwaltung der Benutzeroberfläche für Inventare. Sie definieren, wie Gegenstände zwischen Slots verschoben werden können und wie das Inventar mit dem Inventar des Spielers interagiert. ### Generische Screen Handler verwenden Pumpkin bietet einen generischen Bildschirmhandler für gängige Inventarlayouts. So verwenden Sie ihn: ```rust use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3; use pumpkin_inventory::player::player_inventory::PlayerInventory; use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory}; struct MyScreenFactory { inventory: Arc, } impl ScreenHandlerFactory for MyScreenFactory { fn create_screen_handler( &self, sync_id: u8, player_inventory: &Arc, _player: &dyn InventoryPlayer, ) -> Option>> { Some(Arc::new(Mutex::new(create_generic_9x3( sync_id, player_inventory, self.inventory.clone(), )))) } fn get_display_name(&self) -> TextComponent { TextComponent::translate("container.barrel", vec![]) } } // Erstellen Sie eine Factory für den Bildschirm let factory = MyScreenFactory { inventory: inventory.clone(), }; // Öffne das Inventar eines Spieler player.open_handled_screen(factory).await; // Der Bildschirm wird folgendes automatisch verarbeiten: // - Item-Bewegungen zwischen Slots // - Schnellbewegungsfunktion // - Spieler Inventar Interaktion // - Bildschirm schließen ``` ### Eigene Screen Handler erstellen Du kannst benutzerdefinierte Bildschirmhandler erstellen, indem du das `ScreenHandler`-Trait implementierst. Das bietet mehr Flexibilität und ermöglicht es, Bildschirme für andere Zwecke als nur die Inventarverwaltung zu erstellen. Um einen benutzerdefinierten Bildschirmhandler zu erstellen, kannst du eine neue Struktur anlegen, die das `Slot`-Trait implementiert. `NormalSlot` ist ein Slot, der mit Pumpkin mitgeliefert wird und lediglich als Index für ein Inventar dient und keine Einschränkungen hat. TODO: Beispiel für einen benutzerdefinierten Slot Hier ist ein Beispiel für den Quellcode des generischen Bildschirmhandlers: ```rust use std::{any::Any, sync::Arc}; use pumpkin_data::screen::WindowType; use pumpkin_world::{inventory::Inventory, item::ItemStack}; use pumpkin_world::{ player::player_inventory::PlayerInventory, screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour}, slot::NormalSlot, }; pub fn create_generic_9x3( sync_id: u8, player_inventory: &Arc, inventory: Arc, ) -> GenericContainerScreenHandler { GenericContainerScreenHandler::new( WindowType::Generic9x3, sync_id, player_inventory, inventory, 3, ) } pub struct GenericContainerScreenHandler { pub inventory: Arc, pub rows: u8, behaviour: ScreenHandlerBehaviour, } impl GenericContainerScreenHandler { fn new( screen_type: WindowType, sync_id: u8, player_inventory: &Arc, inventory: Arc, rows: u8, ) -> Self { let mut handler = Self { inventory, rows, behaviour: ScreenHandlerBehaviour::new(sync_id, Some(screen_type)), }; handler.add_inventory_slots(); let player_inventory: Arc = player_inventory.clone(); handler.add_player_slots(&player_inventory); handler } fn add_inventory_slots(&mut self) { for i in 0..self.rows { for j in 0..9 { self.add_slot(Arc::new(NormalSlot::new( self.inventory.clone(), (j + i * 9) as usize, ))); } } } } impl ScreenHandler for GenericContainerScreenHandler { fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> { Box::pin(async move { self.default_on_closed(player).await; self.inventory.on_close().await; }) } fn as_any(&self) -> &dyn Any { self } fn get_behaviour(&self) -> &ScreenHandlerBehaviour { &self.behaviour } fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour { &mut self.behaviour } fn quick_move<'a>( &'a mut self, _player: &'a dyn InventoryPlayer, slot_index: i32, ) -> ItemStackFuture<'a> { Box::pin(async move { let mut stack_left = ItemStack::EMPTY.clone(); // Assuming bounds check passed for slot_index by caller or within quick_move spec let slot = self.get_behaviour().slots[slot_index as usize].clone(); if slot.has_stack().await { let slot_stack_lock = slot.get_stack().await; let slot_stack_guard = slot_stack_lock.lock().await; stack_left = slot_stack_guard.clone(); // Release the guard before calling insert_item which needs its own lock drop(slot_stack_guard); // Re-acquire lock for insert_item (which expects &mut ItemStack) let mut slot_stack_mut = slot_stack_lock.lock().await; if slot_index < (self.rows * 9) as i32 { // Move from inventory to player area (end) if !self .insert_item( &mut slot_stack_mut, (self.rows * 9).into(), self.get_behaviour().slots.len() as i32, true, ) .await { return ItemStack::EMPTY.clone(); } } else if !self .insert_item(&mut slot_stack_mut, 0, (self.rows * 9).into(), false) .await { // Move from player area to inventory (start) return ItemStack::EMPTY.clone(); } // Check the resulting state of the slot stack after insert_item if slot_stack_mut.is_empty() { drop(slot_stack_mut); // Release lock slot.set_stack(ItemStack::EMPTY.clone()).await; } else { drop(slot_stack_mut); // Release lock slot.mark_dirty().await; } } stack_left }) } } ``` ## Best Practices 1. **Thread-Sicherheit** - Implementiere eine korrekte Fehlerbehandlung für die Slot-Grenzenprüfung. - Denk daran, Stack-Sperren vor der Verwendung von `inventory.set_stack()` oder `slot.get_cloned_stack()` aufzuheben, um Deadlocks zu vermeiden. 2. **Inventarverwaltung** - Verwende die Konstante `ItemStack::EMPTY` zum Leeren von Slots oder zum Initialisieren leerer Inventare. 3. **Implementierung der Bildschirmbehandlung** - Behandel das Schließen des Inventars korrekt, um den Verlust von Gegenständen zu verhindern. - Stelle sicher, dass die Anzahl der Slots der Anzahl der Slots im jeweiligen Fenstertyp entspricht. ## Beispiele ### Grundlegende Inventarnutzung ```rust // Erstellt ein neues Inventar let inventory = BasicInventory { items: [(); 27].map(|_| Arc::new(Mutex::new(ItemStack::EMPTY))), }; // Fügt ein Item dem Slot hinzu inventory.set_stack(0, ItemStack::new(1, &Items::OAK_LOG)).await; // Entfernt Items aus einem Slot let removed = inventory.remove_stack(0).await; // Prüft ob das Inventar leer ist let is_empty = inventory.is_empty().await; ```