aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/plugins/attack.rs
diff options
context:
space:
mode:
Diffstat (limited to 'azalea-client/src/plugins/attack.rs')
-rw-r--r--azalea-client/src/plugins/attack.rs149
1 files changed, 149 insertions, 0 deletions
diff --git a/azalea-client/src/plugins/attack.rs b/azalea-client/src/plugins/attack.rs
new file mode 100644
index 00000000..1b2bc1ee
--- /dev/null
+++ b/azalea-client/src/plugins/attack.rs
@@ -0,0 +1,149 @@
+use azalea_core::{game_type::GameMode, tick::GameTick};
+use azalea_entity::{
+ Attributes, Physics,
+ metadata::{ShiftKeyDown, Sprinting},
+ update_bounding_box,
+};
+use azalea_physics::PhysicsSet;
+use azalea_protocol::packets::game::s_interact::{self, ServerboundInteract};
+use azalea_world::MinecraftEntityId;
+use bevy_app::{App, Plugin, Update};
+use bevy_ecs::prelude::*;
+use derive_more::{Deref, DerefMut};
+
+use super::packet::game::SendPacketEvent;
+use crate::{
+ Client, interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
+ respawn::perform_respawn,
+};
+
+pub struct AttackPlugin;
+impl Plugin for AttackPlugin {
+ fn build(&self, app: &mut App) {
+ app.add_event::<AttackEvent>()
+ .add_systems(
+ Update,
+ handle_attack_event
+ .before(update_bounding_box)
+ .before(MoveEventsSet)
+ .after(perform_respawn),
+ )
+ .add_systems(
+ GameTick,
+ (
+ increment_ticks_since_last_attack,
+ update_attack_strength_scale.after(PhysicsSet),
+ )
+ .chain(),
+ );
+ }
+}
+
+impl Client {
+ /// Attack the entity with the given id.
+ pub fn attack(&mut self, entity_id: MinecraftEntityId) {
+ self.ecs.lock().send_event(AttackEvent {
+ entity: self.entity,
+ target: entity_id,
+ });
+ }
+
+ /// Whether the player has an attack cooldown.
+ pub fn has_attack_cooldown(&self) -> bool {
+ let Some(AttackStrengthScale(ticks_since_last_attack)) =
+ self.get_component::<AttackStrengthScale>()
+ else {
+ // they don't even have an AttackStrengthScale so they probably can't attack
+ // lmao, just return false
+ return false;
+ };
+ ticks_since_last_attack < 1.0
+ }
+}
+
+#[derive(Event)]
+pub struct AttackEvent {
+ pub entity: Entity,
+ pub target: MinecraftEntityId,
+}
+pub fn handle_attack_event(
+ mut events: EventReader<AttackEvent>,
+ mut query: Query<(
+ &LocalGameMode,
+ &mut TicksSinceLastAttack,
+ &mut Physics,
+ &mut Sprinting,
+ &mut ShiftKeyDown,
+ )>,
+ mut send_packet_events: EventWriter<SendPacketEvent>,
+ mut swing_arm_event: EventWriter<SwingArmEvent>,
+) {
+ for event in events.read() {
+ let (game_mode, mut ticks_since_last_attack, mut physics, mut sprinting, sneaking) =
+ query.get_mut(event.entity).unwrap();
+
+ swing_arm_event.send(SwingArmEvent {
+ entity: event.entity,
+ });
+ send_packet_events.send(SendPacketEvent::new(
+ event.entity,
+ ServerboundInteract {
+ entity_id: event.target,
+ action: s_interact::ActionType::Attack,
+ using_secondary_action: **sneaking,
+ },
+ ));
+
+ // we can't attack if we're in spectator mode but it still sends the attack
+ // packet
+ if game_mode.current == GameMode::Spectator {
+ continue;
+ };
+
+ ticks_since_last_attack.0 = 0;
+
+ physics.velocity = physics.velocity.multiply(0.6, 1.0, 0.6);
+ **sprinting = false;
+ }
+}
+
+#[derive(Default, Bundle)]
+pub struct AttackBundle {
+ pub ticks_since_last_attack: TicksSinceLastAttack,
+ pub attack_strength_scale: AttackStrengthScale,
+}
+
+#[derive(Default, Component, Clone, Deref, DerefMut)]
+pub struct TicksSinceLastAttack(pub u32);
+pub fn increment_ticks_since_last_attack(mut query: Query<&mut TicksSinceLastAttack>) {
+ for mut ticks_since_last_attack in query.iter_mut() {
+ **ticks_since_last_attack += 1;
+ }
+}
+
+#[derive(Default, Component, Clone, Deref, DerefMut)]
+pub struct AttackStrengthScale(pub f32);
+pub fn update_attack_strength_scale(
+ mut query: Query<(&TicksSinceLastAttack, &Attributes, &mut AttackStrengthScale)>,
+) {
+ for (ticks_since_last_attack, attributes, mut attack_strength_scale) in query.iter_mut() {
+ // look 0.5 ticks into the future because that's what vanilla does
+ **attack_strength_scale =
+ get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
+ }
+}
+
+/// Returns how long it takes for the attack cooldown to reset (in ticks).
+pub fn get_attack_strength_delay(attributes: &Attributes) -> f32 {
+ ((1. / attributes.attack_speed.calculate()) * 20.) as f32
+}
+
+pub fn get_attack_strength_scale(
+ ticks_since_last_attack: u32,
+ attributes: &Attributes,
+ in_ticks: f32,
+) -> f32 {
+ let attack_strength_delay = get_attack_strength_delay(attributes);
+ let attack_strength = (ticks_since_last_attack as f32 + in_ticks) / attack_strength_delay;
+ attack_strength.clamp(0., 1.)
+}