diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 270b49c7b..3b983fb43 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -327,17 +327,11 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } property_enums.extend(quote! { - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum #property_struct_name { #property_enum_variants } - // impl Property for #property_struct_name { - // type Value = Self; - - // fn try_from_block_state - // } - impl From for #property_struct_name { fn from(value: u32) -> Self { match value { @@ -354,13 +348,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { property_variant_types = vec!["true".to_string(), "false".to_string()]; property_enums.extend(quote! { - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct #property_struct_name(pub bool); - // impl Property for #property_struct_name { - // type Value = bool; - // } - impl From for #property_struct_name { fn from(value: u32) -> Self { match value { @@ -542,10 +532,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // add to properties_to_state_ids let property_variants = properties_to_state_ids .entry(property_value_name_ident.to_string()) - .or_insert_with(Vec::new); - let property_variant_data = property_variants - .iter_mut() - .find(|v| v.ident.to_string() == variant.to_string()); + .or_default(); + let property_variant_data = + property_variants.iter_mut().find(|v| v.ident == variant); if let Some(property_variant_data) = property_variant_data { property_variant_data.block_state_ids.push(state_id); } else { diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index d590bcea4..983de5791 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -167,6 +167,12 @@ impl From for BlockState { } } +impl From for azalea_registry::Block { + fn from(value: BlockState) -> Self { + Box::::from(value).as_registry_block() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 8a98df318..2dec635d3 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -731,7 +731,11 @@ async fn run_schedule_loop( .map(|last_tick| last_tick.elapsed() > Duration::from_millis(50)) .unwrap_or(true) { - last_tick = Some(Instant::now()); + if let Some(last_tick) = &mut last_tick { + *last_tick += Duration::from_millis(50); + } else { + last_tick = Some(Instant::now()); + } ecs.run_schedule(GameTick); } diff --git a/azalea-entity/src/data.rs b/azalea-entity/src/data.rs index 83779b217..b0a05e74d 100755 --- a/azalea-entity/src/data.rs +++ b/azalea-entity/src/data.rs @@ -134,7 +134,7 @@ pub struct Rotations { pub z: f32, } -#[derive(Clone, Debug, Copy, McBuf, Default, Component)] +#[derive(Clone, Debug, Copy, McBuf, Default, Component, Eq, PartialEq)] pub enum Pose { #[default] Standing = 0, diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index bf3dfc823..eb5b5b252 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -116,8 +116,13 @@ pub fn on_pos(offset: f32, chunk_storage: &ChunkStorage, pos: &Position) -> Bloc /// The Minecraft UUID of the entity. For players, this is their actual player /// UUID, and for other entities it's just random. -#[derive(Component, Deref, DerefMut, Clone, Copy)] +#[derive(Component, Deref, DerefMut, Clone, Copy, Default)] pub struct EntityUuid(Uuid); +impl EntityUuid { + pub fn new(uuid: Uuid) -> Self { + Self(uuid) + } +} impl Debug for EntityUuid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { (self.0).fmt(f) @@ -228,6 +233,10 @@ pub struct Physics { pub bounding_box: AABB, pub has_impulse: bool, + + pub horizontal_collision: bool, + // pub minor_horizontal_collision: bool, + pub vertical_collision: bool, } impl Physics { @@ -246,6 +255,9 @@ impl Physics { dimensions, has_impulse: false, + + horizontal_collision: false, + vertical_collision: false, } } } @@ -311,6 +323,7 @@ pub struct EntityBundle { pub attributes: Attributes, pub jumping: Jumping, pub fluid_on_eyes: FluidOnEyes, + pub on_climbable: OnClimbable, } impl EntityBundle { @@ -346,6 +359,7 @@ impl EntityBundle { jumping: Jumping(false), fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty), + on_climbable: OnClimbable(false), } } } @@ -373,6 +387,9 @@ impl FluidOnEyes { } } +#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] +pub struct OnClimbable(bool); + // #[cfg(test)] // mod tests { // use super::*; diff --git a/azalea-entity/src/plugin/mod.rs b/azalea-entity/src/plugin/mod.rs index 9950e6ba3..4b6d99793 100644 --- a/azalea-entity/src/plugin/mod.rs +++ b/azalea-entity/src/plugin/mod.rs @@ -3,6 +3,7 @@ mod relative_updates; use std::collections::HashSet; +use azalea_block::BlockState; use azalea_core::position::{BlockPos, ChunkPos, Vec3}; use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId}; use bevy_app::{App, Plugin, PreUpdate, Update}; @@ -11,7 +12,8 @@ use derive_more::{Deref, DerefMut}; use tracing::debug; use crate::{ - metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, Physics, Position, + metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable, + Physics, Position, }; use indexing::EntityUuidIndex; @@ -48,6 +50,7 @@ impl Plugin for EntityPlugin { add_dead, clamp_look_direction, update_fluid_on_eyes, + update_on_climbable, ), ), ) @@ -106,6 +109,72 @@ pub fn update_fluid_on_eyes( } } +pub fn update_on_climbable( + mut query: Query<(&mut OnClimbable, &Position, &InstanceName)>, + instance_container: Res, +) { + for (mut on_climbable, position, instance_name) in query.iter_mut() { + // TODO: there's currently no gamemode component that can be accessed from here, + // maybe LocalGameMode should be replaced with two components, maybe called + // EntityGameMode and PreviousGameMode? + + // if game_mode == GameMode::Spectator { + // continue; + // } + + let Some(instance) = instance_container.get(instance_name) else { + continue; + }; + + let instance = instance.read(); + + let block_pos = BlockPos::from(position); + let block_state_at_feet = instance.get_block_state(&block_pos).unwrap_or_default(); + let block_at_feet = Box::::from(block_state_at_feet); + let registry_block_at_feet = block_at_feet.as_registry_block(); + + **on_climbable = azalea_registry::tags::blocks::CLIMBABLE.contains(®istry_block_at_feet) + || (azalea_registry::tags::blocks::TRAPDOORS.contains(®istry_block_at_feet) + && is_trapdoor_useable_as_ladder(block_state_at_feet, block_pos, &instance)); + } +} + +fn is_trapdoor_useable_as_ladder( + block_state: BlockState, + block_pos: BlockPos, + instance: &azalea_world::Instance, +) -> bool { + // trapdoor must be open + if !block_state + .property::() + .unwrap_or_default() + { + return false; + } + + // block below must be a ladder + let block_below = instance + .get_block_state(&block_pos.down(1)) + .unwrap_or_default(); + let registry_block_below = + Box::::from(block_below).as_registry_block(); + if registry_block_below != azalea_registry::Block::Ladder { + return false; + } + // and the ladder must be facing the same direction as the trapdoor + let ladder_facing = block_below + .property::() + .expect("ladder block must have facing property"); + let trapdoor_facing = block_state + .property::() + .expect("trapdoor block must have facing property"); + if ladder_facing != trapdoor_facing { + return false; + } + + true +} + /// A component that lists all the local player entities that have this entity /// loaded. If this is empty, the entity will be removed from the ECS. #[derive(Component, Clone, Deref, DerefMut)] diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 2c739b24c..72151b6b3 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -136,7 +136,7 @@ pub fn move_colliding( _mover_type: &MoverType, movement: &Vec3, world: &Instance, - mut position: Mut, + position: &mut Mut, physics: &mut azalea_entity::Physics, ) -> Result<(), MoveEntityError> { // TODO: do all these @@ -175,8 +175,8 @@ pub fn move_colliding( } }; - if new_pos != **position { - **position = new_pos; + if new_pos != ***position { + ***position = new_pos; } } @@ -185,11 +185,14 @@ pub fn move_colliding( let horizontal_collision = x_collision || z_collision; let vertical_collision = movement.y != collide_result.y; let on_ground = vertical_collision && movement.y < 0.; + + physics.horizontal_collision = horizontal_collision; + physics.vertical_collision = vertical_collision; physics.on_ground = on_ground; // TODO: minecraft checks for a "minor" horizontal collision here - let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, &position); + let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, position); // let _block_state_below = self // .world // .get_block_state(&block_pos_below) diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index eec6fdf1f..a69c1bcf2 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -13,7 +13,7 @@ use azalea_core::{ }; use azalea_entity::{ metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity, - LookDirection, Physics, Position, + LookDirection, OnClimbable, Physics, Pose, Position, }; use azalea_world::{Instance, InstanceContainer, InstanceName}; use bevy_app::{App, Plugin}; @@ -52,14 +52,28 @@ fn travel( &mut LookDirection, &mut Position, Option<&Sprinting>, + Option<&Pose>, &Attributes, &InstanceName, + &OnClimbable, + &Jumping, ), (With, With), >, instance_container: Res, ) { - for (mut physics, direction, position, sprinting, attributes, world_name) in &mut query { + for ( + mut physics, + direction, + position, + sprinting, + pose, + attributes, + world_name, + on_climbable, + jumping, + ) in &mut query + { let world_lock = instance_container .get(world_name) .expect("All entities should be in a valid world"); @@ -95,13 +109,18 @@ fn travel( // this applies the current delta let mut movement = handle_relative_friction_and_calculate_movement( - block_friction, - &world, - &mut physics, - &direction, - position, - attributes, - sprinting.map(|s| **s).unwrap_or(false), + HandleRelativeFrictionAndCalculateMovementOpts { + block_friction, + world: &world, + physics: &mut physics, + direction: &direction, + position, + attributes, + is_sprinting: sprinting.map(|s| **s).unwrap_or(false), + on_climbable, + pose, + jumping, + }, ); movement.y -= gravity; @@ -223,15 +242,33 @@ fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos { ) } -fn handle_relative_friction_and_calculate_movement( +// opts for handle_relative_friction_and_calculate_movement +struct HandleRelativeFrictionAndCalculateMovementOpts<'a> { block_friction: f32, - world: &Instance, - physics: &mut Physics, - direction: &LookDirection, - // this is kept as a Mut for bevy change tracking - position: Mut, - attributes: &Attributes, + world: &'a Instance, + physics: &'a mut Physics, + direction: &'a LookDirection, + position: Mut<'a, Position>, + attributes: &'a Attributes, is_sprinting: bool, + on_climbable: &'a OnClimbable, + pose: Option<&'a Pose>, + jumping: &'a Jumping, +} + +fn handle_relative_friction_and_calculate_movement( + HandleRelativeFrictionAndCalculateMovementOpts { + block_friction, + world, + physics, + direction, + mut position, + attributes, + is_sprinting, + on_climbable, + pose, + jumping, + }: HandleRelativeFrictionAndCalculateMovementOpts<'_>, ) -> Vec3 { move_relative( physics, @@ -243,12 +280,14 @@ fn handle_relative_friction_and_calculate_movement( z: physics.zza as f64, }, ); - // entity.delta = entity.handle_on_climbable(entity.delta); + + physics.velocity = handle_on_climbable(physics.velocity, on_climbable, &position, world, pose); + move_colliding( &MoverType::Own, &physics.velocity.clone(), world, - position, + &mut position, physics, ) .expect("Entity should exist."); @@ -258,11 +297,58 @@ fn handle_relative_friction_and_calculate_movement( // || entity.getFeetBlockState().is(Blocks.POWDER_SNOW) && // PowderSnowBlock.canEntityWalkOnPowderSnow(entity))) { var3 = new // Vec3(var3.x, 0.2D, var3.z); } - // TODO: powdered snow + + if physics.horizontal_collision || **jumping { + let block_at_feet: azalea_registry::Block = world + .chunks + .get_block_state(&(*position).into()) + .unwrap_or_default() + .into(); + + // TODO: powdered snow + if **on_climbable || block_at_feet == azalea_registry::Block::PowderSnow { + physics.velocity.y = 0.2; + } + } physics.velocity } +fn handle_on_climbable( + velocity: Vec3, + on_climbable: &OnClimbable, + position: &Position, + world: &Instance, + pose: Option<&Pose>, +) -> Vec3 { + if !**on_climbable { + return velocity; + } + + // minecraft does resetFallDistance here + + const CLIMBING_SPEED: f64 = 0.15_f32 as f64; + + let x = f64::clamp(velocity.x, -CLIMBING_SPEED, CLIMBING_SPEED); + let z = f64::clamp(velocity.z, -CLIMBING_SPEED, CLIMBING_SPEED); + let mut y = f64::max(velocity.y, -CLIMBING_SPEED); + + // sneaking on ladders/vines + if y < 0.0 + && pose.copied() == Some(Pose::Sneaking) + && azalea_registry::Block::from( + world + .chunks + .get_block_state(&position.into()) + .unwrap_or_default(), + ) != azalea_registry::Block::Scaffolding + { + y = 0.; + } + + Vec3 { x, y, z } +} + // private float getFrictionInfluencedSpeed(float friction) { // return this.onGround ? this.getSpeed() * (0.21600002F / (friction * // friction * friction)) : this.flyingSpeed; } diff --git a/azalea/src/auto_tool.rs b/azalea/src/auto_tool.rs index 55ec69248..2cf530858 100644 --- a/azalea/src/auto_tool.rs +++ b/azalea/src/auto_tool.rs @@ -43,6 +43,8 @@ pub fn best_tool_in_hotbar_for_block(block: BlockState, menu: &Menu) -> BestTool dimensions: Default::default(), bounding_box: Default::default(), has_impulse: Default::default(), + horizontal_collision: Default::default(), + vertical_collision: Default::default(), }, &FluidOnEyes::new(Fluid::Empty), ) diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 88261b7f0..b41d895a6 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -7,13 +7,13 @@ use azalea_client::{ }; use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; use azalea_entity::{ - attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics, - Position, + attributes::AttributeInstance, Attributes, EntityDimensions, Physics, Position, }; -use azalea_world::{ChunkStorage, Instance, InstanceContainer, InstanceName, MinecraftEntityId}; +use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId}; use bevy_app::App; use bevy_ecs::prelude::*; use parking_lot::RwLock; +use uuid::Uuid; #[derive(Bundle, Clone)] pub struct SimulatedPlayerBundle { @@ -82,24 +82,24 @@ impl Simulation { schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); }); - let entity = app - .world - .spawn(( - MinecraftEntityId(0), - InstanceName(instance_name), - azalea_entity::LocalEntity, - azalea_entity::Jumping::default(), - azalea_entity::LookDirection::default(), - Sprinting(true), - azalea_entity::metadata::Player, - azalea_entity::EyeHeight::new(player.physics.dimensions.height * 0.85), - player, - )) - .id(); + let mut entity = app.world.spawn(( + MinecraftEntityId(0), + azalea_entity::LocalEntity, + azalea_entity::metadata::PlayerMetadataBundle::default(), + azalea_entity::EntityBundle::new( + Uuid::nil(), + *player.position, + azalea_registry::EntityKind::Player, + instance_name, + ), + )); + entity.insert(player); + + let entity_id = entity.id(); Self { app, - entity, + entity: entity_id, _instance: instance, } }