Skip to content

Commit 9d60273

Browse files
TebaremTestingPlantCuzImClicksandrewgazelka
authored
feat(inventory): Tebarem/inventory (#793)
closes #819 --------- Co-authored-by: TestingPlant <44930139+TestingPlant@users.noreply.github.com> Co-authored-by: Clicks <58398364+CuzImClicks@users.noreply.github.com> Co-authored-by: Andrew Gazelka <andrew.gazelka@gmail.com>
1 parent 65ab975 commit 9d60273

File tree

24 files changed

+1841
-937
lines changed

24 files changed

+1841
-937
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hyperion-gui/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ edition.workspace = true
77
anyhow = { workspace = true }
88
flecs_ecs = { workspace = true }
99
hyperion = { workspace = true }
10+
hyperion-inventory = { workspace = true }
1011
hyperion-utils = { workspace = true }
1112
serde = { version = "1.0", features = ["derive"] }
1213
valence_protocol = { workspace = true }
14+
tracing = { workspace = true }
1315

1416
[lints]
1517
workspace = true

crates/hyperion-gui/src/lib.rs

Lines changed: 55 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
1-
#![feature(thread_local)]
1+
use std::collections::HashMap;
22

3-
use std::{borrow::Cow, cell::Cell, collections::HashMap};
4-
5-
use anyhow::Context;
6-
use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider};
3+
use flecs_ecs::{
4+
core::{Entity, EntityView, EntityViewGet, World, WorldGet, WorldProvider},
5+
macros::Component,
6+
};
77
use hyperion::{
8-
net::{Compose, ConnectionId},
9-
simulation::{handlers::PacketSwitchQuery, packet::HandlerRegistry},
10-
valence_protocol::{
11-
ItemStack, VarInt,
12-
packets::play::{
13-
ClickSlotC2s,
14-
click_slot_c2s::ClickMode,
15-
close_screen_s2c::CloseScreenS2c,
16-
inventory_s2c::InventoryS2c,
17-
open_screen_s2c::{OpenScreenS2c, WindowType},
18-
},
19-
text::IntoText,
8+
simulation::{
9+
Spawn, Uuid, entity_kind::EntityKind, handlers::PacketSwitchQuery, packet::HandlerRegistry,
10+
},
11+
valence_protocol::packets::play::{
12+
ClickSlotC2s, click_slot_c2s::ClickMode, close_screen_s2c::CloseScreenS2c,
2013
},
2114
};
15+
use hyperion_inventory::{Inventory, InventoryState, OpenInventory};
2216
use hyperion_utils::LifetimeHandle;
2317
use serde::{Deserialize, Serialize};
2418

@@ -39,176 +33,79 @@ pub enum ContainerType {
3933
Hopper,
4034
}
4135

42-
#[derive(Clone)]
36+
#[derive(Component, Clone)]
4337
pub struct Gui {
44-
items: HashMap<usize, GuiItem>,
45-
size: usize,
46-
title: String,
47-
window_id: u8,
48-
container_type: ContainerType,
38+
entity: Entity,
39+
items: HashMap<usize, fn(Entity, ClickMode)>,
40+
pub id: u64,
4941
}
5042

51-
#[derive(Clone)]
52-
pub struct GuiItem {
53-
item: ItemStack,
54-
on_click: fn(Entity, ClickMode),
55-
}
56-
57-
/// Thread-local non-zero id means that it will be very unlikely that one player will have two
58-
/// of the same IDs at the same time when opening GUIs in succession.
59-
///
60-
/// We are skipping 0 because it is reserved for the player's inventory.
61-
fn non_zero_window_id() -> u8 {
62-
#[thread_local]
63-
static ID: Cell<u8> = Cell::new(0);
64-
65-
ID.set(ID.get().wrapping_add(1));
43+
impl Gui {
44+
#[must_use]
45+
pub fn new(inventory: Inventory, world: &World, id: u64) -> Self {
46+
let uuid = Uuid::new_v4();
6647

67-
if ID.get() == 0 {
68-
ID.set(1);
69-
}
48+
let entity = world
49+
.entity()
50+
.add_enum(EntityKind::BlockDisplay)
51+
.set(uuid)
52+
.set(inventory);
7053

71-
ID.get()
72-
}
54+
entity.enqueue(Spawn);
7355

74-
impl Gui {
75-
#[must_use]
76-
pub fn new(size: usize, title: String, container_type: ContainerType) -> Self {
7756
Self {
78-
window_id: non_zero_window_id(),
79-
title,
80-
size,
81-
container_type,
57+
entity: *entity,
8258
items: HashMap::new(),
59+
id,
8360
}
8461
}
8562

86-
#[must_use]
87-
pub const fn get_window_type(&self) -> WindowType {
88-
match self.container_type {
89-
ContainerType::Chest => WindowType::Generic9x3,
90-
ContainerType::ShulkerBox => WindowType::ShulkerBox,
91-
ContainerType::Furnace => WindowType::Furnace,
92-
ContainerType::Dispenser => WindowType::Generic3x3,
93-
ContainerType::Hopper => WindowType::Hopper,
94-
}
95-
}
96-
97-
pub fn add_item(&mut self, slot: usize, item: GuiItem) -> Result<(), String> {
98-
if slot >= self.size {
99-
return Err(format!(
100-
"Slot {} is out of bounds for GUI of size {}",
101-
slot, self.size
102-
));
103-
}
104-
105-
self.items.insert(slot, item);
106-
107-
Ok(())
108-
}
109-
110-
pub fn draw<'a>(&'a self, system: EntityView<'_>, player: Entity) {
111-
let container_items: Cow<'a, [ItemStack]> = (0..self.size)
112-
.map(|slot| {
113-
self.items
114-
.get(&slot)
115-
.map(|gui_item| gui_item.item.clone())
116-
.unwrap_or_default()
117-
})
118-
.collect();
119-
120-
let binding = ItemStack::default();
121-
let set_content_packet = InventoryS2c {
122-
window_id: self.window_id,
123-
state_id: VarInt(0),
124-
slots: container_items,
125-
carried_item: Cow::Borrowed(&binding),
126-
};
127-
128-
let world = system.world();
129-
130-
world.get::<&Compose>(|compose| {
131-
player.entity_view(world).get::<&ConnectionId>(|stream| {
132-
compose
133-
.unicast(&set_content_packet, *stream, system)
134-
.unwrap();
135-
});
136-
});
63+
pub fn add_command(&mut self, slot: usize, on_click: fn(Entity, ClickMode)) {
64+
self.items.insert(slot, on_click);
13765
}
13866

139-
pub fn open(&mut self, system: EntityView<'_>, player: Entity) {
140-
let open_screen_packet = OpenScreenS2c {
141-
window_id: VarInt(i32::from(self.window_id)),
142-
window_type: self.get_window_type(),
143-
window_title: self.title.clone().into_cow_text(),
144-
};
145-
146-
let world = system.world();
147-
148-
world.get::<&Compose>(|compose| {
149-
player.entity_view(world).get::<&ConnectionId>(|stream| {
150-
compose
151-
.unicast(&open_screen_packet, *stream, system)
152-
.unwrap();
153-
});
154-
});
155-
156-
self.draw(system, player);
157-
67+
pub fn init(&mut self, world: &World) {
15868
world.get::<&mut HandlerRegistry>(|registry| {
159-
let window_id = self.window_id;
16069
let items = self.items.clone();
161-
let gui = self.clone();
16270
registry.add_handler(Box::new(
16371
move |event: &ClickSlotC2s<'_>,
16472
_: &dyn LifetimeHandle<'_>,
16573
query: &mut PacketSwitchQuery<'_>| {
16674
let system = query.system;
75+
let world = system.world();
16776
let button = event.mode;
168-
169-
if event.window_id != window_id {
170-
return Ok(());
171-
}
172-
173-
let slot = usize::try_from(event.slot_idx).context("invalid slot index")?;
174-
let Some(item) = items.get(&slot) else {
175-
return Ok(());
176-
};
177-
178-
(item.on_click)(player, button);
179-
gui.draw(query.system, player);
180-
181-
let inventory = &*query.inventory;
182-
let compose = query.compose;
183-
let stream = query.io_ref;
184-
185-
// re-draw the inventory
186-
let player_inv = inventory.slots();
187-
188-
let set_content_packet = InventoryS2c {
189-
window_id: 0,
190-
state_id: VarInt(0),
191-
slots: Cow::Borrowed(player_inv),
192-
carried_item: Cow::Borrowed(&ItemStack::EMPTY),
193-
};
194-
195-
compose
196-
.unicast(&set_content_packet, stream, system)
197-
.unwrap();
77+
query
78+
.id
79+
.entity_view(world)
80+
.get::<&InventoryState>(|inv_state| {
81+
if event.window_id != inv_state.window_id() {
82+
return;
83+
}
84+
85+
let Ok(slot) = usize::try_from(event.slot_idx) else {
86+
return;
87+
};
88+
let Some(item) = items.get(&slot) else {
89+
return;
90+
};
91+
92+
item(query.id, button);
93+
});
19894

19995
Ok(())
20096
},
20197
));
20298
});
20399
}
204100

205-
pub fn handle_close(&mut self, _player: Entity, _close_packet: CloseScreenS2c) {
206-
todo!()
101+
pub fn open(&self, system: EntityView<'_>, player: Entity) {
102+
let world = system.world();
103+
player
104+
.entity_view(world)
105+
.set(OpenInventory::new(self.entity));
207106
}
208-
}
209107

210-
impl GuiItem {
211-
pub fn new(item: ItemStack, on_click: fn(Entity, ClickMode)) -> Self {
212-
Self { item, on_click }
108+
pub fn handle_close(&mut self, _player: Entity, _close_packet: CloseScreenS2c) {
109+
todo!()
213110
}
214111
}

crates/hyperion-inventory/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ roaring = {workspace = true}
44
snafu = {workspace = true}
55
valence_protocol = {workspace = true}
66
flecs_ecs = {workspace = true}
7+
derive_more = {workspace = true}
8+
tracing = {workspace = true}
79

810
[lints]
911
workspace = true

0 commit comments

Comments
 (0)