Autowalker AI now knows when it has items, and knows it should find healing, but it's not working quite yet.

master
Zed A. Shaw 11 months ago
parent d15c9b12fd
commit 0623170dbc
  1. 4
      ai.cpp
  2. 37
      assets/ai.json
  3. 95
      autowalker.cpp
  4. 7
      autowalker.hpp
  5. 1
      gui_fsm.hpp
  6. 2
      tests/ai.cpp

@ -27,12 +27,12 @@ namespace ai {
fmt::format("config_action: no 'effects' field", result.name)); fmt::format("config_action: no 'effects' field", result.name));
for(auto& [name_key, value] : config["needs"].items()) { for(auto& [name_key, value] : config["needs"].items()) {
check(profile.contains(name_key), fmt::format("config_action: profile does not have name {}", result.name, name_key)); check(profile.contains(name_key), fmt::format("config_action({}): profile does not have need named {}", result.name, name_key));
result.needs(profile.at(name_key), bool(value)); result.needs(profile.at(name_key), bool(value));
} }
for(auto& [name_key, value] : config["effects"].items()) { for(auto& [name_key, value] : config["effects"].items()) {
check(profile.contains(name_key), fmt::format("config_action: profile does not have name {}", result.name, name_key)); check(profile.contains(name_key), fmt::format("config_action({}): profile does not have effect named {}", result.name, name_key));
result.effect(profile.at(name_key), bool(value)); result.effect(profile.at(name_key), bool(value));
} }

@ -4,13 +4,17 @@
"enemy_dead": 1, "enemy_dead": 1,
"health_good": 2, "health_good": 2,
"no_more_items": 3, "no_more_items": 3,
"no_more_enemies": 4 "no_more_enemies": 4,
"in_combat": 5,
"have_item": 6,
"have_healing": 7
}, },
"actions": [ "actions": [
{ {
"name": "find_enemy", "name": "find_enemy",
"cost": 5, "cost": 5,
"needs": { "needs": {
"in_combat": false,
"no_more_enemies": false, "no_more_enemies": false,
"health_good": true, "health_good": true,
"enemy_found": false "enemy_found": false
@ -45,14 +49,39 @@
}, },
{ {
"name": "find_healing", "name": "find_healing",
"cost": 5, "cost": 0,
"needs": { "needs": {
"enemy_found": false,
"in_combat": false,
"health_good": false, "health_good": false,
"no_more_items": false "no_more_items": false
}, },
"effects": { "effects": {
"health_good": true "health_good": true
} }
},
{
"name": "use_item",
"cost": 0,
"needs": {
"have_item": true,
"health_good": true
},
"effects": {
"have_item": false
}
},
{
"name": "use_healing",
"cost": 0,
"needs": {
"have_item": true,
"have_healing": true,
"health_good": false
},
"effects": {
"health_good": true
}
} }
], ],
"states": { "states": {
@ -76,6 +105,8 @@
["find_enemy", ["find_enemy",
"kill_enemy", "kill_enemy",
"find_healing", "find_healing",
"collect_items"] "collect_items",
"use_item",
"use_healing"]
} }
} }

