1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

container handle

This commit is contained in:
mat 2023-03-13 21:53:53 -05:00
parent 6286e953a6
commit 9254f388c1
3 changed files with 128 additions and 27 deletions

View file

@ -43,7 +43,7 @@ use azalea_world::{
entity::{EntityPlugin, EntityUpdateSet, Local, Position, WorldName}, entity::{EntityPlugin, EntityUpdateSet, Local, Position, WorldName},
Instance, InstanceContainer, PartialInstance, Instance, InstanceContainer, PartialInstance,
}; };
use bevy_app::{App, CoreSchedule, Plugin, PluginGroup, PluginGroupBuilder}; use bevy_app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
use bevy_ecs::{ use bevy_ecs::{
bundle::Bundle, bundle::Bundle,
component::Component, component::Component,
@ -632,32 +632,32 @@ pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::UnboundedSender<(
} }
/// A resource that contains a [`broadcast::Sender`] that will be sent every /// A resource that contains a [`broadcast::Sender`] that will be sent every
/// time the ECS schedule is run. /// Minecraft tick.
/// ///
/// This is useful for running code every schedule from async user code. /// This is useful for running code every schedule from async user code.
/// ///
/// ```no_run /// ```no_run
/// let mut receiver = { /// let mut receiver = {
/// let ecs = client.ecs.lock(); /// let ecs = client.ecs.lock();
/// let schedule_broadcast = ecs.resource::<RanScheduleBroadcast>(); /// let tick_broadcast = ecs.resource::<TickBroadcast>();
/// schedule_broadcast.subscribe() /// tick_broadcast.subscribe()
/// }; /// };
/// while receiver.recv().await.is_ok() { /// while receiver.recv().await.is_ok() {
/// // do something /// // do something
/// } /// }
/// ``` /// ```
#[derive(Resource, Deref)] #[derive(Resource, Deref)]
pub struct RanScheduleBroadcast(broadcast::Sender<()>); pub struct TickBroadcast(broadcast::Sender<()>);
fn send_ran_schedule_event(ran_schedule_broadcast: ResMut<RanScheduleBroadcast>) { fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
let _ = ran_schedule_broadcast.0.send(()); let _ = tick_broadcast.0.send(());
} }
/// A plugin that makes the [`RanScheduleBroadcast`] resource available. /// A plugin that makes the [`RanScheduleBroadcast`] resource available.
pub struct RanSchedulePlugin; pub struct TickBroadcastPlugin;
impl Plugin for RanSchedulePlugin { impl Plugin for TickBroadcastPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(RanScheduleBroadcast(broadcast::channel(1).0)) app.insert_resource(TickBroadcast(broadcast::channel(1).0))
.add_system(send_ran_schedule_event); .add_system(send_tick_broadcast.in_schedule(CoreSchedule::FixedUpdate));
} }
} }
@ -681,6 +681,6 @@ impl PluginGroup for DefaultPlugins {
.add(DisconnectPlugin) .add(DisconnectPlugin)
.add(PlayerMovePlugin) .add(PlayerMovePlugin)
.add(InteractPlugin) .add(InteractPlugin)
.add(RanSchedulePlugin) .add(TickBroadcastPlugin)
} }
} }

View file

@ -1,29 +1,39 @@
use std::fmt::{Debug, Formatter};
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
use azalea_core::BlockPos; use azalea_core::BlockPos;
use azalea_inventory::{ItemSlot, Menu}; use azalea_inventory::{ItemSlot, Menu};
use azalea_protocol::packets::game::serverbound_container_close_packet::ServerboundContainerClosePacket;
use azalea_registry::MenuKind; use azalea_registry::MenuKind;
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
entity::Entity, entity::Entity,
event::EventReader, event::EventReader,
schedule::IntoSystemConfigs, prelude::EventWriter,
schedule::{IntoSystemConfig, IntoSystemConfigs},
system::{Commands, Query}, system::{Commands, Query},
}; };
use crate::{client::RanScheduleBroadcast, Client}; use crate::{client::TickBroadcast, local_player::handle_send_packet_event, Client, LocalPlayer};
pub struct InventoryPlugin; pub struct InventoryPlugin;
impl Plugin for InventoryPlugin { impl Plugin for InventoryPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<ClientSideCloseContainerEvent>() app.add_event::<ClientSideCloseContainerEvent>()
.add_event::<MenuOpenedEvent>() .add_event::<MenuOpenedEvent>()
.add_event::<CloseContainerEvent>()
.add_systems( .add_systems(
( (
handle_menu_opened_event, handle_menu_opened_event,
handle_client_side_close_container_event, handle_client_side_close_container_event,
) )
.chain(), .chain(),
)
.add_system(
handle_container_close_event
.before(handle_send_packet_event)
.before(handle_client_side_close_container_event),
); );
} }
} }
@ -32,7 +42,7 @@ impl Plugin for InventoryPlugin {
pub struct WaitingForInventoryOpen; pub struct WaitingForInventoryOpen;
impl Client { impl Client {
pub async fn open_container(&mut self, pos: BlockPos) -> Option<Menu> { pub async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle> {
self.ecs self.ecs
.lock() .lock()
.entity_mut(self.entity) .entity_mut(self.entity)
@ -41,8 +51,8 @@ impl Client {
let mut receiver = { let mut receiver = {
let ecs = self.ecs.lock(); let ecs = self.ecs.lock();
let schedule_broadcast = ecs.resource::<RanScheduleBroadcast>(); let tick_broadcast = ecs.resource::<TickBroadcast>();
schedule_broadcast.subscribe() tick_broadcast.subscribe()
}; };
while receiver.recv().await.is_ok() { while receiver.recv().await.is_ok() {
let ecs = self.ecs.lock(); let ecs = self.ecs.lock();
@ -52,13 +62,69 @@ impl Client {
} }
let ecs = self.ecs.lock(); let ecs = self.ecs.lock();
let inventory = ecs.get::<InventoryComponent>(self.entity); let inventory = ecs
if let Some(inventory) = inventory { .get::<InventoryComponent>(self.entity)
inventory.container_menu.clone() .expect("no inventory");
if inventory.id == 0 {
None
} else {
Some(ContainerHandle {
id: inventory.id,
client: self.clone(),
})
}
}
/// Return the menu that is currently open. If no menu is open, this will
/// have the player's inventory.
pub fn menu(&self) -> Menu {
let mut ecs = self.ecs.lock();
let inventory = self.query::<&InventoryComponent>(&mut ecs);
inventory.menu().clone()
}
}
/// A handle to the open container. The container will be closed once this is
/// dropped.
pub struct ContainerHandle {
pub id: i8,
client: Client,
}
impl Drop for ContainerHandle {
fn drop(&mut self) {
self.client.ecs.lock().send_event(CloseContainerEvent {
entity: self.client.entity,
id: self.id,
});
}
}
impl Debug for ContainerHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContainerHandle")
.field("id", &self.id)
.finish()
}
}
impl ContainerHandle {
/// Returns the menu of the container. If the container is closed, this
/// will return `None`.
pub fn menu(&self) -> Option<Menu> {
let ecs = self.client.ecs.lock();
let inventory = ecs
.get::<InventoryComponent>(self.client.entity)
.expect("no inventory");
if inventory.id == self.id {
Some(inventory.container_menu.clone().unwrap())
} else { } else {
None None
} }
} }
/// Returns the item slots in the container. If the container is closed,
/// this will return `None`.
pub fn contents(&self) -> Option<Vec<ItemSlot>> {
self.menu().map(|menu| menu.contents())
}
} }
/// A component present on all local players that have an inventory. /// A component present on all local players that have an inventory.
@ -145,6 +211,39 @@ fn handle_menu_opened_event(
} }
} }
/// Tell the server that we want to close a container.
///
/// Note that this is also sent when the client closes its own inventory, even
/// though there is no packet for opening its inventory.
pub struct CloseContainerEvent {
pub entity: Entity,
/// The ID of the container to close. 0 for the player's inventory. If this
/// is not the same as the currently open inventory, nothing will happen.
pub id: i8,
}
fn handle_container_close_event(
mut events: EventReader<CloseContainerEvent>,
mut client_side_events: EventWriter<ClientSideCloseContainerEvent>,
query: Query<(&LocalPlayer, &InventoryComponent)>,
) {
for event in events.iter() {
let (local_player, inventory) = query.get(event.entity).unwrap();
if event.id != inventory.id {
continue;
}
local_player.write_packet(
ServerboundContainerClosePacket {
container_id: inventory.id as u8,
}
.get(),
);
client_side_events.send(ClientSideCloseContainerEvent {
entity: event.entity,
});
}
}
/// Close a container without notifying the server. /// Close a container without notifying the server.
/// ///
/// Note that this also gets fired when we get a [`CloseContainerEvent`]. /// Note that this also gets fired when we get a [`CloseContainerEvent`].

View file

@ -7,7 +7,6 @@ use azalea::entity::metadata::Player;
use azalea::entity::{EyeHeight, Position}; use azalea::entity::{EyeHeight, Position};
use azalea::interact::HitResultComponent; use azalea::interact::HitResultComponent;
use azalea::inventory::ItemSlot; use azalea::inventory::ItemSlot;
use azalea::inventory_plugin::InventoryComponent;
use azalea::pathfinder::BlockPosGoal; use azalea::pathfinder::BlockPosGoal;
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection}; use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
use azalea::{Account, Client, Event}; use azalea::{Account, Client, Event};
@ -145,9 +144,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
std::thread::sleep(Duration::from_millis(1000)); std::thread::sleep(Duration::from_millis(1000));
} }
"inventory" => { "inventory" => {
let mut ecs = bot.ecs.lock(); println!("inventory: {:?}", bot.menu());
let inventory = bot.query::<&InventoryComponent>(&mut ecs);
println!("inventory: {:?}", inventory.menu());
} }
"findblock" => { "findblock" => {
let target_pos = bot let target_pos = bot
@ -196,11 +193,16 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
}; };
bot.look_at(target_pos.center()); bot.look_at(target_pos.center());
let container = bot.open_container(target_pos).await; let container = bot.open_container(target_pos).await;
println!("container: {:?}", container);
if let Some(container) = container { if let Some(container) = container {
for item in container.contents() { if let Some(contents) = container.contents() {
if let ItemSlot::Present(item) = item { for item in contents {
println!("item: {:?}", item); if let ItemSlot::Present(item) = item {
println!("item: {:?}", item);
}
} }
} else {
println!("container was immediately closed");
} }
} else { } else {
println!("no container found"); println!("no container found");