17#include "../system/system_utils.h"
18#include "../system/system_consts.h"
19#include "../NodeGraphCore/GlobalTemplateBlackboard.h"
21#include "../third_party/imgui/imgui.h"
22#include "../third_party/imnodes/imnodes.h"
23#include "../json_helper.h"
24#include "../TaskSystem/TaskGraphLoader.h"
34#include <unordered_set>
136 <<
"' has no embedded presets\n";
187 return nodeID * 10000 + 0;
192 return nodeID * 10000 + 100 +
pinIndex;
197 return nodeID * 10000 + 200 +
pinIndex;
202 return nodeID * 10000 + 300 +
pinIndex;
241 default:
return {
"Out"};
287 if (!std::isfinite(x) || !std::isfinite(y))
289 SYSTEM_LOG <<
"[VSEditor] AddNode: warning - non-finite position provided (x="
290 << x <<
", y=" << y <<
"), resetting to (0, 0)\n";
400 [nodeID](
const VSEditorNode&
n) { return n.nodeID == nodeID; }),
486 <<
"), using auto-layout\n";
487 eNode.posX = 200.0f *
static_cast<float>(
i);
493 eNode.posX = 200.0f *
static_cast<float>(
i);
506 eNode.def.conditionOperandRefs);
550 std::vector<std::string>
outPins;
558 for (
size_t d = 0;
d <
srcNode->DynamicExecOutputPins.size(); ++
d)
607 for (
size_t p = 0;
p <
srcNode->DataPins.size(); ++
p)
632 for (
size_t p = 0;
p <
dstNode->dynamicPins.size(); ++
p)
648 for (
size_t p = 0;
p <
inPins.size(); ++
p)
660 for (
size_t p = 0;
p <
dstNode->DataPins.size(); ++
p)
698 std::unordered_map<int, std::pair<float, float> >
savedPos;
705 if (!std::isfinite(posX) || !std::isfinite(posY) ||
712 <<
" had garbage position, reset to defaults\n";
755 SYSTEM_LOG <<
"[VSEditor] SyncEditorNodesFromTemplate: node #" << def.
NodeID
757 <<
"), falling back\n";
762 eNode.posY =
it->second.second;
783 eNode.posY =
it->second.second;
812 SYSTEM_LOG <<
"[VSEditor] SyncEditorNodesFromTemplate: node #" <<
eNode.nodeID
813 <<
" restored to (" <<
eNode.posX <<
"," <<
eNode.posY <<
")\n";
895 for (
size_t d = 0;
d <
srcNode->DynamicExecOutputPins.size(); ++
d)
930 const std::string& path)
955 <<
"' has no embedded presets - starting with empty bank\n";
967 SYSTEM_LOG <<
"[VSEditor] LoadTemplate: initialized EntityBlackboard with "
976 SYSTEM_LOG <<
"[VSEditor] LoadTemplate: restored global variable overrides from graph\n";
1003 SYSTEM_LOG <<
"[VisualScriptEditorPanel] Save() called. m_currentPath='"
1008 SYSTEM_LOG <<
"[VisualScriptEditorPanel] Save() aborted: m_currentPath is empty\n";
1072 SYSTEM_LOG <<
"[VisualScriptEditorPanel] Save() "
1079 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SaveAs() called. path='" << path <<
"'\n";
1115 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SaveAs() succeeded: '" << path <<
"'\n";
1119 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SaveAs() FAILED: '" << path <<
"'\n";
1186 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SyncPresetsFromRegistryToTemplate: synced "
1192 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: writing to '" << path <<
"'\n";
1197 root[
"schema_version"] = 4;
1199 root[
"graphType"] =
"VisualScript";
1212 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: skipping invalid blackboard entry"
1213 <<
" (key='" <<
entry.Key <<
"', type=None)\n";
1220 e[
"isGlobal"] =
entry.IsGlobal;
1230 ?
entry.Default.AsBool() :
false;
1235 ?
entry.Default.AsInt() : 0;
1238 e[
"type"] =
"Float";
1240 ?
entry.Default.AsFloat() : 0.0f;
1243 e[
"type"] =
"String";
1245 ?
entry.Default.AsString() : std::string(
"");
1248 e[
"type"] =
"EntityID";
1249 e[
"value"] = std::to_string(
1251 ?
entry.Default.AsEntityID() : 0);
1259 ?
entry.Default.AsVector()
1265 e[
"type"] =
"Vector";
1271 e[
"value"] =
nullptr;
1279 <<
" invalid blackboard entries skipped (BUG-001)\n";
1297 if (!def.
BBKey.empty())
1322 if (!
binding.LiteralValue.IsNone())
1324 switch (
binding.LiteralValue.GetType())
1340 const ::Vector
v =
binding.LiteralValue.AsVector();
1412 if (!
sc.customLabel.empty())
1425 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: serialized mathOpRef for MathOp node "
1440 cj[
"leftMode"] =
cond.leftMode;
1441 if (!
cond.leftPin.empty())
1442 cj[
"leftPin"] =
cond.leftPin;
1443 if (!
cond.leftVariable.empty())
1444 cj[
"leftVariable"] =
cond.leftVariable;
1445 if (
cond.leftMode ==
"Const" && !
cond.leftConstValue.IsNone())
1448 switch (
lv.GetType()) {
1458 cj[
"operator"] =
cond.operatorStr;
1461 cj[
"rightMode"] =
cond.rightMode;
1462 if (!
cond.rightPin.empty())
1463 cj[
"rightPin"] =
cond.rightPin;
1464 if (!
cond.rightVariable.empty())
1465 cj[
"rightVariable"] =
cond.rightVariable;
1466 if (
cond.rightMode ==
"Const" && !
cond.rightConstValue.IsNone())
1469 switch (
rv.GetType()) {
1499 refObj[
"conditionIndex"] =
static_cast<int>(
i);
1504 switch (
ref.leftOperand.mode)
1507 lj[
"mode"] =
"Variable";
1508 lj[
"variableName"] =
ref.leftOperand.variableName;
1511 lj[
"mode"] =
"Const";
1512 lj[
"constValue"] =
ref.leftOperand.constValue;
1516 lj[
"dynamicPinID"] =
ref.leftOperand.dynamicPinID;
1519 lj[
"mode"] =
"Const";
1530 switch (
ref.rightOperand.mode)
1533 rj[
"mode"] =
"Variable";
1534 rj[
"variableName"] =
ref.rightOperand.variableName;
1537 rj[
"mode"] =
"Const";
1538 rj[
"constValue"] =
ref.rightOperand.constValue;
1542 rj[
"dynamicPinID"] =
ref.rightOperand.dynamicPinID;
1545 rj[
"mode"] =
"Const";
1559 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: Phase 24: serialized "
1577 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: Phase 24: serialized "
1578 << def.
conditionRefs.size() <<
" nodeConditionRefs for node "
1610 if (!
binding.LiteralValue.IsNone())
1612 switch (
binding.LiteralValue.GetType())
1628 const ::Vector
v =
binding.LiteralValue.AsVector();
1705 n[
"position"] =
pos;
1720 c[
"fromNode"] =
conn.SourceNodeID;
1721 c[
"fromPin"] =
conn.SourcePinName;
1722 c[
"toNode"] =
conn.TargetNodeID;
1723 c[
"toPin"] =
conn.TargetPinName;
1734 c[
"fromNode"] =
conn.SourceNodeID;
1735 c[
"fromPin"] =
conn.SourcePinName;
1736 c[
"toNode"] =
conn.TargetNodeID;
1737 c[
"toPin"] =
conn.TargetPinName;
1747 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: Phase 24 - serialized "
1748 <<
"global variable values\n";
1764 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: Phase 24 - serialized "
1769 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite: opening '"
1770 << path <<
"' for writing\n";
1771 std::ofstream
ofs(path);
1774 std::cerr <<
"[VisualScriptEditorPanel] Cannot open file for write: " << path << std::endl;
1775 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite FAILED: cannot open '"
1782 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SerializeAndWrite succeeded: '" << path <<
"'\n";
1798 if (e.Key.empty()) {
1799 SYSTEM_LOG <<
"[VSEditor] ValidateAndClean: removing entry with empty key\n";
1803 SYSTEM_LOG <<
"[VSEditor] ValidateAndClean: removing entry '"
1804 << e.Key <<
"' with VariableType::None\n";
1815 <<
" invalid blackboard entries\n";
1820void VisualScriptEditorPanel::CommitPendingBlackboardEdits()
1822 for (std::unordered_map<int, std::string>::iterator
it = m_pendingBlackboardEdits.begin();
1823 it != m_pendingBlackboardEdits.end(); ++
it)
1825 int idx =
it->first;
1826 if (
idx >= 0 &&
idx <
static_cast<int>(m_template.Blackboard.size()))
1828 m_template.Blackboard[
static_cast<size_t>(
idx)].
Key =
it->second;
1831 m_pendingBlackboardEdits.clear();
1838void VisualScriptEditorPanel::ResetViewportBeforeSave()
1840 SYSTEM_LOG <<
"[VSEditor] ResetViewportBeforeSave: saving current panning\n";
1842 m_viewportResetDone =
true;
1848 ImNodes::EditorContextResetPanning(
ImVec2(0.0f, 0.0f));
1849 SYSTEM_LOG <<
"[VSEditor] ResetViewportBeforeSave: panning reset to (0,0) "
1850 <<
"(was " << m_lastViewportPanning.x <<
"," << m_lastViewportPanning.y <<
")\n";
1853void VisualScriptEditorPanel::AfterSave()
1855 if (!m_viewportResetDone)
1859 ImNodes::EditorContextResetPanning(m_lastViewportPanning.ToImVec2());
1860 m_viewportResetDone =
false;
1861 SYSTEM_LOG <<
"[VSEditor] AfterSave: viewport panning restored to ("
1862 << m_lastViewportPanning.x <<
"," << m_lastViewportPanning.y <<
")\n";
1883std::vector<BlackboardEntry> VisualScriptEditorPanel::GetVariablesByType(
1884 const std::vector<BlackboardEntry>&
allVars,
1887 std::vector<BlackboardEntry>
filtered;
1890 if (
allVars[
i].Type == expectedType)
1904void VisualScriptEditorPanel::PerformUndo()
1906 if (!m_undoStack.CanUndo())
1909 std::string
desc = m_undoStack.PeekUndoDescription();
1911 m_undoStack.Undo(m_template);
1912 SyncEditorNodesFromTemplate();
1920 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
1922 ImNodes::SetNodeGridSpacePos(
1923 m_editorNodes[
i].nodeID,
1924 ImVec2(m_editorNodes[
i].posX, m_editorNodes[
i].posY));
1930 m_justPerformedUndoRedo =
true;
1931 m_skipPositionSyncNextFrame =
true;
1932 m_nodeDragStartPositions.clear();
1934 m_verificationDone =
false;
1935 SYSTEM_LOG <<
"[VSEditor] Undo complete. Template now has "
1936 << m_template.Nodes.size() <<
" nodes, "
1937 << m_template.ExecConnections.size() <<
" exec connections\n";
1940void VisualScriptEditorPanel::PerformRedo()
1942 if (!m_undoStack.CanRedo())
1945 std::string
desc = m_undoStack.PeekRedoDescription();
1947 m_undoStack.Redo(m_template);
1948 SyncEditorNodesFromTemplate();
1953 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
1955 ImNodes::SetNodeGridSpacePos(
1956 m_editorNodes[
i].nodeID,
1957 ImVec2(m_editorNodes[
i].posX, m_editorNodes[
i].posY));
1960 m_justPerformedUndoRedo =
true;
1961 m_skipPositionSyncNextFrame =
true;
1962 m_nodeDragStartPositions.clear();
1964 m_verificationDone =
false;
1965 SYSTEM_LOG <<
"[VSEditor] Redo complete. Template now has "
1966 << m_template.Nodes.size() <<
" nodes, "
1967 << m_template.ExecConnections.size() <<
" exec connections\n";
1970void VisualScriptEditorPanel::Render()
1975 ImGui::Begin(
"VS Graph Editor", &m_visible);
1980 m_libraryPanel->Render();
1983void VisualScriptEditorPanel::RenderContent()
1986 RenderSaveAsDialog();
1990 float totalWidth = ImGui::GetContentRegionAvail().x;
1993 if (m_propertiesPanelWidth <= 0.0f)
1997 if (m_propertiesPanelWidth < 200.0f) m_propertiesPanelWidth = 200.0f;
2015 if (ImGui::IsItemHovered())
2019 m_propertiesPanelWidth -= ImGui::GetIO().MouseDelta.x;
2020 if (m_propertiesPanelWidth < 200.0f) m_propertiesPanelWidth = 200.0f;
2023 ImGui::PopStyleColor(3);
2028 ImGui::BeginChild(
"VSRightPanel",
ImVec2(m_propertiesPanelWidth, 0),
true);
2034 if (m_nodePropertiesPanelHeight <= 0.0f)
2049 ImGui::BeginChild(
"Part_A_NodeProps",
ImVec2(0, m_nodePropertiesPanelHeight),
false,
2051 RenderNodePropertiesPanel();
2059 if (ImGui::IsItemHovered())
2063 m_nodePropertiesPanelHeight += ImGui::GetIO().MouseDelta.y;
2066 ImGui::PopStyleColor(3);
2069 ImGui::BeginChild(
"Part_B_PresetBank",
ImVec2(0, m_presetBankPanelHeight),
false,
2071 RenderPresetBankPanel();
2079 if (ImGui::IsItemHovered())
2083 m_presetBankPanelHeight += ImGui::GetIO().MouseDelta.y;
2086 ImGui::PopStyleColor(3);
2095 ImGui::SameLine(150.0f);
2097 ImGui::PopStyleVar();
2102 RenderLocalVariablesPanel();
2104 RenderGlobalVariablesPanel();
2111void VisualScriptEditorPanel::RenderToolbar()
2114 const char* title = m_currentPath.empty()
2115 ?
"Untitled VS Graph"
2116 : m_currentPath.c_str();
2117 ImGui::TextDisabled(
"%s%s", title, m_dirty ?
" *" :
"");
2120 if (ImGui::Button(
"Save"))
2122 SYSTEM_LOG <<
"[VisualScriptEditorPanel] Save button clicked. m_currentPath='"
2123 << m_currentPath <<
"'\n";
2124 if (m_currentPath.empty())
2126 m_showSaveAsDialog =
true;
2130 ImGui::OpenPopup(
"SaveError");
2134 if (ImGui::Button(
"Save As"))
2136 m_showSaveAsDialog =
true;
2142 if (ImGui::Button(
"Verify##gvs"))
2147 if (ImGui::Button(
"Condition Presets"))
2149 m_libraryPanel->Open();
2152 if (m_verificationDone)
2154 if (m_verificationResult.HasErrors())
2157 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
2159 if (m_verificationResult.issues[
i].severity == VSVerificationSeverity::Error)
2162 ImGui::TextColored(
ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
2165 else if (m_verificationResult.HasWarnings())
2167 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"[OK - warnings]");
2171 ImGui::TextColored(
ImVec4(0.3f, 1.0f, 0.3f, 1.0f),
"[OK]");
2181 SYSTEM_LOG <<
"[VisualScriptEditorPanel] Ctrl+S pressed. m_currentPath='"
2182 << m_currentPath <<
"'\n";
2183 if (m_currentPath.empty())
2185 m_showSaveAsDialog =
true;
2189 ImGui::OpenPopup(
"SaveError");
2195 m_undoStack.CanUndo())
2202 m_undoStack.CanRedo())
2208 if (ImGui::BeginPopup(
"SaveError"))
2210 ImGui::TextColored(
ImVec4(1,0,0,1),
"Save failed — check file path.");
2211 if (ImGui::Button(
"OK")) ImGui::CloseCurrentPopup();
2216void VisualScriptEditorPanel::RenderSaveAsDialog()
2218 if (m_showSaveAsDialog)
2220 ImGui::OpenPopup(
"SaveAsDialog");
2221 m_showSaveAsDialog =
false;
2226 m_saveAsExtension =
".ats";
2227 if (!m_currentPath.empty())
2229 size_t dotPos = m_currentPath.rfind(
'.');
2230 if (
dotPos != std::string::npos)
2231 m_saveAsExtension = m_currentPath.substr(
dotPos);
2236 if (!m_currentPath.empty())
2238 size_t lastSlash = m_currentPath.find_last_of(
"/\\");
2244 if (
dotPos != std::string::npos)
2255 ImGui::Text(
"Save Visual Script As");
2259 ImGui::Text(
"Directory:");
2261 if (ImGui::BeginCombo(
"##SaveDir", m_saveAsDirectory.c_str()))
2263 static const char*
dirs[] = {
2265 "Blueprints/AI/Tests",
2266 "Gamedata/TaskGraph/Examples",
2267 "Gamedata/TaskGraph/Templates"
2271 bool selected = (m_saveAsDirectory ==
dirs[
i]);
2272 if (ImGui::Selectable(
dirs[
i], selected))
2273 m_saveAsDirectory =
dirs[
i];
2275 ImGui::SetItemDefaultFocus();
2281 ImGui::Text(
"Filename:");
2283 ImGui::InputText(
"##FileName", m_saveAsFilename,
sizeof(m_saveAsFilename));
2286 ImGui::TextDisabled(
"Full path: %s/%s%s",
2287 m_saveAsDirectory.c_str(),
2289 m_saveAsExtension.c_str());
2296 ImGui::BeginDisabled();
2297 if (ImGui::Button(
"Save",
ImVec2(120, 0)))
2299 std::string fullPath = m_saveAsDirectory +
"/" +
2300 std::string(m_saveAsFilename) + m_saveAsExtension;
2301 SYSTEM_LOG <<
"[VisualScriptEditorPanel] SaveAs dialog confirmed. fullPath='"
2302 << fullPath <<
"'\n";
2303 if (SaveAs(fullPath))
2305 std::cout <<
"[VisualScriptEditorPanel] Saved to: " << fullPath << std::endl;
2306 ImGui::CloseCurrentPopup();
2310 ImGui::OpenPopup(
"SaveAsError");
2314 ImGui::EndDisabled();
2317 if (ImGui::Button(
"Cancel",
ImVec2(120, 0)))
2319 ImGui::CloseCurrentPopup();
2325 ImGui::TextColored(
ImVec4(1, 0, 0, 1),
"Save failed — check directory and permissions.");
2326 if (ImGui::Button(
"OK")) ImGui::CloseCurrentPopup();
2334void VisualScriptEditorPanel::RenderCanvas()
2338 if (m_imnodesContext)
2339 ImNodes::EditorContextSet(m_imnodesContext);
2345 if (m_needsPositionSync)
2347 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2349 ImNodes::SetNodeGridSpacePos(
2350 m_editorNodes[
i].nodeID,
2351 ImVec2(m_editorNodes[
i].posX, m_editorNodes[
i].posY));
2353 m_needsPositionSync =
false;
2357 if (m_focusNodeID >= 0)
2359 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2361 if (m_editorNodes[
i].nodeID == m_focusNodeID)
2364 ImNodes::SetNodeGridSpacePos(
2366 ImVec2(m_editorNodes[
i].posX, m_editorNodes[
i].posY));
2373 ImNodes::BeginNodeEditor();
2379 RenderNodePalette();
2385 for (
size_t li = 0;
li < m_editorLinks.size(); ++
li)
2392 int activeNodeID = DebugController::Get().GetCurrentNodeID();
2394 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2398 bool hasBreakpoint = DebugController::Get().HasBreakpoint(
2401 DebugController::Get().IsDebugging());
2405 if (m_verificationDone)
2407 for (
size_t vi = 0;
vi < m_verificationResult.issues.size(); ++
vi)
2409 if (m_verificationResult.issues[
vi].nodeID ==
eNode.nodeID &&
2410 m_verificationResult.issues[
vi].severity == VSVerificationSeverity::Error)
2434 if (
eNode.def.Type == TaskNodeType::GetBBValue &&
eNode.def.DataPins.empty())
2438 pinOut.Dir = DataPinDir::Output;
2439 pinOut.PinType = VariableType::Float;
2441 std::cerr <<
"[VSEditor] Initialized DataPins for GetBBValue (Variable) node #" <<
eNode.nodeID <<
"\n";
2445 if (
eNode.def.Type == TaskNodeType::MathOp &&
eNode.def.DataPins.empty())
2450 pinA.Dir = DataPinDir::Input;
2451 pinA.PinType = VariableType::Float;
2456 pinB.Dir = DataPinDir::Input;
2457 pinB.PinType = VariableType::Float;
2463 pinResult.PinType = VariableType::Float;
2466 std::cerr <<
"[VSEditor] Initialized DataPins for MathOp node #" <<
eNode.nodeID <<
"\n";
2470 if (
eNode.def.Type == TaskNodeType::SetBBValue &&
eNode.def.DataPins.empty())
2474 pinIn.Dir = DataPinDir::Input;
2475 pinIn.PinType = VariableType::Float;
2477 std::cerr <<
"[VSEditor] Initialized DataPins for SetBBValue node #" <<
eNode.nodeID <<
"\n";
2480 std::vector<std::pair<std::string, VariableType>>
dataIn,
dataOut;
2481 for (
size_t p = 0;
p <
eNode.def.DataPins.size(); ++
p)
2484 if (
pin.Dir == DataPinDir::Input)
2491 if (
eNode.def.Type == TaskNodeType::Branch && m_branchRenderer)
2503 ImNodes::BeginNode(
eNode.nodeID);
2510 VisualScriptNodeRenderer::RenderNode(
2519 [](
int nid,
void*
ud) {
2520 VisualScriptEditorPanel* panel =
2521 static_cast<VisualScriptEditorPanel*>(ud);
2522 panel->m_pendingAddPin = true;
2523 panel->m_pendingAddPinNodeID = nid;
2527 VisualScriptEditorPanel* panel =
2528 static_cast<VisualScriptEditorPanel*>(ud);
2529 panel->m_pendingRemovePin = true;
2530 panel->m_pendingRemovePinNodeID = nid;
2531 panel->m_pendingRemovePinDynIdx = dynIdx;
2539 ImNodes::PopColorStyle();
2540 ImNodes::PopColorStyle();
2541 ImNodes::PopColorStyle();
2546 VisualScriptNodeRenderer::RenderBreakpointIndicator(
eNode.nodeID);
2548 VisualScriptNodeRenderer::RenderActiveNodeGlow(
eNode.nodeID);
2551 m_positionedNodes.insert(
eNode.nodeID);
2555 for (
size_t i = 0;
i < m_editorLinks.size(); ++
i)
2559 ImNodes::PushColorStyle(
ImNodesCol_Link, SystemColors::DATA_CONNECTION_COLOR);
2561 ImNodes::PushColorStyle(
ImNodesCol_Link, SystemColors::EXEC_CONNECTION_COLOR);
2562 ImNodes::Link(
link.linkID,
link.srcAttrID,
link.dstAttrID);
2563 ImNodes::PopColorStyle();
2566 ImNodes::EndNodeEditor();
2573 if (m_skipPositionSyncNextFrame)
2575 m_skipPositionSyncNextFrame =
false;
2579 SyncNodePositionsFromImNodes();
2600 ImGui::OpenPopup(
"VSNodeContextMenu");
2606 ImGui::OpenPopup(
"VSLinkContextMenu");
2609 else if (ImNodes::IsEditorHovered())
2619 ImGui::OpenPopup(
"VSNodePalette");
2620 SYSTEM_LOG <<
"[VSEditor] Opened context menu on CANVAS at ("
2621 << m_contextMenuX <<
", " << m_contextMenuY <<
")\n";
2626 RenderContextMenus();
2634 if (ImGui::BeginDragDropTarget())
2644 ImVec2 canvasPos = ImNodes::EditorContextGetPanning();
2655 m_pendingNodeDrop =
true;
2656 m_pendingNodeType = nodeType;
2660 ImGui::EndDragDropTarget();
2668 if (m_pendingNodeDrop)
2671 float safeX = m_pendingNodeX;
2672 float safeY = m_pendingNodeY;
2673 if (!std::isfinite(
safeX) || !std::isfinite(
safeY) ||
2679 SYSTEM_LOG <<
"[VSEditor] Warning: pending node position was garbage; reset to (0, 0)\n";
2689 m_pendingNodeDrop =
false;
2691 std::cout <<
"[VisualScriptEditorPanel] Node created: ID=" <<
newNodeID
2692 <<
" type=" <<
static_cast<int>(m_pendingNodeType)
2703 if (m_pendingAddPin)
2705 m_pendingAddPin =
false;
2708 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2710 if (m_editorNodes[
i].nodeID == m_pendingAddPinNodeID)
2712 eNode = &m_editorNodes[
i];
2716 if (
eNode !=
nullptr &&
2717 (
eNode->def.Type == TaskNodeType::VSSequence ||
2718 eNode->def.Type == TaskNodeType::Switch))
2720 int pinIdx =
static_cast<int>(
eNode->def.DynamicExecOutputPins.size()) + 1;
2721 std::string pinName;
2722 if (
eNode->def.Type == TaskNodeType::VSSequence)
2723 pinName =
"Out_" + std::to_string(
pinIdx);
2725 pinName =
"Case_" + std::to_string(
pinIdx);
2728 eNode->def.DynamicExecOutputPins.push_back(pinName);
2731 m_undoStack.PushCommand(
2732 std::unique_ptr<ICommand>(
2738 SYSTEM_LOG <<
"[VSEditor] AddDynamicPin: node #" << m_pendingAddPinNodeID
2739 <<
" added pin '" << pinName <<
"'\n";
2749 if (m_pendingRemovePin)
2751 m_pendingRemovePin =
false;
2754 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2756 if (m_editorNodes[
i].nodeID == m_pendingRemovePinNodeID)
2758 eNode = &m_editorNodes[
i];
2762 if (
eNode !=
nullptr &&
2763 m_pendingRemovePinDynIdx >= 0 &&
2764 m_pendingRemovePinDynIdx <
static_cast<int>(
eNode->def.DynamicExecOutputPins.size()))
2766 const std::string pinName =
2772 for (
size_t c = 0;
c < m_template.ExecConnections.size(); ++
c)
2775 if (
ec.SourceNodeID == m_pendingRemovePinNodeID &&
2776 ec.SourcePinName == pinName)
2785 eNode->def.DynamicExecOutputPins.erase(
2786 eNode->def.DynamicExecOutputPins.begin() + m_pendingRemovePinDynIdx);
2789 m_undoStack.PushCommand(
2790 std::unique_ptr<ICommand>(
2793 m_pendingRemovePinDynIdx,
2800 SYSTEM_LOG <<
"[VSEditor] RemoveDynamicPin: node #" << m_pendingRemovePinNodeID
2801 <<
" removed pin '" << pinName <<
"'\n";
2818 if (m_justPerformedUndoRedo)
2820 m_justPerformedUndoRedo =
false;
2827 m_nodeDragStartPositions.clear();
2828 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2831 if (m_positionedNodes.count(
eNode.nodeID) == 0)
2833 m_nodeDragStartPositions[
eNode.nodeID] =
2842 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2845 if (m_positionedNodes.count(
eNode.nodeID) == 0)
2856 for (
const auto&
entry : m_nodeDragStartPositions)
2858 const int nodeID =
entry.first;
2865 if (m_positionedNodes.count(nodeID) == 0)
2871 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
2873 if (m_editorNodes[
i].nodeID == nodeID)
2884 m_undoStack.PushCommand(
2885 std::unique_ptr<ICommand>(
2902 m_nodeDragStartPositions.clear();
2914 auto it = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
2916 return n.nodeID == hoveredNode;
2918 if (
it != m_editorNodes.end())
2921 if (
tip &&
tip[0] !=
'\0')
2923 ImGui::BeginTooltip();
2924 ImGui::TextUnformatted(
tip);
2925 ImGui::EndTooltip();
2976 auto dstIt = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
2978 return n.nodeID == dstNodeID;
2981 if (
dstIt != m_editorNodes.end() &&
2982 dstIt->def.Type == TaskNodeType::Branch &&
2983 !
dstIt->def.dynamicPins.empty())
3009 auto srcIt = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
3011 return n.nodeID == srcNodeID;
3013 if (
srcIt != m_editorNodes.end())
3041 auto srcIt = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
3043 return n.nodeID == srcNodeID;
3045 auto dstIt = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
3047 return n.nodeID == dstNodeID;
3050 if (
srcIt != m_editorNodes.end())
3061 for (
size_t p = 0;
p <
srcIt->def.DataPins.size(); ++
p)
3063 if (
srcIt->def.DataPins[
p].Dir == DataPinDir::Output)
3076 if (
dstIt != m_editorNodes.end())
3079 if (
dstIt->def.Type == TaskNodeType::Branch)
3085 if (!
dstIt->def.dynamicPins.empty())
3088 std::cerr <<
"[VSEditor] Data-in pin index corrected to 0 (first available)\n";
3099 std::cerr <<
"[VSEditor] Cannot find valid data-in pin on Branch node\n";
3114 for (
size_t p = 0;
p <
dstIt->def.DataPins.size(); ++
p)
3116 if (
dstIt->def.DataPins[
p].Dir == DataPinDir::Input)
3131 std::cout <<
"[VisualScriptEditorPanel] Created data link: node"
3138 std::cerr <<
"[VisualScriptEditorPanel] Cannot create link"
3139 " — incompatible pin types (exec/data mismatch)\n";
3144 std::cerr <<
"[VisualScriptEditorPanel] Cannot create link"
3145 " — incompatible pin types (both inputs or both outputs)\n";
3164 if (ImNodes::NumSelectedNodes() == 1)
3167 ImNodes::GetSelectedNodes(
selNodes);
3170 else if (ImNodes::NumSelectedNodes() == 0)
3172 m_selectedNodeID = -1;
3176 if (ImGui::IsKeyPressed(
ImGuiKey_F9) && m_selectedNodeID >= 0)
3178 DebugController::Get().ToggleBreakpoint(0, m_selectedNodeID,
3180 "Node " + std::to_string(m_selectedNodeID));
3192 <<
" nodes" << std::endl;
3196 ImNodes::GetSelectedNodes(selectedNodes.data());
3198 for (
int nodeID : selectedNodes)
3200 if (m_selectedNodeID == nodeID)
3201 m_selectedNodeID = -1;
3203 std::cout <<
"[VSEditor] Deleted node " << nodeID << std::endl;
3218 std::cout <<
"[VSEditor] Deleted link " << linkID << std::endl;
3225 RenderValidationOverlay();
3228void VisualScriptEditorPanel::RenderNodePalette()
3230 if (!ImGui::BeginPopup(
"VSNodePalette"))
3233 ImGui::TextDisabled(
"Add Node");
3237 if (ImGui::BeginMenu(
"Flow Control"))
3240 if (ImGui::MenuItem(label))
3242 AddNode(type, m_contextMenuX, m_contextMenuY);
3243 ImGui::CloseCurrentPopup();
3246 addFlowNode(TaskNodeType::EntryPoint,
"EntryPoint");
3248 addFlowNode(TaskNodeType::VSSequence,
"Sequence");
3256 if (ImGui::BeginMenu(
"Actions"))
3258 if (ImGui::MenuItem(
"AtomicTask"))
3260 AddNode(TaskNodeType::AtomicTask, m_contextMenuX, m_contextMenuY);
3261 ImGui::CloseCurrentPopup();
3266 if (ImGui::BeginMenu(
"Data"))
3268 if (ImGui::MenuItem(
"GetBBValue"))
3270 AddNode(TaskNodeType::GetBBValue, m_contextMenuX, m_contextMenuY);
3271 ImGui::CloseCurrentPopup();
3273 if (ImGui::MenuItem(
"SetBBValue"))
3275 AddNode(TaskNodeType::SetBBValue, m_contextMenuX, m_contextMenuY);
3276 ImGui::CloseCurrentPopup();
3278 if (ImGui::MenuItem(
"MathOp"))
3280 AddNode(TaskNodeType::MathOp, m_contextMenuX, m_contextMenuY);
3281 ImGui::CloseCurrentPopup();
3286 if (ImGui::BeginMenu(
"SubGraph"))
3288 if (ImGui::MenuItem(
"SubGraph"))
3290 AddNode(TaskNodeType::SubGraph, m_contextMenuX, m_contextMenuY);
3291 ImGui::CloseCurrentPopup();
3299void VisualScriptEditorPanel::RenderContextMenus()
3304 if (ImGui::BeginPopup(
"VSNodeContextMenu"))
3306 if (ImGui::MenuItem(
"Edit Properties"))
3308 m_selectedNodeID = m_contextNodeID;
3309 SYSTEM_LOG <<
"[VSEditor] Selected node #" << m_contextNodeID
3310 <<
" for editing\n";
3315 if (ImGui::MenuItem(
"Delete Node"))
3317 RemoveNode(m_contextNodeID);
3318 if (m_selectedNodeID == m_contextNodeID)
3319 m_selectedNodeID = -1;
3321 SYSTEM_LOG <<
"[VSEditor] Deleted node #" << m_contextNodeID
3322 <<
" via context menu\n";
3328 bool hasBP = DebugController::Get().HasBreakpoint(0, m_contextNodeID);
3329 if (ImGui::MenuItem(
hasBP ?
"Remove Breakpoint (F9)" :
"Add Breakpoint (F9)"))
3331 DebugController::Get().ToggleBreakpoint(0, m_contextNodeID,
3333 "Node " + std::to_string(m_contextNodeID));
3334 SYSTEM_LOG <<
"[VSEditor] Toggled breakpoint on node #"
3335 << m_contextNodeID <<
" -> "
3336 << (
hasBP ?
"OFF" :
"ON") <<
"\n";
3342 if (ImGui::MenuItem(
"Duplicate"))
3344 auto it = std::find_if(m_editorNodes.begin(), m_editorNodes.end(),
3345 [
this](
const VSEditorNode&
n) { return n.nodeID == m_contextNodeID; });
3346 if (
it != m_editorNodes.end())
3350 newDef.NodeName +=
" (Copy)";
3351 newDef.EditorPosX =
it->posX + 50.0f;
3352 newDef.EditorPosY =
it->posY + 50.0f;
3353 newDef.HasEditorPos =
true;
3360 m_editorNodes.push_back(
eNew);
3362 m_undoStack.PushCommand(
3366 SYSTEM_LOG <<
"[VSEditor] Node " << m_contextNodeID
3367 <<
" duplicated as #" <<
newDef.NodeID <<
"\n";
3377 if (ImGui::BeginPopup(
"VSLinkContextMenu"))
3379 if (ImGui::MenuItem(
"Delete Connection"))
3381 RemoveLink(m_contextLinkID);
3383 SYSTEM_LOG <<
"[VSEditor] Deleted link #" << m_contextLinkID
3384 <<
" via context menu\n";
3402 ImGui::Selectable(def.
NodeName.c_str(),
true,
3404 ImGui::PopStyleColor(4);
3410 if (m_conditionsPanel)
3413 if (m_condPanelNodeID !=
eNode.nodeID)
3415 m_condPanelNodeID =
eNode.nodeID;
3416 m_conditionsPanel->SetNodeName(def.
NodeName);
3418 m_conditionsPanel->SetDynamicPins(def.
dynamicPins);
3419 m_conditionsPanel->ClearDirty();
3424 m_conditionsPanel->SetNodeName(def.
NodeName);
3427 m_conditionsPanel->Render();
3429 if (m_conditionsPanel->IsDirty())
3434 for (
size_t ti = 0;
ti < m_template.Nodes.size(); ++
ti)
3436 if (m_template.Nodes[
ti].NodeID == m_selectedNodeID)
3442 m_conditionsPanel->ClearDirty();
3451 bool hasBP = DebugController::Get().HasBreakpoint(0, m_selectedNodeID);
3452 if (ImGui::Checkbox(
"Breakpoint (F9)##vsbp_branch", &
hasBP))
3454 DebugController::Get().ToggleBreakpoint(0, m_selectedNodeID,
3459 RenderVerificationPanel();
3476 ImGui::Selectable(def.
NodeName.c_str(),
true,
3478 ImGui::PopStyleColor(4);
3489 m_mathOpPanel = std::unique_ptr<MathOpPropertyPanel>(
3493 m_mathOpPanel->SetNodeName(def.
NodeName);
3494 m_mathOpPanel->SetMathOpRef(def.
mathOpRef);
3497 m_mathOpPanel->SetOnOperandChange([
this]() {
3499 if (m_pinManager && m_selectedNodeID >= 0)
3501 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
3503 if (m_editorNodes[
i].nodeID == m_selectedNodeID)
3505 m_editorNodes[
i].def.mathOpRef = m_mathOpPanel->GetMathOpRef();
3513 m_mathOpPanel->Render();
3515 if (m_mathOpPanel->IsDirty())
3517 def.
mathOpRef = m_mathOpPanel->GetMathOpRef();
3520 for (
size_t ti = 0;
ti < m_template.Nodes.size(); ++
ti)
3522 if (m_template.Nodes[
ti].NodeID == m_selectedNodeID)
3528 m_mathOpPanel->ClearDirty();
3536 RenderVerificationPanel();
3558 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"Node Parameters:");
3562 ImGui::TextDisabled(
"(no user parameters - add one below)");
3577 ImGui::TextColored(
ImVec4(0.8f, 0.95f, 1.0f, 1.0f),
"%s",
paramName.c_str());
3583 case ParameterBindingType::Literal:
typeLabel =
"Literal";
break;
3584 case ParameterBindingType::LocalVariable:
typeLabel =
"Variable";
break;
3585 case ParameterBindingType::AtomicTaskID:
typeLabel =
"AtomicTaskID";
break;
3586 case ParameterBindingType::ConditionID:
typeLabel =
"ConditionID";
break;
3587 case ParameterBindingType::MathOperator:
typeLabel =
"MathOp";
break;
3588 case ParameterBindingType::ComparisonOp:
typeLabel =
"CompOp";
break;
3589 case ParameterBindingType::SubGraphPath:
typeLabel =
"SubGraph";
break;
3595 if (
binding.Type == ParameterBindingType::Literal)
3599 if (!
binding.LiteralValue.IsNone())
3606 ImGui::SetNextItemWidth(-1.0f);
3610 std::string strVal(
buf);
3614 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3616 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3625 else if (
binding.Type == ParameterBindingType::LocalVariable)
3630 const std::vector<VarSpec>
vars =
bbReg.GetAllVariables();
3633 ImGui::SetNextItemWidth(-1.0f);
3638 bool selected = (
var.name ==
binding.VariableName);
3639 if (ImGui::Selectable(
var.displayLabel.c_str(), selected))
3644 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3646 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3655 ImGui::SetItemDefaultFocus();
3665 ImGui::SetNextItemWidth(-1.0f);
3671 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3673 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3688 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"Add Parameter:");
3691 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 80.0f);
3694 if (ImGui::Button(
"Add",
ImVec2(70.0f, 0.0f)))
3707 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3709 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3722void VisualScriptEditorPanel::RenderProperties()
3724 ImGui::TextDisabled(
"Properties");
3726 if (m_selectedNodeID < 0)
3728 ImGui::TextDisabled(
"(select a node)");
3734 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
3736 if (m_editorNodes[
i].nodeID == m_selectedNodeID)
3738 eNode = &m_editorNodes[
i];
3742 if (
eNode ==
nullptr)
3750 m_propEditNodeIDOnFocus = m_selectedNodeID;
3760 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3762 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3764 m_template.Nodes[
i].NodeName = def.
NodeName;
3770 if (ImGui::IsItemActivated())
3773 m_propEditNodeIDOnFocus = m_selectedNodeID;
3775 if (ImGui::IsItemDeactivatedAfterEdit() &&
3776 m_propEditNodeIDOnFocus == m_selectedNodeID &&
3779 m_undoStack.PushCommand(
3781 m_selectedNodeID,
"NodeName",
3782 PropertyValue::FromString(m_propEditOldName),
3783 PropertyValue::FromString(def.
NodeName))),
3792 case TaskNodeType::AtomicTask:
3795 const std::vector<TaskSpec> tasks = AtomicTaskUIRegistry::Get().GetSortedForUI();
3799 if (ImGui::IsItemActivated())
3802 m_propEditNodeIDOnFocus = m_selectedNodeID;
3805 if (ImGui::BeginCombo(
"TaskType##vstask",
previewLabel))
3810 m_propEditNodeIDOnFocus = m_selectedNodeID;
3813 for (
size_t ti = 0;
ti < tasks.size(); ++
ti)
3821 ImGui::TextDisabled(
"%s",
spec.category.c_str());
3825 std::string label =
" " +
spec.displayName +
"##" +
spec.id;
3826 if (ImGui::Selectable(label.c_str(), selected))
3832 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3834 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3837 m_template.Nodes[
i].NodeName = def.
NodeName;
3843 m_undoStack.PushCommand(
3845 m_selectedNodeID,
"AtomicTaskID",
3853 ImGui::SetItemDefaultFocus();
3855 if (ImGui::IsItemHovered() && !
spec.description.empty())
3857 ImGui::BeginTooltip();
3858 ImGui::TextUnformatted(
spec.description.c_str());
3859 ImGui::EndTooltip();
3866 case TaskNodeType::Delay:
3869 if (ImGui::InputFloat(
"Delay (s)##vsdelay", &
delay, 0.1f, 1.0f))
3872 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3874 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3882 if (ImGui::IsItemActivated())
3885 m_propEditNodeIDOnFocus = m_selectedNodeID;
3887 if (ImGui::IsItemDeactivatedAfterEdit() &&
3888 m_propEditNodeIDOnFocus == m_selectedNodeID &&
3891 m_undoStack.PushCommand(
3893 m_selectedNodeID,
"DelaySeconds",
3894 PropertyValue::FromFloat(m_propEditOldDelay),
3900 case TaskNodeType::GetBBValue:
3904 if (m_variablePanel)
3906 m_variablePanel->SetNodeName(def.
NodeName);
3907 m_variablePanel->SetTemplate(&m_template);
3908 m_variablePanel->SetBBKey(def.
BBKey);
3910 m_variablePanel->Render();
3912 if (m_variablePanel->IsDirty())
3915 def.
BBKey = m_variablePanel->GetBBKey();
3917 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3919 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3921 m_template.Nodes[
i].BBKey = def.
BBKey;
3928 m_undoStack.PushCommand(
3930 m_selectedNodeID,
"BBKey",
3931 PropertyValue::FromString(
oldKey),
3932 PropertyValue::FromString(def.
BBKey))),
3935 m_variablePanel->ClearDirty();
3941 RenderNodeDataParameters(def);
3944 case TaskNodeType::SetBBValue:
3949 m_setBBPanel->SetNodeName(def.
NodeName);
3950 m_setBBPanel->SetTemplate(&m_template);
3951 m_setBBPanel->SetBBKey(def.
BBKey);
3953 m_setBBPanel->Render();
3955 if (m_setBBPanel->IsDirty())
3958 def.
BBKey = m_setBBPanel->GetBBKey();
3960 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
3962 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
3964 m_template.Nodes[
i].BBKey = def.
BBKey;
3971 m_undoStack.PushCommand(
3973 m_selectedNodeID,
"BBKey",
3974 PropertyValue::FromString(
oldKey),
3975 PropertyValue::FromString(def.
BBKey))),
3978 m_setBBPanel->ClearDirty();
3984 RenderNodeDataParameters(def);
3987 case TaskNodeType::Branch:
3988 case TaskNodeType::While:
3993 RenderBranchNodeProperties(*
eNode, def);
3996 case TaskNodeType::SubGraph:
4003 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
4005 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
4013 if (ImGui::IsItemActivated())
4016 m_propEditNodeIDOnFocus = m_selectedNodeID;
4018 if (ImGui::IsItemDeactivatedAfterEdit() &&
4019 m_propEditNodeIDOnFocus == m_selectedNodeID &&
4022 m_undoStack.PushCommand(
4024 m_selectedNodeID,
"SubGraphPath",
4025 PropertyValue::FromString(m_propEditOldSubGraphPath),
4031 case TaskNodeType::MathOp:
4035 RenderMathOpNodeProperties(*
eNode, def);
4038 RenderNodeDataParameters(def);
4041 case TaskNodeType::Switch:
4044 if (m_propEditNodeIDOnFocus != m_selectedNodeID)
4049 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
4051 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
4064 const std::vector<VarSpec>
vars =
bbReg.GetVariablesByType(VariableType::Int);
4068 if (ImGui::BeginCombo(
"Switch Var##vsswitchvar",
previewVar))
4073 bool selected = (
v.name ==
curVar);
4074 if (ImGui::Selectable(
v.displayLabel.c_str(), selected))
4082 ImGui::SetItemDefaultFocus();
4092 ImGui::TextDisabled(
"Case Labels");
4096 if (m_propEditSwitchCases.size() != def.
switchCases.size())
4104 ImGui::TextUnformatted(
pinLabel.c_str());
4108 const std::string&
curLabel = m_propEditSwitchCases[
ci].customLabel;
4112 std::string
widgetID =
"##vscaselabel" + std::to_string(
ci);
4115 m_propEditSwitchCases[
ci].customLabel =
labelBuf;
4130 bool hasBP = DebugController::Get().HasBreakpoint(0, m_selectedNodeID);
4131 if (ImGui::Checkbox(
"Breakpoint (F9)##vsbp", &
hasBP))
4133 DebugController::Get().ToggleBreakpoint(0, m_selectedNodeID,
4138 RenderVerificationPanel();
4141void VisualScriptEditorPanel::RenderBlackboard()
4143 ImGui::TextDisabled(
"Local Blackboard");
4149 for (
size_t i = 0;
i < m_template.Blackboard.size(); ++
i)
4152 if (
entry.Key.empty() ||
entry.Type == VariableType::None)
4161 ImGui::TextUnformatted(
"[!] Invalid entries will be skipped on save");
4162 ImGui::PopStyleColor();
4166 if (ImGui::Button(
"+##vsbbAdd"))
4170 entry.Type = VariableType::Int;
4171 entry.Default = GetDefaultValueForType(VariableType::Int);
4172 entry.IsGlobal =
false;
4173 m_template.Blackboard.push_back(
entry);
4177 ImGui::TextDisabled(
"Add key");
4180 for (
int idx =
static_cast<int>(m_template.Blackboard.size()) - 1;
idx >= 0; --
idx)
4189 ImGui::SetNextItemWidth(120.0f);
4190 if (ImGui::InputText(
"##bbkey",
keyBuf,
sizeof(
keyBuf)))
4200 const char*
typeLabels[] = {
"Bool",
"Int",
"Float",
"Vector",
"EntityID",
"String"};
4205 entry.Type = VariableType::Int;
4207 ImGui::SetNextItemWidth(80.0f);
4218 ImGui::Checkbox(
"G##bbglob", &
entry.IsGlobal);
4222 if (ImGui::SmallButton(
"x##bbdel"))
4224 m_template.Blackboard.erase(m_template.Blackboard.begin() +
idx);
4225 m_pendingBlackboardEdits.erase(
idx);
4232 ImGui::TextDisabled(
"Default:");
4236 case VariableType::Bool:
4239 if (ImGui::Checkbox(
"##bbval", &
bVal))
4246 case VariableType::Int:
4249 ImGui::SetNextItemWidth(70.0f);
4250 if (ImGui::InputInt(
"##bbval", &
iVal))
4257 case VariableType::Float:
4259 float fVal =
entry.Default.IsNone() ? 0.0f :
entry.Default.AsFloat();
4260 ImGui::SetNextItemWidth(70.0f);
4261 if (ImGui::InputFloat(
"##bbval", &
fVal, 0.0f, 0.0f,
"%.3f"))
4268 case VariableType::String:
4270 std::string
sVal =
entry.Default.IsNone() ?
"" :
entry.Default.AsString();
4273 ImGui::SetNextItemWidth(100.0f);
4274 if (ImGui::InputText(
"##bbval",
sBuf,
sizeof(
sBuf)))
4281 case VariableType::Vector:
4286 ImGui::BeginDisabled(
true);
4287 float vecVal[3] = { 0.0f, 0.0f, 0.0f };
4288 ImGui::SetNextItemWidth(140.0f);
4289 ImGui::DragFloat3(
"##bbval",
vecVal, 0.1f);
4290 ImGui::EndDisabled();
4292 ImGui::TextDisabled(
"(auto from entity position)");
4295 case VariableType::EntityID:
4298 ImGui::BeginDisabled(
true);
4300 ImGui::SetNextItemWidth(70.0f);
4301 ImGui::InputInt(
"##bbval", &entityId);
4302 ImGui::EndDisabled();
4304 ImGui::TextDisabled(
"(assigned at runtime)");
4308 ImGui::TextDisabled(
"(n/a)");
4316void VisualScriptEditorPanel::RenderValidationOverlay()
4318 m_validationWarnings.clear();
4319 m_validationErrors.clear();
4322 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
4325 if (
eNode.def.Type == TaskNodeType::EntryPoint)
4329 for (
size_t c = 0;
c < m_template.ExecConnections.size(); ++
c)
4331 if (m_template.ExecConnections[
c].TargetNodeID ==
eNode.nodeID)
4339 m_validationErrors.push_back(
4340 "Node " + std::to_string(
eNode.nodeID) +
" (" +
4341 eNode.def.NodeName +
"): no exec-in connection");
4345 if (
eNode.def.Type == TaskNodeType::SubGraph &&
4346 eNode.def.SubGraphPath.empty())
4348 m_validationWarnings.push_back(
4349 "Node " + std::to_string(
eNode.nodeID) +
4350 " (SubGraph): SubGraphPath is empty");
4359void VisualScriptEditorPanel::RunVerification()
4361 SYSTEM_LOG <<
"[VisualScriptEditorPanel] RunVerification() called for graph '"
4362 << m_template.Name <<
"'\n";
4363 m_verificationResult = VSGraphVerifier::Verify(m_template);
4364 m_verificationDone =
true;
4367 m_verificationLogs.clear();
4368 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
4374 if (
issue.severity == VSVerificationSeverity::Error)
4376 else if (
issue.severity == VSVerificationSeverity::Warning)
4382 if (
issue.nodeID >= 0)
4383 logEntry +=
" (Node: " + std::to_string(
issue.nodeID) +
")";
4385 m_verificationLogs.push_back(
logEntry);
4388 SYSTEM_LOG <<
"[VisualScriptEditorPanel] RunVerification() done: "
4389 << m_verificationResult.issues.size() <<
" issue(s), "
4390 <<
"errors=" << (m_verificationResult.HasErrors() ?
"yes" :
"no") <<
", "
4391 <<
"warnings=" << (m_verificationResult.HasWarnings() ?
"yes" :
"no") <<
"\n";
4394void VisualScriptEditorPanel::RenderVerificationPanel()
4397 ImGui::TextDisabled(
"Graph Verification");
4399 if (!m_verificationDone)
4401 ImGui::TextDisabled(
"Click 'Verify' in toolbar to run verification.");
4406 if (m_verificationResult.HasErrors())
4409 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
4411 if (m_verificationResult.issues[
i].severity == VSVerificationSeverity::Error)
4414 ImGui::TextColored(
ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
4417 else if (m_verificationResult.HasWarnings())
4419 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"OK — warnings present");
4423 ImGui::TextColored(
ImVec4(0.3f, 1.0f, 0.3f, 1.0f),
"OK — no issues");
4426 if (m_verificationResult.issues.empty())
4431 VSVerificationSeverity::Error,
4432 VSVerificationSeverity::Warning,
4433 VSVerificationSeverity::Info
4436 for (
int s = 0;
s < 3; ++
s)
4439 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
4445 ImGui::PushID(
static_cast<int>(
i));
4447 if (
sev == VSVerificationSeverity::Error)
4448 ImGui::TextColored(
ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"[E]");
4449 else if (
sev == VSVerificationSeverity::Warning)
4450 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"[W]");
4452 ImGui::TextColored(
ImVec4(0.4f, 0.7f, 1.0f, 1.0f),
"[I]");
4455 ImGui::Text(
"%s: %s",
issue.ruleID.c_str(),
issue.message.c_str());
4457 if (
issue.nodeID >= 0)
4460 std::string
btnLabel =
"Go##go" + std::to_string(
i);
4461 if (ImGui::SmallButton(
btnLabel.c_str()))
4463 m_focusNodeID =
issue.nodeID;
4464 m_selectedNodeID =
issue.nodeID;
4477void VisualScriptEditorPanel::RenderVerificationLogsPanel()
4482 if (!m_verificationDone)
4484 ImGui::TextDisabled(
"(Click 'Verify' button to run verification)");
4492 ImGui::TextDisabled(
"Issues found: %zu", m_verificationResult.issues.size());
4495 if (m_verificationResult.HasErrors())
4497 ImGui::TextColored(
ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
4498 "[ERROR] Graph has %d error(s)",
4499 (
int)m_verificationResult.issues.size());
4501 else if (m_verificationResult.HasWarnings())
4503 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
4504 "[WARNING] Graph is valid but has warnings");
4508 ImGui::TextColored(
ImVec4(0.3f, 1.0f, 0.3f, 1.0f),
4509 "[OK] Graph is valid - no issues found");
4515 ImGui::BeginChild(
"VerificationLogsChild",
ImVec2(0, 0),
true);
4518 VSVerificationSeverity::Error,
4519 VSVerificationSeverity::Warning,
4520 VSVerificationSeverity::Info
4523 const char*
sevLabels[3] = {
"[ERROR]",
"[WARN]",
"[INFO]" };
4525 ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
4526 ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
4527 ImVec4(0.5f, 0.8f, 1.0f, 1.0f)
4530 for (
int s = 0;
s < 3; ++
s)
4536 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
4538 if (m_verificationResult.issues[
i].severity ==
sev)
4552 for (
size_t i = 0;
i < m_verificationResult.issues.size(); ++
i)
4560 if (
issue.nodeID >= 0)
4562 message +=
" (Node: " + std::to_string(
issue.nodeID) +
")";
4565 ImGui::BulletText(
"%s", message.c_str());
4578void VisualScriptEditorPanel::RenderConditionEditor(
4581 const std::vector<BlackboardEntry>&
allVars,
4584 ImGui::PushID(conditionIndex);
4586 ImGui::Text(
"Condition #%d", conditionIndex + 1);
4589 ImGui::Text(
"Left:");
4619 else if (
condition.leftMode ==
"Variable")
4623 RenderConstValueInput(
condition.leftConstValue,
4630 const char*
operators[] = {
"==",
"!=",
"<",
">",
"<=",
">=" };
4632 for (
int i = 0;
i < 6; ++
i)
4640 ImGui::SetNextItemWidth(70.0f);
4648 ImGui::Text(
"Right:");
4678 else if (
condition.rightMode ==
"Variable")
4682 RenderConstValueInput(
condition.rightConstValue,
4687 ImGui::Text(
"Type:");
4689 const char* types[] = {
"None",
"Bool",
"Int",
"Float",
"String",
"Vector" };
4691 VariableType::None, VariableType::Bool, VariableType::Int,
4692 VariableType::Float, VariableType::String, VariableType::Vector
4695 for (
int i = 0;
i < 6; ++
i)
4703 ImGui::SetNextItemWidth(80.0f);
4704 if (ImGui::Combo(
"##cmptype", &
typeIdx, types, 6))
4712 ImGui::TextColored(
ImVec4(0.7f, 1.0f, 0.7f, 1.0f),
4713 "Preview: %s",
preview.c_str());
4720void VisualScriptEditorPanel::RenderVariableSelector(
4722 const std::vector<BlackboardEntry>&
allVars,
4727 std::vector<std::string>
names;
4730 if (expectedType == VariableType::None ||
allVars[
i].Type == expectedType)
4739 ImGui::TextDisabled(
"(no variables)");
4763 std::vector<const char*>
cstrs;
4765 for (
size_t i = 0;
i <
names.size(); ++
i)
4768 ImGui::SetNextItemWidth(120.0f);
4769 if (ImGui::Combo(label, &selected,
cstrs.data(),
static_cast<int>(
cstrs.size())))
4778void VisualScriptEditorPanel::RenderConstValueInput(
4791 case VariableType::Bool: value =
TaskValue(
false);
break;
4792 case VariableType::Int: value =
TaskValue(0);
break;
4793 case VariableType::Float: value =
TaskValue(0.0f);
break;
4794 case VariableType::String: value =
TaskValue(std::string(
""));
break;
4804 case VariableType::Bool:
4807 if (ImGui::Checkbox(label, &
bVal))
4814 case VariableType::Int:
4817 ImGui::SetNextItemWidth(80.0f);
4818 if (ImGui::InputInt(label, &
iVal))
4825 case VariableType::Float:
4828 ImGui::SetNextItemWidth(80.0f);
4829 if (ImGui::InputFloat(label, &
fVal, 0.0f, 0.0f,
"%.3f"))
4836 case VariableType::String:
4841 ImGui::SetNextItemWidth(120.0f);
4842 if (ImGui::InputText(label,
sBuf,
sizeof(
sBuf)))
4849 case VariableType::Vector:
4853 ImGui::SetNextItemWidth(160.0f);
4854 if (ImGui::InputFloat3(label,
v))
4864 ImGui::TextDisabled(
"(set Type first)");
4872void VisualScriptEditorPanel::RenderPinSelector(
4879 ImGui::TextDisabled(
"(no data-output pins in graph)");
4883 ImGui::SetNextItemWidth(160.0f);
4892 ImGui::SetItemDefaultFocus();
4901std::string VisualScriptEditorPanel::BuildConditionPreview(
const Condition&
cond)
4903 auto descSide = [](
const std::string& mode,
4904 const std::string&
pin,
4905 const std::string&
var,
4906 const TaskValue& constValue) -> std::string
4909 return "[Pin: " + (
pin.empty() ?
"?" :
pin) +
"]";
4910 if (mode ==
"Variable")
4911 return "[Var: " + (
var.empty() ?
"?" :
var) +
"]";
4914 if (!constValue.IsNone())
4916 std::ostringstream
oss;
4917 switch (constValue.GetType())
4919 case VariableType::Bool:
oss << (constValue.AsBool() ?
"true" :
"false");
break;
4920 case VariableType::Int:
oss << constValue.AsInt();
break;
4921 case VariableType::Float:
oss << constValue.AsFloat();
break;
4922 case VariableType::String:
oss <<
'"' << constValue.AsString() <<
'"';
break;
4923 case VariableType::Vector:
4925 const ::Vector
v = constValue.AsVector();
4926 oss <<
"(" <<
v.x <<
"," <<
v.y <<
"," <<
v.z <<
")";
4929 default:
oss <<
"?";
break;
4931 return "[Const: " +
oss.str() +
"]";
4933 return "[Const: ?]";
4938 const std::string op =
cond.operatorStr.empty() ?
"?" :
cond.operatorStr;
4940 return left +
" " + op +
" " + right;
4947void VisualScriptEditorPanel::RenderNodePropertiesPanel()
4949 ImGui::TextDisabled(
"Node Properties");
4951 if (m_selectedNodeID < 0)
4953 ImGui::TextDisabled(
"(select a node)");
4959 for (
size_t i = 0;
i < m_editorNodes.size(); ++
i)
4961 if (m_editorNodes[
i].nodeID == m_selectedNodeID)
4963 eNode = &m_editorNodes[
i];
4967 if (
eNode ==
nullptr)
4977 if (ImGui::InputText(
"Name##nodeprops_name",
nameBuf,
sizeof(
nameBuf)))
4980 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
4982 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
4984 m_template.Nodes[
i].NodeName = def.
NodeName;
4996 if (def.
Type != TaskNodeType::Branch)
5004 case TaskNodeType::AtomicTask:
5006 const std::vector<TaskSpec> tasks = AtomicTaskUIRegistry::Get().GetSortedForUI();
5010 ImGui::SetNextItemWidth(-1.0f);
5011 if (ImGui::BeginCombo(
"Task##nodeprops_task",
previewLabel))
5013 for (
const auto&
spec : tasks)
5016 if (ImGui::Selectable(
spec.displayName.c_str(), selected))
5021 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5023 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5026 m_template.Nodes[
i].NodeName = def.
NodeName;
5033 ImGui::SetItemDefaultFocus();
5045 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"Parameters:");
5049 ImGui::PushID(
param.name.c_str());
5052 std::string label =
param.name +
" (" +
param.type +
")";
5063 ImGui::TextColored(
ImVec4(0.8f, 0.95f, 1.0f, 1.0f),
"%s",
param.name.c_str());
5067 ImGui::TextDisabled(
"(?)");
5070 if (ImGui::IsItemHovered() && !
param.description.empty())
5072 ImGui::BeginTooltip();
5073 ImGui::TextWrapped(
"%s",
param.description.c_str());
5075 ImGui::TextDisabled(
"Type: %s",
param.type.c_str());
5076 ImGui::TextDisabled(
"Default: %s",
param.defaultValue.c_str());
5077 ImGui::EndTooltip();
5081 if (!
param.description.empty())
5083 ImGui::TextDisabled(
"%s",
param.description.c_str());
5086 if (
param.type ==
"Bool")
5089 ImGui::SetNextItemWidth(-1.0f);
5090 if (ImGui::Checkbox((
"##" +
param.name +
"_input").c_str(), &value))
5097 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5099 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5108 else if (
param.type ==
"Int")
5111 try { value = std::stoi(
currentValue); }
catch (...) {}
5112 ImGui::SetNextItemWidth(-1.0f);
5113 if (ImGui::InputInt((
"##" +
param.name +
"_input").c_str(), &value))
5120 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5122 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5131 else if (
param.type ==
"Float")
5134 try { value = std::stof(
currentValue); }
catch (...) {}
5135 ImGui::SetNextItemWidth(-1.0f);
5136 if (ImGui::InputFloat((
"##" +
param.name +
"_input").c_str(), &value, 0.1f))
5143 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5145 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5154 else if (
param.type ==
"String")
5156 static char buffer[512] = {0};
5160 ImGui::SetNextItemWidth(-1.0f);
5161 if (ImGui::InputText((
"##" +
param.name +
"_input").c_str(),
buffer,
sizeof(
buffer)))
5168 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5170 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5188 case TaskNodeType::Delay:
5191 if (ImGui::InputFloat(
"Delay (s)##nodeprops_delay", &
delay, 0.1f, 1.0f))
5194 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5196 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5207 case TaskNodeType::Switch:
5209 ImGui::TextDisabled(
"Switch node - edit via modal");
5210 if (ImGui::Button(
"Edit Switch Cases"))
5217 case TaskNodeType::GetBBValue:
5218 case TaskNodeType::SetBBValue:
5220 const char* nodeType = (def.
Type == TaskNodeType::GetBBValue) ?
"Get" :
"Set";
5221 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"%s Blackboard Value", nodeType);
5227 const std::vector<VarSpec>
allVars =
bbReg.GetAllVariables();
5231 ImGui::SetNextItemWidth(-1.0f);
5232 if (ImGui::BeginCombo(
"Blackboard Variable##bbkey_combo",
previewLabel))
5236 bool selected = (
var.name == def.
BBKey);
5237 if (ImGui::Selectable(
var.displayLabel.c_str(), selected))
5243 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5245 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5247 m_template.Nodes[
i].BBKey = def.
BBKey;
5255 m_undoStack.PushCommand(
5257 m_selectedNodeID,
"BBKey",
5258 PropertyValue::FromString(
oldBBKey),
5259 PropertyValue::FromString(def.
BBKey))),
5265 ImGui::SetItemDefaultFocus();
5274 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"Parameters:");
5275 RenderNodeDataParameters(def);
5281 case TaskNodeType::MathOp:
5283 ImGui::TextColored(
ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"Math Operation");
5287 static const char*
operators[] = {
"+",
"-",
"*",
"/",
"%",
"^" };
5292 for (
int i = 0;
i < 6; ++
i)
5302 ImGui::SetNextItemWidth(-1.0f);
5306 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5308 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5319 ImGui::TextColored(
ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
"Operation:");
5341 ImGui::TextColored(
ImVec4(0.4f, 1.0f, 0.5f, 1.0f),
5351 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"Custom Parameters:");
5352 RenderNodeDataParameters(def);
5358 case TaskNodeType::SubGraph:
5360 ImGui::TextDisabled(
"SubGraph");
5361 ImGui::TextDisabled(
"Path: %s", def.
SubGraphPath.c_str());
5365 case TaskNodeType::Sequence:
5366 case TaskNodeType::Selector:
5367 case TaskNodeType::Parallel:
5369 ImGui::TextDisabled(
"Control flow node");
5374 ImGui::TextDisabled(
"(type-specific properties)");
5382 if (def.
Type == TaskNodeType::Branch)
5385 if (m_condPanelNodeID != m_selectedNodeID)
5389 m_conditionsPanel->SetDynamicPins(def.
dynamicPins);
5390 m_conditionsPanel->SetNodeName(def.
NodeName);
5391 m_condPanelNodeID = m_selectedNodeID;
5395 m_conditionsPanel->Render();
5398 if (m_conditionsPanel->IsDirty())
5402 m_conditionsPanel->ClearDirty();
5406 for (
size_t i = 0;
i < m_template.Nodes.size(); ++
i)
5408 if (m_template.Nodes[
i].NodeID == m_selectedNodeID)
5421 bool hasBP = DebugController::Get().HasBreakpoint(0, m_selectedNodeID);
5422 if (ImGui::Checkbox(
"Breakpoint (F9)##nodeprops_bp", &
hasBP))
5424 DebugController::Get().ToggleBreakpoint(0, m_selectedNodeID,
5434void VisualScriptEditorPanel::RenderPresetBankPanel()
5436 ImGui::TextDisabled(
"Preset Bank (Global)");
5439 if (!m_libraryPanel)
5442 size_t presetCount = m_presetRegistry.GetPresetCount();
5445 if (ImGui::Button(
"+##addpreset",
ImVec2(25, 0)))
5447 m_libraryPanel->OnAddPresetClicked();
5450 ImGui::TextDisabled(
"New Preset");
5453 ImGui::TextDisabled(
"Total: %zu preset(s)",
presetCount);
5457 std::vector<ConditionPreset>
allPresets = m_presetRegistry.GetFilteredPresets(
"");
5461 ImGui::TextDisabled(
"(no presets - create one to get started)");
5467 ImGui::PushID(
preset.id.c_str());
5468 RenderPresetItemCompact(
preset,
i + 1);
5475#ifndef OLYMPE_HEADLESS
5490 ImGui::TextColored(
ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
"Condition #%zu",
index);
5492 ImGui::SameLine(0.0f, 12.0f);
5499 ImGui::SameLine(0.0f, 6.0f);
5505 case ComparisonOp::Equal:
opStr =
"==";
break;
5506 case ComparisonOp::NotEqual:
opStr =
"!=";
break;
5507 case ComparisonOp::Less:
opStr =
"<";
break;
5508 case ComparisonOp::LessEqual:
opStr =
"<=";
break;
5509 case ComparisonOp::Greater:
opStr =
">";
break;
5510 case ComparisonOp::GreaterEqual:
opStr =
">=";
break;
5511 default:
opStr =
"?";
break;
5514 const char*
opNames[] = {
"==",
"!=",
"<",
"<=",
">",
">=" };
5516 ComparisonOp::Equal, ComparisonOp::NotEqual,
5517 ComparisonOp::Less, ComparisonOp::LessEqual,
5518 ComparisonOp::Greater, ComparisonOp::GreaterEqual
5521 for (
int i = 0;
i < 6; ++
i)
5530 ImGui::SetNextItemWidth(50.0f);
5536 ImGui::SameLine(0.0f, 6.0f);
5543 ImGui::SameLine(0.0f, 12.0f);
5552 for (
size_t pi = 0;
pi < m_template.Presets.size(); ++
pi)
5565 if (ImGui::Button(
"Dup##dup_preset",
ImVec2(40, 0)))
5575 m_template.Presets.push_back(*
newPreset);
5581 ImGui::SameLine(0.0f, 4.0f);
5584 if (ImGui::Button(
"X##del_preset",
ImVec2(25, 0)))
5590 for (
size_t pi = 0;
pi < m_template.Presets.size(); ++
pi)
5594 m_template.Presets.erase(m_template.Presets.begin() +
pi);
5599 m_presetRegistry.Save(
"Blueprints/Presets/condition_presets.json");
5602 ImGui::PopStyleColor(3);
5611#ifndef OLYMPE_HEADLESS
5627 std::vector<DynamicDataPin>
allPins = m_pinManager->GetAllPins();
5634 if (
operand.mode == OperandMode::Pin &&
5644 allOptions.push_back(
"[Pin-in] (none available)");
5653 std::ostringstream
oss;
5654 oss << std::fixed << std::setprecision(3) <<
operand.constValue;
5658 if (
dot != std::string::npos)
5672 if (
operand.mode == OperandMode::Const)
5681 for (
const auto&
entry : m_template.Blackboard)
5683 if (
entry.Type != VariableType::None && !
entry.Key.empty())
5698 if (
operand.mode == OperandMode::Variable &&
5709 const std::vector<GlobalEntryDefinition>&
globalVars =
gtb.GetAllVariables();
5712 if (!m_template.Blackboard.empty() && !
globalVars.empty())
5714 allOptions.push_back(
"--- Global Variables ---");
5727 if (
operand.mode == OperandMode::Variable &&
5736 ImGui::SetNextItemWidth(120.0f);
5764 operand.mode = OperandMode::Variable;
5768 operand.mode = OperandMode::Const;
5776 operand.mode = OperandMode::Pin;
5789 if (
operand.mode == OperandMode::Const)
5791 ImGui::SameLine(0.0f, 4.0f);
5792 ImGui::SetNextItemWidth(60.0f);
5793 if (ImGui::InputDouble(
"##const_value", &
operand.constValue, 0.0, 0.0,
"%.3f"))
5809void VisualScriptEditorPanel::RenderLocalVariablesPanel()
5811 ImGui::TextDisabled(
"Local Blackboard");
5817 for (
size_t i = 0;
i < m_template.Blackboard.size(); ++
i)
5820 if (
entry.Key.empty() ||
entry.Type == VariableType::None)
5829 ImGui::TextUnformatted(
"[!] Invalid entries will be skipped on save");
5830 ImGui::PopStyleColor();
5834 if (ImGui::Button(
"+##vsbbAdd"))
5838 entry.Type = VariableType::Int;
5839 entry.Default = GetDefaultValueForType(VariableType::Int);
5840 entry.IsGlobal =
false;
5841 m_template.Blackboard.push_back(
entry);
5845 ImGui::TextDisabled(
"Add key");
5848 for (
int idx =
static_cast<int>(m_template.Blackboard.size()) - 1;
idx >= 0; --
idx)
5859 ImGui::SetNextItemWidth(140.0f);
5860 if (ImGui::InputText(
"##bbkey",
keyBuf,
sizeof(
keyBuf)))
5870 const char*
typeNames[] = {
"None",
"Bool",
"Int",
"Float",
"String",
"Vector" };
5872 VariableType::None, VariableType::Bool, VariableType::Int,
5873 VariableType::Float, VariableType::String, VariableType::Vector
5876 for (
int ti = 0;
ti < 6; ++
ti)
5880 ImGui::SetNextItemWidth(80.0f);
5884 entry.Default = GetDefaultValueForType(
entry.Type);
5889 if (
entry.Type != VariableType::None)
5892 ImGui::TextDisabled(
"Default:");
5894 RenderConstValueInput(
entry.Default,
entry.Type,
"##bbdefault");
5899 bool isGlobal =
entry.IsGlobal;
5900 if (ImGui::Checkbox(
"G##bbglobal", &isGlobal))
5902 entry.IsGlobal = isGlobal;
5905 if (ImGui::IsItemHovered())
5906 ImGui::SetTooltip(
"Mark as global variable");
5910 if (ImGui::Button(
"X##bbdel"))
5912 m_template.Blackboard.erase(m_template.Blackboard.begin() +
idx);
5913 m_pendingBlackboardEdits.erase(
idx);
5925void VisualScriptEditorPanel::RenderGlobalVariablesPanel()
5927 ImGui::TextDisabled(
"Global Variables (Editor Instance)");
5932 const std::vector<GlobalEntryDefinition>&
globalVars =
gtb.GetAllVariables();
5935 if (ImGui::Button(
"+##globalVarAdd",
ImVec2(30, 0)))
5937 ImGui::OpenPopup(
"AddGlobalVariablePopup");
5940 ImGui::TextDisabled(
"Add global variable");
5951 const char*
typeOptions[] = {
"Bool",
"Int",
"Float",
"String",
"Vector",
"EntityID" };
5953 VariableType::Bool, VariableType::Int, VariableType::Float,
5954 VariableType::String, VariableType::Vector, VariableType::EntityID
5960 if (ImGui::Button(
"Create",
ImVec2(120, 0)))
5971 GlobalTemplateBlackboard::Reload();
5982 ImGui::CloseCurrentPopup();
5987 if (ImGui::Button(
"Cancel",
ImVec2(120, 0)))
5989 ImGui::CloseCurrentPopup();
5999 ImGui::TextDisabled(
"(no global variables defined)");
6000 ImGui::TextDisabled(
"Click [+] above to create new global variables");
6004 ImGui::TextDisabled(
"Global variables from project registry");
6005 ImGui::TextDisabled(
"Values shown are editor-specific (persisted with graph)");
6009 if (!m_entityBlackboard)
6011 ImGui::TextColored(
ImVec4(1.0f, 0.5f, 0.5f, 1.0f),
"[ERROR] EntityBlackboard not initialized");
6020 ImGui::PushID(
static_cast<int>(
gi));
6023 ImGui::TextColored(
ImVec4(0.8f, 0.95f, 1.0f, 1.0f),
"(%s) %s",
6024 VariableTypeToString(
globalDef.Type).c_str(),
6028 ImGui::TextDisabled(
"(%.1f KB)", 0.1f);
6032 if (ImGui::SmallButton(
"Delete##globalvar"))
6042 GlobalTemplateBlackboard::Reload();
6053 ImGui::TextDisabled(
" %s",
globalDef.Description.c_str());
6057 std::string
tableId =
"##GlobalVarTable_" + std::to_string(
gi);
6060 ImGui::TableSetupColumn(
"Label", 0);
6061 ImGui::TableSetupColumn(
"Value", 0);
6064 ImGui::TableNextRow();
6065 ImGui::TableSetColumnIndex(0);
6066 ImGui::TextDisabled(
"Default:");
6067 ImGui::TableSetColumnIndex(1);
6073 case VariableType::Bool:
6076 case VariableType::Int:
6079 case VariableType::Float:
6081 std::ostringstream
oss;
6082 oss << std::fixed << std::setprecision(2);
6087 case VariableType::String:
6090 case VariableType::Vector:
6093 case VariableType::EntityID:
6100 ImGui::TextDisabled(
"%s",
defaultStr.c_str());
6103 ImGui::TableNextRow();
6104 ImGui::TableSetColumnIndex(0);
6105 ImGui::TextDisabled(
"Current:");
6106 ImGui::TableSetColumnIndex(1);
6116 case VariableType::Bool:
6119 if (ImGui::Checkbox(
"##bool_val", &
bVal))
6127 case VariableType::Int:
6130 if (ImGui::InputInt(
"##int_val", &
iVal))
6138 case VariableType::Float:
6141 if (ImGui::InputFloat(
"##float_val", &
fVal))
6149 case VariableType::String:
6151 static std::unordered_map<size_t, std::vector<char>>
stringBuffers;
6161 ImGui::SetNextItemWidth(-1.0f);
6171 case VariableType::Vector:
6175 if (ImGui::InputFloat3(
"##vector_val",
vArray))
6184 case VariableType::EntityID:
6187 if (ImGui::InputInt(
"##entityid_val", &
eID))
6196 ImGui::TextDisabled(
"(unsupported type)");
6203 ImGui::TableNextRow();
6204 ImGui::TableSetColumnIndex(0);
6205 ImGui::TextDisabled(
"Flags:");
6206 ImGui::TableSetColumnIndex(1);
6207 ImGui::TextColored(
ImVec4(0.7f, 0.9f, 0.5f, 1.0f),
"[Persistent]");
UI-side registry of available atomic tasks with display metadata.
Wrapper around the graph blackboard entries for dropdown editors.
Registry of available condition types for Branch/While node dropdowns.
Runtime debug controller for ATS Visual Scripting (Phase 5).
ComponentTypeID GetComponentTypeID_Static()
Defines MathOpOperand — operand references for MathOp nodes.
Hardcoded lists of math and comparison operators for dropdown editors.
ImNodes-based graph editor for ATS Visual Script graphs (Phase 5).
Records an "add exec connection" operation for undo/redo.
Records an "add data connection" operation for undo/redo.
Records "add dynamic exec-out pin" on a VSSequence or VSSwitch node for undo/redo.
Command to add a node to the tree.
Non-singleton registry populated from the active TaskGraphTemplate.
void LoadFromTemplate(const TaskGraphTemplate &tmpl)
Rebuilds the registry from the blackboard entries of a template.
ImGui panel for creating, editing, duplicating, and deleting global condition presets.
void LoadFromPresetList(const std::vector< ConditionPreset > &presets)
Loads presets from a vector of ConditionPreset objects (clears existing data first).
void Clear()
Clears all presets and resets error state.
std::vector< std::string > GetAllPresetIDs() const
Returns all preset UUIDs in display order.
ConditionPreset * GetPreset(const std::string &id)
Returns a mutable pointer to the preset, or nullptr if not found.
Records a "delete link" operation for undo/redo.
Command to delete a node from the tree.
Generates, tracks, and invalidates DynamicDataPin objects for a node.
Records a property edit on a single node for undo/redo.
ImGui sub-panel for editing GetBBValue node's blackboard variable selection.
static void Reload()
Force reload of the registry from file (useful for hot reload)
ImGui sub-panel for editing MathOp operands and operator.
Renders a NodeBranch using ImGui/ImNodes with 4 sections.
ImGui sub-panel for managing the condition list of a single NodeBranch.
Records "remove dynamic exec-out pin" on a VSSequence or VSSwitch node for undo/redo.
ImGui sub-panel for editing SetBBValue node's variable selection and value.
Immutable, shareable task graph asset.
std::vector< TaskNodeDefinition > Nodes
All graph nodes.
std::vector< ExecPinConnection > ExecConnections
Explicit exec connections (ATS VS only)
std::string Name
Friendly name of this template (e.g. "PatrolBehaviour")
std::vector< BlackboardEntry > Blackboard
Local blackboard declared in this graph.
int32_t RootNodeID
ID of the root node (must exist in Nodes)
void BuildLookupCache()
Rebuilds the internal ID-to-node lookup map from the Nodes vector.
const TaskNodeDefinition * GetNode(int32_t nodeId) const
Returns a pointer to the node with the given ID, or nullptr.
json GlobalVariableValues
Stores JSON representation of global variable values for this specific graph instance.
int32_t EntryPointID
ID of the EntryPoint node (for VS graphs)
std::vector< DataPinConnection > DataConnections
Explicit data connections (ATS VS only)
std::vector< ConditionPreset > Presets
Presets are now stored in the graph JSON, not in external files.
C++14-compliant type-safe value container for task parameters.
int AsInt() const
Returns the int value.
::Vector AsVector() const
Returns the Vector value.
EntityID AsEntityID() const
Returns the EntityID value.
float AsFloat() const
Returns the float value.
bool IsNone() const
Returns true if the value has not been set (type == None).
std::string AsString() const
Returns the string value.
bool AsBool() const
Returns the bool value.
void PushCommand(std::unique_ptr< ICommand > cmd, TaskGraphTemplate &graph)
Executes the command on graph, then pushes it onto the undo stack.
ImGui sub-panel for editing Variable node's blackboard variable selection.
std::unique_ptr< DynamicDataPinManager > m_pinManager
Dynamic pin manager shared across all Branch nodes in this panel.
UndoRedoStack m_undoStack
Undo/Redo command stack for reversible graph editing operations.
bool Save()
Saves the current canvas state to JSON v4 at the loaded path.
char m_saveAsFilename[256]
Buffer for the user-entered filename (without extension)
std::vector< VSEditorLink > m_editorLinks
Editor links (exec + data)
std::unique_ptr< VariablePropertyPanel > m_variablePanel
Properties-panel sub-widget for the selected Variable node (data pure).
VisualScriptEditorPanel()
VisualScriptEditorPanel Constructor.
int ExecOutAttrUID(int nodeID, int pinIndex) const
Maps node ID + pin index -> ImNodes attribute UID for exec-out pins.
TaskGraphTemplate m_template
The template currently being edited.
void Shutdown()
Shutdown the editor panel and release all resources.
void CommitPendingBlackboardEdits()
Commits any pending key-name edits stored in m_pendingBlackboardEdits.
std::unique_ptr< MathOpPropertyPanel > m_mathOpPanel
Properties-panel sub-widget for the selected MathOp node.
int AllocLinkID()
Allocate a unique link ID.
void Initialize()
Initialize the editor panel with ImNodes context and UI helpers.
void RemoveNode(int nodeID)
Removes a node from the canvas.
std::unique_ptr< EntityBlackboard > m_entityBlackboard
Per-entity blackboard instance (combines local + global variables) Created in Initialize() and manage...
void RemoveLink(int linkID)
Removes an ImNodes link (and its underlying template connection) by link ID.
int m_condPanelNodeID
ID of the node currently loaded into m_conditionsPanel (-1 = none).
void ConnectData(int srcNodeID, const std::string &srcPinName, int dstNodeID, const std::string &dstPinName)
Creates a data connection between two nodes.
static std::vector< std::string > GetExecInputPins(TaskNodeType type)
Returns the exec-in pin names for a node type.
std::unique_ptr< NodeConditionsPanel > m_conditionsPanel
Properties-panel sub-widget for the selected Branch node.
std::unique_ptr< NodeBranchRenderer > m_branchRenderer
Specialized renderer for Branch nodes (4-section layout with conditions).
void ConnectExec(int srcNodeID, const std::string &srcPinName, int dstNodeID, const std::string &dstPinName)
Creates an exec connection between two nodes.
void SyncCanvasFromTemplate()
Builds the editor canvas from the in-memory TaskGraphTemplate.
void SyncTemplateFromCanvas()
Builds the in-memory TaskGraphTemplate from the editor nodes/links.
void AfterSave()
Restores the ImNodes canvas panning saved by ResetViewportBeforeSave().
static std::vector< std::string > GetDataOutputPins(TaskNodeType type)
Returns the data-out pin names for a node type.
void ValidateAndCleanBlackboardEntries()
Removes blackboard entries with empty keys or VariableType::None.
int AddNode(TaskNodeType type, float x, float y)
Creates a new node on the canvas.
std::unique_ptr< GetBBValuePropertyPanel > m_getBBPanel
Properties-panel sub-widget for the selected GetBBValue node.
void SyncPresetsFromRegistryToTemplate()
Syncs ALL presets from the registry to the template.
static std::vector< std::string > GetDataInputPins(TaskNodeType type)
Returns the data-in pin names for a node type.
void ResetViewportBeforeSave()
Resets the ImNodes canvas panning to (0,0) before saving node positions.
std::unique_ptr< SetBBValuePropertyPanel > m_setBBPanel
Properties-panel sub-widget for the selected SetBBValue node.
std::unordered_set< int > m_positionedNodes
Nodes for which ImNodes has been given a position.
bool SaveAs(const std::string &path)
Saves the current canvas state to a new JSON v4 file.
int DataInAttrUID(int nodeID, int pinIndex) const
Maps node ID + data pin index -> ImNodes attribute UID for data-in pins.
void RebuildLinks()
Rebuilds ImNodes exec/data link arrays from the template.
void SyncNodePositionsFromImNodes()
Pulls the current node positions from ImNodes into m_editorNodes.
int m_nextNodeID
Next available node ID.
ImNodesEditorContext * m_imnodesContext
bool SerializeAndWrite(const std::string &path)
Serializes the template to JSON v4 and writes to a file.
std::unordered_map< int, std::pair< float, float > > m_nodeDragStartPositions
Per-node drag-start positions used to record a single MoveNodeCommand per drag gesture instead of one...
int AllocNodeID()
Allocate a unique node ID.
int DataOutAttrUID(int nodeID, int pinIndex) const
Maps node ID + data pin index -> ImNodes attribute UID for data-out pins.
int ExecInAttrUID(int nodeID) const
Maps node ID -> ImNodes attribute UID for an exec-in pin.
std::string m_currentPath
std::vector< std::string > GetExecOutputPinsForNode(const TaskNodeDefinition &def) const
Returns exec-out pin names for a node definition, including any dynamically-added pins (VSSequence).
bool m_verificationDone
True once RunVerification() has been called at least once for the current graph.
std::vector< VSEditorNode > m_editorNodes
Editor nodes (mirrors m_template.Nodes + position/selection state)
int m_selectedNodeID
Currently selected node (for properties panel)
void SyncEditorNodesFromTemplate()
Rebuilds m_editorNodes from m_template, preserving existing node positions.
~VisualScriptEditorPanel()
VisualScriptEditorPanel Destructor.
int m_nextLinkID
Next available ImNodes link ID.
ConditionPresetRegistry m_presetRegistry
Global registry of ConditionPreset objects.
std::unique_ptr< ConditionPresetLibraryPanel > m_libraryPanel
Global condition preset library panel (UI for creating/editing/deleting presets).
void LoadTemplate(const TaskGraphTemplate *tmpl, const std::string &path)
Loads a VS graph template into the editor canvas.
static std::vector< std::string > GetExecOutputPins(TaskNodeType type)
Returns the exec-out pin names for a node type.
static Vector FromImVec2(const ImVec2 &v)
< Provides AssetID and INVALID_ASSET_ID
const char * GetNodeTypeLabel(TaskNodeType type)
Returns a human-readable label for a TaskNodeType.
VariableType
Type tags used by TaskValue to identify stored data.
@ Int
32-bit signed integer
@ Float
Single-precision float.
@ Vector
3-component vector (Vector from vector.h)
@ None
Uninitialized / empty value.
@ EntityID
Entity identifier (uint64_t)
@ MathOperator
Math operator symbol (+, -, *, /, %) (from OperatorRegistry)
@ ConditionID
ID of a condition type (from ConditionRegistry)
@ AtomicTaskID
ID of an atomic task (from AtomicTaskUIRegistry)
@ SubGraphPath
File path to a sub-graph .ats file.
@ LocalVariable
Value is read from the local blackboard at runtime.
@ Literal
Value is embedded directly in the template.
@ ComparisonOp
Comparison operator (==, !=, <, <=, >, >=) (from OperatorRegistry)
ComparisonOp
The relational operator used in a ConditionPreset.
TaskNodeType
Identifies the role of a node in the task graph.
@ AtomicTask
Leaf node that executes a single atomic task.
@ While
Conditional loop (Loop / Completed exec outputs)
@ SubGraph
Sub-graph call (SubTask)
@ DoOnce
Single-fire execution (reset via Reset pin)
@ Delay
Timer (Completed exec output after N seconds)
@ GetBBValue
Data node – reads a Blackboard key.
@ MathOp
Data node – arithmetic operation (+, -, *, /)
@ SetBBValue
Data node – writes a Blackboard key.
@ ForEach
Iterate over BB list (Loop Body / Completed exec outputs)
@ Switch
Multi-branch on value (N exec outputs)
@ EntryPoint
Unique entry node for VS graphs (replaces Root)
@ Branch
If/Else conditional (Then / Else exec outputs)
@ VSSequence
Execute N outputs in order ("VS" prefix avoids collision with BT Sequence=1)
constexpr int32_t NODE_INDEX_NONE
Sentinel value for "no node" in node index / ID fields.
@ Output
Value produced by the node.
@ Input
Value consumed by the node.
static std::string VariableTypeToString(VariableType type)
Converts a VariableType to its canonical string representation.
Single entry in the graph's declared blackboard schema (local or global).
A globally-stored, reusable condition expression.
Stores the complete reference for one condition including operand-to-DynamicDataPin mapping.
Describes a single condition expression for Branch/While nodes.
Explicit connection between an output data pin of a source node and an input data pin of a target nod...
Describes a data pin declared on a Visual Script node.
std::string PinName
Pin name ("Value", "Result", etc.)
Explicit connection between a named exec-out pin of a source node and the exec-in pin of a target nod...
@ Const
Literal constant value.
std::string variableName
Blackboard key (mode == Variable), e.g. "mMoveSpeed".
std::string constValue
Literal string (mode == Const), e.g. "5.0".
MathOpOperand leftOperand
Left-hand side operand (A)
std::string mathOperator
Arithmetic operator: "+", "-", "*", "/", "%", "^".
nlohmann::json ToJson() const
Serializes this MathOpRef to a JSON object.
MathOpOperand rightOperand
Right-hand side operand (B)
Lightweight snapshot of a NodeBranch required for rendering.
int nodeID
Numeric node identifier (for ImNodes attribute UIDs)
@ Variable
References a blackboard variable by name.
@ Const
Literal constant value.
@ Pin
External data-input pin on the owning node.
One side of a ConditionPreset comparison expression.
Describes how a single parameter value is supplied to a task node.
ParameterBindingType Type
Binding mode.
Describes a single case branch on a Switch node.
Full description of a single node in the task graph.
MathOpRef mathOpRef
For MathOp: complete operand configuration (left operand, operator, right operand).
std::vector< NodeConditionRef > conditionRefs
Multi-condition refs to global presets (Phase 24)
std::string SubGraphPath
For SubGraph: path to the sub-graph JSON.
std::vector< Condition > conditions
For Branch/While: structured condition list (implicit AND)
std::string BBKey
For GetBBValue/SetBBValue: BB key (scope:key)
std::string ConditionID
For Branch/While/Switch: ATS condition ID.
std::string MathOperator
For MathOp: "+", "-", "*", "/".
float EditorPosY
Canvas Y position loaded from JSON.
std::string AtomicTaskID
Atomic task type identifier (used when Type == AtomicTask)
std::string switchVariable
For Switch: BB key of the variable to switch on.
float EditorPosX
Canvas X position loaded from JSON.
TaskNodeType Type
Node role.
std::vector< DataPinDefinition > DataPins
Data pins declared on this node.
std::vector< ConditionRef > conditionOperandRefs
Parallel to conditions[]: each entry stores the OperandRef->DynamicDataPin UUID mapping for the corre...
std::unordered_map< std::string, ParameterBinding > InputParams
Input parameter bindings.
std::unordered_map< std::string, std::string > OutputParams
Output param -> BB key mapping.
std::vector< SwitchCaseDefinition > switchCases
For Switch: structured case definitions.
std::string NodeName
Human-readable name.
std::unordered_map< std::string, ParameterBinding > Parameters
Named parameter bindings passed to the atomic task.
std::vector< DynamicDataPin > dynamicPins
Dynamic data-input pins for Pin-mode operands (Phase 24)
std::vector< std::string > DynamicExecOutputPins
For VSSequence: dynamically-added exec-out pins beyond the default "Out".
float DelaySeconds
For Delay: duration in seconds.
bool HasEditorPos
True when EditorPosX/Y were loaded from JSON.
int32_t NodeID
Unique ID within this template.
Display metadata for a single atomic task type.
Editor-side representation of an exec or data connection.
int linkID
ImNodes link UID.
int srcAttrID
Source attribute UID.
Editor-side representation of a node in the VS graph canvas.
Metadata for a single blackboard variable entry.