From 614b21129804930159f041ce3f51202bc3e1c0b6 Mon Sep 17 00:00:00 2001 From: mat Date: Fri, 17 Jun 2022 23:55:11 -0500 Subject: EntityStorage --- azalea-world/src/entity.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 azalea-world/src/entity.rs (limited to 'azalea-world/src/entity.rs') diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs new file mode 100644 index 00000000..a25b1f40 --- /dev/null +++ b/azalea-world/src/entity.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use azalea_core::ChunkPos; +use azalea_entity::Entity; +use nohash_hasher::IntMap; + +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, +} + +impl EntityStorage { + pub fn new() -> Self { + Self { + by_id: IntMap::default(), + by_chunk: HashMap::default(), + } + } + + /// Add an entity to the storage. + #[inline] + pub fn insert(&mut self, entity: Entity) { + self.by_id.insert(entity.id, entity); + } + + /// Remove an entity from the storage by its id. + #[inline] + pub fn remove_by_id(&mut self, id: u32) { + self.by_id.remove(&id); + } + + /// Get a reference to an entity by its id. + #[inline] + pub fn get_by_id(&self, id: u32) -> Option<&Entity> { + self.by_id.get(&id) + } + + /// Get a mutable reference to an entity by its id. + #[inline] + pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> { + self.by_id.get_mut(&id) + } +} -- cgit v1.2.3 From fc3151f89db1cf018bfebebb8f102e20911e64d3 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Jun 2022 16:54:49 -0500 Subject: account.rs and client.rs --- azalea-client/src/account.rs | 27 +++ azalea-client/src/client.rs | 465 +++++++++++++++++++++++++++++++++++++++++ azalea-client/src/connect.rs | 483 ------------------------------------------- azalea-client/src/lib.rs | 6 +- azalea-world/src/entity.rs | 1 + bot/src/main.rs | 5 +- 6 files changed, 499 insertions(+), 488 deletions(-) create mode 100644 azalea-client/src/account.rs create mode 100644 azalea-client/src/client.rs delete mode 100755 azalea-client/src/connect.rs (limited to 'azalea-world/src/entity.rs') diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs new file mode 100644 index 00000000..00a5d0f6 --- /dev/null +++ b/azalea-client/src/account.rs @@ -0,0 +1,27 @@ +use crate::Client; +use azalea_protocol::{ + packets::game::{ + clientbound_player_chat_packet::ClientboundPlayerChatPacket, + clientbound_system_chat_packet::ClientboundSystemChatPacket, + }, + ServerAddress, +}; +use std::fmt::Debug; + +///! Connect to Minecraft servers. + +/// Something that can join Minecraft servers. +pub struct Account { + pub username: String, +} +impl Account { + pub fn offline(username: &str) -> Self { + Self { + username: username.to_string(), + } + } + + pub async fn join(&self, address: &ServerAddress) -> Result { + Client::join(self, address).await + } +} diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs new file mode 100644 index 00000000..ff8729cb --- /dev/null +++ b/azalea-client/src/client.rs @@ -0,0 +1,465 @@ +use crate::{Account, Player}; +use azalea_core::{resource_location::ResourceLocation, ChunkPos}; +use azalea_entity::Entity; +use azalea_protocol::{ + connect::{GameConnection, HandshakeConnection}, + packets::{ + game::{ + clientbound_player_chat_packet::ClientboundPlayerChatPacket, + clientbound_system_chat_packet::ClientboundSystemChatPacket, + serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, + serverbound_keep_alive_packet::ServerboundKeepAlivePacket, GamePacket, + }, + handshake::client_intention_packet::ClientIntentionPacket, + login::{ + serverbound_hello_packet::ServerboundHelloPacket, + serverbound_key_packet::{NonceOrSaltSignature, ServerboundKeyPacket}, + LoginPacket, + }, + ConnectionProtocol, PROTOCOL_VERSION, + }, + resolver, ServerAddress, +}; +use azalea_world::{ChunkStorage, EntityStorage, World}; +use std::{fmt::Debug, sync::Arc}; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::sync::Mutex; + +#[derive(Default)] +pub struct ClientState { + pub player: Player, + pub world: Option, +} + +#[derive(Debug, Clone)] +pub enum Event { + Login, + Chat(ChatPacket), +} + +#[derive(Debug, Clone)] +pub enum ChatPacket { + System(ClientboundSystemChatPacket), + Player(ClientboundPlayerChatPacket), +} + +// impl ChatPacket { +// pub fn message(&self) -> &str { +// match self { +// ChatPacket::System(p) => &p.content, +// ChatPacket::Player(p) => &p.message, +// } +// } +// } + +/// A player that you can control that is currently in a Minecraft server. +pub struct Client { + event_receiver: UnboundedReceiver, + pub conn: Arc>, + pub state: Arc>, + // game_loop +} + +/// Whether we should ignore errors when decoding packets. +const IGNORE_ERRORS: bool = !cfg!(debug_assertions); + +impl Client { + /// Connect to a Minecraft server with an account. + pub async fn join(account: &Account, address: &ServerAddress) -> Result { + let resolved_address = resolver::resolve_address(address).await?; + + let mut conn = HandshakeConnection::new(&resolved_address).await?; + + // handshake + conn.write( + ClientIntentionPacket { + protocol_version: PROTOCOL_VERSION, + hostname: address.host.clone(), + port: address.port, + intention: ConnectionProtocol::Login, + } + .get(), + ) + .await; + let mut conn = conn.login(); + + // login + conn.write( + ServerboundHelloPacket { + username: account.username.clone(), + public_key: None, + } + .get(), + ) + .await; + + let conn = loop { + let packet_result = conn.read().await; + match packet_result { + Ok(packet) => match packet { + LoginPacket::ClientboundHelloPacket(p) => { + println!("Got encryption request"); + let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap(); + + // TODO: authenticate with the server here (authenticateServer) + + conn.write( + ServerboundKeyPacket { + nonce_or_salt_signature: NonceOrSaltSignature::Nonce( + e.encrypted_nonce, + ), + key_bytes: e.encrypted_public_key, + } + .get(), + ) + .await; + conn.set_encryption_key(e.secret_key); + } + LoginPacket::ClientboundLoginCompressionPacket(p) => { + println!("Got compression request {:?}", p.compression_threshold); + conn.set_compression_threshold(p.compression_threshold); + } + LoginPacket::ClientboundGameProfilePacket(p) => { + println!("Got profile {:?}", p.game_profile); + break conn.game(); + } + LoginPacket::ClientboundLoginDisconnectPacket(p) => { + println!("Got disconnect {:?}", p); + } + LoginPacket::ClientboundCustomQueryPacket(p) => { + println!("Got custom query {:?}", p); + } + _ => panic!("Unexpected packet {:?}", packet), + }, + Err(e) => { + panic!("Error: {:?}", e); + } + } + }; + + let conn = Arc::new(Mutex::new(conn)); + + let (tx, rx) = mpsc::unbounded_channel(); + + // we got the GameConnection, so the server is now connected :) + let client = Client { + event_receiver: rx, + conn: conn.clone(), + state: Arc::new(Mutex::new(ClientState::default())), + }; + // let client = Arc::new(Mutex::new(client)); + // let weak_client = Arc::<_>::downgrade(&client); + + // just start up the game loop and we're ready! + // tokio::spawn(Self::game_loop(conn, tx, handler, state)) + + let game_loop_state = client.state.clone(); + + tokio::spawn(Self::game_loop(conn, tx, game_loop_state)); + + Ok(client) + } + + async fn game_loop( + conn: Arc>, + tx: UnboundedSender, + state: Arc>, + ) { + loop { + let r = conn.lock().await.read().await; + match r { + Ok(packet) => Self::handle(&packet, &tx, &state, &conn).await, + Err(e) => { + if IGNORE_ERRORS { + println!("Error: {:?}", e); + if e == "length wider than 21-bit" { + panic!(); + } + } else { + panic!("Error: {:?}", e); + } + } + }; + } + } + + async fn handle( + packet: &GamePacket, + tx: &UnboundedSender, + state: &Arc>, + conn: &Arc>, + ) { + match packet { + GamePacket::ClientboundLoginPacket(p) => { + println!("Got login packet {:?}", p); + + let mut state = state.lock().await; + + // // write p into login.txt + // std::io::Write::write_all( + // &mut std::fs::File::create("login.txt").unwrap(), + // format!("{:#?}", p).as_bytes(), + // ) + // .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 + + let registry_holder = p + .registry_holder + .as_compound() + .expect("Registry holder is not a compound") + .get("") + .expect("No \"\" tag") + .as_compound() + .expect("\"\" tag is not a compound"); + let dimension_types = registry_holder + .get("minecraft:dimension_type") + .expect("No dimension_type tag") + .as_compound() + .expect("dimension_type is not a compound") + .get("value") + .expect("No dimension_type value") + .as_list() + .expect("dimension_type value is not a list"); + let dimension_type = dimension_types + .iter() + .find(|t| { + t.as_compound() + .expect("dimension_type value is not a compound") + .get("name") + .expect("No name tag") + .as_string() + .expect("name is not a string") + == p.dimension_type.to_string() + }) + .expect(&format!("No dimension_type with name {}", p.dimension_type)) + .as_compound() + .unwrap() + .get("element") + .expect("No element tag") + .as_compound() + .expect("element is not a compound"); + let height = (*dimension_type + .get("height") + .expect("No height tag") + .as_int() + .expect("height tag is not an int")) + .try_into() + .expect("height is not a u32"); + let min_y = (*dimension_type + .get("min_y") + .expect("No min_y tag") + .as_int() + .expect("min_y tag is not an int")) + .try_into() + .expect("min_y is not an i32"); + + state.world = Some(World { + height, + min_y, + storage: ChunkStorage::new(16), + entities: EntityStorage::new(), + }); + + conn.lock() + .await + .write( + ServerboundCustomPayloadPacket { + identifier: ResourceLocation::new("brand").unwrap(), + // they don't have to know :) + data: "vanilla".into(), + } + .get(), + ) + .await; + + tx.send(Event::Login).unwrap(); + } + GamePacket::ClientboundUpdateViewDistancePacket(p) => { + println!("Got view distance packet {:?}", p); + } + GamePacket::ClientboundCustomPayloadPacket(p) => { + println!("Got custom payload packet {:?}", p); + } + GamePacket::ClientboundChangeDifficultyPacket(p) => { + println!("Got difficulty packet {:?}", p); + } + GamePacket::ClientboundDeclareCommandsPacket(_p) => { + println!("Got declare commands packet"); + } + GamePacket::ClientboundPlayerAbilitiesPacket(p) => { + println!("Got player abilities packet {:?}", p); + } + GamePacket::ClientboundSetCarriedItemPacket(p) => { + println!("Got set carried item packet {:?}", p); + } + GamePacket::ClientboundUpdateTagsPacket(_p) => { + println!("Got update tags packet"); + } + GamePacket::ClientboundDisconnectPacket(p) => { + println!("Got disconnect packet {:?}", p); + } + GamePacket::ClientboundUpdateRecipesPacket(_p) => { + println!("Got update recipes packet"); + } + GamePacket::ClientboundEntityEventPacket(p) => { + // println!("Got entity event packet {:?}", p); + } + GamePacket::ClientboundRecipePacket(_p) => { + println!("Got recipe packet"); + } + GamePacket::ClientboundPlayerPositionPacket(p) => { + // TODO: reply with teleport confirm + println!("Got player position packet {:?}", p); + } + GamePacket::ClientboundPlayerInfoPacket(p) => { + println!("Got player info packet {:?}", p); + } + GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { + println!("Got chunk cache center packet {:?}", p); + state + .lock() + .await + .world + .as_mut() + .unwrap() + .update_view_center(&ChunkPos::new(p.x, p.z)); + } + GamePacket::ClientboundLevelChunkWithLightPacket(p) => { + println!("Got chunk with light packet {} {}", p.x, p.z); + let pos = ChunkPos::new(p.x, p.z); + // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); + // println("chunk {:?}") + state + .lock() + .await + .world + .as_mut() + .expect("World doesn't exist! We should've gotten a login packet by now.") + .replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice()) + .unwrap(); + } + GamePacket::ClientboundLightUpdatePacket(p) => { + println!("Got light update packet {:?}", p); + } + GamePacket::ClientboundAddEntityPacket(p) => { + println!("Got add entity packet {:?}", p); + let entity = Entity::from(p); + state + .lock() + .await + .world + .as_mut() + .expect("World doesn't exist! We should've gotten a login packet by now.") + .entities + .insert(entity); + } + GamePacket::ClientboundSetEntityDataPacket(p) => { + // println!("Got set entity data packet {:?}", p); + } + GamePacket::ClientboundUpdateAttributesPacket(p) => { + // println!("Got update attributes packet {:?}", p); + } + GamePacket::ClientboundEntityVelocityPacket(p) => { + // println!("Got entity velocity packet {:?}", p); + } + GamePacket::ClientboundSetEntityLinkPacket(p) => { + println!("Got set entity link packet {:?}", p); + } + GamePacket::ClientboundAddPlayerPacket(p) => { + println!("Got add player packet {:?}", p); + } + GamePacket::ClientboundInitializeBorderPacket(p) => { + println!("Got initialize border packet {:?}", p); + } + GamePacket::ClientboundSetTimePacket(p) => { + println!("Got set time packet {:?}", p); + } + GamePacket::ClientboundSetDefaultSpawnPositionPacket(p) => { + println!("Got set default spawn position packet {:?}", p); + } + GamePacket::ClientboundContainerSetContentPacket(p) => { + println!("Got container set content packet {:?}", p); + } + GamePacket::ClientboundSetHealthPacket(p) => { + println!("Got set health packet {:?}", p); + } + GamePacket::ClientboundSetExperiencePacket(p) => { + println!("Got set experience packet {:?}", p); + } + GamePacket::ClientboundTeleportEntityPacket(p) => { + // println!("Got teleport entity packet {:?}", p); + } + GamePacket::ClientboundUpdateAdvancementsPacket(p) => { + println!("Got update advancements packet {:?}", p); + } + GamePacket::ClientboundRotateHeadPacket(p) => { + // println!("Got rotate head packet {:?}", p); + } + GamePacket::ClientboundMoveEntityPosPacket(p) => { + // println!("Got move entity pos packet {:?}", p); + } + GamePacket::ClientboundMoveEntityPosRotPacket(p) => { + // println!("Got move entity pos rot packet {:?}", p); + } + GamePacket::ClientboundMoveEntityRotPacket(p) => { + println!("Got move entity rot packet {:?}", p); + } + GamePacket::ClientboundKeepAlivePacket(p) => { + println!("Got keep alive packet {:?}", p); + conn.lock() + .await + .write(ServerboundKeepAlivePacket { id: p.id }.get()) + .await; + } + GamePacket::ClientboundRemoveEntitiesPacket(p) => { + println!("Got remove entities packet {:?}", p); + } + GamePacket::ClientboundPlayerChatPacket(p) => { + println!("Got player chat packet {:?}", p); + tx.send(Event::Chat(ChatPacket::Player(p.clone()))).unwrap(); + } + GamePacket::ClientboundSystemChatPacket(p) => { + println!("Got system chat packet {:?}", p); + tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap(); + } + GamePacket::ClientboundSoundPacket(p) => { + println!("Got sound packet {:?}", p); + } + GamePacket::ClientboundLevelEventPacket(p) => { + println!("Got level event packet {:?}", p); + } + GamePacket::ClientboundBlockUpdatePacket(p) => { + println!("Got block update packet {:?}", p); + // TODO: update world + } + GamePacket::ClientboundAnimatePacket(p) => { + println!("Got animate packet {:?}", p); + } + GamePacket::ClientboundSectionBlocksUpdatePacket(p) => { + println!("Got section blocks update packet {:?}", p); + // TODO: update world + } + GamePacket::ClientboundGameEventPacket(p) => { + println!("Got game event packet {:?}", p); + } + GamePacket::ClientboundLevelParticlesPacket(p) => { + println!("Got level particles packet {:?}", p); + } + GamePacket::ClientboundServerDataPacket(p) => { + println!("Got server data packet {:?}", p); + } + GamePacket::ClientboundSetEquipmentPacket(p) => { + println!("Got set equipment packet {:?}", p); + } + _ => panic!("Unexpected packet {:?}", packet), + } + } + + pub async fn next(&mut self) -> Option { + self.event_receiver.recv().await + } +} diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs deleted file mode 100755 index dd3162eb..00000000 --- a/azalea-client/src/connect.rs +++ /dev/null @@ -1,483 +0,0 @@ -use crate::Player; -use azalea_core::{resource_location::ResourceLocation, ChunkPos, EntityPos}; -use azalea_entity::Entity; -use azalea_protocol::{ - connect::{GameConnection, HandshakeConnection}, - packets::{ - game::{ - clientbound_player_chat_packet::ClientboundPlayerChatPacket, - clientbound_system_chat_packet::ClientboundSystemChatPacket, - serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, - serverbound_keep_alive_packet::ServerboundKeepAlivePacket, GamePacket, - }, - handshake::client_intention_packet::ClientIntentionPacket, - login::{ - serverbound_hello_packet::ServerboundHelloPacket, - serverbound_key_packet::{NonceOrSaltSignature, ServerboundKeyPacket}, - LoginPacket, - }, - ConnectionProtocol, PROTOCOL_VERSION, - }, - resolver, ServerAddress, -}; -use azalea_world::{ChunkStorage, EntityStorage, World}; -use std::{fmt::Debug, sync::Arc}; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::sync::Mutex; - -///! Connect to Minecraft servers. - -/// Something that can join Minecraft servers. -pub struct Account { - username: String, -} - -#[derive(Default)] -pub struct ClientState { - pub player: Player, - pub world: Option, -} - -/// A player that you can control that is currently in a Minecraft server. -pub struct Client { - event_receiver: UnboundedReceiver, - pub conn: Arc>, - pub state: Arc>, - // game_loop -} - -#[derive(Debug, Clone)] -pub enum ChatPacket { - System(ClientboundSystemChatPacket), - Player(ClientboundPlayerChatPacket), -} - -// impl ChatPacket { -// pub fn message(&self) -> &str { -// match self { -// ChatPacket::System(p) => &p.content, -// ChatPacket::Player(p) => &p.message, -// } -// } -// } - -#[derive(Debug, Clone)] -pub enum Event { - Login, - Chat(ChatPacket), -} - -/// Whether we should ignore errors when decoding packets. -const IGNORE_ERRORS: bool = false; - -impl Client { - async fn join(account: &Account, address: &ServerAddress) -> Result { - let resolved_address = resolver::resolve_address(address).await?; - - let mut conn = HandshakeConnection::new(&resolved_address).await?; - - // handshake - conn.write( - ClientIntentionPacket { - protocol_version: PROTOCOL_VERSION, - hostname: address.host.clone(), - port: address.port, - intention: ConnectionProtocol::Login, - } - .get(), - ) - .await; - let mut conn = conn.login(); - - // login - conn.write( - ServerboundHelloPacket { - username: account.username.clone(), - public_key: None, - } - .get(), - ) - .await; - - let conn = loop { - let packet_result = conn.read().await; - match packet_result { - Ok(packet) => match packet { - LoginPacket::ClientboundHelloPacket(p) => { - println!("Got encryption request"); - let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap(); - - // TODO: authenticate with the server here (authenticateServer) - - conn.write( - ServerboundKeyPacket { - nonce_or_salt_signature: NonceOrSaltSignature::Nonce( - e.encrypted_nonce, - ), - key_bytes: e.encrypted_public_key, - } - .get(), - ) - .await; - conn.set_encryption_key(e.secret_key); - } - LoginPacket::ClientboundLoginCompressionPacket(p) => { - println!("Got compression request {:?}", p.compression_threshold); - conn.set_compression_threshold(p.compression_threshold); - } - LoginPacket::ClientboundGameProfilePacket(p) => { - println!("Got profile {:?}", p.game_profile); - break conn.game(); - } - LoginPacket::ClientboundLoginDisconnectPacket(p) => { - println!("Got disconnect {:?}", p); - } - LoginPacket::ClientboundCustomQueryPacket(p) => { - println!("Got custom query {:?}", p); - } - _ => panic!("Unexpected packet {:?}", packet), - }, - Err(e) => { - panic!("Error: {:?}", e); - } - } - }; - - let conn = Arc::new(Mutex::new(conn)); - - let (tx, rx) = mpsc::unbounded_channel(); - - // we got the GameConnection, so the server is now connected :) - let client = Client { - event_receiver: rx, - conn: conn.clone(), - state: Arc::new(Mutex::new(ClientState::default())), - }; - // let client = Arc::new(Mutex::new(client)); - // let weak_client = Arc::<_>::downgrade(&client); - - // just start up the game loop and we're ready! - // tokio::spawn(Self::game_loop(conn, tx, handler, state)) - - let game_loop_state = client.state.clone(); - - tokio::spawn(Self::game_loop(conn, tx, game_loop_state)); - - Ok(client) - } - - async fn game_loop( - conn: Arc>, - tx: UnboundedSender, - state: Arc>, - ) { - loop { - let r = conn.lock().await.read().await; - match r { - Ok(packet) => Self::handle(&packet, &tx, &state, &conn).await, - Err(e) => { - if IGNORE_ERRORS { - println!("Error: {:?}", e); - if e == "length wider than 21-bit" { - panic!(); - } - } else { - panic!("Error: {:?}", e); - } - } - }; - } - } - - async fn handle( - packet: &GamePacket, - tx: &UnboundedSender, - state: &Arc>, - conn: &Arc>, - ) { - match packet { - GamePacket::ClientboundLoginPacket(p) => { - println!("Got login packet {:?}", p); - - let mut state = state.lock().await; - - // // write p into login.txt - // std::io::Write::write_all( - // &mut std::fs::File::create("login.txt").unwrap(), - // format!("{:#?}", p).as_bytes(), - // ) - // .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 - - let registry_holder = p - .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types - .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) - .expect(&format!("No dimension_type with name {}", p.dimension_type)) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = (*dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int")) - .try_into() - .expect("min_y is not an i32"); - - state.world = Some(World { - height, - min_y, - storage: ChunkStorage::new(16), - entities: EntityStorage::new(), - }); - - conn.lock() - .await - .write( - ServerboundCustomPayloadPacket { - identifier: ResourceLocation::new("brand").unwrap(), - // they don't have to know :) - data: "vanilla".into(), - } - .get(), - ) - .await; - - tx.send(Event::Login).unwrap(); - } - GamePacket::ClientboundUpdateViewDistancePacket(p) => { - println!("Got view distance packet {:?}", p); - } - GamePacket::ClientboundCustomPayloadPacket(p) => { - println!("Got custom payload packet {:?}", p); - } - GamePacket::ClientboundChangeDifficultyPacket(p) => { - println!("Got difficulty packet {:?}", p); - } - GamePacket::ClientboundDeclareCommandsPacket(_p) => { - println!("Got declare commands packet"); - } - GamePacket::ClientboundPlayerAbilitiesPacket(p) => { - println!("Got player abilities packet {:?}", p); - } - GamePacket::ClientboundSetCarriedItemPacket(p) => { - println!("Got set carried item packet {:?}", p); - } - GamePacket::ClientboundUpdateTagsPacket(_p) => { - println!("Got update tags packet"); - } - GamePacket::ClientboundDisconnectPacket(p) => { - println!("Got disconnect packet {:?}", p); - } - GamePacket::ClientboundUpdateRecipesPacket(_p) => { - println!("Got update recipes packet"); - } - GamePacket::ClientboundEntityEventPacket(p) => { - // println!("Got entity event packet {:?}", p); - } - GamePacket::ClientboundRecipePacket(_p) => { - println!("Got recipe packet"); - } - GamePacket::ClientboundPlayerPositionPacket(p) => { - // TODO: reply with teleport confirm - println!("Got player position packet {:?}", p); - } - GamePacket::ClientboundPlayerInfoPacket(p) => { - println!("Got player info packet {:?}", p); - } - GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { - println!("Got chunk cache center packet {:?}", p); - state - .lock() - .await - .world - .as_mut() - .unwrap() - .update_view_center(&ChunkPos::new(p.x, p.z)); - } - GamePacket::ClientboundLevelChunkWithLightPacket(p) => { - println!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); - // println("chunk {:?}") - state - .lock() - .await - .world - .as_mut() - .expect("World doesn't exist! We should've gotten a login packet by now.") - .replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice()) - .unwrap(); - } - GamePacket::ClientboundLightUpdatePacket(p) => { - println!("Got light update packet {:?}", p); - } - GamePacket::ClientboundAddEntityPacket(p) => { - println!("Got add entity packet {:?}", p); - let entity = Entity::from(p); - state - .lock() - .await - .world - .as_mut() - .expect("World doesn't exist! We should've gotten a login packet by now.") - .entities - .insert(entity); - } - GamePacket::ClientboundSetEntityDataPacket(p) => { - // println!("Got set entity data packet {:?}", p); - } - GamePacket::ClientboundUpdateAttributesPacket(p) => { - // println!("Got update attributes packet {:?}", p); - } - GamePacket::ClientboundEntityVelocityPacket(p) => { - // println!("Got entity velocity packet {:?}", p); - } - GamePacket::ClientboundSetEntityLinkPacket(p) => { - println!("Got set entity link packet {:?}", p); - } - GamePacket::ClientboundAddPlayerPacket(p) => { - println!("Got add player packet {:?}", p); - } - GamePacket::ClientboundInitializeBorderPacket(p) => { - println!("Got initialize border packet {:?}", p); - } - GamePacket::ClientboundSetTimePacket(p) => { - println!("Got set time packet {:?}", p); - } - GamePacket::ClientboundSetDefaultSpawnPositionPacket(p) => { - println!("Got set default spawn position packet {:?}", p); - } - GamePacket::ClientboundContainerSetContentPacket(p) => { - println!("Got container set content packet {:?}", p); - } - GamePacket::ClientboundSetHealthPacket(p) => { - println!("Got set health packet {:?}", p); - } - GamePacket::ClientboundSetExperiencePacket(p) => { - println!("Got set experience packet {:?}", p); - } - GamePacket::ClientboundTeleportEntityPacket(p) => { - // println!("Got teleport entity packet {:?}", p); - } - GamePacket::ClientboundUpdateAdvancementsPacket(p) => { - println!("Got update advancements packet {:?}", p); - } - GamePacket::ClientboundRotateHeadPacket(p) => { - // println!("Got rotate head packet {:?}", p); - } - GamePacket::ClientboundMoveEntityPosPacket(p) => { - // println!("Got move entity pos packet {:?}", p); - } - GamePacket::ClientboundMoveEntityPosRotPacket(p) => { - // println!("Got move entity pos rot packet {:?}", p); - } - GamePacket::ClientboundMoveEntityRotPacket(p) => { - println!("Got move entity rot packet {:?}", p); - } - GamePacket::ClientboundKeepAlivePacket(p) => { - println!("Got keep alive packet {:?}", p); - conn.lock() - .await - .write(ServerboundKeepAlivePacket { id: p.id }.get()) - .await; - } - GamePacket::ClientboundRemoveEntitiesPacket(p) => { - println!("Got remove entities packet {:?}", p); - } - GamePacket::ClientboundPlayerChatPacket(p) => { - println!("Got player chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::Player(p.clone()))).unwrap(); - } - GamePacket::ClientboundSystemChatPacket(p) => { - println!("Got system chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap(); - } - GamePacket::ClientboundSoundPacket(p) => { - println!("Got sound packet {:?}", p); - } - GamePacket::ClientboundLevelEventPacket(p) => { - println!("Got level event packet {:?}", p); - } - GamePacket::ClientboundBlockUpdatePacket(p) => { - println!("Got block update packet {:?}", p); - // TODO: update world - } - GamePacket::ClientboundAnimatePacket(p) => { - println!("Got animate packet {:?}", p); - } - GamePacket::ClientboundSectionBlocksUpdatePacket(p) => { - println!("Got section blocks update packet {:?}", p); - // TODO: update world - } - GamePacket::ClientboundGameEventPacket(p) => { - println!("Got game event packet {:?}", p); - } - GamePacket::ClientboundLevelParticlesPacket(p) => { - println!("Got level particles packet {:?}", p); - } - GamePacket::ClientboundServerDataPacket(p) => { - println!("Got server data packet {:?}", p); - } - GamePacket::ClientboundSetEquipmentPacket(p) => { - println!("Got set equipment packet {:?}", p); - } - _ => panic!("Unexpected packet {:?}", packet), - } - } - - pub async fn next(&mut self) -> Option { - self.event_receiver.recv().await - } -} - -impl Account { - pub fn offline(username: &str) -> Self { - Self { - username: username.to_string(), - } - } - - pub async fn join(&self, address: &ServerAddress) -> Result { - Client::join(self, address).await - } -} diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index db935897..867f05a1 100755 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -1,10 +1,12 @@ //! Significantly abstract azalea-protocol so it's actually useable for bots. -mod connect; +mod account; +mod client; pub mod ping; mod player; -pub use connect::{Account, Client, Event}; +pub use account::Account; +pub use client::{Client, Event}; pub use player::Player; #[cfg(test)] diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index a25b1f40..49e1ae73 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -4,6 +4,7 @@ use azalea_core::ChunkPos; use azalea_entity::Entity; use nohash_hasher::IntMap; +#[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) diff --git a/bot/src/main.rs b/bot/src/main.rs index 0b9da787..bfcba7f5 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,5 +1,4 @@ use azalea_client::{Account, Event}; -use azalea_core::BlockPos; #[tokio::main] async fn main() { @@ -20,10 +19,10 @@ async fn main() { match e { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} - Event::Chat(p) => { + Event::Chat(_p) => { let state = client.state.lock().await; let world = state.world.as_ref().unwrap(); - println!("{:?}", state.world.entities.get_player(player)); + println!("{:?}", world.entities); // world.get_block_state(state.player.entity.pos); // println!("{}", p.message.to_ansi(None)); // if p.message.to_ansi(None) == " ok" { -- cgit v1.2.3 From bb6b116cb81a64deeb5ee8c1d021f27dba1cbc58 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 19 Jun 2022 00:30:24 -0500 Subject: Improvements to azalea-world for entities --- Cargo.lock | 1 + azalea-client/src/client.rs | 197 +++++++++++++++++++++++++------------------- azalea-core/src/position.rs | 19 ++++- azalea-entity/src/lib.rs | 17 ++-- azalea-world/Cargo.toml | 1 + azalea-world/src/chunk.rs | 33 +++++++- azalea-world/src/entity.rs | 47 +++++++++-- azalea-world/src/lib.rs | 80 +++++++++++------- bot/src/main.rs | 12 +-- examples/pvp.rs | 2 +- 10 files changed, 267 insertions(+), 142 deletions(-) (limited to 'azalea-world/src/entity.rs') diff --git a/Cargo.lock b/Cargo.lock index 74375949..a3ceba57 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,7 @@ dependencies = [ "azalea-entity", "azalea-nbt", "azalea-protocol", + "log", "nohash-hasher", ] diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index ff8729cb..dc2fe70f 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -21,9 +21,11 @@ use azalea_protocol::{ resolver, ServerAddress, }; use azalea_world::{ChunkStorage, EntityStorage, World}; -use std::{fmt::Debug, sync::Arc}; +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, +}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::sync::Mutex; #[derive(Default)] pub struct ClientState { @@ -55,7 +57,7 @@ pub enum ChatPacket { /// A player that you can control that is currently in a Minecraft server. pub struct Client { event_receiver: UnboundedReceiver, - pub conn: Arc>, + pub conn: Arc>, pub state: Arc>, // game_loop } @@ -63,6 +65,8 @@ pub struct Client { /// Whether we should ignore errors when decoding packets. const IGNORE_ERRORS: bool = !cfg!(debug_assertions); +struct HandleError(String); + impl Client { /// Connect to a Minecraft server with an account. pub async fn join(account: &Account, address: &ServerAddress) -> Result { @@ -137,7 +141,7 @@ impl Client { } }; - let conn = Arc::new(Mutex::new(conn)); + let conn = Arc::new(tokio::sync::Mutex::new(conn)); let (tx, rx) = mpsc::unbounded_channel(); @@ -161,14 +165,16 @@ impl Client { } async fn game_loop( - conn: Arc>, + conn: Arc>, tx: UnboundedSender, state: Arc>, ) { loop { let r = conn.lock().await.read().await; match r { - Ok(packet) => Self::handle(&packet, &tx, &state, &conn).await, + Ok(packet) => { + Self::handle(&packet, &tx, &state, &conn).await; + } Err(e) => { if IGNORE_ERRORS { println!("Error: {:?}", e); @@ -187,82 +193,79 @@ impl Client { packet: &GamePacket, tx: &UnboundedSender, state: &Arc>, - conn: &Arc>, - ) { + conn: &Arc>, + ) -> Result<(), HandleError> { match packet { GamePacket::ClientboundLoginPacket(p) => { println!("Got login packet {:?}", p); - let mut state = state.lock().await; - - // // write p into login.txt - // std::io::Write::write_all( - // &mut std::fs::File::create("login.txt").unwrap(), - // format!("{:#?}", p).as_bytes(), - // ) - // .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 - - let registry_holder = p - .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types - .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) - .expect(&format!("No dimension_type with name {}", p.dimension_type)) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = (*dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int")) - .try_into() - .expect("min_y is not an i32"); - - state.world = Some(World { - height, - min_y, - storage: ChunkStorage::new(16), - entities: EntityStorage::new(), - }); + { + let mut state = state.lock()?; + + // // write p into login.txt + // std::io::Write::write_all( + // &mut std::fs::File::create("login.txt").unwrap(), + // format!("{:#?}", p).as_bytes(), + // ) + // .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 + + let registry_holder = p + .registry_holder + .as_compound() + .expect("Registry holder is not a compound") + .get("") + .expect("No \"\" tag") + .as_compound() + .expect("\"\" tag is not a compound"); + let dimension_types = registry_holder + .get("minecraft:dimension_type") + .expect("No dimension_type tag") + .as_compound() + .expect("dimension_type is not a compound") + .get("value") + .expect("No dimension_type value") + .as_list() + .expect("dimension_type value is not a list"); + let dimension_type = dimension_types + .iter() + .find(|t| { + t.as_compound() + .expect("dimension_type value is not a compound") + .get("name") + .expect("No name tag") + .as_string() + .expect("name is not a string") + == p.dimension_type.to_string() + }) + .expect(&format!("No dimension_type with name {}", p.dimension_type)) + .as_compound() + .unwrap() + .get("element") + .expect("No element tag") + .as_compound() + .expect("element is not a compound"); + let height = (*dimension_type + .get("height") + .expect("No height tag") + .as_int() + .expect("height tag is not an int")) + .try_into() + .expect("height is not a u32"); + let min_y = (*dimension_type + .get("min_y") + .expect("No min_y tag") + .as_int() + .expect("min_y tag is not an int")) + .try_into() + .expect("min_y is not an i32"); + + state.world = Some(World::new(16, height, min_y)); + } conn.lock() .await @@ -321,8 +324,7 @@ impl Client { GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { println!("Got chunk cache center packet {:?}", p); state - .lock() - .await + .lock()? .world .as_mut() .unwrap() @@ -334,8 +336,7 @@ impl Client { // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); // println("chunk {:?}") state - .lock() - .await + .lock()? .world .as_mut() .expect("World doesn't exist! We should've gotten a login packet by now.") @@ -349,13 +350,11 @@ impl Client { println!("Got add entity packet {:?}", p); let entity = Entity::from(p); state - .lock() - .await + .lock()? .world .as_mut() .expect("World doesn't exist! We should've gotten a login packet by now.") - .entities - .insert(entity); + .add_entity(entity); } GamePacket::ClientboundSetEntityDataPacket(p) => { // println!("Got set entity data packet {:?}", p); @@ -392,6 +391,18 @@ impl Client { } GamePacket::ClientboundTeleportEntityPacket(p) => { // println!("Got teleport entity packet {:?}", p); + let state_lock = state.lock()?; + + // let entity = state_lock + // .world + // .unwrap() + // .entity_by_id(p.id) + // .ok_or("Teleporting entity that doesn't exist.".to_string())?; + // state_lock + // .world + // .as_mut() + // .expect("World doesn't exist! We should've gotten a login packet by now.") + // .move_entity(&mut entity, new_pos) } GamePacket::ClientboundUpdateAdvancementsPacket(p) => { println!("Got update advancements packet {:?}", p); @@ -457,9 +468,23 @@ impl Client { } _ => panic!("Unexpected packet {:?}", packet), } + + Ok(()) } pub async fn next(&mut self) -> Option { self.event_receiver.recv().await } } + +impl From> for HandleError { + fn from(e: std::sync::PoisonError) -> Self { + HandleError(e.to_string()) + } +} + +impl From for HandleError { + fn from(e: String) -> Self { + HandleError(e) + } +} diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 24be5f6a..43881f1c 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -27,7 +27,7 @@ impl Rem for BlockPos { } } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct ChunkPos { pub x: i32, pub z: i32, @@ -164,6 +164,12 @@ impl From<&EntityPos> for BlockPos { } } +impl From<&EntityPos> for ChunkPos { + fn from(pos: &EntityPos) -> Self { + ChunkPos::from(&BlockPos::from(pos)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -181,4 +187,15 @@ mod tests { let chunk_block_pos = ChunkBlockPos::from(&block_pos); assert_eq!(chunk_block_pos, ChunkBlockPos::new(5, 78, 14)); } + + #[test] + fn test_from_entity_pos_to_chunk_pos() { + let entity_pos = EntityPos { + x: 33.5, + y: 77.0, + z: -19.6, + }; + let chunk_pos = ChunkPos::from(&entity_pos); + assert_eq!(chunk_pos, ChunkPos::new(2, -2)); + } } diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index f9d808c2..f776f16f 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -1,4 +1,4 @@ -use azalea_core::EntityPos; +use azalea_core::{ChunkPos, EntityPos}; #[cfg(feature = "protocol")] use azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket; use uuid::Uuid; @@ -16,19 +16,16 @@ impl Entity { &self.pos } - pub fn set_pos(&mut self, pos: EntityPos) { - // TODO: check if it moved to another chunk - self.pos = pos; + /// Sets the position of the entity. This doesn't update the cache in + /// azalea-world, and should only be used within azalea-world! + pub fn unsafe_move(&mut self, new_pos: EntityPos) { + self.pos = new_pos; } } #[cfg(feature = "protocol")] -impl From<&azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket> - for Entity -{ - fn from( - p: &azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket, - ) -> Self { +impl From<&ClientboundAddEntityPacket> for Entity { + fn from(p: &ClientboundAddEntityPacket) -> Self { Self { id: p.id, uuid: p.uuid, diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index e5e9da1d..96942138 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -11,6 +11,7 @@ azalea-core = {path = "../azalea-core"} azalea-entity = {path = "../azalea-entity"} azalea-nbt = {path = "../azalea-nbt"} azalea-protocol = {path = "../azalea-protocol"} +log = "0.4.17" nohash-hasher = "0.2.0" [profile.release] diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk.rs index 96bbd922..cbf77b20 100644 --- a/azalea-world/src/chunk.rs +++ b/azalea-world/src/chunk.rs @@ -1,4 +1,3 @@ -use crate::bit_storage::BitStorage; use crate::palette::PalettedContainer; use crate::palette::PalettedContainerType; use crate::World; @@ -13,10 +12,13 @@ use std::{ const SECTION_HEIGHT: u32 = 16; +#[derive(Debug)] pub struct ChunkStorage { pub view_center: ChunkPos, chunk_radius: u32, view_range: u32, + pub height: u32, + pub min_y: i32, // chunks is a list of size chunk_radius * chunk_radius chunks: Vec>>>, } @@ -32,12 +34,14 @@ fn floor_mod(x: i32, y: u32) -> u32 { } impl ChunkStorage { - pub fn new(chunk_radius: u32) -> Self { + pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self { let view_range = chunk_radius * 2 + 1; ChunkStorage { view_center: ChunkPos::new(0, 0), chunk_radius, view_range, + height, + min_y, chunks: vec![None; (view_range * view_range) as usize], } } @@ -61,6 +65,29 @@ impl ChunkStorage { None => None, } } + + pub fn replace_with_packet_data( + &mut self, + pos: &ChunkPos, + data: &mut impl Read, + ) -> Result<(), String> { + if !self.in_range(pos) { + println!( + "Ignoring chunk since it's not in the view range: {}, {}", + pos.x, pos.z + ); + return Ok(()); + } + + let chunk = Arc::new(Mutex::new(Chunk::read_with_world_height( + data, + self.height, + )?)); + println!("Loaded chunk {:?}", pos); + self[pos] = Some(chunk); + + Ok(()) + } } impl Index<&ChunkPos> for ChunkStorage { @@ -84,7 +111,7 @@ pub struct Chunk { impl Chunk { pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result { - Self::read_with_world_height(buf, data.height) + Self::read_with_world_height(buf, data.height()) } pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result { diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index 49e1ae73..7077d0c4 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -1,14 +1,14 @@ -use std::collections::HashMap; - use azalea_core::ChunkPos; use azalea_entity::Entity; -use nohash_hasher::IntMap; +use log::warn; +use nohash_hasher::{IntMap, IntSet}; +use std::collections::HashMap; #[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_chunk: HashMap>, } impl EntityStorage { @@ -22,13 +22,24 @@ impl EntityStorage { /// Add an entity to the storage. #[inline] pub fn insert(&mut self, entity: Entity) { + self.by_chunk + .entry(ChunkPos::from(entity.pos())) + .or_default() + .insert(entity.id); self.by_id.insert(entity.id, entity); } /// Remove an entity from the storage by its id. #[inline] pub fn remove_by_id(&mut self, id: u32) { - self.by_id.remove(&id); + if let Some(entity) = self.by_id.remove(&id) { + let entity_chunk = ChunkPos::from(entity.pos()); + if let None = self.by_chunk.remove(&entity_chunk) { + warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found."); + } + } else { + warn!("Tried to remove entity with id {id} but it was not found.") + } } /// Get a reference to an entity by its id. @@ -42,4 +53,30 @@ impl EntityStorage { pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> { 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); + } + } + } + + /// Updates an entity from its old chunk. + #[inline] + pub fn update_entity_chunk( + &mut self, + entity_id: u32, + old_chunk: &ChunkPos, + new_chunk: &ChunkPos, + ) { + if let Some(entities) = self.by_chunk.get_mut(old_chunk) { + entities.remove(&entity_id); + } + self.by_chunk + .entry(*new_chunk) + .or_default() + .insert(entity_id); + } } diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index b47126d4..746143c7 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -6,7 +6,8 @@ mod entity; mod palette; use azalea_block::BlockState; -use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; +use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, EntityPos}; +use azalea_entity::Entity; use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; pub use bit_storage::BitStorage; pub use chunk::{Chunk, ChunkStorage}; @@ -26,61 +27,78 @@ mod tests { } } +#[derive(Debug)] pub struct World { - pub storage: ChunkStorage, - pub entities: EntityStorage, - pub height: u32, - pub min_y: i32, + chunk_storage: ChunkStorage, + entity_storage: EntityStorage, } impl World { + pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self { + World { + chunk_storage: ChunkStorage::new(chunk_radius, height, min_y), + entity_storage: EntityStorage::new(), + } + } + pub fn replace_with_packet_data( &mut self, pos: &ChunkPos, data: &mut impl Read, ) -> Result<(), String> { - if !self.storage.in_range(pos) { - println!( - "Ignoring chunk since it's not in the view range: {}, {}", - pos.x, pos.z - ); - return Ok(()); - } - // let existing_chunk = &self.storage[pos]; + self.chunk_storage.replace_with_packet_data(pos, data) + } - let chunk = Arc::new(Mutex::new(Chunk::read_with_world(data, self)?)); - println!("Loaded chunk {:?}", pos); - self.storage[pos] = Some(chunk); + pub fn update_view_center(&mut self, pos: &ChunkPos) { + self.chunk_storage.view_center = *pos; + } + + pub fn get_block_state(&self, pos: &BlockPos) -> Option { + self.chunk_storage.get_block_state(pos, self.min_y()) + } + pub fn move_entity(&mut self, entity_id: u32, new_pos: EntityPos) -> Result<(), String> { + let entity = self + .entity_storage + .get_mut_by_id(entity_id) + .ok_or("Moving entity that doesn't exist".to_string())?; + let old_chunk = ChunkPos::from(entity.pos()); + let new_chunk = ChunkPos::from(&new_pos); + // this is fine because we update the chunk below + entity.unsafe_move(new_pos); + if old_chunk != new_chunk { + self.entity_storage + .update_entity_chunk(entity_id, &old_chunk, &new_chunk); + } Ok(()) } - pub fn update_view_center(&mut self, pos: &ChunkPos) { - self.storage.view_center = *pos; + pub fn add_entity(&mut self, entity: Entity) { + self.entity_storage.insert(entity); } - pub fn get_block_state(&self, pos: &BlockPos) -> Option { - self.storage.get_block_state(pos, self.min_y) + pub fn height(&self) -> u32 { + self.chunk_storage.height + } + + pub fn min_y(&self) -> i32 { + self.chunk_storage.min_y + } + + pub fn entity_by_id(&self, id: u32) -> Option<&Entity> { + self.entity_storage.get_by_id(id) } } + impl Index<&ChunkPos> for World { type Output = Option>>; fn index(&self, pos: &ChunkPos) -> &Self::Output { - &self.storage[pos] + &self.chunk_storage[pos] } } impl IndexMut<&ChunkPos> for World { fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output { - &mut self.storage[pos] + &mut self.chunk_storage[pos] } } -// impl Index<&BlockPos> for World { -// type Output = Option>>; - -// fn index(&self, pos: &BlockPos) -> &Self::Output { -// let chunk = &self[ChunkPos::from(pos)]; -// // chunk. - -// } -// } diff --git a/bot/src/main.rs b/bot/src/main.rs index bfcba7f5..6b318157 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,7 +1,7 @@ use azalea_client::{Account, Event}; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { println!("Hello, world!"); // let address = "95.111.249.143:10000"; @@ -15,18 +15,18 @@ async fn main() { let mut client = account.join(&address.try_into().unwrap()).await.unwrap(); println!("connected"); - while let Some(e) = client.next().await { + while let Some(e) = &client.next().await { match e { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} Event::Chat(_p) => { - let state = client.state.lock().await; + let state = &client.state.lock()?; let world = state.world.as_ref().unwrap(); - println!("{:?}", world.entities); + println!("{:?}", world); // 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().await; + // 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); @@ -36,4 +36,6 @@ async fn main() { } println!("done"); + + Ok(()) } diff --git a/examples/pvp.rs b/examples/pvp.rs index 61382ecd..5febdd45 100644 --- a/examples/pvp.rs +++ b/examples/pvp.rs @@ -21,7 +21,7 @@ async fn main() { if bot.entity.can_reach(target.bounding_box) { bot.swing(); } - if !h.using_held_item() && bot.state.lock().await.hunger <= 17 { + if !h.using_held_item() && bot.state.lock().hunger <= 17 { bot.hold(azalea::ItemGroup::Food); tokio::task::spawn(bot.use_held_item()); } -- cgit v1.2.3 From c9a070f711a6fdaf505f522cb8809749c9190e38 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 19 Jun 2022 22:01:54 -0500 Subject: Fix some clippy warnings --- Cargo.lock | 16 +++++++++++ azalea-client/Cargo.toml | 1 + azalea-client/src/account.rs | 9 +----- azalea-client/src/client.rs | 61 +++++++++++++++++++++++++++-------------- azalea-entity/src/lib.rs | 2 +- azalea-world/src/bit_storage.rs | 6 ++-- azalea-world/src/chunk.rs | 10 +++---- azalea-world/src/entity.rs | 8 +++++- azalea-world/src/lib.rs | 7 ++--- bot/src/main.rs | 9 ++++-- 10 files changed, 83 insertions(+), 46 deletions(-) (limited to 'azalea-world/src/entity.rs') diff --git a/Cargo.lock b/Cargo.lock index a3ceba57..59edd75e 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,7 @@ dependencies = [ "azalea-entity", "azalea-protocol", "azalea-world", + "owning_ref", "tokio", ] @@ -891,6 +892,15 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "packet-macros" version = "0.1.0" @@ -1239,6 +1249,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" version = "1.0.96" diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index cc34f03a..9c9ae746 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -12,4 +12,5 @@ azalea-crypto = {path = "../azalea-crypto"} azalea-entity = {path = "../azalea-entity"} azalea-protocol = {path = "../azalea-protocol"} azalea-world = {path = "../azalea-world"} +owning_ref = "0.4.1" tokio = {version = "1.18.0", features = ["sync"]} diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 00a5d0f6..0bcad630 100644 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -1,12 +1,5 @@ use crate::Client; -use azalea_protocol::{ - packets::game::{ - clientbound_player_chat_packet::ClientboundPlayerChatPacket, - clientbound_system_chat_packet::ClientboundSystemChatPacket, - }, - ServerAddress, -}; -use std::fmt::Debug; +use azalea_protocol::ServerAddress; ///! Connect to Minecraft servers. diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index dc2fe70f..3dd206b5 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -20,7 +20,8 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; -use azalea_world::{ChunkStorage, EntityStorage, World}; +use azalea_world::World; +use owning_ref::OwningRef; use std::{ fmt::Debug, sync::{Arc, Mutex}, @@ -42,7 +43,7 @@ pub enum Event { #[derive(Debug, Clone)] pub enum ChatPacket { System(ClientboundSystemChatPacket), - Player(ClientboundPlayerChatPacket), + Player(Box), } // impl ChatPacket { @@ -65,6 +66,7 @@ pub struct Client { /// Whether we should ignore errors when decoding packets. const IGNORE_ERRORS: bool = !cfg!(debug_assertions); +#[derive(Debug)] struct HandleError(String); impl Client { @@ -172,9 +174,17 @@ impl Client { loop { let r = conn.lock().await.read().await; match r { - Ok(packet) => { - Self::handle(&packet, &tx, &state, &conn).await; - } + 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); + } + } + }, Err(e) => { if IGNORE_ERRORS { println!("Error: {:?}", e); @@ -242,7 +252,9 @@ impl Client { .expect("name is not a string") == p.dimension_type.to_string() }) - .expect(&format!("No dimension_type with name {}", p.dimension_type)) + .unwrap_or_else(|| { + panic!("No dimension_type with name {}", p.dimension_type) + }) .as_compound() .unwrap() .get("element") @@ -256,13 +268,11 @@ impl Client { .expect("height tag is not an int")) .try_into() .expect("height is not a u32"); - let min_y = (*dimension_type + let min_y = *dimension_type .get("min_y") .expect("No min_y tag") .as_int() - .expect("min_y tag is not an int")) - .try_into() - .expect("min_y is not an i32"); + .expect("min_y tag is not an int"); state.world = Some(World::new(16, height, min_y)); } @@ -308,7 +318,7 @@ impl Client { GamePacket::ClientboundUpdateRecipesPacket(_p) => { println!("Got update recipes packet"); } - GamePacket::ClientboundEntityEventPacket(p) => { + GamePacket::ClientboundEntityEventPacket(_p) => { // println!("Got entity event packet {:?}", p); } GamePacket::ClientboundRecipePacket(_p) => { @@ -356,13 +366,13 @@ impl Client { .expect("World doesn't exist! We should've gotten a login packet by now.") .add_entity(entity); } - GamePacket::ClientboundSetEntityDataPacket(p) => { + GamePacket::ClientboundSetEntityDataPacket(_p) => { // println!("Got set entity data packet {:?}", p); } - GamePacket::ClientboundUpdateAttributesPacket(p) => { + GamePacket::ClientboundUpdateAttributesPacket(_p) => { // println!("Got update attributes packet {:?}", p); } - GamePacket::ClientboundEntityVelocityPacket(p) => { + GamePacket::ClientboundEntityVelocityPacket(_p) => { // println!("Got entity velocity packet {:?}", p); } GamePacket::ClientboundSetEntityLinkPacket(p) => { @@ -389,9 +399,9 @@ impl Client { GamePacket::ClientboundSetExperiencePacket(p) => { println!("Got set experience packet {:?}", p); } - GamePacket::ClientboundTeleportEntityPacket(p) => { + GamePacket::ClientboundTeleportEntityPacket(_p) => { // println!("Got teleport entity packet {:?}", p); - let state_lock = state.lock()?; + // let state_lock = state.lock()?; // let entity = state_lock // .world @@ -407,13 +417,13 @@ impl Client { GamePacket::ClientboundUpdateAdvancementsPacket(p) => { println!("Got update advancements packet {:?}", p); } - GamePacket::ClientboundRotateHeadPacket(p) => { + GamePacket::ClientboundRotateHeadPacket(_p) => { // println!("Got rotate head packet {:?}", p); } - GamePacket::ClientboundMoveEntityPosPacket(p) => { + GamePacket::ClientboundMoveEntityPosPacket(_p) => { // println!("Got move entity pos packet {:?}", p); } - GamePacket::ClientboundMoveEntityPosRotPacket(p) => { + GamePacket::ClientboundMoveEntityPosRotPacket(_p) => { // println!("Got move entity pos rot packet {:?}", p); } GamePacket::ClientboundMoveEntityRotPacket(p) => { @@ -431,7 +441,8 @@ impl Client { } GamePacket::ClientboundPlayerChatPacket(p) => { println!("Got player chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::Player(p.clone()))).unwrap(); + tx.send(Event::Chat(ChatPacket::Player(Box::new(p.clone())))) + .unwrap(); } GamePacket::ClientboundSystemChatPacket(p) => { println!("Got system chat packet {:?}", p); @@ -475,6 +486,16 @@ impl Client { pub async fn next(&mut self) -> Option { self.event_receiver.recv().await } + + /// Gets the `World` the client is in. + /// + /// This is basically a shortcut for `let world = 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!")) + } } impl From> for HandleError { diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index f776f16f..d32a1a96 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -1,4 +1,4 @@ -use azalea_core::{ChunkPos, EntityPos}; +use azalea_core::EntityPos; #[cfg(feature = "protocol")] use azalea_protocol::packets::game::clientbound_add_entity_packet::ClientboundAddEntityPacket; use uuid::Uuid; diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index 0dc81f9a..fcb3f8f9 100644 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -107,7 +107,7 @@ impl BitStorage { // assert!(bits >= 1 && bits <= 32); if let Some(data) = &data { - if data.len() == 0 { + if data.is_empty() { // TODO: make 0 bit storage actually work return Ok(BitStorage::default()); } @@ -162,7 +162,7 @@ impl BitStorage { // return (int)(var3 >> var5 & this.mask); assert!( - index <= self.size - 1, + index < self.size, "Index {} out of bounds (max is {})", index, self.size - 1 @@ -181,7 +181,7 @@ impl BitStorage { // int var6 = (var1 - var3 * this.valuesPerLong) * this.bits; // this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6; - assert!(index <= self.size - 1); + assert!(index < self.size); assert!(value <= self.mask); let cell_index = self.cell_index(index as u64); let cell = &mut self.data[cell_index as usize]; diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk.rs index 5de39e52..77fa8786 100644 --- a/azalea-world/src/chunk.rs +++ b/azalea-world/src/chunk.rs @@ -60,10 +60,9 @@ impl ChunkStorage { let chunk_pos = ChunkPos::from(pos); println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos); let chunk = &self[&chunk_pos]; - match chunk { - Some(chunk) => Some(chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)), - None => None, - } + chunk + .as_ref() + .map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)) } pub fn replace_with_packet_data( @@ -137,8 +136,7 @@ impl Chunk { // TODO: make sure the section exists let section = &self.sections[section_index as usize]; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - let block_state = section.get(chunk_section_pos); - block_state + section.get(chunk_section_pos) } } diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index 7077d0c4..2409995c 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -34,7 +34,7 @@ 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()); - if let None = self.by_chunk.remove(&entity_chunk) { + 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."); } } else { @@ -80,3 +80,9 @@ impl EntityStorage { .insert(entity_id); } } + +impl Default for EntityStorage { + fn default() -> Self { + Self::new() + } +} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 746143c7..dc538618 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -6,14 +6,13 @@ mod entity; mod palette; use azalea_block::BlockState; -use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, EntityPos}; +use azalea_core::{BlockPos, ChunkPos, EntityPos}; use azalea_entity::Entity; -use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; pub use bit_storage::BitStorage; pub use chunk::{Chunk, ChunkStorage}; pub use entity::EntityStorage; use std::{ - io::{Read, Write}, + io::Read, ops::{Index, IndexMut}, sync::{Arc, Mutex}, }; @@ -61,7 +60,7 @@ impl World { let entity = self .entity_storage .get_mut_by_id(entity_id) - .ok_or("Moving entity that doesn't exist".to_string())?; + .ok_or_else(|| "Moving entity that doesn't exist".to_string())?; let old_chunk = ChunkPos::from(entity.pos()); let new_chunk = ChunkPos::from(&new_pos); // this is fine because we update the chunk below diff --git a/bot/src/main.rs b/bot/src/main.rs index e8ff5f95..f0f915f9 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,4 +1,5 @@ use azalea_client::{Account, Event}; +use azalea_core::BlockPos; #[tokio::main] async fn main() -> Result<(), Box> { @@ -20,9 +21,11 @@ async fn main() -> Result<(), Box> { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} Event::Chat(_p) => { - let state = client.state.lock().unwrap(); - let world = state.world.as_ref().unwrap(); - println!("{:#?}", world); + let world = client.world(); + world.get_block_state(&BlockPos::new(0, 0, 0)).unwrap(); + // let world = state.world.as_ref().unwrap(); + // world. + // println!("{:#?}", world); // world.get_block_state(state.player.entity.pos); // println!("{}", p.message.to_ansi(None)); // if p.message.to_ansi(None) == " ok" { -- cgit v1.2.3 From efd874957331d8bff1cefa0f43d81685690391bf Mon Sep 17 00:00:00 2001 From: mat Date: Mon, 20 Jun 2022 01:19:59 -0500 Subject: add gametick event and find_one_entity --- Cargo.lock | 1 + azalea-client/src/client.rs | 46 +++++++++++++++++++++++++++++++++++++++------ azalea-world/src/entity.rs | 34 +++++++++++++++++++++++++++++++++ azalea-world/src/lib.rs | 13 +++++++++++++ bot/Cargo.toml | 1 + bot/src/main.rs | 14 +++++++++----- 6 files changed, 98 insertions(+), 11 deletions(-) (limited to 'azalea-world/src/entity.rs') diff --git a/Cargo.lock b/Cargo.lock index 59edd75e..c3e5a0ce 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "azalea-core", "azalea-protocol", "tokio", + "uuid", ] [[package]] diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 3dd206b5..828578de 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -26,7 +26,10 @@ use std::{ fmt::Debug, sync::{Arc, Mutex}, }; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::{ + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + time::{self, MissedTickBehavior}, +}; #[derive(Default)] pub struct ClientState { @@ -38,6 +41,8 @@ pub struct ClientState { pub enum Event { Login, Chat(ChatPacket), + /// A game tick, happens 20 times per second. + GameTick, } #[derive(Debug, Clone)] @@ -153,20 +158,22 @@ impl Client { conn: conn.clone(), state: Arc::new(Mutex::new(ClientState::default())), }; - // let client = Arc::new(Mutex::new(client)); - // let weak_client = Arc::<_>::downgrade(&client); // just start up the game loop and we're ready! - // tokio::spawn(Self::game_loop(conn, tx, handler, state)) let game_loop_state = client.state.clone(); - tokio::spawn(Self::game_loop(conn, tx, game_loop_state)); + tokio::spawn(Self::protocol_loop( + conn.clone(), + tx.clone(), + game_loop_state.clone(), + )); + tokio::spawn(Self::game_tick_loop(conn, tx, game_loop_state)); Ok(client) } - async fn game_loop( + async fn protocol_loop( conn: Arc>, tx: UnboundedSender, state: Arc>, @@ -487,6 +494,33 @@ impl Client { self.event_receiver.recv().await } + /// Runs game_tick every 50 milliseconds. + async fn game_tick_loop( + conn: Arc>, + tx: UnboundedSender, + state: Arc>, + ) { + let mut game_tick_interval = time::interval(time::Duration::from_millis(50)); + // TODO: Minecraft bursts up to 10 ticks and then skips, we should too + game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst); + loop { + game_tick_interval.tick().await; + Self::game_tick(&conn, &tx, &state).await; + } + } + + /// Runs every 50 milliseconds. + async fn game_tick( + conn: &Arc>, + tx: &UnboundedSender, + state: &Arc>, + ) { + if state.lock().unwrap().world.is_none() { + return; + } + tx.send(Event::GameTick).unwrap(); + } + /// Gets the `World` the client is in. /// /// This is basically a shortcut for `let world = client.state.lock().unwrap().world.as_ref().unwrap()`. diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs index 2409995c..e4e9864f 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity.rs @@ -79,6 +79,40 @@ impl EntityStorage { .or_default() .insert(entity_id); } + + /// Get an iterator over all entities. + #[inline] + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { + self.by_id.values() + } + + pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + for entity in self.entities() { + if f(entity) { + return Some(entity); + } + } + None + } + + pub fn find_one_entity_in_chunk(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + if let Some(entities) = self.by_chunk.get(chunk) { + for entity_id in entities { + if let Some(entity) = self.by_id.get(entity_id) { + if f(entity) { + return Some(entity); + } + } + } + } + None + } } impl Default for EntityStorage { diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index dc538618..10beb309 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -87,6 +87,19 @@ impl World { pub fn entity_by_id(&self, id: u32) -> Option<&Entity> { self.entity_storage.get_by_id(id) } + + /// Get an iterator over all entities. + #[inline] + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { + self.entity_storage.entities() + } + + pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + where + F: FnMut(&Entity) -> bool, + { + self.entity_storage.find_one_entity(|entity| f(entity)) + } } impl Index<&ChunkPos> for World { diff --git a/bot/Cargo.toml b/bot/Cargo.toml index b66e3b75..f61bf2fa 100755 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -10,3 +10,4 @@ azalea-client = {path = "../azalea-client"} azalea-core = {path = "../azalea-core"} azalea-protocol = {path = "../azalea-protocol"} tokio = "^1.19.2" +uuid = "^1.1.2" diff --git a/bot/src/main.rs b/bot/src/main.rs index 2c2bee32..6ff4cc0b 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -20,12 +20,15 @@ async fn main() -> Result<(), Box> { match e { // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded Event::Login => {} - Event::Chat(_p) => { + Event::GameTick => { let world = client.world(); - let b = world.get_block_state(&BlockPos::new(0, 0, 0)).unwrap(); - // let world = state.world.as_ref().unwrap(); - // world. - println!("{:?}", b); + 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" { @@ -35,6 +38,7 @@ async fn main() -> Result<(), Box> { // println!("block state: {:?}", c); // } } + _ => {} } } -- cgit v1.2.3 From b030b0ea330c674415f7e30634957167b2fa6a6d Mon Sep 17 00:00:00 2001 From: mat Date: Fri, 24 Jun 2022 23:10:59 -0500 Subject: start adding moving --- Cargo.lock | 2 + azalea-auth/Cargo.toml | 2 +- azalea-client/Cargo.toml | 1 + azalea-client/src/account.rs | 4 +- azalea-client/src/client.rs | 153 +++++++++++++++++--- azalea-client/src/movement.rs | 24 +++- azalea-client/src/player.rs | 24 +++- azalea-core/src/delta.rs | 59 ++++++-- azalea-core/src/position.rs | 157 ++++++++++++++------- azalea-entity/src/lib.rs | 30 +++- .../game/clientbound_move_entity_pos_packet.rs | 4 +- .../game/clientbound_move_entity_posrot_packet.rs | 4 +- azalea-world/Cargo.toml | 1 + azalea-world/src/entity.rs | 27 +++- azalea-world/src/lib.rs | 13 +- bot/src/main.rs | 52 ++++--- 16 files changed, 433 insertions(+), 124 deletions(-) (limited to 'azalea-world/src/entity.rs') 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 trait PositionDeltaTrait { + fn x(&self) -> f64; + fn y(&self) -> f64; + fn z(&self) -> f64; +} + +#[derive(Clone, Debug, McBuf, Default)] pub struct PositionDelta { - xa: i16, - ya: i16, - za: i16, + 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 PositionDelta { +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,23 +85,6 @@ impl ChunkSectionPos { ChunkSectionPos { x, y, z } } } - -impl From for ChunkSectionPos { - fn from(pos: BlockPos) -> Self { - ChunkSectionPos { - x: pos.x.div_floor(16), - y: pos.y.div_floor(16), - z: pos.z.div_floor(16), - } - } -} - -impl From for ChunkPos { - fn from(pos: ChunkSectionPos) -> Self { - ChunkPos { x: pos.x, z: pos.z } - } -} - /// The coordinates of a block inside a chunk. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct ChunkBlockPos { @@ -94,17 +98,6 @@ impl ChunkBlockPos { ChunkBlockPos { x, y, z } } } - -impl From<&BlockPos> for ChunkBlockPos { - fn from(pos: &BlockPos) -> Self { - ChunkBlockPos { - x: pos.x.rem_euclid(16).abs() as u8, - y: pos.y, - z: pos.z.rem_euclid(16).abs() as u8, - } - } -} - /// The coordinates of a block inside a chunk section. #[derive(Clone, Copy, Debug, Default)] pub struct ChunkSectionBlockPos { @@ -122,6 +115,80 @@ impl 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, 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 { + ChunkSectionPos { + x: pos.x.div_floor(16), + y: pos.y.div_floor(16), + z: pos.z.div_floor(16), + } + } +} + +impl From for ChunkPos { + fn from(pos: ChunkSectionPos) -> Self { + ChunkPos { x: pos.x, z: pos.z } + } +} + +impl From<&BlockPos> for ChunkBlockPos { + fn from(pos: &BlockPos) -> Self { + ChunkBlockPos { + x: pos.x.rem_euclid(16).abs() as u8, + y: pos.y, + z: pos.z.rem_euclid(16).abs() as u8, + } + } +} + 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(); } _ => {} } -- cgit v1.2.3