1
- #! [ feature ( thread_local ) ]
1
+ use std :: collections :: HashMap ;
2
2
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
+ } ;
7
7
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 ,
20
13
} ,
21
14
} ;
15
+ use hyperion_inventory:: { Inventory , InventoryState , OpenInventory } ;
22
16
use hyperion_utils:: LifetimeHandle ;
23
17
use serde:: { Deserialize , Serialize } ;
24
18
@@ -39,176 +33,79 @@ pub enum ContainerType {
39
33
Hopper ,
40
34
}
41
35
42
- #[ derive( Clone ) ]
36
+ #[ derive( Component , Clone ) ]
43
37
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 ,
49
41
}
50
42
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 ( ) ;
66
47
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) ;
70
53
71
- ID . get ( )
72
- }
54
+ entity. enqueue ( Spawn ) ;
73
55
74
- impl Gui {
75
- #[ must_use]
76
- pub fn new ( size : usize , title : String , container_type : ContainerType ) -> Self {
77
56
Self {
78
- window_id : non_zero_window_id ( ) ,
79
- title,
80
- size,
81
- container_type,
57
+ entity : * entity,
82
58
items : HashMap :: new ( ) ,
59
+ id,
83
60
}
84
61
}
85
62
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) ;
137
65
}
138
66
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 ) {
158
68
world. get :: < & mut HandlerRegistry > ( |registry| {
159
- let window_id = self . window_id ;
160
69
let items = self . items . clone ( ) ;
161
- let gui = self . clone ( ) ;
162
70
registry. add_handler ( Box :: new (
163
71
move |event : & ClickSlotC2s < ' _ > ,
164
72
_: & dyn LifetimeHandle < ' _ > ,
165
73
query : & mut PacketSwitchQuery < ' _ > | {
166
74
let system = query. system ;
75
+ let world = system. world ( ) ;
167
76
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
+ } ) ;
198
94
199
95
Ok ( ( ) )
200
96
} ,
201
97
) ) ;
202
98
} ) ;
203
99
}
204
100
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 ) ) ;
207
106
}
208
- }
209
107
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 ! ( )
213
110
}
214
111
}
0 commit comments