diff --git a/ai.cpp b/ai.cpp index 14f292e..87b8758 100644 --- a/ai.cpp +++ b/ai.cpp @@ -22,17 +22,17 @@ namespace ai { Action result(config["name"], config["cost"]); check(config.contains("needs"), - fmt::format("config_action: no 'needs' field", result.$name)); + fmt::format("config_action: no 'needs' field", result.name)); check(config.contains("effects"), - 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()) { - 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 name {}", result.name, name_key)); result.needs(profile.at(name_key), bool(value)); } 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 name {}", result.name, name_key)); result.effect(profile.at(name_key), bool(value)); } @@ -79,7 +79,7 @@ namespace ai { auto& actions = config["actions"]; for(auto& action_vars : actions) { auto the_action = config_action(AIMGR.profile, action_vars); - AIMGR.actions.insert_or_assign(the_action.$name, the_action); + AIMGR.actions.insert_or_assign(the_action.name, the_action); } // load all states diff --git a/ai_debug.cpp b/ai_debug.cpp index 8c93ae2..d808e45 100644 --- a/ai_debug.cpp +++ b/ai_debug.cpp @@ -22,7 +22,7 @@ namespace ai { } void dump_action(AIProfile& profile, Action& action) { - fmt::println(" --ACTION: {}, cost={}", action.$name, action.$cost); + fmt::println(" --ACTION: {}, cost={}", action.name, action.cost); fmt::println(" PRECONDS:"); dump_only(profile, action.$positive_preconds, true, true); @@ -39,7 +39,7 @@ namespace ai { dump_state(profile, start); fmt::print("% ACTIONS PLANNED:"); for(auto& action : script) { - fmt::print("{} ", action.$name); + fmt::print("{} ", action.name); } fmt::print("\n"); diff --git a/autowalker.cpp b/autowalker.cpp index cbbc7dd..063def6 100644 --- a/autowalker.cpp +++ b/autowalker.cpp @@ -89,9 +89,12 @@ bool Autowalker::path_player(Pathing& paths, Point& target_out) { bool found = paths.random_walk(target_out, false, PATHING_TOWARD); if(!found) { - dbc::log("no neighbor found in any direction, aborting autowalk"); - matrix::dump("NO TOWARD", paths.$paths, target_out.x, target_out.y); - return false; + // failed to find a linear path, try diagonal + if(!paths.random_walk(target_out, false, PATHING_TOWARD, MOVE_DIAGONAL)) { + dbc::log("couldn't find a diagonal direction"); + matrix::dump("MOVE FAIL PATHS", paths.$paths, target_out.x, target_out.y); + return false; + } } if(!fsm.$level.map->can_move(target_out)) { @@ -111,6 +114,11 @@ void Autowalker::rotate_player(Point current, Point target) { int facing = fsm.$main_ui.$compass_dir; int target_facing = 0; + /* This is a massive pile of garbage. Need a way + * to determine player facing direction without + * hacking into the compass, and also do accurate + * turns. + */ if(delta_x == -1 && delta_y == 0) { // west target_facing = 4; @@ -123,9 +131,21 @@ void Autowalker::rotate_player(Point current, Point target) { } else if(delta_x == 0 && delta_y == -1) { // north target_facing = 6; + } else if(delta_x == 1 && delta_y == -1) { + // north east + target_facing = 5; + } else if(delta_x == 1 && delta_y == 1) { + // south east + target_facing = 1; + } else if(delta_x == -1 && delta_y == 1) { + // south west + target_facing = 3; + } else if(delta_x == -1 && delta_y == -1) { + // north west + target_facing = 5; } else { dbc::sentinel( - fmt::format("got more than 4 direction result: " + fmt::format("got more than 8 direction result: " "current={},{} " "target={},{} " "delta={},{} ", @@ -197,31 +217,30 @@ void Autowalker::autowalk() { // need a test for plan complete and only action is END 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 fmt::println("FINDING AN ENEMY"); auto paths = path_to_enemies(); - auto pos = get_current_position(); - matrix::dump("FINDING", paths.$paths, pos.x, pos.y); process_move(paths); - } else if(action.$name == "kill_enemy") { + send_event(gui::Event::ATTACK); + } else if(action.name == "kill_enemy") { fmt::println("KILLING ENEMY"); process_combat(); - } else if(action.$name == "find_healing") { + } else if(action.name == "find_healing") { fmt::println("FINDING HEALING"); auto paths = path_to_items(); process_move(paths); // do the path to healing thing - } else if(action.$name == "collect_items") { + } else if(action.name == "collect_items") { fmt::println("COLLECTING ITEMS"); auto paths = path_to_items(); process_move(paths); // path to the items and get them all - } else if(action.$name == "END") { + } else if(action == ai::FINAL_ACTION) { fmt::println("END STATE, complete? {}", a_plan.complete); fsm.autowalking = false; } else { - fmt::println("Unknown action: {}", action.$name); + fmt::println("Unknown action: {}", action.name); } move_attempts++; diff --git a/goap.cpp b/goap.cpp index 931bfa2..a6736b6 100644 --- a/goap.cpp +++ b/goap.cpp @@ -31,6 +31,11 @@ namespace ai { } } + void Action::ignore(int name) { + $positive_preconds[name] = false; + $negative_preconds[name] = false; + } + bool Action::can_effect(State& state) { return ((state & $positive_preconds) == $positive_preconds) && @@ -41,16 +46,15 @@ namespace ai { return (state | $positive_effects) & ~$negative_effects; } - int distance_to_goal(State& from, State& to) { + int distance_to_goal(State from, State to, Action& action) { auto result = from ^ to; - return result.count(); + return result.count() + action.cost; } Script reconstruct_path(std::unordered_map& came_from, Action& current) { Script total_path{current}; - int count = 0; - while(came_from.contains(current) && count++ < 10) { + while(came_from.contains(current)) { current = came_from.at(current); if(current != FINAL_ACTION) { total_path.push_front(current); @@ -60,12 +64,12 @@ namespace ai { return total_path; } - inline int h(State& start, State& goal) { - return distance_to_goal(start, goal); + inline int h(State start, State goal, Action& action) { + return distance_to_goal(start, goal, action); } - inline int d(State& start, State& goal) { - return distance_to_goal(start, goal); + inline int d(State start, State goal, Action& action) { + return distance_to_goal(start, goal, action); } ActionState find_lowest(std::unordered_map& open_set) { @@ -90,7 +94,7 @@ namespace ai { ActionState current{FINAL_ACTION, start}; g_score[start] = 0; - open_set[current] = g_score[start] + h(start, goal); + open_set[current] = g_score[start] + h(start, goal, current.action); while(!open_set.empty()) { current = find_lowest(open_set); @@ -109,7 +113,7 @@ namespace ai { } auto neighbor = neighbor_action.apply_effect(current.state); - int d_score = d(current.state, neighbor); + int d_score = d(current.state, neighbor, current.action); int tentative_g_score = g_score[current.state] + d_score; int neighbor_g_score = g_score.contains(neighbor) ? g_score[neighbor] : SCORE_MAX; if(tentative_g_score < neighbor_g_score) { @@ -118,7 +122,7 @@ namespace ai { g_score[neighbor] = tentative_g_score; // open_set gets the fScore ActionState neighbor_as{neighbor_action, neighbor}; - open_set[neighbor_as] = tentative_g_score + h(neighbor, goal); + open_set[neighbor_as] = tentative_g_score + h(neighbor, goal, neighbor_as.action); } } } diff --git a/goap.hpp b/goap.hpp index d02634b..e01234f 100644 --- a/goap.hpp +++ b/goap.hpp @@ -12,6 +12,7 @@ namespace ai { using AIProfile = std::unordered_map; constexpr const int SCORE_MAX = std::numeric_limits::max(); + constexpr const size_t STATE_MAX = 32; using State = std::bitset; @@ -20,8 +21,8 @@ namespace ai { const State ALL_ONES = ~ALL_ZERO; struct Action { - std::string $name; - int $cost = 0; + std::string name; + int cost = 0; State $positive_preconds; State $negative_preconds; @@ -30,16 +31,17 @@ namespace ai { State $negative_effects; Action(std::string name, int cost) : - $name(name), $cost(cost) { } + name(name), cost(cost) { } void needs(int name, bool val); void effect(int name, bool val); + void ignore(int name); bool can_effect(State& state); State apply_effect(State& state); bool operator==(const Action& other) const { - return other.$name == $name; + return other.name == name; } }; @@ -66,14 +68,14 @@ namespace ai { bool is_subset(State& source, State& target); - int distance_to_goal(State& from, State& to); + int distance_to_goal(State from, State to, Action& action); ActionPlan plan_actions(std::vector& actions, State start, State goal); } template<> struct std::hash { size_t operator()(const ai::Action& p) const { - return std::hash{}(p.$name); + return std::hash{}(p.name); } }; diff --git a/matrix.cpp b/matrix.cpp index 73f1c23..c32c036 100644 --- a/matrix.cpp +++ b/matrix.cpp @@ -19,8 +19,10 @@ namespace matrix { print("{:x}<", cell); } else if(cell == WALL_PATH_LIMIT) { print("# "); - } else if(cell > 15) { - print("[{:x}]", cell); + } else if(cell > 15 && cell < 32) { + print("{:x}+", cell - 16); + } else if(cell > 31) { + print("* "); } else { print("{:x} ", cell); } diff --git a/pathing.cpp b/pathing.cpp index c58f3e6..369ee4a 100644 --- a/pathing.cpp +++ b/pathing.cpp @@ -74,15 +74,21 @@ void Pathing::clear_target(const Point &at) { $input[at.y][at.x] = 1; } -bool Pathing::random_walk(Point &out, bool random, int direction) { +bool Pathing::random_walk(Point &out, bool random, int direction, size_t dir_count) { bool zero_found = false; + dbc::check(dir_count == 4 || dir_count == 8, "Only 8 or 4 directions allowed."); // just make a list of the four directions - std::array dirs{{ + std::array dirs{{ {out.x,out.y-1}, // north {out.x+1,out.y}, // east {out.x,out.y+1}, // south - {out.x-1,out.y} // west + {out.x-1,out.y}, // west + + {out.x+1,out.y-1}, // north east + {out.x+1,out.y+1}, // south east + {out.x-1,out.y+1}, // south west + {out.x-1,out.y-1} // north west }}; // get the current dijkstra number @@ -93,7 +99,7 @@ bool Pathing::random_walk(Point &out, bool random, int direction) { int rand_start = Random::uniform(0, dirs.size()); // go through all possible directions - for(size_t i = 0; i < dirs.size(); i++) { + for(size_t i = 0; i < dir_count; i++) { // but start at the random start, effectively randomizing // which valid direction to go // BUG: this might be wrong given the above ranom from 0-size diff --git a/pathing.hpp b/pathing.hpp index f13150f..c55acf1 100644 --- a/pathing.hpp +++ b/pathing.hpp @@ -7,6 +7,7 @@ using matrix::Matrix; constexpr const int PATHING_TOWARD=1; constexpr const int PATHING_AWAY=-1; +constexpr const int MOVE_DIAGONAL=8; class Pathing { public: @@ -28,7 +29,7 @@ public: Matrix &paths() { return $paths; } Matrix &input() { return $input; } int distance(Point to) { return $paths[to.y][to.x];} - bool random_walk(Point &out, bool random, int direction); + bool random_walk(Point &out, bool random, int direction, size_t dir_count = 4); bool INVARIANT(); }; diff --git a/tests/ai.cpp b/tests/ai.cpp index 353fb9b..0a689d2 100644 --- a/tests/ai.cpp +++ b/tests/ai.cpp @@ -7,7 +7,7 @@ using namespace dbc; using namespace nlohmann; -TEST_CASE("worldstate works", "[ai]") { +TEST_CASE("state and actions work", "[ai]") { enum StateNames { ENEMY_IN_RANGE, ENEMY_DEAD @@ -36,7 +36,7 @@ TEST_CASE("worldstate works", "[ai]") { // 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) == 1); + 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); @@ -48,7 +48,10 @@ TEST_CASE("worldstate works", "[ai]") { 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) == 1); + 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); @@ -117,7 +120,7 @@ TEST_CASE("ai as a module like sound/sprites", "[ai]") { auto state = start; for(auto& action : a_plan.script) { - fmt::println("ACTION: {}", action.$name); + fmt::println("ACTION: {}", action.name); state = action.apply_effect(state); }