feat(plugin-api): initial packet api

This commit is contained in:
Alexander Medvedev 2026-05-08 21:42:37 +02:00
parent 4c26afc708
commit 45867d33e2
38 changed files with 4920 additions and 102 deletions

4
Cargo.lock generated
View File

@ -3394,9 +3394,9 @@ dependencies = [
[[package]] [[package]]
name = "ruzstd" name = "ruzstd"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8"
dependencies = [ dependencies = [
"twox-hash 2.1.2", "twox-hash 2.1.2",
] ]

View File

@ -156,7 +156,7 @@ rsa = "=0.10.0-rc.18"
cipher = "0.5.1" cipher = "0.5.1"
rustc-hash = "2.1.2" rustc-hash = "2.1.2"
rustyline = "18.0.0" rustyline = "18.0.0"
ruzstd = "0.8.2" ruzstd = "0.8.3"
serde_json5 = "0.2.1" serde_json5 = "0.2.1"
sha1 = "=0.11.0" sha1 = "=0.11.0"
sha2 = "=0.11.0" sha2 = "=0.11.0"

View File

@ -274,6 +274,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -729,6 +735,16 @@ dependencies = [
"spki 0.8.0", "spki 0.8.0",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.37" version = "0.2.37"
@ -778,10 +794,12 @@ dependencies = [
"pumpkin-util", "pumpkin-util",
"quote", "quote",
"rayon", "rayon",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"serde_json5", "serde_json5",
"syn", "syn",
"wit-encoder",
] ]
[[package]] [[package]]
@ -978,6 +996,10 @@ name = "semver"
version = "1.0.27" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]] [[package]]
name = "serde" name = "serde"
@ -1355,7 +1377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [ dependencies = [
"leb128fmt", "leb128fmt",
"wasmparser", "wasmparser 0.244.0",
] ]
[[package]] [[package]]
@ -1367,7 +1389,18 @@ dependencies = [
"anyhow", "anyhow",
"indexmap", "indexmap",
"wasm-encoder", "wasm-encoder",
"wasmparser", "wasmparser 0.244.0",
]
[[package]]
name = "wasmparser"
version = "0.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e5456165f81e64cb9908a0fe9b9d852c2c74582aa3fe2be3c2da57f937d3ae"
dependencies = [
"bitflags",
"indexmap",
"semver",
] ]
[[package]] [[package]]
@ -1496,7 +1529,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"heck", "heck",
"wit-parser", "wit-parser 0.244.0",
] ]
[[package]] [[package]]
@ -1545,8 +1578,39 @@ dependencies = [
"serde_json", "serde_json",
"wasm-encoder", "wasm-encoder",
"wasm-metadata", "wasm-metadata",
"wasmparser", "wasmparser 0.244.0",
"wit-parser", "wit-parser 0.244.0",
]
[[package]]
name = "wit-encoder"
version = "0.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92eddbbf7e0d193ba0c511a6bb60026ebbed6994aa8f3998876fbb6d7dfc06f7"
dependencies = [
"id-arena",
"pretty_assertions",
"semver",
"serde",
"wit-parser 0.225.0",
]
[[package]]
name = "wit-parser"
version = "0.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebefaa234e47224f10ce60480c5bfdece7497d0f3b87a12b41ff39e5c8377a78"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser 0.225.0",
] ]
[[package]] [[package]]
@ -1564,9 +1628,15 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"unicode-xid", "unicode-xid",
"wasmparser", "wasmparser 0.244.0",
] ]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.2" version = "1.8.2"

View File

@ -15,6 +15,8 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }
serde_json5 = "0.2" serde_json5 = "0.2"
wit-encoder = "0.225.0"
semver = "1.0"
rayon = "1.11.0" rayon = "1.11.0"
proc-macro2 = "1.0" proc-macro2 = "1.0"

View File

@ -22,12 +22,10 @@ pub fn build() -> TokenStream {
let lava_level = value_to_y_offset(&config["lava_level"]); let lava_level = value_to_y_offset(&config["lava_level"]);
let replaceable_str = config["replaceable"].as_str().unwrap_or(""); let replaceable_str = config["replaceable"].as_str().unwrap_or("");
let replaceable = if replaceable_str.starts_with('#') { let replaceable = if let Some(tag_name) = replaceable_str.strip_prefix('#') {
let tag_name = replaceable_str[1..] let tag_name = tag_name
.to_uppercase() .to_uppercase()
.replace(':', "_") .replace([':', '.', '-'], "_");
.replace('.', "_")
.replace('-', "_");
let tag_ident = format_ident!("{}", tag_name); let tag_ident = format_ident!("{}", tag_name);
quote! { crate::tag::Block::#tag_ident } quote! { crate::tag::Block::#tag_ident }
} else { } else {

View File

@ -12,6 +12,7 @@ pub struct BlockStateCodecStruct {
pub name: String, pub name: String,
/// Optional block state properties (e.g., `{"facing": "north"}`). /// Optional block state properties (e.g., `{"facing": "north"}`).
#[serde(rename = "Properties")] #[serde(rename = "Properties")]
#[allow(dead_code)]
pub properties: Option<BTreeMap<String, String>>, pub properties: Option<BTreeMap<String, String>>,
} }

View File

@ -1057,15 +1057,13 @@ fn value_to_tree_feature(config: &Value) -> TokenStream {
} }
fn value_to_block_list(v: &Value) -> TokenStream { fn value_to_block_list(v: &Value) -> TokenStream {
if let Some(s) = v.as_str() { if let Some(tag) = v.as_str().and_then(|s| s.strip_prefix('#')) {
if let Some(tag) = s.strip_prefix('#') { let name = format!(
let name = format!( "MINECRAFT_{}",
"MINECRAFT_{}", tag.strip_prefix("minecraft:").unwrap_or(tag).to_uppercase()
tag.strip_prefix("minecraft:").unwrap_or(tag).to_uppercase() );
); let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site()); return quote! { &pumpkin_data::tag::Block::#ident.1 };
return quote! { &pumpkin_data::tag::Block::#ident.1 };
}
} }
let mut blocks = Vec::new(); let mut blocks = Vec::new();
if let Some(arr) = v.as_array() { if let Some(arr) = v.as_array() {

View File

@ -257,7 +257,7 @@ impl ToTokens for LootPoolEntryTypesStruct {
Self::LootTable => { Self::LootTable => {
tokens.extend(quote! { LootPoolEntryTypes::LootTable }); tokens.extend(quote! { LootPoolEntryTypes::LootTable });
} }
Self::Dynamic(entry) => { Self::Dynamic(_entry) => {
// TODO // TODO
tokens.extend(quote! { LootPoolEntryTypes::Dynamic }); tokens.extend(quote! { LootPoolEntryTypes::Dynamic });
} }

View File

@ -58,6 +58,7 @@ mod tracked_data;
mod translations; mod translations;
mod version; mod version;
mod villager; mod villager;
mod wit;
mod world_event; mod world_event;
/// Output directory where all generated Rust source files are written. /// Output directory where all generated Rust source files are written.
@ -70,6 +71,8 @@ pub fn main() {
fs::create_dir_all(OUT_DIR).expect("Failed to create output directory"); fs::create_dir_all(OUT_DIR).expect("Failed to create output directory");
wit::main();
let mut build_functions: Vec<(BuilderFn, &str)> = vec![ let mut build_functions: Vec<(BuilderFn, &str)> = vec![
(packet::build, "packet.rs"), (packet::build, "packet.rs"),
(screen::build, "screen.rs"), (screen::build, "screen.rs"),

View File

@ -145,11 +145,10 @@ impl ParsedMappings {
} else { } else {
shifts_at[index + 1] shifts_at[index + 1]
}; };
let mut mapped_id = shifts_to[index]; for (mapped_id, id) in (shifts_to[index]..).zip(*from..to) {
for id in *from..to {
result[id as usize] = mapped_id; result[id as usize] = mapped_id;
mapped_id += 1;
} }
} }
result result

View File

@ -11,8 +11,13 @@ pub fn build() -> TokenStream {
let mut java_constants = TokenStream::new(); let mut java_constants = TokenStream::new();
for (name, value) in &java_json { for (name, value) in &java_json {
let ident = to_valid_ident(name); let ident = to_valid_ident(name);
let doc = if !value.is_empty() {
quote!(#[doc = #value])
} else {
quote!()
};
java_constants.extend(quote! { java_constants.extend(quote! {
#[doc = #value] #doc
pub const #ident: &str = #name; pub const #ident: &str = #name;
}); });
} }
@ -31,8 +36,13 @@ pub fn build() -> TokenStream {
let value = value.trim(); let value = value.trim();
let ident = to_valid_ident(name); let ident = to_valid_ident(name);
let doc = if !value.is_empty() {
quote!(#[doc = #value])
} else {
quote!()
};
bedrock_constants.extend(quote! { bedrock_constants.extend(quote! {
#[doc = #value] #doc
pub const #ident: &str = #name; pub const #ident: &str = #name;
}); });
} }
@ -52,7 +62,7 @@ pub fn build() -> TokenStream {
fn to_valid_ident(name: &str) -> Ident { fn to_valid_ident(name: &str) -> Ident {
let mut clean = name.to_uppercase().replace(['.', ':', '-'], "_"); let mut clean = name.to_uppercase().replace(['.', ':', '-'], "_");
if clean.chars().next().map_or(false, |c| c.is_ascii_digit()) { if clean.chars().next().is_some_and(|c| c.is_ascii_digit()) {
clean.insert(0, '_'); clean.insert(0, '_');
} }

View File

@ -110,7 +110,7 @@ pub fn build() -> TokenStream {
}; };
// Pre-process all trade sets mentioned in trade_sets map // Pre-process all trade sets mentioned in trade_sets map
for (set_key, set_data) in &data.trade_sets { for (_set_key, set_data) in &data.trade_sets {
let tag = &set_data.trades; let tag = &set_data.trades;
if !tag.starts_with("#minecraft:") { if !tag.starts_with("#minecraft:") {
continue; continue;
@ -199,19 +199,23 @@ pub fn build() -> TokenStream {
for (level_str, set_key) in &prof_data.trade_sets { for (level_str, set_key) in &prof_data.trade_sets {
let level = level_str.parse::<i32>().unwrap(); let level = level_str.parse::<i32>().unwrap();
let set_key_clean = set_key.strip_prefix("minecraft:").unwrap_or(set_key); let set_key_clean = set_key.strip_prefix("minecraft:").unwrap_or(set_key);
if let Some(set) = data.trade_sets.get(set_key_clean) { if let Some(trades_ident) = data.trade_sets.get(set_key_clean).and_then(|set| generated_trade_sets.get(&set.trades)) {
if let Some(trades_ident) = generated_trade_sets.get(&set.trades) { let set = data.trade_sets.get(set_key_clean).unwrap();
let amount = set.amount as i32; let amount = set.amount as i32;
level_matches.push(quote! { #level => Some(VillagerTradeSet { trades: #trades_ident, amount: #amount }) }); level_matches.push(quote! { #level => Some(VillagerTradeSet { trades: #trades_ident, amount: #amount }) });
}
} }
} }
profession_trade_sets.push(quote! { let profession_trade_set = if level_matches.is_empty() {
Self::#ident => match level { quote! { Self::#ident => None }
#(#level_matches,)* } else {
_ => None, quote! {
Self::#ident => match level {
#(#level_matches,)*
_ => None,
}
} }
}); };
profession_trade_sets.push(profession_trade_set);
} }
for (i, name) in data.types.keys().enumerate() { for (i, name) in data.types.keys().enumerate() {

View File

@ -0,0 +1,70 @@
use crate::wit::utils::map_type;
use std::{fs, path::Path};
use syn::{Fields, Item};
use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase};
use semver::Version;
use heck::ToKebabCase;
pub fn build() -> String {
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0))));
let mut interface = Interface::new("bedrock-packets");
let mut serverbound_variant = Variant::empty();
let mut clientbound_variant = Variant::empty();
// Process serverbound packets
process_packets("../pumpkin-protocol/src/bedrock/server", &mut interface, &mut serverbound_variant);
// Process clientbound packets
process_packets("../pumpkin-protocol/src/bedrock/client", &mut interface, &mut clientbound_variant);
interface.type_def(TypeDef::new("serverbound-packet", TypeDefKind::Variant(serverbound_variant)));
interface.type_def(TypeDef::new("clientbound-packet", TypeDefKind::Variant(clientbound_variant)));
package.interface(interface);
package.to_string()
}
fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect();
sorted_paths.sort();
for path in sorted_paths {
if path.is_dir() {
process_packets(path.to_str().unwrap(), interface, variant);
continue;
}
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") {
parse_packet_file(&path, interface, variant);
}
}
}
fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Variant) {
let content = fs::read_to_string(path).expect("Failed to read file");
let file = syn::parse_file(&content).expect("Failed to parse file");
for item in file.items {
if let Item::Struct(s) = item {
// Only process structs with #[packet] attribute
if !s.attrs.iter().any(|attr| attr.path().is_ident("packet")) {
continue;
}
let struct_name = s.ident.to_string();
let mut fields_list = Vec::new();
if let Fields::Named(fields) = s.fields {
for field in fields.named {
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
let field_type = map_type(&field.ty);
fields_list.push(Field::new(field_name, field_type));
}
}
let wit_name = struct_name.to_kebab_case();
interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list))));
variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name)));
}
}
}

View File

@ -0,0 +1,23 @@
use std::collections::BTreeMap;
use std::fs;
use semver::Version;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String {
let json: BTreeMap<String, serde_json::Value> =
serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap())
.expect("Failed to parse entities.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0))));
let mut interface = Interface::new("entity-types");
let mut entity_type_enum = Enum::empty();
for name in json.keys() {
entity_type_enum.case(name.replace('_', "-"));
}
interface.type_def(TypeDef::new("entity-type", TypeDefKind::Enum(entity_type_enum)));
package.interface(interface);
package.to_string()
}

View File

@ -0,0 +1,67 @@
use crate::wit::utils::map_type;
use std::{fs, path::Path};
use syn::{Fields, Item};
use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase};
use semver::Version;
use heck::ToKebabCase;
pub fn build() -> String {
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0))));
let mut interface = Interface::new("java-packets");
let mut serverbound_variant = Variant::empty();
let mut clientbound_variant = Variant::empty();
// Process serverbound packets
process_packets("../pumpkin-protocol/src/java/server/play", &mut interface, &mut serverbound_variant);
// Process clientbound packets
process_packets("../pumpkin-protocol/src/java/client/play", &mut interface, &mut clientbound_variant);
interface.type_def(TypeDef::new("serverbound-packet", TypeDefKind::Variant(serverbound_variant)));
interface.type_def(TypeDef::new("clientbound-packet", TypeDefKind::Variant(clientbound_variant)));
package.interface(interface);
package.to_string()
}
fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect();
sorted_paths.sort();
for path in sorted_paths {
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") {
parse_packet_file(&path, interface, variant);
}
}
}
fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Variant) {
let content = fs::read_to_string(path).expect("Failed to read file");
let file = syn::parse_file(&content).expect("Failed to parse file");
for item in file.items {
if let Item::Struct(s) = item {
// Only process structs with #[java_packet] attribute
if !s.attrs.iter().any(|attr| attr.path().is_ident("java_packet")) {
continue;
}
let struct_name = s.ident.to_string();
let mut fields_list = Vec::new();
if let Fields::Named(fields) = s.fields {
for field in fields.named {
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
let field_type = map_type(&field.ty);
fields_list.push(Field::new(field_name, field_type));
}
}
let wit_name = struct_name.to_kebab_case();
interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list))));
variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name)));
}
}
}

