108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
"""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
|