make it so plugins can send and receive packets during the login state

This commit is contained in:
mat 2023-12-03 02:41:09 -06:00
parent 0713223e12
commit 1f46ef8c11
22 changed files with 211 additions and 80 deletions

12
Cargo.lock generated
View file

@ -196,6 +196,7 @@ dependencies = [
"base64", "base64",
"chrono", "chrono",
"env_logger", "env_logger",
"md-5",
"num-bigint", "num-bigint",
"once_cell", "once_cell",
"reqwest", "reqwest",
@ -1637,6 +1638,16 @@ dependencies = [
"regex-automata 0.1.10", "regex-automata 0.1.10",
] ]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.4" version = "2.6.4"
@ -2878,6 +2889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"md-5",
"serde", "serde",
] ]

View file

@ -25,7 +25,8 @@ serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108" serde_json = "1.0.108"
thiserror = "1.0.50" thiserror = "1.0.50"
tokio = { version = "1.34.0", features = ["fs"] } tokio = { version = "1.34.0", features = ["fs"] }
uuid = { version = "1.5.0", features = ["serde"] } uuid = { version = "1.5.0", features = ["serde", "v3"] }
md-5 = "0.10.6"
[dev-dependencies] [dev-dependencies]
env_logger = "0.10.1" env_logger = "0.10.1"

View file

@ -4,6 +4,7 @@ mod auth;
pub mod cache; pub mod cache;
pub mod certs; pub mod certs;
pub mod game_profile; pub mod game_profile;
pub mod offline;
pub mod sessionserver; pub mod sessionserver;
pub use auth::*; pub use auth::*;

View file

@ -0,0 +1,17 @@
use md5::{Digest, Md5};
use uuid::Uuid;
pub fn generate_uuid(username: &str) -> Uuid {
uuid::Builder::from_md5_bytes(hash(format!("OfflinePlayer:{username}").as_bytes())).into_uuid()
}
fn hash(data: &[u8]) -> [u8; 16] {
let mut hasher = Md5::new();
hasher.update(data);
let mut bytes = [0; 16];
bytes.copy_from_slice(&hasher.finalize()[..16]);
bytes
}

View file

@ -200,6 +200,13 @@ impl Account {
} }
} }
} }
/// Get the UUID of this account. This will generate an offline-mode UUID
/// by making a hash with the username if the `uuid` field is None.
pub fn uuid_or_offline(&self) -> Uuid {
self.uuid
.unwrap_or_else(|| azalea_auth::offline::generate_uuid(&self.username))
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -226,3 +233,5 @@ impl Account {
Ok(()) Ok(())
} }
} }
fn uuid_from_username() {}

View file

@ -13,11 +13,8 @@ use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use crate::{ use crate::{
interact::SwingArmEvent, interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
local_player::{LocalGameMode, SendPacketEvent}, packet_handling::game::SendPacketEvent, respawn::perform_respawn, Client,
movement::MoveEventsSet,
respawn::perform_respawn,
Client,
}; };
pub struct AttackPlugin; pub struct AttackPlugin;

View file

@ -22,7 +22,7 @@ use uuid::Uuid;
use crate::{ use crate::{
client::Client, client::Client,
local_player::{handle_send_packet_event, SendPacketEvent}, packet_handling::game::{handle_send_packet_event, SendPacketEvent},
}; };
/// A chat packet, either a system message or a chat message. /// A chat packet, either a system message or a chat message.

View file

@ -21,7 +21,7 @@ use tracing::{error, trace};
use crate::{ use crate::{
interact::handle_block_interact_event, interact::handle_block_interact_event,
inventory::InventorySet, inventory::InventorySet,
local_player::{handle_send_packet_event, SendPacketEvent}, packet_handling::game::{handle_send_packet_event, SendPacketEvent},
respawn::perform_respawn, respawn::perform_respawn,
InstanceHolder, InstanceHolder,
}; };

View file

