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 clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position,
12};
13use azalea_inventory::{components, ItemStack, ItemStackData};
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::{
23 component::Component,
24 entity::Entity,
25 event::{Event, EventReader, EventWriter},
26 query::{Changed, With},
27 schedule::IntoSystemConfigs,
28 system::{Commands, Query, Res},
29};
30use derive_more::{Deref, DerefMut};
31use tracing::warn;
32
33use crate::{
34 attack::handle_attack_event,
35 inventory::{Inventory, InventorySet},
36 local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
37 movement::MoveEventsSet,
38 packet_handling::game::{handle_send_packet_event, SendPacketEvent},
39 respawn::perform_respawn,
40 Client,
41};
42
43pub struct InteractPlugin;
45impl Plugin for InteractPlugin {
46 fn build(&self, app: &mut App) {
47 app.add_event::<BlockInteractEvent>()
48 .add_event::<SwingArmEvent>()
49 .add_systems(
50 Update,
51 (
52 (
53 update_hit_result_component.after(clamp_look_direction),
54 handle_block_interact_event,
55 handle_swing_arm_event,
56 )
57 .before(handle_send_packet_event)
58 .after(InventorySet)
59 .after(perform_respawn)
60 .after(handle_attack_event)
61 .chain(),
62 update_modifiers_for_held_item
63 .after(InventorySet)
64 .after(MoveEventsSet),
65 ),
66 );
67 }
68}
69
70impl Client {
71 pub fn block_interact(&mut self, position: BlockPos) {
78 self.ecs.lock().send_event(BlockInteractEvent {
79 entity: self.entity,
80 position,
81 });
82 }
83}
84
85#[derive(Event)]
89pub struct BlockInteractEvent {
90 pub entity: Entity,
92 pub position: BlockPos,
94}
95
96#[derive(Component, Copy, Clone, Debug, Default, Deref)]
99pub struct CurrentSequenceNumber(u32);
100
101impl AddAssign<u32> for CurrentSequenceNumber {
102 fn add_assign(&mut self, rhs: u32) {
103 self.0 += rhs;
104 }
105}
106
107#[derive(Component, Clone, Debug, Deref, DerefMut)]
109pub struct HitResultComponent(BlockHitResult);
110
111pub fn handle_block_interact_event(
112 mut events: EventReader<BlockInteractEvent>,
113 mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
114 mut send_packet_events: EventWriter<SendPacketEvent>,
115) {
116 for event in events.read() {
117 let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
118 warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
119 continue;
120 };
121
122 *sequence_number += 1;
125
126 let block_hit = if hit_result.block_pos == event.position {
133 BlockHit {
135 block_pos: hit_result.block_pos,
136 direction: hit_result.direction,
137 location: hit_result.location,
138 inside: hit_result.inside,
139 world_border: hit_result.world_border,
140 }
141 } else {
142 BlockHit {
144 block_pos: event.position,
145 direction: Direction::Up,
146 location: event.position.center(),
147 inside: false,
148 world_border: false,
149 }
150 };
151
152 send_packet_events.send(SendPacketEvent::new(
153 entity,
154 ServerboundUseItemOn {
155 hand: InteractionHand::MainHand,
156 block_hit,
157 sequence: sequence_number.0,
158 },
159 ));
160 }
161}
162
163#[allow(clippy::type_complexity)]
164pub fn update_hit_result_component(
165 mut commands: Commands,
166 mut query: Query<(
167 Entity,
168 Option<&mut HitResultComponent>,
169 &LocalGameMode,
170 &Position,
171 &EyeHeight,
172 &LookDirection,
173 &InstanceName,
174 )>,
175 instance_container: Res<InstanceContainer>,
176) {
177 for (entity, hit_result_ref, game_mode, position, eye_height, look_direction, world_name) in
178 &mut query
179 {
180 let pick_range = if game_mode.current == GameMode::Creative {
181 6.
182 } else {
183 4.5
184 };
185 let eye_position = Vec3 {
186 x: position.x,
187 y: position.y + **eye_height as f64,
188 z: position.z,
189 };
190
191 let Some(instance_lock) = instance_container.get(world_name) else {
192 continue;
193 };
194 let instance = instance_lock.read();
195
196 let hit_result = pick(look_direction, &eye_position, &instance.chunks, pick_range);
197 if let Some(mut hit_result_ref) = hit_result_ref {
198 **hit_result_ref = hit_result;
199 } else {
200 commands
201 .entity(entity)
202 .insert(HitResultComponent(hit_result));
203 }
204 }
205}
206
207pub fn pick(
213 look_direction: &LookDirection,
214 eye_position: &Vec3,
215 chunks: &azalea_world::ChunkStorage,
216 pick_range: f64,
217) -> BlockHitResult {
218 let view_vector = view_vector(look_direction);
219 let end_position = eye_position + &(view_vector * pick_range);
220 azalea_physics::clip::clip(
221 chunks,
222 ClipContext {
223 from: *eye_position,
224 to: end_position,
225 block_shape_type: BlockShapeType::Outline,
226 fluid_pick_type: FluidPickType::None,
227 },
228 )
229}
230
231pub fn check_is_interaction_restricted(
237 instance: &Instance,
238 block_pos: &BlockPos,
239 game_mode: &GameMode,
240 inventory: &Inventory,
241) -> bool {
242 match game_mode {
243 GameMode::Adventure => {
244 let held_item = inventory.held_item();
248 if let ItemStack::Present(item) = &held_item {
249 let block = instance.chunks.get_block_state(block_pos);
250 let Some(block) = block else {
251 return true;
253 };
254 check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
255 } else {
256 true
257 }
258 }
259 GameMode::Spectator => true,
260 _ => false,
261 }
262}
263
264pub fn check_block_can_be_broken_by_item_in_adventure_mode(
266 item: &ItemStackData,
267 _block: &BlockState,
268) -> bool {
269 if !item.components.has::<components::CanBreak>() {
273 return false;
275 };
276
277 false
278
279 }
286
287pub fn can_use_game_master_blocks(
288 abilities: &PlayerAbilities,
289 permission_level: &PermissionLevel,
290) -> bool {
291 abilities.instant_break && **permission_level >= 2
292}
293
294#[derive(Event)]
297pub struct SwingArmEvent {
298 pub entity: Entity,
299}
300pub fn handle_swing_arm_event(
301 mut events: EventReader<SwingArmEvent>,
302 mut send_packet_events: EventWriter<SendPacketEvent>,
303) {
304 for event in events.read() {
305 send_packet_events.send(SendPacketEvent::new(
306 event.entity,
307 ServerboundSwing {
308 hand: InteractionHand::MainHand,
309 },
310 ));
311 }
312}
313
314#[allow(clippy::type_complexity)]
315fn update_modifiers_for_held_item(
316 mut query: Query<(&mut Attributes, &Inventory), (With<LocalEntity>, Changed<Inventory>)>,
317) {
318 for (mut attributes, inventory) in &mut query {
319 let held_item = inventory.held_item();
320
321 use azalea_registry::Item;
322 let added_attack_speed = match held_item.kind() {
323 Item::WoodenSword => -2.4,
324 Item::WoodenShovel => -3.0,
325 Item::WoodenPickaxe => -2.8,
326 Item::WoodenAxe => -3.2,
327 Item::WoodenHoe => -3.0,
328
329 Item::StoneSword => -2.4,
330 Item::StoneShovel => -3.0,
331 Item::StonePickaxe => -2.8,
332 Item::StoneAxe => -3.2,
333 Item::StoneHoe => -2.0,
334
335 Item::GoldenSword => -2.4,
336 Item::GoldenShovel => -3.0,
337 Item::GoldenPickaxe => -2.8,
338 Item::GoldenAxe => -3.0,
339 Item::GoldenHoe => -3.0,
340
341 Item::IronSword => -2.4,
342 Item::IronShovel => -3.0,
343 Item::IronPickaxe => -2.8,
344 Item::IronAxe => -3.1,
345 Item::IronHoe => -1.0,
346
347 Item::DiamondSword => -2.4,
348 Item::DiamondShovel => -3.0,
349 Item::DiamondPickaxe => -2.8,
350 Item::DiamondAxe => -3.0,
351 Item::DiamondHoe => 0.0,
352
353 Item::NetheriteSword => -2.4,
354 Item::NetheriteShovel => -3.0,
355 Item::NetheritePickaxe => -2.8,
356 Item::NetheriteAxe => -3.0,
357 Item::NetheriteHoe => 0.0,
358
359 Item::Trident => -2.9,
360 _ => 0.,
361 };
362 attributes
363 .attack_speed
364 .insert(azalea_entity::attributes::base_attack_speed_modifier(
365 added_attack_speed,
366 ));
367 }
368}