"""Build the vanilla entity-texture index for the model viewer. Scans ``MEDIA_ROOT/vanilla/entity/`` (extracted from a Minecraft client jar) and writes ``MEDIA_ROOT/vanilla/entity_index.json`` mapping each entity name to the best "base skin" relative path, so the renderer can fall back to real vanilla textures for models that have no pack textures of their own. """ import json import os from django.conf import settings ENTITY_ROOT = 'entity' INDEX_NAME = 'entity_index.json' # Entity names whose vanilla layout differs from the model-name convention. ALIASES = { 'polar_bear': 'bear/polarbear.png', 'cave_spider': 'spider/cave_spider.png', 'husk': 'zombie/husk.png', 'drowned': 'zombie/drowned.png', 'stray': 'skeleton/stray.png', 'zoglin': 'hoglin/zoglin.png', 'piglin_brute': 'piglin/piglin_brute.png', 'zombified_piglin': 'piglin/zombified_piglin.png', 'horse': 'horse/horse_brown.png', 'donkey': 'horse/donkey.png', 'mule': 'horse/mule.png', 'skeleton_horse': 'horse/horse_skeleton.png', 'zombie_horse': 'horse/horse_zombie.png', 'evoker': 'illager/evoker.png', 'pillager': 'illager/pillager.png', 'vex': 'illager/vex.png', 'vindicator': 'illager/vindicator.png', 'ravager': 'illager/ravager.png', 'cat': 'cat/tabby.png', 'cow': 'cow/temperate_cow.png', 'pig': 'pig/temperate_pig.png', 'slime_size_0': 'slime/slime.png', 'slime_size_1': 'slime/slime.png', 'slime_size_3': 'slime/slime.png', 'player': 'player/wide/steve.png', 'player_slim': 'player/slim/steve.png', } # Texture names that are auxiliary layers, never a base skin. _LAYER_KEYWORDS = ( 'overlay', '_eyes', '_armor', '_collar', '_markings', '_tame', '_angry', '_saddle', '_crack', '_outer', '_inner', '_layer', '_emissive', '_pelt', '_fur', '_shell', '_crackiness', ) def _base_candidates(entity): """Return (entity, [candidate relative paths]) in priority order.""" if entity in ALIASES: return entity, [ALIASES[entity]] root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT) top = os.path.join(root, entity + '.png') if os.path.isfile(top): return entity, [entity + '.png'] folder = os.path.join(root, entity) if not os.path.isdir(folder): return entity, [] try: names = sorted(os.listdir(folder)) except OSError: return entity, [] pngs = [n for n in names if n.endswith('.png')] exact = [f'{entity}/{n}' for n in pngs if n == f'{entity}.png'] default = [f'{entity}/{n}' for n in pngs if n in ('temperate_' + entity + '.png', 'default.png')] plain = [ f'{entity}/{n}' for n in pngs if not any(k in n for k in _LAYER_KEYWORDS) ] return entity, exact + default + plain + [f'{entity}/{n}' for n in pngs] def refresh_vanilla_index(): """Rebuild the entity → base-skin map from the extracted vanilla set.""" root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT) if not os.path.isdir(root): raise FileNotFoundError(f'{root} missing — extract vanilla entity textures first') index = {} for entry in sorted(os.listdir(root)): rel = os.path.join(root, entry) if entry.endswith('.png'): entity = entry[:-4] if not index.get(entity): index[entity] = entry continue if not os.path.isdir(rel): continue entity = entry _, candidates = _base_candidates(entity) if candidates and not index.get(entity): index[entity] = candidates[0] for entity, rel in ALIASES.items(): if not index.get(entity): index[entity] = rel out = os.path.join(settings.MEDIA_ROOT, 'vanilla', INDEX_NAME) with open(out, 'w') as f: json.dump(index, f, indent=1, sort_keys=True) return index