diff --git a/pumpkin-protocol/src/java/client/play/spawn_entity.rs b/pumpkin-protocol/src/java/client/play/spawn_entity.rs index a77272d84..af00980b6 100644 --- a/pumpkin-protocol/src/java/client/play/spawn_entity.rs +++ b/pumpkin-protocol/src/java/client/play/spawn_entity.rs @@ -39,8 +39,8 @@ impl CSpawnEntity { r#type, position, pitch: (pitch * 256.0 / 360.0).floor() as u8, - yaw: (yaw * 256.0 / 360.0).floor() as u8, - head_yaw: (head_yaw * 256.0 / 360.0).floor() as u8, + yaw: (yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8, + head_yaw: (head_yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8, data, velocity: Vector3::new( (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16, diff --git a/pumpkin-util/src/math/boundingbox.rs b/pumpkin-util/src/math/boundingbox.rs index 6604ba737..2f64ac63f 100644 --- a/pumpkin-util/src/math/boundingbox.rs +++ b/pumpkin-util/src/math/boundingbox.rs @@ -130,6 +130,14 @@ impl BoundingBox { Some(collision_time) } + pub fn get_average_side_length(&self) -> f64 { + let width = self.max.x - self.min.x; + let height = self.max.y - self.min.y; + let depth = self.max.z - self.min.z; + + (width + height + depth) / 3.0 + } + pub fn min_block_pos(&self) -> BlockPos { BlockPos::floored_v(self.min) } diff --git a/pumpkin-util/src/math/euler_angle.rs b/pumpkin-util/src/math/euler_angle.rs new file mode 100644 index 000000000..f8b9f0be2 --- /dev/null +++ b/pumpkin-util/src/math/euler_angle.rs @@ -0,0 +1,69 @@ +use pumpkin_nbt::tag::NbtTag; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct EulerAngle { + pub pitch: f32, + pub yaw: f32, + pub roll: f32, +} + +impl EulerAngle { + pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self { + let pitch = pitch % 360.0; + let yaw = yaw % 360.0; + let roll = roll % 360.0; + + Self { pitch, yaw, roll } + } + + pub const ZERO: EulerAngle = EulerAngle { + pitch: 0.0, + yaw: 0.0, + roll: 0.0, + }; +} + +impl Default for EulerAngle { + fn default() -> Self { + Self::ZERO + } +} + +impl From for NbtTag { + fn from(val: EulerAngle) -> Self { + NbtTag::List(vec![ + NbtTag::Float(val.pitch), + NbtTag::Float(val.yaw), + NbtTag::Float(val.roll), + ]) + } +} + +impl From for EulerAngle { + fn from(tag: NbtTag) -> Self { + if let NbtTag::List(list) = tag + && list.len() == 3 + { + let pitch = if let NbtTag::Float(f) = list[0] { + f + } else { + 0.0 + }; + let yaw = if let NbtTag::Float(f) = list[1] { + f + } else { + 0.0 + }; + let roll = if let NbtTag::Float(f) = list[2] { + f + } else { + 0.0 + }; + + return Self::new(pitch, yaw, roll); + } + + Self::ZERO + } +} diff --git a/pumpkin-util/src/math/mod.rs b/pumpkin-util/src/math/mod.rs index 6d1b7fa25..887870ba4 100644 --- a/pumpkin-util/src/math/mod.rs +++ b/pumpkin-util/src/math/mod.rs @@ -1,6 +1,7 @@ use num_traits::{Float, One, PrimInt, Zero}; pub mod boundingbox; +pub mod euler_angle; pub mod experience; pub mod float_provider; pub mod int_provider; diff --git a/pumpkin/src/entity/decoration/armor_stand.rs b/pumpkin/src/entity/decoration/armor_stand.rs new file mode 100644 index 000000000..c851f25e0 --- /dev/null +++ b/pumpkin/src/entity/decoration/armor_stand.rs @@ -0,0 +1,413 @@ +use std::sync::{ + Arc, + atomic::{AtomicI32, AtomicI64, AtomicU8, Ordering}, +}; + +use crate::entity::{ + Entity, EntityBase, NBTStorage, + living::{LivingEntity, LivingEntityTrait}, +}; +use async_trait::async_trait; +use crossbeam::atomic::AtomicCell; +use pumpkin_data::{ + damage::DamageType, + data_component_impl::{EquipmentSlot, EquipmentType}, + entity::EntityStatus, + item::Item, + sound::{Sound, SoundCategory}, +}; +use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; +use pumpkin_util::math::{euler_angle::EulerAngle, vector3::Vector3}; +use pumpkin_world::item::ItemStack; + +#[derive(Debug, Clone, Copy)] +pub struct PackedRotation { + pub head: EulerAngle, + pub body: EulerAngle, + pub left_arm: EulerAngle, + pub right_arm: EulerAngle, + pub left_leg: EulerAngle, + pub right_leg: EulerAngle, +} + +impl Default for PackedRotation { + fn default() -> Self { + Self { + head: EulerAngle::new(0.0, 0.0, 0.0), + body: EulerAngle::new(0.0, 0.0, 0.0), + left_arm: EulerAngle::new(-10.0, 0.0, -10.0), + right_arm: EulerAngle::new(-15.0, 0.0, 10.0), + left_leg: EulerAngle::new(-1.0, 0.0, -1.0), + right_leg: EulerAngle::new(1.0, 0.0, 1.0), + } + } +} + +impl From for NbtTag { + fn from(val: PackedRotation) -> Self { + let mut compound = NbtCompound::new(); + compound.put("Head", val.head); + compound.put("Body", val.body); + compound.put("LeftArm", val.left_arm); + compound.put("RightArm", val.right_arm); + compound.put("LeftLeg", val.left_leg); + compound.put("RightLeg", val.right_leg); + Self::Compound(compound) + } +} + +impl From for PackedRotation { + #[allow(clippy::unnecessary_fallible_conversions)] + fn from(tag: NbtTag) -> Self { + if let NbtTag::Compound(compound) = tag { + fn get_rotation( + compound: &NbtCompound, + key: &'static str, + default: EulerAngle, + ) -> EulerAngle { + compound + .get(key) + .and_then(|tag| tag.clone().try_into().ok()) + .unwrap_or(default) + } + + let default = Self::default(); + + Self { + head: get_rotation(&compound, "Head", default.head), + body: get_rotation(&compound, "Body", default.body), + left_arm: get_rotation(&compound, "LeftArm", default.left_arm), + right_arm: get_rotation(&compound, "RightArm", default.right_arm), + left_leg: get_rotation(&compound, "LeftLeg", default.left_leg), + right_leg: get_rotation(&compound, "RightLeg", default.right_leg), + } + } else { + Self::default() + } + } +} + +#[allow(dead_code)] +pub struct ArmorStandEntity { + living_entity: LivingEntity, + + armor_stand_flags: AtomicU8, + last_hit_time: AtomicI64, + disabled_slots: AtomicI32, + + rotation: AtomicCell, +} + +impl ArmorStandEntity { + pub fn new(entity: Entity) -> Self { + let living_entity = LivingEntity::new(entity); + let packed_rotation = PackedRotation::default(); + + Self { + living_entity, + armor_stand_flags: AtomicU8::new(0), + last_hit_time: AtomicI64::new(0), + disabled_slots: AtomicI32::new(0), + rotation: AtomicCell::new(packed_rotation), + } + } + + pub fn set_small(&self, small: bool) { + self.set_bit_field(ArmorStandFlags::Small, small); + } + + pub fn is_small(&self) -> bool { + (self.armor_stand_flags.load(Ordering::Relaxed) & ArmorStandFlags::Small as u8) != 0 + } + + pub fn set_show_arms(&self, show_arms: bool) { + self.set_bit_field(ArmorStandFlags::ShowArms, show_arms); + } + + pub fn should_show_arms(&self) -> bool { + (self.armor_stand_flags.load(Ordering::Relaxed) & ArmorStandFlags::ShowArms as u8) != 0 + } + + pub fn set_hide_base_plate(&self, hide_base_plate: bool) { + self.set_bit_field(ArmorStandFlags::HideBasePlate, hide_base_plate); + } + + pub fn should_show_base_plate(&self) -> bool { + (self.armor_stand_flags.load(Ordering::Relaxed) & ArmorStandFlags::HideBasePlate as u8) == 0 + } + + pub fn set_marker(&self, marker: bool) { + self.set_bit_field(ArmorStandFlags::Marker, marker); + } + + pub fn is_marker(&self) -> bool { + (self.armor_stand_flags.load(Ordering::Relaxed) & ArmorStandFlags::Marker as u8) != 0 + } + + fn set_bit_field(&self, bit_field: ArmorStandFlags, set: bool) { + let current = self.armor_stand_flags.load(Ordering::Relaxed); + let new_value = if set { + current | bit_field as u8 + } else { + current & !(bit_field as u8) + }; + self.armor_stand_flags.store(new_value, Ordering::Relaxed); + } + + pub fn can_use_slot(&self, slot: &EquipmentSlot) -> bool { + !matches!(slot, EquipmentSlot::Body(_) | EquipmentSlot::Saddle(_)) + && !self.is_slot_disabled(slot) + } + + pub fn is_slot_disabled(&self, slot: &EquipmentSlot) -> bool { + let disabled_slots = self.disabled_slots.load(Ordering::Relaxed); + let slot_bit = 1 << slot.get_offset_entity_slot_id(0); + + (disabled_slots & slot_bit) != 0 + || (slot.slot_type() == EquipmentType::Hand && !self.should_show_arms()) + } + + pub fn set_slot_disabled(&self, slot: &EquipmentSlot, disabled: bool) { + let slot_bit = 1 << slot.get_offset_entity_slot_id(0); + let current = self.disabled_slots.load(Ordering::Relaxed); + + let new_val = if disabled { + current | slot_bit + } else { + current & !slot_bit + }; + + self.disabled_slots.store(new_val, Ordering::Relaxed); + } + + pub fn is_invisible(&self) -> bool { + self.get_entity().invisible.load(Ordering::Relaxed) + } + + pub fn pack_rotation(&self) -> PackedRotation { + self.rotation.load() + } + + pub fn unpack_rotation(&self, packed: &PackedRotation) { + self.rotation.store(packed.to_owned()); + } + + async fn break_and_drop_items(&self) { + let entity = self.get_entity(); + //let name = entity.custom_name.unwrap_or(entity.get_name()); + + //TODO: i am stupid! let armor_stand_item = ItemStack::new_with_component(1, &Item::ARMOR_STAND, vec![(DataComponent::CustomName, self.get_custom_name())]); + let armor_stand_item = ItemStack::new(1, &Item::ARMOR_STAND); + entity + .world + .drop_stack(&entity.block_pos.load(), armor_stand_item) + .await; + + self.on_break(entity).await; + } + + async fn on_break(&self, entity: &Entity) { + let world = &entity.world; + world + .play_sound( + Sound::EntityArmorStandBreak, + SoundCategory::Neutral, + &entity.pos.load(), + ) + .await; + + // TODO: Implement equipment slots and make them drop all of their stored items. + } +} + +#[async_trait] +impl NBTStorage for ArmorStandEntity { + async fn write_nbt(&self, nbt: &mut NbtCompound) { + let disabled_slots = self.disabled_slots.load(Ordering::Relaxed); + + nbt.put_bool("Invisible", self.is_invisible()); + nbt.put_bool("Small", self.is_small()); + nbt.put_bool("ShowArms", self.should_show_arms()); + nbt.put_int("DisabledSlots", disabled_slots); + nbt.put_bool("NoBasePlate", !self.should_show_base_plate()); + if self.is_marker() { + nbt.put_bool("Marker", true); + } + + nbt.put("Pose", self.pack_rotation()); + } + + async fn read_nbt_non_mut(&self, nbt: &NbtCompound) { + let mut flags = 0u8; + + if let Some(invisible) = nbt.get_bool("Invisible") + && invisible + { + self.get_entity().set_invisible(invisible).await; + } + + if let Some(small) = nbt.get_bool("Small") + && small + { + flags |= ArmorStandFlags::Small as u8; + } + + if let Some(show_arms) = nbt.get_bool("ShowArms") + && show_arms + { + flags |= ArmorStandFlags::ShowArms as u8; + } + + if let Some(disabled_slots) = nbt.get_int("DisabledSlots") { + self.disabled_slots.store(disabled_slots, Ordering::Relaxed); + } + + if let Some(no_base_plate) = nbt.get_bool("NoBasePlate") { + if !no_base_plate { + flags |= ArmorStandFlags::HideBasePlate as u8; + } + } else { + flags |= ArmorStandFlags::HideBasePlate as u8; + } + + if let Some(marker) = nbt.get_bool("Marker") + && marker + { + flags |= ArmorStandFlags::Marker as u8; + } + + self.armor_stand_flags.store(flags, Ordering::Relaxed); + + if let Some(pose_tag) = nbt.get("Pose") { + let packed: PackedRotation = pose_tag.clone().into(); + self.unpack_rotation(&packed); + } + } +} + +#[async_trait] +impl LivingEntityTrait for ArmorStandEntity { + //async fn on_actually_hurt() +} + +#[async_trait] +impl EntityBase for ArmorStandEntity { + fn get_entity(&self) -> &Entity { + &self.living_entity.entity + } + + fn get_living_entity(&self) -> Option<&LivingEntity> { + Some(&self.living_entity) + } + + fn as_nbt_storage(&self) -> &dyn NBTStorage { + self + } + + async fn damage_with_context( + &self, + caller: Arc, + _amount: f32, + damage_type: DamageType, + _position: Option>, + source: Option<&dyn EntityBase>, + _cause: Option<&dyn EntityBase>, + ) -> bool { + let entity = self.get_entity(); + if entity.is_removed() { + return false; + } + + let world = &entity.world; + + let mob_griefing_gamerule = { + let game_rules = &world.level_info.read().await.game_rules; + game_rules.mob_griefing + }; + + if !mob_griefing_gamerule && source.is_some_and(|source| source.get_player().is_none()) { + return false; + } + + // TODO: .isIn(DamageTypeTags::BYPASSES_INVULNERABILITY) + + if damage_type == DamageType::EXPLOSION { + // TODO: Implement Dropping Items that are in the Equipment Slots & entity.kill() + self.on_break(entity).await; + entity.kill(caller).await; + //entity.remove().await; + return false; + } // TODO: Implement .isIn(DamageTypeTags::IGNITES_ARMOR_STANDS) + + // TODO: Implement .isIn(DamageTypeTags::BURNS_ARMOR_STANDS) + + /* // TODO: + bl1: bool = .isIn(DamageTypeTags.CAN_BREAK_ARMOR_STAND); + bl2: bool = .isIn(DamageTypeTags.ALWAYS_KILLS_ARMOR_STANDS); + + if !bl1 && !bl2 { + return false; + } + */ + + let Some(source) = source else { return false }; + + // TODO: source is not giving the real player or wrong stuff cause .is_creative() is false even tho the player is in creative. + if let Some(player) = source.get_player() { + if !player.abilities.lock().await.allow_modify_world { + return false; + } else if player.is_creative() { + world + .play_sound( + Sound::EntityArmorStandBreak, + SoundCategory::Neutral, + &entity.block_pos.load().to_f64(), + ) + .await; + self.break_and_drop_items().await; + entity.kill(caller).await; + return true; + } + } + + let time = world.level_time.lock().await.query_gametime(); + + if time - self.last_hit_time.load(Ordering::Relaxed) > 5 { + // && !bl2 { + world + .send_entity_status(entity, EntityStatus::HitArmorStand) + .await; + world + .play_sound( + Sound::EntityArmorStandHit, + SoundCategory::Neutral, + &entity.block_pos.load().to_f64(), + ) + .await; + self.last_hit_time.store(time, Ordering::Relaxed); + } else { + world + .play_sound( + Sound::EntityArmorStandBreak, + SoundCategory::Neutral, + &entity.block_pos.load().to_f64(), + ) + .await; + self.break_and_drop_items().await; + entity.kill(caller).await; + } + + true + } +} + +pub enum ArmorStandFlags { + /// Small armor stand Flag + Small = 1, + /// Show arms Flag + ShowArms = 4, + /// Hide base plate fLag + HideBasePlate = 8, + /// Marker Flag + Marker = 16, +} diff --git a/pumpkin/src/entity/decoration/mod.rs b/pumpkin/src/entity/decoration/mod.rs index 93a4c23f2..7ef7e02ae 100644 --- a/pumpkin/src/entity/decoration/mod.rs +++ b/pumpkin/src/entity/decoration/mod.rs @@ -1,2 +1,3 @@ +pub mod armor_stand; pub mod end_crystal; pub mod painting; diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 9225887e9..0e18dbca2 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -60,6 +60,7 @@ pub mod living; pub mod mob; pub mod player; pub mod projectile; +pub mod projectile_deflection; pub mod tnt; pub mod r#type; @@ -260,6 +261,8 @@ pub struct Entity { pub sneaking: AtomicBool, /// Indicates whether the entity is sprinting pub sprinting: AtomicBool, + /// Indicates whether the entity is invisible + pub invisible: AtomicBool, /// Indicates whether the entity is flying due to a fall pub fall_flying: AtomicBool, /// The entity's current velocity vector, aka knockback @@ -362,6 +365,7 @@ impl Entity { get_section_cord(floor_z), )), sneaking: AtomicBool::new(false), + invisible: AtomicBool::new(false), world, sprinting: AtomicBool::new(false), fall_flying: AtomicBool::new(false), @@ -1422,6 +1426,12 @@ impl Entity { } } + pub async fn set_invisible(&self, invisible: bool) { + assert!(self.invisible.load(Relaxed) != invisible); + self.invisible.store(invisible, Relaxed); + self.set_flag(Flag::Invisible, invisible).await; + } + pub async fn set_on_fire(&self, on_fire: bool) { if self.has_visual_fire.load(Ordering::Relaxed) != on_fire { self.has_visual_fire.store(on_fire, Ordering::Relaxed); diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index 4cc455cb5..63886c4ce 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -7,6 +7,8 @@ use super::{Entity, EntityBase, NBTStorage, living::LivingEntity}; use async_trait::async_trait; use pumpkin_util::math::vector3::Vector3; +pub mod wind_charge; + pub struct ThrownItemEntity { entity: Entity, } diff --git a/pumpkin/src/entity/projectile/wind_charge.rs b/pumpkin/src/entity/projectile/wind_charge.rs new file mode 100644 index 000000000..d055196ef --- /dev/null +++ b/pumpkin/src/entity/projectile/wind_charge.rs @@ -0,0 +1,120 @@ +use async_trait::async_trait; +use pumpkin_util::math::vector3::Vector3; +use std::{ + f64, + sync::{ + Arc, + atomic::{AtomicU8, Ordering}, + }, +}; + +use crate::{ + entity::{ + Entity, EntityBase, NBTStorage, living::LivingEntity, projectile::ThrownItemEntity, + projectile_deflection::ProjectileDeflectionType, + }, + server::Server, +}; + +const EXPLOSION_POWER: f32 = 1.2; +// square(3.5) +const MAX_RENDER_DISTANCE_WHEN_NEWLY_SPAWNED: f32 = 3.5 * 3.5; +const DEFAULT_DEFLECT_COOLDOWN: u8 = 5; + +pub struct WindChargeEntity { + deflect_cooldown: AtomicU8, + thrown_item_entity: ThrownItemEntity, +} + +impl WindChargeEntity { + #[must_use] + pub fn new(thrown_item_entity: ThrownItemEntity) -> Self { + Self { + deflect_cooldown: AtomicU8::new(DEFAULT_DEFLECT_COOLDOWN), + thrown_item_entity, + } + } + + pub fn get_deflect_cooldown(&self) -> u8 { + self.deflect_cooldown.load(Ordering::Relaxed) + } + + pub fn set_deflect_cooldown(&self, value: u8) { + self.deflect_cooldown.store(value, Ordering::Relaxed); + } + + pub async fn create_explosion(&self, position: Vector3) { + self.get_entity() + .world + .explode(position, EXPLOSION_POWER) + .await; + } + + pub fn should_render(&self, distance: f64) -> bool { + if self.get_entity().age.load(Ordering::Relaxed) < 2 + && distance < f64::from(MAX_RENDER_DISTANCE_WHEN_NEWLY_SPAWNED) + { + return false; + } + + let mut average_side_length = self + .get_entity() + .bounding_box + .load() + .get_average_side_length(); + + if average_side_length.is_nan() { + average_side_length = 1.0; + } + + // TODO: IMPLEMENT renderDistanceMultiplier instead of the 1.0 + average_side_length *= 64.0 * 1.0; + distance < average_side_length * average_side_length + } + + pub fn deflect( + &mut self, + deflection: &ProjectileDeflectionType, + deflector: Option<&dyn EntityBase>, + _from_attack: bool, + ) -> bool { + if self.deflect_cooldown.load(Ordering::Relaxed) > 0 { + return false; + } + + deflection.deflect(self, deflector); + + /* TODO: Does this need to be implemented? + if self.get_entity().world().is_client() { + self.set_owner(); + self.on_Deflected(from_attack); + } + */ + true + } +} + +impl NBTStorage for WindChargeEntity {} + +#[async_trait] +impl EntityBase for WindChargeEntity { + async fn tick(&self, caller: Arc, server: &Server) { + if self.get_deflect_cooldown() > 0 { + self.set_deflect_cooldown(self.get_deflect_cooldown() - 1); + } + + self.thrown_item_entity.tick(caller, server).await; + } + + fn get_entity(&self) -> &Entity { + &self.thrown_item_entity.entity + } + + fn get_living_entity(&self) -> Option<&LivingEntity> { + None + } + + fn as_nbt_storage(&self) -> &dyn NBTStorage { + self + } +} diff --git a/pumpkin/src/entity/projectile_deflection.rs b/pumpkin/src/entity/projectile_deflection.rs new file mode 100644 index 000000000..56bda31c9 --- /dev/null +++ b/pumpkin/src/entity/projectile_deflection.rs @@ -0,0 +1,54 @@ +use crate::entity::EntityBase; +use std::sync::atomic::Ordering; + +#[derive(Clone, Copy)] +pub enum ProjectileDeflectionType { + None, + Simple, + Redirected, + TransferVelocityDirection, +} + +impl ProjectileDeflectionType { + pub fn deflect(&self, projectile: &mut dyn EntityBase, hit_entity: Option<&dyn EntityBase>) { + match self { + Self::None => {} + Self::Simple => { + let vel = 170.0 + rand::random::() * 20.0; + + let current_velocity = projectile + .get_entity() + .velocity + .load() + .multiply(-0.5, -0.5, -0.5); + + let entity = projectile.get_entity(); + entity.velocity.store(current_velocity); + + let yaw = entity.yaw.load() + vel; + let pitch = entity.pitch.load(); + entity.set_rotation(yaw, pitch); + // TODO: Add entity.lastYaw += vel + entity.velocity_dirty.store(true, Ordering::Relaxed); + } + Self::Redirected => { + if let Some(hit_entity) = hit_entity { + let rotation_vector = hit_entity.get_entity().rotation(); + + let entity = projectile.get_entity(); + entity.velocity.store(rotation_vector.to_f64()); + entity.velocity_dirty.store(true, Ordering::Relaxed); + } + } + Self::TransferVelocityDirection => { + if let Some(hit_entity) = hit_entity { + let hit_velocity = hit_entity.get_entity().velocity.load().normalize(); + + let entity = projectile.get_entity(); + entity.velocity.store(hit_velocity); + entity.velocity_dirty.store(true, Ordering::Relaxed); + } + } + } + } +} diff --git a/pumpkin/src/item/items/armor_stand.rs b/pumpkin/src/item/items/armor_stand.rs new file mode 100644 index 000000000..f33357545 --- /dev/null +++ b/pumpkin/src/item/items/armor_stand.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use crate::entity::Entity; +use crate::entity::decoration::armor_stand::ArmorStandEntity; +use crate::entity::player::Player; +use crate::item::{ItemBehaviour, ItemMetadata}; +use crate::server::Server; +use async_trait::async_trait; +use pumpkin_data::entity::EntityType; +use pumpkin_data::item::Item; +use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_util::math::boundingbox::BoundingBox; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::math::wrap_degrees; +use pumpkin_world::item::ItemStack; +use uuid::Uuid; + +pub struct ArmorStandItem; + +impl ArmorStandItem { + fn calculate_placement_position(location: &BlockPos, face: BlockDirection) -> BlockPos { + match face { + BlockDirection::Up => location.offset(Vector3::new(0, 1, 0)), + BlockDirection::Down => location.offset(Vector3::new(0, -1, 0)), + BlockDirection::North => location.offset(Vector3::new(0, 0, -1)), + BlockDirection::South => location.offset(Vector3::new(0, 0, 1)), + BlockDirection::West => location.offset(Vector3::new(-1, 0, 0)), + BlockDirection::East => location.offset(Vector3::new(1, 0, 0)), + } + } +} + +impl ItemMetadata for ArmorStandItem { + fn ids() -> Box<[u16]> { + [Item::ARMOR_STAND.id].into() + } +} + +#[async_trait] +impl ItemBehaviour for ArmorStandItem { + async fn use_on_block( + &self, + _item: &mut ItemStack, + player: &Player, + location: BlockPos, + face: BlockDirection, + _block: &Block, + _server: &Server, + ) { + let world = player.world(); + let position = Self::calculate_placement_position(&location, face).to_f64(); + + let bottom_center = Vector3::new(position.x, position.y, position.z); + + let armor_stand_dimensions = EntityType::ARMOR_STAND.dimension; + let width = f64::from(armor_stand_dimensions[0]); + let height = f64::from(armor_stand_dimensions[1]); + + let bounding_box = BoundingBox::new( + Vector3::new( + bottom_center.x - width / 2.0, + bottom_center.y, + bottom_center.z - width / 2.0, + ), + Vector3::new( + bottom_center.x + width / 2.0, + bottom_center.y + height, + bottom_center.z + width / 2.0, + ), + ); + + if world.is_space_empty(bounding_box).await + && world.get_entities_at_box(&bounding_box).await.is_empty() + { + let (player_yaw, _) = player.rotation(); + let rotation = ((wrap_degrees(player_yaw - 180.0) + 22.5) / 45.0).floor() * 45.0; + + let entity = Entity::new( + Uuid::new_v4(), + world.clone(), + position, + &EntityType::ARMOR_STAND, + false, + ); + + entity.set_rotation(rotation, 0.0); + + world + .play_sound( + Sound::EntityArmorStandPlace, + SoundCategory::Blocks, + &entity.pos.load(), + ) + .await; + + let armor_stand = ArmorStandEntity::new(entity); + + world.spawn_entity(Arc::new(armor_stand)).await; + } + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index 87865103f..434cf43d1 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -1,3 +1,4 @@ +pub mod armor_stand; pub mod axe; pub mod bucket; pub mod dye; @@ -17,11 +18,14 @@ pub mod snowball; pub mod spawn_egg; pub mod swords; pub mod trident; +pub mod wind_charge; +use crate::item::items::armor_stand::ArmorStandItem; use crate::item::items::end_crystal::EndCrystalItem; use crate::item::items::minecart::MinecartItem; use crate::item::items::name_tag::NameTagItem; use crate::item::items::spawn_egg::SpawnEggItem; +use crate::item::items::wind_charge::WindChargeItem; use super::registry::ItemRegistry; use axe::AxeItem; @@ -67,6 +71,8 @@ pub fn default_registry() -> Arc { manager.register(DyeItem); manager.register(InkSacItem); manager.register(GlowingInkSacItem); + manager.register(ArmorStandItem); + manager.register(WindChargeItem); Arc::new(manager) } diff --git a/pumpkin/src/item/items/wind_charge.rs b/pumpkin/src/item/items/wind_charge.rs new file mode 100644 index 000000000..178f74155 --- /dev/null +++ b/pumpkin/src/item/items/wind_charge.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use crate::entity::player::Player; +use async_trait::async_trait; +use pumpkin_data::entity::EntityType; +use pumpkin_data::item::Item; +use pumpkin_data::sound::Sound; +use uuid::Uuid; + +use crate::entity::Entity; +use crate::entity::projectile::ThrownItemEntity; +use crate::entity::projectile::wind_charge::WindChargeEntity; +use crate::item::{ItemBehaviour, ItemMetadata}; + +pub struct WindChargeItem; + +impl ItemMetadata for WindChargeItem { + fn ids() -> Box<[u16]> { + [Item::WIND_CHARGE.id].into() + } +} + +const POWER: f32 = 1.5; + +#[async_trait] +impl ItemBehaviour for WindChargeItem { + async fn normal_use(&self, _block: &Item, player: &Player) { + let world = player.world(); + let position = player.position(); + + // TODO: Implement Cooldown to throw the item + + world + .play_sound( + Sound::EntityWindChargeThrow, + pumpkin_data::sound::SoundCategory::Neutral, + &position, + ) + .await; + + let entity = Entity::new( + Uuid::new_v4(), + world.clone(), + position, + &EntityType::WIND_CHARGE, + false, + ); + + let wind_charge = ThrownItemEntity::new(entity, &player.living_entity.entity); + let yaw = player.living_entity.entity.yaw.load(); + let pitch = player.living_entity.entity.pitch.load(); + + wind_charge.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0); + // TODO: player.incrementStat(Stats.USED) + + // TODO: Implement that the projectile will explode on impact on ground + world + .spawn_entity(Arc::new(WindChargeEntity::new(wind_charge))) + .await; + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +}