use std::ops::AddAssign;
use azalea_block::BlockState;
use azalea_core::{
block_hit_result::BlockHitResult,
direction::Direction,
game_type::GameMode,
position::{BlockPos, Vec3},
};
use azalea_entity::{
clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position,
};
use azalea_inventory::{ItemSlot, ItemSlotData};
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
use azalea_protocol::packets::game::{
serverbound_interact_packet::InteractionHand,
serverbound_swing_packet::ServerboundSwingPacket,
serverbound_use_item_on_packet::{BlockHit, ServerboundUseItemOnPacket},
};
use azalea_registry::DataComponentKind;
use azalea_world::{Instance, InstanceContainer, InstanceName};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{
component::Component,
entity::Entity,
event::{Event, EventReader, EventWriter},
query::{Changed, With},
schedule::IntoSystemConfigs,
system::{Commands, Query, Res},
};
use derive_more::{Deref, DerefMut};
use tracing::warn;
use crate::{
attack::handle_attack_event,
inventory::{Inventory, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet,
packet_handling::game::{handle_send_packet_event, SendPacketEvent},
respawn::perform_respawn,
Client,
};
pub struct InteractPlugin;
impl Plugin for InteractPlugin {
fn build(&self, app: &mut App) {
app.add_event::<BlockInteractEvent>()
.add_event::<SwingArmEvent>()
.add_systems(
Update,
(
(
update_hit_result_component.after(clamp_look_direction),
handle_block_interact_event,
handle_swing_arm_event,
)
.before(handle_send_packet_event)
.after(InventorySet)
.after(perform_respawn)
.after(handle_attack_event)
.chain(),
update_modifiers_for_held_item
.after(InventorySet)
.after(MoveEventsSet),
),
);
}
}
impl Client {
pub fn block_interact(&mut self, position: BlockPos) {
self.ecs.lock().send_event(BlockInteractEvent {
entity: self.entity,
position,
});
}
}
#[derive(Event)]
pub struct BlockInteractEvent {
pub entity: Entity,
pub position: BlockPos,
}
#[derive(Component, Copy, Clone, Debug, Default, Deref)]
pub struct CurrentSequenceNumber(u32);
impl AddAssign<u32> for CurrentSequenceNumber {
fn add_assign(&mut self, rhs: u32) {
self.0 += rhs;
}
}
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct HitResultComponent(BlockHitResult);
pub fn handle_block_interact_event(
mut events: EventReader<BlockInteractEvent>,
mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
mut send_packet_events: EventWriter<SendPacketEvent>,
) {
for event in events.read() {
let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
continue;
};
*sequence_number += 1;
let block_hit = if hit_result.block_pos == event.position {
BlockHit {
block_pos: hit_result.block_pos,
direction: hit_result.direction,
location: hit_result.location,
inside: hit_result.inside,
}
} else {
BlockHit {
block_pos: event.position,
direction: Direction::Up,
location: event.position.center(),
inside: false,
}
};
send_packet_events.send(SendPacketEvent {
entity,
packet: ServerboundUseItemOnPacket {
hand: InteractionHand::MainHand,
block_hit,
sequence: sequence_number.0,
}
.get(),
});
}
}
#[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));
}
}
}
pub fn pick(
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,
},
)
}
pub fn check_is_interaction_restricted(
instance: &Instance,
block_pos: &BlockPos,
game_mode: &GameMode,
inventory: &Inventory,
) -> bool {
match game_mode {
GameMode::Adventure => {
let held_item = inventory.held_item();
if let ItemSlot::Present(item) = &held_item {
let block = instance.chunks.get_block_state(block_pos);
let Some(block) = block else {
return true;
};
check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
} else {
true
}
}
GameMode::Spectator => true,
_ => false,
}
}
pub fn check_block_can_be_broken_by_item_in_adventure_mode(
item: &ItemSlotData,
_block: &BlockState,
) -> bool {
let Some(_can_destroy) = item.components.get(DataComponentKind::CanBreak) else {
return false;
};
false
}
pub fn can_use_game_master_blocks(
abilities: &PlayerAbilities,
permission_level: &PermissionLevel,
) -> bool {
abilities.instant_break && **permission_level >= 2
}
#[derive(Event)]
pub struct SwingArmEvent {
pub entity: Entity,
}
pub fn handle_swing_arm_event(
mut events: EventReader<SwingArmEvent>,
mut send_packet_events: EventWriter<SendPacketEvent>,
) {
for event in events.read() {
send_packet_events.send(SendPacketEvent {
entity: event.entity,
packet: ServerboundSwingPacket {
hand: InteractionHand::MainHand,
}
.get(),
});
}
}
#[allow(clippy::type_complexity)]
fn update_modifiers_for_held_item(
mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
) {
for (mut attributes, inventory) in &mut query {
let held_item = inventory.held_item();
use azalea_registry::Item;
let added_attack_speed = match held_item.kind() {
Item::WoodenSword => -2.4,
Item::WoodenShovel => -3.0,
Item::WoodenPickaxe => -2.8,
Item::WoodenAxe => -3.2,
Item::WoodenHoe => -3.0,
Item::StoneSword => -2.4,
Item::StoneShovel => -3.0,
Item::StonePickaxe => -2.8,
Item::StoneAxe => -3.2,
Item::StoneHoe => -2.0,
Item::GoldenSword => -2.4,
Item::GoldenShovel => -3.0,
Item::GoldenPickaxe => -2.8,
Item::GoldenAxe => -3.0,
Item::GoldenHoe => -3.0,
Item::IronSword => -2.4,
Item::IronShovel => -3.0,
Item::IronPickaxe => -2.8,
Item::IronAxe => -3.1,
Item::IronHoe => -1.0,
Item::DiamondSword => -2.4,
Item::DiamondShovel => -3.0,
Item::DiamondPickaxe => -2.8,
Item::DiamondAxe => -3.0,
Item::DiamondHoe => 0.0,
Item::NetheriteSword => -2.4,
Item::NetheriteShovel => -3.0,
Item::NetheritePickaxe => -2.8,
Item::NetheriteAxe => -3.0,
Item::NetheriteHoe => 0.0,
Item::Trident => -2.9,
_ => 0.,
};
attributes
.attack_speed
.insert(azalea_entity::attributes::base_attack_speed_modifier(
added_attack_speed,
));
}
}