diff options
| author | mat <git@matdoes.dev> | 2025-12-28 14:31:41 +0500 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2025-12-28 14:31:41 +0500 |
| commit | 7ab3b8924f64f7eadb6b8928b6fae73cb06e4c2f (patch) | |
| tree | 5add5d27fd7c2638ebe6fab9bd7adcdae28a358d /azalea-client | |
| parent | 9513f42e87f64c409cdb2a100500a50e5a713bac (diff) | |
| download | azalea-drasl-7ab3b8924f64f7eadb6b8928b6fae73cb06e4c2f.tar.xz | |
move Event and auto_reconnect to the azalea crate
Diffstat (limited to 'azalea-client')
| -rw-r--r-- | azalea-client/src/account.rs | 4 | ||||
| -rw-r--r-- | azalea-client/src/lib.rs | 1 | ||||
| -rw-r--r-- | azalea-client/src/local_player.rs | 28 | ||||
| -rw-r--r-- | azalea-client/src/plugins/auto_reconnect.rs | 145 | ||||
| -rw-r--r-- | azalea-client/src/plugins/events.rs | 317 | ||||
| -rw-r--r-- | azalea-client/src/plugins/join.rs | 8 | ||||
| -rw-r--r-- | azalea-client/src/plugins/mod.rs | 4 | ||||
| -rw-r--r-- | azalea-client/src/plugins/packet/mod.rs | 4 |
8 files changed, 6 insertions, 505 deletions
diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index f988ade9..4db19d0e 100644 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -14,7 +14,7 @@ use uuid::Uuid; /// Something that can join Minecraft servers. /// -/// To join a server using this account, use [`Client::join`] or +/// To join a server using this account, use [`StartJoinServerEvent`] or /// [`azalea::ClientBuilder`]. /// /// This is also an ECS component that is present on our client entities. @@ -31,7 +31,7 @@ use uuid::Uuid; /// # } /// ``` /// -/// [`Client::join`]: crate::Client::join +/// [`StartJoinServerEvent`]: crate::join::StartJoinServerEvent /// [`azalea::ClientBuilder`]: https://docs.rs/azalea/latest/azalea/struct.ClientBuilder.html #[derive(Clone, Component, Debug)] pub struct Account { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index d9e66333..6bdc2713 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -22,6 +22,5 @@ pub use bevy_tasks; pub use client::{ InConfigState, InGameState, JoinedClientBundle, LocalPlayerBundle, start_ecs_runner, }; -pub use events::Event; pub use movement::{StartSprintEvent, StartWalkEvent}; pub use plugins::*; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 977b38f3..cc2a28dc 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -1,19 +1,13 @@ -use std::{ - collections::HashMap, - error, io, - sync::{Arc, PoisonError}, -}; +use std::{collections::HashMap, sync::Arc}; use azalea_core::game_type::GameMode; use azalea_world::{Instance, PartialInstance}; use bevy_ecs::{component::Component, prelude::*}; use derive_more::{Deref, DerefMut}; use parking_lot::RwLock; -use thiserror::Error; -use tokio::sync::mpsc; use uuid::Uuid; -use crate::{ClientInformation, events::Event as AzaleaEvent, player::PlayerInfo}; +use crate::{ClientInformation, player::PlayerInfo}; /// A component that keeps strong references to our [`PartialInstance`] and /// [`Instance`] for local players. @@ -145,21 +139,3 @@ impl InstanceHolder { self.partial_instance.write().reset(); } } - -#[derive(Debug, Error)] -pub enum HandlePacketError { - #[error("{0}")] - Poison(String), - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - Other(#[from] Box<dyn error::Error + Send + Sync>), - #[error("{0}")] - Send(#[from] mpsc::error::SendError<AzaleaEvent>), -} - -impl<T> From<PoisonError<T>> for HandlePacketError { - fn from(e: PoisonError<T>) -> Self { - HandlePacketError::Poison(e.to_string()) - } -} diff --git a/azalea-client/src/plugins/auto_reconnect.rs b/azalea-client/src/plugins/auto_reconnect.rs deleted file mode 100644 index 4851a4e7..00000000 --- a/azalea-client/src/plugins/auto_reconnect.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Auto-reconnect to the server when the client is kicked. -//! -//! See [`AutoReconnectPlugin`] for more information. - -use std::time::{Duration, Instant}; - -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; - -use super::{ - disconnect::DisconnectEvent, - events::LocalPlayerEvents, - join::{ConnectOpts, ConnectionFailedEvent, StartJoinServerEvent}, -}; -use crate::Account; - -/// The default delay that Azalea will use for reconnecting our clients. -/// -/// See [`AutoReconnectPlugin`] for more information. -pub const DEFAULT_RECONNECT_DELAY: Duration = Duration::from_secs(5); - -/// A default plugin that makes clients automatically rejoin the server when -/// they're disconnected. -/// -/// The reconnect delay is configurable globally or per-client with the -/// [`AutoReconnectDelay`] resource/component. Auto reconnecting can be disabled -/// by removing the resource from the ECS. -/// -/// The delay defaults to [`DEFAULT_RECONNECT_DELAY`]. -pub struct AutoReconnectPlugin; -impl Plugin for AutoReconnectPlugin { - fn build(&self, app: &mut App) { - app.insert_resource(AutoReconnectDelay::new(DEFAULT_RECONNECT_DELAY)) - .add_systems( - Update, - (start_rejoin_on_disconnect, rejoin_after_delay) - .chain() - .before(super::join::handle_start_join_server_event), - ); - } -} - -pub fn start_rejoin_on_disconnect( - mut commands: Commands, - mut disconnect_events: MessageReader<DisconnectEvent>, - mut connection_failed_events: MessageReader<ConnectionFailedEvent>, - auto_reconnect_delay_res: Option<Res<AutoReconnectDelay>>, - auto_reconnect_delay_query: Query<&AutoReconnectDelay>, -) { - for entity in disconnect_events - .read() - .map(|e| e.entity) - .chain(connection_failed_events.read().map(|e| e.entity)) - { - let Some(delay) = get_delay( - &auto_reconnect_delay_res, - auto_reconnect_delay_query, - entity, - ) else { - // no auto reconnect - continue; - }; - - let reconnect_after = Instant::now() + delay; - commands.entity(entity).insert(InternalReconnectAfter { - instant: reconnect_after, - }); - } -} - -fn get_delay( - auto_reconnect_delay_res: &Option<Res<AutoReconnectDelay>>, - auto_reconnect_delay_query: Query<&AutoReconnectDelay>, - entity: Entity, -) -> Option<Duration> { - let delay = if let Ok(c) = auto_reconnect_delay_query.get(entity) { - Some(c.delay) - } else { - auto_reconnect_delay_res.as_ref().map(|r| r.delay) - }; - - if delay == Some(Duration::MAX) { - // if the duration is set to max, treat that as autoreconnect being disabled - return None; - } - delay -} - -pub fn rejoin_after_delay( - mut commands: Commands, - mut join_events: MessageWriter<StartJoinServerEvent>, - query: Query<( - Entity, - &InternalReconnectAfter, - &Account, - &ConnectOpts, - Option<&LocalPlayerEvents>, - )>, -) { - for (entity, reconnect_after, account, connect_opts, local_player_events) in query.iter() { - if Instant::now() >= reconnect_after.instant { - // don't keep trying to reconnect - commands.entity(entity).remove::<InternalReconnectAfter>(); - - // our Entity will be reused since the account has the same uuid - join_events.write(StartJoinServerEvent { - account: account.clone(), - connect_opts: connect_opts.clone(), - // not actually necessary since we're reusing the same entity and LocalPlayerEvents - // isn't removed, but this is more readable and just in case it's changed in the - // future - event_sender: local_player_events.map(|e| e.0.clone()), - start_join_callback_tx: None, - }); - } - } -} - -/// A resource *and* component that indicates how long to wait before -/// reconnecting when we're kicked. -/// -/// Initially, it's a resource in the ECS set to 5 seconds. You can modify -/// the resource to update the global reconnect delay, or insert it as a -/// component to set the individual delay for a single client. -/// -/// You can also remove this resource from the ECS to disable the default -/// auto-reconnecting behavior. Inserting the resource/component again will not -/// make clients that were already disconnected automatically reconnect. -#[derive(Clone, Component, Debug, Resource)] -pub struct AutoReconnectDelay { - pub delay: Duration, -} -impl AutoReconnectDelay { - pub fn new(delay: Duration) -> Self { - Self { delay } - } -} - -/// This is inserted when we're disconnected and indicates when we'll reconnect. -/// -/// This is set based on [`AutoReconnectDelay`]. -#[derive(Clone, Component, Debug)] -pub struct InternalReconnectAfter { - pub instant: Instant, -} diff --git a/azalea-client/src/plugins/events.rs b/azalea-client/src/plugins/events.rs deleted file mode 100644 index bd0419cb..00000000 --- a/azalea-client/src/plugins/events.rs +++ /dev/null @@ -1,317 +0,0 @@ -//! Defines the [`enum@Event`] enum and makes those events trigger when they're -//! sent in the ECS. - -use std::sync::Arc; - -use azalea_chat::FormattedText; -use azalea_core::{position::ChunkPos, tick::GameTick}; -use azalea_entity::{Dead, InLoadedChunk}; -use azalea_protocol::packets::game::c_player_combat_kill::ClientboundPlayerCombatKill; -use azalea_world::{InstanceName, MinecraftEntityId}; -use bevy_app::{App, Plugin, PreUpdate, Update}; -use bevy_ecs::prelude::*; -use derive_more::{Deref, DerefMut}; -use tokio::sync::mpsc; - -use crate::{ - chat::{ChatPacket, ChatReceivedEvent}, - chunks::ReceiveChunkEvent, - disconnect::DisconnectEvent, - packet::game::{ - AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent, - }, - player::PlayerInfo, -}; - -// (for contributors): -// HOW TO ADD A NEW (packet based) EVENT: -// - Add it as an ECS event first: -// - Make a struct that contains an entity field and some data fields (look -// in packet/game/events.rs for examples. These structs should always have -// their names end with "Event". -// - (the `entity` field is the local player entity that's receiving the -// event) -// - In the GamePacketHandler, you always have a `player` field that you can -// use. -// - Add the event struct in PacketPlugin::build -// - (in the `impl Plugin for PacketPlugin`) -// - To get the event writer, you have to get an MessageWriter<ThingEvent>. -// Look at other packets in packet/game/mod.rs for examples. -// -// At this point, you've created a new ECS event. That's annoying for bots to -// use though, so you might wanna add it to the Event enum too: -// - In this file, add a new variant to that Event enum with the same name -// as your event (without the "Event" suffix). -// - Create a new system function like the other ones here, and put that -// system function in the `impl Plugin for EventsPlugin` - -/// Something that happened in-game, such as a tick passing or chat message -/// being sent. -/// -/// Note: Events are sent before they're processed, so for example game ticks -/// happen at the beginning of a tick before anything has happened. -#[derive(Clone, Debug)] -#[non_exhaustive] -pub enum Event { - /// Happens right after the bot switches into the Game state, but before - /// it's actually spawned. - /// - /// This can be useful for setting the client information with - /// [`Client::set_client_information`], so the packet doesn't have to be - /// sent twice. - /// - /// You may want to use [`Event::Spawn`] instead to wait for the bot to be - /// in the world. - /// - /// [`Client::set_client_information`]: crate::Client::set_client_information - Init, - /// Fired when we receive a login packet, which is after [`Event::Init`] but - /// before [`Event::Spawn`]. You usually want [`Event::Spawn`] instead. - /// - /// Your position may be [`Vec3::ZERO`] immediately after you receive this - /// event, but it'll be ready by the time you get [`Event::Spawn`]. - /// - /// It's possible for this event to be sent multiple times per client if a - /// server sends multiple login packets (like when switching worlds). - /// - /// [`Vec3::ZERO`]: azalea_core::position::Vec3::ZERO - Login, - /// Fired when the player fully spawns into the world (is in a loaded chunk) - /// and is ready to interact with it. - /// - /// This is usually the event you should listen for when waiting for the bot - /// to be ready. - /// - /// This event will be sent every time the client respawns or switches - /// worlds, as long as the server sends chunks to the client. - Spawn, - /// A chat message was sent in the game chat. - Chat(ChatPacket), - /// Happens 20 times per second, but only when the world is loaded. - Tick, - #[cfg(feature = "packet-event")] - /// We received a packet from the server. - /// - /// ``` - /// # use azalea_client::Event; - /// # use azalea_protocol::packets::game::ClientboundGamePacket; - /// # async fn example(event: Event) { - /// # match event { - /// Event::Packet(packet) => match &*packet { - /// ClientboundGamePacket::Login(_) => { - /// println!("login packet"); - /// } - /// _ => {} - /// }, - /// # _ => {} - /// # } - /// # } - /// ``` - Packet(Arc<azalea_protocol::packets::game::ClientboundGamePacket>), - /// A player joined the game (or more specifically, was added to the tab - /// list). - AddPlayer(PlayerInfo), - /// A player left the game (or maybe is still in the game and was just - /// removed from the tab list). - RemovePlayer(PlayerInfo), - /// A player was updated in the tab list (gamemode, display - /// name, or latency changed). - UpdatePlayer(PlayerInfo), - /// The client player died in-game. - Death(Option<Arc<ClientboundPlayerCombatKill>>), - /// A `KeepAlive` packet was sent by the server. - KeepAlive(u64), - /// The client disconnected from the server. - Disconnect(Option<FormattedText>), - ReceiveChunk(ChunkPos), -} - -/// A component that contains an event sender for events that are only -/// received by local players. -/// -/// The receiver for this is returned by [`Client::start_client`]. -/// -/// [`Client::start_client`]: crate::Client::start_client -#[derive(Component, Deref, DerefMut)] -pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>); - -pub struct EventsPlugin; -impl Plugin for EventsPlugin { - fn build(&self, app: &mut App) { - app.add_systems( - Update, - ( - chat_listener, - login_listener, - spawn_listener, - #[cfg(feature = "packet-event")] - packet_listener, - add_player_listener, - update_player_listener, - remove_player_listener, - keepalive_listener, - death_listener, - disconnect_listener, - receive_chunk_listener, - ), - ) - .add_systems( - PreUpdate, - init_listener.before(super::connection::read_packets), - ) - .add_systems(GameTick, tick_listener); - } -} - -// when LocalPlayerEvents is added, it means the client just started -pub fn init_listener(query: Query<&LocalPlayerEvents, Added<LocalPlayerEvents>>) { - for local_player_events in &query { - let _ = local_player_events.send(Event::Init); - } -} - -// when MinecraftEntityId is added, it means the player is now in the world -pub fn login_listener( - query: Query<(Entity, &LocalPlayerEvents), Added<MinecraftEntityId>>, - mut commands: Commands, -) { - for (entity, local_player_events) in &query { - let _ = local_player_events.send(Event::Login); - commands.entity(entity).remove::<SentSpawnEvent>(); - } -} - -/// A unit struct component that indicates that the entity has sent -/// [`Event::Spawn`]. -/// -/// This is just used internally by the [`spawn_listener`] system to avoid -/// sending the event twice if we stop being in an unloaded chunk. It's removed -/// when we receive a login packet. -#[derive(Component)] -pub struct SentSpawnEvent; -#[allow(clippy::type_complexity)] -pub fn spawn_listener( - query: Query<(Entity, &LocalPlayerEvents), (Added<InLoadedChunk>, Without<SentSpawnEvent>)>, - mut commands: Commands, -) { - for (entity, local_player_events) in &query { - let _ = local_player_events.send(Event::Spawn); - commands.entity(entity).insert(SentSpawnEvent); - } -} - -pub fn chat_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<ChatReceivedEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::Chat(event.packet.clone())); - } - } -} - -// only tick if we're in a world -pub fn tick_listener(query: Query<&LocalPlayerEvents, With<InstanceName>>) { - for local_player_events in &query { - let _ = local_player_events.send(Event::Tick); - } -} - -#[cfg(feature = "packet-event")] -pub fn packet_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<super::packet::game::ReceiveGamePacketEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::Packet(event.packet.clone())); - } - } -} - -pub fn add_player_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<AddPlayerEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::AddPlayer(event.info.clone())); - } - } -} - -pub fn update_player_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<UpdatePlayerEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::UpdatePlayer(event.info.clone())); - } - } -} - -pub fn remove_player_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<RemovePlayerEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::RemovePlayer(event.info.clone())); - } - } -} - -pub fn death_listener(query: Query<&LocalPlayerEvents>, mut events: MessageReader<DeathEvent>) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::Death(event.packet.clone().map(|p| p.into()))); - } - } -} - -/// Send the "Death" event for [`LocalEntity`]s that died with no reason. -/// -/// [`LocalEntity`]: azalea_entity::LocalEntity -pub fn dead_component_listener(query: Query<&LocalPlayerEvents, Added<Dead>>) { - for local_player_events in &query { - local_player_events.send(Event::Death(None)).unwrap(); - } -} - -pub fn keepalive_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<KeepAliveEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::KeepAlive(event.id)); - } - } -} - -pub fn disconnect_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<DisconnectEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::Disconnect(event.reason.clone())); - } - } -} - -pub fn receive_chunk_listener( - query: Query<&LocalPlayerEvents>, - mut events: MessageReader<ReceiveChunkEvent>, -) { - for event in events.read() { - if let Ok(local_player_events) = query.get(event.entity) { - let _ = local_player_events.send(Event::ReceiveChunk(ChunkPos::new( - event.packet.x, - event.packet.z, - ))); - } - } -} diff --git a/azalea-client/src/plugins/join.rs b/azalea-client/src/plugins/join.rs index b1759992..058f6c10 100644 --- a/azalea-client/src/plugins/join.rs +++ b/azalea-client/src/plugins/join.rs @@ -19,7 +19,6 @@ use parking_lot::RwLock; use tokio::sync::mpsc; use tracing::{debug, warn}; -use super::events::LocalPlayerEvents; use crate::{ Account, LocalPlayerBundle, connection::RawConnection, @@ -51,7 +50,6 @@ impl Plugin for JoinPlugin { pub struct StartJoinServerEvent { pub account: Account, pub connect_opts: ConnectOpts, - pub event_sender: Option<mpsc::UnboundedSender<crate::Event>>, // this is mpsc instead of oneshot so it can be cloned (since it's sent in an event) pub start_join_callback_tx: Option<mpsc::UnboundedSender<Entity>>, @@ -147,12 +145,6 @@ pub fn handle_start_join_server_event( // immediately when the connection is created )); - if let Some(event_sender) = &event.event_sender { - // this is optional so we don't leak memory in case the user doesn't want to - // handle receiving packets - entity_mut.insert(LocalPlayerEvents(event_sender.clone())); - } - let task_pool = IoTaskPool::get(); let connect_opts = event.connect_opts.clone(); let task = task_pool.spawn(async_compat::Compat::new( diff --git a/azalea-client/src/plugins/mod.rs b/azalea-client/src/plugins/mod.rs index a4aec19f..35e26f9e 100644 --- a/azalea-client/src/plugins/mod.rs +++ b/azalea-client/src/plugins/mod.rs @@ -1,7 +1,6 @@ use bevy_app::{PluginGroup, PluginGroupBuilder}; pub mod attack; -pub mod auto_reconnect; pub mod block_update; pub mod brand; pub mod chat; @@ -12,7 +11,6 @@ pub mod client_information; pub mod connection; pub mod cookies; pub mod disconnect; -pub mod events; pub mod interact; pub mod inventory; pub mod join; @@ -41,7 +39,6 @@ impl PluginGroup for DefaultPlugins { .add(crate::client::AzaleaPlugin) .add(azalea_entity::EntityPlugin) .add(azalea_physics::PhysicsPlugin) - .add(events::EventsPlugin) .add(task_pool::TaskPoolPlugin::default()) .add(inventory::InventoryPlugin) .add(chat::ChatPlugin) @@ -62,7 +59,6 @@ impl PluginGroup for DefaultPlugins { .add(connection::ConnectionPlugin) .add(login::LoginPlugin) .add(join::JoinPlugin) - .add(auto_reconnect::AutoReconnectPlugin) .add(cookies::CookiesPlugin); #[cfg(feature = "online-mode")] { diff --git a/azalea-client/src/plugins/packet/mod.rs b/azalea-client/src/plugins/packet/mod.rs index 30503d50..9d842dc6 100644 --- a/azalea-client/src/plugins/packet/mod.rs +++ b/azalea-client/src/plugins/packet/mod.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ }; use self::game::DeathEvent; -use crate::{chat::ChatReceivedEvent, events::death_listener}; +use crate::chat::ChatReceivedEvent; pub mod config; pub mod game; @@ -33,7 +33,7 @@ impl Plugin for PacketPlugin { app.add_observer(game::handle_outgoing_packets_observer) .add_observer(config::handle_outgoing_packets_observer) .add_observer(login::handle_outgoing_packets_observer) - .add_systems(Update, death_event_on_0_health.before(death_listener)) + .add_systems(Update, death_event_on_0_health) .add_message::<game::ReceiveGamePacketEvent>() .add_message::<config::ReceiveConfigPacketEvent>() .add_message::<login::ReceiveLoginPacketEvent>() |
