1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

Player List (#41)

* keep track of player list

* send player update events
This commit is contained in:
mat 2022-11-18 22:11:30 -06:00 committed by GitHub
parent 6c6eb5572b
commit befa22c1f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 90 deletions

View file

@ -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).

View file

@ -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)]

View file

@ -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: {} {:?}",

View file

@ -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;
}
}

View file

@ -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<GameType> {
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}"),
}
}

View file

@ -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 {

View file

@ -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<RemovePlayer>),
}
#[derive(Clone, Debug, McBuf)]
pub struct PlayerProperty {
pub name: String,
pub value: String,
pub signature: Option<String>,
}
#[derive(Clone, Debug, McBuf)]
pub struct AddPlayer {
pub uuid: Uuid,
pub name: String,
pub properties: Vec<PlayerProperty>,
pub properties: HashMap<String, ProfilePropertyValue>,
pub gamemode: GameType,
#[var]
pub gamemode: u32,
#[var]
pub ping: i32,
pub latency: i32,
pub display_name: Option<Component>,
pub profile_public_key: Option<ProfilePublicKeyData>,
}
@ -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)]

View file

@ -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> {