94 lines
5.4 KiB
Python
94 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()
|