diff --git a/Cargo.lock b/Cargo.lock index aeea0fb1..b9a60580 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,7 @@ dependencies = [ "azalea-world", "owning_ref", "tokio", + "uuid", ] [[package]] @@ -219,6 +220,7 @@ dependencies = [ "azalea-nbt", "log", "nohash-hasher", + "uuid", ] [[package]] diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index a7a11b53..fe306186 100755 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,4 +6,4 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -uuid = "^1.1.2" +uuid = "1.1.2" diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 46ea8039..b39d6a49 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -14,3 +14,4 @@ azalea-protocol = {path = "../azalea-protocol"} azalea-world = {path = "../azalea-world"} owning_ref = "0.4.1" tokio = {version = "1.19.2", features = ["sync"]} +uuid = "1.1.2" diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 0bcad630..56f4918a 100644 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -1,8 +1,8 @@ +//! Connect to Minecraft servers. + use crate::Client; use azalea_protocol::ServerAddress; -///! Connect to Minecraft servers. - /// Something that can join Minecraft servers. pub struct Account { pub username: String, diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 6efe521b..943e0f9f 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,5 +1,6 @@ use crate::{Account, Player}; -use azalea_core::{ChunkPos, EntityPos, ResourceLocation}; +use azalea_auth::game_profile::GameProfile; +use azalea_core::{ChunkPos, EntityPos, PositionDelta, PositionDeltaTrait, ResourceLocation}; use azalea_entity::Entity; use azalea_protocol::{ connect::{GameConnection, HandshakeConnection}, @@ -63,6 +64,7 @@ pub enum ChatPacket { /// A player that you can control that is currently in a Minecraft server. pub struct Client { event_receiver: UnboundedReceiver, + game_profile: GameProfile, pub conn: Arc>, pub state: Arc>, // game_loop @@ -104,7 +106,7 @@ impl Client { ) .await; - let conn = loop { + let (conn, game_profile) = loop { let packet_result = conn.read().await; match packet_result { Ok(packet) => match packet { @@ -132,7 +134,7 @@ impl Client { } LoginPacket::ClientboundGameProfilePacket(p) => { println!("Got profile {:?}", p.game_profile); - break conn.game(); + break (conn.game(), p.game_profile); } LoginPacket::ClientboundLoginDisconnectPacket(p) => { println!("Got disconnect {:?}", p); @@ -154,6 +156,7 @@ impl Client { // we got the GameConnection, so the server is now connected :) let client = Client { + game_profile: game_profile.clone(), event_receiver: rx, conn: conn.clone(), state: Arc::new(Mutex::new(ClientState::default())), @@ -167,6 +170,7 @@ impl Client { conn.clone(), tx.clone(), game_loop_state.clone(), + game_profile.clone(), )); tokio::spawn(Self::game_tick_loop(conn, tx, game_loop_state)); @@ -177,21 +181,24 @@ impl Client { conn: Arc>, tx: UnboundedSender, state: Arc>, + game_profile: GameProfile, ) { loop { let r = conn.lock().await.read().await; match r { - Ok(packet) => match Self::handle(&packet, &tx, &state, &conn).await { - Ok(_) => {} - Err(e) => { - println!("Error handling packet: {:?}", e); - if IGNORE_ERRORS { - continue; - } else { - panic!("Error handling packet: {:?}", e); + Ok(packet) => { + match Self::handle(&packet, &tx, &state, &conn, &game_profile).await { + Ok(_) => {} + Err(e) => { + println!("Error handling packet: {:?}", e); + if IGNORE_ERRORS { + continue; + } else { + panic!("Error handling packet: {:?}", e); + } } } - }, + } Err(e) => { if IGNORE_ERRORS { println!("Error: {:?}", e); @@ -211,13 +218,14 @@ impl Client { tx: &UnboundedSender, state: &Arc>, conn: &Arc>, + game_profile: &GameProfile, ) -> Result<(), HandleError> { match packet { GamePacket::ClientboundLoginPacket(p) => { println!("Got login packet {:?}", p); { - let mut state = state.lock()?; + let mut state_lock = state.lock()?; // // write p into login.txt // std::io::Write::write_all( @@ -226,8 +234,6 @@ impl Client { // ) // .unwrap(); - state.player.entity.id = p.player_id; - // TODO: have registry_holder be a struct because this sucks rn // best way would be to add serde support to azalea-nbt @@ -281,7 +287,18 @@ impl Client { .as_int() .expect("min_y tag is not an int"); - state.world = Some(World::new(16, height, min_y)); + // the 16 here is our render distance + // i'll make this an actual setting later + state_lock.world = Some(World::new(16, height, min_y)); + + let entity = Entity::new(p.player_id, game_profile.uuid, EntityPos::default()); + state_lock + .world + .as_mut() + .expect("World doesn't exist! We should've gotten a login packet by now.") + .add_entity(entity); + + state_lock.player.set_entity_id(p.player_id); } conn.lock() @@ -334,6 +351,99 @@ impl Client { GamePacket::ClientboundPlayerPositionPacket(p) => { // TODO: reply with teleport confirm println!("Got player position packet {:?}", p); + + let mut state_lock = state.lock()?; + let player_entity_id = state_lock.player.entity_id; + let world = state_lock.world.as_mut().unwrap(); + let player_entity = world + .mut_entity_by_id(player_entity_id) + .expect("Player entity doesn't exist"); + let delta_movement = &player_entity.delta; + + let is_x_relative = p.relative_arguments.x; + let is_y_relative = p.relative_arguments.y; + let is_z_relative = p.relative_arguments.z; + + let (delta_x, new_pos_x) = if is_x_relative { + player_entity.old_pos.x += p.x; + (delta_movement.x(), player_entity.pos().x + p.x) + } else { + player_entity.old_pos.x = p.x; + (0.0, p.x) + }; + let (delta_y, new_pos_y) = if is_y_relative { + player_entity.old_pos.y += p.y; + (delta_movement.y(), player_entity.pos().y + p.y) + } else { + player_entity.old_pos.y = p.y; + (0.0, p.y) + }; + let (delta_z, new_pos_z) = if is_z_relative { + player_entity.old_pos.z += p.z; + (delta_movement.z(), player_entity.pos().z + p.z) + } else { + player_entity.old_pos.z = p.z; + (0.0, p.z) + }; + + let mut y_rot = p.y_rot; + let mut x_rot = p.x_rot; + if p.relative_arguments.x_rot { + y_rot += player_entity.x_rot; + } + if p.relative_arguments.y_rot { + x_rot += player_entity.y_rot; + } + + player_entity.delta = PositionDelta { + xa: delta_x, + ya: delta_y, + za: delta_z, + }; + player_entity.set_rotation(x_rot, y_rot); + // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means + // so investigate that ig + world + .move_entity( + player_entity_id, + EntityPos { + x: new_pos_x, + y: new_pos_y, + z: new_pos_z, + }, + ) + .expect("The player entity should always exist"); + + let mut state_lock = state.lock()?; + + let player = &state_lock.player; + let player_entity_id = player.entity_id; + + let world = state_lock.world.as_mut().unwrap(); + world.move_entity( + player_entity_id, + EntityPos { + x: p.x, + y: p.y, + z: p.z, + }, + )?; + + conn.lock() + .await + .write(ServerboundAcceptTeleportationPacket {}.get()) + .await; + conn.lock() + .await + .write( + ServerboundMovePlayerPacketPosRot { + identifier: ResourceLocation::new("brand").unwrap(), + // they don't have to know :) + data: "vanilla".into(), + } + .get(), + ) + .await; } GamePacket::ClientboundPlayerInfoPacket(p) => { println!("Got player info packet {:?}", p); @@ -534,13 +644,22 @@ impl Client { /// Gets the `World` the client is in. /// - /// This is basically a shortcut for `let world = client.state.lock().unwrap().world.as_ref().unwrap()`. + /// This is basically a shortcut for `client.state.lock().unwrap().world.as_ref().unwrap()`. /// If the client hasn't received a login packet yet, this will panic. pub fn world(&self) -> OwningRef, World> { let state_lock: std::sync::MutexGuard = self.state.lock().unwrap(); let state_lock_ref = OwningRef::new(state_lock); state_lock_ref.map(|state| state.world.as_ref().expect("World doesn't exist!")) } + + /// Gets the `Player` struct for our player. + /// + /// This is basically a shortcut for `client.state.lock().unwrap().player`. + pub fn player(&self) -> OwningRef, Player> { + let state_lock: std::sync::MutexGuard = self.state.lock().unwrap(); + let state_lock_ref = OwningRef::new(state_lock); + state_lock_ref.map(|state| &state.player) + } } impl From> for HandleError { diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index c9cd62e9..0402b15b 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -4,15 +4,29 @@ use azalea_protocol::packets::game::serverbound_move_player_packet_pos_rot::Serv impl Client { /// Set the client's position to the given coordinates. - pub async fn move_to(&mut self, pos: &EntityPos) { + pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), String> { + let mut state_lock = self.state.lock().unwrap(); + + let world = state_lock.world.as_ref().unwrap(); + + let player = &state_lock.player; + let player_id = if let Some(player) = player.entity(world) { + player.id + } else { + return Err("Player entity not found".to_string()); + }; + + let world = state_lock.world.as_mut().unwrap(); + world.move_entity(player_id, new_pos)?; + self.conn .lock() .await .write( ServerboundMovePlayerPacketPosRot { - x: pos.x, - y: pos.y, - z: pos.z, + x: new_pos.x, + y: new_pos.y, + z: new_pos.z, x_rot: 0.0, y_rot: 0.0, on_ground: false, @@ -20,5 +34,7 @@ impl Client { .get(), ) .await; + + Ok(()) } } diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index 9a5ba8ab..ee0b9718 100644 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -1,7 +1,27 @@ use azalea_entity::Entity; +use azalea_world::World; +use uuid::Uuid; #[derive(Default, Debug)] pub struct Player { - /// The entity attached to the player. There's some useful fields here. - pub entity: Entity, + /// The player's uuid. + pub uuid: Uuid, + /// The player's entity id. + pub entity_id: u32, +} + +impl Player { + /// Get the entity of the player in the world. + pub fn entity<'a>(&self, world: &'a World) -> Option<&'a Entity> { + // world.entity_by_uuid(&self.uuid) + world.entity_by_id(self.entity_id) + } + + 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/delta.rs b/azalea-core/src/delta.rs index 41923ffb..c0056411 100644 --- a/azalea-core/src/delta.rs +++ b/azalea-core/src/delta.rs @@ -1,15 +1,41 @@ use crate::EntityPos; pub use azalea_buf::McBuf; -/// Only works for up to 8 blocks -#[derive(Clone, Debug, McBuf)] -pub struct PositionDelta { - xa: i16, - ya: i16, - za: i16, +pub trait PositionDeltaTrait { + fn x(&self) -> f64; + fn y(&self) -> f64; + fn z(&self) -> f64; } -impl PositionDelta { +#[derive(Clone, Debug, McBuf, Default)] +pub struct PositionDelta { + pub xa: f64, + pub ya: f64, + pub za: f64, +} + +/// Only works for up to 8 blocks +#[derive(Clone, Debug, McBuf, Default)] +pub struct PositionDelta8 { + pub xa: i16, + pub ya: i16, + pub za: i16, +} + +impl PositionDeltaTrait for PositionDelta { + fn x(&self) -> f64 { + self.xa + } + fn y(&self) -> f64 { + self.ya + } + fn z(&self) -> f64 { + self.za + } +} + +impl PositionDelta8 { + #[deprecated] pub fn float(&self) -> (f64, f64, f64) { ( (self.xa as f64) / 4096.0, @@ -19,13 +45,24 @@ impl PositionDelta { } } +impl PositionDeltaTrait for PositionDelta8 { + fn x(&self) -> f64 { + (self.xa as f64) / 4096.0 + } + fn y(&self) -> f64 { + (self.ya as f64) / 4096.0 + } + fn z(&self) -> f64 { + (self.za as f64) / 4096.0 + } +} + impl EntityPos { - pub fn with_delta(&self, delta: &PositionDelta) -> EntityPos { - let (x, y, z) = delta.float(); + pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> EntityPos { EntityPos { - x: self.x + x, - y: self.y + y, - z: self.z + z, + x: self.x + delta.x(), + y: self.y + delta.y(), + z: self.z + delta.z(), } } } diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 8caa5799..de8e2516 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -5,6 +5,12 @@ use std::{ ops::Rem, }; +pub trait PositionXYZ { + fn add_x(&self, n: T) -> Self; + fn add_y(&self, n: T) -> Self; + fn add_z(&self, n: T) -> Self; +} + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct BlockPos { pub x: i32, @@ -30,6 +36,30 @@ impl Rem for BlockPos { } } +impl PositionXYZ for BlockPos { + fn add_x(&self, n: i32) -> Self { + BlockPos { + x: self.x + n, + y: self.y, + z: self.z, + } + } + fn add_y(&self, n: i32) -> Self { + BlockPos { + x: self.x, + y: self.y + n, + z: self.z, + } + } + fn add_z(&self, n: i32) -> Self { + BlockPos { + x: self.x, + y: self.y, + z: self.z + n, + } + } +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct ChunkPos { pub x: i32, @@ -42,15 +72,6 @@ impl ChunkPos { } } -impl From<&BlockPos> for ChunkPos { - fn from(pos: &BlockPos) -> Self { - ChunkPos { - x: pos.x.div_floor(16), - z: pos.z.div_floor(16), - } - } -} - /// The coordinates of a chunk section in the world. #[derive(Clone, Copy, Debug, Default)] pub struct ChunkSectionPos { @@ -64,6 +85,83 @@ impl ChunkSectionPos { ChunkSectionPos { x, y, z } } } +/// The coordinates of a block inside a chunk. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct ChunkBlockPos { + pub x: u8, + pub y: i32, + pub z: u8, +} + +impl ChunkBlockPos { + pub fn new(x: u8, y: i32, z: u8) -> Self { + ChunkBlockPos { x, y, z } + } +} +/// The coordinates of a block inside a chunk section. +#[derive(Clone, Copy, Debug, Default)] +pub struct ChunkSectionBlockPos { + /// A number between 0 and 16. + pub x: u8, + /// A number between 0 and 16. + pub y: u8, + /// A number between 0 and 16. + pub z: u8, +} + +impl ChunkSectionBlockPos { + pub fn new(x: u8, y: u8, z: u8) -> Self { + ChunkSectionBlockPos { x, y, z } + } +} + +/// A block pos with an attached dimension +#[derive(Debug, Clone)] +pub struct GlobalPos { + pub pos: BlockPos, + // this is actually a ResourceKey in Minecraft, but i don't think it matters? + pub dimension: ResourceLocation, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct EntityPos { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl PositionXYZ for EntityPos { + fn add_x(&self, n: f64) -> Self { + EntityPos { + x: self.x + n, + y: self.y, + z: self.z, + } + } + fn add_y(&self, n: f64) -> Self { + EntityPos { + x: self.x, + y: self.y + n, + z: self.z, + } + } + fn add_z(&self, n: f64) -> Self { + EntityPos { + x: self.x, + y: self.y, + z: self.z + n, + } + } +} + +impl From<&BlockPos> for ChunkPos { + fn from(pos: &BlockPos) -> Self { + ChunkPos { + x: pos.x.div_floor(16), + z: pos.z.div_floor(16), + } + } +} impl From for ChunkSectionPos { fn from(pos: BlockPos) -> Self { @@ -81,20 +179,6 @@ impl From for ChunkPos { } } -/// The coordinates of a block inside a chunk. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct ChunkBlockPos { - pub x: u8, - pub y: i32, - pub z: u8, -} - -impl ChunkBlockPos { - pub fn new(x: u8, y: i32, z: u8) -> Self { - ChunkBlockPos { x, y, z } - } -} - impl From<&BlockPos> for ChunkBlockPos { fn from(pos: &BlockPos) -> Self { ChunkBlockPos { @@ -105,23 +189,6 @@ impl From<&BlockPos> for ChunkBlockPos { } } -/// The coordinates of a block inside a chunk section. -#[derive(Clone, Copy, Debug, Default)] -pub struct ChunkSectionBlockPos { - /// A number between 0 and 16. - pub x: u8, - /// A number between 0 and 16. - pub y: u8, - /// A number between 0 and 16. - pub z: u8, -} - -impl ChunkSectionBlockPos { - pub fn new(x: u8, y: u8, z: u8) -> Self { - ChunkSectionBlockPos { x, y, z } - } -} - impl From<&BlockPos> for ChunkSectionBlockPos { fn from(pos: &BlockPos) -> Self { ChunkSectionBlockPos { @@ -141,22 +208,6 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos { } } } - -/// A block pos with an attached dimension -#[derive(Debug, Clone)] -pub struct GlobalPos { - pub pos: BlockPos, - // this is actually a ResourceKey in Minecraft, but i don't think it matters? - pub dimension: ResourceLocation, -} - -#[derive(Debug, Clone, Default)] -pub struct EntityPos { - pub x: f64, - pub y: f64, - pub z: f64, -} - impl From<&EntityPos> for BlockPos { fn from(pos: &EntityPos) -> Self { BlockPos { diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 065413a5..63c717d3 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -1,11 +1,6 @@ mod data; -use azalea_core::EntityPos; -#[cfg(feature = "protocol")] -use azalea_protocol::packets::game::{ - clientbound_add_entity_packet::ClientboundAddEntityPacket, - clientbound_add_player_packet::ClientboundAddPlayerPacket, -}; +use azalea_core::{EntityPos, PositionDelta}; pub use data::*; use uuid::Uuid; @@ -14,12 +9,27 @@ pub struct Entity { /// The incrementing numerical id of the entity. pub id: u32, pub uuid: Uuid, + /// The position of the entity right now. pos: EntityPos, + /// The position of the entity last tick. + pub old_pos: EntityPos, + pub delta: PositionDelta, + + pub x_rot: f32, + pub y_rot: f32, } impl Entity { pub fn new(id: u32, uuid: Uuid, pos: EntityPos) -> Self { - Self { id, uuid, pos } + Self { + id, + uuid, + pos, + old_pos: pos, + delta: PositionDelta::default(), + x_rot: 0.0, + y_rot: 0.0, + } } pub fn pos(&self) -> &EntityPos { @@ -31,6 +41,12 @@ impl Entity { pub fn unsafe_move(&mut self, new_pos: EntityPos) { self.pos = new_pos; } + + pub fn set_rotation(&mut self, x_rot: f32, y_rot: f32) { + self.x_rot = x_rot % 360.0; + self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0; + // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so + } } // #[cfg(test)] diff --git a/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs b/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs index 63428dd8..cd3e3148 100644 --- a/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs @@ -1,11 +1,11 @@ use azalea_buf::McBuf; -use azalea_core::PositionDelta; +use azalea_core::PositionDelta8; use packet_macros::GamePacket; #[derive(Clone, Debug, McBuf, GamePacket)] pub struct ClientboundMoveEntityPosPacket { #[var] pub entity_id: u32, - pub delta: PositionDelta, + pub delta: PositionDelta8, pub on_ground: bool, } diff --git a/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs b/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs index dd1d96e1..e3422ac0 100644 --- a/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_core::PositionDelta; +use azalea_core::PositionDelta8; use packet_macros::GamePacket; /// This packet is sent by the server when an entity moves less then 8 blocks. @@ -7,7 +7,7 @@ use packet_macros::GamePacket; pub struct ClientboundMoveEntityPosRotPacket { #[var] pub entity_id: u32, - pub delta: PositionDelta, + pub delta: PositionDelta8, pub y_rot: i8, pub x_rot: i8, pub on_ground: bool, diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 02f05b46..ca890d82 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -13,6 +13,7 @@ azalea-entity = {path = "../azalea-entity"} azalea-nbt = {path = "../azalea-nbt"} log = "0.4.17" nohash-hasher = "0.2.0" +uuid = "1.1.2" [profile.release] lto = true diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index e4e9864f..5219e410 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -3,12 +3,13 @@ use azalea_entity::Entity; use log::warn; use nohash_hasher::{IntMap, IntSet}; use std::collections::HashMap; +use uuid::Uuid; #[derive(Debug)] pub struct EntityStorage { by_id: IntMap, - // TODO: this doesn't work yet (should be updated in the set_pos method in azalea-entity) by_chunk: HashMap>, + by_uuid: HashMap, } impl EntityStorage { @@ -16,6 +17,7 @@ impl EntityStorage { Self { by_id: IntMap::default(), by_chunk: HashMap::default(), + by_uuid: HashMap::default(), } } @@ -26,6 +28,7 @@ impl EntityStorage { .entry(ChunkPos::from(entity.pos())) .or_default() .insert(entity.id); + self.by_uuid.insert(entity.uuid, entity.id); self.by_id.insert(entity.id, entity); } @@ -34,9 +37,13 @@ impl EntityStorage { pub fn remove_by_id(&mut self, id: u32) { if let Some(entity) = self.by_id.remove(&id) { let entity_chunk = ChunkPos::from(entity.pos()); + let entity_uuid = entity.uuid; if self.by_chunk.remove(&entity_chunk).is_none() { warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found."); } + if self.by_uuid.remove(&entity_uuid).is_none() { + warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found."); + } } else { warn!("Tried to remove entity with id {id} but it was not found.") } @@ -54,11 +61,27 @@ impl EntityStorage { self.by_id.get_mut(&id) } + /// Get a reference to an entity by its uuid. + #[inline] + pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { + self.by_uuid.get(uuid).and_then(|id| self.by_id.get(id)) + } + + /// Get a mutable reference to an entity by its uuid. + #[inline] + pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Entity> { + self.by_uuid.get(uuid).and_then(|id| self.by_id.get_mut(id)) + } + /// Clear all entities in a chunk. pub fn clear_chunk(&mut self, chunk: &ChunkPos) { if let Some(entities) = self.by_chunk.remove(chunk) { for entity_id in entities { - self.by_id.remove(&entity_id); + if let Some(entity) = self.by_id.remove(&entity_id) { + self.by_uuid.remove(&entity.uuid); + } else { + warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}."); + } } } } diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index bc73c13d..3afa4fee 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -6,7 +6,7 @@ mod entity; mod palette; use azalea_block::BlockState; -use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta}; +use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8}; use azalea_entity::Entity; pub use bit_storage::BitStorage; pub use chunk::{Chunk, ChunkStorage}; @@ -16,6 +16,7 @@ use std::{ ops::{Index, IndexMut}, sync::{Arc, Mutex}, }; +use uuid::Uuid; #[cfg(test)] mod tests { @@ -76,7 +77,7 @@ impl World { pub fn move_entity_with_delta( &mut self, entity_id: u32, - delta: &PositionDelta, + delta: &PositionDelta8, ) -> Result<(), String> { let entity = self .entity_storage @@ -112,6 +113,14 @@ impl World { self.entity_storage.get_by_id(id) } + pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> { + self.entity_storage.get_mut_by_id(id) + } + + pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { + self.entity_storage.get_by_uuid(uuid) + } + /// Get an iterator over all entities. #[inline] pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { diff --git a/bot/src/main.rs b/bot/src/main.rs index 02f802a5..2976920b 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,12 +1,12 @@ use azalea_client::{Account, Event}; -use azalea_core::BlockPos; +use azalea_core::PositionXYZ; #[tokio::main] async fn main() -> Result<(), Box> { println!("Hello, world!"); // let address = "95.111.249.143:10000"; - let address = "localhost:49982"; + let address = "localhost:65399"; // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // .await // .unwrap(); @@ -20,23 +20,37 @@ async fn main() -> Result<(), Box> { match e { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} - Event::GameTick => { - let world = client.world(); - if let Some(b) = world.find_one_entity(|e| { - e.uuid == uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd") - }) { - // let world = state.world.as_ref().unwrap(); - // world. - println!("{:?}", b); - } - // world.get_block_state(state.player.entity.pos); - // println!("{}", p.message.to_ansi(None)); - // if p.message.to_ansi(None) == " ok" { - // let state = client.state.lock(); - // let world = state.world.as_ref().unwrap(); - // let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap(); - // println!("block state: {:?}", c); - // } + // Event::GameTick => { + // let world = client.world(); + // if let Some(b) = world.find_one_entity(|e| { + // e.uuid == uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd") + // }) { + // // let world = state.world.as_ref().unwrap(); + // // world. + // println!("{:?}", b); + // } + // // world.get_block_state(state.player.entity.pos); + // // println!("{}", p.message.to_ansi(None)); + // // if p.message.to_ansi(None) == " ok" { + // // let state = client.state.lock(); + // // let world = state.world.as_ref().unwrap(); + // // let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap(); + // // println!("block state: {:?}", c); + // // } + // } + Event::Chat(msg) => { + let new_pos = { + let state_lock = client.state.lock().unwrap(); + let world = state_lock.world.as_ref().unwrap(); + let player = &state_lock.player; + let entity = player + .entity(&world) + .expect("Player entity is not in world"); + entity.pos().add_y(0.5) + }; + + println!("{:?}", new_pos); + client.move_to(new_pos).await.unwrap(); } _ => {} }