aboutsummaryrefslogtreecommitdiff
path: root/azalea-client
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-12-28 14:31:41 +0500
committermat <git@matdoes.dev>2025-12-28 14:31:41 +0500
commit7ab3b8924f64f7eadb6b8928b6fae73cb06e4c2f (patch)
tree5add5d27fd7c2638ebe6fab9bd7adcdae28a358d /azalea-client
parent9513f42e87f64c409cdb2a100500a50e5a713bac (diff)
downloadazalea-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.rs4
-rw-r--r--azalea-client/src/lib.rs1
-rw-r--r--azalea-client/src/local_player.rs28
-rw-r--r--azalea-client/src/plugins/auto_reconnect.rs145
-rw-r--r--azalea-client/src/plugins/events.rs317
-rw-r--r--azalea-client/src/plugins/join.rs8
-rw-r--r--azalea-client/src/plugins/mod.rs4
-rw-r--r--azalea-client/src/plugins/packet/mod.rs4
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>()