1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
use std::{io, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_core::GameMode;
use azalea_entity::Dead;
use azalea_protocol::packets::game::ServerboundGamePacket;
use azalea_world::{Instance, PartialInstance};
use bevy_ecs::{
component::Component, entity::Entity, event::EventReader, prelude::Event, query::Added,
system::Query,
};
use derive_more::{Deref, DerefMut};
use parking_lot::RwLock;
use thiserror::Error;
use tokio::{sync::mpsc, task::JoinHandle};
use crate::{
events::{Event as AzaleaEvent, LocalPlayerEvents},
ClientInformation,
};
/// This is a component for our local player entities that are probably in a
/// world. If you have access to a [`Client`], you probably don't need to care
/// about this since `Client` gives you access to everything here.
///
/// You can also use the [`LocalEntity`] marker component for queries if you're
/// only checking for a local player and don't need the contents of this
/// component.
///
/// [`LocalEntity`]: azalea_entity::LocalEntity
/// [`Client`]: crate::Client
#[derive(Component)]
pub struct LocalPlayer {
pub packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
/// The partial instance is the world this client currently has loaded. It
/// has a limited render distance.
pub partial_instance: Arc<RwLock<PartialInstance>>,
/// The world is the combined [`PartialInstance`]s of all clients in the
/// same world. (Only relevant if you're using a shared world, i.e. a
/// swarm)
pub world: Arc<RwLock<Instance>>,
/// A task that reads packets from the server. The client is disconnected
/// when this task ends.
pub(crate) read_packets_task: JoinHandle<()>,
/// A task that writes packets from the server.
pub(crate) write_packets_task: JoinHandle<()>,
}
/// A component only present in players that contains the [`GameProfile`] (which
/// you can use to get a player's name).
///
/// Note that it's possible for this to be missing in a player if the server
/// never sent the player info for them (though this is uncommon).
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct GameProfileComponent(pub GameProfile);
/// The gamemode of a local player. For a non-local player, you can look up the
/// player in the [`TabList`].
#[derive(Component, Clone, Debug, Copy)]
pub struct LocalGameMode {
pub current: GameMode,
pub previous: Option<GameMode>,
}
#[derive(Component, Clone)]
pub struct Hunger {
/// The main hunger bar. Goes from 0 to 20.
pub food: u32,
/// The amount of saturation the player has. This isn't shown in normal
/// vanilla clients but it's a separate counter that makes it so your hunger
/// only starts decreasing when this is 0.
pub saturation: f32,
}
impl Default for Hunger {
fn default() -> Self {
Hunger {
food: 20,
saturation: 5.,
}
}
}
impl LocalPlayer {
/// Create a new `LocalPlayer`.
pub fn new(
entity: Entity,
packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
world: Arc<RwLock<Instance>>,
read_packets_task: JoinHandle<()>,
write_packets_task: JoinHandle<()>,
) -> Self {
let client_information = ClientInformation::default();
LocalPlayer {
packet_writer,
world,
partial_instance: Arc::new(RwLock::new(PartialInstance::new(
azalea_world::calculate_chunk_storage_range(
client_information.view_distance.into(),
),
Some(entity),
))),
read_packets_task,
write_packets_task,
}
}
/// Write a packet directly to the server.
pub fn write_packet(&self, packet: ServerboundGamePacket) {
self.packet_writer
.send(packet)
.expect("write_packet shouldn't be able to be called if the connection is closed");
}
}
impl Drop for LocalPlayer {
/// Stop every active task when the `LocalPlayer` is dropped.
fn drop(&mut self) {
self.read_packets_task.abort();
self.write_packets_task.abort();
}
}
/// Send the "Death" event for [`LocalPlayer`]s that died with no reason.
pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) {
for local_player_events in &query {
local_player_events.send(AzaleaEvent::Death(None)).unwrap();
}
}
#[derive(Error, Debug)]
pub enum HandlePacketError {
#[error("{0}")]
Poison(String),
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
#[error("{0}")]
Send(#[from] mpsc::error::SendError<AzaleaEvent>),
}
impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
fn from(e: std::sync::PoisonError<T>) -> Self {
HandlePacketError::Poison(e.to_string())
}
}
/// Event for sending a packet to the server.
#[derive(Event)]
pub struct SendPacketEvent {
pub entity: Entity,
pub packet: ServerboundGamePacket,
}
pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendPacketEvent>,
mut query: Query<&mut LocalPlayer>,
) {
for event in send_packet_events.iter() {
if let Ok(local_player) = query.get_mut(event.entity) {
local_player.write_packet(event.packet.clone());
}
}
}
|