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