use azalea_core::{game_type::GameMode, tick::GameTick};
use azalea_entity::{
Attributes, Physics, indexing::EntityIdIndex, metadata::Sprinting, update_bounding_box,
};
use azalea_physics::PhysicsSystems;
use azalea_protocol::packets::game::ServerboundAttack;
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use tracing::warn;
use super::packet::game::SendGamePacketEvent;
use crate::{
interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSystems,
respawn::perform_respawn,
};
pub struct AttackPlugin;
impl Plugin for AttackPlugin {
fn build(&self, app: &mut App) {
app.add_message::<AttackEvent>()
.add_systems(
Update,
handle_attack_event
.before(update_bounding_box)
.before(MoveEventsSystems)
.after(perform_respawn),
)
.add_systems(
GameTick,
(
increment_ticks_since_last_attack,
update_attack_strength_scale.after(PhysicsSystems),
handle_attack_queued
.before(super::movement::send_sprinting_if_needed)
.before(super::tick_end::game_tick_packet)
.before(super::movement::send_position),
)
.chain(),
);
}
}
#[derive(Clone, Component, Debug)]
pub struct AttackQueued {
pub target: Entity,
}
#[allow(clippy::type_complexity)]
pub fn handle_attack_queued(
mut commands: Commands,
mut query: Query<(
Entity,
&mut TicksSinceLastAttack,
&mut Physics,
&mut Sprinting,
&AttackQueued,
&LocalGameMode,
&EntityIdIndex,
)>,
) {
for (
client_entity,
mut ticks_since_last_attack,
mut physics,
mut sprinting,
attack_queued,
game_mode,
entity_id_index,
) in &mut query
{
let target_entity = attack_queued.target;
let Some(target_entity_id) = entity_id_index.get_by_ecs_entity(target_entity) else {
warn!("tried to attack entity {target_entity} which isn't in our EntityIdIndex");
continue;
};
commands.entity(client_entity).remove::<AttackQueued>();
commands.trigger(SendGamePacketEvent::new(
client_entity,
ServerboundAttack {
entity_id: target_entity_id,
},
));
commands.trigger(SwingArmEvent {
entity: client_entity,
});
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(Message)]
pub struct AttackEvent {
pub entity: Entity,
pub target: Entity,
}
pub fn handle_attack_event(mut events: MessageReader<AttackEvent>, mut commands: Commands) {
for event in events.read() {
commands.entity(event.entity).insert(AttackQueued {
target: event.target,
});
}
}
#[derive(Bundle, Default)]
pub struct AttackBundle {
pub ticks_since_last_attack: TicksSinceLastAttack,
pub attack_strength_scale: AttackStrengthScale,
}
#[derive(Clone, Component, Default, 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(Clone, Component, Default, 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() {
**attack_strength_scale =
get_attack_strength_scale(ticks_since_last_attack.0, attributes, 0.5);
}
}
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.)
}