9#include "../GameEngine.h"
10#include "../ECS_Components.h"
11#include "../ECS_Components_AI.h"
12#include "../json_helper.h"
13#include "../third_party/imgui/imgui.h"
14#include "../third_party/imnodes/imnodes.h"
15#include "../third_party/imgui/backends/imgui_impl_sdl3.h"
16#include "../third_party/imgui/backends/imgui_impl_sdlrenderer3.h"
17#include "../third_party/nlohmann/json.hpp"
24#include <unordered_set>
39 , m_windowCreated(
false)
40 , m_separateImGuiContext(
nullptr)
58 ImNodes::CreateContext();
59 ImNodes::GetStyle().GridSpacing = 32.0f;
60 ImNodes::GetStyle().NodeCornerRounding = 8.0f;
61 ImNodes::GetStyle().NodePadding =
ImVec2(8, 8);
69 std::cout <<
"[BTDebugger] Initialized (window will be created on first F10)" << std::endl;
78 ImNodes::DestroyContext();
101 std::cout <<
"[BTDebugger] F10: Debugger window opened (separate window)" << std::endl;
107 std::cout <<
"[BTDebugger] F10: Debugger window closed" << std::endl;
115 std::cout <<
"[BTDebugger] Separate window already exists" << std::endl;
125 "Behavior Tree Runtime Debugger - Independent Window",
132 std::cout <<
"[BTDebugger] ERROR: Failed to create separate window: "
142 ImGui::StyleColorsDark();
151 std::cout <<
"[BTDebugger] ✅ Separate window created successfully!" << std::endl;
152 std::cout <<
"[BTDebugger] Window can be moved to second monitor" << std::endl;
188 std::cout <<
"[BTDebugger] Separate window destroyed" << std::endl;
255 ImGui::SetNextWindowPos(
ImVec2(0, 0));
256 ImGui::SetNextWindowSize(ImGui::GetIO().
DisplaySize);
266 if (!ImGui::Begin(
"Behavior Tree Runtime Debugger##Main",
nullptr,
windowFlags))
272 if (ImGui::BeginMenuBar())
281 if (ImGui::BeginMenu(
"View"))
287 if (ImGui::SliderFloat(
"Node Spacing X", &
m_nodeSpacingX, 80.0f, 400.0f))
291 if (ImGui::SliderFloat(
"Node Spacing Y", &
m_nodeSpacingY, 60.0f, 300.0f))
296 if (ImGui::Button(
"Reset Spacing to Defaults"))
307 std::cout <<
"[BTDebugger] Grid snapping "
310 if (ImGui::IsItemHovered())
320 if (ImGui::IsItemHovered())
322 ImGui::SetTooltip(
"Automatically fit tree to view when selecting an entity");
327 if (ImGui::MenuItem(
"Reload Config"))
332 std::cout <<
"[BTDebugger] Configuration reloaded" << std::endl;
336 ImGui::Text(
"Window Mode: Separate (Independent)");
337 ImGui::Text(
"Press F10 to close window");
342 if (ImGui::BeginMenu(
"Tools"))
344 if (ImGui::MenuItem(
"Auto Organize Graph"))
356 std::cout <<
"[BTDebugger] Graph reorganized with current settings" << std::endl;
361 if (ImGui::IsItemHovered())
363 ImGui::SetTooltip(
"Recalculate all node positions using the current layout algorithm");
366 if (ImGui::MenuItem(
"Reset View"))
370 std::cout <<
"[BTDebugger] View reset to default (zoom 100%%, centered)" << std::endl;
372 if (ImGui::IsItemHovered())
374 ImGui::SetTooltip(
"Reset zoom to 100%% and center camera");
380 if (ImGui::BeginMenu(
"Actions"))
382 if (ImGui::MenuItem(
"Refresh Now (F5)"))
386 if (ImGui::MenuItem(
"Clear Execution Log"))
393 if (ImGui::MenuItem(
"Fit Graph to View",
"F"))
396 if (ImGui::MenuItem(
"Center View",
"C"))
399 if (ImGui::MenuItem(
"Reset Zoom",
"0"))
464 float windowWidth = ImGui::GetContentRegionAvail().x;
509 info.entityName =
"Entity " + std::to_string(entity);
529 info.treeName =
"Not Loaded: " + path;
533 info.treeName =
"Unknown (ID=" + std::to_string(
info.treeId) +
")";
540 std::cout <<
"[BTDebugger] WARNING: Entity " << entity <<
" (" <<
info.entityName
541 <<
") has unknown tree ID=" <<
info.treeId << std::endl;
557 default:
info.aiMode =
"Unknown";
break;
571 info.lastUpdateTime = 0.0f;
615 result = a.entityName < b.entityName;
617 case SortMode::TreeName:
618 result = a.treeName < b.treeName;
620 case SortMode::LastUpdate:
621 result = a.lastUpdateTime > b.lastUpdateTime;
623 case SortMode::AIMode:
624 result = a.aiMode < b.aiMode;
631 void BehaviorTreeDebugWindow::RenderEntityListPanel()
633 ImGui::Text(
"Entities with Behavior Trees");
636 ImGui::InputText(
"Search", m_filterText,
sizeof(m_filterText));
637 if (ImGui::IsItemEdited())
639 UpdateEntityFiltering();
640 UpdateEntitySorting();
643 if (ImGui::Checkbox(
"Active Only", &m_filterActiveOnly))
645 UpdateEntityFiltering();
646 UpdateEntitySorting();
649 if (ImGui::Checkbox(
"Has Target", &m_filterHasTarget))
651 UpdateEntityFiltering();
652 UpdateEntitySorting();
657 ImGui::Text(
"Sort by:");
658 const char*
sortModes[] = {
"Name",
"Tree Name",
"Last Update",
"AI Mode" };
663 UpdateEntitySorting();
666 if (ImGui::Button(m_sortAscending ?
"Asc" :
"Desc"))
668 m_sortAscending = !m_sortAscending;
669 UpdateEntitySorting();
674 ImGui::Text(
"Entities: %d / %d", (
int)m_filteredEntities.size(), (
int)m_entities.size());
676 ImGui::BeginChild(
"EntityList",
ImVec2(0, 0),
false);
677 for (
const auto&
info : m_filteredEntities)
679 RenderEntityEntry(
info);
686 ImGui::PushID((
unsigned int)
info.entityId);
704 bool isSelected = (m_selectedEntity ==
info.entityId);
710 if (ImGui::Selectable(
info.entityName.c_str(), isSelected))
712 m_selectedEntity =
info.entityId;
717 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
718 m_needsLayoutUpdate =
false;
727 if (ImGui::IsItemHovered())
729 ImGui::BeginTooltip();
730 ImGui::Text(
"Entity ID: %u",
info.entityId);
731 ImGui::Text(
"Tree: %s",
info.treeName.c_str());
732 ImGui::Text(
"AI Mode: %s",
info.aiMode.c_str());
733 ImGui::Text(
"Active: %s",
info.isActive ?
"Yes" :
"No");
734 ImGui::Text(
"Has Target: %s",
info.hasTarget ?
"Yes" :
"No");
739 ImGui::TextColored(
ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
"%s",
info.treeName.c_str());
745 void BehaviorTreeDebugWindow::RenderNodeGraphPanel()
747 if (m_selectedEntity == 0)
749 ImGui::Text(
"Select an entity from the list to view its behavior tree");
756 ImGui::Text(
"Selected entity no longer has a behavior tree");
757 m_selectedEntity = 0;
769 ImGui::TextColored(
ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
"Behavior Tree asset not found!");
771 ImGui::Text(
"Tree ID: %u",
btRuntime.AITreeAssetId);
775 ImGui::Text(
"Expected Path: %s", path.c_str());
777 ImGui::TextWrapped(
"The tree file may not be loaded. Check if the JSON file exists and is loaded during level initialization.");
782 ImGui::TextWrapped(
"This tree ID is not registered in the BehaviorTreeManager.");
783 ImGui::TextWrapped(
"Possible causes:");
784 ImGui::BulletText(
"Tree JSON file not loaded");
785 ImGui::BulletText(
"Prefab uses obsolete tree ID");
786 ImGui::BulletText(
"Tree ID mismatch between prefab and runtime");
790 if (ImGui::Button(
"Show All Loaded Trees"))
799 ImGui::Checkbox(
"Editor Mode", &m_editorMode);
800 if (ImGui::IsItemHovered())
802 ImGui::SetTooltip(
"Enable editing mode to add/remove/connect nodes");
813 m_selectedNodes.clear();
814 m_commandStack.Clear();
816 m_nextLinkId = 100000;
818 for (
const auto&
node : m_editingTree.nodes)
820 if (
node.id >= m_nextNodeId)
822 m_nextNodeId =
node.
id + 1;
826 std::cout <<
"[BTEditor] Entered editor mode, editing tree: " << m_editingTree.name << std::endl;
835 ImGui::TextColored(
ImVec4(1.0f, 0.7f, 0.0f, 1.0f),
"[Modified]");
839 ImGui::TextColored(
ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[Unmodified]");
842 RenderEditorToolbar();
847 ImGui::Text(
"Layout:");
851 if (ImGui::RadioButton(
"Vertical", m_layoutDirection == BTLayoutDirection::TopToBottom))
853 if (m_layoutDirection != BTLayoutDirection::TopToBottom)
855 m_layoutDirection = BTLayoutDirection::TopToBottom;
860 if (ImGui::RadioButton(
"Horizontal", m_layoutDirection == BTLayoutDirection::LeftToRight))
862 if (m_layoutDirection != BTLayoutDirection::LeftToRight)
864 m_layoutDirection = BTLayoutDirection::LeftToRight;
871 m_layoutEngine.SetLayoutDirection(m_layoutDirection);
872 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
873 m_needsLayoutUpdate =
false;
876 if (m_needsLayoutUpdate &&
tree)
878 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
879 m_needsLayoutUpdate =
false;
883 if (ImGui::Button(
"Reset Camera"))
911 std::cout <<
"[BTDebugger] Camera reset to center: ("
918 ImNodes::BeginNodeEditor();
938 if (m_lastCenteredEntity != m_selectedEntity)
948 m_selectedNodes.clear();
949 m_commandStack.Clear();
962 std::cout <<
"[BTDebugger] ✅ Camera " << (m_autoFitOnLoad ?
"fitted" :
"centered") <<
" on graph" << std::endl;
963 m_lastCenteredEntity = m_selectedEntity;
968 if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemHovered())
970 if (
io.MouseWheel != 0.0f)
978 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
981 std::cout <<
"[BTDebugger] Zoom: " << (
int)(m_currentZoom * 100)
982 <<
"% (layout recomputed)" << std::endl;
987 if (ImGui::IsWindowFocused())
1001 m_showMinimap = !m_showMinimap;
1005 float oldZoom = m_currentZoom;
1006 m_currentZoom = std::min(
MAX_ZOOM, m_currentZoom * 1.2f);
1010 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1017 float oldZoom = m_currentZoom;
1018 m_currentZoom = std::max(
MIN_ZOOM, m_currentZoom / 1.2f);
1022 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1028 RenderBehaviorTreeGraph();
1033 ImNodes::EndNodeEditor();
1043 if (ValidateConnection(parentId, childId))
1046 auto cmd = std::make_unique<ConnectNodesCommand>(&m_editingTree, parentId, childId);
1047 m_commandStack.Execute(std::move(
cmd));
1053 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1058 std::cout <<
"[BTEditor] Connection created: " << parentId <<
" -> " << childId << std::endl;
1062 std::cout <<
"[BTEditor] Invalid connection: " << parentId <<
" -> " << childId << std::endl;
1067 if (ImNodes::IsLinkDestroyed(&linkId))
1069 auto it = std::find_if(m_linkMap.begin(), m_linkMap.end(),
1070 [linkId](
const LinkInfo&
info) { return info.linkId == linkId; });
1072 if (
it != m_linkMap.end())
1078 auto cmd = std::make_unique<DisconnectNodesCommand>(&m_editingTree, parentId, childId);
1079 m_commandStack.Execute(std::move(
cmd));
1085 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1090 std::cout <<
"[BTEditor] Connection deleted: " << parentId <<
" -> " << childId << std::endl;
1099 m_selectedNodes.clear();
1102 m_selectedNodes.push_back(
static_cast<uint32_t>(
id));
1108 m_showNodePalette =
true;
1109 m_nodeCreationPos.Set(ImGui::GetMousePos().x, ImGui::GetMousePos().y, 0.f);
1114 HandleNodeDeletion();
1117 if (ImGui::GetIO().
KeyCtrl && ImGui::IsKeyPressed(
ImGuiKey_D) && !m_selectedNodes.empty())
1119 HandleNodeDuplication();
1127 if (m_showNodePalette)
1129 RenderNodePalette();
1133 void BehaviorTreeDebugWindow::RenderBehaviorTreeGraph()
1140 if (m_editorMode && !m_editingTree.nodes.empty())
1142 tree = &m_editingTree;
1154 for (
const auto&
node :
tree->nodes)
1164 for (
const auto&
node :
tree->nodes)
1173 for (
const auto&
node :
tree->nodes)
1192 std::cout <<
"[RenderNode] Node " <<
node->id
1193 <<
" (" <<
node->name <<
") at ("
1194 << (
int)
layout->position.x <<
", " << (
int)
layout->position.y <<
")" << std::endl;
1200 ImNodes::BeginNode(
node->id);
1202 ImNodes::BeginNodeTitleBar();
1211 const char*
icon = GetNodeIcon(
node->type);
1212 ImGui::Text(
"%s %s",
icon,
node->name.c_str());
1214 ImNodes::PopColorStyle();
1215 ImNodes::PopColorStyle();
1216 ImNodes::PopColorStyle();
1218 ImNodes::EndNodeTitleBar();
1220 ImGui::PushItemWidth(200);
1222 const char*
typeStr =
"Unknown";
1232 ImGui::TextColored(
ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
"Type: %s",
typeStr);
1234 ImGui::TextColored(
ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
"ID: %u",
node->id);
1236 ImGui::Dummy(
ImVec2(0.0f, 5.0f));
1238 ImGui::PopItemWidth();
1242 ImNodes::BeginInputAttribute(
node->id * 10000);
1244 ImNodes::EndInputAttribute();
1250 ImNodes::BeginOutputAttribute(
node->id * 10000 + 1);
1252 ImNodes::EndOutputAttribute();
1257 float pulse = 0.5f + 0.5f *
sinf(m_pulseTimer * 2.0f * 3.14159265f);
1266 ImNodes::PopColorStyle();
1275 static int currentFrame = 0;
1288 int linkId = m_nextLinkId++;
1289 ImNodes::Link(linkId,
node->id * 10000 + 1, childId * 10000);
1296 info.childId = childId;
1297 m_linkMap.push_back(
info);
1302 node->decoratorChildId != 0)
1304 int linkId = m_nextLinkId++;
1305 ImNodes::Link(linkId,
node->id * 10000 + 1,
node->decoratorChildId * 10000);
1312 info.childId =
node->decoratorChildId;
1313 m_linkMap.push_back(
info);
1323 return IM_COL32(100, 150, 255, 255);
1325 return IM_COL32(100, 255, 150, 255);
1327 return IM_COL32(255, 200, 100, 255);
1329 return IM_COL32(255, 100, 150, 255);
1331 return IM_COL32(200, 100, 255, 255);
1333 return IM_COL32(150, 150, 255, 255);
1335 return IM_COL32(128, 128, 128, 255);
1339 const char* BehaviorTreeDebugWindow::GetNodeIcon(
BTNodeType type)
const
1349 default:
return "•";
1353 void BehaviorTreeDebugWindow::RenderInspectorPanel()
1355 if (m_selectedEntity == 0)
1357 ImGui::Text(
"No entity selected");
1362 RenderValidationPanel();
1363 RenderNodeProperties();
1369 ImGui::Text(
"Inspector");
1377 RenderValidationPanel();
1380 if (m_inspectedNodeId != 0)
1384 RenderNodeProperties();
1391 RenderRuntimeInfo();
1396 RenderBlackboardSection();
1401 RenderExecutionLog();
1405 RenderNewBTDialog();
1408 void BehaviorTreeDebugWindow::RenderRuntimeInfo()
1419 ImGui::Text(
"Tree ID: %u",
btRuntime.AITreeAssetId);
1423 ImGui::TextColored(
ImVec4(0.0f, 1.0f, 0.0f, 1.0f),
"Tree Name: %s",
tree->name.c_str());
1424 ImGui::Text(
"Node Count: %zu",
tree->nodes.size());
1428 ImGui::TextColored(
ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
"Tree: NOT FOUND");
1433 ImGui::TextColored(
ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
"Expected: %s", path.c_str());
1436 if (ImGui::Button(
"Debug: List All Trees"))
1444 ImGui::Text(
"Current Node ID: %u",
btRuntime.AICurrentNodeIndex);
1451 ImGui::Text(
"Node Name: %s",
currentNode->name.c_str());
1470 ImGui::Text(
"Active: %s",
btRuntime.isActive ?
"Yes" :
"No");
1472 if (world.HasComponent<
AIState_data>(m_selectedEntity))
1475 const char*
modeStr =
"Unknown";
1485 ImGui::Text(
"AI Mode: %s",
modeStr);
1486 ImGui::Text(
"Time in Mode: %.2f s",
aiState.timeInCurrentMode);
1490 void BehaviorTreeDebugWindow::RenderBlackboardSection()
1496 ImGui::Text(
"No blackboard data");
1502 if (ImGui::TreeNode(
"Target"))
1504 ImGui::Text(
"Has Target: %s",
blackboard.hasTarget ?
"Yes" :
"No");
1505 ImGui::Text(
"Target Entity: %u",
blackboard.targetEntity);
1506 ImGui::Text(
"Target Visible: %s",
blackboard.targetVisible ?
"Yes" :
"No");
1507 ImGui::Text(
"Distance: %.2f",
blackboard.distanceToTarget);
1508 ImGui::Text(
"Time Since Seen: %.2f s",
blackboard.timeSinceTargetSeen);
1509 ImGui::Text(
"Last Known Pos: (%.1f, %.1f)",
1515 if (ImGui::TreeNode(
"Movement"))
1517 ImGui::Text(
"Has Move Goal: %s",
blackboard.hasMoveGoal ?
"Yes" :
"No");
1518 ImGui::Text(
"Goal Position: (%.1f, %.1f)",
1524 if (ImGui::TreeNode(
"Patrol"))
1526 ImGui::Text(
"Has Patrol Path: %s",
blackboard.hasPatrolPath ?
"Yes" :
"No");
1527 ImGui::Text(
"Current Point: %d",
blackboard.currentPatrolPoint);
1528 ImGui::Text(
"Point Count: %d",
blackboard.patrolPointCount);
1532 if (ImGui::TreeNode(
"Combat"))
1534 ImGui::Text(
"Can Attack: %s",
blackboard.canAttack ?
"Yes" :
"No");
1535 ImGui::Text(
"Attack Cooldown: %.2f s",
blackboard.attackCooldown);
1539 ImGui::Text(
"Last Attack Time: %.2f",
blackboard.lastAttackTime);
1543 ImGui::Text(
"Last Attack: Never");
1549 if (ImGui::TreeNode(
"Stimuli"))
1551 ImGui::Text(
"Heard Noise: %s",
blackboard.heardNoise ?
"Yes" :
"No");
1552 ImGui::Text(
"Last Damage: %.2f",
blackboard.damageAmount);
1556 if (ImGui::TreeNode(
"Wander"))
1558 ImGui::Text(
"Has Destination: %s",
blackboard.hasWanderDestination ?
"Yes" :
"No");
1559 ImGui::Text(
"Destination: (%.1f, %.1f)",
1562 ImGui::Text(
"Wait Timer: %.2f / %.2f s",
1569 void BehaviorTreeDebugWindow::RenderExecutionLog()
1571 if (ImGui::Button(
"Clear Log"))
1573 m_executionLog.clear();
1578 ImGui::BeginChild(
"ExecutionLogScroll",
ImVec2(0, 0),
false);
1580 for (
auto it = m_executionLog.rbegin();
it != m_executionLog.rend(); ++
it)
1584 if (
entry.entity != m_selectedEntity)
1588 const char*
icon =
"▶";
1591 color =
ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
1596 color =
ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
1600 ImGui::TextColored(color,
"[%.2fs ago] %s Node %u (%s)",
1611 entry.entity = entity;
1612 entry.nodeId = nodeId;
1613 entry.nodeName = nodeName;
1614 entry.status = status;
1616 m_executionLog.push_back(
entry);
1618 while (m_executionLog.size() > MAX_LOG_ENTRIES)
1620 m_executionLog.pop_front();
1624 void BehaviorTreeDebugWindow::ApplyZoomToStyle()
1626 ImNodes::GetStyle().NodePadding =
ImVec2(8.0f * m_currentZoom, 8.0f * m_currentZoom);
1627 ImNodes::GetStyle().NodeCornerRounding = 8.0f * m_currentZoom;
1628 ImNodes::GetStyle().GridSpacing = 32.0f * m_currentZoom;
1645 float BehaviorTreeDebugWindow::GetSafeZoom()
const
1660 void BehaviorTreeDebugWindow::FitGraphToView()
1674 CenterViewOnGraph();
1680 float targetZoom = std::min(
zoomX,
zoomY) * 0.9f;
1690 std::cout <<
"[BTDebugger] Fit to view: zoom=" << (
int)(m_currentZoom * 100)
1694 void BehaviorTreeDebugWindow::CenterViewOnGraph()
1710 std::cout <<
"[BTDebugger] Centered view on graph (" << (
int)
graphCenter.x
1711 <<
", " << (
int)
graphCenter.y <<
")" << std::endl;
1714 void BehaviorTreeDebugWindow::ResetZoom()
1724 m_currentZoom = 1.0f;
1725 m_currentLayout = m_layoutEngine.ComputeLayout(
tree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1728 std::cout <<
"[BTDebugger] Reset zoom to 100% (layout recomputed)" << std::endl;
1733 m_currentZoom = 1.0f;
1735 std::cout <<
"[BTDebugger] Reset zoom to 100%" << std::endl;
1738 void BehaviorTreeDebugWindow::RenderMinimap()
1768 ImGui::TextColored(
ImVec4(1, 1, 1, 0.7f),
"Minimap");
1779 ImGui::TextColored(
ImVec4(1, 1, 1, 0.7f),
"Minimap");
1795 color =
IM_COL32(255, 255, 0, 255);
1821 ImGui::TextColored(
ImVec4(1, 1, 1, 0.7f),
"Minimap");
1824 void BehaviorTreeDebugWindow::RenderEditorToolbar()
1826 if (ImGui::Button(
"Add Node"))
1828 m_showNodePalette =
true;
1829 m_nodeCreationPos.Set(ImGui::GetMousePos().x, ImGui::GetMousePos().y, 0.f);
1833 if (ImGui::Button(
"Save Tree"))
1839 bool canUndo = m_commandStack.CanUndo();
1840 if (!
canUndo) ImGui::BeginDisabled();
1841 if (ImGui::Button(
"Undo"))
1843 m_commandStack.Undo();
1845 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1848 if (!
canUndo) ImGui::EndDisabled();
1851 bool canRedo = m_commandStack.CanRedo();
1852 if (!
canRedo) ImGui::BeginDisabled();
1853 if (ImGui::Button(
"Redo"))
1855 m_commandStack.Redo();
1857 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1860 if (!
canRedo) ImGui::EndDisabled();
1863 ImGui::Text(
"Selected: %zu", m_selectedNodes.size());
1866 void BehaviorTreeDebugWindow::RenderNodePalette()
1868 ImGui::OpenPopup(
"##NodePalette");
1870 if (ImGui::BeginPopup(
"##NodePalette"))
1872 ImGui::Text(
"Add Node");
1875 if (ImGui::MenuItem(
"Selector"))
1878 m_showNodePalette =
false;
1881 if (ImGui::MenuItem(
"Sequence"))
1884 m_showNodePalette =
false;
1887 if (ImGui::MenuItem(
"Condition"))
1890 m_showNodePalette =
false;
1893 if (ImGui::MenuItem(
"Action"))
1896 m_showNodePalette =
false;
1899 if (ImGui::MenuItem(
"Inverter"))
1902 m_showNodePalette =
false;
1905 if (ImGui::MenuItem(
"Repeater"))
1908 m_showNodePalette =
false;
1915 m_showNodePalette =
false;
1925 std::string nodeName;
1929 nodeName =
"New Selector";
1932 nodeName =
"New Sequence";
1935 nodeName =
"New Condition";
1938 nodeName =
"New Action";
1941 nodeName =
"New Inverter";
1944 nodeName =
"New Repeater";
1949 if (m_editingTree.nodes.empty() && m_selectedEntity != 0)
1964 m_editingTree.name =
"New Tree";
1965 m_editingTree.rootNodeId = 0;
1971 auto cmd = std::make_unique<AddNodeCommand>(&m_editingTree,
nodeType, nodeName, m_nodeCreationPos);
1972 m_commandStack.Execute(std::move(
cmd));
1978 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
1983 std::cout <<
"[BTEditor] Created node: " << nodeName << std::endl;
1986 void BehaviorTreeDebugWindow::HandleNodeDeletion()
1988 if (m_selectedNodes.empty() || !m_editorMode)
1991 for (
uint32_t nodeId : m_selectedNodes)
1994 auto cmd = std::make_unique<DeleteNodeCommand>(&m_editingTree, nodeId);
1995 m_commandStack.Execute(std::move(
cmd));
1997 std::cout <<
"[BTEditor] Deleted node ID: " << nodeId << std::endl;
2000 m_selectedNodes.clear();
2005 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2011 for (
auto&
node : m_editingTree.nodes)
2013 auto childIt = std::find(
node.childIds.begin(),
node.childIds.end(), nodeId);
2019 if (
node.decoratorChildId == nodeId)
2021 node.decoratorChildId = 0;
2025 std::cout <<
"[BTEditor] Deleted node ID: " << nodeId << std::endl;
2029 m_selectedNodes.clear();
2034 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2040 void BehaviorTreeDebugWindow::HandleNodeDuplication()
2042 if (m_selectedNodes.empty() || !m_editorMode)
2047 for (
uint32_t nodeId : m_selectedNodes)
2055 m_commandStack.Execute(std::move(
cmd));
2058 if (!m_editingTree.nodes.empty())
2074 std::cout <<
"[BTEditor] Duplicated node ID: " << nodeId << std::endl;
2083 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2086 std::cout <<
"[BTEditor] Duplicated node: " <<
duplicate.name <<
" (ID: " <<
duplicate.id <<
")" << std::endl;
2094 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2100 bool BehaviorTreeDebugWindow::ValidateConnection(
uint32_t parentId,
uint32_t childId)
const
2102 const BTNode* parent = m_editingTree.GetNode(parentId);
2103 const BTNode*
child = m_editingTree.GetNode(childId);
2105 if (!parent || !
child)
2108 if (parentId == childId)
2161 if (
current->decoratorChildId != 0)
2171 void BehaviorTreeDebugWindow::SaveEditedTree()
2175 std::cout <<
"[BTEditor] No changes to save" << std::endl;
2182 treeJson[
"blueprintType"] =
"BehaviorTree";
2184 treeJson[
"description"] =
"Edited in BT Editor";
2187 metadata[
"author"] =
"BT Editor";
2189 auto now = std::time(
nullptr);
2212 editorState[
"zoom"] = 1.0;
2213 editorState[
"scrollOffset"] = { {
"x", 0}, {
"y", 0} };
2214 treeJson[
"editorState"] = editorState;
2217 dataSection[
"rootNodeId"] =
static_cast<int>(m_editingTree.rootNodeId);
2220 for (
const auto&
node : m_editingTree.nodes)
2236 nodeJson[
"position"] = { {
"x", 0.0}, {
"y", 0.0} };
2241 switch (
node.conditionType)
2256 if (
node.conditionParam != 0.0f)
2258 nodeJson[
"parameters"] = { {
"param",
node.conditionParam} };
2262 nodeJson[
"parameters"] = json::object();
2268 switch (
node.actionType)
2286 if (
node.actionParam1 != 0.0f)
params[
"param1"] =
node.actionParam1;
2287 if (
node.actionParam2 != 0.0f)
params[
"param2"] =
node.actionParam2;
2295 if (!
node.childIds.empty())
2302 if (
node.decoratorChildId != 0)
2304 nodeJson[
"decoratorChildId"] =
static_cast<int>(
node.decoratorChildId);
2313 std::string
filename =
"Blueprints/AI/" + m_editingTree.name +
"_edited.json";
2324 std::cout <<
"[BTEditor] Tree saved to: " <<
filename << std::endl;
2328 std::cerr <<
"[BTEditor] ERROR: Failed to open file for writing: " <<
filename << std::endl;
2331 catch (
const std::exception&
e)
2333 std::cerr <<
"[BTEditor] ERROR: Exception during save: " <<
e.what() << std::endl;
2337 void BehaviorTreeDebugWindow::UndoLastAction()
2339 if (!m_commandStack.CanUndo())
2342 m_commandStack.Undo();
2348 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2353 std::cout <<
"[BTEditor] Undo performed" << std::endl;
2356 void BehaviorTreeDebugWindow::RedoLastAction()
2358 if (!m_commandStack.CanRedo())
2361 m_commandStack.Redo();
2367 m_currentLayout = m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);
2372 std::cout <<
"[BTEditor] Redo performed" << std::endl;
2375 void BehaviorTreeDebugWindow::LoadBTConfig()
2380 std::cerr <<
"[BTDebugger] Failed to load BT_config.json, using defaults" << std::endl;
2381 m_configLoaded =
false;
2406 const auto& nodeColors =
configJson[
"nodeColors"];
2408 std::vector<std::string>
nodeTypes = {
"Selector",
"Sequence",
"Condition",
"Action",
"Inverter",
"Repeater" };
2409 std::vector<std::string>
statusTypes = {
"idle",
"running",
"success",
"failure",
"aborted" };
2428 m_config.nodeColors[
nodeType][status] = color;
2439 std::map<std::string, BTNodeType>
typeMap;
2447 std::map<std::string, BTStatus>
statusMap;
2482 m_nodeColors[
nodeType][status] = color;
2486 std::cout <<
"[BTDebugger] Loaded " << m_nodeColors.size() <<
" node color schemes" << std::endl;
2489 m_configLoaded =
true;
2490 std::cout <<
"[BTDebugger] Configuration loaded from BT_config.json" << std::endl;
2493 void BehaviorTreeDebugWindow::ApplyConfigToLayout()
2495 if (!m_configLoaded)
2498 m_layoutDirection = m_config.defaultHorizontal ? BTLayoutDirection::LeftToRight : BTLayoutDirection::TopToBottom;
2499 m_layoutEngine.SetLayoutDirection(m_layoutDirection);
2501 m_nodeSpacingX = m_config.horizontalSpacing;
2502 m_nodeSpacingY = m_config.verticalSpacing;
2504 std::cout <<
"[BTDebugger] Applied configuration to layout engine" << std::endl;
2509 if (!m_config.gridSnappingEnabled)
2512 float gridSize = m_config.gridSize;
2514 std::round(
pos.x / gridSize) * gridSize,
2515 std::round(
pos.y / gridSize) * gridSize,
2566 if (!m_configLoaded)
2568 return GetNodeColor(type);
2571 auto typeIt = m_nodeColors.find(type);
2572 if (
typeIt != m_nodeColors.end())
2583 return GetNodeColor(type);
2590 void BehaviorTreeDebugWindow::RenderValidationPanel()
2592 if (!m_showValidationPanel || !m_editorMode)
2598 if (ImGui::BeginChild(
"ValidationMessages",
ImVec2(0, 150),
true))
2607 color =
ImVec4(1.0f, 0.3f, 0.3f, 1.0f);
2612 color =
ImVec4(1.0f, 0.8f, 0.2f, 1.0f);
2617 color =
ImVec4(0.3f, 0.8f, 1.0f, 1.0f);
2622 ImGui::Text(
"%s Node %u: %s",
icon,
msg.nodeId,
msg.message.c_str());
2623 ImGui::PopStyleColor();
2634 if (
msg.nodeId == nodeId)
2642 return IM_COL32(255, 200, 80, 255);
2656 const BTNode* parent = m_editingTree.GetNode(parentId);
2657 const BTNode*
child = m_editingTree.GetNode(childId);
2659 if (!parent || !
child)
2682 return !
tempTree.DetectCycle(parentId);
2689 void BehaviorTreeDebugWindow::RenderNodeProperties()
2691 if (!m_showNodeProperties || m_inspectedNodeId == 0)
2694 BTNode*
node = m_editingTree.GetNode(m_inspectedNodeId);
2697 m_showNodeProperties =
false;
2702 ImGui::Text(
"Node Properties");
2704 if (ImGui::BeginChild(
"NodeProperties",
ImVec2(0, 300),
true))
2706 ImGui::Text(
"ID: %u",
node->id);
2707 ImGui::Text(
"Type: %d",
static_cast<int>(
node->type));
2724 ImGui::Text(
"Action Parameters");
2728 "SetMoveGoalToLastKnownTargetPos",
2729 "SetMoveGoalToTarget",
2730 "SetMoveGoalToPatrolPoint",
2733 "PatrolPickNextPoint",
2737 "ChooseRandomNavigablePoint",
2738 "RequestPathfinding",
2750 if (ImGui::InputFloat(
"Param 1", &
node->actionParam1))
2755 if (ImGui::InputFloat(
"Param 2", &
node->actionParam2))
2763 ImGui::Text(
"Condition Parameters");
2773 "IsWaitTimerExpired",
2774 "HasNavigableDestination",
2776 "HasReachedDestination"
2787 if (ImGui::InputFloat(
"Param", &
node->conditionParam))
2795 ImGui::Text(
"Repeater Parameters");
2797 if (ImGui::InputInt(
"Repeat Count", &
node->repeatCount))
2805 if (ImGui::Button(
"Close Properties"))
2807 m_showNodeProperties =
false;
2815 std::string BehaviorTreeDebugWindow::GetCurrentTimestamp()
const
2828 return std::string(
buffer);
2833 json j = json::object();
2835 j[
"schema_version"] = 2;
2836 j[
"type"] =
"BehaviorTree";
2837 j[
"blueprintType"] =
"BehaviorTree";
2838 j[
"name"] =
tree.name;
2839 j[
"description"] =
"";
2842 json metadata = json::object();
2843 metadata[
"author"] =
"Atlasbruce";
2844 metadata[
"created"] = GetCurrentTimestamp();
2845 metadata[
"lastModified"] = GetCurrentTimestamp();
2847 json tags = json::array();
2848 tags.push_back(
"AI");
2849 tags.push_back(
"BehaviorTree");
2850 metadata[
"tags"] = tags;
2852 j[
"metadata"] = metadata;
2855 json editorState = json::object();
2856 editorState[
"zoom"] = 1.0f;
2858 json scrollOffset = json::object();
2859 scrollOffset[
"x"] = 0.0f;
2860 scrollOffset[
"y"] = 0.0f;
2861 editorState[
"scrollOffset"] = scrollOffset;
2863 j[
"editorState"] = editorState;
2866 json data = json::object();
2867 data[
"rootNodeId"] =
tree.rootNodeId;
2870 for (
const auto&
node :
tree.nodes)
2892 pos[
"y"] = 100.0f *
static_cast<float>(
node.id);
2898 json children = json::array();
2901 children.push_back(childId);
2909 if (
node.decoratorChildId != 0)
2924 switch (
node.actionType)
2951 switch (
node.conditionType)
2972 if (!
nodeJson.contains(
"parameters"))
2974 nodeJson[
"parameters"] = json::object();
2986 void BehaviorTreeDebugWindow::Save()
2988 if (m_currentFilePath.empty())
3010 std::cout <<
"[BTEditor] Cannot save: tree has validation errors" << std::endl;
3015 json j = SerializeTreeToJson(m_editingTree);
3019 std::cout <<
"[BTEditor] Saved tree to: " << m_currentFilePath << std::endl;
3024 std::cout <<
"[BTEditor] Failed to save tree" << std::endl;
3028 void BehaviorTreeDebugWindow::SaveAs()
3031 std::string
filename =
"Blueprints/AI/" + m_editingTree.name +
"_edited.json";
3036 void BehaviorTreeDebugWindow::RenderFileMenu()
3038 if (ImGui::BeginMenu(
"File"))
3040 if (ImGui::MenuItem(
"New BT...",
""))
3042 m_showNewBTDialog =
true;
3047 if (ImGui::MenuItem(
"Save",
"Ctrl+S",
false, m_editorMode &&
m_isDirty))
3052 if (ImGui::MenuItem(
"Save As...",
"Ctrl+Shift+S",
false, m_editorMode))
3059 if (ImGui::MenuItem(
"Close",
"",
false, m_editorMode))
3062 m_editorMode =
false;
3069 void BehaviorTreeDebugWindow::RenderEditMenu()
3071 if (ImGui::BeginMenu(
"Edit"))
3073 bool canUndo = m_commandStack.CanUndo();
3074 bool canRedo = m_commandStack.CanRedo();
3079 undoText +=
" (" + m_commandStack.GetUndoDescription() +
")";
3085 redoText +=
" (" + m_commandStack.GetRedoDescription() +
")";
3090 m_commandStack.Undo();
3096 m_commandStack.Redo();
3120 root.name =
"Root Selector";
3122 tree.rootNodeId = 1;
3130 root.name =
"Root Selector";
3131 root.childIds.push_back(2);
3147 wait.actionParam1 = 2.0f;
3148 wait.actionParam2 = 6.0f;
3154 choose.name =
"Choose Point";
3156 choose.actionParam1 = 500.0f;
3157 choose.actionParam2 = 10.0f;
3160 tree.rootNodeId = 1;
3168 root.name =
"Patrol Sequence";
3169 root.childIds.push_back(2);
3170 root.childIds.push_back(3);
3171 root.childIds.push_back(4);
3177 pick.name =
"Pick Next Point";
3195 tree.rootNodeId = 1;
3203 root.name =
"Root Selector";
3204 root.childIds.push_back(2);
3205 root.childIds.push_back(5);
3220 hasTarget.
name =
"Has Target";
3222 tree.nodes.push_back(hasTarget);
3237 wander.actionParam1 = 300.0f;
3240 tree.rootNodeId = 1;
3246 void BehaviorTreeDebugWindow::RenderNewBTDialog()
3248 if (!m_showNewBTDialog)
3251 ImGui::OpenPopup(
"New Behavior Tree");
3258 ImGui::Text(
"Create a new behavior tree from template");
3261 ImGui::InputText(
"Name", m_newBTName,
sizeof(m_newBTName));
3263 ImGui::Text(
"Template:");
3264 ImGui::RadioButton(
"Empty (root node only)", &m_selectedTemplate, 0);
3265 ImGui::RadioButton(
"Basic AI (idle + wander)", &m_selectedTemplate, 1);
3266 ImGui::RadioButton(
"Patrol (patrol points)", &m_selectedTemplate, 2);
3267 ImGui::RadioButton(
"Combat (combat + wander)", &m_selectedTemplate, 3);
3271 if (ImGui::Button(
"Create",
ImVec2(120, 0)))
3273 if (
strlen(m_newBTName) > 0)
3275 m_editingTree = CreateFromTemplate(m_selectedTemplate, std::string(m_newBTName));
3276 m_editorMode =
true;
3278 m_currentFilePath =
"";
3279 m_showNewBTDialog =
false;
3280 std::cout <<
"[BTEditor] Created new tree: " << m_newBTName << std::endl;
3286 if (ImGui::Button(
"Cancel",
ImVec2(120, 0)))
3288 m_showNewBTDialog =
false;
Command pattern implementation for behavior tree editor undo/redo.
std::cout<< "[BTEditor] Duplicated node: "<< duplicate.name<< " (ID: "<< duplicate.id<< ")"<< std::endl;} } m_selectedNodes=newNodes;m_treeModified=true;m_currentLayout=m_layoutEngine.ComputeLayout(&m_editingTree, m_nodeSpacingX, m_nodeSpacingY, m_currentZoom);m_validationMessages=m_editingTree.ValidateTreeFull();} bool BehaviorTreeDebugWindow::ValidateConnection(uint32_t parentId, uint32_t childId) const { const BTNode *parent=m_editingTree.GetNode(parentId);const BTNode *child=m_editingTree.GetNode(childId);if(!parent||!child) return false;if(parentId==childId) return false;if(parent->type !=BTNodeType::Selector &&parent->type !=BTNodeType::Sequence &&parent->type !=BTNodeType::Inverter &&parent->type !=BTNodeType::Repeater) { return false;} if((parent->type==BTNodeType::Inverter||parent->type==BTNodeType::Repeater) &&parent->decoratorChildId !=0) { return false;} if(parent->type==BTNodeType::Selector||parent->type==BTNodeType::Sequence) { if(std::find(parent->childIds.begin(), parent->childIds.end(), childId) !=parent->childIds.end()) { return false;} } std::vector< uint32_t > visited
std::vector< uint32_t > toVisit
Runtime debugger for behavior tree visualization and inspection.
BTActionType
Built-in action types for behavior trees.
@ SetMoveGoalToTarget
Move towards current target.
@ SetMoveGoalToLastKnownTargetPos
Move to last seen target position.
@ WaitRandomTime
Initialize random timer (param1=min, param2=max)
@ MoveToGoal
Execute movement to goal.
@ ClearTarget
Clear current target.
@ PatrolPickNextPoint
Select next patrol point.
@ ChooseRandomNavigablePoint
Choose navigable point (param1=searchRadius, param2=maxAttempts)
@ AttackIfClose
Attack if in range.
@ SetMoveGoalToPatrolPoint
Move to next patrol waypoint.
@ RequestPathfinding
Request pathfinding to moveGoal via MoveIntent.
@ FollowPath
Follow the path (check progression)
BTStatus
Behavior tree node execution status.
@ Success
Node completed successfully.
@ Running
Node is currently executing.
@ Aborted
Node execution interrupted (e.g., entity destroyed)
@ Idle
Node waiting for execution (not yet started)
BTNodeType
Behavior tree node types.
@ Action
Leaf node - performs an action.
@ Selector
OR node - succeeds if any child succeeds.
@ Sequence
AND node - succeeds if all children succeed.
@ Inverter
Decorator - inverts child result.
@ Condition
Leaf node - checks a condition.
@ Repeater
Decorator - repeats child N times.
BTConditionType
Built-in condition types for behavior trees.
@ HasValidPath
Valid path calculated?
@ CanAttack
Attack is available.
@ IsWaitTimerExpired
Wait timer expired?
@ HeardNoise
Detected noise.
@ TargetVisible
Can see target entity.
@ HasMoveGoal
Movement goal is set.
@ HasNavigableDestination
Navigable destination chosen?
@ HealthBelow
Health below threshold.
@ HasReachedDestination
Reached destination?
@ TargetInRange
Target within specified range.
ComponentTypeID GetComponentTypeID_Static()
std::string GetTreePathFromId(uint32_t treeId) const
void DebugPrintLoadedTrees() const
static BehaviorTreeManager & Get()
const BehaviorTreeAsset * GetTreeByAnyId(uint32_t treeId) const
static float fDt
Delta time between frames in seconds.
bool CanRedo() const
Check if redo is available.
void Undo()
Undo the last command.
bool CanUndo() const
Check if undo is available.
void Redo()
Redo the last undone command.
std::vector< BTNodeLayout > ComputeLayout(const BehaviorTreeAsset *tree, float nodeSpacingX=180.0f, float nodeSpacingY=120.0f, float zoomFactor=1.0f)
Compute layout for a behavior tree.
~BehaviorTreeDebugWindow()
float m_autoRefreshInterval
void Initialize()
Initialize the debug window.
SDL_Renderer * m_separateRenderer
ImGuiContext * m_separateImGuiContext
void RenderInspectorPanel()
void RenderNodeGraphPanel()
void RenderEntityListPanel()
std::vector< EntityDebugInfo > m_filteredEntities
void DestroySeparateWindow()
void ToggleVisibility()
Toggle window visibility (creates/destroys separate window)
std::deque< ExecutionLogEntry > m_executionLog
void UpdateEntitySorting()
BehaviorTreeDebugWindow()
void HandleNodeDeletion()
void UpdateEntityFiltering()
void HandleNodeDuplication()
std::vector< uint32_t > m_selectedNodes
void ApplyConfigToLayout()
SDL_Window * m_separateWindow
EntityID m_selectedEntity
BTGraphLayoutEngine m_layoutEngine
void CreateSeparateWindow()
void Shutdown()
Shutdown and cleanup.
void Render()
Render the debug window (in separate SDL3 window)
std::vector< BTNodeLayout > m_currentLayout
bool m_imnodesInitialized
std::vector< EntityDebugInfo > m_entities
void RenderInSeparateWindow()
void ProcessEvent(SDL_Event *event)
Process SDL events for separate window.
BTCommandStack m_commandStack
static World & Get()
Get singleton instance (short form)
std::string GetString(const json &j, const std::string &key, const std::string &defaultValue="")
Safely get a string value from JSON.
bool LoadJsonFromFile(const std::string &filepath, json &j)
Load and parse a JSON file.
int GetInt(const json &j, const std::string &key, int defaultValue=0)
Safely get an integer value from JSON.
float GetFloat(const json &j, const std::string &key, float defaultValue=0.0f)
Safely get a float value from JSON.
bool SaveJsonToFile(const std::string &filepath, const json &j, int indent=4)
Save a JSON object to a file with formatting.
bool IsObject(const json &j, const std::string &key)
Check if a key contains an object.
bool GetBool(const json &j, const std::string &key, bool defaultValue=false)
Safely get a boolean value from JSON.
PinType
Type of connection pin on a node.
constexpr float ZOOM_EPSILON
Represents a single node in a behavior tree.
std::vector< uint32_t > childIds
IDs of child nodes.
uint32_t id
Unique node ID within tree.
BTConditionType conditionType
Condition type (enum)
uint32_t decoratorChildId
BTActionType actionType
Action type.
BTNodeType type
Node type.
bool ConnectNodes(uint32_t parentId, uint32_t childId)
Identity component for entity identification.
std::string name
Entity name identifier.
RGBA color value (0-255 range)
Layout information for a single behavior tree node.
Cached debug information for a single entity.
Single entry in the execution log.
float timeAgo
Time since entry (seconds)