- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting. - Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn): Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes. - Formal partners for all ten pets in the arena and portraits, including new VineSprite, ThunderLeopard and TideWhale. - Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands, configurable Unity run_script timeout, companion static stage. - Also includes concurrent work from another tool in this working tree (FlameLion, enemy combat FX, challenge mode, home explorer, server updates). - Resources registered in Doc/ResDesc_*.md; verified on isolated :18081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.9 KiB
Python
61 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""P0 enemy (or companion, --kind Animal) static stage: Hunyuan high GLB -> 2000-tri XPbr bake -> Bundles/Characters/Enemy/{Name} -> static Prefab -> static audit.
|
|
|
|
Usage:
|
|
python Tools/enemy_static_pipeline.py Dashlet 0.5 # bake + install + SetupCharacter + AuditCharacterVisual
|
|
python Tools/enemy_static_pipeline.py Dashlet 0.5 --source Tools/output/enemy_3d/Dashlet/dashlet_forward.glb
|
|
The high model must already face -Y in Blender (check with renders first; rotate with model_tools/bl_orient_character.py).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PROJECT = ROOT / "AIClient"
|
|
|
|
|
|
def run(cmd: list[str], cwd: Path = ROOT) -> str:
|
|
print("$", " ".join(cmd), flush=True)
|
|
done = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
if done.returncode != 0:
|
|
raise SystemExit(f"failed ({done.returncode}): {' '.join(cmd)}\n{done.stdout[-2000:]}\n{done.stderr[-2000:]}")
|
|
return done.stdout
|
|
|
|
|
|
def unity(file: str, entry: str, args: list) -> str:
|
|
out = run(["unity", "command", "run_script", "--project-path", str(PROJECT), "--file", file, "--entry", entry,
|
|
"--args", json.dumps(args), "--timeout_ms", "90000", "--timeout", "120"], PROJECT)
|
|
if '"success":true' not in out.replace(" ", ""):
|
|
raise SystemExit(f"Unity {entry} failed:\n{out[-2000:]}")
|
|
return out
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("name")
|
|
ap.add_argument("height", type=float)
|
|
ap.add_argument("--source", type=Path)
|
|
ap.add_argument("--kind", default="Enemy", choices=("Enemy", "Animal"), help="Animal installs a companion under Characters/Animal")
|
|
a = ap.parse_args()
|
|
low = a.name.lower()
|
|
tag = "enemy" if a.kind == "Enemy" else "companion"
|
|
source = a.source or Path(f"Tools/output/{tag}_3d/{a.name}/{low}.glb")
|
|
game = Path(f"Tools/output/{tag}_game/{low}_game")
|
|
stem = source.stem
|
|
run([sys.executable, "Tools/model_tools/model_lowpoly_bake.py", str(source), "-o", str(game), "--tris", "2000", "--size", "1024",
|
|
"--callback-file", str(game / "bake_status.json")])
|
|
run([sys.executable, "Tools/install_character_bundles.py", "-a", a.name, "--kind", a.kind, "-b", str(game / f"{stem}_xpbr")])
|
|
unity("AgentScripts/SetupCharacter.cs", "SetupCharacter.Run", [a.kind, a.name, a.height])
|
|
audit = ROOT / (f"Tools/output/enemy_characters/{a.name}/static_audit" if a.kind == "Enemy" else f"Tools/output/companions/{a.name}/static_audit")
|
|
unity("AgentScripts/AuditCharacterVisual.cs", "AuditCharacterVisual.Capture",
|
|
[f"Assets/Bundles/Characters/{a.kind}/{a.name}/{a.name}.prefab", str(audit).replace("\\", "/"), a.height])
|
|
print((audit / "static_verification.txt").read_text(encoding="utf-8"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|