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+ } ;
77use 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 } ;
2216use hyperion_utils:: LifetimeHandle ;
2317use serde:: { Deserialize , Serialize } ;
2418
@@ -39,176 +33,79 @@ pub enum ContainerType {
3933 Hopper ,
4034}
4135
42- #[ derive( Clone ) ]
36+ #[ derive( Component , Clone ) ]
4337pub 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}
0 commit comments