feat: add shield & mace
This commit is contained in:
parent
60bfdf62db
commit
38f6b0fa8b
@ -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<f64>, world: &World, attack_type:
|
||||
.play_sound(Sound::EntityPlayerAttackWeak, SoundCategory::Players, pos)
|
||||
.await;
|
||||
}
|
||||
AttackType::MaceSmash => {
|
||||
world
|
||||
.play_sound(Sound::ItemMaceSmashAir, SoundCategory::Players, pos)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<BlocksAttacksImpl>().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) =
|
||||
|
||||
@ -818,6 +818,7 @@ impl Player {
|
||||
//self.world().level.list_cached();
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
|
||||
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(
|
||||
|
||||
@ -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::<ConsumableImpl>().is_some() {
|
||||
if held.get_data_component::<ConsumableImpl>().is_some()
|
||||
|| held.get_data_component::<BlocksAttacksImpl>().is_some()
|
||||
{
|
||||
// If its food we want to make sure we can actually consume it
|
||||
if let Some(food) = held.get_data_component::<FoodImpl>() {
|
||||
if player.abilities.lock().await.invulnerable
|
||||
|
||||
Loading…
Reference in New Issue
Block a user