feat(bedrock): add creative content

This commit is contained in:
Alexander Medvedev
2026-06-19 18:52:50 +02:00
parent 02f6c8994e
commit 8ce76261ac
23 changed files with 15201 additions and 142 deletions

33
Cargo.lock generated
View File

@@ -270,9 +270,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.11.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]]
name = "cap-fs-ext"
@@ -471,9 +471,9 @@ dependencies = [
[[package]]
name = "cmov"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
[[package]]
name = "cobs"
@@ -919,9 +919,9 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3"
checksum = "97bb4a855e3b10f84c4e7e895a7de01db7f9a7b7eb7f73ed9773fd52ac686451"
dependencies = [
"cpubits",
"ctutils",
@@ -956,7 +956,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a"
dependencies = [
"crypto-bigint 0.7.3",
"crypto-bigint 0.7.4",
"libm",
"rand_core 0.10.1",
]
@@ -1890,6 +1890,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -2841,7 +2850,7 @@ dependencies = [
"base64 0.22.1",
"bytes",
"colored",
"crypto-bigint 0.7.3",
"crypto-bigint 0.7.4",
"ecdsa",
"enum_dispatch",
"md5",
@@ -2877,7 +2886,7 @@ dependencies = [
"enum_dispatch",
"flate2",
"futures",
"itertools 0.14.0",
"itertools 0.15.0",
"lz4-java-wrc",
"pumpkin-config",
"pumpkin-data",
@@ -3067,7 +3076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
dependencies = [
"const-oid 0.10.2",
"crypto-bigint 0.7.3",
"crypto-bigint 0.7.4",
"crypto-primes",
"digest 0.11.3",
"pkcs1",
@@ -3950,9 +3959,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.20.0"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ucd-trie"

View File

@@ -102,7 +102,7 @@ syn = { version = "2.0", default-features = false, features = ["printing"] }
thiserror = { version = "2.0", default-features = false }
bytes = { version = "1.11", default-features = false, features = ["std"] }
bytes = { version = "1.12", default-features = false, features = ["std"] }
# Concurrency/Parallelism and Synchronization
futures = { version = "0.3", default-features = false, features = ["executor"] }
@@ -128,7 +128,7 @@ crc-fast = { version = "1.10.0", default-features = false, features = ["std"] }
criterion = { version = "0.8", default-features = false }
crossbeam-utils = { version = "0.8.21", default-features = false, features = ["std"] }
crossfire = { version = "3.1.16", default-features = false, features = ["compat"] }
crypto-bigint = { version = "0.7.3", default-features = false }
crypto-bigint = { version = "0.7.4", default-features = false }
dashmap = { version = "6.2", default-features = false }
ecdsa = { version = "0.16.9", default-features = false, features = ["std"] }
either = { version = "1.16.0", default-features = false }
@@ -137,7 +137,7 @@ flate2 = { version = "1.1.9", default-features = false, features = ["rust_backen
heck = { version = "0.5", default-features = false }
hmac = { version = "=0.13.0", default-features = false }
indexmap = { version = "2.14", default-features = false, features = ["std"] }
itertools = { version = "0.14.0", default-features = false, features = ["use_std"] }
itertools = { version = "0.15.0", default-features = false, features = ["use_std"] }
libloading = { version = "0.9", default-features = false, features = ["std"] }
lru = { version = "0.18.0", default-features = false }
lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] }

View File

@@ -0,0 +1,131 @@
use std::{collections::HashMap, fs, io::Cursor};
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
use serde_repr::Deserialize_repr;
#[derive(Deserialize_repr, Debug)]
#[repr(i32)]
enum BedrockItemVersion {
Legacy = 0,
DataDriven = 1,
None = 2,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct BedrockRuntimeItemState {
name: String,
id: i16,
version: BedrockItemVersion,
component_based: bool,
}
pub fn build() -> TokenStream {
let be_runtime_item_states: Vec<BedrockRuntimeItemState> = serde_json::from_str(
&fs::read_to_string("../assets/bedrock/runtime_item_states.json").unwrap(),
)
.expect("Failed to parse bedrock/runtime_item_states.json");
let bedrock_items_map: HashMap<String, i16> = be_runtime_item_states
.into_iter()
.map(|item| (item.name, item.id))
.collect();
let nbt_bytes = fs::read("../assets/bedrock/creative_items.nbt")
.expect("Failed to read bedrock/creative_items.nbt");
let mut cursor = Cursor::new(nbt_bytes);
#[derive(serde::Deserialize)]
struct CreativeGroupEntry {
category: i32,
name: String,
icon: Option<CreativeItemEntry>,
}
#[derive(serde::Deserialize)]
struct CreativeItemEntry {
name: String,
meta: Option<i16>,
group_index: Option<i32>,
}
#[derive(serde::Deserialize)]
struct CreativeData {
groups: Vec<CreativeGroupEntry>,
items: Vec<CreativeItemEntry>,
}
let data: CreativeData = pumpkin_nbt::deserializer::from_bytes_bedrock(&mut cursor)
.expect("Failed to deserialize creative_items.nbt");
let mut group_tokens = Vec::new();
for g in &data.groups {
let category = g.category;
let name = &g.name;
let mut icon_item_id = 0i16;
let mut icon_item_aux_value = 0u32;
if let Some(ref icon) = g.icon {
if let Some(&id) = bedrock_items_map.get(icon.name.as_str()) {
icon_item_id = id;
icon_item_aux_value = icon.meta.unwrap_or(0) as u32;
}
}
group_tokens.push(quote! {
CreativeGroup {
category: #category,
name: #name,
icon_item_id: #icon_item_id,
icon_item_aux_value: #icon_item_aux_value,
}
});
}
// 4. Build entry definitions TokenStream
let mut entry_tokens = Vec::new();
for item in &data.items {
if let Some(&id) = bedrock_items_map.get(item.name.as_str()) {
let item_aux_value = item.meta.unwrap_or(0) as u32;
let group_index = item.group_index.unwrap_or(0) as u32;
entry_tokens.push(quote! {
CreativeEntry {
item_id: #id,
item_aux_value: #item_aux_value,
group_index: #group_index,
}
});
}
}
let groups_len = group_tokens.len();
let entries_len = entry_tokens.len();
quote! {
#[derive(Clone, Copy, Debug)]
pub struct CreativeGroup {
pub category: i32,
pub name: &'static str,
pub icon_item_id: i16,
pub icon_item_aux_value: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct CreativeEntry {
pub item_id: i16,
pub item_aux_value: u32,
pub group_index: u32,
}
pub const CREATIVE_GROUPS: &[CreativeGroup; #groups_len] = &[
#(#group_tokens),*
];
pub const CREATIVE_ENTRIES: &[CreativeEntry; #entries_len] = &[
#(#entry_tokens),*
];
}
}

View File

@@ -1146,6 +1146,11 @@ pub fn build() -> TokenStream {
all_java_item_ids.push(id_lit);
}
let bedrock_id_by_key: std::collections::HashMap<String, i16> = bedrock_item_definitions
.iter()
.map(|item| (item.registry_key.clone(), item.id))
.collect();
for item in bedrock_item_definitions {
let const_ident = format_ident!(
"{}",
@@ -1211,6 +1216,35 @@ pub fn build() -> TokenStream {
});
}
let mut bedrock_to_java_arms = TokenStream::new();
let mut seen_bedrock_pairs = std::collections::HashSet::new();
let mut bedrock_id_fallback_arms = TokenStream::new();
let mut seen_bedrock_ids = std::collections::HashSet::new();
for (java_name, mapping) in &be_item_remaps {
let java_const_ident = format_ident!("{}", java_name.to_shouty_snake_case());
let bedrock_id = *bedrock_id_by_key.get(&mapping.identifier).unwrap_or_else(|| {
panic!("Missing bedrock item for identifier: {}", mapping.identifier)
});
let bedrock_data = mapping.data;
if seen_bedrock_pairs.insert((bedrock_id, bedrock_data)) {
let id_lit = syn::LitInt::new(&bedrock_id.to_string(), proc_macro2::Span::call_site());
let data_lit = syn::LitInt::new(&bedrock_data.to_string(), proc_macro2::Span::call_site());
bedrock_to_java_arms.extend(quote! {
(#id_lit, #data_lit) => Some(&Self::#java_const_ident),
});
}
if seen_bedrock_ids.insert(bedrock_id) {
let id_lit = syn::LitInt::new(&bedrock_id.to_string(), proc_macro2::Span::call_site());
bedrock_id_fallback_arms.extend(quote! {
#id_lit => Some(&Self::#java_const_ident),
});
}
}
quote! {
#[allow(clippy::wildcard_imports, clippy::enum_glob_use, clippy::too_many_lines)]
use crate::data_component::DataComponent::*;
@@ -1371,6 +1405,17 @@ pub fn build() -> TokenStream {
_ => None,
}
}
#[must_use]
pub fn from_bedrock(bedrock_id: i16, bedrock_data: u32) -> Option<&'static Self> {
match (bedrock_id, bedrock_data) {
#bedrock_to_java_arms
_ => match bedrock_id {
#bedrock_id_fallback_arms
_ => None,
}
}
}
}
}
}

View File

@@ -11,6 +11,7 @@ use std::{
mod advancement;
mod attributes;
mod bedrock_creative;
mod biome;
mod bitsets;
mod block;
@@ -78,6 +79,7 @@ pub fn main() {
let mut build_functions: Vec<(BuilderFn, &str)> = vec![
(advancement::build, "advancement.rs"),
(bedrock_creative::build, "bedrock_creative.rs"),
(packet::build, "packet.rs"),
(screen::build, "screen.rs"),
(particle::build, "particle.rs"),

View File

@@ -47,10 +47,12 @@ default = [
"villager",
"placed_feature",
"configured_feature",
"slot_ranges"
"slot_ranges",
"bedrock_creative"
]
advancement = []
bedrock_creative = []
item = []
packet = []
jukebox_song = []

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -250,6 +250,11 @@ pub mod entity_id_remap;
#[path = "generated/sound_id_remap.rs"]
pub mod sound_id_remap;
#[cfg(feature = "bedrock_creative")]
#[rustfmt::skip]
#[path = "generated/bedrock_creative.rs"]
pub mod bedrock_creative;
#[cfg(feature = "tag")]
#[rustfmt::skip]
#[path = "generated/tag.rs"]

View File

@@ -13,7 +13,7 @@ query = []
[dependencies]
pumpkin-nbt.workspace = true
pumpkin-data = { workspace = true, features = ["packet", "item_id_remap", "entity_id_remap", "sound_id_remap", "recipes"] }
pumpkin-data = { workspace = true, features = ["packet", "item_id_remap", "entity_id_remap", "sound_id_remap", "recipes", "bedrock_creative"] }
pumpkin-macros.workspace = true
pumpkin-world.workspace = true
pumpkin-util.workspace = true

View File

@@ -0,0 +1,62 @@
use std::io::{Error, Write};
use crate::{
bedrock::network_item::FullContainerName,
codec::{var_int::VarInt, var_uint::VarUInt},
serial::PacketWrite,
};
use pumpkin_macros::packet;
#[derive(PacketWrite, Debug, Clone)]
pub struct ItemStackResponseSlotInfo {
pub slot: u8,
pub hotbar_slot: u8,
pub count: u8,
pub item_stack_id: VarInt,
pub custom_name: String,
pub filtered_custom_name: String,
pub durability_correction: VarInt,
}
#[derive(PacketWrite, Debug, Clone)]
pub struct ItemStackResponseContainerInfo {
pub container_name: FullContainerName,
pub slots: Vec<ItemStackResponseSlotInfo>,
}
#[derive(Debug, Clone)]
pub struct ItemStackResponse {
pub result: u8, // 0 = SUCCESS, 1 = ERROR
pub request_id: VarInt,
pub container_infos: Vec<ItemStackResponseContainerInfo>,
}
impl PacketWrite for ItemStackResponse {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.result.write(writer)?;
self.request_id.write(writer)?;
if self.result == 0 {
VarUInt(self.container_infos.len() as u32).write(writer)?;
for info in &self.container_infos {
info.write(writer)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[packet(148)]
pub struct CItemStackResponse {
pub responses: Vec<ItemStackResponse>,
}
impl PacketWrite for CItemStackResponse {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarUInt(self.responses.len() as u32).write(writer)?;
for response in &self.responses {
response.write(writer)?;
}
Ok(())
}
}

View File

@@ -13,6 +13,7 @@ pub mod gamerules_changed;
pub mod handshake;
pub mod inventory_content;
pub mod item_registry;
pub mod item_stack_response;
pub mod level_chunk;
pub mod level_event;
pub mod modal_form_request;
@@ -56,6 +57,8 @@ pub use disconnect_player::*;
pub use gamerules_changed::*;
pub use handshake::*;
pub use inventory_content::*;
pub use item_registry::*;
pub use item_stack_response::*;
pub use level_chunk::*;
pub use level_event::*;
pub use modal_form_request::*;

View File

@@ -115,7 +115,7 @@ impl From<&ItemStack> for NetworkItemDescriptor {
}
}
#[derive(Default, Clone)]
#[derive(Default, Clone, Debug)]
pub struct NetworkItemStackDescriptor {
// I hate mojang
// https://mojang.github.io/bedrock-protocol-docs/html/cerealizer_NetworkItemStackDescriptor___SerializedData.html
@@ -130,6 +130,13 @@ impl PacketWrite for NetworkItemStackDescriptor {
}
}
impl PacketRead for NetworkItemStackDescriptor {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let item = NetworkItemDescriptor::read(buf)?;
Ok(Self { item, net_id: None })
}
}
impl From<&ItemStack> for NetworkItemStackDescriptor {
fn from(stack: &ItemStack) -> Self {
if stack.is_empty() {
@@ -155,13 +162,13 @@ impl From<&ItemStack> for NetworkItemStackDescriptor {
}
}
#[derive(PacketWrite, Clone)]
#[derive(PacketWrite, PacketRead, Clone, Debug, PartialEq, Eq)]
pub struct FullContainerName {
pub container_name: ContainerName,
pub dynamic_id: Option<u32>,
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ContainerName {
AnvilInput,
@@ -233,9 +240,135 @@ pub enum ContainerName {
RecipeFurnaceItems,
}
impl TryFrom<u8> for ContainerName {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::AnvilInput),
1 => Ok(Self::AnvilMaterial),
2 => Ok(Self::AnvilResultPreview),
3 => Ok(Self::SmithingTableInput),
4 => Ok(Self::SmithingTableMaterial),
5 => Ok(Self::SmithingTableResultPreview),
6 => Ok(Self::Armor),
7 => Ok(Self::LevelEntity),
8 => Ok(Self::BeaconPayment),
9 => Ok(Self::BrewingStandInput),
10 => Ok(Self::BrewingStandResult),
11 => Ok(Self::BrewingStandFuel),
12 => Ok(Self::CombinedHotBarAndInventory),
13 => Ok(Self::CraftingInput),
14 => Ok(Self::CraftingOutputPreview),
15 => Ok(Self::RecipeConstruction),
16 => Ok(Self::RecipeNature),
17 => Ok(Self::RecipeItems),
18 => Ok(Self::RecipeSearch),
19 => Ok(Self::RecipeSearchBar),
20 => Ok(Self::RecipeEquipment),
21 => Ok(Self::RecipeBook),
22 => Ok(Self::EnchantingInput),
23 => Ok(Self::EnchantingMaterial),
24 => Ok(Self::FurnaceFuel),
25 => Ok(Self::FurnaceIngredient),
26 => Ok(Self::FurnaceResult),
27 => Ok(Self::HorseEquip),
28 => Ok(Self::HotBar),
29 => Ok(Self::Inventory),
30 => Ok(Self::ShulkerBox),
31 => Ok(Self::TradeIngredient1),
32 => Ok(Self::TradeIngredient2),
33 => Ok(Self::TradeResultPreview),
34 => Ok(Self::Offhand),
35 => Ok(Self::CompoundCreatorInput),
36 => Ok(Self::CompoundCreatorOutputPreview),
37 => Ok(Self::ElementConstructorOutputPreview),
38 => Ok(Self::MaterialReducerInput),
39 => Ok(Self::MaterialReducerOutput),
40 => Ok(Self::LabTableInput),
41 => Ok(Self::LoomInput),
42 => Ok(Self::LoomDye),
43 => Ok(Self::LoomMaterial),
44 => Ok(Self::LoomResultPreview),
45 => Ok(Self::BlastFurnaceIngredient),
46 => Ok(Self::SmokerIngredient),
47 => Ok(Self::Trade2Ingredient1),
48 => Ok(Self::Trade2Ingredient2),
49 => Ok(Self::Trade2ResultPreview),
50 => Ok(Self::GrindstoneInput),
51 => Ok(Self::GrindstoneAdditional),
52 => Ok(Self::GrindstoneResultPreview),
53 => Ok(Self::StonecutterInput),
54 => Ok(Self::StonecutterResultPreview),
55 => Ok(Self::CartographyInput),
56 => Ok(Self::CartographyAdditional),
57 => Ok(Self::CartographyResultPreview),
58 => Ok(Self::Barrel),
59 => Ok(Self::Cursor),
60 => Ok(Self::CreatedOutput),
61 => Ok(Self::SmithingTableTemplate),
62 => Ok(Self::CrafterLevelEntity),
63 => Ok(Self::Dynamic),
64 => Ok(Self::RecipeFood),
65 => Ok(Self::RecipeBlocks),
66 => Ok(Self::RecipeFurnaceItems),
_ => Err(Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid ContainerName ID: {value}"),
)),
}
}
}
impl PacketWrite for ContainerName {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
(*self as u8).write(writer)?;
Ok(())
}
}
impl PacketRead for ContainerName {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let value = u8::read(buf)?;
Self::try_from(value)
}
}
#[derive(Debug, Clone)]
pub struct NetworkItemStack {
pub id: VarInt,
pub count: u16,
pub aux_value: VarUInt,
pub block_runtime_id: VarInt,
pub extra_data: Vec<u8>,
}
impl PacketRead for NetworkItemStack {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let id = VarInt::read(buf)?;
if id.0 == 0 {
return Ok(Self {
id,
count: 0,
aux_value: VarUInt(0),
block_runtime_id: VarInt(0),
extra_data: Vec::new(),
});
}
let count = u16::read(buf)?;
let aux_value = VarUInt::read(buf)?;
let block_runtime_id = VarInt::read(buf)?;
let extra_data_len = VarUInt::read(buf)?.0;
let mut extra_data = vec![0u8; extra_data_len as usize];
buf.read_exact(&mut extra_data)?;
Ok(Self {
id,
count,
aux_value,
block_runtime_id,
extra_data,
})
}
}

View File

@@ -0,0 +1,12 @@
use pumpkin_macros::packet;
use pumpkin_util::math::position::BlockPos;
use crate::serial::PacketRead;
#[derive(Debug, PacketRead)]
#[packet(34)]
pub struct SBlockPickRequest {
pub block_pos: BlockPos,
pub add_block_nbt: bool,
pub hotbar_slot: u8,
}

View File

@@ -0,0 +1,285 @@
use std::io::{Error, ErrorKind, Read, Write};
use crate::{
bedrock::network_item::FullContainerName,
codec::{var_int::VarInt, var_uint::VarUInt},
serial::{PacketRead, PacketWrite},
};
use pumpkin_macros::packet;
#[derive(Debug)]
pub struct ItemStackRequestSlotInfo {
pub container_name: FullContainerName,
pub slot_id: u8,
pub stack_id: VarInt,
}
impl PacketRead for ItemStackRequestSlotInfo {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let container_name = FullContainerName::read(buf)?;
let slot_id = u8::read(buf)?;
let stack_id = VarInt::read(buf)?;
Ok(Self {
container_name,
slot_id,
stack_id,
})
}
}
impl PacketWrite for ItemStackRequestSlotInfo {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.container_name.write(writer)?;
self.slot_id.write(writer)?;
self.stack_id.write(writer)?;
Ok(())
}
}
#[derive(Debug)]
pub enum ItemStackRequestAction {
Take {
count: u8,
source: ItemStackRequestSlotInfo,
destination: ItemStackRequestSlotInfo,
},
Place {
count: u8,
source: ItemStackRequestSlotInfo,
destination: ItemStackRequestSlotInfo,
},
Swap {
slot1: ItemStackRequestSlotInfo,
slot2: ItemStackRequestSlotInfo,
},
Drop {
count: u8,
source: ItemStackRequestSlotInfo,
randomly: bool,
},
Destroy {
count: u8,
source: ItemStackRequestSlotInfo,
},
Consume {
count: u8,
source: ItemStackRequestSlotInfo,
},
Create {
result_index: u8,
},
PlaceInContainer {
count: u8,
source: ItemStackRequestSlotInfo,
destination: ItemStackRequestSlotInfo,
},
TakeOutContainer {
count: u8,
source: ItemStackRequestSlotInfo,
destination: ItemStackRequestSlotInfo,
},
LabTableCombine,
BeaconPayment {
primary_effect_id: VarInt,
secondary_effect_id: VarInt,
},
MineBlock {
hotbar_slot: VarInt,
predicted_durability: VarInt,
stack_id: VarInt,
},
CraftRecipe {
recipe_id: VarUInt,
repetitions: u8,
},
CraftRecipeAuto {
recipe_id: VarUInt,
repetitions: u8,
repetitions2: u8,
},
CraftCreative {
creative_item_id: VarUInt,
repetitions: u8,
},
Optional {
recipe_id: VarUInt,
filter_string_index: i32,
},
Grindstone {
recipe_id: VarUInt,
repair_cost: VarInt,
repetitions: u8,
},
Loom {
pattern_id: String,
repetitions: u8,
},
CraftNonImplemented,
CraftResultsDeprecated {
result_items: Vec<crate::bedrock::network_item::NetworkItemStack>,
times_crafted: u8,
},
}
impl PacketRead for ItemStackRequestAction {
#[allow(clippy::too_many_lines)]
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let action_type = u8::read(buf)?;
match action_type {
0 => Ok(Self::Take {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
destination: ItemStackRequestSlotInfo::read(buf)?,
}),
1 => Ok(Self::Place {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
destination: ItemStackRequestSlotInfo::read(buf)?,
}),
2 => Ok(Self::Swap {
slot1: ItemStackRequestSlotInfo::read(buf)?,
slot2: ItemStackRequestSlotInfo::read(buf)?,
}),
3 => Ok(Self::Drop {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
randomly: bool::read(buf)?,
}),
4 => Ok(Self::Destroy {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
}),
5 => Ok(Self::Consume {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
}),
6 => Ok(Self::Create {
result_index: u8::read(buf)?,
}),
7 => Ok(Self::PlaceInContainer {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
destination: ItemStackRequestSlotInfo::read(buf)?,
}),
8 => Ok(Self::TakeOutContainer {
count: u8::read(buf)?,
source: ItemStackRequestSlotInfo::read(buf)?,
destination: ItemStackRequestSlotInfo::read(buf)?,
}),
9 => Ok(Self::LabTableCombine),
10 => Ok(Self::BeaconPayment {
primary_effect_id: VarInt::read(buf)?,
secondary_effect_id: VarInt::read(buf)?,
}),
11 => Ok(Self::MineBlock {
hotbar_slot: VarInt::read(buf)?,
predicted_durability: VarInt::read(buf)?,
stack_id: VarInt::read(buf)?,
}),
12 => Ok(Self::CraftRecipe {
recipe_id: VarUInt::read(buf)?,
repetitions: u8::read(buf)?,
}),
13 => {
let recipe_id = VarUInt::read(buf)?;
let repetitions = u8::read(buf)?;
let repetitions2 = u8::read(buf)?;
let count = u8::read(buf)?;
// Skip ingredients
if count > 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"CraftRecipeAuto stack action is unimplemented",
));
}
Ok(Self::CraftRecipeAuto {
recipe_id,
repetitions,
repetitions2,
})
}
14 => Ok(Self::CraftCreative {
creative_item_id: VarUInt::read(buf)?,
repetitions: u8::read(buf)?,
}),
15 => Ok(Self::Optional {
recipe_id: VarUInt::read(buf)?,
filter_string_index: i32::read(buf)?,
}),
16 => Ok(Self::Grindstone {
recipe_id: VarUInt::read(buf)?,
repair_cost: VarInt::read(buf)?,
repetitions: u8::read(buf)?,
}),
17 => Ok(Self::Loom {
pattern_id: String::read(buf)?,
repetitions: u8::read(buf)?,
}),
18 => Ok(Self::CraftNonImplemented),
19 => {
let result_items_len = VarUInt::read(buf)?.0;
let mut result_items = Vec::with_capacity(result_items_len as usize);
for _ in 0..result_items_len {
result_items.push(crate::bedrock::network_item::NetworkItemStack::read(buf)?);
}
let times_crafted = u8::read(buf)?;
Ok(Self::CraftResultsDeprecated {
result_items,
times_crafted,
})
}
_ => Err(Error::new(
ErrorKind::InvalidData,
format!("Unknown ItemStackRequestAction ID: {action_type}"),
)),
}
}
}
#[derive(Debug)]
pub struct ItemStackRequest {
pub request_id: VarInt,
pub actions: Vec<ItemStackRequestAction>,
pub filter_strings: Vec<String>,
pub filter_cause: i32,
}
impl PacketRead for ItemStackRequest {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let request_id = VarInt::read(buf)?;
let actions_len = VarUInt::read(buf)?.0;
let mut actions = Vec::with_capacity(actions_len as usize);
for _ in 0..actions_len {
actions.push(ItemStackRequestAction::read(buf)?);
}
let filter_strings_len = VarUInt::read(buf)?.0;
let mut filter_strings = Vec::with_capacity(filter_strings_len as usize);
for _ in 0..filter_strings_len {
filter_strings.push(String::read(buf)?);
}
let filter_cause = i32::read(buf)?;
Ok(Self {
request_id,
actions,
filter_strings,
filter_cause,
})
}
}
#[derive(Debug)]
#[packet(147)]
pub struct SItemStackRequest {
pub requests: Vec<ItemStackRequest>,
}
impl PacketRead for SItemStackRequest {
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
let requests_len = VarUInt::read(buf)?.0;
let mut requests = Vec::with_capacity(requests_len as usize);
for _ in 0..requests_len {
requests.push(ItemStackRequest::read(buf)?);
}
Ok(Self { requests })
}
}