@ -40,6 +40,19 @@ Pathing compute_paths(gui::FSM& fsm) {
return paths; return paths;
} }
void Autowalker::log(std::string msg) {
fmt::println(">>> AUTOWALK: {}", msg);
fsm.$status_ui.log(msg);
}
void Autowalker::status(std::string msg) {
fsm.$main_ui.$overlay_ui.show_text("bottom", msg);
}
void Autowalker::close_status() {
fsm.$main_ui.$overlay_ui.close_text("bottom");
}
Pathing Autowalker::path_to_enemies() { Pathing Autowalker::path_to_enemies() {
return compute_paths<components::Combat>(fsm); return compute_paths<components::Combat>(fsm);
} }
@ -57,11 +70,13 @@ void Autowalker::window_events() {
fsm.$window.handleEvents( fsm.$window.handleEvents(
[&](const sf::Event::KeyPressed &) { [&](const sf::Event::KeyPressed &) {
fsm.autowalking = false; fsm.autowalking = false;
fmt::println("ABORT AUTOWALK"); close_status();
log("Aborting autowalk. You can move now.");
}, },
[&](const sf::Event::MouseButtonPressed &) { [&](const sf::Event::MouseButtonPressed &) {
fsm.autowalking = false; fsm.autowalking = false;
fmt::println("ABORT AUTOWALK"); close_status();
log("Aborting autowalk. You can move now.");
} }
); );
} }
@ -71,10 +86,8 @@ void Autowalker::process_combat() {
|| fsm.in_state(gui::State::ATTACKING)) || fsm.in_state(gui::State::ATTACKING))
{ {
if(fsm.in_state(gui::State::ATTACKING)) { if(fsm.in_state(gui::State::ATTACKING)) {
fmt::println("In attacking state, sending a TICK");
send_event(gui::Event::TICK); send_event(gui::Event::TICK);
} else { } else {
fmt::println("Not in ATTACK, sending an ATTACK to continue combat.");
send_event(gui::Event::ATTACK);; send_event(gui::Event::ATTACK);;
} }
} }
@ -91,14 +104,16 @@ bool Autowalker::path_player(Pathing& paths, Point& target_out) {
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)) {
dbc::log("couldn't find a diagonal direction"); status("PATH FAIL");
log("Autowalk failed to find a path.");
matrix::dump("MOVE FAIL PATHS", paths.$paths, target_out.x, target_out.y); 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)) {
dbc::log("neighbors is telling me to go to a bad spot."); status("PATH FAIL");
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 PATHS", paths.$paths, target_out.x, target_out.y);
matrix::dump("BAD TARGET MAP", fsm.$level.map->walls(), 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;
@ -133,7 +148,7 @@ void Autowalker::rotate_player(Point current, Point target) {
target_facing = 6; target_facing = 6;
} else if(delta_x == 1 && delta_y == -1) { } else if(delta_x == 1 && delta_y == -1) {
// north east // north east
target_facing = 5; target_facing = 7;
} else if(delta_x == 1 && delta_y == 1) { } else if(delta_x == 1 && delta_y == 1) {
// south east // south east
target_facing = 1; target_facing = 1;
@ -165,35 +180,12 @@ void Autowalker::rotate_player(Point current, Point target) {
"player isn't facing the correct direction"); "player isn't facing the correct direction");
} }
void Autowalker::show_map_overlay(matrix::Matrix& map, Point current) {
auto debug = fsm.$level.world->get_the<components::Debug>();
if(!debug.FPS) {
fsm.$main_ui.$overlay_ui.close_text("top_right");
return;
}
std::string map_overlay;
for(matrix::box it{map, current.x, current.y, 6}; it.next();) {
if(it.x == it.left) map_overlay += "\n";
int cell = map[it.y][it.x];
if(it.x == current.x && it.y == current.y) {
map_overlay += fmt::format("{:x}<", cell);
} else if(cell == WALL_PATH_LIMIT) {
map_overlay += fmt::format("# ");
} else if(cell > 15) {
map_overlay += fmt::format("* ");
} else {
map_overlay += fmt::format("{:x} ", cell);
}
}
fsm.$main_ui.$overlay_ui.show_text("top_right", map_overlay);
}
void Autowalker::autowalk() { void Autowalker::autowalk() {
window_events(); window_events();
if(!fsm.autowalking) return; if(!fsm.autowalking) {
close_status();
return;
}
int move_attempts = 0; int move_attempts = 0;
@ -204,14 +196,17 @@ void Autowalker::autowalk() {
int enemy_count = number_left<components::Combat>(fsm); int enemy_count = number_left<components::Combat>(fsm);
int item_count = number_left<components::InventoryItem>(fsm); int item_count = number_left<components::InventoryItem>(fsm);
fmt::println("ENEMY COUNT: {}, ITEM COUNT: {}", enemy_count, item_count);
window_events(); window_events();
ai::set(start, "no_more_enemies", enemy_count == 0); ai::set(start, "no_more_enemies", enemy_count == 0);
ai::set(start, "no_more_items", item_count == 0); ai::set(start, "no_more_items", item_count == 0);
ai::set(start, "enemy_found", ai::set(start, "enemy_found",
fsm.in_state(gui::State::IN_COMBAT) || fsm.in_state(gui::State::IN_COMBAT) ||
fsm.in_state(gui::State::ATTACKING)); 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); auto a_plan = ai::plan("Walker::actions", start, goal);
@ -219,27 +214,32 @@ void Autowalker::autowalk() {
for(auto action : a_plan.script) { for(auto action : a_plan.script) {
if(action.name == "find_enemy") { if(action.name == "find_enemy") {
// this is where to test if enemy found and update state // this is where to test if enemy found and update state
fmt::println("FINDING AN ENEMY"); status("FINDING ENEMY");
auto paths = path_to_enemies(); auto paths = path_to_enemies();
process_move(paths); process_move(paths);
send_event(gui::Event::ATTACK); send_event(gui::Event::ATTACK);
} else if(action.name == "use_item") {
status("USE ITEMS");
} else if(action.name == "kill_enemy") { } else if(action.name == "kill_enemy") {
fmt::println("KILLING ENEMY"); status("KILLING ENEMY");
process_combat(); process_combat();
} else if(action.name == "find_healing") { } else if(action.name == "find_healing") {
fmt::println("FINDING HEALING"); status("FINDING HEALING");
auto paths = path_to_items(); auto paths = path_to_items();
process_move(paths); process_move(paths);
// do the path to healing thing // do the path to healing thing
} else if(action.name == "collect_items") { } else if(action.name == "collect_items") {
fmt::println("COLLECTING ITEMS"); status("COLLECTING ITEMS");
auto paths = path_to_items(); auto paths = path_to_items();
process_move(paths); process_move(paths);
// path to the items and get them all // path to the items and get them all
} else if(action == ai::FINAL_ACTION) { } else if(action == ai::FINAL_ACTION) {
fmt::println("END STATE, complete? {}", a_plan.complete); close_status();
log("Autowalk done, nothing left to do.");
fsm.autowalking = false; fsm.autowalking = false;
} else { } else {
close_status();
log("Autowalk has a bug. Unknown action.");
fmt::println("Unknown action: {}", action.name); fmt::println("Unknown action: {}", action.name);
} }
@ -253,7 +253,8 @@ void Autowalker::process_move(Pathing& paths) {
Point target = current; Point target = current;
if(!path_player(paths, target)) { if(!path_player(paths, target)) {
dbc::log("no paths found, aborting autowalk"); close_status();
log("No paths found, aborting autowalk. You can move now.");
fsm.autowalking = false; fsm.autowalking = false;
return; return;
} }
@ -271,6 +272,16 @@ void Autowalker::send_event(gui::Event ev) {
fsm.handle_world_events(); fsm.handle_world_events();
} }
bool Autowalker::player_health_good() {
auto combat = fsm.$level.world->get<components::Combat>(fsm.$level.player);
return float(combat.hp) / float(combat.max_hp) > 0.5f;
}
int Autowalker::player_item_count() {
auto inventory = fsm.$level.world->get<components::Inventory>(fsm.$level.player);
return inventory.count();
}
void Autowalker::start_autowalk() { void Autowalker::start_autowalk() {
fsm.autowalking = true; fsm.autowalking = true;
} }

@ -20,8 +20,13 @@ struct Autowalker {
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);
void log(std::string msg);
void status(std::string msg);
void close_status();
bool player_health_good();
int player_item_count();
Pathing path_to_enemies(); Pathing path_to_enemies();
Pathing path_to_items(); Pathing path_to_items();
Pathing path_to_devices(); Pathing path_to_devices();
void show_map_overlay(matrix::Matrix& map, Point current);
}; };

@ -8,7 +8,6 @@
#include "main_ui.hpp" #include "main_ui.hpp"
#include "combat_ui.hpp" #include "combat_ui.hpp"
#include "status_ui.hpp" #include "status_ui.hpp"
#include "overlay_ui.hpp"
#include "boss_fight_ui.hpp" #include "boss_fight_ui.hpp"
namespace gui { namespace gui {

@ -148,6 +148,8 @@ TEST_CASE("ai autowalker ai test", "[ai]") {
// health is low, go heal // health is low, go heal
ai::set(result, "health_good", false); ai::set(result, "health_good", false);
ai::set(result, "in_combat", false);
ai::set(result, "enemy_found", false);
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);