Files
GaCy-GO/tools/build_gacha_presentation_probe.py
2026-10-28 18:54:43 +07:00

95 lines
5.4 KiB
Python

"""Render an explicitly staged, resource-fed native Warp adapter capture.
This never modifies scripts/, policy or saves. The operator supplies an output
path, then stages that file only after the walking scene is stable. The default
is one banner. --assets-only checks Exists without creating pools or UI.
"""
import argparse
import hashlib
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def quote(value):
separator = ""
while "]" + separator + "]" in value:
separator += "="
return "[" + separator + "[" + value + "]" + separator + "]"
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--limit", type=int, default=1)
parser.add_argument("--assets-only", action="store_true")
args = parser.parse_args()
policy = json.loads((ROOT / "resources/GachaSandboxPolicy.json").read_text(encoding="utf-8"))
catalog = json.loads((ROOT / "resources/PreviewGameplayCatalog.json").read_text(encoding="utf-8"))
mapping = json.loads((ROOT / "resources/GachaBannerMapping.json").read_text(encoding="utf-8"))
if mapping["client_assembly_sha256"] != catalog["client_assembly_sha256"]:
raise ValueError("Mapping/captured catalog identity mismatch")
template_id = policy["native_presentation"]["template_gacha_id"]
template = next(row for row in catalog["tables"]["gacha"] if row["GachaID"] == template_id)
if template["PrefabPath"] not in policy["verified_banner_prefabs"]:
raise ValueError("Template has no installed-asset verification")
avatars = {row["AvatarID"]: row for row in json.loads((ROOT / "resources/AvatarConfig.json").read_text(encoding="utf-8"))["avatar_config"]}
active = [row for row in mapping["banners"] if row.get("roster_available", True)]
roster = json.loads((ROOT / 'freesr-data.json').read_text(encoding='utf-8'))['avatars']
active = [row for row in active if str(row['avatar_id']) in roster]
four = sorted(row['avatar_id'] for row in active if avatars[row['avatar_id']]['Rarity'] == 'CombatPowerAvatarRarityType4')
banners = [row for row in active if avatars[row['avatar_id']]['Rarity'] == 'CombatPowerAvatarRarityType5']
if len(four) < 3:
raise ValueError('Need three actual imported four-star avatars')
if not 1 <= args.limit <= len(banners):
raise ValueError("Limit must select at least one existing mapped banner")
config_name = "__GAC_GACHA_ASSET_CAPTURE_CONFIG" if args.assets_only else "__GAC_GACHA_PRESENTATION"
rules = policy['limited_rules']
payload = ["_G." + config_name + "={enabled=true,template_id=" + str(template_id) + ",template_prefab=" + quote(template["PrefabPath"]) + ",rules={five_star_pity="+str(rules['five_star_pity'])+",four_star_pity="+str(rules['four_star_pity'])+"},banners={"]
for index, banner in enumerate(banners[:args.limit], 1):
row = avatars[banner["avatar_id"]]
featured = [four[(index-1+offset) % len(four)] for offset in range(3)]
cards = ','.join('{avatar_id=%d,portrait_path=%s}' % (id, quote(avatars[id]['AvatarGachaResultImgPath'])) for id in featured)
payload.append("{gacha_id=%d,avatar_id=%d,sort_order=%d,art_path=%s,icon_path=%s,four_stars={%s}}," % (banner["gacha_id"], banner["avatar_id"], index, quote(row["AvatarCutinFrontImgPath"]), quote(row["DefaultAvatarHeadIconPath"]), cards))
payload.append("}}")
adapter = "" if args.assets_only else (ROOT / "internal/gameserver/gacha_presentation.lua").read_text(encoding="utf-8")
digest = hashlib.sha256(("\n".join(payload) + adapter).encode()).hexdigest()
payload.append("_G." + config_name + ".digest=" + quote(digest))
pity = json.loads((ROOT / 'freesr-data.json').read_text(encoding='utf-8')).get('gacha_limited', {})
payload.append('_G.' + config_name + '.pity={since_five_star=%d,since_four_star=%d}' % (pity.get('since_five_star', 0), pity.get('since_four_star', 0)))
if args.assets_only:
payload.append("""
do
local data=_G.__GAC_GACHA_ASSET_CAPTURE_CONFIG
local s=_G.__GAC_GACHA_ART_ASSETS
if not s or s.digest~=data.digest then s={digest=data.digest,index=1};_G.__GAC_GACHA_ART_ASSETS=s end
local flags=CS.System.Enum.ToObject(typeof(CS.System.Reflection.BindingFlags),60)
local manager=CS.RPG.Client.GlobalVars.s_GamePhaseManager
local phase=manager and typeof(CS.RPG.Client.GamePhaseManager):GetField('_CurrentPhase',flags):GetValue(manager)
if phase and not typeof(CS.RPG.Client.BattleGamePhase):IsInstanceOfType(phase) then
local ok,err=pcall(function()
for n=1,8 do
local row=data.banners[s.index]
if not row then
if not s.done then CS.UnityEngine.Debug.Log('[GAC_GACHA_ART_ASSET] END count='..#data.banners);s.done=true end
break
end
s.index=s.index+1
local mgr=CS.RPG.Client.GlobalVars.s_AssetMgr
CS.UnityEngine.Debug.Log('[GAC_GACHA_ART_ASSET] banner='..row.gacha_id..' avatar='..row.avatar_id..' art='..tostring(mgr:Exists(row.art_path))..' icon='..tostring(mgr:Exists(row.icon_path)))
end
end)
if not ok then s.done=true;s.index=#data.banners+1;CS.UnityEngine.Debug.Log('[GAC_GACHA_ART_ASSET] ERROR '..tostring(err)) end
end
end
""")
else:
payload.append(adapter)
args.output.write_text("\n".join(payload) + "\n", encoding="utf-8")
print(f"Rendered {args.limit} mapped banners; assets_only={args.assets_only}; output={args.output}")
if __name__ == "__main__":
main()