backup for Phase 2.5
This commit is contained in:
+167
-1
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user