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

Refactor azalea-client (#205)

* start organizing packet_handling more by moving packet handlers into their own functions

* finish writing all the handler functions for packets

* use macro for generating match statement for packet handler functions

* fix set_entity_data

* update config state to also use handler functions

* organize az-client file structure by moving things into plugins directory

* fix merge issues
This commit is contained in:
mat 2025-02-22 21:45:26 -06:00 committed by GitHub
parent f8130c3c92
commit e21e1b97bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2342 additions and 2087 deletions

7
Cargo.lock generated
View file

@ -356,6 +356,7 @@ dependencies = [
"derive_more 2.0.1",
"minecraft_folder_path",
"parking_lot",
"paste",
"regex",
"reqwest",
"simdnbt",
@ -2219,6 +2220,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"

View file

@ -32,7 +32,6 @@ repository = "https://github.com/azalea-rs/azalea"
aes = "0.8.4"
anyhow = "1.0.95"
async-recursion = "1.1.1"
async-trait = "0.1.86"
base64 = "0.22.1"
bevy_app = "0.15.2"
bevy_ecs = { version = "0.15.2", default-features = false }
@ -49,7 +48,6 @@ env_logger = "0.11.6"
flate2 = "1.0.35"
futures = "0.3.31"
futures-lite = "2.6.0"
log = "0.4.25"
md-5 = "0.10.6"
minecraft_folder_path = "0.1.2"
nohash-hasher = "0.2.0"
@ -80,6 +78,7 @@ hickory-resolver = { version = "0.24.3", default-features = false }
uuid = "1.12.1"
num-format = "0.4.4"
indexmap = "2.7.1"
paste = "1.0.15"
compact_str = "0.8.1"
# --- Profile Settings ---

View file

@ -288,21 +288,16 @@ impl<S> CommandDispatcher<S> {
next.push(child.copy_for(context.source.clone()));
}
}
} else {
match &context.command {
Some(context_command) => {
found_command = true;
} else if let Some(context_command) = &context.command {
found_command = true;
let value = context_command(context);
result += value;
// consumer.on_command_complete(context, true, value);
successful_forks += 1;
let value = context_command(context);
result += value;
// consumer.on_command_complete(context, true, value);
successful_forks += 1;
// TODO: allow context_command to error and handle
// those errors
}
_ => {}
}
// TODO: allow context_command to error and handle
// those errors
}
}

View file

@ -27,6 +27,7 @@ bevy_time.workspace = true
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
minecraft_folder_path.workspace = true
parking_lot.workspace = true
paste.workspace = true
regex.workspace = true
reqwest.workspace = true
simdnbt.workspace = true

View file

