/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "tc_catch2.h" #include "AI/Decision/BehaviorTree.h" using namespace Playerbot::bot::ai; TEST_CASE("BehaviorTree - NodeStatus Values", "[Phase5][BehaviorTree]") { SECTION("NodeStatus enum values exist") { NodeStatus success = NodeStatus::SUCCESS; NodeStatus failure = NodeStatus::FAILURE; NodeStatus running = NodeStatus::RUNNING; REQUIRE(success != failure); REQUIRE(success != running); REQUIRE(failure != running); } } TEST_CASE("BehaviorTree - NodeType Values", "[Phase5][BehaviorTree]") { SECTION("NodeType enum values exist") { NodeType composite = NodeType::COMPOSITE; NodeType decorator = NodeType::DECORATOR; NodeType leaf = NodeType::LEAF; REQUIRE(composite != decorator); REQUIRE(composite != leaf); REQUIRE(decorator != leaf); } } TEST_CASE("BehaviorTree - ConditionNode", "[Phase5][BehaviorTree]") { SECTION("Condition returns SUCCESS when true") { auto condition = std::make_shared("AlwaysTrue", [](Player*, Unit*) { return true; }); NodeStatus status = condition->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } SECTION("Condition returns FAILURE when false") { auto condition = std::make_shared("AlwaysFalse", [](Player*, Unit*) { return false; }); NodeStatus status = condition->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); } SECTION("Condition can access parameters") { int callCount = 0; auto condition = std::make_shared("Counter", [&callCount](Player*, Unit*) { callCount++; return true; }); condition->Tick(nullptr, nullptr); REQUIRE(callCount == 1); condition->Tick(nullptr, nullptr); REQUIRE(callCount == 2); } } TEST_CASE("BehaviorTree - ActionNode", "[Phase5][BehaviorTree]") { SECTION("Action returns SUCCESS") { auto action = std::make_shared("SuccessAction", [](Player*, Unit*) { return NodeStatus::SUCCESS; }); NodeStatus status = action->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } SECTION("Action returns FAILURE") { auto action = std::make_shared("FailureAction", [](Player*, Unit*) { return NodeStatus::FAILURE; }); NodeStatus status = action->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); } SECTION("Action returns RUNNING") { auto action = std::make_shared("RunningAction", [](Player*, Unit*) { return NodeStatus::RUNNING; }); NodeStatus status = action->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::RUNNING); } SECTION("Action executes custom logic") { int executionCount = 0; auto action = std::make_shared("CustomAction", [&executionCount](Player*, Unit*) { executionCount++; return NodeStatus::SUCCESS; }); action->Tick(nullptr, nullptr); REQUIRE(executionCount == 1); action->Tick(nullptr, nullptr); REQUIRE(executionCount == 2); } } TEST_CASE("BehaviorTree - SequenceNode", "[Phase5][BehaviorTree]") { SECTION("Empty sequence returns SUCCESS") { auto sequence = std::make_shared("EmptySequence"); NodeStatus status = sequence->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } SECTION("Sequence with all SUCCESS children returns SUCCESS") { auto sequence = std::make_shared("AllSuccess"); sequence->AddChild(std::make_shared("True1", [](Player*, Unit*) { return true; })); sequence->AddChild(std::make_shared("True2", [](Player*, Unit*) { return true; })); sequence->AddChild(std::make_shared("True3", [](Player*, Unit*) { return true; })); NodeStatus status = sequence->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } SECTION("Sequence stops at first FAILURE") { int firstExecuted = 0; int secondExecuted = 0; int thirdExecuted = 0; auto sequence = std::make_shared("FailSequence"); sequence->AddChild(std::make_shared("First", [&firstExecuted](Player*, Unit*) { firstExecuted++; return NodeStatus::SUCCESS; })); sequence->AddChild(std::make_shared("Second", [&secondExecuted](Player*, Unit*) { secondExecuted++; return NodeStatus::FAILURE; })); sequence->AddChild(std::make_shared("Third", [&thirdExecuted](Player*, Unit*) { thirdExecuted++; return NodeStatus::SUCCESS; })); NodeStatus status = sequence->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); REQUIRE(firstExecuted == 1); REQUIRE(secondExecuted == 1); REQUIRE(thirdExecuted == 0); // Should not execute after failure } SECTION("Sequence returns RUNNING when child is RUNNING") { auto sequence = std::make_shared("RunningSequence"); sequence->AddChild(std::make_shared("True", [](Player*, Unit*) { return true; })); sequence->AddChild(std::make_shared("Running", [](Player*, Unit*) { return NodeStatus::RUNNING; })); sequence->AddChild(std::make_shared("NeverReached", [](Player*, Unit*) { return true; })); NodeStatus status = sequence->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::RUNNING); } } TEST_CASE("BehaviorTree - SelectorNode", "[Phase5][BehaviorTree]") { SECTION("Empty selector returns FAILURE") { auto selector = std::make_shared("EmptySelector"); NodeStatus status = selector->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); } SECTION("Selector returns SUCCESS at first SUCCESS child") { int firstExecuted = 0; int secondExecuted = 0; int thirdExecuted = 0; auto selector = std::make_shared("SuccessSelector"); selector->AddChild(std::make_shared("First", [&firstExecuted](Player*, Unit*) { firstExecuted++; return NodeStatus::FAILURE; })); selector->AddChild(std::make_shared("Second", [&secondExecuted](Player*, Unit*) { secondExecuted++; return NodeStatus::SUCCESS; })); selector->AddChild(std::make_shared("Third", [&thirdExecuted](Player*, Unit*) { thirdExecuted++; return NodeStatus::SUCCESS; })); NodeStatus status = selector->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); REQUIRE(firstExecuted == 1); REQUIRE(secondExecuted == 1); REQUIRE(thirdExecuted == 0); // Should not execute after success } SECTION("Selector returns FAILURE when all children FAIL") { auto selector = std::make_shared("AllFail"); selector->AddChild(std::make_shared("False1", [](Player*, Unit*) { return false; })); selector->AddChild(std::make_shared("False2", [](Player*, Unit*) { return false; })); selector->AddChild(std::make_shared("False3", [](Player*, Unit*) { return false; })); NodeStatus status = selector->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); } SECTION("Selector returns RUNNING when child is RUNNING") { auto selector = std::make_shared("RunningSelector"); selector->AddChild(std::make_shared("False", [](Player*, Unit*) { return false; })); selector->AddChild(std::make_shared("Running", [](Player*, Unit*) { return NodeStatus::RUNNING; })); selector->AddChild(std::make_shared("NeverReached", [](Player*, Unit*) { return true; })); NodeStatus status = selector->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::RUNNING); } } TEST_CASE("BehaviorTree - InverterNode", "[Phase5][BehaviorTree]") { SECTION("Inverter converts SUCCESS to FAILURE") { auto condition = std::make_shared("True", [](Player*, Unit*) { return true; }); auto inverter = std::make_shared("Inverter", condition); NodeStatus status = inverter->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::FAILURE); } SECTION("Inverter converts FAILURE to SUCCESS") { auto condition = std::make_shared("False", [](Player*, Unit*) { return false; }); auto inverter = std::make_shared("Inverter", condition); NodeStatus status = inverter->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } SECTION("Inverter does not affect RUNNING") { auto action = std::make_shared("Running", [](Player*, Unit*) { return NodeStatus::RUNNING; }); auto inverter = std::make_shared("Inverter", action); NodeStatus status = inverter->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::RUNNING); } } TEST_CASE("BehaviorTree - RepeaterNode", "[Phase5][BehaviorTree]") { SECTION("Repeater with max repeats 0 runs indefinitely") { int executionCount = 0; auto action = std::make_shared("Increment", [&executionCount](Player*, Unit*) { executionCount++; return NodeStatus::SUCCESS; }); auto repeater = std::make_shared("InfiniteRepeater", action, 0); // First tick should return RUNNING NodeStatus status = repeater->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::RUNNING); REQUIRE(executionCount == 1); } SECTION("Repeater executes N times") { int executionCount = 0; auto action = std::make_shared("Increment", [&executionCount](Player*, Unit*) { executionCount++; return NodeStatus::SUCCESS; }); auto repeater = std::make_shared("ThreeRepeater", action, 3); // Execute 3 times for (int i = 0; i < 3; ++i) { NodeStatus status = repeater->Tick(nullptr, nullptr); if (i < 2) REQUIRE(status == NodeStatus::RUNNING); else REQUIRE(status == NodeStatus::SUCCESS); } REQUIRE(executionCount == 3); } SECTION("Repeater stops on FAILURE") { int executionCount = 0; auto action = std::make_shared("FailAfterTwo", [&executionCount](Player*, Unit*) { executionCount++; return (executionCount < 2) ? NodeStatus::SUCCESS : NodeStatus::FAILURE; }); auto repeater = std::make_shared("RepeatUntilFail", action, 5); repeater->Tick(nullptr, nullptr); // Count = 1, returns RUNNING NodeStatus status = repeater->Tick(nullptr, nullptr); // Count = 2, returns FAILURE REQUIRE(status == NodeStatus::FAILURE); REQUIRE(executionCount == 2); } } TEST_CASE("BehaviorTree - Complex Tree Structures", "[Phase5][BehaviorTree]") { SECTION("Nested sequence in selector") { // Selector { // Sequence { false, true } -> FAILURE // Sequence { true, true } -> SUCCESS // } // Expected: SUCCESS auto selector = std::make_shared("Root"); auto failSeq = std::make_shared("FailSeq"); failSeq->AddChild(std::make_shared("False", [](Player*, Unit*) { return false; })); failSeq->AddChild(std::make_shared("True", [](Player*, Unit*) { return true; })); auto successSeq = std::make_shared("SuccessSeq"); successSeq->AddChild(std::make_shared("True1", [](Player*, Unit*) { return true; })); successSeq->AddChild(std::make_shared("True2", [](Player*, Unit*) { return true; })); selector->AddChild(failSeq); selector->AddChild(successSeq); NodeStatus status = selector->Tick(nullptr, nullptr); REQUIRE(status == NodeStatus::SUCCESS); } } TEST_CASE("BehaviorTree - Tree Reset Functionality", "[Phase5][BehaviorTree]") { SECTION("Tree reset works correctly") { auto tree = std::make_shared("TestTree"); auto action = std::make_shared("Action", [](Player*, Unit*) { return NodeStatus::SUCCESS; }); tree->SetRoot(action); // Execute tree NodeStatus status1 = tree->Tick(nullptr, nullptr); REQUIRE(status1 == NodeStatus::SUCCESS); // Reset tree tree->Reset(); // Execute again after reset NodeStatus status2 = tree->Tick(nullptr, nullptr); REQUIRE(status2 == NodeStatus::SUCCESS); } } TEST_CASE("BehaviorTree - Tree Name and Status", "[Phase5][BehaviorTree]") { auto tree = std::make_shared("MyTree"); SECTION("Tree has correct name") { REQUIRE(tree->GetName() == "MyTree"); } SECTION("Tree status tracking") { auto runningAction = std::make_shared("Running", [](Player*, Unit*) { return NodeStatus::RUNNING; }); tree->SetRoot(runningAction); tree->Tick(nullptr, nullptr); REQUIRE(tree->IsRunning() == true); REQUIRE(tree->GetLastStatus() == NodeStatus::RUNNING); } } TEST_CASE("BehaviorTree - Debug Logging", "[Phase5][BehaviorTree]") { auto tree = std::make_shared("DebugTree"); SECTION("Enable debug logging") { tree->EnableDebugLogging(true); auto action = std::make_shared("Action", [](Player*, Unit*) { return NodeStatus::SUCCESS; }); tree->SetRoot(action); tree->Tick(nullptr, nullptr); // Logging enabled - verified by compilation REQUIRE(true); } SECTION("Disable debug logging") { tree->EnableDebugLogging(false); auto action = std::make_shared("Action", [](Player*, Unit*) { return NodeStatus::SUCCESS; }); tree->SetRoot(action); tree->Tick(nullptr, nullptr); // Logging disabled - verified by compilation REQUIRE(true); } }