1use std::{fmt, fmt::Debug};
2
3use azalea_client::{
4 Client,
5 inventory::{CloseContainerEvent, ContainerClickEvent, Inventory},
6 packet::game::ReceiveGamePacketEvent,
7};
8use azalea_core::position::BlockPos;
9use azalea_inventory::{
10 ItemStack, Menu,
11 operations::{ClickOperation, PickupClick, QuickMoveClick},
12};
13use azalea_physics::collision::BlockWithShape;
14use azalea_protocol::packets::game::ClientboundGamePacket;
15use bevy_app::{App, Plugin, Update};
16use bevy_ecs::{component::Component, prelude::MessageReader, system::Commands};
17use derive_more::Deref;
18use futures_lite::Future;
19
20use crate::bot::BotClientExt;
21
22pub struct ContainerPlugin;
23impl Plugin for ContainerPlugin {
24 fn build(&self, app: &mut App) {
25 app.add_systems(Update, handle_menu_opened_event);
26 }
27}
28
29pub trait ContainerClientExt {
30 fn open_container_at(
48 &self,
49 pos: BlockPos,
50 ) -> impl Future<Output = Option<ContainerHandle>> + Send;
51 fn open_inventory(&self) -> Option<ContainerHandle>;
62 fn get_inventory(&self) -> ContainerHandleRef;
74 fn get_held_item(&self) -> ItemStack;
77}
78
79impl ContainerClientExt for Client {
80 async fn open_container_at(&self, pos: BlockPos) -> Option<ContainerHandle> {
81 let mut ticks = self.get_tick_broadcaster();
82 for _ in 0..10 {
84 if !self
85 .world()
86 .read()
87 .get_block_state(pos)
88 .unwrap_or_default()
89 .is_collision_shape_empty()
90 {
91 break;
92 }
93 let _ = ticks.recv().await;
94 }
95
96 self.ecs
97 .lock()
98 .entity_mut(self.entity)
99 .insert(WaitingForInventoryOpen);
100 self.block_interact(pos);
101
102 while ticks.recv().await.is_ok() {
103 let ecs = self.ecs.lock();
104 if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
105 break;
106 }
107 }
108
109 let ecs = self.ecs.lock();
110 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
111 if inventory.id == 0 {
112 None
113 } else {
114 Some(ContainerHandle::new(inventory.id, self.clone()))
115 }
116 }
117
118 fn open_inventory(&self) -> Option<ContainerHandle> {
119 let ecs = self.ecs.lock();
120 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
121 if inventory.id == 0 {
122 Some(ContainerHandle::new(0, self.clone()))
123 } else {
124 None
125 }
126 }
127
128 fn get_inventory(&self) -> ContainerHandleRef {
129 self.query_self::<&Inventory, _>(|inv| ContainerHandleRef::new(inv.id, self.clone()))
130 }
131
132 fn get_held_item(&self) -> ItemStack {
133 self.query_self::<&Inventory, _>(|inv| inv.held_item())
134 }
135}
136
137pub struct ContainerHandleRef {
140 id: i32,
141 client: Client,
142}
143impl Debug for ContainerHandleRef {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 f.debug_struct("ContainerHandle")
146 .field("id", &self.id())
147 .finish()
148 }
149}
150impl ContainerHandleRef {
151 pub fn new(id: i32, client: Client) -> Self {
152 Self { id, client }
153 }
154
155 pub fn close(&self) {
156 self.client.ecs.lock().trigger(CloseContainerEvent {
157 entity: self.client.entity,
158 id: self.id,
159 });
160 }
161
162 pub fn id(&self) -> i32 {
166 self.id
167 }
168
169 pub fn menu(&self) -> Option<Menu> {
176 let ecs = self.client.ecs.lock();
177 let inventory = ecs
178 .get::<Inventory>(self.client.entity)
179 .expect("no inventory");
180
181 if inventory.id == self.id {
183 if self.id == 0 {
184 Some(inventory.inventory_menu.clone())
185 } else {
186 Some(inventory.container_menu.clone().unwrap())
187 }
188 } else {
189 None
190 }
191 }
192
193 pub fn contents(&self) -> Option<Vec<ItemStack>> {
196 self.menu().map(|menu| menu.contents())
197 }
198
199 pub fn slots(&self) -> Option<Vec<ItemStack>> {
202 self.menu().map(|menu| menu.slots())
203 }
204
205 pub fn left_click(&self, slot: impl Into<usize>) {
207 self.click(PickupClick::Left {
208 slot: Some(slot.into() as u16),
209 });
210 }
211 pub fn shift_click(&self, slot: impl Into<usize>) {
213 self.click(QuickMoveClick::Left {
214 slot: slot.into() as u16,
215 });
216 }
217 pub fn right_click(&self, slot: impl Into<usize>) {
219 self.click(PickupClick::Right {
220 slot: Some(slot.into() as u16),
221 });
222 }
223
224 pub fn click(&self, operation: impl Into<ClickOperation>) {
227 let operation = operation.into();
228 self.client.ecs.lock().trigger(ContainerClickEvent {
229 entity: self.client.entity,
230 window_id: self.id,
231 operation,
232 });
233 }
234}
235
236#[derive(Deref)]
239pub struct ContainerHandle(ContainerHandleRef);
240
241impl Drop for ContainerHandle {
242 fn drop(&mut self) {
243 self.0.close();
244 }
245}
246impl Debug for ContainerHandle {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.debug_struct("ContainerHandle")
249 .field("id", &self.id())
250 .finish()
251 }
252}
253impl ContainerHandle {
254 fn new(id: i32, client: Client) -> Self {
255 Self(ContainerHandleRef { id, client })
256 }
257
258 pub fn close(self) {
260 }
262}
263
264#[derive(Component, Debug)]
265pub struct WaitingForInventoryOpen;
266
267pub fn handle_menu_opened_event(
268 mut commands: Commands,
269 mut events: MessageReader<ReceiveGamePacketEvent>,
270) {
271 for event in events.read() {
272 if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet.as_ref() {
273 commands
274 .entity(event.entity)
275 .remove::<WaitingForInventoryOpen>();
276 }
277 }
278}