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