aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src
diff options
context:
space:
mode:
Diffstat (limited to 'azalea-client/src')
-rw-r--r--azalea-client/src/attack.rs138
-rw-r--r--azalea-client/src/client.rs4
-rw-r--r--azalea-client/src/interact.rs83
-rw-r--r--azalea-client/src/lib.rs1
-rw-r--r--azalea-client/src/mining.rs10
-rw-r--r--azalea-client/src/packet_handling.rs10
6 files changed, 234 insertions, 12 deletions
diff --git a/azalea-client/src/attack.rs b/azalea-client/src/attack.rs
new file mode 100644
index 00000000..34083ffc
--- /dev/null
+++ b/azalea-client/src/attack.rs
@@ -0,0 +1,138 @@
+use azalea_core::GameMode;
+use azalea_entity::{
+ metadata::{ShiftKeyDown, Sprinting},
+ Attributes, Physics,
+};
+use azalea_protocol::packets::game::serverbound_interact_packet::{
+ self, ServerboundInteractPacket,
+};
+use azalea_world::MinecraftEntityId;
+use bevy_app::{App, FixedUpdate, Plugin, Update};
+use bevy_ecs::prelude::*;
+use derive_more::{Deref, DerefMut};
+
+use crate::{
+ interact::SwingArmEvent,
+ local_player::{LocalGameMode, SendPacketEvent},
+ Client,
+};
+
+pub struct AttackPlugin;
+impl Plugin for AttackPlugin {
+ fn build(&self, app: &mut App) {
+ app.add_event::<AttackEvent>()
+ .add_systems(Update, handle_attack_event)
+ .add_systems(
+ FixedUpdate,
+ (
+ increment_ticks_since_last_attack,
+ update_attack_strength_scale,
+ )
+ .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 ticks_since_last_attack = *self.component::<AttackStrengthScale>();
+ 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.iter() {
+ 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 {
+ entity: event.entity,
+ packet: ServerboundInteractPacket {
+ entity_id: *event.target,
+ action: serverbound_interact_packet::ActionType::Attack,
+ using_secondary_action: **sneaking,
+ }
+ .get(),
+ });
+
+ // 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.delta = physics.delta.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.)
+}
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index 301d9197..f7fcb16c 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -1,4 +1,5 @@
use crate::{
+ attack::{self, AttackPlugin},
chat::ChatPlugin,
disconnect::{DisconnectEvent, DisconnectPlugin},
events::{Event, EventPlugin, LocalPlayerEvents},
@@ -306,6 +307,7 @@ impl Client {
abilities: PlayerAbilities::default(),
permission_level: PermissionLevel::default(),
mining: mining::MineBundle::default(),
+ attack: attack::AttackBundle::default(),
_local: Local,
});
@@ -574,6 +576,7 @@ pub struct JoinedClientBundle {
pub permission_level: PermissionLevel,
pub mining: mining::MineBundle,
+ pub attack: attack::AttackBundle,
pub _local: Local,
}
@@ -722,6 +725,7 @@ impl PluginGroup for DefaultPlugins {
.add(InteractPlugin)
.add(RespawnPlugin)
.add(MinePlugin)
+ .add(AttackPlugin)
.add(TickBroadcastPlugin);
#[cfg(feature = "log")]
{
diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs
index dc0213b0..3c9428ff 100644
--- a/azalea-client/src/interact.rs
+++ b/azalea-client/src/interact.rs
@@ -2,7 +2,9 @@ use std::ops::AddAssign;
use azalea_block::BlockState;
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
-use azalea_entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position};
+use azalea_entity::{
+ clamp_look_direction, view_vector, Attributes, EyeHeight, Local, LookDirection, Position,
+};
use azalea_inventory::{ItemSlot, ItemSlotData};
use azalea_nbt::NbtList;
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
@@ -17,6 +19,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
event::{Event, EventReader, EventWriter},
+ query::{Changed, With},
schedule::IntoSystemConfigs,
system::{Commands, Query, Res},
};
@@ -39,12 +42,15 @@ impl Plugin for InteractPlugin {
.add_systems(
Update,
(
- update_hit_result_component.after(clamp_look_direction),
- handle_block_interact_event,
- handle_swing_arm_event,
- )
- .before(handle_send_packet_event)
- .chain(),
+ (
+ update_hit_result_component.after(clamp_look_direction),
+ handle_block_interact_event,
+ handle_swing_arm_event,
+ )
+ .before(handle_send_packet_event)
+ .chain(),
+ update_modifiers_for_held_item,
+ ),
);
}
}
@@ -305,3 +311,66 @@ fn handle_swing_arm_event(
});
}
}
+
+#[allow(clippy::type_complexity)]
+fn update_modifiers_for_held_item(
+ mut query: Query<
+ (&mut Attributes, &InventoryComponent),
+ (With<Local>, Changed<InventoryComponent>),
+ >,
+) {
+ for (mut attributes, inventory) in &mut query {
+ let held_item = inventory.held_item();
+
+ use azalea_registry::Item;
+ let added_attack_speed = match held_item.kind() {
+ Item::WoodenSword => -2.4,
+ Item::WoodenShovel => -3.0,
+ Item::WoodenPickaxe => -2.8,
+ Item::WoodenAxe => -3.2,
+ Item::WoodenHoe => -3.0,
+
+ Item::StoneSword => -2.4,
+ Item::StoneShovel => -3.0,
+ Item::StonePickaxe => -2.8,
+ Item::StoneAxe => -3.2,
+ Item::StoneHoe => -2.0,
+
+ Item::GoldenSword => -2.4,
+ Item::GoldenShovel => -3.0,
+ Item::GoldenPickaxe => -2.8,
+ Item::GoldenAxe => -3.0,
+ Item::GoldenHoe => -3.0,
+
+ Item::IronSword => -2.4,
+ Item::IronShovel => -3.0,
+ Item::IronPickaxe => -2.8,
+ Item::IronAxe => -3.1,
+ Item::IronHoe => -1.0,
+
+ Item::DiamondSword => -2.4,
+ Item::DiamondShovel => -3.0,
+ Item::DiamondPickaxe => -2.8,
+ Item::DiamondAxe => -3.0,
+ Item::DiamondHoe => 0.0,
+
+ Item::NetheriteSword => -2.4,
+ Item::NetheriteShovel => -3.0,
+ Item::NetheritePickaxe => -2.8,
+ Item::NetheriteAxe => -3.0,
+ Item::NetheriteHoe => 0.0,
+
+ Item::Trident => -2.9,
+ _ => 0.,
+ };
+ attributes
+ .attack_speed
+ .remove(&azalea_entity::attributes::BASE_ATTACK_SPEED_UUID);
+ attributes
+ .attack_speed
+ .insert(azalea_entity::attributes::tool_attack_speed_modifier(
+ added_attack_speed,
+ ))
+ .unwrap();
+ }
+}
diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs
index e36cb846..e8698fc7 100644
--- a/azalea-client/src/lib.rs
+++ b/azalea-client/src/lib.rs
@@ -12,6 +12,7 @@
#![feature(type_alias_impl_trait)]
mod account;
+pub mod attack;
pub mod chat;
mod client;
pub mod disconnect;
diff --git a/azalea-client/src/mining.rs b/azalea-client/src/mining.rs
index 049bc859..c087c467 100644
--- a/azalea-client/src/mining.rs
+++ b/azalea-client/src/mining.rs
@@ -18,6 +18,7 @@ use crate::{
},
inventory::InventoryComponent,
local_player::{LocalGameMode, SendPacketEvent},
+ Client,
};
/// A plugin that allows clients to break blocks in the world.
@@ -44,6 +45,15 @@ impl Plugin for MinePlugin {
}
}
+impl Client {
+ pub fn start_mining(&mut self, position: BlockPos) {
+ self.ecs.lock().send_event(StartMiningBlockEvent {
+ entity: self.entity,
+ position,
+ });
+ }
+}
+
/// Information about the block we're currently mining. This is only present if
/// we're currently mining a block.
#[derive(Component)]
diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs
index 4ceb3999..b4f4e045 100644
--- a/azalea-client/src/packet_handling.rs
+++ b/azalea-client/src/packet_handling.rs
@@ -574,13 +574,14 @@ fn process_packet_events(ecs: &mut World) {
ClientboundGamePacket::AddEntity(p) => {
debug!("Got add entity packet {:?}", p);
+ #[allow(clippy::type_complexity)]
let mut system_state: SystemState<(
Commands,
Query<Option<&InstanceName>>,
- ResMut<InstanceContainer>,
+ Res<InstanceContainer>,
ResMut<EntityInfos>,
)> = SystemState::new(ecs);
- let (mut commands, mut query, mut instance_container, mut entity_infos) =
+ let (mut commands, mut query, instance_container, mut entity_infos) =
system_state.get_mut(ecs);
let instance_name = query.get_mut(player_entity).unwrap();
@@ -694,9 +695,8 @@ fn process_packet_events(ecs: &mut World) {
ClientboundGamePacket::SetHealth(p) => {
debug!("Got set health packet {:?}", p);
- let mut system_state: SystemState<(Query<&mut Health>, EventWriter<DeathEvent>)> =
- SystemState::new(ecs);
- let (mut query, mut death_events) = system_state.get_mut(ecs);
+ let mut system_state: SystemState<Query<&mut Health>> = SystemState::new(ecs);
+ let mut query = system_state.get_mut(ecs);
let mut health = query.get_mut(player_entity).unwrap();
**health = p.health;