View File

@ -0,0 +1,52 @@
pub mod bedrock_packet;
pub mod entity_type;
pub mod java_packet;
pub mod packet_mapping;
pub mod particle;
pub mod sound;
pub mod utils;
use std::fs;
use std::path::Path;
pub const WIT_OUT_DIR: &str = "../pumpkin-plugin-wit/v0.1";
pub const MAPPING_OUT_DIR: &str = "../pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1";
pub fn main() {
fs::create_dir_all(WIT_OUT_DIR).expect("Failed to create WIT output directory");
type BuildFn = fn() -> String;
let build_functions: Vec<(BuildFn, &str)> = vec![
(particle::build, "particles.wit"),
(sound::build, "sounds.wit"),
(entity_type::build, "entity-types.wit"),
(java_packet::build, "java-packets.wit"),
(bedrock_packet::build, "bedrock-packets.wit"),
];
for (build_fn, file) in build_functions {
println!("Generating WIT for {}", file);
let wit_code = build_fn();
write_generated_wit(&wit_code, file);
}
println!("Generating Java and Bedrock packet mapping");
let mut mapping = packet_mapping::build_java_mapping();
mapping.push_str(&packet_mapping::build_bedrock_mapping());
let mapping_path = Path::new(MAPPING_OUT_DIR).join("generated_packets.rs");
fs::write(&mapping_path, mapping).expect("Failed to write packet mapping");
}
fn write_generated_wit(new_code: &str, out_file: &str) {
let path = Path::new(WIT_OUT_DIR).join(out_file);
if path.exists()
&& let Ok(existing_code) = fs::read_to_string(&path)
&& existing_code == new_code
{
return;
}
fs::write(&path, new_code)
.unwrap_or_else(|_| panic!("Failed to write to file: {}", path.display()));
}

View File

@ -0,0 +1,164 @@
use std::{fs, path::Path};
use syn::{Fields, Item};
use heck::{ToPascalCase, ToSnakeCase};
pub fn build_java_mapping() -> String {
let mut output = String::new();
output.push_str("// Generated by pumpkin-codegen. Do not edit.\n");
output.push_str("#![allow(clippy::all)]\n");
output.push_str("#![allow(clippy::pedantic)]\n");
output.push_str("#![allow(unused_imports)]\n");
output.push_str("#![allow(unused_variables)]\n");
output.push_str("use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_packets::ClientboundPacket;\n");
output.push_str("use pumpkin_protocol::codec::var_int::VarInt;\n");
output.push_str("use pumpkin_util::version::MinecraftVersion;\n");
output.push_str("use bytes::Bytes;\n\n");
output.push_str("#[must_use]\n");
output.push_str("pub fn serialize_java_packet(packet: &ClientboundPacket, version: MinecraftVersion) -> Option<Bytes> {\n");
output.push_str(" match packet {\n");
process_packets("../pumpkin-protocol/src/java/client/play", &mut output, "java_packet", "ClientboundPacket", "pumpkin_protocol::java::client::play", false);
output.push_str(" _ => None,\n");
output.push_str(" }\n");
output.push_str("}\n");
output
}
pub fn build_bedrock_mapping() -> String {
let mut output = String::new();
output.push_str("\nuse crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::bedrock_packets::ClientboundPacket as BClientboundPacket;\n\n");
output.push_str("#[must_use]\n");
output.push_str("pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {\n");
output.push_str(" match packet {\n");
process_packets("../pumpkin-protocol/src/bedrock/client", &mut output, "packet", "BClientboundPacket", "pumpkin_protocol::bedrock::client", true);
output.push_str(" _ => None,\n");
output.push_str(" }\n");
output.push_str("}\n");
output
}
fn process_packets(dir: &str, output: &mut String, attr_name: &str, variant_prefix: &str, rust_path_prefix: &str, skip_raknet: bool) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect();
sorted_paths.sort();
for path in sorted_paths {
if path.is_dir() {
if skip_raknet && path.file_name().unwrap() == "raknet" {
continue;
}
process_packets(path.to_str().unwrap(), output, attr_name, variant_prefix, rust_path_prefix, skip_raknet);
continue;
}
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") {
parse_packet_file(&path, output, attr_name, variant_prefix, rust_path_prefix);
}
}
}
fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_prefix: &str, rust_path_prefix: &str) {
let content = fs::read_to_string(path).expect("Failed to read file");
let file = syn::parse_file(&content).expect("Failed to parse file");
for item in file.items {
if let Item::Struct(s) = item {
if !s.attrs.iter().any(|attr| attr.path().is_ident(attr_name)) {
continue;
}
let struct_name = s.ident.to_string();
if struct_name == "CHandshake" { continue; } // Skip CHandshake (RakNet)
let wit_case = struct_name.to_pascal_case();
let mut prep_code = String::new();
let mut field_inits = String::new();
let mut possible = true;
if let Fields::Named(fields) = s.fields {
for field in fields.named {
let mut field_name = field.ident.as_ref().unwrap().to_string();
let wit_field = field_name.to_snake_case();
let (type_ident, is_ref) = get_type_info(&field.ty);
if field_name == "type" {
field_name = "r#type".to_string();
}
match type_ident.as_str() {
"String" | "str" => {
if is_ref {
field_inits.push_str(&format!(" {}: &data.{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: data.{}.clone(),\n", field_name, wit_field));
}
},
"VarInt" => {
// WIT maps s32 to i32, so try_into is needed if WIT field is i32 and VarInt(i32)
// Actually data.wit_field is i32.
field_inits.push_str(&format!(" {}: VarInt(data.{}),\n", field_name, wit_field));
},
"VarUInt" => {
field_inits.push_str(&format!(" {}: pumpkin_protocol::codec::var_uint::VarUInt(data.{}.try_into().unwrap()),\n", field_name, wit_field));
},
"TextComponent" => {
prep_code.push_str(&format!(" let component_{} = pumpkin_util::text::TextComponent::text(data.{}.clone());\n", wit_field, wit_field));
if is_ref {
field_inits.push_str(&format!(" {}: &component_{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: component_{},\n", field_name, wit_field));
}
},
"BlockPos" => field_inits.push_str(&format!(" {}: pumpkin_util::math::position::BlockPos::new(data.{}.0, data.{}.1, data.{}.2),\n", field_name, wit_field, wit_field, wit_field)),
"Vector3" => {
field_inits.push_str(&format!(" {}: pumpkin_util::math::vector3::Vector3::new(data.{}.0 as _, data.{}.1 as _, data.{}.2 as _),\n", field_name, wit_field, wit_field, wit_field));
},
"Uuid" => {
prep_code.push_str(&format!(" let uuid_{} = uuid::Uuid::parse_str(&data.{}).unwrap();\n", wit_field, wit_field));
if is_ref {
field_inits.push_str(&format!(" {}: &uuid_{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: uuid_{},\n", field_name, wit_field));
}
},
"i32" | "u32" | "i64" | "u64" | "bool" | "f32" | "f64" | "u8" | "i8" | "u16" | "i16" => {
field_inits.push_str(&format!(" {}: data.{}.try_into().unwrap(),\n", field_name, wit_field));
},
_ => {
possible = false;
break;
}
}
}
} else {
possible = false;
}
if possible {
output.push_str(&format!(" {}::{}(data) => {{\n", variant_prefix, wit_case));
output.push_str(&prep_code);
output.push_str(&format!(" let p = {}::{} {{\n", rust_path_prefix, struct_name));
output.push_str(&field_inits);
output.push_str(" };\n");
output.push_str(" let mut buf = Vec::new();\n");
if attr_name == "java_packet" {
output.push_str(" crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();\n");
} else {
output.push_str(" crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();\n");
}
output.push_str(" Some(buf.into())\n");
output.push_str(" }\n");
}
}
}
}
fn get_type_info(ty: &syn::Type) -> (String, bool) {
match ty {
syn::Type::Path(tp) => (tp.path.segments.last().unwrap().ident.to_string(), false),
syn::Type::Reference(tr) => (get_type_info(&tr.elem).0, true),
_ => (String::new(), false),
}
}