@ -7,12 +7,16 @@ use crate::{
interact::{CurrentSequenceNumber, InteractPlugin}, interact::{CurrentSequenceNumber, InteractPlugin},
inventory::{InventoryComponent, InventoryPlugin}, inventory::{InventoryComponent, InventoryPlugin},
local_player::{ local_player::{
death_event, handle_send_packet_event, GameProfileComponent, Hunger, InstanceHolder, death_event, GameProfileComponent, Hunger, InstanceHolder, PermissionLevel,
PermissionLevel, PlayerAbilities, SendPacketEvent, TabList, PlayerAbilities, TabList,
}, },
mining::{self, MinePlugin}, mining::{self, MinePlugin},
movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin}, movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin},
packet_handling::PacketHandlerPlugin, packet_handling::{
game::{handle_send_packet_event, SendPacketEvent},
login::{self, LoginSendPacketQueue},
PacketHandlerPlugin,
},
player::retroactively_add_game_profile_component, player::retroactively_add_game_profile_component,
raw_connection::RawConnection, raw_connection::RawConnection,
respawn::RespawnPlugin, respawn::RespawnPlugin,
@ -35,6 +39,7 @@ use azalea_protocol::{
packets::{ packets::{
configuration::{ configuration::{
serverbound_client_information_packet::ClientInformation, serverbound_client_information_packet::ClientInformation,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
ClientboundConfigurationPacket, ServerboundConfigurationPacket, ClientboundConfigurationPacket, ServerboundConfigurationPacket,
}, },
game::ServerboundGamePacket, game::ServerboundGamePacket,
@ -208,8 +213,29 @@ impl Client {
resolved_address: &SocketAddr, resolved_address: &SocketAddr,
run_schedule_sender: mpsc::UnboundedSender<()>, run_schedule_sender: mpsc::UnboundedSender<()>,
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> { ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
// check if an entity with our uuid already exists in the ecs and if so then
// just use that
let entity = {
let mut ecs = ecs_lock.lock();
let entity_uuid_index = ecs.resource::<EntityUuidIndex>();
let uuid = account.uuid_or_offline();
if let Some(entity) = entity_uuid_index.get(&account.uuid_or_offline()) {
debug!("Reusing entity {entity:?} for client");
entity
} else {
let entity = ecs.spawn_empty().id();
debug!("Created new entity {entity:?} for client");
// add to the uuid index
let mut entity_uuid_index = ecs.resource_mut::<EntityUuidIndex>();
entity_uuid_index.insert(uuid, entity);
entity
}
};
let conn = Connection::new(resolved_address).await?; let conn = Connection::new(resolved_address).await?;
let (mut conn, game_profile) = Self::handshake(conn, account, address).await?; let (mut conn, game_profile) =
Self::handshake(ecs_lock.clone(), entity, conn, account, address).await?;
{ {
// quickly send the brand here // quickly send the brand here
@ -217,12 +243,13 @@ impl Client {
// they don't have to know :) // they don't have to know :)
"vanilla".write_into(&mut brand_data).unwrap(); "vanilla".write_into(&mut brand_data).unwrap();
conn.write( conn.write(
azalea_protocol::packets::configuration::serverbound_custom_payload_packet::ServerboundCustomPayloadPacket { ServerboundCustomPayloadPacket {
identifier: ResourceLocation::new("brand"), identifier: ResourceLocation::new("brand"),
data: brand_data.into(), data: brand_data.into(),
} }
.get(), .get(),
).await?; )
.await?;
} }
let (read_conn, write_conn) = conn.into_split(); let (read_conn, write_conn) = conn.into_split();
@ -234,22 +261,6 @@ impl Client {
let mut ecs = ecs_lock.lock(); let mut ecs = ecs_lock.lock();
// check if an entity with our uuid already exists in the ecs and if so then
// just use that
let entity = {
let entity_uuid_index = ecs.resource::<EntityUuidIndex>();
if let Some(entity) = entity_uuid_index.get(&game_profile.uuid) {
debug!("Reusing entity {entity:?} for client");
entity
} else {
let entity = ecs.spawn_empty().id();
debug!("Created new entity {entity:?} for client");
// add to the uuid index
let mut entity_uuid_index = ecs.resource_mut::<EntityUuidIndex>();
entity_uuid_index.insert(game_profile.uuid, entity);
entity
}
};
// we got the ConfigurationConnection, so the client is now connected :) // we got the ConfigurationConnection, so the client is now connected :)
let client = Client::new( let client = Client::new(
game_profile.clone(), game_profile.clone(),
@ -284,6 +295,8 @@ impl Client {
/// This will also automatically refresh the account's access token if /// This will also automatically refresh the account's access token if
/// it's expired. /// it's expired.
pub async fn handshake( pub async fn handshake(
ecs_lock: Arc<Mutex<World>>,
entity: Entity,
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>, mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
account: &Account, account: &Account,
address: &ServerAddress, address: &ServerAddress,
@ -307,6 +320,14 @@ impl Client {
.await?; .await?;
let mut conn = conn.login(); let mut conn = conn.login();
// this makes it so plugins can send an `SendLoginPacketEvent` event to the ecs
// and we'll send it to the server
let (ecs_packets_tx, mut ecs_packets_rx) = mpsc::unbounded_channel();
ecs_lock
.lock()
.entity_mut(entity)
.insert(LoginSendPacketQueue { tx: ecs_packets_tx });
// login // login
conn.write( conn.write(
ServerboundHelloPacket { ServerboundHelloPacket {
@ -320,7 +341,20 @@ impl Client {
.await?; .await?;
let (conn, profile) = loop { let (conn, profile) = loop {
let packet = conn.read().await?; let packet = tokio::select! {
packet = conn.read() => packet?,
Some(packet) = ecs_packets_rx.recv() => {
// write this packet to the server
conn.write(packet).await?;
continue;
}
};
ecs_lock.lock().send_event(login::LoginPacketEvent {
entity,
packet: Arc::new(packet.clone()),
});
match packet { match packet {
ClientboundLoginPacket::Hello(p) => { ClientboundLoginPacket::Hello(p) => {
debug!("Got encryption request"); debug!("Got encryption request");
@ -655,7 +689,7 @@ impl Plugin for AzaleaPlugin {
/// [`DefaultPlugins`]. /// [`DefaultPlugins`].
#[doc(hidden)] #[doc(hidden)]
pub fn start_ecs_runner( pub fn start_ecs_runner(
mut app: App, app: App,
run_schedule_receiver: mpsc::UnboundedReceiver<()>, run_schedule_receiver: mpsc::UnboundedReceiver<()>,
run_schedule_sender: mpsc::UnboundedSender<()>, run_schedule_sender: mpsc::UnboundedSender<()>,
) -> Arc<Mutex<World>> { ) -> Arc<Mutex<World>> {

View file

@ -34,10 +34,9 @@ use tracing::warn;
use crate::{ use crate::{
attack::handle_attack_event, attack::handle_attack_event,
inventory::{InventoryComponent, InventorySet}, inventory::{InventoryComponent, InventorySet},
local_player::{ local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
handle_send_packet_event, LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent,
},
movement::MoveEventsSet, movement::MoveEventsSet,
packet_handling::game::{handle_send_packet_event, SendPacketEvent},
respawn::perform_respawn, respawn::perform_respawn,
Client, Client,
}; };

View file

@ -26,7 +26,8 @@ use bevy_ecs::{
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
local_player::{handle_send_packet_event, PlayerAbilities, SendPacketEvent}, local_player::PlayerAbilities,
packet_handling::game::{handle_send_packet_event, SendPacketEvent},
respawn::perform_respawn, respawn::perform_respawn,
Client, Client,
}; };

View file

@ -36,7 +36,7 @@ pub use client::{
start_ecs_runner, Client, DefaultPlugins, JoinError, JoinedClientBundle, TickBroadcast, start_ecs_runner, Client, DefaultPlugins, JoinError, JoinedClientBundle, TickBroadcast,
}; };
pub use events::Event; pub use events::Event;
pub use local_player::{GameProfileComponent, InstanceHolder, SendPacketEvent, TabList}; pub use local_player::{GameProfileComponent, InstanceHolder, TabList};
pub use movement::{ pub use movement::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
}; };

View file

@ -160,24 +160,3 @@ impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
HandlePacketError::Poison(e.to_string()) HandlePacketError::Poison(e.to_string())
} }
} }
/// Event for sending a packet to the server.
#[derive(Event)]
pub struct SendPacketEvent {
pub entity: Entity,
pub packet: ServerboundGamePacket,
}
pub fn handle_send_packet_event(
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.entity) {
// debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
}
}
}

View file

@ -17,8 +17,9 @@ use crate::{
HitResultComponent, SwingArmEvent, HitResultComponent, SwingArmEvent,
}, },
inventory::{InventoryComponent, InventorySet}, inventory::{InventoryComponent, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent}, local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet, movement::MoveEventsSet,
packet_handling::game::SendPacketEvent,
Client, Client,
}; };

View file

@ -1,5 +1,5 @@
use crate::client::Client; use crate::client::Client;
use crate::local_player::SendPacketEvent; use crate::packet_handling::game::SendPacketEvent;
use azalea_core::position::Vec3; use azalea_core::position::Vec3;
use azalea_entity::{metadata::Sprinting, Attributes, Jumping}; use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position}; use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};

View file

@ -22,7 +22,7 @@ use crate::packet_handling::game::KeepAliveEvent;
use crate::raw_connection::RawConnection; use crate::raw_connection::RawConnection;
#[derive(Event, Debug, Clone)] #[derive(Event, Debug, Clone)]
pub struct PacketEvent { pub struct ConfigurationPacketEvent {
/// The client entity that received the packet. /// The client entity that received the packet.
pub entity: Entity, pub entity: Entity,
/// The packet that was actually received. /// The packet that was actually received.
@ -31,7 +31,7 @@ pub struct PacketEvent {
pub fn send_packet_events( pub fn send_packet_events(
query: Query<(Entity, &RawConnection), With<InConfigurationState>>, query: Query<(Entity, &RawConnection), With<InConfigurationState>>,
mut packet_events: ResMut<Events<PacketEvent>>, mut packet_events: ResMut<Events<ConfigurationPacketEvent>>,
) { ) {
// we manually clear and send the events at the beginning of each update // 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 // since otherwise it'd cause issues with events in process_packet_events
@ -51,7 +51,7 @@ pub fn send_packet_events(
continue; continue;
} }
}; };
packet_events.send(PacketEvent { packet_events.send(ConfigurationPacketEvent {
entity: player_entity, entity: player_entity,
packet, packet,
}); });
@ -64,9 +64,10 @@ pub fn send_packet_events(
pub fn process_packet_events(ecs: &mut World) { pub fn process_packet_events(ecs: &mut World) {
let mut events_owned = Vec::new(); let mut events_owned = Vec::new();
let mut system_state: SystemState<EventReader<PacketEvent>> = SystemState::new(ecs); let mut system_state: SystemState<EventReader<ConfigurationPacketEvent>> =
SystemState::new(ecs);
let mut events = system_state.get_mut(ecs); let mut events = system_state.get_mut(ecs);
for PacketEvent { for ConfigurationPacketEvent {
entity: player_entity, entity: player_entity,
packet, packet,
} in events.read() } in events.read()

View file

@ -23,6 +23,7 @@ use azalea_protocol::{
serverbound_keep_alive_packet::ServerboundKeepAlivePacket, serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
serverbound_pong_packet::ServerboundPongPacket, ClientboundGamePacket, serverbound_pong_packet::ServerboundPongPacket, ClientboundGamePacket,
ServerboundGamePacket,
}, },
read::deserialize_packet, read::deserialize_packet,
}; };
@ -40,8 +41,7 @@ use crate::{
SetContainerContentEvent, SetContainerContentEvent,
}, },
local_player::{ local_player::{
GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
SendPacketEvent, TabList,
}, },
movement::{KnockbackEvent, KnockbackType}, movement::{KnockbackEvent, KnockbackType},
raw_connection::RawConnection, raw_connection::RawConnection,
@ -1391,3 +1391,24 @@ pub fn process_packet_events(ecs: &mut World) {
} }
} }
} }
/// An event for sending a packet to the server while we're in the `game` state.
#[derive(Event)]
pub struct SendPacketEvent {
pub entity: Entity,
pub packet: ServerboundGamePacket,
}
pub fn handle_send_packet_event(
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.entity) {
// debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
}
}
}

View file

@ -0,0 +1,49 @@
// login packets aren't actually handled here because compression/encryption
// would make packet handling a lot messier
use std::sync::Arc;
use azalea_protocol::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
use bevy_ecs::prelude::*;
use tokio::sync::mpsc;
use tracing::error;
use crate::raw_connection::RawConnection;
use super::game::SendPacketEvent;
// this struct is defined here anyways though so it's consistent with the other
// ones
#[derive(Event, Debug, Clone)]
pub struct LoginPacketEvent {
/// The client entity that received the packet.
pub entity: Entity,
/// The packet that was actually received.
pub packet: Arc<ClientboundLoginPacket>,
}
/// Event for sending a login packet to the server.
#[derive(Event)]
pub struct SendLoginPacketEvent {
pub entity: Entity,
pub packet: ServerboundLoginPacket,
}
#[derive(Component)]
pub struct LoginSendPacketQueue {
pub tx: mpsc::UnboundedSender<ServerboundLoginPacket>,
}
pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendLoginPacketEvent>,
mut query: Query<&mut LoginSendPacketQueue>,
) {
for event in send_packet_events.read() {
if let Ok(queue) = query.get_mut(event.entity) {
let _ = queue.tx.send(event.packet.clone());
} else {
error!("Sent SendPacketEvent for entity that doesn't have a LoginSendPacketQueue");
}
}
}

View file

@ -4,13 +4,17 @@ use bevy_ecs::prelude::*;
use crate::{chat::ChatReceivedEvent, events::death_listener}; use crate::{chat::ChatReceivedEvent, events::death_listener};
use self::game::{ use self::{
game::{
AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent, AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent,
ResourcePackEvent, UpdatePlayerEvent, ResourcePackEvent, UpdatePlayerEvent,
},
login::{LoginPacketEvent, SendLoginPacketEvent},
}; };
pub mod configuration; pub mod configuration;
pub mod game; pub mod game;
pub mod login;
pub struct PacketHandlerPlugin; pub struct PacketHandlerPlugin;
@ -37,16 +41,17 @@ impl Plugin for PacketHandlerPlugin {
.add_systems( .add_systems(
PreUpdate, PreUpdate,
( (
game::process_packet_events, game::process_packet_events
configuration::process_packet_events,
)
// we want to index and deindex right after // we want to index and deindex right after
.before(EntityUpdateSet::Deindex), .before(EntityUpdateSet::Deindex),
configuration::process_packet_events,
login::handle_send_packet_event,
),
) )
.add_systems(Update, death_event_on_0_health.before(death_listener)) .add_systems(Update, death_event_on_0_health.before(death_listener))
// we do this instead of add_event so we can handle the events ourselves // we do this instead of add_event so we can handle the events ourselves
.init_resource::<Events<game::PacketEvent>>() .init_resource::<Events<game::PacketEvent>>()
.init_resource::<Events<configuration::PacketEvent>>() .init_resource::<Events<configuration::ConfigurationPacketEvent>>()
.add_event::<AddPlayerEvent>() .add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>() .add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>() .add_event::<UpdatePlayerEvent>()
@ -54,6 +59,8 @@ impl Plugin for PacketHandlerPlugin {
.add_event::<DeathEvent>() .add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>() .add_event::<KeepAliveEvent>()
.add_event::<ResourcePackEvent>() .add_event::<ResourcePackEvent>()
.add_event::<InstanceLoadedEvent>(); .add_event::<InstanceLoadedEvent>()
.add_event::<LoginPacketEvent>()
.add_event::<SendLoginPacketEvent>();
} }
} }

View file

@ -4,7 +4,7 @@ use azalea_protocol::packets::game::serverbound_client_command_packet::{
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use crate::local_player::{handle_send_packet_event, SendPacketEvent}; use crate::packet_handling::game::{handle_send_packet_event, SendPacketEvent};
/// Tell the server that we're respawning. /// Tell the server that we're respawning.
#[derive(Event, Debug, Clone)] #[derive(Event, Debug, Clone)]

View file

@ -1,9 +1,9 @@
use crate::app::{App, Plugin}; use crate::app::{App, Plugin};
use azalea_client::chunks::handle_chunk_batch_finished_event; use azalea_client::chunks::handle_chunk_batch_finished_event;
use azalea_client::inventory::InventorySet; 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_handling::{death_event_on_0_health, game::ResourcePackEvent};
use azalea_client::respawn::perform_respawn; use azalea_client::respawn::perform_respawn;
use azalea_client::SendPacketEvent;
use azalea_protocol::packets::game::serverbound_resource_pack_packet::{ use azalea_protocol::packets::game::serverbound_resource_pack_packet::{
self, ServerboundResourcePackPacket, self, ServerboundResourcePackPacket,
}; };

View file

@ -2,7 +2,9 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use azalea_client::{inventory::InventoryComponent, PhysicsState}; use azalea_client::{
inventory::InventoryComponent, packet_handling::game::SendPacketEvent, PhysicsState,
};
use azalea_core::{position::Vec3, resource_location::ResourceLocation}; use azalea_core::{position::Vec3, resource_location::ResourceLocation};
use azalea_entity::{ use azalea_entity::{
attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics, attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics,
@ -77,7 +79,7 @@ impl Simulation {
.cloned() .cloned()
.collect(), .collect(),
}) })
.add_event::<azalea_client::SendPacketEvent>(); .add_event::<SendPacketEvent>();
app.edit_schedule(bevy_app::Main, |schedule| { app.edit_schedule(bevy_app::Main, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);