Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTreeEditorPlugin.cpp
Go to the documentation of this file.
1/*
2 * Olympe Blueprint Editor - Behavior Tree Plugin Implementation
3 */
4
6#include "SubgraphMigrator.h"
7#include "../../Source/third_party/imgui/imgui.h"
8#include <chrono>
9#include <sstream>
10#include <iomanip>
11
13
14namespace Olympe
15{
19
23
25 {
26 auto now = std::chrono::system_clock::now();
27 auto time = std::chrono::system_clock::to_time_t(now);
28 std::stringstream ss;
29
30 // Use localtime_s for MSVC, localtime_r for other platforms
31 #ifdef _MSC_VER
32 std::tm timeinfo;
34 ss << std::put_time(&timeinfo, "%Y-%m-%dT%H:%M:%S");
35 #else
36 ss << std::put_time(std::localtime(&time), "%Y-%m-%dT%H:%M:%S");
37 #endif
38
39 return ss.str();
40 }
41
43 {
44 json bt;
45 bt["schema_version"] = 2;
46 bt["blueprintType"] = "BehaviorTree";
47 bt["name"] = name;
48 bt["description"] = "";
49 bt["metadata"]["author"] = "Atlasbruce";
50 bt["metadata"]["created"] = GetCurrentTimestamp();
51 bt["metadata"]["lastModified"] = GetCurrentTimestamp();
52 bt["metadata"]["tags"] = json::array();
53 bt["editorState"]["zoom"] = 1.0;
54 bt["editorState"]["scrollOffset"] = nlohmann::json::object();
55 bt["editorState"]["scrollOffset"]["x"] = 0;
56 bt["editorState"]["scrollOffset"]["y"] = 0;
57
58 // Root node by default
59 bt["data"]["rootNodeId"] = 1;
61 rootNode["id"] = 1;
62 rootNode["name"] = "Root Selector";
63 rootNode["type"] = "Selector";
64 nlohmann::json position = nlohmann::json::object();
65 position["x"] = 400;
66 position["y"] = 300;
67 rootNode["position"] = position;
68 rootNode["children"] = json::array();
69 rootNode["parameters"] = json::object();
70 bt["data"]["nodes"] = json::array();
71 bt["data"]["nodes"].push_back(rootNode);
72
73 return bt;
74 }
75
77 {
78 // V2 format check
79 if (blueprint.contains("blueprintType") &&
80 blueprint["blueprintType"].get<std::string>() == "BehaviorTree")
81 {
82 return true;
83 }
84
85 // V1 format heuristic
86 return blueprint.contains("rootNodeId") &&
87 blueprint.contains("nodes") &&
88 !blueprint.contains("states"); // Not an HFSM
89 }
90
91 std::vector<ValidationError> BehaviorTreeEditorPlugin::Validate(const json& blueprint)
92 {
93 std::vector<ValidationError> errors;
94
95 // Check if data section exists
96 if (!blueprint.contains("data"))
97 {
98 errors.push_back(ValidationError(-1, "", "Missing 'data' section", ErrorSeverity::Error));
99 return errors;
100 }
101
102 const json& data = blueprint["data"];
103
104 // Check root node exists
105 int rootId = data.contains("rootNodeId") ? data["rootNodeId"].get<int>() : -1;
106 if (rootId == -1)
107 {
108 errors.push_back(ValidationError(-1, "", "Missing rootNodeId", ErrorSeverity::Error));
109 return errors;
110 }
111
112 bool rootFound = false;
113
114 if (data.contains("nodes") && data["nodes"].is_array())
115 {
116 for (size_t i = 0; i < data["nodes"].size(); ++i)
117 {
118 const nlohmann::json& nodeObj = data["nodes"][i];
119 int nodeId = nodeObj.contains("id") && nodeObj["id"].is_number() ? nodeObj["id"].get<int>() : -1;
120 if (nodeId == rootId)
121 {
122 rootFound = true;
123 break;
124 }
125 }
126 }
127
128 if (!rootFound)
129 {
130 errors.push_back(ValidationError(-1, "", "Root node not found in nodes list", ErrorSeverity::Error));
131 }
132
133 // Verify all children exist
134 if (data.contains("nodes") && data["nodes"].is_array())
135 {
136 for (size_t i = 0; i < data["nodes"].size(); ++i)
137 {
138 const nlohmann::json& node = data["nodes"][i];
139 if (node.contains("children") && node["children"].is_array())
140 {
141 for (size_t j = 0; j < node["children"].size(); ++j)
142 {
143 const nlohmann::json& childIdJson = node["children"][j];
144 int cid = childIdJson.is_number() ? childIdJson.get<int>() : -1;
145 bool found = false;
146
147 for (size_t k = 0; k < data["nodes"].size(); ++k)
148 {
149 const nlohmann::json& n = data["nodes"][k];
150 if (n.is_object() && n.contains("id") && n["id"].is_number() && n["id"].get<int>() == cid)
151 {
152 found = true;
153 break;
154 }
155 }
156
157 if (!found)
158 {
159 int nodeId = node.contains("id") && node["id"].is_number() ? node["id"].get<int>() : -1;
160 std::string nodeIdStr = std::to_string(nodeId);
161 errors.push_back(ValidationError(-1,
162 nodeIdStr,
163 "Child node " + std::to_string(cid) + " not found",
165 ));
166 }
167 }
168 }
169 }
170 }
171
172 // Phase 8: validate subgraph references and circular dependencies.
173 {
174 std::string subgraphError;
176 {
177 errors.push_back(ValidationError(-1, "", subgraphError, ErrorSeverity::Error));
178 }
179 }
180
181 return errors;
182 }
183
185 {
186 if (!blueprintData.contains("data"))
187 {
188 ImGui::Text("Invalid blueprint: missing 'data' section");
189 return;
190 }
191
192 RenderBehaviorTreeGraph(blueprintData["data"], ctx);
193 }
194
196 {
197 ImGui::Text("Type: Behavior Tree");
198
199 if (blueprintData.contains("data") && blueprintData["data"].contains("nodes"))
200 {
201 ImGui::Text("Nodes: %d", (int)blueprintData["data"]["nodes"].size());
202 }
203 else
204 {
205 ImGui::Text("Nodes: 0");
206 }
207
208 ImGui::Separator();
209
210 if (ImGui::Button("Validate Tree", ImVec2(-1, 0)))
211 {
212 // Validation logic would go here
213 ImGui::OpenPopup("Validation Results");
214 }
215 }
216
218 {
219 if (ImGui::Button("Add Node"))
220 {
221 ImGui::OpenPopup("AddNodePopup");
222 }
223
224 ImGui::SameLine();
225
226 if (ImGui::Button("Validate"))
227 {
228 // Trigger validation
229 }
230 }
231
233 {
234 // This would integrate with the existing NodeGraphPanel
235 // For now, just show a placeholder
236 ImGui::Text("Behavior Tree Graph");
237 ImGui::Text("Root Node ID: %d", data.value("rootNodeId", -1));
238
239 if (data.contains("nodes") && data["nodes"].is_array())
240 {
241 ImGui::Separator();
242 ImGui::Text("Nodes:");
243
244 for (size_t i = 0; i < data["nodes"].size(); ++i)
245 {
246 const nlohmann::json& node = data["nodes"][i];
247 std::string nodeName = node.is_object() && node.contains("name") ? node["name"].get<std::string>() : "Unnamed";
248 std::string nodeType = node.is_object() && node.contains("type") ? node["type"].get<std::string>() : "Unknown";
249 int nodeId = node.value("id", -1);
250
251 ImGui::BulletText("[%d] %s (%s)", nodeId, nodeName.c_str(), nodeType.c_str());
252 }
253 }
254 }
255}
nlohmann::json json
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Phase 8 — Migrates legacy blueprint data to the flat-dictionary subgraph format.
void RenderToolbar(nlohmann::json &blueprintData) override
void RenderProperties(const nlohmann::json &blueprintData) override
nlohmann::json CreateNew(const std::string &name) override
std::vector< ValidationError > Validate(const nlohmann::json &blueprint) override
void RenderEditor(nlohmann::json &blueprintData, EditorContext_st &ctx) override
bool CanHandle(const nlohmann::json &blueprint) const override
void RenderBehaviorTreeGraph(nlohmann::json &data, EditorContext_st &ctx)
static bool ValidateSubgraphReferences(const nlohmann::json &blueprint, std::string &outError)
Validates that every subgraphUUID referenced by SubGraph nodes actually exists in data....
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
nlohmann::json json