azalea_client/
attack.rs

1use azalea_core::{game_type::GameMode, tick::GameTick};
2use azalea_entity::{
3    metadata::{ShiftKeyDown, Sprinting},
4    update_bounding_box, Attributes, Physics,
5};
6use azalea_physics::PhysicsSet;
7use azalea_protocol::packets::game::s_interact::{self, ServerboundInteract};
8use azalea_world::MinecraftEntityId;
9use bevy_app::{App, Plugin, Update};
10use bevy_ecs::prelude::*;
11use derive_more::{Deref, DerefMut};
12
13use crate::{
14    interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
15    packet_handling::game::SendPacketEvent, respawn::perform_respawn, Client,
16};
17
18pub struct AttackPlugin;
19impl Plugin for AttackPlugin {
20    fn build(&self, app: &mut App) {
21        app.add_event::<AttackEvent>()
22            .add_systems(
23                Update,
24                handle_attack_event
25                    .before(update_bounding_box)
26                    .before(MoveEventsSet)
27                    .after(perform_respawn),
28            )
29            .add_systems(
30                GameTick,
31                (
32                    increment_ticks_since_last_attack,
33                    update_attack_strength_scale.after(PhysicsSet),
34                )
35                    .chain(),
36            );
37    }
38}
39
40impl Client {
41    /// Attack the entity with the given id.
42    pub fn attack(&mut self, entity_id: MinecraftEntityId) {
43        self.ecs.lock().send_event(AttackEvent {
44            entity: self.entity,
45            target: entity_id,
46        });
47    }
48
49    /// Whether the player has an attack cooldown.
50    pub fn has_attack_cooldown(&self) -> bool {
51        let Some(AttackStrengthScale(ticks_since_last_attack)) =
52            self.get_component::<AttackStrengthScale>()
53        else {
54            // they don't even have an AttackStrengthScale so they probably can't attack
55            // lmao, just return false
56            return false;
57        };
58        ticks_since_last_attack < 1.0
59    }
60}
61
62#[derive(Event)]
63pub struct AttackEvent {
64    pub entity: Entity,
65    pub target: MinecraftEntityId,
66}
67pub fn handle_attack_event(
68    mut events: EventReader<AttackEvent>,
69    mut query: Query<(
70        &LocalGameMode,
71        &mut TicksSinceLastAttack,
72        &mut Physics,
73        &mut Sprinting,
74        &mut ShiftKeyDown,
75    )>,
76    mut send_packet_events: EventWriter<SendPacketEvent>,
77    mut swing_arm_event: EventWriter<SwingArmEvent>,
78) {
79    for event in events.read() {
80        let (game_mode, mut ticks_since_last_attack, mut physics, mut sprinting, sneaking) =
81            query.get_mut(event.entity).unwrap();
82
83        swing_arm_event.send(SwingArmEvent {
84            entity: event.entity,
85        });
86        send_packet_events.send(SendPacketEvent::new(
87            event.entity,
88            ServerboundInteract {
89                entity_id: event.target,
90                action: s_interact::ActionType::Attack,
91                using_secondary_action: **sneaking,
92            },
93        ));
94
95        // we can't attack if we're in spectator mode but it still sends the attack
96        // packet
97        if game_mode.current == GameMode::Spectator {
98            continue;
99        };
100
101        ticks_since_last_attack.0 = 0;
102
103        physics.velocity = physics.velocity.multiply(0.6, 1.0, 0.6);
104        **sprinting = false;
105    }
106}
107
108#[derive(Default, Bundle)]
109pub struct AttackBundle {
110    pub ticks_since_last_attack: TicksSinceLastAttack,
111    pub attack_strength_scale: AttackStrengthScale,
112}
113
114#[derive(Default, Component, Clone, Deref, DerefMut)]
115pub struct TicksSinceLastAttack(pub u32);
116pub fn increment_ticks_since_last_attack(mut query: Query<&mut TicksSinceLastAttack>) {
117    for mut ticks_since_last_attack in query.iter_mut() {
118        **ticks_since_last_attack += 1;
119    }
120}
121
122#[derive(Default, Component, Clone, Deref, DerefMut)]
123pub struct AttackStrengthScale(pub f32);
124pub fn update_attack_strength_scale(
125    mut query: Query<(&TicksSinceLastAttack, &Attributes, &mut AttackStrengthScale)>,
126) {
127    for (ticks_since_last_attack, attributes, mut attack_strength_scale) in query.iter_mut() {
128        // look 0.5 ticks into the future because that's what vanilla does
129        **attack_strength_scale =
130            get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
131    }
132}
133
134/// Returns how long it takes for the attack cooldown to reset (in ticks).
135pub fn get_attack_strength_delay(attributes: &Attributes) -> f32 {
136    ((1. / attributes.attack_speed.calculate()) * 20.) as f32
137}
138
139pub fn get_attack_strength_scale(
140    ticks_since_last_attack: u32,
141    attributes: &Attributes,
142    in_ticks: f32,
143) -> f32 {
144    let attack_strength_delay = get_attack_strength_delay(attributes);
145    let attack_strength = (ticks_since_last_attack as f32 + in_ticks) / attack_strength_delay;
146    attack_strength.clamp(0., 1.)
147}