aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-11-18 22:11:30 -0600
committerGitHub <noreply@github.com>2022-11-18 22:11:30 -0600
commitbefa22c1f3ff0c1f0cb745b3f4e736910c053a8b (patch)
tree17037d0d62d1259366d9385c47236774886b4002 /azalea-client/src
parent6c6eb5572b869e3199adc560b101fa663c01b5d2 (diff)
downloadazalea-drasl-befa22c1f3ff0c1f0cb745b3f4e736910c053a8b.tar.xz
Player List (#41)
* keep track of player list * send player update events
Diffstat (limited to 'azalea-client/src')
-rw-r--r--azalea-client/src/client.rs168
-rwxr-xr-xazalea-client/src/lib.rs2
-rwxr-xr-xazalea-client/src/movement.rs10
-rwxr-xr-xazalea-client/src/player.rs36
4 files changed, 166 insertions, 50 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<ClientboundGamePacket>),
+ /// 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<Component>,
+ },
}
/// 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<tokio::sync::Mutex<ReadConnection<ClientboundGamePacket>>>,
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
- pub player: Arc<RwLock<Player>>,
+ pub entity_id: Arc<RwLock<u32>>,
pub world: Arc<RwLock<World>>,
pub physics_state: Arc<Mutex<PhysicsState>>,
pub client_information: Arc<RwLock<ClientInformation>>,
@@ -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<Plugins>,
+ /// A map of player uuids to their information in the tab list
+ pub players: Arc<RwLock<HashMap<Uuid, PlayerInfo>>>,
tasks: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
@@ -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<RwLockWriteGuard<World>> {
- 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<RwLockReadGuard<World>> {
- 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<Component>,
}
-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<Entity<&World>> {
- 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<Entity> {
- 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;
- }
}