From befa22c1f3ff0c1f0cb745b3f4e736910c053a8b Mon Sep 17 00:00:00 2001 From: mat <27899617+mat-1@users.noreply.github.com> Date: Fri, 18 Nov 2022 22:11:30 -0600 Subject: [PATCH] Player List (#41) * keep track of player list * send player update events --- azalea-client/src/client.rs | 168 +++++++++++++++--- azalea-client/src/lib.rs | 2 +- azalea-client/src/movement.rs | 10 +- azalea-client/src/player.rs | 36 ++-- azalea-core/src/game_type.rs | 48 ++--- azalea-physics/src/collision/mod.rs | 3 +- .../game/clientbound_player_info_packet.rs | 22 +-- azalea-world/src/lib.rs | 4 + 8 files changed, 203 insertions(+), 90 deletions(-) diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index e271065c..c2d5b745 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,7 +1,8 @@ pub use crate::chat::ChatPacket; -use crate::{movement::WalkDirection, plugins::Plugins, Account, Player}; +use crate::{movement::WalkDirection, plugins::Plugins, Account, PlayerInfo}; use azalea_auth::game_profile::GameProfile; -use azalea_core::{ChunkPos, ResourceLocation, Vec3}; +use azalea_chat::Component; +use azalea_core::{ChunkPos, GameType, ResourceLocation, Vec3}; use azalea_protocol::{ connect::{Connection, ConnectionError, ReadConnection, WriteConnection}, packets::{ @@ -32,6 +33,7 @@ use azalea_world::{ use log::{debug, error, info, warn}; use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ + collections::HashMap, fmt::Debug, io::{self, Cursor}, sync::Arc, @@ -42,6 +44,7 @@ use tokio::{ task::JoinHandle, time::{self}, }; +use uuid::Uuid; pub type ClientInformation = ServerboundClientInformationPacket; @@ -59,15 +62,43 @@ pub enum Event { /// Happens 20 times per second, but only when the world is loaded. Tick, Packet(Box), + /// Happens when a player is added, removed, or updated in the tab list. + UpdatePlayers(UpdatePlayersEvent), +} + +/// Happens when a player is added, removed, or updated in the tab list. +#[derive(Debug, Clone)] +pub enum UpdatePlayersEvent { + /// A player with the given info was added to the tab list (usually means + /// they joined the server). + Add(PlayerInfo), + /// A player with the given UUID was removed from the tab list (usually + /// means they left the server) + Remove { uuid: Uuid }, + /// The latency of the player with the given UUID was updated in the tab + /// list. Note that this can be spoofed by the player and may not represent + /// their actual latency. + Latency { + uuid: Uuid, + /// The time it took in milliseconds for this player to reply to the ping packet. + latency: i32, + }, + /// The played switched to a different gamemode (i.e. survival, creative, spectator) + GameMode { uuid: Uuid, game_mode: GameType }, + /// The name of the player with the given UUID in the tab list was changed or reset. + DisplayName { + uuid: Uuid, + display_name: Option, + }, } /// A player that you control that is currently in a Minecraft server. #[derive(Clone)] pub struct Client { - pub game_profile: GameProfile, + pub profile: GameProfile, pub read_conn: Arc>>, pub write_conn: Arc>>, - pub player: Arc>, + pub entity_id: Arc>, pub world: Arc>, pub physics_state: Arc>, pub client_information: Arc>, @@ -75,6 +106,8 @@ pub struct Client { /// client and keep state. If you're not making a plugin and you're using /// the `azalea` crate. you can ignore this field. pub plugins: Arc, + /// A map of player uuids to their information in the tab list + pub players: Arc>>, tasks: Arc>>>, } @@ -175,7 +208,7 @@ impl Client { ) .await?; - let (conn, game_profile) = loop { + let (conn, profile) = loop { let packet = conn.read().await?; match packet { ClientboundLoginPacket::Hello(p) => { @@ -239,16 +272,18 @@ impl Client { // we got the GameConnection, so the server is now connected :) let client = Client { - game_profile, + profile, read_conn, write_conn, - player: Arc::new(RwLock::new(Player::default())), + // default our id to 0, it'll be set later + entity_id: Arc::new(RwLock::new(0)), world: Arc::new(RwLock::new(World::default())), physics_state: Arc::new(Mutex::new(PhysicsState::default())), client_information: Arc::new(RwLock::new(ClientInformation::default())), // The plugins can be modified by the user by replacing the plugins // field right after this. No Mutex so the user doesn't need to .lock(). plugins: Arc::new(Plugins::new()), + players: Arc::new(RwLock::new(HashMap::new())), tasks: Arc::new(Mutex::new(Vec::new())), }; @@ -401,15 +436,13 @@ impl Client { *world_lock = World::new(16, height, min_y); let entity = EntityData::new( - client.game_profile.uuid, + client.profile.uuid, Vec3::default(), EntityMetadata::Player(metadata::Player::default()), ); world_lock.add_entity(p.player_id, entity); - let mut player_lock = client.player.write(); - - player_lock.set_entity_id(p.player_id); + *client.entity_id.write() = p.player_id; } // send the client information that we have set @@ -473,10 +506,7 @@ impl Client { debug!("Got player position packet {:?}", p); let (new_pos, y_rot, x_rot) = { - let player_entity_id = { - let player_lock = client.player.write(); - player_lock.entity_id - }; + let player_entity_id = *client.entity_id.read(); let mut world_lock = client.world.write(); @@ -560,7 +590,97 @@ impl Client { .await?; } ClientboundGamePacket::PlayerInfo(p) => { + use azalea_protocol::packets::game::clientbound_player_info_packet::Action; + debug!("Got player info packet {:?}", p); + let mut players_lock = client.players.write(); + match &p.action { + Action::AddPlayer(players) => { + for player in players { + let player_info = PlayerInfo { + profile: GameProfile { + uuid: player.uuid, + name: player.name.clone(), + properties: player.properties.clone(), + }, + uuid: player.uuid, + gamemode: player.gamemode, + latency: player.latency, + display_name: player.display_name.clone(), + }; + players_lock.insert(player.uuid, player_info.clone()); + tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Add(player_info))) + .unwrap(); + } + } + Action::UpdateGameMode(players) => { + for player in players { + if let Some(p) = players_lock.get_mut(&player.uuid) { + p.gamemode = player.gamemode; + tx.send(Event::UpdatePlayers(UpdatePlayersEvent::GameMode { + uuid: player.uuid, + game_mode: player.gamemode, + })) + .unwrap(); + } else { + warn!( + "Ignoring PlayerInfo (UpdateGameMode) for unknown player {}", + player.uuid + ); + } + } + } + Action::UpdateLatency(players) => { + for player in players { + if let Some(p) = players_lock.get_mut(&player.uuid) { + p.latency = player.latency; + tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Latency { + uuid: player.uuid, + latency: player.latency, + })) + .unwrap(); + } else { + warn!( + "Ignoring PlayerInfo (UpdateLatency) for unknown player {}", + player.uuid + ); + } + } + } + Action::UpdateDisplayName(players) => { + for player in players { + if let Some(p) = players_lock.get_mut(&player.uuid) { + p.display_name = player.display_name.clone(); + tx.send(Event::UpdatePlayers(UpdatePlayersEvent::DisplayName { + uuid: player.uuid, + display_name: player.display_name.clone(), + })) + .unwrap(); + } else { + warn!( + "Ignoring PlayerInfo (UpdateDisplayName) for unknown player {}", + player.uuid + ); + } + } + } + Action::RemovePlayer(players) => { + for player in players { + if players_lock.remove(&player.uuid).is_some() { + tx.send(Event::UpdatePlayers(UpdatePlayersEvent::Remove { + uuid: player.uuid, + })) + .unwrap(); + } else { + warn!( + "Ignoring PlayerInfo (RemovePlayer) for unknown player {}", + player.uuid + ); + } + } + } + } + // TODO } ClientboundGamePacket::SetChunkCacheCenter(p) => { debug!("Got chunk cache center packet {:?}", p); @@ -801,8 +921,8 @@ impl Client { // return if there's no chunk at the player's position { let world_lock = client.world.write(); - let player_lock = client.player.write(); - let player_entity = player_lock.entity(&world_lock); + let player_entity_id = *client.entity_id.read(); + let player_entity = world_lock.entity(player_entity_id); let player_entity = if let Some(player_entity) = player_entity { player_entity } else { @@ -828,10 +948,7 @@ impl Client { /// Returns the entity associated to the player. pub fn entity_mut(&self) -> Entity> { - let entity_id = { - let player_lock = self.player.write(); - player_lock.entity_id - }; + let entity_id = *self.entity_id.read(); let mut world = self.world.write(); @@ -844,10 +961,7 @@ impl Client { } /// Returns the entity associated to the player. pub fn entity(&self) -> Entity> { - let entity_id = { - let player_lock = self.player.read(); - player_lock.entity_id - }; + let entity_id = *self.entity_id.read(); let world = self.world.read(); @@ -862,8 +976,8 @@ impl Client { /// Returns whether we have a received the login packet yet. pub fn logged_in(&self) -> bool { let world = self.world.read(); - let player = self.player.write(); - player.entity(&world).is_some() + let entity_id = *self.entity_id.read(); + world.entity(entity_id).is_some() } /// Tell the server we changed our game options (i.e. render distance, main hand). diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 544ea0f4..ebcc4477 100755 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -20,7 +20,7 @@ mod plugins; pub use account::Account; pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError}; pub use movement::{SprintDirection, WalkDirection}; -pub use player::Player; +pub use player::PlayerInfo; pub use plugins::{Plugin, Plugins}; #[cfg(test)] diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index d33b4b4a..87ac8d85 100755 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -153,20 +153,20 @@ impl Client { // Set our current position to the provided Vec3, potentially clipping through blocks. pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> { - let player_lock = self.player.write(); + let player_entity_id = *self.entity_id.read(); let mut world_lock = self.world.write(); - world_lock.set_entity_pos(player_lock.entity_id, new_pos)?; + world_lock.set_entity_pos(player_entity_id, new_pos)?; Ok(()) } pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> { let mut world_lock = self.world.write(); - let player = self.player.write(); + let player_entity_id = *self.entity_id.read(); - let mut entity = player - .entity_mut(&mut world_lock) + let mut entity = world_lock + .entity_mut(player_entity_id) .ok_or(MovePlayerError::PlayerNotInWorld)?; log::trace!( "move entity bounding box: {} {:?}", diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index a355831b..5db5c864 100755 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -1,37 +1,39 @@ -use azalea_world::entity::Entity; +use azalea_auth::game_profile::GameProfile; +use azalea_chat::Component; +use azalea_core::GameType; +use azalea_world::entity::EntityData; use azalea_world::World; use uuid::Uuid; -/// Something that has a world associated to it. Usually, this is a `Client`. +/// Something that has a world associated to it. this is usually a `Client`. pub trait WorldHaver { fn world(&self) -> &World; } -/// A player in the world or tab list. -#[derive(Default, Debug)] -pub struct Player { - /// The player's uuid. +/// A player in the tab list. +#[derive(Debug, Clone)] +pub struct PlayerInfo { + pub profile: GameProfile, + /// The player's UUID. pub uuid: Uuid, - /// The player's entity id. - pub entity_id: u32, + pub gamemode: GameType, + pub latency: i32, + /// The player's display name in the tab list. + pub display_name: Option, } -impl Player { +impl PlayerInfo { /// Get a reference to the entity of the player in the world. - pub fn entity<'d>(&'d self, world: &'d World) -> Option> { - world.entity(self.entity_id) + pub fn entity<'d>(&'d self, world: &'d World) -> Option<&EntityData> { + world.entity_by_uuid(&self.uuid) } /// Get a mutable reference to the entity of the player in the world. - pub fn entity_mut<'d>(&'d self, world: &'d mut World) -> Option { - world.entity_mut(self.entity_id) + pub fn entity_mut<'d>(&'d mut self, world: &'d mut World) -> Option<&'d mut EntityData> { + world.entity_mut_by_uuid(&self.uuid) } pub fn set_uuid(&mut self, uuid: Uuid) { self.uuid = uuid; } - - pub fn set_entity_id(&mut self, entity_id: u32) { - self.entity_id = entity_id; - } } diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs index f1f08962..75ee0674 100755 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -3,19 +3,19 @@ use std::io::{Cursor, Write}; #[derive(Hash, Copy, Clone, Debug)] pub enum GameType { - SURVIVAL, - CREATIVE, - ADVENTURE, - SPECTATOR, + Survival, + Creative, + Adventure, + Spectator, } impl GameType { pub fn to_id(&self) -> u8 { match self { - GameType::SURVIVAL => 0, - GameType::CREATIVE => 1, - GameType::ADVENTURE => 2, - GameType::SPECTATOR => 3, + GameType::Survival => 0, + GameType::Creative => 1, + GameType::Adventure => 2, + GameType::Spectator => 3, } } @@ -29,10 +29,10 @@ impl GameType { pub fn from_id(id: u8) -> Option { Some(match id { - 0 => GameType::SURVIVAL, - 1 => GameType::CREATIVE, - 2 => GameType::ADVENTURE, - 3 => GameType::SPECTATOR, + 0 => GameType::Survival, + 1 => GameType::Creative, + 2 => GameType::Adventure, + 3 => GameType::Spectator, _ => return None, }) } @@ -50,29 +50,29 @@ impl GameType { pub fn short_name(&self) -> &'static str { // TODO: these should be translated TranslatableComponent("selectWorld.gameMode." + string2) match self { - GameType::SURVIVAL => "Survival", - GameType::CREATIVE => "Creative", - GameType::ADVENTURE => "Adventure", - GameType::SPECTATOR => "Spectator", + GameType::Survival => "Survival", + GameType::Creative => "Creative", + GameType::Adventure => "Adventure", + GameType::Spectator => "Spectator", } } pub fn long_name(&self) -> &'static str { // TODO: These should be translated TranslatableComponent("gameMode." + string2); match self { - GameType::SURVIVAL => "Survival Mode", - GameType::CREATIVE => "Creative Mode", - GameType::ADVENTURE => "Adventure Mode", - GameType::SPECTATOR => "Spectator Mode", + GameType::Survival => "Survival Mode", + GameType::Creative => "Creative Mode", + GameType::Adventure => "Adventure Mode", + GameType::Spectator => "Spectator Mode", } } pub fn from_name(name: &str) -> GameType { match name { - "survival" => GameType::SURVIVAL, - "creative" => GameType::CREATIVE, - "adventure" => GameType::ADVENTURE, - "spectator" => GameType::SPECTATOR, + "survival" => GameType::Survival, + "creative" => GameType::Creative, + "adventure" => GameType::Adventure, + "spectator" => GameType::Spectator, _ => panic!("Unknown game type name: {name}"), } } diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index aa585aa8..7fb2cf97 100755 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -4,14 +4,13 @@ mod mergers; mod shape; mod world_collisions; -use std::ops::DerefMut; - use azalea_core::{Axis, Vec3, AABB, EPSILON}; use azalea_world::entity::{Entity, EntityData}; use azalea_world::{MoveEntityError, World}; pub use blocks::BlockWithShape; pub use discrete_voxel_shape::*; pub use shape::*; +use std::ops::DerefMut; use world_collisions::CollisionGetter; pub enum MoverType { diff --git a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs index 84218842..dea3b784 100755 --- a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs @@ -1,8 +1,11 @@ use crate::packets::login::serverbound_hello_packet::ProfilePublicKeyData; +use azalea_auth::game_profile::ProfilePropertyValue; use azalea_buf::{BufReadError, McBuf}; use azalea_buf::{McBufReadable, McBufWritable}; use azalea_chat::Component; +use azalea_core::GameType; use azalea_protocol_macros::ClientboundGamePacket; +use std::collections::HashMap; use std::io::{Cursor, Write}; use uuid::Uuid; @@ -20,22 +23,14 @@ pub enum Action { RemovePlayer(Vec), } -#[derive(Clone, Debug, McBuf)] -pub struct PlayerProperty { - pub name: String, - pub value: String, - pub signature: Option, -} - #[derive(Clone, Debug, McBuf)] pub struct AddPlayer { pub uuid: Uuid, pub name: String, - pub properties: Vec, + pub properties: HashMap, + pub gamemode: GameType, #[var] - pub gamemode: u32, - #[var] - pub ping: i32, + pub latency: i32, pub display_name: Option, pub profile_public_key: Option, } @@ -43,15 +38,14 @@ pub struct AddPlayer { #[derive(Clone, Debug, McBuf)] pub struct UpdateGameMode { pub uuid: Uuid, - #[var] - pub gamemode: u32, + pub gamemode: GameType, } #[derive(Clone, Debug, McBuf)] pub struct UpdateLatency { pub uuid: Uuid, #[var] - pub ping: i32, + pub latency: i32, } #[derive(Clone, Debug, McBuf)] diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index a802f4c3..26cae205 100755 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -142,6 +142,10 @@ impl World { self.entity_storage.get_by_uuid(uuid) } + pub fn entity_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> { + self.entity_storage.get_mut_by_uuid(uuid) + } + /// Get an iterator over all entities. #[inline] pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {