feat: 1.21.9-1.21.11 support

more versions will come
This commit is contained in:
Alexander Medvedev 2026-01-22 17:16:37 +01:00
parent 5ed0e57843
commit 142296a9b2
42 changed files with 660 additions and 19212 deletions

View File

@ -5,7 +5,6 @@
![CI](https://github.com/Pumpkin-MC/Pumpkin/actions/workflows/rust.yml/badge.svg)
[![Discord](https://img.shields.io/discord/1268592337445978193.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/wT8XjrjKkf)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![Current version)](https://img.shields.io/badge/current_version-1.21.11-blue)
</div>

1
assets/jukebox_song.json Normal file
View File

@ -0,0 +1 @@
{"11":0,"13":1,"5":2,"blocks":3,"cat":4,"chirp":5,"creator":6,"creator_music_box":7,"far":8,"lava_chicken":9,"mall":10,"mellohi":11,"otherside":12,"pigstep":13,"precipice":14,"relic":15,"stal":16,"strad":17,"tears":18,"wait":19,"ward":20}

File diff suppressed because one or more lines are too long

View File

@ -1,276 +0,0 @@
{
"version": 774,
"serverbound": {
"handshake": [
"intention"
],
"status": [
"status_request",
"ping_request"
],
"login": [
"hello",
"key",
"custom_query_answer",
"login_acknowledged",
"cookie_response"
],
"config": [
"client_information",
"cookie_response",
"custom_payload",
"finish_configuration",
"keep_alive",
"pong",
"resource_pack",
"select_known_packs",
"custom_click_action",
"accept_code_of_conduct"
],
"play": [
"accept_teleportation",
"block_entity_tag_query",
"bundle_item_selected",
"change_difficulty",
"change_game_mode",
"chat_ack",
"chat_command",
"chat_command_signed",
"chat",
"chat_session_update",
"chunk_batch_received",
"client_command",
"client_tick_end",
"client_information",
"command_suggestion",
"configuration_acknowledged",
"container_button_click",
"container_click",
"container_close",
"container_slot_state_changed",
"cookie_response",
"custom_payload",
"debug_subscription_request",
"edit_book",
"entity_tag_query",
"interact",
"jigsaw_generate",
"keep_alive",
"lock_difficulty",
"move_player_pos",
"move_player_pos_rot",
"move_player_rot",
"move_player_status_only",
"move_vehicle",
"paddle_boat",
"pick_item_from_block",
"pick_item_from_entity",
"ping_request",
"place_recipe",
"player_abilities",
"player_action",
"player_command",
"player_input",
"player_loaded",
"pong",
"recipe_book_change_settings",
"recipe_book_seen_recipe",
"rename_item",
"resource_pack",
"seen_advancements",
"select_trade",
"set_beacon",
"set_carried_item",
"set_command_block",
"set_command_minecart",
"set_creative_mode_slot",
"set_jigsaw_block",
"set_structure_block",
"set_test_block",
"sign_update",
"swing",
"teleport_to_entity",
"test_instance_block_action",
"use_item_on",
"use_item",
"custom_click_action"
]
},
"clientbound": {
"status": [
"status_response",
"pong_response"
],
"login": [
"login_disconnect",
"hello",
"login_finished",
"login_compression",
"custom_query",
"cookie_request"
],
"config": [
"cookie_request",
"custom_payload",
"disconnect",
"finish_configuration",
"keep_alive",
"ping",
"reset_chat",
"registry_data",
"resource_pack_pop",
"resource_pack_push",
"store_cookie",
"transfer",
"update_enabled_features",
"update_tags",
"select_known_packs",
"custom_report_details",
"server_links",
"clear_dialog",
"show_dialog",
"code_of_conduct"
],
"play": [
"bundle_delimiter",
"add_entity",
"animate",
"award_stats",
"block_changed_ack",
"block_destruction",
"block_entity_data",
"block_event",
"block_update",
"boss_event",
"change_difficulty",
"chunk_batch_finished",
"chunk_batch_start",
"chunks_biomes",
"clear_titles",
"command_suggestions",
"commands",
"container_close",
"container_set_content",
"container_set_data",
"container_set_slot",
"cookie_request",
"cooldown",
"custom_chat_completions",
"custom_payload",
"damage_event",
"debug/block_value",
"debug/chunk_value",
"debug/entity_value",
"debug/event",
"debug_sample",
"delete_chat",
"disconnect",
"disguised_chat",
"entity_event",
"entity_position_sync",
"explode",
"forget_level_chunk",
"game_event",
"game_test_highlight_pos",
"mount_screen_open",
"hurt_animation",
"initialize_border",
"keep_alive",
"level_chunk_with_light",
"level_event",
"level_particles",
"light_update",
"login",
"map_item_data",
"merchant_offers",
"move_entity_pos",
"move_entity_pos_rot",
"move_minecart_along_track",
"move_entity_rot",
"move_vehicle",
"open_book",
"open_screen",
"open_sign_editor",
"ping",
"pong_response",
"place_ghost_recipe",
"player_abilities",
"player_chat",
"player_combat_end",
"player_combat_enter",
"player_combat_kill",
"player_info_remove",
"player_info_update",
"player_look_at",
"player_position",
"player_rotation",
"recipe_book_add",
"recipe_book_remove",
"recipe_book_settings",
"remove_entities",
"remove_mob_effect",
"reset_score",
"resource_pack_pop",
"resource_pack_push",
"respawn",
"rotate_head",
"section_blocks_update",
"select_advancements_tab",
"server_data",
"set_action_bar_text",
"set_border_center",
"set_border_lerp_size",
"set_border_size",
"set_border_warning_delay",
"set_border_warning_distance",
"set_camera",
"set_chunk_cache_center",
"set_chunk_cache_radius",
"set_cursor_item",
"set_default_spawn_position",
"set_display_objective",
"set_entity_data",
"set_entity_link",
"set_entity_motion",
"set_equipment",
"set_experience",
"set_health",
"set_held_slot",
"set_objective",
"set_passengers",
"set_player_inventory",
"set_player_team",
"set_score",
"set_simulation_distance",
"set_subtitle_text",
"set_time",
"set_title_text",
"set_titles_animation",
"sound_entity",
"sound",
"start_configuration",
"stop_sound",
"store_cookie",
"system_chat",
"tab_list",
"tag_query",
"take_item_entity",
"teleport_entity",
"test_instance_block_status",
"ticking_state",
"ticking_step",
"transfer",
"update_advancements",
"update_attributes",
"update_mob_effect",
"update_recipes",
"update_tags",
"projectile_power",
"custom_report_details",
"server_links",
"waypoint",
"clear_dialog",
"show_dialog"
]
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,7 @@ mod fuels;
mod game_event;
mod game_rules;
mod item;
mod jukebox_song;
pub mod loot;
mod message_type;
mod meta_data_type;
@ -71,6 +72,7 @@ pub fn main() {
(game_rules::build, "game_rules.rs"),
(registry::build, "registry.rs"),
(dimension::build, "dimension.rs"),
(jukebox_song::build, "jukebox_song.rs"),
(sound_category::build, "sound_category.rs"),
(entity_pose::build, "entity_pose.rs"),
(scoreboard_slot::build, "scoreboard_slot.rs"),

View File

@ -0,0 +1,88 @@
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::BTreeMap;
use std::fs;
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/jukebox_song.json");
let songs: BTreeMap<String, u32> = serde_json::from_str(
&fs::read_to_string("../assets/jukebox_song.json").expect("Missing jukebox_song.json"),
)
.expect("Failed to parse jukebox_song.json");
// Helper to handle numeric keys like "11" -> "Id11"
let make_variant_ident = |name: &str| {
let pascal = name.to_pascal_case();
if pascal.chars().next().is_some_and(|c| c.is_ascii_digit()) {
format_ident!("Id{}", pascal)
} else {
format_ident!("{}", pascal)
}
};
let variants = songs
.keys()
.map(|name| {
let variant_name = make_variant_ident(name);
quote! { #variant_name, }
})
.collect::<TokenStream>();
let type_from_name = songs
.keys()
.map(|name| {
let variant_name = make_variant_ident(name);
quote! { #name => Some(Self::#variant_name), }
})
.collect::<TokenStream>();
let type_to_name = songs
.keys()
.map(|name| {
let variant_name = make_variant_ident(name);
quote! { Self::#variant_name => #name, }
})
.collect::<TokenStream>();
let type_to_id = songs
.iter()
.map(|(name, id)| {
let variant_name = make_variant_ident(name);
quote! { Self::#variant_name => #id, }
})
.collect::<TokenStream>();
quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum JukeboxSong {
#variants
}
impl JukeboxSong {
#[doc = r" Returns the JukeboxSong from the string name (e.g., 'pigstep')."]
pub fn from_name(name: &str) -> Option<Self> {
match name {
#type_from_name
_ => None
}
}
#[doc = r" Returns the string name of the song."]
pub const fn to_name(&self) -> &'static str {
match self {
#type_to_name
}
}
#[doc = r" Returns the numeric ID associated with the song."]
pub const fn get_id(&self) -> u32 {
match self {
#type_to_id
}
}
}
}
}

View File

@ -7,22 +7,23 @@ use serde::Deserialize;
#[derive(Deserialize)]
pub struct Packets {
version: u32,
serverbound: BTreeMap<String, Vec<String>>,
clientbound: BTreeMap<String, Vec<String>>,
serverbound: BTreeMap<String, BTreeMap<String, i32>>,
clientbound: BTreeMap<String, BTreeMap<String, i32>>,
}
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/packets.json");
println!("cargo:rerun-if-changed=../assets/packet/1_21_11_packets.json");
let packets: Packets =
serde_json::from_str(&fs::read_to_string("../assets/packets.json").unwrap())
serde_json::from_str(&fs::read_to_string("../assets/packet/1_21_11_packets.json").unwrap())
.expect("Failed to parse packets.json");
let version = packets.version;
let serverbound_consts = parse_packets(packets.serverbound);
let clientbound_consts = parse_packets(packets.clientbound);
quote!(
/// The current Minecraft protocol version. This changes only when the protocol itself is modified.
/// The current Minecraft protocol version.
pub const CURRENT_MC_PROTOCOL: u32 = #version;
pub mod serverbound {
@ -35,20 +36,21 @@ pub(crate) fn build() -> TokenStream {
)
}
pub(crate) fn parse_packets(packets: BTreeMap<String, Vec<String>>) -> proc_macro2::TokenStream {
pub(crate) fn parse_packets(phases: BTreeMap<String, BTreeMap<String, i32>>) -> TokenStream {
let mut consts = TokenStream::new();
for packet in packets {
let phase = packet.0;
for (phase_name, packets) in phases {
for (packet_name, packet_id) in packets {
let sanitized_name = packet_name
.replace("/", "_")
.replace("-", "_")
.to_uppercase();
for (id, packet_name) in packet.1.iter().enumerate() {
let packet_id = id as i32;
let packet_name = packet_name.replace("/", "_");
let name = format!("{phase}_{packet_name}").to_uppercase();
let name = format_ident!("{}", name);
consts.extend([quote! {
pub const #name: i32 = #packet_id;
}]);
let const_name = format_ident!("{}_{}", phase_name.to_uppercase(), sanitized_name);
consts.extend(quote! {
pub const #const_name: i32 = #packet_id;
});
}
}
consts

View File

@ -5,83 +5,64 @@ use serde_json::Value;
use std::fs;
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/synced_registries.json");
println!("cargo:rerun-if-changed=../assets/registry/1_21_9_synced_registries.json");
println!("cargo:rerun-if-changed=../assets/registry/1_21_11_synced_registries.json");
let json_str = fs::read_to_string("../assets/synced_registries.json")
.expect("Failed to read synced_registries.json");
let mut data: IndexMap<String, IndexMap<String, Value>> =
serde_json::from_str(&json_str).expect("Failed to parse synced_registries.json");
let process_version = |path: &str| -> TokenStream {
let json_str =
fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read {}", path));
let mut data: IndexMap<String, IndexMap<String, Value>> =
serde_json::from_str(&json_str).expect("Failed to parse JSON");
let chat_type = if data.contains_key("minecraft:chat_type") {
data.get_mut("minecraft:chat_type")
} else {
data.get_mut("chat_type")
// Inject "raw" chat type for vanilla parity
if let Some(chat) = data.get_mut("minecraft:chat_type") {
chat.insert("raw".to_string(), serde_json::json!({
"chat": { "translation_key": "%s", "parameters": ["content"] },
"narration": { "translation_key": "%s says %s", "parameters": ["sender", "content"] }
}));
}
let reg_tokens: Vec<TokenStream> = data
.iter()
.map(|(reg_name, entries)| {
let entry_tokens: Vec<TokenStream> = entries
.iter()
.map(|(entry_name, entry_data)| {
let mut bytes = Vec::new();
pumpkin_nbt::serializer::to_bytes_unnamed(entry_data, &mut bytes).unwrap();
let byte_literal = Literal::byte_string(&bytes);
quote! {
StaticRegistryEntry {
name: #entry_name,
data: #byte_literal
}
}
})
.collect();
quote! {
StaticRegistry {
registry_id: #reg_name,
entries: &[#(#entry_tokens),*],
}
}
})
.collect();
quote! { &[#(#reg_tokens),*] }
};
if let Some(chat_type) = chat_type {
chat_type.insert(
"raw".to_string(),
serde_json::json!({
"chat": {
"translation_key": "%s",
"parameters": ["content"],
"style": null
},
"narration": {
"translation_key": "%s says %s",
"parameters": ["sender", "content"],
"style": null
}
}),
);
}
let registries_tokens = data.iter().map(|(reg_name, entries)| {
let entry_tokens = entries.iter().map(|(entry_name, entry_data)| {
let mut nbt_bytes = Vec::new();
pumpkin_nbt::serializer::to_bytes_unnamed(entry_data, &mut nbt_bytes)
.expect("Failed to serialize NBT at build time");
let nbt_literal = Literal::byte_string(&nbt_bytes);
quote! {
StaticRegistryEntry {
name: #entry_name,
nbt_bytes: #nbt_literal,
}
}
});
quote! {
StaticRegistry {
registry_id: #reg_name,
entries: &[#(#entry_tokens),*],
}
}
});
let jukebox_entries = data
.get("minecraft:jukebox_song")
.or_else(|| data.get("jukebox_song"));
let jukebox_index_arms = jukebox_entries
.map(|entries| {
entries
.iter()
.enumerate()
.map(|(index, (name, _))| {
quote! { #name => Some(#index), }
})
.collect::<TokenStream>()
})
.unwrap_or_default();
let v1_21_9_registries = process_version("../assets/registry/1_21_9_synced_registries.json");
let v1_21_11_registries = process_version("../assets/registry/1_21_11_synced_registries.json");
quote! {
use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::version::MinecraftVersion;
pub struct StaticRegistryEntry {
pub name: &'static str,
pub nbt_bytes: &'static [u8],
pub data: &'static [u8],
}
pub struct StaticRegistry {
@ -89,10 +70,6 @@ pub(crate) fn build() -> TokenStream {
pub entries: &'static [StaticRegistryEntry],
}
pub static SYNCED_REGISTRIES: &[StaticRegistry] = &[
#(#registries_tokens),*
];
pub struct RegistryEntryData {
pub entry_id: ResourceLocation,
pub data: Option<Box<[u8]>>,
@ -103,20 +80,17 @@ pub(crate) fn build() -> TokenStream {
pub registry_entries: Vec<RegistryEntryData>,
}
pub struct SyncedRegistry;
impl SyncedRegistry {
pub fn get_jukebox_song_index(song_key: &str) -> Option<usize> {
match song_key {
#jukebox_index_arms
_ => None
}
}
}
pub static REGISTRIES_1_21_9: &[StaticRegistry] = #v1_21_9_registries;
pub static REGISTRIES_1_21_11: &[StaticRegistry] = #v1_21_11_registries;
impl Registry {
pub fn get_synced() -> Vec<Self> {
SYNCED_REGISTRIES.iter().map(|static_reg| {
pub fn get_synced(version: MinecraftVersion) -> Vec<Self> {
let static_regs = match version {
MinecraftVersion::V_1_21_9 => REGISTRIES_1_21_9,
_ => REGISTRIES_1_21_11,
};
static_regs.iter().map(|static_reg| {
let registry_id = if static_reg.registry_id.contains(':') {
ResourceLocation::from(static_reg.registry_id)
} else {
@ -124,24 +98,17 @@ pub(crate) fn build() -> TokenStream {
};
let registry_entries = static_reg.entries.iter().map(|entry| {
let entry_id = if entry.name.contains(':') {
ResourceLocation::from(entry.name)
} else {
ResourceLocation::vanilla(entry.name)
};
let data = Some(entry.nbt_bytes.to_vec().into_boxed_slice());
let entry_id =
ResourceLocation::vanilla(entry.name);
RegistryEntryData {
entry_id,
data,
// Data is now sourced directly from the entry
data: Some(entry.data.to_vec().into_boxed_slice()),
}
}).collect();
Self {
registry_id,
registry_entries,
}
Self { registry_id, registry_entries }
}).collect()
}
}

View File

@ -1,4 +1,8 @@
use std::{collections::BTreeMap, fs};
use std::{
collections::{BTreeMap, HashSet},
fs,
path::Path,
};
use crate::biome::Biome;
use crate::block::BlockAssets;
@ -10,73 +14,87 @@ use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
// --- EnumCreator with from_string support ---
pub struct EnumCreator {
pub name: String,
pub value: Vec<String>,
pub values: Vec<String>,
}
impl ToTokens for EnumCreator {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = format_ident!("{}", self.name.to_pascal_case());
let values = self
.value
.iter()
.map(|value| {
let name = format_ident!("{}", value.to_pascal_case());
name
})
.collect::<Vec<_>>();
let variants = self.values.iter().map(|v| {
let variant_name = format_ident!("{}", v.to_pascal_case());
quote! { #variant_name }
});
let from_string_arms = self.values.iter().map(|v| {
let variant_name = format_ident!("{}", v.to_pascal_case());
quote! { #v => Some(Self::#variant_name) }
});
let to_string_arms = self.values.iter().map(|v| {
let variant_name = format_ident!("{}", v.to_pascal_case());
quote! { Self::#variant_name => #v }
});
tokens.extend(quote! {
#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)]
pub enum #name {
#(#values),*
#(#variants),*
}
impl #name {
pub fn from_string(s: &str) -> Option<Self> {
match s {
#(#from_string_arms,)*
_ => None,
}
}
pub fn identifier_string(&self) -> &str {
match self {
#(#to_string_arms),*
}
}
}
});
}
}
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/tags.json");
// --- Rerun Triggers ---
println!("cargo:rerun-if-changed=../assets/blocks.json");
println!("cargo:rerun-if-changed=../assets/items.json");
println!("cargo:rerun-if-changed=../assets/biome.json");
println!("cargo:rerun-if-changed=../assets/fluids.json");
println!("cargo:rerun-if-changed=../assets/entities.json");
let tags: BTreeMap<String, BTreeMap<String, Vec<String>>> =
serde_json::from_str(&fs::read_to_string("../assets/tags.json").unwrap())
.expect("Failed to parse tags.json");
// Watch specific tag versions
println!("cargo:rerun-if-changed=../assets/tags/1_21_11_tags.json");
println!("cargo:rerun-if-changed=../assets/tags/1_21_9_tags.json");
// --- Load Global Assets ---
let blocks_assets: BlockAssets =
serde_json::from_str(&fs::read_to_string("../assets/blocks.json").unwrap())
.expect("Failed to parse blocks.json");
let items: BTreeMap<String, Item> =
serde_json::from_str(&fs::read_to_string("../assets/items.json").unwrap())
.expect("Failed to parse items.json");
let biomes: BTreeMap<String, Biome> =
serde_json::from_str(&fs::read_to_string("../assets/biome.json").unwrap())
.expect("Failed to parse biome.json");
let fluids: Vec<Fluid> =
match serde_json::from_str(&fs::read_to_string("../assets/fluids.json").unwrap()) {
Ok(fluids) => fluids,
Err(e) => panic!("Failed to parse fluids.json: {e}"),
};
serde_json::from_str(&fs::read_to_string("../assets/fluids.json").unwrap())
.expect("Failed to parse fluids.json");
let enchantments: BTreeMap<String, Enchantment> =
serde_json::from_str(&fs::read_to_string("../assets/enchantments.json").unwrap())
.expect("Failed to parse enchantments.json");
let entities: BTreeMap<String, EntityType> =
serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap())
.expect("Failed to parse entities.json");
let registry_key_enum = EnumCreator {
name: "RegistryKey".to_string(),
value: tags.keys().map(|key| key.to_string()).collect(),
}
.to_token_stream();
let block_id_map: BTreeMap<String, u16> = blocks_assets
.blocks
.iter()
@ -85,115 +103,143 @@ pub(crate) fn build() -> TokenStream {
let fluid_id_map: BTreeMap<String, u16> =
fluids.iter().map(|f| (f.name.clone(), f.id)).collect();
// Generate tag arrays for each registry key
let mut tag_dicts = Vec::new();
let mut match_arms_value = Vec::new();
let mut match_arms_id = Vec::new();
let mut match_arms_tags_all = Vec::new();
let mut tag_identifiers = Vec::new();
let latest_version_key = "1_21_11";
let legacy_version_key = "1_21_9";
for (key, tag_map) in tags {
let key_pascal = format_ident!("{}", key.to_pascal_case());
let dict_name = format_ident!("{}_TAGS", key.to_pascal_case().to_uppercase());
let mut all_registry_keys = HashSet::new();
let mut latest_modules = Vec::new();
let mut legacy_modules = Vec::new();
let mut tag_entries = Vec::new();
let mut tag_map_entries = Vec::new();
let mut match_get_map = Vec::new();
for (tag_name, values) in tag_map {
let ids: Vec<u16> = values
.iter()
.filter_map(|v| match key.as_str() {
"worldgen/biome" => biomes.get(v).map(|b| b.id as u16),
"fluid" => fluid_id_map.get(v).copied(),
"item" => items.get(v).map(|i| i.id),
"block" => block_id_map.get(v).copied(),
"enchantment" => enchantments
.get(&format!("minecraft:{}", v))
.map(|e| e.id as u16),
"entity_type" => entities.get(v).map(|e| e.id),
_ => None,
})
.collect();
let tag_const_name =
format_ident!("{}", tag_name.replace([':', '/'], "_").to_uppercase());
let versions = vec![latest_version_key, legacy_version_key];
tag_entries.push(quote! {
pub const #tag_const_name: Tag = (&[#(#values),*], &[#(#ids),*]);
});
tag_map_entries.push(quote! {
#tag_name => &#key_pascal::#tag_const_name
});
for version in versions {
let file_path = format!("../assets/tags/{}_tags.json", version);
if !Path::new(&file_path).exists() {
continue;
}
tag_dicts.push(quote! {
#[allow(non_snake_case)]
pub mod #key_pascal {
use super::Tag;
#(#tag_entries)*
}
static #dict_name: phf::Map<&'static str, &'static Tag> = phf::phf_map! {
#(#tag_map_entries),*
let tags: BTreeMap<String, BTreeMap<String, Vec<String>>> =
serde_json::from_str(&fs::read_to_string(&file_path).unwrap()).unwrap();
let is_latest = version == latest_version_key;
let mut tag_dicts = Vec::new();
let mut match_local_map = Vec::new();
for (key, tag_map) in tags {
all_registry_keys.insert(key.clone());
let key_pascal = format_ident!("{}", key.to_pascal_case());
let dict_name = format_ident!("{}_TAGS", key.to_pascal_case().to_uppercase());
let mut tag_entries = Vec::new();
let mut tag_map_entries = Vec::new();
let tag_type_path = if is_latest {
quote!(super::Tag)
} else {
quote!(super::super::Tag)
};
});
// Add match arm for this registry key
match_arms_value.push(quote! {
RegistryKey::#key_pascal => {
#dict_name.get(tag).map(|i| i.0)
for (tag_name, values) in tag_map {
let ids: Vec<u16> = values
.iter()
.filter_map(|v| match key.as_str() {
"worldgen/biome" => biomes.get(v).map(|b| b.id as u16),
"fluid" => fluid_id_map.get(v).copied(),
"item" => items.get(v).map(|i| i.id),
"block" => block_id_map.get(v).copied(),
"enchantment" => enchantments
.get(&format!("minecraft:{}", v))
.map(|e| e.id as u16),
"entity_type" => entities.get(v).map(|e| e.id),
_ => None,
})
.collect();
let tag_const_name =
format_ident!("{}", tag_name.replace([':', '/'], "_").to_uppercase());
tag_entries.push(quote! {
pub const #tag_const_name: #tag_type_path = (&[#(#values),*], &[#(#ids),*]);
});
tag_map_entries.push(quote! { #tag_name => &#key_pascal::#tag_const_name });
}
});
match_arms_id.push(quote! {
RegistryKey::#key_pascal => {
#dict_name.get(tag).map(|i| i.1 )
}
});
let tag_type_path = if is_latest {
quote!(Tag)
} else {
quote!(super::Tag)
};
match_arms_tags_all.push(quote! {
RegistryKey::#key_pascal => {
&#dict_name
}
});
tag_dicts.push(quote! {
#[allow(non_snake_case)]
pub mod #key_pascal {
#(#tag_entries)* }
pub(crate) static #dict_name: phf::Map<&'static str, &'static #tag_type_path> = phf::phf_map! {
#(#tag_map_entries),* };
});
tag_identifiers.push(quote! {
Self::#key_pascal => #key
});
match_local_map.push(quote! { RegistryKey::#key_pascal => Some(&#dict_name) });
}
if is_latest {
latest_modules.push(quote! {
#(#tag_dicts)*
#[allow(unreachable_patterns)]
pub fn get_latest_map(key: RegistryKey) -> Option<&'static phf::Map<&'static str, &'static Tag>> {
match key { #(#match_local_map,)* _ => None }
}
});
match_get_map
.push(quote! { MinecraftVersion::V_1_21_11 => get_latest_map(tag_category) });
} else {
let mod_name = format_ident!("tags_{}", version);
legacy_modules.push(quote! {
mod #mod_name {
use super::RegistryKey;
#(#tag_dicts)*
pub fn get_map(key: RegistryKey) -> Option<&'static phf::Map<&'static str, &'static super::Tag>> {
match key { #(#match_local_map,)* _ => None }
}
}
});
match_get_map
.push(quote! { MinecraftVersion::V_1_21_9 => tags_1_21_9::get_map(tag_category) });
}
}
quote! {
#[derive(Eq, PartialEq, Hash, Debug)]
#registry_key_enum
// --- Generate RegistryKey Enum ---
let registry_key_enum = EnumCreator {
name: "RegistryKey".to_string(),
values: all_registry_keys.into_iter().collect(),
}
.to_token_stream();
impl RegistryKey {
// IDK why the linter is saying this isn't used
#[allow(dead_code)]
pub fn identifier_string(&self) -> &str {
match self {
#(#tag_identifiers),*
}
}
}
quote! {
use pumpkin_util::version::MinecraftVersion;
pub type Tag = (&'static [&'static str], &'static [u16]);
#(#tag_dicts)*
#registry_key_enum
// Latest tags are exported directly here
#(#latest_modules)*
// Legacy tags are hidden in their own module
#(#legacy_modules)*
pub fn get_tag_values(tag_category: RegistryKey, tag: &str) -> Option<&'static [&'static str]> {
match tag_category {
#(#match_arms_value),*
}
get_latest_map(tag_category).and_then(|m| m.get(tag)).map(|t| t.0)
}
pub fn get_tag_ids(tag_category: RegistryKey, tag: &str) -> Option<&'static [u16]> {
match tag_category {
#(#match_arms_id),*
}
get_latest_map(tag_category).and_then(|m| m.get(tag)).map(|t| t.1)
}
pub fn get_registry_key_tags(tag_category: &RegistryKey) -> &phf::Map<&'static str, &'static Tag> {
match tag_category {
#(#match_arms_tags_all),*
pub fn get_registry_key_tags(version: MinecraftVersion, tag_category: RegistryKey) -> Option<&'static phf::Map<&'static str, &'static Tag>> {
match version {
#(#match_get_map),*,
_ => get_latest_map(tag_category)
}
}
@ -202,8 +248,7 @@ pub(crate) fn build() -> TokenStream {
fn registry_key(&self) -> &str;
fn registry_id(&self) -> u16;
/// Returns `None` if the tag does not exist.
fn is_tagged_with(&self, tag: &str) -> Option<bool> {
fn is_tagged_with(&self, tag: &str) -> Option<bool> {
let tag = tag.strip_prefix("#").unwrap_or(tag);
let items = get_tag_ids(Self::tag_key(), tag)?;
Some(items.contains(&self.registry_id()))

View File

@ -8,6 +8,10 @@ pub mod item;
#[path = "generated/packet.rs"]
pub mod packet;
#[rustfmt::skip]
#[path = "generated/jukebox_song.rs"]
pub mod jukebox_song;
#[rustfmt::skip]
#[path = "generated/registry.rs"]
pub mod registry;

View File

@ -9,6 +9,7 @@ use pumpkin_data::{
};
use pumpkin_macros::packet;
use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::version::MinecraftVersion;
#[packet(CONFIG_UPDATE_TAGS)]
pub struct CUpdateTags<'a> {
@ -22,14 +23,18 @@ impl<'a> CUpdateTags<'a> {
}
impl ClientPacket for CUpdateTags<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_list(self.tags, |p, registry_key| {
p.write_resource_location(&ResourceLocation::vanilla(
registry_key.identifier_string(),
))?;
let values = get_registry_key_tags(registry_key);
let values = get_registry_key_tags(*version, *registry_key).unwrap();
p.write_var_int(&values.len().try_into().map_err(|_| {
WritingError::Message(format!("{} isn't representable as a VarInt", values.len()))
})?)?;

View File

@ -5,6 +5,7 @@ use crate::ser::NetworkWriteExt;
use crate::{ClientPacket, WritingError};
use pumpkin_data::packet::clientbound::PLAY_BOSS_EVENT;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
#[packet(PLAY_BOSS_EVENT)]
pub struct CBossEvent<'a> {
@ -19,7 +20,11 @@ impl<'a> CBossEvent<'a> {
}
impl ClientPacket for CBossEvent<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_uuid(self.uuid)?;

View File

@ -5,6 +5,7 @@ use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
use pumpkin_macros::packet;
use pumpkin_nbt::END_ID;
use pumpkin_util::math::position::get_local_cord;
use pumpkin_util::version::MinecraftVersion;
use pumpkin_world::chunk::format::LightContainer;
use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette};
use std::io::Write;
@ -13,7 +14,11 @@ use std::io::Write;
pub struct CChunkData<'a>(pub &'a ChunkData);
impl ClientPacket for CChunkData<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
// Chunk X

View File

@ -2,6 +2,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_COMMANDS;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
use crate::{ClientPacket, VarInt, WritingError, ser::NetworkWriteExt};
@ -21,7 +22,11 @@ impl<'a> CCommands<'a> {
}
impl ClientPacket for CCommands<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_list(&self.nodes, |bytebuf, node: &ProtoNode| {
node.write_to(bytebuf)

View File

@ -2,7 +2,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_PLAYER_CHAT;
use pumpkin_macros::packet;
use pumpkin_util::text::TextComponent;
use pumpkin_util::{text::TextComponent, version::MinecraftVersion};
use crate::{
ClientPacket, WritingError,
@ -67,7 +67,11 @@ impl CPlayerChatMessage {
//TODO: Check if we need this custom impl
impl ClientPacket for CPlayerChatMessage {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&self.global_index)?;

View File

@ -3,6 +3,7 @@ use std::io::Write;
use bitflags::bitflags;
use pumpkin_data::packet::clientbound::PLAY_PLAYER_INFO_UPDATE;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
use crate::{ClientPacket, Property, WritingError, ser::NetworkWriteExt};
@ -41,7 +42,11 @@ impl<'a> CPlayerInfoUpdate<'a> {
// TODO: Check if we need this custom impl
impl ClientPacket for CPlayerInfoUpdate<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_u8(self.actions)?;

View File

@ -2,7 +2,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_PLAYER_POSITION;
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
use crate::{
ClientPacket, PositionFlag, ServerPacket, VarInt, WritingError, ser::NetworkReadExt,
@ -41,7 +41,11 @@ impl CPlayerPosition {
// TODO: Do we need a custom impl?
impl ClientPacket for CPlayerPosition {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&self.teleport_id)?;

View File

@ -6,6 +6,7 @@ use crate::{
};
use pumpkin_data::packet::clientbound::PLAY_SET_EQUIPMENT;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
use serde::Serialize;
use crate::{
@ -29,7 +30,11 @@ impl CSetEquipment {
}
impl ClientPacket for CSetEquipment {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&self.entity_id)?;

View File

@ -6,6 +6,7 @@ use crate::{ClientPacket, WritingError};
use pumpkin_data::{packet::clientbound::PLAY_STOP_SOUND, sound::SoundCategory};
use pumpkin_macros::packet;
use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::version::MinecraftVersion;
#[packet(PLAY_STOP_SOUND)]
pub struct CStopSound {
@ -20,7 +21,11 @@ impl CStopSound {
}
impl ClientPacket for CStopSound {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
const NO_CATEGORY_NO_SOUND: u8 = 0;

View File

@ -2,7 +2,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_TELEPORT_ENTITY;
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
use crate::{ClientPacket, PositionFlag, VarInt, WritingError, ser::NetworkWriteExt};
@ -42,7 +42,11 @@ impl<'a> CTeleportEntity<'a> {
// TODO: Do we need a custom impl?
impl ClientPacket for CTeleportEntity<'_> {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&self.entity_id)?;

View File

@ -2,7 +2,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_SET_OBJECTIVE;
use pumpkin_macros::packet;
use pumpkin_util::text::TextComponent;
use pumpkin_util::{text::TextComponent, version::MinecraftVersion};
use crate::{ClientPacket, NumberFormat, VarInt, WritingError, ser::NetworkWriteExt};
@ -34,7 +34,11 @@ impl CUpdateObjectives {
}
impl ClientPacket for CUpdateObjectives {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut write = write;
write.write_string(&self.objective_name)?;

View File

@ -284,6 +284,7 @@ mod tests {
use flate2::read::ZlibDecoder;
use pumpkin_data::packet::clientbound::STATUS_STATUS_RESPONSE;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
use serde::Serialize;
/// Define a custom packet for testing maximum packet size
@ -339,7 +340,9 @@ mod tests {
let mut packet_buf = Vec::new();
let writer = &mut packet_buf;
writer.write_var_int(&VarInt(T::PACKET_ID)).unwrap();
packet.write_packet_data(writer).unwrap();
packet
.write_packet_data(writer, &MinecraftVersion::V_1_21_11)
.unwrap();
encoder.write_packet(packet_buf.into()).await.unwrap();
@ -374,7 +377,9 @@ mod tests {
// Remaining buffer is the payload
// We need to obtain the expected payload
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
assert_eq!(buffer, expected_payload);
}
@ -402,7 +407,9 @@ mod tests {
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
let uncompressed_data_length =
VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
assert_eq!(data_length as usize, uncompressed_data_length);
@ -458,7 +465,9 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
assert_eq!(buffer, expected_payload);
}
@ -492,7 +501,9 @@ mod tests {
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
let uncompressed_data_length =
VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
assert_eq!(data_length as usize, uncompressed_data_length);
@ -542,7 +553,9 @@ mod tests {
// Remaining buffer is the payload (empty)
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
assert_eq!(
buffer.len(),
@ -587,7 +600,9 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
assert_eq!(buffer, expected_payload);
}
@ -639,7 +654,9 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet.write_packet_data(&mut expected_payload).unwrap();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
assert_eq!(buffer, expected_payload);
}

View File

@ -6,6 +6,7 @@ use crate::{
};
use pumpkin_data::packet::serverbound::HANDSHAKE_INTENTION;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
#[packet(HANDSHAKE_INTENTION)]
pub struct SHandShake {
@ -34,6 +35,7 @@ impl ClientPacket for SHandShake {
fn write_packet_data(
&self,
mut write: impl std::io::Write,
_version: &MinecraftVersion,
) -> Result<(), crate::ser::WritingError> {
write.write_var_int(&self.protocol_version)?;
write.write_string(&self.server_address)?;

View File

@ -2,6 +2,7 @@ use std::io::Read;
use pumpkin_data::packet::serverbound::PLAY_CHAT;
use pumpkin_macros::packet;
use pumpkin_util::version::MinecraftVersion;
use crate::{
ClientPacket, ServerPacket,
@ -40,6 +41,7 @@ impl ClientPacket for SChatMessage {
fn write_packet_data(
&self,
mut write: impl std::io::Write,
_version: &MinecraftVersion,
) -> Result<(), crate::ser::WritingError> {
write.write_string(&self.message)?;
write.write_i64_be(self.timestamp)?;

View File

@ -11,6 +11,7 @@ use codec::var_int::VarInt;
use pumpkin_util::{
resource_location::ResourceLocation,
text::{TextComponent, style::Style},
version::MinecraftVersion,
};
use ser::{ReadingError, WritingError};
use serde::{
@ -311,7 +312,11 @@ pub struct RawPacket {
}
pub trait ClientPacket: Packet {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError>;
fn write_packet_data(
&self,
write: impl Write,
version: &MinecraftVersion,
) -> Result<(), WritingError>;
}
pub trait ServerPacket: Packet + Sized {

View File

@ -1,5 +1,6 @@
use std::io::{Error, Read, Write};
use pumpkin_util::version::MinecraftVersion;
use serde::{Serialize, de::DeserializeOwned};
use crate::{
@ -14,7 +15,11 @@ pub trait Packet {
}
impl<P: Packet + Serialize> ClientPacket for P {
fn write_packet_data(&self, write: impl Write) -> Result<(), WritingError> {
fn write_packet_data(
&self,
write: impl Write,
_version: &MinecraftVersion,
) -> Result<(), WritingError> {
let mut serializer = serializer::Serializer::new(write);
self.serialize(&mut serializer)
}

View File

@ -20,6 +20,7 @@ pub mod resource_location;
pub mod serde_enum_as_integer;
pub mod text;
pub mod translation;
pub mod version;
pub mod world_seed;
pub mod jwt;

166
pumpkin-util/src/version.rs Normal file
View File

@ -0,0 +1,166 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[allow(non_camel_case_types)]
pub enum MinecraftVersion {
V_1_7_2,
V_1_7_6,
V_1_8,
V_1_9,
V_1_9_1,
V_1_9_2,
V_1_9_3,
V_1_10,
V_1_11,
V_1_11_1,
V_1_12,
V_1_12_1,
V_1_12_2,
V_1_13,
V_1_13_1,
V_1_13_2,
V_1_14,
V_1_14_1,
V_1_14_2,
V_1_14_3,
V_1_14_4,
V_1_15,
V_1_15_1,
V_1_15_2,
V_1_16,
V_1_16_1,
V_1_16_2,
V_1_16_3,
V_1_16_4,
V_1_17,
V_1_17_1,
V_1_18,
V_1_18_2,
V_1_19,
V_1_19_1,
V_1_19_3,
V_1_19_4,
V_1_20,
V_1_20_2,
V_1_20_3,
V_1_20_5,
V_1_21,
V_1_21_2,
V_1_21_4,
V_1_21_5,
V_1_21_6,
V_1_21_7,
V_1_21_9,
V_1_21_11,
Unknown,
}
impl MinecraftVersion {
pub fn protocol_version(&self) -> i32 {
match self {
Self::V_1_7_2 => 4,
Self::V_1_7_6 => 5,
Self::V_1_8 => 47,
Self::V_1_9 => 107,
Self::V_1_9_1 => 108,
Self::V_1_9_2 => 109,
Self::V_1_9_3 => 110,
Self::V_1_10 => 210,
Self::V_1_11 => 315,
Self::V_1_11_1 => 316,
Self::V_1_12 => 335,
Self::V_1_12_1 => 338,
Self::V_1_12_2 => 340,
Self::V_1_13 => 393,
Self::V_1_13_1 => 401,
Self::V_1_13_2 => 404,
Self::V_1_14 => 477,
Self::V_1_14_1 => 480,
Self::V_1_14_2 => 485,
Self::V_1_14_3 => 490,
Self::V_1_14_4 => 498,
Self::V_1_15 => 573,
Self::V_1_15_1 => 575,
Self::V_1_15_2 => 578,
Self::V_1_16 => 735,
Self::V_1_16_1 => 736,
Self::V_1_16_2 => 751,
Self::V_1_16_3 => 753,
Self::V_1_16_4 => 754,
Self::V_1_17 => 755,
Self::V_1_17_1 => 756,
Self::V_1_18 => 757,
Self::V_1_18_2 => 758,
Self::V_1_19 => 759,
Self::V_1_19_1 => 760,
Self::V_1_19_3 => 761,
Self::V_1_19_4 => 762,
Self::V_1_20 => 763,
Self::V_1_20_2 => 764,
Self::V_1_20_3 => 765,
Self::V_1_20_5 => 766,
Self::V_1_21 => 767,
Self::V_1_21_2 => 768,
Self::V_1_21_4 => 769,
Self::V_1_21_5 => 770,
Self::V_1_21_6 => 771,
Self::V_1_21_7 => 772,
Self::V_1_21_9 => 773,
Self::V_1_21_11 => 774,
Self::Unknown => -1,
}
}
pub fn from_protocol(protocol: u32) -> Self {
match protocol {
4 => Self::V_1_7_2,
5 => Self::V_1_7_6,
47 => Self::V_1_8,
107 => Self::V_1_9,
108 => Self::V_1_9_1,
109 => Self::V_1_9_2,
110 => Self::V_1_9_3,
210 => Self::V_1_10,
315 => Self::V_1_11,
316 => Self::V_1_11_1,
335 => Self::V_1_12,
338 => Self::V_1_12_1,
340 => Self::V_1_12_2,
393 => Self::V_1_13,
401 => Self::V_1_13_1,
404 => Self::V_1_13_2,
477 => Self::V_1_14,
480 => Self::V_1_14_1,
485 => Self::V_1_14_2,
490 => Self::V_1_14_3,
498 => Self::V_1_14_4,
573 => Self::V_1_15,
575 => Self::V_1_15_1,
578 => Self::V_1_15_2,
735 => Self::V_1_16,
736 => Self::V_1_16_1,
751 => Self::V_1_16_2,
753 => Self::V_1_16_3,
754 => Self::V_1_16_4,
755 => Self::V_1_17,
756 => Self::V_1_17_1,
757 => Self::V_1_18,
758 => Self::V_1_18_2,
759 => Self::V_1_19,
760 => Self::V_1_19_1,
761 => Self::V_1_19_3,
762 => Self::V_1_19_4,
763 => Self::V_1_20,
764 => Self::V_1_20_2,
765 => Self::V_1_20_3,
766 => Self::V_1_20_5,
767 => Self::V_1_21,
768 => Self::V_1_21_2,
769 => Self::V_1_21_4,
770 => Self::V_1_21_5,
771 => Self::V_1_21_6,
772 => Self::V_1_21_7,
773 => Self::V_1_21_9,
774 => Self::V_1_21_11,
_ => Self::Unknown,
}
}
}

View File

@ -22,6 +22,8 @@ pub type BlockId = u16;
pub type BlockStateId = u16;
pub const CURRENT_MC_VERSION: &str = "1.21.11";
pub const LOWEST_SUPPRORTED_PROTOCOL_VERSION: u32 = 773;
pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.32";
pub const CURRENT_BEDROCK_MC_PROTOCOL: u32 = 898;

View File

@ -4,7 +4,7 @@ use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, BrokenArgs, UseWithItemArgs};
use crate::world::World;
use pumpkin_data::data_component_impl::JukeboxPlayableImpl;
use pumpkin_data::registry::SyncedRegistry;
use pumpkin_data::jukebox_song::JukeboxSong;
use pumpkin_data::world::WorldEvent;
use pumpkin_data::{
Block,
@ -73,7 +73,7 @@ impl BlockBehaviour for JukeboxBlock {
return BlockActionResult::Pass;
};
let Some(jukebox_song) = SyncedRegistry::get_jukebox_song_index(song) else {
let Some(jukebox_song) = JukeboxSong::from_name(song) else {
log::error!("Jukebox playable song not registered!");
return BlockActionResult::Pass;
};
@ -86,7 +86,7 @@ impl BlockBehaviour for JukeboxBlock {
.sync_world_event(
WorldEvent::JukeboxStartsPlaying,
*args.position,
jukebox_song as i32,
jukebox_song.get_id() as i32,
)
.await;

View File

@ -6,6 +6,7 @@ use crate::{
server::Server,
};
use core::str;
use pumpkin_data::{registry::Registry, tag::RegistryKey};
use pumpkin_protocol::{
ConnectionState,
java::{
@ -149,9 +150,11 @@ impl JavaClient {
);
}
pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) {
pub async fn handle_known_packs(&self, _config_acknowledged: SKnownPacks) {
log::debug!("Handling known packs");
for registry in &server.cached_registry {
let mut tags_to_send = Vec::new();
let registry = Registry::get_synced(self.version.load());
for registry in registry {
let entries: Vec<RegistryEntry> = registry
.registry_entries
.iter()
@ -159,18 +162,13 @@ impl JavaClient {
.collect();
self.send_packet_now(&CRegistryData::new(&registry.registry_id, &entries))
.await;
if let Some(tag) = RegistryKey::from_string(&registry.registry_id.path)
&& pumpkin_data::tag::get_registry_key_tags(self.version.load(), tag).is_some()
{
tags_to_send.push(tag);
}
}
self.send_packet_now(&CUpdateTags::new(&[
pumpkin_data::tag::RegistryKey::Block,
pumpkin_data::tag::RegistryKey::Fluid,
pumpkin_data::tag::RegistryKey::Enchantment,
pumpkin_data::tag::RegistryKey::WorldgenBiome,
pumpkin_data::tag::RegistryKey::Item,
pumpkin_data::tag::RegistryKey::EntityType,
pumpkin_data::tag::RegistryKey::Dialog,
pumpkin_data::tag::RegistryKey::Timeline,
]))
.await;
self.send_packet_now(&CUpdateTags::new(&tags_to_send)).await;
// We are done with configuring
log::debug!("Finished config");

View File

@ -1,36 +1,33 @@
use pumpkin_data::packet::CURRENT_MC_PROTOCOL;
use pumpkin_protocol::{ConnectionState, java::server::handshake::SHandShake};
use pumpkin_util::text::TextComponent;
use pumpkin_util::{text::TextComponent, version::MinecraftVersion};
use pumpkin_world::CURRENT_MC_VERSION;
use pumpkin_world::{CURRENT_MC_VERSION, LOWEST_SUPPRORTED_PROTOCOL_VERSION};
use crate::net::java::JavaClient;
impl JavaClient {
pub async fn handle_handshake(&self, handshake: SHandShake) {
let version = handshake.protocol_version.0;
let version = handshake.protocol_version.0 as u32;
*self.server_address.lock().await = handshake.server_address;
self.version.store(MinecraftVersion::from_protocol(version));
log::debug!("Handshake: next state is {:?}", &handshake.next_state);
self.connection_state.store(handshake.next_state);
if self.connection_state.load() != ConnectionState::Status {
let protocol = version;
match protocol.cmp(&(CURRENT_MC_PROTOCOL as i32)) {
std::cmp::Ordering::Less => {
self.kick(TextComponent::translate(
"multiplayer.disconnect.outdated_client",
[TextComponent::text(CURRENT_MC_VERSION.to_string())],
))
.await;
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.kick(TextComponent::translate(
"multiplayer.disconnect.incompatible",
[TextComponent::text(CURRENT_MC_VERSION.to_string())],
))
.await;
}
if protocol < LOWEST_SUPPRORTED_PROTOCOL_VERSION {
self.kick(TextComponent::translate(
"multiplayer.disconnect.outdated_client",
[TextComponent::text(CURRENT_MC_VERSION.to_string())],
))
.await;
} else if protocol > CURRENT_MC_PROTOCOL {
self.kick(TextComponent::translate(
"multiplayer.disconnect.incompatible",
[TextComponent::text(CURRENT_MC_VERSION.to_string())],
))
.await;
}
}
}

View File

@ -4,6 +4,7 @@ use std::{io::Write, sync::Arc};
use bytes::Bytes;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::networking::compression::CompressionInfo;
use pumpkin_data::packet::CURRENT_MC_PROTOCOL;
use pumpkin_protocol::java::server::play::{
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
@ -36,6 +37,7 @@ use pumpkin_protocol::{
ser::{NetworkWriteExt, ReadingError, WritingError},
};
use pumpkin_util::text::TextComponent;
use pumpkin_util::version::MinecraftVersion;
use tokio::{
io::{BufReader, BufWriter},
net::{
@ -55,6 +57,7 @@ pub mod config;
pub mod handshake;
pub mod login;
pub mod play;
pub mod remapper;
pub mod status;
use crate::entity::player::Player;
@ -63,6 +66,7 @@ use crate::{error::PumpkinError, net::EncryptionError, server::Server};
pub struct JavaClient {
pub id: u64,
pub version: AtomicCell<MinecraftVersion>,
/// The client's game profile information.
pub gameprofile: Mutex<Option<GameProfile>>,
/// The client's configuration settings, Optional
@ -106,7 +110,7 @@ impl JavaClient {
tasks: TaskTracker::new(),
outgoing_packet_queue_send: send,
outgoing_packet_queue_recv: Some(recv),
version: AtomicCell::new(MinecraftVersion::from_protocol(CURRENT_MC_PROTOCOL)),
network_writer: Arc::new(Mutex::new(TCPNetworkEncoder::new(BufWriter::new(write)))),
network_reader: Mutex::new(TCPNetworkDecoder::new(BufReader::new(read))),
brand: Mutex::new(None),
@ -193,7 +197,7 @@ impl JavaClient {
pub async fn enqueue_packet<P: ClientPacket>(&self, packet: &P) {
let mut buf = Vec::new();
let writer = &mut buf;
Self::write_packet(packet, writer).unwrap();
self.write_packet(packet, writer).unwrap();
self.enqueue_packet_data(buf.into()).await;
}
@ -266,7 +270,7 @@ impl JavaClient {
pub async fn send_packet_now<P: ClientPacket>(&self, packet: &P) {
let mut packet_buf = Vec::new();
let writer = &mut packet_buf;
Self::write_packet(packet, writer).unwrap();
self.write_packet(packet, writer).unwrap();
self.send_packet_now_data(packet_buf).await;
}
@ -289,12 +293,13 @@ impl JavaClient {
}
pub fn write_packet<P: ClientPacket>(
&self,
packet: &P,
write: impl Write,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&VarInt(P::PACKET_ID))?;
packet.write_packet_data(write)
packet.write_packet_data(write, &self.version.load())
}
/// Handles an incoming packet, routing it to the appropriate handler based on the current connection state.
@ -503,8 +508,7 @@ impl JavaClient {
self.handle_config_acknowledged(server).await;
}
SKnownPacks::PACKET_ID => {
self.handle_known_packs(server, SKnownPacks::read(payload)?)
.await;
self.handle_known_packs(SKnownPacks::read(payload)?).await;
}
SConfigCookieResponse::PACKET_ID => {
self.handle_config_cookie_response(&SConfigCookieResponse::read(payload)?);

View File

@ -0,0 +1 @@
pub mod registry;

View File

@ -0,0 +1 @@

View File

@ -14,7 +14,6 @@ use connection_cache::{CachedBranding, CachedStatus};
use key_store::KeyStore;
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::registry::Registry;
use pumpkin_world::dimension::into_level;
use crate::command::CommandSender;
@ -74,8 +73,6 @@ pub struct Server {
pub worlds: RwLock<Vec<Arc<World>>>,
/// All the dimensions that exist on the server.
pub dimensions: Vec<Dimension>,
/// Caches game registries for efficient access.
pub cached_registry: Vec<Registry>,
/// Assigns unique IDs to containers.
container_id: AtomicU32,
/// Mojang's public keys, used for chat session signing
@ -178,8 +175,6 @@ impl Server {
let server = Self {
basic_config,
advanced_config,
cached_registry: Registry::get_synced(),
container_id: 0.into(),
worlds: RwLock::new(vec![]),
dimensions: vec![