chore: changed block registration

only require block ids, should make bin size smaller and let the compiler better optimize, should also be faster performance wise
This commit is contained in:
Alexander Medvedev
2026-01-23 23:32:17 +01:00
parent a04b7036bc
commit 5bfa6528da
80 changed files with 773 additions and 863 deletions

89
Cargo.lock generated
View File

@@ -309,13 +309,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chacha20"
version = "0.10.0-rc.6"
version = "0.10.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f895fb33c1ad22da4bc79d37c0bddff8aee2ba4575705345eb73b8ffbc386074"
checksum = "31cd65b2ca03198c223cd9a8fa1152c4ec251cd79049f6dc584152ad3fb5ba9d"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core 0.10.0-rc-3",
"rand_core 0.10.0-rc-5",
]
[[package]]
@@ -391,9 +391,9 @@ dependencies = [
[[package]]
name = "cmov"
version = "0.5.0-pre.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96"
checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3"
[[package]]
name = "colorchoice"
@@ -658,13 +658,13 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.7.0-rc.18"
version = "0.7.0-rc.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37387ceb32048ff590f2cbd24d8b05fffe63c3f69a5cfa089d4f722ca4385a19"
checksum = "f9f9a78b88bb8255ec59a81423aa92ada22f96883f9ae59dcb68613907636ae5"
dependencies = [
"ctutils",
"num-traits",
"rand_core 0.10.0-rc-3",
"rand_core 0.10.0-rc-5",
"serdect",
"zeroize",
]
@@ -681,29 +681,29 @@ dependencies = [
[[package]]
name = "crypto-common"
version = "0.2.0-rc.9"
version = "0.2.0-rc.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809"
checksum = "a6dcdb44f2c3ee25689ca12a4c19e664fd09f97aeae0bc5043b2dbab6389e308"
dependencies = [
"hybrid-array",
]
[[package]]
name = "crypto-primes"
version = "0.7.0-pre.6"
version = "0.7.0-pre.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79c98a281f9441200b24e3151407a629bfbe720399186e50516da939195e482"
checksum = "c6372ba15f988d7cd77e9cfbc42b269601c006f2f16a21a72b886136caf04bfb"
dependencies = [
"crypto-bigint 0.7.0-rc.18",
"crypto-bigint 0.7.0-rc.21",
"libm",
"rand_core 0.10.0-rc-3",
"rand_core 0.10.0-rc-5",
]
[[package]]
name = "ctutils"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758e5ed90be3c8abff7f9a6f37ab7f6d8c59c2210d448b81f3f508134aec84e4"
checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4"
dependencies = [
"cmov",
]
@@ -767,13 +767,13 @@ dependencies = [
[[package]]
name = "digest"
version = "0.11.0-rc.5"
version = "0.11.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9"
checksum = "ca14c221bd9052fd2da7c34a2eeb5ae54732db28be47c35937be71793d675422"
dependencies = [
"block-buffer 0.11.0",
"const-oid 0.10.2",
"crypto-common 0.2.0-rc.9",
"crypto-common 0.2.0-rc.12",
"subtle",
]
@@ -1097,7 +1097,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
"rand_core 0.10.0-rc-3",
"rand_core 0.10.0-rc-5",
"wasip2",
]
@@ -1208,7 +1208,7 @@ version = "0.13.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1c597ac7d6cc8143e30e83ef70915e7f883b18d8bec2e2b2bce47f5bbb06d57"
dependencies = [
"digest 0.11.0-rc.5",
"digest 0.11.0-rc.7",
]
[[package]]
@@ -1538,9 +1538,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
version = "0.8.9"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
dependencies = [
"cfg-if",
"windows-link",
@@ -1993,9 +1993,9 @@ dependencies = [
[[package]]
name = "pkcs8"
version = "0.11.0-rc.8"
version = "0.11.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0"
checksum = "80f8fa6196ede5a9f9ee95b44ca134bddc9b70e8913f9297bd58c909f5889a09"
dependencies = [
"der 0.8.0-rc.10",
"spki 0.8.0-rc.4",
@@ -2117,7 +2117,7 @@ dependencies = [
"libloading",
"log",
"num-bigint",
"pkcs8 0.11.0-rc.8",
"pkcs8 0.11.0-rc.9",
"pumpkin-config",
"pumpkin-data",
"pumpkin-inventory",
@@ -2206,6 +2206,7 @@ version = "0.1.0-dev+1.21.11"
dependencies = [
"proc-macro-error2",
"proc-macro2",
"pumpkin-data",
"quote",
"syn",
]
@@ -2336,12 +2337,12 @@ dependencies = [
[[package]]
name = "rand"
version = "0.10.0-rc.6"
source = "git+https://github.com/rust-random/rand#f356bbae5817f382b19ac5741b549c4d2cd49b50"
version = "0.10.0-rc.7"
source = "git+https://github.com/rust-random/rand#6e80534233d4bb09ed0abc086edeacaae6f54dc0"
dependencies = [
"chacha20",
"getrandom 0.4.0-rc.0",
"rand_core 0.10.0-rc-3",
"rand_core 0.10.0-rc-5",
]
[[package]]
@@ -2355,9 +2356,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.10.0-rc-3"
version = "0.10.0-rc-5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66ee92bc15280519ef199a274fe0cafff4245d31bc39aaa31c011ad56cb1f05"
checksum = "05a06e03bd1f2ae861ab9e7498b6c64ed3dadb9ce175c0464a2522a5f23c0045"
[[package]]
name = "rayon"
@@ -2443,19 +2444,19 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.10.0-rc.12"
version = "0.10.0-rc.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9a2b1eacbc34fbaf77f6f1db1385518446008d49b9f9f59dc9d1340fce4ca9e"
checksum = "8ff85dd219e338d42a2eada54ad71fe515717e08d53f728a8803de33b83f80b8"
dependencies = [
"const-oid 0.10.2",
"crypto-bigint 0.7.0-rc.18",
"crypto-bigint 0.7.0-rc.21",
"crypto-primes",
"digest 0.11.0-rc.5",
"digest 0.11.0-rc.7",
"pkcs1",
"pkcs8 0.11.0-rc.8",
"rand_core 0.10.0-rc-3",
"pkcs8 0.11.0-rc.9",
"rand_core 0.10.0-rc-5",
"sha1",
"signature 3.0.0-rc.6",
"signature 3.0.0-rc.8",
"spki 0.8.0-rc.4",
"zeroize",
]
@@ -2661,7 +2662,7 @@ checksum = "aa1ae819b9870cadc959a052363de870944a1646932d274a4e270f64bf79e5ef"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.11.0-rc.5",
"digest 0.11.0-rc.7",
]
[[package]]
@@ -2683,7 +2684,7 @@ checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.11.0-rc.5",
"digest 0.11.0-rc.7",
]
[[package]]
@@ -2723,12 +2724,12 @@ dependencies = [
[[package]]
name = "signature"
version = "3.0.0-rc.6"
version = "3.0.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d"
checksum = "c04b70a14ee5f15e2e0c785a5fdb2e9a51138dfe13ba3cf8eab037a9e60b1879"
dependencies = [
"digest 0.11.0-rc.5",
"rand_core 0.10.0-rc-3",
"digest 0.11.0-rc.7",
"rand_core 0.10.0-rc-5",
]
[[package]]

View File

@@ -59,7 +59,7 @@ cesu8 = "1.1"
cfb8 = "0.8"
colored = "3.1"
console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = "1.9.0"
crc-fast = "1.10.0"
criterion = { version = "0.8", default-features = false }
crossbeam-utils = "0.8.21"
crossfire = "2.1.10"
@@ -72,7 +72,7 @@ heck = "0.5"
hmac = "=0.13.0-rc.3"
indexmap = "2.13"
itertools = "0.14.0"
libloading = "0.8"
libloading = "0.9"
lru = "0.16.3"
lz4-java-wrc = "0.2.0"
md5 = "0.8"
@@ -82,7 +82,7 @@ num-traits = "0.2"
num_cpus = "1.17.0"
p384 = "0.13.1"
phf = "0.13.1"
pkcs8 = "=0.11.0-rc.8"
pkcs8 = "=0.11.0-rc.9"
proc-macro2 = "1.0"
pumpkin-config = { path = "pumpkin-config" }
pumpkin-data = { path = "pumpkin-data" }
@@ -94,7 +94,7 @@ pumpkin-util = { path = "pumpkin-util" }
pumpkin-world = { path = "pumpkin-world" }
quote = "1.0"
rand = { git = "https://github.com/rust-random/rand"}
rsa = "=0.10.0-rc.12"
rsa = "=0.10.0-rc.13"
rustc-hash = "2.1.1"
rustyline = "17.0.2"
ruzstd = "0.8.2"

View File

@@ -9,6 +9,8 @@ proc-macro = true
[dependencies]
proc-macro-error2.workspace = true
pumpkin-data.workspace = true
proc-macro2.workspace = true
quote.workspace = true

View File

@@ -1,9 +1,11 @@
use proc_macro::TokenStream;
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
use pumpkin_data::Block;
use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use quote::quote;
use syn::spanned::Spanned;
use syn::{self, Attribute, DeriveInput, LitStr, Type, parse_quote};
use syn::{Block, Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input};
use syn::{Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input};
#[proc_macro_derive(Event)]
pub fn event(item: TokenStream) -> TokenStream {
@@ -79,7 +81,7 @@ pub fn cancellable(_args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_error]
#[proc_macro]
pub fn send_cancellable(input: TokenStream) -> TokenStream {
let block = parse_macro_input!(input as Block);
let block = parse_macro_input!(input as syn::Block);
let mut event_expr = None;
let mut after_block = None;
@@ -167,42 +169,44 @@ pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn pumpkin_block(args: TokenStream, item: TokenStream) -> TokenStream {
let input_item = item.clone();
let arg_lit = parse_macro_input!(args as LitStr);
let arg_value = arg_lit.value();
let (namespace, id) = match arg_value.split_once(':') {
Some(pair) => pair,
let block_name = arg_value.strip_prefix("minecraft:").unwrap_or(&arg_value);
let block = match Block::from_name(block_name) {
Some(b) => b,
None => {
return syn::Error::new(
arg_lit.span(),
"Expected format \"namespace:id\" (e.g. \"minecraft:stone\")",
)
.to_compile_error()
.into();
return syn::Error::new(arg_lit.span(), "Invalid block name")
.to_compile_error()
.into();
}
};
let block_id = block.id;
let ast = parse_macro_input!(item as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let code = quote! {
#ast
let generated = quote! {
impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause {
fn namespace(&self) -> &'static str {
#namespace
}
fn ids(&self) -> &'static [&'static str] {
&[#id]
fn ids() -> Box<[u16]> {
[#block_id].into()
}
}
};
code.into()
// Combine original item and new impl
let mut output = input_item;
output.extend(TokenStream::from(generated));
output
}
#[proc_macro_attribute]
pub fn pumpkin_block_from_tag(args: TokenStream, item: TokenStream) -> TokenStream {
let original_item = item.clone();
let arg_lit = parse_macro_input!(args as LitStr);
let ast = parse_macro_input!(item as DeriveInput);
@@ -211,24 +215,29 @@ pub fn pumpkin_block_from_tag(args: TokenStream, item: TokenStream) -> TokenStre
let full_tag = arg_lit.value();
// Efficient splitting
let namespace = match full_tag.split_once(':') {
Some((ns, _)) => ns,
None => abort!(arg_lit.span(), "Expected format 'namespace:path'"),
let values = match get_tag_ids(RegistryKey::Block, &full_tag) {
Some(v) => v,
None => {
return syn::Error::new(
arg_lit.span(),
format!("Failed to get tag IDs: {}", full_tag),
)
.to_compile_error()
.into();
}
};
quote! {
#ast
let expanded = quote! {
impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause {
fn namespace(&self) -> &'static str {
#namespace
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, #arg_lit).unwrap()
fn ids() -> Box<[u16]> {
Box::new([ #(#values),* ])
}
}
}
.into()
};
let mut output = original_item;
output.extend(TokenStream::from(expanded));
output
}
// #[proc_macro_error]

View File

@@ -8,6 +8,12 @@ use crate::{END_ID, Error, Nbt, get_nbt_string};
use std::io::{ErrorKind, Read, Seek, Write};
use std::vec::IntoIter;
/// Represents a Compound NBT tag, effectively a Key-Value map.
///
/// Internally, this uses a `Vec<(String, NbtTag)>` to preserve insertion order,
/// which is often preferred in NBT serialization, though lookups are O(n).
///
///
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct NbtCompound {
pub child_tags: Vec<(String, NbtTag)>,
@@ -24,20 +30,10 @@ impl NbtCompound {
loop {
let tag_id = match reader.get_u8_be() {
Ok(id) => id,
Err(err) => match err {
Error::Incomplete(err) => match err.kind() {
ErrorKind::UnexpectedEof => {
break;
}
_ => {
return Err(Error::Incomplete(err));
}
},
_ => {
return Err(err);
}
},
Err(Error::Incomplete(e)) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
};
if tag_id == END_ID {
break;
}
@@ -45,6 +41,7 @@ impl NbtCompound {
let len = reader.get_u16_be()?;
reader.skip_bytes(len as i64)?;
// Skip Value
NbtTag::skip_data(reader, tag_id)?;
}
@@ -59,27 +56,18 @@ impl NbtCompound {
loop {
let tag_id = match reader.get_u8_be() {
Ok(id) => id,
Err(err) => match err {
Error::Incomplete(err) => match err.kind() {
ErrorKind::UnexpectedEof => {
break;
}
_ => {
return Err(Error::Incomplete(err));
}
},
_ => {
return Err(err);
}
},
Err(Error::Incomplete(e)) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
};
if tag_id == END_ID {
break;
}
let name = get_nbt_string(reader)?;
let tag = NbtTag::deserialize_data(reader, tag_id)?;
compound.put(&name, tag);
compound.child_tags.push((name, tag));
}
Ok(compound)
@@ -100,9 +88,8 @@ impl NbtCompound {
}
pub fn put(&mut self, name: &str, value: impl Into<NbtTag>) {
let name = name.to_string();
if !self.child_tags.iter().any(|(key, _)| key == &name) {
self.child_tags.push((name, value.into()));
if !self.child_tags.iter().any(|(key, _)| key == name) {
self.child_tags.push((name.to_string(), value.into()));
}
}
@@ -292,7 +279,7 @@ impl Display for NbtCompound {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(&format!("{key}: {value}"))?;
write!(f, "{}: {}", key, value)?;
}
f.write_str("}")
}
@@ -302,51 +289,51 @@ impl Display for NbtTag {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NbtTag::End => Ok(()),
NbtTag::Byte(value) => f.write_fmt(format_args!("{value}")),
NbtTag::Short(value) => f.write_fmt(format_args!("{value}")),
NbtTag::Int(value) => f.write_fmt(format_args!("{value}")),
NbtTag::Long(value) => f.write_fmt(format_args!("{value}")),
NbtTag::Float(value) => f.write_fmt(format_args!("{value}")),
NbtTag::Double(value) => f.write_fmt(format_args!("{value}")),
NbtTag::String(value) => f.write_fmt(format_args!("\"{value}\"")),
NbtTag::Compound(value) => f.write_fmt(format_args!("{value}")),
NbtTag::ByteArray(value) => {
f.write_str("[B; ")?;
for (i, byte) in value.iter().enumerate() {
NbtTag::Byte(v) => write!(f, "{v}b"),
NbtTag::Short(v) => write!(f, "{v}s"),
NbtTag::Int(v) => write!(f, "{v}"),
NbtTag::Long(v) => write!(f, "{v}L"),
NbtTag::Float(v) => write!(f, "{v}f"),
NbtTag::Double(v) => write!(f, "{v}d"),
NbtTag::String(v) => write!(f, "\"{v}\""), // TODO: Proper escaping needed for robust SNBT
NbtTag::Compound(v) => write!(f, "{v}"),
NbtTag::ByteArray(v) => {
f.write_str("[B;")?;
for (i, byte) in v.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
f.write_str(",")?
}
f.write_fmt(format_args!("{byte}"))?;
write!(f, " {}b", byte)?;
}
f.write_str("]")
}
NbtTag::List(value) => {
NbtTag::List(v) => {
f.write_str("[")?;
for (i, tag) in value.iter().enumerate() {
for (i, tag) in v.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
f.write_str(", ")?
}
f.write_fmt(format_args!("{tag}"))?;
write!(f, "{}", tag)?;
}
f.write_str("]")
}
NbtTag::IntArray(value) => {
f.write_str("[I; ")?;
for (i, int) in value.iter().enumerate() {
NbtTag::IntArray(v) => {
f.write_str("[I;")?;
for (i, int) in v.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
f.write_str(",")?
}
f.write_fmt(format_args!("{int}"))?;
write!(f, " {}", int)?;
}
f.write_str("]")
}
NbtTag::LongArray(value) => {
f.write_str("[L; ")?;
for (i, long) in value.iter().enumerate() {
NbtTag::LongArray(v) => {
f.write_str("[L;")?;
for (i, long) in v.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
f.write_str(",")?
}
f.write_fmt(format_args!("{long}"))?;
write!(f, " {}L", long)?;
}
f.write_str("]")
}

View File

@@ -302,14 +302,13 @@ impl NbtTag {
pub fn extract_bool(&self) -> Option<bool> {
match self {
NbtTag::Byte(byte) => Some(*byte != 0),
NbtTag::Byte(byte) => Some(byte != &0),
_ => None,
}
}
pub fn extract_byte_array(&self) -> Option<Box<[u8]>> {
match self {
// Note: Bytes are free to clone, so we can hand out an owned type.
NbtTag::ByteArray(byte_array) => Some(byte_array.clone()),
_ => None,
}

View File

@@ -4,14 +4,14 @@ use pumpkin_macros::packet;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use crate::{
codec::{var_int::VarInt, var_uint::VarUInt},
codec::{bedrock_block_pos::NetworkPos, var_int::VarInt, var_uint::VarUInt},
serial::PacketWrite,
};
#[packet(121)]
pub struct CNetworkChunkPublisherUpdate {
// https://mojang.github.io/bedrock-protocol-docs/html/NetworkChunkPublisherUpdatePacket.html
pub pos_for_view: BlockPos,
pub pos_for_view: NetworkPos,
// Is in blocks, not chunks!
pub new_radius: VarUInt,
// TODO
@@ -21,8 +21,8 @@ pub struct CNetworkChunkPublisherUpdate {
impl CNetworkChunkPublisherUpdate {
pub fn new(pos_for_view: BlockPos, new_radius: u32) -> Self {
Self {
pos_for_view,
new_radius: VarUInt(new_radius << 4),
pos_for_view: NetworkPos(pos_for_view),
new_radius: VarUInt(new_radius),
server_build_chunk_list: Vec::new(),
}
}
@@ -30,7 +30,7 @@ impl CNetworkChunkPublisherUpdate {
impl PacketWrite for CNetworkChunkPublisherUpdate {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.pos_for_view.write(writer)?;
self.pos_for_view.write_signed(writer)?;
self.new_radius.write(writer)?;
(self.server_build_chunk_list.len() as u32).write(writer)?;

View File

@@ -35,7 +35,7 @@ pub struct CStartGame {
pub current_level_time: u64,
pub enchantment_seed: VarInt,
pub block_properties_size: VarUInt,
//pub block_properties: [GG; 2],
pub multiplayer_correlation_id: String,
pub enable_itemstack_net_manager: bool,
pub server_version: String,

View File

@@ -1,3 +1,212 @@
pub mod verifier;
//! # JWT Verifier for Minecraft: Bedrock Edition
//!
//! This module provides the core functionality for verifying the chain of JWT tokens
//! sent by a Minecraft: Bedrock Edition client. It handles cryptographic signature
//! verification, public key extraction, and decoding of player data.
pub use verifier::*;
use base64::{Engine as _, engine::general_purpose};
use ecdsa::Signature;
use p384::PublicKey;
use p384::ecdsa::{VerifyingKey, signature::Verifier};
use p384::pkcs8::DecodePublicKey;
use serde::Deserialize;
use serde_json::Value;
use thiserror::Error;
/// Represents the claims extracted from the player's JWT token.
///
/// This struct contains the player's display name, UUID, and XUID.
#[derive(Debug, Deserialize)]
pub struct PlayerClaims {
/// The player's display name (in-game name).
#[serde(rename = "displayName")]
pub display_name: String,
/// The player's unique identifier (UUID).
#[serde(rename = "identity")]
pub uuid: String,
/// The player's Xbox User ID (XUID).
#[serde(rename = "XUID")]
pub xuid: String,
}
/// Represents the possible errors that can occur during JWT verification.
#[derive(Debug, Error)]
pub enum AuthError {
/// Indicates that a JWT token has an invalid format (not enough parts).
#[error("Invalid token format")]
InvalidTokenFormat,
/// Indicates that the 'x5u' (X.509 URL) header parameter is missing from a token.
#[error("x5u not found in header")]
MissingX5U,
/// Indicates a failure in Base64 decoding.
#[error("Base64 decoding failed: {0}")]
Base64Decode(#[from] base64::DecodeError),
/// Indicates a failure in parsing JSON data.
#[error("JSON parse error: {0}")]
JsonParse(#[from] serde_json::Error),
/// Indicates a failure in building a public key from its representation.
#[error("Public key build failed: {0}")]
PublicKeyBuild(String),
/// Indicates that the token was not signed by the trusted Mojang public key.
#[error("Token not signed by trusted Mojang key")]
MojangKeyMismatch,
/// Indicates that the token's signature is invalid.
#[error("Invalid signature")]
InvalidSignature,
/// Indicates an error related to ECDSA signature operations.
#[error("ECDSA signature error: {0}")]
Ecdsa(#[from] ecdsa::Error),
}
/// Decodes a Base64 URL-safe encoded string with no padding.
///
/// # Arguments
///
/// * `s` - The Base64 URL-safe encoded string to decode.
///
/// # Returns
///
/// A `Result` containing the decoded bytes or a `base64::DecodeError`.
pub fn decode_b64_url_nopad(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::URL_SAFE_NO_PAD.decode(s)
}
/// Decodes a standard Base64 encoded string.
///
/// # Arguments
///
/// * `s` - The standard Base64 encoded string to decode.
///
/// # Returns
///
/// A `Result` containing the decoded bytes or a `base64::DecodeError`.
pub fn decode_b64_standard(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::STANDARD.decode(s)
}
/// Builds a P-384 public key from a Base64 encoded string.
///
/// This function supports several common public key formats.
///
/// # Arguments
///
/// * `b64` - The Base64 encoded public key.
///
/// # Returns
///
/// A `Result` containing the `p384::PublicKey` or an `AuthError`.
pub fn build_public_key_from_b64(b64: &str) -> Result<PublicKey, AuthError> {
let bytes = decode_b64_standard(b64)?;
if !bytes.is_empty() && bytes[0] == 0x30 {
PublicKey::from_public_key_der(&bytes).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else if bytes.len() == 97 && bytes[0] == 0x04 {
PublicKey::from_sec1_bytes(&bytes).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else if bytes.len() == 96 {
let mut sec1 = Vec::with_capacity(97);
sec1.push(0x04u8);
sec1.extend_from_slice(&bytes);
PublicKey::from_sec1_bytes(&sec1).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else {
Err(AuthError::PublicKeyBuild(format!(
"Unsupported key format/length: {} bytes",
bytes.len()
)))
}
}
/// Decodes the header of a JWT and extracts the 'x5u' (X.509 URL) value.
///
/// # Arguments
///
/// * `header_b64` - The Base64 URL-safe encoded header of the JWT.
///
/// # Returns
///
/// A `Result` containing the 'x5u' value as a string or an `AuthError`.
pub fn decode_header_get_x5u(header_b64: &str) -> Result<String, AuthError> {
let header_bytes = decode_b64_url_nopad(header_b64)?;
let header_json: Value = serde_json::from_slice(&header_bytes)?;
if let Some(x5u) = header_json.get("x5u")
&& let Some(s) = x5u.as_str()
{
return Ok(s.to_string());
}
Err(AuthError::MissingX5U)
}
/// Verifies a chain of JWT tokens from a Minecraft: Bedrock Edition client.
///
/// This function performs the following steps:
/// 1. Decodes and verifies each token in the chain.
/// 2. Ensures that the chain is properly linked, with each token being signed by the key from the previous one.
/// 3. Verifies that the second token in the chain is signed by the trusted Mojang public key.
/// 4. Extracts and returns the player's claims from the final token in the chain.
///
/// # Arguments
///
/// * `raw_chain` - A slice of strings, where each string is a raw JWT token.
/// * `mojang_key_b64` - The Base64 encoded Mojang public key.
///
/// # Returns
///
/// A `Result` containing the `PlayerClaims` if verification is successful, or an `AuthError` if it fails.
pub fn verify_chain(raw_chain: &[&str], mojang_key_b64: &str) -> Result<PlayerClaims, AuthError> {
let mojang_pk = VerifyingKey::from(build_public_key_from_b64(mojang_key_b64)?);
let tokens: Vec<String> = raw_chain
.iter()
.map(|t| t.trim().replace(['\n', '\r'], ""))
.collect();
let first_token = tokens.first().ok_or(AuthError::InvalidTokenFormat)?;
let first_header_b64 = first_token
.split('.')
.next()
.ok_or(AuthError::InvalidTokenFormat)?;
let mut current_verifying_key = VerifyingKey::from(build_public_key_from_b64(
&decode_header_get_x5u(first_header_b64)?,
)?);
// 4. Verification Loop
for (i, token) in tokens.iter().enumerate() {
let (signing_input, b64_sig) = token
.rsplit_once('.')
.ok_or(AuthError::InvalidTokenFormat)?;
let sig_bytes = decode_b64_url_nopad(b64_sig)?;
let signature =
Signature::from_slice(&sig_bytes).map_err(|_| AuthError::InvalidSignature)?;
current_verifying_key
.verify(signing_input.as_bytes(), &signature)
.map_err(|_| AuthError::InvalidSignature)?;
if i == 1 && current_verifying_key != mojang_pk {
return Err(AuthError::MojangKeyMismatch);
}
let payload_b64 = signing_input
.split('.')
.nth(1)
.ok_or(AuthError::InvalidTokenFormat)?;
let payload_bytes = decode_b64_url_nopad(payload_b64)?;
let payload: Value = serde_json::from_slice(&payload_bytes)?;
if let Some(next_key_b64) = payload.get("identityPublicKey").and_then(|v| v.as_str()) {
current_verifying_key = VerifyingKey::from(build_public_key_from_b64(next_key_b64)?);
}
}
let last_token = tokens.last().ok_or(AuthError::InvalidTokenFormat)?;
let last_payload_b64 = last_token
.split('.')
.nth(1)
.ok_or(AuthError::InvalidTokenFormat)?;
let payload_bytes = decode_b64_url_nopad(last_payload_b64)?;
let v: Value = serde_json::from_slice(&payload_bytes)?;
let extra_data: PlayerClaims = serde_json::from_value(v["extraData"].clone())?;
Ok(extra_data)
}

View File

@@ -1,276 +0,0 @@
//! # JWT Verifier for Minecraft: Bedrock Edition
//!
//! This module provides the core functionality for verifying the chain of JWT tokens
//! sent by a Minecraft: Bedrock Edition client. It handles cryptographic signature
//! verification, public key extraction, and decoding of player data.
use base64::{Engine as _, engine::general_purpose};
use p384::PublicKey;
use p384::ecdsa::{Signature as EcdsaSignature, VerifyingKey, signature::Verifier};
use p384::pkcs8::{DecodePublicKey, EncodePublicKey};
use serde::Deserialize;
use serde_json::Value;
use thiserror::Error;
/// Represents the claims extracted from the player's JWT token.
///
/// This struct contains the player's display name, UUID, and XUID.
#[derive(Debug, Deserialize)]
pub struct PlayerClaims {
/// The player's display name (in-game name).
#[serde(rename = "displayName")]
pub display_name: String,
/// The player's unique identifier (UUID).
#[serde(rename = "identity")]
pub uuid: String,
/// The player's Xbox User ID (XUID).
#[serde(rename = "XUID")]
pub xuid: String,
}
/// Represents the possible errors that can occur during JWT verification.
#[derive(Debug, Error)]
pub enum AuthError {
/// Indicates that a JWT token has an invalid format (not enough parts).
#[error("Invalid token format")]
InvalidTokenFormat,
/// Indicates that the 'x5u' (X.509 URL) header parameter is missing from a token.
#[error("x5u not found in header")]
MissingX5U,
/// Indicates a failure in Base64 decoding.
#[error("Base64 decoding failed: {0}")]
Base64Decode(#[from] base64::DecodeError),
/// Indicates a failure in parsing JSON data.
#[error("JSON parse error: {0}")]
JsonParse(#[from] serde_json::Error),
/// Indicates a failure in building a public key from its representation.
#[error("Public key build failed: {0}")]
PublicKeyBuild(String),
/// Indicates that the token was not signed by the trusted Mojang public key.
#[error("Token not signed by trusted Mojang key")]
MojangKeyMismatch,
/// Indicates that the token's signature is invalid.
#[error("Invalid signature")]
InvalidSignature,
/// Indicates an error related to ECDSA signature operations.
#[error("ECDSA signature error: {0}")]
Ecdsa(#[from] ecdsa::Error),
}
/// Decodes a Base64 URL-safe encoded string with no padding.
///
/// # Arguments
///
/// * `s` - The Base64 URL-safe encoded string to decode.
///
/// # Returns
///
/// A `Result` containing the decoded bytes or a `base64::DecodeError`.
pub fn decode_b64_url_nopad(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::URL_SAFE_NO_PAD.decode(s)
}
/// Decodes a standard Base64 encoded string.
///
/// # Arguments
///
/// * `s` - The standard Base64 encoded string to decode.
///
/// # Returns
///
/// A `Result` containing the decoded bytes or a `base64::DecodeError`.
pub fn decode_b64_standard(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::STANDARD.decode(s)
}
/// Builds a P-384 public key from a Base64 encoded string.
///
/// This function supports several common public key formats.
///
/// # Arguments
///
/// * `b64` - The Base64 encoded public key.
///
/// # Returns
///
/// A `Result` containing the `p384::PublicKey` or an `AuthError`.
pub fn build_public_key_from_b64(b64: &str) -> Result<PublicKey, AuthError> {
let bytes = decode_b64_standard(b64)?;
if !bytes.is_empty() && bytes[0] == 0x30 {
PublicKey::from_public_key_der(&bytes).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else if bytes.len() == 97 && bytes[0] == 0x04 {
PublicKey::from_sec1_bytes(&bytes).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else if bytes.len() == 96 {
let mut sec1 = Vec::with_capacity(97);
sec1.push(0x04u8);
sec1.extend_from_slice(&bytes);
PublicKey::from_sec1_bytes(&sec1).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
} else {
Err(AuthError::PublicKeyBuild(format!(
"Unsupported key format/length: {} bytes",
bytes.len()
)))
}
}
/// Converts a JOSE (JWS) format signature to a DER-encoded signature.
///
/// # Arguments
///
/// * `jose_sig` - The signature in JOSE format.
///
/// # Returns
///
/// A `Result` containing the DER-encoded signature or an `AuthError`.
pub fn jose_sig_to_der(jose_sig: &[u8]) -> Result<Vec<u8>, AuthError> {
if !jose_sig.len().is_multiple_of(2) {
return Err(AuthError::InvalidSignature);
}
let n = jose_sig.len() / 2;
let r = &jose_sig[..n];
let s = &jose_sig[n..];
fn encode_integer_be(bytes: &[u8]) -> (usize, Vec<u8>) {
let mut i = 0usize;
while i < bytes.len() && bytes[i] == 0 {
i += 1;
}
let mut v = bytes[i..].to_vec();
if v.is_empty() {
v.push(0u8);
}
if v[0] & 0x80 != 0 {
let mut pref = Vec::with_capacity(v.len() + 1);
pref.push(0u8);
pref.extend_from_slice(&v);
(pref.len(), pref)
} else {
let len = v.len();
(len, v)
}
}
let (r_len, r_enc) = encode_integer_be(r);
let (s_len, s_enc) = encode_integer_be(s);
let seq_len = 2 + r_len + 2 + s_len;
let mut der = Vec::with_capacity(2 + seq_len);
der.push(0x30);
der.push(seq_len as u8);
der.push(0x02);
der.push(r_len as u8);
der.extend_from_slice(&r_enc);
der.push(0x02);
der.push(s_len as u8);
der.extend_from_slice(&s_enc);
Ok(der)
}
/// Decodes the header of a JWT and extracts the 'x5u' (X.509 URL) value.
///
/// # Arguments
///
/// * `header_b64` - The Base64 URL-safe encoded header of the JWT.
///
/// # Returns
///
/// A `Result` containing the 'x5u' value as a string or an `AuthError`.
pub fn decode_header_get_x5u(header_b64: &str) -> Result<String, AuthError> {
let header_bytes = decode_b64_url_nopad(header_b64)?;
let header_json: Value = serde_json::from_slice(&header_bytes)?;
if let Some(x5u) = header_json.get("x5u")
&& let Some(s) = x5u.as_str()
{
return Ok(s.to_string());
}
Err(AuthError::MissingX5U)
}
/// Verifies a chain of JWT tokens from a Minecraft: Bedrock Edition client.
///
/// This function performs the following steps:
/// 1. Decodes and verifies each token in the chain.
/// 2. Ensures that the chain is properly linked, with each token being signed by the key from the previous one.
/// 3. Verifies that the second token in the chain is signed by the trusted Mojang public key.
/// 4. Extracts and returns the player's claims from the final token in the chain.
///
/// # Arguments
///
/// * `raw_chain` - A slice of strings, where each string is a raw JWT token.
/// * `mojang_key_b64` - The Base64 encoded Mojang public key.
///
/// # Returns
///
/// A `Result` containing the `PlayerClaims` if verification is successful, or an `AuthError` if it fails.
pub fn verify_chain(raw_chain: &[&str], mojang_key_b64: &str) -> Result<PlayerClaims, AuthError> {
let tokens: Vec<String> = raw_chain
.iter()
.map(|t| t.replace(['\n', '\r'], ""))
.collect();
let first_parts: Vec<&str> = tokens[0].split('.').collect();
if first_parts.len() != 3 {
return Err(AuthError::InvalidTokenFormat);
}
let mut next_public_b64 = decode_header_get_x5u(first_parts[0])?;
let mojang_pk = build_public_key_from_b64(mojang_key_b64)?;
for (i, token) in tokens.iter().enumerate() {
let current_pub = build_public_key_from_b64(&next_public_b64)?;
if i == 1 {
let cur_der = current_pub
.to_public_key_der()
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
let moj_der = mojang_pk
.to_public_key_der()
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
if cur_der.as_ref() != moj_der.as_ref() {
return Err(AuthError::MojangKeyMismatch);
}
}
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err(AuthError::InvalidTokenFormat);
}
let signing_input = format!("{}.{}", parts[0], parts[1]);
let signing_input_bytes = signing_input.as_bytes();
let sig_bytes = decode_b64_url_nopad(parts[2])?;
let der_sig = jose_sig_to_der(&sig_bytes)?;
let verifying_key = VerifyingKey::from(&current_pub);
let signature = EcdsaSignature::from_der(&der_sig)?;
if verifying_key
.verify(signing_input_bytes, &signature)
.is_err()
{
return Err(AuthError::InvalidSignature);
}
let payload_bytes = decode_b64_url_nopad(parts[1])?;
let payload_json: Value = serde_json::from_slice(&payload_bytes)?;
if let Some(id_pk) = payload_json.get("identityPublicKey")
&& let Some(s) = id_pk.as_str()
{
next_public_b64 = s.to_string();
}
}
let final_token = &tokens[tokens.len() - 1];
let parts: Vec<&str> = final_token.split('.').collect();
let payload = decode_b64_url_nopad(parts[1])?;
let v: Value = serde_json::from_slice(&payload)?;
let extra_data: PlayerClaims = serde_json::from_value(v["extraData"].clone())?;
Ok(extra_data)
}

View File

@@ -4,7 +4,6 @@ use crate::block::{
PlacedArgs,
};
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::BlockStateId;

View File

@@ -3,7 +3,6 @@ use crate::block::{
};
use crate::entity::EntityBase;
use pumpkin_data::block_properties::{BlockProperties, WhiteBannerLikeProperties};
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;

View File

@@ -5,7 +5,6 @@ use pumpkin_data::block_properties::BedPart;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::GameMode;
use pumpkin_util::math::position::BlockPos;

View File

@@ -17,12 +17,8 @@ use crate::{
pub struct CampfireBlock;
impl BlockMetadata for CampfireBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::CAMPFIRE.name, Block::SOUL_CAMPFIRE.name]
fn ids() -> Box<[u16]> {
[Block::CAMPFIRE.id, Block::SOUL_CAMPFIRE.id].into()
}
}

View File

@@ -1,10 +1,6 @@
use std::sync::Arc;
use pumpkin_data::{
Block,
item::Item,
tag::{RegistryKey, get_tag_values},
};
use pumpkin_data::{Block, item::Item};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::{GameMode, math::position::BlockPos};
use pumpkin_world::{

View File

@@ -3,7 +3,6 @@ use pumpkin_data::{
BlockDirection,
block_properties::{BlockProperties, CandleLikeProperties, EnumVariants, Integer1To4},
entity::EntityPose,
tag::{RegistryKey, get_tag_values},
};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;

View File

@@ -2,7 +2,6 @@ use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
};
use pumpkin_data::block_properties::is_air;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;

View File

@@ -17,12 +17,8 @@ use crate::{
pub struct CarvedPumpkinBlock;
impl BlockMetadata for CarvedPumpkinBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::JACK_O_LANTERN.name, Block::CARVED_PUMPKIN.name]
fn ids() -> Box<[u16]> {
[Block::JACK_O_LANTERN.id, Block::CARVED_PUMPKIN.id].into()
}
}

View File

@@ -5,7 +5,7 @@ use pumpkin_data::block_properties::{
BlockProperties, ChestLikeProperties, ChestType, HorizontalFacing,
};
use pumpkin_data::entity::EntityPose;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_data::tag::{self};
use pumpkin_data::{Block, BlockDirection};
use pumpkin_inventory::double::DoubleInventory;
use pumpkin_inventory::generic_container_screen_handler::{create_generic_9x3, create_generic_9x6};
@@ -67,12 +67,8 @@ impl ScreenHandlerFactory for ChestScreenFactory {
pub struct ChestBlock;
impl BlockMetadata for ChestBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "c:chests/wooden").unwrap()
fn ids() -> Box<[u16]> {
tag::Block::C_CHESTS_WOODEN.1.into()
}
}

View File

@@ -210,16 +210,13 @@ impl CommandBlock {
}
impl BlockMetadata for CommandBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
Block::COMMAND_BLOCK.name,
Block::CHAIN_COMMAND_BLOCK.name,
Block::REPEATING_COMMAND_BLOCK.name,
fn ids() -> Box<[u16]> {
[
Block::COMMAND_BLOCK.id,
Block::CHAIN_COMMAND_BLOCK.id,
Block::REPEATING_COMMAND_BLOCK.id,
]
.into()
}
}

View File

@@ -7,9 +7,7 @@ use pumpkin_data::block_properties::DoubleBlockHalf;
use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::sound::Sound;
use pumpkin_data::sound::SoundCategory;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;

View File

@@ -23,12 +23,8 @@ impl FallingBlock {
}
impl BlockMetadata for FallingBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::GRAVEL.name, Block::SAND.name, Block::RED_SAND.name]
fn ids() -> Box<[u16]> {
[Block::GRAVEL.id, Block::SAND.id, Block::RED_SAND.id].into()
}
}

View File

@@ -7,9 +7,7 @@ use crate::block::OnPlaceArgs;
use crate::entity::player::Player;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;

View File

@@ -4,9 +4,7 @@ use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection;
use pumpkin_data::BlockState;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;
@@ -19,12 +17,8 @@ use crate::world::World;
pub struct FenceBlock;
impl BlockMetadata for FenceBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "c:fences").unwrap()
fn ids() -> Box<[u16]> {
tag::Block::C_FENCES.1.into()
}
}

View File

@@ -3,7 +3,6 @@ use crate::block::{BlockBehaviour, BlockFuture, RandomTickArgs, UseWithItemArgs}
use pumpkin_data::Block;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::flower_pot_transformations::get_potted_item;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::world::BlockFlags;

View File

@@ -3,9 +3,7 @@ use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;
@@ -17,12 +15,8 @@ use crate::world::World;
pub struct GlassPaneBlock;
impl BlockMetadata for GlassPaneBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "c:glass_panes").unwrap()
fn ids() -> Box<[u16]> {
tag::Block::C_GLASS_PANES.1.into()
}
}

View File

@@ -1,16 +1,12 @@
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs};
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_data::tag::{self};
use pumpkin_world::BlockStateId;
pub struct GlazedTerracottaBlock;
impl BlockMetadata for GlazedTerracottaBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "c:glazed_terracottas").unwrap()
fn ids() -> Box<[u16]> {
tag::Item::C_GLAZED_TERRACOTTAS.1.into()
}
}

View File

@@ -14,12 +14,8 @@ use pumpkin_world::world::BlockFlags;
pub struct LanternBlock;
impl BlockMetadata for LanternBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::LANTERN.name, Block::SOUL_LANTERN.name]
fn ids() -> Box<[u16]> {
[Block::LANTERN.id, Block::SOUL_LANTERN.id].into()
}
}

View File

@@ -1,5 +1,4 @@
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::BlockStateId;

View File

@@ -29,12 +29,8 @@ pub(crate) type PistonProps = pumpkin_data::block_properties::StickyPistonLikePr
pub struct PistonBlock;
impl BlockMetadata for PistonBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::PISTON.name, Block::STICKY_PISTON.name]
fn ids() -> Box<[u16]> {
[Block::PISTON.id, Block::STICKY_PISTON.id].into()
}
}

View File

@@ -9,12 +9,8 @@ use crate::block::{
pub struct BushBlock;
impl BlockMetadata for BushBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::BUSH.name, Block::FIREFLY_BUSH.name]
fn ids() -> Box<[u16]> {
[Block::BUSH.id, Block::FIREFLY_BUSH.id].into()
}
}

View File

@@ -18,15 +18,12 @@ type StemProperties = WheatLikeProperties;
pub struct AttachedStemBlock;
impl BlockMetadata for AttachedStemBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
Block::ATTACHED_PUMPKIN_STEM.name,
Block::ATTACHED_MELON_STEM.name,
fn ids() -> Box<[u16]> {
[
Block::ATTACHED_PUMPKIN_STEM.id,
Block::ATTACHED_MELON_STEM.id,
]
.into()
}
}

View File

@@ -27,12 +27,8 @@ type AttachedStemProperties = WallTorchLikeProperties;
pub struct StemBlock;
impl BlockMetadata for StemBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::PUMPKIN_STEM.name, Block::MELON_STEM.name]
fn ids() -> Box<[u16]> {
[Block::PUMPKIN_STEM.id, Block::MELON_STEM.id].into()
}
}

View File

@@ -1,5 +1,5 @@
use pumpkin_data::tag;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, world::BlockAccessor};
@@ -11,12 +11,13 @@ use crate::block::{
pub struct DryVegetationBlock;
impl BlockMetadata for DryVegetationBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&["dead_bush", "tall_dry_grass", "short_dry_grass"]
fn ids() -> Box<[u16]> {
[
Block::DEAD_BUSH.id,
Block::TALL_DRY_GRASS.id,
Block::SHORT_DRY_GRASS.id,
]
.into()
}
}

View File

@@ -1,6 +1,5 @@
use pumpkin_data::Block;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_data::{Block, tag};
use pumpkin_world::BlockStateId;
use pumpkin_world::world::BlockFlags;
@@ -14,12 +13,8 @@ use crate::block::RandomTickArgs;
pub struct FlowerBlock;
impl BlockMetadata for FlowerBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "c:flowers/small").unwrap()
fn ids() -> Box<[u16]> {
tag::Block::C_FLOWERS_SMALL.1.into()
}
}

View File

@@ -16,12 +16,8 @@ type FlowerbedProperties = pumpkin_data::block_properties::PinkPetalsLikePropert
pub struct FlowerbedBlock;
impl BlockMetadata for FlowerbedBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&["pink_petals", "wildflowers"]
fn ids() -> Box<[u16]> {
[Block::PINK_PETALS.id, Block::WILDFLOWERS.id].into()
}
}

View File

@@ -13,12 +13,8 @@ type LeafLitterProperties = pumpkin_data::block_properties::LeafLitterLikeProper
pub struct LeafLitterBlock;
impl BlockMetadata for LeafLitterBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&["leaf_litter"]
fn ids() -> Box<[u16]> {
[Block::LEAF_LITTER.id].into()
}
}

View File

@@ -1,5 +1,5 @@
use pumpkin_data::tag;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, world::BlockAccessor};
@@ -11,12 +11,8 @@ use crate::block::{
pub struct MushroomPlantBlock;
impl BlockMetadata for MushroomPlantBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&["brown_mushroom", "red_mushroom"]
fn ids() -> Box<[u16]> {
[Block::BROWN_MUSHROOM.id, Block::RED_MUSHROOM.id].into()
}
}

View File

@@ -12,12 +12,8 @@ use crate::block::{
pub struct RootsBlock;
impl BlockMetadata for RootsBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::WARPED_ROOTS.name, Block::CRIMSON_ROOTS.name]
fn ids() -> Box<[u16]> {
[Block::WARPED_ROOTS.id, Block::CRIMSON_ROOTS.id].into()
}
}

View File

@@ -1,5 +1,4 @@
use pumpkin_data::block_properties::{BlockProperties, Integer0To1};
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;

View File

@@ -1,3 +1,4 @@
use pumpkin_data::Block;
use pumpkin_world::BlockStateId;
use crate::block::{
@@ -8,12 +9,8 @@ use crate::block::{
pub struct ShortPlantBlock;
impl BlockMetadata for ShortPlantBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&["short_grass", "fern"]
fn ids() -> Box<[u16]> {
[Block::SHORT_GRASS.id, Block::FERN.id].into()
}
}

View File

@@ -13,21 +13,18 @@ use crate::block::{
pub struct TallPlantBlock;
impl BlockMetadata for TallPlantBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
"tall_grass",
"large_fern",
"pitcher_plant",
fn ids() -> Box<[u16]> {
[
Block::TALL_GRASS.id,
Block::LARGE_FERN.id,
Block::PITCHER_PLANT.id,
// TallFlowerBlocks
"sunflower",
"lilac",
"peony",
"rose_bush",
Block::SUNFLOWER.id,
Block::LILAC.id,
Block::PEONY.id,
Block::ROSE_BUSH.id,
]
.into()
}
}
@@ -56,7 +53,7 @@ impl BlockBehaviour for TallPlantBlock {
};
let (other_block, other_state_id) =
args.world.get_block_and_state_id(&other_block_pos).await;
if self.ids().contains(&other_block.name) {
if Self::ids().contains(&other_block.id) {
let other_props =
TallSeagrassLikeProperties::from_state_id(other_state_id, other_block);
let opposite_half = match tall_plant_props.half {

View File

@@ -5,8 +5,6 @@ use pumpkin_data::BlockDirection;
use pumpkin_data::HorizontalFacingExt;
use pumpkin_data::block_properties::BlockFace;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::get_tag_values;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;

View File

@@ -1,5 +1,6 @@
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnNeighborUpdateArgs, OnPlaceArgs};
use pumpkin_data::Block;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_world::BlockStateId;
@@ -10,21 +11,18 @@ type CopperBulbLikeProperties = pumpkin_data::block_properties::CopperBulbLikePr
pub struct CopperBulbBlock;
impl BlockMetadata for CopperBulbBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
"copper_bulb",
"exposed_copper_bulb",
"weathered_copper_bulb",
"oxidized_copper_bulb",
"waxed_copper_bulb",
"waxed_exposed_copper_bulb",
"waxed_weathered_copper_bulb",
"waxed_oxidized_copper_bulb",
fn ids() -> Box<[u16]> {
[
Block::COPPER_BULB.id,
Block::EXPOSED_COPPER_BULB.id,
Block::WEATHERED_COPPER_BULB.id,
Block::OXIDIZED_COPPER_BULB.id,
Block::WAXED_COPPER_BULB.id,
Block::WAXED_EXPOSED_COPPER_BULB.id,
Block::WAXED_WEATHERED_COPPER_BULB.id,
Block::WAXED_OXIDIZED_COPPER_BULB.id,
]
.into()
}
}

View File

@@ -1,7 +1,7 @@
use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::BlockProperties,
tag::{RegistryKey, get_tag_values},
tag::{self},
};
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos};
use pumpkin_world::{BlockStateId, world::BlockFlags};
@@ -23,19 +23,11 @@ pub struct PressurePlateBlock;
type PressurePlateProps = pumpkin_data::block_properties::StonePressurePlateLikeProperties;
impl BlockMetadata for PressurePlateBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
fn ids() -> Box<[u16]> {
let mut combined = Vec::new();
combined.extend_from_slice(
get_tag_values(RegistryKey::Block, "minecraft:wooden_pressure_plates").unwrap(),
);
combined.extend_from_slice(
get_tag_values(RegistryKey::Block, "minecraft:stone_pressure_plates").unwrap(),
);
combined.leak()
combined.extend_from_slice(tag::Block::MINECRAFT_WOODEN_PRESSURE_PLATES.1);
combined.extend_from_slice(tag::Block::MINECRAFT_STONE_PRESSURE_PLATES.1);
combined.into_boxed_slice()
}
}

View File

@@ -22,17 +22,14 @@ pub struct WeightedPressurePlateBlock;
type PressurePlateProps = pumpkin_data::block_properties::LightWeightedPressurePlateLikeProperties;
impl BlockMetadata for WeightedPressurePlateBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
fn ids() -> Box<[u16]> {
// light = Gold
// heavy = Iron
&[
"light_weighted_pressure_plate",
"heavy_weighted_pressure_plate",
[
Block::LIGHT_WEIGHTED_PRESSURE_PLATE.id,
Block::HEAVY_WEIGHTED_PRESSURE_PLATE.id,
]
.into()
}
}

View File

@@ -35,12 +35,8 @@ use super::get_redstone_power;
pub struct RedstoneTorchBlock;
impl BlockMetadata for RedstoneTorchBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::REDSTONE_TORCH.name, Block::REDSTONE_WALL_TORCH.name]
fn ids() -> Box<[u16]> {
[Block::REDSTONE_TORCH.id, Block::REDSTONE_WALL_TORCH.id].into()
}
}

View File

@@ -7,7 +7,7 @@ use crate::block::{
};
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_data::tag::{self};
use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
@@ -44,12 +44,8 @@ impl ScreenHandlerFactory for ShulkerBoxScreenFactory {
pub struct ShulkerBoxBlock;
impl BlockMetadata for ShulkerBoxBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
get_tag_values(RegistryKey::Block, "minecraft:shulker_boxes").unwrap()
fn ids() -> Box<[u16]> {
tag::Block::MINECRAFT_SHULKER_BOXES.1.into()
}
}

View File

@@ -5,8 +5,6 @@ use pumpkin_data::Block;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::EnumVariants;
use pumpkin_data::block_properties::Integer0To15;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::get_tag_values;
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
@@ -160,7 +158,8 @@ impl BlockBehaviour for SignBlock {
let mut item = args.item_stack.lock().await;
let Some(pumpkin_item) = args.server.item_registry.get_pumpkin_item(item.item) else {
let Some(pumpkin_item) = args.server.item_registry.get_pumpkin_item(item.item.id)
else {
return BlockActionResult::PassToDefaultBlockAction;
};

View File

@@ -1,6 +1,7 @@
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnNeighborUpdateArgs, OnPlaceArgs};
use crate::entity::EntityBase;
use pumpkin_data::Block;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_world::BlockStateId;
use pumpkin_world::world::BlockFlags;
@@ -10,19 +11,16 @@ type SkeletonSkullLikeProperties = pumpkin_data::block_properties::SkeletonSkull
pub struct SkullBlock;
impl BlockMetadata for SkullBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
"skeleton_skull",
"player_head",
"zombie_head",
"creeper_head",
"piglin_head",
"dragon_head",
fn ids() -> Box<[u16]> {
[
Block::SKELETON_SKULL.id,
Block::PLAYER_HEAD.id,
Block::ZOMBIE_HEAD.id,
Block::CREEPER_HEAD.id,
Block::PIGLIN_HEAD.id,
Block::DRAGON_HEAD.id,
]
.into()
}
}

View File

@@ -1,8 +1,6 @@
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::SlabType;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::get_tag_values;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::BlockStateId;

View File

@@ -2,9 +2,7 @@ use pumpkin_data::block_properties::BlockHalf;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::block_properties::StairShape;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{BlockDirection, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;

View File

@@ -17,17 +17,14 @@ use crate::block::{
pub struct TorchBlock;
impl BlockMetadata for TorchBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[
Block::TORCH.name,
Block::SOUL_TORCH.name,
Block::WALL_TORCH.name,
Block::SOUL_WALL_TORCH.name,
fn ids() -> Box<[u16]> {
[
Block::TORCH.id,
Block::SOUL_TORCH.id,
Block::WALL_TORCH.id,
Block::SOUL_WALL_TORCH.id,
]
.into()
}
}

View File

@@ -6,7 +6,7 @@ use crate::world::World;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::{BlockHalf, BlockProperties};
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::tag::{RegistryKey, Taggable, get_tag_values};
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;

View File

@@ -9,9 +9,7 @@ use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::block_properties::NorthWallShape;
use pumpkin_data::block_properties::SouthWallShape;
use pumpkin_data::block_properties::WestWallShape;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;

View File

@@ -12,12 +12,8 @@ use crate::{
pub struct WitherSkeletonSkullBlock;
impl BlockMetadata for WitherSkeletonSkullBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::WITHER_SKELETON_SKULL.name]
fn ids() -> Box<[u16]> {
[Block::WITHER_SKELETON_SKULL.id].into()
}
}

View File

@@ -6,13 +6,12 @@ use pumpkin_data::{
fluid::{Falling, Fluid, FluidProperties, Level},
world::WorldEvent,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, tick::TickPriority, world::BlockFlags};
use crate::{
block::{
BlockFuture,
BlockFuture, BlockMetadata,
fluid::{FluidBehaviour, flowing::FluidFuture},
},
entity::EntityBase,
@@ -22,9 +21,14 @@ use crate::{
use super::flowing::FlowingFluid;
type FlowingFluidProperties = pumpkin_data::fluid::FlowingWaterLikeFluidProperties;
#[pumpkin_block("minecraft:flowing_lava")]
pub struct FlowingLava;
impl BlockMetadata for FlowingLava {
fn ids() -> Box<[u16]> {
[Fluid::FLOWING_LAVA.id].into()
}
}
impl FlowingLava {
async fn receive_neighbor_fluids(
&self,

View File

@@ -1,21 +1,25 @@
use std::sync::Arc;
use pumpkin_data::fluid::Fluid;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{BlockStateId, tick::TickPriority};
use crate::{
block::{BlockFuture, fluid::FluidBehaviour},
block::{BlockFuture, BlockMetadata, fluid::FluidBehaviour},
entity::EntityBase,
world::World,
};
use super::flowing::FlowingFluid;
#[pumpkin_block("minecraft:flowing_water")]
pub struct FlowingWater;
impl BlockMetadata for FlowingWater {
fn ids() -> Box<[u16]> {
[Fluid::FLOWING_WATER.id].into()
}
}
const WATER_FLOW_SPEED: u8 = 5;
impl FluidBehaviour for FlowingWater {

View File

@@ -27,14 +27,7 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
use tokio::sync::Mutex;
pub trait BlockMetadata {
fn namespace(&self) -> &'static str;
fn ids(&self) -> &'static [&'static str];
fn names(&self) -> Vec<String> {
self.ids()
.iter()
.map(|f| format!("{}:{}", self.namespace(), f))
.collect()
}
fn ids() -> Box<[u16]>;
}
pub type BlockFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

View File

@@ -88,7 +88,6 @@ use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use pumpkin_data::fluid;
use pumpkin_data::fluid::Fluid;
use pumpkin_data::item::Item;
use pumpkin_data::{Block, BlockDirection, BlockState};
@@ -338,22 +337,20 @@ impl BlockRegistryExt for BlockRegistry {
impl BlockRegistry {
pub fn register<T: BlockBehaviour + BlockMetadata + 'static>(&mut self, block: T) {
let names = block.names();
let ids = T::ids();
let val = Arc::new(block);
self.blocks.reserve(names.len());
for i in names {
self.blocks
.insert(Block::from_name(i.as_str()).unwrap().id, val.clone());
self.blocks.reserve(ids.len());
for i in ids {
self.blocks.insert(i, val.clone());
}
}
pub fn register_fluid<T: FluidBehaviour + BlockMetadata + 'static>(&mut self, fluid: T) {
let names = fluid.names();
let ids = T::ids();
let val = Arc::new(fluid);
self.fluids.reserve(names.len());
for i in names {
self.fluids
.insert(fluid::get_fluid(i.as_str()).unwrap().id, val.clone());
self.fluids.reserve(ids.len());
for i in ids {
self.fluids.insert(i, val.clone());
}
}

View File

@@ -8,7 +8,7 @@ use crate::{
},
data::{
SaveJSONConfiguration, banlist_serializer::BannedPlayerEntry,
banned_player_data::BANNED_PLAYER_LIST,
banned_player::BANNED_PLAYER_LIST,
},
entity::player::Player,
net::DisconnectReason,

View File

@@ -6,9 +6,7 @@ use crate::{
args::{Arg, ConsumedArgs, message::MsgArgConsumer, simple::SimpleArgConsumer},
tree::{CommandTree, builder::argument},
},
data::{
SaveJSONConfiguration, banlist_serializer::BannedIpEntry, banned_ip_data::BANNED_IP_LIST,
},
data::{SaveJSONConfiguration, banlist_serializer::BannedIpEntry, banned_ip::BANNED_IP_LIST},
net::DisconnectReason,
server::Server,
};

View File

@@ -4,7 +4,7 @@ use crate::{
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::{CommandTree, builder::argument},
},
data::{banned_ip_data::BANNED_IP_LIST, banned_player_data::BANNED_PLAYER_LIST},
data::{banned_ip::BANNED_IP_LIST, banned_player::BANNED_PLAYER_LIST},
};
use CommandError::InvalidConsumption;
use pumpkin_util::text::TextComponent;

View File

@@ -7,7 +7,7 @@ use crate::{
tree::CommandTree,
tree::builder::argument,
},
data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG},
data::{SaveJSONConfiguration, op::OPERATOR_CONFIG},
};
use CommandError::InvalidConsumption;
use pumpkin_util::text::TextComponent;

View File

@@ -7,7 +7,7 @@ use crate::{
tree::CommandTree,
tree::builder::argument,
},
data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG},
data::{SaveJSONConfiguration, op::OPERATOR_CONFIG},
};
use CommandError::InvalidConsumption;
use pumpkin_config::op::Op;

View File

@@ -4,7 +4,7 @@ use crate::{
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::{CommandTree, builder::argument},
},
data::{SaveJSONConfiguration, banned_player_data::BANNED_PLAYER_LIST},
data::{SaveJSONConfiguration, banned_player::BANNED_PLAYER_LIST},
};
use CommandError::InvalidConsumption;
use pumpkin_util::text::TextComponent;

View File

@@ -6,7 +6,7 @@ use crate::{
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::{CommandTree, builder::argument},
},
data::{SaveJSONConfiguration, banned_ip_data::BANNED_IP_LIST},
data::{SaveJSONConfiguration, banned_ip::BANNED_IP_LIST},
};
use CommandError::InvalidConsumption;
use pumpkin_util::text::TextComponent;

View File

@@ -17,7 +17,7 @@ use crate::{
},
data::{
LoadJSONConfiguration, SaveJSONConfiguration,
whitelist_data::{WHITELIST_CONFIG, WhitelistConfig},
whitelist::{WHITELIST_CONFIG, WhitelistConfig},
},
net::DisconnectReason,
server::Server,

View File

@@ -4,13 +4,13 @@ use serde::{Deserialize, Serialize};
const DATA_FOLDER: &str = "data/";
pub mod op_data;
pub mod op;
pub mod banlist_serializer;
pub mod banned_ip_data;
pub mod banned_player_data;
pub mod player_server_data;
pub mod whitelist_data;
pub mod banned_ip;
pub mod banned_player;
pub mod player_server;
pub mod whitelist;
pub trait LoadJSONConfiguration {
#[must_use]

View File

@@ -177,7 +177,7 @@ impl ServerPlayerData {
#[cfg(test)]
mod test {
use crate::data::player_server_data::ServerPlayerData;
use crate::data::player_server::ServerPlayerData;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_world::data::player_data::PlayerDataStorage;
use std::time::Duration;

View File

@@ -1,18 +1,18 @@
use std::sync::Arc;
use crate::entity::NbtFuture;
use super::{EntityBase, NBTStorage, NBTStorageInit, player::Player};
use crate::entity::NbtFuture;
use crossbeam::atomic::AtomicCell;
use pumpkin_data::damage::DamageType;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::Difficulty;
// TODO: This entire thing should be atomic, not individual fields
const MAX_FOOD: u8 = 20;
const EXHAUSTION_COST: f32 = 4.0;
const MAX_EXHAUSTION: f32 = 40.0;
pub struct HungerManager {
/// The current hunger level.
pub level: AtomicCell<u8>,
/// The food saturation level.
pub saturation: AtomicCell<f32>,
pub exhaustion: AtomicCell<f32>,
pub tick_timer: AtomicCell<u32>,
@@ -21,7 +21,7 @@ pub struct HungerManager {
impl Default for HungerManager {
fn default() -> Self {
Self {
level: AtomicCell::new(20),
level: AtomicCell::new(MAX_FOOD),
saturation: AtomicCell::new(5.0),
exhaustion: AtomicCell::new(0.0),
tick_timer: AtomicCell::new(0),
@@ -31,76 +31,113 @@ impl Default for HungerManager {
impl HungerManager {
pub async fn tick(&self, player: &Arc<Player>) {
let saturation = self.saturation.load();
let level = self.level.load();
let exhaustion = self.exhaustion.load();
let health = player.living_entity.health.load();
let mut level = self.level.load();
let mut saturation = self.saturation.load();
let mut exhaustion = self.exhaustion.load();
let mut timer = self.tick_timer.load();
let difficulty = player.world().level_info.read().await.difficulty;
// Decrease hunger level on exhaustion
if level != 0 && exhaustion > 4.0 {
self.exhaustion.store(exhaustion - 4.0);
let health = player.living_entity.health.load();
let can_heal = player.can_food_heal();
let mut needs_sync = false;
let mut heal_amount = 0.0;
let mut damage_amount = 0.0;
if exhaustion > EXHAUSTION_COST {
exhaustion -= EXHAUSTION_COST;
if saturation > 0.0 {
self.saturation.store((saturation - 1.0).max(0.0));
saturation = (saturation - 1.0).max(0.0);
} else if difficulty != Difficulty::Peaceful {
self.level.store(level - 1);
player.send_health().await;
level = level.saturating_sub(1);
}
needs_sync = true;
}
// Heal when hunger is full
let natural_regen = true; // TODO: Get the actual value when this will be implemented.
if natural_regen && saturation > 0.0 && player.can_food_heal() && level >= 20 {
self.tick_timer.fetch_add(1);
if self.tick_timer.load() >= 10 {
let saturation = saturation.min(6.0);
player.heal(saturation / 6.0).await;
self.add_exhaustion(saturation);
self.tick_timer.store(0);
let natural_regen = true; // TODO: GameRule check
if natural_regen && saturation > 0.0 && can_heal && level >= 20 {
timer += 1;
if timer >= 10 {
let cost = saturation.min(6.0);
saturation -= cost;
exhaustion += cost;
heal_amount = cost / 6.0;
timer = 0;
needs_sync = true;
}
} else if natural_regen && level >= 18 && player.can_food_heal() {
self.tick_timer.fetch_add(1);
if self.tick_timer.load() >= 80 {
player.heal(1.0).await;
self.add_exhaustion(6.0);
self.tick_timer.store(0);
} else if natural_regen && level >= 18 && can_heal {
timer += 1;
if timer >= 80 {
heal_amount = 1.0;
exhaustion += 6.0;
timer = 0;
needs_sync = true;
}
} else if level == 0 {
self.tick_timer.fetch_add(1);
if self.tick_timer.load() >= 80 {
if (health > 10.0)
|| (difficulty == Difficulty::Hard)
|| (health > 1.0 && difficulty == Difficulty::Normal)
{
player.damage(&**player, 1.0, DamageType::STARVE).await;
timer += 1;
if timer >= 80 {
timer = 0;
let should_starve = match difficulty {
Difficulty::Peaceful => false,
Difficulty::Easy => health > 10.0,
Difficulty::Normal => health > 1.0,
Difficulty::Hard => true,
};
if should_starve {
damage_amount = 1.0;
}
self.tick_timer.store(0);
}
} else {
self.tick_timer.store(0);
timer = 0;
}
if needs_sync || timer != self.tick_timer.load() {
self.level.store(level);
self.saturation.store(saturation);
self.exhaustion.store(exhaustion);
self.tick_timer.store(timer);
}
if needs_sync {
player.send_health().await;
}
if heal_amount > 0.0 {
player.heal(heal_amount).await;
}
if damage_amount > 0.0 {
player
.damage(&**player, damage_amount, DamageType::STARVE)
.await;
}
}
pub async fn add_modifier(&self, player: &Player, food: u8, saturation_modifier: f32) {
let saturation = f32::from(food) * saturation_modifier * 2.0;
self.level.store(food + self.level.load());
self.saturation.store(saturation + self.saturation.load());
player.send_health().await;
}
pub async fn eat(&self, player: &Player, food: u8, saturation_modifier: f32) {
let added_saturation = f32::from(food) * saturation_modifier * 2.0;
pub async fn eat(&self, player: &Player, food: u8, saturation: f32) {
self.level.store(food + self.level.load());
self.saturation.store(saturation + self.saturation.load());
let current_level = self.level.load();
let current_sat = self.saturation.load();
let new_level = (current_level + food).min(MAX_FOOD);
let new_sat = (current_sat + added_saturation).min(f32::from(new_level));
self.level.store(new_level);
self.saturation.store(new_sat);
player.send_health().await;
}
pub fn add_exhaustion(&self, exhaustion: f32) {
let current = self.exhaustion.load();
self.exhaustion
.store((self.exhaustion.load() + exhaustion).min(40.0));
.store((current + exhaustion).min(MAX_EXHAUSTION));
}
pub fn restart(&self) {
self.level.store(20);
self.level.store(MAX_FOOD);
self.saturation.store(5.0);
self.exhaustion.store(0.0);
self.tick_timer.store(0);
@@ -108,7 +145,6 @@ impl HungerManager {
}
impl NBTStorage for HungerManager {
// TODO: Proper value checks
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
nbt.put_int("foodLevel", self.level.load().into());

View File

@@ -77,7 +77,7 @@ use pumpkin_world::level::{Level, SyncChunk, SyncEntityChunk};
use crate::block::blocks::bed::BedBlock;
use crate::command::client_suggestions;
use crate::command::dispatcher::CommandDispatcher;
use crate::data::op_data::OPERATOR_CONFIG;
use crate::data::op::OPERATOR_CONFIG;
use crate::entity::{EntityBaseFuture, NbtFuture, TeleportFuture};
use crate::net::{ClientPlatform, GameProfile};
use crate::net::{DisconnectReason, PlayerConfig};
@@ -1210,47 +1210,76 @@ impl Player {
.await;
}
ClientPlatform::Bedrock(bedrock) => {
let mut ability_value = 0;
let abilities = &self.abilities.lock().await;
let abilities = self.abilities.lock().await;
let is_op = self.permission_lvl.load() == PermissionLvl::Four;
let is_spectator = self.gamemode.load() == GameMode::Spectator;
if abilities.invulnerable {
ability_value |= 1 << Ability::Invulnerable as u32;
}
// 1. Permission Mapping
let player_perm = if is_op { 2 } else { 1 }; // 1: Member, 2: Operator
let command_perm = u8::from(is_op); // 0: Normal, 1: Operator
if abilities.flying {
ability_value |= 1 << Ability::Flying as u32;
}
// 2. Build the Ability Bitmask
let mut ability_value: u32 = 0;
if abilities.allow_flying {
ability_value |= 1 << Ability::MayFly as u32;
}
// Helper closure to set bits using your enum
let mut set_ability = |ability: Ability, enabled: bool| {
if enabled {
ability_value |= 1 << (ability as u32);
}
};
if abilities.creative {
ability_value |= 1 << Ability::OperatorCommands as u32;
ability_value |= 1 << Ability::Teleport as u32;
ability_value |= 1 << Ability::Invulnerable as u32;
}
// Base Permissions
set_ability(Ability::MayFly, abilities.allow_flying);
set_ability(Ability::Flying, abilities.flying);
set_ability(
Ability::Invulnerable,
abilities.invulnerable || abilities.creative,
);
// Todo: Integrate this into the system
ability_value |= 1 << Ability::AttackMobs as u32;
ability_value |= 1 << Ability::AttackPlayers as u32;
ability_value |= 1 << Ability::Build as u32;
ability_value |= 1 << Ability::DoorsAndSwitches as u32;
ability_value |= 1 << Ability::Instabuild as u32;
ability_value |= 1 << Ability::Mine as u32;
// Operator Specifics
set_ability(Ability::OperatorCommands, is_op);
set_ability(Ability::Teleport, is_op);
let packet = CUpdateAbilities {
target_player_raw_id: self.entity_id().into(),
player_permission: 2,
command_permission: 4,
layers: vec![AbilityLayer {
// Interaction Permissions (Disabled for Spectators)
let can_interact = !is_spectator;
set_ability(Ability::Build, can_interact);
set_ability(Ability::Mine, can_interact);
set_ability(Ability::DoorsAndSwitches, can_interact);
set_ability(Ability::OpenContainers, can_interact);
set_ability(Ability::AttackPlayers, can_interact);
set_ability(Ability::AttackMobs, can_interact);
// Creative/Spectator Extras
set_ability(Ability::Instabuild, abilities.creative);
set_ability(Ability::NoClip, is_spectator);
// 3. Construct the Layers
let mut layers = vec![AbilityLayer {
serialized_layer: 0, // LAYER_BASE
// 0x3FFFF defines the first 18 bits as "provided" by this packet
abilities_set: (1 << Ability::AbilityCount as u32) - 1,
ability_value,
fly_speed: 0.05,
vertical_fly_speed: 1.0,
walk_speed: 0.1,
}];
if is_spectator {
layers.push(AbilityLayer {
serialized_layer: 1,
abilities_set: (1 << Ability::AbilityCount as u32) - 1,
ability_value,
abilities_set: 1 << (Ability::Flying as u32),
ability_value: 1 << (Ability::Flying as u32),
fly_speed: 0.05,
vertical_fly_speed: 1.0,
walk_speed: 0.1,
}],
});
}
let packet = CUpdateAbilities {
target_player_raw_id: self.entity_id().into(),
player_permission: player_perm,
command_permission: command_perm,
layers,
};
bedrock.send_game_packet(&packet).await;

View File

@@ -14,7 +14,7 @@ use super::{ItemBehaviour, ItemMetadata};
#[derive(Default)]
pub struct ItemRegistry {
items: HashMap<&'static Item, Arc<dyn ItemBehaviour>>,
items: HashMap<u16, Arc<dyn ItemBehaviour>>,
}
impl ItemRegistry {
@@ -22,12 +22,12 @@ impl ItemRegistry {
let val = Arc::new(item);
self.items.reserve(T::ids().len());
for i in T::ids() {
self.items.insert(Item::from_id(i).unwrap(), val.clone());
self.items.insert(i, val.clone());
}
}
pub async fn on_use(&self, item: &Item, player: &Player) {
let pumpkin_item = self.get_pumpkin_item(item);
let pumpkin_item = self.get_pumpkin_item(item.id);
if let Some(pumpkin_item) = pumpkin_item {
pumpkin_item.normal_use(item, player).await;
}
@@ -44,7 +44,7 @@ impl ItemRegistry {
block: &Block,
server: &Server,
) {
let pumpkin_item = self.get_pumpkin_item(stack.item);
let pumpkin_item = self.get_pumpkin_item(stack.item.id);
if let Some(pumpkin_item) = pumpkin_item {
pumpkin_item
.use_on_block(stack, player, location, face, cursor_pos, block, server)
@@ -58,14 +58,14 @@ impl ItemRegistry {
player: &Player,
entity: Arc<dyn EntityBase>,
) {
let pumpkin_item = self.get_pumpkin_item(stack.item);
let pumpkin_item = self.get_pumpkin_item(stack.item.id);
if let Some(pumpkin_item) = pumpkin_item {
pumpkin_item.use_on_entity(stack, player, entity).await;
}
}
pub fn can_mine(&self, item: &Item, player: &Player) -> bool {
let pumpkin_block = self.get_pumpkin_item(item);
let pumpkin_block = self.get_pumpkin_item(item.id);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block.can_mine(player);
}
@@ -73,7 +73,7 @@ impl ItemRegistry {
}
#[must_use]
pub fn get_pumpkin_item(&self, item: &Item) -> Option<&dyn ItemBehaviour> {
self.items.get(item).map(|value| &**value)
pub fn get_pumpkin_item(&self, item: u16) -> Option<&Arc<dyn ItemBehaviour>> {
self.items.get(&item)
}
}

View File

@@ -6,8 +6,8 @@ use std::{
use crate::{
data::{
banned_ip_data::BANNED_IP_LIST, banned_player_data::BANNED_PLAYER_LIST,
op_data::OPERATOR_CONFIG, whitelist_data::WHITELIST_CONFIG,
banned_ip::BANNED_IP_LIST, banned_player::BANNED_PLAYER_LIST, op::OPERATOR_CONFIG,
whitelist::WHITELIST_CONFIG,
},
entity::player::ChatMode,
net::{bedrock::BedrockClient, java::JavaClient},

View File

@@ -1,7 +1,7 @@
use crate::block::registry::BlockRegistry;
use crate::command::commands::default_dispatcher;
use crate::command::commands::defaultgamemode::DefaultGamemode;
use crate::data::player_server_data::ServerPlayerData;
use crate::data::player_server::ServerPlayerData;
use crate::entity::{EntityBase, NBTStorage};
use crate::item::registry::ItemRegistry;
use crate::net::{ClientPlatform, DisconnectReason, EncryptionError, GameProfile, PlayerConfig};

View File

@@ -1,4 +1,3 @@
use std::num::NonZeroU32;
use std::pin::Pin;
use std::sync::Weak;
use std::sync::atomic::Ordering::Relaxed;
@@ -57,8 +56,6 @@ use pumpkin_data::{
use pumpkin_data::{BlockDirection, BlockState};
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
use pumpkin_protocol::bedrock::client::chunk_radius_update::CChunkRadiusUpdate;
use pumpkin_protocol::bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate;
use pumpkin_protocol::bedrock::client::start_game::CStartGame;
use pumpkin_protocol::bedrock::frame_set::FrameSet;
use pumpkin_protocol::java::client::play::CPlayerSpawnPosition;
@@ -1175,6 +1172,24 @@ impl World {
let level_info = server.level_info.read().await;
let weather = self.weather.lock().await;
let runtime_id = player.entity_id() as u64;
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
let position = player.position();
let yaw = player.living_entity.entity.yaw.load(); //info.spawn_angle;
let pitch = player.living_entity.entity.pitch.load();
(position, yaw, pitch)
} else {
let info = &self.level_info.read().await;
let spawn_position = Vector2::new(info.spawn_x, info.spawn_z);
let pos_y = self.get_top_block(spawn_position).await + 1; // +1 to spawn on top of the block
let position = Vector3::new(
f64::from(info.spawn_x) + 0.5,
f64::from(pos_y),
f64::from(info.spawn_z) + 0.5,
);
(position, info.spawn_yaw, info.spawn_pitch)
};
// Todo make the data less spread
let level_settings = LevelSettings {
seed: self.level.seed.0,
@@ -1253,22 +1268,22 @@ impl World {
entity_id: VarLong(runtime_id as _),
runtime_entity_id: VarULong(runtime_id),
player_gamemode: player.gamemode.load(),
position: Vector3::new(0.0, 200.0, 0.0),
pitch: 0.0,
yaw: 0.0,
position: Vector3::new(position.x as f32, position.y as f32, position.z as f32),
pitch,
yaw,
level_settings,
level_id: String::new(),
level_name: "Pumpkin world".to_string(),
premium_world_template_id: String::new(),
is_trial: false,
rewind_history_size: VarInt(40),
server_authoritative_block_breaking: false,
rewind_history_size: VarInt(0),
server_authoritative_block_breaking: true,
current_level_time: self.level_time.lock().await.world_age as _,
enchantment_seed: VarInt(0),
block_properties_size: VarUInt(0),
// TODO Make this unique
multiplayer_correlation_id: Uuid::default().to_string(),
enable_itemstack_net_manager: false,
enable_itemstack_net_manager: true,
// TODO Make this description better!
// This gets send from the client to mojang for telemetry
server_version: "Pumpkin Rust Server".to_string(),
@@ -1278,18 +1293,13 @@ impl World {
compound_end: 0,
block_registry_checksum: 0,
world_template_id: Uuid::default(),
world_template_id: Uuid::nil(),
// TODO The client needs extra biome data for this
enable_clientside_generation: false,
blocknetwork_ids_are_hashed: false,
server_auth_sounds: false,
})
.await;
client
.send_game_packet(&CChunkRadiusUpdate {
chunk_radius: VarInt(player.config.read().await.view_distance.get().into()),
})
.await;
chunker::update_position(&player).await;
client
.send_game_packet(&CreativeContent {
@@ -1302,48 +1312,91 @@ impl World {
})
.await;
let mut frame_set = FrameSet::default();
client
.write_game_packet_to_set(
&CNetworkChunkPublisherUpdate::new(
BlockPos::new(0, 200, 0),
NonZeroU32::from(player.config.read().await.view_distance).into(),
),
&mut frame_set,
)
.await;
client
.write_game_packet_to_set(
&CUpdateAttributes {
runtime_id: VarULong(runtime_id),
attributes: vec![Attribute {
min_value: 0.0,
max_value: f32::MAX,
current_value: 0.1,
default_min_value: 0.0,
default_max_value: f32::MAX,
default_value: 0.1,
name: "minecraft:movement".to_string(),
modifiers_list_size: VarUInt(0),
}],
player_tick: VarULong(0),
},
&mut frame_set,
)
.await;
client
.write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set)
.await;
client.send_frame_set(frame_set, 0x84).await;
{
let mut abilities = player.abilities.lock().await;
abilities.set_for_gamemode(player.gamemode.load());
};
player.send_abilities_update().await;
let mut frame_set = FrameSet::default();
// https://github.com/pmmp/PocketMine-MP/blob/0b6d8f8cb2aaa05ffad0b6386bd88d73ef54b395/src/entity/AttributeFactory.php#L34
client
.write_game_packet_to_set(
&CUpdateAttributes {
runtime_id: VarULong(runtime_id),
attributes: vec![
Attribute {
min_value: 0.0,
max_value: 3.402_823_5E38,
current_value: 0.1,
default_min_value: 0.0,
default_max_value: 3.402_823_5E38,
default_value: 0.1,
name: "minecraft:movement".to_string(),
modifiers_list_size: VarUInt(0),
},
Attribute {
min_value: 0.0,
max_value: 3.402_823_5E38,
current_value: 0.02,
default_min_value: 0.0,
default_max_value: 3.402_823_5E38,
default_value: 0.02,
name: "minecraft:underwater_movement".to_string(),
modifiers_list_size: VarUInt(0),
},
Attribute {
min_value: 0.0,
max_value: 1.0,
current_value: 0.08,
default_min_value: 0.0,
default_max_value: 1.0,
default_value: 0.08,
name: "minecraft:gravity".to_string(),
modifiers_list_size: VarUInt(0),
},
Attribute {
min_value: 0.0,
max_value: 400.0,
current_value: 400.0,
default_min_value: 0.0,
default_max_value: 400.0,
default_value: 400.0,
name: "minecraft:air".to_string(),
modifiers_list_size: VarUInt(0),
},
Attribute {
min_value: 0.0,
max_value: 20.0,
current_value: 20.0,
default_min_value: 0.0,
default_max_value: 20.0,
default_value: 20.0,
name: "minecraft:health".to_string(),
modifiers_list_size: VarUInt(0),
},
Attribute {
min_value: 0.0,
max_value: 20.0,
current_value: 20.0,
default_min_value: 0.0,
default_max_value: 20.0,
default_value: 20.0,
name: "minecraft:player.hunger".to_string(),
modifiers_list_size: VarUInt(0),
},
],
player_tick: VarULong(0),
},
&mut frame_set,
)
.await;
client
.write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set)
.await;
client.send_frame_set(frame_set, 0x84).await;
}
#[expect(clippy::too_many_lines)]