c46ac98b1a
Entity, block entity and item schemas below 1.11 (CamelCase ids, Equipment, Riding, numeric item ids), their read hooks, and the fixes: equipment, item ids, potions, spawn eggs, spawners, minecarts, UUIDs, health, saddles, paintings, riding, signs and books, the 1.10 splits, id namespacing, banners, shulkers, totems. 1.11.2 passes the bands 1343 and 1631. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
184 lines
8.6 KiB
Python
184 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Copy the lookup tables of vanilla's flattening-era DataFixers (1.12.2 -> 1.13.2) into
|
|
crates/horizon-world/src/datafix/flat_tables.rs, so that the Rust fixes use exactly vanilla's
|
|
data. Run from the repository root; the vanilla sources are the ones under .local/minecraft.
|
|
|
|
gen_flat_tables.py [-o OUT]
|
|
"""
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
FIXES = ROOT / '.local' / 'minecraft' / '26.3' / 'sources' / 'server' / 'net' / 'minecraft' / 'util' / 'datafix' / 'fixes'
|
|
OUT = ROOT / 'crates' / 'horizon-world' / 'src' / 'datafix' / 'flat_tables.rs'
|
|
|
|
STR = r'"((?:[^"\\]|\\.)*)"'
|
|
|
|
|
|
def declaration(text, var):
|
|
"""The source of `NAME = ...;` for a field called `var`."""
|
|
m = re.search(r'\b' + re.escape(var) + r'\s*=', text)
|
|
if not m:
|
|
sys.exit(f'declaration {var} not found')
|
|
depth = 0
|
|
i = m.end()
|
|
while True:
|
|
c = text[i]
|
|
if c in '({[':
|
|
depth += 1
|
|
elif c in ')}]':
|
|
depth -= 1
|
|
elif c == '"':
|
|
i = text.index('"', i + 1)
|
|
while text[i - 1] == '\\':
|
|
i = text.index('"', i + 1)
|
|
elif c == ';' and depth == 0:
|
|
return text[m.end():i]
|
|
i += 1
|
|
|
|
|
|
def pairs(path, var, value_int=False, key_int=False):
|
|
body = declaration((FIXES / path).read_text(), var)
|
|
key = r'(\d+)' if key_int else STR
|
|
value = r'(\d+)' if value_int else STR
|
|
out = re.findall(r'put\(\s*' + key + r',\s*' + value + r'\s*\)', body)
|
|
if not out:
|
|
sys.exit(f'{path} {var}: no entries')
|
|
return out
|
|
|
|
|
|
def pairs_array(path, var, nullable=False):
|
|
"""`map[N] = "x";` entries of an array declaration."""
|
|
text = (FIXES / path).read_text()
|
|
start = text.index(var)
|
|
body = text[start:text.index('});', start)]
|
|
out = re.findall(r'map\[(\d+)\]\s*=\s*' + STR + r';', body)
|
|
if nullable:
|
|
out = [(i, v) for i, v in out]
|
|
if not out:
|
|
sys.exit(f'{path} {var}: no entries')
|
|
return out
|
|
|
|
|
|
def strings(path, var, call):
|
|
body = declaration((FIXES / path).read_text(), var)
|
|
out = re.findall(call + r'\(\s*' + STR + r'\s*\)', body) if call else re.findall(STR, body)
|
|
if not out:
|
|
sys.exit(f'{path} {var}: no entries')
|
|
return out
|
|
|
|
|
|
def rs_str(s):
|
|
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
|
|
|
|
|
|
def emit_pairs(name, entries, int_key=False, int_value=False, doc=''):
|
|
lines = [f'/// {doc}'] if doc else []
|
|
kt = 'i32' if int_key else '&str'
|
|
vt = 'i32' if int_value else '&str'
|
|
lines.append(f'pub static {name}: &[({kt}, {vt})] = &[')
|
|
for k, v in entries:
|
|
a = k if int_key else rs_str(k)
|
|
b = v if int_value else rs_str(v)
|
|
lines.append(f' ({a}, {b}),')
|
|
lines.append('];')
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def emit_strs(name, entries, doc=''):
|
|
lines = [f'/// {doc}'] if doc else []
|
|
lines.append(f'pub static {name}: &[&str] = &[')
|
|
lines += [f' {rs_str(s)},' for s in entries]
|
|
lines.append('];')
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def schema_entities(schema_file, method='registerEntities'):
|
|
"""The entity (or block entity) ids a schema's `registerEntities` registers."""
|
|
text = (FIXES.parent / 'schemas' / schema_file).read_text()
|
|
start = text.index(method)
|
|
body = text[start:text.index('return map;', start)]
|
|
names = re.findall(r'(?:registerMob|registerSimple|registerThrowableProjectile|registerMinecart|registerInventory)\(schema, map, "([^"]+)"', body)
|
|
names += re.findall(r'schema\.registerSimple\(map, "([^"]+)"', body)
|
|
names += re.findall(r'schema\.register\(\s*map,\s*"([^"]+)"', body)
|
|
return sorted(set(names))
|
|
|
|
|
|
def structure_renames():
|
|
text = (FIXES / 'ChunkStructuresTemplateRenameFix.java').read_text()
|
|
out = []
|
|
for m in re.finditer(r'\.put\(\s*' + STR + r',\s*Pair\.of\(\s*' + STR + r',\s*ImmutableMap\.<String, String>builder\(\)((?:\s*\.put\(' + STR + r',\s*' + STR + r'\))*)\s*\.build\(\)', text):
|
|
structure, child = m.group(1), m.group(2)
|
|
entries = re.findall(r'\.put\(' + STR + r',\s*' + STR + r'\)', m.group(3))
|
|
out.append((structure, child, entries))
|
|
if len(out) != 4:
|
|
sys.exit(f'expected 4 structure renames, found {len(out)}')
|
|
lines = ['/// `ChunkStructuresTemplateRenameFix.RENAMES`: structure id, child id, template renames.',
|
|
'pub static STRUCTURE_TEMPLATE_RENAMES: &[(&str, &str, &[(&str, &str)])] = &[']
|
|
for structure, child, entries in out:
|
|
lines.append(f' ({rs_str(structure)}, {rs_str(child)}, &[')
|
|
lines += [f' ({rs_str(a)}, {rs_str(b)}),' for a, b in entries]
|
|
lines.append(' ]),')
|
|
lines.append('];')
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('-o', '--out', default=str(OUT))
|
|
args = p.parse_args()
|
|
parts = [
|
|
'//! Lookup tables of the DataFixers of 1.13 (DataVersion 1343 to 1631), copied from vanilla\'s\n'
|
|
'//! sources by `tools/gen_flat_tables.py`. Do not edit.\n',
|
|
emit_pairs('ITEM_FLATTENING', pairs('ItemStackTheFlatteningFix.java', 'MAP'),
|
|
doc='`ItemStackTheFlatteningFix.MAP`: `id.damage` to the flattened item.'),
|
|
emit_strs('ITEM_DAMAGE_IDS', strings('ItemStackTheFlatteningFix.java', 'DAMAGE_IDS', ''),
|
|
doc='`ItemStackTheFlatteningFix.DAMAGE_IDS`: items that keep their damage in `tag.Damage`.'),
|
|
emit_pairs('BLOCK_IDS', pairs('EntityBlockStateFix.java', 'MAP', value_int=True), int_value=True,
|
|
doc='`EntityBlockStateFix.MAP`: 1.12 block name to numeric id.'),
|
|
emit_pairs('ITEM_IDS', pairs('ItemIdFix.java', 'ITEM_NAMES', key_int=True), int_key=True,
|
|
doc='`ItemIdFix.ITEM_NAMES`: numeric item id to name.'),
|
|
emit_strs('SPECIAL_OBJECTIVE_CRITERIA', strings('StatsCounterFix.java', 'SPECIAL_OBJECTIVE_CRITERIA', '')),
|
|
emit_strs('STATS_SKIP', strings('StatsCounterFix.java', 'SKIP', 'add')),
|
|
emit_pairs('STATS_CUSTOM', pairs('StatsCounterFix.java', 'CUSTOM_MAP')),
|
|
emit_pairs('STATS_ITEM_KEYS', pairs('StatsCounterFix.java', 'ITEM_KEYS')),
|
|
emit_pairs('STATS_ENTITY_KEYS', pairs('StatsCounterFix.java', 'ENTITY_KEYS')),
|
|
emit_pairs('STATS_ENTITIES', pairs('StatsCounterFix.java', 'ENTITIES')),
|
|
emit_pairs('RECIPES', pairs('RecipesFix.java', 'RECIPES')),
|
|
emit_pairs('RECIPES_RENAMENING', pairs('RecipesRenameningFix.java', 'RECIPES')),
|
|
emit_pairs('ADVANCEMENTS', pairs('AdvancementsFix.java', 'RENAMES')),
|
|
emit_pairs('GENERATOR_BIOMES', pairs('LevelDataGeneratorOptionsFix.java', 'MAP')),
|
|
emit_pairs('SPAWN_EGGS', pairs('ItemStackSpawnEggFix.java', 'MAP')),
|
|
emit_pairs('ENCHANTMENTS', pairs('ItemStackEnchantmentNamesFix.java', 'MAP', key_int=True), int_key=True),
|
|
emit_pairs('CORAL', pairs('RenamedCoralFix.java', 'RENAMED_IDS')),
|
|
emit_pairs('CORAL_FANS', pairs('RenamedCoralFansFix.java', 'RENAMED_IDS')),
|
|
emit_pairs('THE_RENAMENING_ENTITIES', pairs('EntityTheRenameningFix.java', 'RENAMED_IDS')),
|
|
emit_pairs('THE_RENAMENING_BLOCKS', pairs('EntityTheRenameningFix.java', 'RENAMED_BLOCKS')),
|
|
emit_pairs('COD_SALMON', pairs('EntityCodSalmonFix.java', 'RENAMED_IDS')),
|
|
emit_pairs('COD_SALMON_EGGS', pairs('EntityCodSalmonFix.java', 'RENAMED_EGG_IDS')),
|
|
emit_strs('ENTITIES_V1460', schema_entities('V1460.java'),
|
|
doc='The entity ids V1460 registers (V705 has the same ones).'),
|
|
emit_strs('ENTITIES_V99', schema_entities('V99.java'), doc='The entity ids V99 registers.'),
|
|
emit_strs('BLOCK_ENTITIES_V99', schema_entities('V99.java', 'registerBlockEntities'),
|
|
doc='The block entity ids V99 registers.'),
|
|
emit_pairs('SPAWN_EGG_ENTITIES', pairs_array('ItemSpawnEggFix.java', 'ID_TO_ENTITY'), int_key=True,
|
|
doc='`ItemSpawnEggFix.ID_TO_ENTITY`: spawn egg damage to entity id.'),
|
|
emit_pairs('ENTITY_IDS', pairs('EntityIdFix.java', 'ID_MAP'), doc='`EntityIdFix.ID_MAP`.'),
|
|
emit_pairs('BLOCK_ENTITY_IDS', pairs('BlockEntityIdFix.java', 'ID_MAP'), doc='`BlockEntityIdFix.ID_MAP`.'),
|
|
emit_pairs('ITEM_TO_BLOCK_ENTITY_V99', pairs('../schemas/V99.java', 'ITEM_TO_BLOCKENTITY'),
|
|
doc='`V99.ITEM_TO_BLOCKENTITY`.'),
|
|
emit_pairs('ITEM_POTIONS', pairs_array('ItemPotionFix.java', 'POTIONS', nullable=True), int_key=True,
|
|
doc='`ItemPotionFix.POTIONS`: damage & 127 to potion (absent: null).'),
|
|
structure_renames(),
|
|
]
|
|
Path(args.out).write_text('\n\n'.join(parts) + '\n')
|
|
subprocess.run(['rustfmt', '--edition', '2024', args.out], check=True)
|
|
print('wrote', args.out)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|