diff --git a/Cargo.lock b/Cargo.lock index af1f9712..97bcb5b3 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,10 +21,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", @@ -136,9 +137,10 @@ version = "0.1.0" dependencies = [ "anyhow", "azalea-auth", + "azalea-block", "azalea-core", "azalea-crypto", - "azalea-entity", + "azalea-physics", "azalea-protocol", "azalea-world", "thiserror", @@ -171,17 +173,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "azalea-entity" -version = "0.1.0" -dependencies = [ - "azalea-buf", - "azalea-chat", - "azalea-core", - "azalea-nbt", - "uuid", -] - [[package]] name = "azalea-language" version = "0.1.0" @@ -204,6 +195,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "azalea-physics" +version = "0.1.0" +dependencies = [ + "azalea-block", + "azalea-core", + "azalea-world", +] + [[package]] name = "azalea-protocol" version = "0.1.0" @@ -216,8 +216,8 @@ dependencies = [ "azalea-chat", "azalea-core", "azalea-crypto", - "azalea-entity", "azalea-nbt", + "azalea-world", "byteorder", "bytes", "flate2", @@ -244,8 +244,8 @@ version = "0.1.0" dependencies = [ "azalea-block", "azalea-buf", + "azalea-chat", "azalea-core", - "azalea-entity", "azalea-nbt", "log", "nohash-hasher", @@ -283,6 +283,7 @@ version = "0.1.0" dependencies = [ "azalea-client", "azalea-core", + "azalea-physics", "azalea-protocol", "tokio", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 48755155..0ba31604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ members = [ "azalea-world", "azalea-language", "azalea-block", - "azalea-entity", "azalea-buf", + "azalea-physics", "azalea-registry", ] @@ -29,5 +29,13 @@ opt-level = 3 opt-level = 3 [profile.dev.package.aes] opt-level = 3 +[profile.dev.package.crypto-common] +opt-level = 3 +[profile.dev.package.generic-array] +opt-level = 3 +[profile.dev.package.typenum] +opt-level = 3 +[profile.dev.package.inout] +opt-level = 3 [profile.dev.package.flate2] opt-level = 3 diff --git a/README.md b/README.md index 52637efe..fd838dc8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ A collection of Rust crates primarily for creating Minecraft bots. ## ⚠️ Azalea is still super unfinished, you probably shouldn't use it -I named this Azalea because it sounds like a cool word and this is a cool library. This project was heavily inspired by PrismarineJS. +I named this Azalea because it sounds like a cool word and this is a cool library. +This project was heavily inspired by [PrismarineJS](https://github.com/PrismarineJS). ## Why diff --git a/azalea-block/Cargo.toml b/azalea-block/Cargo.toml index 71d7149f..edeba385 100644 --- a/azalea-block/Cargo.toml +++ b/azalea-block/Cargo.toml @@ -9,7 +9,3 @@ version = "0.1.0" [dependencies] block-macros = {path = "./block-macros"} - -[features] -default = ["trait"] -trait = [] diff --git a/azalea-block/block-macros/src/lib.rs b/azalea-block/block-macros/src/lib.rs index e6585600..ac61912f 100644 --- a/azalea-block/block-macros/src/lib.rs +++ b/azalea-block/block-macros/src/lib.rs @@ -426,7 +426,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { quote! { BlockState::#block_name_pascal_case } }; - if cfg!(feature = "trait") { let block_struct = quote! { #[derive(Debug)] pub struct #block_struct_name { @@ -459,7 +458,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { block_structs.extend(block_struct); } - } let last_state_id = (state_id - 1) as u32; let mut generated = quote! { @@ -480,7 +478,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } }; - if cfg!(feature = "trait") { generated.extend(quote! { #block_structs @@ -494,7 +491,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } } }); - } generated.into() } diff --git a/azalea-block/src/behavior.rs b/azalea-block/src/behavior.rs index 949f3bd8..db357632 100644 --- a/azalea-block/src/behavior.rs +++ b/azalea-block/src/behavior.rs @@ -1,6 +1,7 @@ #[derive(Default)] pub struct BlockBehavior { pub has_collision: bool, + pub friction: f32, } impl BlockBehavior { @@ -9,4 +10,10 @@ impl BlockBehavior { self.has_collision = false; self } + + #[inline] + pub fn friction(mut self, friction: f32) -> Self { + self.friction = friction; + self + } } diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index 9320a2a5..3eb86a90 100644 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -1,8 +1,6 @@ -#[cfg(feature = "trait")] mod behavior; mod blocks; -#[cfg(feature = "trait")] pub use behavior::BlockBehavior; pub use blocks::*; diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs index a294eb19..c2ac7e14 100755 --- a/azalea-brigadier/src/lib.rs +++ b/azalea-brigadier/src/lib.rs @@ -8,3 +8,4 @@ pub mod modifier; pub mod parse_results; pub mod string_reader; pub mod tree; +pub mod suggestion; diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs index ab3a5964..4c9a9547 100644 --- a/azalea-brigadier/src/suggestion/mod.rs +++ b/azalea-brigadier/src/suggestion/mod.rs @@ -1,6 +1,7 @@ mod suggestions; use crate::{context::StringRange, message::Message}; +pub use suggestions::*; #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct Suggestion { diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index 6c325039..1fe361f1 100644 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -13,18 +13,18 @@ impl Suggestions { if input.is_empty() { return Suggestions::default(); } else if input.len() == 1 { - return input[0]; + return input[0].clone(); }; - let texts = HashSet::new(); + let mut texts = HashSet::new(); for suggestions in input { - texts.extend(suggestions.suggestions); + texts.extend(suggestions.suggestions.clone()); } - Suggestions::create(command, texts) + Suggestions::create(command, &texts) } - pub fn create(command: &str, suggestions: &[Suggestions]) { + pub fn create(command: &str, suggestions: &HashSet) -> Self { if suggestions.is_empty() { return Suggestions::default(); }; @@ -34,5 +34,16 @@ impl Suggestions { start = suggestion.range.start().min(start); end = suggestion.range.end().max(end); } + let range = StringRange::new(start, end); + let mut texts = HashSet::new(); + for suggestion in suggestions { + texts.insert(suggestion.expand(command, &range)); + } + let mut sorted: Vec = texts.into_iter().collect(); + sorted.sort_by(|a, b| a.text.cmp(&b.text)); + Suggestions { + range, + suggestions: sorted, + } } } diff --git a/azalea-buf/src/definitions.rs b/azalea-buf/src/definitions.rs index cfe1bd8a..ab75267b 100644 --- a/azalea-buf/src/definitions.rs +++ b/azalea-buf/src/definitions.rs @@ -53,4 +53,4 @@ impl McBufWritable for BitSet { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { self.data.write_into(buf) } -} +} \ No newline at end of file diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 50ba09e2..9fa32b75 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -7,12 +7,13 @@ version = "0.1.0" [dependencies] anyhow = "1.0.59" -azalea-auth = {path = "../azalea-auth"} -azalea-core = {path = "../azalea-core"} -azalea-crypto = {path = "../azalea-crypto"} -azalea-entity = {path = "../azalea-entity"} -azalea-protocol = {path = "../azalea-protocol"} -azalea-world = {path = "../azalea-world"} +azalea-auth = { path = "../azalea-auth" } +azalea-core = { path = "../azalea-core" } +azalea-crypto = { path = "../azalea-crypto" } +azalea-physics = { path = "../azalea-physics" } +azalea-protocol = { path = "../azalea-protocol" } +azalea-world = { path = "../azalea-world" } +azalea-block = { path = "../azalea-block" } thiserror = "^1.0.32" -tokio = {version = "^1.19.2", features = ["sync"]} +tokio = { version = "^1.19.2", features = ["sync"] } uuid = "^1.1.2" diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index fcb624b4..c495bc5c 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,7 +1,7 @@ use crate::{Account, Player}; use azalea_auth::game_profile::GameProfile; -use azalea_core::{ChunkPos, EntityPos, PositionDelta, PositionDeltaTrait, ResourceLocation}; -use azalea_entity::Entity; +use azalea_block::BlockState; +use azalea_core::{ChunkPos, ResourceLocation, Vec3}; use azalea_protocol::{ connect::{Connection, ConnectionError}, packets::{ @@ -11,7 +11,7 @@ use azalea_protocol::{ serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket, serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, serverbound_keep_alive_packet::ServerboundKeepAlivePacket, - serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot, + serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, ClientboundGamePacket, ServerboundGamePacket, }, handshake::client_intention_packet::ClientIntentionPacket, @@ -25,6 +25,7 @@ use azalea_protocol::{ read::ReadPacketError, resolver, ServerAddress, }; +use azalea_world::entity::EntityData; use azalea_world::Dimension; use std::{ fmt::Debug, @@ -66,8 +67,10 @@ pub struct Client { game_profile: GameProfile, pub conn: Arc>>, pub player: Arc>, - pub dimension: Arc>>, - // game_loop + pub dimension: Arc>, + + /// Minecraft only sends a movement packet either after 20 ticks or if the player moved enough. This is that tick counter. + pub position_remainder: u32, } /// Whether we should ignore errors when decoding packets. @@ -181,7 +184,9 @@ impl Client { game_profile, conn, player: Arc::new(Mutex::new(Player::default())), - dimension: Arc::new(Mutex::new(None)), + dimension: Arc::new(Mutex::new(Dimension::default())), + + position_remainder: 0, }; // just start up the game loop and we're ready! @@ -298,16 +303,10 @@ impl Client { let mut dimension_lock = client.dimension.lock().unwrap(); // the 16 here is our render distance // i'll make this an actual setting later - *dimension_lock = Some(Dimension::new(16, height, min_y)); + *dimension_lock = Dimension::new(16, height, min_y); - let entity = - Entity::new(p.player_id, client.game_profile.uuid, EntityPos::default()); - dimension_lock - .as_mut() - .expect( - "Dimension doesn't exist! We should've gotten a login packet by now.", - ) - .add_entity(entity); + let entity = EntityData::new(client.game_profile.uuid, Vec3::default()); + dimension_lock.add_entity(p.player_id, entity); let mut player_lock = client.player.lock().unwrap(); @@ -368,42 +367,42 @@ impl Client { println!("Got player position packet {:?}", p); let (new_pos, y_rot, x_rot) = { - let player_lock = client.player.lock().unwrap(); - let player_entity_id = player_lock.entity_id; - drop(player_lock); + let player_entity_id = { + let player_lock = client.player.lock().unwrap(); + player_lock.entity_id + }; let mut dimension_lock = client.dimension.lock().unwrap(); - let dimension = dimension_lock.as_mut().unwrap(); - let player_entity = dimension - .mut_entity_by_id(player_entity_id) + let mut player_entity = dimension_lock + .entity_mut(player_entity_id) .expect("Player entity doesn't exist"); - let delta_movement = &player_entity.delta; + 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) + player_entity.last_pos.x += p.x; + (delta_movement.x, player_entity.pos().x + p.x) } else { - player_entity.old_pos.x = p.x; + player_entity.last_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) + player_entity.last_pos.y += p.y; + (delta_movement.y, player_entity.pos().y + p.y) } else { - player_entity.old_pos.y = p.y; + player_entity.last_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) + player_entity.last_pos.z += p.z; + (delta_movement.z, player_entity.pos().z + p.z) } else { - player_entity.old_pos.z = p.z; + player_entity.last_pos.z = p.z; (0.0, p.z) }; @@ -416,21 +415,21 @@ impl Client { x_rot += player_entity.y_rot; } - player_entity.delta = PositionDelta { - xa: delta_x, - ya: delta_y, - za: delta_z, + player_entity.delta = Vec3 { + x: delta_x, + y: delta_y, + z: delta_z, }; player_entity.set_rotation(y_rot, x_rot); // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means // so investigate that ig - let new_pos = EntityPos { + let new_pos = Vec3 { x: new_pos_x, y: new_pos_y, z: new_pos_z, }; - dimension - .move_entity(player_entity_id, new_pos) + dimension_lock + .set_entity_pos(player_entity_id, new_pos) .expect("The player entity should always exist"); (new_pos, y_rot, x_rot) @@ -442,7 +441,7 @@ impl Client { .await?; conn_lock .write( - ServerboundMovePlayerPacketPosRot { + ServerboundMovePlayerPosRotPacket { x: new_pos.x, y: new_pos.y, z: new_pos.z, @@ -463,8 +462,6 @@ impl Client { client .dimension .lock()? - .as_mut() - .unwrap() .update_view_center(&ChunkPos::new(p.x, p.z)); } ClientboundGamePacket::ClientboundLevelChunkWithLightPacket(p) => { @@ -475,8 +472,6 @@ impl Client { client .dimension .lock()? - .as_mut() - .expect("Dimension 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(); } @@ -485,13 +480,8 @@ impl Client { } ClientboundGamePacket::ClientboundAddEntityPacket(p) => { println!("Got add entity packet {:?}", p); - let entity = Entity::from(p); - client - .dimension - .lock()? - .as_mut() - .expect("Dimension doesn't exist! We should've gotten a login packet by now.") - .add_entity(entity); + let entity = EntityData::from(p); + client.dimension.lock()?.add_entity(p.id, entity); } ClientboundGamePacket::ClientboundSetEntityDataPacket(_p) => { // println!("Got set entity data packet {:?}", p); @@ -507,13 +497,8 @@ impl Client { } ClientboundGamePacket::ClientboundAddPlayerPacket(p) => { println!("Got add player packet {:?}", p); - let entity = Entity::from(p); - client - .dimension - .lock()? - .as_mut() - .expect("Dimension doesn't exist! We should've gotten a login packet by now.") - .add_entity(entity); + let entity = EntityData::from(p); + client.dimension.lock()?.add_entity(p.id, entity); } ClientboundGamePacket::ClientboundInitializeBorderPacket(p) => { println!("Got initialize border packet {:?}", p); @@ -535,12 +520,11 @@ impl Client { } ClientboundGamePacket::ClientboundTeleportEntityPacket(p) => { let mut dimension_lock = client.dimension.lock()?; - let dimension = dimension_lock.as_mut().unwrap(); - dimension - .move_entity( + dimension_lock + .set_entity_pos( p.id, - EntityPos { + Vec3 { x: p.x, y: p.y, z: p.z, @@ -556,17 +540,15 @@ impl Client { } ClientboundGamePacket::ClientboundMoveEntityPosPacket(p) => { let mut dimension_lock = client.dimension.lock()?; - let dimension = dimension_lock.as_mut().unwrap(); - dimension + dimension_lock .move_entity_with_delta(p.entity_id, &p.delta) .map_err(|e| HandleError::Other(e.into()))?; } ClientboundGamePacket::ClientboundMoveEntityPosRotPacket(p) => { let mut dimension_lock = client.dimension.lock()?; - let dimension = dimension_lock.as_mut().unwrap(); - dimension + dimension_lock .move_entity_with_delta(p.entity_id, &p.delta) .map_err(|e| HandleError::Other(e.into()))?; } @@ -603,6 +585,16 @@ impl Client { ClientboundGamePacket::ClientboundBlockUpdatePacket(p) => { println!("Got block update packet {:?}", p); // TODO: update world + let mut dimension = client.dimension.lock()?; + // dimension.get_block_state(pos) + if let Ok(block_state) = BlockState::try_from(p.block_state) { + dimension.set_block_state(&p.pos, block_state); + } else { + eprintln!( + "Non-existent block state for block update packet {}", + p.block_state + ); + } } ClientboundGamePacket::ClientboundAnimatePacket(p) => { println!("Got animate packet {:?}", p); @@ -626,28 +618,107 @@ impl Client { ClientboundGamePacket::ClientboundUpdateMobEffectPacket(p) => { println!("Got update mob effect packet {:?}", p); } - _ => panic!("Unexpected packet {:?}", packet), + ClientboundGamePacket::ClientboundAddExperienceOrbPacket(_) => {} + ClientboundGamePacket::ClientboundAwardStatsPacket(_) => {} + ClientboundGamePacket::ClientboundBlockChangedAckPacket(_) => {} + ClientboundGamePacket::ClientboundBlockDestructionPacket(_) => {} + ClientboundGamePacket::ClientboundBlockEntityDataPacket(_) => {} + ClientboundGamePacket::ClientboundBlockEventPacket(_) => {} + ClientboundGamePacket::ClientboundBossEventPacket(_) => {} + ClientboundGamePacket::ClientboundChatPreviewPacket(_) => {} + ClientboundGamePacket::ClientboundCommandSuggestionsPacket(_) => {} + ClientboundGamePacket::ClientboundContainerSetDataPacket(_) => {} + ClientboundGamePacket::ClientboundContainerSetSlotPacket(_) => {} + ClientboundGamePacket::ClientboundCooldownPacket(_) => {} + ClientboundGamePacket::ClientboundCustomChatCompletionsPacket(_) => {} + ClientboundGamePacket::ClientboundCustomSoundPacket(_) => {} + ClientboundGamePacket::ClientboundDeleteChatPacket(_) => {} + ClientboundGamePacket::ClientboundExplodePacket(_) => {} + ClientboundGamePacket::ClientboundForgetLevelChunkPacket(_) => {} + ClientboundGamePacket::ClientboundHorseScreenOpenPacket(_) => {} + ClientboundGamePacket::ClientboundMapItemDataPacket(_) => {} + ClientboundGamePacket::ClientboundMerchantOffersPacket(_) => {} + ClientboundGamePacket::ClientboundMoveVehiclePacket(_) => {} + ClientboundGamePacket::ClientboundOpenBookPacket(_) => {} + ClientboundGamePacket::ClientboundOpenScreenPacket(_) => {} + ClientboundGamePacket::ClientboundOpenSignEditorPacket(_) => {} + ClientboundGamePacket::ClientboundPingPacket(_) => {} + ClientboundGamePacket::ClientboundPlaceGhostRecipePacket(_) => {} + ClientboundGamePacket::ClientboundPlayerChatHeaderPacket(_) => {} + ClientboundGamePacket::ClientboundPlayerCombatEndPacket(_) => {} + ClientboundGamePacket::ClientboundPlayerCombatEnterPacket(_) => {} + ClientboundGamePacket::ClientboundPlayerCombatKillPacket(_) => {} + ClientboundGamePacket::ClientboundPlayerLookAtPacket(_) => {} + ClientboundGamePacket::ClientboundRemoveMobEffectPacket(_) => {} + ClientboundGamePacket::ClientboundResourcePackPacket(_) => {} + ClientboundGamePacket::ClientboundRespawnPacket(_) => {} + ClientboundGamePacket::ClientboundSelectAdvancementsTabPacket(_) => {} + ClientboundGamePacket::ClientboundSetActionBarTextPacket(_) => {} + ClientboundGamePacket::ClientboundSetBorderCenterPacket(_) => {} + ClientboundGamePacket::ClientboundSetBorderLerpSizePacket(_) => {} + ClientboundGamePacket::ClientboundSetBorderSizePacket(_) => {} + ClientboundGamePacket::ClientboundSetBorderWarningDelayPacket(_) => {} + ClientboundGamePacket::ClientboundSetBorderWarningDistancePacket(_) => {} + ClientboundGamePacket::ClientboundSetCameraPacket(_) => {} + ClientboundGamePacket::ClientboundSetChunkCacheRadiusPacket(_) => {} + ClientboundGamePacket::ClientboundSetDisplayChatPreviewPacket(_) => {} + ClientboundGamePacket::ClientboundSetDisplayObjectivePacket(_) => {} + ClientboundGamePacket::ClientboundSetEntityMotionPacket(_) => {} + ClientboundGamePacket::ClientboundSetObjectivePacket(_) => {} + ClientboundGamePacket::ClientboundSetPassengersPacket(_) => {} + ClientboundGamePacket::ClientboundSetPlayerTeamPacket(_) => {} + ClientboundGamePacket::ClientboundSetScorePacket(_) => {} + ClientboundGamePacket::ClientboundSetSimulationDistancePacket(_) => {} + ClientboundGamePacket::ClientboundSetSubtitleTextPacket(_) => {} + ClientboundGamePacket::ClientboundSetTitleTextPacket(_) => {} + ClientboundGamePacket::ClientboundSetTitlesAnimationPacket(_) => {} + ClientboundGamePacket::ClientboundSoundEntityPacket(_) => {} + ClientboundGamePacket::ClientboundStopSoundPacket(_) => {} + ClientboundGamePacket::ClientboundTabListPacket(_) => {} + ClientboundGamePacket::ClientboundTagQueryPacket(_) => {} + ClientboundGamePacket::ClientboundTakeItemEntityPacket(_) => {} } Ok(()) } /// Runs game_tick every 50 milliseconds. - async fn game_tick_loop(client: Client, tx: UnboundedSender) { + async fn game_tick_loop(mut client: Client, tx: UnboundedSender) { 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(&client, &tx).await; + Self::game_tick(&mut client, &tx).await; } } /// Runs every 50 milliseconds. - async fn game_tick(client: &Client, tx: &UnboundedSender) { - if client.dimension.lock().unwrap().is_none() { - return; + async fn game_tick(client: &mut Client, tx: &UnboundedSender) { + // return if there's no chunk at the player's position + { + let dimension_lock = client.dimension.lock().unwrap(); + let player_lock = client.player.lock().unwrap(); + let player_entity = player_lock.entity(&dimension_lock); + let player_entity = if let Some(player_entity) = player_entity { + player_entity + } else { + return; + }; + let player_chunk_pos: ChunkPos = player_entity.pos().into(); + if dimension_lock[&player_chunk_pos].is_none() { + return; + } } + + // TODO: if we're a passenger, send the required packets + + if let Err(e) = client.send_position().await { + println!("Error sending position: {:?}", e); + } + + // TODO: minecraft does ambient sounds here + tx.send(Event::GameTick).unwrap(); } } diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index 5f9533be..df2af9d8 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -1,6 +1,13 @@ use crate::Client; -use azalea_core::EntityPos; -use azalea_protocol::packets::game::serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot; +use azalea_core::Vec3; +use azalea_physics::collision::{MovableEntity, MoverType}; +use azalea_physics::HasPhysics; +use azalea_protocol::packets::game::{ + serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, + serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, + serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket, + serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket, +}; use azalea_world::MoveEntityError; use thiserror::Error; @@ -12,45 +19,149 @@ pub enum MovePlayerError { Io(#[from] std::io::Error), } -impl Client { - /// Set the client's position to the given coordinates. - pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), MovePlayerError> { - { - let mut dimension_lock = self.dimension.lock().unwrap(); - let dimension = dimension_lock.as_mut().unwrap(); +impl From for MovePlayerError { + fn from(err: MoveEntityError) -> Self { + match err { + MoveEntityError::EntityDoesNotExist => MovePlayerError::PlayerNotInWorld, + } + } +} +impl Client { + /// This gets called every tick. + pub async fn send_position(&mut self) -> Result<(), MovePlayerError> { + let packet = { let player_lock = self.player.lock().unwrap(); - let player_id = if let Some(player_lock) = player_lock.entity(dimension) { - player_lock.id + let mut dimension_lock = self.dimension.lock().unwrap(); + + let mut player_entity = player_lock + .entity_mut(&mut dimension_lock) + .expect("Player must exist"); + let player_pos = player_entity.pos(); + let player_old_pos = player_entity.last_pos; + + // TODO: send sprinting and sneaking packets here if they changed + + // TODO: the camera being able to be controlled by other entities isn't implemented yet + // if !self.is_controlled_camera() { return }; + + let x_delta = player_pos.x - player_old_pos.x; + let y_delta = player_pos.y - player_old_pos.y; + let z_delta = player_pos.z - player_old_pos.z; + let y_rot_delta = (player_entity.y_rot - player_entity.y_rot_last) as f64; + let x_rot_delta = (player_entity.x_rot - player_entity.x_rot_last) as f64; + + self.position_remainder += 1; + + // boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) > Mth.square(2.0E-4D) || this.positionReminder >= 20; + let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2)) + > 2.0e-4f64.powi(2)) + || self.position_remainder >= 20; + let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0; + + // if self.is_passenger() { + // TODO: posrot packet for being a passenger + // } + let packet = if sending_position && sending_rotation { + Some( + ServerboundMovePlayerPosRotPacket { + x: player_pos.x, + y: player_pos.y, + z: player_pos.z, + x_rot: player_entity.x_rot, + y_rot: player_entity.y_rot, + on_ground: player_entity.on_ground, + } + .get(), + ) + } else if sending_position { + Some( + ServerboundMovePlayerPosPacket { + x: player_pos.x, + y: player_pos.y, + z: player_pos.z, + on_ground: player_entity.on_ground, + } + .get(), + ) + } else if sending_rotation { + Some( + ServerboundMovePlayerRotPacket { + x_rot: player_entity.x_rot, + y_rot: player_entity.y_rot, + on_ground: player_entity.on_ground, + } + .get(), + ) + } else if player_entity.last_on_ground != player_entity.on_ground { + Some( + ServerboundMovePlayerStatusOnlyPacket { + on_ground: player_entity.on_ground, + } + .get(), + ) } else { - return Err(MovePlayerError::PlayerNotInWorld); + None }; - match dimension.move_entity(player_id, new_pos) { - Ok(_) => Ok(()), - Err(e) => match e { - MoveEntityError::EntityDoesNotExist => Err(MovePlayerError::PlayerNotInWorld), - }, - }?; - } + if sending_position { + player_entity.last_pos = *player_entity.pos(); + self.position_remainder = 0; + } + if sending_rotation { + player_entity.y_rot_last = player_entity.y_rot; + player_entity.x_rot_last = player_entity.x_rot; + } - self.conn - .lock() - .await - .write( - ServerboundMovePlayerPacketPosRot { - x: new_pos.x, - y: new_pos.y, - z: new_pos.z, - x_rot: 0.0, - y_rot: 0.0, - on_ground: false, - } - .get(), - ) - .await?; + player_entity.last_on_ground = player_entity.on_ground; + // minecraft checks for autojump here, but also autojump is bad so + + packet + }; + + if let Some(packet) = packet { + self.conn.lock().await.write(packet).await?; + } Ok(()) } + + // Set our current position to the provided Vec3, potentially clipping through blocks. + pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> { + let player_lock = self.player.lock().unwrap(); + let mut dimension_lock = self.dimension.lock().unwrap(); + + dimension_lock.set_entity_pos(player_lock.entity_id, new_pos)?; + + Ok(()) + } + + pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> { + let mut dimension_lock = self.dimension.lock().unwrap(); + let player = self.player.lock().unwrap(); + + let mut entity = player + .entity_mut(&mut dimension_lock) + .ok_or(MovePlayerError::PlayerNotInWorld)?; + println!( + "move entity bounding box: {} {:?}", + entity.id, entity.bounding_box + ); + + entity.move_colliding(&MoverType::Own, movement)?; + + Ok(()) + } + + pub fn ai_step(&mut self) { + let player_lock = self.player.lock().unwrap(); + let mut dimension_lock = self.dimension.lock().unwrap(); + + let mut player_entity = player_lock + .entity_mut(&mut dimension_lock) + .expect("Player must exist"); + + player_entity.ai_step(); + } } diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index 6c093517..11651b9c 100644 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -1,4 +1,4 @@ -use azalea_entity::Entity; +use azalea_world::entity::{EntityMut, EntityRef}; use azalea_world::Dimension; use uuid::Uuid; @@ -7,6 +7,7 @@ pub trait DimensionHaver { fn dimension(&self) -> &Dimension; } +/// A player in the dimension or tab list. #[derive(Default, Debug)] pub struct Player { /// The player's uuid. @@ -16,10 +17,14 @@ pub struct Player { } impl Player { - /// Get the entity of the player in the world. - pub fn entity<'a>(&self, world: &'a Dimension) -> Option<&'a Entity> { - // world.entity_by_uuid(&self.uuid) - world.entity_by_id(self.entity_id) + /// Get a reference to the entity of the player in the world. + pub fn entity<'d>(&'d self, dimension: &'d Dimension) -> Option { + dimension.entity(self.entity_id) + } + + /// Get a mutable reference to the entity of the player in the world. + pub fn entity_mut<'d>(&'d self, dimension: &'d mut Dimension) -> Option { + dimension.entity_mut(self.entity_id) } pub fn set_uuid(&mut self, uuid: Uuid) { diff --git a/azalea-core/README.md b/azalea-core/README.md new file mode 100644 index 00000000..7d826076 --- /dev/null +++ b/azalea-core/README.md @@ -0,0 +1,3 @@ +# Azalea Core + +Miscellaneous things in Azalea. diff --git a/azalea-core/src/aabb.rs b/azalea-core/src/aabb.rs new file mode 100644 index 00000000..40230fe4 --- /dev/null +++ b/azalea-core/src/aabb.rs @@ -0,0 +1,447 @@ +use crate::{Axis, BlockHitResult, BlockPos, Direction, PositionXYZ, Vec3}; + +pub const EPSILON: f64 = 1.0E-7; + +/// A rectangular prism with a starting and ending point. +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub struct AABB { + pub min_x: f64, + pub min_y: f64, + pub min_z: f64, + + pub max_x: f64, + pub max_y: f64, + pub max_z: f64, +} + +impl AABB { + pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB { + let mut min_x = self.min_x; + let mut min_y = self.min_y; + let mut min_z = self.min_z; + + let mut max_x = self.max_x; + let mut max_y = self.max_y; + let mut max_z = self.max_z; + + if x < 0.0 { + min_x -= x; + } else if x > 0.0 { + max_x -= x; + } + + if y < 0.0 { + min_y -= y; + } else if y > 0.0 { + max_y -= y; + } + + if z < 0.0 { + min_z -= z; + } else if z > 0.0 { + max_z -= z; + } + + AABB { + min_x, + min_y, + min_z, + + max_x, + max_y, + max_z, + } + } + + pub fn expand_towards(&self, other: &Vec3) -> AABB { + let mut min_x = self.min_x; + let mut min_y = self.min_y; + let mut min_z = self.min_z; + + let mut max_x = self.max_x; + let mut max_y = self.max_y; + let mut max_z = self.max_z; + + if other.x < 0.0 { + min_x += other.x; + } else if other.x > 0.0 { + max_x += other.x; + } + + if other.y < 0.0 { + min_y += other.y; + } else if other.y > 0.0 { + max_y += other.y; + } + + if other.z < 0.0 { + min_z += other.z; + } else if other.z > 0.0 { + max_z += other.z; + } + + AABB { + min_x, + min_y, + min_z, + + max_x, + max_y, + max_z, + } + } + + pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB { + let min_x = self.min_x - x; + let min_y = self.min_y - y; + let min_z = self.min_z - z; + + let max_x = self.max_x + x; + let max_y = self.max_y + y; + let max_z = self.max_z + z; + + AABB { + min_x, + min_y, + min_z, + + max_x, + max_y, + max_z, + } + } + + pub fn intersect(&self, other: &AABB) -> AABB { + let min_x = self.min_x.max(other.min_x); + let min_y = self.min_y.max(other.min_y); + let min_z = self.min_z.max(other.min_z); + + let max_x = self.max_x.min(other.max_x); + let max_y = self.max_y.min(other.max_y); + let max_z = self.max_z.min(other.max_z); + + AABB { + min_x, + min_y, + min_z, + + max_x, + max_y, + max_z, + } + } + + pub fn minmax(&self, other: &AABB) -> AABB { + let min_x = self.min_x.min(other.min_x); + let min_y = self.min_y.min(other.min_y); + let min_z = self.min_z.min(other.min_z); + + let max_x = self.max_x.max(other.max_x); + let max_y = self.max_y.max(other.max_y); + let max_z = self.max_z.max(other.max_z); + + AABB { + min_x, + min_y, + min_z, + + max_x, + max_y, + max_z, + } + } + + pub fn move_relative(&self, x: f64, y: f64, z: f64) -> AABB { + AABB { + min_x: self.min_x + x, + min_y: self.min_y + y, + min_z: self.min_z + z, + + max_x: self.max_x + x, + max_y: self.max_y + y, + max_z: self.max_z + z, + } + } + + pub fn intersects_aabb(&self, other: &AABB) -> bool { + self.min_x < other.max_x + && self.max_x > other.min_x + && self.min_y < other.max_y + && self.max_y > other.min_y + && self.min_z < other.max_z + && self.max_z > other.min_z + } + pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool { + self.intersects_aabb(&AABB { + min_x: other.x.min(other2.x), + min_y: other.y.min(other2.y), + min_z: other.z.min(other2.z), + + max_x: other.x.max(other2.x), + max_y: other.y.max(other2.y), + max_z: other.z.max(other2.z), + }) + } + + pub fn contains(&self, x: f64, y: f64, z: f64) -> bool { + x >= self.min_x + && x < self.max_x + && y >= self.min_y + && y < self.max_y + && z >= self.min_z + && z < self.max_z + } + + pub fn size(&self) -> f64 { + let x = self.get_size(Axis::X); + let y = self.get_size(Axis::Y); + let z = self.get_size(Axis::Z); + (x + y + z) / 3.0 + } + + pub fn get_size(&self, axis: Axis) -> f64 { + axis.choose( + self.max_x - self.min_x, + self.max_y - self.min_y, + self.max_z - self.min_z, + ) + } + + pub fn deflate(&mut self, x: f64, y: f64, z: f64) -> AABB { + self.inflate(-x, -y, -z) + } + + pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option { + let mut t = [1.0]; + let x = max.x - min.x; + let y = max.y - min.y; + let z = max.z - min.z; + let dir = self.get_direction(self, min, &mut t, None, &Vec3 { x, y, z }); + if dir.is_none() { + return None; + } + let t = t[0]; + Some(min.add(t * x, t * y, t * z)) + } + + pub fn clip_iterable( + &self, + boxes: &Vec, + from: &Vec3, + to: &Vec3, + pos: &BlockPos, + ) -> Option { + let mut t = [1.0]; + let mut dir = None; + let x = to.x - from.x; + let y = to.y - from.y; + let z = to.z - from.z; + + for aabb in boxes { + dir = self.get_direction(aabb, from, &mut t, dir, &Vec3 { x, y, z }); + } + if dir.is_none() { + return None; + } + let t = t[0]; + Some(BlockHitResult { + location: from.add(t * x, t * y, t * z), + direction: dir.unwrap(), + block_pos: *pos, + inside: false, + miss: false, + }) + } + + fn get_direction( + &self, + aabb: &AABB, + from: &Vec3, + t: &mut [f64], + dir: Option, + delta: &Vec3, + ) -> Option { + if delta.x > EPSILON { + return self.clip_point( + t, + dir, + delta, + aabb.min_x, + aabb.min_y, + aabb.max_y, + aabb.min_z, + aabb.max_z, + Direction::West, + from, + ); + } else if delta.x < -EPSILON { + return self.clip_point( + t, + dir, + delta, + aabb.max_x, + aabb.min_y, + aabb.max_y, + aabb.min_z, + aabb.max_z, + Direction::East, + from, + ); + } + + if delta.y > EPSILON { + return self.clip_point( + t, + dir, + &Vec3 { + x: delta.y, + y: delta.z, + z: delta.x, + }, + aabb.min_y, + aabb.min_z, + aabb.max_z, + aabb.min_x, + aabb.max_x, + Direction::Down, + &Vec3 { + x: from.y, + y: from.z, + z: from.x, + }, + ); + } else if delta.y < -EPSILON { + return self.clip_point( + t, + dir, + &Vec3 { + x: delta.y, + y: delta.z, + z: delta.x, + }, + aabb.max_y, + aabb.min_z, + aabb.max_z, + aabb.min_x, + aabb.max_x, + Direction::Up, + &Vec3 { + x: from.y, + y: from.z, + z: from.x, + }, + ); + } + + if delta.z > EPSILON { + return self.clip_point( + t, + dir, + &Vec3 { + x: delta.z, + y: delta.x, + z: delta.y, + }, + aabb.min_z, + aabb.min_x, + aabb.max_x, + aabb.min_y, + aabb.max_y, + Direction::North, + &Vec3 { + x: from.z, + y: from.x, + z: from.y, + }, + ); + } else if delta.z < -EPSILON { + return self.clip_point( + t, + dir, + &Vec3 { + x: delta.z, + y: delta.x, + z: delta.y, + }, + aabb.max_z, + aabb.min_x, + aabb.max_x, + aabb.min_y, + aabb.max_y, + Direction::South, + &Vec3 { + x: from.z, + y: from.x, + z: from.y, + }, + ); + } + + dir + } + + fn clip_point( + &self, + t: &mut [f64], + approach_dir: Option, + delta: &Vec3, + begin: f64, + min_x: f64, + max_x: f64, + min_z: f64, + max_z: f64, + result_dir: Direction, + start: &Vec3, + ) -> Option { + let t_x = (begin - start.x) / delta.x; + let t_y = (start.y + t_x) / delta.y; + let t_z = (start.z + t_x) / delta.z; + if 0.0 < t_x + && t_x < t[0] + && min_x - EPSILON < t_y + && t_y < max_x + EPSILON + && min_z - EPSILON < t_z + && t_z < max_z + EPSILON + { + t[0] = t_x; + Some(result_dir) + } else { + approach_dir + } + } + + pub fn has_nan(&self) -> bool { + self.min_x.is_nan() + || self.min_y.is_nan() + || self.min_z.is_nan() + || self.max_x.is_nan() + || self.max_y.is_nan() + || self.max_z.is_nan() + } + + pub fn get_center(&self) -> Vec3 { + Vec3 { + x: (self.min_x + self.max_x) / 2.0, + y: (self.min_y + self.max_y) / 2.0, + z: (self.min_z + self.max_z) / 2.0, + } + } + + pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> AABB { + AABB { + min_x: center.x - dx / 2.0, + min_y: center.y - dy / 2.0, + min_z: center.z - dz / 2.0, + max_x: center.x + dx / 2.0, + max_y: center.y + dy / 2.0, + max_z: center.z + dz / 2.0, + } + } + + pub fn max(&self, axis: &Axis) -> f64 { + axis.choose(self.max_x, self.max_y, self.max_z) + } + pub fn min(&self, axis: &Axis) -> f64 { + axis.choose(self.min_x, self.min_y, self.min_z) + } +} diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs new file mode 100644 index 00000000..2ffd5657 --- /dev/null +++ b/azalea-core/src/bitset.rs @@ -0,0 +1,58 @@ +use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; +use std::io::{Read, Write}; + +/// Represents Java's BitSet, a list of bits. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct BitSet { + data: Vec, +} + +// the Index trait requires us to return a reference, but we can't do that +impl BitSet { + pub fn new(size: usize) -> Self { + BitSet { + data: vec![0; size.div_ceil(64)], + } + } + + pub fn index(&self, index: usize) -> bool { + (self.data[index / 64] & (1u64 << (index % 64))) != 0 + } +} + +impl McBufReadable for BitSet { + fn read_from(buf: &mut impl Read) -> Result { + Ok(Self { + data: Vec::::read_from(buf)?, + }) + } +} + +impl McBufWritable for BitSet { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + self.data.write_into(buf) + } +} + +impl BitSet { + pub fn set(&mut self, bit_index: usize) { + self.data[bit_index / 64] |= 1u64 << (bit_index % 64); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bitset() { + let mut bitset = BitSet::new(64); + assert_eq!(bitset.index(0), false); + assert_eq!(bitset.index(1), false); + assert_eq!(bitset.index(2), false); + bitset.set(1); + assert_eq!(bitset.index(0), false); + assert_eq!(bitset.index(1), true); + assert_eq!(bitset.index(2), false); + } +} diff --git a/azalea-core/src/block_hit_result.rs b/azalea-core/src/block_hit_result.rs new file mode 100644 index 00000000..80c9b8fc --- /dev/null +++ b/azalea-core/src/block_hit_result.rs @@ -0,0 +1,9 @@ +use crate::{BlockPos, Direction, Vec3}; + +pub struct BlockHitResult { + pub location: Vec3, + pub direction: Direction, + pub block_pos: BlockPos, + pub miss: bool, + pub inside: bool, +} diff --git a/azalea-core/src/cursor3d.rs b/azalea-core/src/cursor3d.rs new file mode 100644 index 00000000..fa265c8a --- /dev/null +++ b/azalea-core/src/cursor3d.rs @@ -0,0 +1,115 @@ +use crate::BlockPos; + +pub struct Cursor3d { + index: usize, + + origin_x: i32, + origin_y: i32, + origin_z: i32, + + width: usize, + height: usize, + depth: usize, + + end: usize, +} + +impl Iterator for Cursor3d { + type Item = CursorIteration; + + fn next(&mut self) -> Option { + if self.index == self.end { + return None; + } + let x = self.index % self.width; + let r = self.index / self.width; + let y = r % self.height; + let z = r / self.height; + self.index += 1; + + let mut iteration_type = 0; + if x == 0 || x == self.width - 1 { + iteration_type += 1; + } + if y == 0 || y == self.height - 1 { + iteration_type += 1; + } + if z == 0 || z == self.depth - 1 { + iteration_type += 1; + } + + Some(CursorIteration { + pos: BlockPos { + x: self.origin_x + x as i32, + y: self.origin_y + y as i32, + z: self.origin_z + z as i32, + }, + iteration_type: iteration_type.into(), + }) + } +} + +#[repr(u8)] +#[derive(Eq, PartialEq, Debug)] +pub enum CursorIterationType { + Inside = 0, + Face = 1, + Edge = 2, + Corner = 3, +} + +pub struct CursorIteration { + pub pos: BlockPos, + pub iteration_type: CursorIterationType, +} + +impl Cursor3d { + pub fn new( + origin_x: i32, + origin_y: i32, + origin_z: i32, + end_x: i32, + end_y: i32, + end_z: i32, + ) -> Self { + println!( + "making cursor3d with origin: {}, {}, {} and end: {}, {}, {}", + origin_x, origin_y, origin_z, end_x, end_y, end_z + ); + let width = (end_x - origin_x + 1) + .try_into() + .expect("Impossible width."); + let height = (end_y - origin_y + 1) + .try_into() + .expect("Impossible height."); + let depth = (end_z - origin_z + 1) + .try_into() + .expect("Impossible depth."); + + Self { + index: 0, + + origin_x, + origin_y, + origin_z, + + width, + height, + depth, + + end: width * height * depth, + } + } +} + +impl From for CursorIterationType { + fn from(value: u8) -> Self { + match value { + 0 => CursorIterationType::Inside, + 1 => CursorIterationType::Face, + 2 => CursorIterationType::Edge, + 3 => CursorIterationType::Corner, + _ => panic!("Invalid iteration type"), + } + } +} diff --git a/azalea-core/src/delta.rs b/azalea-core/src/delta.rs index c0056411..d1f72c17 100644 --- a/azalea-core/src/delta.rs +++ b/azalea-core/src/delta.rs @@ -1,4 +1,6 @@ -use crate::EntityPos; +use std::ops::{Add, AddAssign}; + +use crate::Vec3; pub use azalea_buf::McBuf; pub trait PositionDeltaTrait { @@ -7,13 +9,6 @@ pub trait PositionDeltaTrait { fn z(&self) -> f64; } -#[derive(Clone, Debug, McBuf, Default)] -pub struct PositionDelta { - pub xa: f64, - pub ya: f64, - pub za: f64, -} - /// Only works for up to 8 blocks #[derive(Clone, Debug, McBuf, Default)] pub struct PositionDelta8 { @@ -22,18 +17,6 @@ pub struct PositionDelta8 { pub za: i16, } -impl PositionDeltaTrait for PositionDelta { - fn x(&self) -> f64 { - self.xa - } - fn y(&self) -> f64 { - self.ya - } - fn z(&self) -> f64 { - self.za - } -} - impl PositionDelta8 { #[deprecated] pub fn float(&self) -> (f64, f64, f64) { @@ -57,12 +40,60 @@ impl PositionDeltaTrait for PositionDelta8 { } } -impl EntityPos { - pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> EntityPos { - EntityPos { +impl Vec3 { + pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> Vec3 { + Vec3 { x: self.x + delta.x(), y: self.y + delta.y(), z: self.z + delta.z(), } } + + pub fn length_squared(&self) -> f64 { + self.x * self.x + self.y * self.y + self.z * self.z + } + + pub fn normalize(&self) -> Vec3 { + let length = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z); + if length < 1e-4 { + return Vec3::default(); + } + Vec3 { + x: self.x / length, + y: self.y / length, + z: self.z / length, + } + } + + pub fn multiply(&self, x: f64, y: f64, z: f64) -> Vec3 { + Vec3 { + x: self.x * x, + y: self.y * y, + z: self.z * z, + } + } + pub fn scale(&self, amount: f64) -> Vec3 { + self.multiply(amount, amount, amount) + } +} + +// impl + and += +impl Add for Vec3 { + type Output = Vec3; + + fn add(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } +} + +impl AddAssign for Vec3 { + fn add_assign(&mut self, other: Vec3) { + self.x += other.x; + self.y += other.y; + self.z += other.z; + } } diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs index d3083922..96d20a10 100644 --- a/azalea-core/src/direction.rs +++ b/azalea-core/src/direction.rs @@ -1,5 +1,7 @@ use azalea_buf::McBuf; +use crate::floor_mod; + #[derive(Clone, Copy, Debug, McBuf)] pub enum Direction { Down = 0, @@ -9,3 +11,73 @@ pub enum Direction { West = 4, East = 5, } + +#[derive(Clone, Copy, Debug)] +pub enum Axis { + X = 0, + Y = 1, + Z = 2, +} + +#[derive(Clone, Copy, Debug)] +pub enum AxisCycle { + None = 0, + Forward = 1, + Backward = 2, +} + +impl Axis { + /// Pick x, y, or z from the arguments depending on the axis. + #[inline] + pub fn choose(&self, x: T, y: T, z: T) -> T { + match self { + Axis::X => x, + Axis::Y => y, + Axis::Z => z, + } + } + + pub fn from_ordinal(ordinal: u32) -> Self { + match ordinal { + 0 => Axis::X, + 1 => Axis::Y, + 2 => Axis::Z, + _ => panic!("Invalid ordinal {}", ordinal), + } + } +} + +impl AxisCycle { + pub fn from_ordinal(ordinal: u32) -> Self { + match ordinal { + 0 => Self::None, + 1 => Self::Forward, + 2 => Self::Backward, + _ => panic!("invalid ordinal"), + } + } + pub fn between(axis0: Axis, axis1: Axis) -> Self { + Self::from_ordinal(floor_mod(axis1 as i32 - axis0 as i32, 3)) + } + pub fn inverse(self) -> Self { + match self { + Self::None => Self::None, + Self::Forward => Self::Backward, + Self::Backward => Self::Forward, + } + } + pub fn cycle(self, axis: Axis) -> Axis { + match self { + Self::None => axis, + Self::Forward => Axis::from_ordinal(floor_mod(axis as i32 + 1, 3)), + Self::Backward => Axis::from_ordinal(floor_mod(axis as i32 - 1, 3)), + } + } + pub fn cycle_xyz(self, x: u32, y: u32, z: u32, axis: Axis) -> u32 { + match self { + Self::None => axis.choose(x, y, z), + Self::Forward => axis.choose(z, x, y), + Self::Backward => axis.choose(y, z, x), + } + } +} diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index a1fa1fca..5aca7d52 100755 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -12,16 +12,55 @@ mod game_type; pub use game_type::*; mod slot; -pub use slot::{Slot, SlotData}; +pub use slot::*; mod position; pub use position::*; mod direction; -pub use direction::Direction; +pub use direction::*; mod delta; pub use delta::*; mod particle; pub use particle::*; + +mod cursor3d; +pub use cursor3d::*; + +mod bitset; +pub use bitset::*; + +mod aabb; +pub use aabb::*; + +mod block_hit_result; +pub use block_hit_result::*; + +// java moment +// TODO: add tests and optimize/simplify this +pub fn floor_mod(x: i32, y: u32) -> u32 { + if x < 0 { + y - ((-x) as u32 % y) + } else { + x as u32 % y + } +} + +// TODO: make this generic +pub fn binary_search(mut min: u32, max: u32, predicate: &dyn Fn(u32) -> bool) -> u32 { + let mut diff = max - min; + while diff > 0 { + let diff_mid = diff / 2; + let mid = min + diff_mid; + if predicate(mid) { + diff = diff_mid; + } else { + min = mid + 1; + diff -= diff_mid + 1; + } + } + + min +} diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 7371d530..f54510b5 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -2,13 +2,54 @@ use crate::ResourceLocation; use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use std::{ io::{Read, Write}, - ops::Rem, + ops::{Add, Mul, 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; +pub trait PositionXYZ +where + T: Add + Mul, +{ + fn x(&self) -> T; + fn y(&self) -> T; + fn z(&self) -> T; + + fn set_x(&self, n: T) -> Self; + fn set_y(&self, n: T) -> Self; + fn set_z(&self, n: T) -> Self; + + // hopefully these get optimized + fn add_x(&self, n: T) -> Self + where + Self: Sized, + { + self.set_x(self.x() + n) + } + fn add_y(&self, n: T) -> Self + where + Self: Sized, + { + self.set_y(self.y() + n) + } + fn add_z(&self, n: T) -> Self + where + Self: Sized, + { + self.set_z(self.z() + n) + } + + fn add(&self, x: T, y: T, z: T) -> Self + where + Self: Sized, + { + self.add_x(x).add_y(y).add_z(z) + } + + fn length_sqr(&self) -> T + where + Self: Sized, + { + self.x() * self.x() + self.y() * self.y() + self.z() * self.z() + } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -22,6 +63,10 @@ impl BlockPos { pub fn new(x: i32, y: i32, z: i32) -> Self { BlockPos { x, y, z } } + + pub fn below(&self) -> Self { + self.add(0, -1, 0) + } } impl Rem for BlockPos { @@ -37,25 +82,34 @@ impl Rem for BlockPos { } impl PositionXYZ for BlockPos { - fn add_x(&self, n: i32) -> Self { + fn x(&self) -> i32 { + self.x + } + fn y(&self) -> i32 { + self.y + } + fn z(&self) -> i32 { + self.z + } + fn set_x(&self, n: i32) -> Self { BlockPos { - x: self.x + n, + x: n, y: self.y, z: self.z, } } - fn add_y(&self, n: i32) -> Self { + fn set_y(&self, n: i32) -> Self { BlockPos { x: self.x, - y: self.y + n, + y: n, z: self.z, } } - fn add_z(&self, n: i32) -> Self { + fn set_z(&self, n: i32) -> Self { BlockPos { x: self.x, y: self.y, - z: self.z + n, + z: n, } } } @@ -84,6 +138,9 @@ impl ChunkSectionPos { pub fn new(x: i32, y: i32, z: i32) -> Self { ChunkSectionPos { x, y, z } } + pub fn block_to_section_coord(block: i32) -> i32 { + block >> 4 + } } /// The coordinates of a block inside a chunk. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -123,33 +180,43 @@ pub struct GlobalPos { pub dimension: ResourceLocation, } +/// An exact point in the world. #[derive(Debug, Clone, Copy, Default)] -pub struct EntityPos { +pub struct Vec3 { 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, +impl PositionXYZ for Vec3 { + fn x(&self) -> f64 { + self.x + } + fn y(&self) -> f64 { + self.y + } + fn z(&self) -> f64 { + self.z + } + fn set_x(&self, n: f64) -> Self { + Vec3 { + x: n, y: self.y, z: self.z, } } - fn add_y(&self, n: f64) -> Self { - EntityPos { + fn set_y(&self, n: f64) -> Self { + Vec3 { x: self.x, - y: self.y + n, + y: n, z: self.z, } } - fn add_z(&self, n: f64) -> Self { - EntityPos { + fn set_z(&self, n: f64) -> Self { + Vec3 { x: self.x, y: self.y, - z: self.z + n, + z: n, } } } @@ -208,8 +275,8 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos { } } } -impl From<&EntityPos> for BlockPos { - fn from(pos: &EntityPos) -> Self { +impl From<&Vec3> for BlockPos { + fn from(pos: &Vec3) -> Self { BlockPos { x: pos.x.floor() as i32, y: pos.y.floor() as i32, @@ -218,18 +285,27 @@ impl From<&EntityPos> for BlockPos { } } -impl From<&EntityPos> for ChunkPos { - fn from(pos: &EntityPos) -> Self { +impl From<&Vec3> for ChunkPos { + fn from(pos: &Vec3) -> Self { ChunkPos::from(&BlockPos::from(pos)) } } +const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25 +const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH; +const PACKED_Y_LENGTH: u64 = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH; +const PACKED_X_MASK: u64 = (1 << PACKED_X_LENGTH) - 1; +const PACKED_Y_MASK: u64 = (1 << PACKED_Y_LENGTH) - 1; +const PACKED_Z_MASK: u64 = (1 << PACKED_Z_LENGTH) - 1; +const Z_OFFSET: u64 = PACKED_Y_LENGTH; +const X_OFFSET: u64 = PACKED_Y_LENGTH + PACKED_Z_LENGTH; + impl McBufReadable for BlockPos { fn read_from(buf: &mut impl Read) -> Result { let val = u64::read_from(buf)?; - let x = (val >> 38) as i32; - let y = (val & 0xFFF) as i32; - let z = ((val >> 12) & 0x3FFFFFF) as i32; + let x = (val << 64 - X_OFFSET - PACKED_X_LENGTH >> 64 - PACKED_X_LENGTH) as i32; + let y = (val << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH) as i32; + let z = (val << 64 - Z_OFFSET - PACKED_Z_LENGTH >> 64 - PACKED_Z_LENGTH) as i32; Ok(BlockPos { x, y, z }) } } @@ -256,10 +332,11 @@ impl McBufReadable for ChunkSectionPos { impl McBufWritable for BlockPos { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let data = (((self.x & 0x3FFFFFF) as i64) << 38) - | (((self.z & 0x3FFFFFF) as i64) << 12) - | ((self.y & 0xFFF) as i64); - data.write_into(buf) + let mut val: u64 = 0; + val |= ((self.x as u64) & PACKED_X_MASK) << X_OFFSET; + val |= ((self.y as u64) & PACKED_Y_MASK) << 0; + val |= ((self.z as u64) & PACKED_Z_MASK) << Z_OFFSET; + val.write_into(buf) } } @@ -302,7 +379,7 @@ mod tests { #[test] fn test_from_entity_pos_to_block_pos() { - let entity_pos = EntityPos { + let entity_pos = Vec3 { x: 31.5, y: 80.0, z: -16.1, @@ -313,7 +390,7 @@ mod tests { #[test] fn test_from_entity_pos_to_chunk_pos() { - let entity_pos = EntityPos { + let entity_pos = Vec3 { x: 31.5, y: 80.0, z: -16.1, diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs deleted file mode 100644 index 9436d753..00000000 --- a/azalea-entity/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -mod data; - -use azalea_core::{EntityPos, PositionDelta}; -pub use data::*; -use uuid::Uuid; - -#[derive(Default, Debug)] -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, - old_pos: pos, - delta: PositionDelta::default(), - x_rot: 0.0, - y_rot: 0.0, - } - } - - pub fn pos(&self) -> &EntityPos { - &self.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; - } - - pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { - self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0; - self.x_rot = x_rot % 360.0; - // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so - } -} - -// #[cfg(test)] -// mod tests { -// #[test] -// fn it_works() { -// let result = 2 + 2; -// assert_eq!(result, 4); -// } -// } diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml index 3b334e37..4a03880c 100755 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ahash = "0.7.6" +ahash = "0.8.0" azalea-buf = {path = "../azalea-buf"} byteorder = "1.4.3" flate2 = "1.0.23" diff --git a/azalea-entity/Cargo.toml b/azalea-physics/Cargo.toml similarity index 56% rename from azalea-entity/Cargo.toml rename to azalea-physics/Cargo.toml index 022a3343..856c8ba6 100644 --- a/azalea-entity/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -1,13 +1,11 @@ [package] edition = "2021" -name = "azalea-entity" +name = "azalea-physics" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-buf = {path = "../azalea-buf"} -azalea-chat = {path = "../azalea-chat"} +azalea-block = {path = "../azalea-block"} azalea-core = {path = "../azalea-core"} -azalea-nbt = {path = "../azalea-nbt"} -uuid = "^1.1.2" +azalea-world = {path = "../azalea-world"} diff --git a/azalea-physics/README.md b/azalea-physics/README.md new file mode 100644 index 00000000..21ee18ba --- /dev/null +++ b/azalea-physics/README.md @@ -0,0 +1,3 @@ +# Azalea Physics + +Physics for Minecraft entities. diff --git a/azalea-physics/src/collision/dimension_collisions.rs b/azalea-physics/src/collision/dimension_collisions.rs new file mode 100644 index 00000000..59514fda --- /dev/null +++ b/azalea-physics/src/collision/dimension_collisions.rs @@ -0,0 +1,137 @@ +use crate::collision::{VoxelShape, AABB}; +use azalea_block::BlockState; +use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; +use azalea_world::entity::EntityData; +use azalea_world::{Chunk, Dimension}; +use std::sync::{Arc, Mutex}; + +pub trait CollisionGetter { + fn get_block_collisions<'a>( + &'a self, + entity: Option<&EntityData>, + aabb: AABB, + ) -> BlockCollisions<'a>; +} + +impl CollisionGetter for Dimension { + fn get_block_collisions<'a>( + &'a self, + entity: Option<&EntityData>, + aabb: AABB, + ) -> BlockCollisions<'a> { + BlockCollisions::new(self, entity, aabb) + } +} + +pub struct BlockCollisions<'a> { + pub dimension: &'a Dimension, + // context: CollisionContext, + pub aabb: AABB, + + pub cursor: Cursor3d, + pub only_suffocating_blocks: bool, +} + +impl<'a> BlockCollisions<'a> { + pub fn new(dimension: &'a Dimension, _entity: Option<&EntityData>, aabb: AABB) -> Self { + let origin_x = (aabb.min_x - EPSILON) as i32 - 1; + let origin_y = (aabb.min_y - EPSILON) as i32 - 1; + let origin_z = (aabb.min_z - EPSILON) as i32 - 1; + + let end_x = (aabb.max_x + EPSILON) as i32 + 1; + let end_y = (aabb.max_y + EPSILON) as i32 + 1; + let end_z = (aabb.max_z + EPSILON) as i32 + 1; + + let cursor = Cursor3d::new(origin_x, origin_y, origin_z, end_x, end_y, end_z); + + Self { + dimension, + aabb, + cursor, + only_suffocating_blocks: false, + } + } + + fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<&Arc>> { + let chunk_x = ChunkSectionPos::block_to_section_coord(block_x); + let chunk_z = ChunkSectionPos::block_to_section_coord(block_z); + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + + // TODO: minecraft caches chunk here + // int chunkX = SectionPos.blockToSectionCoord(blockX); + // int chunkZ = SectionPos.blockToSectionCoord(blockZ); + // long chunkPosLong = ChunkPos.asLong(chunkX, chunkZ); + // if (this.cachedBlockGetter != null && this.cachedBlockGetterPos == var5) { + // return this.cachedBlockGetter; + // } else { + // BlockGetter var7 = this.collisionGetter.getChunkForCollisions(chunkX, chunkZ); + // this.cachedBlockGetter = var7; + // this.cachedBlockGetterPos = chunkPosLong; + // return var7; + // } + + self.dimension[&chunk_pos].as_ref() + } +} + +impl<'a> Iterator for BlockCollisions<'a> { + type Item = Box; + + fn next(&mut self) -> Option { + while let Some(item) = self.cursor.next() { + if item.iteration_type == CursorIterationType::Corner { + continue; + } + + let chunk = self.get_chunk(item.pos.x, item.pos.z); + let chunk = match chunk { + Some(chunk) => chunk, + None => continue, + }; + let chunk_lock = chunk.lock().unwrap(); + + let pos = item.pos; + println!("getting block at {:?}", pos); + let block_state: BlockState = chunk_lock.get(&(&pos).into(), self.dimension.min_y()); + // let block: Box = block_state.into(); + + // TODO: continue if self.only_suffocating_blocks and the block is not suffocating + + let block_shape = if block_state == BlockState::Air { + crate::collision::empty_shape() + } else { + crate::collision::block_shape() + }; + // let block_shape = block.get_collision_shape(); + // if block_shape == Shapes::block() { + if true { + // TODO: this can be optimized + if !self.aabb.intersects_aabb(&AABB { + min_x: item.pos.x as f64, + min_y: item.pos.y as f64, + min_z: item.pos.z as f64, + max_x: (item.pos.x + 1) as f64, + max_y: (item.pos.y + 1) as f64, + max_z: (item.pos.z + 1) as f64, + }) { + continue; + } + + return Some(block_shape.move_relative( + item.pos.x as f64, + item.pos.y as f64, + item.pos.z as f64, + )); + } + + // let block_shape = block_shape.move_relative(item.pos.x, item.pos.y, item.pos.z); + // if (!Shapes.joinIsNotEmpty(block_shape, this.entityShape, BooleanOp.AND)) { + // continue; + // } + + // return block_shape; + } + + None + } +} diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs new file mode 100644 index 00000000..6eb425ce --- /dev/null +++ b/azalea-physics/src/collision/discrete_voxel_shape.rs @@ -0,0 +1,156 @@ +use azalea_core::{Axis, AxisCycle, BitSet}; + +// TODO: every impl of DiscreteVoxelShape could be turned into a single enum as an optimization + +pub trait DiscreteVoxelShape { + fn size(&self, axis: Axis) -> u32; + + fn first_full_x(&self) -> u32; + fn first_full_y(&self) -> u32; + fn first_full_z(&self) -> u32; + + fn last_full_x(&self) -> u32; + fn last_full_y(&self) -> u32; + fn last_full_z(&self) -> u32; + + fn is_empty(&self) -> bool { + if self.first_full_x() >= self.last_full_x() { + return true; + } + if self.first_full_y() >= self.last_full_y() { + return true; + } + if self.first_full_x() >= self.last_full_x() { + return true; + } + false + } + + fn is_full_wide(&self, x: u32, y: u32, z: u32) -> bool { + (x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z)) + && (self.is_full(x, y, z)) + } + fn is_full_wide_axis_cycle(&self, axis_cycle: AxisCycle, x: u32, y: u32, z: u32) -> bool { + self.is_full_wide( + axis_cycle.cycle_xyz(x, y, z, Axis::X), + axis_cycle.cycle_xyz(x, y, z, Axis::Y), + axis_cycle.cycle_xyz(x, y, z, Axis::Z), + ) + } + + fn is_full(&self, x: u32, y: u32, z: u32) -> bool; + + // i don't know how to do this properly + fn clone(&self) -> Box; +} + +#[derive(Default, Clone)] +pub struct BitSetDiscreteVoxelShape { + x_size: u32, + y_size: u32, + z_size: u32, + + storage: BitSet, + x_min: u32, + y_min: u32, + z_min: u32, + x_max: u32, + y_max: u32, + z_max: u32, +} + +impl BitSetDiscreteVoxelShape { + // public BitSetDiscreteVoxelShape(int var1, int var2, int var3) { + // super(var1, var2, var3); + // this.storage = new BitSet(var1 * var2 * var3); + // this.xMin = var1; + // this.yMin = var2; + // this.zMin = var3; + // } + pub fn new(x_min: u32, y_min: u32, z_min: u32) -> Self { + BitSetDiscreteVoxelShape { + x_size: x_min, + y_size: y_min, + z_size: z_min, + + storage: BitSet::new((x_min * y_min * z_min).try_into().unwrap()), + x_min, + y_min, + z_min, + x_max: 0, + y_max: 0, + z_max: 0, + } + } + + // private void fillUpdateBounds(int var1, int var2, int var3, boolean var4) { + // this.storage.set(this.getIndex(var1, var2, var3)); + // if (var4) { + // this.xMin = Math.min(this.xMin, var1); + // this.yMin = Math.min(this.yMin, var2); + // this.zMin = Math.min(this.zMin, var3); + // this.xMax = Math.max(this.xMax, var1 + 1); + // this.yMax = Math.max(this.yMax, var2 + 1); + // this.zMax = Math.max(this.zMax, var3 + 1); + // } + // } + fn fill_update_bounds(&mut self, x: u32, y: u32, z: u32, update: bool) { + self.storage.set(self.get_index(x, y, z)); + if update { + self.x_min = std::cmp::min(self.x_min, x); + self.y_min = std::cmp::min(self.y_min, y); + self.z_min = std::cmp::min(self.z_min, z); + self.x_max = std::cmp::max(self.x_max, x + 1); + self.y_max = std::cmp::max(self.y_max, y + 1); + self.z_max = std::cmp::max(self.z_max, z + 1); + } + } + + // public void fill(int var1, int var2, int var3) { + // this.fillUpdateBounds(var1, var2, var3, true); + // } + pub fn fill(&mut self, x: u32, y: u32, z: u32) { + self.fill_update_bounds(x, y, z, true); + } + + // protected int getIndex(int var1, int var2, int var3) { + // return (var1 * this.ySize + var2) * this.zSize + var3; + // } + fn get_index(&self, x: u32, y: u32, z: u32) -> usize { + ((x * self.y_size + y) * self.z_size + z) as usize + } +} + +impl DiscreteVoxelShape for BitSetDiscreteVoxelShape { + fn size(&self, axis: Axis) -> u32 { + axis.choose(self.x_size, self.y_size, self.z_size) + } + + fn first_full_x(&self) -> u32 { + self.x_min + } + fn first_full_y(&self) -> u32 { + self.y_min + } + fn first_full_z(&self) -> u32 { + self.z_min + } + + fn last_full_x(&self) -> u32 { + self.x_max + } + fn last_full_y(&self) -> u32 { + self.y_max + } + fn last_full_z(&self) -> u32 { + self.z_max + } + + fn clone(&self) -> Box { + Box::new(Clone::clone(self)) + } + + fn is_full(&self, x: u32, y: u32, z: u32) -> bool { + self.storage.index(self.get_index(x, y, z)) + } +} diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs new file mode 100644 index 00000000..465698b2 --- /dev/null +++ b/azalea-physics/src/collision/mod.rs @@ -0,0 +1,273 @@ +mod dimension_collisions; +mod discrete_voxel_shape; +mod shape; + +use azalea_core::{Axis, PositionXYZ, Vec3, AABB, EPSILON}; +use azalea_world::entity::{EntityData, EntityMut}; +use azalea_world::{Dimension, MoveEntityError}; +use dimension_collisions::CollisionGetter; +pub use discrete_voxel_shape::*; +pub use shape::*; + +pub enum MoverType { + Own, + Player, + Piston, + ShulkerBox, + Shulker, +} + +pub trait HasCollision { + fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3; +} + +pub trait MovableEntity { + fn move_colliding( + &mut self, + mover_type: &MoverType, + movement: &Vec3, + ) -> Result<(), MoveEntityError>; +} + +impl HasCollision for Dimension { + // private Vec3 collide(Vec3 var1) { + // AABB var2 = this.getBoundingBox(); + // List var3 = this.level.getEntityCollisions(this, var2.expandTowards(var1)); + // Vec3 var4 = var1.lengthSqr() == 0.0D ? var1 : collideBoundingBox(this, var1, var2, this.level, var3); + // boolean var5 = var1.x != var4.x; + // boolean var6 = var1.y != var4.y; + // boolean var7 = var1.z != var4.z; + // boolean var8 = this.onGround || var6 && var1.y < 0.0D; + // if (this.maxUpStep > 0.0F && var8 && (var5 || var7)) { + // Vec3 var9 = collideBoundingBox(this, new Vec3(var1.x, (double)this.maxUpStep, var1.z), var2, this.level, var3); + // Vec3 var10 = collideBoundingBox(this, new Vec3(0.0D, (double)this.maxUpStep, 0.0D), var2.expandTowards(var1.x, 0.0D, var1.z), this.level, var3); + // if (var10.y < (double)this.maxUpStep) { + // Vec3 var11 = collideBoundingBox(this, new Vec3(var1.x, 0.0D, var1.z), var2.move(var10), this.level, var3).add(var10); + // if (var11.horizontalDistanceSqr() > var9.horizontalDistanceSqr()) { + // var9 = var11; + // } + // } + + // if (var9.horizontalDistanceSqr() > var4.horizontalDistanceSqr()) { + // return var9.add(collideBoundingBox(this, new Vec3(0.0D, -var9.y + var1.y, 0.0D), var2.move(var9), this.level, var3)); + // } + // } + + // return var4; + // } + fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3 { + let entity_bounding_box = entity.bounding_box; + println!("collide: entity_bounding_box: {:?}", entity_bounding_box); + // TODO: get_entity_collisions + // let entity_collisions = dimension.get_entity_collisions(self, entity_bounding_box.expand_towards(movement)); + let entity_collisions = Vec::new(); + if movement.length_sqr() == 0.0 { + *movement + } else { + collide_bounding_box( + Some(entity), + movement, + &entity_bounding_box, + self, + entity_collisions, + ) + } + + // TODO: stepping (for stairs and stuff) + + // collided_movement + } +} + +impl MovableEntity for EntityMut<'_> { + /// Move an entity by a given delta, checking for collisions. + fn move_colliding( + &mut self, + _mover_type: &MoverType, + movement: &Vec3, + ) -> Result<(), MoveEntityError> { + // TODO: do all these + + // if self.no_physics { + // return; + // }; + + // if (var1 == MoverType.PISTON) { + // var2 = this.limitPistonMovement(var2); + // if (var2.equals(Vec3.ZERO)) { + // return; + // } + // } + + // if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { + // var2 = var2.multiply(this.stuckSpeedMultiplier); + // this.stuckSpeedMultiplier = Vec3.ZERO; + // this.setDeltaMovement(Vec3.ZERO); + // } + + // movement = this.maybeBackOffFromEdge(movement, moverType); + + println!("move_entity {:?}", movement); + + let collide_result = { self.dimension.collide(movement, self) }; + + let move_distance = collide_result.length_sqr(); + + println!("move_entity move_distance: {}", move_distance); + + if move_distance > EPSILON { + // TODO: fall damage + + let new_pos = { + let entity_pos = self.pos(); + Vec3 { + x: entity_pos.x + collide_result.x, + y: entity_pos.y + collide_result.y, + z: entity_pos.z + collide_result.z, + } + }; + + self.dimension.set_entity_pos(self.id, new_pos)?; + + println!("move_entity set_entity_pos {:?}", new_pos) + } + + let x_collision = movement.x != collide_result.x; + let z_collision = movement.z != collide_result.z; + let horizontal_collision = x_collision || z_collision; + let vertical_collision = movement.y != collide_result.y; + let on_ground = vertical_collision && movement.y < 0.; + // self.on_ground = on_ground; + + println!( + "move_entity {} {} {}", + x_collision, z_collision, vertical_collision + ); + + // TODO: minecraft checks for a "minor" horizontal collision here + + let block_pos_below = { self.on_pos_legacy() }; + let _block_state_below = self + .dimension + .get_block_state(&block_pos_below) + .expect("Couldn't get block state below"); + + println!("move_entity 4"); + // self.check_fall_damage(collide_result.y, on_ground, block_state_below, block_pos_below); + + // if self.isRemoved() { return; } + + if horizontal_collision { + let delta_movement = &self.delta; + self.delta = Vec3 { + x: if x_collision { 0. } else { delta_movement.x }, + y: delta_movement.y, + z: if z_collision { 0. } else { delta_movement.z }, + } + } + + if vertical_collision { + // blockBelow.updateEntityAfterFallOn(this.level, this); + } + + if on_ground { + // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, this); + } + + // sounds + + // this.tryCheckInsideBlocks(); + + // float var25 = this.getBlockSpeedFactor(); + // this.setDeltaMovement(this.getDeltaMovement().multiply((double)var25, 1.0D, (double)var25)); + // if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((var0) -> { + // return var0.is(BlockTags.FIRE) || var0.is(Blocks.LAVA); + // })) { + // if (this.remainingFireTicks <= 0) { + // this.setRemainingFireTicks(-this.getFireImmuneTicks()); + // } + + // if (this.wasOnFire && (this.isInPowderSnow || this.isInWaterRainOrBubble())) { + // this.playEntityOnFireExtinguishedSound(); + // } + // } + + // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) { + // this.setRemainingFireTicks(-this.getFireImmuneTicks()); + // } + + println!("move_entity 5"); + + Ok(()) + } +} + +fn collide_bounding_box( + entity: Option<&EntityData>, + movement: &Vec3, + entity_bounding_box: &AABB, + dimension: &Dimension, + entity_collisions: Vec>, +) -> Vec3 { + let mut collision_boxes: Vec> = + Vec::with_capacity(entity_collisions.len() + 1); + + if !entity_collisions.is_empty() { + collision_boxes.extend(entity_collisions); + } + + // TODO: world border + + let block_collisions = + dimension.get_block_collisions(entity, entity_bounding_box.expand_towards(movement)); + collision_boxes.extend(block_collisions); + collide_with_shapes(movement, *entity_bounding_box, &collision_boxes) +} + +fn collide_with_shapes( + movement: &Vec3, + mut entity_box: AABB, + collision_boxes: &Vec>, +) -> Vec3 { + if collision_boxes.is_empty() { + return *movement; + } + + let mut x_movement = movement.x; + let mut y_movement = movement.y; + let mut z_movement = movement.z; + if y_movement != 0. { + y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement); + if y_movement != 0. { + entity_box = entity_box.move_relative(0., y_movement, 0.); + } + } + + // whether the player is moving more in the z axis than x + // this is done to fix a movement bug, minecraft does this too + let more_z_movement = x_movement.abs() < z_movement.abs(); + + if more_z_movement && z_movement != 0. { + z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement); + if z_movement != 0. { + entity_box = entity_box.move_relative(0., 0., z_movement); + } + } + + if x_movement != 0. { + x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement); + if x_movement != 0. { + entity_box = entity_box.move_relative(x_movement, 0., 0.); + } + } + + if !more_z_movement && z_movement != 0. { + z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement); + } + + Vec3 { + x: x_movement, + y: y_movement, + z: z_movement, + } +} diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs new file mode 100644 index 00000000..45822d07 --- /dev/null +++ b/azalea-physics/src/collision/shape.rs @@ -0,0 +1,254 @@ +use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB}; +use azalea_core::{binary_search, Axis, AxisCycle, EPSILON}; +use std::cmp; + +pub struct Shapes {} + +pub fn block_shape() -> Box { + let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1); + shape.fill(0, 0, 0); + Box::new(CubeVoxelShape::new(Box::new(shape))) +} +pub fn empty_shape() -> Box { + Box::new(ArrayVoxelShape::new( + Box::new(BitSetDiscreteVoxelShape::new(0, 0, 0)), + vec![0.], + vec![0.], + vec![0.], + )) +} + +impl Shapes { + pub fn collide( + axis: &Axis, + entity_box: &AABB, + collision_boxes: &Vec>, + mut movement: f64, + ) -> f64 { + for shape in collision_boxes { + if movement.abs() < EPSILON { + return 0.; + } + movement = shape.collide(axis, entity_box, movement); + } + movement + } +} + +pub trait VoxelShape { + fn shape(&self) -> Box; + + fn get_coords(&self, axis: Axis) -> Vec; + + // TODO: optimization: should this be changed to return ArrayVoxelShape? + // i might change the implementation of empty_shape in the future so not 100% sure + fn move_relative(&self, x: f64, y: f64, z: f64) -> Box { + if self.shape().is_empty() { + return empty_shape(); + } + + println!( + "making new voxel shape {:?} {:?} {:?}", + self.get_coords(Axis::X), + self.get_coords(Axis::Y), + self.get_coords(Axis::Z) + ); + + Box::new(ArrayVoxelShape::new( + self.shape(), + self.get_coords(Axis::X).iter().map(|c| c + x).collect(), + self.get_coords(Axis::Y).iter().map(|c| c + y).collect(), + self.get_coords(Axis::Z).iter().map(|c| c + z).collect(), + )) + } + + fn get(&self, axis: Axis, index: usize) -> f64 { + self.get_coords(axis)[index] + } + + fn find_index(&self, axis: Axis, coord: f64) -> u32 { + binary_search(0, self.shape().size(axis) + 1, &|t| { + coord < self.get(axis, t as usize) + }) - 1 + } + + fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 { + self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement) + } + fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 { + if self.shape().is_empty() { + return movement; + } + if movement.abs() < EPSILON { + return 0.; + } + + let inverse_axis_cycle = axis_cycle.inverse(); + + // probably not good names but idk what this does + let x_axis = inverse_axis_cycle.cycle(Axis::X); + let y_axis = inverse_axis_cycle.cycle(Axis::Y); + let z_axis = inverse_axis_cycle.cycle(Axis::Z); + + // i gave up on names at this point (these are the obfuscated names from fernflower) + let var9 = entity_box.max(&x_axis); + let var11 = entity_box.min(&x_axis); + + let var13 = self.find_index(x_axis, var11 + EPSILON); + let var14 = self.find_index(x_axis, var9 - EPSILON); + + let var15 = cmp::max( + 0, + self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON), + ); + let var16 = cmp::min( + self.shape().size(y_axis), + self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1, + ); + + let var17 = cmp::max( + 0, + self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON), + ); + let var18 = cmp::min( + self.shape().size(z_axis), + self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1, + ); + + let var19 = self.shape().size(x_axis); + + if movement > 0. { + for var20 in var14 + 1..var19 { + for var21 in var15..var16 { + for var22 in var17..var18 { + if self.shape().is_full_wide_axis_cycle( + inverse_axis_cycle, + var20, + var21, + var22, + ) { + let var23 = self.get(x_axis, var20 as usize) - var9; + if var23 >= -EPSILON { + movement = f64::min(movement, var23); + } + return movement; + } + } + } + } + } else if movement < 0. { + for var20 in (var13 - 1)..=0 { + for var21 in var15..var16 { + for var22 in var17..var18 { + if self.shape().is_full_wide_axis_cycle( + inverse_axis_cycle, + var20, + var21, + var22, + ) { + let var23 = self.get(x_axis, (var20 + 1) as usize) - var11; + if var23 <= EPSILON { + movement = f64::max(movement, var23); + } + return movement; + } + } + } + } + } + + movement + } +} + +pub struct ArrayVoxelShape { + shape: Box, + // TODO: check where faces is used in minecraft + #[allow(dead_code)] + faces: Option>>, + + pub xs: Vec, + pub ys: Vec, + pub zs: Vec, +} + +pub struct CubeVoxelShape { + shape: Box, + // TODO: check where faces is used in minecraft + #[allow(dead_code)] + faces: Option>>, +} + +impl ArrayVoxelShape { + pub fn new( + shape: Box, + xs: Vec, + ys: Vec, + zs: Vec, + ) -> Self { + let x_size = shape.size(Axis::X) + 1; + let y_size = shape.size(Axis::Y) + 1; + let z_size = shape.size(Axis::Z) + 1; + + // Lengths of point arrays must be consistent with the size of the VoxelShape. + assert_eq!(x_size, xs.len() as u32); + assert_eq!(y_size, ys.len() as u32); + assert_eq!(z_size, zs.len() as u32); + + Self { + faces: None, + shape, + xs, + ys, + zs, + } + } +} + +impl CubeVoxelShape { + pub fn new(shape: Box) -> Self { + Self { shape, faces: None } + } +} + +impl VoxelShape for ArrayVoxelShape { + fn shape(&self) -> Box { + self.shape.clone() + } + + fn get_coords(&self, axis: Axis) -> Vec { + axis.choose(self.xs.clone(), self.ys.clone(), self.zs.clone()) + } +} + +impl VoxelShape for CubeVoxelShape { + fn shape(&self) -> Box { + self.shape.clone() + } + + fn get_coords(&self, axis: Axis) -> Vec { + let size = self.shape.size(axis); + let mut parts = Vec::with_capacity(size as usize); + for i in 0..=size { + parts.push(i as f64 / size as f64); + } + parts + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_block_shape() { + let shape = block_shape(); + assert_eq!(shape.shape().size(Axis::X), 1); + assert_eq!(shape.shape().size(Axis::Y), 1); + assert_eq!(shape.shape().size(Axis::Z), 1); + + assert_eq!(shape.get_coords(Axis::X).len(), 2); + assert_eq!(shape.get_coords(Axis::Y).len(), 2); + assert_eq!(shape.get_coords(Axis::Z).len(), 2); + } +} diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs new file mode 100644 index 00000000..8842727b --- /dev/null +++ b/azalea-physics/src/lib.rs @@ -0,0 +1,127 @@ +pub mod collision; + +use azalea_block::Block; +use azalea_core::{BlockPos, Vec3}; +use azalea_world::entity::{EntityData, EntityMut}; +use collision::{MovableEntity, MoverType}; + +pub trait HasPhysics { + fn travel(&mut self, acceleration: &Vec3); + fn ai_step(&mut self); +} + +impl HasPhysics for EntityMut<'_> { + /// Move the entity with the given acceleration while handling friction, + /// gravity, collisions, and some other stuff. + fn travel(&mut self, acceleration: &Vec3) { + // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() { + // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal); + // return; + // } + + let gravity: f64 = 0.08; + + // TODO: slow falling effect + // let is_falling = self.delta.y <= 0.; + + // TODO: fluids + + // TODO: elytra + + let block_pos_below = get_block_pos_below_that_affects_movement(self); + let block_friction = + if let Some(block_state_below) = self.dimension.get_block_state(&block_pos_below) { + let block_below: Box = block_state_below.into(); + block_below.behavior().friction + } else { + unreachable!("Block below should be a real block.") + }; + + let inertia = if self.on_ground { + block_friction * 0.91 + } else { + 0.91 + }; + let mut movement = + handle_relative_friction_and_calculate_movement(self, acceleration, block_friction); + + movement.y -= gravity; + + // if (this.shouldDiscardFriction()) { + // this.setDeltaMovement(movement.x, yMovement, movement.z); + // } else { + // this.setDeltaMovement(movement.x * (double)inertia, yMovement * 0.9800000190734863D, movement.z * (double)inertia); + // } + + // if should_discard_friction(self) { + if false { + self.delta = movement; + } else { + self.delta = Vec3 { + x: movement.x * inertia as f64, + y: movement.y * 0.98f64, + z: movement.z * inertia as f64, + }; + } + } + + /// applies air resistance, calls self.travel(), and some other random + /// stuff. + fn ai_step(&mut self) { + // vanilla does movement interpolation here, doesn't really matter much for a bot though + + self.xxa *= 0.98; + self.zza *= 0.98; + + self.travel(&Vec3 { + x: self.xxa as f64, + y: self.yya as f64, + z: self.zza as f64, + }); + // freezing + // pushEntities + // drowning damage + } +} + +fn get_block_pos_below_that_affects_movement(entity: &EntityData) -> BlockPos { + BlockPos::new( + entity.pos().x as i32, + // TODO: this uses bounding_box.min_y instead of position.y + (entity.pos().y - 0.5f64) as i32, + entity.pos().z as i32, + ) +} + +fn handle_relative_friction_and_calculate_movement( + entity: &mut EntityMut, + acceleration: &Vec3, + block_friction: f32, +) -> Vec3 { + entity.move_relative(get_speed(&*entity, block_friction), acceleration); + // entity.delta = entity.handle_on_climbable(entity.delta); + entity + .move_colliding(&MoverType::Own, &entity.delta.clone()) + .expect("Entity should exist."); + // let delta_movement = entity.delta; + // if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable() || entity.getFeetBlockState().is(Blocks.POWDER_SNOW) && PowderSnowBlock.canEntityWalkOnPowderSnow(entity))) { + // var3 = new Vec3(var3.x, 0.2D, var3.z); + // } + // TODO: powdered snow + + entity.delta +} + +// private float getFrictionInfluencedSpeed(float friction) { +// return this.onGround ? this.getSpeed() * (0.21600002F / (friction * friction * friction)) : this.flyingSpeed; +// } +fn get_speed(entity: &EntityData, friction: f32) -> f32 { + // TODO: have speed & flying_speed fields in entity + if entity.on_ground { + let speed: f32 = 0.7; + speed * (0.216f32 / (friction * friction * friction)) + } else { + // entity.flying_speed + 0.02 + } +} diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index bd475676..ab9b322f 100755 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -14,8 +14,8 @@ azalea-buf = {path = "../azalea-buf"} azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core", optional = true} azalea-crypto = {path = "../azalea-crypto"} -azalea-entity = {path = "../azalea-entity"} azalea-nbt = {path = "../azalea-nbt"} +azalea-world = {path = "../azalea-world"} byteorder = "^1.4.3" bytes = "^1.1.0" flate2 = "1.0.23" diff --git a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs index b79646c0..4b26efe7 100644 --- a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs @@ -1,6 +1,6 @@ use azalea_buf::McBuf; -use azalea_core::EntityPos; -use azalea_entity::Entity; +use azalea_core::Vec3; +use azalea_world::entity::EntityData; use packet_macros::ClientboundGamePacket; use uuid::Uuid; @@ -26,12 +26,11 @@ pub struct ClientboundAddEntityPacket { pub z_vel: i16, } -impl From<&ClientboundAddEntityPacket> for Entity { +impl From<&ClientboundAddEntityPacket> for EntityData { fn from(p: &ClientboundAddEntityPacket) -> Self { Self::new( - p.id, p.uuid, - EntityPos { + Vec3 { x: p.x, y: p.y, z: p.z, diff --git a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs index 2e450084..cd07d033 100644 --- a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs @@ -1,6 +1,6 @@ use azalea_buf::McBuf; -use azalea_core::EntityPos; -use azalea_entity::Entity; +use azalea_core::Vec3; +use azalea_world::entity::EntityData; use packet_macros::ClientboundGamePacket; use uuid::Uuid; @@ -17,12 +17,11 @@ pub struct ClientboundAddPlayerPacket { pub y_rot: i8, } -impl From<&ClientboundAddPlayerPacket> for Entity { +impl From<&ClientboundAddPlayerPacket> for EntityData { fn from(p: &ClientboundAddPlayerPacket) -> Self { Self::new( - p.id, p.uuid, - EntityPos { + Vec3 { x: p.x, y: p.y, z: p.z, diff --git a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs index 038e6202..e7fa936f 100644 --- a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs @@ -1,4 +1,5 @@ -use azalea_buf::{BitSet, McBuf}; +use azalea_buf::McBuf; +use azalea_core::BitSet; use packet_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] diff --git a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs index 7414e10c..492f352f 100644 --- a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs @@ -1,6 +1,8 @@ -use azalea_buf::{BitSet, BufReadError, McBuf, McBufReadable, McBufVarWritable}; -use azalea_buf::{McBufVarReadable, McBufWritable}; +use azalea_buf::{ + BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, +}; use azalea_chat::component::Component; +use azalea_core::BitSet; use azalea_crypto::{MessageSignature, SignedMessageHeader}; use packet_macros::ClientboundGamePacket; use std::io::{Read, Write}; diff --git a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs index d53774b7..4c61d5c7 100644 --- a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs @@ -1,3 +1,4 @@ +use crate::packets::login::serverbound_hello_packet::ProfilePublicKeyData; use azalea_buf::{BufReadError, McBuf}; use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable}; use azalea_chat::component::Component; @@ -28,14 +29,15 @@ pub struct PlayerProperty { #[derive(Clone, Debug, McBuf)] pub struct AddPlayer { - uuid: Uuid, - name: String, - properties: Vec, + pub uuid: Uuid, + pub name: String, + pub properties: Vec, #[var] - gamemode: u32, + pub gamemode: u32, #[var] - ping: i32, - display_name: Option, + pub ping: i32, + pub display_name: Option, + pub profile_public_key: Option, } #[derive(Clone, Debug, McBuf)] diff --git a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs index 7468fc91..35319acc 100644 --- a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_entity::EntityMetadata; +use azalea_world::entity::EntityMetadata; use packet_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs old mode 100755 new mode 100644 index 194c7680..ca647241 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -183,10 +183,10 @@ declare_state_packets!( 0x11: serverbound_jigsaw_generate_packet::ServerboundJigsawGeneratePacket, 0x12: serverbound_keep_alive_packet::ServerboundKeepAlivePacket, 0x13: serverbound_lock_difficulty_packet::ServerboundLockDifficultyPacket, - 0x14: serverbound_move_player_pos_packet::ServerboundMovePlayerPacketPos, - 0x15: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPacketPosRot, - 0x16: serverbound_move_player_rot_packet::ServerboundMovePlayerPacketRot, - 0x17: serverbound_move_player_status_only_packet::ServerboundMovePlayerPacketStatusOnly, + 0x14: serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, + 0x15: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, + 0x16: serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket, + 0x17: serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket, 0x18: serverbound_move_vehicle_packet::ServerboundMoveVehiclePacket, 0x19: serverbound_paddle_boat_packet::ServerboundPaddleBoatPacket, 0x1a: serverbound_pick_item_packet::ServerboundPickItemPacket, diff --git a/azalea-protocol/src/packets/game/serverbound_interact_packet.rs b/azalea-protocol/src/packets/game/serverbound_interact_packet.rs index 47843d47..7f54bd44 100644 --- a/azalea-protocol/src/packets/game/serverbound_interact_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_interact_packet.rs @@ -1,7 +1,6 @@ use crate::packets::BufReadError; -use azalea_buf::McBufVarReadable; -use azalea_buf::{McBuf, McBufReadable, McBufVarWritable, McBufWritable}; -use azalea_core::EntityPos; +use azalea_buf::{McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable}; +use azalea_core::Vec3; use packet_macros::ServerboundGamePacket; use std::io::{Read, Write}; @@ -21,7 +20,7 @@ pub enum ActionType { }, Attack, InteractAt { - location: EntityPos, + location: Vec3, hand: InteractionHand, }, } @@ -63,7 +62,7 @@ impl McBufReadable for ActionType { let z = f32::read_from(buf)?; let hand = InteractionHand::read_from(buf)?; Ok(ActionType::InteractAt { - location: EntityPos { + location: Vec3 { x: x as f64, y: y as f64, z: z as f64, diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs index 9e70eec6..aac85d6b 100644 --- a/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_move_player_pos_packet.rs @@ -2,7 +2,7 @@ use azalea_buf::McBuf; use packet_macros::ServerboundGamePacket; #[derive(Clone, Debug, McBuf, ServerboundGamePacket)] -pub struct ServerboundMovePlayerPacketPos { +pub struct ServerboundMovePlayerPosPacket { pub x: f64, pub y: f64, pub z: f64, diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs index 6933a724..a1ee359f 100644 --- a/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_move_player_pos_rot_packet.rs @@ -2,7 +2,7 @@ use azalea_buf::McBuf; use packet_macros::ServerboundGamePacket; #[derive(Clone, Debug, McBuf, ServerboundGamePacket)] -pub struct ServerboundMovePlayerPacketPosRot { +pub struct ServerboundMovePlayerPosRotPacket { pub x: f64, pub y: f64, pub z: f64, diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs index 493c5eab..86a4669f 100644 --- a/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_move_player_rot_packet.rs @@ -2,7 +2,7 @@ use azalea_buf::McBuf; use packet_macros::ServerboundGamePacket; #[derive(Clone, Debug, McBuf, ServerboundGamePacket)] -pub struct ServerboundMovePlayerPacketRot { +pub struct ServerboundMovePlayerRotPacket { pub y_rot: f32, pub x_rot: f32, pub on_ground: bool, diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs b/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs index 8b08154b..64ba93ae 100644 --- a/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_move_player_status_only_packet.rs @@ -2,6 +2,6 @@ use azalea_buf::McBuf; use packet_macros::ServerboundGamePacket; #[derive(Clone, Debug, McBuf, ServerboundGamePacket)] -pub struct ServerboundMovePlayerPacketStatusOnly { +pub struct ServerboundMovePlayerStatusOnlyPacket { pub on_ground: bool, } diff --git a/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs b/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs index f2fb1b2a..39762c2e 100644 --- a/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_use_item_on_packet.rs @@ -1,6 +1,6 @@ use crate::packets::game::serverbound_interact_packet::InteractionHand; use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; -use azalea_core::{BlockPos, Direction, EntityPos}; +use azalea_core::{BlockPos, Direction, Vec3}; use packet_macros::ServerboundGamePacket; use std::io::{Read, Write}; @@ -16,7 +16,7 @@ pub struct ServerboundUseItemOnPacket { pub struct BlockHitResult { pub block_pos: BlockPos, pub direction: Direction, - pub location: EntityPos, + pub location: Vec3, pub inside: bool, } @@ -43,7 +43,7 @@ impl McBufReadable for BlockHitResult { Ok(Self { block_pos, direction, - location: EntityPos { + location: Vec3 { x: block_pos.x as f64 + cursor_x as f64, y: block_pos.y as f64 + cursor_y as f64, z: block_pos.z as f64 + cursor_z as f64, diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 250a7bd9..524616e6 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -8,8 +8,8 @@ version = "0.1.0" [dependencies] azalea-block = {path = "../azalea-block", default-features = false} azalea-buf = {path = "../azalea-buf"} +azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core"} -azalea-entity = {path = "../azalea-entity"} azalea-nbt = {path = "../azalea-nbt"} log = "0.4.17" nohash-hasher = "0.2.0" diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index fcb3f8f9..2626c312 100644 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -103,16 +103,21 @@ impl BitStorage { /// Create a new BitStorage with the given number of bits per entry. /// `size` is the number of entries in the BitStorage. pub fn new(bits: usize, size: usize, data: Option>) -> Result { - // vanilla has this assert but it's not always true for some reason?? - // assert!(bits >= 1 && bits <= 32); - if let Some(data) = &data { + // 0 bit storage if data.is_empty() { - // TODO: make 0 bit storage actually work - return Ok(BitStorage::default()); + return Ok(BitStorage { + data: Vec::with_capacity(0), + bits, + size, + ..Default::default() + }); } } + // vanilla has this assert but it's not always true for some reason?? + // assert!(bits >= 1 && bits <= 32); + let values_per_long = 64 / bits; let magic_index = values_per_long - 1; let (divide_mul, divide_add, divide_shift) = MAGIC[magic_index as usize]; @@ -163,23 +168,43 @@ impl BitStorage { assert!( index < self.size, - "Index {} out of bounds (max is {})", + "Index {} out of bounds (must be less than {})", index, - self.size - 1 + self.size ); + + // 0 bit storage + if self.data.is_empty() { + return 0; + } + let cell_index = self.cell_index(index as u64); let cell = &self.data[cell_index as usize]; let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; cell >> bit_index & self.mask } + pub fn get_and_set(&mut self, index: usize, value: u64) -> u64 { + // 0 bit storage + if self.data.is_empty() { + return 0; + } + + 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]; + let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; + let old_value = *cell >> (bit_index as u64) & self.mask; + *cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index; + old_value + } + pub fn set(&mut self, index: usize, value: u64) { - // Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1); - // Validate.inclusiveBetween(0L, this.mask, (long)var2); - // int var3 = this.cellIndex(var1); - // long var4 = this.data[var3]; - // int var6 = (var1 - var3 * this.valuesPerLong) * this.bits; - // this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6; + // 0 bit storage + if self.data.is_empty() { + return; + } assert!(index < self.size); assert!(value <= self.mask); diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk_storage.rs similarity index 67% rename from azalea-world/src/chunk.rs rename to azalea-world/src/chunk_storage.rs index 9d393c2d..4ceea347 100644 --- a/azalea-world/src/chunk.rs +++ b/azalea-world/src/chunk_storage.rs @@ -4,6 +4,7 @@ use crate::Dimension; use azalea_block::BlockState; use azalea_buf::BufReadError; use azalea_buf::{McBufReadable, McBufWritable}; +use azalea_core::floor_mod; use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use std::fmt::Debug; use std::{ @@ -24,13 +25,33 @@ pub struct ChunkStorage { chunks: Vec>>>, } -// java moment -// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case -fn floor_mod(x: i32, y: u32) -> u32 { - if x < 0 { - y - ((-x) as u32 % y) - } else { - x as u32 % y +#[derive(Debug)] +pub struct Chunk { + pub sections: Vec
, +} + +#[derive(Clone, Debug)] +pub struct Section { + pub block_count: u16, + pub states: PalettedContainer, + pub biomes: PalettedContainer, +} + +impl Default for Section { + fn default() -> Self { + Section { + block_count: 0, + states: PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(), + biomes: PalettedContainer::new(&PalettedContainerType::Biomes).unwrap(), + } + } +} + +impl Default for Chunk { + fn default() -> Self { + Chunk { + sections: vec![Section::default(); (384 / 16) as usize], + } } } @@ -59,13 +80,24 @@ impl ChunkStorage { pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option { let chunk_pos = ChunkPos::from(pos); - println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos); let chunk = &self[&chunk_pos]; chunk .as_ref() .map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)) } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState, min_y: i32) -> BlockState { + let chunk_pos = ChunkPos::from(pos); + let chunk = &self[&chunk_pos]; + if let Some(chunk) = chunk.as_ref() { + let mut chunk = chunk.lock().unwrap(); + chunk.get_and_set(&ChunkBlockPos::from(pos), state, min_y) + } else { + // nothing is in this chunk, just return air + BlockState::Air + } + } + pub fn replace_with_packet_data( &mut self, pos: &ChunkPos, @@ -104,11 +136,6 @@ impl IndexMut<&ChunkPos> for ChunkStorage { } } -#[derive(Debug)] -pub struct Chunk { - pub sections: Vec
, -} - impl Chunk { pub fn read_with_dimension( buf: &mut impl Read, @@ -131,9 +158,7 @@ impl Chunk { } pub fn section_index(&self, y: i32, min_y: i32) -> u32 { - // TODO: check the build height and stuff, this code will be broken if the min build height is 0 - // (LevelHeightAccessor.getMinSection in vanilla code) - assert!(y >= 0); + assert!(y >= min_y, "y ({}) must be at least {}", y, min_y); let min_section_index = min_y.div_floor(16); (y.div_floor(16) - min_section_index) as u32 } @@ -145,6 +170,27 @@ impl Chunk { let chunk_section_pos = ChunkSectionBlockPos::from(pos); section.get(chunk_section_pos) } + + pub fn get_and_set( + &mut self, + pos: &ChunkBlockPos, + state: BlockState, + min_y: i32, + ) -> BlockState { + let section_index = self.section_index(pos.y, min_y); + // TODO: make sure the section exists + let section = &mut self.sections[section_index as usize]; + let chunk_section_pos = ChunkSectionBlockPos::from(pos); + section.get_and_set(chunk_section_pos, state) + } + + pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { + let section_index = self.section_index(pos.y, min_y); + // TODO: make sure the section exists + let section = &mut self.sections[section_index as usize]; + let chunk_section_pos = ChunkSectionBlockPos::from(pos); + section.set(chunk_section_pos, state) + } } impl McBufWritable for Chunk { @@ -170,13 +216,6 @@ impl Debug for ChunkStorage { } } -#[derive(Clone, Debug)] -pub struct Section { - pub block_count: u16, - pub states: PalettedContainer, - pub biomes: PalettedContainer, -} - impl McBufReadable for Section { fn read_from(buf: &mut impl Read) -> Result { let block_count = u16::read_from(buf)?; @@ -220,9 +259,47 @@ impl McBufWritable for Section { impl Section { fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { // TODO: use the unsafe method and do the check earlier + let state = self + .states + .get(pos.x as usize, pos.y as usize, pos.z as usize); + // if there's an unknown block assume it's air + BlockState::try_from(state).unwrap_or(BlockState::Air) + } + + fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState { + let previous_state = + self.states + .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32); + // if there's an unknown block assume it's air + BlockState::try_from(previous_state).unwrap_or(BlockState::Air) + } + + fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { self.states - .get(pos.x as usize, pos.y as usize, pos.z as usize) - .try_into() - .expect("Invalid block state.") + .set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32); + } +} + +impl Default for ChunkStorage { + fn default() -> Self { + Self::new(8, 384, -64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_section_index() { + let chunk = Chunk::default(); + assert_eq!(chunk.section_index(0, 0), 0); + assert_eq!(chunk.section_index(128, 0), 8); + assert_eq!(chunk.section_index(127, 0), 7); + assert_eq!(chunk.section_index(0, -64), 4); + assert_eq!(chunk.section_index(-64, -64), 0); + assert_eq!(chunk.section_index(-49, -64), 0); + assert_eq!(chunk.section_index(-48, -64), 1); + assert_eq!(chunk.section_index(128, -64), 12); } } diff --git a/azalea-entity/src/data.rs b/azalea-world/src/entity/data.rs similarity index 100% rename from azalea-entity/src/data.rs rename to azalea-world/src/entity/data.rs diff --git a/azalea-world/src/entity/dimensions.rs b/azalea-world/src/entity/dimensions.rs new file mode 100644 index 00000000..1d013d10 --- /dev/null +++ b/azalea-world/src/entity/dimensions.rs @@ -0,0 +1,23 @@ +use azalea_core::{Vec3, AABB}; + +#[derive(Debug, Default)] +pub struct EntityDimensions { + pub width: f32, + pub height: f32, +} + +impl EntityDimensions { + pub fn make_bounding_box(&self, pos: &Vec3) -> AABB { + let radius = (self.width / 2.0) as f64; + let height = self.height as f64; + AABB { + min_x: pos.x - radius, + min_y: pos.y, + min_z: pos.z - radius, + + max_x: pos.x + radius, + max_y: pos.y + height, + max_z: pos.z + radius, + } + } +} diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs new file mode 100644 index 00000000..37321e0a --- /dev/null +++ b/azalea-world/src/entity/mod.rs @@ -0,0 +1,316 @@ +mod data; +mod dimensions; + +use crate::Dimension; +use azalea_core::{BlockPos, Vec3, AABB}; +pub use data::*; +pub use dimensions::*; +use std::ops::{Deref, DerefMut}; +use std::ptr::NonNull; +use uuid::Uuid; + +#[derive(Debug)] +pub struct EntityRef<'d> { + /// The dimension this entity is in. + pub dimension: &'d Dimension, + /// The incrementing numerical id of the entity. + pub id: u32, + pub data: &'d EntityData, +} + +impl<'d> EntityRef<'d> { + pub fn new(dimension: &'d Dimension, id: u32, data: &'d EntityData) -> Self { + // TODO: have this be based on the entity type + Self { + dimension, + id, + data, + } + } +} + +impl<'d> EntityRef<'d> { + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub fn make_bounding_box(&self) -> AABB { + self.dimensions.make_bounding_box(&self.pos()) + } + + /// Get the position of the block below the entity, but a little lower. + pub fn on_pos_legacy(&self) -> BlockPos { + self.on_pos(0.2) + } + + // int x = Mth.floor(this.position.x); + // int y = Mth.floor(this.position.y - (double)var1); + // int z = Mth.floor(this.position.z); + // BlockPos var5 = new BlockPos(x, y, z); + // if (this.level.getBlockState(var5).isAir()) { + // BlockPos var6 = var5.below(); + // BlockState var7 = this.level.getBlockState(var6); + // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) { + // return var6; + // } + // } + // return var5; + pub fn on_pos(&self, offset: f32) -> BlockPos { + let x = self.pos().x.floor() as i32; + let y = (self.pos().y - offset as f64).floor() as i32; + let z = self.pos().z.floor() as i32; + let pos = BlockPos { x, y, z }; + + // TODO: check if block below is a fence, wall, or fence gate + // let block_pos = pos.below(); + // let block_state = dimension.get_block_state(&block_pos); + // if block_state == Some(BlockState::Air) { + // let block_pos_below = block_pos.below(); + // let block_state_below = dimension.get_block_state(&block_pos_below); + // if let Some(block_state_below) = block_state_below { + // if block_state_below.is_fence() + // || block_state_below.is_wall() + // || block_state_below.is_fence_gate() + // { + // return block_pos_below; + // } + // } + // } + + pos + } +} + +#[derive(Debug)] +pub struct EntityMut<'d> { + /// The dimension this entity is in. + pub dimension: &'d mut Dimension, + /// The incrementing numerical id of the entity. + pub id: u32, + pub data: NonNull, +} + +impl<'d> EntityMut<'d> { + pub fn new(dimension: &'d mut Dimension, id: u32, data: NonNull) -> Self { + Self { + dimension, + id, + data, + } + } + + /// Sets the position of the entity. This doesn't update the cache in + /// azalea-world, and should only be used within azalea-world! + pub unsafe fn move_unchecked(&mut self, new_pos: Vec3) { + self.pos = new_pos; + let bounding_box = self.make_bounding_box(); + self.bounding_box = bounding_box; + } + + pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { + self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0; + self.x_rot = x_rot % 360.0; + // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so + } + + pub fn move_relative(&mut self, speed: f32, acceleration: &Vec3) { + let input_vector = self.input_vector(speed, acceleration); + self.delta += input_vector; + } + + pub fn input_vector(&self, speed: f32, acceleration: &Vec3) -> Vec3 { + let distance = acceleration.length_squared(); + if distance < 1.0E-7 { + return Vec3::default(); + } + let acceleration = if distance > 1.0 { + acceleration.normalize() + } else { + *acceleration + } + .scale(speed as f64); + let y_rot = f32::sin(self.y_rot * 0.017453292f32); + let x_rot = f32::cos(self.y_rot * 0.017453292f32); + Vec3 { + x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64), + y: acceleration.y, + z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64), + } + } +} + +impl<'d> EntityMut<'d> { + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub fn make_bounding_box(&self) -> AABB { + self.dimensions.make_bounding_box(&self.pos()) + } + + /// Get the position of the block below the entity, but a little lower. + pub fn on_pos_legacy(&self) -> BlockPos { + self.on_pos(0.2) + } + + // int x = Mth.floor(this.position.x); + // int y = Mth.floor(this.position.y - (double)var1); + // int z = Mth.floor(this.position.z); + // BlockPos var5 = new BlockPos(x, y, z); + // if (this.level.getBlockState(var5).isAir()) { + // BlockPos var6 = var5.below(); + // BlockState var7 = this.level.getBlockState(var6); + // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) { + // return var6; + // } + // } + // return var5; + pub fn on_pos(&self, offset: f32) -> BlockPos { + let x = self.pos().x.floor() as i32; + let y = (self.pos().y - offset as f64).floor() as i32; + let z = self.pos().z.floor() as i32; + let pos = BlockPos { x, y, z }; + + // TODO: check if block below is a fence, wall, or fence gate + // let block_pos = pos.below(); + // let block_state = dimension.get_block_state(&block_pos); + // if block_state == Some(BlockState::Air) { + // let block_pos_below = block_pos.below(); + // let block_state_below = dimension.get_block_state(&block_pos_below); + // if let Some(block_state_below) = block_state_below { + // if block_state_below.is_fence() + // || block_state_below.is_wall() + // || block_state_below.is_fence_gate() + // { + // return block_pos_below; + // } + // } + // } + + pos + } +} + +impl<'d> From> for EntityRef<'d> { + fn from(entity: EntityMut<'d>) -> EntityRef<'d> { + let data = unsafe { entity.data.as_ref() }; + EntityRef { + dimension: entity.dimension, + id: entity.id, + data, + } + } +} + +impl Deref for EntityMut<'_> { + type Target = EntityData; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.as_ref() } + } +} + +impl DerefMut for EntityMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.data.as_mut() } + } +} + +impl Deref for EntityRef<'_> { + type Target = EntityData; + + fn deref(&self) -> &Self::Target { + self.data + } +} + +#[derive(Debug)] +pub struct EntityData { + pub uuid: Uuid, + /// The position of the entity right now. + /// This can be changde with unsafe_move, but the correct way is with dimension.move_entity + pos: Vec3, + /// The position of the entity last tick. + pub last_pos: Vec3, + pub delta: Vec3, + + /// X acceleration. + pub xxa: f32, + /// Y acceleration. + pub yya: f32, + /// Z acceleration. + pub zza: f32, + + pub x_rot: f32, + pub y_rot: f32, + + pub x_rot_last: f32, + pub y_rot_last: f32, + + pub on_ground: bool, + pub last_on_ground: bool, + + /// The width and height of the entity. + pub dimensions: EntityDimensions, + /// The bounding box of the entity. This is more than just width and height, unlike dimensions. + pub bounding_box: AABB, +} + +impl EntityData { + pub fn new(uuid: Uuid, pos: Vec3) -> Self { + let dimensions = EntityDimensions { + width: 0.8, + height: 1.8, + }; + + Self { + uuid, + pos, + last_pos: pos, + delta: Vec3::default(), + + xxa: 0., + yya: 0., + zza: 0., + + x_rot: 0., + y_rot: 0., + + y_rot_last: 0., + x_rot_last: 0., + + on_ground: false, + last_on_ground: false, + + // TODO: have this be based on the entity type + bounding_box: dimensions.make_bounding_box(&pos), + dimensions, + } + } + + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub(crate) unsafe fn as_ptr(&mut self) -> NonNull { + NonNull::new_unchecked(self as *mut EntityData) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_mut_entity_to_ref_entity() { + let mut dim = Dimension::default(); + let uuid = Uuid::from_u128(100); + dim.add_entity(0, EntityData::new(uuid, Vec3::default())); + let entity: EntityMut = dim.entity_mut(0).unwrap(); + let entity_ref: EntityRef = entity.into(); + assert_eq!(entity_ref.uuid, uuid); + } +} diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity_storage.rs similarity index 52% rename from azalea-world/src/entity.rs rename to azalea-world/src/entity_storage.rs index 69529294..c7fd9c0b 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity_storage.rs @@ -1,5 +1,5 @@ +use crate::entity::EntityData; use azalea_core::ChunkPos; -use azalea_entity::Entity; use log::warn; use nohash_hasher::{IntMap, IntSet}; use std::collections::HashMap; @@ -7,41 +7,41 @@ use uuid::Uuid; #[derive(Debug)] pub struct EntityStorage { - by_id: IntMap, - by_chunk: HashMap>, - by_uuid: HashMap, + data_by_id: IntMap, + id_by_chunk: HashMap>, + id_by_uuid: HashMap, } impl EntityStorage { pub fn new() -> Self { Self { - by_id: IntMap::default(), - by_chunk: HashMap::default(), - by_uuid: HashMap::default(), + data_by_id: IntMap::default(), + id_by_chunk: HashMap::default(), + id_by_uuid: HashMap::default(), } } /// Add an entity to the storage. #[inline] - pub fn insert(&mut self, entity: Entity) { - self.by_chunk + pub fn insert(&mut self, id: u32, entity: EntityData) { + self.id_by_chunk .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); + .insert(id); + self.id_by_uuid.insert(entity.uuid, id); + self.data_by_id.insert(id, entity); } /// Remove an entity from the storage by its id. #[inline] pub fn remove_by_id(&mut self, id: u32) { - if let Some(entity) = self.by_id.remove(&id) { + if let Some(entity) = self.data_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() { + if self.id_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() { + if self.id_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 { @@ -49,36 +49,46 @@ impl EntityStorage { } } + /// Check if there is an entity that exists with the given id. + #[inline] + pub fn contains_id(&self, id: &u32) -> bool { + self.data_by_id.contains_key(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) + pub fn get_by_id(&self, id: u32) -> Option<&EntityData> { + self.data_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) + pub fn get_mut_by_id<'d>(&'d mut self, id: u32) -> Option<&'d mut EntityData> { + self.data_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)) + pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> { + self.id_by_uuid + .get(uuid) + .and_then(|id| self.data_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)) + pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> { + self.id_by_uuid + .get(uuid) + .and_then(|id| self.data_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) { + if let Some(entities) = self.id_by_chunk.remove(chunk) { for entity_id in entities { - if let Some(entity) = self.by_id.remove(&entity_id) { - self.by_uuid.remove(&entity.uuid); + if let Some(entity) = self.data_by_id.remove(&entity_id) { + self.id_by_uuid.remove(&entity.uuid); } else { warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}."); } @@ -94,10 +104,10 @@ impl EntityStorage { old_chunk: &ChunkPos, new_chunk: &ChunkPos, ) { - if let Some(entities) = self.by_chunk.get_mut(old_chunk) { + if let Some(entities) = self.id_by_chunk.get_mut(old_chunk) { entities.remove(&entity_id); } - self.by_chunk + self.id_by_chunk .entry(*new_chunk) .or_default() .insert(entity_id); @@ -105,24 +115,24 @@ impl EntityStorage { /// Get an iterator over all entities. #[inline] - pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { - self.by_id.values() + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> { + self.data_by_id.values() } - pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + pub fn find_one_entity(&self, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { self.entities().find(|&entity| f(entity)) } - pub fn find_one_entity_in_chunk(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity> + pub fn find_one_entity_in_chunk(&self, chunk: &ChunkPos, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { - if let Some(entities) = self.by_chunk.get(chunk) { + if let Some(entities) = self.id_by_chunk.get(chunk) { for entity_id in entities { - if let Some(entity) = self.by_id.get(entity_id) { + if let Some(entity) = self.data_by_id.get(entity_id) { if f(entity) { return Some(entity); } @@ -138,3 +148,22 @@ impl Default for EntityStorage { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + use azalea_core::Vec3; + + #[test] + fn test_store_entity() { + let mut storage = EntityStorage::new(); + assert!(storage.get_by_id(0).is_none()); + + let uuid = Uuid::from_u128(100); + storage.insert(0, EntityData::new(uuid, Vec3::default())); + assert_eq!(storage.get_by_id(0).unwrap().uuid, uuid); + + storage.remove_by_id(0); + assert!(storage.get_by_id(0).is_none()); + } +} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 646ba1d9..77bb0f0f 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -1,17 +1,18 @@ #![feature(int_roundings)] mod bit_storage; -mod chunk; -mod entity; +mod chunk_storage; +pub mod entity; +mod entity_storage; mod palette; use azalea_block::BlockState; use azalea_buf::BufReadError; -use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8}; -use azalea_entity::Entity; +use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3}; pub use bit_storage::BitStorage; -pub use chunk::{Chunk, ChunkStorage}; -pub use entity::EntityStorage; +pub use chunk_storage::{Chunk, ChunkStorage}; +use entity::{EntityData, EntityMut, EntityRef}; +pub use entity_storage::EntityStorage; use std::{ io::Read, ops::{Index, IndexMut}, @@ -20,17 +21,10 @@ use std::{ use thiserror::Error; use uuid::Uuid; -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} - /// A dimension is a collection of chunks and entities. -#[derive(Debug)] +/// Minecraft calls these "Levels", Fabric calls them "Worlds", Minestom calls them "Instances". +/// Yeah. +#[derive(Debug, Default)] pub struct Dimension { chunk_storage: ChunkStorage, entity_storage: EntityStorage, @@ -66,20 +60,20 @@ impl Dimension { self.chunk_storage.get_block_state(pos, self.min_y()) } - pub fn move_entity( - &mut self, - entity_id: u32, - new_pos: EntityPos, - ) -> Result<(), MoveEntityError> { - let entity = self - .entity_storage - .get_mut_by_id(entity_id) + pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> BlockState { + self.chunk_storage.set_block_state(pos, state, self.min_y()) + } + + pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> { + println!("set_entity_pos({}, {:?})", entity_id, new_pos); + let mut entity = self + .entity_mut(entity_id) .ok_or(MoveEntityError::EntityDoesNotExist)?; 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); + unsafe { entity.move_unchecked(new_pos) }; if old_chunk != new_chunk { self.entity_storage .update_entity_chunk(entity_id, &old_chunk, &new_chunk); @@ -92,9 +86,8 @@ impl Dimension { entity_id: u32, delta: &PositionDelta8, ) -> Result<(), MoveEntityError> { - let entity = self - .entity_storage - .get_mut_by_id(entity_id) + let mut entity = self + .entity_mut(entity_id) .ok_or(MoveEntityError::EntityDoesNotExist)?; let new_pos = entity.pos().with_delta(delta); @@ -102,7 +95,7 @@ impl Dimension { let new_chunk = ChunkPos::from(&new_pos); // this is fine because we update the chunk below - entity.unsafe_move(new_pos); + unsafe { entity.move_unchecked(new_pos) }; if old_chunk != new_chunk { self.entity_storage .update_entity_chunk(entity_id, &old_chunk, &new_chunk); @@ -110,8 +103,8 @@ impl Dimension { Ok(()) } - pub fn add_entity(&mut self, entity: Entity) { - self.entity_storage.insert(entity); + pub fn add_entity(&mut self, id: u32, entity: EntityData) { + self.entity_storage.insert(id, entity); } pub fn height(&self) -> u32 { @@ -122,27 +115,46 @@ impl Dimension { self.chunk_storage.min_y } - pub fn entity_by_id(&self, id: u32) -> Option<&Entity> { + pub fn entity_data_by_id(&self, id: u32) -> Option<&EntityData> { self.entity_storage.get_by_id(id) } - pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> { + pub fn entity_data_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> { self.entity_storage.get_mut_by_id(id) } - pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { + pub fn entity<'d>(&'d self, id: u32) -> Option> { + let entity_data = self.entity_storage.get_by_id(id); + if let Some(entity_data) = entity_data { + Some(EntityRef::new(self, id, entity_data)) + } else { + None + } + } + + pub fn entity_mut<'d>(&'d mut self, id: u32) -> Option> { + let entity_data = self.entity_storage.get_mut_by_id(id); + if let Some(entity_data) = entity_data { + let entity_ptr = unsafe { entity_data.as_ptr() }; + Some(EntityMut::new(self, id, entity_ptr)) + } else { + None + } + } + + pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> { 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> { + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> { self.entity_storage.entities() } - pub fn find_one_entity(&self, mut f: F) -> Option<&Entity> + pub fn find_one_entity(&self, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { self.entity_storage.find_one_entity(|entity| f(entity)) } diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index 2473a7e5..4e0f9a96 100644 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -21,23 +21,27 @@ pub struct PalettedContainer { } impl PalettedContainer { + pub fn new(container_type: &'static PalettedContainerType) -> Result { + let palette = Palette::SingleValue(0); + let size = container_type.size(); + let storage = BitStorage::new(0, size, Some(vec![])).unwrap(); + + Ok(PalettedContainer { + bits_per_entry: 0, + palette, + storage, + container_type: *container_type, + }) + } + pub fn read_with_type( buf: &mut impl Read, - type_: &'static PalettedContainerType, + container_type: &'static PalettedContainerType, ) -> Result { let bits_per_entry = buf.read_byte()?; - let palette = match type_ { - PalettedContainerType::BlockStates => { - Palette::block_states_read_with_bits_per_entry(buf, bits_per_entry)? - } - PalettedContainerType::Biomes => { - Palette::biomes_read_with_bits_per_entry(buf, bits_per_entry)? - } - }; - let size = match type_ { - PalettedContainerType::BlockStates => 4096, - PalettedContainerType::Biomes => 64, - }; + let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type); + let palette = palette_type.read(buf)?; + let size = container_type.size(); let data = Vec::::read_from(buf)?; debug_assert!( @@ -50,24 +54,130 @@ impl PalettedContainer { bits_per_entry, palette, storage, - container_type: *type_, + container_type: *container_type, }) } + /// Calculates the index of the given coordinates. pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize { - let size_bits = match self.container_type { - PalettedContainerType::BlockStates => 4, - PalettedContainerType::Biomes => 2, - }; + let size_bits = self.container_type.size_bits(); (((y << size_bits) | z) << size_bits) | x } - pub fn get(&self, x: usize, y: usize, z: usize) -> u32 { - let paletted_value = self.storage.get(self.get_index(x, y, z)); - println!("palette: {:?}", self.palette); + /// Returns the value at the given index. + pub fn get_at_index(&self, index: usize) -> u32 { + let paletted_value = self.storage.get(index); self.palette.value_for(paletted_value as usize) } + + /// Returns the value at the given coordinates. + pub fn get(&self, x: usize, y: usize, z: usize) -> u32 { + // let paletted_value = self.storage.get(self.get_index(x, y, z)); + // self.palette.value_for(paletted_value as usize) + self.get_at_index(self.get_index(x, y, z)) + } + + /// Sets the id at the given coordinates and return the previous id + pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: u32) -> u32 { + let paletted_value = self.id_for(value); + self.storage + .get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32 + } + + /// Sets the id at the given index and return the previous id. You probably want `.set` instead. + pub fn set_at_index(&mut self, index: usize, value: u32) { + let paletted_value = self.id_for(value); + self.storage.set(index, paletted_value as u64) + } + + /// Sets the id at the given coordinates and return the previous id + pub fn set(&mut self, x: usize, y: usize, z: usize, value: u32) { + self.set_at_index(self.get_index(x, y, z), value); + } + + fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { + let new_palette_type = + PaletteType::from_bits_and_type(bits_per_entry, &self.container_type); + // note for whoever is trying to optimize this: vanilla has this + // but it causes a stack overflow since it's not changing the bits per entry + // i don't know how to fix this properly so glhf + // let old_palette_type: PaletteType = (&self.palette).into(); + // if new_palette_type == old_palette_type { + // return self.clone(); + // } + let storage = + BitStorage::new(bits_per_entry as usize, self.container_type.size(), None).unwrap(); + + // sanity check + debug_assert_eq!(storage.size(), self.container_type.size()); + + // let palette = new_palette_type.into_empty_palette(1usize << (bits_per_entry as usize)); + let palette = new_palette_type.into_empty_palette(); + PalettedContainer { + bits_per_entry, + palette, + storage, + container_type: self.container_type, + } + } + + fn on_resize(&mut self, bits_per_entry: u8, value: u32) -> usize { + if bits_per_entry > 5 { + panic!("bits_per_entry must be <= 5"); + } + let mut new_data = self.create_or_reuse_data(bits_per_entry); + new_data.copy_from(&self.palette, &self.storage); + *self = new_data; + let id = self.id_for(value); + id + } + + fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) { + for i in 0..storage.size() { + let value = palette.value_for(storage.get(i) as usize); + let id = self.id_for(value) as u64; + self.storage.set(i, id); + } + } + + pub fn id_for(&mut self, value: u32) -> usize { + match &mut self.palette { + Palette::SingleValue(v) => { + if *v != value { + self.on_resize(1, value) + } else { + 0 + } + } + Palette::Linear(palette) => { + if let Some(index) = palette.iter().position(|v| *v == value) { + return index as usize; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Hashmap(palette) => { + // TODO? vanilla keeps this in memory as a hashmap, but also i don't care + if let Some(index) = palette.iter().position(|v| *v == value) { + return index as usize; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Global => value as usize, + } + } } impl McBufWritable for PalettedContainer { @@ -79,45 +189,37 @@ impl McBufWritable for PalettedContainer { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaletteType { + SingleValue, + Linear, + Hashmap, + Global, +} + #[derive(Clone, Debug)] pub enum Palette { /// ID of the corresponding entry in its global palette SingleValue(u32), + // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure it's actually needed? Linear(Vec), Hashmap(Vec), Global, } impl Palette { - pub fn block_states_read_with_bits_per_entry( - buf: &mut impl Read, - bits_per_entry: u8, - ) -> Result { - Ok(match bits_per_entry { - 0 => Palette::SingleValue(u32::var_read_from(buf)?), - 1..=4 => Palette::Linear(Vec::::var_read_from(buf)?), - 5..=8 => Palette::Hashmap(Vec::::var_read_from(buf)?), - _ => Palette::Global, - }) - } - - pub fn biomes_read_with_bits_per_entry( - buf: &mut impl Read, - bits_per_entry: u8, - ) -> Result { - Ok(match bits_per_entry { - 0 => Palette::SingleValue(u32::var_read_from(buf)?), - 1..=3 => Palette::Linear(Vec::::var_read_from(buf)?), - _ => Palette::Global, - }) - } - - pub fn value_for(&self, value: usize) -> u32 { + pub fn value_for(&self, id: usize) -> u32 { match self { Palette::SingleValue(v) => *v, - Palette::Linear(v) => v[value], - Palette::Hashmap(v) => v[value], - Palette::Global => value as u32, + Palette::Linear(v) => v[id], + Palette::Hashmap(v) => { + if id >= v.len() { + 0 + } else { + v[id] + } + } + Palette::Global => id as u32, } } } @@ -139,3 +241,123 @@ impl McBufWritable for Palette { Ok(()) } } + +impl PaletteType { + pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self { + match container_type { + PalettedContainerType::BlockStates => match bits_per_entry { + 0 => PaletteType::SingleValue, + 1..=4 => PaletteType::Linear, + 5..=8 => PaletteType::Hashmap, + _ => PaletteType::Global, + }, + PalettedContainerType::Biomes => match bits_per_entry { + 0 => PaletteType::SingleValue, + 1..=3 => PaletteType::Linear, + _ => PaletteType::Global, + }, + } + } + + pub fn read(&self, buf: &mut impl Read) -> Result { + Ok(match self { + PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?), + PaletteType::Linear => Palette::Linear(Vec::::var_read_from(buf)?), + PaletteType::Hashmap => Palette::Hashmap(Vec::::var_read_from(buf)?), + PaletteType::Global => Palette::Global, + }) + } + + pub fn into_empty_palette(&self) -> Palette { + match self { + PaletteType::SingleValue => Palette::SingleValue(0), + PaletteType::Linear => Palette::Linear(Vec::new()), + PaletteType::Hashmap => Palette::Hashmap(Vec::new()), + PaletteType::Global => Palette::Global, + } + } +} + +impl From<&Palette> for PaletteType { + fn from(palette: &Palette) -> Self { + match palette { + Palette::SingleValue(_) => PaletteType::SingleValue, + Palette::Linear(_) => PaletteType::Linear, + Palette::Hashmap(_) => PaletteType::Hashmap, + Palette::Global => PaletteType::Global, + } + } +} + +impl PalettedContainerType { + fn size_bits(&self) -> usize { + match self { + PalettedContainerType::BlockStates => 4, + PalettedContainerType::Biomes => 2, + } + } + + fn size(&self) -> usize { + 1 << self.size_bits() * 3 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resize_0_bits_to_1() { + let mut palette_container = + PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(); + + assert_eq!(palette_container.bits_per_entry, 0); + assert_eq!(palette_container.get_at_index(0), 0); + assert_eq!( + PaletteType::from(&palette_container.palette), + PaletteType::SingleValue + ); + palette_container.set_at_index(0, 1); + assert_eq!(palette_container.get_at_index(0), 1); + assert_eq!( + PaletteType::from(&palette_container.palette), + PaletteType::Linear + ); + } + + #[test] + fn test_resize_0_bits_to_5() { + let mut palette_container = + PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(); + + palette_container.set_at_index(0, 0); // 0 bits + assert_eq!(palette_container.bits_per_entry, 0); + + palette_container.set_at_index(1, 1); // 1 bit + assert_eq!(palette_container.bits_per_entry, 1); + + palette_container.set_at_index(2, 2); // 2 bits + assert_eq!(palette_container.bits_per_entry, 2); + palette_container.set_at_index(3, 3); + + palette_container.set_at_index(4, 4); // 3 bits + assert_eq!(palette_container.bits_per_entry, 3); + palette_container.set_at_index(5, 5); + palette_container.set_at_index(6, 6); + palette_container.set_at_index(7, 7); + + palette_container.set_at_index(8, 8); // 4 bits + assert_eq!(palette_container.bits_per_entry, 4); + palette_container.set_at_index(9, 9); + palette_container.set_at_index(10, 10); + palette_container.set_at_index(11, 11); + palette_container.set_at_index(12, 12); + palette_container.set_at_index(13, 13); + palette_container.set_at_index(14, 14); + palette_container.set_at_index(15, 15); + assert_eq!(palette_container.bits_per_entry, 4); + + palette_container.set_at_index(16, 16); // 5 bits + assert_eq!(palette_container.bits_per_entry, 5); + } +} diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 974d6f61..fa0b0c67 100755 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" [dependencies] azalea-client = {path = "../azalea-client"} azalea-core = {path = "../azalea-core"} +azalea-physics = {path = "../azalea-physics"} 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 0f3ea31a..c8f6bea7 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,12 +1,14 @@ +#![allow(unused_variables, unused_imports)] use azalea_client::{Account, Event}; -use azalea_core::PositionXYZ; +use azalea_core::{PositionXYZ, Vec3}; +use azalea_physics::collision::{HasCollision, MoverType}; #[tokio::main] async fn main() -> Result<(), Box> { println!("Hello, world!"); // let address = "95.111.249.143:10000"; - let address = "localhost:25565"; + let address = "localhost"; // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // .await // .unwrap(); @@ -38,19 +40,31 @@ async fn main() -> Result<(), Box> { // // println!("block state: {:?}", c); // // } // } - Event::Chat(_m) => { - let new_pos = { - let dimension_lock = client.dimension.lock().unwrap(); - let dimension = dimension_lock.as_ref().unwrap(); - let player = client.player.lock().unwrap(); - let entity = player - .entity(dimension) - .expect("Player entity is not in world"); - entity.pos().add_y(0.5) - }; + Event::Chat(m) => { + // let new_pos = { + // let dimension_lock = client.dimension.lock().unwrap(); + // let player = client.player.lock().unwrap(); + // let entity = player + // .entity(&dimension_lock) + // .expect("Player entity is not in world"); + // entity.pos().add_y(-0.5) + // }; - println!("{:?}", new_pos); - client.move_to(new_pos).await.unwrap(); + // println!("{:?}", new_pos); + // client.set_pos(new_pos).await.unwrap(); + // client.move_entity() + + // println!("{}", m.to_ansi(None)); + if let Err(e) = client + .move_entity(&Vec3 { + x: 0., + y: -0.5, + z: 0., + }) + .await + { + eprintln!("{:?}", e); + } } _ => {} }