"""Preset library — typical Blueprint patterns packaged as one-line MCP calls. Each preset is a pure function: args dict → NodeSpec graph dict (apply_graph format). The dispatcher then runs apply_graph on it. Add a new preset: 1. Write `def my_preset(args): return {...nodes/edges...}` below. 2. Register it in PRESETS at the bottom. 3. Call via MCP: preset_op({name: "my_preset", args: {...}}). Ships with: print_on_begin_play {bp_path, text?} — BeginPlay → PrintString print_on_event {bp_path, event, text?} — any event → PrintString toggle_visibility {bp_path, event, component} — event → ToggleVisibility on a component damage_drops_health {bp_path, health_var} — AnyDamage → Health -= Damage, branch on <=0 → DestroyActor """ from __future__ import annotations import json import os import traceback try: import unreal # type: ignore except ImportError: unreal = None # --- preset implementations --------------------------------------------------- def print_on_begin_play(args: dict) -> dict: text = args.get("text", "BeginPlay") return { "bp_path": args["bp_path"], "function": "EventGraph", "mode": "append", "nodes": [ {"id": "ev", "kind": "event", "target": "ReceiveBeginPlay", "position": [0, 0]}, {"id": "print", "kind": "call_function", "target": "/Script/Engine.KismetSystemLibrary:PrintString", "position": [300, 0], "params": {"InString": text}}, ], "edges": [{"from": ["ev", "then"], "to": ["print", "execute"]}], } def print_on_event(args: dict) -> dict: event = args["event"] # e.g. "ReceiveActorBeginOverlap" text = args.get("text", event) return { "bp_path": args["bp_path"], "function": "EventGraph", "mode": "append", "nodes": [ {"id": "ev", "kind": "event", "target": event, "position": [0, 0]}, {"id": "print", "kind": "call_function", "target": "/Script/Engine.KismetSystemLibrary:PrintString", "position": [300, 0], "params": {"InString": text}}, ], "edges": [{"from": ["ev", "then"], "to": ["print", "execute"]}], } def toggle_visibility(args: dict) -> dict: event = args.get("event", "ReceiveActorBeginOverlap") comp = args["component"] # variable name of a SceneComponent on this BP return { "bp_path": args["bp_path"], "function": "EventGraph", "mode": "append", "nodes": [ {"id": "ev", "kind": "event", "target": event, "position": [0, 0]}, {"id": "getc", "kind": "variable_get", "target": comp, "position": [180, 80]}, {"id": "toggle", "kind": "call_function", "target": "/Script/Engine.SceneComponent:ToggleVisibility", "position": [380, 0]}, ], "edges": [ {"from": ["ev", "then"], "to": ["toggle", "execute"]}, {"from": ["getc", comp], "to": ["toggle", "self"]}, ], } def damage_drops_health(args: dict) -> dict: """AnyDamage → Health -= Damage; if Health<=0 → DestroyActor.""" hv = args.get("health_var", "Health") return { "bp_path": args["bp_path"], "function": "EventGraph", "mode": "append", "nodes": [ {"id": "ev", "kind": "event", "target": "ReceiveAnyDamage", "position": [-200, 0]}, {"id": "getH", "kind": "variable_get", "target": hv, "position": [-20, 60]}, {"id": "sub", "kind": "call_function", "target": "/Script/Engine.KismetMathLibrary:Subtract_FloatFloat", "position": [160, 60]}, {"id": "setH", "kind": "variable_set", "target": hv, "position": [340, 0]}, {"id": "getH2", "kind": "variable_get", "target": hv, "position": [560, 80]}, {"id": "le", "kind": "call_function", "target": "/Script/Engine.KismetMathLibrary:LessEqual_FloatFloat", "position": [720, 60]}, {"id": "br", "kind": "branch", "position": [900, 0]}, {"id": "destroy", "kind": "call_function", "target": "/Script/Engine.Actor:K2_DestroyActor", "position": [1080, 0]}, ], "edges": [ {"from": ["ev", "then"], "to": ["setH", "execute"]}, {"from": ["getH", hv], "to": ["sub", "A"]}, {"from": ["ev", "Damage"], "to": ["sub", "B"]}, {"from": ["sub", "ReturnValue"], "to": ["setH", hv]}, {"from": ["setH", "then"], "to": ["br", "execute"]}, {"from": ["getH2", hv], "to": ["le", "A"]}, {"from": ["le", "ReturnValue"], "to": ["br", "Condition"]}, {"from": ["br", "then"], "to": ["destroy", "execute"]}, ], } # --- dispatch ----------------------------------------------------------------- PRESETS = { "print_on_begin_play": print_on_begin_play, "print_on_event": print_on_event, "toggle_visibility": toggle_visibility, "damage_drops_health": damage_drops_health, } def entrypoint(payload_json: str) -> str: try: p = json.loads(payload_json) except Exception as e: # noqa: BLE001 return _tee({"ok": False, "error": f"bad json: {e}"}, {}) name = p.get("name") if name == "__list__": return _tee({"ok": True, "presets": sorted(PRESETS.keys())}, p) fn = PRESETS.get(name) if not fn: return _tee({"ok": False, "error": f"unknown preset {name!r}", "available": sorted(PRESETS.keys())}, p) try: spec = fn(p.get("args") or {}) # Run through apply_graph import importlib, apply_graph importlib.reload(apply_graph) result = apply_graph.apply(spec) result["preset"] = name return _tee(result, p) except Exception as e: # noqa: BLE001 return _tee({"ok": False, "error": str(e), "traceback": traceback.format_exc(), "preset": name}, p) def _tee(result: dict, p: dict) -> str: txt = json.dumps(result) try: if unreal is not None: sd = unreal.Paths.project_saved_dir() + "MCP/" os.makedirs(sd, exist_ok=True) rid = p.get("__rid") or "last" with open(sd + "last.json", "w", encoding="utf-8") as f: f.write(txt) if rid != "last": with open(sd + f"{rid}.json", "w", encoding="utf-8") as f: f.write(txt) unreal.log("[MCP-PRESET] " + txt[:1500]) except Exception: pass return txt