Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTree.cpp
Go to the documentation of this file.
1/*
2Olympe Engine V2 - 2025
3Nicolas Chereau
4nchereau@gmail.com
5
6This file is part of Olympe Engine V2.
7
8Behavior Tree implementation: JSON loading and built-in node execution.
9
10*/
11
12#include "BehaviorTree.h"
14#include "../ECS_Components_AI.h"
15#include "../ECS_Components.h"
16#include "../World.h"
17#include "../system/system_utils.h"
18#include "../system/EventQueue.h"
19#include "../json_helper.h"
20#include "../CollisionMap.h"
21#include "../GameEngine.h"
22#include <cmath>
23#include <functional>
24#include <set>
25#include <fstream>
26
28
29// --- BehaviorTreeManager Implementation ---
30
31bool BehaviorTreeManager::LoadTreeFromFile(const std::string& filepath, uint32_t treeId)
32{
33 std::cout << "\n[BehaviorTreeManager] ========================================" << std::endl;
34 std::cout << "[BehaviorTreeManager] Loading: " << filepath << std::endl;
35
36 try
37 {
38 // 1. Load JSON file
39 std::cout << "[BehaviorTreeManager] Step 1: Loading JSON file..." << std::endl;
40 json j;
41 if (!JsonHelper::LoadJsonFromFile(filepath, j))
42 {
43 std::cerr << "[BehaviorTreeManager] ERROR: Failed to load file: " << filepath << std::endl;
44 std::cout << "[BehaviorTreeManager] ========================================+n" << std::endl;
45 return false;
46 }
47 std::cout << "[BehaviorTreeManager] JSON loaded successfully" << std::endl;
48
49 // 2. Detect version
50 std::cout << "[BehaviorTreeManager] Step 2: Detecting format version..." << std::endl;
51 bool isV2 = j.contains("schema_version") && j["schema_version"].get<int>() == 2;
52 std::cout << "[BehaviorTreeManager] Version: " << (isV2 ? "v2" : "v1") << std::endl;
53
54 // 3. Extract tree metadata and data section
55 std::cout << "[BehaviorTreeManager] Step 3: Extracting tree metadata..." << std::endl;
57 tree.id = treeId;
58
59 const json* dataSection = &j;
60
61 if (isV2)
62 {
63 tree.name = JsonHelper::GetString(j, "name", "Unnamed Tree");
64
65 if (!j.contains("data"))
66 {
67 std::cerr << "[BehaviorTreeManager] ERROR: v2 format but no 'data' section" << std::endl;
68 std::cout << "[BehaviorTreeManager] ========================================+n" << std::endl;
69 return false;
70 }
71
72 dataSection = &j["data"];
73 std::cout << "[BehaviorTreeManager] Extracted 'data' section from v2 format" << std::endl;
74 }
75 else
76 {
77 tree.name = JsonHelper::GetString(j, "name", "Unnamed Tree");
78 std::cout << "[BehaviorTreeManager] Using root as data section (v1 format)" << std::endl;
79 }
80
81 tree.rootNodeId = JsonHelper::GetUInt(*dataSection, "rootNodeId", 0);
82 std::cout << "[BehaviorTreeManager] Tree name: " << tree.name << std::endl;
83 std::cout << "[BehaviorTreeManager] Root node ID: " << tree.rootNodeId << std::endl;
84
85 // 4. Parse nodes
86 std::cout << "[BehaviorTreeManager] Step 4: Parsing nodes..." << std::endl;
87 if (!JsonHelper::IsArray(*dataSection, "nodes"))
88 {
89 std::cerr << "[BehaviorTreeManager] ERROR: No 'nodes' array in data section" << std::endl;
90 std::cerr << "[BehaviorTreeManager] This may be an empty or invalid tree" << std::endl;
91
92 // For debug: display JSON structure
93 std::cout << "[BehaviorTreeManager] JSON structure:" << std::endl;
94 std::cout << j.dump(2) << std::endl;
95
96 std::cout << "[BehaviorTreeManager] ========================================+n" << std::endl;
97 return false;
98 }
99
100 // Count nodes first
101 int nodeCount = 0;
102 JsonHelper::ForEachInArray(*dataSection, "nodes", [&nodeCount](const json& nodeJson, size_t i) { nodeCount++; });
103 std::cout << "[BehaviorTreeManager] Found " << nodeCount << " nodes to parse" << std::endl;
104
105 JsonHelper::ForEachInArray(*dataSection, "nodes", [&tree, isV2](const json& nodeJson, size_t i)
106 {
107 BTNode node;
109 node.name = JsonHelper::GetString(nodeJson, "name", "");
110
111 // Phase 38: Load editor positions for execution order sorting
112 if (nodeJson.contains("position") && nodeJson["position"].is_object())
113 {
114 node.editorPosX = JsonHelper::GetFloat(nodeJson["position"], "x", 0.0f);
115 node.editorPosY = JsonHelper::GetFloat(nodeJson["position"], "y", 0.0f);
116 }
117 else
118 {
119 node.editorPosX = 0.0f;
120 node.editorPosY = 0.0f;
121 }
122
123 std::string typeStr = JsonHelper::GetString(nodeJson, "type", "Action");
124 if (typeStr == "Selector") node.type = BTNodeType::Selector;
125 else if (typeStr == "Sequence") node.type = BTNodeType::Sequence;
126 else if (typeStr == "Condition") node.type = BTNodeType::Condition;
127 else if (typeStr == "Action") node.type = BTNodeType::Action;
128 else if (typeStr == "Inverter") node.type = BTNodeType::Inverter;
129 else if (typeStr == "Repeater") node.type = BTNodeType::Repeater;
130 else if (typeStr == "Root") node.type = BTNodeType::Root;
131 else if (typeStr == "OnEvent") node.type = BTNodeType::OnEvent;
132 else if (typeStr == "SubGraph") node.type = BTNodeType::SubGraph;
133
134 // Parse child IDs for composite nodes
135 if (JsonHelper::IsArray(nodeJson, "children"))
136 {
137 JsonHelper::ForEachInArray(nodeJson, "children", [&node](const json& childId, size_t j)
138 {
139 node.childIds.push_back(childId.get<uint32_t>());
140 });
141 }
142
143 // Parse condition type
144 if (node.type == BTNodeType::Condition && nodeJson.contains("conditionType"))
145 {
146 std::string condStr = JsonHelper::GetString(nodeJson, "conditionType", "");
147 node.conditionTypeString = condStr; // Store string for flexible conditions
148
149 if (condStr == "TargetVisible" || condStr == "HasTarget")
150 node.conditionType = BTConditionType::TargetVisible;
151 else if (condStr == "TargetInRange" || condStr == "IsTargetInAttackRange")
152 node.conditionType = BTConditionType::TargetInRange;
153 else if (condStr == "HealthBelow")
154 node.conditionType = BTConditionType::HealthBelow;
155 else if (condStr == "HasMoveGoal")
156 node.conditionType = BTConditionType::HasMoveGoal;
157 else if (condStr == "CanAttack")
158 node.conditionType = BTConditionType::CanAttack;
159 else if (condStr == "HeardNoise")
160 node.conditionType = BTConditionType::HeardNoise;
161 // NEW: Wander behavior conditions
162 else if (condStr == "IsWaitTimerExpired")
164 else if (condStr == "HasNavigableDestination")
166 else if (condStr == "HasValidPath")
167 node.conditionType = BTConditionType::HasValidPath;
168 else if (condStr == "HasReachedDestination")
170 // NEW: CheckBlackboardValue is handled via string type, not enum
171
172 // Handle v2 format parameters (nested in "parameters" object)
173 if (isV2 && nodeJson.contains("parameters") && nodeJson["parameters"].is_object())
174 {
175 const json& params = nodeJson["parameters"];
176 node.conditionParam = JsonHelper::GetFloat(params, "param", 0.0f);
177
178 // Parse flexible parameters for conditions like CheckBlackboardValue
179 for (auto it = params.begin(); it != params.end(); ++it)
180 {
181 if (it.value().is_string())
182 node.stringParams[it.key()] = it.value().get<std::string>();
183 else if (it.value().is_number_integer())
184 node.intParams[it.key()] = it.value().get<int>();
185 else if (it.value().is_number_float())
186 node.floatParams[it.key()] = it.value().get<float>();
187 }
188 }
189 else
190 {
191 // v1 format (flat)
192 node.conditionParam = JsonHelper::GetFloat(nodeJson, "param", 0.0f);
193 }
194 }
195
196 // Parse action type
197 if (node.type == BTNodeType::Action && nodeJson.contains("actionType"))
198 {
199 std::string actStr = JsonHelper::GetString(nodeJson, "actionType", "");
200 if (actStr == "SetMoveGoalToLastKnownTargetPos")
202 else if (actStr == "SetMoveGoalToTarget")
204 else if (actStr == "SetMoveGoalToPatrolPoint")
206 else if (actStr == "MoveToGoal" || actStr == "MoveTo")
207 node.actionType = BTActionType::MoveToGoal;
208 else if (actStr == "AttackIfClose" || actStr == "AttackMelee")
210 else if (actStr == "PatrolPickNextPoint")
212 else if (actStr == "ClearTarget")
213 node.actionType = BTActionType::ClearTarget;
214 else if (actStr == "Idle")
215 node.actionType = BTActionType::Idle;
216 // NEW: Wander behavior actions
217 else if (actStr == "WaitRandomTime")
219 else if (actStr == "ChooseRandomNavigablePoint")
221 else if (actStr == "RequestPathfinding")
223 else if (actStr == "FollowPath")
224 node.actionType = BTActionType::FollowPath;
225
226 // Handle v2 format parameters (nested in "parameters" object)
227 if (isV2 && nodeJson.contains("parameters") && nodeJson["parameters"].is_object())
228 {
229 const json& params = nodeJson["parameters"];
230 node.actionParam1 = JsonHelper::GetFloat(params, "param1", 0.0f);
231 node.actionParam2 = JsonHelper::GetFloat(params, "param2", 0.0f);
232 }
233 else
234 {
235 // v1 format (flat)
236 node.actionParam1 = JsonHelper::GetFloat(nodeJson, "param1", 0.0f);
237 node.actionParam2 = JsonHelper::GetFloat(nodeJson, "param2", 0.0f);
238 }
239 }
240
241 // Phase 39: Parse SubGraph node
242 if (node.type == BTNodeType::SubGraph || typeStr == "SubGraph")
243 {
245 node.subgraphPath = JsonHelper::GetString(nodeJson, "subgraphPath", "");
246
247 // Parse input bindings (childVar -> parentVar)
248 if (nodeJson.contains("subgraphInputs") && nodeJson["subgraphInputs"].is_object())
249 {
250 const json& inputs = nodeJson["subgraphInputs"];
251 for (auto it = inputs.begin(); it != inputs.end(); ++it)
252 {
253 node.subgraphInputs[it.key()] = it.value().get<std::string>();
254 }
255 }
256
257 // Parse output bindings (childVar -> parentVar)
258 if (nodeJson.contains("subgraphOutputs") && nodeJson["subgraphOutputs"].is_object())
259 {
260 const json& outputs = nodeJson["subgraphOutputs"];
261 for (auto it = outputs.begin(); it != outputs.end(); ++it)
262 {
263 node.subgraphOutputs[it.key()] = it.value().get<std::string>();
264 }
265 }
266
267 std::cout << "[BehaviorTreeManager] SubGraph loaded: " << node.subgraphPath
268 << " with " << node.subgraphInputs.size() << " inputs, "
269 << node.subgraphOutputs.size() << " outputs" << std::endl;
270 }
271
272 // Parse decorator child
273 if (node.type == BTNodeType::Inverter || node.type == BTNodeType::Repeater)
274 {
275 node.decoratorChildId = JsonHelper::GetInt(nodeJson, "decoratorChildId", 0);
276 }
277
278 if (node.type == BTNodeType::Repeater)
279 {
280 node.repeatCount = JsonHelper::GetInt(nodeJson, "repeatCount", 0);
281 }
282
283 tree.nodes.push_back(node);
284
285 std::cout << "[BehaviorTreeManager] Node " << node.id << ": " << node.name
286 << " (" << typeStr << ") children: " << node.childIds.size() << std::endl;
287 });
288
289 std::cout << "[BehaviorTreeManager] Parsed " << tree.nodes.size() << " nodes successfully" << std::endl;
290
291 // 5. Validate tree structure
292 std::cout << "[BehaviorTreeManager] Step 5: Validating tree structure..." << std::endl;
293 std::string validationError;
295 if (!valid)
296 {
297 std::cerr << "[BehaviorTreeManager] WARNING: Tree validation failed: " << validationError << std::endl;
298 // Don't fail loading - allow hot-reload to fix issues
299 }
300 else
301 {
302 std::cout << "[BehaviorTreeManager] Tree validation: OK" << std::endl;
303 }
304
305 // Phase 38b: Step 6: Ensure Root node exists (auto-create if missing)
306 std::cout << "[BehaviorTreeManager] Step 6: Ensuring Root node exists..." << std::endl;
307 tree.EnsureRootNodeExists();
308
309 // 7. Store the tree
310 std::cout << "[BehaviorTreeManager] Step 7: Registering tree..." << std::endl;
311 m_trees.push_back(tree);
312
313 // Register the path -> ID mapping
314 m_pathToIdMap[filepath] = treeId;
315
316 std::cout << "[BehaviorTreeManager] SUCCESS: Loaded '" << tree.name << "' (ID=" << treeId << ") with "
317 << tree.nodes.size() << " nodes" << std::endl;
318 std::cout << "[BehaviorTreeManager] Registered path mapping: " << filepath << " -> ID " << treeId << "\n";
319 std::cout << "[BehaviorTreeManager] ========================================+n" << std::endl;
320
321 return true;
322 }
323 catch (const std::exception& e)
324 {
325 std::cerr << "\n[BehaviorTreeManager] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
326 std::cerr << "[BehaviorTreeManager] EXCEPTION: " << e.what() << std::endl;
327 std::cerr << "[BehaviorTreeManager] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
328 std::cout << "[BehaviorTreeManager] ========================================+n" << std::endl;
329 return false;
330 }
331}
332
334{
335 for (const auto& tree : m_trees)
336 {
337 if (tree.id == treeId)
338 return &tree;
339 }
340 return nullptr;
341}
342
344{
345 m_trees.clear();
346 m_pathToIdMap.clear();
347}
348
350{
351 // Find the tree
352 for (auto& tree : m_trees)
353 {
354 if (tree.id == treeId)
355 {
356 // Get the original filepath (we need to store it)
357 // For now, reconstruct it from tree name
358 std::string filepath = "Blueprints/AI/" + tree.name + ".json";
359
360 // Remove old tree
361 m_trees.erase(std::remove_if(m_trees.begin(), m_trees.end(),
362 [treeId](const BehaviorTreeAsset& t) { return t.id == treeId; }),
363 m_trees.end());
364
365 // Load new version
366 bool success = LoadTreeFromFile(filepath, treeId);
367 if (success)
368 {
369 SYSTEM_LOG << "BehaviorTreeManager: Hot-reloaded tree ID=" << treeId << "\n";
370 }
371 return success;
372 }
373 }
374
375 SYSTEM_LOG << "BehaviorTreeManager: Cannot reload tree ID=" << treeId << " (not found)\n";
376 return false;
377}
378
379bool BehaviorTreeManager::ValidateTree(const BehaviorTreeAsset& tree, std::string& errorMessage) const
380{
381 errorMessage.clear();
382
383 // Check if tree has nodes
384 if (tree.nodes.empty())
385 {
386 errorMessage = "Tree has no nodes";
387 return false;
388 }
389
390 // Check if root node exists
391 const BTNode* root = tree.GetNode(tree.rootNodeId);
392 if (!root)
393 {
394 errorMessage = "Root node ID " + std::to_string(tree.rootNodeId) + " not found";
395 return false;
396 }
397
398 // Validate each node
399 for (const auto& node : tree.nodes)
400 {
401 // Check composite nodes have children
402 if (node.type == BTNodeType::Selector || node.type == BTNodeType::Sequence)
403 {
404 if (node.childIds.empty())
405 {
406 errorMessage = "Composite node '" + node.name + "' (ID=" + std::to_string(node.id) + ") has no children";
407 return false;
408 }
409
410 // Validate all children exist
411 for (uint32_t childId : node.childIds)
412 {
413 if (!tree.GetNode(childId))
414 {
415 errorMessage = "Node '" + node.name + "' references missing child ID " + std::to_string(childId);
416 return false;
417 }
418 }
419 }
420
421 // Check decorator nodes have a child
422 if (node.type == BTNodeType::Inverter || node.type == BTNodeType::Repeater)
423 {
424 if (!tree.GetNode(node.decoratorChildId))
425 {
426 errorMessage = "Decorator node '" + node.name + "' references missing child ID " + std::to_string(node.decoratorChildId);
427 return false;
428 }
429 }
430
431 // Check for duplicate node IDs
432 int count = 0;
433 for (const auto& other : tree.nodes)
434 {
435 if (other.id == node.id)
436 count++;
437 }
438 if (count > 1)
439 {
440 errorMessage = "Duplicate node ID " + std::to_string(node.id);
441 return false;
442 }
443 }
444
445 return true;
446}
447
448// --- Behavior Tree Execution ---
449
451{
452 switch (node.type)
453 {
455 {
456 // OR node: succeeds if any child succeeds
457 for (uint32_t childId : node.childIds)
458 {
459 const BTNode* child = tree.GetNode(childId);
460 if (!child) continue;
461
462 BTStatus status = ExecuteBTNode(*child, entity, blackboard, tree);
463 if (status == BTStatus::Success)
464 return BTStatus::Success;
465 if (status == BTStatus::Running)
466 return BTStatus::Running;
467 }
468 return BTStatus::Failure;
469 }
470
472 {
473 // AND node: succeeds if all children succeed
474 for (uint32_t childId : node.childIds)
475 {
476 const BTNode* child = tree.GetNode(childId);
477 if (!child) continue;
478
479 BTStatus status = ExecuteBTNode(*child, entity, blackboard, tree);
480 if (status == BTStatus::Failure)
481 return BTStatus::Failure;
482 if (status == BTStatus::Running)
483 return BTStatus::Running;
484 }
485 return BTStatus::Success;
486 }
487
489 {
490 // Check if this is a string-based condition (like CheckBlackboardValue)
491 if (!node.conditionTypeString.empty() && node.conditionTypeString == "CheckBlackboardValue")
492 {
493 // Execute CheckBlackboardValue condition
494 std::string key = node.GetParameterString("key");
495 std::string op = node.GetParameterString("operator");
496 int expectedValue = node.GetParameterInt("value");
497
498 int actualValue = 0;
499
500 // Get value from blackboard
501 if (key == "AIMode")
502 {
503 actualValue = blackboard.AIMode;
504 }
505 else
506 {
507 // Future: support other blackboard integer fields
508 return BTStatus::Failure; // Key not found
509 }
510
511 // Perform comparison
512 if (op == "Equals" || op == "equals" || op == "==")
514 else if (op == "NotEquals" || op == "notequals" || op == "!=")
516 else if (op == "GreaterThan" || op == "greaterthan" || op == ">")
518 else if (op == "LessThan" || op == "lessthan" || op == "<")
520 else if (op == "GreaterOrEqual" || op == "greaterorequal" || op == ">=")
522 else if (op == "LessOrEqual" || op == "lessorequal" || op == "<=")
524
525 return BTStatus::Failure;
526 }
527 else
528 {
529 // Execute enum-based condition (legacy)
530 return ExecuteBTCondition(node.conditionType, node.conditionParam, entity, blackboard);
531 }
532 }
533
535 {
536 return ExecuteBTAction(node.actionType, node.actionParam1, node.actionParam2, entity, blackboard);
537 }
538
540 {
541 const BTNode* child = tree.GetNode(node.decoratorChildId);
542 if (!child) return BTStatus::Failure;
543
544 BTStatus status = ExecuteBTNode(*child, entity, blackboard, tree);
545 if (status == BTStatus::Success)
546 return BTStatus::Failure;
547 if (status == BTStatus::Failure)
548 return BTStatus::Success;
549 return status;
550 }
551
553 {
554 // Simplified repeater: just execute once per tick
555 const BTNode* child = tree.GetNode(node.decoratorChildId);
556 if (!child) return BTStatus::Failure;
557
558 return ExecuteBTNode(*child, entity, blackboard, tree);
559 }
560 }
561
562 return BTStatus::Failure;
563}
564
566{
567 switch (condType)
568 {
570 return blackboard.targetVisible ? BTStatus::Success : BTStatus::Failure;
571
573 if (!blackboard.hasTarget) return BTStatus::Failure;
574 return (blackboard.distanceToTarget <= param) ? BTStatus::Success : BTStatus::Failure;
575
578 {
580 float healthPercent = static_cast<float>(health.currentHealth) / static_cast<float>(health.maxHealth);
582 }
583 return BTStatus::Failure;
584
586 return blackboard.hasMoveGoal ? BTStatus::Success : BTStatus::Failure;
587
590
592 return blackboard.heardNoise ? BTStatus::Success : BTStatus::Failure;
593
594 // NEW: Wander behavior conditions
596 return (blackboard.wanderWaitTimer >= blackboard.wanderTargetWaitTime) ? BTStatus::Success : BTStatus::Failure;
597
599 return blackboard.hasWanderDestination ? BTStatus::Success : BTStatus::Failure;
600
602 // Check if NavigationAgent has a valid path
604 {
606 return (!navAgent.currentPath.empty()) ? BTStatus::Success : BTStatus::Failure;
607 }
608 return BTStatus::Failure;
609
611 if (!blackboard.hasWanderDestination) return BTStatus::Failure;
612
614 {
616 Vector vDest = pos.position;
617 vDest -= blackboard.wanderDestination;
618 float dist = vDest.Magnitude();
619
620 // Use arrival threshold from MoveIntent if available, otherwise use default
621 float threshold = 5.0f;
623 {
626 }
627
629 }
630 return BTStatus::Failure;
631 }
632
633 return BTStatus::Failure;
634}
635
636BTStatus ExecuteBTAction(BTActionType actionType, float param1, float param2, EntityID entity, AIBlackboard_data& blackboard)
637{
638 switch (actionType)
639 {
641 blackboard.moveGoal = blackboard.lastKnownTargetPosition;
642 blackboard.hasMoveGoal = true;
643 return BTStatus::Success;
644
646 if (blackboard.hasTarget && blackboard.targetEntity != INVALID_ENTITY_ID)
647 {
649 {
651 blackboard.moveGoal = targetPos.position;
652 blackboard.hasMoveGoal = true;
653 return BTStatus::Success;
654 }
655 }
656 return BTStatus::Failure;
657
659 if (blackboard.patrolPointCount > 0)
660 {
661 int index = static_cast<int>(param1);
662 if (index < 0 || index >= blackboard.patrolPointCount)
663 index = blackboard.currentPatrolPoint;
664
665 blackboard.moveGoal = blackboard.patrolPoints[index];
666 blackboard.hasMoveGoal = true;
667 return BTStatus::Success;
668 }
669 return BTStatus::Failure;
670
672 if (!blackboard.hasMoveGoal) return BTStatus::Failure;
673
674 // Set MoveIntent if component exists
676 {
679 intent.desiredSpeed = (param1 > 0.0f) ? param1 : 1.0f;
680 intent.hasIntent = true;
681
682 // Check if we've arrived
684 {
686 float dist = (pos.position - blackboard.moveGoal).Magnitude();
687 if (dist < intent.arrivalThreshold)
688 {
689 blackboard.hasMoveGoal = false;
690 intent.hasIntent = false;
691 return BTStatus::Success;
692 }
693 }
694
695 return BTStatus::Running;
696 }
697 return BTStatus::Failure;
698
700 {
701 float range = (param1 > 0.0f) ? param1 : 50.0f;
702 if (blackboard.hasTarget && blackboard.distanceToTarget <= range && blackboard.canAttack)
703 {
704 // Set AttackIntent if component exists
706 {
708 intent.targetEntity = blackboard.targetEntity;
709 intent.targetPosition = blackboard.lastKnownTargetPosition;
710 intent.range = range;
711 intent.damage = (param2 > 0.0f) ? param2 : 10.0f;
712 intent.hasIntent = true;
713
714 blackboard.canAttack = false;
715 return BTStatus::Success;
716 }
717 }
718 return BTStatus::Failure;
719 }
720
722 if (blackboard.patrolPointCount > 0)
723 {
724 blackboard.currentPatrolPoint = (blackboard.currentPatrolPoint + 1) % blackboard.patrolPointCount;
725 blackboard.moveGoal = blackboard.patrolPoints[blackboard.currentPatrolPoint];
726 blackboard.hasMoveGoal = true;
727 return BTStatus::Success;
728 }
729 return BTStatus::Failure;
730
732 blackboard.hasTarget = false;
733 blackboard.targetEntity = INVALID_ENTITY_ID;
734 blackboard.targetVisible = false;
735 return BTStatus::Success;
736
738 // Do nothing
739 return BTStatus::Success;
740
741 // NEW: Wander behavior actions
743 {
744 // If timer not initialized, create a random time
745 if (blackboard.wanderTargetWaitTime == 0.0f)
746 {
747 float minWait = (param1 > 0.0f) ? param1 : 2.0f;
748 float maxWait = (param2 > 0.0f) ? param2 : 6.0f;
749
750 // Random generation between min and max
751 float randomFactor = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
752 blackboard.wanderTargetWaitTime = minWait + randomFactor * (maxWait - minWait);
753 blackboard.wanderWaitTimer = 0.0f;
754 }
755
756 // Increment timer with engine deltaTime
757 blackboard.wanderWaitTimer += GameEngine::fDt;
758
759 // Check if timer expired
760 if (blackboard.wanderWaitTimer >= blackboard.wanderTargetWaitTime)
761 {
762 // Reset for next cycle
763 blackboard.wanderTargetWaitTime = 0.0f;
764 blackboard.wanderWaitTimer = 0.0f;
765 return BTStatus::Success;
766 }
767
768 return BTStatus::Running;
769 }
770
772 {
773 // Get current position
774 if (!World::Get().HasComponent<Position_data>(entity))
775 return BTStatus::Failure;
776
778
779 // Parameters
780 float searchRadius = (param1 > 0.0f) ? param1 : blackboard.wanderSearchRadius;
781 int maxAttempts = (param2 > 0.0f) ? static_cast<int>(param2) : blackboard.wanderMaxSearchAttempts;
782
783 // Search for a random navigable point
784 float destX, destY;
786 pos.position.x, pos.position.y, searchRadius, maxAttempts, destX, destY
787 );
788
789 if (found)
790 {
791 blackboard.wanderDestination = Vector(destX, destY);
792 blackboard.hasWanderDestination = true;
793 blackboard.moveGoal = blackboard.wanderDestination;
794 blackboard.hasMoveGoal = true;
795 return BTStatus::Success;
796 }
797
798 return BTStatus::Failure;
799 }
800
802 {
803 if (!blackboard.hasMoveGoal) return BTStatus::Failure;
804
805 // Go through MoveIntent_data with pathfinding enabled
807 {
810 intent.desiredSpeed = 1.0f;
811 intent.hasIntent = true;
812 intent.usePathfinding = true; // Enable pathfinding
813 intent.avoidObstacles = true;
814
815 return BTStatus::Success;
816 }
817
818 return BTStatus::Failure;
819 }
820
822 {
823 if (!blackboard.hasWanderDestination) return BTStatus::Failure;
824
825 // Check if we have an active MoveIntent
827 {
829
830 // Maintain the active intent
831 if (!intent.hasIntent)
832 {
833 intent.targetPosition = blackboard.wanderDestination;
834 intent.desiredSpeed = 1.0f;
835 intent.hasIntent = true;
836 intent.usePathfinding = true;
837 }
838
839 // Check if we have arrived
841 {
843 Vector vDest = pos.position;
844 vDest -= blackboard.wanderDestination;
845 float dist = vDest.Magnitude();
846
847 if (dist < intent.arrivalThreshold)
848 {
849 // Arrived at destination
850 blackboard.hasWanderDestination = false;
851 blackboard.hasMoveGoal = false;
852 intent.hasIntent = false;
853 return BTStatus::Success;
854 }
855 }
856
857 return BTStatus::Running;
858 }
859
860 return BTStatus::Failure;
861 }
862
864 {
865 // Phase 38b: Emit event to EventQueue for OnEvent node processing
866 // param1 encoded as EventType enum value
867
868 EventType eventType = static_cast<EventType>(static_cast<int>(param1));
869
870 Message msg;
871 msg.msg_type = eventType;
872 msg.domain = EventDomain::Gameplay;
873 msg.targetUid = entity;
874 msg.param1 = 0;
875 msg.param2 = 0;
876 msg.state = 0;
877
879
880 return BTStatus::Success;
881 }
882 }
883
884 return BTStatus::Failure;
885 }
886
887// --- Path-to-ID Registry Methods ---
888
889uint32_t BehaviorTreeManager::GetTreeIdFromPath(const std::string& treePath) const
890{
891 auto it = m_pathToIdMap.find(treePath);
892 if (it != m_pathToIdMap.end())
893 return it->second;
894
895 // Fallback: generate ID from path if not in registry
896 // This allows forward compatibility with paths that aren't loaded yet
898}
899
900bool BehaviorTreeManager::IsTreeLoadedByPath(const std::string& treePath) const
901{
902 return m_pathToIdMap.find(treePath) != m_pathToIdMap.end();
903}
904
905const BehaviorTreeAsset* BehaviorTreeManager::GetTreeByPath(const std::string& treePath) const
906{
907 uint32_t treeId = GetTreeIdFromPath(treePath);
908 return GetTree(treeId);
909}
910
912{
913 // Strategy 1: Direct ID lookup
914 for (const auto& tree : m_trees)
915 {
916 if (tree.id == treeId)
917 return &tree;
918 }
919
920 // No additional strategies currently implemented
921 // Future enhancement: Could add tree structure matching for corrupted prefabs
922
923 return nullptr;
924}
925
927{
928 // Check path-to-ID registry
929 for (const auto& entry : m_pathToIdMap)
930 {
931 if (entry.second == treeId)
932 return entry.first;
933 }
934
935 // Check if a tree with this ID exists (might be loaded with different path)
936 for (const auto& tree : m_trees)
937 {
938 if (tree.id == treeId)
939 return "TreeName:" + tree.name; // Prefix to distinguish from actual file path
940 }
941
942 return "";
943}
944
946{
947 std::cout << "[BehaviorTreeManager] Loaded trees (" << m_trees.size() << "):" << std::endl;
948 for (const auto& tree : m_trees)
949 {
950 std::cout << " - ID=" << tree.id << " Name='" << tree.name << "' Nodes=" << tree.nodes.size() << std::endl;
951 }
952
953 std::cout << "[BehaviorTreeManager] Path-to-ID registry (" << m_pathToIdMap.size() << "):" << std::endl;
954 for (const auto& entry : m_pathToIdMap)
955 {
956 std::cout << " - '" << entry.first << "' -> ID=" << entry.second << std::endl;
957 }
958}
959
960// =============================================================================
961// BehaviorTreeAsset - Validation Methods
962// =============================================================================
963
964std::vector<BTValidationMessage> BehaviorTreeAsset::ValidateTreeFull() const
965{
966 std::vector<BTValidationMessage> messages;
967
968 // Rule 1: Exactly one root node
969 int rootCount = 0;
970 const BTNode* rootNode = nullptr;
971 for (const auto& node : nodes)
972 {
973 if (node.id == rootNodeId)
974 {
975 rootNode = &node;
976 rootCount++;
977 }
978 }
979
980 if (rootCount == 0)
981 {
984 msg.nodeId = 0;
985 msg.message = "No root node found (rootNodeId=" + std::to_string(rootNodeId) + ")";
986 messages.push_back(msg);
987 return messages; // Cannot continue without root
988 }
989 else if (rootCount > 1)
990 {
993 msg.nodeId = 0;
994 msg.message = "Multiple nodes with root ID found";
995 messages.push_back(msg);
996 }
997
998 // Build parent count map
999 std::map<uint32_t, int> parentCounts;
1000 for (const auto& node : nodes)
1001 {
1002 parentCounts[node.id] = 0;
1003 }
1004
1005 // Count parents for each node
1006 for (const auto& node : nodes)
1007 {
1008 // Check childIds for composites
1009 if (node.type == BTNodeType::Selector || node.type == BTNodeType::Sequence)
1010 {
1011 for (uint32_t childId : node.childIds)
1012 {
1013 if (parentCounts.find(childId) != parentCounts.end())
1014 {
1015 parentCounts[childId]++;
1016 }
1017 else
1018 {
1021 msg.nodeId = node.id;
1022 msg.message = "Node references non-existent child ID " + std::to_string(childId);
1023 messages.push_back(msg);
1024 }
1025 }
1026 }
1027
1028 // Check decoratorChildId for decorators
1029 if (node.type == BTNodeType::Inverter || node.type == BTNodeType::Repeater)
1030 {
1031 if (node.decoratorChildId != 0)
1032 {
1033 if (parentCounts.find(node.decoratorChildId) != parentCounts.end())
1034 {
1035 parentCounts[node.decoratorChildId]++;
1036 }
1037 else
1038 {
1041 msg.nodeId = node.id;
1042 msg.message = "Decorator references non-existent child ID " + std::to_string(node.decoratorChildId);
1043 messages.push_back(msg);
1044 }
1045 }
1046 }
1047 }
1048
1049 // Rule 2: No node should have multiple parents
1050 for (const auto& entry : parentCounts)
1051 {
1052 if (entry.second > 1)
1053 {
1056 msg.nodeId = entry.first;
1057 msg.message = "Node has multiple parents (count=" + std::to_string(entry.second) + ")";
1058 messages.push_back(msg);
1059 }
1060 }
1061
1062 // Rule 3: Root should have no parent
1063 if (rootNode && parentCounts[rootNode->id] > 0)
1064 {
1067 msg.nodeId = rootNode->id;
1068 msg.message = "Root node has parent(s)";
1069 messages.push_back(msg);
1070 }
1071
1072 // Rule 4: No orphan nodes (except root)
1073 for (const auto& entry : parentCounts)
1074 {
1075 if (entry.second == 0 && entry.first != rootNodeId)
1076 {
1079 msg.nodeId = entry.first;
1080 msg.message = "Orphan node (no parent, not root)";
1081 messages.push_back(msg);
1082 }
1083 }
1084
1085 // Rule 5: Validate node type-specific constraints
1086 for (const auto& node : nodes)
1087 {
1088 if (node.type == BTNodeType::Inverter || node.type == BTNodeType::Repeater)
1089 {
1090 // Decorators must have exactly 1 child
1091 if (node.decoratorChildId == 0)
1092 {
1095 msg.nodeId = node.id;
1096 msg.message = "Decorator has no child (decoratorChildId=0)";
1097 messages.push_back(msg);
1098 }
1099 }
1100 else if (node.type == BTNodeType::Selector || node.type == BTNodeType::Sequence)
1101 {
1102 // Composites should have at least 1 child (warning if 0)
1103 if (node.childIds.empty())
1104 {
1107 msg.nodeId = node.id;
1108 msg.message = "Composite has no children";
1109 messages.push_back(msg);
1110 }
1111 }
1112 }
1113
1114 // Rule 6: Detect cycles
1115 for (const auto& node : nodes)
1116 {
1117 if (DetectCycle(node.id))
1118 {
1121 msg.nodeId = node.id;
1122 msg.message = "Cycle detected starting from this node";
1123 messages.push_back(msg);
1124 }
1125 }
1126
1127 return messages;
1128}
1129
1130// =============================================================================
1131// BehaviorTreeAsset - Phase 38b: Root Node Auto-Creation
1132// =============================================================================
1133
1135{
1136 // Check if Root node already exists
1137 if (rootNodeId != 0)
1138 {
1140 if (rootNode != nullptr)
1141 {
1142 return; // Root node exists and is valid
1143 }
1144
1145 // Root ID is set but node doesn't exist - invalid, will recreate
1146 std::cerr << "[BehaviorTreeAsset::EnsureRootNodeExists] "
1147 << "Root node ID " << rootNodeId << " not found in nodes list, creating new Root" << std::endl;
1148 }
1149
1150 // Create default Root node if missing
1151 std::cout << "[BehaviorTreeAsset::EnsureRootNodeExists] Creating default Root node" << std::endl;
1152
1155 rootNode.type = BTNodeType::Selector; // Default to Selector as root composite
1156 rootNode.name = "Root";
1157 rootNode.editorPosX = 0.0f;
1158 rootNode.editorPosY = 0.0f;
1159
1160 nodes.push_back(rootNode);
1161 rootNodeId = rootNode.id;
1162
1163 std::cout << "[BehaviorTreeAsset::EnsureRootNodeExists] "
1164 << "Created Root node with ID=" << rootNode.id << ", total nodes=" << nodes.size() << std::endl;
1165}
1166
1167// =============================================================================
1168// BehaviorTreeAsset - Validation Methods (continued)
1169// =============================================================================
1170
1172{
1173 std::set<uint32_t> visited;
1174 std::set<uint32_t> recursionStack;
1175
1176 // DFS helper function
1177 std::function<bool(uint32_t)> dfs = [&](uint32_t nodeId) -> bool
1178 {
1179 if (recursionStack.find(nodeId) != recursionStack.end())
1180 {
1181 return true; // Cycle detected
1182 }
1183
1184 if (visited.find(nodeId) != visited.end())
1185 {
1186 return false; // Already checked this node
1187 }
1188
1189 visited.insert(nodeId);
1190 recursionStack.insert(nodeId);
1191
1192 const BTNode* node = GetNode(nodeId);
1193 if (!node)
1194 {
1195 recursionStack.erase(nodeId);
1196 return false;
1197 }
1198
1199 // Check children in childIds (composites)
1200 for (uint32_t childId : node->childIds)
1201 {
1202 if (dfs(childId))
1203 {
1204 return true;
1205 }
1206 }
1207
1208 // Check decoratorChildId (decorators)
1209 if ((node->type == BTNodeType::Inverter || node->type == BTNodeType::Repeater) &&
1210 node->decoratorChildId != 0)
1211 {
1212 if (dfs(node->decoratorChildId))
1213 {
1214 return true;
1215 }
1216 }
1217
1218 recursionStack.erase(nodeId);
1219 return false;
1220 };
1221
1222 return dfs(startNodeId);
1223}
1224
1225// =============================================================================
1226// BehaviorTreeAsset - Editor CRUD Operations
1227// =============================================================================
1228
1230{
1231 uint32_t maxId = 0;
1232 for (const auto& node : nodes)
1233 {
1234 if (node.id > maxId)
1235 {
1236 maxId = node.id;
1237 }
1238 }
1239 return maxId + 1;
1240}
1241
1242uint32_t BehaviorTreeAsset::AddNode(BTNodeType type, const std::string& name, const Vector& position)
1243{
1245 newNode.type = type;
1247 newNode.name = name;
1248 newNode.editorPosX = position.x;
1249 newNode.editorPosY = position.y;
1250
1251 // Set default parameters based on type
1252 if (type == BTNodeType::Action)
1253 {
1254 newNode.actionType = BTActionType::Idle;
1255 newNode.actionParam1 = 0.0f;
1256 newNode.actionParam2 = 0.0f;
1257 }
1258 else if (type == BTNodeType::Condition)
1259 {
1261 newNode.conditionParam = 0.0f;
1262 }
1263 else if (type == BTNodeType::Repeater)
1264 {
1265 newNode.repeatCount = 1;
1266 newNode.decoratorChildId = 0;
1267 }
1268 else if (type == BTNodeType::Inverter)
1269 {
1270 newNode.decoratorChildId = 0;
1271 }
1272
1273 nodes.push_back(newNode);
1274
1275 std::cout << "[BehaviorTreeAsset] Added node ID=" << newNode.id
1276 << " type=" << static_cast<int>(type) << " name='" << name << "'"
1277 << " pos=(" << position.x << "," << position.y << ")" << std::endl;
1278
1279 return newNode.id;
1280}
1281
1283{
1284 // Find and remove the node
1285 auto it = nodes.begin();
1286 while (it != nodes.end())
1287 {
1288 if (it->id == nodeId)
1289 {
1290 nodes.erase(it);
1291 break;
1292 }
1293 ++it;
1294 }
1295
1296 // Clean up connections to/from this node
1297 for (auto& node : nodes)
1298 {
1299 // Remove from childIds
1300 auto childIt = node.childIds.begin();
1301 while (childIt != node.childIds.end())
1302 {
1303 if (*childIt == nodeId)
1304 {
1305 childIt = node.childIds.erase(childIt);
1306 }
1307 else
1308 {
1309 ++childIt;
1310 }
1311 }
1312
1313 // Remove from decoratorChildId
1314 if (node.decoratorChildId == nodeId)
1315 {
1316 node.decoratorChildId = 0;
1317 }
1318 }
1319
1320 std::cout << "[BehaviorTreeAsset] Removed node ID=" << nodeId << std::endl;
1321 return true;
1322}
1323
1325{
1326 BTNode* parent = GetNode(parentId);
1327 const BTNode* child = GetNode(childId);
1328
1329 if (!parent || !child)
1330 {
1331 std::cerr << "[BehaviorTreeAsset] ConnectNodes failed: invalid node IDs" << std::endl;
1332 return false;
1333 }
1334
1335 // Add connection based on parent type
1336 if (parent->type == BTNodeType::Selector || parent->type == BTNodeType::Sequence)
1337 {
1338 // Check if already connected
1339 for (uint32_t id : parent->childIds)
1340 {
1341 if (id == childId)
1342 {
1343 std::cerr << "[BehaviorTreeAsset] Connection already exists" << std::endl;
1344 return false;
1345 }
1346 }
1347
1348 parent->childIds.push_back(childId);
1349 std::cout << "[BehaviorTreeAsset] Connected composite node " << parentId << " -> " << childId << std::endl;
1350 return true;
1351 }
1352 else if (parent->type == BTNodeType::Inverter || parent->type == BTNodeType::Repeater)
1353 {
1354 if (parent->decoratorChildId != 0)
1355 {
1356 std::cerr << "[BehaviorTreeAsset] Decorator already has a child" << std::endl;
1357 return false;
1358 }
1359
1360 parent->decoratorChildId = childId;
1361 std::cout << "[BehaviorTreeAsset] Connected decorator node " << parentId << " -> " << childId << std::endl;
1362 return true;
1363 }
1364 else
1365 {
1366 std::cerr << "[BehaviorTreeAsset] Cannot connect from leaf node" << std::endl;
1367 return false;
1368 }
1369}
1370
1372{
1373 BTNode* parent = GetNode(parentId);
1374
1375 if (!parent)
1376 {
1377 return false;
1378 }
1379
1380 // Remove from childIds
1381 auto it = parent->childIds.begin();
1382 while (it != parent->childIds.end())
1383 {
1384 if (*it == childId)
1385 {
1386 parent->childIds.erase(it);
1387 std::cout << "[BehaviorTreeAsset] Disconnected " << parentId << " -X-> " << childId << std::endl;
1388 return true;
1389 }
1390 ++it;
1391 }
1392
1393 // Remove from decoratorChildId
1394 if (parent->decoratorChildId == childId)
1395 {
1396 parent->decoratorChildId = 0;
1397 std::cout << "[BehaviorTreeAsset] Disconnected decorator " << parentId << " -X-> " << childId << std::endl;
1398 return true;
1399 }
1400
1401 return false;
1402}
1403
1404// ============================================================================
1405// Phase 39c Step 6: SubGraph Validation Methods
1406// ============================================================================
1407
1408bool BehaviorTreeManager::ValidateSubGraphPath(const std::string& path) const
1409{
1410 // 1. Check if path is empty
1411 if (path.empty())
1412 {
1413 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: Path is empty\n";
1414 return false;
1415 }
1416
1417 // 2. Check if file exists and is readable
1418 std::ifstream fileTest(path.c_str());
1419 if (!fileTest.good())
1420 {
1421 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: File not found or not readable: " << path << "\n";
1422 return false;
1423 }
1424 fileTest.close();
1425
1426 // 3. Check if it's a .bt.json file
1427 std::string filename = path;
1428 size_t lastSlash = filename.find_last_of("/\\");
1429 if (lastSlash != std::string::npos)
1430 {
1431 filename = filename.substr(lastSlash + 1);
1432 }
1433
1434 if (filename.length() < 8 || filename.substr(filename.length() - 7) != ".bt.json")
1435 {
1436 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: Not a .bt.json file: " << path << "\n";
1437 return false;
1438 }
1439
1440 // 4. Try to parse JSON to verify it's valid
1441 try
1442 {
1443 json j;
1444 if (!JsonHelper::LoadJsonFromFile(path, j))
1445 {
1446 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: Failed to load JSON: " << path << "\n";
1447 return false;
1448 }
1449 }
1450 catch (const std::exception& e)
1451 {
1452 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: JSON parse error: " << e.what() << "\n";
1453 return false;
1454 }
1455
1456 SYSTEM_LOG << "[BehaviorTreeManager] ValidateSubGraphPath: Valid - " << path << "\n";
1457 return true;
1458}
1459
1461{
1462 if (!parentTree)
1463 {
1464 return false;
1465 }
1466
1467 const BTNode* node = parentTree->GetNode(nodeId);
1468 if (!node || node->type != BTNodeType::SubGraph)
1469 {
1470 return false;
1471 }
1472
1473 // 1. Check if we've already visited this path (cycle detected)
1474 if (visited.find(node->subgraphPath) != visited.end())
1475 {
1476 SYSTEM_LOG << "[BehaviorTreeManager] Circular dependency detected: " << node->subgraphPath << "\n";
1477 return true;
1478 }
1479
1480 // 2. Check if path is valid first
1481 if (!ValidateSubGraphPath(node->subgraphPath))
1482 {
1483 SYSTEM_LOG << "[BehaviorTreeManager] SubGraph path invalid for circular check: " << node->subgraphPath << "\n";
1484 return false; // Invalid path is not a circular dependency, just a broken reference
1485 }
1486
1487 // 3. Add this path to visited set
1488 visited.insert(node->subgraphPath);
1489
1490 // 4. Try to load the SubGraph
1491 uint32_t subgraphId = std::hash<std::string>{}(node->subgraphPath) & 0x7FFFFFFF;
1492
1493 // Load if not already loaded
1494 if (!IsTreeLoadedByPath(node->subgraphPath))
1495 {
1496 if (!const_cast<BehaviorTreeManager*>(this)->LoadTreeFromFile(node->subgraphPath, subgraphId))
1497 {
1498 SYSTEM_LOG << "[BehaviorTreeManager] Failed to load SubGraph for circular check: " << node->subgraphPath << "\n";
1499 return false;
1500 }
1501 }
1502
1504 if (!subgraph)
1505 {
1506 return false;
1507 }
1508
1509 // 5. Recursively check all SubGraph nodes in the loaded tree
1510 for (const BTNode& subNode : subgraph->nodes)
1511 {
1512 if (subNode.type == BTNodeType::SubGraph)
1513 {
1515 {
1516 return true; // Cycle found deeper in the graph
1517 }
1518 }
1519 }
1520
1521 // 6. Backtrack: remove from visited set for other branches
1522 visited.erase(node->subgraphPath);
1523
1524 return false; // No cycle in this path
1525}
1526
1528{
1529 std::vector<std::string> errors;
1530
1531 const BehaviorTreeAsset* tree = GetTree(graphId);
1532 if (!tree)
1533 {
1534 errors.push_back("Graph not found with ID: " + std::to_string(graphId));
1535 return errors;
1536 }
1537
1538 // 1. Check each SubGraph node
1539 std::set<std::string> visited;
1540
1541 for (const BTNode& node : tree->nodes)
1542 {
1543 if (node.type == BTNodeType::SubGraph)
1544 {
1545 // Check 1: Empty path
1546 if (node.subgraphPath.empty())
1547 {
1548 errors.push_back("SubGraph node '" + node.name + "' (ID:" + std::to_string(node.id) + ") has no path specified");
1549 continue;
1550 }
1551
1552 // Check 2: Path validity
1553 if (!ValidateSubGraphPath(node.subgraphPath))
1554 {
1555 errors.push_back("SubGraph node '" + node.name + "' (ID:" + std::to_string(node.id) + ") path invalid: " + node.subgraphPath);
1556 continue;
1557 }
1558
1559 // Check 3: Circular dependency
1560 std::set<std::string> circularVisited;
1562 {
1563 errors.push_back("SubGraph node '" + node.name + "' (ID:" + std::to_string(node.id) + ") creates circular reference: " + node.subgraphPath);
1564 }
1565 }
1566 }
1567
1568 return errors;
1569}
1570
1571// ============================================================================
1572// Phase 38b: OnEvent Root Node Activation via EventQueue
1573// ============================================================================
1574
1576{
1577 // Guard: If tree has no event roots, skip
1578 if (tree.m_eventRootIds.empty())
1579 return;
1580
1581 // Get all events from EventQueue (these are frame N-1 events, now readable in frame N)
1582 const std::vector<Message>& events = eventQueue.GetEvents();
1583
1584 // Iterate through all events
1585 for (const Message& msg : events)
1586 {
1587 // Phase 38b: Match event type to OnEvent nodes
1588 // Find OnEvent root nodes that are listening for this event type
1589 for (uint32_t eventRootId : tree.m_eventRootIds)
1590 {
1591 // Get the OnEvent root node
1592 const BTNode* onEventRoot = tree.GetNode(eventRootId);
1593 if (!onEventRoot)
1594 continue;
1595
1596 // Check if this OnEvent node is listening for this event type
1597 // eventType field contains the EventType enum value as string (e.g., "Olympe_EventType_AI_Explosion")
1598 if (onEventRoot->eventType.empty())
1599 continue;
1600
1601 // Convert message type to string for comparison
1602 // For now, simple enum-to-string mapping
1603 std::string msgTypeStr;
1604 switch (msg.msg_type)
1605 {
1607 msgTypeStr = "Olympe_EventType_AI_Explosion";
1608 break;
1610 msgTypeStr = "Olympe_EventType_AI_Noise";
1611 break;
1613 msgTypeStr = "Olympe_EventType_AI_DamageDealt";
1614 break;
1616 msgTypeStr = "Olympe_EventType_Object_Create";
1617 break;
1619 msgTypeStr = "Olympe_EventType_Object_Destroy";
1620 break;
1621 default:
1622 msgTypeStr = "";
1623 break;
1624 }
1625
1626 // Phase 38b: Optional event message filtering (future enhancement)
1627 // If eventMessage is set, only activate if message content matches
1628 // For now, simple event type matching
1629
1630 // Check if this OnEvent node matches the event type
1631 if (onEventRoot->eventType == msgTypeStr)
1632 {
1633 // Execute this OnEvent root node
1634 std::cout << "[TickEventRoots] Entity " << entity << ": Executing OnEvent root " << eventRootId
1635 << " for event '" << msgTypeStr << "'" << std::endl;
1636
1637 // Execute the OnEvent root (will traverse its child tree)
1639 }
1640 }
1641 }
1642}
BTStatus ExecuteBTAction(BTActionType actionType, float param1, float param2, EntityID entity, AIBlackboard_data &blackboard)
nlohmann::json json
BTStatus ExecuteBTNode(const BTNode &node, EntityID entity, AIBlackboard_data &blackboard, const BehaviorTreeAsset &tree)
BTStatus ExecuteBTCondition(BTConditionType condType, float param, EntityID entity, const AIBlackboard_data &blackboard)
void TickEventRoots(EventQueue &eventQueue, const BehaviorTreeAsset &tree, EntityID entity, AIBlackboard_data &blackboard)
Data-driven behavior tree system for AI decision making.
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.
@ SendMessage
Emit event to EventQueue (param1=EventType enum)
@ PatrolPickNextPoint
Select next patrol point.
@ ChooseRandomNavigablePoint
Choose navigable point (param1=searchRadius, param2=maxAttempts)
@ AttackIfClose
Attack if in range.
@ Idle
Do nothing.
@ 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.
@ Failure
Node failed.
BTNodeType
Behavior tree node types.
@ Action
Leaf node - performs an action.
@ Selector
OR node - succeeds if any child succeeds.
@ OnEvent
Phase 38b: OnEvent root - event-driven entry point (orange, event-triggered)
@ Sequence
AND node - succeeds if all children succeed.
@ SubGraph
Phase 39: SubGraph - external graph reference (recursive BT or ATS)
@ Inverter
Decorator - inverts child result.
@ Condition
Leaf node - checks a condition.
@ Repeater
Decorator - repeats child N times.
@ Root
Phase 38b: Root node - entry point of behavior tree (green, fixed position)
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()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
const EntityID INVALID_ENTITY_ID
Definition ECS_Entity.h:23
static uint32_t GenerateTreeIdFromPath(const std::string &treePath)
bool LoadTreeFromFile(const std::string &filepath, uint32_t treeId)
std::string GetTreePathFromId(uint32_t treeId) const
void DebugPrintLoadedTrees() const
uint32_t GetTreeIdFromPath(const std::string &treePath) const
bool DetectCircularDependencies(uint32_t graphId, uint32_t nodeId, const BehaviorTreeAsset *parentTree, std::set< std::string > &visited)
const BehaviorTreeAsset * GetTree(uint32_t treeId) const
bool ValidateSubGraphPath(const std::string &path) const
std::vector< BehaviorTreeAsset > m_trees
bool IsTreeLoadedByPath(const std::string &treePath) const
bool ValidateTree(const BehaviorTreeAsset &tree, std::string &errorMessage) const
std::map< std::string, uint32_t > m_pathToIdMap
std::vector< std::string > GetValidationErrors(uint32_t graphId)
bool ReloadTree(uint32_t treeId)
const BehaviorTreeAsset * GetTreeByPath(const std::string &treePath) const
const BehaviorTreeAsset * GetTreeByAnyId(uint32_t treeId) const
void Push(const Message &msg)
Definition EventQueue.h:37
static EventQueue & Get()
Definition EventQueue.h:34
static float fDt
Delta time between frames in seconds.
Definition GameEngine.h:120
bool GetRandomNavigablePoint(float centerX, float centerY, float radius, int maxAttempts, float &outX, float &outY, CollisionLayer layer=CollisionLayer::Ground) const
static NavigationMap & Get()
float Magnitude() const
Definition vector.h:79
float y
Definition vector.h:25
float x
Definition vector.h:25
static World & Get()
Get singleton instance (short form)
Definition World.h:232
bool HasComponent(EntityID entity) const
Definition World.h:451
T & GetComponent(EntityID entity)
Definition World.h:438
std::string GetString(const json &j, const std::string &key, const std::string &defaultValue="")
Safely get a string value from JSON.
uint32_t GetUInt(const json &j, const std::string &key, uint32_t defaultValue=0)
Safely get an unsigned integer value from JSON.
bool LoadJsonFromFile(const std::string &filepath, json &j)
Load and parse a JSON file.
Definition json_helper.h:42
int GetInt(const json &j, const std::string &key, int defaultValue=0)
Safely get an integer value from JSON.
void ForEachInArray(const json &j, const std::string &key, std::function< void(const json &, size_t)> callback)
Iterate over an array with a callback function.
float GetFloat(const json &j, const std::string &key, float defaultValue=0.0f)
Safely get a float value from JSON.
bool IsArray(const json &j, const std::string &key)
Check if a key contains an array.
nlohmann::json json
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.
std::string name
std::string subgraphPath
Path to external .bt.json or .ats file.
uint32_t decoratorChildId
BTNodeType type
Node type.
Validation message for behavior tree structure checking.
uint32_t AddNode(BTNodeType type, const std::string &name, const Vector &position)
bool RemoveNode(uint32_t nodeId)
std::vector< BTValidationMessage > ValidateTreeFull() const
bool ConnectNodes(uint32_t parentId, uint32_t childId)
uint32_t GenerateNextNodeId() const
std::vector< BTNode > nodes
BTNode * GetNode(uint32_t nodeId)
bool DetectCycle(uint32_t startNodeId) const
bool DisconnectNodes(uint32_t parentId, uint32_t childId)
EventType msg_type
Definition message.h:11
Position component for spatial location.
Vector position
2D/3D position vector
EventType
@ Olympe_EventType_AI_DamageDealt
@ Olympe_EventType_AI_Noise
@ Olympe_EventType_Object_Destroy
@ Olympe_EventType_Object_Create
@ Olympe_EventType_AI_Explosion
#define SYSTEM_LOG