mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
add StartUseItemEvent and improve code related to interactions
This commit is contained in:
parent
e9b3128103
commit
e1d3b902ba
16 changed files with 325 additions and 144 deletions
|
@ -25,7 +25,8 @@ is breaking anyways, semantic versioning is not followed.
|
||||||
- [BREAKING] The `BlockState::id` field is now private, use `.id()` instead.
|
- [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] Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/).
|
||||||
- [BREAKING] Rename `InstanceContainer::insert` to `get_or_insert`.
|
- [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
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use bevy_ecs::prelude::*;
|
||||||
use tracing::{error, trace};
|
use tracing::{error, trace};
|
||||||
|
|
||||||
use crate::{
|
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,
|
packet::game::SendPacketEvent, respawn::perform_respawn,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ impl Plugin for ChunksPlugin {
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.before(InventorySet)
|
.before(InventorySet)
|
||||||
.before(handle_block_interact_event)
|
.before(handle_start_use_item_queued)
|
||||||
.before(perform_respawn),
|
.before(perform_respawn),
|
||||||
)
|
)
|
||||||
.add_event::<ReceiveChunkEvent>()
|
.add_event::<ReceiveChunkEvent>()
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
use std::ops::AddAssign;
|
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
block_hit_result::BlockHitResult,
|
|
||||||
direction::Direction,
|
direction::Direction,
|
||||||
game_type::GameMode,
|
game_type::GameMode,
|
||||||
|
hit_result::{BlockHitResult, HitResult},
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
|
tick::GameTick,
|
||||||
};
|
};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
|
Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
|
||||||
};
|
};
|
||||||
use azalea_inventory::{ItemStack, ItemStackData, components};
|
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::{
|
use azalea_protocol::packets::game::{
|
||||||
s_interact::InteractionHand,
|
ServerboundUseItem, s_interact::InteractionHand, s_swing::ServerboundSwing,
|
||||||
s_swing::ServerboundSwing,
|
s_use_item_on::ServerboundUseItemOn,
|
||||||
s_use_item_on::{BlockHit, ServerboundUseItemOn},
|
|
||||||
};
|
};
|
||||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
|
@ -23,6 +24,7 @@ use bevy_ecs::prelude::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
use super::mining::{Mining, MiningSet};
|
||||||
use crate::{
|
use crate::{
|
||||||
Client,
|
Client,
|
||||||
attack::handle_attack_event,
|
attack::handle_attack_event,
|
||||||
|
@ -37,14 +39,14 @@ use crate::{
|
||||||
pub struct InteractPlugin;
|
pub struct InteractPlugin;
|
||||||
impl Plugin for InteractPlugin {
|
impl Plugin for InteractPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<BlockInteractEvent>()
|
app.add_event::<StartUseItemEvent>()
|
||||||
.add_event::<SwingArmEvent>()
|
.add_event::<SwingArmEvent>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
|
handle_start_use_item_event,
|
||||||
update_hit_result_component.after(clamp_look_direction),
|
update_hit_result_component.after(clamp_look_direction),
|
||||||
handle_block_interact_event,
|
|
||||||
handle_swing_arm_event,
|
handle_swing_arm_event,
|
||||||
)
|
)
|
||||||
.after(InventorySet)
|
.after(InventorySet)
|
||||||
|
@ -56,34 +58,47 @@ impl Plugin for InteractPlugin {
|
||||||
.after(MoveEventsSet),
|
.after(MoveEventsSet),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.add_systems(
|
||||||
|
GameTick,
|
||||||
|
handle_start_use_item_queued
|
||||||
|
.after(MiningSet)
|
||||||
|
.before(PhysicsSet),
|
||||||
|
)
|
||||||
.add_observer(handle_swing_arm_trigger);
|
.add_observer(handle_swing_arm_trigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
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
|
/// and it'll either place the block you're holding in your hand or use the
|
||||||
/// block you clicked (like toggling a lever).
|
/// block you clicked (like toggling a lever).
|
||||||
///
|
///
|
||||||
/// Note that this may trigger anticheats as it doesn't take into account
|
/// Note that this may trigger anticheats as it doesn't take into account
|
||||||
/// whether you're actually looking at the block.
|
/// whether you're actually looking at the block.
|
||||||
pub fn block_interact(&self, position: BlockPos) {
|
pub fn block_interact(&self, position: BlockPos) {
|
||||||
self.ecs.lock().send_event(BlockInteractEvent {
|
self.ecs.lock().send_event(StartUseItemEvent {
|
||||||
entity: self.entity,
|
entity: self.entity,
|
||||||
position,
|
hand: InteractionHand::MainHand,
|
||||||
|
force_block: Some(position),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Right click a block. The behavior of this depends on the target block,
|
/// Use the current item.
|
||||||
/// and it'll either place the block you're holding in your hand or use the
|
///
|
||||||
/// block you clicked (like toggling a lever).
|
/// If the item is consumable, then it'll act as if right-click was held
|
||||||
#[derive(Event)]
|
/// until the item finished being consumed. You can use this to eat food.
|
||||||
pub struct BlockInteractEvent {
|
///
|
||||||
/// The local player entity that's opening the container.
|
/// If we're looking at a block or entity, then it will be clicked. Also see
|
||||||
pub entity: Entity,
|
/// [`Client::block_interact`].
|
||||||
/// The coordinates of the container.
|
pub fn start_use_item(&self) {
|
||||||
pub position: BlockPos,
|
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
|
/// 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)]
|
#[derive(Component, Copy, Clone, Debug, Default, Deref)]
|
||||||
pub struct CurrentSequenceNumber(u32);
|
pub struct CurrentSequenceNumber(u32);
|
||||||
|
|
||||||
impl AddAssign<u32> for CurrentSequenceNumber {
|
impl CurrentSequenceNumber {
|
||||||
fn add_assign(&mut self, rhs: u32) {
|
/// Get the next sequence number that we're going to use and increment the
|
||||||
self.0 += rhs;
|
/// 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"))]
|
#[doc(alias("looking at", "looking at block", "crosshair"))]
|
||||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||||
pub struct HitResultComponent(BlockHitResult);
|
pub struct HitResultComponent(HitResult);
|
||||||
|
|
||||||
pub fn handle_block_interact_event(
|
/// An event that makes one of our clients simulate a right-click.
|
||||||
mut events: EventReader<BlockInteractEvent>,
|
///
|
||||||
mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
|
/// 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 commands: Commands,
|
||||||
|
mut events: EventReader<StartUseItemEvent>,
|
||||||
) {
|
) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
|
commands.entity(event.entity).insert(StartUseItemQueued {
|
||||||
warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
|
hand: event.hand,
|
||||||
continue;
|
force_block: event.force_block,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: check to make sure we're within the world border
|
/// 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>();
|
||||||
|
|
||||||
*sequence_number += 1;
|
if mining.is_some() {
|
||||||
|
warn!("Got a StartUseItemEvent for a client that was mining");
|
||||||
|
}
|
||||||
|
|
||||||
// minecraft also does the interaction client-side (so it looks like clicking a
|
// TODO: this also skips if LocalPlayer.handsBusy is true, which is used when
|
||||||
// button is instant) but we don't really need that
|
// rowing a boat
|
||||||
|
|
||||||
// the block_hit data will depend on whether we're looking at the block and
|
let mut hit_result = hit_result.0.clone();
|
||||||
// whether we can reach it
|
|
||||||
|
|
||||||
let block_hit = if hit_result.block_pos == event.position {
|
if let Some(force_block) = start_use_item.force_block {
|
||||||
// we're looking at the block :)
|
let hit_result_matches = if let HitResult::Block(block_hit_result) = hit_result {
|
||||||
BlockHit {
|
block_hit_result.block_pos == force_block
|
||||||
block_pos: hit_result.block_pos,
|
} else {
|
||||||
direction: hit_result.direction,
|
false
|
||||||
location: hit_result.location,
|
};
|
||||||
inside: hit_result.inside,
|
|
||||||
world_border: hit_result.world_border,
|
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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} 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,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
commands.trigger(SendPacketEvent::new(
|
match hit_result {
|
||||||
entity,
|
HitResult::Block(block_hit_result) => {
|
||||||
ServerboundUseItemOn {
|
if block_hit_result.miss {
|
||||||
hand: InteractionHand::MainHand,
|
commands.trigger(SendPacketEvent::new(
|
||||||
block_hit,
|
entity,
|
||||||
sequence: sequence_number.0,
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HitResult::Entity => {
|
||||||
|
// TODO: implement HitResult::Entity
|
||||||
|
|
||||||
|
// TODO: worldborder check
|
||||||
|
|
||||||
|
// commands.trigger(SendPacketEvent::new(
|
||||||
|
// entity,
|
||||||
|
// ServerboundInteract {
|
||||||
|
// entity_id: todo!(),
|
||||||
|
// action: todo!(),
|
||||||
|
// using_secondary_action: todo!(),
|
||||||
|
// },
|
||||||
|
// ));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,12 +296,32 @@ pub fn update_hit_result_component(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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/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
|
/// Get the block that a player would be looking at if their eyes were at the
|
||||||
/// given direction and position.
|
/// given direction and position.
|
||||||
///
|
///
|
||||||
/// If you need to get the block the player is looking at right now, use
|
/// Also see [`pick`].
|
||||||
/// [`HitResultComponent`].
|
pub fn pick_block(
|
||||||
pub fn pick(
|
|
||||||
look_direction: &LookDirection,
|
look_direction: &LookDirection,
|
||||||
eye_position: &Vec3,
|
eye_position: &Vec3,
|
||||||
chunks: &azalea_world::ChunkStorage,
|
chunks: &azalea_world::ChunkStorage,
|
||||||
|
@ -211,6 +329,7 @@ pub fn pick(
|
||||||
) -> BlockHitResult {
|
) -> BlockHitResult {
|
||||||
let view_vector = view_vector(look_direction);
|
let view_vector = view_vector(look_direction);
|
||||||
let end_position = eye_position + &(view_vector * pick_range);
|
let end_position = eye_position + &(view_vector * pick_range);
|
||||||
|
|
||||||
azalea_physics::clip::clip(
|
azalea_physics::clip::clip(
|
||||||
chunks,
|
chunks,
|
||||||
ClipContext {
|
ClipContext {
|
||||||
|
|
|
@ -39,7 +39,8 @@ impl Plugin for MiningPlugin {
|
||||||
handle_mining_queued,
|
handle_mining_queued,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.before(PhysicsSet),
|
.before(PhysicsSet)
|
||||||
|
.in_set(MiningSet),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
@ -56,7 +57,7 @@ impl Plugin for MiningPlugin {
|
||||||
.after(azalea_entity::update_fluid_on_eyes)
|
.after(azalea_entity::update_fluid_on_eyes)
|
||||||
.after(crate::interact::update_hit_result_component)
|
.after(crate::interact::update_hit_result_component)
|
||||||
.after(crate::attack::handle_attack_event)
|
.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),
|
.before(crate::interact::handle_swing_arm_event),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -121,22 +122,25 @@ fn handle_auto_mine(
|
||||||
current_mining_item,
|
current_mining_item,
|
||||||
) in &mut query.iter_mut()
|
) in &mut query.iter_mut()
|
||||||
{
|
{
|
||||||
let block_pos = hit_result_component.block_pos;
|
let block_pos = hit_result_component
|
||||||
|
.as_block_hit_result_if_not_miss()
|
||||||
|
.map(|b| b.block_pos);
|
||||||
|
|
||||||
if (mining.is_none()
|
// start mining if we're looking at a block and we're not already mining it
|
||||||
|| !is_same_mining_target(
|
if let Some(block_pos) = block_pos
|
||||||
block_pos,
|
&& (mining.is_none()
|
||||||
inventory,
|
|| !is_same_mining_target(
|
||||||
current_mining_pos,
|
block_pos,
|
||||||
current_mining_item,
|
inventory,
|
||||||
))
|
current_mining_pos,
|
||||||
&& !hit_result_component.miss
|
current_mining_item,
|
||||||
|
))
|
||||||
{
|
{
|
||||||
start_mining_block_event.write(StartMiningBlockEvent {
|
start_mining_block_event.write(StartMiningBlockEvent {
|
||||||
entity,
|
entity,
|
||||||
position: block_pos,
|
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 });
|
stop_mining_block_event.write(StopMiningBlockEvent { entity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,9 +170,11 @@ fn handle_start_mining_block_event(
|
||||||
) {
|
) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
let hit_result = query.get_mut(event.entity).unwrap();
|
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
|
// we're looking at the block
|
||||||
hit_result.direction
|
block_hit_result.direction
|
||||||
} else {
|
} else {
|
||||||
// we're not looking at the block, arbitrary direction
|
// we're not looking at the block, arbitrary direction
|
||||||
Direction::Down
|
Direction::Down
|
||||||
|
@ -241,7 +247,6 @@ fn handle_mining_queued(
|
||||||
// is outside of the worldborder
|
// is outside of the worldborder
|
||||||
|
|
||||||
if game_mode.current == GameMode::Creative {
|
if game_mode.current == GameMode::Creative {
|
||||||
*sequence_number += 1;
|
|
||||||
finish_mining_events.write(FinishMiningBlockEvent {
|
finish_mining_events.write(FinishMiningBlockEvent {
|
||||||
entity,
|
entity,
|
||||||
position: mining_queued.position,
|
position: mining_queued.position,
|
||||||
|
@ -318,14 +323,13 @@ fn handle_mining_queued(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
*sequence_number += 1;
|
|
||||||
commands.trigger(SendPacketEvent::new(
|
commands.trigger(SendPacketEvent::new(
|
||||||
entity,
|
entity,
|
||||||
ServerboundPlayerAction {
|
ServerboundPlayerAction {
|
||||||
action: s_player_action::Action::StartDestroyBlock,
|
action: s_player_action::Action::StartDestroyBlock,
|
||||||
pos: mining_queued.position,
|
pos: mining_queued.position,
|
||||||
direction: mining_queued.direction,
|
direction: mining_queued.direction,
|
||||||
sequence: **sequence_number,
|
sequence: sequence_number.get_and_increment(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
commands.trigger(SwingArmEvent { entity });
|
commands.trigger(SwingArmEvent { entity });
|
||||||
|
@ -558,14 +562,13 @@ pub fn continue_mining_block(
|
||||||
entity,
|
entity,
|
||||||
position: mining.pos,
|
position: mining.pos,
|
||||||
});
|
});
|
||||||
*sequence_number += 1;
|
|
||||||
commands.trigger(SendPacketEvent::new(
|
commands.trigger(SendPacketEvent::new(
|
||||||
entity,
|
entity,
|
||||||
ServerboundPlayerAction {
|
ServerboundPlayerAction {
|
||||||
action: s_player_action::Action::StartDestroyBlock,
|
action: s_player_action::Action::StartDestroyBlock,
|
||||||
pos: mining.pos,
|
pos: mining.pos,
|
||||||
direction: mining.dir,
|
direction: mining.dir,
|
||||||
sequence: **sequence_number,
|
sequence: sequence_number.get_and_increment(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
commands.trigger(SwingArmEvent { entity });
|
commands.trigger(SwingArmEvent { entity });
|
||||||
|
@ -602,7 +605,6 @@ pub fn continue_mining_block(
|
||||||
|
|
||||||
if **mine_progress >= 1. {
|
if **mine_progress >= 1. {
|
||||||
commands.entity(entity).remove::<Mining>();
|
commands.entity(entity).remove::<Mining>();
|
||||||
*sequence_number += 1;
|
|
||||||
println!("finished mining block at {:?}", mining.pos);
|
println!("finished mining block at {:?}", mining.pos);
|
||||||
finish_mining_events.write(FinishMiningBlockEvent {
|
finish_mining_events.write(FinishMiningBlockEvent {
|
||||||
entity,
|
entity,
|
||||||
|
@ -614,7 +616,7 @@ pub fn continue_mining_block(
|
||||||
action: s_player_action::Action::StopDestroyBlock,
|
action: s_player_action::Action::StopDestroyBlock,
|
||||||
pos: mining.pos,
|
pos: mining.pos,
|
||||||
direction: mining.dir,
|
direction: mining.dir,
|
||||||
sequence: **sequence_number,
|
sequence: sequence_number.get_and_increment(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
**mine_progress = 0.;
|
**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 commands: Commands,
|
||||||
mut query: Query<(Entity, &mut Mining, &HitResultComponent)>,
|
mut query: Query<(Entity, &mut Mining, &HitResultComponent)>,
|
||||||
) {
|
) {
|
||||||
for (entity, mut mining, hit_result_component) in &mut query.iter_mut() {
|
for (entity, mut mining, hit_result_component) in &mut query.iter_mut() {
|
||||||
if hit_result_component.miss {
|
if let Some(block_hit_result) = hit_result_component.as_block_hit_result_if_not_miss() {
|
||||||
commands.entity(entity).remove::<Mining>();
|
mining.pos = block_hit_result.block_pos;
|
||||||
|
mining.dir = block_hit_result.direction;
|
||||||
} else {
|
} else {
|
||||||
mining.pos = hit_result_component.block_pos;
|
commands.entity(entity).remove::<Mining>();
|
||||||
mining.dir = hit_result_component.direction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
block_hit_result::BlockHitResult,
|
|
||||||
direction::{Axis, Direction},
|
direction::{Axis, Direction},
|
||||||
|
hit_result::BlockHitResult,
|
||||||
math::EPSILON,
|
math::EPSILON,
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
68
azalea-core/src/hit_result.rs
Normal file
68
azalea-core/src/hit_result.rs
Normal file
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod bitset;
|
pub mod bitset;
|
||||||
pub mod block_hit_result;
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod cursor3d;
|
pub mod cursor3d;
|
||||||
pub mod data_registry;
|
pub mod data_registry;
|
||||||
|
@ -11,6 +10,7 @@ pub mod difficulty;
|
||||||
pub mod direction;
|
pub mod direction;
|
||||||
pub mod filterable;
|
pub mod filterable;
|
||||||
pub mod game_type;
|
pub mod game_type;
|
||||||
|
pub mod hit_result;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod objectives;
|
pub mod objectives;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
|
|
|
@ -218,9 +218,9 @@ pub struct Jumping(pub bool);
|
||||||
/// A component that contains the direction an entity is looking.
|
/// A component that contains the direction an entity is looking.
|
||||||
#[derive(Debug, Component, Copy, Clone, Default, PartialEq, AzBuf)]
|
#[derive(Debug, Component, Copy, Clone, Default, PartialEq, AzBuf)]
|
||||||
pub struct LookDirection {
|
pub struct LookDirection {
|
||||||
/// Left and right. Aka yaw.
|
/// Left and right. AKA yaw.
|
||||||
pub y_rot: f32,
|
pub y_rot: f32,
|
||||||
/// Up and down. Aka pitch.
|
/// Up and down. AKA pitch.
|
||||||
pub x_rot: f32,
|
pub x_rot: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ use azalea_block::{
|
||||||
};
|
};
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
aabb::AABB,
|
aabb::AABB,
|
||||||
block_hit_result::BlockHitResult,
|
|
||||||
direction::{Axis, Direction},
|
direction::{Axis, Direction},
|
||||||
|
hit_result::BlockHitResult,
|
||||||
math::{self, EPSILON, lerp},
|
math::{self, EPSILON, lerp},
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{cmp, num::NonZeroU32, sync::LazyLock};
|
use std::{cmp, num::NonZeroU32, sync::LazyLock};
|
||||||
|
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
block_hit_result::BlockHitResult,
|
|
||||||
direction::{Axis, AxisCycle, Direction},
|
direction::{Axis, AxisCycle, Direction},
|
||||||
|
hit_result::BlockHitResult,
|
||||||
math::{EPSILON, binary_search},
|
math::{EPSILON, binary_search},
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,8 +80,9 @@ impl AzaleaRead for ActionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AzBuf, Clone, Copy, Debug)]
|
#[derive(AzBuf, Clone, Copy, Debug, Default)]
|
||||||
pub enum InteractionHand {
|
pub enum InteractionHand {
|
||||||
|
#[default]
|
||||||
MainHand = 0,
|
MainHand = 0,
|
||||||
OffHand = 1,
|
OffHand = 1,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@ pub struct ServerboundUseItem {
|
||||||
pub hand: InteractionHand,
|
pub hand: InteractionHand,
|
||||||
#[var]
|
#[var]
|
||||||
pub sequence: u32,
|
pub sequence: u32,
|
||||||
pub yaw: f32,
|
pub y_rot: f32,
|
||||||
pub pitch: f32,
|
pub x_rot: f32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::io::{Cursor, Write};
|
||||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
direction::Direction,
|
direction::Direction,
|
||||||
|
hit_result::BlockHitResult,
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
};
|
};
|
||||||
use azalea_protocol_macros::ServerboundGamePacket;
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -104,10 +104,10 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||||
|
|
||||||
let hit_result = *source.bot.component::<HitResultComponent>();
|
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");
|
source.reply("I'm not looking at anything");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
};
|
||||||
|
|
||||||
let block_pos = hit_result.block_pos;
|
let block_pos = hit_result.block_pos;
|
||||||
let block = source.bot.world().read().get_block_state(&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
|
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| {
|
commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
|
||||||
let source = ctx.source.lock();
|
let source = ctx.source.lock();
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,7 @@ impl Goal for ReachBlockPosGoal {
|
||||||
|
|
||||||
let eye_position = n.to_vec3_floored() + Vec3::new(0.5, 1.62, 0.5);
|
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 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,
|
&look_direction,
|
||||||
&eye_position,
|
&eye_position,
|
||||||
&self.chunk_storage,
|
&self.chunk_storage,
|
||||||
|
|
Loading…
Add table
Reference in a new issue