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}