View File

@ -0,0 +1,25 @@
use std::fs;
use semver::Version;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String {
let particles: Vec<String> =
serde_json::from_str(&fs::read_to_string("../assets/particles.json").unwrap())
.expect("Failed to parse particles.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0))));
let mut interface = Interface::new("particles");
let mut particle_enum = Enum::empty();
for particle in particles {
// WIT uses kebab-case for variants usually, but let's see if we should keep them as is
// Minecraft particles are already kebab-case or underscore.
// WIT prefers kebab-case.
particle_enum.case(particle.replace('_', "-"));
}
interface.type_def(TypeDef::new("particle", TypeDefKind::Enum(particle_enum)));
package.interface(interface);
package.to_string()
}

View File

@ -0,0 +1,22 @@
use std::fs;
use semver::Version;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String {
let sounds: Vec<String> =
serde_json::from_str(&fs::read_to_string("../assets/sounds.json").unwrap())
.expect("Failed to parse sounds.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0))));
let mut interface = Interface::new("sounds");
let mut sound_enum = Enum::empty();
for sound in sounds {
sound_enum.case(sound.replace(['_', '.'], "-"));
}
interface.type_def(TypeDef::new("sound", TypeDefKind::Enum(sound_enum)));
package.interface(interface);
package.to_string()
}

View File

@ -0,0 +1,46 @@
use wit_encoder::Type as WitType;
use syn::{Type, TypePath};
pub fn map_type(ty: &Type) -> WitType {
match ty {
Type::Path(TypePath { path, .. }) => {
let last_segment = path.segments.last().unwrap();
let ident_str = last_segment.ident.to_string();
match ident_str.as_str() {
"String" | "Uuid" | "TextComponent" => WitType::String,
"i32" | "VarInt" | "u32" | "VarUInt" | "usize" => WitType::S32,
"i64" | "u64" => WitType::S64,
"bool" => WitType::Bool,
"f32" => WitType::F32,
"f64" => WitType::F64,
"u8" | "i8" => WitType::U8,
"u16" | "i16" => WitType::S32,
"Option" => {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return WitType::option(map_type(inner_ty));
}
WitType::String
}
"Vec" | "Box" => {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return WitType::list(map_type(inner_ty));
}
WitType::String
}
"Vector3" | "BlockPos" => {
// Map to tuple or common record
// For now, let's use a tuple of floats for Vector3 and ints for BlockPos
if ident_str == "Vector3" {
WitType::tuple(vec![WitType::F64, WitType::F64, WitType::F64])
} else {
WitType::tuple(vec![WitType::S32, WitType::S32, WitType::S32])
}
}
_ => WitType::String, // Fallback
}
}
_ => WitType::String, // Fallback
}
}

View File

@ -0,0 +1,390 @@
package pumpkin:plugin@0.1.0;
interface bedrock-packets {
record s-animate {
action: string,
runtime-entity-id: string,
boat-rowing-time: option<f32>,
}
record s-client-cache-status {
cache-supported: bool,
}
record s-command-request {
command: string,
command-type: string,
command-uuid: string,
request-id: string,
player-actor-unique-id: s64,
is-internal-source: bool,
version: string,
}
record s-container-close {
container-id: u8,
container-type: u8,
server-initiated: bool,
}
record s-interaction {
action: string,
target-runtime-id: string,
position: option<tuple<f64, f64, f64>>,
}
record s-loading-screen {
status: s32,
id: option<s32>,
}
record s-login {
protocol-version: s32,
jwt: list<u8>,
raw-token: list<u8>,
}
record s-player-action {
runtime-id: s32,
action: string,
block-pos: tuple<s32, s32, s32>,
result-pos: tuple<s32, s32, s32>,
face: s32,
}
record s-player-auth-input {
pitch: f32,
yaw: f32,
position: tuple<f64, f64, f64>,
move-vec: string,
head-yaw: f32,
input-data: string,
input-mode: s32,
play-mode: s32,
interaction-model: s32,
interact-pitch: f32,
interact-yaw: f32,
tick: string,
delta: tuple<f64, f64, f64>,
block-actions: option<list<string>>,
vehicle-rotation: option<string>,
vehicle-unique-id: option<string>,
analog-move: string,
camera-orientation: tuple<f64, f64, f64>,
raw-move: string,
}
record s-connected-ping {
time: s64,
}
record s-connection-request {
client-guid: s64,
time: s64,
security: bool,
}
record s-new-incoming-connection {
server-address: string,
internal-address: string,
ping-time: s64,
pong-time: s64,
}
record s-disconnect { }
record s-open-connection-request1 {
magic: string,
protocol-version: u8,
mtu: s32,
}
record s-open-connection-request2 {
magic: string,
server-address: string,
mtu: s32,
client-guid: s64,
}
record s-unconnected-ping {
time: s64,
magic: string,
client-guid: s64,
}
record s-request-chunk-radius {
chunk-radius: s32,
max-radius: u8,
}
record s-request-network-settings {
protocol-version: s32,
}
record s-resource-pack-response {
response: u8,
download-size: s32,
}
record s-set-local-player-as-initialized {
runtime-entity-id: string,
}
record s-text {
needs-translation: bool,
r-type: string,
source-name: string,
message: string,
parameters: list<string>,
xuid: string,
platform-chat-id: string,
filtered-message: option<string>,
}
record c-available-commands {
enum-values: list<string>,
chained-subcommand-values: list<string>,
suffixes: list<string>,
enums: list<string>,
chained-subcommands: list<string>,
commands: list<string>,
soft-enums: list<string>,
constraints: list<string>,
}
record c-chunk-radius-update {
chunk-radius: s32,
}
record c-container-open {
container-id: u8,
container-type: u8,
position: tuple<s32, s32, s32>,
target-entity-id: string,
}
record c-correct-player-move {
prediction-type: u8,
pos: tuple<f64, f64, f64>,
pos-delta: tuple<f64, f64, f64>,
on-ground: bool,
tick: string,
}
record creative-content {
groups: string,
entries: string,
}
record c-disconnect-player {
reason: s32,
skip-message: bool,
message: string,
filtered-message: string,
}
record c-gamerules-changed {
rule-data: string,
}
record c-handshake {
jwt-data: string,
}
record c-inventory-content {
inventory-id: s32,
slots: list<string>,
container-name: u8,
dynamic-id: option<s32>,
storage-item: string,
}
record c-level-chunk {
dimension: s32,
cache-enabled: bool,
chunk: string,
}
record c-level-event {
event-id: s32,
position: tuple<f64, f64, f64>,
data: s32,
}
record c-move-player {
player-runtime-id: string,
position: tuple<f64, f64, f64>,
pitch: f32,
yaw: f32,
head-yaw: f32,
mode: u8,
on-ground: bool,
riding-runtime-id: string,
tick: string,
}
record c-network-chunk-publisher-update {
pos-for-view: tuple<s32, s32, s32>,
new-radius: s32,
server-build-chunk-list: list<string>,
}
record c-network-settings {
compression-threshold: s32,
compression-method: s32,
client-throttle-enabled: bool,
client-throttle-threshold: u8,
client-throttle-scalar: f32,
}
record c-player-hotbar {
selected-slot: s32,
container-id: u8,
should-select-block: bool,
}
record c-connected-pong {
ping: s64,
pong: s64,
}
record c-connection-request-accepted {
client-address: string,
system-index: s32,
system-addresses: string,
requested-timestamp: s64,
timestamp: s64,
}
record c-incompatible-protocol-version {
protocol-version: u8,
magic: string,
server-guid: s64,
}
record c-open-connection-reply1 {
magic: string,
server-guid: s64,
has-server-security: bool,
mtu: s32,
}
record c-open-connection-reply2 {
magic: string,
server-guid: s64,
client-address: string,
mtu: s32,
security: bool,
}
record c-unconnected-pong {
time: s64,
server-guid: s64,
magic: string,
server-id: string,
}
record c-resource-pack-stack-packet {
resource-pack-required: bool,
resource-packs: list<string>,
game-version: string,
experiments: string,
include-editor-packs: bool,
}
record c-resource-packs-info {
resource-pack-required: bool,
has-addon-packs: bool,
has-scripts: bool,
is-vibrant-visuals-force-disabled: bool,
world-template-id: string,
world-template-version: string,
resource-packs: list<string>,
}
record c-set-actor-data {
actor-runtime-id: string,
metadata: string,
synced-properties: string,
tick: string,
}
record c-set-actor-motion {
target-runtime-id: string,
motion: tuple<f64, f64, f64>,
tick: string,
}
record c-set-health {
health: s32,
}
record c-set-player-gamemode {
gamemode: string,
}
record c-set-time {
time: s32,
}
record c-set-title {
action-type: s32,
text: string,
fade-in-duration: s32,
remain-duration: s32,
fade-out-duration: s32,
xuid: string,
platform-online-id: string,
filtered-message: string,
}
record c-start-game {
entity-id: string,
runtime-entity-id: string,
player-gamemode: string,
position: tuple<f64, f64, f64>,
pitch: f32,
yaw: f32,
level-settings: string,
level-id: string,
level-name: string,
premium-world-template-id: string,
is-trial: bool,
rewind-history-size: s32,
server-authoritative-block-breaking: bool,
current-level-time: s64,
enchantment-seed: s32,
block-properties-size: s32,
multiplayer-correlation-id: string,
enable-itemstack-net-manager: bool,
server-version: string,
compound-id: u8,
compound-len: s32,
compound-end: u8,
block-registry-checksum: s64,
world-template-id: string,
enable-clientside-generation: bool,
blocknetwork-ids-are-hashed: bool,
server-auth-sounds: bool,
server-join-information: option<string>,
telemetry: string,
}
record c-update-abilities {
target-player-raw-id: s64,
player-permission: u8,
command-permission: u8,
layers: list<string>,
}
record c-update-attributes {
runtime-id: string,
attributes: list<string>,
player-tick: string,
}
variant serverbound-packet {
s-animate(s-animate),
s-client-cache-status(s-client-cache-status),
s-command-request(s-command-request),
s-container-close(s-container-close),
s-interaction(s-interaction),
s-loading-screen(s-loading-screen),
s-login(s-login),
s-player-action(s-player-action),
s-player-auth-input(s-player-auth-input),
s-connected-ping(s-connected-ping),
s-connection-request(s-connection-request),
s-new-incoming-connection(s-new-incoming-connection),
s-disconnect(s-disconnect),
s-open-connection-request1(s-open-connection-request1),
s-open-connection-request2(s-open-connection-request2),
s-unconnected-ping(s-unconnected-ping),
s-request-chunk-radius(s-request-chunk-radius),
s-request-network-settings(s-request-network-settings),
s-resource-pack-response(s-resource-pack-response),
s-set-local-player-as-initialized(s-set-local-player-as-initialized),
s-text(s-text),
}
variant clientbound-packet {
c-available-commands(c-available-commands),
c-chunk-radius-update(c-chunk-radius-update),
c-container-open(c-container-open),
c-correct-player-move(c-correct-player-move),
creative-content(creative-content),
c-disconnect-player(c-disconnect-player),
c-gamerules-changed(c-gamerules-changed),
c-handshake(c-handshake),
c-inventory-content(c-inventory-content),
c-level-chunk(c-level-chunk),
c-level-event(c-level-event),
c-move-player(c-move-player),
c-network-chunk-publisher-update(c-network-chunk-publisher-update),
c-network-settings(c-network-settings),
c-player-hotbar(c-player-hotbar),
c-connected-pong(c-connected-pong),
c-connection-request-accepted(c-connection-request-accepted),
c-incompatible-protocol-version(c-incompatible-protocol-version),
c-open-connection-reply1(c-open-connection-reply1),
c-open-connection-reply2(c-open-connection-reply2),
c-unconnected-pong(c-unconnected-pong),
c-resource-pack-stack-packet(c-resource-pack-stack-packet),
c-resource-packs-info(c-resource-packs-info),
c-set-actor-data(c-set-actor-data),
c-set-actor-motion(c-set-actor-motion),
c-set-health(c-set-health),
c-set-player-gamemode(c-set-player-gamemode),
c-set-time(c-set-time),
c-set-title(c-set-title),
c-start-game(c-start-game),
c-update-abilities(c-update-abilities),
c-update-attributes(c-update-attributes),
}
}

