feat(bedrock): add item drops

This commit is contained in:
Alexander Medvedev 2026-06-07 14:34:54 +02:00
parent 3a5f2d0855
commit e47695d810
12 changed files with 200 additions and 27 deletions

18
Cargo.lock generated
View File

@ -219,9 +219,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
version = "2.12.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "block-buffer"
@ -490,7 +490,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -1170,7 +1170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -2199,7 +2199,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@ -3118,7 +3118,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -3441,7 +3441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@ -3560,7 +3560,7 @@ dependencies = [
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -4614,7 +4614,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]

View File

@ -119,7 +119,7 @@ proc-macro-error2 = { version = "2", default-features = false }
aes = { version = "0.9", default-features = false }
async-compression = { version = "0.4.42", default-features = false }
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
bitflags = { version = "2.12.1", default-features = false, features = ["std"] }
bitflags = { version = "2.13.0", default-features = false, features = ["std"] }
cesu8 = { version = "1.1", default-features = false }
cfb8 = { version = "0.9", default-features = false }
colored = { version = "3.1", default-features = false }

View File

@ -0,0 +1,34 @@
use crate::{
codec::{var_long::VarLong, var_ulong::VarULong},
serial::PacketWrite,
};
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use std::io::{Error, Write};
use super::set_actor_data::EntityMetadata;
use crate::bedrock::network_item::NetworkItemStackDescriptor;
#[packet(15)]
pub struct CAddItemActor {
pub entity_unique_id: VarLong,
pub entity_runtime_id: VarULong,
pub item: NetworkItemStackDescriptor,
pub position: Vector3<f32>,
pub velocity: Vector3<f32>,
pub metadata: EntityMetadata,
pub from_fishing: bool,
}
impl PacketWrite for CAddItemActor {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.entity_unique_id.write(writer)?;
self.entity_runtime_id.write(writer)?;
self.item.write(writer)?;
self.position.write(writer)?;
self.velocity.write(writer)?;
self.metadata.write(writer)?;
self.from_fishing.write(writer)?;
Ok(())
}
}

View File

@ -1,4 +1,5 @@
pub mod add_actor;
pub mod add_item_actor;
pub mod add_player;
pub mod available_commands;
pub mod boss_event;
@ -42,6 +43,7 @@ pub mod update_attributes;
pub mod update_block;
pub use add_actor::*;
pub use add_item_actor::*;
pub use add_player::*;
pub use available_commands::*;
pub use boss_event::*;

View File

@ -79,6 +79,12 @@ impl NetworkItemDescriptor {
}
}
impl From<&ItemStack> for NetworkItemDescriptor {
fn from(stack: &ItemStack) -> Self {
NetworkItemStackDescriptor::from(stack).item
}
}
#[derive(Default, Clone)]
pub struct NetworkItemStackDescriptor {
// I hate mojang

View File

@ -522,6 +522,21 @@ impl<T> From<Vector3<T>> for (T, T, T) {
}
}
impl Vector3<f64> {
/// Converts this vector to a `Vector3<f32>` (lossy).
///
/// # Returns
/// A new `Vector3<f32>` with each component cast to `f32`.
#[must_use]
pub const fn to_f32_lossy(&self) -> Vector3<f32> {
Vector3 {
x: self.x as f32,
y: self.y as f32,
z: self.z as f32,
}
}
}
impl<T: Math + Copy + Into<f64>> Vector3<T> {
/// Converts this vector to a `Vector3<f64>`.
///

View File

@ -4,9 +4,12 @@ use pumpkin_data::data_component_impl::DamageResistantImpl;
use pumpkin_data::data_component_impl::DamageResistantType;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::{damage::DamageType, meta_data_type::MetaDataType, tracked_data::TrackedData};
use pumpkin_protocol::{
codec::item_stack_seralizer::ItemStackSerializer, java::client::play::Metadata,
};
use pumpkin_protocol::bedrock::client::CAddItemActor;
use pumpkin_protocol::bedrock::network_item::NetworkItemStackDescriptor;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_protocol::codec::var_long::VarLong;
use pumpkin_protocol::codec::var_ulong::VarULong;
use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::atomic_f32::AtomicF32;
use pumpkin_util::math::vector3::Vector3;
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
@ -503,4 +506,25 @@ impl EntityBase for ItemEntity {
fn cast_any(&self) -> &dyn std::any::Any {
self
}
fn send_bedrock_spawn_packet<'a>(
&'a self,
client: &'a crate::net::bedrock::BedrockClient,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let entity = &self.entity;
let runtime_id = entity.entity_id as u64;
let item_stack = self.item_stack.lock().await;
let packet = CAddItemActor {
entity_unique_id: VarLong(runtime_id as i64),
entity_runtime_id: VarULong(runtime_id),
item: NetworkItemStackDescriptor::from(&*item_stack),
position: entity.pos.load().to_f32_lossy(),
velocity: entity.velocity.load().to_f32_lossy(),
metadata: entity.bedrock_metadata(),
from_fishing: false,
};
client.send_game_packet(&packet).await;
})
}
}

