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