#include "testHarness.h" #include "vf/Behavior.h" #include #include #include using namespace vf; namespace { World flatWorld(int halfSpan = 24) { World world; for (int x = -halfSpan; x <= halfSpan; ++x) { for (int z = -halfSpan; z <= halfSpan; ++z) world.setBlock(x, 0, z, Block::Stone); } return world; } World grassWorld(int halfSpan = 12) { World world; for (int x = -halfSpan; x <= halfSpan; ++x) { for (int z = -halfSpan; z <= halfSpan; ++z) world.setBlock(x, 0, z, Block::Grass); } return world; } std::unique_ptr constantAction(const char* name, BehaviorStatus status, int* counter) { return std::unique_ptr(new ActionNode(name, [status, counter](BehaviorContext&) { if (counter != nullptr) ++(*counter); return status; })); } std::unique_ptr flagCondition(const char* name, const bool* source) { return std::unique_ptr( new ConditionNode(name, [source](BehaviorContext&) { return *source; })); } // Reports Running for `runningTicks` calls, then Success once, then restarts. std::unique_ptr slowAction(const char* name, int runningTicks, int* counter, int* progress) { return std::unique_ptr( new ActionNode(name, [runningTicks, counter, progress](BehaviorContext&) { ++(*counter); ++(*progress); if (*progress <= runningTicks) return BehaviorStatus::Running; *progress = 0; return BehaviorStatus::Success; })); } BehaviorContext freshContext(const World* world) { BehaviorContext ctx; ctx.world = world; return ctx; } BehaviorAgent makeAgent(Species species, const Vec3& position) { BehaviorAgent agent; agent.species = species; agent.position = position; agent.homePosition = position; const SpeciesStats& stats = speciesStats(species); agent.health = stats.maxHealth; agent.maxHealth = stats.maxHealth; agent.moveStep = 0.25f; return agent; } } // namespace VF_TEST(blackboardKeepsEachValueTypeInItsOwnDrawer) { Blackboard board; board.setNumber("threat", 4.5f); board.setCount("meals", 3); board.setFlag("engaged", true); board.setPoint("lastSeen", Vec3{1.0f, 2.0f, 3.0f}); board.setCell("target", Vec3i{4, 5, 6}); board.setRoute("path", {Vec3i{0, 1, 0}, Vec3i{1, 1, 0}}); CHECK_NEAR(board.number("threat"), 4.5f, 1e-6f); CHECK_EQ(board.count("meals"), 3); CHECK(board.flag("engaged")); CHECK_NEAR(board.point("lastSeen").y, 2.0f, 1e-6f); CHECK(board.cell("target") == Vec3i(4, 5, 6)); CHECK(board.route("path") != nullptr); CHECK_EQ(board.route("path")->size(), 2u); CHECK_EQ(board.entryCount(), 6u); // A key written as one type is invisible to every other type. CHECK(!board.hasCount("threat")); CHECK(!board.hasNumber("meals")); CHECK(board.route("threat") == nullptr); CHECK_NEAR(board.number("missing", -1.0f), -1.0f, 1e-6f); board.eraseNumber("threat"); CHECK(!board.hasNumber("threat")); CHECK_EQ(board.entryCount(), 5u); board.eraseRoute("path"); CHECK(!board.hasRoute("path")); board.clear(); CHECK_EQ(board.entryCount(), 0u); } VF_TEST(sequenceStopsAtTheChildThatFailsAndSkipsTheRest) { int firstRuns = 0; int lastRuns = 0; bool gateOpen = false; auto root = std::unique_ptr(new SequenceNode("sequence")); root->add(constantAction("first", BehaviorStatus::Success, &firstRuns)); root->add(flagCondition("gate", &gateOpen)); root->add(constantAction("last", BehaviorStatus::Success, &lastRuns)); BehaviorTree tree(std::move(root)); BehaviorContext ctx; CHECK(tree.tick(ctx) == BehaviorStatus::Failure); CHECK_EQ(firstRuns, 1); CHECK_EQ(lastRuns, 0); gateOpen = true; CHECK(tree.tick(ctx) == BehaviorStatus::Success); CHECK_EQ(firstRuns, 2); CHECK_EQ(lastRuns, 1); } VF_TEST(selectorTakesTheFirstBranchThatSucceedsAndFailsWhenNoneDo) { int firstRuns = 0; int secondRuns = 0; int thirdRuns = 0; auto root = std::unique_ptr(new SelectorNode("selector")); root->add(constantAction("first", BehaviorStatus::Failure, &firstRuns)); root->add(constantAction("second", BehaviorStatus::Success, &secondRuns)); root->add(constantAction("third", BehaviorStatus::Success, &thirdRuns)); BehaviorTree tree(std::move(root)); BehaviorContext ctx; CHECK(tree.tick(ctx) == BehaviorStatus::Success); CHECK_EQ(firstRuns, 1); CHECK_EQ(secondRuns, 1); CHECK_EQ(thirdRuns, 0); auto allFail = std::unique_ptr(new SelectorNode("selector")); allFail->add(constantAction("a", BehaviorStatus::Failure, nullptr)); allFail->add(constantAction("b", BehaviorStatus::Failure, nullptr)); BehaviorTree hopeless(std::move(allFail)); CHECK(hopeless.tick(ctx) == BehaviorStatus::Failure); } VF_TEST(aRunningNodeResumesNextTickInsteadOfRestartingTheSequence) { int firstRuns = 0; int slowRuns = 0; int slowProgress = 0;