0.2.6-dev: the 19 local 玩法说明 pages get a lossless WebP copy in tools/local-extract (25.6 → 17.4 MB; lossy waits for the owner's comparison) and the guide preloads only the next page (host bandwidth layer 0.3); golden: unchanged
This commit is contained in:
1 parent
8edda589f3
commit
05b0dba8b5
7 files changed
+112
-16
No files matched your search
@@ -32,8 +32,8 @@ aklz4.py registers a decoder for it. This script pulls the art the web sources l
|
||||
DXT5nm in an older bundle: AG = XY, R = 1) and metallic/gloss with the smoothness in A; the board renderer needs
|
||||
an RGB normal map (Z rebuilt) and a roughness map in G (1 − smoothness; metalness B = 0), written next to the
|
||||
source as <name>_rgb.png / _rough.png
|
||||
- WebP copies of the board textures every player downloads with the 3D board (WEBP), written next to their PNGs;
|
||||
the manifest lists the copy
|
||||
- WebP copies of the board textures every player downloads with the 3D board (WEBP) and of the 19 guidebook pages
|
||||
(GUIDE_WEBP_*), written next to their PNGs; the manifest lists the copy
|
||||
- the official battlefield scenes (scenes.py SCENES: one prebuilt scene per 卫戍协议 battlefield) → scene/<stageId>/
|
||||
scene.json + scene.bin + lightmap pages and scene/tex/ (the materials' albedo textures). The scene bundles ship
|
||||
only while the season runs: they are read from the client when it has them, else from --scenes <dir> (default
|
||||
@@ -43,8 +43,9 @@ Usage:
|
||||
python3 -m venv .venv && .venv/bin/pip install -r tools/local-extract/requirements.txt
|
||||
.venv/bin/python tools/local-extract/extract.py [--game <AB root>] [--out public/assets/local] [--only <subdir prefix>]
|
||||
python3 tools/local-extract/extract.py --print-jobs (the job table as JSON; needs no dependencies)
|
||||
python3 tools/local-extract/extract.py --webp (only the WebP copies, from the PNGs already extracted —
|
||||
e.g. the local art copied from a release bundle; needs Pillow)
|
||||
python3 tools/local-extract/extract.py --webp (only the WebP copies of the board textures and the guide
|
||||
pages, from the PNGs already extracted — e.g. the local art
|
||||
copied from a release bundle; needs Pillow)
|
||||
.venv/bin/python tools/local-extract/extract.py --only scene [--scenes <dir of in-season bundles>]
|
||||
|
||||
Writes <out>/**.png|.webp|.skel|.atlas and data/local-assets.json (manifest of what was extracted). With --only, just
|
||||
@@ -188,6 +189,19 @@ WEBP_OPTIONS = {
|
||||
'lossless': {'lossless': True, 'quality': 100, 'method': 6, 'exact': True},
|
||||
}
|
||||
|
||||
# The 19 玩法说明 pages (output subdir guide: 1024², opaque) get a WebP copy too — every page PNG of the group, each a
|
||||
# 1.2–1.5 MB download whenever a player opens the guide (public/js/ui/guide.js shows the local page first and preloads
|
||||
# the next one). Lossless by default: 25.6 MB → 17.4 MB on the CN client's pages. Lossy quality 90 would make them
|
||||
# 4.2 MB; the maintainer decides on that after a side-by-side comparison (docs/history/0.2.5.md, the 0.2.6-dev note),
|
||||
# and then only GUIDE_WEBP_MODE changes (`extract.py --webp` rewrites the copies). Method 4: method 6 spends about 8 s
|
||||
# a page for 0.4 % less.
|
||||
GUIDE_WEBP_SUB = 'guide'
|
||||
GUIDE_WEBP_MODE = 'lossless'
|
||||
GUIDE_WEBP_OPTIONS = {
|
||||
'lossless': {'lossless': True, 'quality': 100, 'method': 4, 'exact': True},
|
||||
'lossy': {'quality': 90, 'method': 4, 'alpha_quality': 100},
|
||||
}
|
||||
|
||||
# Shader bundles loaded beside every job that exports Materials, only so that the materials' shader references
|
||||
# (external CABs) resolve to a name in materials.json (e.g. Torappu/Scene/StandardDirectional); nothing is exported
|
||||
# from them.
|
||||
@@ -454,11 +468,22 @@ def run_derived(out_root, sub, manifest, log):
|
||||
return n
|
||||
|
||||
|
||||
def webp_targets(sub, manifest):
|
||||
"""(name, mode, Pillow options) of every WebP copy output subdir `sub` gets: its WEBP rows and, for the guide, each
|
||||
page of the group (an image entry: its path ends in .png, or in .webp after an earlier run)."""
|
||||
out = [(name, mode, WEBP_OPTIONS[mode]) for wsub, name, mode in WEBP if wsub == sub]
|
||||
if sub == GUIDE_WEBP_SUB:
|
||||
out += [(name, GUIDE_WEBP_MODE, GUIDE_WEBP_OPTIONS[GUIDE_WEBP_MODE])
|
||||
for name, entry in sorted(manifest.get(sub, {}).items())
|
||||
if isinstance(entry, dict) and str(entry.get('path', '')).endswith(('.png', '.webp'))]
|
||||
return out
|
||||
|
||||
|
||||
def run_webp(out_root, sub, manifest, log):
|
||||
"""Write the WEBP copies of output subdir `sub` from its PNGs and point their manifest entries at them."""
|
||||
"""Write the WebP copies of output subdir `sub` from its PNGs and point their manifest entries at them."""
|
||||
n = 0
|
||||
for wsub, name, mode in WEBP:
|
||||
entry = manifest.get(sub, {}).get(name) if wsub == sub else None
|
||||
for name, mode, options in webp_targets(sub, manifest):
|
||||
entry = manifest.get(sub, {}).get(name)
|
||||
png = Path(out_root) / sub / f'{name}.png'
|
||||
if not entry or not png.exists():
|
||||
continue
|
||||
@@ -466,7 +491,7 @@ def run_webp(out_root, sub, manifest, log):
|
||||
try:
|
||||
from PIL import Image
|
||||
with Image.open(png) as img:
|
||||
img.save(dst, 'WEBP', **WEBP_OPTIONS[mode])
|
||||
img.save(dst, 'WEBP', **options)
|
||||
except Exception as e: # no WebP support in this Pillow, or a broken PNG: the entry keeps the PNG
|
||||
log(f' warn webp {name}: {e}')
|
||||
continue
|
||||
@@ -486,7 +511,7 @@ def webp_only(out_root, manifest_path, log):
|
||||
if not isinstance(groups, dict):
|
||||
print(f'{manifest_path} has no groups. Extract the local art first.', file=sys.stderr)
|
||||
return 2
|
||||
n = sum(run_webp(out_root, sub, groups, log) for sub in dict.fromkeys(s for s, _, _ in WEBP))
|
||||
n = sum(run_webp(out_root, sub, groups, log) for sub in dict.fromkeys([*(s for s, _, _ in WEBP), GUIDE_WEBP_SUB]))
|
||||
Path(manifest_path).write_text(json.dumps(doc, ensure_ascii=False, separators=(',', ':')), encoding='utf-8')
|
||||
log(f'done: {n} WebP copies, manifest {manifest_path}')
|
||||
return 0 if n else 1
|
||||
@@ -846,8 +871,8 @@ def main():
|
||||
'searched after --game and before .cache/official-scenes/*)')
|
||||
ap.add_argument('--print-jobs', action='store_true', help='print the job table as JSON and exit')
|
||||
ap.add_argument('--webp', action='store_true',
|
||||
help='only write the WebP copies of the board textures from the PNGs already under --out and list '
|
||||
'them in the manifest (needs Pillow, not the client)')
|
||||
help='only write the WebP copies of the board textures and the guide pages from the PNGs already '
|
||||
'under --out and list them in the manifest (needs Pillow, not the client)')
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.print_jobs:
|
||||
@@ -857,6 +882,8 @@ def main():
|
||||
webp = [{'sub': sub, 'name': name, 'mode': mode} for sub, name, mode in WEBP]
|
||||
print(json.dumps({'emoteThemes': [{'themeId': t, 'dir': d} for t, d in EMOTE_THEMES], 'jobs': jobs,
|
||||
'derived': derived, 'webp': webp,
|
||||
'guideWebp': {'sub': GUIDE_WEBP_SUB, 'mode': GUIDE_WEBP_MODE,
|
||||
'options': GUIDE_WEBP_OPTIONS[GUIDE_WEBP_MODE]},
|
||||
'enemySpines': {'bundles': ENEMY_ART, 'sub': ENEMY_SPINE_SUB, 'ids': ENEMY_SPINES},
|
||||
'tokenSpines': {'bundles': TOKEN_PACKS, 'sub': TOKEN_SPINE_SUB, 'ids': TOKEN_SPINES},
|
||||
'scenes': {'sub': scenes.SCENE_SUB, 'tex': scenes.TEX_SUB,
|
||||
|
||||
Reference in new issue
Block a user