diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index af5354159..87aece2c9 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -30,7 +30,7 @@ use azalea_core::{position::Vec3, tick::GameTick}; use azalea_entity::{ indexing::{EntityIdIndex, EntityUuidIndex}, metadata::Health, - EntityPlugin, EntityUpdateSet, EyeHeight, LocalEntity, Position, + EntityPlugin, EntityUpdateSet, EyeHeight, LocalEntity, Position, Sneaking, }; use azalea_physics::PhysicsPlugin; use azalea_protocol::{ @@ -640,6 +640,7 @@ pub struct JoinedClientBundle { pub permission_level: PermissionLevel, pub chunk_batch_info: ChunkBatchInfo, pub hunger: Hunger, + pub sneaking: Sneaking, pub entity_id_index: EntityIdIndex, diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index b0582d8ba..fdee00c31 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -47,21 +47,6 @@ pub struct LocalGameMode { pub previous: Option, } -/// A component that contains the abilities the player has, like flying -/// or instantly breaking blocks. This is only present on local players. -#[derive(Clone, Debug, Component, Default)] -pub struct PlayerAbilities { - pub invulnerable: bool, - pub flying: bool, - pub can_fly: bool, - /// Whether the player can instantly break blocks and can duplicate blocks - /// in their inventory. - pub instant_break: bool, - - pub flying_speed: f32, - /// Used for the fov - pub walking_speed: f32, -} impl From<&ClientboundPlayerAbilitiesPacket> for PlayerAbilities { fn from(packet: &ClientboundPlayerAbilitiesPacket) -> Self { Self { diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index ba47b7c84..6e61ade42 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -1,9 +1,12 @@ use crate::client::Client; +use crate::inventory::InventoryComponent; +use crate::local_player::PlayerAbilities; use crate::packet_handling::game::SendPacketEvent; use azalea_core::position::Vec3; use azalea_core::tick::GameTick; +use azalea_entity::metadata::{ShiftKeyDown, Sleeping, SleepingPos, Swimming}; use azalea_entity::{metadata::Sprinting, Attributes, Jumping}; -use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position}; +use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position, Sneaking}; use azalea_physics::{ai_step, PhysicsSet}; use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket; use azalea_protocol::packets::game::{ @@ -12,10 +15,11 @@ use azalea_protocol::packets::game::{ serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket, serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket, }; -use azalea_world::{MinecraftEntityId, MoveEntityError}; +use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, MoveEntityError}; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::{Event, EventWriter}; use bevy_ecs::schedule::SystemSet; +use bevy_ecs::system::Res; use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, query::With, schedule::IntoSystemConfigs, system::Query, @@ -57,11 +61,13 @@ impl Plugin for PlayerMovePlugin { .add_systems( GameTick, ( - (tick_controls, local_player_ai_step) + (update_sneaking, tick_controls, local_player_ai_step) .chain() .in_set(PhysicsSet) .before(ai_step), - send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk), + (send_sprinting_if_needed, send_shift_key_down_if_needed) + .chain() + .after(azalea_entity::update_in_loaded_chunk), send_position.after(PhysicsSet), ) .chain(), @@ -117,7 +123,7 @@ pub struct LastSentLookDirection { pub y_rot: f32, } -/// Component for entities that can move and sprint. Usually only in +/// Component for entities that can move, sprint, and sneak. Usually only in /// [`LocalEntity`]s. /// /// [`LocalEntity`]: azalea_entity::LocalEntity @@ -126,7 +132,15 @@ pub struct PhysicsState { /// 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, + pub was_sprinting: bool, + + /// Whether the player was sneaking last tick. You shouldn't modify this. + /// + /// If you want to check or change the player's sneak state from the ECS, + /// use the [`ShiftKeyDown`] component. + pub was_sneaking: bool, + // Whether we're going to try to start sprinting this tick. Equivalent to // holding down ctrl for a tick. pub trying_to_sprint: bool, @@ -251,10 +265,11 @@ fn send_sprinting_if_needed( mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>, mut send_packet_events: EventWriter, ) { - for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() { + for (entity, &minecraft_entity_id, &Sprinting(sprinting), mut physics_state) in query.iter_mut() + { let was_sprinting = physics_state.was_sprinting; - if **sprinting != was_sprinting { - let sprinting_action = if **sprinting { + if sprinting != was_sprinting { + let sprinting_action = if sprinting { azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting } else { azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting @@ -262,22 +277,122 @@ fn send_sprinting_if_needed( send_packet_events.send(SendPacketEvent { entity, packet: ServerboundPlayerCommandPacket { - id: **minecraft_entity_id, + id: *minecraft_entity_id, action: sprinting_action, data: 0, } .get(), }); - physics_state.was_sprinting = **sprinting; + physics_state.was_sprinting = sprinting; + } + } +} + +fn send_shift_key_down_if_needed( + mut query: Query<(Entity, &MinecraftEntityId, &ShiftKeyDown, &mut PhysicsState)>, + mut send_packet_events: EventWriter, +) { + for (entity, &minecraft_entity_id, &ShiftKeyDown(sneaking), mut physics_state) in + query.iter_mut() + { + let was_sneaking = physics_state.was_sneaking; + if sneaking != was_sneaking { + let sneaking_action = if sneaking { + azalea_protocol::packets::game::serverbound_player_command_packet::Action::PressShiftKey + } else { + azalea_protocol::packets::game::serverbound_player_command_packet::Action::ReleaseShiftKey + }; + send_packet_events.send(SendPacketEvent { + entity, + packet: ServerboundPlayerCommandPacket { + id: *minecraft_entity_id, + action: sneaking_action, + data: 0, + } + .get(), + }); + physics_state.was_sneaking = sneaking; } } } +fn can_player_fit_within_blocks_and_entities_when( + _pose: azalea_entity::Pose, + _position: Vec3, + _instance: &azalea_world::Instance, +) -> bool { + // TODO + true +} + +pub fn update_sneaking( + mut query: Query<( + &PlayerAbilities, + &Swimming, + // TODO: isPassenger + &ShiftKeyDown, + &SleepingPos, + &Position, + &InstanceName, + &mut Sneaking, + )>, + instances: Res, +) { + for ( + player_abilities, + &Swimming(swimming), + &ShiftKeyDown(shift_key_down), + &SleepingPos(sleeping_pos), + &Position(position), + instance_name, + mut sneaking, + ) in query.iter_mut() + { + let Some(instance) = instances.get(instance_name) else { + continue; + }; + // this.crouching = !this.getAbilities().flying + // && !this.isSwimming() + // && !this.isPassenger() + // && this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING) + // && (this.isShiftKeyDown() + // || !this.isSleeping() && !this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.STANDING)); + + let instance = instance.read(); + + **sneaking = !player_abilities.flying + && !swimming + // && !isPassenger + // && this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING) + && can_player_fit_within_blocks_and_entities_when( + azalea_entity::Pose::Crouching, + position, + &instance, + ) + && (shift_key_down + || ( + sleeping_pos.is_none() + && !can_player_fit_within_blocks_and_entities_when(azalea_entity::Pose::Standing, position, &instance) + ) + ); + } +} + +fn is_moving_slowly(sneaking: bool, is_visually_crawling: bool) -> bool { + sneaking || is_visually_crawling +} + /// Update the impulse from self.move_direction. The multiplier is used for /// sneaking. -pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) { - for mut physics_state in query.iter_mut() { - let multiplier: Option = None; +pub(crate) fn tick_controls(mut query: Query<(&mut PhysicsState, &Sneaking, &InventoryComponent)>) { + for (mut physics_state, &Sneaking(sneaking), inventory) in query.iter_mut() { + let multiplier: Option = if is_moving_slowly(sneaking, false) { + let sneaking_speed_bonus = + azalea_entity::enchantments::get_sneaking_speed_bonus(&inventory.inventory_menu); + Some(f32::clamp(0.3 + sneaking_speed_bonus, 0., 1.)) + } else { + None + }; let mut forward_impulse: f32 = 0.; let mut left_impulse: f32 = 0.; @@ -397,6 +512,27 @@ impl Client { direction, }); } + + /// Start or stop sneaking. + /// + /// ```rust,no_run + /// # use azalea_client::{Client, WalkDirection, SprintDirection}; + /// # use std::time::Duration; + /// # async fn example(mut bot: Client) { + /// // toggle sneak + /// bot.sneak(!bot.sneaking()); + /// # } + /// ``` + pub fn sneak(&mut self, sneaking: bool) { + let mut ecs = self.ecs.lock(); + let mut sneaking_component = self.query::<&mut ShiftKeyDown>(&mut ecs); + **sneaking_component = sneaking; + } + + /// Returns whether the player is sneaking. + pub fn sneaking(&self) -> bool { + *self.component::() + } } /// An event sent when the client starts walking. This does not get sent for diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index da5ce57fe..1f40d947b 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -136,8 +136,9 @@ pub fn process_packet_events(ecs: &mut World) { last_sent_direction: crate::movement::LastSentLookDirection::default(), abilities: crate::local_player::PlayerAbilities::default(), permission_level: crate::local_player::PermissionLevel::default(), - hunger: Hunger::default(), chunk_batch_info: crate::chunks::ChunkBatchInfo::default(), + hunger: Hunger::default(), + sneaking: azalea_entity::Sneaking::default(), entity_id_index: EntityIdIndex::default(), diff --git a/azalea-entity/src/data.rs b/azalea-entity/src/data.rs index b0a05e74d..75d6d9838 100755 --- a/azalea-entity/src/data.rs +++ b/azalea-entity/src/data.rs @@ -142,9 +142,19 @@ pub enum Pose { Sleeping, Swimming, SpinAttack, - Sneaking, + Crouching, LongJumping, Dying, + Croaking, + UsingTongue, + Sitting, + Roaring, + Sniffing, + Emerging, + Digging, + Sliding, + Shooting, + Inhaling, } #[derive(Debug, Clone, McBuf)] diff --git a/azalea-entity/src/enchantments.rs b/azalea-entity/src/enchantments.rs index fd238bf2d..542749139 100644 --- a/azalea-entity/src/enchantments.rs +++ b/azalea-entity/src/enchantments.rs @@ -1,8 +1,14 @@ +use azalea_registry::Enchantment; + pub fn get_enchant_level( - _enchantment: azalea_registry::Enchantment, + _enchantment: Enchantment, _player_inventory: &azalea_inventory::Menu, ) -> u32 { // TODO 0 } + +pub fn get_sneaking_speed_bonus(player_inventory: &azalea_inventory::Menu) -> f32 { + get_enchant_level(Enchantment::SwiftSneak, player_inventory) as f32 * 0.15 +} diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index dd818c6dc..f444dfd07 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -4,7 +4,7 @@ pub mod attributes; mod data; mod dimensions; mod effects; -mod enchantments; +pub mod enchantments; pub mod metadata; pub mod mining; mod plugin; @@ -138,7 +138,7 @@ impl Debug for EntityUuid { /// You are free to change this; there's systems that update the indexes /// automatically. #[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] -pub struct Position(Vec3); +pub struct Position(pub Vec3); impl Position { pub fn new(pos: Vec3) -> Self { Self(pos) @@ -425,6 +425,26 @@ impl FluidOnEyes { #[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] pub struct OnClimbable(bool); +/// A component that indicates whether the player is currently sneaking. +#[derive(Component, Clone, Deref, DerefMut, Default)] +pub struct Sneaking(pub bool); + +/// A component that contains the abilities the player has, like flying +/// or instantly breaking blocks. This is only present on local players. +#[derive(Clone, Debug, Component, Default)] +pub struct PlayerAbilities { + pub invulnerable: bool, + pub flying: bool, + pub can_fly: bool, + /// Whether the player can instantly break blocks and can duplicate blocks + /// in their inventory. + pub instant_break: bool, + + pub flying_speed: f32, + /// Used for the fov + pub walking_speed: f32, +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/azalea-entity/src/metadata.rs b/azalea-entity/src/metadata.rs index c73136b3b..73cb24f19 100644 --- a/azalea-entity/src/metadata.rs +++ b/azalea-entity/src/metadata.rs @@ -1986,7 +1986,7 @@ impl Default for DolphinMetadataBundle { aggressive: Aggressive(false), }, }, - treasure_pos: TreasurePos(Default::default()), + treasure_pos: TreasurePos(BlockPos::new(0, 0, 0)), got_fish: GotFish(false), moistness_level: MoistnessLevel(2400), } @@ -3014,7 +3014,7 @@ impl Default for FallingBlockMetadataBundle { pose: Pose::default(), ticks_frozen: TicksFrozen(0), }, - start_pos: StartPos(Default::default()), + start_pos: StartPos(BlockPos::new(0, 0, 0)), } } } @@ -8604,10 +8604,10 @@ impl Default for TurtleMetadataBundle { abstract_ageable_baby: AbstractAgeableBaby(false), }, }, - home_pos: HomePos(Default::default()), + home_pos: HomePos(BlockPos::new(0, 0, 0)), has_egg: HasEgg(false), laying_egg: LayingEgg(false), - travel_pos: TravelPos(Default::default()), + travel_pos: TravelPos(BlockPos::new(0, 0, 0)), going_home: GoingHome(false), travelling: Travelling(false), } diff --git a/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs b/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs index 20c2ddc30..de1ec9723 100644 --- a/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs +++ b/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs @@ -1,5 +1,5 @@ use syn::{ - braced, + braced, parse::{Parse, ParseStream, Result}, Ident, LitInt, Token, }; diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 3986dc477..5fb67fa77 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -7,6 +7,7 @@ mod world_collisions; use std::ops::Add; use azalea_core::{aabb::AABB, direction::Axis, math::EPSILON, position::Vec3}; +use azalea_entity::{metadata::ShiftKeyDown, PlayerAbilities}; use azalea_world::{Instance, MoveEntityError}; use bevy_ecs::world::Mut; pub use blocks::BlockWithShape; @@ -138,6 +139,7 @@ pub fn move_colliding( world: &Instance, position: &mut Mut, physics: &mut azalea_entity::Physics, + abilities: &PlayerAbilities, ) -> Result<(), MoveEntityError> { // TODO: do all these @@ -250,6 +252,24 @@ pub fn move_colliding( Ok(()) } +fn maybe_back_off_from_edge( + abilities: &PlayerAbilities, + movement: &Vec3, + mover_type: MoverType, + is_shift_key_held: &ShiftKeyDown +) -> Vec3 { + // if (!this.abilities.flying + // && movement.y <= 0.0 + // && (moverType == MoverType.SELF || moverType == MoverType.PLAYER) + // && this.isStayingOnGroundSurface() + // && this.isAboveGround()) { + + let is_backing_off = !abilities.flying + && movement.y <= 0. + && matches!(mover_type, MoverType::Own | MoverType::Player) + +} + fn collide_bounding_box( movement: &Vec3, entity_bounding_box: &AABB, diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index ed813b569..f2c056451 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -335,7 +335,7 @@ fn handle_on_climbable( // sneaking on ladders/vines if y < 0.0 - && pose.copied() == Some(Pose::Sneaking) + && pose.copied() == Some(Pose::Crouching) && azalea_registry::Block::from( world .chunks diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs index 4957533f2..fb9d6f54c 100644 --- a/azalea/examples/testbot/commands/movement.rs +++ b/azalea/examples/testbot/commands/movement.rs @@ -136,11 +136,19 @@ pub fn register(commands: &mut CommandDispatcher>) { tokio::time::sleep(Duration::from_secs_f32(seconds)).await; bot.walk(WalkDirection::None); }); - source.reply(&format!("ok, spriting for {seconds} seconds")); + source.reply(&format!("ok, sprinting for {seconds} seconds")); 1 })), ); + commands.register(literal("sneak").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + let mut bot = source.bot.clone(); + bot.sneak(!bot.sneaking()); + source.reply("ok"); + 1 + })); + commands.register(literal("north").executes(|ctx: &Ctx| { let mut source = ctx.source.lock(); source.bot.set_direction(180., 0.); diff --git a/codegen/lib/code/entity.py b/codegen/lib/code/entity.py index 8fa114307..d22651e84 100644 --- a/codegen/lib/code/entity.py +++ b/codegen/lib/code/entity.py @@ -347,7 +347,7 @@ def maybe_rename_field(name: str, index: int) -> str: # _marker: Allay, # parent: AbstractCreatureBundle { # on_fire: OnFire(false), - # shift_key_down: ShiftKeyDown(false), + # sneaking: ShiftKeyDown(false), # }, # sprinting: Sprinting(false), # swimming: Swimming(false) @@ -360,7 +360,7 @@ def maybe_rename_field(name: str, index: int) -> str: def generate_fields(this_entity_id: str): # on_fire: OnFire(false), - # shift_key_down: ShiftKeyDown(false), + # sneaking: ShiftKeyDown(false), # _marker this_entity_struct_name = upper_first_letter(