azalea_client/
local_player.rs

1use std::{
2    collections::HashMap,
3    error, io,
4    sync::{Arc, PoisonError},
5};
6
7use azalea_auth::game_profile::GameProfile;
8use azalea_core::game_type::GameMode;
9use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
10use azalea_world::{Instance, PartialInstance};
11use bevy_ecs::{component::Component, prelude::*};
12use derive_more::{Deref, DerefMut};
13use parking_lot::RwLock;
14use thiserror::Error;
15use tokio::sync::mpsc;
16use tracing::error;
17use uuid::Uuid;
18
19use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
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    /// Reset the `Instance` to a new reference to an empty instance, but with
153    /// the same registries as the current one.
154    ///
155    /// This is used by Azalea when entering the config state.
156    pub fn reset(&mut self) {
157        let registries = self.instance.read().registries.clone();
158
159        let new_instance = Instance {
160            registries,
161            ..Default::default()
162        };
163        self.instance = Arc::new(RwLock::new(new_instance));
164
165        self.partial_instance.write().reset();
166    }
167}
168
169#[derive(Error, Debug)]
170pub enum HandlePacketError {
171    #[error("{0}")]
172    Poison(String),
173    #[error(transparent)]
174    Io(#[from] io::Error),
175    #[error(transparent)]
176    Other(#[from] Box<dyn error::Error + Send + Sync>),
177    #[error("{0}")]
178    Send(#[from] mpsc::error::SendError<AzaleaEvent>),
179}
180
181impl<T> From<PoisonError<T>> for HandlePacketError {
182    fn from(e: PoisonError<T>) -> Self {
183        HandlePacketError::Poison(e.to_string())
184    }
185}