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: 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 ++++----- 4 files changed, 166 insertions(+), 50 deletions(-) (limited to 'azalea-client/src') 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; - } } -- cgit v1.2.3