View File

@@ -0,0 +1,42 @@
use crate::{
bedrock::network_item::NetworkItemDescriptor,
codec::{var_int::VarInt, var_ulong::VarULong},
serial::PacketRead,
};
use pumpkin_macros::packet;
use std::io::{Error, Read};
#[derive(Debug)]
#[packet(31)]
pub struct SMobEquipment {
pub entity_runtime_id: VarULong,
pub stack_id: VarInt,
pub item: Option<NetworkItemDescriptor>,
pub inventory_slot: u8,
pub hotbar_slot: u8,
pub window_id: u8,
}
impl PacketRead for SMobEquipment {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let entity_runtime_id = VarULong::read(reader)?;
let stack_id = VarInt::read(reader)?;
let item = if stack_id.0 == 0 {
None
} else {
Some(NetworkItemDescriptor::read(reader)?)
};
let inventory_slot = u8::read(reader)?;
let hotbar_slot = u8::read(reader)?;
let window_id = u8::read(reader)?;
Ok(Self {
entity_runtime_id,
stack_id,
item,
inventory_slot,
hotbar_slot,
window_id,
})
}
}

View File

@@ -1,5 +1,6 @@
pub mod actor_event;
pub mod animate;
pub mod block_pick_request;
pub mod client_cache_status;
pub mod command_request;
pub mod container_close;
@@ -7,8 +8,10 @@ pub mod emote;
pub mod emote_list;
pub mod interaction;
pub mod inventory_transaction;
pub mod item_stack_request;
pub mod loading_screen;
pub mod login;
pub mod mob_equipment;
pub mod modal_form_response;
pub mod player_action;
pub mod player_auth_input;
@@ -21,6 +24,7 @@ pub mod text;
pub use actor_event::*;
pub use animate::*;
pub use block_pick_request::*;
pub use client_cache_status::*;
pub use command_request::*;
pub use container_close::*;
@@ -28,8 +32,10 @@ pub use emote::*;
pub use emote_list::*;
pub use interaction::{Action as InteractAction, SInteraction};
pub use inventory_transaction::*;
pub use item_stack_request::*;
pub use loading_screen::*;
pub use login::*;
pub use mob_equipment::*;
pub use modal_form_response::*;
pub use player_action::{Action as PlayerActionType, SPlayerAction};
pub use player_auth_input::*;

