Merge of the two project copies into one self-contained plugin (the superset: variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring). Made fully project- and machine-agnostic — no hardcoded paths: - New src/projectPaths.js auto-detects the host .uproject (walk-up), project name, Editor build target, log file, and engine install (EngineAssociation via launcher manifest/registry, else installed-engine scan). All overridable via UE_* env vars. - Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver. - Voxel made optional: Build.cs auto-detects the Voxel plugin (env UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL, so the module builds in projects without Voxel; .uplugin marks Voxel optional. - De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded node_modules/Binaries/Intermediate/__pycache__/secrets from the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
168 lines
6.8 KiB
Python
168 lines
6.8 KiB
Python
"""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
|