diff --git a/pumpkin/src/entity/combat.rs b/pumpkin/src/entity/combat.rs index 1db2b67e2..c56d08f00 100644 --- a/pumpkin/src/entity/combat.rs +++ b/pumpkin/src/entity/combat.rs @@ -11,13 +11,14 @@ use crate::{ world::World, }; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AttackType { Knockback, Critical, Sweeping, Strong, Weak, + MaceSmash, } impl AttackType { @@ -27,22 +28,31 @@ impl AttackType { let sprinting = entity.sprinting.load(Ordering::Relaxed); let on_ground = entity.on_ground.load(Ordering::Relaxed); let fall_distance = player.living_entity.fall_distance.load(); - let sword = player.inventory().held_item().lock().await.is_sword(); + let held_item = player.inventory().held_item(); + let is_mace = { + let stack = held_item.lock().await; + stack.item.id == pumpkin_data::item::Item::MACE.id + }; + + if is_mace && !on_ground && fall_distance > 1.5 { + return Self::MaceSmash; + } + + let sword = { + let stack = held_item.lock().await; + stack.is_sword() + }; let is_strong = attack_cooldown_progress > 0.9; if sprinting && is_strong { return Self::Knockback; } - // TODO: even more checks if is_strong && !on_ground && fall_distance > 0.0 { - // !sprinting omitted return Self::Critical; } - // TODO: movement speed check if sword && is_strong { - // !is_crit, !is_knockback_hit, on_ground omitted return Self::Sweeping; } @@ -112,5 +122,10 @@ pub async fn player_attack_sound(pos: &Vector3, world: &World, attack_type: .play_sound(Sound::EntityPlayerAttackWeak, SoundCategory::Players, pos) .await; } + AttackType::MaceSmash => { + world + .play_sound(Sound::ItemMaceSmashAir, SoundCategory::Players, pos) + .await; + } } } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index d829561b8..ce142d374 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -35,11 +35,11 @@ use pumpkin_data::attributes::Attributes; use pumpkin_data::damage::DeathMessageType; use pumpkin_data::data_component_impl::Operation; use pumpkin_data::data_component_impl::{ - DeathProtectionImpl, EquipmentSlot, EquippableImpl, FoodImpl, + BlocksAttacksImpl, DeathProtectionImpl, EquipmentSlot, EquippableImpl, FoodImpl, }; use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType}; -use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::item_stack::{DamageResult, ItemStack}; use pumpkin_data::sound::SoundCategory; use pumpkin_data::{Block, translation}; use pumpkin_data::{damage::DamageType, sound::Sound}; @@ -252,6 +252,17 @@ impl LivingEntity { self.set_living_flag(Self::USING_ITEM_FLAG, false).await; } + pub async fn is_blocking(&self) -> bool { + let item_in_use = self.item_in_use.lock().await; + if let Some(item) = item_in_use.as_ref() + && item.get_data_component::().is_some() + { + let use_time = self.item_use_time.load(Ordering::Relaxed); + return item.get_max_use_time() - use_time >= 5; + } + false + } + pub async fn heal(&self, additional_health: f32) { assert!(additional_health > 0.0); self.set_health(self.health.load() + additional_health) @@ -1929,6 +1940,56 @@ impl EntityBase for LivingEntity { // Total damage after reductions let effective_amount = amount * (1.0 - resistance_reduction); + // Check for shield blocking + if self.is_blocking().await + && !damage_type.has_tag(&tag::DamageType::MINECRAFT_BYPASSES_SHIELD) + && let Some(pos) = position + { + let player_pos = self.entity.pos.load(); + let look_vec = Vector3::rotation_vector(0.0, self.entity.yaw.load() as f64); + let mut source_to_player = (player_pos - pos).normalize(); + source_to_player.y = 0.0; + + if source_to_player.dot(&look_vec) < 0.0 { + world + .play_sound(Sound::ItemShieldBlock, SoundCategory::Players, &player_pos) + .await; + + let active_hand = self.active_hand.lock().await; + if let Some(hand) = *active_hand { + let slot = if hand == Hand::Left { + EquipmentSlot::MAIN_HAND + } else { + EquipmentSlot::OFF_HAND + }; + + let equipment_lock = self.entity_equipment.lock().await; + let stack_arc = equipment_lock.get(&slot); + let mut stack = stack_arc.lock().await; + + let durability_damage = (amount / 1.0).floor().max(1.0) as i32; + if stack.damage_item(durability_damage) == DamageResult::Broken { + world + .send_entity_status( + &self.entity, + crate::entity::equipment_break_status(&slot), + ) + .await; + *stack = ItemStack::EMPTY.clone(); + let broken_stack = stack.clone(); + drop(stack); + drop(stack_arc); + drop(equipment_lock); + + self.send_equipment_changes(&[(slot, broken_stack)]).await; + self.clear_active_hand().await; + } + } + + return false; + } + } + // Apply hurt cooldown logic let last_damage = self.last_damage_taken.load(); let (damage_amount, play_sound) = diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 432701849..d867245f2 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -818,6 +818,7 @@ impl Player { //self.world().level.list_cached(); } + #[expect(clippy::too_many_lines)] pub async fn attack(&self, victim: Arc) { let world = self.world(); let server = world.server.upgrade().unwrap(); @@ -879,11 +880,21 @@ impl Player { damage *= 1.5; } + let is_mace_smash = matches!(attack_type, AttackType::MaceSmash); + if is_mace_smash { + let fall_distance = self.living_entity.fall_distance.load(); + damage += 1.5 * f64::from(fall_distance); + } + if !victim .damage_with_context( &*victim, damage as f32, - DamageType::PLAYER_ATTACK, + if is_mace_smash { + DamageType::MACE_SMASH + } else { + DamageType::PLAYER_ATTACK + }, None, Some(self), Some(self), @@ -900,6 +911,22 @@ impl Player { return; } + if is_mace_smash { + let fall_distance = self.living_entity.fall_distance.load(); + self.living_entity.fall_distance.store(0.0); + world + .play_sound( + if fall_distance > 5.0 { + Sound::ItemMaceSmashGroundHeavy + } else { + Sound::ItemMaceSmashGround + }, + SoundCategory::Players, + &pos, + ) + .await; + } + player_attack_sound(&pos, &world, attack_type).await; self.living_entity.last_attacking_id.store( diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index afe70d0c6..99c07eac5 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -41,7 +41,9 @@ use crate::world::{World, chunker}; use pumpkin_data::block_properties::{ BlockProperties, CommandBlockLikeProperties, WaterLikeProperties, }; -use pumpkin_data::data_component_impl::{ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl}; +use pumpkin_data::data_component_impl::{ + BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl, +}; use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; @@ -2335,7 +2337,9 @@ impl JavaClient { } } - if held.get_data_component::().is_some() { + if held.get_data_component::().is_some() + || held.get_data_component::().is_some() + { // If its food we want to make sure we can actually consume it if let Some(food) = held.get_data_component::() { if player.abilities.lock().await.invulnerable