mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
chore: add reusable version adaptation scripts
Add tools/ directory with Python scripts for comparing Minecraft version registries and generating MCC palette files: - diff_registries.py: Compare Items/EntityTypes/Blocks/DataComponents/ EntityDataSerializers between two decompiled MC versions, reporting which palettes need updating with ID shift analysis. - gen_item_palette.py: Generate ItemPaletteXXX.cs from Items.java field declaration order, with name validation against ItemType.cs. - gen_entity_metadata_palette.py: Generate EntityMetadataPaletteXXX.cs from EntityDataSerializers.java registration order. - README.md: Usage documentation for all scripts. Made-with: Cursor
This commit is contained in:
parent
e1cb18c6f8
commit
7609832976
6 changed files with 667 additions and 0 deletions
130
.cursor/skills/mcc-version-adaptation/SKILL.md
Normal file
130
.cursor/skills/mcc-version-adaptation/SKILL.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
name: mcc-version-adaptation
|
||||
description: Adapt MCC palettes and protocol handling for a new Minecraft version. Use when the user wants to add support for a new MC version, compare version registries, update item/entity/block/metadata palettes, or fix protocol mismatches between MC versions.
|
||||
---
|
||||
|
||||
# MCC Version Adaptation
|
||||
|
||||
Systematic workflow for updating Minecraft Console Client to support a new Minecraft version, focusing on palette/registry changes and entity metadata.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Decompiled server source for both the old and new MC versions in `$MCC_REPO/MinecraftOfficial/<version>-decompiled/`
|
||||
- If missing, decompile first:
|
||||
```bash
|
||||
cd $MCC_REPO/MinecraftOfficial
|
||||
java -jar MinecraftDecompiler.jar --version <ver> --side SERVER \
|
||||
--decompile --output <ver>-remapped.jar --decompiled-output <ver>-decompiled
|
||||
```
|
||||
|
||||
## Step 1: Run Registry Diff
|
||||
|
||||
```bash
|
||||
python3 $MCC_REPO/tools/diff_registries.py <old_ver> <new_ver>
|
||||
```
|
||||
|
||||
This compares five registries and reports which need palette updates:
|
||||
|
||||
| Registry | MCC File | When to Update |
|
||||
|----------|----------|----------------|
|
||||
| Items.java | `ItemPalettes/ItemPaletteXXX.cs` | New/removed/reordered items |
|
||||
| EntityType.java | `EntityPalettes/EntityPaletteXXX.cs` | New/removed/reordered entity types |
|
||||
| Blocks.java | `BlockPalettes/BlockPaletteXXX.cs` | New/removed/reordered blocks |
|
||||
| DataComponents.java | `StructuredComponents/StructuredComponentsRegistryXXX.cs` | New/reordered components |
|
||||
| EntityDataSerializers.java | `EntityMetadataPalettes/EntityMetadataPaletteXXX.cs` | New/reordered serializer types |
|
||||
|
||||
## Step 2: Generate Updated Palettes
|
||||
|
||||
For registries marked "PALETTE UPDATE NEEDED":
|
||||
|
||||
### Item Palette
|
||||
```bash
|
||||
python3 $MCC_REPO/tools/gen_item_palette.py <new_ver> <suffix>
|
||||
# e.g., gen_item_palette.py 1.21.1 121
|
||||
```
|
||||
- If new items are reported missing from `ItemType.cs`, add them to the enum in alphabetical order.
|
||||
- The script auto-generates the C# palette file.
|
||||
|
||||
### Entity Metadata Palette
|
||||
```bash
|
||||
python3 $MCC_REPO/tools/gen_entity_metadata_palette.py <new_ver> <suffix>
|
||||
# e.g., gen_entity_metadata_palette.py 1.20.6 1206
|
||||
```
|
||||
- If new serializer types appear as UNMAPPED, add them to both:
|
||||
1. The script's `FIELD_TO_ENUM` dictionary
|
||||
2. MCC's `EntityMetaDataType.cs` enum
|
||||
3. `DataTypes.cs` read logic (add a `case` to consume the correct bytes)
|
||||
|
||||
### Entity/Block Palettes
|
||||
No generator script yet — these change rarely. When needed, manually create by following the pattern of existing palette files, using `register("name", ...)` call order from the decompiled source.
|
||||
|
||||
### DataComponents / StructuredComponents
|
||||
Compare `DataComponents.java` registration order. If new components appear, update `StructuredComponentsRegistryXXX.cs`. For new component types, implement corresponding reader in `StructuredComponents/Components/`.
|
||||
|
||||
## Step 3: Update Version Routing
|
||||
|
||||
After creating palette files, update version selection logic:
|
||||
|
||||
| Palette Type | Routing Location |
|
||||
|-------------|-----------------|
|
||||
| Item | `Protocol18.cs` → `itemPalette` switch expression |
|
||||
| Entity | `Protocol18.cs` → `entityPalette` switch expression |
|
||||
| Block | `Protocol18.cs` → `blockPalette` initialization |
|
||||
| EntityMetadata | `EntityMetadataPalette.cs` → `GetPalette()` switch |
|
||||
| DataComponents | `StructuredComponentsRegistry.cs` → factory/routing |
|
||||
|
||||
Pattern: add a new `>= MC_X_Y_Z_Version => new XxxPaletteXYZ()` case.
|
||||
|
||||
## Step 4: Check Variant Encoding Changes
|
||||
|
||||
For entity types that use variant serializers (Cat, Wolf, Frog, Painting), check if the codec changed between versions by inspecting:
|
||||
|
||||
- `EntityDataSerializers.java` — look at how each `*_VARIANT` field is constructed
|
||||
- Key codecs:
|
||||
- `ByteBufCodecs.holderRegistry()` → wire format: `VarInt(registry_id)`
|
||||
- `ByteBufCodecs.holder()` → wire format: `VarInt(id+1)` for registered, `VarInt(0) + inline_data` for direct
|
||||
- If codec changed, update `DataTypes.cs` entity metadata reading logic accordingly.
|
||||
|
||||
## Step 5: Handle New EntityDataSerializer Types
|
||||
|
||||
When new serializer types are added (detected in Step 1):
|
||||
|
||||
1. Add enum value to `EntityMetaDataType.cs` with XML doc comment
|
||||
2. Add read logic in `DataTypes.cs` `ReadNextMetadata()`:
|
||||
- Determine byte consumption from the decompiled codec
|
||||
- Examples: VarInt read, list of particles, etc.
|
||||
3. Create the new palette file (Step 2)
|
||||
4. Update palette routing (Step 3)
|
||||
|
||||
## Step 6: Compile and Verify
|
||||
|
||||
```bash
|
||||
dotnet build $MCC_REPO/MinecraftClient.sln -c Release
|
||||
```
|
||||
|
||||
Then connect to a test server of the target version (see `mcc-dev-workflow` skill) and verify:
|
||||
- Successful connection
|
||||
- `/give` new items → check inventory
|
||||
- Summon entities (especially variant types) → no metadata parse errors
|
||||
- Particle effects → no crashes
|
||||
|
||||
## Key Source Files Reference
|
||||
|
||||
| Decompiled Java Source | Purpose |
|
||||
|----------------------|---------|
|
||||
| `world/item/Items.java` | Item registry (field declaration order = ID) |
|
||||
| `world/entity/EntityType.java` | Entity type registry (`register()` call order = ID) |
|
||||
| `world/level/block/Blocks.java` | Block registry (`register()` call order = ID) |
|
||||
| `core/component/DataComponents.java` | Data component registry |
|
||||
| `network/syncher/EntityDataSerializers.java` | Entity metadata type registry (static block order = ID) |
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **ID order matters**: IDs are determined by declaration/registration order, not alphabetical. Always use the decompiled source as ground truth.
|
||||
- **Cross-version jumps**: When MCC skips versions (e.g., 1.20.4→1.20.6), registries from ALL intermediate versions may have changed. Always diff against the actual last-supported version, not the latest palette.
|
||||
- **EntityMetadata type shifts**: A single new serializer type shifts all subsequent IDs, causing widespread metadata parse failures. Symptoms: entity rendering glitches, disconnections, or silent data corruption.
|
||||
- **CUT_STANDSTONE_SLAB**: This is an intentional typo in Minecraft source (should be SANDSTONE). MCC's `ItemType.cs` uses `CutSandstoneSlab` — the gen script handles this via the OVERRIDES dict.
|
||||
|
||||
## Reusable Scripts
|
||||
|
||||
All scripts are in `$MCC_REPO/tools/`. See `tools/README.md` for detailed usage.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -387,6 +387,9 @@ FodyWeavers.xsd
|
|||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Cursor files
|
||||
!.cursor/
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
|
|
|
|||
47
tools/README.md
Normal file
47
tools/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# MCC Version Adaptation Tools
|
||||
|
||||
Scripts for analyzing Minecraft version differences and generating MCC palette files.
|
||||
|
||||
Requires: Python 3.10+, decompiled MC server source in `MinecraftOfficial/<version>-decompiled/`.
|
||||
|
||||
## Decompiling a new MC version
|
||||
|
||||
```bash
|
||||
cd MinecraftOfficial
|
||||
java -jar MinecraftDecompiler.jar --version 1.21.4 --side SERVER \
|
||||
--decompile --output 1.21.4-remapped.jar --decompiled-output 1.21.4-decompiled
|
||||
```
|
||||
|
||||
## diff_registries.py — Compare registries between versions
|
||||
|
||||
Compares Items, EntityTypes, Blocks, DataComponents, and EntityDataSerializers between two MC versions. Reports whether each palette needs updating, lists added/removed entries, and shows ID shift statistics.
|
||||
|
||||
```bash
|
||||
python3 tools/diff_registries.py 1.20.6 1.21.1
|
||||
```
|
||||
|
||||
Output indicates for each registry:
|
||||
- **IDENTICAL** → reuse existing palette
|
||||
- **PALETTE UPDATE NEEDED** → create new palette file + update version routing
|
||||
|
||||
## gen_item_palette.py — Generate ItemPalette C# file
|
||||
|
||||
Reads `Items.java` field declaration order to generate a complete `ItemPaletteXXX.cs`.
|
||||
|
||||
```bash
|
||||
python3 tools/gen_item_palette.py 1.21.1 121
|
||||
# → MinecraftClient/Inventory/ItemPalettes/ItemPalette121.cs
|
||||
```
|
||||
|
||||
Also validates each item name against `ItemType.cs` and warns about missing enum values.
|
||||
|
||||
## gen_entity_metadata_palette.py — Generate EntityMetadataPalette C# file
|
||||
|
||||
Reads `EntityDataSerializers.java` static block registration order to generate `EntityMetadataPaletteXXX.cs`.
|
||||
|
||||
```bash
|
||||
python3 tools/gen_entity_metadata_palette.py 1.20.6 1206
|
||||
# → MinecraftClient/Mapping/EntityMetadataPalettes/EntityMetadataPalette1206.cs
|
||||
```
|
||||
|
||||
The script maps Java field names to MCC's `EntityMetaDataType` enum. If a new serializer type appears that isn't in the mapping table, it will warn you to update both the script's `FIELD_TO_ENUM` dict and MCC's `EntityMetaDataType.cs` enum.
|
||||
221
tools/diff_registries.py
Normal file
221
tools/diff_registries.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Compare Minecraft registry data between two decompiled server versions.
|
||||
|
||||
Compares Items, EntityTypes, Blocks, DataComponents, and EntityDataSerializers
|
||||
to determine which MCC palettes need updating for a new MC version.
|
||||
|
||||
Usage:
|
||||
python3 tools/diff_registries.py <old_version> <new_version>
|
||||
|
||||
Example:
|
||||
python3 tools/diff_registries.py 1.20.6 1.21.1
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
|
||||
|
||||
|
||||
def find_java_file(version_dir: Path, *possible_paths: str) -> Path | None:
|
||||
for p in possible_paths:
|
||||
full = version_dir / p
|
||||
if full.exists():
|
||||
return full
|
||||
return None
|
||||
|
||||
|
||||
def extract_field_names(filepath: Path, pattern: str) -> list[str]:
|
||||
"""Extract field names from public static final declarations."""
|
||||
results = []
|
||||
with open(filepath) as f:
|
||||
for line in f:
|
||||
m = re.match(pattern, line)
|
||||
if m:
|
||||
results.append(m.group(1))
|
||||
return results
|
||||
|
||||
|
||||
def extract_register_multiline(filepath: Path) -> list[str]:
|
||||
"""Extract register("name", ...) calls, handling multiline Java formatting."""
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
flat = re.sub(r'\s+', ' ', content)
|
||||
return re.findall(r'(?:= |return )register\(\s*"([^"]+)"', flat)
|
||||
|
||||
|
||||
def extract_static_register_order(filepath: Path) -> list[str]:
|
||||
"""Extract registerSerializer(FIELD_NAME) calls from the static {} block."""
|
||||
results = []
|
||||
in_static = False
|
||||
with open(filepath) as f:
|
||||
for line in f:
|
||||
if 'static {' in line:
|
||||
in_static = True
|
||||
continue
|
||||
if in_static and 'registerSerializer(' in line:
|
||||
m = re.search(r'registerSerializer\((\w+)\)', line)
|
||||
if m:
|
||||
results.append(m.group(1))
|
||||
if in_static and '}' in line and 'registerSerializer' not in line:
|
||||
break
|
||||
return results
|
||||
|
||||
|
||||
def compare_lists(old: list[str], new: list[str], label: str):
|
||||
"""Compare two ordered lists and report differences."""
|
||||
set_old, set_new = set(old), set(new)
|
||||
added = sorted(set_new - set_old)
|
||||
removed = sorted(set_old - set_new)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {label}")
|
||||
print(f"{'='*60}")
|
||||
print(f" Old: {len(old)} entries, New: {len(new)} entries")
|
||||
|
||||
if not added and not removed:
|
||||
if old == new:
|
||||
print(f" Result: IDENTICAL — no palette update needed")
|
||||
else:
|
||||
print(f" Result: Same set but DIFFERENT ORDER — palette update needed!")
|
||||
for i, (a, b) in enumerate(zip(old, new)):
|
||||
if a != b:
|
||||
print(f" First diff at index {i}: old={a}, new={b}")
|
||||
break
|
||||
return False
|
||||
|
||||
if added:
|
||||
print(f" Added ({len(added)}): {added}")
|
||||
for item in added:
|
||||
idx = new.index(item)
|
||||
prev_name = new[idx - 1] if idx > 0 else "(start)"
|
||||
next_name = new[idx + 1] if idx < len(new) - 1 else "(end)"
|
||||
print(f" \"{item}\" at index {idx}, between \"{prev_name}\" and \"{next_name}\"")
|
||||
if removed:
|
||||
print(f" Removed ({len(removed)}): {removed}")
|
||||
|
||||
common_old = [x for x in old if x in set_new]
|
||||
common_new = [x for x in new if x in set_old]
|
||||
if common_old != common_new:
|
||||
print(f" Common items REORDERED — palette update needed!")
|
||||
else:
|
||||
print(f" Common items have same relative order")
|
||||
|
||||
# ID shift analysis
|
||||
id_old = {name: i for i, name in enumerate(old)}
|
||||
id_new = {name: i for i, name in enumerate(new)}
|
||||
shifted = [(n, id_old[n], id_new[n]) for n in sorted(set_old & set_new) if id_old[n] != id_new[n]]
|
||||
if shifted:
|
||||
from collections import Counter
|
||||
deltas = Counter(new_id - old_id for _, old_id, new_id in shifted)
|
||||
print(f" {len(shifted)} entries with changed IDs, delta distribution: {sorted(deltas.items())}")
|
||||
|
||||
print(f" Result: PALETTE UPDATE NEEDED")
|
||||
return True
|
||||
|
||||
|
||||
def diff_items(old_dir: Path, new_dir: Path):
|
||||
old_f = find_java_file(old_dir, "net/minecraft/world/item/Items.java")
|
||||
new_f = find_java_file(new_dir, "net/minecraft/world/item/Items.java")
|
||||
if not old_f or not new_f:
|
||||
print(" [SKIP] Items.java not found")
|
||||
return
|
||||
pattern = r'\s+public static final Item (\w+)\s*='
|
||||
old = extract_field_names(old_f, pattern)
|
||||
new = extract_field_names(new_f, pattern)
|
||||
compare_lists(old, new, "Items.java (Item registry)")
|
||||
|
||||
|
||||
def diff_entity_types(old_dir: Path, new_dir: Path):
|
||||
old_f = find_java_file(old_dir, "net/minecraft/world/entity/EntityType.java")
|
||||
new_f = find_java_file(new_dir, "net/minecraft/world/entity/EntityType.java")
|
||||
if not old_f or not new_f:
|
||||
print(" [SKIP] EntityType.java not found")
|
||||
return
|
||||
old = extract_register_multiline(old_f)
|
||||
new = extract_register_multiline(new_f)
|
||||
compare_lists(old, new, "EntityType.java (Entity registry)")
|
||||
|
||||
|
||||
def diff_blocks(old_dir: Path, new_dir: Path):
|
||||
old_f = find_java_file(old_dir, "net/minecraft/world/level/block/Blocks.java")
|
||||
new_f = find_java_file(new_dir, "net/minecraft/world/level/block/Blocks.java")
|
||||
if not old_f or not new_f:
|
||||
print(" [SKIP] Blocks.java not found")
|
||||
return
|
||||
old = extract_register_multiline(old_f)
|
||||
new = extract_register_multiline(new_f)
|
||||
compare_lists(old, new, "Blocks.java (Block registry)")
|
||||
|
||||
|
||||
def diff_data_components(old_dir: Path, new_dir: Path):
|
||||
old_f = find_java_file(old_dir, "net/minecraft/core/component/DataComponents.java")
|
||||
new_f = find_java_file(new_dir, "net/minecraft/core/component/DataComponents.java")
|
||||
if not old_f or not new_f:
|
||||
print(" [SKIP] DataComponents.java not found")
|
||||
return
|
||||
old = extract_register_multiline(old_f)
|
||||
new = extract_register_multiline(new_f)
|
||||
needs_update = compare_lists(old, new, "DataComponents.java (StructuredComponents registry)")
|
||||
if needs_update or True:
|
||||
print("\n Registration order (new version):")
|
||||
for i, name in enumerate(new):
|
||||
marker = " <-- NEW" if name not in set(old) else ""
|
||||
print(f" {i}: {name}{marker}")
|
||||
|
||||
|
||||
def diff_entity_data_serializers(old_dir: Path, new_dir: Path):
|
||||
old_f = find_java_file(old_dir, "net/minecraft/network/syncher/EntityDataSerializers.java")
|
||||
new_f = find_java_file(new_dir, "net/minecraft/network/syncher/EntityDataSerializers.java")
|
||||
if not old_f or not new_f:
|
||||
print(" [SKIP] EntityDataSerializers.java not found")
|
||||
return
|
||||
old = extract_static_register_order(old_f)
|
||||
new = extract_static_register_order(new_f)
|
||||
needs_update = compare_lists(old, new, "EntityDataSerializers.java (EntityMetadata palette)")
|
||||
print("\n Registration order (new version):")
|
||||
for i, name in enumerate(new):
|
||||
marker = " <-- NEW" if name not in set(old) else ""
|
||||
print(f" {i}: {name}{marker}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
old_ver, new_ver = sys.argv[1], sys.argv[2]
|
||||
old_dir = DECOMPILED_ROOT / f"{old_ver}-decompiled"
|
||||
new_dir = DECOMPILED_ROOT / f"{new_ver}-decompiled"
|
||||
|
||||
for d, v in [(old_dir, old_ver), (new_dir, new_ver)]:
|
||||
if not d.exists():
|
||||
print(f"Error: {d} not found. Decompile {v} first:")
|
||||
print(f" cd MinecraftOfficial && java -jar MinecraftDecompiler.jar "
|
||||
f"--version {v} --side SERVER --decompile "
|
||||
f"--output {v}-remapped.jar --decompiled-output {v}-decompiled")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Comparing MC {old_ver} → {new_ver}")
|
||||
print(f"Old: {old_dir}")
|
||||
print(f"New: {new_dir}")
|
||||
|
||||
diff_items(old_dir, new_dir)
|
||||
diff_entity_types(old_dir, new_dir)
|
||||
diff_blocks(old_dir, new_dir)
|
||||
diff_data_components(old_dir, new_dir)
|
||||
diff_entity_data_serializers(old_dir, new_dir)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(" Summary")
|
||||
print(f"{'='*60}")
|
||||
print(" Review each section above. For any marked 'PALETTE UPDATE NEEDED',")
|
||||
print(" create a new palette file in MCC and update the version routing.")
|
||||
print(" For 'IDENTICAL' sections, the existing palette can be reused.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
142
tools/gen_entity_metadata_palette.py
Normal file
142
tools/gen_entity_metadata_palette.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate an MCC EntityMetadataPalette C# file from decompiled EntityDataSerializers.java.
|
||||
|
||||
Reads the static {} block registration order to determine serializer IDs,
|
||||
then maps Java field names to MCC's EntityMetaDataType enum values.
|
||||
|
||||
Usage:
|
||||
python3 tools/gen_entity_metadata_palette.py <mc_version> <class_suffix>
|
||||
|
||||
Example:
|
||||
python3 tools/gen_entity_metadata_palette.py 1.20.6 1206
|
||||
# Generates EntityMetadataPalette1206.cs
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
|
||||
OUTPUT_DIR = (Path(__file__).resolve().parent.parent /
|
||||
"MinecraftClient" / "Mapping" / "EntityMetadataPalettes")
|
||||
|
||||
# Java field name → MCC EntityMetaDataType enum name
|
||||
FIELD_TO_ENUM = {
|
||||
"BYTE": "Byte",
|
||||
"INT": "VarInt",
|
||||
"LONG": "VarLong",
|
||||
"FLOAT": "Float",
|
||||
"STRING": "String",
|
||||
"COMPONENT": "Chat",
|
||||
"OPTIONAL_COMPONENT": "OptionalChat",
|
||||
"ITEM_STACK": "Slot",
|
||||
"BOOLEAN": "Boolean",
|
||||
"ROTATIONS": "Rotation",
|
||||
"BLOCK_POS": "Position",
|
||||
"OPTIONAL_BLOCK_POS": "OptionalPosition",
|
||||
"DIRECTION": "Direction",
|
||||
"OPTIONAL_UUID": "OptionalUuid",
|
||||
"BLOCK_STATE": "BlockId",
|
||||
"OPTIONAL_BLOCK_STATE": "OptionalBlockId",
|
||||
"COMPOUND_TAG": "Nbt",
|
||||
"PARTICLE": "Particle",
|
||||
"PARTICLES": "Particles",
|
||||
"VILLAGER_DATA": "VillagerData",
|
||||
"OPTIONAL_UNSIGNED_INT": "OptionalVarInt",
|
||||
"POSE": "Pose",
|
||||
"CAT_VARIANT": "CatVariant",
|
||||
"WOLF_VARIANT": "WolfVariant",
|
||||
"FROG_VARIANT": "FrogVariant",
|
||||
"OPTIONAL_GLOBAL_POS": "OptionalGlobalPosition",
|
||||
"PAINTING_VARIANT": "PaintingVariant",
|
||||
"SNIFFER_STATE": "SnifferState",
|
||||
"ARMADILLO_STATE": "ArmadilloState",
|
||||
"VECTOR3": "Vector3",
|
||||
"QUATERNION": "Quaternion",
|
||||
}
|
||||
|
||||
|
||||
def extract_static_register_order(filepath: Path) -> list[str]:
|
||||
results = []
|
||||
in_static = False
|
||||
with open(filepath) as f:
|
||||
for line in f:
|
||||
if 'static {' in line:
|
||||
in_static = True
|
||||
continue
|
||||
if in_static and 'registerSerializer(' in line:
|
||||
m = re.search(r'registerSerializer\((\w+)\)', line)
|
||||
if m:
|
||||
results.append(m.group(1))
|
||||
if in_static and '}' in line and 'registerSerializer' not in line:
|
||||
break
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
mc_version = sys.argv[1]
|
||||
class_suffix = sys.argv[2]
|
||||
version_dir = DECOMPILED_ROOT / f"{mc_version}-decompiled"
|
||||
eds_java = version_dir / "net" / "minecraft" / "network" / "syncher" / "EntityDataSerializers.java"
|
||||
|
||||
if not eds_java.exists():
|
||||
print(f"Error: {eds_java} not found")
|
||||
sys.exit(1)
|
||||
|
||||
fields = extract_static_register_order(eds_java)
|
||||
print(f"Found {len(fields)} entity data serializers in MC {mc_version}:")
|
||||
|
||||
unmapped = []
|
||||
mappings = []
|
||||
for i, field in enumerate(fields):
|
||||
if field in FIELD_TO_ENUM:
|
||||
enum_name = FIELD_TO_ENUM[field]
|
||||
mappings.append((i, enum_name))
|
||||
print(f" {i}: {field} -> EntityMetaDataType.{enum_name}")
|
||||
else:
|
||||
unmapped.append((i, field))
|
||||
print(f" {i}: {field} -> ??? UNMAPPED")
|
||||
|
||||
if unmapped:
|
||||
print(f"\nWARNING: {len(unmapped)} unmapped fields:")
|
||||
for idx, field in unmapped:
|
||||
print(f" [{idx}] {field}")
|
||||
print("\nAdd entries to FIELD_TO_ENUM in this script and to EntityMetaDataType.cs enum.")
|
||||
|
||||
class_name = f"EntityMetadataPalette{class_suffix}"
|
||||
output_path = OUTPUT_DIR / f"{class_name}.cs"
|
||||
|
||||
lines = [
|
||||
"using System.Collections.Generic;",
|
||||
"",
|
||||
f"namespace MinecraftClient.Mapping.EntityMetadataPalettes;",
|
||||
"",
|
||||
f"public class {class_name} : EntityMetadataPalette",
|
||||
"{",
|
||||
" private readonly Dictionary<int, EntityMetaDataType> entityMetadataMappings = new()",
|
||||
" {",
|
||||
]
|
||||
for idx, enum_name in mappings:
|
||||
lines.append(f" {{ {idx}, EntityMetaDataType.{enum_name} }},")
|
||||
lines += [
|
||||
" };",
|
||||
"",
|
||||
" public override Dictionary<int, EntityMetaDataType> GetEntityMetadataMappingsList()",
|
||||
" {",
|
||||
" return entityMetadataMappings;",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
|
||||
output_path.write_text("\n".join(lines))
|
||||
print(f"\nGenerated {output_path} with {len(mappings)} mappings")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
124
tools/gen_item_palette.py
Normal file
124
tools/gen_item_palette.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate an MCC ItemPalette C# file from decompiled Items.java.
|
||||
|
||||
Reads public static final Item field declarations (which define item IDs
|
||||
by declaration order) and generates a complete C# palette class.
|
||||
|
||||
Usage:
|
||||
python3 tools/gen_item_palette.py <mc_version> <class_suffix>
|
||||
|
||||
Example:
|
||||
python3 tools/gen_item_palette.py 1.21.1 121
|
||||
# Generates ItemPalette121.cs
|
||||
|
||||
The <class_suffix> determines the class name (ItemPalette<suffix>) and
|
||||
should match MCC's naming convention (e.g., 121 for 1.21, 1206 for 1.20.6).
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
|
||||
OUTPUT_DIR = Path(__file__).resolve().parent.parent / "MinecraftClient" / "Inventory" / "ItemPalettes"
|
||||
|
||||
# Java field name → C# ItemType enum name
|
||||
# Most conversions are automatic (SCREAMING_SNAKE → PascalCase).
|
||||
# Add manual overrides here for irregular names.
|
||||
OVERRIDES = {
|
||||
"CUT_STANDSTONE_SLAB": "CutSandstoneSlab", # Mojang typo in source
|
||||
}
|
||||
|
||||
|
||||
def java_to_csharp_name(java_name: str) -> str:
|
||||
"""Convert SCREAMING_SNAKE_CASE Java field name to PascalCase C# enum name."""
|
||||
if java_name in OVERRIDES:
|
||||
return OVERRIDES[java_name]
|
||||
return "".join(word.capitalize() for word in java_name.lower().split("_"))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
mc_version = sys.argv[1]
|
||||
class_suffix = sys.argv[2]
|
||||
version_dir = DECOMPILED_ROOT / f"{mc_version}-decompiled"
|
||||
items_java = version_dir / "net" / "minecraft" / "world" / "item" / "Items.java"
|
||||
|
||||
if not items_java.exists():
|
||||
print(f"Error: {items_java} not found")
|
||||
sys.exit(1)
|
||||
|
||||
pattern = re.compile(r'\s+public static final Item (\w+)\s*=')
|
||||
field_names = []
|
||||
with open(items_java) as f:
|
||||
for line in f:
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
field_names.append(m.group(1))
|
||||
|
||||
print(f"Found {len(field_names)} items in MC {mc_version}")
|
||||
|
||||
# Verify enum name conversion against existing ItemType.cs
|
||||
item_type_cs = OUTPUT_DIR.parent / "ItemType.cs"
|
||||
known_enums = set()
|
||||
if item_type_cs.exists():
|
||||
with open(item_type_cs) as f:
|
||||
for line in f:
|
||||
m = re.match(r'\s+(\w+),?\s*$', line)
|
||||
if m and m.group(1) not in ("Null", "Unknown"):
|
||||
known_enums.add(m.group(1))
|
||||
|
||||
missing = []
|
||||
mappings = []
|
||||
for i, name in enumerate(field_names):
|
||||
cs_name = java_to_csharp_name(name)
|
||||
mappings.append((i, cs_name))
|
||||
if known_enums and cs_name not in known_enums:
|
||||
missing.append((i, name, cs_name))
|
||||
|
||||
if missing:
|
||||
print(f"\nWARNING: {len(missing)} items not found in ItemType.cs enum:")
|
||||
for idx, java_name, cs_name in missing:
|
||||
print(f" [{idx}] {java_name} -> {cs_name}")
|
||||
print("\nYou need to add these to ItemType.cs before the palette will compile.")
|
||||
print("Insert them in alphabetical order within the enum.")
|
||||
|
||||
class_name = f"ItemPalette{class_suffix}"
|
||||
output_path = OUTPUT_DIR / f"{class_name}.cs"
|
||||
|
||||
lines = [
|
||||
"using System.Collections.Generic;",
|
||||
"",
|
||||
"namespace MinecraftClient.Inventory.ItemPalettes",
|
||||
"{",
|
||||
f" public class {class_name} : ItemPalette",
|
||||
" {",
|
||||
" private static readonly Dictionary<int, ItemType> mappings = new();",
|
||||
"",
|
||||
f" static {class_name}()",
|
||||
" {",
|
||||
]
|
||||
for idx, cs_name in mappings:
|
||||
lines.append(f" mappings[{idx}] = ItemType.{cs_name};")
|
||||
lines += [
|
||||
" }",
|
||||
"",
|
||||
" protected override Dictionary<int, ItemType> GetDict()",
|
||||
" {",
|
||||
" return mappings;",
|
||||
" }",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
|
||||
output_path.write_text("\n".join(lines))
|
||||
print(f"Generated {output_path} with {len(mappings)} mappings")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue