1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//! Inventory and container windows for the ghast Minecraft Java Edition client library
//! (1.8.9 and 1.16.5).
//!
//! [`Inventory`] holds what the vanilla client holds — the player inventory, the cursor stack
//! and the open window — fed by the server's window packets, and turns click intents into
//! `window_click` packets with **vanilla's client-side prediction**: the new slot contents, the
//! "clicked item" field (what vanilla's `onSlotClick` returns; the server rejects the click when
//! its own result differs) and the per-window action number.
//!
//! - **One click in flight.** A click stays pending until the server's confirm (`transaction`)
//! with its action number arrives. A rejected click makes the server resend the window;
//! clicks wait for that resync. [`Inventory::on_transaction`] also produces vanilla's
//! answer to rejected confirms — exactly once per confirm (route every confirm through it).
//! - **Exact or refused.** Clicks are simulated on a scratch copy; when the outcome depends on
//! data this crate does not model (recipes, furnace fuel, brewing, trading, …) the click is
//! refused with [`ClickError::Unpredictable`] instead of guessing, so the client state never
//! silently diverges from the server's (after an accepted click the server sends no
//! corrections — it assumes the client predicted right).
//! - **Lazy NBT.** Stacks keep their NBT as raw bytes ([`ghast_protocol::nbt::RawNbt`]);
//! `window_items` is applied without parsing any NBT. Stack comparisons compare the bytes
//! and only parse when they differ (vanilla compares trees).
//! - **Pure.** No I/O, no clock, no async: packets in, packets out. The client writes the
//! returned packets immediately, in call order, before the tick's movement packet, and calls
//! [`Inventory::tick`] once per tick.
//!
//! ```
//! use ghast_inventory::{Click, Inventory, Outgoing, Stack, Version};
//!
//! let mut inv = Inventory::new(Version::V1_8);
//! // The server sends window 0 (45 slots): 64 stone in hotbar slot 0 (window slot 36).
//! let mut items = vec![None; 45];
//! items[36] = Some(Stack::new(1, 64, 0));
//! inv.on_window_items(0, items);
//!
//! inv.tick();
//! assert_eq!(inv.open_inventory(), Ok(Some(Outgoing::OpenInventory))); // 1.8: C16
//! inv.tick();
//! let Ok(Outgoing::Click(c)) = inv.click(Click::Pickup { slot: 36, right: true }) else { panic!() };
//! assert_eq!((c.action, c.item.as_ref().unwrap().count), (1, 64)); // carries the slot before the click
//! assert_eq!(inv.cursor().unwrap().count, 32);
//! assert_eq!(inv.window0_slot(36).unwrap().count, 32);
//! assert!(inv.pending());
//! assert_eq!(inv.on_transaction(0, 1, true), None); // accepted: nothing to answer
//! assert!(!inv.pending());
//! ```
pub use RecipeBook;
pub use ;
pub use Version;
pub use OUTSIDE;
pub use ;
pub use ;