View File

@ -1,6 +1,6 @@
use crate::entity::item::ItemEntity;
use crate::net::ClientPlatform;
use crate::{
entity::item::ItemEntity,
net::{ClientPlatform, bedrock::BedrockClient},
server::Server,
world::{
World,
@ -32,7 +32,8 @@ use pumpkin_data::{
sound::{Sound, SoundCategory},
};
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_protocol::bedrock::client::CSetActorMotion;
use pumpkin_protocol::bedrock::client::{CAddActor, CSetActorMotion};
use pumpkin_protocol::codec::var_long::VarLong;
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
use pumpkin_protocol::{
PositionFlag,
@ -248,6 +249,35 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
None
}
fn send_bedrock_spawn_packet<'a>(
&'a self,
client: &'a BedrockClient,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let entity = self.get_entity();
let runtime_id = entity.entity_id as u64;
let packet = CAddActor::new(
VarLong(runtime_id as i64),
VarULong(runtime_id),
self.get_entity().entity_type.resource_name.to_string(),
entity.pos.load().to_f32_lossy(),
entity.velocity.load().to_f32_lossy(),
entity.pitch.load(),
entity.yaw.load(),
entity.head_yaw.load(),
entity.body_yaw.load(),
Vec::new(),
entity.bedrock_metadata(),
PropertySyncData {
int_properties: std::collections::HashMap::new(),
float_properties: std::collections::HashMap::new(),
},
Vec::new(),
);
client.send_game_packet(&packet).await;
})
}
fn damage_with_context<'a>(
&'a self,
caller: &'a dyn EntityBase,

View File

