diff --git a/Makefile b/Makefile index 39d1b1b..7408a75 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests + ./builddir/runtests "[ai-enemy]" run: build test powershell "cp ./builddir/zedcaster.exe ." diff --git a/ai.cpp b/ai.cpp index 6edb434..5ae08d1 100644 --- a/ai.cpp +++ b/ai.cpp @@ -93,8 +93,8 @@ namespace ai { auto& scripts = config["scripts"]; for(auto& [script_name, action_names] : scripts.items()) { std::vector the_script; - for(auto name : action_names) { + for(auto name : action_names) { check(AIMGR.actions.contains(name), fmt::format("ai::init(): script {} uses action {} that doesn't exist", (std::string)script_name, (std::string)name)); @@ -110,6 +110,12 @@ namespace ai { } } + void check_valid_action(std::string name, std::string msg) { + dbc::check(AIMGR.actions.contains(name), + fmt::format("{} tried to access action that doesn't exist {}", + msg, name)); + } + State load_state(std::string state_name) { check(initialized, "you forgot to initialize the AI first."); check(AIMGR.states.contains(state_name), fmt::format( @@ -158,8 +164,8 @@ namespace ai { return &AIMGR.profile; } - bool EntityAI::wants_to(std::string name) { + ai::check_valid_action(name, "EntityAI::wants_to"); return plan.script[0].name == name; } diff --git a/ai.hpp b/ai.hpp index f4c17c8..76a1777 100644 --- a/ai.hpp +++ b/ai.hpp @@ -27,6 +27,8 @@ namespace ai { void set_state(std::string name, bool setting); void update(); + + void dump(); }; struct AIManager { @@ -55,4 +57,5 @@ namespace ai { /* Mostly used for debugging and validation. */ AIProfile* profile(); + void check_valid_action(std::string name, std::string msg); } diff --git a/ai_debug.cpp b/ai_debug.cpp index 16034a7..ce838dc 100644 --- a/ai_debug.cpp +++ b/ai_debug.cpp @@ -56,4 +56,8 @@ namespace ai { return start; } + + void EntityAI::dump() { + dump_script(script, start, plan.script); + } } diff --git a/assets/ai.json b/assets/ai.json index 62dfa3d..19b4804 100644 --- a/assets/ai.json +++ b/assets/ai.json @@ -28,6 +28,7 @@ "name": "kill_enemy", "cost": 5, "needs": { + "health_good": true, "no_more_enemies": false, "enemy_found": true, "enemy_dead": false @@ -54,11 +55,24 @@ "needs": { "have_item": true, "have_healing": true, + "in_combat": false, "health_good": false }, "effects": { "health_good": true } + }, + { + "name": "run_away", + "cost": 0, + "needs": { + "in_combat": true, + "have_healing": false, + "health_good": false + }, + "effects": { + "in_combat": false + } } ], "states": { @@ -102,6 +116,6 @@ "collect_items", "use_healing"], "Enemy::actions": - ["find_enemy", "kill_enemy"] + ["find_enemy", "kill_enemy", "run_away", "use_healing"] } } diff --git a/tests/ai.cpp b/tests/ai.cpp index 505244d..ecd54ca 100644 --- a/tests/ai.cpp +++ b/tests/ai.cpp @@ -171,6 +171,32 @@ TEST_CASE("ai autowalker ai test", "[ai]") { REQUIRE(ai::test(result, "no_more_enemies")); } -TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") { - // nothing yet +TEST_CASE("Confirm EntityAI behaves as expected", "[ai-enemy]") { + ai::reset(); + ai::init("assets/ai.json"); + auto ai_start = ai::load_state("Enemy::initial_state"); + auto ai_goal = ai::load_state("Enemy::final_state"); + + ai::EntityAI enemy("Enemy::actions", ai_start, ai_goal); + + enemy.set_state("detect_enemy", true); + enemy.update(); + REQUIRE(enemy.wants_to("find_enemy")); + + enemy.set_state("enemy_found", true); + enemy.update(); + REQUIRE(enemy.wants_to("kill_enemy")); + + enemy.set_state("in_combat", true); + enemy.set_state("health_good", false); + enemy.update(); + enemy.dump(); + REQUIRE(enemy.wants_to("run_away")); + + enemy.set_state("have_item", true); + enemy.set_state("have_healing", true); + enemy.set_state("in_combat", false); + enemy.update(); + enemy.dump(); + REQUIRE(enemy.wants_to("use_healing")); }