@ -63,28 +63,27 @@ use uuid::Uuid;
use crate::{
Account, PlayerInfo,
attack::{self, AttackPlugin},
brand::BrandPlugin,
chat::ChatPlugin,
chunks::{ChunkBatchInfo, ChunkPlugin},
configuration::ConfigurationPlugin,
chunks::{ChunkBatchInfo, ChunksPlugin},
disconnect::{DisconnectEvent, DisconnectPlugin},
events::{Event, EventPlugin, LocalPlayerEvents},
events::{Event, EventsPlugin, LocalPlayerEvents},
interact::{CurrentSequenceNumber, InteractPlugin},
inventory::{Inventory, InventoryPlugin},
local_player::{
GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList,
death_event,
},
mining::{self, MinePlugin},
movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin},
packet_handling::{
PacketHandlerPlugin,
mining::{self, MiningPlugin},
movement::{LastSentLookDirection, MovementPlugin, PhysicsState},
packet::{
PacketPlugin,
login::{self, InLoginState, LoginSendPacketQueue},
},
player::retroactively_add_game_profile_component,
raw_connection::RawConnection,
respawn::RespawnPlugin,
send_client_end::TickEndPlugin,
task_pool::TaskPoolPlugin,
tick_end::TickEndPlugin,
};
/// `Client` has the things that a user interacting with the library will want.
@ -370,7 +369,7 @@ impl Client {
let (ecs_packets_tx, mut ecs_packets_rx) = mpsc::unbounded_channel();
ecs_lock.lock().entity_mut(entity).insert((
LoginSendPacketQueue { tx: ecs_packets_tx },
login::IgnoreQueryIds::default(),
crate::packet::login::IgnoreQueryIds::default(),
InLoginState,
));
@ -468,7 +467,7 @@ impl Client {
ClientboundLoginPacket::CustomQuery(p) => {
debug!("Got custom query {:?}", p);
// replying to custom query is done in
// packet_handling::login::process_packet_events
// packet::login::process_packet_events
}
ClientboundLoginPacket::CookieRequest(p) => {
debug!("Got cookie request {:?}", p);
@ -794,7 +793,7 @@ pub struct LocalPlayerBundle {
/// A bundle for the components that are present on a local player that is
/// currently in the `game` protocol state. If you want to filter for this, just
/// use [`LocalEntity`].
#[derive(Bundle)]
#[derive(Bundle, Default)]
pub struct JoinedClientBundle {
// note that InstanceHolder isn't here because it's set slightly before we fully join the world
pub physics_state: PhysicsState,
@ -826,8 +825,6 @@ impl Plugin for AzaleaPlugin {
app.add_systems(
Update,
(
// fire the Death event when the player dies.
death_event,
// add GameProfileComponent when we get an AddPlayerEvent
retroactively_add_game_profile_component.after(EntityUpdateSet::Index),
),
@ -972,23 +969,23 @@ impl PluginGroup for DefaultPlugins {
let mut group = PluginGroupBuilder::start::<Self>()
.add(AmbiguityLoggerPlugin)
.add(TimePlugin)
.add(PacketHandlerPlugin)
.add(PacketPlugin)
.add(AzaleaPlugin)
.add(EntityPlugin)
.add(PhysicsPlugin)
.add(EventPlugin)
.add(EventsPlugin)
.add(TaskPoolPlugin::default())
.add(InventoryPlugin)
.add(ChatPlugin)
.add(DisconnectPlugin)
.add(PlayerMovePlugin)
.add(MovementPlugin)
.add(InteractPlugin)
.add(RespawnPlugin)
.add(MinePlugin)
.add(MiningPlugin)
.add(AttackPlugin)
.add(ChunkPlugin)
.add(ChunksPlugin)
.add(TickEndPlugin)
.add(ConfigurationPlugin)
.add(BrandPlugin)
.add(TickBroadcastPlugin);
#[cfg(feature = "log")]
{

View file

@ -8,26 +8,13 @@
#![feature(error_generic_member_access)]
mod account;
pub mod attack;
pub mod chat;
pub mod chunks;
mod client;
pub mod configuration;
pub mod disconnect;
mod entity_query;
pub mod events;
pub mod interact;
pub mod inventory;
mod local_player;
pub mod mining;
pub mod movement;
pub mod packet_handling;
pub mod ping;
mod player;
mod plugins;
pub mod raw_connection;
pub mod respawn;
pub mod send_client_end;
pub mod task_pool;
#[doc(hidden)]
pub mod test_simulation;
@ -44,3 +31,4 @@ pub use movement::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
};
pub use player::PlayerInfo;
pub use plugins::*;

View file

@ -2,7 +2,6 @@ use std::{collections::HashMap, io, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_core::game_type::GameMode;
use azalea_entity::Dead;
use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
use azalea_world::{Instance, PartialInstance};
use bevy_ecs::{component::Component, prelude::*};
@ -13,10 +12,7 @@ use tokio::sync::mpsc;
use tracing::error;
use uuid::Uuid;
use crate::{
ClientInformation, PlayerInfo,
events::{Event as AzaleaEvent, LocalPlayerEvents},
};
use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
/// A component that keeps strong references to our [`PartialInstance`] and
/// [`Instance`] for local players.
@ -150,13 +146,6 @@ impl InstanceHolder {
}
}
/// Send the "Death" event for [`LocalEntity`]s that died with no reason.
pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) {
for local_player_events in &query {
local_player_events.send(AzaleaEvent::Death(None)).unwrap();
}
}
#[derive(Error, Debug)]
pub enum HandlePacketError {
#[error("{0}")]

View file

@ -1,271 +0,0 @@
use std::io::Cursor;
use azalea_entity::indexing::EntityIdIndex;
use azalea_protocol::packets::config::s_finish_configuration::ServerboundFinishConfiguration;
use azalea_protocol::packets::config::s_keep_alive::ServerboundKeepAlive;
use azalea_protocol::packets::config::s_select_known_packs::ServerboundSelectKnownPacks;
use azalea_protocol::packets::config::{
self, ClientboundConfigPacket, ServerboundConfigPacket, ServerboundCookieResponse,
ServerboundResourcePack,
};
use azalea_protocol::packets::{ConnectionProtocol, Packet};
use azalea_protocol::read::deserialize_packet;
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemState;
use tracing::{debug, error, warn};
use crate::InstanceHolder;
use crate::client::InConfigState;
use crate::disconnect::DisconnectEvent;
use crate::local_player::Hunger;
use crate::packet_handling::game::KeepAliveEvent;
use crate::raw_connection::RawConnection;
#[derive(Event, Debug, Clone)]
pub struct ConfigurationEvent {
/// The client entity that received the packet.
pub entity: Entity,
/// The packet that was actually received.
pub packet: ClientboundConfigPacket,
}
pub fn send_packet_events(
query: Query<(Entity, &RawConnection), With<InConfigState>>,
mut packet_events: ResMut<Events<ConfigurationEvent>>,
) {
// we manually clear and send the events at the beginning of each update
// since otherwise it'd cause issues with events in process_packet_events
// running twice
packet_events.clear();
for (player_entity, raw_conn) in &query {
let packets_lock = raw_conn.incoming_packet_queue();
let mut packets = packets_lock.lock();
if !packets.is_empty() {
for raw_packet in packets.iter() {
let packet = match deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(
raw_packet,
)) {
Ok(packet) => packet,
Err(err) => {
error!("failed to read packet: {err:?}");
debug!("packet bytes: {raw_packet:?}");
continue;
}
};
packet_events.send(ConfigurationEvent {
entity: player_entity,
packet,
});
}
// clear the packets right after we read them
packets.clear();
}
}
}
pub fn process_packet_events(ecs: &mut World) {
let mut events_owned = Vec::new();
let mut system_state: SystemState<EventReader<ConfigurationEvent>> = SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);
for ConfigurationEvent {
entity: player_entity,
packet,
} in events.read()
{
// we do this so `ecs` isn't borrowed for the whole loop
events_owned.push((*player_entity, packet.clone()));
}
for (player_entity, packet) in events_owned {
match packet {
ClientboundConfigPacket::RegistryData(p) => {
let mut system_state: SystemState<Query<&mut InstanceHolder>> =
SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let instance_holder = query.get_mut(player_entity).unwrap();
let mut instance = instance_holder.instance.write();
// add the new registry data
instance.registries.append(p.registry_id, p.entries);
}
ClientboundConfigPacket::CustomPayload(p) => {
debug!("Got custom payload packet {p:?}");
}
ClientboundConfigPacket::Disconnect(p) => {
warn!("Got disconnect packet {p:?}");
let mut system_state: SystemState<EventWriter<DisconnectEvent>> =
SystemState::new(ecs);
let mut disconnect_events = system_state.get_mut(ecs);
disconnect_events.send(DisconnectEvent {
entity: player_entity,
reason: Some(p.reason.clone()),
});
}
ClientboundConfigPacket::FinishConfiguration(p) => {
debug!("got FinishConfiguration packet: {p:?}");
let mut system_state: SystemState<Query<&mut RawConnection>> =
SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let mut raw_conn = query.get_mut(player_entity).unwrap();
raw_conn
.write_packet(ServerboundFinishConfiguration)
.expect(
"we should be in the right state and encoding this packet shouldn't fail",
);
raw_conn.set_state(ConnectionProtocol::Game);
// these components are added now that we're going to be in the Game state
ecs.entity_mut(player_entity)
.remove::<InConfigState>()
.insert(crate::JoinedClientBundle {
physics_state: crate::PhysicsState::default(),
inventory: crate::inventory::Inventory::default(),
tab_list: crate::local_player::TabList::default(),
current_sequence_number: crate::interact::CurrentSequenceNumber::default(),
last_sent_direction: crate::movement::LastSentLookDirection::default(),
abilities: crate::local_player::PlayerAbilities::default(),
permission_level: crate::local_player::PermissionLevel::default(),
hunger: Hunger::default(),
chunk_batch_info: crate::chunks::ChunkBatchInfo::default(),
entity_id_index: EntityIdIndex::default(),
mining: crate::mining::MineBundle::default(),
attack: crate::attack::AttackBundle::default(),
_local_entity: azalea_entity::LocalEntity,
});
}
ClientboundConfigPacket::KeepAlive(p) => {
debug!("Got keep alive packet (in configuration) {p:?} for {player_entity:?}");
let mut system_state: SystemState<(
Query<&RawConnection>,
EventWriter<KeepAliveEvent>,
)> = SystemState::new(ecs);
let (query, mut keepalive_events) = system_state.get_mut(ecs);
let raw_conn = query.get(player_entity).unwrap();
keepalive_events.send(KeepAliveEvent {
entity: player_entity,
id: p.id,
});
raw_conn
.write_packet(ServerboundKeepAlive { id: p.id })
.unwrap();
}
ClientboundConfigPacket::Ping(p) => {
debug!("Got ping packet {p:?}");
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let raw_conn = query.get_mut(player_entity).unwrap();
raw_conn
.write_packet(config::s_pong::ServerboundPong { id: p.id })
.unwrap();
}
ClientboundConfigPacket::ResourcePackPush(p) => {
debug!("Got resource pack packet {p:?}");
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let raw_conn = query.get_mut(player_entity).unwrap();
// always accept resource pack
raw_conn
.write_packet(ServerboundResourcePack {
id: p.id,
action: config::s_resource_pack::Action::Accepted,
})
.unwrap();
}
ClientboundConfigPacket::ResourcePackPop(_) => {
// we can ignore this
}
ClientboundConfigPacket::UpdateEnabledFeatures(p) => {
debug!("Got update enabled features packet {p:?}");
}
ClientboundConfigPacket::UpdateTags(_p) => {
debug!("Got update tags packet");
}
ClientboundConfigPacket::CookieRequest(p) => {
debug!("Got cookie request packet {p:?}");
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let raw_conn = query.get_mut(player_entity).unwrap();
raw_conn
.write_packet(ServerboundCookieResponse {
key: p.key,
// cookies aren't implemented
payload: None,
})
.unwrap();
}
ClientboundConfigPacket::ResetChat(p) => {
debug!("Got reset chat packet {p:?}");
}
ClientboundConfigPacket::StoreCookie(p) => {
debug!("Got store cookie packet {p:?}");
}
ClientboundConfigPacket::Transfer(p) => {
debug!("Got transfer packet {p:?}");
}
ClientboundConfigPacket::SelectKnownPacks(p) => {
debug!("Got select known packs packet {p:?}");
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let raw_conn = query.get_mut(player_entity).unwrap();
// resource pack management isn't implemented
raw_conn
.write_packet(ServerboundSelectKnownPacks {
known_packs: vec![],
})
.unwrap();
}
ClientboundConfigPacket::ServerLinks(_) => {}
ClientboundConfigPacket::CustomReportDetails(_) => {}
}
}
}
/// An event for sending a packet to the server while we're in the
/// `configuration` state.
#[derive(Event)]
pub struct SendConfigurationEvent {
pub sent_by: Entity,
pub packet: ServerboundConfigPacket,
}
impl SendConfigurationEvent {
pub fn new(sent_by: Entity, packet: impl Packet<ServerboundConfigPacket>) -> Self {
let packet = packet.into_variant();
Self { sent_by, packet }
}
}
pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendConfigurationEvent>,
mut query: Query<(&mut RawConnection, Option<&InConfigState>)>,
) {
for event in send_packet_events.read() {
if let Ok((raw_conn, in_configuration_state)) = query.get_mut(event.sent_by) {
if in_configuration_state.is_none() {
error!(
"Tried to send a configuration packet {:?} while not in configuration state",
event.packet
);
continue;
}
debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_conn.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
};
use uuid::Uuid;
use crate::{GameProfileComponent, packet_handling::game::AddPlayerEvent};
use crate::{GameProfileComponent, packet::game::AddPlayerEvent};
/// A player in the tab list.
#[derive(Debug, Clone)]

View file

@ -11,9 +11,10 @@ use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use super::packet::game::SendPacketEvent;
use crate::{
Client, interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
packet_handling::game::SendPacketEvent, respawn::perform_respawn,
respawn::perform_respawn,
};
pub struct AttackPlugin;

View file

@ -11,15 +11,15 @@ use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use tracing::{debug, warn};
use crate::packet_handling::{configuration::SendConfigurationEvent, login::InLoginState};
use super::packet::config::SendConfigPacketEvent;
use crate::packet::login::InLoginState;
pub struct ConfigurationPlugin;
impl Plugin for ConfigurationPlugin {
pub struct BrandPlugin;
impl Plugin for BrandPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
handle_end_login_state
.before(crate::packet_handling::configuration::handle_send_packet_event),
handle_end_login_state.before(crate::packet::config::handle_send_packet_event),
);
}
}
@ -27,13 +27,14 @@ impl Plugin for ConfigurationPlugin {
fn handle_end_login_state(
mut removed: RemovedComponents<InLoginState>,
query: Query<&ClientInformation>,
mut send_packet_events: EventWriter<SendConfigurationEvent>,
mut send_packet_events: EventWriter<SendConfigPacketEvent>,
) {
for entity in removed.read() {
let mut brand_data = Vec::new();
// they don't have to know :)
// azalea pretends to be vanilla everywhere else so it makes sense to lie here
// too
"vanilla".azalea_write(&mut brand_data).unwrap();
send_packet_events.send(SendConfigurationEvent::new(
send_packet_events.send(SendConfigPacketEvent::new(
entity,
ServerboundCustomPayload {
identifier: ResourceLocation::new("brand"),
@ -52,7 +53,7 @@ fn handle_end_login_state(
};
debug!("Writing ClientInformation while in config state: {client_information:?}");
send_packet_events.send(SendConfigurationEvent::new(
send_packet_events.send(SendConfigPacketEvent::new(
entity,
ServerboundClientInformation {
information: client_information.clone(),

View file

@ -0,0 +1,61 @@
use std::time::{SystemTime, UNIX_EPOCH};
use azalea_protocol::packets::{
game::{s_chat::LastSeenMessagesUpdate, ServerboundChat, ServerboundChatCommand},
Packet,
};
use bevy_ecs::prelude::*;
use super::ChatKind;
use crate::packet::game::SendPacketEvent;
/// Send a chat packet to the server of a specific kind (chat message or
/// command). Usually you just want [`SendChatEvent`] instead.
///
/// Usually setting the kind to `Message` will make it send a chat message even
/// if it starts with a slash, but some server implementations will always do a
/// command if it starts with a slash.
///
/// If you're wondering why this isn't two separate events, it's so ordering is
/// preserved if multiple chat messages and commands are sent at the same time.
#[derive(Event)]
pub struct SendChatKindEvent {
pub entity: Entity,
pub content: String,
pub kind: ChatKind,
}
pub fn handle_send_chat_kind_event(
mut events: EventReader<SendChatKindEvent>,
mut send_packet_events: EventWriter<SendPacketEvent>,
) {
for event in events.read() {
let content = event
.content
.chars()
.filter(|c| !matches!(c, '\x00'..='\x1F' | '\x7F' | '§'))
.take(256)
.collect::<String>();
let packet = match event.kind {
ChatKind::Message => ServerboundChat {
message: content,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time shouldn't be before epoch")
.as_millis()
.try_into()
.expect("Instant should fit into a u64"),
salt: azalea_crypto::make_salt(),
signature: None,
last_seen_messages: LastSeenMessagesUpdate::default(),
}
.into_variant(),
ChatKind::Command => {
// TODO: chat signing
ServerboundChatCommand { command: content }.into_variant()
}
};
send_packet_events.send(SendPacketEvent::new(event.entity, packet));
}
}

View file

@ -1,20 +1,13 @@
//! Implementations of chat-related features.
use std::{
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
pub mod handler;
use std::sync::Arc;
use azalea_chat::FormattedText;
use azalea_protocol::packets::{
Packet,
game::{
c_disguised_chat::ClientboundDisguisedChat,
c_player_chat::ClientboundPlayerChat,
c_system_chat::ClientboundSystemChat,
s_chat::{LastSeenMessagesUpdate, ServerboundChat},
s_chat_command::ServerboundChatCommand,
},
use azalea_protocol::packets::game::{
c_disguised_chat::ClientboundDisguisedChat, c_player_chat::ClientboundPlayerChat,
c_system_chat::ClientboundSystemChat,
};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{
@ -23,12 +16,28 @@ use bevy_ecs::{
prelude::Event,
schedule::IntoSystemConfigs,
};
use handler::{SendChatKindEvent, handle_send_chat_kind_event};
use uuid::Uuid;
use crate::{
client::Client,
packet_handling::game::{SendPacketEvent, handle_send_packet_event},
};
use super::packet::game::handle_outgoing_packets;
use crate::client::Client;
pub struct ChatPlugin;
impl Plugin for ChatPlugin {
fn build(&self, app: &mut App) {
app.add_event::<SendChatEvent>()
.add_event::<SendChatKindEvent>()
.add_event::<ChatReceivedEvent>()
.add_systems(
Update,
(
handle_send_chat_event,
handle_send_chat_kind_event.after(handle_outgoing_packets),
)
.chain(),
);
}
}
/// A chat packet, either a system message or a chat message.
#[derive(Debug, Clone, PartialEq)]
@ -183,23 +192,6 @@ impl Client {
}
}
pub struct ChatPlugin;
impl Plugin for ChatPlugin {
fn build(&self, app: &mut App) {
app.add_event::<SendChatEvent>()
.add_event::<SendChatKindEvent>()
.add_event::<ChatReceivedEvent>()
.add_systems(
Update,
(
handle_send_chat_event,
handle_send_chat_kind_event.after(handle_send_packet_event),
)
.chain(),
);
}
}
/// A client received a chat message packet.
#[derive(Event, Debug, Clone)]
pub struct ChatReceivedEvent {
@ -235,63 +227,12 @@ pub fn handle_send_chat_event(
}
}
/// Send a chat packet to the server of a specific kind (chat message or
/// command). Usually you just want [`SendChatEvent`] instead.
///
/// Usually setting the kind to `Message` will make it send a chat message even
/// if it starts with a slash, but some server implementations will always do a
/// command if it starts with a slash.
///
/// If you're wondering why this isn't two separate events, it's so ordering is
/// preserved if multiple chat messages and commands are sent at the same time.
#[derive(Event)]
pub struct SendChatKindEvent {
pub entity: Entity,
pub content: String,
pub kind: ChatKind,
}
/// A kind of chat packet, either a chat message or a command.
pub enum ChatKind {
Message,
Command,
}
pub fn handle_send_chat_kind_event(
mut events: EventReader<SendChatKindEvent>,
mut send_packet_events: EventWriter<SendPacketEvent>,
) {
for event in events.read() {
let content = event
.content
.chars()
.filter(|c| !matches!(c, '\x00'..='\x1F' | '\x7F' | '§'))
.take(256)
.collect::<String>();
let packet = match event.kind {
ChatKind::Message => ServerboundChat {
message: content,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time shouldn't be before epoch")
.as_millis()
.try_into()
.expect("Instant should fit into a u64"),
salt: azalea_crypto::make_salt(),
signature: None,
last_seen_messages: LastSeenMessagesUpdate::default(),
}
.into_variant(),
ChatKind::Command => {
// TODO: chat signing
ServerboundChatCommand { command: content }.into_variant()
}
};
send_packet_events.send(SendPacketEvent::new(event.entity, packet));
}
}
// TODO
// MessageSigner, ChatMessageContent, LastSeenMessages
// fn sign_message() -> MessageSignature {

View file

@ -18,16 +18,14 @@ use bevy_ecs::prelude::*;
use simdnbt::owned::BaseNbt;
use tracing::{error, trace};
use super::packet::game::handle_outgoing_packets;
use crate::{
InstanceHolder,
interact::handle_block_interact_event,
inventory::InventorySet,
packet_handling::game::{SendPacketEvent, handle_send_packet_event},
respawn::perform_respawn,
InstanceHolder, interact::handle_block_interact_event, inventory::InventorySet,
packet::game::SendPacketEvent, respawn::perform_respawn,
};
pub struct ChunkPlugin;
impl Plugin for ChunkPlugin {
pub struct ChunksPlugin;
impl Plugin for ChunksPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
@ -37,7 +35,7 @@ impl Plugin for ChunkPlugin {
handle_chunk_batch_finished_event,
)
.chain()
.before(handle_send_packet_event)
.before(handle_outgoing_packets)
.before(InventorySet)
.before(handle_block_interact_event)
.before(perform_respawn),

View file

@ -5,6 +5,7 @@ use std::sync::Arc;
use azalea_chat::FormattedText;
use azalea_core::tick::GameTick;
use azalea_entity::Dead;
use azalea_protocol::packets::game::{
ClientboundGamePacket, c_player_combat_kill::ClientboundPlayerCombatKill,
};
@ -24,28 +25,33 @@ use crate::{
PlayerInfo,
chat::{ChatPacket, ChatReceivedEvent},
disconnect::DisconnectEvent,
packet_handling::game::{
AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketEvent, RemovePlayerEvent,
packet::game::{
AddPlayerEvent, DeathEvent, KeepAliveEvent, ReceivePacketEvent, RemovePlayerEvent,
UpdatePlayerEvent,
},
};
// (for contributors):
// HOW TO ADD A NEW (packet based) EVENT:
// - make a struct that contains an entity field and a data field (look in
// packet_handling.rs for examples, also you should end the struct name with
// "Event")
// - the entity field is the local player entity that's receiving the event
// - in packet_handling, you always have a variable called player_entity that
// you can use
// - add the event struct in the `impl Plugin for PacketHandlerPlugin`
// - to get the event writer, you have to get an
// EventWriter<SomethingHappenedEvent> from the SystemState (the convention is
// to end your variable with the word "events", like "something_events")
// - 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 EventWriter<ThingEvent>.
// Look at other packets in packet/game/mod.rs for examples.
//
// - then here in this file, add it to the Event enum
// - and make an event listener system/function like the other ones and put the
// function in the `impl Plugin for EventPlugin`
// 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.
@ -111,8 +117,8 @@ pub enum Event {
#[derive(Component, Deref, DerefMut)]
pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>);
pub struct EventPlugin;
impl Plugin for EventPlugin {
pub struct EventsPlugin;
impl Plugin for EventsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
@ -130,7 +136,7 @@ impl Plugin for EventPlugin {
)
.add_systems(
PreUpdate,
init_listener.before(crate::packet_handling::game::process_packet_events),
init_listener.before(crate::packet::game::process_packet_events),
)
.add_systems(GameTick, tick_listener);
}
@ -166,7 +172,10 @@ pub fn tick_listener(query: Query<&LocalPlayerEvents, With<InstanceName>>) {
}
}
pub fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<PacketEvent>) {
pub fn packet_listener(
query: Query<&LocalPlayerEvents>,
mut events: EventReader<ReceivePacketEvent>,
) {
for event in events.read() {
let local_player_events = query
.get(event.entity)
@ -219,6 +228,13 @@ pub fn death_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<
}
}
/// Send the "Death" event for [`LocalEntity`]s that died with no reason.
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: EventReader<KeepAliveEvent>,

View file

@ -30,13 +30,14 @@ use bevy_ecs::{
use derive_more::{Deref, DerefMut};
use tracing::warn;
use super::packet::game::handle_outgoing_packets;
use crate::{
Client,
attack::handle_attack_event,
inventory::{Inventory, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet,
packet_handling::game::{SendPacketEvent, handle_send_packet_event},
packet::game::SendPacketEvent,
respawn::perform_respawn,
};
@ -54,7 +55,7 @@ impl Plugin for InteractPlugin {
handle_block_interact_event,
handle_swing_arm_event,
)
.before(handle_send_packet_event)
.before(handle_outgoing_packets)
.after(InventorySet)
.after(perform_respawn)
.after(handle_attack_event)

View file

@ -25,11 +25,9 @@ use bevy_ecs::{
};
use tracing::warn;
use super::packet::game::handle_outgoing_packets;
use crate::{
Client,
local_player::PlayerAbilities,
packet_handling::game::{SendPacketEvent, handle_send_packet_event},
respawn::perform_respawn,
Client, local_player::PlayerAbilities, packet::game::SendPacketEvent, respawn::perform_respawn,
};
pub struct InventoryPlugin;
@ -48,7 +46,7 @@ impl Plugin for InventoryPlugin {
handle_menu_opened_event,
handle_set_container_content_event,
handle_container_click_event,
handle_container_close_event.before(handle_send_packet_event),
handle_container_close_event.before(handle_outgoing_packets),
handle_client_side_close_container_event,
)
.chain()

View file

@ -18,12 +18,12 @@ use crate::{
inventory::{Inventory, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet,
packet_handling::game::SendPacketEvent,
packet::game::SendPacketEvent,
};
/// A plugin that allows clients to break blocks in the world.
pub struct MinePlugin;
impl Plugin for MinePlugin {
pub struct MiningPlugin;
impl Plugin for MiningPlugin {
fn build(&self, app: &mut App) {
app.add_event::<StartMiningBlockEvent>()
.add_event::<StartMiningBlockWithDirectionEvent>()
@ -59,6 +59,7 @@ impl Plugin for MinePlugin {
}
}
/// The Bevy system set for things related to mining.
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct MiningSet;

View file

@ -0,0 +1,14 @@
pub mod attack;
pub mod brand;
pub mod chat;
pub mod chunks;
pub mod disconnect;
pub mod events;
pub mod interact;
pub mod inventory;
pub mod mining;
pub mod movement;
pub mod packet;
pub mod respawn;
pub mod task_pool;
pub mod tick_end;

View file

@ -27,7 +27,7 @@ use bevy_ecs::{
use thiserror::Error;
use crate::client::Client;
use crate::packet_handling::game::SendPacketEvent;
use crate::packet::game::SendPacketEvent;
#[derive(Error, Debug)]
pub enum MovePlayerError {
@ -47,9 +47,9 @@ impl From<MoveEntityError> for MovePlayerError {
}
}
pub struct PlayerMovePlugin;
pub struct MovementPlugin;
impl Plugin for PlayerMovePlugin {
impl Plugin for MovementPlugin {
fn build(&self, app: &mut App) {
app.add_event::<StartWalkEvent>()
.add_event::<StartSprintEvent>()

View file

@ -0,0 +1,90 @@
use std::io::Cursor;
use azalea_protocol::{
packets::{
config::{ClientboundConfigPacket, ServerboundConfigPacket},
Packet,
},
read::deserialize_packet,
};
use bevy_ecs::prelude::*;
use tracing::{debug, error};
use crate::{raw_connection::RawConnection, InConfigState};
#[derive(Event, Debug, Clone)]
pub struct ReceiveConfigPacketEvent {
/// The client entity that received the packet.
pub entity: Entity,
/// The packet that was actually received.
pub packet: ClientboundConfigPacket,
}
/// An event for sending a packet to the server while we're in the
/// `configuration` state.
#[derive(Event)]
pub struct SendConfigPacketEvent {
pub sent_by: Entity,
pub packet: ServerboundConfigPacket,
}
impl SendConfigPacketEvent {
pub fn new(sent_by: Entity, packet: impl Packet<ServerboundConfigPacket>) -> Self {
let packet = packet.into_variant();
Self { sent_by, packet }
}
}
pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendConfigPacketEvent>,
mut query: Query<(&mut RawConnection, Option<&InConfigState>)>,
) {
for event in send_packet_events.read() {
if let Ok((raw_conn, in_configuration_state)) = query.get_mut(event.sent_by) {
if in_configuration_state.is_none() {
error!(
"Tried to send a configuration packet {:?} while not in configuration state",
event.packet
);
continue;
}
debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_conn.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
}
}
}
pub fn send_packet_events(
query: Query<(Entity, &RawConnection), With<InConfigState>>,
mut packet_events: ResMut<Events<ReceiveConfigPacketEvent>>,
) {
// we manually clear and send the events at the beginning of each update
// since otherwise it'd cause issues with events in process_packet_events
// running twice
packet_events.clear();
for (player_entity, raw_conn) in &query {
let packets_lock = raw_conn.incoming_packet_queue();
let mut packets = packets_lock.lock();
if !packets.is_empty() {
for raw_packet in packets.iter() {
let packet = match deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(
raw_packet,
)) {
Ok(packet) => packet,
Err(err) => {
error!("failed to read packet: {err:?}");
debug!("packet bytes: {raw_packet:?}");
continue;
}
};
packet_events.send(ReceiveConfigPacketEvent {
entity: player_entity,
packet,
});
}
// clear the packets right after we read them
packets.clear();
}
}
}

View file

@ -0,0 +1,223 @@
mod events;
use azalea_protocol::packets::config::*;
use azalea_protocol::packets::ConnectionProtocol;
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemState;
pub use events::*;
use tracing::{debug, warn};
use super::as_system;
use crate::client::InConfigState;
use crate::disconnect::DisconnectEvent;
use crate::packet::game::KeepAliveEvent;
use crate::raw_connection::RawConnection;
use crate::{declare_packet_handlers, InstanceHolder};
pub fn process_packet_events(ecs: &mut World) {
let mut events_owned = Vec::new();
let mut system_state: SystemState<EventReader<ReceiveConfigPacketEvent>> =
SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);
for ReceiveConfigPacketEvent {
entity: player_entity,
packet,
} in events.read()
{
// we do this so `ecs` isn't borrowed for the whole loop
events_owned.push((*player_entity, packet.clone()));
}
for (player_entity, packet) in events_owned {
let mut handler = ConfigPacketHandler {
player: player_entity,
ecs,
};
declare_packet_handlers!(
ClientboundConfigPacket,
packet,
handler,
[
cookie_request,
custom_payload,
disconnect,
finish_configuration,
keep_alive,
ping,
reset_chat,
registry_data,
resource_pack_pop,
resource_pack_push,
store_cookie,
transfer,
update_enabled_features,
update_tags,
select_known_packs,
custom_report_details,
server_links,
]
);
}
}
pub struct ConfigPacketHandler<'a> {
pub ecs: &'a mut World,
pub player: Entity,
}
impl ConfigPacketHandler<'_> {
pub fn registry_data(&mut self, p: ClientboundRegistryData) {
as_system::<Query<&mut InstanceHolder>>(self.ecs, |mut query| {
let instance_holder = query.get_mut(self.player).unwrap();
let mut instance = instance_holder.instance.write();
// add the new registry data
instance.registries.append(p.registry_id, p.entries);
});
}
pub fn custom_payload(&mut self, p: ClientboundCustomPayload) {
debug!("Got custom payload packet {p:?}");
}
pub fn disconnect(&mut self, p: ClientboundDisconnect) {
warn!("Got disconnect packet {p:?}");
as_system::<EventWriter<_>>(self.ecs, |mut events| {
events.send(DisconnectEvent {
entity: self.player,
reason: Some(p.reason),
});
});
}
pub fn finish_configuration(&mut self, p: ClientboundFinishConfiguration) {
debug!("got FinishConfiguration packet: {p:?}");
as_system::<(Commands, Query<&mut RawConnection>)>(
self.ecs,
|(mut commands, mut query)| {
let mut raw_conn = query.get_mut(self.player).unwrap();
raw_conn
.write_packet(ServerboundFinishConfiguration)
.expect(
"we should be in the right state and encoding this packet shouldn't fail",
);
raw_conn.set_state(ConnectionProtocol::Game);
// these components are added now that we're going to be in the Game state
commands
.entity(self.player)
.remove::<InConfigState>()
.insert(crate::JoinedClientBundle::default());
},
);
}
pub fn keep_alive(&mut self, p: ClientboundKeepAlive) {
debug!(
"Got keep alive packet (in configuration) {p:?} for {:?}",
self.player
);
as_system::<(Query<&RawConnection>, EventWriter<_>)>(self.ecs, |(query, mut events)| {
let raw_conn = query.get(self.player).unwrap();
events.send(KeepAliveEvent {
entity: self.player,
id: p.id,
});
raw_conn
.write_packet(ServerboundKeepAlive { id: p.id })
.unwrap();
});
}
pub fn ping(&mut self, p: ClientboundPing) {
debug!("Got ping packet (in configuration) {p:?}");
as_system::<Query<&RawConnection>>(self.ecs, |query| {
let raw_conn = query.get(self.player).unwrap();
raw_conn.write_packet(ServerboundPong { id: p.id }).unwrap();
});
}
pub fn resource_pack_push(&mut self, p: ClientboundResourcePackPush) {
debug!("Got resource pack push packet {p:?}");
as_system::<Query<&RawConnection>>(self.ecs, |query| {
let raw_conn = query.get(self.player).unwrap();
// always accept resource pack
raw_conn
.write_packet(ServerboundResourcePack {
id: p.id,
action: s_resource_pack::Action::Accepted,
})
.unwrap();
});
}
pub fn resource_pack_pop(&mut self, p: ClientboundResourcePackPop) {
debug!("Got resource pack pop packet {p:?}");
}
pub fn update_enabled_features(&mut self, p: ClientboundUpdateEnabledFeatures) {
debug!("Got update enabled features packet {p:?}");
}
pub fn update_tags(&mut self, _p: ClientboundUpdateTags) {
debug!("Got update tags packet");
}
pub fn cookie_request(&mut self, p: ClientboundCookieRequest) {
debug!("Got cookie request packet {p:?}");
as_system::<Query<&RawConnection>>(self.ecs, |query| {
let raw_conn = query.get(self.player).unwrap();
raw_conn
.write_packet(ServerboundCookieResponse {
key: p.key,
// cookies aren't implemented
payload: None,
})
.unwrap();
});
}
pub fn reset_chat(&mut self, p: ClientboundResetChat) {
debug!("Got reset chat packet {p:?}");
}
pub fn store_cookie(&mut self, p: ClientboundStoreCookie) {
debug!("Got store cookie packet {p:?}");
}
pub fn transfer(&mut self, p: ClientboundTransfer) {
debug!("Got transfer packet {p:?}");
}
pub fn select_known_packs(&mut self, p: ClientboundSelectKnownPacks) {
debug!("Got select known packs packet {p:?}");
as_system::<Query<&RawConnection>>(self.ecs, |query| {
let raw_conn = query.get(self.player).unwrap();
// resource pack management isn't implemented
raw_conn
.write_packet(ServerboundSelectKnownPacks {
known_packs: vec![],
})
.unwrap();
});
}
pub fn server_links(&mut self, p: ClientboundServerLinks) {
debug!("Got server links packet {p:?}");
}
pub fn custom_report_details(&mut self, p: ClientboundCustomReportDetails) {
debug!("Got custom report details packet {p:?}");
}
}

View file

@ -0,0 +1,178 @@
use std::{
io::Cursor,
sync::{Arc, Weak},
};
use azalea_chat::FormattedText;
use azalea_core::resource_location::ResourceLocation;
use azalea_entity::LocalEntity;
use azalea_protocol::{
packets::{
Packet,
game::{ClientboundGamePacket, ClientboundPlayerCombatKill, ServerboundGamePacket},
},
read::deserialize_packet,
};
use azalea_world::Instance;
use bevy_ecs::prelude::*;
use parking_lot::RwLock;
use tracing::{debug, error};
use uuid::Uuid;
use crate::{PlayerInfo, raw_connection::RawConnection};
/// An event that's sent when we receive a packet.
/// ```
/// # use azalea_client::packet::game::ReceivePacketEvent;
/// # use azalea_protocol::packets::game::ClientboundGamePacket;
/// # use bevy_ecs::event::EventReader;
///
/// fn handle_packets(mut events: EventReader<ReceivePacketEvent>) {
/// for ReceivePacketEvent {
/// entity,
/// packet,
/// } in events.read() {
/// match packet.as_ref() {
/// ClientboundGamePacket::LevelParticles(p) => {
/// // ...
/// }
/// _ => {}
/// }
/// }
/// }
/// ```
#[derive(Event, Debug, Clone)]
pub struct ReceivePacketEvent {
/// The client entity that received the packet.
pub entity: Entity,
/// The packet that was actually received.
pub packet: Arc<ClientboundGamePacket>,
}
/// An event for sending a packet to the server while we're in the `game` state.
#[derive(Event)]
pub struct SendPacketEvent {
pub sent_by: Entity,
pub packet: ServerboundGamePacket,
}
impl SendPacketEvent {
pub fn new(sent_by: Entity, packet: impl Packet<ServerboundGamePacket>) -> Self {
let packet = packet.into_variant();
Self { sent_by, packet }
}
}
pub fn handle_outgoing_packets(
mut send_packet_events: EventReader<SendPacketEvent>,
mut query: Query<&mut RawConnection>,
) {
for event in send_packet_events.read() {
if let Ok(raw_connection) = query.get_mut(event.sent_by) {
// debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
}
}
}
pub fn send_receivepacketevent(
query: Query<(Entity, &RawConnection), With<LocalEntity>>,
mut packet_events: ResMut<Events<ReceivePacketEvent>>,
) {
// we manually clear and send the events at the beginning of each update
// since otherwise it'd cause issues with events in process_packet_events
// running twice
packet_events.clear();
for (player_entity, raw_connection) in &query {
let packets_lock = raw_connection.incoming_packet_queue();
let mut packets = packets_lock.lock();
if !packets.is_empty() {
for raw_packet in packets.iter() {
let packet =
match deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new(raw_packet))
{
Ok(packet) => packet,
Err(err) => {
error!("failed to read packet: {err:?}");
debug!("packet bytes: {raw_packet:?}");
continue;
}
};
packet_events.send(ReceivePacketEvent {
entity: player_entity,
packet: Arc::new(packet),
});
}
// clear the packets right after we read them
packets.clear();
}
}
}
/// A player joined the game (or more specifically, was added to the tab
/// list of a local player).
#[derive(Event, Debug, Clone)]
pub struct AddPlayerEvent {
/// The local player entity that received this event.
pub entity: Entity,
pub info: PlayerInfo,
}
/// A player left the game (or maybe is still in the game and was just
/// removed from the tab list of a local player).
#[derive(Event, Debug, Clone)]
pub struct RemovePlayerEvent {
/// The local player entity that received this event.
pub entity: Entity,
pub info: PlayerInfo,
}
/// A player was updated in the tab list of a local player (gamemode, display
/// name, or latency changed).
#[derive(Event, Debug, Clone)]
pub struct UpdatePlayerEvent {
/// The local player entity that received this event.
pub entity: Entity,
pub info: PlayerInfo,
}
/// Event for when an entity dies. dies. If it's a local player and there's a
/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
/// be included.
#[derive(Event, Debug, Clone)]
pub struct DeathEvent {
pub entity: Entity,
pub packet: Option<ClientboundPlayerCombatKill>,
}
/// A KeepAlive packet is sent from the server to verify that the client is
/// still connected.
#[derive(Event, Debug, Clone)]
pub struct KeepAliveEvent {
pub entity: Entity,
/// The ID of the keepalive. This is an arbitrary number, but vanilla
/// servers use the time to generate this.
pub id: u64,
}
#[derive(Event, Debug, Clone)]
pub struct ResourcePackEvent {
pub entity: Entity,
/// The random ID for this request to download the resource pack. The packet
/// for replying to a resource pack push must contain the same ID.
pub id: Uuid,
pub url: String,
pub hash: String,
pub required: bool,
pub prompt: Option<FormattedText>,
}
/// An instance (aka world, dimension) was loaded by a client.
///
/// Since the instance is given to you as a weak reference, it won't be able to
/// be `upgrade`d if all local players leave it.
#[derive(Event, Debug, Clone)]
pub struct InstanceLoadedEvent {
pub entity: Entity,
pub name: ResourceLocation,
pub instance: Weak<RwLock<Instance>>,
}

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ use tracing::error;
/// An event that's sent when we receive a login packet from the server. Note
/// that if you want to handle this in a system, you must add
/// `.before(azalea::packet_handling::login::process_packet_events)` to it
/// `.before(azalea::packet::login::process_packet_events)` to it
/// because that system clears the events.
#[derive(Event, Debug, Clone)]
pub struct LoginPacketEvent {

View file

@ -1,6 +1,9 @@
use azalea_entity::{EntityUpdateSet, metadata::Health};
use bevy_app::{App, First, Plugin, PreUpdate, Update};
use bevy_ecs::prelude::*;
use bevy_ecs::{
prelude::*,
system::{SystemParam, SystemState},
};
use self::{
game::{
@ -11,11 +14,11 @@ use self::{
};
use crate::{chat::ChatReceivedEvent, events::death_listener};
pub mod configuration;
pub mod config;
pub mod game;
pub mod login;
pub struct PacketHandlerPlugin;
pub struct PacketPlugin;
pub fn death_event_on_0_health(
query: Query<(Entity, &Health), Changed<Health>>,
@ -31,11 +34,11 @@ pub fn death_event_on_0_health(
}
}
impl Plugin for PacketHandlerPlugin {
impl Plugin for PacketPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
First,
(game::send_packet_events, configuration::send_packet_events),
(game::send_receivepacketevent, config::send_packet_events),
)
.add_systems(
PreUpdate,
@ -43,7 +46,7 @@ impl Plugin for PacketHandlerPlugin {
game::process_packet_events
// we want to index and deindex right after
.before(EntityUpdateSet::Deindex),
configuration::process_packet_events,
config::process_packet_events,
login::handle_send_packet_event,
login::process_packet_events,
),
@ -52,18 +55,18 @@ impl Plugin for PacketHandlerPlugin {
Update,
(
(
configuration::handle_send_packet_event,
game::handle_send_packet_event,
config::handle_send_packet_event,
game::handle_outgoing_packets,
)
.chain(),
death_event_on_0_health.before(death_listener),
),
)
// we do this instead of add_event so we can handle the events ourselves
.init_resource::<Events<game::PacketEvent>>()
.init_resource::<Events<configuration::ConfigurationEvent>>()
.init_resource::<Events<game::ReceivePacketEvent>>()
.init_resource::<Events<config::ReceiveConfigPacketEvent>>()
.add_event::<game::SendPacketEvent>()
.add_event::<configuration::SendConfigurationEvent>()
.add_event::<config::SendConfigPacketEvent>()
.add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>()
@ -76,3 +79,31 @@ impl Plugin for PacketHandlerPlugin {
.add_event::<SendLoginPacketEvent>();
}
}
#[macro_export]
macro_rules! declare_packet_handlers {
(
$packetenum:ident,
$packetvar:expr,
$handler:ident,
[$($packet:path),+ $(,)?]
) => {
paste::paste! {
match $packetvar {
$(
$packetenum::[< $packet:camel >](p) => $handler.$packet(p),
)+
}
}
};
}
pub(crate) fn as_system<T>(ecs: &mut World, f: impl FnOnce(T::Item<'_, '_>))
where
T: SystemParam + 'static,
{
let mut system_state = SystemState::<T>::new(ecs);
let values = system_state.get_mut(ecs);
f(values);
system_state.apply(ecs);
}

View file

@ -2,7 +2,8 @@ use azalea_protocol::packets::game::s_client_command::{self, ServerboundClientCo
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use crate::packet_handling::game::{SendPacketEvent, handle_send_packet_event};
use super::packet::game::handle_outgoing_packets;
use crate::packet::game::SendPacketEvent;
/// Tell the server that we're respawning.
#[derive(Event, Debug, Clone)]
@ -15,7 +16,7 @@ pub struct RespawnPlugin;
impl Plugin for RespawnPlugin {
fn build(&self, app: &mut App) {
app.add_event::<PerformRespawnEvent>()
.add_systems(Update, perform_respawn.before(handle_send_packet_event));
.add_systems(Update, perform_respawn.before(handle_outgoing_packets));
}
}

View file

@ -8,7 +8,7 @@ use azalea_world::InstanceName;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use crate::{mining::MiningSet, packet_handling::game::SendPacketEvent};
use crate::{mining::MiningSet, packet::game::SendPacketEvent};
/// A plugin that makes clients send a [`ServerboundClientTickEnd`] packet every
/// tick.

View file

@ -482,7 +482,7 @@ impl EntityBundle {
/// be updated by other clients.
///
/// If this is for a client then all of our clients will have this.
#[derive(Component, Clone, Debug)]
#[derive(Component, Clone, Debug, Default)]
pub struct LocalEntity;
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]

View file

@ -1,5 +1,5 @@
use azalea_buf::AzBuf;
use azalea_core::{position::Vec3, resource_location::ResourceLocation};
use azalea_core::{delta::PositionDelta8, position::Vec3, resource_location::ResourceLocation};
use azalea_entity::{metadata::apply_default_metadata, EntityBundle};
use azalea_protocol_macros::ClientboundGamePacket;
use azalea_world::MinecraftEntityId;
@ -18,9 +18,7 @@ pub struct ClientboundAddEntity {
pub y_head_rot: i8,
#[var]
pub data: u32,
pub x_vel: i16,
pub y_vel: i16,
pub z_vel: i16,
pub velocity: PositionDelta8,
}
impl ClientboundAddEntity {

View file

@ -1,4 +1,5 @@
use azalea_buf::AzBuf;
use azalea_core::delta::PositionDelta8;
use azalea_protocol_macros::ClientboundGamePacket;
use azalea_world::MinecraftEntityId;
@ -6,7 +7,5 @@ use azalea_world::MinecraftEntityId;
pub struct ClientboundSetEntityMotion {
#[var]
pub id: MinecraftEntityId,
pub xa: i16,
pub ya: i16,
pub za: i16,
pub delta: PositionDelta8,
}

View file

@ -8,7 +8,7 @@ use azalea::{
chunks::ReceiveChunkEvent,
entity::{LookDirection, Position},
interact::HitResultComponent,
packet_handling::game,
packet::game,
pathfinder::{ExecutingPath, Pathfinder},
world::MinecraftEntityId,
};
@ -240,8 +240,8 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
}
}
}
"bevy_ecs::event::collections::Events<azalea_client::packet_handling::game::PacketEvent>" => {
let events = ecs.resource::<Events<game::PacketEvent>>();
"bevy_ecs::event::collections::Events<azalea_client::packet::game::ReceivePacketEvent>" => {
let events = ecs.resource::<Events<game::ReceivePacketEvent>>();
writeln!(report, "- Event count: {}", events.len()).unwrap();
}
"bevy_ecs::event::collections::Events<azalea_client::chunks::ReceiveChunkEvent>" => {

View file

@ -1,7 +1,7 @@
use azalea_client::chunks::handle_chunk_batch_finished_event;
use azalea_client::inventory::InventorySet;
use azalea_client::packet_handling::game::SendPacketEvent;
use azalea_client::packet_handling::{death_event_on_0_health, game::ResourcePackEvent};
use azalea_client::packet::game::SendPacketEvent;
use azalea_client::packet::{death_event_on_0_health, game::ResourcePackEvent};
use azalea_client::respawn::perform_respawn;
use azalea_protocol::packets::game::s_resource_pack::{self, ServerboundResourcePack};
use bevy_app::Update;

View file

@ -1,5 +1,5 @@
use azalea_client::{
packet_handling::{death_event_on_0_health, game::DeathEvent},
packet::{death_event_on_0_health, game::DeathEvent},
respawn::{PerformRespawnEvent, perform_respawn},
};
use bevy_app::Update;

View file

@ -1,10 +1,10 @@
use std::fmt::Debug;
use std::fmt::Formatter;
use azalea_client::packet::game::ReceivePacketEvent;
use azalea_client::{
Client,
inventory::{CloseContainerEvent, ContainerClickEvent, Inventory},
packet_handling::game::PacketEvent,
};
use azalea_core::position::BlockPos;
use azalea_inventory::{ItemStack, Menu, operations::ClickOperation};
@ -234,7 +234,7 @@ impl ContainerHandle {
#[derive(Component, Debug)]
pub struct WaitingForInventoryOpen;
fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader<PacketEvent>) {
fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader<ReceivePacketEvent>) {
for event in events.read() {
if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet.as_ref() {
commands

View file

@ -2,7 +2,7 @@
use std::sync::Arc;
use azalea_client::{PhysicsState, inventory::Inventory, packet_handling::game::SendPacketEvent};
use azalea_client::{PhysicsState, inventory::Inventory, packet::game::SendPacketEvent};
use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick};
use azalea_entity::{
Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance,
@ -60,13 +60,13 @@ fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc<RwLock<Instance
app.add_plugins((
azalea_physics::PhysicsPlugin,
azalea_entity::EntityPlugin,
azalea_client::movement::PlayerMovePlugin,
azalea_client::movement::MovementPlugin,
super::PathfinderPlugin,
crate::BotPlugin,
azalea_client::task_pool::TaskPoolPlugin::default(),
// for mining
azalea_client::inventory::InventoryPlugin,
azalea_client::mining::MinePlugin,
azalea_client::mining::MiningPlugin,
azalea_client::interact::InteractPlugin,
))
.insert_resource(InstanceContainer {