use std::{io, sync::Arc}; use azalea_auth::game_profile::GameProfile; use azalea_core::GameMode; use azalea_entity::Dead; use azalea_protocol::packets::game::ServerboundGamePacket; use azalea_world::{Instance, PartialInstance}; use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, prelude::Event, query::Added, system::Query, }; use derive_more::{Deref, DerefMut}; use parking_lot::RwLock; use thiserror::Error; use tokio::{sync::mpsc, task::JoinHandle}; use crate::{ events::{Event as AzaleaEvent, LocalPlayerEvents}, ClientInformation, }; /// This is a component for our local player entities that are probably in a /// world. If you have access to a [`Client`], you probably don't need to care /// about this since `Client` gives you access to everything here. /// /// You can also use the [`LocalEntity`] marker component for queries if you're /// only checking for a local player and don't need the contents of this /// component. /// /// [`LocalEntity`]: azalea_entity::LocalEntity /// [`Client`]: crate::Client #[derive(Component)] pub struct LocalPlayer { pub packet_writer: mpsc::UnboundedSender, /// The partial instance is the world this client currently has loaded. It /// has a limited render distance. pub partial_instance: Arc>, /// The world is the combined [`PartialInstance`]s of all clients in the /// same world. (Only relevant if you're using a shared world, i.e. a /// swarm) pub world: Arc>, /// A task that reads packets from the server. The client is disconnected /// when this task ends. pub(crate) read_packets_task: JoinHandle<()>, /// A task that writes packets from the server. pub(crate) write_packets_task: JoinHandle<()>, } /// A component only present in players that contains the [`GameProfile`] (which /// you can use to get a player's name). /// /// Note that it's possible for this to be missing in a player if the server /// never sent the player info for them (though this is uncommon). #[derive(Component, Clone, Debug, Deref, DerefMut)] pub struct GameProfileComponent(pub GameProfile); /// The gamemode of a local player. For a non-local player, you can look up the /// player in the [`TabList`]. #[derive(Component, Clone, Debug, Copy)] pub struct LocalGameMode { pub current: GameMode, pub previous: Option, } #[derive(Component, Clone)] pub struct Hunger { /// The main hunger bar. Goes from 0 to 20. pub food: u32, /// The amount of saturation the player has. This isn't shown in normal /// vanilla clients but it's a separate counter that makes it so your hunger /// only starts decreasing when this is 0. pub saturation: f32, } impl Default for Hunger { fn default() -> Self { Hunger { food: 20, saturation: 5., } } } impl LocalPlayer { /// Create a new `LocalPlayer`. pub fn new( entity: Entity, packet_writer: mpsc::UnboundedSender, world: Arc>, read_packets_task: JoinHandle<()>, write_packets_task: JoinHandle<()>, ) -> Self { let client_information = ClientInformation::default(); LocalPlayer { packet_writer, world, partial_instance: Arc::new(RwLock::new(PartialInstance::new( azalea_world::calculate_chunk_storage_range( client_information.view_distance.into(), ), Some(entity), ))), read_packets_task, write_packets_task, } } /// Write a packet directly to the server. pub fn write_packet(&self, packet: ServerboundGamePacket) { self.packet_writer .send(packet) .expect("write_packet shouldn't be able to be called if the connection is closed"); } } impl Drop for LocalPlayer { /// Stop every active task when the `LocalPlayer` is dropped. fn drop(&mut self) { self.read_packets_task.abort(); self.write_packets_task.abort(); } } /// Send the "Death" event for [`LocalPlayer`]s that died with no reason. pub fn death_event(query: Query<&LocalPlayerEvents, Added>) { for local_player_events in &query { local_player_events.send(AzaleaEvent::Death(None)).unwrap(); } } #[derive(Error, Debug)] pub enum HandlePacketError { #[error("{0}")] Poison(String), #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] Other(#[from] anyhow::Error), #[error("{0}")] Send(#[from] mpsc::error::SendError), } impl From> for HandlePacketError { fn from(e: std::sync::PoisonError) -> Self { 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, mut query: Query<&mut LocalPlayer>, ) { for event in send_packet_events.iter() { if let Ok(local_player) = query.get_mut(event.entity) { local_player.write_packet(event.packet.clone()); } } }