"""Reproducible presentation derivatives; editable originals stay in SourceAssets.""" import hashlib import json import math import random import re import shutil import struct import wave import zipfile from pathlib import Path import resvg_py from fontTools.ttLib import TTFont from fontTools.varLib.instancer import instantiateVariableFont ROOT=Path(__file__).resolve().parents[2] STRINGS=""" subtitle=机甲搜打撤 · 轨道边境|MECH EXTRACTION / ORBITAL FRONTIER baseTitle=机库指挥台|HANGAR COMMAND enterHangar=进入机库|ENTER HANGAR deploy=整备并出击|DEPLOY resume=继续出击|RESUME SORTIE training=试验场|TRAINING RANGE research=研究与制造|RESEARCH & FABRICATION settings=设置|SETTINGS performance=性能统计|PERFORMANCE perfOn=开启|ON perfOff=关闭|OFF perfPosition=显示位置|POSITION perfTopRight=右上角|TOP RIGHT perfTopLeft=左上角|TOP LEFT perfBottomRight=右下角|BOTTOM RIGHT perfBottomLeft=左下角|BOTTOM LEFT perfHint=60 FPS 参考预算 16.67 ms · — 表示暂无数据|60 FPS BUDGET: 16.67 ms / — MEANS UNAVAILABLE perfFps=FPS|FPS perfFrame=帧耗时|FRAME perfGpu=GPU|GPU perfGame=CPU 游戏|CPU GAME perfRender=CPU 渲染|CPU RENDER perfRhi=RHI|RHI quit=退出游戏|EXIT GAME baseHint=整备装备,搜索物资,活着撤离。|Equip. Search. Extract alive. pilot=驾驶员档案|PILOT DOSSIER warehouse=基地仓库|BASE WAREHOUSE pending=待整理物资|RECOVERY INBOX sorties=出击|SORTIES returns=撤离|RETURNS activeRun=任务进行中 · 可继续|ACTIVE SORTIE / RESUMABLE standby=基地待命|BASE STANDBY carryHint=拖动放置 · R 旋转 · 安全箱机毁保全|DRAG TO PLACE / R ROTATES / SECURE CASE SURVIVES fieldSystems=战地改装与回收|FIELD SYSTEMS fieldHint=安装实物装备,选择货舱物品后回收|INSTALL REAL PARTS / SELECT CARGO FOR RECOVERY fieldEquipment=当前实物装备 · 可战地改装|INSTALLED PARTS / FIELD MODIFICATIONS warehouseHint=滚轮浏览 · 有限容量 · 物品唯一归属|SCROLL TO BROWSE / LIMITED CAPACITY / UNIQUE ITEMS primary=主武器|PRIMARY secondary=副武器|SECONDARY booster=推进器|BOOSTER guard=防御模块|GUARD mass=载重|MASS power=功率|POWER cargo=货仓|CARGO safeCase=安全箱 · 2 × 2|SECURE CASE / 2 × 2 cellsUsed=格已用|CELLS USED sort=整理仓库|SORT STASH podSelect=选择主动回收物品|SELECT RECOVERY CARGO podRule=每次出击一舱 · 2 × 2 · 最多四件|ONE POD PER SORTIE / 2 × 2 / UP TO FOUR ITEMS holdPod=关闭物品栏,落地静止长按|CLOSE INVENTORY, STAND STILL, HOLD selected=已选择|SELECTED uninstall=卸入货仓|UNINSTALL TO CARGO borrow=借用基础件|BORROW BASELINE PART borrowed=借用件|BORROWED equip=装备此物品|EQUIP ITEM toCargo=预装到货仓|PREPARE CARGO toSafe=预装到安全箱|PREPARE SECURE CASE unprepare=取消预装|CANCEL PREPARATION install=安装装备|INSTALL PART transfer=转移至另一容器|TRANSFER CONTAINER recycle=回收已研究蓝图|RECYCLE KNOWN BLUEPRINT dragHint=拖拽摆放 · R 旋转 · 数量可拆分|DRAG / R ROTATES / QUANTITY SPLITS STACKS selectItem=选择物品查看操作|SELECT AN ITEM FOR ACTIONS back=返回|BACK researchHint=研究消耗合金 2、电芯 1;制造按配方消耗实物材料。|RESEARCH COSTS 2 ALLOY + 1 CELL. FABRICATION USES PHYSICAL STOCK. manufacture=制造装备|FABRICATE PART inboxHint=仓库满时物资保留在此;整理完才能再次出击。|OVERFLOW IS PRESERVED HERE. CLEAR INBOX BEFORE A NEW SORTIE. claim=收入仓库|CLAIM claimAll=全部收入仓库|CLAIM ALL discard=丢弃|DISCARD confirmDiscard=再次点击丢弃以确认。|CLICK DISCARD AGAIN TO CONFIRM. settingsHint=显示、鼠标、声音与键位立即生效并保存。|DISPLAY, MOUSE, AUDIO AND KEY BINDINGS APPLY AND SAVE. display=显示|DISPLAY fullscreen=切换全屏 / 窗口|TOGGLE FULLSCREEN / WINDOWED sensitivity=鼠标灵敏度|MOUSE SENSITIVITY aimSensitivity=瞄准灵敏度倍率|AIM SENSITIVITY fire=主武器开火|PRIMARY FIRE aim=瞄准|AIM invertY=反转鼠标纵轴|INVERT MOUSE Y controls=键鼠操作|KEYBOARD & MOUSE forward=向前|FORWARD backward=向后|BACKWARD left=向左|LEFT right=向右|RIGHT jump=跳跃 / 轻型悬浮|JUMP / LIGHT-CHASSIS LIFT boost=推进冲刺|BOOST reload=装填|RELOAD view=切换第一 / 越肩视角|COCKPIT / SHOULDER VIEW interact=交互 / 登机|INTERACT / BOARD inventory=携带物品|INVENTORY pod=主动回收(长按)|RECOVERY POD (HOLD) pressKey=请按新按键 · Esc 取消|PRESS A KEY / ESC CANCELS keyConflict=该按键已被其他操作使用。|THAT KEY IS ALREADY ASSIGNED. volume=音量|VOLUME quality=切换画质等级|CYCLE QUALITY paused=任务暂停|SORTIE PAUSED upgrade=局内改装|FIELD UPGRADE pauseHint=查看携带物资,或保存进度后返回基地。|REVIEW YOUR CARGO OR SAVE YOUR SORTIE AND RETURN TO BASE. saveAndHome=保存并返回主页|SAVE & RETURN HOME upgradeDamage=火力强化 · 提升伤害|FIREPOWER / INCREASE DAMAGE upgradeRepair=战地修复 · 恢复装甲|FIELD REPAIR / RESTORE ARMOR upgradeEnergy=能源维护 · 恢复能源|ENERGY SERVICE / RESTORE ENERGY armor=装甲|ARMOR energy=能源|ENERGY stage=区域|SECTOR salvage=携带|CARGO routeDone=路线完成,仍需主动撤离|ROUTE COMPLETE / EXTRACT TO BANK LOOT objectiveDone=目标完成,可前往下一片区|OBJECTIVE COMPLETE / NEXT SECTOR UNLOCKED findObjective=搜索物资并寻找目标终端|SEARCH SALVAGE AND FIND THE OBJECTIVE TERMINAL podUsed=主动回收舱:已使用|RECOVERY POD: USED podAvailable=主动回收舱:1 / 1|RECOVERY POD: 1 / 1 reloading=装填中|RELOADING wait=接应抵达倒计时|EXTRACTION ARRIVES IN podHolding=货舱回收准备中,保持静止|RECOVERY ARMING / HOLD POSITION footer=键鼠操作 · Esc 返回 · 搜索,战斗,撤离|KEYBOARD & MOUSE / ESC BACK / SEARCH, FIGHT, EXTRACT completed=完成路线|COMPLETED ROUTES rangeStats=试验场 · 命中 / 关卡 / 用时|RANGE / HITS / GATES / TIME rangeReset=试验场计时和命中已重置。|RANGE TIMER AND HITS RESET. courseGate=机动计时门|TIMED GATE extraction=撤离平台|EXTRACTION PAD routeBoss=首领路线|BOSS ROUTE routeEscort=护送路线|ESCORT ROUTE rangeResetLabel=重置试验场|RESET RANGE saved=操作已保存|SAVED invalid=该操作当前不可用。|THAT ACTION IS NOT AVAILABLE. missing=找不到对应物品。|ITEM NOT FOUND. noSpace=空间不足或物品无法放入。|NOT ENOUGH SPACE OR ITEM DOES NOT FIT. locked=当前状态禁止该操作。|ACTION LOCKED IN THE CURRENT STATE. budget=装备超出底盘载重或功率预算。|LOADOUT EXCEEDS CHASSIS MASS OR POWER BUDGET. funds=可用材料不足。|INSUFFICIENT AVAILABLE MATERIALS. duplicate=该物资或事件已经处理。|THIS SOURCE OR EVENT HAS ALREADY BEEN PROCESSED. saveFailed=保存失败,操作未结算。请检查磁盘。|SAVE FAILED. TRANSACTION NOT COMMITTED. CHECK DISK. corrupt=存档及备份无法读取,已保留原文件。|PROFILE AND BACKUP UNREADABLE. ORIGINAL FILES PRESERVED. unsupported=存档版本不受支持。|UNSUPPORTED SAVE VERSION. destroyed=机体损毁。安全箱已保全,其余物资损失。|MECH DESTROYED. SECURE CASE PRESERVED; OTHER CARGO LOST. extracted=撤离成功,物资已回收入库。|EXTRACTED. RECOVERED ITEMS BANKED. nothingNear=靠近物资箱、终端或撤离点后交互。|MOVE CLOSER TO SALVAGE, TERMINALS OR EXTRACTION. loot=已搜索物资|SALVAGE RECOVERED called=已呼叫接应,等待后需主动登机。|EXTRACTION CALLED. WAIT, THEN BOARD MANUALLY. finalObjective=完成首领战或护送后仍需主动撤离。|DEFEAT THE BOSS OR COMPLETE ESCORT, THEN EXTRACT. escort=护送运输车|ESCORT TRANSPORT explore=搜索箱体 · 击败敌人 · 终端推进 · 绿色平台撤离|SEARCH CRATES / FIGHT / ACTIVATE TERMINALS / GREEN PAD EXTRACTS search=搜索|SEARCH board=登机并撤离|BOARD & EXTRACT call=呼叫接应|CALL EXTRACTION nextStage=前往下一片区(右侧为护送路线)|NEXT SECTOR (RIGHT GATE: ESCORT ROUTE) activate=启动目标终端|ACTIVATE OBJECTIVE TERMINAL readyToBoard=接应已抵达,回到平台按交互键登机。|TRANSPORT READY. RETURN TO PAD AND PRESS INTERACT TO BOARD. podSent=选定货舱物资已发射回收,本次回收舱已用尽。|SELECTED CARGO RECOVERED. THIS SORTIE'S POD IS USED. podInterrupted=回收被移动、起跳或命中打断。|RECOVERY INTERRUPTED BY MOVEMENT, AIRBORNE STATE OR DAMAGE. escortLost=护送运输车已损毁。|ESCORT TRANSPORT DESTROYED. """ def prepare_localization(): raw=ROOT/"Content/IRONSPORE/Raw" pairs={} for line in STRINGS.strip().splitlines(): key,text=line.split("=",1) pairs["ue."+key]=text.split("|",1) required=set() for file in (ROOT/"Source").rglob("*.cpp"): required.update(re.findall(r'TEXT\("(ue\.[^" ]+)"\)',file.read_text(encoding="utf-8"))) if required-pairs.keys(): raise RuntimeError("Missing strings: "+str(sorted(required-pairs.keys()))) for index,locale in enumerate(("zh-Hans","en")): data=json.loads((ROOT/f"SourceAssets/Localization/Tables/{locale}.json").read_text(encoding="utf-8")) entries={item["key"]:item["value"] for item in data["entries"]} entries.update({key:values[index] for key,values in pairs.items()}) folder=raw/"Localization";folder.mkdir(exist_ok=True) (folder/f"{locale}.json").write_text(json.dumps({"entries":[{"key":k,"value":v} for k,v in entries.items()]},ensure_ascii=False,indent=2)+"\n",encoding="utf-8") def prepare(): raw=ROOT/"Content/IRONSPORE/Raw" (raw/"Fonts").mkdir(parents=True,exist_ok=True) font=TTFont(ROOT/"SourceAssets/ThirdParty/Adobe/NotoSansSC/NotoSansSC-VF.ttf") font=instantiateVariableFont(font,{"wght":400},inplace=True) for name in font["name"].names: if name.nameID in (1,4,6,16,17): value="Regular" if name.nameID==17 else "IronsSansSC-Regular" if name.nameID==6 else "Irons Sans SC" name.string=value.encode(name.getEncoding()) font.recalcTimestamp=False font.save(raw/"Fonts/IronsSansSC-Regular.ttf") prepare_localization() derived=ROOT/"SourceAssets/Derived/UI";derived.mkdir(parents=True,exist_ok=True) for svg in (ROOT/"SourceAssets/UI").rglob("*.svg"): (derived/f"T_{svg.stem}.png").write_bytes(resvg_py.svg_to_bytes(svg_path=str(svg),zoom=2,skip_system_fonts=True)) audio=ROOT/"SourceAssets/Audio/Generated";audio.mkdir(parents=True,exist_ok=True) rng=random.Random(7001) for name,frequency,duration in [("S_Shot",140,.12),("S_Impact",75,.18),("S_Secondary",65,.42),("S_UI",700,.08)]: rate=44100;samples=[] for n in range(int(rate*duration)): t=n/rate;decay=(1-t/duration)**3 value=(math.sin(2*math.pi*frequency*t*(1-.5*t/duration))*.55+rng.uniform(-1,1)*.45)*decay*.65 samples.append(struct.pack("