use core::f32; use std::sync::{ Arc, atomic::{AtomicU32, Ordering}, }; use pumpkin_data::entity::EntityType; use pumpkin_util::math::vector3::Vector3; use crate::{entity::EntityBaseFuture, server::Server, world::World}; use super::{Entity, EntityBase, NBTStorage, living::LivingEntity, player::Player}; pub struct ExperienceOrbEntity { entity: Entity, amount: u32, orb_age: AtomicU32, } impl ExperienceOrbEntity { pub fn new(entity: Entity, amount: u32) -> Self { entity.yaw.store(rand::random::() * 360.0); Self { entity, amount, orb_age: AtomicU32::new(0), } } pub async fn spawn(world: &Arc, position: Vector3, amount: u32) { let mut amount = amount; while amount > 0 { let i = Self::round_to_orb_size(amount); amount -= i; let entity = Entity::new(world.clone(), position, &EntityType::EXPERIENCE_ORB); let orb = Arc::new(Self::new(entity, i)); world.spawn_entity(orb).await; } } const fn round_to_orb_size(value: u32) -> u32 { if value >= 2477 { 2477 } else if value >= 1237 { 1237 } else if value >= 617 { 617 } else if value >= 307 { 307 } else if value >= 149 { 149 } else if value >= 73 { 73 } else if value >= 37 { 37 } else if value >= 17 { 17 } else if value >= 7 { 7 } else if value >= 3 { 3 } else { 1 } } } impl NBTStorage for ExperienceOrbEntity {} impl EntityBase for ExperienceOrbEntity { fn tick<'a>( &'a self, caller: &'a Arc, server: &'a Server, ) -> EntityBaseFuture<'a, ()> { Box::pin(async move { let entity = &self.entity; entity.tick(caller, server).await; let bounding_box = entity.bounding_box.load(); let original_velo = entity.velocity.load(); let mut velo = original_velo; let no_clip = !self .entity .world .load() .is_space_empty(bounding_box.expand(-1.0e-7, -1.0e-7, -1.0e-7)); // TODO: isSubmergedIn if !no_clip { velo.y -= self.get_gravity(); } entity.velocity.store(velo); entity.move_entity(caller, velo).await; entity.tick_block_collisions(caller, server).await; let age = self.orb_age.fetch_add(1, Ordering::Relaxed); if age >= 6000 { self.entity.remove().await; } }) } fn get_entity(&self) -> &Entity { &self.entity } fn on_player_collision<'a>(&'a self, player: &'a Arc) -> EntityBaseFuture<'a, ()> { Box::pin(async move { if player.living_entity.health.load() > 0.0 { let mut delay = player.experience_pick_up_delay.lock().await; if *delay == 0 { *delay = 2; player.living_entity.pickup(&self.entity, 1); let remaining = player.apply_mending_from_xp(self.amount as i32).await; if remaining > 0 { player.add_experience_points(remaining).await; } // TODO: pickingCount for merging self.entity.remove().await; } } }) } fn get_living_entity(&self) -> Option<&LivingEntity> { None } fn as_nbt_storage(&self) -> &dyn NBTStorage { self } fn get_gravity(&self) -> f64 { 0.03 } fn cast_any(&self) -> &dyn std::any::Any { self } }