#include "autowalker.hpp" #include "ai_debug.hpp" #include "gui/ritual_ui.hpp" #include "game_level.hpp" #define DEBUG struct InventoryStats { int healing = 0; int other = 0; }; template int number_left() { int count = 0; auto world = GameDB::current_world(); auto player = GameDB::the_player(); world->query( [&](const auto ent, auto&, auto&) { if(ent != player) { count++; } }); return count; } template Pathing compute_paths() { auto& level = GameDB::current_level(); auto& walls_original = level.map->$walls; auto walls_copy = walls_original; Pathing paths{matrix::width(walls_copy), matrix::height(walls_copy)}; // first, put everything of this type as a target level.world->query( [&](const auto ent, auto& position, auto&) { if(ent != level.player) { paths.set_target(position.location); } }); level.world->query( [&](const auto ent, auto& collision) { if(collision.has) { auto& pos = level.world->get(ent); walls_copy[pos.location.y][pos.location.x] = WALL_VALUE; } }); paths.compute_paths(walls_copy); auto pos = GameDB::player_position().location; matrix::dump("compute_paths walls", walls_copy, pos.x, pos.y); matrix::dump("compute_paths input", paths.$input, pos.x, pos.y); matrix::dump("compute_paths paths", paths.$paths, pos.x, pos.y); return paths; } DinkyECS::Entity Autowalker::camera_aim() { auto& level = GameDB::current_level(); // what happens if there's two things at that spot if(level.collision->something_there(fsm.$main_ui.$rayview->aiming_at)) { return level.collision->get(fsm.$main_ui.$rayview->aiming_at); } else { return DinkyECS::NONE; } } void Autowalker::log(std::wstring msg) { fsm.$map_ui.log(msg); } void Autowalker::status(std::wstring 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() { return compute_paths(); } Pathing Autowalker::path_to_items() { return compute_paths(); } void Autowalker::handle_window_events() { fsm.$window.handleEvents( [&](const sf::Event::KeyPressed &) { fsm.autowalking = false; close_status(); log(L"Aborting autowalk."); }, [&](const sf::Event::MouseButtonPressed &) { fsm.autowalking = false; close_status(); log(L"Aborting autowalk."); } ); } void Autowalker::process_combat() { while(fsm.in_state(gui::State::IN_COMBAT) || fsm.in_state(gui::State::ATTACKING)) { if(fsm.in_state(gui::State::ATTACKING)) { send_event(gui::Event::TICK); } else { send_event(gui::Event::ATTACK); } } } void Autowalker::path_fail(const std::string& msg, Matrix& bad_paths, Point pos) { status(L"PATH FAIL"); #ifdef DEBUG matrix::dump("MOVE FAIL PATHS", bad_paths, pos.x, pos.y); dbc::sentinel(fmt::format("[{}]: Autowalk failed to find a path.", msg)); #else log(L"Autowalk failed to find a path."); (void)bad_paths; // shutup compiler errors (void)pos; (void)msg; send_event(gui::Event::STAIRS_DOWN); #endif } bool Autowalker::path_player(Pathing& paths, Point& target_out) { auto &level = GameDB::current_level(); auto found = paths.find_path(target_out, PATHING_TOWARD, false); if(found == PathingResult::FAIL) { // failed to find a linear path, try diagonal if(paths.find_path(target_out, PATHING_TOWARD, true) == PathingResult::FAIL) { path_fail("random_walk", paths.$paths, target_out); return false; } } if(!level.map->can_move(target_out)) { fmt::println("----- FAIL MAP IS, cell is {}", paths.$paths[target_out.y][target_out.x]); level.map->dump(target_out.x, target_out.y); path_fail("level_map->can_move", paths.$paths, target_out); return false; } return true; } void Autowalker::rotate_player(Point target) { // auto dir = facing > target_facing ? gui::Event::ROTATE_LEFT : gui::Event::ROTATE_RIGHT; auto dir = gui::Event::ROTATE_LEFT; fmt::println("ROTATE TO: {},{} aim is {},{}", target.x, target.y, rayview->aiming_at.x, rayview->aiming_at.y); while(rayview->aiming_at != target) { send_event(dir); while(fsm.in_state(gui::State::ROTATING)) send_event(gui::Event::TICK); } dbc::check(rayview->aiming_at == target, "failed to aim at target"); } ai::State Autowalker::update_state(ai::State start) { int enemy_count = number_left(); int item_count = number_left(); ai::set(start, "no_more_enemies", enemy_count == 0); ai::set(start, "no_more_items", item_count == 0); // BUG: so isn't this wrong? we "find" an enemy when we are aiming at one ai::set(start, "enemy_found", found_enemy()); 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("Host::actions", start, goal); auto action = a_plan.script.front(); ai::dump_script("AUTOWALK", start, a_plan.script); if(action.name == "find_enemy") { status(L"FINDING ENEMY"); auto paths = path_to_enemies(); process_move(paths); face_enemy(); } else if(action.name == "face_enemy") { face_enemy(); } else if(action.name == "kill_enemy") { status(L"KILLING ENEMY"); if(fsm.in_state(gui::State::IN_COMBAT)) { if(face_enemy()) { process_combat(); } } } else if(action.name == "use_healing") { status(L"USING HEALING"); player_use_healing(); } else if(action.name == "collect_items") { status(L"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(L"FINAL ACTION! Autowalk done."); fsm.autowalking = false; } else { close_status(); dbc::log(fmt::format("Unknown action: {}", action.name)); } } void Autowalker::craft_weapon() { if(!weapon_crafted) { auto& ritual_ui = fsm.$status_ui.$ritual_ui; fsm.$status_ui.$gui.click_on("ritual_ui"); while(!ritual_ui.in_state(gui::ritual::State::OPENED)) { send_event(gui::Event::TICK); } ritual_ui.$gui.click_on("inv_slot0"); send_event(gui::Event::TICK); ritual_ui.$gui.click_on("inv_slot1"); send_event(gui::Event::TICK); while(!ritual_ui.in_state(gui::ritual::State::CRAFTING)) { send_event(gui::Event::TICK); } ritual_ui.$gui.click_on("result_image", true); send_event(gui::Event::TICK); ritual_ui.$gui.click_on("ritual_ui"); send_event(gui::Event::TICK); weapon_crafted = true; } } void Autowalker::open_map() { if(map_opened_once) return; if(!fsm.$map_open) { send_event(gui::Event::MAP_OPEN); map_opened_once = true; } } void Autowalker::autowalk() { handle_window_events(); if(!fsm.autowalking) { close_status(); return; } craft_weapon(); open_map(); int move_attempts = 0; auto start = ai::load_state("Host::initial_state"); auto goal = ai::load_state("Host::final_state"); do { handle_window_events(); handle_boss_fight(); handle_player_walk(start, goal); if(map_opened_once && move_attempts > 20) send_event(gui::Event::MAP_OPEN); move_attempts++; } while(move_attempts < 100 && fsm.autowalking); } void Autowalker::process_move(Pathing& paths) { auto world = GameDB::current_world(); // target has to start at the player location then... auto target = GameDB::player_position().location; // ... target gets modified as an out parameter to find the path if(!path_player(paths, target)) { close_status(); log(L"No paths found, aborting autowalk."); return; } if(rayview->aiming_at != target) rotate_player(target); send_event(gui::Event::MOVE_FORWARD); while(fsm.in_state(gui::State::MOVING)) send_event(gui::Event::TICK); } bool Autowalker::found_enemy() { auto world = GameDB::current_world(); auto aimed_at = camera_aim(); return aimed_at != DinkyECS::NONE && world->has(aimed_at); } bool Autowalker::found_item() { auto world = GameDB::current_world(); auto aimed_at = camera_aim(); return aimed_at != DinkyECS::NONE && world->has(aimed_at); } void Autowalker::send_event(gui::Event ev) { fsm.event(ev); fsm.render(); fsm.handle_world_events(); } bool Autowalker::player_health_good() { auto world = GameDB::current_world(); auto player = GameDB::the_player(); auto combat = world->get(player); return float(combat.hp) / float(combat.max_hp) > 0.5f; } InventoryStats Autowalker::player_item_count() { InventoryStats stats; stats.healing = 0; return stats; } void Autowalker::player_use_healing() { } void Autowalker::start_autowalk() { fsm.autowalking = true; } bool Autowalker::face_enemy() { auto& level = GameDB::current_level(); auto player_at = GameDB::player_position(); auto [found, neighbors] = level.collision->neighbors(player_at.location, true); if(found) { fmt::println("FOUND ENEMIES:"); for(auto& ent : neighbors) { auto enemy_pos = level.world->get(ent); fmt::println("\t{}={},{}", ent, enemy_pos.location.x, enemy_pos.location.y); } auto enemy_pos = level.world->get(neighbors[0]); if(rayview->aiming_at != enemy_pos.location) rotate_player(enemy_pos.location); } else { dbc::log("No enemies nearby, moving on."); } return found; }