Autowalker is working way better and now I have a plan for using the AI in the System.

master
Zed A. Shaw 3 weeks ago
parent 0623170dbc
commit ee804581a8
  1. 29
      ai_debug.cpp
  2. 8
      ai_debug.hpp
  3. 50
      assets/ai.json
  4. 4
      assets/config.json
  5. 183
      autowalker.cpp
  6. 14
      autowalker.hpp
  7. 2
      inventory.cpp
  8. 7
      pathing.cpp
  9. 17
      systems.cpp
  10. 1
      systems.hpp
  11. 9
      tests/ai.cpp

@ -1,3 +1,4 @@
#include "ai.hpp"
#include "ai_debug.hpp" #include "ai_debug.hpp"
namespace ai { namespace ai {
@ -6,37 +7,39 @@ namespace ai {
* Yeah this is weird but it's only to debug things like * Yeah this is weird but it's only to debug things like
* the preconditions which are weirdly done. * the preconditions which are weirdly done.
*/ */
void dump_only(AIProfile& profile, State state, bool matching, bool show_as) { void dump_only(State state, bool matching, bool show_as) {
for(auto& [name, name_id] : profile) { AIProfile* profile = ai::profile();
for(auto& [name, name_id] : *profile) {
if(state.test(name_id) == matching) { if(state.test(name_id) == matching) {
fmt::println("\t{}={}", name, show_as); fmt::println("\t{}={}", name, show_as);
} }
} }
} }
void dump_state(AIProfile& profile, State state) { void dump_state(State state) {
for(auto& [name, name_id] : profile) { AIProfile* profile = ai::profile();
for(auto& [name, name_id] : *profile) {
fmt::println("\t{}={}", name, fmt::println("\t{}={}", name,
state.test(name_id)); state.test(name_id));
} }
} }
void dump_action(AIProfile& profile, Action& action) { void dump_action(Action& action) {
fmt::println(" --ACTION: {}, cost={}", action.name, action.cost); fmt::println(" --ACTION: {}, cost={}", action.name, action.cost);
fmt::println(" PRECONDS:"); fmt::println(" PRECONDS:");
dump_only(profile, action.$positive_preconds, true, true); dump_only(action.$positive_preconds, true, true);
dump_only(profile, action.$negative_preconds, true, false); dump_only(action.$negative_preconds, true, false);
fmt::println(" EFFECTS:"); fmt::println(" EFFECTS:");
dump_only(profile, action.$positive_effects, true, true); dump_only(action.$positive_effects, true, true);
dump_only(profile, action.$negative_effects, true, false); dump_only(action.$negative_effects, true, false);
} }
State dump_script(AIProfile& profile, std::string msg, State start, Script& script) { State dump_script(std::string msg, State start, Script& script) {
fmt::println("--SCRIPT DUMP: {}", msg); fmt::println("--SCRIPT DUMP: {}", msg);
fmt::println("# STATE BEFORE:"); fmt::println("# STATE BEFORE:");
dump_state(profile, start); dump_state(start);
fmt::print("% ACTIONS PLANNED:"); fmt::print("% ACTIONS PLANNED:");
for(auto& action : script) { for(auto& action : script) {
fmt::print("{} ", action.name); fmt::print("{} ", action.name);
@ -44,11 +47,11 @@ namespace ai {
fmt::print("\n"); fmt::print("\n");
for(auto& action : script) { for(auto& action : script) {
dump_action(profile, action); dump_action(action);
start = action.apply_effect(start); start = action.apply_effect(start);
fmt::println(" ## STATE AFTER:"); fmt::println(" ## STATE AFTER:");
dump_state(profile, start); dump_state(start);
} }
return start; return start;

@ -2,8 +2,8 @@
#include "goap.hpp" #include "goap.hpp"
namespace ai { namespace ai {
void dump_only(AIProfile& profile, State state, bool matching, bool show_as); void dump_only(State state, bool matching, bool show_as);
void dump_state(AIProfile& profile, State state); void dump_state(State state);
void dump_action(AIProfile& profile, Action& action); void dump_action(Action& action);
State dump_script(AIProfile& profile, std::string msg, State start, Script& script); State dump_script(std::string msg, State start, Script& script);
} }

@ -16,7 +16,6 @@
"needs": { "needs": {
"in_combat": false, "in_combat": false,
"no_more_enemies": false, "no_more_enemies": false,
"health_good": true,
"enemy_found": false "enemy_found": false
}, },
"effects": { "effects": {
@ -29,9 +28,9 @@
"needs": { "needs": {
"no_more_enemies": false, "no_more_enemies": false,
"enemy_found": true, "enemy_found": true,
"health_good": true,
"enemy_dead": false "enemy_dead": false
}, },
"effects": { "effects": {
"enemy_dead": true "enemy_dead": true
} }
@ -47,30 +46,6 @@
"no_more_items": true "no_more_items": true
} }
}, },
{
"name": "find_healing",
"cost": 0,
"needs": {
"enemy_found": false,
"in_combat": false,
"health_good": false,
"no_more_items": false
},
"effects": {
"health_good": true
}
},
{
"name": "use_item",
"cost": 0,
"needs": {
"have_item": true,
"health_good": true
},
"effects": {
"have_item": false
}
},
{ {
"name": "use_healing", "name": "use_healing",
"cost": 0, "cost": 0,
@ -90,23 +65,38 @@
"enemy_dead": false, "enemy_dead": false,
"health_good": true, "health_good": true,
"no_more_items": false, "no_more_items": false,
"no_more_enemies": false "no_more_enemies": false,
"in_combat": false,
"have_item": false,
"have_healing": false
}, },
"Walker::final_state": { "Walker::final_state": {
"enemy_found": true, "enemy_found": true,
"enemy_dead": true, "enemy_dead": true,
"health_good": true, "health_good": true,
"no_more_items": true, "no_more_items": true,
"in_combat": false,
"no_more_enemies": true "no_more_enemies": true
},
"Enemy::initial_state": {
"enemy_found": false,
"enemy_dead": false,
"health_good": true,
"in_combat": false
},
"Enemy::final_state": {
"enemy_found": true,
"enemy_dead": true,
"health_good": true
} }
}, },
"scripts": { "scripts": {
"Walker::actions": "Walker::actions":
["find_enemy", ["find_enemy",
"kill_enemy", "kill_enemy",
"find_healing",
"collect_items", "collect_items",
"use_item", "use_healing"],
"use_healing"] "Enemy::actions":
["find_enemy", "kill_enemy"]
} }
} }

@ -54,8 +54,8 @@
"tunnel_with_rocks_stage": "assets/tunnel_with_rocks_stage.png" "tunnel_with_rocks_stage": "assets/tunnel_with_rocks_stage.png"
}, },
"worldgen": { "worldgen": {
"enemy_probability": 30, "enemy_probability": 50,
"empty_room_probability": 10, "empty_room_probability": 1,
"device_probability": 10 "device_probability": 10
} }
} }

