138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""Build the vanilla entity-texture index for the model viewer.
|
|
|
|
Scans ``static/vanilla/entity/`` (extracted from a Minecraft client jar,
|
|
tracked in git) and writes ``static/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'
|
|
|
|
|
|
def vanilla_dir():
|
|
"""The tracked static directory holding the vanilla texture set."""
|
|
return os.path.join(settings.BASE_DIR, 'static', 'vanilla')
|
|
|
|
# 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',
|
|
)
|
|
|
|
# Entities whose base skin is one of several fur/colour variants. The preview
|
|
# picks one at random so the model matches the variety the pack shows in-game.
|
|
VARIANTS = {
|
|
'wolf': [
|
|
'wolf/wolf.png', 'wolf/wolf_ashen.png', 'wolf/wolf_black.png',
|
|
'wolf/wolf_chestnut.png', 'wolf/wolf_rusty.png', 'wolf/wolf_snowy.png',
|
|
'wolf/wolf_spotted.png', 'wolf/wolf_striped.png', 'wolf/wolf_woods.png',
|
|
],
|
|
'fox': ['fox/fox.png', 'fox/snow_fox.png'],
|
|
'cat': [
|
|
'cat/tabby.png', 'cat/black.png', 'cat/red.png', 'cat/siamese.png',
|
|
'cat/british_shorthair.png', 'cat/calico.png', 'cat/persian.png',
|
|
'cat/ragdoll.png', 'cat/white.png', 'cat/jellie.png', 'cat/all_black.png',
|
|
],
|
|
'cow': [
|
|
'cow/temperate_cow.png', 'cow/cold_cow.png', 'cow/warm_cow.png',
|
|
'cow/red_mooshroom.png', 'cow/brown_mooshroom.png',
|
|
],
|
|
}
|
|
|
|
|
|
def _base_candidates(entity):
|
|
"""Return (entity, [candidate relative paths]) in priority order."""
|
|
if entity in ALIASES:
|
|
return entity, [ALIASES[entity]]
|
|
root = os.path.join(vanilla_dir(), 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 → {default, variants} skin map from the vanilla set."""
|
|
root = os.path.join(vanilla_dir(), 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]
|
|
index.setdefault(entity, {'default': entry, 'variants': [entry]})
|
|
continue
|
|
if not os.path.isdir(rel):
|
|
continue
|
|
entity = entry
|
|
_, candidates = _base_candidates(entity)
|
|
if candidates and entity not in index:
|
|
index[entity] = {'default': candidates[0], 'variants': [candidates[0]]}
|
|
|
|
for entity, rel in ALIASES.items():
|
|
if entity not in index:
|
|
index[entity] = {'default': rel, 'variants': [rel]}
|
|
|
|
for entity, variants in VARIANTS.items():
|
|
if entity in index and variants:
|
|
index[entity]['default'] = variants[0]
|
|
index[entity]['variants'] = variants
|
|
|
|
out = os.path.join(vanilla_dir(), INDEX_NAME)
|
|
with open(out, 'w') as f:
|
|
json.dump(index, f, indent=1, sort_keys=True)
|
|
return index
|