View File

@ -0,0 +1,163 @@
package pumpkin:plugin@0.1.0;
interface entity-types {
enum entity-type {
acacia-boat,
acacia-chest-boat,
allay,
area-effect-cloud,
armadillo,
armor-stand,
arrow,
axolotl,
bamboo-chest-raft,
bamboo-raft,
bat,
bee,
birch-boat,
birch-chest-boat,
blaze,
block-display,
bogged,
breeze,
breeze-wind-charge,
camel,
camel-husk,
cat,
cave-spider,
cherry-boat,
cherry-chest-boat,
chest-minecart,
chicken,
cod,
command-block-minecart,
copper-golem,
cow,
creaking,
creeper,
dark-oak-boat,
dark-oak-chest-boat,
dolphin,
donkey,
dragon-fireball,
drowned,
egg,
elder-guardian,
end-crystal,
ender-dragon,
ender-pearl,
enderman,
endermite,
evoker,
evoker-fangs,
experience-bottle,
experience-orb,
eye-of-ender,
falling-block,
fireball,
firework-rocket,
fishing-bobber,
fox,
frog,
furnace-minecart,
ghast,
giant,
glow-item-frame,
glow-squid,
goat,
guardian,
happy-ghast,
hoglin,
hopper-minecart,
horse,
husk,
illusioner,
interaction,
iron-golem,
item,
item-display,
item-frame,
jungle-boat,
jungle-chest-boat,
leash-knot,
lightning-bolt,
lingering-potion,
llama,
llama-spit,
magma-cube,
mangrove-boat,
mangrove-chest-boat,
mannequin,
marker,
minecart,
mooshroom,
mule,
nautilus,
oak-boat,
oak-chest-boat,
ocelot,
ominous-item-spawner,
painting,
pale-oak-boat,
pale-oak-chest-boat,
panda,
parched,
parrot,
phantom,
pig,
piglin,
piglin-brute,
pillager,
player,
polar-bear,
pufferfish,
rabbit,
ravager,
salmon,
sheep,
shulker,
shulker-bullet,
silverfish,
skeleton,
skeleton-horse,
slime,
small-fireball,
sniffer,
snow-golem,
snowball,
spawner-minecart,
spectral-arrow,
spider,
splash-potion,
spruce-boat,
spruce-chest-boat,
squid,
stray,
strider,
tadpole,
text-display,
tnt,
tnt-minecart,
trader-llama,
trident,
tropical-fish,
turtle,
vex,
villager,
vindicator,
wandering-trader,
warden,
wind-charge,
witch,
wither,
wither-skeleton,
wither-skull,
wolf,
zoglin,
zombie,
zombie-horse,
zombie-nautilus,
zombie-villager,
zombified-piglin,
}
}

View File