@ -1,6 +1,6 @@
#include "autowalker.hpp" #include "autowalker.hpp"
#include "inventory.hpp" #include "inventory.hpp"
#include "ai.hpp" #include "ai_debug.hpp"
template<typename Comp> template<typename Comp>
int number_left(gui::FSM& fsm) { int number_left(gui::FSM& fsm) {
@ -66,17 +66,17 @@ Pathing Autowalker::path_to_devices() {
} }
void Autowalker::window_events() { void Autowalker::handle_window_events() {
fsm.$window.handleEvents( fsm.$window.handleEvents(
[&](const sf::Event::KeyPressed &) { [&](const sf::Event::KeyPressed &) {
fsm.autowalking = false; fsm.autowalking = false;
close_status(); close_status();
log("Aborting autowalk. You can move now."); log("Aborting autowalk.");
}, },
[&](const sf::Event::MouseButtonPressed &) { [&](const sf::Event::MouseButtonPressed &) {
fsm.autowalking = false; fsm.autowalking = false;
close_status(); close_status();
log("Aborting autowalk. You can move now."); log("Aborting autowalk.");
} }
); );
} }
@ -98,24 +98,26 @@ Point Autowalker::get_current_position() {
return player_position.location; return player_position.location;
} }
void Autowalker::path_fail(Matrix& bad_paths, Point pos) {
status("PATH FAIL");
log("Autowalk failed to find a path.");
matrix::dump("MOVE FAIL PATHS", bad_paths, pos.x, pos.y);
send_event(gui::Event::STAIRS_DOWN);
}
bool Autowalker::path_player(Pathing& paths, Point& target_out) { bool Autowalker::path_player(Pathing& paths, Point& target_out) {
bool found = paths.random_walk(target_out, false, PATHING_TOWARD); bool found = paths.random_walk(target_out, false, PATHING_TOWARD);
if(!found) { if(!found) {
// failed to find a linear path, try diagonal // failed to find a linear path, try diagonal
if(!paths.random_walk(target_out, false, PATHING_TOWARD, MOVE_DIAGONAL)) { if(!paths.random_walk(target_out, false, PATHING_TOWARD, MOVE_DIAGONAL)) {
status("PATH FAIL"); path_fail(paths.$paths, target_out);
log("Autowalk failed to find a path.");
matrix::dump("MOVE FAIL PATHS", paths.$paths, target_out.x, target_out.y);
return false; return false;
} }
} }
if(!fsm.$level.map->can_move(target_out)) { if(!fsm.$level.map->can_move(target_out)) {
status("PATH FAIL"); path_fail(paths.$paths, target_out);
log("Autowalk major pathing failure. You can move now.");
matrix::dump("BAD TARGET PATHS", paths.$paths, target_out.x, target_out.y);
matrix::dump("BAD TARGET MAP", fsm.$level.map->walls(), target_out.x, target_out.y);
return false; return false;
} }
@ -180,8 +182,78 @@ void Autowalker::rotate_player(Point current, Point target) {
"player isn't facing the correct direction"); "player isn't facing the correct direction");
} }
struct InventoryStats {
int healing = 0;
int other = 0;
};
ai::State Autowalker::update_state(ai::State start) {
int enemy_count = number_left<components::Combat>(fsm);
int item_count = number_left<components::InventoryItem>(fsm);
ai::set(start, "no_more_enemies", enemy_count == 0);
ai::set(start, "no_more_items", item_count == 0);
ai::set(start, "enemy_found",
fsm.in_state(gui::State::IN_COMBAT) ||
fsm.in_state(gui::State::ATTACKING));
ai::set(start, "health_good", player_health_good());
ai::set(start, "in_combat",
fsm.in_state(gui::State::IN_COMBAT) ||
fsm.in_state(gui::State::ATTACKING));
auto inv = player_item_count();
ai::set(start, "have_item", inv.other > 0 || inv.healing > 0);
ai::set(start, "have_healing", inv.healing > 0);
return start;
}
void Autowalker::handle_boss_fight() {
// skip the boss fight for now
if(fsm.in_state(gui::State::NEXT_LEVEL)) {
// eventually we'll have AI handle this too
send_event(gui::Event::STAIRS_DOWN);
}
}
void Autowalker::handle_player_walk(ai::State& start, ai::State& goal) {
start = update_state(start);
auto a_plan = ai::plan("Walker::actions", start, goal);
dump_script("\n\n\n-----WALKER SCRIPT", start, a_plan.script);
auto action = a_plan.script.front();
if(action.name == "find_enemy") {
// this is where to test if enemy found and update state
status("FINDING ENEMY");
auto paths = path_to_enemies();
process_move(paths);
send_event(gui::Event::ATTACK);
} else if(action.name == "kill_enemy") {
status("KILLING ENEMY");
process_combat();
} else if(action.name == "use_healing") {
status("USING HEALING");
player_use_healing();
} else if(action.name == "collect_items") {
status("COLLECTING ITEMS");
auto paths = path_to_items();
process_move(paths);
// path to the items and get them all
} else if(action == ai::FINAL_ACTION) {
close_status();
log("Autowalk done, nothing left to do.");
send_event(gui::Event::STAIRS_DOWN);
} else {
close_status();
log("Autowalk has a bug. Unknown action.");
fmt::println("Unknown action: {}", action.name);
}
}
void Autowalker::autowalk() { void Autowalker::autowalk() {
window_events(); handle_window_events();
if(!fsm.autowalking) { if(!fsm.autowalking) {
close_status(); close_status();
return; return;
@ -193,58 +265,11 @@ void Autowalker::autowalk() {
auto goal = ai::load_state("Walker::final_state"); auto goal = ai::load_state("Walker::final_state");
do { do {
int enemy_count = number_left<components::Combat>(fsm); handle_window_events();
int item_count = number_left<components::InventoryItem>(fsm); handle_boss_fight();
handle_player_walk(start, goal);
window_events();
ai::set(start, "no_more_enemies", enemy_count == 0);
ai::set(start, "no_more_items", item_count == 0);
ai::set(start, "enemy_found",
fsm.in_state(gui::State::IN_COMBAT) ||
fsm.in_state(gui::State::ATTACKING));
ai::set(start, "health_good", player_health_good());
ai::set(start, "in_combat",
fsm.in_state(gui::State::IN_COMBAT) ||
fsm.in_state(gui::State::ATTACKING));
ai::set(start, "have_item", player_item_count() > 0);
auto a_plan = ai::plan("Walker::actions", start, goal);
// need a test for plan complete and only action is END
for(auto action : a_plan.script) {
if(action.name == "find_enemy") {
// this is where to test if enemy found and update state
status("FINDING ENEMY");
auto paths = path_to_enemies();
process_move(paths);
send_event(gui::Event::ATTACK);
} else if(action.name == "use_item") {
status("USE ITEMS");
} else if(action.name == "kill_enemy") {
status("KILLING ENEMY");
process_combat();
} else if(action.name == "find_healing") {
status("FINDING HEALING");
auto paths = path_to_items();
process_move(paths);
// do the path to healing thing
} else if(action.name == "collect_items") {
status("COLLECTING ITEMS");
auto paths = path_to_items();
process_move(paths);
// path to the items and get them all
} else if(action == ai::FINAL_ACTION) {
close_status();
log("Autowalk done, nothing left to do.");
fsm.autowalking = false;
} else {
close_status();
log("Autowalk has a bug. Unknown action.");
fmt::println("Unknown action: {}", action.name);
}
move_attempts++; move_attempts++;
}
} while(move_attempts < 100 && fsm.autowalking); } while(move_attempts < 100 && fsm.autowalking);
} }
@ -254,8 +279,7 @@ void Autowalker::process_move(Pathing& paths) {
if(!path_player(paths, target)) { if(!path_player(paths, target)) {
close_status(); close_status();
log("No paths found, aborting autowalk. You can move now."); log("No paths found, aborting autowalk.");
fsm.autowalking = false;
return; return;
} }
@ -277,9 +301,32 @@ bool Autowalker::player_health_good() {
return float(combat.hp) / float(combat.max_hp) > 0.5f; return float(combat.hp) / float(combat.max_hp) > 0.5f;
} }
int Autowalker::player_item_count() { InventoryStats Autowalker::player_item_count() {
auto inventory = fsm.$level.world->get<components::Inventory>(fsm.$level.player); auto& inventory = fsm.$level.world->get<components::Inventory>(fsm.$level.player);
return inventory.count(); InventoryStats stats;
for(auto& item : inventory.items) {
if(item.data["id"] == "POTION_HEALING_SMALL") {
stats.healing += item.count;
} else {
stats.other += item.count;
}
}
return stats;
}
void Autowalker::player_use_healing() {
auto& inventory = fsm.$level.world->get<components::Inventory>(fsm.$level.player);
// find the healing slot
for(size_t slot = 0; slot < inventory.count(); slot++) {
auto& item = inventory.get(slot);
if(item.data["id"] == "POTION_HEALING_SMALL") {
inventory.use(fsm.$level, slot);
fsm.$status_ui.update();
return;
}
}
} }
void Autowalker::start_autowalk() { void Autowalker::start_autowalk() {

@ -1,7 +1,10 @@
#pragma once #pragma once
#include "ai.hpp"
#include "gui_fsm.hpp" #include "gui_fsm.hpp"
struct InventoryStats;
struct Autowalker { struct Autowalker {
int enemy_count = 0; int enemy_count = 0;
int item_count = 0; int item_count = 0;
@ -13,10 +16,15 @@ struct Autowalker {
void autowalk(); void autowalk();
void start_autowalk(); void start_autowalk();
void handle_window_events();
void handle_boss_fight();
void handle_player_walk(ai::State& start, ai::State& goal);
void send_event(gui::Event ev); void send_event(gui::Event ev);
void window_events();
void process_combat(); void process_combat();
bool path_player(Pathing& paths, Point &target_out); bool path_player(Pathing& paths, Point &target_out);
void path_fail(Matrix& bad_paths, Point pos);
Point get_current_position(); Point get_current_position();
void rotate_player(Point current, Point target); void rotate_player(Point current, Point target);
void process_move(Pathing& paths); void process_move(Pathing& paths);
@ -24,7 +32,9 @@ struct Autowalker {
void status(std::string msg); void status(std::string msg);
void close_status(); void close_status();
bool player_health_good(); bool player_health_good();
int player_item_count(); void player_use_healing();
InventoryStats player_item_count();
ai::State update_state(ai::State start);
Pathing path_to_enemies(); Pathing path_to_enemies();
Pathing path_to_items(); Pathing path_to_items();

@ -52,6 +52,8 @@ namespace components {
if(item.count == 0) return {false, item.data["name"]}; if(item.count == 0) return {false, item.data["name"]};
dbc::log("INVENTORY IS HARDCODED YOU FUCKING MORON!!!!!");
if(item.data["id"] == "SWORD_RUSTY") { if(item.data["id"] == "SWORD_RUSTY") {
auto weapon = components::get<components::Weapon>(item.data); auto weapon = components::get<components::Weapon>(item.data);
player_combat.damage = weapon.damage; player_combat.damage = weapon.damage;

@ -78,13 +78,14 @@ bool Pathing::random_walk(Point &out, bool random, int direction, size_t dir_cou
bool zero_found = false; bool zero_found = false;
dbc::check(dir_count == 4 || dir_count == 8, "Only 8 or 4 directions allowed."); dbc::check(dir_count == 4 || dir_count == 8, "Only 8 or 4 directions allowed.");
// just make a list of the four directions // first 4 directions are n/s/e/w for most enemies
std::array<Point, 8> dirs{{ std::array<Point, 8> dirs{{
{out.x,out.y-1}, // north {out.x,out.y-1}, // north
{out.x+1,out.y}, // east {out.x+1,out.y}, // east
{out.x,out.y+1}, // south {out.x,out.y+1}, // south
{out.x-1,out.y}, // west {out.x-1,out.y}, // west
// the player and some enemies are more "agile"
{out.x+1,out.y-1}, // north east {out.x+1,out.y-1}, // north east
{out.x+1,out.y+1}, // south east {out.x+1,out.y+1}, // south east
{out.x-1,out.y+1}, // south west {out.x-1,out.y+1}, // south west
@ -96,14 +97,14 @@ bool Pathing::random_walk(Point &out, bool random, int direction, size_t dir_cou
// pick a random start of directions // pick a random start of directions
// BUG: is uniform inclusive of the dir.size()? // BUG: is uniform inclusive of the dir.size()?
int rand_start = Random::uniform<int>(0, dirs.size()); int rand_start = Random::uniform<int>(0, dir_count);
// go through all possible directions // go through all possible directions
for(size_t i = 0; i < dir_count; i++) { for(size_t i = 0; i < dir_count; i++) {
// but start at the random start, effectively randomizing // but start at the random start, effectively randomizing
// which valid direction to go // which valid direction to go
// BUG: this might be wrong given the above ranom from 0-size // BUG: this might be wrong given the above ranom from 0-size
Point dir = dirs[(i + rand_start) % dirs.size()]; Point dir = dirs[(i + rand_start) % dir_count];
if(!shiterator::inbounds($paths, dir.x, dir.y)) continue; //skip unpathable stuff if(!shiterator::inbounds($paths, dir.x, dir.y)) continue; //skip unpathable stuff
int weight = cur - $paths[dir.y][dir.x]; int weight = cur - $paths[dir.y][dir.x];

@ -34,18 +34,28 @@ void System::lighting(GameLevel &level) {
}); });
} }
void System::enemy_ai(GameLevel &level) {
(void)level;
// AI: look up Enemy::actions in ai.json
// AI: setup the state
// AI: process it and keep the next action in the world
}
void System::enemy_pathing(GameLevel &level) { void System::enemy_pathing(GameLevel &level) {
auto &world = *level.world; auto &world = *level.world;
auto &map = *level.map; auto &map = *level.map;
auto player = world.get_the<Player>(); auto player = world.get_the<Player>();
// TODO: this will be on each enemy not a global thing
const auto &player_position = world.get<Position>(player.entity); const auto &player_position = world.get<Position>(player.entity);
map.set_target(player_position.location); map.set_target(player_position.location);
map.make_paths(); map.make_paths();
world.query<Position, Motion>([&](auto ent, auto &position, auto &motion) { world.query<Position, Motion>([&](auto ent, auto &position, auto &motion) {
if(ent != player.entity) { if(ent != player.entity) {
// AI: EnemyConfig can be replaced with an AI thing
// AI: after the enemy_ai systems are run we can then look at what
// AI: their next actions is, and if it's pathing do that
dbc::check(world.has<EnemyConfig>(ent), "enemy is missing config"); dbc::check(world.has<EnemyConfig>(ent), "enemy is missing config");
const auto &config = world.get<EnemyConfig>(ent); const auto &config = world.get<EnemyConfig>(ent);
@ -159,8 +169,11 @@ void System::combat(GameLevel &level) {
// this is guaranteed to not return the given position // this is guaranteed to not return the given position
auto [found, nearby] = collider.neighbors(player_position.location); auto [found, nearby] = collider.neighbors(player_position.location);
if(found) { if(found) {
for(auto entity : nearby) { for(auto entity : nearby) {
// AI: process AI combat actions here
if(world.has<Combat>(entity)) { if(world.has<Combat>(entity)) {
auto& enemy_combat = world.get<Combat>(entity); auto& enemy_combat = world.get<Combat>(entity);
@ -196,6 +209,8 @@ void System::collision(GameLevel &level) {
auto [found, nearby] = collider.neighbors(player_position.location); auto [found, nearby] = collider.neighbors(player_position.location);
int combat_count = 0; int combat_count = 0;
// AI: I think also this would a possible place to run AI decisions
// BUG: this logic is garbage, needs a refactor // BUG: this logic is garbage, needs a refactor
for(auto entity : nearby) { for(auto entity : nearby) {
if(world.has<Combat>(entity)) { if(world.has<Combat>(entity)) {

@ -12,6 +12,7 @@ namespace System {
void collision(GameLevel &level); void collision(GameLevel &level);
void death(GameLevel &level, components::ComponentMap& components); void death(GameLevel &level, components::ComponentMap& components);
void enemy_pathing(GameLevel &level); void enemy_pathing(GameLevel &level);
void enemy_ai(GameLevel &level);
void init_positions(DinkyECS::World &world, SpatialMap &collider); void init_positions(DinkyECS::World &world, SpatialMap &collider);
void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);

@ -130,7 +130,6 @@ TEST_CASE("ai as a module like sound/sprites", "[ai]") {
TEST_CASE("ai autowalker ai test", "[ai]") { TEST_CASE("ai autowalker ai test", "[ai]") {
ai::reset(); ai::reset();
ai::init("assets/ai.json"); ai::init("assets/ai.json");
ai::AIProfile* profile = ai::profile();
auto start = ai::load_state("Walker::initial_state"); auto start = ai::load_state("Walker::initial_state");
auto goal = ai::load_state("Walker::final_state"); auto goal = ai::load_state("Walker::final_state");
int enemy_count = 5; int enemy_count = 5;
@ -141,7 +140,7 @@ TEST_CASE("ai autowalker ai test", "[ai]") {
auto a_plan = ai::plan("Walker::actions", start, goal); auto a_plan = ai::plan("Walker::actions", start, goal);
REQUIRE(!a_plan.complete); REQUIRE(!a_plan.complete);
auto result = ai::dump_script(*profile, "\n\nWALKER KILL STUFF", start, a_plan.script); auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script);
REQUIRE(ai::test(result, "enemy_found")); REQUIRE(ai::test(result, "enemy_found"));
REQUIRE(ai::test(result, "enemy_dead")); REQUIRE(ai::test(result, "enemy_dead"));
REQUIRE(!ai::test(result, "no_more_enemies")); REQUIRE(!ai::test(result, "no_more_enemies"));
@ -150,10 +149,12 @@ TEST_CASE("ai autowalker ai test", "[ai]") {
ai::set(result, "health_good", false); ai::set(result, "health_good", false);
ai::set(result, "in_combat", false); ai::set(result, "in_combat", false);
ai::set(result, "enemy_found", 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")); REQUIRE(!ai::test(result, "health_good"));
auto health_plan = ai::plan("Walker::actions", result, goal); auto health_plan = ai::plan("Walker::actions", result, goal);
result = ai::dump_script(*profile, "\n\nWALKER NEED HEALTH", result, health_plan.script); result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script);
REQUIRE(!health_plan.complete); REQUIRE(!health_plan.complete);
REQUIRE(ai::test(result, "health_good")); REQUIRE(ai::test(result, "health_good"));
@ -162,7 +163,7 @@ TEST_CASE("ai autowalker ai test", "[ai]") {
REQUIRE(ai::test(result, "no_more_enemies")); REQUIRE(ai::test(result, "no_more_enemies"));
auto new_plan = ai::plan("Walker::actions", result, goal); auto new_plan = ai::plan("Walker::actions", result, goal);
result = ai::dump_script(*profile, "\n\nWALKER COMPLETE", result, new_plan.script); result = ai::dump_script("\n\nWALKER COMPLETE", result, new_plan.script);
REQUIRE(new_plan.complete); REQUIRE(new_plan.complete);
REQUIRE(ai::test(result, "enemy_found")); REQUIRE(ai::test(result, "enemy_found"));

Loading…
Cancel
Save