mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: BlockState Remaping (#1457)
* vn translation * feat: blockstate remapping * formatting cleanup * damm dot * damm comma * fix: mapping 1.21.8 tracker * fix: entity metadata * formatting * clippy * fix: item id remap * formatting
This commit is contained in:
BIN
assets/viaversion/data/mappings-1.21.7to1.21.9.nbt
Normal file
BIN
assets/viaversion/data/mappings-1.21.7to1.21.9.nbt
Normal file
Binary file not shown.
BIN
assets/viaversion/data/mappings-1.21.9to1.21.11.nbt
Normal file
BIN
assets/viaversion/data/mappings-1.21.9to1.21.11.nbt
Normal file
Binary file not shown.
210
pumpkin-data/build/block_state_remap.rs
Normal file
210
pumpkin-data/build/block_state_remap.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use std::{fs, io::Cursor};
|
||||
|
||||
use pumpkin_nbt::{Nbt, compound::NbtCompound, deserializer::NbtReadHelper};
|
||||
|
||||
struct ParsedMappings {
|
||||
mapped_size: usize,
|
||||
forward: Vec<i32>,
|
||||
}
|
||||
|
||||
fn parse_mapping_file(path: &str) -> ParsedMappings {
|
||||
let bytes = fs::read(path).unwrap_or_else(|_| panic!("Failed to read {path}"));
|
||||
let mut reader = NbtReadHelper::new(Cursor::new(bytes));
|
||||
let nbt = Nbt::read(&mut reader).unwrap_or_else(|_| panic!("Failed to parse NBT at {path}"));
|
||||
|
||||
let blockstates = nbt
|
||||
.root_tag
|
||||
.get_compound("blockstates")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates` compound in {path}"));
|
||||
|
||||
parse_blockstate_mappings(blockstates, path)
|
||||
}
|
||||
|
||||
fn parse_blockstate_mappings(blockstates: &NbtCompound, path: &str) -> ParsedMappings {
|
||||
let mapped_size = blockstates
|
||||
.get_int("mappedSize")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.mappedSize` in {path}"));
|
||||
let strategy = blockstates
|
||||
.get_byte("id")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.id` in {path}"));
|
||||
|
||||
let forward = match strategy {
|
||||
// Direct
|
||||
0 => blockstates
|
||||
.get_int_array("val")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.val` for direct mapping in {path}"))
|
||||
.to_vec(),
|
||||
// Shifts
|
||||
1 => {
|
||||
let shifts_at = blockstates
|
||||
.get_int_array("at")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.at` for shift mapping in {path}"));
|
||||
let shifts_to = blockstates
|
||||
.get_int_array("to")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.to` for shift mapping in {path}"));
|
||||
let size = blockstates
|
||||
.get_int("size")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.size` for shift mapping in {path}"))
|
||||
as usize;
|
||||
|
||||
assert_eq!(
|
||||
shifts_at.len(),
|
||||
shifts_to.len(),
|
||||
"Shift mapping length mismatch in {path}"
|
||||
);
|
||||
|
||||
let mut mappings = vec![-1; size];
|
||||
|
||||
if !shifts_at.is_empty() && shifts_at[0] != 0 {
|
||||
for id in 0..shifts_at[0] {
|
||||
mappings[id as usize] = id;
|
||||
}
|
||||
}
|
||||
|
||||
for (index, from) in shifts_at.iter().enumerate() {
|
||||
let to = if index + 1 == shifts_at.len() {
|
||||
size as i32
|
||||
} else {
|
||||
shifts_at[index + 1]
|
||||
};
|
||||
let mut mapped_id = shifts_to[index];
|
||||
for id in *from..to {
|
||||
mappings[id as usize] = mapped_id;
|
||||
mapped_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
mappings
|
||||
}
|
||||
// Changes
|
||||
2 => {
|
||||
let changes_at = blockstates
|
||||
.get_int_array("at")
|
||||
.unwrap_or_else(|| panic!("Missing `blockstates.at` for change mapping in {path}"));
|
||||
let values = blockstates.get_int_array("val").unwrap_or_else(|| {
|
||||
panic!("Missing `blockstates.val` for change mapping in {path}")
|
||||
});
|
||||
let size = blockstates.get_int("size").unwrap_or_else(|| {
|
||||
panic!("Missing `blockstates.size` for change mapping in {path}")
|
||||
}) as usize;
|
||||
let fill_between = blockstates.get("nofill").is_none();
|
||||
|
||||
assert_eq!(
|
||||
changes_at.len(),
|
||||
values.len(),
|
||||
"Change mapping length mismatch in {path}"
|
||||
);
|
||||
|
||||
let mut mappings = vec![-1; size];
|
||||
let mut next_unhandled_id = 0;
|
||||
|
||||
for (index, changed_id) in changes_at.iter().enumerate() {
|
||||
if fill_between {
|
||||
for id in next_unhandled_id..*changed_id {
|
||||
mappings[id as usize] = id;
|
||||
}
|
||||
next_unhandled_id = changed_id + 1;
|
||||
}
|
||||
mappings[*changed_id as usize] = values[index];
|
||||
}
|
||||
|
||||
mappings
|
||||
}
|
||||
// Identity
|
||||
3 => {
|
||||
let size = blockstates.get_int("size").unwrap_or_else(|| {
|
||||
panic!("Missing `blockstates.size` for identity mapping in {path}")
|
||||
}) as usize;
|
||||
(0..size as i32).collect::<Vec<_>>()
|
||||
}
|
||||
_ => panic!("Unknown blockstate mapping strategy {strategy} in {path}"),
|
||||
};
|
||||
|
||||
ParsedMappings {
|
||||
mapped_size: mapped_size as usize,
|
||||
forward,
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_to_u16(forward: &[i32], mapped_size: usize, name: &str) -> Vec<u16> {
|
||||
let mut inverse = vec![0u16; mapped_size];
|
||||
let mut seen = vec![false; mapped_size];
|
||||
|
||||
for (old_id, mapped_id) in forward.iter().enumerate() {
|
||||
let Ok(mapped_id) = usize::try_from(*mapped_id) else {
|
||||
continue;
|
||||
};
|
||||
if mapped_id >= mapped_size || seen[mapped_id] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_u16 = u16::try_from(old_id)
|
||||
.unwrap_or_else(|_| panic!("{name}: id {old_id} does not fit in u16"));
|
||||
inverse[mapped_id] = old_u16;
|
||||
seen[mapped_id] = true;
|
||||
}
|
||||
|
||||
inverse
|
||||
}
|
||||
|
||||
fn compose(first: &[u16], second: &[u16]) -> Vec<u16> {
|
||||
first
|
||||
.iter()
|
||||
.map(|id| second.get(usize::from(*id)).copied().unwrap_or(0))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build() -> TokenStream {
|
||||
println!("cargo:rerun-if-changed=../assets/viaversion/data/mappings-1.21.7to1.21.9.nbt");
|
||||
println!("cargo:rerun-if-changed=../assets/viaversion/data/mappings-1.21.9to1.21.11.nbt");
|
||||
|
||||
let mappings_7_to_9 =
|
||||
parse_mapping_file("../assets/viaversion/data/mappings-1.21.7to1.21.9.nbt");
|
||||
let mappings_9_to_11 =
|
||||
parse_mapping_file("../assets/viaversion/data/mappings-1.21.9to1.21.11.nbt");
|
||||
|
||||
let remap_9_to_7 = invert_to_u16(
|
||||
&mappings_7_to_9.forward,
|
||||
mappings_7_to_9.mapped_size,
|
||||
"1.21.9->1.21.7",
|
||||
);
|
||||
let remap_11_to_9 = invert_to_u16(
|
||||
&mappings_9_to_11.forward,
|
||||
mappings_9_to_11.mapped_size,
|
||||
"1.21.11->1.21.9",
|
||||
);
|
||||
let remap_11_to_7 = compose(&remap_11_to_9, &remap_9_to_7);
|
||||
|
||||
let remap_11_to_9_tokens: Vec<Literal> = remap_11_to_9
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
let remap_11_to_7_tokens: Vec<Literal> = remap_11_to_7
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
pub static BLOCK_STATE_REMAP_1_21_11_TO_1_21_9: &[u16] = &[#(#remap_11_to_9_tokens),*];
|
||||
pub static BLOCK_STATE_REMAP_1_21_11_TO_1_21_7: &[u16] = &[#(#remap_11_to_7_tokens),*];
|
||||
|
||||
#[must_use]
|
||||
pub fn remap_block_state_for_version(state_id: u16, version: MinecraftVersion) -> u16 {
|
||||
match version {
|
||||
MinecraftVersion::V_1_21_7 => BLOCK_STATE_REMAP_1_21_11_TO_1_21_7
|
||||
.get(usize::from(state_id))
|
||||
.copied()
|
||||
.unwrap_or(0),
|
||||
MinecraftVersion::V_1_21_9 => BLOCK_STATE_REMAP_1_21_11_TO_1_21_9
|
||||
.get(usize::from(state_id))
|
||||
.copied()
|
||||
.unwrap_or(0),
|
||||
_ => state_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ mod attributes;
|
||||
mod biome;
|
||||
mod bitsets;
|
||||
mod block;
|
||||
mod block_state_remap;
|
||||
mod chunk_gen_settings;
|
||||
mod chunk_status;
|
||||
mod composter_increase_chance;
|
||||
@@ -30,6 +31,7 @@ mod fuels;
|
||||
mod game_event;
|
||||
mod game_rules;
|
||||
mod item;
|
||||
mod item_id_remap;
|
||||
mod jukebox_song;
|
||||
pub mod loot;
|
||||
mod message_type;
|
||||
@@ -86,7 +88,9 @@ pub fn main() {
|
||||
(message_type::build, "message_type.rs"),
|
||||
(spawn_egg::build, "spawn_egg.rs"),
|
||||
(block::build, "block.rs"),
|
||||
(block_state_remap::build, "block_state_remap.rs"),
|
||||
(item::build, "item.rs"),
|
||||
(item_id_remap::build, "item_id_remap.rs"),
|
||||
(structures::build, "structures.rs"),
|
||||
(chunk_gen_settings::build, "chunk_gen_settings.rs"),
|
||||
(fluid::build, "fluid.rs"),
|
||||
|
||||
266
pumpkin-data/build/item_id_remap.rs
Normal file
266
pumpkin-data/build/item_id_remap.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use std::{fs, io::Cursor};
|
||||
|
||||
use pumpkin_nbt::{Nbt, compound::NbtCompound, deserializer::NbtReadHelper};
|
||||
|
||||
struct ParsedMappings {
|
||||
mapped_size: usize,
|
||||
forward: Vec<i32>,
|
||||
}
|
||||
|
||||
fn parse_mapping_file(path: &str, section: &str) -> ParsedMappings {
|
||||
let bytes = fs::read(path).unwrap_or_else(|_| panic!("Failed to read {path}"));
|
||||
let mut reader = NbtReadHelper::new(Cursor::new(bytes));
|
||||
let nbt = Nbt::read(&mut reader).unwrap_or_else(|_| panic!("Failed to parse NBT at {path}"));
|
||||
|
||||
let mappings = nbt
|
||||
.root_tag
|
||||
.get_compound(section)
|
||||
.unwrap_or_else(|| panic!("Missing `{section}` compound in {path}"));
|
||||
|
||||
parse_mappings(mappings, path, section)
|
||||
}
|
||||
|
||||
fn parse_mappings(mappings: &NbtCompound, path: &str, section: &str) -> ParsedMappings {
|
||||
let mapped_size = mappings
|
||||
.get_int("mappedSize")
|
||||
.unwrap_or_else(|| panic!("Missing `{section}.mappedSize` in {path}"));
|
||||
let strategy = mappings
|
||||
.get_byte("id")
|
||||
.unwrap_or_else(|| panic!("Missing `{section}.id` in {path}"));
|
||||
|
||||
let forward =
|
||||
match strategy {
|
||||
// Direct
|
||||
0 => mappings
|
||||
.get_int_array("val")
|
||||
.unwrap_or_else(|| panic!("Missing `{section}.val` for direct mapping in {path}"))
|
||||
.to_vec(),
|
||||
// Shifts
|
||||
1 => {
|
||||
let shifts_at = mappings.get_int_array("at").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.at` for shift mapping in {path}")
|
||||
});
|
||||
let shifts_to = mappings.get_int_array("to").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.to` for shift mapping in {path}")
|
||||
});
|
||||
let size = mappings.get_int("size").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.size` for shift mapping in {path}")
|
||||
}) as usize;
|
||||
|
||||
assert_eq!(
|
||||
shifts_at.len(),
|
||||
shifts_to.len(),
|
||||
"Shift mapping length mismatch in {path}"
|
||||
);
|
||||
|
||||
let mut result = vec![-1; size];
|
||||
|
||||
if !shifts_at.is_empty() && shifts_at[0] != 0 {
|
||||
for id in 0..shifts_at[0] {
|
||||
result[id as usize] = id;
|
||||
}
|
||||
}
|
||||
|
||||
for (index, from) in shifts_at.iter().enumerate() {
|
||||
let to = if index + 1 == shifts_at.len() {
|
||||
size as i32
|
||||
} else {
|
||||
shifts_at[index + 1]
|
||||
};
|
||||
let mut mapped_id = shifts_to[index];
|
||||
for id in *from..to {
|
||||
result[id as usize] = mapped_id;
|
||||
mapped_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
// Changes
|
||||
2 => {
|
||||
let changes_at = mappings.get_int_array("at").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.at` for change mapping in {path}")
|
||||
});
|
||||
let values = mappings.get_int_array("val").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.val` for change mapping in {path}")
|
||||
});
|
||||
let size = mappings.get_int("size").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.size` for change mapping in {path}")
|
||||
}) as usize;
|
||||
let fill_between = mappings.get("nofill").is_none();
|
||||
|
||||
assert_eq!(
|
||||
changes_at.len(),
|
||||
values.len(),
|
||||
"Change mapping length mismatch in {path}"
|
||||
);
|
||||
|
||||
let mut result = vec![-1; size];
|
||||
let mut next_unhandled_id = 0;
|
||||
|
||||
for (index, changed_id) in changes_at.iter().enumerate() {
|
||||
if fill_between {
|
||||
for id in next_unhandled_id..*changed_id {
|
||||
result[id as usize] = id;
|
||||
}
|
||||
next_unhandled_id = changed_id + 1;
|
||||
}
|
||||
result[*changed_id as usize] = values[index];
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
// Identity
|
||||
3 => {
|
||||
let size = mappings.get_int("size").unwrap_or_else(|| {
|
||||
panic!("Missing `{section}.size` for identity mapping in {path}")
|
||||
}) as usize;
|
||||
(0..size as i32).collect::<Vec<_>>()
|
||||
}
|
||||
_ => panic!("Unknown {section} mapping strategy {strategy} in {path}"),
|
||||
};
|
||||
|
||||
ParsedMappings {
|
||||
mapped_size: mapped_size as usize,
|
||||
forward,
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_with_default_to_u16(forward: &[i32], mapped_size: usize, name: &str) -> Vec<u16> {
|
||||
let mut inverse = vec![0u16; mapped_size];
|
||||
let mut seen = vec![false; mapped_size];
|
||||
|
||||
for (old_id, mapped_id) in forward.iter().enumerate() {
|
||||
let Ok(mapped_id) = usize::try_from(*mapped_id) else {
|
||||
continue;
|
||||
};
|
||||
if mapped_id >= mapped_size || seen[mapped_id] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_u16 = u16::try_from(old_id)
|
||||
.unwrap_or_else(|_| panic!("{name}: id {old_id} does not fit in u16"));
|
||||
inverse[mapped_id] = old_u16;
|
||||
seen[mapped_id] = true;
|
||||
}
|
||||
|
||||
for (mapped_id, mapped_to) in inverse.iter_mut().enumerate() {
|
||||
if !seen[mapped_id] {
|
||||
*mapped_to = u16::try_from(mapped_id)
|
||||
.unwrap_or_else(|_| panic!("{name}: id {mapped_id} does not fit in u16"));
|
||||
}
|
||||
}
|
||||
|
||||
inverse
|
||||
}
|
||||
|
||||
fn forward_with_default_to_u16(forward: &[i32], name: &str) -> Vec<u16> {
|
||||
forward
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(old_id, mapped_id)| {
|
||||
let mapped = usize::try_from(*mapped_id).ok().unwrap_or(old_id);
|
||||
u16::try_from(mapped).unwrap_or_else(|_| {
|
||||
u16::try_from(old_id).unwrap_or_else(|_| {
|
||||
panic!("{name}: mapped id {mapped} and fallback id {old_id} do not fit in u16")
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn compose_with_default(first: &[u16], second: &[u16]) -> Vec<u16> {
|
||||
first
|
||||
.iter()
|
||||
.map(|id| second.get(usize::from(*id)).copied().unwrap_or(*id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build() -> TokenStream {
|
||||
println!("cargo:rerun-if-changed=../assets/viaversion/data/mappings-1.21.7to1.21.9.nbt");
|
||||
println!("cargo:rerun-if-changed=../assets/viaversion/data/mappings-1.21.9to1.21.11.nbt");
|
||||
|
||||
let mappings_7_to_9 = parse_mapping_file(
|
||||
"../assets/viaversion/data/mappings-1.21.7to1.21.9.nbt",
|
||||
"items",
|
||||
);
|
||||
let mappings_9_to_11 = parse_mapping_file(
|
||||
"../assets/viaversion/data/mappings-1.21.9to1.21.11.nbt",
|
||||
"items",
|
||||
);
|
||||
|
||||
let remap_9_to_7 = invert_with_default_to_u16(
|
||||
&mappings_7_to_9.forward,
|
||||
mappings_7_to_9.mapped_size,
|
||||
"1.21.9->1.21.7 items",
|
||||
);
|
||||
let remap_7_to_9 =
|
||||
forward_with_default_to_u16(&mappings_7_to_9.forward, "1.21.7->1.21.9 items");
|
||||
let remap_11_to_9 = invert_with_default_to_u16(
|
||||
&mappings_9_to_11.forward,
|
||||
mappings_9_to_11.mapped_size,
|
||||
"1.21.11->1.21.9 items",
|
||||
);
|
||||
let remap_9_to_11 =
|
||||
forward_with_default_to_u16(&mappings_9_to_11.forward, "1.21.9->1.21.11 items");
|
||||
let remap_11_to_7 = compose_with_default(&remap_11_to_9, &remap_9_to_7);
|
||||
let remap_7_to_11 = compose_with_default(&remap_7_to_9, &remap_9_to_11);
|
||||
|
||||
let remap_11_to_9_tokens: Vec<Literal> = remap_11_to_9
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
let remap_9_to_11_tokens: Vec<Literal> = remap_9_to_11
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
let remap_11_to_7_tokens: Vec<Literal> = remap_11_to_7
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
let remap_7_to_11_tokens: Vec<Literal> = remap_7_to_11
|
||||
.into_iter()
|
||||
.map(Literal::u16_unsuffixed)
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
pub static ITEM_ID_REMAP_1_21_11_TO_1_21_9: &[u16] = &[#(#remap_11_to_9_tokens),*];
|
||||
pub static ITEM_ID_REMAP_1_21_11_TO_1_21_7: &[u16] = &[#(#remap_11_to_7_tokens),*];
|
||||
pub static ITEM_ID_REMAP_1_21_9_TO_1_21_11: &[u16] = &[#(#remap_9_to_11_tokens),*];
|
||||
pub static ITEM_ID_REMAP_1_21_7_TO_1_21_11: &[u16] = &[#(#remap_7_to_11_tokens),*];
|
||||
|
||||
#[must_use]
|
||||
pub fn remap_item_id_for_version(item_id: u16, version: MinecraftVersion) -> u16 {
|
||||
match version {
|
||||
MinecraftVersion::V_1_21_7 => ITEM_ID_REMAP_1_21_11_TO_1_21_7
|
||||
.get(usize::from(item_id))
|
||||
.copied()
|
||||
.unwrap_or(item_id),
|
||||
MinecraftVersion::V_1_21_9 => ITEM_ID_REMAP_1_21_11_TO_1_21_9
|
||||
.get(usize::from(item_id))
|
||||
.copied()
|
||||
.unwrap_or(item_id),
|
||||
_ => item_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn remap_item_id_from_version(item_id: u16, version: MinecraftVersion) -> u16 {
|
||||
match version {
|
||||
MinecraftVersion::V_1_21_7 => ITEM_ID_REMAP_1_21_7_TO_1_21_11
|
||||
.get(usize::from(item_id))
|
||||
.copied()
|
||||
.unwrap_or(item_id),
|
||||
MinecraftVersion::V_1_21_9 => ITEM_ID_REMAP_1_21_9_TO_1_21_11
|
||||
.get(usize::from(item_id))
|
||||
.copied()
|
||||
.unwrap_or(item_id),
|
||||
_ => item_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
impl TrackedId {
|
||||
pub fn get(&self, version: &MinecraftVersion) -> u8 {
|
||||
match version {
|
||||
MinecraftVersion::V_1_21_7 => self.v1_21_7,
|
||||
MinecraftVersion::V_1_21_7 | MinecraftVersion::V_1_21_9 => self.v1_21_7,
|
||||
_ => self.latest,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,14 @@ pub mod fluid;
|
||||
#[path = "generated/block.rs"]
|
||||
pub mod block_properties;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/block_state_remap.rs"]
|
||||
pub mod block_state_remap;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/item_id_remap.rs"]
|
||||
pub mod item_id_remap;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/tag.rs"]
|
||||
pub mod tag;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::VarInt;
|
||||
use crate::codec::data_component::{deserialize, serialize};
|
||||
use crate::ser::{WritingError, serializer};
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_id_remap::{remap_item_id_for_version, remap_item_id_from_version};
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{
|
||||
@@ -12,6 +15,53 @@ use std::borrow::Cow;
|
||||
|
||||
pub struct ItemStackSerializer<'a>(pub Cow<'a, ItemStack>);
|
||||
|
||||
fn item_component_counts(stack: &ItemStack) -> (u8, u8) {
|
||||
let mut to_add = 0u8;
|
||||
let mut to_remove = 0u8;
|
||||
|
||||
for (_id, data) in &stack.patch {
|
||||
if data.is_none() {
|
||||
to_remove += 1;
|
||||
} else {
|
||||
to_add += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(to_add, to_remove)
|
||||
}
|
||||
|
||||
fn serialize_item_stack_with_id<S: Serializer>(
|
||||
stack: &ItemStack,
|
||||
item_id: u16,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
if stack.is_empty() {
|
||||
VarInt(0).serialize(serializer)
|
||||
} else {
|
||||
let (to_add, to_remove) = item_component_counts(stack);
|
||||
let mut seq = serializer.serialize_struct("", 0)?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(stack.item_count))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(item_id))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(to_add))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(to_remove))?;
|
||||
|
||||
for (id, data) in &stack.patch {
|
||||
if let Some(data) = data {
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(id.to_id()))?;
|
||||
serialize(*id, data.as_ref(), &mut seq)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (id, data) in &stack.patch {
|
||||
if data.is_none() {
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(id.to_id()))?;
|
||||
}
|
||||
}
|
||||
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ItemStackSerializer<'static> {
|
||||
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct Visitor;
|
||||
@@ -93,48 +143,37 @@ impl<'de> Deserialize<'de> for ItemStackSerializer<'static> {
|
||||
|
||||
impl Serialize for ItemStackSerializer<'_> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
if self.0.is_empty() {
|
||||
VarInt(0).serialize(serializer)
|
||||
} else {
|
||||
let calc = || {
|
||||
let mut to_add = 0u8;
|
||||
let mut to_remove = 0u8;
|
||||
for (_id, data) in &self.0.patch {
|
||||
if data.is_none() {
|
||||
to_remove += 1;
|
||||
} else {
|
||||
to_add += 1;
|
||||
}
|
||||
}
|
||||
(to_add, to_remove)
|
||||
};
|
||||
let (to_add, to_remove) = calc();
|
||||
let mut seq = serializer.serialize_struct("", 0)?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(self.0.item_count))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(self.0.item.id))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(to_add))?;
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(to_remove))?;
|
||||
for (id, data) in &self.0.patch {
|
||||
if let Some(data) = data {
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(id.to_id()))?;
|
||||
serialize(*id, data.as_ref(), &mut seq)?;
|
||||
}
|
||||
}
|
||||
for (id, data) in &self.0.patch {
|
||||
if data.is_none() {
|
||||
seq.serialize_field::<VarInt>("", &VarInt::from(id.to_id()))?;
|
||||
}
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
serialize_item_stack_with_id(self.0.as_ref(), self.0.item.id, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemStackSerializer<'_> {
|
||||
pub fn write_with_version(
|
||||
&self,
|
||||
write: impl std::io::Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let remapped_item_id = remap_item_id_for_version(self.0.item.id, *version);
|
||||
let mut network_serializer = serializer::Serializer::new(write);
|
||||
serialize_item_stack_with_id(self.0.as_ref(), remapped_item_id, &mut network_serializer)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_stack(self) -> ItemStack {
|
||||
self.0.into_owned()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_stack_for_version(self, version: &MinecraftVersion) -> ItemStack {
|
||||
let mut stack = self.0.into_owned();
|
||||
if stack.is_empty() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
let remapped_item_id = remap_item_id_from_version(stack.item.id, *version);
|
||||
stack.item = Item::from_id(remapped_item_id).unwrap_or(&Item::AIR);
|
||||
stack
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ItemStack> for ItemStackSerializer<'_> {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::packet::clientbound::PLAY_BLOCK_UPDATE;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::VarInt;
|
||||
use crate::{
|
||||
ClientPacket, VarInt,
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
|
||||
/// Updates a single block state at a specific location in the world.
|
||||
///
|
||||
/// This is the most common way to sync world changes to the client, such as
|
||||
/// when a player places a block, a fluid flows, or a redstone component toggles.
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_BLOCK_UPDATE)]
|
||||
pub struct CBlockUpdate {
|
||||
/// The world coordinates of the block being updated.
|
||||
@@ -25,3 +29,21 @@ impl CBlockUpdate {
|
||||
Self { location, state_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CBlockUpdate {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
write.write_block_pos(&self.location)?;
|
||||
|
||||
let remapped_state = u16::try_from(self.state_id.0).map_or(self.state_id.0, |state_id| {
|
||||
i32::from(remap_block_state_for_version(state_id, *version))
|
||||
});
|
||||
write.write_var_int(&VarInt(remapped_state))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::WritingError;
|
||||
use crate::codec::bit_set::BitSet;
|
||||
use crate::{ClientPacket, VarInt, ser::NetworkWriteExt};
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_nbt::END_ID;
|
||||
@@ -23,7 +24,7 @@ impl ClientPacket for CChunkData<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
_version: &MinecraftVersion,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
|
||||
@@ -57,7 +58,34 @@ impl ClientPacket for CChunkData<'_> {
|
||||
let non_empty_block_count = block_palette.non_air_block_count() as i16;
|
||||
blocks_and_biomes_buf.write_i16_be(non_empty_block_count)?;
|
||||
|
||||
let block_network = block_palette.convert_network();
|
||||
let mut block_network = block_palette.convert_network();
|
||||
match &mut block_network.palette {
|
||||
NetworkPalette::Single(registry_id) => {
|
||||
*registry_id = remap_block_state_for_version(*registry_id, *version);
|
||||
}
|
||||
NetworkPalette::Indirect(palette) => {
|
||||
for registry_id in palette.iter_mut() {
|
||||
*registry_id = remap_block_state_for_version(*registry_id, *version);
|
||||
}
|
||||
}
|
||||
NetworkPalette::Direct => {
|
||||
let bits_per_entry = usize::from(block_network.bits_per_entry);
|
||||
let values_per_i64 = 64 / bits_per_entry;
|
||||
let id_mask = (1u64 << bits_per_entry) - 1;
|
||||
|
||||
for packed_word in &mut block_network.packed_data {
|
||||
let mut remapped_word = 0u64;
|
||||
let packed_word_u64 = *packed_word as u64;
|
||||
for index in 0..values_per_i64 {
|
||||
let shift = index * bits_per_entry;
|
||||
let state_id = ((packed_word_u64 >> shift) & id_mask) as u16;
|
||||
let remapped_id = remap_block_state_for_version(state_id, *version);
|
||||
remapped_word |= u64::from(remapped_id) << shift;
|
||||
}
|
||||
*packed_word = remapped_word as i64;
|
||||
}
|
||||
}
|
||||
}
|
||||
blocks_and_biomes_buf.write_u8(block_network.bits_per_entry)?;
|
||||
|
||||
match block_network.palette {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use pumpkin_data::{
|
||||
meta_data_type::MetaDataType, packet::clientbound::PLAY_SET_ENTITY_DATA,
|
||||
tracked_data::TrackedId,
|
||||
block_state_remap::remap_block_state_for_version, meta_data_type::MetaDataType,
|
||||
packet::clientbound::PLAY_SET_ENTITY_DATA, tracked_data::TrackedId,
|
||||
};
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
@@ -10,6 +12,36 @@ use crate::{
|
||||
ser::{NetworkWriteExt, WritingError, network_serialize_no_prefix, serializer},
|
||||
};
|
||||
|
||||
const fn remap_metadata_type_id_for_version(
|
||||
type_id: i32,
|
||||
version: pumpkin_util::version::MinecraftVersion,
|
||||
) -> Option<i32> {
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
match version {
|
||||
// 1.21.7 / 1.21.8 (protocol 772) has a different metadata type table than 1.21.11.
|
||||
MinecraftVersion::V_1_21_7 => match type_id {
|
||||
// `compound_tag` exists at 16 in 1.21.7, so later ids are shifted.
|
||||
16..=27 => Some(type_id + 1),
|
||||
// 1.21.7 has no copper/weathering/profile/arm metadata types.
|
||||
28 | 33 | 34 | 37 | 38 => None,
|
||||
// `vector_3f` and `quaternion_f` are lower in 1.21.7.
|
||||
35 => Some(33),
|
||||
36 => Some(34),
|
||||
_ => Some(type_id),
|
||||
},
|
||||
// 1.21.9 / 1.21.10 (protocol 773) is close to latest but lacks some tail variants.
|
||||
MinecraftVersion::V_1_21_9 => match type_id {
|
||||
// Everything after that is shifted by one.
|
||||
29..=37 => Some(type_id - 1),
|
||||
// 1.21.11-only variants.
|
||||
28 | 38 => None,
|
||||
_ => Some(type_id),
|
||||
},
|
||||
_ => Some(type_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the "Data Tracker" values for an entity.
|
||||
///
|
||||
/// Entity Metadata (or `DataWatchers`) controls persistent visual states that
|
||||
@@ -65,8 +97,34 @@ impl<T> Metadata<T> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(remapped_type_id) = remap_metadata_type_id_for_version(self.r#type.0, *version)
|
||||
else {
|
||||
// Metadata type does not exist in this protocol version.
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
writer.write_u8(resolved_index)?;
|
||||
self.r#type.encode(&mut writer)?;
|
||||
writer.write_var_int(&VarInt(remapped_type_id))?;
|
||||
|
||||
if self.r#type.0 == MetaDataType::BlockState as i32 {
|
||||
let mut serialized_value = Vec::new();
|
||||
{
|
||||
let mut serializer = serializer::Serializer::new(&mut serialized_value);
|
||||
self.value
|
||||
.serialize(&mut serializer)
|
||||
.map_err(|e| WritingError::Serde(e.to_string()))?;
|
||||
};
|
||||
|
||||
let mut cursor = Cursor::new(serialized_value);
|
||||
let decoded_state = VarInt::decode(&mut cursor).map_err(|e| {
|
||||
WritingError::Message(format!("Failed to decode block state metadata: {e}"))
|
||||
})?;
|
||||
let remapped_state = u16::try_from(decoded_state.0).map_or(decoded_state, |state_id| {
|
||||
VarInt(i32::from(remap_block_state_for_version(state_id, *version)))
|
||||
});
|
||||
writer.write_var_int(&remapped_state)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut serializer = serializer::Serializer::new(&mut writer);
|
||||
self.value
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::packet::clientbound::PLAY_LEVEL_EVENT;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
/// Triggers a specific sound or particle effect at a world location.
|
||||
///
|
||||
/// This packet handles a wide variety of "world-level" events, such as
|
||||
/// block breaking particles, firework explosions, or ambient sounds
|
||||
/// like doors opening and portals humming.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[java_packet(PLAY_LEVEL_EVENT)]
|
||||
pub struct CLevelEvent {
|
||||
/// The ID of the event to trigger.
|
||||
@@ -41,3 +48,27 @@ impl CLevelEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CLevelEvent {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
write.write_i32_be(self.event)?;
|
||||
write.write_block_pos(&self.location)?;
|
||||
|
||||
let data = if self.event == WorldEvent::BlockBroken as i32 {
|
||||
u16::try_from(self.data).map_or(self.data, |state_id| {
|
||||
i32::from(remap_block_state_for_version(state_id, *version))
|
||||
})
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
write.write_i32_be(data)?;
|
||||
write.write_bool(self.disable_relative_volume)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::packet::clientbound::PLAY_SECTION_BLOCKS_UPDATE;
|
||||
use pumpkin_util::math::{
|
||||
position::{BlockPos, chunk_section_from_pos, pack_local_chunk_section},
|
||||
vector3::{self},
|
||||
};
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::{Serialize, ser::SerializeTuple};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::codec::{var_int::VarInt, var_long::VarLong};
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
codec::{var_int::VarInt, var_long::VarLong},
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
|
||||
/// Updates multiple blocks within a single 16x16x16 chunk section.
|
||||
///
|
||||
@@ -45,18 +51,25 @@ impl CMultiBlockUpdate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for CMultiBlockUpdate {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut tuple = serializer.serialize_tuple(2 + self.updates.len())?;
|
||||
|
||||
tuple.serialize_element(&self.chunk_section)?;
|
||||
tuple.serialize_element(&VarInt(self.updates.len() as i32))?;
|
||||
impl ClientPacket for CMultiBlockUpdate {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
write.write_i64_be(self.chunk_section)?;
|
||||
write.write_var_int(&VarInt(self.updates.len() as i32))?;
|
||||
|
||||
for update in &self.updates {
|
||||
tuple.serialize_element(update)?;
|
||||
let packed_update = update.0 as u64;
|
||||
let local_pos = packed_update & 0xFFF;
|
||||
let state_id = (packed_update >> 12) as u16;
|
||||
let remapped_state_id = remap_block_state_for_version(state_id, *version);
|
||||
let remapped_packed = (u64::from(remapped_state_id) << 12) | local_pos;
|
||||
write.write_var_long(&VarLong(remapped_packed as i64))?;
|
||||
}
|
||||
|
||||
tuple.end()
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::VarInt;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use crate::{ClientPacket, WritingError, ser::NetworkWriteExt};
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_CONTENT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_CONTAINER_SET_CONTENT)]
|
||||
pub struct CSetContainerContent<'a> {
|
||||
pub window_id: VarInt,
|
||||
@@ -30,3 +32,29 @@ impl<'a> CSetContainerContent<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CSetContainerContent<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
|
||||
write.write_var_int(&self.window_id)?;
|
||||
write.write_var_int(&self.state_id)?;
|
||||
let slot_count = i32::try_from(self.slot_data.len()).map_err(|_| {
|
||||
WritingError::Message(format!(
|
||||
"{} slot entries do not fit in VarInt",
|
||||
self.slot_data.len()
|
||||
))
|
||||
})?;
|
||||
write.write_var_int(&VarInt(slot_count))?;
|
||||
for stack in self.slot_data {
|
||||
stack.write_with_version(&mut write, version)?;
|
||||
}
|
||||
self.carried_item.write_with_version(&mut write, version)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::VarInt;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use crate::{ClientPacket, WritingError, ser::NetworkWriteExt};
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_SLOT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_CONTAINER_SET_SLOT)]
|
||||
pub struct CSetContainerSlot<'a> {
|
||||
pub window_id: i8,
|
||||
@@ -30,3 +32,20 @@ impl<'a> CSetContainerSlot<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CSetContainerSlot<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
|
||||
write.write_i8(self.window_id)?;
|
||||
write.write_var_int(&self.state_id)?;
|
||||
write.write_i16_be(self.slot)?;
|
||||
self.slot_data.write_with_version(&mut write, version)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use crate::{ClientPacket, WritingError};
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_SET_CURSOR_ITEM;
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_SET_CURSOR_ITEM)]
|
||||
pub struct CSetCursorItem<'a> {
|
||||
pub stack: &'a ItemStackSerializer<'a>,
|
||||
@@ -16,3 +18,13 @@ impl<'a> CSetCursorItem<'a> {
|
||||
Self { stack }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CSetCursorItem<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
self.stack.write_with_version(write, version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{
|
||||
WritingError,
|
||||
ser::{NetworkWriteExt, serializer::Serializer},
|
||||
};
|
||||
use crate::{WritingError, ser::NetworkWriteExt};
|
||||
use pumpkin_data::packet::clientbound::PLAY_SET_EQUIPMENT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
@@ -37,7 +33,7 @@ impl ClientPacket for CSetEquipment {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
_version: &MinecraftVersion,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
|
||||
@@ -50,11 +46,7 @@ impl ClientPacket for CSetEquipment {
|
||||
} else {
|
||||
write.write_i8(*slot | -128)?;
|
||||
}
|
||||
let mut serializer = Serializer::new(&mut write);
|
||||
equipment
|
||||
.1
|
||||
.serialize(&mut serializer)
|
||||
.expect("Could not serialize `EquipmentSlot`");
|
||||
equipment.1.write_with_version(&mut write, version)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::VarInt;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use crate::{ClientPacket, WritingError, ser::NetworkWriteExt};
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_SET_PLAYER_INVENTORY;
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_SET_PLAYER_INVENTORY)]
|
||||
pub struct CSetPlayerInventory<'a> {
|
||||
pub slot: VarInt,
|
||||
@@ -18,3 +20,15 @@ impl<'a> CSetPlayerInventory<'a> {
|
||||
Self { slot, item }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CSetPlayerInventory<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
write.write_var_int(&self.slot)?;
|
||||
self.item.write_with_version(write, version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::io::Write;
|
||||
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::packet::clientbound::PLAY_ADD_ENTITY;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
|
||||
@@ -10,6 +12,44 @@ use crate::{
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
|
||||
const fn remap_entity_type_for_version(type_id: i32, version: MinecraftVersion) -> i32 {
|
||||
use MinecraftVersion::{V_1_21_7, V_1_21_9};
|
||||
|
||||
match version {
|
||||
// ViaVersion mappings-1.21.9to1.21.11.nbt (inverse, latest -> 1.21.9)
|
||||
//
|
||||
// mapped by shifts:
|
||||
// 0..=19 => +0
|
||||
// 21..=87 => -1
|
||||
// 89..=96 => -2
|
||||
// 98..=151 => -3
|
||||
// 153..=156 => -4
|
||||
// unsupported ids (20, 88, 97, 152) fall back to identity.
|
||||
// getNewIdOrDefault(id, id) behavior for unmapped ids.
|
||||
V_1_21_9 => match type_id {
|
||||
21..=87 => type_id - 1,
|
||||
89..=96 => type_id - 2,
|
||||
98..=151 => type_id - 3,
|
||||
153..=156 => type_id - 4,
|
||||
_ => type_id,
|
||||
},
|
||||
// ViaVersion mappings-1.21.7to1.21.9.nbt + mappings-1.21.9to1.21.11.nbt
|
||||
// (inverse composition, latest -> 1.21.7/1.21.8 protocol 772).
|
||||
//
|
||||
// unsupported ids (20, 28, 83, 88, 97, 152) keep identity, same rationale as above.
|
||||
V_1_21_7 => match type_id {
|
||||
21..=27 => type_id - 1,
|
||||
29..=82 => type_id - 2,
|
||||
84..=87 => type_id - 3,
|
||||
89..=96 => type_id - 4,
|
||||
98..=151 => type_id - 5,
|
||||
153..=156 => type_id - 6,
|
||||
_ => type_id,
|
||||
},
|
||||
_ => type_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[java_packet(PLAY_ADD_ENTITY)]
|
||||
pub struct CSpawnEntity {
|
||||
pub entity_id: VarInt,
|
||||
@@ -61,7 +101,8 @@ impl ClientPacket for CSpawnEntity {
|
||||
|
||||
write.write_var_int(&self.entity_id)?;
|
||||
write.write_uuid(&self.entity_uuid)?;
|
||||
write.write_var_int(&self.r#type)?;
|
||||
let remapped_type = VarInt(remap_entity_type_for_version(self.r#type.0, *version));
|
||||
write.write_var_int(&remapped_type)?;
|
||||
|
||||
write.write_f64_be(self.position.x)?;
|
||||
write.write_f64_be(self.position.y)?;
|
||||
@@ -75,7 +116,14 @@ impl ClientPacket for CSpawnEntity {
|
||||
write.write_u8(self.yaw)?;
|
||||
write.write_u8(self.head_yaw)?;
|
||||
|
||||
write.write_var_int(&self.data)?;
|
||||
let data = if self.r#type.0 == i32::from(EntityType::FALLING_BLOCK.id) {
|
||||
u16::try_from(self.data.0).map_or(self.data, |state_id| {
|
||||
VarInt(i32::from(remap_block_state_for_version(state_id, *version)))
|
||||
})
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
write.write_var_int(&data)?;
|
||||
|
||||
if version <= &MinecraftVersion::V_1_21_7 {
|
||||
write.write_i16_be(self.velocity.0.x as i16)?;
|
||||
@@ -86,3 +134,153 @@ impl ClientPacket for CSpawnEntity {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::remap_entity_type_for_version;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
#[test]
|
||||
fn remaps_entity_types_for_1_21_9_like_viaversion() {
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(0, MinecraftVersion::V_1_21_9),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(19, MinecraftVersion::V_1_21_9),
|
||||
19
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(20, MinecraftVersion::V_1_21_9),
|
||||
20
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(21, MinecraftVersion::V_1_21_9),
|
||||
20
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(87, MinecraftVersion::V_1_21_9),
|
||||
86
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(88, MinecraftVersion::V_1_21_9),
|
||||
88
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(89, MinecraftVersion::V_1_21_9),
|
||||
87
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(96, MinecraftVersion::V_1_21_9),
|
||||
94
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(97, MinecraftVersion::V_1_21_9),
|
||||
97
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(98, MinecraftVersion::V_1_21_9),
|
||||
95
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(151, MinecraftVersion::V_1_21_9),
|
||||
148
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(152, MinecraftVersion::V_1_21_9),
|
||||
152
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(153, MinecraftVersion::V_1_21_9),
|
||||
149
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(156, MinecraftVersion::V_1_21_9),
|
||||
152
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remaps_entity_types_for_1_21_7_like_viaversion() {
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(0, MinecraftVersion::V_1_21_7),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(19, MinecraftVersion::V_1_21_7),
|
||||
19
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(20, MinecraftVersion::V_1_21_7),
|
||||
20
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(21, MinecraftVersion::V_1_21_7),
|
||||
20
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(27, MinecraftVersion::V_1_21_7),
|
||||
26
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(28, MinecraftVersion::V_1_21_7),
|
||||
28
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(29, MinecraftVersion::V_1_21_7),
|
||||
27
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(82, MinecraftVersion::V_1_21_7),
|
||||
80
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(83, MinecraftVersion::V_1_21_7),
|
||||
83
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(84, MinecraftVersion::V_1_21_7),
|
||||
81
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(87, MinecraftVersion::V_1_21_7),
|
||||
84
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(88, MinecraftVersion::V_1_21_7),
|
||||
88
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(89, MinecraftVersion::V_1_21_7),
|
||||
85
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(96, MinecraftVersion::V_1_21_7),
|
||||
92
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(97, MinecraftVersion::V_1_21_7),
|
||||
97
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(98, MinecraftVersion::V_1_21_7),
|
||||
93
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(151, MinecraftVersion::V_1_21_7),
|
||||
146
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(152, MinecraftVersion::V_1_21_7),
|
||||
152
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(153, MinecraftVersion::V_1_21_7),
|
||||
147
|
||||
);
|
||||
assert_eq!(
|
||||
remap_entity_type_for_version(156, MinecraftVersion::V_1_21_7),
|
||||
150
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
use pumpkin_data::block_state_remap::remap_block_state_for_version;
|
||||
use pumpkin_data::packet::clientbound::PLAY_LEVEL_EVENT;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Sent by the server to trigger a specific sound or particle effect at a world location.
|
||||
///
|
||||
/// This is used for a wide variety of effects, from breaking blocks and firework
|
||||
/// explosions to splashing water or record playing.
|
||||
#[derive(Serialize)]
|
||||
#[java_packet(PLAY_LEVEL_EVENT)]
|
||||
pub struct CWorldEvent {
|
||||
/// The ID of the event to trigger (e.g., 1000 for a bow shoot, 2001 for block break).
|
||||
@@ -42,3 +49,27 @@ impl CWorldEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CWorldEvent {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
write: impl Write,
|
||||
version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
let mut write = write;
|
||||
write.write_i32_be(self.event)?;
|
||||
write.write_block_pos(&self.location)?;
|
||||
|
||||
let data = if self.event == WorldEvent::BlockBroken as i32 {
|
||||
u16::try_from(self.data).map_or(self.data, |state_id| {
|
||||
i32::from(remap_block_state_for_version(state_id, *version))
|
||||
})
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
write.write_i32_be(data)?;
|
||||
write.write_bool(self.disable_relative_volume)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1847,7 +1847,9 @@ impl JavaClient {
|
||||
}
|
||||
let is_negative = packet.slot < 0;
|
||||
let valid_slot = packet.slot >= 1 && packet.slot as usize <= 45;
|
||||
let item_stack = packet.clicked_item.to_stack();
|
||||
let item_stack = packet
|
||||
.clicked_item
|
||||
.to_stack_for_version(&self.version.load());
|
||||
let is_legal =
|
||||
item_stack.is_empty() || item_stack.item_count <= item_stack.get_max_stack_size();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user