1use std::fmt::Debug;
2use std::fmt::Formatter;
3
4use azalea_client::{
5 inventory::{CloseContainerEvent, ContainerClickEvent, Inventory},
6 packet_handling::game::PacketEvent,
7 Client,
8};
9use azalea_core::position::BlockPos;
10use azalea_inventory::{operations::ClickOperation, ItemStack, Menu};
11use azalea_protocol::packets::game::ClientboundGamePacket;
12use bevy_app::{App, Plugin, Update};
13use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
14use futures_lite::Future;
15
16use crate::bot::BotClientExt;
17
18pub struct ContainerPlugin;
19impl Plugin for ContainerPlugin {
20 fn build(&self, app: &mut App) {
21 app.add_systems(Update, handle_menu_opened_event);
22 }
23}
24
25pub trait ContainerClientExt {
26 fn open_container_at(
27 &mut self,
28 pos: BlockPos,
29 ) -> impl Future<Output = Option<ContainerHandle>> + Send;
30 fn open_inventory(&mut self) -> Option<ContainerHandle>;
31 fn get_open_container(&self) -> Option<ContainerHandleRef>;
32}
33
34impl ContainerClientExt for Client {
35 async fn open_container_at(&mut self, pos: BlockPos) -> Option<ContainerHandle> {
53 self.ecs
54 .lock()
55 .entity_mut(self.entity)
56 .insert(WaitingForInventoryOpen);
57 self.block_interact(pos);
58
59 let mut receiver = self.get_tick_broadcaster();
60 while receiver.recv().await.is_ok() {
61 let ecs = self.ecs.lock();
62 if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
63 break;
64 }
65 }
66
67 let ecs = self.ecs.lock();
68 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
69 if inventory.id == 0 {
70 None
71 } else {
72 Some(ContainerHandle::new(inventory.id, self.clone()))
73 }
74 }
75
76 fn open_inventory(&mut self) -> Option<ContainerHandle> {
87 let ecs = self.ecs.lock();
88 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
89
90 if inventory.id == 0 {
91 Some(ContainerHandle::new(0, self.clone()))
92 } else {
93 None
94 }
95 }
96
97 fn get_open_container(&self) -> Option<ContainerHandleRef> {
103 let ecs = self.ecs.lock();
104 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
105
106 if inventory.id == 0 {
107 None
108 } else {
109 Some(ContainerHandleRef {
110 id: inventory.id,
111 client: self.clone(),
112 })
113 }
114 }
115}
116
117pub struct ContainerHandleRef {
120 id: i32,
121 client: Client,
122}
123impl Debug for ContainerHandleRef {
124 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125 f.debug_struct("ContainerHandle")
126 .field("id", &self.id())
127 .finish()
128 }
129}
130impl ContainerHandleRef {
131 pub fn close(&self) {
132 self.client.ecs.lock().send_event(CloseContainerEvent {
133 entity: self.client.entity,
134 id: self.id,
135 });
136 }
137
138 pub fn id(&self) -> i32 {
142 self.id
143 }
144
145 pub fn menu(&self) -> Option<Menu> {
152 let ecs = self.client.ecs.lock();
153 let inventory = ecs
154 .get::<Inventory>(self.client.entity)
155 .expect("no inventory");
156
157 if inventory.id == self.id {
159 if self.id == 0 {
160 Some(inventory.inventory_menu.clone())
161 } else {
162 Some(inventory.container_menu.clone().unwrap())
163 }
164 } else {
165 None
166 }
167 }
168
169 pub fn contents(&self) -> Option<Vec<ItemStack>> {
172 self.menu().map(|menu| menu.contents())
173 }
174
175 pub fn click(&self, operation: impl Into<ClickOperation>) {
176 let operation = operation.into();
177 self.client.ecs.lock().send_event(ContainerClickEvent {
178 entity: self.client.entity,
179 window_id: self.id,
180 operation,
181 });
182 }
183}
184
185pub struct ContainerHandle(ContainerHandleRef);
188
189impl Drop for ContainerHandle {
190 fn drop(&mut self) {
191 self.0.close();
192 }
193}
194impl Debug for ContainerHandle {
195 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("ContainerHandle")
197 .field("id", &self.id())
198 .finish()
199 }
200}
201impl ContainerHandle {
202 fn new(id: i32, client: Client) -> Self {
203 Self(ContainerHandleRef { id, client })
204 }
205
206 pub fn id(&self) -> i32 {
210 self.0.id()
211 }
212
213 pub fn menu(&self) -> Option<Menu> {
220 self.0.menu()
221 }
222
223 pub fn contents(&self) -> Option<Vec<ItemStack>> {
226 self.0.contents()
227 }
228
229 pub fn click(&self, operation: impl Into<ClickOperation>) {
230 self.0.click(operation);
231 }
232}
233
234#[derive(Component, Debug)]
235pub struct WaitingForInventoryOpen;
236
237fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader<PacketEvent>) {
238 for event in events.read() {
239 if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet.as_ref() {
240 commands
241 .entity(event.entity)
242 .remove::<WaitingForInventoryOpen>();
243 }
244 }
245}