Add Experience Orb and drop it when breaking Blocks
This commit is contained in:
parent
27f239a612
commit
a60a3ba243
@ -8,8 +8,8 @@ use crate::VarInt;
|
|||||||
#[packet(PLAY_SET_EXPERIENCE)]
|
#[packet(PLAY_SET_EXPERIENCE)]
|
||||||
pub struct CSetExperience {
|
pub struct CSetExperience {
|
||||||
progress: f32,
|
progress: f32,
|
||||||
level: VarInt,
|
|
||||||
total_experience: VarInt,
|
total_experience: VarInt,
|
||||||
|
level: VarInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CSetExperience {
|
impl CSetExperience {
|
||||||
|
|||||||
@ -8,6 +8,8 @@ pumpkin-nbt = { path = "../pumpkin-nbt" }
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
|
rand = "0.9"
|
||||||
|
|
||||||
|
|
||||||
num-traits = "0.2"
|
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 boundingbox;
|
||||||
pub mod experience;
|
pub mod experience;
|
||||||
|
pub mod int_provider;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
pub mod vector2;
|
pub mod vector2;
|
||||||
pub mod vector3;
|
pub mod vector3;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use pumpkin_util::math::int_provider::InvProvider;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::loot::LootTable;
|
use crate::loot::LootTable;
|
||||||
@ -118,6 +119,7 @@ pub struct Block {
|
|||||||
pub id: u16,
|
pub id: u16,
|
||||||
pub item_id: u16,
|
pub item_id: u16,
|
||||||
pub hardness: f32,
|
pub hardness: f32,
|
||||||
|
pub experience: Option<Experience>,
|
||||||
pub blast_resistance: f32,
|
pub blast_resistance: f32,
|
||||||
pub wall_variant_id: Option<u16>,
|
pub wall_variant_id: Option<u16>,
|
||||||
pub translation_key: String,
|
pub translation_key: String,
|
||||||
@ -127,6 +129,10 @@ pub struct Block {
|
|||||||
pub default_state_id: u16,
|
pub default_state_id: u16,
|
||||||
pub states: Vec<State>,
|
pub states: Vec<State>,
|
||||||
}
|
}
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub struct Experience {
|
||||||
|
pub experience: InvProvider,
|
||||||
|
}
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub struct Property {
|
pub struct Property {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@ -25,12 +25,12 @@ use pumpkin_world::block::registry::{Block, State};
|
|||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::block::blocks::jukebox::JukeboxBlock;
|
|
||||||
use crate::block::registry::BlockRegistry;
|
use crate::block::registry::BlockRegistry;
|
||||||
use crate::entity::item::ItemEntity;
|
use crate::entity::item::ItemEntity;
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player};
|
use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player};
|
||||||
|
use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
mod blocks;
|
mod blocks;
|
||||||
@ -52,7 +52,13 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
|||||||
Arc::new(manager)
|
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
|
// TODO: Currently only the item block is dropped, We should drop the loop table
|
||||||
let height = EntityType::ITEM.dimension[1] / 2.0;
|
let height = EntityType::ITEM.dimension[1] / 2.0;
|
||||||
let pos = Vector3::new(
|
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;
|
world.spawn_entity(item_entity.clone()).await;
|
||||||
item_entity.send_meta_packet().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 {
|
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 async_trait::async_trait;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
client::play::{CTakeItemEntity, MetaDataType, Metadata},
|
client::play::{MetaDataType, Metadata},
|
||||||
codec::slot::Slot,
|
codec::slot::Slot,
|
||||||
};
|
};
|
||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
@ -23,6 +23,7 @@ pub struct ItemEntity {
|
|||||||
|
|
||||||
impl ItemEntity {
|
impl ItemEntity {
|
||||||
pub fn new(entity: Entity, stack: ItemStack) -> Self {
|
pub fn new(entity: Entity, stack: ItemStack) -> Self {
|
||||||
|
entity.yaw.store(rand::random::<f32>() * 360.0);
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
item: stack,
|
item: stack,
|
||||||
@ -85,12 +86,8 @@ impl EntityBase for ItemEntity {
|
|||||||
item.item_count = stack.item_count;
|
item.item_count = stack.item_count;
|
||||||
|
|
||||||
player
|
player
|
||||||
.client
|
.living_entity
|
||||||
.send_packet(&CTakeItemEntity::new(
|
.pickup(&self.entity, u32::from(item.item_count))
|
||||||
self.entity.entity_id.into(),
|
|
||||||
player.entity_id().into(),
|
|
||||||
item.item_count.into(),
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
self.entity.remove().await;
|
self.entity.remove().await;
|
||||||
}
|
}
|
||||||
@ -99,12 +96,8 @@ impl EntityBase for ItemEntity {
|
|||||||
item.item_count = self.count.load(std::sync::atomic::Ordering::Relaxed);
|
item.item_count = self.count.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
player
|
player
|
||||||
.client
|
.living_entity
|
||||||
.send_packet(&CTakeItemEntity::new(
|
.pickup(&self.entity, u32::from(item.item_count))
|
||||||
self.entity.entity_id.into(),
|
|
||||||
player.entity_id().into(),
|
|
||||||
item.item_count.into(),
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
self.entity.remove().await;
|
self.entity.remove().await;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use pumpkin_config::ADVANCED_CONFIG;
|
|||||||
use pumpkin_data::entity::{EffectType, EntityStatus};
|
use pumpkin_data::entity::{EffectType, EntityStatus};
|
||||||
use pumpkin_data::{damage::DamageType, sound::Sound};
|
use pumpkin_data::{damage::DamageType, sound::Sound};
|
||||||
use pumpkin_nbt::tag::NbtTag;
|
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::codec::var_int::VarInt;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata},
|
client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata},
|
||||||
@ -70,6 +70,21 @@ impl LivingEntity {
|
|||||||
.await;
|
.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>) {
|
pub fn set_pos(&self, position: Vector3<f64>) {
|
||||||
self.last_pos.store(self.entity.pos.load());
|
self.last_pos.store(self.entity.pos.load());
|
||||||
self.entity.set_pos(position);
|
self.entity.set_pos(position);
|
||||||
|
|||||||
@ -35,6 +35,7 @@ use crate::world::World;
|
|||||||
|
|
||||||
pub mod ai;
|
pub mod ai;
|
||||||
pub mod effect;
|
pub mod effect;
|
||||||
|
pub mod experience_orb;
|
||||||
pub mod hunger;
|
pub mod hunger;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
pub mod living;
|
pub mod living;
|
||||||
|
|||||||
@ -147,6 +147,7 @@ pub struct Player {
|
|||||||
pub experience_progress: AtomicCell<f32>,
|
pub experience_progress: AtomicCell<f32>,
|
||||||
/// The player's total experience points
|
/// The player's total experience points
|
||||||
pub experience_points: AtomicI32,
|
pub experience_points: AtomicI32,
|
||||||
|
pub experience_pick_up_delay: Mutex<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
@ -201,6 +202,7 @@ impl Player {
|
|||||||
packet_sequence: AtomicI32::new(-1),
|
packet_sequence: AtomicI32::new(-1),
|
||||||
start_mining_time: AtomicI32::new(0),
|
start_mining_time: AtomicI32::new(0),
|
||||||
carried_item: AtomicCell::new(None),
|
carried_item: AtomicCell::new(None),
|
||||||
|
experience_pick_up_delay: Mutex::new(0),
|
||||||
teleport_id_count: AtomicI32::new(0),
|
teleport_id_count: AtomicI32::new(0),
|
||||||
mining: AtomicBool::new(false),
|
mining: AtomicBool::new(false),
|
||||||
mining_pos: Mutex::new(BlockPos(Vector3::new(0, 0, 0))),
|
mining_pos: Mutex::new(BlockPos(Vector3::new(0, 0, 0))),
|
||||||
@ -449,6 +451,12 @@ impl Player {
|
|||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let mut xp = self.experience_pick_up_delay.lock().await;
|
||||||
|
if *xp > 0 {
|
||||||
|
*xp -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
@ -1050,8 +1058,8 @@ impl Player {
|
|||||||
self.client
|
self.client
|
||||||
.send_packet(&CSetExperience::new(
|
.send_packet(&CSetExperience::new(
|
||||||
progress.clamp(0.0, 1.0),
|
progress.clamp(0.0, 1.0),
|
||||||
level.into(),
|
|
||||||
points.into(),
|
points.into(),
|
||||||
|
level.into(),
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -1140,7 +1148,7 @@ impl Player {
|
|||||||
let total_exp = experience::points_to_level(current_level) + current_points;
|
let total_exp = experience::points_to_level(current_level) + current_points;
|
||||||
let new_total_exp = total_exp + added_points;
|
let new_total_exp = total_exp + added_points;
|
||||||
let (new_level, new_points) = experience::total_to_level_and_points(new_total_exp);
|
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;
|
self.set_experience(new_level, progress, new_points).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ impl Explosion {
|
|||||||
let block = world.get_block(&pos).await.unwrap();
|
let block = world.get_block(&pos).await.unwrap();
|
||||||
let pumpkin_block = server.block_registry.get_pumpkin_block(block);
|
let pumpkin_block = server.block_registry.get_pumpkin_block(block);
|
||||||
if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) {
|
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 {
|
if let Some(pumpkin_block) = pumpkin_block {
|
||||||
pumpkin_block.explode(block, world, pos, server).await;
|
pumpkin_block.explode(block, world, pos, server).await;
|
||||||
|
|||||||
@ -1003,15 +1003,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a living entity to the world.
|
/// Adds a 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.
|
|
||||||
pub async fn spawn_entity(&self, entity: Arc<dyn EntityBase>) {
|
pub async fn spawn_entity(&self, entity: Arc<dyn EntityBase>) {
|
||||||
let base_entity = entity.get_entity();
|
let base_entity = entity.get_entity();
|
||||||
self.broadcast_packet_all(&base_entity.create_spawn_packet())
|
self.broadcast_packet_all(&base_entity.create_spawn_packet())
|
||||||
@ -1120,7 +1112,7 @@ impl World {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if drop {
|
if drop {
|
||||||
block::drop_loot(server, self, block, position).await;
|
block::drop_loot(server, self, block, position, true).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
match cause {
|
match cause {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user