azalea_client/plugins/
attack.rs1use azalea_core::{game_type::GameMode, tick::GameTick};
2use azalea_entity::{
3 Attributes, Crouching, Physics, indexing::EntityIdIndex, metadata::Sprinting,
4 update_bounding_box,
5};
6use azalea_physics::PhysicsSet;
7use azalea_protocol::packets::game::s_interact::{self, ServerboundInteract};
8use bevy_app::{App, Plugin, Update};
9use bevy_ecs::prelude::*;
10use derive_more::{Deref, DerefMut};
11use tracing::warn;
12
13use super::packet::game::SendPacketEvent;
14use crate::{
15 Client, interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
16 respawn::perform_respawn,
17};
18
19pub struct AttackPlugin;
20impl Plugin for AttackPlugin {
21 fn build(&self, app: &mut App) {
22 app.add_event::<AttackEvent>()
23 .add_systems(
24 Update,
25 handle_attack_event
26 .before(update_bounding_box)
27 .before(MoveEventsSet)
28 .after(perform_respawn),
29 )
30 .add_systems(
31 GameTick,
32 (
33 increment_ticks_since_last_attack,
34 update_attack_strength_scale.after(PhysicsSet),
35 handle_attack_queued
36 .before(super::tick_end::game_tick_packet)
37 .after(super::movement::send_sprinting_if_needed)
38 .before(super::movement::send_position),
39 )
40 .chain(),
41 );
42 }
43}
44
45impl Client {
46 pub fn attack(&self, entity: Entity) {
48 self.ecs.lock().send_event(AttackEvent {
49 entity: self.entity,
50 target: entity,
51 });
52 }
53
54 pub fn has_attack_cooldown(&self) -> bool {
58 let Some(attack_strength_scale) = self.get_component::<AttackStrengthScale>() else {
59 return false;
62 };
63 *attack_strength_scale < 1.0
64 }
65
66 pub fn attack_cooldown_remaining_ticks(&self) -> usize {
70 let mut ecs = self.ecs.lock();
71 let Ok((attributes, ticks_since_last_attack)) = ecs
72 .query::<(&Attributes, &TicksSinceLastAttack)>()
73 .get(&ecs, self.entity)
74 else {
75 return 0;
76 };
77
78 let attack_strength_delay = get_attack_strength_delay(attributes);
79 let remaining_ticks = attack_strength_delay - **ticks_since_last_attack as f32;
80
81 remaining_ticks.max(0.).ceil() as usize
82 }
83}
84
85#[derive(Component, Clone, Debug)]
88pub struct AttackQueued {
89 pub target: Entity,
90}
91#[allow(clippy::type_complexity)]
92pub fn handle_attack_queued(
93 mut commands: Commands,
94 mut query: Query<(
95 Entity,
96 &mut TicksSinceLastAttack,
97 &mut Physics,
98 &mut Sprinting,
99 &AttackQueued,
100 &LocalGameMode,
101 &Crouching,
102 &EntityIdIndex,
103 )>,
104) {
105 for (
106 client_entity,
107 mut ticks_since_last_attack,
108 mut physics,
109 mut sprinting,
110 attack_queued,
111 game_mode,
112 crouching,
113 entity_id_index,
114 ) in &mut query
115 {
116 let target_entity = attack_queued.target;
117 let Some(target_entity_id) = entity_id_index.get_by_ecs_entity(target_entity) else {
118 warn!("tried to attack entity {target_entity} which isn't in our EntityIdIndex");
119 continue;
120 };
121
122 commands.entity(client_entity).remove::<AttackQueued>();
123
124 commands.trigger(SendPacketEvent::new(
125 client_entity,
126 ServerboundInteract {
127 entity_id: target_entity_id,
128 action: s_interact::ActionType::Attack,
129 using_secondary_action: **crouching,
130 },
131 ));
132 commands.trigger(SwingArmEvent {
133 entity: client_entity,
134 });
135
136 if game_mode.current == GameMode::Spectator {
139 continue;
140 };
141
142 ticks_since_last_attack.0 = 0;
143
144 physics.velocity = physics.velocity.multiply(0.6, 1.0, 0.6);
145 **sprinting = false;
146 }
147}
148
149#[derive(Event)]
152pub struct AttackEvent {
153 pub entity: Entity,
155 pub target: Entity,
157}
158pub fn handle_attack_event(mut events: EventReader<AttackEvent>, mut commands: Commands) {
159 for event in events.read() {
160 commands.entity(event.entity).insert(AttackQueued {
161 target: event.target,
162 });
163 }
164}
165
166#[derive(Default, Bundle)]
167pub struct AttackBundle {
168 pub ticks_since_last_attack: TicksSinceLastAttack,
169 pub attack_strength_scale: AttackStrengthScale,
170}
171
172#[derive(Default, Component, Clone, Deref, DerefMut)]
173pub struct TicksSinceLastAttack(pub u32);
174pub fn increment_ticks_since_last_attack(mut query: Query<&mut TicksSinceLastAttack>) {
175 for mut ticks_since_last_attack in query.iter_mut() {
176 **ticks_since_last_attack += 1;
177 }
178}
179
180#[derive(Default, Component, Clone, Deref, DerefMut)]
181pub struct AttackStrengthScale(pub f32);
182pub fn update_attack_strength_scale(
183 mut query: Query<(&TicksSinceLastAttack, &Attributes, &mut AttackStrengthScale)>,
184) {
185 for (ticks_since_last_attack, attributes, mut attack_strength_scale) in query.iter_mut() {
186 **attack_strength_scale =
188 get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
189 }
190}
191
192pub fn get_attack_strength_delay(attributes: &Attributes) -> f32 {
194 ((1. / attributes.attack_speed.calculate()) * 20.) as f32
195}
196
197pub fn get_attack_strength_scale(
198 ticks_since_last_attack: u32,
199 attributes: &Attributes,
200 in_ticks: f32,
201) -> f32 {
202 let attack_strength_delay = get_attack_strength_delay(attributes);
203 let attack_strength = (ticks_since_last_attack as f32 + in_ticks) / attack_strength_delay;
204 attack_strength.clamp(0., 1.)
205}