backup for Phase 2.5

This commit is contained in:
JakeBreath
2026-08-04 00:34:02 -05:00
parent a81e31bf1a
commit 8c13c5e5ec
9 changed files with 745 additions and 11 deletions
+167 -1
View File
@@ -8,6 +8,14 @@ adopted file (under MEDIA_ROOT) and return plain dicts/values.
import json
import zipfile
INJECTOR_NAMES = {'V': 'Vaginal', 'M': 'Mouth', 'A': 'Anal'}
PROBLEM_TAGS = {'broken', 'bugged', 'borked'}
def _decode(data):
"""Decode JSON text, tolerating a UTF-8 BOM (common in hand-edited packs)."""
return json.loads(data.decode('utf-8-sig', 'replace'))
def _open_zip(path):
"""Return a ZipFile for the path (None when it's not a zip)."""
@@ -27,7 +35,7 @@ def read_pack_mcmeta(path):
return None, ''
try:
try:
data = json.loads(zf.read('pack.mcmeta').decode('utf-8', 'replace'))
data = _decode(zf.read('pack.mcmeta'))
except (KeyError, json.JSONDecodeError):
return None, ''
pack = data.get('pack') or {}
@@ -43,6 +51,164 @@ def read_pack_mcmeta(path):
zf.close()
def _friendly_entity(entity):
"""minecraft:zombie -> Zombie; needsofnature:horse_liquid_collector -> Horse Liquid Collector."""
name = entity.rsplit(':', 1)[-1]
return name.replace('_', ' ').title()
def _gender(actor_tags):
for tag in actor_tags or []:
if str(tag).startswith('gender.'):
return str(tag).split('.', 1)[1]
return None
def _summarize_animdef(filename, data):
stem = filename.rsplit('/', 1)[-1]
if stem.endswith('.json'):
stem = stem[:-5]
display = data.get('display_name') or stem.replace('_', ' ').title()
participants = len(data.get('actors') or [])
actors = []
has_entity = False
entity_names = []
for actor in data.get('actors') or []:
entity_types = actor.get('entity_types') or []
entity = 'player'
if entity_types:
non_players = [e for e in entity_types if e != 'minecraft:player']
if non_players:
entity = _friendly_entity(non_players[0])
has_entity = True
entity_names.append(entity)
injector = actor.get('injector')
inj = None
inj_name = None
if injector is True:
inj = 'injector'
inj_name = 'injector'
elif isinstance(injector, str) and injector:
inj = injector
inj_name = INJECTOR_NAMES.get(injector, injector)
actors.append({
'label': actor.get('label') or '',
'entity': entity,
'gender': _gender(actor.get('actor_tags')),
'activity': actor.get('activity') or '',
'injector': inj,
'injector_name': inj_name,
'receiver': bool(actor.get('receiver')),
})
content_tags = [t for t in data.get('content_tags') or []]
animation_tags = data.get('animation_tags') or []
problem_tags = sorted({
t for t in list(content_tags) + list(animation_tags)
if str(t).lower() in PROBLEM_TAGS
})
if participants == 1:
type_ = 'Solo'
elif has_entity:
type_ = 'Entity x Player'
elif participants == 2:
type_ = 'Pair'
elif participants == 3:
type_ = 'Threesome'
else:
type_ = f'Group of {participants}'
stages = []
for stage in data.get('stages') or []:
stages.append({
'stage': stage.get('stage'),
'loop': bool(stage.get('loop')),
'cycle_seconds': stage.get('cycle_seconds'),
'speed': stage.get('speed'),
'climax': bool(stage.get('non_peak') or stage.get('manual_peak')),
'use_stage': stage.get('use_stage'),
'joinable': bool(stage.get('allow_join', True)),
'escapable': bool(stage.get('escapable', True)),
})
return {
'name': display,
'content_tags': content_tags,
'animation_tags': list(animation_tags),
'type': type_,
'entity_names': entity_names,
'participants': participants,
'actors': actors,
'stages': stages,
'weight': data.get('weight'),
'block_requirements': bool(data.get('block_requirements')),
'water': bool(data.get('water')),
'problem_tags': problem_tags,
}
def read_animation_manifest(path):
"""Build the animation manifest for a NoN animation pack.
Detected by the presence of data/*/afw_animdefs/*.json. Returns None when
the archive isn't an animation pack. Reads the animationframework block
(id/name/version/authors/description) plus a summary of every animdef.
"""
zf = _open_zip(path)
if zf is None:
return None
try:
names = set(zf.namelist())
animdef_names = sorted(
n for n in names if '/afw_animdefs/' in n and n.endswith('.json')
)
if not animdef_names:
return None
animation_id = None
pack_name = ''
pack_version = ''
description = ''
authors = []
try:
mcmeta = _decode(zf.read('pack.mcmeta'))
af = mcmeta.get('animationframework') or {}
animation_id = af.get('id') or None
pack_name = str(af.get('name') or '')
pack_version = str(af.get('version') or '')
description = str(af.get('description') or '')
author = af.get('author')
if author:
authors = [a.strip() for a in str(author).split(',') if a.strip()]
except (KeyError, json.JSONDecodeError):
pass
animations = []
distinct_content_tags = set()
for name in animdef_names:
try:
data = _decode(zf.read(name))
except (KeyError, json.JSONDecodeError):
continue
summary = _summarize_animdef(name, data)
distinct_content_tags.update(summary['content_tags'])
animations.append(summary)
return {
'animation_id': animation_id,
'name': pack_name,
'version': pack_version,
'authors': authors,
'description': description,
'animations': animations,
'content_tags': sorted(distinct_content_tags),
}
finally:
zf.close()
def parse_mods_manifest(path, filename=''):
"""Inspect a modpack archive and build a manifest for the Mods tab.