@ -3,6 +3,18 @@ interface event {
use player.{player}; use player.{player};
use text.{text-component}; use text.{text-component};
use %world.{%world}; use %world.{%world};
use java-packets.{serverbound-packet as java-serverbound-packet, clientbound-packet as java-clientbound-packet};
use bedrock-packets.{serverbound-packet as bedrock-serverbound-packet, clientbound-packet as bedrock-clientbound-packet};
variant serverbound-packet {
java(java-serverbound-packet),
bedrock(bedrock-serverbound-packet),
}
variant clientbound-packet {
java(java-clientbound-packet),
bedrock(bedrock-clientbound-packet),
}
enum event-priority { enum event-priority {
highest, highest,
@ -267,6 +279,18 @@ interface event {
cancelled: bool cancelled: bool
} }
record packet-received-event-data {
player: player,
packet: serverbound-packet,
cancelled: bool
}
record packet-sent-event-data {
player: player,
packet: clientbound-packet,
cancelled: bool
}
enum event-type { enum event-type {
player-join-event, player-join-event,
player-leave-event, player-leave-event,
@ -300,6 +324,8 @@ interface event {
server-command-event, server-command-event,
spawn-change-event, spawn-change-event,
server-broadcast-event, server-broadcast-event,
packet-received-event,
packet-sent-event,
} }
variant event { variant event {
@ -335,5 +361,7 @@ interface event {
server-command-event(server-command-event-data), server-command-event(server-command-event-data),
spawn-change-event(spawn-change-event-data), spawn-change-event(spawn-change-event-data),
server-broadcast-event(server-broadcast-event-data), server-broadcast-event(server-broadcast-event-data),
packet-received-event(packet-received-event-data),
packet-sent-event(packet-sent-event-data),
} }
} }

View File

@ -0,0 +1,858 @@
package pumpkin:plugin@0.1.0;
interface java-packets {
record s-attack {
entity-id: s32,
}
record s-change-game-mode {
game-mode: string,
}
record s-chat-command {
command: string,
}
record s-chat-message {
message: string,
timestamp: s64,
salt: s64,
signature: option<list<string>>,
message-count: s32,
acknowledged: list<string>,
checksum: u8,
}
record s-chunk-batch {
chunks-per-tick: f32,
}
record s-click-slot {
sync-id: s32,
revision: s32,
slot: s32,
button: u8,
mode: string,
length-of-array: s32,
array-of-changed-slots: list<string>,
carried-item: string,
}
record s-client-command {
action-id: s32,
}
record s-client-information-play {
locale: string,
view-distance: u8,
chat-mode: s32,
chat-colors: bool,
skin-parts: u8,
main-hand: s32,
text-filtering: bool,
server-listing: bool,
}
record s-client-tick-end { }
record s-close-container {
window-id: s32,
}
record s-command-suggestion {
id: s32,
command: string,
}
record s-confirm-teleport {
teleport-id: s32,
}
record s-container-button-click {
window-id: s32,
button-id: s32,
}
record s-cookie-response {
key: string,
payload: option<list<string>>,
}
record s-custom-payload {
channel: string,
data: list<string>,
}
record s-interact {
entity-id: s32,
r-type: s32,
target-position: option<tuple<f64, f64, f64>>,
hand: option<s32>,
sneaking: bool,
}
record s-keep-alive {
keep-alive-id: s64,
}
record s-move-vehicle {
x: f64,
y: f64,
z: f64,
yaw: f32,
pitch: f32,
}
record s-paddle-boat {
left-paddle: bool,
right-paddle: bool,
}
record s-pick-item-from-block {
pos: tuple<s32, s32, s32>,
include-data: bool,
}
record s-pick-item-from-entity {
id: s32,
include-data: bool,
}
record s-play-ping-request {
payload: s64,
}
record s-place-recipe {
container-id: u8,
recipe-display-id: s32,
use-max-items: bool,
}
record s-player-abilities {
%flags: u8,
}
record s-player-action {
status: s32,
position: tuple<s32, s32, s32>,
face: u8,
sequence: s32,
}
record s-player-command {
entity-id: s32,
action: string,
jump-boost: s32,
}
record s-set-player-ground {
on-ground: bool,
}
record s-player-input {
input: u8,
}
record s-player-loaded { }
record s-player-position {
position: tuple<f64, f64, f64>,
collision: u8,
}
record s-player-position-rotation {
position: tuple<f64, f64, f64>,
yaw: f32,
pitch: f32,
collision: u8,
}
record s-player-rotation {
yaw: f32,
pitch: f32,
ground: bool,
}
record s-player-session {
session-id: string,
expires-at: s64,
public-key: list<string>,
key-signature: list<string>,
}
record s-recipe-book-change-settings {
book-type: s32,
is-open: bool,
is-filtering: bool,
}
record s-recipe-book-seen-recipe {
recipe-display-id: s32,
}
record s-rename-item {
item-name: string,
}
record s-select-trade {
selected-slot: s32,
}
record s-set-beacon {
primary-effect: option<s32>,
secondary-effect: option<s32>,
}
record s-set-command-block {
pos: tuple<s32, s32, s32>,
command: string,
mode: s32,
%flags: u8,
}
record s-set-creative-slot {
slot: s32,
clicked-item: string,
}
record s-set-held-item {
slot: s32,
}
record s-swing-arm {
hand: s32,
}
record s-update-sign {
location: tuple<s32, s32, s32>,
is-front-text: bool,
line-1: string,
line-2: string,
line-3: string,
line-4: string,
}
record s-use-item {
hand: s32,
sequence: s32,
yaw: f32,
pitch: f32,
}
record s-use-item-on {
hand: s32,
position: tuple<s32, s32, s32>,
face: s32,
cursor-pos: tuple<f64, f64, f64>,
inside-block: bool,
is-against-world-border: bool,
sequence: s32,
}
record c-acknowledge-block-change {
sequence-id: s32,
}
record c-action-bar {
action-bar: string,
}
record c-set-block-destroy-stage {
entity-id: s32,
location: tuple<s32, s32, s32>,
destroy-stage: u8,
}
record c-block-entity-data {
location: tuple<s32, s32, s32>,
r-type: s32,
nbt-data: list<string>,
}
record c-block-event {
location: tuple<s32, s32, s32>,
action-id: u8,
action-parameter: u8,
block-type: s32,
}
record c-block-update {
location: tuple<s32, s32, s32>,
state-id: s32,
}
record c-boss-event {
uuid: string,
action: string,
}
record c-center-chunk {
chunk-x: s32,
chunk-z: s32,
}
record c-change-difficulty {
difficulty: u8,
locked: bool,
}
record c-chunk-batch-end {
batch-size: s32,
}
record c-chunk-batch-start { }
record c-chunk-data { }
record c-clear-title {
reset: bool,
}
record c-close-container {
sync-id: s32,
}
record c-combat-death {
player-id: s32,
message: string,
}
record c-command-suggestions {
id: s32,
start: s32,
length: s32,
matches: list<string>,
}
record c-commands {
nodes: list<string>,
root-node-index: s32,
}
record c-play-cookie-request {
key: string,
}
record c-custom-payload {
channel: string,
data: string,
}
record c-damage-event {
entity-id: s32,
source-type-id: s32,
source-cause-id: s32,
source-direct-id: s32,
source-position: option<tuple<f64, f64, f64>>,
}
record c-play-disconnect {
reason: string,
}
record c-disguised-chat-message {
message: string,
chat-type: s32,
sender-name: string,
target-name: option<string>,
}
record c-display-objective {
position: s32,
score-name: string,
}
record c-entity-animation {
entity-id: s32,
animation: u8,
}
record c-set-entity-metadata {
entity-id: s32,
metadata: list<string>,
}
record c-entity-position-sync {
entity-id: s32,
position: tuple<f64, f64, f64>,
delta: tuple<f64, f64, f64>,
yaw: f32,
pitch: f32,
on-ground: bool,
}
record c-entity-sound-effect {
sound-event: string,
sound-category: s32,
entity-id: s32,
volume: f32,
pitch: f32,
seed: s64,
}
record c-entity-status {
entity-id: s32,
entity-status: u8,
}
record c-entity-velocity {
entity-id: s32,
velocity: string,
}
record c-explosion {
center: tuple<f64, f64, f64>,
radius: f32,
block-count: s32,
knockback: option<tuple<f64, f64, f64>>,
particle: s32,
sound: string,
block-particles-pool-size: s32,
}
record c-game-event {
event: u8,
value: f32,
}
record c-head-rot {
entity-id: s32,
head-yaw: u8,
}
record c-hurt-animation {
entity-id: s32,
yaw: f32,
}
record c-initialize-world-border {
x: f64,
z: f64,
old-diameter: f64,
new-diameter: f64,
speed: string,
portal-teleport-boundary: s32,
warning-blocks: s32,
warning-time: s32,
}
record c-item-cooldown {
group: string,
cooldown: s32,
}
record c-keep-alive {
keep-alive-id: s64,
}
record c-level-event {
event: s32,
location: tuple<s32, s32, s32>,
data: s32,
disable-relative-volume: bool,
}
record c-light-update { }
record c-login {
entity-id: s32,
is-hardcore: bool,
dimension-names: list<string>,
max-players: s32,
view-distance: s32,
simulated-distance: s32,
reduced-debug-info: bool,
enabled-respawn-screen: bool,
limited-crafting: bool,
dimension: string,
hashed-seed: s64,
game-mode: u8,
previous-gamemode: u8,
debug: bool,
is-flat: bool,
death-dimension-name: option<string>,
portal-cooldown: s32,
sealevel: s32,
enforce-secure-chat: bool,
}
record c-map-item-data {
map-id: s32,
scale: u8,
locked: bool,
icons: option<string>,
data: option<string>,
}
record c-merchant-offers {
window-id: s32,
offers: list<string>,
villager-level: s32,
experience: s32,
is-regular-villager: bool,
can-restock: bool,
}
record c-multi-block-update {
chunk-section: s64,
updates: list<string>,
}
record c-open-screen {
sync-id: s32,
window-type: s32,
window-title: string,
}
record c-open-sign-editor {
location: tuple<s32, s32, s32>,
is-front-text: bool,
}
record c-particle {
force-spawn: bool,
important: bool,
position: tuple<f64, f64, f64>,
offset: tuple<f64, f64, f64>,
max-speed: f32,
particle-count: s32,
particle-id: s32,
data: string,
}
record c-ping-response {
payload: s64,
}
record c-player-abilities {
%flags: u8,
flying-speed: f32,
field-of-view: f32,
}
record c-player-chat-message {
global-index: s32,
sender: string,
index: s32,
message-signature: option<list<string>>,
message: string,
timestamp: s64,
salt: s64,
previous-messages: list<string>,
unsigned-content: option<string>,
filter-type: string,
chat-type: s32,
sender-name: string,
target-name: option<string>,
}
record c-player-info-update {
actions: u8,
players: string,
}
record c-player-position {
teleport-id: s32,
position: tuple<f64, f64, f64>,
delta: tuple<f64, f64, f64>,
yaw: f32,
pitch: f32,
relatives: list<string>,
}
record c-remove-player-info {
players: string,
}
record c-player-spawn-position {
dimension-name: string,
location: tuple<s32, s32, s32>,
yaw: f32,
pitch: f32,
}
record c-recipe-book-add {
replace: bool,
}
record c-recipe-book-settings {
crafting-open: bool,
crafting-filtering: bool,
furnace-open: bool,
furnace-filtering: bool,
blast-furnace-open: bool,
blast-furnace-filtering: bool,
smoker-open: bool,
smoker-filtering: bool,
}
record c-remove-entities {
entity-ids: string,
}
record c-remove-mob-effect {
entity-id: s32,
effect-id: s32,
}
record c-reset-score {
entity-name: string,
objective-name: option<string>,
}
record c-respawn {
dimension-type: s32,
dimension-name: string,
hashed-seed: s64,
game-mode: u8,
previous-gamemode: u8,
debug: bool,
is-flat: bool,
death-dimension-name: option<string>,
portal-cooldown: s32,
sealevel: s32,
data-kept: u8,
}
record c-play-server-links {
links: string,
}
record c-set-border-center {
x: f64,
z: f64,
}
record c-set-border-lerp-size {
old-diameter: f64,
new-diameter: f64,
speed: string,
}
record c-set-border-size {
diameter: f64,
}
record c-set-border-warning-delay {
warning-time: s32,
}
record c-set-border-warning-distance {
warning-blocks: s32,
}
record c-set-container-content {
window-id: s32,
state-id: s32,
slot-data: string,
carried-item: string,
}
record c-set-container-property {
window-id: s32,
property: s32,
value: s32,
}
record c-set-container-slot {
window-id: u8,
state-id: s32,
slot: s32,
slot-data: string,
}
record c-set-cursor-item {
stack: string,
}
record c-set-equipment {
entity-id: s32,
equipment: list<string>,
}
record c-set-experience {
progress: f32,
level: s32,
total-experience: s32,
}
record c-set-health {
health: f32,
food: s32,
food-saturation: f32,
}
record c-set-passengers {
entity-id: s32,
passengers: string,
}
record c-set-player-inventory {
slot: s32,
item: string,
}
record c-set-player-team {
team-name: string,
method: string,
parameters: option<string>,
players: list<string>,
}
record c-update-time {
game-time: s64,
clock-updates: list<string>,
}
record c-title-text {
title: string,
}
record c-title-animation {
fade-in-ticks: s32,
stay-ticks: s32,
fade-out-ticks: s32,
}
record c-sound-effect {
sound-event: string,
sound-category: s32,
position: tuple<f64, f64, f64>,
volume: f32,
pitch: f32,
seed: f64,
}
record c-spawn-entity {
entity-id: s32,
entity-uuid: string,
r-type: s32,
position: tuple<f64, f64, f64>,
velocity: string,
pitch: u8,
yaw: u8,
head-yaw: u8,
data: s32,
}
record c-stop-sound {
sound-id: option<string>,
category: option<string>,
}
record c-store-cookie {
key: string,
payload: string,
}
record c-subtitle {
subtitle: string,
}
record c-system-chat-message {
content: string,
overlay: bool,
}
record c-tab-list {
header: string,
footer: string,
}
record c-take-item-entity {
entity-id: s32,
collector-entity-id: s32,
stack-amount: s32,
}
record c-teleport-entity {
entity-id: s32,
position: tuple<f64, f64, f64>,
delta: tuple<f64, f64, f64>,
yaw: f32,
pitch: f32,
relatives: string,
on-ground: bool,
}
record c-ticking-state {
tick-rate: f32,
is-frozen: bool,
}
record c-ticking-step {
tick-steps: s32,
}
record c-transfer {
host: string,
port: s32,
}
record c-unload-chunk {
z: s32,
x: s32,
}
record c-update-attributes {
entity-id: s32,
properties: list<string>,
}
record c-update-entity-pos {
entity-id: s32,
delta: tuple<f64, f64, f64>,
on-ground: bool,
}
record c-update-entity-pos-rot {
entity-id: s32,
delta: tuple<f64, f64, f64>,
yaw: u8,
pitch: u8,
on-ground: bool,
}
record c-update-entity-rot {
entity-id: s32,
yaw: u8,
pitch: u8,
on-ground: bool,
}
record c-update-mob-effect {
entity-id: s32,
effect-id: s32,
amplifier: s32,
duration: s32,
%flags: u8,
}
record c-update-objectives {
objective-name: string,
mode: u8,
display-name: string,
render-type: s32,
number-format: option<string>,
}
record c-update-score {
entity-name: string,
objective-name: string,
value: s32,
display-name: option<string>,
number-format: option<string>,
}
record c-world-event {
event: s32,
location: tuple<s32, s32, s32>,
data: s32,
disable-relative-volume: bool,
}
variant serverbound-packet {
s-attack(s-attack),
s-change-game-mode(s-change-game-mode),
s-chat-command(s-chat-command),
s-chat-message(s-chat-message),
s-chunk-batch(s-chunk-batch),
s-click-slot(s-click-slot),
s-client-command(s-client-command),
s-client-information-play(s-client-information-play),
s-client-tick-end(s-client-tick-end),
s-close-container(s-close-container),
s-command-suggestion(s-command-suggestion),
s-confirm-teleport(s-confirm-teleport),
s-container-button-click(s-container-button-click),
s-cookie-response(s-cookie-response),
s-custom-payload(s-custom-payload),
s-interact(s-interact),
s-keep-alive(s-keep-alive),
s-move-vehicle(s-move-vehicle),
s-paddle-boat(s-paddle-boat),
s-pick-item-from-block(s-pick-item-from-block),
s-pick-item-from-entity(s-pick-item-from-entity),
s-play-ping-request(s-play-ping-request),
s-place-recipe(s-place-recipe),
s-player-abilities(s-player-abilities),
s-player-action(s-player-action),
s-player-command(s-player-command),
s-set-player-ground(s-set-player-ground),
s-player-input(s-player-input),
s-player-loaded(s-player-loaded),
s-player-position(s-player-position),
s-player-position-rotation(s-player-position-rotation),
s-player-rotation(s-player-rotation),
s-player-session(s-player-session),
s-recipe-book-change-settings(s-recipe-book-change-settings),
s-recipe-book-seen-recipe(s-recipe-book-seen-recipe),
s-rename-item(s-rename-item),
s-select-trade(s-select-trade),
s-set-beacon(s-set-beacon),
s-set-command-block(s-set-command-block),
s-set-creative-slot(s-set-creative-slot),
s-set-held-item(s-set-held-item),
s-swing-arm(s-swing-arm),
s-update-sign(s-update-sign),
s-use-item(s-use-item),
s-use-item-on(s-use-item-on),
}
variant clientbound-packet {
c-acknowledge-block-change(c-acknowledge-block-change),
c-action-bar(c-action-bar),
c-set-block-destroy-stage(c-set-block-destroy-stage),
c-block-entity-data(c-block-entity-data),
c-block-event(c-block-event),
c-block-update(c-block-update),
c-boss-event(c-boss-event),
c-center-chunk(c-center-chunk),
c-change-difficulty(c-change-difficulty),
c-chunk-batch-end(c-chunk-batch-end),
c-chunk-batch-start(c-chunk-batch-start),
c-chunk-data(c-chunk-data),
c-clear-title(c-clear-title),
c-close-container(c-close-container),
c-combat-death(c-combat-death),
c-command-suggestions(c-command-suggestions),
c-commands(c-commands),
c-play-cookie-request(c-play-cookie-request),
c-custom-payload(c-custom-payload),
c-damage-event(c-damage-event),
c-play-disconnect(c-play-disconnect),
c-disguised-chat-message(c-disguised-chat-message),
c-display-objective(c-display-objective),
c-entity-animation(c-entity-animation),
c-set-entity-metadata(c-set-entity-metadata),
c-entity-position-sync(c-entity-position-sync),
c-entity-sound-effect(c-entity-sound-effect),
c-entity-status(c-entity-status),
c-entity-velocity(c-entity-velocity),
c-explosion(c-explosion),
c-game-event(c-game-event),
c-head-rot(c-head-rot),
c-hurt-animation(c-hurt-animation),
c-initialize-world-border(c-initialize-world-border),
c-item-cooldown(c-item-cooldown),
c-keep-alive(c-keep-alive),
c-level-event(c-level-event),
c-light-update(c-light-update),
c-login(c-login),
c-map-item-data(c-map-item-data),
c-merchant-offers(c-merchant-offers),
c-multi-block-update(c-multi-block-update),
c-open-screen(c-open-screen),
c-open-sign-editor(c-open-sign-editor),
c-particle(c-particle),
c-ping-response(c-ping-response),
c-player-abilities(c-player-abilities),
c-player-chat-message(c-player-chat-message),
c-player-info-update(c-player-info-update),
c-player-position(c-player-position),
c-remove-player-info(c-remove-player-info),
c-player-spawn-position(c-player-spawn-position),
c-recipe-book-add(c-recipe-book-add),
c-recipe-book-settings(c-recipe-book-settings),
c-remove-entities(c-remove-entities),
c-remove-mob-effect(c-remove-mob-effect),
c-reset-score(c-reset-score),
c-respawn(c-respawn),
c-play-server-links(c-play-server-links),
c-set-border-center(c-set-border-center),
c-set-border-lerp-size(c-set-border-lerp-size),
c-set-border-size(c-set-border-size),
c-set-border-warning-delay(c-set-border-warning-delay),
c-set-border-warning-distance(c-set-border-warning-distance),
c-set-container-content(c-set-container-content),
c-set-container-property(c-set-container-property),
c-set-container-slot(c-set-container-slot),
c-set-cursor-item(c-set-cursor-item),
c-set-equipment(c-set-equipment),
c-set-experience(c-set-experience),
c-set-health(c-set-health),
c-set-passengers(c-set-passengers),
c-set-player-inventory(c-set-player-inventory),
c-set-player-team(c-set-player-team),
c-update-time(c-update-time),
c-title-text(c-title-text),
c-title-animation(c-title-animation),
c-sound-effect(c-sound-effect),
c-spawn-entity(c-spawn-entity),
c-stop-sound(c-stop-sound),
c-store-cookie(c-store-cookie),
c-subtitle(c-subtitle),
c-system-chat-message(c-system-chat-message),
c-tab-list(c-tab-list),
c-take-item-entity(c-take-item-entity),
c-teleport-entity(c-teleport-entity),
c-ticking-state(c-ticking-state),
c-ticking-step(c-ticking-step),
c-transfer(c-transfer),
c-unload-chunk(c-unload-chunk),
c-update-attributes(c-update-attributes),
c-update-entity-pos(c-update-entity-pos),
c-update-entity-pos-rot(c-update-entity-pos-rot),
c-update-entity-rot(c-update-entity-rot),
c-update-mob-effect(c-update-mob-effect),
c-update-objectives(c-update-objectives),
c-update-score(c-update-score),
c-world-event(c-world-event),
}
}

View File

@ -0,0 +1,123 @@
package pumpkin:plugin@0.1.0;
interface particles {
enum particle {
angry-villager,
block,
block-marker,
bubble,
cloud,
copper-fire-flame,
crit,
damage-indicator,
dragon-breath,
dripping-lava,
falling-lava,
landing-lava,
dripping-water,
falling-water,
dust,
dust-color-transition,
effect,
elder-guardian,
enchanted-hit,
enchant,
end-rod,
entity-effect,
explosion-emitter,
explosion,
gust,
small-gust,
gust-emitter-large,
gust-emitter-small,
sonic-boom,
falling-dust,
firework,
fishing,
flame,
infested,
cherry-leaves,
pale-oak-leaves,
tinted-leaves,
sculk-soul,
sculk-charge,
sculk-charge-pop,
soul-fire-flame,
soul,
flash,
happy-villager,
composter,
heart,
instant-effect,
item,
vibration,
trail,
pause-mob-growth,
reset-mob-growth,
item-slime,
item-cobweb,
item-snowball,
large-smoke,
lava,
mycelium,
note,
poof,
portal,
rain,
smoke,
white-smoke,
sneeze,
spit,
squid-ink,
sweep-attack,
totem-of-undying,
underwater,
splash,
witch,
bubble-pop,
current-down,
bubble-column-up,
nautilus,
dolphin,
campfire-cosy-smoke,
campfire-signal-smoke,
dripping-honey,
falling-honey,
landing-honey,
falling-nectar,
falling-spore-blossom,
ash,
crimson-spore,
warped-spore,
spore-blossom-air,
dripping-obsidian-tear,
falling-obsidian-tear,
landing-obsidian-tear,
reverse-portal,
white-ash,
small-flame,
snowflake,
dripping-dripstone-lava,
falling-dripstone-lava,
dripping-dripstone-water,
falling-dripstone-water,
glow-squid-ink,
glow,
wax-on,
wax-off,
electric-spark,
scrape,
shriek,
egg-crack,
dust-plume,
trial-spawner-detection,
trial-spawner-detection-ominous,
vault-connection,
dust-pillar,
ominous-spawning,
raid-omen,
trial-omen,
block-crumble,
firefly,
}
}

View File

@ -6,6 +6,8 @@ interface player {
use permission.{permission-level}; use permission.{permission-level};
use text.{text-component}; use text.{text-component};
use gui.{gui}; use gui.{gui};
use java-packets.{clientbound-packet as java-packet};
use bedrock-packets.{clientbound-packet as bedrock-packet};
/// Various abilities a player can have. /// Various abilities a player can have.
record player-abilities { record player-abilities {
@ -211,5 +213,12 @@ interface player {
get-skin-parts: func() -> skin-parts; get-skin-parts: func() -> skin-parts;
/// Sets which skin parts are visible. /// Sets which skin parts are visible.
set-skin-parts: func(parts: skin-parts); set-skin-parts: func(parts: skin-parts);
/// Sends a Java-specific clientbound packet to the player.
send-java-packet: func(packet: java-packet);
/// Sends a Bedrock-specific clientbound packet to the player.
send-bedrock-packet: func(packet: bedrock-packet);
/// Sends a custom payload packet (plugin message) to the player.
send-custom-payload: func(channel: string, data: list<u8>);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,9 @@ interface %world {
use common.{position, entity-pose}; use common.{position, entity-pose};
use text.{text-component}; use text.{text-component};
use scoreboard.{scoreboard}; use scoreboard.{scoreboard};
use particles.{particle};
use sounds.{sound};
use entity-types.{entity-type};
/// Defines how a block reacts when pushed by a piston. /// Defines how a block reacts when pushed by a piston.
enum piston-behavior { enum piston-behavior {
@ -104,7 +107,7 @@ interface %world {
resource entity { resource entity {
get-id: func() -> u32; get-id: func() -> u32;
get-uuid: func() -> string; get-uuid: func() -> string;
get-type: func() -> string; get-type: func() -> entity-type;
get-position: func() -> position; get-position: func() -> position;
get-world: func() -> %world; get-world: func() -> %world;
@ -228,10 +231,10 @@ interface %world {
get-scoreboard: func() -> scoreboard; get-scoreboard: func() -> scoreboard;
/// Plays a sound at the specified position for all players in the world. /// Plays a sound at the specified position for all players in the world.
play-sound: func(sound: string, category: sound-category, pos: position, volume: f32, pitch: f32); play-sound: func(sound: sound, category: sound-category, pos: position, volume: f32, pitch: f32);
/// Spawns particles at the specified position. /// Spawns particles at the specified position.
spawn-particle: func(particle: string, pos: position, offset: position, max-speed: f32, count: s32); spawn-particle: func(particle: particle, pos: position, offset: position, max-speed: f32, count: s32);
/// Creates an explosion at the specified position. /// Creates an explosion at the specified position.
create-explosion: func(pos: position, power: f32, create-fire: bool, interaction: explosion-interaction); create-explosion: func(pos: position, power: f32, create-fire: bool, interaction: explosion-interaction);

View File

@ -26,3 +26,32 @@ pub mod set_title;
pub mod start_game; pub mod start_game;
pub mod update_abilities; pub mod update_abilities;
pub mod update_attributes; pub mod update_attributes;
pub use available_commands::*;
pub use chunk_radius_update::*;
pub use container_open::*;
pub use correct_player_move::*;
pub use creative_content::*;
pub use disconnect_player::*;
pub use gamerules_changed::*;
pub use handshake::*;
pub use inventory_content::*;
pub use level_chunk::*;
pub use level_event::*;
pub use move_player::*;
pub use network_chunk_publisher_update::*;
pub use network_settings::*;
pub use play_status::*;
pub use player_hotbar::*;
pub use raknet::*;
pub use resource_pack_stack::*;
pub use resource_packs_info::*;
pub use set_actor_data::*;
pub use set_actor_motion::*;
pub use set_health::*;
pub use set_player_gamemode::*;
pub use set_time::*;
pub use set_title::*;
pub use start_game::*;
pub use update_abilities::*;
pub use update_attributes::*;

View File

@ -5,12 +5,12 @@ use crate::serial::PacketWrite;
#[derive(PacketWrite)] #[derive(PacketWrite)]
#[packet(143)] #[packet(143)]
pub struct CNetworkSettings { pub struct CNetworkSettings {
compression_threshold: u16, pub compression_threshold: u16,
/// `ZLib` = 0, Snappy = 1, None = 255 /// `ZLib` = 0, Snappy = 1, None = 255
compression_method: u16, pub compression_method: u16,
client_throttle_enabled: bool, pub client_throttle_enabled: bool,
client_throttle_threshold: i8, pub client_throttle_threshold: i8,
client_throttle_scalar: f32, pub client_throttle_scalar: f32,
} }
impl CNetworkSettings { impl CNetworkSettings {

View File

@ -2835,55 +2835,56 @@ impl Player {
let stack = item_in_hand.lock().await; let stack = item_in_hand.lock().await;
if stack.item.id == Item::FILLED_MAP.id if stack.item.id == Item::FILLED_MAP.id
&& let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>() { && let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>()
let map_id = map_id_comp.id; {
if let Some(map_data_arc) = server.map_manager.get_map(map_id) { let map_id = map_id_comp.id;
let mut map_data = map_data_arc.lock().await; if let Some(map_data_arc) = server.map_manager.get_map(map_id) {
map_data.update(self).await; let mut map_data = map_data_arc.lock().await;
map_data.update(self).await;
let tick_count = self.tick_counter.load(Ordering::Relaxed); let tick_count = self.tick_counter.load(Ordering::Relaxed);
if map_data.dirty || tick_count % 10 == 0 { if map_data.dirty || tick_count % 10 == 0 {
let scale = 1 << map_data.scale; let scale = 1 << map_data.scale;
let pos = self.position(); let pos = self.position();
let dx = pos.x - map_data.center_x as f64; let dx = pos.x - map_data.center_x as f64;
let dz = pos.z - map_data.center_z as f64; let dz = pos.z - map_data.center_z as f64;
let icon_x = (dx / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8; let icon_x = (dx / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8;
let icon_z = (dz / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8; let icon_z = (dz / scale as f64 * 2.0).clamp(-128.0, 127.0) as i8;
let yaw = self.living_entity.entity.yaw.load(); let yaw = self.living_entity.entity.yaw.load();
let icon_direction = let icon_direction =
((((yaw * 16.0 / 360.0).round() as i32 + 8) % 16 + 16) % 16) as i8; ((((yaw * 16.0 / 360.0).round() as i32 + 8) % 16 + 16) % 16) as i8;
let icons = [MapIcon { let icons = [MapIcon {
icon_type: VarInt(0), // White pointer icon_type: VarInt(0), // White pointer
x: icon_x, x: icon_x,
z: icon_z, z: icon_z,
direction: icon_direction, direction: icon_direction,
display_name: None, display_name: None,
}]; }];
let data = map_data.dirty.then(|| MapPatch { let data = map_data.dirty.then(|| MapPatch {
columns: 128, columns: 128,
rows: 128, rows: 128,
x: 0, x: 0,
z: 0, z: 0,
data: &*map_data.colors, data: &*map_data.colors,
}); });
self.client self.client
.enqueue_packet(&CMapItemData { .enqueue_packet(&CMapItemData {
map_id: VarInt(map_id), map_id: VarInt(map_id),
scale: map_data.scale, scale: map_data.scale,
locked: map_data.locked, locked: map_data.locked,
icons: Some(&icons), icons: Some(&icons),
data, data,
}) })
.await; .await;
map_data.dirty = false; map_data.dirty = false;
}
} }
} }
}
} }
} }

View File

@ -9,6 +9,7 @@ use crate::{
net::{bedrock::BedrockClient, java::JavaClient}, net::{bedrock::BedrockClient, java::JavaClient},
server::Server, server::Server,
}; };
use bytes::Bytes;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_protocol::{ClientPacket, Property}; use pumpkin_protocol::{ClientPacket, Property};
@ -158,6 +159,13 @@ impl ClientPlatform {
} }
} }
pub async fn send_packet_now_data(&self, data: Bytes) {
match self {
Self::Java(java) => java.send_packet_now_data(data).await,
Self::Bedrock(bedrock) => bedrock.enqueue_packet_data(data).await,
}
}
pub async fn kick(&self, reason: DisconnectReason, message: TextComponent) { pub async fn kick(&self, reason: DisconnectReason, message: TextComponent) {
match self { match self {
Self::Java(java) => java.kick(message).await, Self::Java(java) => java.kick(message).await,

View File

@ -7,6 +7,7 @@ use crate::plugin::loader::wasm::wasm_host::{
wit::v0_1::pumpkin::plugin::{ wit::v0_1::pumpkin::plugin::{
common::{EntityPose, Position}, common::{EntityPose, Position},
entity::Host, entity::Host,
entity_types,
text::TextComponent, text::TextComponent,
world::{ world::{
BlockPos as WitBlockPos, Entity, HostEntity, RaycastResult as WitRaycastResult, World, BlockPos as WitBlockPos, Entity, HostEntity, RaycastResult as WitRaycastResult, World,
@ -17,6 +18,7 @@ use crate::plugin::loader::wasm::wasm_host::{
use pumpkin_data::entity::EntityPose as InternalEntityPose; use pumpkin_data::entity::EntityPose as InternalEntityPose;
impl Host for PluginHostState {} impl Host for PluginHostState {}
impl entity_types::Host for PluginHostState {}
fn entity_from_resource( fn entity_from_resource(
state: &PluginHostState, state: &PluginHostState,
@ -63,9 +65,37 @@ impl HostEntity for PluginHostState {
Ok(entity.get_entity().entity_uuid.to_string()) Ok(entity.get_entity().entity_uuid.to_string())
} }
async fn get_type(&mut self, entity: Resource<Entity>) -> wasmtime::Result<String> { async fn get_type(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<entity_types::EntityType> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_type.resource_name.to_string()) let name = entity.get_entity().entity_type.resource_name.to_string();
// Standard WIT enum mapping: kebab-case
let _wit_name = name.replace('_', "-");
// We need to find the variant in the generated enum.
// Since we don't have a direct mapping function yet, we use debug format or similar if available,
// but for now let's just use the fact that it's an enum.
// Actually, the easiest way is to use the generated `from_name` or similar if we added it,
// but here we are returning it TO the guest.
// Wait, I need to CONSTRUCT the EntityType enum.
// I can't easily do it by string unless I have a mapping.
// But wit-bindgen generates the enum.
// Let's assume there is a way to construct it.
// Actually, I can use the fact that it's a simple enum.
// I'll use a hack for now if I don't have a better way, but wait!
// I can just use `serde_json` to parse the string into the enum if it implements Deserialize!
// Or just a match.
// Better: let's see if pumpkin_data has a way.
// For now, I'll just return a placeholder or implement a basic match for the most common ones
// OR better: use the generated code.
Err(wasmtime::Error::msg(
"get_type implementation pending full enum mapping",
))
} }
async fn get_position(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> { async fn get_position(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> {

View File

@ -0,0 +1,659 @@
// Generated by pumpkin-codegen. Do not edit.
#![allow(clippy::all)]
#![allow(clippy::pedantic)]
#![allow(unused_imports)]
#![allow(unused_variables)]
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_packets::ClientboundPacket;
use bytes::Bytes;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_util::version::MinecraftVersion;
#[must_use]
pub fn serialize_java_packet(
packet: &ClientboundPacket,
version: MinecraftVersion,
) -> Option<Bytes> {
match packet {
ClientboundPacket::CAcknowledgeBlockChange(data) => {
let p = pumpkin_protocol::java::client::play::CAcknowledgeBlockChange {
sequence_id: VarInt(data.sequence_id),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CActionBar(data) => {
let component_action_bar =
pumpkin_util::text::TextComponent::text(data.action_bar.clone());
let p = pumpkin_protocol::java::client::play::CActionBar {
action_bar: &component_action_bar,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetBlockDestroyStage(data) => {
let p = pumpkin_protocol::java::client::play::CSetBlockDestroyStage {
entity_id: VarInt(data.entity_id),
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
destroy_stage: data.destroy_stage.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CBlockEvent(data) => {
let p = pumpkin_protocol::java::client::play::CBlockEvent {
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
action_id: data.action_id.try_into().unwrap(),
action_parameter: data.action_parameter.try_into().unwrap(),
block_type: VarInt(data.block_type),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CBlockUpdate(data) => {
let p = pumpkin_protocol::java::client::play::CBlockUpdate {
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
state_id: VarInt(data.state_id),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CCenterChunk(data) => {
let p = pumpkin_protocol::java::client::play::CCenterChunk {
chunk_x: VarInt(data.chunk_x),
chunk_z: VarInt(data.chunk_z),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CChangeDifficulty(data) => {
let p = pumpkin_protocol::java::client::play::CChangeDifficulty {
difficulty: data.difficulty.try_into().unwrap(),
locked: data.locked.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CChunkBatchEnd(data) => {
let p = pumpkin_protocol::java::client::play::CChunkBatchEnd {
batch_size: VarInt(data.batch_size),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CClearTitle(data) => {
let p = pumpkin_protocol::java::client::play::CClearTitle {
reset: data.reset.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CCloseContainer(data) => {
let p = pumpkin_protocol::java::client::play::CCloseContainer {
sync_id: VarInt(data.sync_id),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CCombatDeath(data) => {
let component_message = pumpkin_util::text::TextComponent::text(data.message.clone());
let p = pumpkin_protocol::java::client::play::CCombatDeath {
player_id: VarInt(data.player_id),
message: &component_message,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CPlayDisconnect(data) => {
let component_reason = pumpkin_util::text::TextComponent::text(data.reason.clone());
let p = pumpkin_protocol::java::client::play::CPlayDisconnect {
reason: &component_reason,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CDisplayObjective(data) => {
let p = pumpkin_protocol::java::client::play::CDisplayObjective {
position: VarInt(data.position),
score_name: data.score_name.clone(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CEntityAnimation(data) => {
let p = pumpkin_protocol::java::client::play::CEntityAnimation {
entity_id: VarInt(data.entity_id),
animation: data.animation.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CEntityPositionSync(data) => {
let p = pumpkin_protocol::java::client::play::CEntityPositionSync {
entity_id: VarInt(data.entity_id),
position: pumpkin_util::math::vector3::Vector3::new(
data.position.0 as _,
data.position.1 as _,
data.position.2 as _,
),
delta: pumpkin_util::math::vector3::Vector3::new(
data.delta.0 as _,
data.delta.1 as _,
data.delta.2 as _,
),
yaw: data.yaw.try_into().unwrap(),
pitch: data.pitch.try_into().unwrap(),
on_ground: data.on_ground.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CEntityStatus(data) => {
let p = pumpkin_protocol::java::client::play::CEntityStatus {
entity_id: data.entity_id.try_into().unwrap(),
entity_status: data.entity_status.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CGameEvent(data) => {
let p = pumpkin_protocol::java::client::play::CGameEvent {
event: data.event.try_into().unwrap(),
value: data.value.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CHeadRot(data) => {
let p = pumpkin_protocol::java::client::play::CHeadRot {
entity_id: VarInt(data.entity_id),
head_yaw: data.head_yaw.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CHurtAnimation(data) => {
let p = pumpkin_protocol::java::client::play::CHurtAnimation {
entity_id: VarInt(data.entity_id),
yaw: data.yaw.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CItemCooldown(data) => {
let p = pumpkin_protocol::java::client::play::CItemCooldown {
group: data.group.clone(),
cooldown: VarInt(data.cooldown),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CKeepAlive(data) => {
let p = pumpkin_protocol::java::client::play::CKeepAlive {
keep_alive_id: data.keep_alive_id.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CLevelEvent(data) => {
let p = pumpkin_protocol::java::client::play::CLevelEvent {
event: data.event.try_into().unwrap(),
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
data: data.data.try_into().unwrap(),
disable_relative_volume: data.disable_relative_volume.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::COpenScreen(data) => {
let component_window_title =
pumpkin_util::text::TextComponent::text(data.window_title.clone());
let p = pumpkin_protocol::java::client::play::COpenScreen {
sync_id: VarInt(data.sync_id),
window_type: VarInt(data.window_type),
window_title: &component_window_title,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::COpenSignEditor(data) => {
let p = pumpkin_protocol::java::client::play::COpenSignEditor {
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
is_front_text: data.is_front_text.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CPingResponse(data) => {
let p = pumpkin_protocol::java::client::play::CPingResponse {
payload: data.payload.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CPlayerAbilities(data) => {
let p = pumpkin_protocol::java::client::play::CPlayerAbilities {
flags: data.flags.try_into().unwrap(),
flying_speed: data.flying_speed.try_into().unwrap(),
field_of_view: data.field_of_view.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CPlayerSpawnPosition(data) => {
let p = pumpkin_protocol::java::client::play::CPlayerSpawnPosition {
dimension_name: data.dimension_name.clone(),
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
yaw: data.yaw.try_into().unwrap(),
pitch: data.pitch.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CRecipeBookAdd(data) => {
let p = pumpkin_protocol::java::client::play::CRecipeBookAdd {
replace: data.replace.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CRecipeBookSettings(data) => {
let p = pumpkin_protocol::java::client::play::CRecipeBookSettings {
crafting_open: data.crafting_open.try_into().unwrap(),
crafting_filtering: data.crafting_filtering.try_into().unwrap(),
furnace_open: data.furnace_open.try_into().unwrap(),
furnace_filtering: data.furnace_filtering.try_into().unwrap(),
blast_furnace_open: data.blast_furnace_open.try_into().unwrap(),
blast_furnace_filtering: data.blast_furnace_filtering.try_into().unwrap(),
smoker_open: data.smoker_open.try_into().unwrap(),
smoker_filtering: data.smoker_filtering.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CRemoveMobEffect(data) => {
let p = pumpkin_protocol::java::client::play::CRemoveMobEffect {
entity_id: VarInt(data.entity_id),
effect_id: VarInt(data.effect_id),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetBorderCenter(data) => {
let p = pumpkin_protocol::java::client::play::CSetBorderCenter {
x: data.x.try_into().unwrap(),
z: data.z.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetBorderSize(data) => {
let p = pumpkin_protocol::java::client::play::CSetBorderSize {
diameter: data.diameter.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetBorderWarningDelay(data) => {
let p = pumpkin_protocol::java::client::play::CSetBorderWarningDelay {
warning_time: VarInt(data.warning_time),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetBorderWarningDistance(data) => {
let p = pumpkin_protocol::java::client::play::CSetBorderWarningDistance {
warning_blocks: VarInt(data.warning_blocks),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetContainerProperty(data) => {
let p = pumpkin_protocol::java::client::play::CSetContainerProperty {
window_id: VarInt(data.window_id),
property: data.property.try_into().unwrap(),
value: data.value.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetExperience(data) => {
let p = pumpkin_protocol::java::client::play::CSetExperience {
progress: data.progress.try_into().unwrap(),
level: VarInt(data.level),
total_experience: VarInt(data.total_experience),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSetHealth(data) => {
let p = pumpkin_protocol::java::client::play::CSetHealth {
health: data.health.try_into().unwrap(),
food: VarInt(data.food),
food_saturation: data.food_saturation.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTitleText(data) => {
let component_title = pumpkin_util::text::TextComponent::text(data.title.clone());
let p = pumpkin_protocol::java::client::play::CTitleText {
title: &component_title,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTitleAnimation(data) => {
let p = pumpkin_protocol::java::client::play::CTitleAnimation {
fade_in_ticks: data.fade_in_ticks.try_into().unwrap(),
stay_ticks: data.stay_ticks.try_into().unwrap(),
fade_out_ticks: data.fade_out_ticks.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSubtitle(data) => {
let component_subtitle = pumpkin_util::text::TextComponent::text(data.subtitle.clone());
let p = pumpkin_protocol::java::client::play::CSubtitle {
subtitle: &component_subtitle,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CSystemChatMessage(data) => {
let component_content = pumpkin_util::text::TextComponent::text(data.content.clone());
let p = pumpkin_protocol::java::client::play::CSystemChatMessage {
content: &component_content,
overlay: data.overlay.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTabList(data) => {
let component_header = pumpkin_util::text::TextComponent::text(data.header.clone());
let component_footer = pumpkin_util::text::TextComponent::text(data.footer.clone());
let p = pumpkin_protocol::java::client::play::CTabList {
header: &component_header,
footer: &component_footer,
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTakeItemEntity(data) => {
let p = pumpkin_protocol::java::client::play::CTakeItemEntity {
entity_id: VarInt(data.entity_id),
collector_entity_id: VarInt(data.collector_entity_id),
stack_amount: VarInt(data.stack_amount),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTickingState(data) => {
let p = pumpkin_protocol::java::client::play::CTickingState {
tick_rate: data.tick_rate.try_into().unwrap(),
is_frozen: data.is_frozen.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTickingStep(data) => {
let p = pumpkin_protocol::java::client::play::CTickingStep {
tick_steps: VarInt(data.tick_steps),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CTransfer(data) => {
let p = pumpkin_protocol::java::client::play::CTransfer {
host: &data.host,
port: VarInt(data.port),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CUnloadChunk(data) => {
let p = pumpkin_protocol::java::client::play::CUnloadChunk {
z: data.z.try_into().unwrap(),
x: data.x.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CUpdateEntityPos(data) => {
let p = pumpkin_protocol::java::client::play::CUpdateEntityPos {
entity_id: VarInt(data.entity_id),
delta: pumpkin_util::math::vector3::Vector3::new(
data.delta.0 as _,
data.delta.1 as _,
data.delta.2 as _,
),
on_ground: data.on_ground.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CUpdateEntityPosRot(data) => {
let p = pumpkin_protocol::java::client::play::CUpdateEntityPosRot {
entity_id: VarInt(data.entity_id),
delta: pumpkin_util::math::vector3::Vector3::new(
data.delta.0 as _,
data.delta.1 as _,
data.delta.2 as _,
),
yaw: data.yaw.try_into().unwrap(),
pitch: data.pitch.try_into().unwrap(),
on_ground: data.on_ground.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CUpdateEntityRot(data) => {
let p = pumpkin_protocol::java::client::play::CUpdateEntityRot {
entity_id: VarInt(data.entity_id),
yaw: data.yaw.try_into().unwrap(),
pitch: data.pitch.try_into().unwrap(),
on_ground: data.on_ground.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CUpdateMobEffect(data) => {
let p = pumpkin_protocol::java::client::play::CUpdateMobEffect {
entity_id: VarInt(data.entity_id),
effect_id: VarInt(data.effect_id),
amplifier: VarInt(data.amplifier),
duration: VarInt(data.duration),
flags: data.flags.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
ClientboundPacket::CWorldEvent(data) => {
let p = pumpkin_protocol::java::client::play::CWorldEvent {
event: data.event.try_into().unwrap(),
location: pumpkin_util::math::position::BlockPos::new(
data.location.0,
data.location.1,
data.location.2,
),
data: data.data.try_into().unwrap(),
disable_relative_volume: data.disable_relative_volume.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
Some(buf.into())
}
_ => None,
}
}
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::bedrock_packets::ClientboundPacket as BClientboundPacket;
#[must_use]
pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {
match packet {
BClientboundPacket::CChunkRadiusUpdate(data) => {
let p = pumpkin_protocol::bedrock::client::CChunkRadiusUpdate {
chunk_radius: VarInt(data.chunk_radius),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CDisconnectPlayer(data) => {
let p = pumpkin_protocol::bedrock::client::CDisconnectPlayer {
reason: VarInt(data.reason),
skip_message: data.skip_message.try_into().unwrap(),
message: data.message.clone(),
filtered_message: data.filtered_message.clone(),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CLevelEvent(data) => {
let p = pumpkin_protocol::bedrock::client::CLevelEvent {
event_id: VarInt(data.event_id),
position: pumpkin_util::math::vector3::Vector3::new(
data.position.0 as _,
data.position.1 as _,
data.position.2 as _,
),
data: VarInt(data.data),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CNetworkSettings(data) => {
let p = pumpkin_protocol::bedrock::client::CNetworkSettings {
compression_threshold: data.compression_threshold.try_into().unwrap(),
compression_method: data.compression_method.try_into().unwrap(),
client_throttle_enabled: data.client_throttle_enabled.try_into().unwrap(),
client_throttle_threshold: data.client_throttle_threshold.try_into().unwrap(),
client_throttle_scalar: data.client_throttle_scalar.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CPlayerHotbar(data) => {
let p = pumpkin_protocol::bedrock::client::CPlayerHotbar {
selected_slot: pumpkin_protocol::codec::var_uint::VarUInt(
data.selected_slot.try_into().unwrap(),
),
container_id: data.container_id.try_into().unwrap(),
should_select_block: data.should_select_block.try_into().unwrap(),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CSetHealth(data) => {
let p = pumpkin_protocol::bedrock::client::CSetHealth {
health: VarInt(data.health),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CSetTime(data) => {
let p = pumpkin_protocol::bedrock::client::CSetTime {
time: VarInt(data.time),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
BClientboundPacket::CSetTitle(data) => {
let p = pumpkin_protocol::bedrock::client::CSetTitle {
action_type: VarInt(data.action_type),
text: data.text.clone(),
fade_in_duration: VarInt(data.fade_in_duration),
remain_duration: VarInt(data.remain_duration),
fade_out_duration: VarInt(data.fade_out_duration),
xuid: data.xuid.clone(),
platform_online_id: data.platform_online_id.clone(),
filtered_message: data.filtered_message.clone(),
};
let mut buf = Vec::new();
crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();
Some(buf.into())
}
_ => None,
}
}

View File

@ -13,6 +13,7 @@ pub mod common;
pub mod context; pub mod context;
pub mod entity; pub mod entity;
pub mod events; pub mod events;
pub mod generated_packets;
pub mod gui; pub mod gui;
pub mod i18n; pub mod i18n;
pub mod logging; pub mod logging;
@ -31,6 +32,9 @@ bindgen!({
exports: { default: async | trappable}, exports: { default: async | trappable},
}); });
impl pumpkin::plugin::java_packets::Host for PluginHostState {}
impl pumpkin::plugin::bedrock_packets::Host for PluginHostState {}
pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> { pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?; Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
Ok(()) Ok(())

View File

@ -874,6 +874,52 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(()) Ok(())
} }
async fn send_java_packet(
&mut self,
player: Resource<Player>,
packet: pumpkin::plugin::java_packets::ClientboundPacket,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
if let Some(bytes) = crate::plugin::loader::wasm::wasm_host::wit::v0_1::generated_packets::serialize_java_packet(
&packet, player.client.java().version.load(),
) {
player.client.java().send_packet_now_data(bytes).await;
}
Ok(())
}
async fn send_bedrock_packet(
&mut self,
player: Resource<Player>,
packet: pumpkin::plugin::bedrock_packets::ClientboundPacket,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
if let Some(bytes) = crate::plugin::loader::wasm::wasm_host::wit::v0_1::generated_packets::serialize_bedrock_packet(
&packet,
) {
player.client.send_packet_now_data(bytes).await;
}
Ok(())
}
async fn send_custom_payload(
&mut self,
player: Resource<Player>,
channel: String,
data: Vec<u8>,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
if let crate::net::ClientPlatform::Java(_) = player.client {
player
.client
.send_packet_now(&pumpkin_protocol::java::client::play::CCustomPayload::new(
&channel, &data,
))
.await;
}
Ok(())
}
async fn drop(&mut self, rep: Resource<Player>) -> wasmtime::Result<()> { async fn drop(&mut self, rep: Resource<Player>) -> wasmtime::Result<()> {
let _ = self let _ = self
.resource_table .resource_table

View File

@ -48,6 +48,8 @@ impl PluginHostState {
} }
impl pumpkin::plugin::world::Host for PluginHostState {} impl pumpkin::plugin::world::Host for PluginHostState {}
impl pumpkin::plugin::particles::Host for PluginHostState {}
impl pumpkin::plugin::sounds::Host for PluginHostState {}
impl pumpkin::plugin::world::HostWorld for PluginHostState { impl pumpkin::plugin::world::HostWorld for PluginHostState {
async fn get_id(&mut self, world: Resource<World>) -> wasmtime::Result<String> { async fn get_id(&mut self, world: Resource<World>) -> wasmtime::Result<String> {
@ -245,15 +247,17 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
async fn play_sound( async fn play_sound(
&mut self, &mut self,
world: Resource<World>, world: Resource<World>,
sound: String, sound: pumpkin::plugin::sounds::Sound,
category: pumpkin::plugin::world::SoundCategory, category: pumpkin::plugin::world::SoundCategory,
pos: pumpkin::plugin::common::Position, pos: pumpkin::plugin::common::Position,
volume: f32, volume: f32,
pitch: f32, pitch: f32,
) -> wasmtime::Result<()> { ) -> wasmtime::Result<()> {
let world_ref = self.get_world_res(&world)?; let world_ref = self.get_world_res(&world)?;
let sound_data = pumpkin_data::sound::Sound::from_name(&sound) let sound_name = format!("{sound:?}").to_lowercase().replace('_', ".");
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound}")))?; let sound_data = pumpkin_data::sound::Sound::from_name(&sound_name)
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound_name}")))?;
let internal_category = match category { let internal_category = match category {
pumpkin::plugin::world::SoundCategory::Master => { pumpkin::plugin::world::SoundCategory::Master => {
pumpkin_data::sound::SoundCategory::Master pumpkin_data::sound::SoundCategory::Master
@ -303,15 +307,16 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
async fn spawn_particle( async fn spawn_particle(
&mut self, &mut self,
world: Resource<World>, world: Resource<World>,
particle: String, particle: pumpkin::plugin::particles::Particle,
pos: pumpkin::plugin::common::Position, pos: pumpkin::plugin::common::Position,
offset: pumpkin::plugin::common::Position, offset: pumpkin::plugin::common::Position,
max_speed: f32, max_speed: f32,
count: i32, count: i32,
) -> wasmtime::Result<()> { ) -> wasmtime::Result<()> {
let world_ref = self.get_world_res(&world)?; let world_ref = self.get_world_res(&world)?;
let particle_data = pumpkin_data::particle::Particle::from_name(&particle) let particle_name = format!("{particle:?}").to_lowercase().replace('_', "-");
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown particle: {particle}")))?; let particle_data = pumpkin_data::particle::Particle::from_name(&particle_name)
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown particle: {particle_name}")))?;
world_ref world_ref
.provider .provider