mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
Add Experience Orb and drop it when breaking Blocks
This commit is contained in:
@@ -8,8 +8,8 @@ use crate::VarInt;
|
||||
#[packet(PLAY_SET_EXPERIENCE)]
|
||||
pub struct CSetExperience {
|
||||
progress: f32,
|
||||
level: VarInt,
|
||||
total_experience: VarInt,
|
||||
level: VarInt,
|
||||
}
|
||||
|
||||
impl CSetExperience {
|
||||
|
||||
@@ -8,6 +8,8 @@ pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
bytes.workspace = true
|
||||
rand = "0.9"
|
||||
|
||||
|
||||
num-traits = "0.2"
|
||||
|
||||
|
||||
63
pumpkin-util/src/math/int_provider.rs
Normal file
63
pumpkin-util/src/math/int_provider.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NormalInvProvider {
|
||||
#[serde(rename = "minecraft:uniform")]
|
||||
Uniform(UniformIntProvider),
|
||||
// TODO: Add more...
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum InvProvider {
|
||||
Object(NormalInvProvider),
|
||||
Constant(i32),
|
||||
}
|
||||
|
||||
impl InvProvider {
|
||||
pub fn get_min(&self) -> i32 {
|
||||
match self {
|
||||
InvProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalInvProvider::Uniform(uniform) => uniform.get_min(),
|
||||
},
|
||||
InvProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> i32 {
|
||||
match self {
|
||||
InvProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalInvProvider::Uniform(uniform) => uniform.get(),
|
||||
},
|
||||
InvProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
match self {
|
||||
InvProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalInvProvider::Uniform(uniform) => uniform.get_max(),
|
||||
},
|
||||
InvProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct UniformIntProvider {
|
||||
min_inclusive: i32,
|
||||
max_inclusive: i32,
|
||||
}
|
||||
|
||||
impl UniformIntProvider {
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive
|
||||
}
|
||||
pub fn get(&self) -> i32 {
|
||||
rand::random_range(self.min_inclusive..self.max_inclusive)
|
||||
}
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use num_traits::{One, PrimInt, Zero};
|
||||
|
||||
pub mod boundingbox;
|
||||
pub mod experience;
|
||||
pub mod int_provider;
|
||||
pub mod position;
|
||||
pub mod vector2;
|
||||
pub mod vector3;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use pumpkin_util::math::int_provider::InvProvider;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::loot::LootTable;
|
||||
@@ -118,6 +119,7 @@ pub struct Block {
|
||||
pub id: u16,
|
||||
pub item_id: u16,
|
||||
pub hardness: f32,
|
||||
pub experience: Option<Experience>,
|
||||
pub blast_resistance: f32,
|
||||
pub wall_variant_id: Option<u16>,
|
||||
pub translation_key: String,
|
||||
@@ -127,6 +129,10 @@ pub struct Block {
|
||||
pub default_state_id: u16,
|
||||
pub states: Vec<State>,
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct Experience {
|
||||
pub experience: InvProvider,
|
||||
}
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct Property {
|
||||
pub name: String,
|
||||
|
||||
@@ -25,12 +25,12 @@ use pumpkin_world::block::registry::{Block, State};
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::block::blocks::jukebox::JukeboxBlock;
|
||||
use crate::block::registry::BlockRegistry;
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player};
|
||||
use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod blocks;
|
||||
@@ -52,7 +52,13 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
Arc::new(manager)
|
||||
}
|
||||
|
||||
pub async fn drop_loot(server: &Server, world: &Arc<World>, block: &Block, pos: &BlockPos) {
|
||||
pub async fn drop_loot(
|
||||
server: &Server,
|
||||
world: &Arc<World>,
|
||||
block: &Block,
|
||||
pos: &BlockPos,
|
||||
experience: bool,
|
||||
) {
|
||||
// TODO: Currently only the item block is dropped, We should drop the loop table
|
||||
let height = EntityType::ITEM.dimension[1] / 2.0;
|
||||
let pos = Vector3::new(
|
||||
@@ -68,6 +74,16 @@ pub async fn drop_loot(server: &Server, world: &Arc<World>, block: &Block, pos:
|
||||
));
|
||||
world.spawn_entity(item_entity.clone()).await;
|
||||
item_entity.send_meta_packet().await;
|
||||
|
||||
if experience {
|
||||
if let Some(experience) = &block.experience {
|
||||
let amount = experience.experience.get();
|
||||
// TODO: Silk touch gives no exp
|
||||
if amount > 0 {
|
||||
ExperienceOrbEntity::spawn(world, server, pos, amount as u32).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn calc_block_breaking(player: &Player, state: &State, block_name: &str) -> f32 {
|
||||
|
||||
98
pumpkin/src/entity/experience_orb.rs
Normal file
98
pumpkin/src/entity/experience_orb.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::sync::{Arc, atomic::AtomicU32};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::{damage::DamageType, entity::EntityType};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
use crate::{server::Server, world::World};
|
||||
|
||||
use super::{Entity, EntityBase, 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::<f32>() * 360.0);
|
||||
Self {
|
||||
entity,
|
||||
amount,
|
||||
orb_age: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn(world: &Arc<World>, server: &Server, position: Vector3<f64>, amount: u32) {
|
||||
let mut amount = amount;
|
||||
while amount > 0 {
|
||||
let i = Self::round_to_orb_size(amount);
|
||||
amount -= i;
|
||||
let entity = server.add_entity(position, EntityType::EXPERIENCE_ORB, world);
|
||||
let orb = Arc::new(Self::new(entity, i));
|
||||
world.spawn_entity(orb).await;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EntityBase for ExperienceOrbEntity {
|
||||
async fn tick(&self, _: &Server) {
|
||||
let age = self
|
||||
.orb_age
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if age >= 6000 {
|
||||
self.entity.remove().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_entity(&self) -> &Entity {
|
||||
&self.entity
|
||||
}
|
||||
|
||||
async fn on_player_collision(&self, player: Arc<Player>) {
|
||||
let mut delay = player.experience_pick_up_delay.lock().await;
|
||||
if *delay == 0 {
|
||||
*delay = 2;
|
||||
player.living_entity.pickup(&self.entity, 1).await;
|
||||
player.add_experience_points(self.amount as i32).await;
|
||||
// TODO: pickingCount for merging
|
||||
self.entity.remove().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::server::Server;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CTakeItemEntity, MetaDataType, Metadata},
|
||||
client::play::{MetaDataType, Metadata},
|
||||
codec::slot::Slot,
|
||||
};
|
||||
use pumpkin_world::item::ItemStack;
|
||||
@@ -23,6 +23,7 @@ pub struct ItemEntity {
|
||||
|
||||
impl ItemEntity {
|
||||
pub fn new(entity: Entity, stack: ItemStack) -> Self {
|
||||
entity.yaw.store(rand::random::<f32>() * 360.0);
|
||||
Self {
|
||||
entity,
|
||||
item: stack,
|
||||
@@ -85,12 +86,8 @@ impl EntityBase for ItemEntity {
|
||||
item.item_count = stack.item_count;
|
||||
|
||||
player
|
||||
.client
|
||||
.send_packet(&CTakeItemEntity::new(
|
||||
self.entity.entity_id.into(),
|
||||
player.entity_id().into(),
|
||||
item.item_count.into(),
|
||||
))
|
||||
.living_entity
|
||||
.pickup(&self.entity, u32::from(item.item_count))
|
||||
.await;
|
||||
self.entity.remove().await;
|
||||
}
|
||||
@@ -99,12 +96,8 @@ impl EntityBase for ItemEntity {
|
||||
item.item_count = self.count.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
player
|
||||
.client
|
||||
.send_packet(&CTakeItemEntity::new(
|
||||
self.entity.entity_id.into(),
|
||||
player.entity_id().into(),
|
||||
item.item_count.into(),
|
||||
))
|
||||
.living_entity
|
||||
.pickup(&self.entity, u32::from(item.item_count))
|
||||
.await;
|
||||
self.entity.remove().await;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use pumpkin_config::ADVANCED_CONFIG;
|
||||
use pumpkin_data::entity::{EffectType, EntityStatus};
|
||||
use pumpkin_data::{damage::DamageType, sound::Sound};
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_protocol::client::play::CHurtAnimation;
|
||||
use pumpkin_protocol::client::play::{CHurtAnimation, CTakeItemEntity};
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata},
|
||||
@@ -70,6 +70,21 @@ impl LivingEntity {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Picks up and Item entity or XP Orb
|
||||
pub async fn pickup(&self, item: &Entity, stack_amount: u32) {
|
||||
// TODO: Only nearby
|
||||
self.entity
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CTakeItemEntity::new(
|
||||
item.entity_id.into(),
|
||||
self.entity.entity_id.into(),
|
||||
stack_amount.into(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn set_pos(&self, position: Vector3<f64>) {
|
||||
self.last_pos.store(self.entity.pos.load());
|
||||
self.entity.set_pos(position);
|
||||
|
||||
@@ -35,6 +35,7 @@ use crate::world::World;
|
||||
|
||||
pub mod ai;
|
||||
pub mod effect;
|
||||
pub mod experience_orb;
|
||||
pub mod hunger;
|
||||
pub mod item;
|
||||
pub mod living;
|
||||
|
||||
@@ -147,6 +147,7 @@ pub struct Player {
|
||||
pub experience_progress: AtomicCell<f32>,
|
||||
/// The player's total experience points
|
||||
pub experience_points: AtomicI32,
|
||||
pub experience_pick_up_delay: Mutex<u32>,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
@@ -201,6 +202,7 @@ impl Player {
|
||||
packet_sequence: AtomicI32::new(-1),
|
||||
start_mining_time: AtomicI32::new(0),
|
||||
carried_item: AtomicCell::new(None),
|
||||
experience_pick_up_delay: Mutex::new(0),
|
||||
teleport_id_count: AtomicI32::new(0),
|
||||
mining: AtomicBool::new(false),
|
||||
mining_pos: Mutex::new(BlockPos(Vector3::new(0, 0, 0))),
|
||||
@@ -449,6 +451,12 @@ impl Player {
|
||||
))
|
||||
.await;
|
||||
}
|
||||
{
|
||||
let mut xp = self.experience_pick_up_delay.lock().await;
|
||||
if *xp > 0 {
|
||||
*xp -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
@@ -1050,8 +1058,8 @@ impl Player {
|
||||
self.client
|
||||
.send_packet(&CSetExperience::new(
|
||||
progress.clamp(0.0, 1.0),
|
||||
level.into(),
|
||||
points.into(),
|
||||
level.into(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
@@ -1140,7 +1148,7 @@ impl Player {
|
||||
let total_exp = experience::points_to_level(current_level) + current_points;
|
||||
let new_total_exp = total_exp + added_points;
|
||||
let (new_level, new_points) = experience::total_to_level_and_points(new_total_exp);
|
||||
let progress = experience::progress_in_level(new_level, new_points);
|
||||
let progress = experience::progress_in_level(new_points, new_level);
|
||||
self.set_experience(new_level, progress, new_points).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl Explosion {
|
||||
let block = world.get_block(&pos).await.unwrap();
|
||||
let pumpkin_block = server.block_registry.get_pumpkin_block(block);
|
||||
if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) {
|
||||
drop_loot(server, world, block, &pos).await;
|
||||
drop_loot(server, world, block, &pos, false).await;
|
||||
}
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
pumpkin_block.explode(block, world, pos, server).await;
|
||||
|
||||
@@ -1003,15 +1003,7 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a living entity to the world.
|
||||
///
|
||||
/// This function takes a living entity's UUID and an `Arc<LivingEntity>` reference.
|
||||
/// It inserts the living entity into the world's `current_living_entities` map using the UUID as the key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `uuid`: The unique UUID of the living entity to add.
|
||||
/// * `living_entity`: A `Arc<LivingEntity>` reference to the living entity object.
|
||||
/// Adds a entity to the world.
|
||||
pub async fn spawn_entity(&self, entity: Arc<dyn EntityBase>) {
|
||||
let base_entity = entity.get_entity();
|
||||
self.broadcast_packet_all(&base_entity.create_spawn_packet())
|
||||
@@ -1120,7 +1112,7 @@ impl World {
|
||||
);
|
||||
|
||||
if drop {
|
||||
block::drop_loot(server, self, block, position).await;
|
||||
block::drop_loot(server, self, block, position, true).await;
|
||||
}
|
||||
|
||||
match cause {
|
||||
|
||||
Reference in New Issue
Block a user