azalea_client/
local_player.rs

1use std::{collections::HashMap, io, sync::Arc};
2
3use azalea_auth::game_profile::GameProfile;
4use azalea_core::game_type::GameMode;
5use azalea_entity::Dead;
6use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
7use azalea_world::{Instance, PartialInstance};
8use bevy_ecs::{component::Component, prelude::*};
9use derive_more::{Deref, DerefMut};
10use parking_lot::RwLock;
11use thiserror::Error;
12use tokio::sync::mpsc;
13use tracing::error;
14use uuid::Uuid;
15
16use crate::{
17    events::{Event as AzaleaEvent, LocalPlayerEvents},
18    ClientInformation, PlayerInfo,
19};
20
21/// A component that keeps strong references to our [`PartialInstance`] and
22/// [`Instance`] for local players.
23///
24/// This can also act as a convenience for accessing the player's Instance since
25/// the alternative is to look up the player's [`InstanceName`] in the
26/// [`InstanceContainer`].
27///
28/// [`InstanceContainer`]: azalea_world::InstanceContainer
29/// [`InstanceName`]: azalea_world::InstanceName
30#[derive(Component, Clone)]
31pub struct InstanceHolder {
32    /// The partial instance is the world this client currently has loaded. It
33    /// has a limited render distance.
34    pub partial_instance: Arc<RwLock<PartialInstance>>,
35    /// The world is the combined [`PartialInstance`]s of all clients in the
36    /// same world.
37    ///
38    /// This is only relevant if you're using a shared world (i.e. a
39    /// swarm).
40    pub instance: Arc<RwLock<Instance>>,
41}
42
43/// A component only present in players that contains the [`GameProfile`] (which
44/// you can use to get a player's name).
45///
46/// Note that it's possible for this to be missing in a player if the server
47/// never sent the player info for them (though this is uncommon).
48#[derive(Component, Clone, Debug, Deref, DerefMut)]
49pub struct GameProfileComponent(pub GameProfile);
50
51/// The gamemode of a local player. For a non-local player, you can look up the
52/// player in the [`TabList`].
53#[derive(Component, Clone, Debug, Copy)]
54pub struct LocalGameMode {
55    pub current: GameMode,
56    pub previous: Option<GameMode>,
57}
58
59/// A component that contains the abilities the player has, like flying
60/// or instantly breaking blocks. This is only present on local players.
61#[derive(Clone, Debug, Component, Default)]
62pub struct PlayerAbilities {
63    pub invulnerable: bool,
64    pub flying: bool,
65    pub can_fly: bool,
66    /// Whether the player can instantly break blocks and can duplicate blocks
67    /// in their inventory.
68    pub instant_break: bool,
69
70    pub flying_speed: f32,
71    /// Used for the fov
72    pub walking_speed: f32,
73}
74impl From<&ClientboundPlayerAbilities> for PlayerAbilities {
75    fn from(packet: &ClientboundPlayerAbilities) -> Self {
76        Self {
77            invulnerable: packet.flags.invulnerable,
78            flying: packet.flags.flying,
79            can_fly: packet.flags.can_fly,
80            instant_break: packet.flags.instant_break,
81            flying_speed: packet.flying_speed,
82            walking_speed: packet.walking_speed,
83        }
84    }
85}
86
87/// Level must be 0..=4
88#[derive(Component, Clone, Default, Deref, DerefMut)]
89pub struct PermissionLevel(pub u8);
90
91/// A component that contains a map of player UUIDs to their information in the
92/// tab list.
93///
94/// ```
95/// # use azalea_client::TabList;
96/// # fn example(client: &azalea_client::Client) {
97/// let tab_list = client.component::<TabList>();
98/// println!("Online players:");
99/// for (uuid, player_info) in tab_list.iter() {
100///     println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
101/// }
102/// # }
103/// ```
104///
105/// For convenience, `TabList` is also a resource in the ECS.
106/// It's set to be the same as the tab list for the last client whose tab list
107/// was updated.
108/// This means you should avoid using `TabList` as a resource unless you know
109/// all of your clients will have the same tab list.
110#[derive(Component, Resource, Clone, Debug, Deref, DerefMut, Default)]
111pub struct TabList(HashMap<Uuid, PlayerInfo>);
112
113#[derive(Component, Clone)]
114pub struct Hunger {
115    /// The main hunger bar. Goes from 0 to 20.
116    pub food: u32,
117    /// The amount of saturation the player has. This isn't shown in normal
118    /// vanilla clients but it's a separate counter that makes it so your hunger
119    /// only starts decreasing when this is 0.
120    pub saturation: f32,
121}
122
123impl Default for Hunger {
124    fn default() -> Self {
125        Hunger {
126            food: 20,
127            saturation: 5.,
128        }
129    }
130}
131
132impl InstanceHolder {
133    /// Create a new `InstanceHolder` for the given entity.
134    ///
135    /// The partial instance will be created for you. The render distance will
136    /// be set to a default value, which you can change by creating a new
137    /// partial_instance.
138    pub fn new(entity: Entity, instance: Arc<RwLock<Instance>>) -> Self {
139        let client_information = ClientInformation::default();
140
141        InstanceHolder {
142            instance,
143            partial_instance: Arc::new(RwLock::new(PartialInstance::new(
144                azalea_world::chunk_storage::calculate_chunk_storage_range(
145                    client_information.view_distance.into(),
146                ),
147                Some(entity),
148            ))),
149        }
150    }
151}
152
153/// Send the "Death" event for [`LocalEntity`]s that died with no reason.
154pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) {
155    for local_player_events in &query {
156        local_player_events.send(AzaleaEvent::Death(None)).unwrap();
157    }
158}
159
160#[derive(Error, Debug)]
161pub enum HandlePacketError {
162    #[error("{0}")]
163    Poison(String),
164    #[error(transparent)]
165    Io(#[from] io::Error),
166    #[error(transparent)]
167    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
168    #[error("{0}")]
169    Send(#[from] mpsc::error::SendError<AzaleaEvent>),
170}
171
172impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
173    fn from(e: std::sync::PoisonError<T>) -> Self {
174        HandlePacketError::Poison(e.to_string())
175    }
176}