mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
implement EntityHitResult
This commit is contained in:
parent
713dae7110
commit
fd9bf16871
14 changed files with 485 additions and 189 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
Due to the complexity of Azalea and the fact that almost every Minecraft version
|
Due to the complexity of Azalea and the fact that almost every Minecraft version
|
||||||
is breaking anyways, semantic versioning is not followed.
|
is breaking anyways, semantic versioning is not followed.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `HitResult` now contains the entity that's being looked at.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Renamed `azalea_entity::EntityKind` to `EntityKindComponent` to disambiguate with `azalea_registry::EntityKind`.
|
||||||
|
- Moved functions and types related to hit results from `azalea::interact` to `azalea::interact::pick`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
## [0.13.0] - 2025-06-15
|
## [0.13.0] - 2025-06-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -44,7 +57,7 @@ is breaking anyways, semantic versioning is not followed.
|
||||||
- Replace `wait_one_tick` and `wait_one_update` with `wait_ticks` and `wait_updates`.
|
- Replace `wait_one_tick` and `wait_one_update` with `wait_ticks` and `wait_updates`.
|
||||||
- Functions that took `&Vec3` or `&BlockPos` as arguments now only take them as owned types.
|
- Functions that took `&Vec3` or `&BlockPos` as arguments now only take them as owned types.
|
||||||
- Rename `azalea_block::Block` to `BlockTrait` to disambiguate with `azalea_registry::Block`.
|
- Rename `azalea_block::Block` to `BlockTrait` to disambiguate with `azalea_registry::Block`.
|
||||||
- `GotoEvent` is now non-enhaustive, it should be constructed by calling its methods now.
|
- `GotoEvent` is now non-enhaustive and should instead be constructed by calling its methods.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ _Currently supported Minecraft version: `1.21.5`._
|
||||||
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
|
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
|
||||||
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet, but it's usually fine)
|
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet, but it's usually fine)
|
||||||
- [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client)
|
- [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client)
|
||||||
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack) (but you can't get the entity at the crosshair yet)
|
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack)
|
||||||
- [Plugins](#plugins)
|
- [Plugins](#plugins)
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod pick;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
|
@ -9,27 +11,30 @@ use azalea_core::{
|
||||||
tick::GameTick,
|
tick::GameTick,
|
||||||
};
|
};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
|
Attributes, LocalEntity, LookDirection,
|
||||||
|
attributes::{
|
||||||
|
creative_block_interaction_range_modifier, creative_entity_interaction_range_modifier,
|
||||||
|
},
|
||||||
|
clamp_look_direction,
|
||||||
};
|
};
|
||||||
use azalea_inventory::{ItemStack, ItemStackData, components};
|
use azalea_inventory::{ItemStack, ItemStackData, components};
|
||||||
use azalea_physics::{
|
use azalea_physics::PhysicsSet;
|
||||||
PhysicsSet,
|
|
||||||
clip::{BlockShapeType, ClipContext, FluidPickType},
|
|
||||||
};
|
|
||||||
use azalea_protocol::packets::game::{
|
use azalea_protocol::packets::game::{
|
||||||
ServerboundUseItem, s_interact::InteractionHand, s_swing::ServerboundSwing,
|
ServerboundInteract, ServerboundUseItem,
|
||||||
|
s_interact::{self, InteractionHand},
|
||||||
|
s_swing::ServerboundSwing,
|
||||||
s_use_item_on::ServerboundUseItemOn,
|
s_use_item_on::ServerboundUseItemOn,
|
||||||
};
|
};
|
||||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
use azalea_world::{Instance, MinecraftEntityId};
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use super::mining::Mining;
|
use super::mining::Mining;
|
||||||
use crate::{
|
use crate::{
|
||||||
Client,
|
Client,
|
||||||
attack::handle_attack_event,
|
attack::handle_attack_event,
|
||||||
|
interact::pick::{HitResultComponent, update_hit_result_component},
|
||||||
inventory::{Inventory, InventorySet},
|
inventory::{Inventory, InventorySet},
|
||||||
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||||
movement::MoveEventsSet,
|
movement::MoveEventsSet,
|
||||||
|
@ -47,24 +52,29 @@ impl Plugin for InteractPlugin {
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
handle_start_use_item_event,
|
update_attributes_for_held_item,
|
||||||
update_hit_result_component.after(clamp_look_direction),
|
update_attributes_for_gamemode,
|
||||||
handle_swing_arm_event,
|
|
||||||
)
|
)
|
||||||
.after(InventorySet)
|
.in_set(UpdateAttributesSet)
|
||||||
.after(perform_respawn)
|
|
||||||
.after(handle_attack_event)
|
|
||||||
.chain(),
|
.chain(),
|
||||||
update_modifiers_for_held_item
|
handle_start_use_item_event,
|
||||||
.after(InventorySet)
|
update_hit_result_component.after(clamp_look_direction),
|
||||||
.after(MoveEventsSet),
|
handle_swing_arm_event,
|
||||||
),
|
)
|
||||||
|
.after(InventorySet)
|
||||||
|
.after(MoveEventsSet)
|
||||||
|
.after(perform_respawn)
|
||||||
|
.after(handle_attack_event)
|
||||||
|
.chain(),
|
||||||
)
|
)
|
||||||
.add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet))
|
.add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet))
|
||||||
.add_observer(handle_swing_arm_trigger);
|
.add_observer(handle_swing_arm_trigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub struct UpdateAttributesSet;
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Right-click a block.
|
/// Right-click a block.
|
||||||
///
|
///
|
||||||
|
@ -190,12 +200,6 @@ impl BlockStatePredictionHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(HitResult);
|
|
||||||
|
|
||||||
/// An event that makes one of our clients simulate a right-click.
|
/// An event that makes one of our clients simulate a right-click.
|
||||||
///
|
///
|
||||||
/// This event just inserts the [`StartUseItemQueued`] component on the given
|
/// This event just inserts the [`StartUseItemQueued`] component on the given
|
||||||
|
@ -248,6 +252,7 @@ pub fn handle_start_use_item_queued(
|
||||||
&LookDirection,
|
&LookDirection,
|
||||||
Option<&Mining>,
|
Option<&Mining>,
|
||||||
)>,
|
)>,
|
||||||
|
entity_id_query: Query<&MinecraftEntityId>,
|
||||||
) {
|
) {
|
||||||
for (entity, start_use_item, mut prediction_handler, hit_result, look_direction, mining) in
|
for (entity, start_use_item, mut prediction_handler, hit_result, look_direction, mining) in
|
||||||
query
|
query
|
||||||
|
@ -261,7 +266,7 @@ pub fn handle_start_use_item_queued(
|
||||||
// TODO: this also skips if LocalPlayer.handsBusy is true, which is used when
|
// TODO: this also skips if LocalPlayer.handsBusy is true, which is used when
|
||||||
// rowing a boat
|
// rowing a boat
|
||||||
|
|
||||||
let mut hit_result = hit_result.0.clone();
|
let mut hit_result = (**hit_result).clone();
|
||||||
|
|
||||||
if let Some(force_block) = start_use_item.force_block {
|
if let Some(force_block) = start_use_item.force_block {
|
||||||
let hit_result_matches = if let HitResult::Block(block_hit_result) = &hit_result {
|
let hit_result_matches = if let HitResult::Block(block_hit_result) = &hit_result {
|
||||||
|
@ -284,9 +289,9 @@ pub fn handle_start_use_item_queued(
|
||||||
}
|
}
|
||||||
|
|
||||||
match &hit_result {
|
match &hit_result {
|
||||||
HitResult::Block(block_hit_result) => {
|
HitResult::Block(r) => {
|
||||||
let seq = prediction_handler.start_predicting();
|
let seq = prediction_handler.start_predicting();
|
||||||
if block_hit_result.miss {
|
if r.miss {
|
||||||
commands.trigger(SendPacketEvent::new(
|
commands.trigger(SendPacketEvent::new(
|
||||||
entity,
|
entity,
|
||||||
ServerboundUseItem {
|
ServerboundUseItem {
|
||||||
|
@ -301,124 +306,41 @@ pub fn handle_start_use_item_queued(
|
||||||
entity,
|
entity,
|
||||||
ServerboundUseItemOn {
|
ServerboundUseItemOn {
|
||||||
hand: start_use_item.hand,
|
hand: start_use_item.hand,
|
||||||
block_hit: block_hit_result.into(),
|
block_hit: r.into(),
|
||||||
seq,
|
seq,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
// TODO: depending on the result of useItemOn, this might
|
// TODO: depending on the result of useItemOn, this might
|
||||||
// also need to send a SwingArmEvent.
|
// also need to send a SwingArmEvent.
|
||||||
// basically, this TODO is for
|
// basically, this TODO is for simulating block
|
||||||
// simulating block interactions/placements on the
|
// interactions/placements on the client-side.
|
||||||
// client-side.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HitResult::Entity => {
|
HitResult::Entity(r) => {
|
||||||
// TODO: implement HitResult::Entity
|
|
||||||
|
|
||||||
// TODO: worldborder check
|
// TODO: worldborder check
|
||||||
|
|
||||||
// commands.trigger(SendPacketEvent::new(
|
let Ok(entity_id) = entity_id_query.get(r.entity).copied() else {
|
||||||
// entity,
|
warn!("tried to interact with an entity that doesn't have MinecraftEntityId");
|
||||||
// ServerboundInteract {
|
continue;
|
||||||
// entity_id: todo!(),
|
};
|
||||||
// action: todo!(),
|
|
||||||
// using_secondary_action: todo!(),
|
commands.trigger(SendPacketEvent::new(
|
||||||
// },
|
entity,
|
||||||
// ));
|
ServerboundInteract {
|
||||||
|
entity_id,
|
||||||
|
action: s_interact::ActionType::InteractAt {
|
||||||
|
location: r.location,
|
||||||
|
hand: InteractionHand::MainHand,
|
||||||
|
},
|
||||||
|
// TODO: sneaking
|
||||||
|
using_secondary_action: false,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn update_hit_result_component(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut query: Query<(
|
|
||||||
Entity,
|
|
||||||
Option<&mut HitResultComponent>,
|
|
||||||
&LocalGameMode,
|
|
||||||
&Position,
|
|
||||||
&EyeHeight,
|
|
||||||
&LookDirection,
|
|
||||||
&InstanceName,
|
|
||||||
)>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
) {
|
|
||||||
for (entity, hit_result_ref, game_mode, position, eye_height, look_direction, world_name) in
|
|
||||||
&mut query
|
|
||||||
{
|
|
||||||
let pick_range = if game_mode.current == GameMode::Creative {
|
|
||||||
6.
|
|
||||||
} else {
|
|
||||||
4.5
|
|
||||||
};
|
|
||||||
let eye_position = Vec3 {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y + **eye_height as f64,
|
|
||||||
z: position.z,
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(instance_lock) = instance_container.get(world_name) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let instance = instance_lock.read();
|
|
||||||
|
|
||||||
let hit_result = pick(*look_direction, eye_position, &instance.chunks, pick_range);
|
|
||||||
if let Some(mut hit_result_ref) = hit_result_ref {
|
|
||||||
**hit_result_ref = hit_result;
|
|
||||||
} else {
|
|
||||||
commands
|
|
||||||
.entity(entity)
|
|
||||||
.insert(HitResultComponent(hit_result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// 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 {
|
|
||||||
from: eye_position,
|
|
||||||
to: end_position,
|
|
||||||
block_shape_type: BlockShapeType::Outline,
|
|
||||||
fluid_pick_type: FluidPickType::None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether we can't interact with the block, based on your gamemode. If
|
/// Whether we can't interact with the block, based on your gamemode. If
|
||||||
/// this is false, then we can interact with the block.
|
/// this is false, then we can interact with the block.
|
||||||
///
|
///
|
||||||
|
@ -504,7 +426,7 @@ pub fn handle_swing_arm_event(mut events: EventReader<SwingArmEvent>, mut comman
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn update_modifiers_for_held_item(
|
fn update_attributes_for_held_item(
|
||||||
mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
|
mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
|
||||||
) {
|
) {
|
||||||
for (mut attributes, inventory) in &mut query {
|
for (mut attributes, inventory) in &mut query {
|
||||||
|
@ -558,3 +480,25 @@ fn update_modifiers_for_held_item(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_attributes_for_gamemode(
|
||||||
|
query: Query<(&mut Attributes, &LocalGameMode), (With<LocalEntity>, Changed<LocalGameMode>)>,
|
||||||
|
) {
|
||||||
|
for (mut attributes, game_mode) in query {
|
||||||
|
if game_mode.current == GameMode::Creative {
|
||||||
|
attributes
|
||||||
|
.block_interaction_range
|
||||||
|
.insert(creative_block_interaction_range_modifier());
|
||||||
|
attributes
|
||||||
|
.entity_interaction_range
|
||||||
|
.insert(creative_entity_interaction_range_modifier());
|
||||||
|
} else {
|
||||||
|
attributes
|
||||||
|
.block_interaction_range
|
||||||
|
.remove(&creative_block_interaction_range_modifier().id);
|
||||||
|
attributes
|
||||||
|
.entity_interaction_range
|
||||||
|
.remove(&creative_entity_interaction_range_modifier().id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
268
azalea-client/src/plugins/interact/pick.rs
Normal file
268
azalea-client/src/plugins/interact/pick.rs
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
use azalea_core::{
|
||||||
|
aabb::AABB,
|
||||||
|
direction::Direction,
|
||||||
|
hit_result::{BlockHitResult, EntityHitResult, HitResult},
|
||||||
|
position::Vec3,
|
||||||
|
};
|
||||||
|
use azalea_entity::{
|
||||||
|
Attributes, Dead, EyeHeight, LocalEntity, LookDirection, Physics, Position,
|
||||||
|
metadata::{ArmorStandMarker, Marker},
|
||||||
|
view_vector,
|
||||||
|
};
|
||||||
|
use azalea_physics::{
|
||||||
|
clip::{BlockShapeType, ClipContext, FluidPickType},
|
||||||
|
collision::entity_collisions::{PhysicsQuery, get_entities},
|
||||||
|
};
|
||||||
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
|
/// 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(HitResult);
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn update_hit_result_component(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<
|
||||||
|
(
|
||||||
|
Entity,
|
||||||
|
Option<&mut HitResultComponent>,
|
||||||
|
&Position,
|
||||||
|
&EyeHeight,
|
||||||
|
&LookDirection,
|
||||||
|
&InstanceName,
|
||||||
|
&Physics,
|
||||||
|
&Attributes,
|
||||||
|
),
|
||||||
|
With<LocalEntity>,
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
physics_query: PhysicsQuery,
|
||||||
|
pickable_query: PickableEntityQuery,
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
entity,
|
||||||
|
hit_result_ref,
|
||||||
|
position,
|
||||||
|
eye_height,
|
||||||
|
look_direction,
|
||||||
|
world_name,
|
||||||
|
physics,
|
||||||
|
attributes,
|
||||||
|
) in &mut query
|
||||||
|
{
|
||||||
|
let block_pick_range = attributes.block_interaction_range.calculate();
|
||||||
|
let entity_pick_range = attributes.entity_interaction_range.calculate();
|
||||||
|
|
||||||
|
let eye_position = position.up(eye_height.into());
|
||||||
|
|
||||||
|
let Some(world_lock) = instance_container.get(world_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let world = world_lock.read();
|
||||||
|
|
||||||
|
let aabb = &physics.bounding_box;
|
||||||
|
let hit_result = pick(
|
||||||
|
entity,
|
||||||
|
*look_direction,
|
||||||
|
eye_position,
|
||||||
|
aabb,
|
||||||
|
&world,
|
||||||
|
entity_pick_range,
|
||||||
|
block_pick_range,
|
||||||
|
&physics_query,
|
||||||
|
&pickable_query,
|
||||||
|
);
|
||||||
|
if let Some(mut hit_result_ref) = hit_result_ref {
|
||||||
|
**hit_result_ref = hit_result;
|
||||||
|
} else {
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(HitResultComponent(hit_result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PickableEntityQuery<'world, 'state, 'a> = Query<
|
||||||
|
'world,
|
||||||
|
'state,
|
||||||
|
Option<&'a ArmorStandMarker>,
|
||||||
|
(Without<Dead>, Without<Marker>, Without<LocalEntity>),
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
source_entity: Entity,
|
||||||
|
look_direction: LookDirection,
|
||||||
|
eye_position: Vec3,
|
||||||
|
aabb: &AABB,
|
||||||
|
world: &Instance,
|
||||||
|
entity_pick_range: f64,
|
||||||
|
block_pick_range: f64,
|
||||||
|
physics_query: &PhysicsQuery,
|
||||||
|
pickable_query: &PickableEntityQuery,
|
||||||
|
) -> HitResult {
|
||||||
|
// vanilla does extra math here to calculate the pick result in between ticks by
|
||||||
|
// interpolating, but since clients can still only interact on exact ticks, that
|
||||||
|
// isn't relevant for us.
|
||||||
|
|
||||||
|
let mut max_range = entity_pick_range.max(block_pick_range);
|
||||||
|
let mut max_range_squared = max_range.powi(2);
|
||||||
|
|
||||||
|
let block_hit_result = pick_block(look_direction, eye_position, &world.chunks, max_range);
|
||||||
|
let block_hit_result_dist_squared = block_hit_result.location.distance_squared_to(eye_position);
|
||||||
|
if !block_hit_result.miss {
|
||||||
|
max_range_squared = block_hit_result_dist_squared;
|
||||||
|
max_range = block_hit_result_dist_squared.sqrt();
|
||||||
|
}
|
||||||
|
|
||||||
|
let view_vector = view_vector(look_direction);
|
||||||
|
let end_position = eye_position + (view_vector * max_range);
|
||||||
|
let inflate_by = 1.;
|
||||||
|
let pick_aabb = aabb
|
||||||
|
.expand_towards(view_vector * max_range)
|
||||||
|
.inflate_all(inflate_by);
|
||||||
|
|
||||||
|
let is_pickable = |entity: Entity| {
|
||||||
|
// TODO: ender dragon and projectiles have extra logic here. also, we shouldn't
|
||||||
|
// be able to pick spectators.
|
||||||
|
if let Ok(armor_stand_marker) = pickable_query.get(entity) {
|
||||||
|
if let Some(armor_stand_marker) = armor_stand_marker
|
||||||
|
&& armor_stand_marker.0
|
||||||
|
{
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let entity_hit_result = pick_entity(
|
||||||
|
source_entity,
|
||||||
|
eye_position,
|
||||||
|
end_position,
|
||||||
|
world,
|
||||||
|
max_range_squared,
|
||||||
|
&is_pickable,
|
||||||
|
&pick_aabb,
|
||||||
|
physics_query,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
if let Some(entity_hit_result) = entity_hit_result
|
||||||
|
&& entity_hit_result.location.distance_squared_to(eye_position)
|
||||||
|
< block_hit_result_dist_squared
|
||||||
|
{
|
||||||
|
filter_hit_result(
|
||||||
|
HitResult::Entity(entity_hit_result),
|
||||||
|
eye_position,
|
||||||
|
entity_pick_range,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
filter_hit_result(
|
||||||
|
HitResult::Block(block_hit_result),
|
||||||
|
eye_position,
|
||||||
|
block_pick_range,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_hit_result(hit_result: HitResult, eye_position: Vec3, range: f64) -> HitResult {
|
||||||
|
let location = hit_result.location();
|
||||||
|
if !location.closer_than(eye_position, range) {
|
||||||
|
let direction = Direction::nearest(location - eye_position);
|
||||||
|
HitResult::new_miss(location, direction, location.into())
|
||||||
|
} else {
|
||||||
|
hit_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
from: eye_position,
|
||||||
|
to: end_position,
|
||||||
|
block_shape_type: BlockShapeType::Outline,
|
||||||
|
fluid_pick_type: FluidPickType::None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// port of getEntityHitResult
|
||||||
|
fn pick_entity(
|
||||||
|
source_entity: Entity,
|
||||||
|
eye_position: Vec3,
|
||||||
|
end_position: Vec3,
|
||||||
|
world: &azalea_world::Instance,
|
||||||
|
pick_range_squared: f64,
|
||||||
|
predicate: &dyn Fn(Entity) -> bool,
|
||||||
|
aabb: &AABB,
|
||||||
|
physics_query: &PhysicsQuery,
|
||||||
|
) -> Option<EntityHitResult> {
|
||||||
|
let mut picked_distance_squared = pick_range_squared;
|
||||||
|
let mut result = None;
|
||||||
|
|
||||||
|
for (candidate, candidate_aabb) in
|
||||||
|
get_entities(world, Some(source_entity), aabb, predicate, physics_query)
|
||||||
|
{
|
||||||
|
// TODO: if the entity is "REDIRECTABLE_PROJECTILE" then this should be 1.0.
|
||||||
|
// azalea needs support for entity tags first for this to be possible. see
|
||||||
|
// getPickRadius in decompiled minecraft source
|
||||||
|
let candidate_pick_radius = 0.;
|
||||||
|
let candidate_aabb = candidate_aabb.inflate_all(candidate_pick_radius);
|
||||||
|
let clip_location = candidate_aabb.clip(eye_position, end_position);
|
||||||
|
|
||||||
|
if candidate_aabb.contains(eye_position) {
|
||||||
|
if picked_distance_squared >= 0. {
|
||||||
|
result = Some(EntityHitResult {
|
||||||
|
location: clip_location.unwrap_or(eye_position),
|
||||||
|
entity: candidate,
|
||||||
|
});
|
||||||
|
picked_distance_squared = 0.;
|
||||||
|
}
|
||||||
|
} else if let Some(clip_location) = clip_location {
|
||||||
|
let distance_squared = eye_position.distance_squared_to(clip_location);
|
||||||
|
if distance_squared < picked_distance_squared || picked_distance_squared == 0. {
|
||||||
|
// TODO: don't pick the entity we're riding on
|
||||||
|
// if candidate_root_vehicle == entity_root_vehicle {
|
||||||
|
// if picked_distance_squared == 0. {
|
||||||
|
// picked_entity = Some(candidate);
|
||||||
|
// picked_location = Some(clip_location);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
result = Some(EntityHitResult {
|
||||||
|
location: clip_location,
|
||||||
|
entity: candidate,
|
||||||
|
});
|
||||||
|
picked_distance_squared = distance_squared;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
|
@ -13,8 +13,8 @@ use tracing::trace;
|
||||||
use crate::{
|
use crate::{
|
||||||
Client,
|
Client,
|
||||||
interact::{
|
interact::{
|
||||||
BlockStatePredictionHandler, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
|
BlockStatePredictionHandler, SwingArmEvent, can_use_game_master_blocks,
|
||||||
check_is_interaction_restricted,
|
check_is_interaction_restricted, pick::HitResultComponent,
|
||||||
},
|
},
|
||||||
inventory::{Inventory, InventorySet},
|
inventory::{Inventory, InventorySet},
|
||||||
local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
|
local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||||
|
@ -57,7 +57,7 @@ impl Plugin for MiningPlugin {
|
||||||
.after(MoveEventsSet)
|
.after(MoveEventsSet)
|
||||||
.before(azalea_entity::update_bounding_box)
|
.before(azalea_entity::update_bounding_box)
|
||||||
.after(azalea_entity::update_fluid_on_eyes)
|
.after(azalea_entity::update_fluid_on_eyes)
|
||||||
.after(crate::interact::update_hit_result_component)
|
.after(crate::interact::pick::update_hit_result_component)
|
||||||
.after(crate::attack::handle_attack_event)
|
.after(crate::attack::handle_attack_event)
|
||||||
.after(crate::interact::handle_start_use_item_queued)
|
.after(crate::interact::handle_start_use_item_queued)
|
||||||
.before(crate::interact::handle_swing_arm_event),
|
.before(crate::interact::handle_swing_arm_event),
|
||||||
|
@ -143,7 +143,7 @@ fn handle_auto_mine(
|
||||||
entity,
|
entity,
|
||||||
position: block_pos,
|
position: block_pos,
|
||||||
});
|
});
|
||||||
} else if mining.is_some() && hit_result_component.is_miss() {
|
} else if mining.is_some() && hit_result_component.miss() {
|
||||||
stop_mining_block_event.write(StopMiningBlockEvent { entity });
|
stop_mining_block_event.write(StopMiningBlockEvent { entity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ use azalea_core::{
|
||||||
position::{ChunkPos, Vec3},
|
position::{ChunkPos, Vec3},
|
||||||
};
|
};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection,
|
Dead, EntityBundle, EntityKindComponent, LastSentPosition, LoadedBy, LocalEntity,
|
||||||
Physics, Position, RelativeEntityUpdate,
|
LookDirection, Physics, Position, RelativeEntityUpdate,
|
||||||
indexing::{EntityIdIndex, EntityUuidIndex},
|
indexing::{EntityIdIndex, EntityUuidIndex},
|
||||||
metadata::{Health, apply_metadata},
|
metadata::{Health, apply_metadata},
|
||||||
};
|
};
|
||||||
|
@ -665,7 +665,7 @@ impl GamePacketHandler<'_> {
|
||||||
Query<(&EntityIdIndex, &InstanceHolder)>,
|
Query<(&EntityIdIndex, &InstanceHolder)>,
|
||||||
// this is a separate query since it's applied on the entity id that's being updated
|
// this is a separate query since it's applied on the entity id that's being updated
|
||||||
// instead of the player that received the packet
|
// instead of the player that received the packet
|
||||||
Query<&EntityKind>,
|
Query<&EntityKindComponent>,
|
||||||
)>(self.ecs, |(mut commands, query, entity_kind_query)| {
|
)>(self.ecs, |(mut commands, query, entity_kind_query)| {
|
||||||
let (entity_id_index, instance_holder) = query.get(self.player).unwrap();
|
let (entity_id_index, instance_holder) = query.get(self.player).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use bevy_ecs::entity::Entity;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
direction::Direction,
|
direction::Direction,
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
|
@ -9,30 +11,47 @@ use crate::{
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum HitResult {
|
pub enum HitResult {
|
||||||
Block(BlockHitResult),
|
Block(BlockHitResult),
|
||||||
/// TODO
|
Entity(EntityHitResult),
|
||||||
Entity,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HitResult {
|
impl HitResult {
|
||||||
pub fn is_miss(&self) -> bool {
|
pub fn miss(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
HitResult::Block(block_hit_result) => block_hit_result.miss,
|
HitResult::Block(r) => r.miss,
|
||||||
HitResult::Entity => false,
|
HitResult::Entity(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn location(&self) -> Vec3 {
|
||||||
|
match self {
|
||||||
|
HitResult::Block(r) => r.location,
|
||||||
|
HitResult::Entity(r) => r.location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
|
||||||
|
HitResult::Block(BlockHitResult {
|
||||||
|
location,
|
||||||
|
miss: true,
|
||||||
|
direction,
|
||||||
|
block_pos,
|
||||||
|
inside: false,
|
||||||
|
world_border: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_block_hit_and_not_miss(&self) -> bool {
|
pub fn is_block_hit_and_not_miss(&self) -> bool {
|
||||||
match self {
|
matches!(self, HitResult::Block(r) if !r.miss)
|
||||||
HitResult::Block(block_hit_result) => !block_hit_result.miss,
|
|
||||||
HitResult::Entity => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`BlockHitResult`], if we were looking at a block and it
|
/// Returns the [`BlockHitResult`], if we were looking at a block and it
|
||||||
/// wasn't a miss.
|
/// wasn't a miss.
|
||||||
pub fn as_block_hit_result_if_not_miss(&self) -> Option<&BlockHitResult> {
|
pub fn as_block_hit_result_if_not_miss(&self) -> Option<&BlockHitResult> {
|
||||||
match self {
|
if let HitResult::Block(r) = self
|
||||||
HitResult::Block(block_hit_result) if !block_hit_result.miss => Some(block_hit_result),
|
&& !r.miss
|
||||||
_ => None,
|
{
|
||||||
|
Some(r)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,20 +59,21 @@ impl HitResult {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct BlockHitResult {
|
pub struct BlockHitResult {
|
||||||
pub location: Vec3,
|
pub location: Vec3,
|
||||||
|
pub miss: bool,
|
||||||
|
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub block_pos: BlockPos,
|
pub block_pos: BlockPos,
|
||||||
pub inside: bool,
|
pub inside: bool,
|
||||||
pub world_border: bool,
|
pub world_border: bool,
|
||||||
pub miss: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHitResult {
|
impl BlockHitResult {
|
||||||
pub fn miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
|
pub fn miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
location,
|
location,
|
||||||
|
miss: true,
|
||||||
|
|
||||||
direction,
|
direction,
|
||||||
block_pos,
|
block_pos,
|
||||||
miss: true,
|
|
||||||
inside: false,
|
inside: false,
|
||||||
world_border: false,
|
world_border: false,
|
||||||
}
|
}
|
||||||
|
@ -66,3 +86,20 @@ impl BlockHitResult {
|
||||||
Self { block_pos, ..*self }
|
Self { block_pos, ..*self }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct EntityHitResult {
|
||||||
|
pub location: Vec3,
|
||||||
|
pub entity: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockHitResult> for HitResult {
|
||||||
|
fn from(value: BlockHitResult) -> Self {
|
||||||
|
HitResult::Block(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<EntityHitResult> for HitResult {
|
||||||
|
fn from(value: EntityHitResult) -> Self {
|
||||||
|
HitResult::Entity(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -350,6 +350,12 @@ impl Vec3 {
|
||||||
z: self.z.ceil() as i32,
|
z: self.z.ceil() as i32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the distance between this point and `other` is less than
|
||||||
|
/// `range`.
|
||||||
|
pub fn closer_than(&self, other: Vec3, range: f64) -> bool {
|
||||||
|
self.distance_squared_to(other) < range.powi(2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The coordinates of a block in the world.
|
/// The coordinates of a block in the world.
|
||||||
|
|
|
@ -12,6 +12,9 @@ pub struct Attributes {
|
||||||
pub speed: AttributeInstance,
|
pub speed: AttributeInstance,
|
||||||
pub attack_speed: AttributeInstance,
|
pub attack_speed: AttributeInstance,
|
||||||
pub water_movement_efficiency: AttributeInstance,
|
pub water_movement_efficiency: AttributeInstance,
|
||||||
|
|
||||||
|
pub block_interaction_range: AttributeInstance,
|
||||||
|
pub entity_interaction_range: AttributeInstance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -93,7 +96,6 @@ pub fn sprinting_modifier() -> AttributeModifier {
|
||||||
operation: AttributeModifierOperation::MultiplyTotal,
|
operation: AttributeModifierOperation::MultiplyTotal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
||||||
AttributeModifier {
|
AttributeModifier {
|
||||||
id: ResourceLocation::new("base_attack_speed"),
|
id: ResourceLocation::new("base_attack_speed"),
|
||||||
|
@ -101,3 +103,18 @@ pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
||||||
operation: AttributeModifierOperation::Addition,
|
operation: AttributeModifierOperation::Addition,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn creative_block_interaction_range_modifier() -> AttributeModifier {
|
||||||
|
AttributeModifier {
|
||||||
|
id: ResourceLocation::new("creative_mode_block_range"),
|
||||||
|
amount: 0.5,
|
||||||
|
operation: AttributeModifierOperation::Addition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn creative_entity_interaction_range_modifier() -> AttributeModifier {
|
||||||
|
AttributeModifier {
|
||||||
|
id: ResourceLocation::new("creative_mode_entity_range"),
|
||||||
|
amount: 2.0,
|
||||||
|
operation: AttributeModifierOperation::Addition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ use azalea_core::{
|
||||||
position::{BlockPos, ChunkPos, Vec3},
|
position::{BlockPos, ChunkPos, Vec3},
|
||||||
resource_location::ResourceLocation,
|
resource_location::ResourceLocation,
|
||||||
};
|
};
|
||||||
|
use azalea_registry::EntityKind;
|
||||||
use azalea_world::{ChunkStorage, InstanceName};
|
use azalea_world::{ChunkStorage, InstanceName};
|
||||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
pub use data::*;
|
pub use data::*;
|
||||||
|
@ -426,13 +427,13 @@ impl From<&EyeHeight> for f64 {
|
||||||
/// Most of the time, you should be using `azalea_registry::EntityKind`
|
/// Most of the time, you should be using `azalea_registry::EntityKind`
|
||||||
/// directly instead.
|
/// directly instead.
|
||||||
#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)]
|
#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)]
|
||||||
pub struct EntityKind(pub azalea_registry::EntityKind);
|
pub struct EntityKindComponent(pub azalea_registry::EntityKind);
|
||||||
|
|
||||||
/// A bundle of components that every entity has. This doesn't contain metadata,
|
/// A bundle of components that every entity has. This doesn't contain metadata,
|
||||||
/// that has to be added separately.
|
/// that has to be added separately.
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct EntityBundle {
|
pub struct EntityBundle {
|
||||||
pub kind: EntityKind,
|
pub kind: EntityKindComponent,
|
||||||
pub uuid: EntityUuid,
|
pub uuid: EntityUuid,
|
||||||
pub world_name: InstanceName,
|
pub world_name: InstanceName,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
@ -465,7 +466,7 @@ impl EntityBundle {
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
kind: EntityKind(kind),
|
kind: EntityKindComponent(kind),
|
||||||
uuid: EntityUuid(uuid),
|
uuid: EntityUuid(uuid),
|
||||||
world_name: InstanceName(world_name),
|
world_name: InstanceName(world_name),
|
||||||
position: Position(pos),
|
position: Position(pos),
|
||||||
|
@ -475,13 +476,7 @@ impl EntityBundle {
|
||||||
eye_height: EyeHeight(eye_height),
|
eye_height: EyeHeight(eye_height),
|
||||||
direction: LookDirection::default(),
|
direction: LookDirection::default(),
|
||||||
|
|
||||||
attributes: Attributes {
|
attributes: default_attributes(EntityKind::Player),
|
||||||
// TODO: do the correct defaults for everything, some
|
|
||||||
// entities have different defaults
|
|
||||||
speed: AttributeInstance::new(0.1),
|
|
||||||
attack_speed: AttributeInstance::new(4.0),
|
|
||||||
water_movement_efficiency: AttributeInstance::new(0.0),
|
|
||||||
},
|
|
||||||
|
|
||||||
jumping: Jumping(false),
|
jumping: Jumping(false),
|
||||||
fluid_on_eyes: FluidOnEyes(FluidKind::Empty),
|
fluid_on_eyes: FluidOnEyes(FluidKind::Empty),
|
||||||
|
@ -490,6 +485,18 @@ impl EntityBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_attributes(_entity_kind: EntityKind) -> Attributes {
|
||||||
|
// TODO: do the correct defaults for everything, some
|
||||||
|
// entities have different defaults
|
||||||
|
Attributes {
|
||||||
|
speed: AttributeInstance::new(0.1),
|
||||||
|
attack_speed: AttributeInstance::new(4.0),
|
||||||
|
water_movement_efficiency: AttributeInstance::new(0.0),
|
||||||
|
block_interaction_range: AttributeInstance::new(4.5),
|
||||||
|
entity_interaction_range: AttributeInstance::new(3.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A marker component that signifies that this entity is "local" and shouldn't
|
/// A marker component that signifies that this entity is "local" and shouldn't
|
||||||
/// be updated by other clients.
|
/// be updated by other clients.
|
||||||
///
|
///
|
||||||
|
|
|
@ -147,11 +147,8 @@ impl PartialEntityInfos {
|
||||||
///
|
///
|
||||||
/// Also see [`PartialInstance`].
|
/// Also see [`PartialInstance`].
|
||||||
///
|
///
|
||||||
/// The reason this is called "instance" instead of "world" or "dimension" is
|
/// This is sometimes interchangably called a "world". However, this type is
|
||||||
/// because "world" already means the entire ECS (which can contain multiple
|
/// called `Instance` to avoid colliding with the `World` type from Bevy ECS.
|
||||||
/// instances if we're in a swarm) and "dimension" can be ambiguous (for
|
|
||||||
/// instance there can be multiple overworlds, and "dimension" is also a math
|
|
||||||
/// term)
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub chunks: ChunkStorage,
|
pub chunks: ChunkStorage,
|
||||||
|
|
|
@ -7,11 +7,13 @@ use azalea::{
|
||||||
brigadier::prelude::*,
|
brigadier::prelude::*,
|
||||||
chunks::ReceiveChunkEvent,
|
chunks::ReceiveChunkEvent,
|
||||||
entity::{LookDirection, Position},
|
entity::{LookDirection, Position},
|
||||||
interact::HitResultComponent,
|
interact::pick::HitResultComponent,
|
||||||
packet::game,
|
packet::game,
|
||||||
pathfinder::{ExecutingPath, Pathfinder},
|
pathfinder::{ExecutingPath, Pathfinder},
|
||||||
world::MinecraftEntityId,
|
world::MinecraftEntityId,
|
||||||
};
|
};
|
||||||
|
use azalea_core::hit_result::HitResult;
|
||||||
|
use azalea_entity::EntityKindComponent;
|
||||||
use azalea_world::InstanceContainer;
|
use azalea_world::InstanceContainer;
|
||||||
use bevy_ecs::event::Events;
|
use bevy_ecs::event::Events;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -104,15 +106,24 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||||
|
|
||||||
let hit_result = source.bot.component::<HitResultComponent>();
|
let hit_result = source.bot.component::<HitResultComponent>();
|
||||||
|
|
||||||
let Some(hit_result) = hit_result.as_block_hit_result_if_not_miss() else {
|
match &*hit_result {
|
||||||
source.reply("I'm not looking at anything");
|
HitResult::Block(r) => {
|
||||||
return 1;
|
if r.miss {
|
||||||
};
|
source.reply("I'm not looking at anything");
|
||||||
|
return 0;
|
||||||
let block_pos = hit_result.block_pos;
|
}
|
||||||
let block = source.bot.world().read().get_block_state(block_pos);
|
let block_pos = r.block_pos;
|
||||||
|
let block = source.bot.world().read().get_block_state(block_pos);
|
||||||
source.reply(&format!("I'm looking at {block:?} at {block_pos:?}"));
|
source.reply(&format!("I'm looking at {block:?} at {block_pos:?}"));
|
||||||
|
}
|
||||||
|
HitResult::Entity(r) => {
|
||||||
|
let entity_kind = *source.bot.entity_component::<EntityKindComponent>(r.entity);
|
||||||
|
source.reply(&format!(
|
||||||
|
"I'm looking at {entity_kind} ({:?}) at {}",
|
||||||
|
r.entity, r.location
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1
|
1
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -237,7 +237,7 @@ impl Goal for ReachBlockPosGoal {
|
||||||
|
|
||||||
let eye_position = n.center_bottom().up(1.62);
|
let eye_position = n.center_bottom().up(1.62);
|
||||||
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_block(
|
let block_hit_result = azalea_client::interact::pick::pick_block(
|
||||||
look_direction,
|
look_direction,
|
||||||
eye_position,
|
eye_position,
|
||||||
&self.chunk_storage,
|
&self.chunk_storage,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use azalea_core::{
|
||||||
game_type::GameMode, position::Vec3, resource_location::ResourceLocation, tick::GameTick,
|
game_type::GameMode, position::Vec3, resource_location::ResourceLocation, tick::GameTick,
|
||||||
};
|
};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance,
|
Attributes, EntityDimensions, LookDirection, Physics, Position, default_attributes,
|
||||||
};
|
};
|
||||||
use azalea_registry::EntityKind;
|
use azalea_registry::EntityKind;
|
||||||
use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
|
use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
|
||||||
|
@ -38,11 +38,7 @@ impl SimulatedPlayerBundle {
|
||||||
physics: Physics::new(dimensions, position),
|
physics: Physics::new(dimensions, position),
|
||||||
physics_state: PhysicsState::default(),
|
physics_state: PhysicsState::default(),
|
||||||
look_direction: LookDirection::default(),
|
look_direction: LookDirection::default(),
|
||||||
attributes: Attributes {
|
attributes: default_attributes(EntityKind::Player),
|
||||||
speed: AttributeInstance::new(0.1),
|
|
||||||
attack_speed: AttributeInstance::new(4.0),
|
|
||||||
water_movement_efficiency: AttributeInstance::new(0.0),
|
|
||||||
},
|
|
||||||
inventory: Inventory::default(),
|
inventory: Inventory::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue