mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
merge main
This commit is contained in:
commit
e1d9c729d3
39 changed files with 1125 additions and 762 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -10,14 +10,29 @@ is breaking anyways, semantic versioning is not followed.
|
|||
|
||||
### 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`.
|
||||
- `Client::attack` now takes `Entity` instead of `MinecraftEntityId`.
|
||||
|
||||
### Fixed
|
||||
|
||||
## [0.13.0] - 2025-06-15
|
||||
|
||||
### Added
|
||||
|
||||
- This changelog. To see changes before this update, look at the git commits.
|
||||
- azalea and azalea-client now have a `packet-event` feature, which can be disabled for efficiency if you're not using `Event::Packet`.
|
||||
- `StartJoinServerEvent` can now be used to join servers exclusively from the ECS without a Tokio runtime.
|
||||
- `FormattedText::to_html` and `FormattedText::to_custom_format`. (@Kumpelinus)
|
||||
- Add `FormattedText::to_html` and `FormattedText::to_custom_format`. (@Kumpelinus)
|
||||
- Non-standard legacy hex colors like `§#ff0000` are now supported in azalea-chat.
|
||||
- Chat signing.
|
||||
- Add auto-reconnecting which is enabled by default.
|
||||
- `Client::start_use_item`.
|
||||
- `ClientBuilder` and `SwarmBuilder` are now Send.
|
||||
- Add `Client::start_use_item`.
|
||||
- The pathfinder no longer avoids slabs, stairs, and dirt path blocks.
|
||||
- The pathfinder now immediately recalculates if blocks are placed in its path.
|
||||
- Bots that use custom pathfinder moves can now keep arbitrary persistent state by using the `CustomPathfinderState` component and `PathfinderCtx::custom_state`.
|
||||
|
@ -27,9 +42,11 @@ is breaking anyways, semantic versioning is not followed.
|
|||
- Proper support for getting biomes at coordinates.
|
||||
- Add a new `Client::entities_by` which sorts entities that match a criteria by their distance to the client.
|
||||
- New client event `Event::ReceiveChunk`.
|
||||
- Several new functions for interacting with inventories.
|
||||
- Several new functions for interacting with inventories (`Client::get_inventory`, `get_held_item`, `ContainerHandleRef::left_click`, `shift_click`, `right_click`, `slots`).
|
||||
- Add `Client::mine_with_auto_tool`.
|
||||
- Add `Client::set_selected_hotbar_slot` and `Client::selected_hotbar_slot`.
|
||||
- Add `Client::attack_cooldown_remaining_ticks` to complement `has_attack_cooldown`.
|
||||
- Add `BlockPos::length`, `distance_to`, and `center_bottom`.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -38,11 +55,10 @@ is breaking anyways, semantic versioning is not followed.
|
|||
- Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/).
|
||||
- Rename `InstanceContainer::insert` to `get_or_insert`.
|
||||
- Replace `BlockInteractEvent` with the more general-purpose `StartUseItemEvent`.
|
||||
- `ClientBuilder` and `SwarmBuilder` are now Send.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
|
875
Cargo.lock
generated
875
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
57
Cargo.toml
57
Cargo.toml
|
@ -22,7 +22,7 @@ resolver = "2"
|
|||
# --- Workspace Settings ---
|
||||
|
||||
[workspace.package]
|
||||
version = "0.12.0+mc1.21.6"
|
||||
version = "0.13.0+mc1.21.6"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/azalea-rs/azalea"
|
||||
|
@ -33,19 +33,19 @@ aes = "0.8.4"
|
|||
anyhow = "1.0.98"
|
||||
async-recursion = "1.1.1"
|
||||
base64 = "0.22.1"
|
||||
bevy_app = "0.16.0"
|
||||
bevy_ecs = { version = "0.16.0", default-features = false }
|
||||
bevy_log = "0.16.0"
|
||||
bevy_tasks = "0.16.0"
|
||||
bevy_time = "0.16.0"
|
||||
bevy_app = "0.16.1"
|
||||
bevy_ecs = { version = "0.16.1", default-features = false }
|
||||
bevy_log = "0.16.1"
|
||||
bevy_tasks = "0.16.1"
|
||||
bevy_time = "0.16.1"
|
||||
byteorder = "1.5.0"
|
||||
cfb8 = "0.8.1"
|
||||
chrono = { version = "0.4.40", default-features = false }
|
||||
criterion = "0.5.1"
|
||||
chrono = { version = "0.4.41", default-features = false }
|
||||
criterion = "0.6.0"
|
||||
derive_more = "2.0.1"
|
||||
enum-as-inner = "0.6.1"
|
||||
env_logger = "0.11.8"
|
||||
flate2 = { version = "1.1.1", features = ["zlib-rs"] }
|
||||
flate2 = { version = "1.1.2", features = ["zlib-rs"] }
|
||||
futures = "0.3.31"
|
||||
futures-lite = "2.6.0"
|
||||
md-5 = "0.10.6"
|
||||
|
@ -53,29 +53,29 @@ minecraft_folder_path = "0.1.2"
|
|||
nohash-hasher = "0.2.0"
|
||||
num-bigint = "0.4.6"
|
||||
num-traits = "0.2.19"
|
||||
parking_lot = "0.12.3"
|
||||
parking_lot = "0.12.4"
|
||||
proc-macro2 = "1.0.95"
|
||||
quote = "1.0.40"
|
||||
rand = "0.8.4"
|
||||
rand = "0.9.1"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.15", default-features = false }
|
||||
rsa = "0.9.8"
|
||||
reqwest = { version = "0.12.20", default-features = false }
|
||||
rsa = "0.10.0-rc.0"
|
||||
rsa_public_encrypt_pkcs1 = "0.4.0"
|
||||
rustc-hash = "2.1.1"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
sha-1 = "0.10.1"
|
||||
sha2 = "0.10.8"
|
||||
sha2 = "0.11.0-rc.0"
|
||||
simdnbt = "0.7"
|
||||
socks5-impl = "0.6.2"
|
||||
syn = "2.0.100"
|
||||
socks5-impl = "0.7.0"
|
||||
syn = "2.0.103"
|
||||
thiserror = "2.0.12"
|
||||
tokio = "1.44.2"
|
||||
tokio = "1.45.1"
|
||||
tokio-util = "0.7.15"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
hickory-resolver = "0.25.2"
|
||||
uuid = "1.16"
|
||||
uuid = "1.17"
|
||||
num-format = "0.4.4"
|
||||
indexmap = "2.9.0"
|
||||
paste = "1.0.15"
|
||||
|
@ -83,6 +83,27 @@ compact_str = "0.9.0"
|
|||
crc32fast = "1.4.2"
|
||||
async-compat = "0.2.4"
|
||||
|
||||
azalea-block-macros = { path = "azalea-block/azalea-block-macros", version = "0.13.0" }
|
||||
azalea-block = { path = "azalea-block", version = "0.13.0" }
|
||||
azalea-auth = { path = "azalea-auth", version = "0.13.0" }
|
||||
azalea-brigadier = { path = "azalea-brigadier", version = "0.13.0" }
|
||||
azalea-buf-macros = { path = "azalea-buf/azalea-buf-macros", version = "0.13.0" }
|
||||
azalea-buf = { path = "azalea-buf", version = "0.13.0" }
|
||||
azalea-chat = { path = "azalea-chat", version = "0.13.0" }
|
||||
azalea-client = { path = "azalea-client", version = "0.13.0", default-features = false }
|
||||
azalea-core = { path = "azalea-core", version = "0.13.0" }
|
||||
azalea-crypto = { path = "azalea-crypto", version = "0.13.0" }
|
||||
azalea-entity = { path = "azalea-entity", version = "0.13.0" }
|
||||
azalea-inventory-macros = { path = "azalea-inventory/azalea-inventory-macros", version = "0.13.0" }
|
||||
azalea-inventory = { path = "azalea-inventory", version = "0.13.0" }
|
||||
azalea-language = { path = "azalea-language", version = "0.13.0" }
|
||||
azalea-physics = { path = "azalea-physics", version = "0.13.0" }
|
||||
azalea-protocol-macros = { path = "azalea-protocol/azalea-protocol-macros", version = "0.13.0" }
|
||||
azalea-protocol = { path = "azalea-protocol", version = "0.13.0" }
|
||||
azalea-registry-macros = { path = "azalea-registry/azalea-registry-macros", version = "0.13.0" }
|
||||
azalea-registry = { path = "azalea-registry", version = "0.13.0" }
|
||||
azalea-world = { path = "azalea-world", version = "0.13.0" }
|
||||
|
||||
# --- Profile Settings ---
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -23,7 +23,7 @@ _Currently supported Minecraft version: `1.21.6`._
|
|||
- [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)
|
||||
- [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)
|
||||
|
||||
## Docs
|
||||
|
|
|
@ -7,8 +7,8 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" }
|
||||
azalea-buf.workspace = true
|
||||
azalea-crypto.workspace = true
|
||||
base64.workspace = true
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
md-5.workspace = true
|
||||
|
|
|
@ -7,6 +7,6 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-block-macros = { path = "./azalea-block-macros", version = "0.12.0" }
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-block-macros.workspace = true
|
||||
azalea-buf.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
|
|
|
@ -11,8 +11,8 @@ bevy_app.workspace = true
|
|||
bevy_ecs.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0", optional = true }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0", optional = true }
|
||||
azalea-buf = { workspace = true, optional = true }
|
||||
azalea-chat = { workspace = true, optional = true }
|
||||
parking_lot.workspace = true
|
||||
|
||||
[features]
|
||||
|
|
|
@ -3,12 +3,11 @@ mod suggestions;
|
|||
mod suggestions_builder;
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use std::io::Write;
|
||||
use std::io::{self, Write};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display},
|
||||
hash::Hash,
|
||||
io,
|
||||
};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#[cfg(feature = "azalea-buf")]
|
||||
use std::io::{Cursor, Write};
|
||||
use std::{collections::HashSet, hash::Hash, io};
|
||||
use std::io::{self, Cursor, Write};
|
||||
use std::{collections::HashSet, hash::Hash};
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||
|
|
|
@ -7,7 +7,7 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.12.0" }
|
||||
azalea-buf-macros.workspace = true
|
||||
byteorder.workspace = true
|
||||
serde_json = { workspace = true, optional = true }
|
||||
simdnbt.workspace = true
|
||||
|
|
|
@ -13,11 +13,9 @@ azalea-buf = ["dep:azalea-buf", "simdnbt"]
|
|||
numbers = ["dep:azalea-registry", "dep:simdnbt"]
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0", optional = true, features = [
|
||||
"serde_json",
|
||||
] }
|
||||
azalea-language = { path = "../azalea-language", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0", optional = true }
|
||||
azalea-buf = { workspace = true, optional = true, features = ["serde_json"] }
|
||||
azalea-language.workspace = true
|
||||
azalea-registry = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
simdnbt = { workspace = true, optional = true }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#[cfg(all(feature = "azalea-buf", feature = "simdnbt"))]
|
||||
use std::io::{self, Cursor, Write};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
io::{self, Cursor, Write},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
|
@ -9,6 +10,7 @@ use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
|||
use serde::{Deserialize, Deserializer, Serialize, de};
|
||||
#[cfg(feature = "simdnbt")]
|
||||
use simdnbt::{Deserialize as _, FromNbtTag as _, Serialize as _};
|
||||
#[cfg(all(feature = "azalea-buf", feature = "simdnbt"))]
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
|
@ -546,8 +548,7 @@ impl From<&simdnbt::Mutf8Str> for FormattedText {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
#[cfg(feature = "simdnbt")]
|
||||
#[cfg(all(feature = "azalea-buf", feature = "simdnbt"))]
|
||||
impl AzaleaRead for FormattedText {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let nbt = simdnbt::borrow::read_optional_tag(buf)?;
|
||||
|
@ -560,8 +561,7 @@ impl AzaleaRead for FormattedText {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
#[cfg(feature = "simdnbt")]
|
||||
#[cfg(all(feature = "azalea-buf", feature = "simdnbt"))]
|
||||
impl AzaleaWrite for FormattedText {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
|
||||
let mut out = Vec::new();
|
||||
|
|
|
@ -8,18 +8,18 @@ repository.workspace = true
|
|||
|
||||
[dependencies]
|
||||
async-compat.workspace = true
|
||||
azalea-auth = { path = "../azalea-auth", version = "0.12.0" }
|
||||
azalea-block = { path = "../azalea-block", version = "0.12.0" }
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0" }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0" }
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" }
|
||||
azalea-entity = { path = "../azalea-entity", version = "0.12.0" }
|
||||
azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" }
|
||||
azalea-physics = { path = "../azalea-physics", version = "0.12.0" }
|
||||
azalea-protocol = { path = "../azalea-protocol", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "0.12.0" }
|
||||
azalea-auth.workspace = true
|
||||
azalea-block.workspace = true
|
||||
azalea-buf.workspace = true
|
||||
azalea-chat.workspace = true
|
||||
azalea-core.workspace = true
|
||||
azalea-crypto.workspace = true
|
||||
azalea-entity.workspace = true
|
||||
azalea-inventory.workspace = true
|
||||
azalea-physics.workspace = true
|
||||
azalea-protocol.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
azalea-world.workspace = true
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
bevy_log = { workspace = true, optional = true }
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use azalea_core::{game_type::GameMode, tick::GameTick};
|
||||
use azalea_entity::{
|
||||
Attributes, Physics,
|
||||
indexing::EntityIdIndex,
|
||||
metadata::{ShiftKeyDown, Sprinting},
|
||||
update_bounding_box,
|
||||
};
|
||||
use azalea_physics::PhysicsSet;
|
||||
use azalea_protocol::packets::game::s_interact::{self, ServerboundInteract};
|
||||
use azalea_world::MinecraftEntityId;
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use tracing::warn;
|
||||
|
||||
use super::packet::game::SendPacketEvent;
|
||||
use crate::{
|
||||
|
@ -45,10 +46,10 @@ impl Plugin for AttackPlugin {
|
|||
|
||||
impl Client {
|
||||
/// Attack the entity with the given id.
|
||||
pub fn attack(&self, entity_id: MinecraftEntityId) {
|
||||
pub fn attack(&self, entity: Entity) {
|
||||
self.ecs.lock().send_event(AttackEvent {
|
||||
entity: self.entity,
|
||||
target: entity_id,
|
||||
target: entity,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -87,41 +88,51 @@ impl Client {
|
|||
/// entity next tick.
|
||||
#[derive(Component, Clone, Debug)]
|
||||
pub struct AttackQueued {
|
||||
pub target: MinecraftEntityId,
|
||||
pub target: Entity,
|
||||
}
|
||||
pub fn handle_attack_queued(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(
|
||||
Entity,
|
||||
&AttackQueued,
|
||||
&LocalGameMode,
|
||||
&mut TicksSinceLastAttack,
|
||||
&mut Physics,
|
||||
&mut Sprinting,
|
||||
&AttackQueued,
|
||||
&LocalGameMode,
|
||||
&ShiftKeyDown,
|
||||
&EntityIdIndex,
|
||||
)>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
attack_queued,
|
||||
game_mode,
|
||||
client_entity,
|
||||
mut ticks_since_last_attack,
|
||||
mut physics,
|
||||
mut sprinting,
|
||||
attack_queued,
|
||||
game_mode,
|
||||
sneaking,
|
||||
entity_id_index,
|
||||
) in &mut query
|
||||
{
|
||||
commands.entity(entity).remove::<AttackQueued>();
|
||||
let target_entity = attack_queued.target;
|
||||
let Some(target_entity_id) = entity_id_index.get_by_ecs_entity(target_entity) else {
|
||||
warn!("tried to attack entity {target_entity} which isn't in our EntityIdIndex");
|
||||
continue;
|
||||
};
|
||||
|
||||
commands.entity(client_entity).remove::<AttackQueued>();
|
||||
|
||||
commands.trigger(SendPacketEvent::new(
|
||||
entity,
|
||||
client_entity,
|
||||
ServerboundInteract {
|
||||
entity_id: attack_queued.target,
|
||||
entity_id: target_entity_id,
|
||||
action: s_interact::ActionType::Attack,
|
||||
using_secondary_action: **sneaking,
|
||||
},
|
||||
));
|
||||
commands.trigger(SwingArmEvent { entity });
|
||||
commands.trigger(SwingArmEvent {
|
||||
entity: client_entity,
|
||||
});
|
||||
|
||||
// we can't attack if we're in spectator mode but it still sends the attack
|
||||
// packet
|
||||
|
@ -142,7 +153,8 @@ pub fn handle_attack_queued(
|
|||
pub struct AttackEvent {
|
||||
/// Our client entity that will send the packets to attack.
|
||||
pub entity: Entity,
|
||||
pub target: MinecraftEntityId,
|
||||
/// The entity that will be attacked.
|
||||
pub target: Entity,
|
||||
}
|
||||
pub fn handle_attack_event(mut events: EventReader<AttackEvent>, mut commands: Commands) {
|
||||
for event in events.read() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub mod pick;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use azalea_block::BlockState;
|
||||
|
@ -9,27 +11,30 @@ use azalea_core::{
|
|||
tick::GameTick,
|
||||
};
|
||||
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_physics::{
|
||||
PhysicsSet,
|
||||
clip::{BlockShapeType, ClipContext, FluidPickType},
|
||||
};
|
||||
use azalea_physics::PhysicsSet;
|
||||
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,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use azalea_world::{Instance, MinecraftEntityId};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use tracing::warn;
|
||||
|
||||
use super::mining::Mining;
|
||||
use crate::{
|
||||
Client,
|
||||
attack::handle_attack_event,
|
||||
interact::pick::{HitResultComponent, update_hit_result_component},
|
||||
inventory::{Inventory, InventorySet},
|
||||
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
movement::MoveEventsSet,
|
||||
|
@ -47,24 +52,29 @@ impl Plugin for InteractPlugin {
|
|||
Update,
|
||||
(
|
||||
(
|
||||
handle_start_use_item_event,
|
||||
update_hit_result_component.after(clamp_look_direction),
|
||||
handle_swing_arm_event,
|
||||
update_attributes_for_held_item,
|
||||
update_attributes_for_gamemode,
|
||||
)
|
||||
.after(InventorySet)
|
||||
.after(perform_respawn)
|
||||
.after(handle_attack_event)
|
||||
.in_set(UpdateAttributesSet)
|
||||
.chain(),
|
||||
update_modifiers_for_held_item
|
||||
.after(InventorySet)
|
||||
.after(MoveEventsSet),
|
||||
),
|
||||
handle_start_use_item_event,
|
||||
update_hit_result_component.after(clamp_look_direction),
|
||||
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_observer(handle_swing_arm_trigger);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub struct UpdateAttributesSet;
|
||||
|
||||
impl Client {
|
||||
/// 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.
|
||||
///
|
||||
/// This event just inserts the [`StartUseItemQueued`] component on the given
|
||||
|
@ -248,6 +252,7 @@ pub fn handle_start_use_item_queued(
|
|||
&LookDirection,
|
||||
Option<&Mining>,
|
||||
)>,
|
||||
entity_id_query: Query<&MinecraftEntityId>,
|
||||
) {
|
||||
for (entity, start_use_item, mut prediction_handler, hit_result, look_direction, mining) in
|
||||
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
|
||||
// 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 {
|
||||
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 {
|
||||
HitResult::Block(block_hit_result) => {
|
||||
HitResult::Block(r) => {
|
||||
let seq = prediction_handler.start_predicting();
|
||||
if block_hit_result.miss {
|
||||
if r.miss {
|
||||
commands.trigger(SendPacketEvent::new(
|
||||
entity,
|
||||
ServerboundUseItem {
|
||||
|
@ -301,124 +306,41 @@ pub fn handle_start_use_item_queued(
|
|||
entity,
|
||||
ServerboundUseItemOn {
|
||||
hand: start_use_item.hand,
|
||||
block_hit: block_hit_result.into(),
|
||||
block_hit: r.into(),
|
||||
seq,
|
||||
},
|
||||
));
|
||||
// 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.
|
||||
// basically, this TODO is for simulating block
|
||||
// interactions/placements on the client-side.
|
||||
}
|
||||
}
|
||||
HitResult::Entity => {
|
||||
// TODO: implement HitResult::Entity
|
||||
|
||||
HitResult::Entity(r) => {
|
||||
// TODO: worldborder check
|
||||
|
||||
// commands.trigger(SendPacketEvent::new(
|
||||
// entity,
|
||||
// ServerboundInteract {
|
||||
// entity_id: todo!(),
|
||||
// action: todo!(),
|
||||
// using_secondary_action: todo!(),
|
||||
// },
|
||||
// ));
|
||||
let Ok(entity_id) = entity_id_query.get(r.entity).copied() else {
|
||||
warn!("tried to interact with an entity that doesn't have MinecraftEntityId");
|
||||
continue;
|
||||
};
|
||||
|
||||
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
|
||||
/// 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)]
|
||||
fn update_modifiers_for_held_item(
|
||||
fn update_attributes_for_held_item(
|
||||
mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
|
||||
) {
|
||||
for (mut attributes, inventory) in &mut query {
|
||||
|
@ -558,3 +480,26 @@ fn update_modifiers_for_held_item(
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
282
azalea-client/src/plugins/interact/pick.rs
Normal file
282
azalea-client/src/plugins/interact/pick.rs
Normal file
|
@ -0,0 +1,282 @@
|
|||
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 hit_result = pick(PickOpts {
|
||||
source_entity: entity,
|
||||
look_direction: *look_direction,
|
||||
eye_position,
|
||||
aabb: &physics.bounding_box,
|
||||
world: &world,
|
||||
entity_pick_range,
|
||||
block_pick_range,
|
||||
physics_query: &physics_query,
|
||||
pickable_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>),
|
||||
>;
|
||||
|
||||
pub struct PickOpts<'world, 'state, 'a, 'b, 'c> {
|
||||
source_entity: Entity,
|
||||
look_direction: LookDirection,
|
||||
eye_position: Vec3,
|
||||
aabb: &'a AABB,
|
||||
world: &'a Instance,
|
||||
entity_pick_range: f64,
|
||||
block_pick_range: f64,
|
||||
physics_query: &'a PhysicsQuery<'world, 'state, 'b>,
|
||||
pickable_query: &'a PickableEntityQuery<'world, 'state, 'c>,
|
||||
}
|
||||
|
||||
/// 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`].
|
||||
pub fn pick(opts: PickOpts<'_, '_, '_, '_, '_>) -> 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 = opts.entity_pick_range.max(opts.block_pick_range);
|
||||
let mut max_range_squared = max_range.powi(2);
|
||||
|
||||
let block_hit_result = pick_block(
|
||||
opts.look_direction,
|
||||
opts.eye_position,
|
||||
&opts.world.chunks,
|
||||
max_range,
|
||||
);
|
||||
let block_hit_result_dist_squared = block_hit_result
|
||||
.location
|
||||
.distance_squared_to(opts.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(opts.look_direction);
|
||||
let end_position = opts.eye_position + (view_vector * max_range);
|
||||
let inflate_by = 1.;
|
||||
let pick_aabb = opts
|
||||
.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) = opts.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(PickEntityOpts {
|
||||
source_entity: opts.source_entity,
|
||||
eye_position: opts.eye_position,
|
||||
end_position,
|
||||
world: opts.world,
|
||||
pick_range_squared: max_range_squared,
|
||||
predicate: &is_pickable,
|
||||
aabb: &pick_aabb,
|
||||
physics_query: opts.physics_query,
|
||||
});
|
||||
|
||||
if let Some(entity_hit_result) = entity_hit_result
|
||||
&& entity_hit_result
|
||||
.location
|
||||
.distance_squared_to(opts.eye_position)
|
||||
< block_hit_result_dist_squared
|
||||
{
|
||||
filter_hit_result(
|
||||
HitResult::Entity(entity_hit_result),
|
||||
opts.eye_position,
|
||||
opts.entity_pick_range,
|
||||
)
|
||||
} else {
|
||||
filter_hit_result(
|
||||
HitResult::Block(block_hit_result),
|
||||
opts.eye_position,
|
||||
opts.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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
struct PickEntityOpts<'world, 'state, 'a, 'b> {
|
||||
source_entity: Entity,
|
||||
eye_position: Vec3,
|
||||
end_position: Vec3,
|
||||
world: &'a azalea_world::Instance,
|
||||
pick_range_squared: f64,
|
||||
predicate: &'a dyn Fn(Entity) -> bool,
|
||||
aabb: &'a AABB,
|
||||
physics_query: &'a PhysicsQuery<'world, 'state, 'b>,
|
||||
}
|
||||
|
||||
// port of getEntityHitResult
|
||||
fn pick_entity(opts: PickEntityOpts) -> Option<EntityHitResult> {
|
||||
let mut picked_distance_squared = opts.pick_range_squared;
|
||||
let mut result = None;
|
||||
|
||||
for (candidate, candidate_aabb) in get_entities(
|
||||
opts.world,
|
||||
Some(opts.source_entity),
|
||||
opts.aabb,
|
||||
opts.predicate,
|
||||
opts.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(opts.eye_position, opts.end_position);
|
||||
|
||||
if candidate_aabb.contains(opts.eye_position) {
|
||||
if picked_distance_squared >= 0. {
|
||||
result = Some(EntityHitResult {
|
||||
location: clip_location.unwrap_or(opts.eye_position),
|
||||
entity: candidate,
|
||||
});
|
||||
picked_distance_squared = 0.;
|
||||
}
|
||||
} else if let Some(clip_location) = clip_location {
|
||||
let distance_squared = opts.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::{
|
||||
Client,
|
||||
interact::{
|
||||
BlockStatePredictionHandler, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
|
||||
check_is_interaction_restricted,
|
||||
BlockStatePredictionHandler, SwingArmEvent, can_use_game_master_blocks,
|
||||
check_is_interaction_restricted, pick::HitResultComponent,
|
||||
},
|
||||
inventory::{Inventory, InventorySet},
|
||||
local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
|
@ -57,7 +57,7 @@ impl Plugin for MiningPlugin {
|
|||
.after(MoveEventsSet)
|
||||
.before(azalea_entity::update_bounding_box)
|
||||
.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::interact::handle_start_use_item_queued)
|
||||
.before(crate::interact::handle_swing_arm_event),
|
||||
|
@ -143,7 +143,7 @@ fn handle_auto_mine(
|
|||
entity,
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ use azalea_core::{
|
|||
position::{ChunkPos, Vec3},
|
||||
};
|
||||
use azalea_entity::{
|
||||
Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection,
|
||||
Physics, Position, RelativeEntityUpdate,
|
||||
Dead, EntityBundle, EntityKindComponent, LastSentPosition, LoadedBy, LocalEntity,
|
||||
LookDirection, Physics, Position, RelativeEntityUpdate,
|
||||
indexing::{EntityIdIndex, EntityUuidIndex},
|
||||
metadata::{Health, apply_metadata},
|
||||
};
|
||||
|
@ -668,7 +668,7 @@ impl GamePacketHandler<'_> {
|
|||
Query<(&EntityIdIndex, &InstanceHolder)>,
|
||||
// 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
|
||||
Query<&EntityKind>,
|
||||
Query<&EntityKindComponent>,
|
||||
)>(self.ecs, |(mut commands, query, entity_kind_query)| {
|
||||
let (entity_id_index, instance_holder) = query.get(self.player).unwrap();
|
||||
|
||||
|
|
|
@ -7,15 +7,15 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-buf.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
bevy_ecs = { workspace = true, optional = true }
|
||||
nohash-hasher.workspace = true
|
||||
num-traits.workspace = true
|
||||
serde = { workspace = true, optional = true }
|
||||
simdnbt.workspace = true
|
||||
tracing.workspace = true
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0" }
|
||||
azalea-chat.workspace = true
|
||||
indexmap.workspace = true
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use bevy_ecs::entity::Entity;
|
||||
|
||||
use crate::{
|
||||
direction::Direction,
|
||||
position::{BlockPos, Vec3},
|
||||
|
@ -9,30 +11,47 @@ use crate::{
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum HitResult {
|
||||
Block(BlockHitResult),
|
||||
/// TODO
|
||||
Entity,
|
||||
Entity(EntityHitResult),
|
||||
}
|
||||
|
||||
impl HitResult {
|
||||
pub fn is_miss(&self) -> bool {
|
||||
pub fn miss(&self) -> bool {
|
||||
match self {
|
||||
HitResult::Block(block_hit_result) => block_hit_result.miss,
|
||||
HitResult::Entity => false,
|
||||
HitResult::Block(r) => r.miss,
|
||||
_ => 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 {
|
||||
match self {
|
||||
HitResult::Block(block_hit_result) => !block_hit_result.miss,
|
||||
HitResult::Entity => false,
|
||||
}
|
||||
matches!(self, HitResult::Block(r) if !r.miss)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
if let HitResult::Block(r) = self
|
||||
&& !r.miss
|
||||
{
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,20 +59,21 @@ impl HitResult {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BlockHitResult {
|
||||
pub location: Vec3,
|
||||
pub miss: bool,
|
||||
|
||||
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,
|
||||
miss: true,
|
||||
|
||||
direction,
|
||||
block_pos,
|
||||
miss: true,
|
||||
inside: false,
|
||||
world_border: false,
|
||||
}
|
||||
|
@ -66,3 +86,20 @@ impl BlockHitResult {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,25 +6,20 @@ use std::{
|
|||
|
||||
pub const EPSILON: f64 = 1.0E-7;
|
||||
|
||||
pub static SIN: LazyLock<[f32; 65536]> = LazyLock::new(|| {
|
||||
let mut sin = [0.0; 65536];
|
||||
for (i, item) in sin.iter_mut().enumerate() {
|
||||
*item = f64::sin((i as f64) * PI * 2.0 / 65536.0) as f32;
|
||||
}
|
||||
sin
|
||||
});
|
||||
pub static SIN: LazyLock<[f32; 65536]> =
|
||||
LazyLock::new(|| std::array::from_fn(|i| f64::sin((i as f64) * PI * 2. / 65536.) as f32));
|
||||
|
||||
/// A sine function that uses a lookup table.
|
||||
pub fn sin(x: f32) -> f32 {
|
||||
let x = x * 10430.378;
|
||||
let x = x as i32 as usize & 65535;
|
||||
let x = x as i32 as usize & 0xFFFF;
|
||||
SIN[x]
|
||||
}
|
||||
|
||||
/// A cosine function that uses a lookup table.
|
||||
pub fn cos(x: f32) -> f32 {
|
||||
let x = x * 10430.378 + 16384.0;
|
||||
let x = x as i32 as usize & 65535;
|
||||
let x = x * 10430.378 + 16384.;
|
||||
let x = x as i32 as usize & 0xFFFF;
|
||||
SIN[x]
|
||||
}
|
||||
|
||||
|
|
|
@ -350,6 +350,12 @@ impl Vec3 {
|
|||
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.
|
||||
|
|
|
@ -11,10 +11,10 @@ criterion.workspace = true
|
|||
|
||||
[dependencies]
|
||||
aes.workspace = true
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-buf.workspace = true
|
||||
cfb8.workspace = true
|
||||
num-bigint.workspace = true
|
||||
rand = { workspace = true, features = ["getrandom"] }
|
||||
rand = { workspace = true, features = ["os_rng"] }
|
||||
rsa = { workspace = true, features = ["sha2"] }
|
||||
rsa_public_encrypt_pkcs1.workspace = true
|
||||
sha-1.workspace = true
|
||||
|
|
|
@ -6,13 +6,13 @@ use aes::{
|
|||
Aes128,
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, inout::InOutBuf},
|
||||
};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use rand::{TryRngCore, rngs::OsRng};
|
||||
use sha1::{Digest, Sha1};
|
||||
pub use signing::*;
|
||||
|
||||
fn generate_secret_key() -> [u8; 16] {
|
||||
let mut key = [0u8; 16];
|
||||
OsRng.fill_bytes(&mut key);
|
||||
OsRng.try_fill_bytes(&mut key).unwrap();
|
||||
key
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ pub fn sign_chat_message(opts: &SignChatMessageOptions) -> MessageSignature {
|
|||
// ... not implemented yet
|
||||
|
||||
let signing_key = rsa::pkcs1v15::SigningKey::<Sha256>::new(opts.private_key.clone());
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = rand::rng();
|
||||
let signature = signing_key
|
||||
.sign_with_rng(&mut rng, &data_to_sign)
|
||||
.to_bytes();
|
||||
|
|
|
@ -7,15 +7,13 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-block = { path = "../azalea-block", version = "0.12.0" }
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [
|
||||
"azalea-buf",
|
||||
] }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0" }
|
||||
azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "0.12.0" }
|
||||
azalea-block.workspace = true
|
||||
azalea-buf.workspace = true
|
||||
azalea-chat = { workspace = true, features = ["azalea-buf"] }
|
||||
azalea-core.workspace = true
|
||||
azalea-inventory.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
azalea-world.workspace = true
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
derive_more.workspace = true
|
||||
|
|
|
@ -12,6 +12,9 @@ pub struct Attributes {
|
|||
pub speed: AttributeInstance,
|
||||
pub attack_speed: AttributeInstance,
|
||||
pub water_movement_efficiency: AttributeInstance,
|
||||
|
||||
pub block_interaction_range: AttributeInstance,
|
||||
pub entity_interaction_range: AttributeInstance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -93,7 +96,6 @@ pub fn sprinting_modifier() -> AttributeModifier {
|
|||
operation: AttributeModifierOperation::MultiplyTotal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
||||
AttributeModifier {
|
||||
id: ResourceLocation::new("base_attack_speed"),
|
||||
|
@ -101,3 +103,18 @@ pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
|
|||
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},
|
||||
resource_location::ResourceLocation,
|
||||
};
|
||||
use azalea_registry::EntityKind;
|
||||
use azalea_world::{ChunkStorage, InstanceName};
|
||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||
pub use data::*;
|
||||
|
@ -426,13 +427,13 @@ impl From<&EyeHeight> for f64 {
|
|||
/// Most of the time, you should be using `azalea_registry::EntityKind`
|
||||
/// directly instead.
|
||||
#[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,
|
||||
/// that has to be added separately.
|
||||
#[derive(Bundle)]
|
||||
pub struct EntityBundle {
|
||||
pub kind: EntityKind,
|
||||
pub kind: EntityKindComponent,
|
||||
pub uuid: EntityUuid,
|
||||
pub world_name: InstanceName,
|
||||
pub position: Position,
|
||||
|
@ -465,7 +466,7 @@ impl EntityBundle {
|
|||
};
|
||||
|
||||
Self {
|
||||
kind: EntityKind(kind),
|
||||
kind: EntityKindComponent(kind),
|
||||
uuid: EntityUuid(uuid),
|
||||
world_name: InstanceName(world_name),
|
||||
position: Position(pos),
|
||||
|
@ -475,13 +476,7 @@ impl EntityBundle {
|
|||
eye_height: EyeHeight(eye_height),
|
||||
direction: LookDirection::default(),
|
||||
|
||||
attributes: Attributes {
|
||||
// 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),
|
||||
},
|
||||
attributes: default_attributes(EntityKind::Player),
|
||||
|
||||
jumping: Jumping(false),
|
||||
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
|
||||
/// be updated by other clients.
|
||||
///
|
||||
|
|
|
@ -7,13 +7,11 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [
|
||||
"azalea-buf",
|
||||
] }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0" }
|
||||
azalea-inventory-macros = { path = "./azalea-inventory-macros", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-buf.workspace = true
|
||||
azalea-chat = { workspace = true, features = ["azalea-buf"] }
|
||||
azalea-core.workspace = true
|
||||
azalea-inventory-macros.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
indexmap.workspace = true
|
||||
|
||||
simdnbt.workspace = true
|
||||
|
|
|
@ -11,12 +11,12 @@ bevy_time.workspace = true
|
|||
uuid.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-block = { path = "../azalea-block", version = "0.12.0" }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0" }
|
||||
azalea-entity = { version = "0.12.0", path = "../azalea-entity" }
|
||||
azalea-inventory = { version = "0.12.0", path = "../azalea-inventory" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "0.12.0" }
|
||||
azalea-block.workspace = true
|
||||
azalea-core.workspace = true
|
||||
azalea-entity.workspace = true
|
||||
azalea-inventory.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
azalea-world.workspace = true
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
tracing.workspace = true
|
||||
|
|
|
@ -13,25 +13,18 @@ tracing-subscriber.workspace = true
|
|||
|
||||
[dependencies]
|
||||
async-recursion.workspace = true
|
||||
azalea-auth = { path = "../azalea-auth", version = "0.12.0" }
|
||||
azalea-block = { path = "../azalea-block", version = "0.12.0", default-features = false }
|
||||
azalea-brigadier = { path = "../azalea-brigadier", version = "0.12.0", features = [
|
||||
"azalea-buf",
|
||||
] }
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [
|
||||
"numbers",
|
||||
"azalea-buf",
|
||||
] }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0", optional = true, features = [
|
||||
"serde",
|
||||
] }
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" }
|
||||
azalea-entity = { path = "../azalea-entity", version = "0.12.0" }
|
||||
azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" }
|
||||
azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.12.0" }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "0.12.0" }
|
||||
azalea-auth.workspace = true
|
||||
azalea-block.workspace = true
|
||||
azalea-brigadier = { workspace = true, features = ["azalea-buf"] }
|
||||
azalea-buf.workspace = true
|
||||
azalea-chat = { workspace = true, features = ["numbers", "azalea-buf"] }
|
||||
azalea-core = { workspace = true, optional = true, features = ["serde"] }
|
||||
azalea-crypto.workspace = true
|
||||
azalea-entity.workspace = true
|
||||
azalea-inventory.workspace = true
|
||||
azalea-protocol-macros.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
azalea-world.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
# byteorder.workspace = true
|
||||
flate2.workspace = true
|
||||
|
|
|
@ -7,8 +7,8 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.12.0" }
|
||||
azalea-buf.workspace = true
|
||||
azalea-registry-macros.workspace = true
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
simdnbt.workspace = true
|
||||
|
||||
|
|
|
@ -7,16 +7,14 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
azalea-client = { path = "../azalea-client" }
|
||||
criterion = "0.5.1"
|
||||
azalea-client.workspace = true
|
||||
criterion.workspace = true
|
||||
|
||||
[dependencies]
|
||||
azalea-block = { path = "../azalea-block", default-features = false, version = "0.12.0" }
|
||||
azalea-buf = { path = "../azalea-buf", version = "0.12.0" }
|
||||
azalea-core = { path = "../azalea-core", version = "0.12.0", features = [
|
||||
"bevy_ecs",
|
||||
] }
|
||||
azalea-registry = { path = "../azalea-registry", version = "0.12.0" }
|
||||
azalea-block.workspace = true
|
||||
azalea-buf.workspace = true
|
||||
azalea-core = { workspace = true, features = ["bevy_ecs"] }
|
||||
azalea-registry.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
||||
nohash-hasher.workspace = true
|
||||
|
|
|
@ -147,11 +147,8 @@ impl PartialEntityInfos {
|
|||
///
|
||||
/// Also see [`PartialInstance`].
|
||||
///
|
||||
/// The reason this is called "instance" instead of "world" or "dimension" is
|
||||
/// because "world" already means the entire ECS (which can contain multiple
|
||||
/// 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)
|
||||
/// This is sometimes interchangably called a "world". However, this type is
|
||||
/// called `Instance` to avoid colliding with the `World` type from Bevy ECS.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Instance {
|
||||
pub chunks: ChunkStorage,
|
||||
|
|
|
@ -13,19 +13,19 @@ pre-release-replacements = [
|
|||
|
||||
[dependencies]
|
||||
#async-trait.workspace = true
|
||||
azalea-auth = { version = "0.12.0", path = "../azalea-auth" }
|
||||
azalea-block = { version = "0.12.0", path = "../azalea-block" }
|
||||
azalea-brigadier = { version = "0.12.0", path = "../azalea-brigadier" }
|
||||
azalea-buf = { version = "0.12.0", path = "../azalea-buf" }
|
||||
azalea-chat = { version = "0.12.0", path = "../azalea-chat" }
|
||||
azalea-client = { version = "0.12.0", path = "../azalea-client", default-features = false }
|
||||
azalea-core = { version = "0.12.0", path = "../azalea-core" }
|
||||
azalea-entity = { version = "0.12.0", path = "../azalea-entity" }
|
||||
azalea-inventory = { version = "0.12.0", path = "../azalea-inventory" }
|
||||
azalea-physics = { version = "0.12.0", path = "../azalea-physics" }
|
||||
azalea-protocol = { version = "0.12.0", path = "../azalea-protocol" }
|
||||
azalea-registry = { version = "0.12.0", path = "../azalea-registry" }
|
||||
azalea-world = { version = "0.12.0", path = "../azalea-world" }
|
||||
azalea-auth.workspace = true
|
||||
azalea-block.workspace = true
|
||||
azalea-brigadier.workspace = true
|
||||
azalea-buf.workspace = true
|
||||
azalea-chat.workspace = true
|
||||
azalea-client.workspace = true
|
||||
azalea-core.workspace = true
|
||||
azalea-entity.workspace = true
|
||||
azalea-inventory.workspace = true
|
||||
azalea-physics.workspace = true
|
||||
azalea-protocol.workspace = true
|
||||
azalea-registry.workspace = true
|
||||
azalea-world.workspace = true
|
||||
bevy_app.workspace = true
|
||||
bevy_ecs.workspace = true
|
||||
bevy_log.workspace = true
|
||||
|
|
|
@ -7,11 +7,13 @@ use azalea::{
|
|||
brigadier::prelude::*,
|
||||
chunks::ReceiveChunkEvent,
|
||||
entity::{LookDirection, Position},
|
||||
interact::HitResultComponent,
|
||||
interact::pick::HitResultComponent,
|
||||
packet::game,
|
||||
pathfinder::{ExecutingPath, Pathfinder},
|
||||
world::MinecraftEntityId,
|
||||
};
|
||||
use azalea_core::hit_result::HitResult;
|
||||
use azalea_entity::EntityKindComponent;
|
||||
use azalea_world::InstanceContainer;
|
||||
use bevy_ecs::event::Events;
|
||||
use parking_lot::Mutex;
|
||||
|
@ -104,15 +106,24 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
|||
|
||||
let hit_result = source.bot.component::<HitResultComponent>();
|
||||
|
||||
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);
|
||||
|
||||
source.reply(&format!("I'm looking at {block:?} at {block_pos:?}"));
|
||||
match &*hit_result {
|
||||
HitResult::Block(r) => {
|
||||
if r.miss {
|
||||
source.reply("I'm not looking at anything");
|
||||
return 0;
|
||||
}
|
||||
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:?}"));
|
||||
}
|
||||
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
|
||||
}));
|
||||
|
|
|
@ -21,12 +21,12 @@ pub fn tick(bot: Client, state: State) -> anyhow::Result<()> {
|
|||
{
|
||||
let mut ecs = bot.ecs.lock();
|
||||
let mut query = ecs
|
||||
.query_filtered::<(&MinecraftEntityId, &Position, &InstanceName), (
|
||||
.query_filtered::<(Entity, &Position, &InstanceName), (
|
||||
With<AbstractMonster>,
|
||||
Without<LocalEntity>,
|
||||
Without<Dead>,
|
||||
)>();
|
||||
for (&entity_id, position, instance_name) in query.iter(&ecs) {
|
||||
for (entity_id, position, instance_name) in query.iter(&ecs) {
|
||||
if instance_name != &bot_instance_name {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ impl Goal for ReachBlockPosGoal {
|
|||
|
||||
let eye_position = n.center_bottom().up(1.62);
|
||||
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,
|
||||
eye_position,
|
||||
&self.chunk_storage,
|
||||
|
|
|
@ -10,7 +10,7 @@ use azalea_core::{
|
|||
game_type::GameMode, position::Vec3, resource_location::ResourceLocation, tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{
|
||||
Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance,
|
||||
Attributes, EntityDimensions, LookDirection, Physics, Position, default_attributes,
|
||||
};
|
||||
use azalea_registry::EntityKind;
|
||||
use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
|
||||
|
@ -38,11 +38,7 @@ impl SimulatedPlayerBundle {
|
|||
physics: Physics::new(dimensions, position),
|
||||
physics_state: PhysicsState::default(),
|
||||
look_direction: LookDirection::default(),
|
||||
attributes: Attributes {
|
||||
speed: AttributeInstance::new(0.1),
|
||||
attack_speed: AttributeInstance::new(4.0),
|
||||
water_movement_efficiency: AttributeInstance::new(0.0),
|
||||
},
|
||||
attributes: default_attributes(EntityKind::Player),
|
||||
inventory: Inventory::default(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue