azalea_client/
local_player.rs

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