View File

@@ -2488,7 +2488,7 @@ impl Entity {
MetadataValue::Long(self.bedrock_flags_two.load(Ordering::Relaxed)),
);
client
.send_game_packet(&CSetActorData {
.enqueue_packet(&CSetActorData {
actor_runtime_id: VarULong(self.entity_id as u64),
metadata,
synced_properties: PropertySyncData {

View File

@@ -3400,16 +3400,40 @@ impl Player {
}
pub async fn close_handled_screen(self: &Arc<Self>) {
let (sync_id, bedrock_window_type) = {
let current_handler_guard = self.current_screen_handler.lock().await;
let handler = current_handler_guard.lock().await;
let sync_id = handler.sync_id();
let window_type = handler.window_type();
let bedrock_window_type = match window_type {
Some(WindowType::Crafting) => 1,
Some(WindowType::Furnace) => 2,
Some(WindowType::Enchantment) => 3,
Some(WindowType::BrewingStand) => 4,
Some(WindowType::Anvil) => 5,
Some(WindowType::Hopper) => 8,
Some(WindowType::Beacon) => 13,
Some(WindowType::BlastFurnace) => 27,
Some(WindowType::Smoker) => 28,
Some(WindowType::Stonecutter) => 29,
Some(WindowType::CartographyTable) => 30,
Some(WindowType::Grindstone) => 26,
Some(WindowType::Loom) => 24,
Some(WindowType::Smithing) => 34,
_ => 0,
};
(sync_id, bedrock_window_type)
};
self.client
.enqueue_packet(&CCloseContainer::new(
self.current_screen_handler
.lock()
.await
.lock()
.await
.sync_id()
.into(),
))
.enqueue_packet_editioned(
&CCloseContainer::new(sync_id.into()),
&pumpkin_protocol::bedrock::server::container_close::SContainerClose {
container_id: sync_id,
container_type: bedrock_window_type,
server_initiated: true,
},
)
.await;
self.on_handled_screen_closed().await;
}

View File

@@ -504,6 +504,13 @@ impl PumpkinServer {
let be_clients = bedrock_clients.clone();
let mut clients_guard = bedrock_clients.lock().await;
if clients_guard
.get(&client_addr)
.is_some_and(|client| client.is_closed())
{
clients_guard.remove(&client_addr);
}
let mut is_new = false;
let client = clients_guard.entry(client_addr).or_insert_with(|| {
is_new = true;
@@ -525,9 +532,10 @@ impl PumpkinServer {
tasks.spawn(async move {
let login_result = client_clone.handle_login_sequence(&server_clone).await;
match login_result {
match login_result {
PacketHandlerResult::Stop => {
client_clone.close().await;
client_clone.await_tasks().await;
}
PacketHandlerResult::ReadyToPlay(profile, config) => {
if let Some((player, _world)) = server_clone
@@ -539,6 +547,7 @@ impl PumpkinServer {
client_clone.progress_player_packets(&player, &server_clone).await;
client_clone.close().await;
client_clone.await_tasks().await;
player.remove().await;
server_clone.remove_player(&player).await;
if let Err(e) = server_clone.player_data_storage
@@ -564,7 +573,8 @@ impl PumpkinServer {
id,
&mut Cursor::new(&udp_buf[1..len]),
client_addr,
sock
sock,
bedrock_clients,
).await;
}
}

View File

@@ -30,6 +30,7 @@ use pumpkin_protocol::{
packet_encoder::UDPNetworkEncoder,
server::{
animate::SAnimate,
block_pick_request::SBlockPickRequest,
client_cache_status::SClientCacheStatus,
command_request::SCommandRequest,
container_close::SContainerClose,
@@ -38,6 +39,7 @@ use pumpkin_protocol::{
inventory_transaction::SInventoryTransaction,
loading_screen::SLoadingScreen,
login::SLogin,
mob_equipment::SMobEquipment,
player_action::SPlayerAction,
player_auth_input::SPlayerAuthInput,
raknet::{
@@ -136,6 +138,7 @@ pub struct BedrockClient {
output_ordered_index: AtomicU32,
/// The next form ID to use for custom forms.
pub next_form_id: AtomicU32,
pub inventory_opened: AtomicBool,
/// An notifier that is triggered when this client is closed.
close_token: CancellationToken,
last_seen: Arc<AtomicCell<std::time::Instant>>,
@@ -184,6 +187,7 @@ impl BedrockClient {
output_sequenced_index: AtomicU32::new(0),
output_ordered_index: AtomicU32::new(0),
next_form_id: AtomicU32::new(0),
inventory_opened: AtomicBool::new(false),
compounds: Arc::new(Mutex::new(HashMap::new())),
close_token: CancellationToken::new(),
last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())),
@@ -650,12 +654,11 @@ impl BedrockClient {
}
self.close_token.cancel();
self.be_clients.lock().await.remove(&self.address);
}
pub async fn await_tasks(&self) {
self.tasks.close();
self.tasks.wait().await;
if let Some(player) = self.player.lock().await.as_ref() {
player.remove().await;
}
}
pub fn is_closed(&self) -> bool {
@@ -1011,6 +1014,9 @@ impl BedrockClient {
SInventoryTransaction::PACKET_ID => {
self.handle_inventory_action(player, SInventoryTransaction::read(reader)?).await;
}
pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest::PACKET_ID => {
self.handle_item_stack_request(player, pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest::read(reader)?).await;
}
SInteraction::PACKET_ID => {
self.handle_interaction(player, SInteraction::read(reader)?)
.await;
@@ -1059,6 +1065,14 @@ impl BedrockClient {
SLoadingScreen::PACKET_ID => {
// Ignore for now
}
SBlockPickRequest::PACKET_ID => {
self.handle_block_pick_request(player, SBlockPickRequest::read(reader)?)
.await;
}
SMobEquipment::PACKET_ID => {
self.handle_mob_equipment(player, SMobEquipment::read(reader)?)
.await;
}
_ => {
warn!("Bedrock: Received Unknown Game packet: {}", packet.id);
}
@@ -1111,8 +1125,24 @@ impl BedrockClient {
payload: &mut Cursor<&[u8]>,
addr: SocketAddr,
socket: &UdpSocket,
be_clients: &Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
) -> Result<(), Error> {
match i32::from(packet_id) {
let packet_id_i32 = i32::from(packet_id);
if packet_id_i32 == SOpenConnectionRequest1::PACKET_ID {
let old_client = {
let mut clients_guard = be_clients.lock().await;
clients_guard.remove(&addr)
};
if let Some(client) = old_client {
debug!(
"Closing old Bedrock client connection for {} due to new connection request",
addr
);
client.close().await;
}
}
match packet_id_i32 {
SUnconnectedPing::PACKET_ID => {
Self::handle_unconnected_ping(
server,

File diff suppressed because it is too large Load Diff

View File

@@ -90,7 +90,7 @@ use pumpkin_protocol::{
bedrock::{
client::{
add_player::CAddPlayer,
creative_content::{CCreativeContent, Group},
creative_content::{CCreativeContent, CreativeCategory, Entry, Group},
gamerules_changed::GameRules,
player_list::{CPlayerList, PlayerListEntry, Skin},
remove_actor::CRemoveActor,
@@ -1755,6 +1755,8 @@ impl World {
player: Arc<Player>,
server: &Server,
) {
static CREATIVE_CONTENT: std::sync::OnceLock<(Vec<Group>, Vec<Entry>)> =
std::sync::OnceLock::new();
let level_info = server.level_info.load();
let weather = self.weather.lock().await;
let runtime_id = player.entity_id() as u64;
@@ -1909,16 +1911,65 @@ impl World {
})
.await;
let (groups, entries) = CREATIVE_CONTENT.get_or_init(|| {
let groups = pumpkin_data::bedrock_creative::CREATIVE_GROUPS
.iter()
.map(|g| {
let creative_category = match g.category {
1 => CreativeCategory::Construction,
2 => CreativeCategory::Nature,
3 => CreativeCategory::Equipment,
4 => CreativeCategory::Items,
5 => CreativeCategory::CommandOnly,
_ => CreativeCategory::Undefined,
};
let icon_item = if g.icon_item_id != 0 {
NetworkItemDescriptor {
id: VarInt::from(g.icon_item_id),
stack_size: 1,
aux_value: VarUInt(g.icon_item_aux_value),
block_runtime_id: VarInt(0),
nbt_data: pumpkin_nbt::Nbt::default(),
place_on_blocks: Vec::new(),
destroy_blocks: Vec::new(),
shield_blocking_tick: 0,
}
} else {
NetworkItemDescriptor::default()
};
Group {
creative_category,
name: g.name.to_string(),
icon_item,
}
})
.collect::<Vec<_>>();
let entries = pumpkin_data::bedrock_creative::CREATIVE_ENTRIES
.iter()
.enumerate()
.map(|(i, e)| Entry {
id: VarUInt((i + 1) as u32),
item: NetworkItemDescriptor {
id: VarInt::from(e.item_id),
stack_size: 1,
aux_value: VarUInt(e.item_aux_value),
block_runtime_id: VarInt(0),
nbt_data: pumpkin_nbt::Nbt::default(),
place_on_blocks: Vec::new(),
destroy_blocks: Vec::new(),
shield_blocking_tick: 0,
},
group_index: VarUInt(e.group_index),
})
.collect::<Vec<_>>();
(groups, entries)
});
client
.send_game_packet(&CCreativeContent {
groups: &[Group {
creative_category:
pumpkin_protocol::bedrock::client::CreativeCategory::Construction,
name: String::new(),
icon_item: NetworkItemDescriptor::default(),
}],
entries: &[],
})
.send_game_packet(&CCreativeContent { groups, entries })
.await;
client
@@ -4982,7 +5033,7 @@ impl World {
Self::broadcast_java_grouped(je_packet, je_recipients_by_version);
for recipient in bedrock_recipients {
recipient.send_game_packet(be_packet).await;
recipient.enqueue_packet(be_packet).await;
}
}
}