mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
feat: add block state data to all palettes
This commit is contained in:
parent
f1d844afbe
commit
cfb77088b6
21 changed files with 57289 additions and 26 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
using MinecraftClient.Mapping;
|
||||||
using MinecraftClient.Mapping.BlockPalettes;
|
using MinecraftClient.Mapping.BlockPalettes;
|
||||||
|
|
||||||
namespace MinecraftClient.Tests;
|
namespace MinecraftClient.Tests;
|
||||||
|
|
@ -40,4 +41,68 @@ public sealed class BlockStatePropertiesTests
|
||||||
|
|
||||||
Assert.Empty(properties);
|
Assert.Empty(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData<string, BlockPalette> ModernPalettes => new()
|
||||||
|
{
|
||||||
|
{ "1.13.2", new Palette113() },
|
||||||
|
{ "1.14.4", new Palette114() },
|
||||||
|
{ "1.15.2", new Palette115() },
|
||||||
|
{ "1.16.5", new Palette116() },
|
||||||
|
{ "1.17.1", new Palette117() },
|
||||||
|
{ "1.19.2", new Palette119() },
|
||||||
|
{ "1.19.3", new Palette1193() },
|
||||||
|
{ "1.19.4", new Palette1194() },
|
||||||
|
{ "1.20", new Palette120() },
|
||||||
|
{ "1.20.4", new Palette1204() },
|
||||||
|
{ "1.20.6", new Palette1206() },
|
||||||
|
{ "1.21.2", new Palette1212() },
|
||||||
|
{ "1.21.4", new Palette1214() },
|
||||||
|
{ "1.21.5", new Palette1215() },
|
||||||
|
{ "1.21.6", new Palette1216() },
|
||||||
|
{ "1.21.9", new Palette1219() },
|
||||||
|
{ "26.1", new Palette261() },
|
||||||
|
{ "26.2", new Palette262() }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ModernPalettes))]
|
||||||
|
public void EveryModernPaletteExposesOakLogAxis(string version, BlockPalette palette)
|
||||||
|
{
|
||||||
|
bool foundExpectedState = false;
|
||||||
|
|
||||||
|
for (int stateId = 0; stateId <= ushort.MaxValue; stateId++)
|
||||||
|
{
|
||||||
|
if (palette.FromId(stateId) != Material.OakLog)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
IReadOnlyDictionary<string, string> properties = palette.GetStateProperties(stateId);
|
||||||
|
if (properties.TryGetValue("axis", out string? axis) && axis == "x")
|
||||||
|
{
|
||||||
|
foundExpectedState = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(foundExpectedState, $"Minecraft {version} did not expose oak_log[axis=x]");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LegacyPaletteExposesPackedMetadata()
|
||||||
|
{
|
||||||
|
BlockPalette previousPalette = Block.Palette;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Block.Palette = new Palette112();
|
||||||
|
Block block = new(17, 4);
|
||||||
|
|
||||||
|
IReadOnlyDictionary<string, string> properties = block.GetStateProperties();
|
||||||
|
|
||||||
|
Assert.Equal(276, block.StateId);
|
||||||
|
Assert.Equal("4", properties["metadata"]);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Block.Palette = previousPalette;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,14 @@ namespace MinecraftClient.Mapping
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<string, string> GetStateProperties()
|
public IReadOnlyDictionary<string, string> GetStateProperties()
|
||||||
{
|
{
|
||||||
|
if (Palette.IdHasMetadata)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["metadata"] = BlockMeta.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return Palette.GetStateProperties(StateId);
|
return Palette.GetStateProperties(StateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -2402,6 +2402,7 @@ namespace MinecraftClient.Mapping.BlockPalettes
|
||||||
materials[i] = Material.FireflyBush;
|
materials[i] = Material.FireflyBush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// <auto-generated block-state-properties>
|
||||||
private static readonly BlockStateDefinition[] stateDefinitions =
|
private static readonly BlockStateDefinition[] stateDefinitions =
|
||||||
[
|
[
|
||||||
new(8, 2,
|
new(8, 2,
|
||||||
|
|
@ -6856,6 +6857,7 @@ namespace MinecraftClient.Mapping.BlockPalettes
|
||||||
new("tip", ["true", "false"], 1)
|
new("tip", ["true", "false"], 1)
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
// </auto-generated block-state-properties>
|
||||||
|
|
||||||
protected override Dictionary<int, Material> GetDict()
|
protected override Dictionary<int, Material> GetDict()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,39 @@ import json
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from collections.abc import Sequence
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
OUTPUT_DIR = (Path(__file__).resolve().parent.parent /
|
OUTPUT_DIR = (Path(__file__).resolve().parent.parent /
|
||||||
"MinecraftClient" / "Mapping" / "BlockPalettes")
|
"MinecraftClient" / "Mapping" / "BlockPalettes")
|
||||||
MATERIAL_CS = OUTPUT_DIR.parent / "Material.cs"
|
MATERIAL_CS = OUTPUT_DIR.parent / "Material.cs"
|
||||||
|
|
||||||
|
# Minecraft renamed these registry keys after the corresponding MCC Material
|
||||||
|
# names had already stabilized. Keep historical reports mapped to the current
|
||||||
|
# enum names instead of generating references to members that do not exist.
|
||||||
|
MATERIAL_NAME_ALIASES = {
|
||||||
|
"Grass": "ShortGrass",
|
||||||
|
"GrassPath": "DirtPath",
|
||||||
|
"Sign": "OakSign",
|
||||||
|
"WallSign": "OakWallSign",
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_FILE_ALIASES = {
|
||||||
|
"120": "BlockPalette120.cs",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def mc_name_to_csharp(mc_name: str) -> str:
|
def mc_name_to_csharp(mc_name: str) -> str:
|
||||||
"""Convert minecraft:snake_case to PascalCase C# enum name."""
|
"""Convert minecraft:snake_case to PascalCase C# enum name."""
|
||||||
name = mc_name.removeprefix("minecraft:")
|
name = mc_name.removeprefix("minecraft:")
|
||||||
return "".join(word.capitalize() for word in name.split("_"))
|
csharp_name = "".join(word.capitalize() for word in name.split("_"))
|
||||||
|
return MATERIAL_NAME_ALIASES.get(csharp_name, csharp_name)
|
||||||
|
|
||||||
|
|
||||||
def load_known_materials() -> set[str]:
|
def load_known_materials() -> set[str]:
|
||||||
known = set()
|
known = set()
|
||||||
if MATERIAL_CS.exists():
|
if MATERIAL_CS.exists():
|
||||||
with open(MATERIAL_CS) as f:
|
with MATERIAL_CS.open(encoding="utf-8-sig") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
m = re.match(r'\s+(\w+),?\s*$', line)
|
m = re.match(r'\s+(\w+),?\s*$', line)
|
||||||
if m:
|
if m:
|
||||||
|
|
@ -89,6 +105,74 @@ def csharp_string(value: str) -> str:
|
||||||
return json.dumps(value, ensure_ascii=False)
|
return json.dumps(value, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def render_state_definitions(
|
||||||
|
block_ranges: Sequence[tuple[int, int, str, list[tuple[str, list[str], int]]]],
|
||||||
|
) -> tuple[list[str], int]:
|
||||||
|
"""Render the generated state-property section and return its definition count."""
|
||||||
|
lines = [
|
||||||
|
" // <auto-generated block-state-properties>",
|
||||||
|
" private static readonly BlockStateDefinition[] stateDefinitions =",
|
||||||
|
" [",
|
||||||
|
]
|
||||||
|
property_definition_count = 0
|
||||||
|
for min_s, max_s, _, properties in block_ranges:
|
||||||
|
if not properties:
|
||||||
|
continue
|
||||||
|
|
||||||
|
property_definition_count += 1
|
||||||
|
lines.append(f" new({min_s}, {max_s - min_s + 1},")
|
||||||
|
lines.append(" [")
|
||||||
|
for index, (name, values, stride) in enumerate(properties):
|
||||||
|
encoded_values = ", ".join(csharp_string(value) for value in values)
|
||||||
|
suffix = "," if index < len(properties) - 1 else ""
|
||||||
|
lines.append(f" new({csharp_string(name)}, [{encoded_values}], {stride}){suffix}")
|
||||||
|
lines.append(" ]),")
|
||||||
|
|
||||||
|
lines += [
|
||||||
|
" ];",
|
||||||
|
" // </auto-generated block-state-properties>",
|
||||||
|
]
|
||||||
|
return lines, property_definition_count
|
||||||
|
|
||||||
|
|
||||||
|
def update_existing_palette(output_path: Path, state_lines: list[str]) -> bool:
|
||||||
|
"""Replace only generated state metadata while preserving established material mappings."""
|
||||||
|
if not output_path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
source = output_path.read_text(encoding="utf-8")
|
||||||
|
dictionary_method = " protected override Dictionary<int, Material> GetDict()"
|
||||||
|
dictionary_index = source.find(dictionary_method)
|
||||||
|
if dictionary_index < 0:
|
||||||
|
raise ValueError(f"{output_path} does not contain the expected GetDict method")
|
||||||
|
|
||||||
|
generated_start = source.find(" // <auto-generated block-state-properties>")
|
||||||
|
legacy_start = source.find(" private static readonly BlockStateDefinition[] stateDefinitions =")
|
||||||
|
section_start = generated_start if generated_start >= 0 else legacy_start
|
||||||
|
if section_start < 0:
|
||||||
|
section_start = dictionary_index
|
||||||
|
|
||||||
|
prefix = source[:section_start].rstrip()
|
||||||
|
suffix = source[dictionary_index:]
|
||||||
|
state_override = """ protected override BlockStateDefinition[] GetStateDefinitions()
|
||||||
|
{
|
||||||
|
return stateDefinitions;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
suffix = suffix.replace("\n" + state_override, "", 1)
|
||||||
|
|
||||||
|
class_end = suffix.rfind("\n }\n}")
|
||||||
|
if class_end < 0:
|
||||||
|
raise ValueError(f"{output_path} does not contain the expected class terminator")
|
||||||
|
suffix = suffix[:class_end].rstrip() + "\n\n" + state_override.rstrip() + suffix[class_end:]
|
||||||
|
|
||||||
|
output_path.write_text(
|
||||||
|
prefix + "\n\n" + "\n".join(state_lines) + "\n\n" + suffix,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
@ -101,7 +185,7 @@ def main():
|
||||||
print(f"Error: {blocks_json} not found")
|
print(f"Error: {blocks_json} not found")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with open(blocks_json) as f:
|
with blocks_json.open(encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
# Build block ranges and compact state-property definitions, sorted by min state.
|
# Build block ranges and compact state-property definitions, sorted by min state.
|
||||||
|
|
@ -130,7 +214,15 @@ def main():
|
||||||
print("Insert them in alphabetical order within the enum.")
|
print("Insert them in alphabetical order within the enum.")
|
||||||
|
|
||||||
class_name = f"Palette{class_suffix}"
|
class_name = f"Palette{class_suffix}"
|
||||||
output_path = OUTPUT_DIR / f"{class_name}.cs"
|
output_path = OUTPUT_DIR / OUTPUT_FILE_ALIASES.get(class_suffix, f"{class_name}.cs")
|
||||||
|
state_lines, property_definition_count = render_state_definitions(block_ranges)
|
||||||
|
|
||||||
|
if update_existing_palette(output_path, state_lines):
|
||||||
|
print(
|
||||||
|
f"Updated {output_path} with {property_definition_count} property definitions "
|
||||||
|
"while preserving material mappings"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"using System.Collections.Generic;",
|
"using System.Collections.Generic;",
|
||||||
|
|
@ -152,28 +244,10 @@ def main():
|
||||||
lines += [
|
lines += [
|
||||||
" }",
|
" }",
|
||||||
"",
|
"",
|
||||||
" private static readonly BlockStateDefinition[] stateDefinitions =",
|
*state_lines,
|
||||||
" [",
|
|
||||||
]
|
|
||||||
|
|
||||||
property_definition_count = 0
|
|
||||||
for min_s, max_s, _, properties in block_ranges:
|
|
||||||
if not properties:
|
|
||||||
continue
|
|
||||||
|
|
||||||
property_definition_count += 1
|
|
||||||
lines.append(f" new({min_s}, {max_s - min_s + 1},")
|
|
||||||
lines.append(" [")
|
|
||||||
property_items = properties
|
|
||||||
for index, (name, values, stride) in enumerate(property_items):
|
|
||||||
encoded_values = ", ".join(csharp_string(value) for value in values)
|
|
||||||
suffix = "," if index < len(property_items) - 1 else ""
|
|
||||||
lines.append(f" new({csharp_string(name)}, [{encoded_values}], {stride}){suffix}")
|
|
||||||
lines.append(" ]),")
|
|
||||||
|
|
||||||
lines += [
|
|
||||||
" ];",
|
|
||||||
"",
|
"",
|
||||||
|
]
|
||||||
|
lines += [
|
||||||
" protected override Dictionary<int, Material> GetDict()",
|
" protected override Dictionary<int, Material> GetDict()",
|
||||||
" {",
|
" {",
|
||||||
" return materials;",
|
" return materials;",
|
||||||
|
|
@ -188,7 +262,7 @@ def main():
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
output_path.write_text("\n".join(lines))
|
output_path.write_text("\n".join(lines), encoding="utf-8")
|
||||||
print(
|
print(
|
||||||
f"Generated {output_path} with {len(block_ranges)} blocks, "
|
f"Generated {output_path} with {len(block_ranges)} blocks, "
|
||||||
f"{max_state + 1} total states, and {property_definition_count} property definitions"
|
f"{max_state + 1} total states, and {property_definition_count} property definitions"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue