aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-05-10 06:22:08 +0330
committermat <git@matdoes.dev>2025-05-10 06:22:08 +0330
commite1d3b902ba08170e4ee82c53f216445f57fbc47e (patch)
treee383f7bb54edf654016a5c370786e6cb67ed28c4
parente9b3128103d203ad4902a40124e4d22a012c196a (diff)
downloadazalea-drasl-e1d3b902ba08170e4ee82c53f216445f57fbc47e.tar.xz
add StartUseItemEvent and improve code related to interactions
-rw-r--r--CHANGELOG.md3
-rw-r--r--azalea-client/src/plugins/chunks.rs4
-rw-r--r--azalea-client/src/plugins/interact.rs259
-rw-r--r--azalea-client/src/plugins/mining.rs56
-rw-r--r--azalea-core/src/aabb.rs2
-rw-r--r--azalea-core/src/block_hit_result.rs34
-rw-r--r--azalea-core/src/hit_result.rs68
-rw-r--r--azalea-core/src/lib.rs2
-rw-r--r--azalea-entity/src/lib.rs4
-rw-r--r--azalea-physics/src/clip.rs2
-rw-r--r--azalea-physics/src/collision/shape.rs2
-rw-r--r--azalea-protocol/src/packets/game/s_interact.rs3
-rw-r--r--azalea-protocol/src/packets/game/s_use_item.rs4
-rw-r--r--azalea-protocol/src/packets/game/s_use_item_on.rs17
-rw-r--r--azalea/examples/testbot/commands/debug.rs11
-rw-r--r--azalea/src/pathfinder/goals.rs2
16 files changed, 327 insertions, 146 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af76fc77..e54daf8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,8 @@ is breaking anyways, semantic versioning is not followed.
- [BREAKING] The `BlockState::id` field is now private, use `.id()` instead.
- [BREAKING] Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/).
- [BREAKING] Rename `InstanceContainer::insert` to `get_or_insert`.
-- ClientBuilder and SwarmBuilder are now Send.
+- [BREAKING] Replace `BlockInteractEvent` with the more general-purpose `StartUseItemEvent`, and add `client.start_use_item()`.
+- `ClientBuilder` and `SwarmBuilder` are now Send.
### Fixed
diff --git a/azalea-client/src/plugins/chunks.rs b/azalea-client/src/plugins/chunks.rs
index 2d31aabb..5e062887 100644
--- a/azalea-client/src/plugins/chunks.rs
+++ b/azalea-client/src/plugins/chunks.rs
@@ -17,7 +17,7 @@ use bevy_ecs::prelude::*;
use tracing::{error, trace};
use crate::{
- InstanceHolder, interact::handle_block_interact_event, inventory::InventorySet,
+ InstanceHolder, interact::handle_start_use_item_queued, inventory::InventorySet,
packet::game::SendPacketEvent, respawn::perform_respawn,
};
@@ -33,7 +33,7 @@ impl Plugin for ChunksPlugin {
)
.chain()
.before(InventorySet)
- .before(handle_block_interact_event)
+ .before(handle_start_use_item_queued)
.before(perform_respawn),
)
.add_event::<ReceiveChunkEvent>()
diff --git a/azalea-client/src/plugins/interact.rs b/azalea-client/src/plugins/interact.rs
index 81c69121..5ec95b46 100644
--- a/azalea-client/src/plugins/interact.rs
+++ b/azalea-client/src/plugins/interact.rs
@@ -1,21 +1,22 @@
-use std::ops::AddAssign;
-
use azalea_block::BlockState;
use azalea_core::{
- block_hit_result::BlockHitResult,
direction::Direction,
game_type::GameMode,
+ hit_result::{BlockHitResult, HitResult},
position::{BlockPos, Vec3},
+ tick::GameTick,
};
use azalea_entity::{
Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
};
use azalea_inventory::{ItemStack, ItemStackData, components};
-use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
+use azalea_physics::{
+ PhysicsSet,
+ clip::{BlockShapeType, ClipContext, FluidPickType},
+};
use azalea_protocol::packets::game::{
- s_interact::InteractionHand,
- s_swing::ServerboundSwing,
- s_use_item_on::{BlockHit, ServerboundUseItemOn},
+ ServerboundUseItem, s_interact::InteractionHand, s_swing::ServerboundSwing,
+ s_use_item_on::ServerboundUseItemOn,
};
use azalea_world::{Instance, InstanceContainer, InstanceName};
use bevy_app::{App, Plugin, Update};
@@ -23,6 +24,7 @@ use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use tracing::warn;
+use super::mining::{Mining, MiningSet};
use crate::{
Client,
attack::handle_attack_event,
@@ -37,14 +39,14 @@ use crate::{
pub struct InteractPlugin;
impl Plugin for InteractPlugin {
fn build(&self, app: &mut App) {
- app.add_event::<BlockInteractEvent>()
+ app.add_event::<StartUseItemEvent>()
.add_event::<SwingArmEvent>()
.add_systems(
Update,
(
(
+ handle_start_use_item_event,
update_hit_result_component.after(clamp_look_direction),
- handle_block_interact_event,
handle_swing_arm_event,
)
.after(InventorySet)
@@ -56,34 +58,47 @@ impl Plugin for InteractPlugin {
.after(MoveEventsSet),
),
)
+ .add_systems(
+ GameTick,
+ handle_start_use_item_queued
+ .after(MiningSet)
+ .before(PhysicsSet),
+ )
.add_observer(handle_swing_arm_trigger);
}
}
impl Client {
- /// Right click a block. The behavior of this depends on the target block,
+ /// Right-click a block.
+ ///
+ /// The behavior of this depends on the target block,
/// and it'll either place the block you're holding in your hand or use the
/// block you clicked (like toggling a lever).
///
/// Note that this may trigger anticheats as it doesn't take into account
/// whether you're actually looking at the block.
pub fn block_interact(&self, position: BlockPos) {
- self.ecs.lock().send_event(BlockInteractEvent {
+ self.ecs.lock().send_event(StartUseItemEvent {
entity: self.entity,
- position,
+ hand: InteractionHand::MainHand,
+ force_block: Some(position),
});
}
-}
-/// Right click a block. The behavior of this depends on the target block,
-/// and it'll either place the block you're holding in your hand or use the
-/// block you clicked (like toggling a lever).
-#[derive(Event)]
-pub struct BlockInteractEvent {
- /// The local player entity that's opening the container.
- pub entity: Entity,
- /// The coordinates of the container.
- pub position: BlockPos,
+ /// Use the current item.
+ ///
+ /// If the item is consumable, then it'll act as if right-click was held
+ /// until the item finished being consumed. You can use this to eat food.
+ ///
+ /// If we're looking at a block or entity, then it will be clicked. Also see
+ /// [`Client::block_interact`].
+ pub fn start_use_item(&self) {
+ self.ecs.lock().send_event(StartUseItemEvent {
+ entity: self.entity,
+ hand: InteractionHand::MainHand,
+ force_block: None,
+ });
+ }
}
/// A component that contains the number of changes this client has made to
@@ -91,66 +106,149 @@ pub struct BlockInteractEvent {
#[derive(Component, Copy, Clone, Debug, Default, Deref)]
pub struct CurrentSequenceNumber(u32);
-impl AddAssign<u32> for CurrentSequenceNumber {
- fn add_assign(&mut self, rhs: u32) {
- self.0 += rhs;
+impl CurrentSequenceNumber {
+ /// Get the next sequence number that we're going to use and increment the
+ /// value.
+ pub fn get_and_increment(&mut self) -> u32 {
+ let cur = self.0;
+ self.0 += 1;
+ cur
}
}
-/// A component that contains the block that the player is currently looking at.
+/// A component that contains the block or entity that the player is currently
+/// looking at.
#[doc(alias("looking at", "looking at block", "crosshair"))]
#[derive(Component, Clone, Debug, Deref, DerefMut)]
-pub struct HitResultComponent(BlockHitResult);
+pub struct HitResultComponent(HitResult);
-pub fn handle_block_interact_event(
- mut events: EventReader<BlockInteractEvent>,
- mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
+/// An event that makes one of our clients simulate a right-click.
+///
+/// This event just inserts the [`StartUseItemQueued`] component on the given
+/// entity.
+#[doc(alias("right click"))]
+#[derive(Event)]
+pub struct StartUseItemEvent {
+ pub entity: Entity,
+ pub hand: InteractionHand,
+ /// See [`QueuedStartUseItem::force_block`].
+ pub force_block: Option<BlockPos>,
+}
+pub fn handle_start_use_item_event(
mut commands: Commands,
+ mut events: EventReader<StartUseItemEvent>,
) {
for event in events.read() {
- let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
- warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
- continue;
- };
-
- // TODO: check to make sure we're within the world border
+ commands.entity(event.entity).insert(StartUseItemQueued {
+ hand: event.hand,
+ force_block: event.force_block,
+ });
+ }
+}
- *sequence_number += 1;
+/// A component that makes our client simulate a right-click on the next
+/// [`GameTick`]. It's removed after that tick.
+///
+/// You may find it more convenient to use [`StartUseItemEvent`] instead, which
+/// just inserts this component for you.
+///
+/// [`GameTick`]: azalea_core::tick::GameTick
+#[derive(Component)]
+pub struct StartUseItemQueued {
+ pub hand: InteractionHand,
+ /// Optionally force us to send a [`ServerboundUseItemOn`] on the given
+ /// block.
+ ///
+ /// This is useful if you want to interact with a block without looking at
+ /// it, but should be avoided to stay compatible with anticheats.
+ pub force_block: Option<BlockPos>,
+}
+pub fn handle_start_use_item_queued(
+ mut commands: Commands,
+ query: Query<(
+ Entity,
+ &StartUseItemQueued,
+ &mut CurrentSequenceNumber,
+ &HitResultComponent,
+ &LookDirection,
+ Option<&Mining>,
+ )>,
+) {
+ for (entity, start_use_item, mut sequence_number, hit_result, look_direction, mining) in query {
+ commands.entity(entity).remove::<StartUseItemQueued>();
- // minecraft also does the interaction client-side (so it looks like clicking a
- // button is instant) but we don't really need that
+ if mining.is_some() {
+ warn!("Got a StartUseItemEvent for a client that was mining");
+ }
- // the block_hit data will depend on whether we're looking at the block and
- // whether we can reach it
+ // TODO: this also skips if LocalPlayer.handsBusy is true, which is used when
+ // rowing a boat
+
+ let mut hit_result = hit_result.0.clone();
+
+ if let Some(force_block) = start_use_item.force_block {
+ let hit_result_matches = if let HitResult::Block(block_hit_result) = hit_result {
+ block_hit_result.block_pos == force_block
+ } else {
+ false
+ };
+
+ if !hit_result_matches {
+ // we're not looking at the block, so make up some numbers
+ hit_result = HitResult::Block(BlockHitResult {
+ location: force_block.center(),
+ direction: Direction::Up,
+ block_pos: force_block,
+ inside: false,
+ world_border: false,
+ miss: false,
+ });
+ }
+ }
- let block_hit = if hit_result.block_pos == event.position {
- // we're looking at the block :)
- BlockHit {
- block_pos: hit_result.block_pos,
- direction: hit_result.direction,
- location: hit_result.location,
- inside: hit_result.inside,
- world_border: hit_result.world_border,
+ match hit_result {
+ HitResult::Block(block_hit_result) => {
+ if block_hit_result.miss {
+ commands.trigger(SendPacketEvent::new(
+ entity,
+ ServerboundUseItem {
+ hand: start_use_item.hand,
+ sequence: sequence_number.get_and_increment(),
+ x_rot: look_direction.x_rot,
+ y_rot: look_direction.y_rot,
+ },
+ ));
+ } else {
+ commands.trigger(SendPacketEvent::new(
+ entity,
+ ServerboundUseItemOn {
+ hand: start_use_item.hand,
+ block_hit: block_hit_result.into(),
+ sequence: sequence_number.get_and_increment(),
+ },
+ ));
+ // TODO: depending on the result of useItemOn, this might
+ // also need to send a SwingArmEvent.
+ // basically, this TODO is for
+ // simulating block interactions/placements on the
+ // client-side.
+ }
}
- } else {
- // we're not looking at the block, so make up some numbers
- BlockHit {
- block_pos: event.position,
- direction: Direction::Up,
- location: event.position.center(),
- inside: false,
- world_border: false,
+ HitResult::Entity => {
+ // TODO: implement HitResult::Entity
+
+ // TODO: worldborder check
+
+ // commands.trigger(SendPacketEvent::new(
+ // entity,
+ // ServerboundInteract {
+ // entity_id: todo!(),
+ // action: todo!(),
+ // using_secondary_action: todo!(),
+ // },
+ // ));
}
- };
-
- commands.trigger(SendPacketEvent::new(
- entity,
- ServerboundUseItemOn {
- hand: InteractionHand::MainHand,
- block_hit,
- sequence: sequence_number.0,
- },
- ));
+ }
}
}
@@ -198,19 +296,40 @@ pub fn update_hit_result_component(
}
}
-/// Get the block that a player would be looking at if their eyes were at the
-/// given direction and position.
+/// Get the block or entity that a player would be looking at if their eyes were
+/// at the given direction and position.
///
-/// If you need to get the block the player is looking at right now, use
+/// If you need to get the block/entity the player is looking at right now, use
/// [`HitResultComponent`].
+///
+/// Also see [`pick_block`].
+///
+/// TODO: does not currently check for entities
pub fn pick(
look_direction: &LookDirection,
eye_position: &Vec3,
chunks: &azalea_world::ChunkStorage,
pick_range: f64,
+) -> HitResult {
+ // TODO
+ // let entity_hit_result = ;
+
+ HitResult::Block(pick_block(look_direction, eye_position, chunks, pick_range))
+}
+
+/// Get the block that a player would be looking at if their eyes were at the
+/// given direction and position.
+///
+/// Also see [`pick`].
+pub fn pick_block(
+ look_direction: &LookDirection,
+ eye_position: &Vec3,
+ chunks: &azalea_world::ChunkStorage,
+ pick_range: f64,
) -> BlockHitResult {
let view_vector = view_vector(look_direction);
let end_position = eye_position + &(view_vector * pick_range);
+
azalea_physics::clip::clip(
chunks,
ClipContext {
diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs
index 7882ddc6..797c4361 100644
--- a/azalea-client/src/plugins/mining.rs
+++ b/azalea-client/src/plugins/mining.rs
@@ -39,7 +39,8 @@ impl Plugin for MiningPlugin {
handle_mining_queued,
)
.chain()
- .before(PhysicsSet),
+ .before(PhysicsSet)
+ .in_set(MiningSet),
)
.add_systems(
Update,
@@ -56,7 +57,7 @@ impl Plugin for MiningPlugin {
.after(azalea_entity::update_fluid_on_eyes)
.after(crate::interact::update_hit_result_component)
.after(crate::attack::handle_attack_event)
- .after(crate::interact::handle_block_interact_event)
+ .after(crate::interact::handle_start_use_item_queued)
.before(crate::interact::handle_swing_arm_event),
);
}
@@ -121,22 +122,25 @@ fn handle_auto_mine(
current_mining_item,
) in &mut query.iter_mut()
{
- let block_pos = hit_result_component.block_pos;
-
- if (mining.is_none()
- || !is_same_mining_target(
- block_pos,
- inventory,
- current_mining_pos,
- current_mining_item,
- ))
- && !hit_result_component.miss
+ let block_pos = hit_result_component
+ .as_block_hit_result_if_not_miss()
+ .map(|b| b.block_pos);
+
+ // start mining if we're looking at a block and we're not already mining it
+ if let Some(block_pos) = block_pos
+ && (mining.is_none()
+ || !is_same_mining_target(
+ block_pos,
+ inventory,
+ current_mining_pos,
+ current_mining_item,
+ ))
{
start_mining_block_event.write(StartMiningBlockEvent {
entity,
position: block_pos,
});
- } else if mining.is_some() && hit_result_component.miss {
+ } else if mining.is_some() && hit_result_component.is_miss() {
stop_mining_block_event.write(StopMiningBlockEvent { entity });
}
}
@@ -166,9 +170,11 @@ fn handle_start_mining_block_event(
) {
for event in events.read() {
let hit_result = query.get_mut(event.entity).unwrap();
- let direction = if hit_result.block_pos == event.position {
+ let direction = if let Some(block_hit_result) = hit_result.as_block_hit_result_if_not_miss()
+ && block_hit_result.block_pos == event.position
+ {
// we're looking at the block
- hit_result.direction
+ block_hit_result.direction
} else {
// we're not looking at the block, arbitrary direction
Direction::Down
@@ -241,7 +247,6 @@ fn handle_mining_queued(
// is outside of the worldborder
if game_mode.current == GameMode::Creative {
- *sequence_number += 1;
finish_mining_events.write(FinishMiningBlockEvent {
entity,
position: mining_queued.position,
@@ -318,14 +323,13 @@ fn handle_mining_queued(
});
}
- *sequence_number += 1;
commands.trigger(SendPacketEvent::new(
entity,
ServerboundPlayerAction {
action: s_player_action::Action::StartDestroyBlock,
pos: mining_queued.position,
direction: mining_queued.direction,
- sequence: **sequence_number,
+ sequence: sequence_number.get_and_increment(),
},
));
commands.trigger(SwingArmEvent { entity });
@@ -558,14 +562,13 @@ pub fn continue_mining_block(
entity,
position: mining.pos,
});
- *sequence_number += 1;
commands.trigger(SendPacketEvent::new(
entity,
ServerboundPlayerAction {
action: s_player_action::Action::StartDestroyBlock,
pos: mining.pos,
direction: mining.dir,
- sequence: **sequence_number,
+ sequence: sequence_number.get_and_increment(),
},
));
commands.trigger(SwingArmEvent { entity });
@@ -602,7 +605,6 @@ pub fn continue_mining_block(
if **mine_progress >= 1. {
commands.entity(entity).remove::<Mining>();
- *sequence_number += 1;
println!("finished mining block at {:?}", mining.pos);
finish_mining_events.write(FinishMiningBlockEvent {
entity,
@@ -614,7 +616,7 @@ pub fn continue_mining_block(
action: s_player_action::Action::StopDestroyBlock,
pos: mining.pos,
direction: mining.dir,
- sequence: **sequence_number,
+ sequence: sequence_number.get_and_increment(),
},
));
**mine_progress = 0.;
@@ -638,16 +640,16 @@ pub fn continue_mining_block(
}
}
-fn update_mining_component(
+pub fn update_mining_component(
mut commands: Commands,
mut query: Query<(Entity, &mut Mining, &HitResultComponent)>,
) {
for (entity, mut mining, hit_result_component) in &mut query.iter_mut() {
- if hit_result_component.miss {
- commands.entity(entity).remove::<Mining>();
+ if let Some(block_hit_result) = hit_result_component.as_block_hit_result_if_not_miss() {
+ mining.pos = block_hit_result.block_pos;
+ mining.dir = block_hit_result.direction;
} else {
- mining.pos = hit_result_component.block_pos;
- mining.dir = hit_result_component.direction;
+ commands.entity(entity).remove::<Mining>();
}
}
}
diff --git a/azalea-core/src/aabb.rs b/azalea-core/src/aabb.rs
index 6796e79c..42ae797d 100644
--- a/azalea-core/src/aabb.rs
+++ b/azalea-core/src/aabb.rs
@@ -1,6 +1,6 @@
use crate::{
- block_hit_result::BlockHitResult,
direction::{Axis, Direction},
+ hit_result::BlockHitResult,
math::EPSILON,
position::{BlockPos, Vec3},
};
diff --git a/azalea-core/src/block_hit_result.rs b/azalea-core/src/block_hit_result.rs
deleted file mode 100644
index 4d930453..00000000
--- a/azalea-core/src/block_hit_result.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use crate::{
- direction::Direction,
- position::{BlockPos, Vec3},
-};
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct BlockHitResult {
- pub location: Vec3,
- pub direction: Direction,
- pub block_pos: BlockPos,
- pub miss: bool,
- pub inside: bool,
- pub world_border: bool,
-}
-
-impl BlockHitResult {
- pub fn miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
- Self {
- location,
- direction,
- block_pos,
- miss: true,
- inside: false,
- world_border: false,
- }
- }
-
- pub fn with_direction(&self, direction: Direction) -> Self {
- Self { direction, ..*self }
- }
- pub fn with_position(&self, block_pos: BlockPos) -> Self {
- Self { block_pos, ..*self }
- }
-}
diff --git a/azalea-core/src/hit_result.rs b/azalea-core/src/hit_result.rs
new file mode 100644
index 00000000..2fc78115
--- /dev/null
+++ b/azalea-core/src/hit_result.rs
@@ -0,0 +1,68 @@
+use crate::{
+ direction::Direction,
+ position::{BlockPos, Vec3},
+};
+
+/// The block or entity that our player is looking at and can interact with.
+///
+/// If there's nothing, it'll be a [`BlockHitResult`] with `miss` set to true.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum HitResult {
+ Block(BlockHitResult),
+ /// TODO
+ Entity,
+}
+impl HitResult {
+ pub fn is_miss(&self) -> bool {
+ match self {
+ HitResult::Block(block_hit_result) => block_hit_result.miss,
+ HitResult::Entity => false,
+ }
+ }
+
+ pub fn is_block_hit_and_not_miss(&self) -> bool {
+ match self {
+ HitResult::Block(block_hit_result) => !block_hit_result.miss,
+ HitResult::Entity => false,
+ }
+ }
+
+ /// Returns the [`BlockHitResult`], if we were looking at a block and it
+ /// wasn't a miss.
+ pub fn as_block_hit_result_if_not_miss(&self) -> Option<&BlockHitResult> {
+ match self {
+ HitResult::Block(block_hit_result) if !block_hit_result.miss => Some(block_hit_result),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct BlockHitResult {
+ pub location: Vec3,
+ pub direction: Direction,
+ pub block_pos: BlockPos,
+ pub inside: bool,
+ pub world_border: bool,
+ pub miss: bool,
+}
+
+impl BlockHitResult {
+ pub fn miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
+ Self {
+ location,
+ direction,
+ block_pos,
+ miss: true,
+ inside: false,
+ world_border: false,
+ }
+ }
+
+ pub fn with_direction(&self, direction: Direction) -> Self {
+ Self { direction, ..*self }
+ }
+ pub fn with_position(&self, block_pos: BlockPos) -> Self {
+ Self { block_pos, ..*self }
+ }
+}
diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs
index a539dbd4..6c5e57c1 100644
--- a/azalea-core/src/lib.rs
+++ b/azalea-core/src/lib.rs
@@ -2,7 +2,6 @@
pub mod aabb;
pub mod bitset;
-pub mod block_hit_result;
pub mod color;
pub mod cursor3d;
pub mod data_registry;
@@ -11,6 +10,7 @@ pub mod difficulty;
pub mod direction;
pub mod filterable;
pub mod game_type;
+pub mod hit_result;
pub mod math;
pub mod objectives;
pub mod position;
diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs
index 88a1cfff..0f49039e 100644
--- a/azalea-entity/src/lib.rs
+++ b/azalea-entity/src/lib.rs
@@ -218,9 +218,9 @@ pub struct Jumping(pub bool);
/// A component that contains the direction an entity is looking.
#[derive(Debug, Component, Copy, Clone, Default, PartialEq, AzBuf)]
pub struct LookDirection {
- /// Left and right. Aka yaw.
+ /// Left and right. AKA yaw.
pub y_rot: f32,
- /// Up and down. Aka pitch.
+ /// Up and down. AKA pitch.
pub x_rot: f32,
}
diff --git a/azalea-physics/src/clip.rs b/azalea-physics/src/clip.rs
index 7c16f5d4..96c11d1a 100644
--- a/azalea-physics/src/clip.rs
+++ b/azalea-physics/src/clip.rs
@@ -6,8 +6,8 @@ use azalea_block::{
};
use azalea_core::{
aabb::AABB,
- block_hit_result::BlockHitResult,
direction::{Axis, Direction},
+ hit_result::BlockHitResult,
math::{self, EPSILON, lerp},
position::{BlockPos, Vec3},
};
diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs
index fc5615c3..e27e4c2a 100644
--- a/azalea-physics/src/collision/shape.rs
+++ b/azalea-physics/src/collision/shape.rs
@@ -1,8 +1,8 @@
use std::{cmp, num::NonZeroU32, sync::LazyLock};
use azalea_core::{
- block_hit_result::BlockHitResult,
direction::{Axis, AxisCycle, Direction},
+ hit_result::BlockHitResult,
math::{EPSILON, binary_search},
position::{BlockPos, Vec3},
};
diff --git a/azalea-protocol/src/packets/game/s_interact.rs b/azalea-protocol/src/packets/game/s_interact.rs
index 89fc896f..a3007749 100644
--- a/azalea-protocol/src/packets/game/s_interact.rs
+++ b/azalea-protocol/src/packets/game/s_interact.rs
@@ -80,8 +80,9 @@ impl AzaleaRead for ActionType {
}
}
-#[derive(AzBuf, Clone, Copy, Debug)]
+#[derive(AzBuf, Clone, Copy, Debug, Default)]
pub enum InteractionHand {
+ #[default]
MainHand = 0,
OffHand = 1,
}
diff --git a/azalea-protocol/src/packets/game/s_use_item.rs b/azalea-protocol/src/packets/game/s_use_item.rs
index ac8ae217..d6ccca87 100644
--- a/azalea-protocol/src/packets/game/s_use_item.rs
+++ b/azalea-protocol/src/packets/game/s_use_item.rs
@@ -8,6 +8,6 @@ pub struct ServerboundUseItem {
pub hand: InteractionHand,
#[var]
pub sequence: u32,
- pub yaw: f32,
- pub pitch: f32,
+ pub y_rot: f32,
+ pub x_rot: f32,
}
diff --git a/azalea-protocol/src/packets/game/s_use_item_on.rs b/azalea-protocol/src/packets/game/s_use_item_on.rs
index 4c87cb72..967fb5e9 100644
--- a/azalea-protocol/src/packets/game/s_use_item_on.rs
+++ b/azalea-protocol/src/packets/game/s_use_item_on.rs
@@ -3,6 +3,7 @@ use std::io::{Cursor, Write};
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
use azalea_core::{
direction::Direction,
+ hit_result::BlockHitResult,
position::{BlockPos, Vec3},
};
use azalea_protocol_macros::ServerboundGamePacket;
@@ -77,3 +78,19 @@ impl AzaleaRead for BlockHit {
})
}
}
+
+impl From<BlockHitResult> for BlockHit {
+ /// Converts a [`BlockHitResult`] to a [`BlockHit`].
+ ///
+ /// The only difference is that the `miss` field is not present in
+ /// [`BlockHit`].
+ fn from(hit_result: BlockHitResult) -> Self {
+ Self {
+ block_pos: hit_result.block_pos,
+ direction: hit_result.direction,
+ location: hit_result.location,
+ inside: hit_result.inside,
+ world_border: hit_result.world_border,
+ }
+ }
+}
diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs
index 31b0ff91..0bac22d4 100644
--- a/azalea/examples/testbot/commands/debug.rs
+++ b/azalea/examples/testbot/commands/debug.rs
@@ -104,10 +104,10 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
let hit_result = *source.bot.component::<HitResultComponent>();
- if hit_result.miss {
+ let Some(hit_result) = hit_result.as_block_hit_result_if_not_miss() else {
source.reply("I'm not looking at anything");
return 1;
- }
+ };
let block_pos = hit_result.block_pos;
let block = source.bot.world().read().get_block_state(&block_pos);
@@ -174,6 +174,13 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
1
}));
+ commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
+ let source = ctx.source.lock();
+ source.bot.start_use_item();
+ source.reply("Ok!");
+ 1
+ }));
+
commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
let source = ctx.source.lock();
diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs
index 9052c8fd..3ef45993 100644
--- a/azalea/src/pathfinder/goals.rs
+++ b/azalea/src/pathfinder/goals.rs
@@ -212,7 +212,7 @@ impl Goal for ReachBlockPosGoal {
let eye_position = n.to_vec3_floored() + Vec3::new(0.5, 1.62, 0.5);
let look_direction = crate::direction_looking_at(&eye_position, &self.pos.center());
- let block_hit_result = azalea_client::interact::pick(
+ let block_hit_result = azalea_client::interact::pick_block(
&look_direction,
&eye_position,
&self.chunk_storage,