mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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:
89
Cargo.lock
generated
89
Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -9,6 +9,8 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error2.workspace = true
|
||||
pumpkin-data.workspace = true
|
||||
|
||||
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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("]")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(¤t_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)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
},
|
||||
data::{
|
||||
LoadJSONConfiguration, SaveJSONConfiguration,
|
||||
whitelist_data::{WHITELIST_CONFIG, WhitelistConfig},
|
||||
whitelist::{WHITELIST_CONFIG, WhitelistConfig},
|
||||
},
|
||||
net::DisconnectReason,
|
||||
server::Server,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user