mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix: send proper dimension/timeline tag data (#1684)
This commit is contained in:
@@ -5,6 +5,15 @@ use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use serde::Deserialize;
|
||||
|
||||
// helper to turn a hex string like "#78a7ff" into an i32
|
||||
fn parse_hex_color(s: &str) -> Option<i32> {
|
||||
if let Some(stripped) = s.strip_prefix('#') {
|
||||
i32::from_str_radix(stripped, 16).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Dimension {
|
||||
pub has_skylight: bool,
|
||||
@@ -17,6 +26,10 @@ pub struct Dimension {
|
||||
pub infiniburn: String,
|
||||
#[serde(rename = "fixed_time")]
|
||||
pub fixed_time: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub attributes: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub timelines: Option<String>,
|
||||
}
|
||||
|
||||
// #[derive(Clone, PartialEq, Deserialize)]
|
||||
@@ -58,6 +71,26 @@ pub fn build() -> TokenStream {
|
||||
.to_shouty_snake_case()
|
||||
);
|
||||
|
||||
// convert optional hex colors from attributes into ints
|
||||
let sky_color = dim
|
||||
.attributes
|
||||
.as_ref()
|
||||
.and_then(|a| a.get("minecraft:visual/sky_color"))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| parse_hex_color(s));
|
||||
let fog_color = dim
|
||||
.attributes
|
||||
.as_ref()
|
||||
.and_then(|a| a.get("minecraft:visual/fog_color"))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| parse_hex_color(s));
|
||||
let cloud_color = dim
|
||||
.attributes
|
||||
.as_ref()
|
||||
.and_then(|a| a.get("minecraft:visual/cloud_color"))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| parse_hex_color(s));
|
||||
|
||||
let fixed_time = if let Some(t) = dim.fixed_time {
|
||||
quote! { Some(#t) }
|
||||
} else {
|
||||
@@ -71,7 +104,19 @@ pub fn build() -> TokenStream {
|
||||
let logical_height = dim.logical_height;
|
||||
let has_skylight = dim.has_skylight;
|
||||
let has_ceiling = dim.has_ceiling;
|
||||
let infiniburn = &dim.infiniburn;
|
||||
// normalize infiniburn to always have namespace
|
||||
let infiniburn = if dim.infiniburn.contains(':') {
|
||||
dim.infiniburn.clone()
|
||||
} else {
|
||||
format!("minecraft:{}", dim.infiniburn)
|
||||
};
|
||||
let timelines = dim.timelines.map(|t| {
|
||||
if t.contains(':') {
|
||||
t
|
||||
} else {
|
||||
format!("minecraft:{}", t)
|
||||
}
|
||||
});
|
||||
|
||||
let minecraft_name = if name.contains(':') {
|
||||
name.clone()
|
||||
@@ -79,6 +124,15 @@ pub fn build() -> TokenStream {
|
||||
format!("minecraft:{name}")
|
||||
};
|
||||
|
||||
let sky_color_literal = if let Some(c) = sky_color { quote! { Some(#c) } } else { quote! { None } };
|
||||
let fog_color_literal = if let Some(c) = fog_color { quote! { Some(#c) } } else { quote! { None } };
|
||||
let cloud_color_literal = if let Some(c) = cloud_color { quote! { Some(#c) } } else { quote! { None } };
|
||||
let timelines_literal = if let Some(t) = timelines.clone() {
|
||||
quote! { Some(#t) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
variants.extend(quote! {
|
||||
pub const #format_name: Self = Self {
|
||||
id: #id,
|
||||
@@ -92,6 +146,10 @@ pub fn build() -> TokenStream {
|
||||
logical_height: #logical_height,
|
||||
infiniburn: #infiniburn,
|
||||
ambient_light: #ambient_light,
|
||||
sky_color: #sky_color_literal,
|
||||
fog_color: #fog_color_literal,
|
||||
cloud_color: #cloud_color_literal,
|
||||
timelines: #timelines_literal,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -114,6 +172,10 @@ pub fn build() -> TokenStream {
|
||||
pub logical_height: i32,
|
||||
pub infiniburn: &'static str,
|
||||
pub ambient_light: f32,
|
||||
pub sky_color: Option<i32>,
|
||||
pub fog_color: Option<i32>,
|
||||
pub cloud_color: Option<i32>,
|
||||
pub timelines: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Dimension {
|
||||
|
||||
@@ -83,6 +83,30 @@ pub fn build() -> TokenStream {
|
||||
serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap())
|
||||
.expect("Failed to parse entities.json");
|
||||
|
||||
// build a map of dimension name -> numeric id
|
||||
let dimension_json: BTreeMap<String, serde_json::Value> =
|
||||
serde_json::from_str(&fs::read_to_string("../assets/dimension.json").unwrap())
|
||||
.expect("Failed to parse dimension.json");
|
||||
let mut dimension_id_map: BTreeMap<String, u16> = BTreeMap::new();
|
||||
for (i, name) in dimension_json.keys().enumerate() {
|
||||
dimension_id_map.insert(name.clone(), i as u16);
|
||||
}
|
||||
|
||||
// also build timeline id map from registry file so timeline tags carry numbers
|
||||
let mut timeline_id_map: BTreeMap<String, u16> = BTreeMap::new();
|
||||
if let Ok(registries) = serde_json::from_str::<serde_json::Value>(
|
||||
&fs::read_to_string("../assets/registry/1_21_11_synced_registries.json").unwrap(),
|
||||
) {
|
||||
if let Some(timelines) = registries.get("timeline") {
|
||||
if let Some(obj) = timelines.as_object() {
|
||||
for (i, name) in obj.keys().enumerate() {
|
||||
timeline_id_map.insert(name.clone(), i as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// dimension_id_map will be used when resolving dimension_type tag entries below
|
||||
|
||||
let block_id_map: BTreeMap<String, u16> = blocks_assets
|
||||
.blocks
|
||||
.iter()
|
||||
@@ -95,6 +119,8 @@ pub fn build() -> TokenStream {
|
||||
let legacy_version_key = "1_21_9";
|
||||
|
||||
let mut all_registry_keys = HashSet::new();
|
||||
all_registry_keys.insert("dimension_type".to_string());
|
||||
|
||||
let mut latest_modules = Vec::new();
|
||||
let mut legacy_modules = Vec::new();
|
||||
|
||||
@@ -140,6 +166,8 @@ pub fn build() -> TokenStream {
|
||||
.get(&format!("minecraft:{v}"))
|
||||
.map(|e| u16::from(e.id)),
|
||||
"entity_type" => entities.get(v).map(|e| e.id),
|
||||
"dimension_type" => dimension_id_map.get(v).copied(),
|
||||
"timeline" => timeline_id_map.get(v).copied(),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -12,6 +12,10 @@ pub struct Dimension {
|
||||
pub logical_height: i32,
|
||||
pub infiniburn: &'static str,
|
||||
pub ambient_light: f32,
|
||||
pub sky_color: Option<i32>,
|
||||
pub fog_color: Option<i32>,
|
||||
pub cloud_color: Option<i32>,
|
||||
pub timelines: Option<&'static str>,
|
||||
}
|
||||
impl Dimension {
|
||||
pub const OVERWORLD: Self = Self {
|
||||
@@ -26,6 +30,10 @@ impl Dimension {
|
||||
logical_height: 384i32,
|
||||
infiniburn: "#minecraft:infiniburn_overworld",
|
||||
ambient_light: 0f32,
|
||||
sky_color: Some(7907327i32),
|
||||
fog_color: Some(12638463i32),
|
||||
cloud_color: None,
|
||||
timelines: Some("#minecraft:in_overworld"),
|
||||
};
|
||||
pub const OVERWORLD_CAVES: Self = Self {
|
||||
id: 1u8,
|
||||
@@ -39,6 +47,10 @@ impl Dimension {
|
||||
logical_height: 384i32,
|
||||
infiniburn: "#minecraft:infiniburn_overworld",
|
||||
ambient_light: 0f32,
|
||||
sky_color: Some(7907327i32),
|
||||
fog_color: Some(12638463i32),
|
||||
cloud_color: None,
|
||||
timelines: Some("#minecraft:in_overworld"),
|
||||
};
|
||||
pub const THE_END: Self = Self {
|
||||
id: 2u8,
|
||||
@@ -52,6 +64,10 @@ impl Dimension {
|
||||
logical_height: 256i32,
|
||||
infiniburn: "#minecraft:infiniburn_end",
|
||||
ambient_light: 0.25f32,
|
||||
sky_color: Some(0i32),
|
||||
fog_color: Some(1577752i32),
|
||||
cloud_color: None,
|
||||
timelines: Some("#minecraft:in_end"),
|
||||
};
|
||||
pub const THE_NETHER: Self = Self {
|
||||
id: 3u8,
|
||||
@@ -65,6 +81,10 @@ impl Dimension {
|
||||
logical_height: 128i32,
|
||||
infiniburn: "#minecraft:infiniburn_nether",
|
||||
ambient_light: 0.1f32,
|
||||
sky_color: None,
|
||||
fog_color: None,
|
||||
cloud_color: None,
|
||||
timelines: Some("#minecraft:in_nether"),
|
||||
};
|
||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||
match name {
|
||||
|
||||
@@ -3,57 +3,60 @@ use pumpkin_util::version::MinecraftVersion;
|
||||
pub type Tag = (&'static [&'static str], &'static [u16]);
|
||||
#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)]
|
||||
pub enum RegistryKey {
|
||||
PaintingVariant,
|
||||
WorldgenBiome,
|
||||
Instrument,
|
||||
Block,
|
||||
BannerPattern,
|
||||
Item,
|
||||
GameEvent,
|
||||
Timeline,
|
||||
Dialog,
|
||||
Enchantment,
|
||||
Fluid,
|
||||
PointOfInterestType,
|
||||
DamageType,
|
||||
PaintingVariant,
|
||||
EntityType,
|
||||
DimensionType,
|
||||
Block,
|
||||
Timeline,
|
||||
BannerPattern,
|
||||
GameEvent,
|
||||
Item,
|
||||
Dialog,
|
||||
PointOfInterestType,
|
||||
Fluid,
|
||||
Instrument,
|
||||
WorldgenBiome,
|
||||
Enchantment,
|
||||
}
|
||||
impl RegistryKey {
|
||||
pub fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"painting_variant" => Some(Self::PaintingVariant),
|
||||
"worldgen/biome" => Some(Self::WorldgenBiome),
|
||||
"instrument" => Some(Self::Instrument),
|
||||
"block" => Some(Self::Block),
|
||||
"banner_pattern" => Some(Self::BannerPattern),
|
||||
"item" => Some(Self::Item),
|
||||
"game_event" => Some(Self::GameEvent),
|
||||
"timeline" => Some(Self::Timeline),
|
||||
"dialog" => Some(Self::Dialog),
|
||||
"enchantment" => Some(Self::Enchantment),
|
||||
"fluid" => Some(Self::Fluid),
|
||||
"point_of_interest_type" => Some(Self::PointOfInterestType),
|
||||
"damage_type" => Some(Self::DamageType),
|
||||
"painting_variant" => Some(Self::PaintingVariant),
|
||||
"entity_type" => Some(Self::EntityType),
|
||||
"dimension_type" => Some(Self::DimensionType),
|
||||
"block" => Some(Self::Block),
|
||||
"timeline" => Some(Self::Timeline),
|
||||
"banner_pattern" => Some(Self::BannerPattern),
|
||||
"game_event" => Some(Self::GameEvent),
|
||||
"item" => Some(Self::Item),
|
||||
"dialog" => Some(Self::Dialog),
|
||||
"point_of_interest_type" => Some(Self::PointOfInterestType),
|
||||
"fluid" => Some(Self::Fluid),
|
||||
"instrument" => Some(Self::Instrument),
|
||||
"worldgen/biome" => Some(Self::WorldgenBiome),
|
||||
"enchantment" => Some(Self::Enchantment),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn identifier_string(&self) -> &str {
|
||||
match self {
|
||||
Self::PaintingVariant => "painting_variant",
|
||||
Self::WorldgenBiome => "worldgen/biome",
|
||||
Self::Instrument => "instrument",
|
||||
Self::Block => "block",
|
||||
Self::BannerPattern => "banner_pattern",
|
||||
Self::Item => "item",
|
||||
Self::GameEvent => "game_event",
|
||||
Self::Timeline => "timeline",
|
||||
Self::Dialog => "dialog",
|
||||
Self::Enchantment => "enchantment",
|
||||
Self::Fluid => "fluid",
|
||||
Self::PointOfInterestType => "point_of_interest_type",
|
||||
Self::DamageType => "damage_type",
|
||||
Self::PaintingVariant => "painting_variant",
|
||||
Self::EntityType => "entity_type",
|
||||
Self::DimensionType => "dimension_type",
|
||||
Self::Block => "block",
|
||||
Self::Timeline => "timeline",
|
||||
Self::BannerPattern => "banner_pattern",
|
||||
Self::GameEvent => "game_event",
|
||||
Self::Item => "item",
|
||||
Self::Dialog => "dialog",
|
||||
Self::PointOfInterestType => "point_of_interest_type",
|
||||
Self::Fluid => "fluid",
|
||||
Self::Instrument => "instrument",
|
||||
Self::WorldgenBiome => "worldgen/biome",
|
||||
Self::Enchantment => "enchantment",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12886,11 +12889,13 @@ pub mod PointOfInterestType {
|
||||
pub(crate) static POINTOFINTERESTTYPE_TAGS: phf::Map<&'static str, &'static Tag> = phf::phf_map! { "minecraft:acquirable_job_site" => & PointOfInterestType :: MINECRAFT_ACQUIRABLE_JOB_SITE , "minecraft:bee_home" => & PointOfInterestType :: MINECRAFT_BEE_HOME , "minecraft:village" => & PointOfInterestType :: MINECRAFT_VILLAGE };
|
||||
#[allow(non_snake_case)]
|
||||
pub mod Timeline {
|
||||
pub const MINECRAFT_IN_END: super::Tag = (&["villager_schedule"], &[]);
|
||||
pub const MINECRAFT_IN_NETHER: super::Tag = (&["villager_schedule"], &[]);
|
||||
pub const MINECRAFT_IN_OVERWORLD: super::Tag =
|
||||
(&["villager_schedule", "day", "moon", "early_game"], &[]);
|
||||
pub const MINECRAFT_UNIVERSAL: super::Tag = (&["villager_schedule"], &[]);
|
||||
pub const MINECRAFT_IN_END: super::Tag = (&["villager_schedule"], &[3u16]);
|
||||
pub const MINECRAFT_IN_NETHER: super::Tag = (&["villager_schedule"], &[3u16]);
|
||||
pub const MINECRAFT_IN_OVERWORLD: super::Tag = (
|
||||
&["villager_schedule", "day", "moon", "early_game"],
|
||||
&[3u16, 0u16, 2u16, 1u16],
|
||||
);
|
||||
pub const MINECRAFT_UNIVERSAL: super::Tag = (&["villager_schedule"], &[3u16]);
|
||||
}
|
||||
pub(crate) static TIMELINE_TAGS: phf::Map<&'static str, &'static Tag> = phf::phf_map! { "minecraft:in_end" => & Timeline :: MINECRAFT_IN_END , "minecraft:in_nether" => & Timeline :: MINECRAFT_IN_NETHER , "minecraft:in_overworld" => & Timeline :: MINECRAFT_IN_OVERWORLD , "minecraft:universal" => & Timeline :: MINECRAFT_UNIVERSAL };
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -32,7 +32,12 @@ impl ClientPacket for CUpdateTags<'_> {
|
||||
write.write_list(self.tags, |p, registry_key| {
|
||||
p.write_string(&format!("minecraft:{}", registry_key.identifier_string(),))?;
|
||||
|
||||
let values = get_registry_key_tags(*version, *registry_key).unwrap();
|
||||
let Some(values) = get_registry_key_tags(*version, *registry_key) else {
|
||||
// no tags defined for that registry key in this version
|
||||
// write an empty list and continue
|
||||
p.write_var_int(&VarInt::from(0))?;
|
||||
return Ok(());
|
||||
};
|
||||
p.write_var_int(&values.len().try_into().map_err(|_| {
|
||||
WritingError::Message(format!("{} isn't representable as a VarInt", values.len()))
|
||||
})?)?;
|
||||
|
||||
@@ -179,10 +179,24 @@ impl JavaClient {
|
||||
pumpkin_data::tag::RegistryKey::EntityType,
|
||||
pumpkin_data::tag::RegistryKey::Dialog,
|
||||
];
|
||||
|
||||
// optionally include timeline/dimension_type if there are any tags to send
|
||||
if self.version.load().protocol_version() >= MinecraftVersion::V_1_21_11.protocol_version()
|
||||
&& let Some(map) = pumpkin_data::tag::get_registry_key_tags(
|
||||
self.version.load(),
|
||||
pumpkin_data::tag::RegistryKey::Timeline,
|
||||
)
|
||||
&& !map.is_empty()
|
||||
{
|
||||
tags.push(pumpkin_data::tag::RegistryKey::Timeline);
|
||||
}
|
||||
if let Some(map) = pumpkin_data::tag::get_registry_key_tags(
|
||||
self.version.load(),
|
||||
pumpkin_data::tag::RegistryKey::DimensionType,
|
||||
) && !map.is_empty()
|
||||
{
|
||||
tags.push(pumpkin_data::tag::RegistryKey::DimensionType);
|
||||
}
|
||||
self.send_packet_now(&CUpdateTags::new(&tags)).await;
|
||||
|
||||
// We are done with configuring
|
||||
|
||||
Reference in New Issue
Block a user