azalea_client/plugins/
attack.rs

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