forked from Pumpkin-MC/Pumpkin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplayer.rs
485 lines (457 loc) · 17.4 KB
/
player.rs
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
use std::sync::{
atomic::{AtomicI32, AtomicU8},
Arc,
};
use crossbeam::atomic::AtomicCell;
use num_derive::FromPrimitive;
use num_traits::ToPrimitive;
use pumpkin_core::{
math::{boundingbox::BoundingBox, position::WorldPosition, vector2::Vector2, vector3::Vector3},
text::TextComponent,
GameMode,
};
use pumpkin_entity::{entity_type::EntityType, EntityId};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_protocol::{
bytebuf::packet_id::Packet,
client::play::{
CGameEvent, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSetHealth,
CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction,
},
server::play::{
SChatCommand, SChatMessage, SClickContainer, SClientInformationPlay, SConfirmTeleport,
SInteract, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand,
SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem,
SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn,
},
RawPacket, ServerPacket, VarInt,
};
use tokio::sync::Mutex;
use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive};
use pumpkin_world::{cylindrical_chunk_iterator::Cylindrical, item::ItemStack};
use super::Entity;
use crate::{
client::{authentication::GameProfile, Client, PlayerConfig},
server::Server,
world::{player_chunker::chunk_section_from_pos, World},
};
use crate::{error::PumpkinError, world::player_chunker::get_view_distance};
use super::living::LivingEntity;
/// Represents a Minecraft player entity.
///
/// A `Player` is a special type of entity that represents a human player connected to the server.
pub struct Player {
/// The underlying living entity object that represents the player.
pub living_entity: LivingEntity,
/// The player's game profile information, including their username and UUID.
pub gameprofile: GameProfile,
/// The client connection associated with the player.
pub client: Arc<Client>,
/// The player's configuration settings. Changes when the Player changes their settings.
pub config: Mutex<PlayerConfig>,
/// The player's current gamemode (e.g., Survival, Creative, Adventure).
pub gamemode: AtomicCell<GameMode>,
/// The player's hunger level.
pub food: AtomicI32,
/// The player's food saturation level.
pub food_saturation: AtomicCell<f32>,
/// The player's inventory, containing items and equipment.
pub inventory: Mutex<PlayerInventory>,
/// The ID of the currently open container (if any).
pub open_container: AtomicCell<Option<u64>>,
/// The item currently being held by the player.
pub carried_item: AtomicCell<Option<ItemStack>>,
/// send `send_abilties_update` when changed
/// The player's abilities and special powers.
///
/// This field represents the various abilities that the player possesses, such as flight, invulnerability, and other special effects.
///
/// **Note:** When the `abilities` field is updated, the server should send a `send_abilities_update` packet to the client to notify them of the changes.
pub abilities: Mutex<PlayerAbilities>,
/// The player's last known position.
///
/// This field is used to calculate the player's movement delta for network synchronization and other purposes.
pub last_position: AtomicCell<Vector3<f64>>,
/// The current stage of the block the player is breaking.
pub current_block_destroy_stage: AtomicU8,
/// A counter for teleport IDs used to track pending teleports.
pub teleport_id_count: AtomicI32,
/// The pending teleport information, including the teleport ID and target location.
pub awaiting_teleport: Mutex<Option<(VarInt, Vector3<f64>)>>,
/// The coordinates of the chunk section the player is currently watching.
pub watched_section: AtomicCell<Vector3<i32>>,
}
impl Player {
pub async fn new(
client: Arc<Client>,
world: Arc<World>,
entity_id: EntityId,
gamemode: GameMode,
) -> Self {
let gameprofile = client.gameprofile.lock().await.clone().map_or_else(
|| {
log::error!("No gameprofile?. Impossible");
GameProfile {
id: uuid::Uuid::new_v4(),
name: "".to_string(),
properties: vec![],
profile_actions: None,
}
},
|profile| profile,
);
let config = client.config.lock().await.clone().unwrap_or_default();
Self {
living_entity: LivingEntity::new(Entity::new(
entity_id,
world,
EntityType::Player,
1.62,
)),
config: Mutex::new(config),
gameprofile,
client,
awaiting_teleport: Mutex::new(None),
// TODO: Load this from previous instance
food: AtomicI32::new(20),
food_saturation: AtomicCell::new(20.0),
current_block_destroy_stage: AtomicU8::new(0),
inventory: Mutex::new(PlayerInventory::new()),
open_container: AtomicCell::new(None),
carried_item: AtomicCell::new(None),
teleport_id_count: AtomicI32::new(0),
abilities: Mutex::new(PlayerAbilities::default()),
gamemode: AtomicCell::new(gamemode),
watched_section: AtomicCell::new(Vector3::new(0, 0, 0)),
last_position: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)),
}
}
/// Removes the Player out of the current World
pub async fn remove(&self) {
self.living_entity.entity.world.remove_player(self).await;
let watched = chunk_section_from_pos(&self.living_entity.entity.block_pos.load());
let view_distance = get_view_distance(self).await as i32;
let cylindrical = Cylindrical::new(Vector2::new(watched.x, watched.z), view_distance);
self.living_entity
.entity
.world
.mark_chunks_as_not_watched(&cylindrical.all_chunks_within())
.await;
}
pub const fn entity_id(&self) -> EntityId {
self.living_entity.entity.entity_id
}
/// Updates the current abilities the Player has
pub async fn send_abilties_update(&mut self) {
let mut b = 0i8;
let abilities = &self.abilities.lock().await;
if abilities.invulnerable {
b |= 1;
}
if abilities.flying {
b |= 2;
}
if abilities.allow_flying {
b |= 4;
}
if abilities.creative {
b |= 8;
}
self.client
.send_packet(&CPlayerAbilities::new(
b,
abilities.fly_speed,
abilities.walk_speed_fov,
))
.await;
}
/// yaw and pitch in degrees
pub async fn teleport(&self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) {
// this is the ultra special magic code used to create the teleport id
// This returns the old value
let i = self
.teleport_id_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if i + 2 == i32::MAX {
self.teleport_id_count
.store(0, std::sync::atomic::Ordering::Relaxed);
}
let teleport_id = i + 1;
let entity = &self.living_entity.entity;
entity.set_pos(x, y, z);
entity.set_rotation(yaw, pitch);
*self.awaiting_teleport.lock().await = Some((teleport_id.into(), Vector3::new(x, y, z)));
self.client
.send_packet(&CSyncPlayerPosition::new(
x,
y,
z,
yaw,
pitch,
0,
teleport_id.into(),
))
.await;
}
pub fn block_interaction_range(&self) -> f64 {
if self.gamemode.load() == GameMode::Creative {
5.0
} else {
4.5
}
}
pub fn can_interact_with_block_at(&self, pos: &WorldPosition, additional_range: f64) -> bool {
let d = self.block_interaction_range() + additional_range;
let box_pos = BoundingBox::from_block(pos);
let entity_pos = self.living_entity.entity.pos.load();
let standing_eye_height = self.living_entity.entity.standing_eye_height;
box_pos.squared_magnitude(Vector3 {
x: entity_pos.x,
y: entity_pos.y + standing_eye_height as f64,
z: entity_pos.z,
}) < d * d
}
/// Kicks the Client with a reason depending on the connection state
pub async fn kick<'a>(&self, reason: TextComponent<'a>) {
assert!(!self
.client
.closed
.load(std::sync::atomic::Ordering::Relaxed));
self.client
.try_send_packet(&CPlayDisconnect::new(&reason))
.await
.unwrap_or_else(|_| self.client.close());
log::info!(
"Kicked {} for {}",
self.gameprofile.name,
reason.to_pretty_console()
);
self.client.close()
}
pub async fn set_health(&self, health: f32, food: i32, food_saturation: f32) {
self.living_entity.set_health(health).await;
self.food.store(food, std::sync::atomic::Ordering::Relaxed);
self.food_saturation.store(food_saturation);
self.client
.send_packet(&CSetHealth::new(health, food.into(), food_saturation))
.await;
}
pub async fn set_gamemode(&self, gamemode: GameMode) {
// We could send the same gamemode without problems. But why waste bandwidth ?
let current_gamemode = self.gamemode.load();
assert!(
current_gamemode != gamemode,
"Setting the same gamemode as already is"
);
self.gamemode.store(gamemode);
// So a little story time. I actually made an abilties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client
// does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense
self.living_entity
.entity
.world
.broadcast_packet_all(&CPlayerInfoUpdate::new(
0x04,
&[pumpkin_protocol::client::play::Player {
uuid: self.gameprofile.id,
actions: vec![PlayerAction::UpdateGameMode((gamemode as i32).into())],
}],
))
.await;
self.client
.send_packet(&CGameEvent::new(
GameEvent::ChangeGameMode,
gamemode.to_f32().unwrap(),
))
.await;
}
pub async fn send_system_message<'a>(&self, text: &TextComponent<'a>) {
self.client
.send_packet(&CSystemChatMessage::new(text, false))
.await;
}
}
impl Player {
pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) {
let mut packets = self.client.client_packets_queue.lock().await;
while let Some(mut packet) = packets.pop_back() {
match self.handle_play_packet(server, &mut packet).await {
Ok(_) => {}
Err(e) => {
if e.is_kick() {
if let Some(kick_reason) = e.client_kick_reason() {
self.kick(TextComponent::text(&kick_reason)).await
} else {
self.kick(TextComponent::text(&format!(
"Error while reading incoming packet {}",
e
)))
.await;
}
}
e.log();
}
};
}
}
pub async fn handle_play_packet(
self: &Arc<Self>,
server: &Arc<Server>,
packet: &mut RawPacket,
) -> Result<(), Box<dyn PumpkinError>> {
let bytebuf = &mut packet.bytebuf;
match packet.id.0 {
SConfirmTeleport::PACKET_ID => {
self.handle_confirm_teleport(SConfirmTeleport::read(bytebuf)?)
.await;
Ok(())
}
SChatCommand::PACKET_ID => {
self.handle_chat_command(server, SChatCommand::read(bytebuf)?)
.await;
Ok(())
}
SPlayerPosition::PACKET_ID => {
self.handle_position(SPlayerPosition::read(bytebuf)?).await;
Ok(())
}
SPlayerPositionRotation::PACKET_ID => {
self.handle_position_rotation(SPlayerPositionRotation::read(bytebuf)?)
.await;
Ok(())
}
SPlayerRotation::PACKET_ID => {
self.handle_rotation(SPlayerRotation::read(bytebuf)?).await;
Ok(())
}
SSetPlayerGround::PACKET_ID => {
self.handle_player_ground(SSetPlayerGround::read(bytebuf)?);
Ok(())
}
SPlayerCommand::PACKET_ID => {
self.handle_player_command(SPlayerCommand::read(bytebuf)?)
.await;
Ok(())
}
SSwingArm::PACKET_ID => {
self.handle_swing_arm(SSwingArm::read(bytebuf)?).await;
Ok(())
}
SChatMessage::PACKET_ID => {
self.handle_chat_message(SChatMessage::read(bytebuf)?).await;
Ok(())
}
SClientInformationPlay::PACKET_ID => {
self.handle_client_information_play(SClientInformationPlay::read(bytebuf)?)
.await;
Ok(())
}
SInteract::PACKET_ID => {
self.handle_interact(server, SInteract::read(bytebuf)?)
.await;
Ok(())
}
SPlayerAction::PACKET_ID => {
self.handle_player_action(SPlayerAction::read(bytebuf)?)
.await;
Ok(())
}
SPlayerAbilities::PACKET_ID => {
self.handle_player_abilities(SPlayerAbilities::read(bytebuf)?)
.await;
Ok(())
}
SUseItemOn::PACKET_ID => {
self.handle_use_item_on(SUseItemOn::read(bytebuf)?).await;
Ok(())
}
SUseItem::PACKET_ID => {
self.handle_use_item(SUseItem::read(bytebuf)?);
Ok(())
}
SSetHeldItem::PACKET_ID => {
self.handle_set_held_item(SSetHeldItem::read(bytebuf)?)
.await;
Ok(())
}
SSetCreativeSlot::PACKET_ID => {
self.handle_set_creative_slot(SSetCreativeSlot::read(bytebuf)?)
.await?;
Ok(())
}
SPlayPingRequest::PACKET_ID => {
self.handle_play_ping_request(SPlayPingRequest::read(bytebuf)?)
.await;
Ok(())
}
SClickContainer::PACKET_ID => {
self.handle_click_container(server, SClickContainer::read(bytebuf)?)
.await?;
Ok(())
}
SCloseContainer::PACKET_ID => {
self.handle_close_container(server, SCloseContainer::read(bytebuf)?)
.await;
Ok(())
}
SKeepAlive::PACKET_ID => {
self.client
.keep_alive_sender
.send(SKeepAlive::read(bytebuf)?.keep_alive_id)
.await
.unwrap();
Ok(())
}
_ => {
log::error!("Failed to handle player packet id {:#04x}", packet.id.0);
Ok(())
}
}
}
}
/// Represents a player's abilities and special powers.
///
/// This struct contains information about the player's current abilities, such as flight, invulnerability, and creative mode.
pub struct PlayerAbilities {
/// Indicates whether the player is invulnerable to damage.
pub invulnerable: bool,
/// Indicates whether the player is currently flying.
pub flying: bool,
/// Indicates whether the player is allowed to fly (if enabled).
pub allow_flying: bool,
/// Indicates whether the player is in creative mode.
pub creative: bool,
/// The player's flying speed.
pub fly_speed: f32,
/// The field of view adjustment when the player is walking or sprinting.
pub walk_speed_fov: f32,
}
impl Default for PlayerAbilities {
fn default() -> Self {
Self {
invulnerable: false,
flying: false,
allow_flying: false,
creative: false,
fly_speed: 0.5,
walk_speed_fov: 0.1,
}
}
}
/// Represents the player's dominant hand.
#[derive(FromPrimitive, Clone)]
pub enum Hand {
/// The player's primary hand (usually the right hand).
Main,
/// The player's off-hand (usually the left hand).
Off,
}
/// Represents the player's chat mode settings.
#[derive(FromPrimitive, Clone)]
pub enum ChatMode {
/// Chat is enabled for the player.
Enabled,
/// The player should only see chat messages from commands
CommandsOnly,
/// All messages should be hidden
Hidden,
}