azalea_client/plugins/
attack.rs1use azalea_core::{game_type::GameMode, tick::GameTick};
2use azalea_entity::{
3 Attributes, Physics, indexing::EntityIdIndex, metadata::Sprinting, update_bounding_box,
4};
5use azalea_physics::PhysicsSystems;
6use azalea_protocol::packets::game::ServerboundAttack;
7use bevy_app::{App, Plugin, Update};
8use bevy_ecs::prelude::*;
9use derive_more::{Deref, DerefMut};
10use tracing::warn;
11
12use super::packet::game::SendGamePacketEvent;
13use crate::{interact::SwingArmEvent, movement::MoveEventsSystems, respawn::perform_respawn};
14
15pub struct AttackPlugin;
16impl Plugin for AttackPlugin {
17 fn build(&self, app: &mut App) {
18 app.add_message::<AttackEvent>()
19 .add_systems(
20 Update,
21 handle_attack_event
22 .before(update_bounding_box)
23 .before(MoveEventsSystems)
24 .after(perform_respawn),
25 )
26 .add_systems(
27 GameTick,
28 (
29 increment_ticks_since_last_attack,
30 update_attack_strength_scale.after(PhysicsSystems),
31 handle_attack_queued
33 .before(super::movement::send_sprinting_if_needed)
34 .before(super::tick_end::game_tick_packet)
35 .before(super::movement::send_position),
36 )
37 .chain(),
38 );
39 }
40}
41
42#[derive(Clone, Component, Debug)]
45pub struct AttackQueued {
46 pub target: Entity,
47}
48#[allow(clippy::type_complexity)]
49pub fn handle_attack_queued(
50 mut commands: Commands,
51 mut query: Query<(
52 Entity,
53 &mut TicksSinceLastAttack,
54 &mut Physics,
55 &mut Sprinting,
56 &AttackQueued,
57 &GameMode,
58 &EntityIdIndex,
59 )>,
60) {
61 for (
62 client_entity,
63 mut ticks_since_last_attack,
64 mut physics,
65 mut sprinting,
66 attack_queued,
67 &game_mode,
68 entity_id_index,
69 ) in &mut query
70 {
71 let target_entity = attack_queued.target;
72 let Some(target_entity_id) = entity_id_index.get_by_ecs_entity(target_entity) else {
73 warn!("tried to attack entity {target_entity} which isn't in our EntityIdIndex");
74 continue;
75 };
76
77 commands.entity(client_entity).remove::<AttackQueued>();
78
79 commands.trigger(SendGamePacketEvent::new(
80 client_entity,
81 ServerboundAttack {
82 entity_id: target_entity_id,
83 },
84 ));
85 commands.trigger(SwingArmEvent {
86 entity: client_entity,
87 });
88
89 if game_mode == GameMode::Spectator {
92 continue;
93 };
94
95 ticks_since_last_attack.0 = 0;
96
97 physics.velocity = physics.velocity.multiply(0.6, 1.0, 0.6);
98 **sprinting = false;
99 }
100}
101
102#[derive(Message)]
105pub struct AttackEvent {
106 pub entity: Entity,
108 pub target: Entity,
110}
111pub fn handle_attack_event(mut events: MessageReader<AttackEvent>, mut commands: Commands) {
112 for event in events.read() {
113 commands.entity(event.entity).insert(AttackQueued {
114 target: event.target,
115 });
116 }
117}
118
119#[derive(Bundle, Default)]
120pub struct AttackBundle {
121 pub ticks_since_last_attack: TicksSinceLastAttack,
122 pub attack_strength_scale: AttackStrengthScale,
123}
124
125#[derive(Clone, Component, Default, Deref, DerefMut)]
126pub struct TicksSinceLastAttack(pub u32);
127pub fn increment_ticks_since_last_attack(mut query: Query<&mut TicksSinceLastAttack>) {
128 for mut ticks_since_last_attack in query.iter_mut() {
129 **ticks_since_last_attack += 1;
130 }
131}
132
133#[derive(Clone, Component, Default, Deref, DerefMut)]
134pub struct AttackStrengthScale(pub f32);
135pub fn update_attack_strength_scale(
136 mut query: Query<(&TicksSinceLastAttack, &Attributes, &mut AttackStrengthScale)>,
137) {
138 for (ticks_since_last_attack, attributes, mut attack_strength_scale) in query.iter_mut() {
139 **attack_strength_scale =
141 get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
142 }
143}
144
145pub fn get_attack_strength_delay(attributes: &Attributes) -> f32 {
147 ((1. / attributes.attack_speed.calculate()) * 20.) as f32
148}
149
150pub fn get_attack_strength_scale(
151 ticks_since_last_attack: u32,
152 attributes: &Attributes,
153 in_ticks: f32,
154) -> f32 {
155 let attack_strength_delay = get_attack_strength_delay(attributes);
156 let attack_strength = (ticks_since_last_attack as f32 + in_ticks) / attack_strength_delay;
157 attack_strength.clamp(0., 1.)
158}