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::EventReader, 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 let ecs = self.ecs.lock();
130 let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
131 ContainerHandleRef::new(inventory.id, self.clone())
132 }
133
134 fn get_held_item(&self) -> ItemStack {
135 self.map_get_component::<Inventory, _>(|inventory| inventory.held_item())
136 .expect("no inventory")
137 }
138}
139
140pub struct ContainerHandleRef {
143 id: i32,
144 client: Client,
145}
146impl Debug for ContainerHandleRef {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_struct("ContainerHandle")
149 .field("id", &self.id())
150 .finish()
151 }
152}
153impl ContainerHandleRef {
154 pub fn new(id: i32, client: Client) -> Self {
155 Self { id, client }
156 }
157
158 pub fn close(&self) {
159 self.client.ecs.lock().send_event(CloseContainerEvent {
160 entity: self.client.entity,
161 id: self.id,
162 });
163 }
164
165 pub fn id(&self) -> i32 {
169 self.id
170 }
171
172 pub fn menu(&self) -> Option<Menu> {
179 let ecs = self.client.ecs.lock();
180 let inventory = ecs
181 .get::<Inventory>(self.client.entity)
182 .expect("no inventory");
183
184 if inventory.id == self.id {
186 if self.id == 0 {
187 Some(inventory.inventory_menu.clone())
188 } else {
189 Some(inventory.container_menu.clone().unwrap())
190 }
191 } else {
192 None
193 }
194 }
195
196 pub fn contents(&self) -> Option<Vec<ItemStack>> {
199 self.menu().map(|menu| menu.contents())
200 }
201
202 pub fn slots(&self) -> Option<Vec<ItemStack>> {
205 self.menu().map(|menu| menu.slots())
206 }
207
208 pub fn left_click(&self, slot: impl Into<usize>) {
210 self.click(PickupClick::Left {
211 slot: Some(slot.into() as u16),
212 });
213 }
214 pub fn shift_click(&self, slot: impl Into<usize>) {
216 self.click(QuickMoveClick::Left {
217 slot: slot.into() as u16,
218 });
219 }
220 pub fn right_click(&self, slot: impl Into<usize>) {
222 self.click(PickupClick::Right {
223 slot: Some(slot.into() as u16),
224 });
225 }
226
227 pub fn click(&self, operation: impl Into<ClickOperation>) {
228 let operation = operation.into();
229 self.client.ecs.lock().send_event(ContainerClickEvent {
230 entity: self.client.entity,
231 window_id: self.id,
232 operation,
233 });
234 }
235}
236
237#[derive(Deref)]
240pub struct ContainerHandle(ContainerHandleRef);
241
242impl Drop for ContainerHandle {
243 fn drop(&mut self) {
244 self.0.close();
245 }
246}
247impl Debug for ContainerHandle {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.debug_struct("ContainerHandle")
250 .field("id", &self.id())
251 .finish()
252 }
253}
254impl ContainerHandle {
255 fn new(id: i32, client: Client) -> Self {
256 Self(ContainerHandleRef { id, client })
257 }
258
259 pub fn close(self) {
261 }
263}
264
265#[derive(Component, Debug)]
266pub struct WaitingForInventoryOpen;
267
268fn handle_menu_opened_event(
269 mut commands: Commands,
270 mut events: EventReader<ReceiveGamePacketEvent>,
271) {
272 for event in events.read() {
273 if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet.as_ref() {
274 commands
275 .entity(event.entity)
276 .remove::<WaitingForInventoryOpen>();
277 }
278 }
279}