MatchMaster/tools/build-shop0917.py
2026-09-22 10:17:58 +08:00

488 lines
25 KiB
Python

"""Import the supplied 0917 shop slices and author the separate Creator 2.4 prefab.
Run with a Pillow-enabled Python. Existing asset UUIDs are deterministic. Artwork is
copied unchanged; missing shared slices are extracted losslessly from their atlas.
"""
import argparse
import copy
import json
import shutil
import uuid
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
DEST = ROOT / "assets/shop/redesign0917"
NS = uuid.UUID("1c30e866-71dc-4b6d-9fb6-3c47a9b69817")
# Source artwork keeps its supplied names; imported assets use English names.
RESOURCE_NAMES = {
"打折数字": "discount_digits", "个数数量": "quantity_digits",
"钱数数字": "price_green_digits", "黄色钱数数字": "price_gold_digits",
"首购数字": "bonus_digits", "商城月卡存图0917": "monthly_card",
"剩余天数数字": "day_digits", "chuizi00.png": "hammer.png",
"lijilq.png": "claim_button.png", "mofab00.png": "magic_wand.png",
"shijian00.png": "freeze.png", "shop_1.png": "header_background.png",
"tu00.png": "gift_coin_icon.png", "wenzi00.png": "gift_title_small.png",
"xianshitub.png": "limited_badge.png", "元.png": "currency_yuan.png",
"加号.png": "bonus_plus.png", "商城底板.png": "shop_background.png",
"图标00.png": "infinite_health.png", "小圆点00.png": "dot_normal.png",
"小圆点01.png": "dot_selected.png", "小鱼干小兔.png": "starter_banner.png",
"幸运礼包小图.png": "lucky_banner.png", "底框.png": "gift_card.png",
"打折标签.png": "discount_tag.png", "文字01.png": "gift_title_medium.png",
"文字大.png": "gift_title_large.png", "标签00.png": "first_purchase_badge.png",
"礼包01.png": "gift_small.png", "礼包02.png": "gift_medium.png",
"礼包03.png": "gift_large.png", "礼包条.png": "gift_section_title.png",
"立即购买按.png": "buy_button.png", "超值tu.png": "value_badge.png",
"金币图底图.png": "coin_card.png", "金币条.png": "coin_section_title.png",
"钱数按钮.png": "coin_price_button.png", "钱数按钮00.png": "gift_price_button.png",
"首购小.png": "first_purchase_special_badge.png",
"30天特权字.png": "privilege_30_days.png", "剩余天数.png": "remaining_days.png",
"月卡小图.png": "month_banner.png", "立即开通.png": "activate_button.png",
"立即续费.png": "renew_button.png",
**{f"金币0{i}.png": f"coin_0{i}.png" for i in range(1, 7)},
}
def english_path(path):
result = Path(*[RESOURCE_NAMES.get(part.strip(), part.strip()) for part in Path(path).parts])
if not result.as_posix().isascii():
raise ValueError(f"Missing English asset name: {path}")
return result
def uid(key):
return str(uuid.uuid5(NS, key))
def compressed_uuid(value):
digits = value.replace("-", "")
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
result = digits[:5]
for i in range(5, 32, 3):
n = int(digits[i:i+3], 16)
result += alphabet[n >> 6] + alphabet[n & 63]
return result
def write_json(path, data):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def folder(path):
path.mkdir(parents=True, exist_ok=True)
meta_path = Path(str(path) + ".meta")
existing = json.loads(meta_path.read_text(encoding="utf-8")) if meta_path.exists() else {}
write_json(meta_path, dict(ver="1.1.3", uuid=existing.get("uuid", uid(str(path.relative_to(ROOT)))),
importer="folder", isBundle=False, bundleName="", priority=1,
compressionType={}, optimizeHotUpdate={}, inlineSpriteFrames={}, isRemoteBundle={}, subMetas={}))
def texture(path):
w, h = Image.open(path).size
key = path.relative_to(ROOT).as_posix()
tex, frame = uid(key), uid(key + "/frame")
meta_path = Path(str(path) + ".meta")
if meta_path.exists():
existing = json.loads(meta_path.read_text(encoding="utf-8"))
tex = existing["uuid"]
frame = next(iter(existing["subMetas"].values()))["uuid"]
sub = dict(ver="1.0.6", uuid=frame, importer="sprite-frame", rawTextureUuid=tex,
trimType="none", trimThreshold=1, rotated=False, offsetX=0, offsetY=0,
trimX=0, trimY=0, width=w, height=h, rawWidth=w, rawHeight=h,
borderTop=0, borderBottom=0, borderLeft=0, borderRight=0, subMetas={})
write_json(Path(str(path) + ".meta"), dict(ver="2.3.7", uuid=tex, importer="texture",
type="sprite", wrapMode="clamp", filterMode="bilinear", premultiplyAlpha=False,
genMipmaps=False, packable=w <= 1024 and h <= 1024, width=w, height=h,
platformSettings={}, subMetas={path.stem: sub}))
return frame, w, h
def trimmed_sprites(data, frame_sizes):
"""Keep authored layout nodes; native-size TRIMMED images are scaled leaves.
Children and widgets stay on the layout node, so changing image size mode
cannot resize its buttons, displace text, or scale the entire child hierarchy.
"""
# Creator's built-in 2x2 splash frame is used by the payment overlay masks.
frame_sizes = {"a23235d1-15db-4b95-8439-a2e005bfff91": (2, 2), **frame_sizes}
for i, sprite in list(enumerate(data)):
if sprite["__type__"] != "cc.Sprite":
continue
owner_id = sprite["node"]["__id__"]
owner = data[owner_id]
if owner["_name"] == "SpriteVisual":
sprite["_sizeMode"] = 1
continue
frame = sprite.get("_spriteFrame")
w, h = frame_sizes[frame["__uuid__"]] if frame else (owner["_contentSize"]["width"], owner["_contentSize"]["height"])
visual = copy.deepcopy(owner)
visual.update(_name="SpriteVisual", _parent={"__id__": owner_id}, _children=[],
_components=[{"__id__": i}], _prefab=None, _opacity=255, _active=True, _id="",
_skewX=0, _skewY=0)
visual["_contentSize"].update(width=w, height=h)
visual["_anchorPoint"].update(x=.5, y=.5)
visual["_eulerAngles"].update(x=0, y=0, z=0)
size, anchor = owner["_contentSize"], owner["_anchorPoint"]
visual["_trs"]["array"] = [(0.5-anchor["x"])*size["width"], (0.5-anchor["y"])*size["height"],
0, 0, 0, 0, 1, size["width"]/w if w else 1, size["height"]/h if h else 1, 1]
visual_id = len(data)
data.append(visual)
owner["_components"].remove({"__id__": i})
owner["_children"].insert(0, {"__id__": visual_id})
sprite.update(node={"__id__": visual_id}, _sizeMode=1)
def asset_index():
frames, textures = {}, {}
for p in (ROOT / "assets").rglob("*.meta"):
if DEST in p.parents:
continue
try:
m = json.loads(p.read_text(encoding="utf-8-sig"))
except (ValueError, OSError):
continue
if m.get("importer") == "texture":
textures[m["uuid"]] = Path(str(p)[:-5])
for name, f in m.get("subMetas", {}).items():
if f.get("importer") == "sprite-frame":
frames[f["uuid"]] = (p, name, f)
return frames, textures
def extract(frame, textures):
im = Image.open(textures[frame["rawTextureUuid"]]).convert("RGBA")
x, y, w, h = [frame[k] for k in ("trimX", "trimY", "width", "height")]
im = im.crop((x, y, x + (h if frame["rotated"] else w), y + (w if frame["rotated"] else h)))
if frame["rotated"]:
im = im.transpose(Image.Transpose.ROTATE_90)
result = Image.new("RGBA", (frame["rawWidth"], frame["rawHeight"]))
result.paste(im, (round((result.width-w)/2+frame["offsetX"]), round((result.height-h)/2-frame["offsetY"])))
return result
def build(source):
folder(DEST)
folder(DEST / "texture")
folder(DEST / "texture/shared")
frames, textures = asset_index()
art, provenance = {}, {}
for p in sorted(source.rglob("*.png")):
rel = Path(*[part.strip() for part in p.relative_to(source).parts])
imported = english_path(rel)
dst = DEST / "texture" / imported
if not dst.parent.exists():
folder(dst.parent)
shutil.copyfile(p, dst)
art[rel.as_posix()] = texture(dst)
provenance[imported.as_posix()] = str(p)
old = json.loads((ROOT / "assets/shop/prefab/shop.prefab").read_text(encoding="utf-8"))
copied = {}
def shared(frame_uuid, name=None):
if frame_uuid in copied:
return copied[frame_uuid]
meta, original, f = frames[frame_uuid]
name = name or (Path(original).stem + "_" + frame_uuid[:8])
rel = "shared/" + name + ".png"
dst = DEST / "texture" / rel
extract(f, textures).save(dst)
art[rel] = texture(dst)
provenance[rel] = f"{meta.relative_to(ROOT).as_posix()} :: {original}"
copied[frame_uuid] = rel
return rel
def named(atlas, name, output):
f = json.loads((ROOT / atlas).read_text(encoding="utf-8"))["subMetas"][name]
return shared(f["uuid"], output)
hour = [named("assets/shop/img/texture_atlas-1.plist.meta", f"{n}h.png", f"hours_{n}") for n in (1, 2, 4)]
multiply = shared("50d5f850-fb89-461d-9f80-becccda30060", "multiply")
yellow_yuan = shared("1375b028-68b3-484a-972b-32553c1269f6", "yuan_yellow")
hp = [named("assets/common/font.plist.meta", f"hp_{n}.png", f"hp_{n}") for n in range(10)]
time = [named("assets/common/font.plist.meta", f"time_{n}.png", f"time_{n}") for n in range(10)]
colon = named("assets/common/font.plist.meta", "time_10.png", "time_colon")
data = [dict(__type__="cc.Prefab", _name="shop0917", _objFlags=0, _native="",
data={"__id__": 1}, optimizationPolicy=0, asyncLoadAssets=False, readonly=False)]
ref = lambda i: {"__id__": i}
asset = lambda key: {"__uuid__": art[key][0]}
templates = {t: next(x for x in old if x["__type__"] == t) for t in
("cc.Node", "cc.Sprite", "cc.Button", "cc.Widget", "cc.Mask", "cc.ScrollView")}
def component(node, kind, **props):
c = copy.deepcopy(templates.get(kind, dict(__type__=kind, _name="", _objFlags=0, _enabled=True, _id="")))
c.update(node=ref(node), **props)
i = len(data)
data.append(c)
data[node]["_components"].append(ref(i))
return i
def node(name, parent, x=0, y=0, w=0, h=0, key=None, active=True, anchor_y=.5):
n = copy.deepcopy(templates["cc.Node"])
n.update(_name=name, _parent=ref(parent) if parent else None, _children=[], _components=[],
_active=active, _prefab=None, _opacity=255, _id="")
n["_trs"]["array"] = [x, y, 0, 0, 0, 0, 1, 1, 1, 1]
n["_contentSize"].update(width=w, height=h)
n["_anchorPoint"].update(x=.5, y=anchor_y)
i = len(data)
data.append(n)
if parent:
data[parent]["_children"].append(ref(i))
if key:
component(i, "cc.Sprite", _spriteFrame=asset(key))
return i
def image(name, parent, x, y, key, w=None, h=None):
_, iw, ih = art[key]
return node(name, parent, x, y, w or iw, h or ih, key)
def widget(n, flags=45, top=0, bottom=0):
component(n, "cc.Widget", _alignFlags=flags, _top=top, _bottom=bottom, _left=0, _right=0)
def button(n, handler, custom="", script="shop"):
e = len(data)
data.append(dict(__type__="cc.ClickEvent", target=ref(1), component=script,
_componentId="", handler=handler, customEventData=custom))
component(n, "cc.Button", clickEvents=[ref(e)], zoomScale=.97, **{"_N$target": ref(n)})
def number(name, parent, x, y, value, group, height, gap=-2, suffix=None):
keys = [f"{group}/{c}.png" for c in str(value)]
if suffix:
keys.append(suffix)
widths = [art[k][1]*height/art[k][2] for k in keys]
total = sum(widths) + gap*(len(keys)-1)
n = node(name, parent, x, y, total, height)
cursor = -total/2
for i, (key, width) in enumerate(zip(keys, widths)):
image(f"glyph_{i}", n, cursor+width/2, 0, key, width, height)
cursor += width+gap
return n
def clone_tree(old_id, parent):
# Clone an existing shared avatar/loading/dialog subtree, preserving local
# references while copying all its visible image slices into the new folder.
ids = set()
def visit(i):
if i in ids:
return
ids.add(i)
obj = old[i]
if obj["__type__"] == "cc.Node":
for r in obj["_children"] + obj["_components"]:
visit(r["__id__"])
for r in obj.get("clickEvents", []):
visit(r["__id__"])
visit(old_id)
mapping = {i: len(data)+j for j, i in enumerate(sorted(ids))}
def remap(v):
if isinstance(v, dict):
if "__id__" in v:
return ref(mapping[v["__id__"]]) if v["__id__"] in mapping else (ref(1) if v["__id__"] == 1 else None)
if "__uuid__" in v and v["__uuid__"] in frames:
return asset(shared(v["__uuid__"]))
return {k: remap(a) for k, a in v.items()}
if isinstance(v, list):
return [remap(a) for a in v]
return v
for i in sorted(ids):
obj = remap(copy.deepcopy(old[i]))
if obj["__type__"] == "cc.Node":
obj["_prefab"] = None
data.append(obj)
result = mapping[old_id]
data[result]["_parent"] = ref(parent)
data[parent]["_children"].append(ref(result))
return result
# Existing delivery/month-card callbacks locate Canvas/shop by name.
root = node("shop", None, 540, 1170, 1080, 2340)
widget(root)
component(root, "cc.BlockInputEvents")
background = image("Background", root, 0, 0, "商城底板.png", 1080, 2340)
widget(background)
# Only the product area scrolls. The first 1005 reference pixels stay fixed.
scroll = node("ProductsScrollView", root, 0, -502.5, 1080, 1335)
widget(scroll, top=1005)
view = node("view", scroll, 0, 0, 1080, 1335)
widget(view)
component(view, "cc.Mask")
content = node("content", view, 0, 667.5, 1080, 2855, anchor_y=1)
scroll_comp = component(scroll, "cc.ScrollView", **{"_N$content": ref(content), "content": ref(content),
"_N$verticalScrollBar": None, "elastic": True, "brake": .75})
image("ProductBackground", content, 0, -1427.5, "商城底板.png", 1080, 2855)
image("GiftSectionTitle", content, 0, -69, "礼包条.png")
for i, (price, coins, discount, title, hours, count) in enumerate([
(10, 2500, "7", "wenzi00.png", 1, 0),
(20, 5000, "55", "文字01.png", 2, 2),
(30, 7500, "4", "文字大.png", 4, 5),
]):
# Reference card tops: 1143, 1635, 2135 (relative to full reference image).
cy = -(138 + [0, 492, 992][i] + 226)
card = image(f"Gift_{price}", content, 0, cy, "底框.png")
image("GiftTitle", card, -252, -149, title)
image("UnlimitedHealth", card, -329, 58 if i == 0 else 112, "图标00.png")
image("Duration", card, -329, -1 if i == 0 else 52, hour[i])
image("CoinsIcon", card, -146, 62 if i == 0 else 110, "tu00.png")
number("CoinAmount", card, -146, -4 if i == 0 else 49, coins, "个数数量", 46)
if count:
for j, key in enumerate(["chuizi00.png", "shijian00.png", "mofab00.png"]):
px = -365+j*120
image("PropIcon"+str(j), card, px, -25, key, art[key][1]*.85, art[key][2]*.85)
amount = node("PropAmount"+str(j), card, px, -87, 70, 40)
image("multiply", amount, -20, 0, multiply, 25, 25)
number("count", amount, 13, 0, count, "个数数量", 42)
tag = image("DiscountTag", card, 32, 154, "打折标签.png")
digits = number("Discount", tag, -9 if len(discount) == 1 else -22, 14, discount, "打折数字", 52)
# The tag art itself is slanted; its digits follow that printed baseline.
import math
data[digits]["_trs"]["array"][5:7] = [math.sin(math.radians(-13)/2), math.cos(math.radians(-13)/2)]
data[digits]["_eulerAngles"]["z"] = -13
gift_key = f"礼包0{i+1}.png"
image("GiftArtwork", card, 259, 48, gift_key)
buy = image("Buy", card, 265, -136, "钱数按钮00.png")
button(buy, "buyProduct", f"unlimited_health_bundle_{price}")
number("Price", buy, 0, 7, price, "黄色钱数数字", 65, 4, yellow_yuan)
image("CoinSectionTitle", content, 0, -1689, "金币条.png")
offers = []
for i, (coins, price) in enumerate(zip([1200, 8000, 16000, 32000, 100000, 240000], [6, 36, 68, 128, 328, 648])):
card = image(f"gold_{i+1}", content, -330+(i%3)*330, -(1988+(i//3)*494), "金币图底图.png")
offer = node("FirstPurchase", card)
image("FirstPurchaseBadge", offer, -116, 149, "标签00.png")
amount = number("BonusAmount", offer, 43, 153, coins, "首购数字", 41)
image("Plus", amount, -data[amount]["_contentSize"]["width"]/2-16, 0, "加号.png", 28, 28)
offers.append(offer)
key = f"金币0{i+1}.png"
image("CoinArtwork", card, 0, -10, key)
number("CoinAmount", card, 0, -87, coins, "个数数量", 46)
buy = image("Buy", card, 0, -181, "钱数按钮.png")
button(buy, "buyProduct", f"gold_{i+1}")
number("Price", buy, 0, 7, price, "钱数数字", 54, 1, "元.png")
top = node("Top", root, 0, 667.5, 1080, 1005)
widget(top, flags=41)
image("HeaderBackground", top, 0, 0, "shop_1.png")
for original, name in [(472, "kuang"), (477, "avatar")]:
n = clone_tree(original, top)
data[n]["_trs"]["array"][:2] = [-427, 185.5]
# Avatar frame is decorative here; the profile page remains outside shop.
data[n]["_components"] = [r for r in data[n]["_components"] if data[r["__id__"]]["__type__"] != "cc.Button"]
close = image("Close", top, 436, 139.5, shared("4eb76a13-bdd5-44cb-975d-32fd4900be79", "close"))
button(close, "closeShop", "exit")
stamina = node("Stamina", top, -169, 224.5, 285, 100)
image("bg", stamina, 26.654, 0, shared("27e4e784-2bb1-40b2-9726-a3d160165c43", "health_background"), 236, 62)
progress = image("progresss", stamina, 26.654, 0, shared("21ec0fc2-b090-4eb7-bb39-83b6de6b2f87", "health_fill"), 238, 64)
data[data[progress]["_components"][0]["__id__"]].update(_type=3, _fillRange=1)
full = node("man", stamina)
image("Full", full, 25, 0, shared("1eabaff0-07cb-4de7-a55d-75a068061ea2", "health_full"), 74, 38)
image("Heart", stamina, -98, 0, shared("b6b570b9-d928-4646-8a2a-70f18cc10991", "heart"), 92, 86)
plus = shared("f9ab818d-bcb7-465b-af54-0af12cf7d936", "plus_green")
image("Plus", stamina, -69, -25, plus, 38, 38)
health = node("health", stamina, -98, 0)
image("glyph_0", health, 0, 0, hp[5], 30, 39)
timer = node("time", stamina, 27, 0, active=False)
skyline = node("skyLine", stamina, 0, 0, active=False)
image("Infinity", skyline, -98, 0, shared("419e6dd8-8ab5-4910-bf94-b82afe18191b", "health_infinite"), 99, 87)
skytime = node("skyTime", skyline, 27, 0)
button(stamina, "openHealth", script="Shop0917View")
coinbar = node("Coin", top, 129, 224.5, 275, 100)
image("Background", coinbar, 5, 0, shared("e29788dd-4216-4e8b-9186-25040fc8e3ec", "coin_background"), 238, 64)
image("Icon", coinbar, -105, 0, shared("94a3bed8-306b-4f18-9806-2df14cc935f3", "coin_icon"), 84, 95)
image("Plus", coinbar, -69, -25, plus, 38, 38)
coin = number("Coin", coinbar, 30, 0, 563, "个数数量", 37)
button(coinbar, "scrollToCoins", script="Shop0917View")
carousel = node("Carousel", top, 0, -247.5, 1080, 381)
component(carousel, "cc.Mask")
banners = []
# The starter_pack purchase opens NewbieGift (lucky gift); Jungle is the fish-box activity.
for i, key in enumerate(["幸运礼包小图.png", "商城月卡存图0917/月卡小图.png", "小鱼干小兔.png"]):
card = image(["StarterBanner", "MonthBanner", "LuckyBanner"][i], carousel, (i-1)*900+5, 0, key, 862, 381)
banners.append(card)
if i == 1:
month_title = image("Privilege", card, 357, 121, "商城月卡存图0917/30天特权字.png")
month_days = node("RemainingDays", card, 357, 121, active=False)
image("Caption", month_days, 0, 0, "商城月卡存图0917/剩余天数.png")
day_number = number("Days", month_days, 0, 0, 30, "商城月卡存图0917/剩余天数数字", 32)
month_buy = image("Open", card, 254, -83, "商城月卡存图0917/立即开通.png")
button(month_buy, "openmonthCard")
else:
image("Badge", card, 337, 110, "xianshitub.png" if i == 0 else "超值tu.png")
buy = image("Open", card, 254, -83, "立即购买按.png" if i == 0 else "lijilq.png")
button(buy, "openStarter" if i == 0 else "openJungleTreasure", script="Shop0917View" if i == 0 else "shop")
dots = []
for i in range(3):
dot = image("Dot"+str(i), top, (i-1)*47, -464.5, "小圆点01.png" if i == 1 else "小圆点00.png")
button(dot, "selectBanner", str(i), "Shop0917View")
dots.append(dot)
# Shared payment overlays retain their existing behavior and are independent
# of the supplied storefront design. The loading animation needs no font text.
loading = clone_tree(560, root)
data[loading]["_active"] = False
data[loading]["_children"] = [r for r in data[loading]["_children"] if data[r["__id__"]]["_name"] != "New Label"]
confirm = clone_tree(597, root)
data[confirm]["_active"] = False
# Serialize the controller with the same payment/lifecycle API as the old shop.
controller = copy.deepcopy(old[624])
controller.update(node=ref(root), shop=ref(root), itemList=ref(content), coin=ref(coin),
Stamina=ref(stamina), monthCardTime=ref(month_days), coinAnim=None)
controller_id = len(data)
data.append(controller)
data[root]["_components"].append(ref(controller_id))
view_uuid = json.loads((ROOT / "assets/shop/script/Shop0917View.ts.meta").read_text())["uuid"]
v = component(root, compressed_uuid(view_uuid), scrollView=ref(scroll_comp), carousel=ref(carousel),
banners=list(map(ref, banners)), dots=list(map(ref, dots)), firstPurchase=list(map(ref, offers)),
coin=ref(coin), health=ref(health), monthPrivilege=ref(month_title), monthRemaining=ref(month_days),
monthDays=ref(day_number), monthButton=ref(data[month_buy]["_components"][0]["__id__"]),
countFrames=[asset(f"个数数量/{i}.png") for i in range(10)],
healthFrames=[asset(k) for k in hp], timeFrames=[asset(k) for k in time], colonFrame=asset(colon),
dayFrames=[asset(f"商城月卡存图0917/剩余天数数字/{i}.png") for i in range(10)],
monthOpen=asset("商城月卡存图0917/立即开通.png"), monthRenew=asset("商城月卡存图0917/立即续费.png"),
dotNormal=asset("小圆点00.png"), dotSelected=asset("小圆点01.png"))
controller["redesignView"] = ref(v)
# Remove detached legacy objects and assign stable prefab file IDs to all nodes.
live = set()
def collect(i):
if i in live:
return
live.add(i)
obj = data[i]
def scan(value):
if isinstance(value, dict):
if "__id__" in value:
collect(value["__id__"])
else:
for a in value.values(): scan(a)
elif isinstance(value, list):
for a in value: scan(a)
scan(obj)
collect(0)
mapping = {i: j for j, i in enumerate(sorted(live))}
def reindex(v):
if isinstance(v, dict):
return {"__id__": mapping[v["__id__"]]} if "__id__" in v else {k: reindex(a) for k,a in v.items()}
return [reindex(a) for a in v] if isinstance(v, list) else v
data = [reindex(data[i]) for i in sorted(live)]
trimmed_sprites(data, {frame: (w, h) for frame, w, h in art.values()})
for i, obj in list(enumerate(data)):
if obj["__type__"] == "cc.Node":
obj["_prefab"] = ref(len(data))
data.append(dict(__type__="cc.PrefabInfo", root=ref(1), asset=ref(0),
fileId=uid(f"node/{i}/{obj['_name']}").replace("-", ""), sync=False))
prefab = ROOT / "assets/shop/prefab/shop0917.prefab"
write_json(prefab, data)
write_json(Path(str(prefab)+".meta"), dict(ver="1.3.2", uuid=uid("shop0917.prefab"),
importer="prefab", optimizationPolicy="MULTI_INSTANCE", asyncLoadAssets=False, readonly=False, subMetas={}))
write_json(ROOT / "docs/shop0917-resource-manifest.json", provenance)
print(f"Authored {prefab.relative_to(ROOT)}: {len(data)} objects, {len(art)} independent image slices")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("source", type=Path)
build(parser.parse_args().source)