#include #include "dbc.hpp" #include "ai.hpp" #include #include "ai_debug.hpp" using namespace dbc; using namespace nlohmann; TEST_CASE("state and actions work", "[ai]") { enum StateNames { ENEMY_IN_RANGE, ENEMY_DEAD }; ai::State goal; ai::State start; std::vector actions; // start off enemy not dead and not in range start[ENEMY_DEAD] = false; start[ENEMY_IN_RANGE] = false; // end goal is enemy is dead goal[ENEMY_DEAD] = true; ai::Action move_closer("move_closer", 10); move_closer.needs(ENEMY_IN_RANGE, false); move_closer.effect(ENEMY_IN_RANGE, true); REQUIRE(move_closer.can_effect(start)); auto after_move_state = move_closer.apply_effect(start); REQUIRE(start[ENEMY_IN_RANGE] == false); REQUIRE(after_move_state[ENEMY_IN_RANGE] == true); REQUIRE(after_move_state[ENEMY_DEAD] == false); // start is clean but after move is dirty REQUIRE(move_closer.can_effect(start)); REQUIRE(!move_closer.can_effect(after_move_state)); REQUIRE(ai::distance_to_goal(start, after_move_state, move_closer) == 11); ai::Action kill_it("kill_it", 10); kill_it.needs(ENEMY_IN_RANGE, true); kill_it.needs(ENEMY_DEAD, false); kill_it.effect(ENEMY_DEAD, true); REQUIRE(!kill_it.can_effect(start)); REQUIRE(kill_it.can_effect(after_move_state)); auto after_kill_state = kill_it.apply_effect(after_move_state); REQUIRE(!kill_it.can_effect(after_kill_state)); REQUIRE(ai::distance_to_goal(after_move_state, after_kill_state, kill_it) == 11); kill_it.ignore(ENEMY_IN_RANGE); REQUIRE(kill_it.can_effect(after_move_state)); actions.push_back(kill_it); actions.push_back(move_closer); REQUIRE(start != goal); } TEST_CASE("basic feature tests", "[ai]") { enum StateNames { ENEMY_IN_RANGE, ENEMY_DEAD }; ai::State goal; ai::State start; std::vector actions; // start off enemy not dead and not in range start[ENEMY_DEAD] = false; start[ENEMY_IN_RANGE] = false; // end goal is enemy is dead goal[ENEMY_DEAD] = true; ai::Action move_closer("move_closer", 10); move_closer.needs(ENEMY_IN_RANGE, false); move_closer.effect(ENEMY_IN_RANGE, true); ai::Action kill_it("kill_it", 10); kill_it.needs(ENEMY_IN_RANGE, true); // this is duplicated on purpose to confirm that setting // a positive then a negative properly cancels out kill_it.needs(ENEMY_DEAD, true); kill_it.needs(ENEMY_DEAD, false); // same thing with effects kill_it.effect(ENEMY_DEAD, false); kill_it.effect(ENEMY_DEAD, true); // order seems to matter which is wrong actions.push_back(kill_it); actions.push_back(move_closer); auto result = ai::plan_actions(actions, start, goal); REQUIRE(result.complete); auto state = start; for(auto& action : result.script) { state = action.apply_effect(state); } REQUIRE(state[ENEMY_DEAD]); } TEST_CASE("ai as a module like sound/sprites", "[ai]") { ai::reset(); ai::init("tests/ai_fixture.json"); auto start = ai::load_state("test_start"); auto goal = ai::load_state("test_goal"); auto a_plan = ai::plan("test1", start, goal); REQUIRE(a_plan.complete); auto state = start; for(auto& action : a_plan.script) { fmt::println("ACTION: {}", action.name); state = action.apply_effect(state); } REQUIRE(ai::test(state, "target_dead")); } TEST_CASE("ai autowalker ai test", "[ai]") { ai::reset(); ai::init("assets/ai.json"); auto start = ai::load_state("Walker::initial_state"); auto goal = ai::load_state("Walker::final_state"); int enemy_count = 5; ai::set(start, "no_more_enemies", enemy_count == 0); // find an enemy and kill them auto a_plan = ai::plan("Walker::actions", start, goal); REQUIRE(!a_plan.complete); auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script); REQUIRE(ai::test(result, "enemy_found")); REQUIRE(ai::test(result, "enemy_dead")); REQUIRE(!ai::test(result, "no_more_enemies")); // health is low, go heal ai::set(result, "health_good", false); ai::set(result, "in_combat", false); ai::set(result, "enemy_found", false); ai::set(result, "have_healing", true); ai::set(result, "have_item", true); REQUIRE(!ai::test(result, "health_good")); auto health_plan = ai::plan("Walker::actions", result, goal); result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script); REQUIRE(!health_plan.complete); REQUIRE(ai::test(result, "health_good")); // health is good, enemies dead, go get stuff ai::set(result, "no_more_enemies", true); REQUIRE(ai::test(result, "no_more_enemies")); auto new_plan = ai::plan("Walker::actions", result, goal); result = ai::dump_script("\n\nWALKER COMPLETE", result, new_plan.script); REQUIRE(new_plan.complete); REQUIRE(ai::test(result, "enemy_found")); REQUIRE(ai::test(result, "enemy_dead")); REQUIRE(ai::test(result, "no_more_enemies")); }