1use std::ops::AddAssign;
2
3use azalea_block::BlockState;
4use azalea_core::{
5 block_hit_result::BlockHitResult,
6 direction::Direction,
7 game_type::GameMode,
8 position::{BlockPos, Vec3},
9};
10use azalea_entity::{
11 Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
12};
13use azalea_inventory::{ItemStack, ItemStackData, components};
14use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
15use azalea_protocol::packets::game::{
16 s_interact::InteractionHand,
17 s_swing::ServerboundSwing,
18 s_use_item_on::{BlockHit, ServerboundUseItemOn},
19};
20use azalea_world::{Instance, InstanceContainer, InstanceName};
21use bevy_app::{App, Plugin, Update};
22use bevy_ecs::prelude::*;
23use derive_more::{Deref, DerefMut};
24use tracing::warn;
25
26use crate::{
27 Client,
28 attack::handle_attack_event,
29 inventory::{Inventory, InventorySet},
30 local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
31 movement::MoveEventsSet,
32 packet::game::SendPacketEvent,
33 respawn::perform_respawn,
34};
35
36pub struct InteractPlugin;
38impl Plugin for InteractPlugin {
39 fn build(&self, app: &mut App) {
40 app.add_event::<BlockInteractEvent>()
41 .add_event::<SwingArmEvent>()
42 .add_systems(
43 Update,
44 (
45 (
46 update_hit_result_component.after(clamp_look_direction),
47 handle_block_interact_event,
48 handle_swing_arm_event,
49 )
50 .after(InventorySet)
51 .after(perform_respawn)
52 .after(handle_attack_event)
53 .chain(),
54 update_modifiers_for_held_item
55 .after(InventorySet)
56 .after(MoveEventsSet),
57 ),
58 )
59 .add_observer(handle_swing_arm_trigger);
60 }
61}
62
63impl Client {
64 pub fn block_interact(&self, position: BlockPos) {
71 self.ecs.lock().send_event(BlockInteractEvent {
72 entity: self.entity,
73 position,
74 });
75 }
76}
77
78#[derive(Event)]
82pub struct BlockInteractEvent {
83 pub entity: Entity,
85 pub position: BlockPos,
87}
88
89#[derive(Component, Copy, Clone, Debug, Default, Deref)]
92pub struct CurrentSequenceNumber(u32);
93
94impl AddAssign<u32> for CurrentSequenceNumber {
95 fn add_assign(&mut self, rhs: u32) {
96 self.0 += rhs;
97 }
98}
99
100#[derive(Component, Clone, Debug, Deref, DerefMut)]
102pub struct HitResultComponent(BlockHitResult);
103
104pub fn handle_block_interact_event(
105 mut events: EventReader<BlockInteractEvent>,
106 mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
107 mut commands: Commands,
108) {
109 for event in events.read() {
110 let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
111 warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
112 continue;
113 };
114
115 *sequence_number += 1;
118
119 let block_hit = if hit_result.block_pos == event.position {
126 BlockHit {
128 block_pos: hit_result.block_pos,
129 direction: hit_result.direction,
130 location: hit_result.location,
131 inside: hit_result.inside,
132 world_border: hit_result.world_border,
133 }
134 } else {
135 BlockHit {
137 block_pos: event.position,
138 direction: Direction::Up,
139 location: event.position.center(),
140 inside: false,
141 world_border: false,
142 }
143 };
144
145 commands.trigger(SendPacketEvent::new(
146 entity,
147 ServerboundUseItemOn {
148 hand: InteractionHand::MainHand,
149 block_hit,
150 sequence: sequence_number.0,
151 },
152 ));
153 }
154}
155
156#[allow(clippy::type_complexity)]
157pub fn update_hit_result_component(
158 mut commands: Commands,
159 mut query: Query<(
160 Entity,
161 Option<&mut HitResultComponent>,
162 &LocalGameMode,
163 &Position,
164 &EyeHeight,
165 &LookDirection,
166 &InstanceName,
167 )>,
168 instance_container: Res<InstanceContainer>,
169) {
170 for (entity, hit_result_ref, game_mode, position, eye_height, look_direction, world_name) in
171 &mut query
172 {
173 let pick_range = if game_mode.current == GameMode::Creative {
174 6.
175 } else {
176 4.5
177 };
178 let eye_position = Vec3 {
179 x: position.x,
180 y: position.y + **eye_height as f64,
181 z: position.z,
182 };
183
184 let Some(instance_lock) = instance_container.get(world_name) else {
185 continue;
186 };
187 let instance = instance_lock.read();
188
189 let hit_result = pick(look_direction, &eye_position, &instance.chunks, pick_range);
190 if let Some(mut hit_result_ref) = hit_result_ref {
191 **hit_result_ref = hit_result;
192 } else {
193 commands
194 .entity(entity)
195 .insert(HitResultComponent(hit_result));
196 }
197 }
198}
199
200pub fn pick(
206 look_direction: &LookDirection,
207 eye_position: &Vec3,
208 chunks: &azalea_world::ChunkStorage,
209 pick_range: f64,
210) -> BlockHitResult {
211 let view_vector = view_vector(look_direction);
212 let end_position = eye_position + &(view_vector * pick_range);
213 azalea_physics::clip::clip(
214 chunks,
215 ClipContext {
216 from: *eye_position,
217 to: end_position,
218 block_shape_type: BlockShapeType::Outline,
219 fluid_pick_type: FluidPickType::None,
220 },
221 )
222}
223
224pub fn check_is_interaction_restricted(
230 instance: &Instance,
231 block_pos: &BlockPos,
232 game_mode: &GameMode,
233 inventory: &Inventory,
234) -> bool {
235 match game_mode {
236 GameMode::Adventure => {
237 let held_item = inventory.held_item();
241 match &held_item {
242 ItemStack::Present(item) => {
243 let block = instance.chunks.get_block_state(block_pos);
244 let Some(block) = block else {
245 return true;
247 };
248 check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
249 }
250 _ => true,
251 }
252 }
253 GameMode::Spectator => true,
254 _ => false,
255 }
256}
257
258pub fn check_block_can_be_broken_by_item_in_adventure_mode(
260 item: &ItemStackData,
261 _block: &BlockState,
262) -> bool {
263 if !item.components.has::<components::CanBreak>() {
267 return false;
269 };
270
271 false
272
273 }
280
281pub fn can_use_game_master_blocks(
282 abilities: &PlayerAbilities,
283 permission_level: &PermissionLevel,
284) -> bool {
285 abilities.instant_break && **permission_level >= 2
286}
287
288#[derive(Event, Clone, Debug)]
291pub struct SwingArmEvent {
292 pub entity: Entity,
293}
294pub fn handle_swing_arm_trigger(trigger: Trigger<SwingArmEvent>, mut commands: Commands) {
295 commands.trigger(SendPacketEvent::new(
296 trigger.event().entity,
297 ServerboundSwing {
298 hand: InteractionHand::MainHand,
299 },
300 ));
301}
302pub fn handle_swing_arm_event(mut events: EventReader<SwingArmEvent>, mut commands: Commands) {
303 for event in events.read() {
304 commands.trigger(event.clone());
305 }
306}
307
308#[allow(clippy::type_complexity)]
309fn update_modifiers_for_held_item(
310 mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
311) {
312 for (mut attributes, inventory) in &mut query {
313 let held_item = inventory.held_item();
314
315 use azalea_registry::Item;
316 let added_attack_speed = match held_item.kind() {
317 Item::WoodenSword => -2.4,
318 Item::WoodenShovel => -3.0,
319 Item::WoodenPickaxe => -2.8,
320 Item::WoodenAxe => -3.2,
321 Item::WoodenHoe => -3.0,
322
323 Item::StoneSword => -2.4,
324 Item::StoneShovel => -3.0,
325 Item::StonePickaxe => -2.8,
326 Item::StoneAxe => -3.2,
327 Item::StoneHoe => -2.0,
328
329 Item::GoldenSword => -2.4,
330 Item::GoldenShovel => -3.0,
331 Item::GoldenPickaxe => -2.8,
332 Item::GoldenAxe => -3.0,
333 Item::GoldenHoe => -3.0,
334
335 Item::IronSword => -2.4,
336 Item::IronShovel => -3.0,
337 Item::IronPickaxe => -2.8,
338 Item::IronAxe => -3.1,
339 Item::IronHoe => -1.0,
340
341 Item::DiamondSword => -2.4,
342 Item::DiamondShovel => -3.0,
343 Item::DiamondPickaxe => -2.8,
344 Item::DiamondAxe => -3.0,
345 Item::DiamondHoe => 0.0,
346
347 Item::NetheriteSword => -2.4,
348 Item::NetheriteShovel => -3.0,
349 Item::NetheritePickaxe => -2.8,
350 Item::NetheriteAxe => -3.0,
351 Item::NetheriteHoe => 0.0,
352
353 Item::Trident => -2.9,
354 _ => 0.,
355 };
356 attributes
357 .attack_speed
358 .insert(azalea_entity::attributes::base_attack_speed_modifier(
359 added_attack_speed,
360 ));
361 }
362}