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:
parent
6c6eb5572b
commit
befa22c1f3
8 changed files with 203 additions and 90 deletions
|
@ -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).
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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: {} {:?}",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Reference in a new issue