@ -662,6 +662,13 @@ impl BedrockClient {
self.close_token.is_cancelled()
}
pub fn enqueue_spawn_packet(self: &Arc<Self>, entity: Arc<dyn crate::entity::EntityBase>) {
let client = self.clone();
self.spawn_task(async move {
entity.send_bedrock_spawn_packet(&client).await;
});
}
pub async fn send_acknowledgement(&self, ack: &Acknowledge, id: u8) -> Result<(), Error> {
let mut packet_buf = Vec::new();
ack.write(&mut packet_buf, id)?;

View File

@ -497,6 +497,7 @@ impl BedrockClient {
}
#[expect(clippy::match_same_arms)]
#[expect(clippy::too_many_lines)]
pub async fn handle_player_action(
&self,
player: &Arc<Player>,
@ -509,7 +510,9 @@ impl BedrockClient {
player.update_last_action_time();
match packet.action {
PlayerAction::StartBreak | PlayerAction::CreativePlayerDestroyBlock => {
PlayerAction::StartBreak
| PlayerAction::CreativePlayerDestroyBlock
| PlayerAction::ContinueDestroyBlock => {
let location = packet.block_pos;
if !player.can_interact_with_block_at(&location, 1.0) {
return;
@ -534,9 +537,6 @@ impl BedrockClient {
.await;
}
} else if !state.is_air() {
// Broadcast that breaking started
world.set_block_breaking(entity, location, 0).await;
let speed = crate::block::calc_block_breaking(player, state, block).await;
if speed >= 1.0 {
let broken_state = world.get_block_state(&location);
@ -565,11 +565,47 @@ impl BedrockClient {
}
}
}
PlayerAction::PredictDestroyBlock | PlayerAction::StopBreak => {
let location = packet.block_pos;
if !player.can_interact_with_block_at(&location, 1.0) {
return;
}
let entity = &player.get_entity();
let world = entity.world.load_full();
player.mining.store(false, Ordering::Relaxed);
world.set_block_breaking(entity, location, -1).await;
let (block, state) = world.get_block_and_state(&location);
if player.gamemode.load() != GameMode::Creative {
let block_drop = player.can_harvest(state, block).await;
let new_state = world
.break_block(
&location,
Some(player.clone()),
if block_drop {
BlockFlags::NOTIFY_NEIGHBORS
} else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
},
)
.await;
if new_state.is_some() {
server
.block_registry
.broken(&world, block, player, &location, server, state)
.await;
player.apply_tool_damage_for_block_break(state).await;
}
}
}
PlayerAction::CrackBreak => {
// Don't do anything for this action. It is no longer used. Block
// cracking is done fully server-side.
}
PlayerAction::AbortBreak | PlayerAction::StopBreak => {
PlayerAction::AbortBreak => {
let location = packet.block_pos;
let entity = &player.get_entity();
let world = entity.world.load();

View File

@ -242,6 +242,13 @@ impl ClientPlatform {
}
}
pub fn try_enqueue_spawn_packet(&self, entity: &Arc<dyn crate::entity::EntityBase>) {
match self {
Self::Java(java) => java.try_enqueue_packet(&entity.get_entity().create_spawn_packet()),
Self::Bedrock(bedrock) => bedrock.enqueue_spawn_packet(entity.clone()),
}
}
pub async fn send_chunks(&self, chunks: &[SyncChunk]) {
match self {
Self::Java(java) => java.send_chunks(chunks).await,

View File

@ -3722,9 +3722,8 @@ impl World {
}
pub fn spawn_entity_non_save(&self, entity: &Arc<dyn EntityBase>) {
let base_entity = entity.get_entity();
let chunk_pos = base_entity.chunk_pos.load();
self.broadcast_to_chunk(chunk_pos, &base_entity.create_spawn_packet());
let _base_entity = entity.get_entity();
self.broadcast_entity_spawn(entity);
self.spawn_state.load().add_entity(self, entity.as_ref());
self.entities.rcu(|current_entities| {
@ -3735,13 +3734,26 @@ impl World {
}
pub async fn spawn_entity(&self, entity: Arc<dyn EntityBase>) {
let base_entity = entity.get_entity();
let chunk_pos = base_entity.chunk_pos.load();
self.broadcast_to_chunk(chunk_pos, &base_entity.create_spawn_packet());
self.broadcast_entity_spawn(&entity);
entity.init_data_tracker().await;
self.add_entity_silent(entity).await;
}
pub fn broadcast_entity_spawn(&self, entity: &Arc<dyn EntityBase>) {
let base_entity = entity.get_entity();
let chunk_pos = base_entity.chunk_pos.load();
let players = self.players.load();
for player in players.iter() {
let center = player.get_entity().chunk_pos.load();
let view_distance = get_view_distance(player).get() as i32;
if is_within_view_distance(chunk_pos, center, view_distance) {
player.client.try_enqueue_spawn_packet(entity);
}
}
}
pub async fn add_entity_silent(&self, entity: Arc<dyn EntityBase>) {
let base_entity = entity.get_entity();