diff --git a/ai.cpp b/ai.cpp index 913c278..54b7283 100644 --- a/ai.cpp +++ b/ai.cpp @@ -171,10 +171,22 @@ namespace ai { return plan.script[0].name == name; } + bool EntityAI::active() { + if(plan.script.size() == 1) { + return plan.script[0] != FINAL_ACTION; + } else { + return plan.script.size() == 0; + } + } + void EntityAI::set_state(std::string name, bool setting) { ai::set(start, name, setting); } + bool EntityAI::get_state(std::string name) { + return ai::test(start, name); + } + void EntityAI::update() { plan = ai::plan(script, start, goal); } diff --git a/ai.hpp b/ai.hpp index 23a5e1e..6113b52 100644 --- a/ai.hpp +++ b/ai.hpp @@ -24,7 +24,10 @@ namespace ai { bool wants_to(std::string name); + bool active(); + void set_state(std::string name, bool setting); + bool get_state(std::string name); void update(); diff --git a/assets/ai.json b/assets/ai.json index 6ff51d4..fb86176 100644 --- a/assets/ai.json +++ b/assets/ai.json @@ -27,10 +27,11 @@ }, { "name": "kill_enemy", - "cost": 5, + "cost": 10, "needs": { - "tough_personality": true, + "health_good": true, "no_more_enemies": false, + "in_combat": true, "enemy_found": true, "enemy_dead": false }, @@ -78,7 +79,7 @@ } ], "states": { - "Walker::initial_state": { + "Host::initial_state": { "tough_personality": true, "enemy_found": false, "enemy_dead": false, @@ -90,7 +91,7 @@ "have_healing": false, "detect_enemy": true }, - "Walker::final_state": { + "Host::final_state": { "enemy_found": true, "enemy_dead": true, "health_good": true, @@ -114,7 +115,7 @@ } }, "scripts": { - "Walker::actions": + "Host::actions": ["find_enemy", "kill_enemy", "collect_items", diff --git a/autowalker.cpp b/autowalker.cpp index ed3e4aa..4f7d9e7 100644 --- a/autowalker.cpp +++ b/autowalker.cpp @@ -220,7 +220,7 @@ void Autowalker::handle_boss_fight() { void Autowalker::handle_player_walk(ai::State& start, ai::State& goal) { start = update_state(start); - auto a_plan = ai::plan("Walker::actions", start, goal); + auto a_plan = ai::plan("Host::actions", start, goal); auto action = a_plan.script.front(); if(action.name == "find_enemy") { @@ -269,8 +269,8 @@ void Autowalker::autowalk() { int move_attempts = 0; - auto start = ai::load_state("Walker::initial_state"); - auto goal = ai::load_state("Walker::final_state"); + auto start = ai::load_state("Host::initial_state"); + auto goal = ai::load_state("Host::final_state"); do { handle_window_events(); diff --git a/combat.cpp b/combat.cpp index 62b6371..419150b 100644 --- a/combat.cpp +++ b/combat.cpp @@ -13,4 +13,6 @@ namespace components { return my_dmg; } + + } diff --git a/dbc.cpp b/dbc.cpp index 4653617..6b17faf 100644 --- a/dbc.cpp +++ b/dbc.cpp @@ -2,7 +2,7 @@ #include void dbc::log(const string &message, const std::source_location location) { - std::clog << '[' << location.file_name() << ':' + std::cout << '[' << location.file_name() << ':' << location.line() << "|" << location.function_name() << "] " << message << std::endl; diff --git a/inventory.cpp b/inventory.cpp index e9e5620..0aec16c 100644 --- a/inventory.cpp +++ b/inventory.cpp @@ -15,9 +15,6 @@ namespace components { } bool Inventory::has_item(size_t at) { - dbc::log( - fmt::format("requesting item at {}, have {} items in stock", - at, items.size())); return at < items.size(); } diff --git a/rituals.cpp b/rituals.cpp index 48f7596..d727e33 100644 --- a/rituals.cpp +++ b/rituals.cpp @@ -4,6 +4,51 @@ namespace combat { + void BattleEngine::add_enemy(DinkyECS::Entity enemy_id, ai::EntityAI& enemy) { + combatants.insert_or_assign(enemy_id, enemy); + } + + bool BattleEngine::plan() { + int active = 0; + + for(auto& [entity, enemy_ai] : combatants) { + fmt::println("\n\n==== ENTITY {} has AI:", entity); + enemy_ai.dump(); + enemy_ai.set_state("enemy_found", true); + enemy_ai.set_state("in_combat", true); + enemy_ai.update(); + + fmt::println("\n\n---- AFTER UPDATE:"); + enemy_ai.dump(); + + active += enemy_ai.active(); + } + + return active > 0; + } + + void BattleEngine::fight(std::function cb) { + for(auto& [entity, enemy_ai] : combatants) { + if(enemy_ai.wants_to("kill_enemy")) { + cb(entity, enemy_ai); + } else if(!enemy_ai.active()) { + enemy_ai.dump(); + dbc::sentinel("enemy AI ended early, fix your ai.json"); + } else { + dbc::log("enemy doesn't want to fight"); + enemy_ai.dump(); + } + } + } + + void BattleEngine::dump() { + for(auto& [entity, enemy_ai] : combatants) { + fmt::println("\n\n###### ENTITY #{}", entity); + enemy_ai.dump(); + } + } + + RitualEngine::RitualEngine(std::string config_path) : $config(config_path) { @@ -23,12 +68,12 @@ namespace combat { auto& scripts = $config["scripts"]; for(auto& [script_name, action_names] : scripts.items()) { - std::vector the_script; - for(auto name : action_names) { - the_script.push_back($actions.at(name)); - } + std::vector the_script; + for(auto name : action_names) { + the_script.push_back($actions.at(name)); + } - $scripts.insert_or_assign(script_name, the_script); + $scripts.insert_or_assign(script_name, the_script); } } diff --git a/rituals.hpp b/rituals.hpp index a6f4b73..54270e8 100644 --- a/rituals.hpp +++ b/rituals.hpp @@ -2,8 +2,20 @@ #include "goap.hpp" #include "ai.hpp" #include "config.hpp" +#include +#include "dinkyecs.hpp" namespace combat { + + struct BattleEngine { + std::unordered_map combatants; + + void add_enemy(DinkyECS::Entity enemy_id, ai::EntityAI& enemy); + bool plan(); + void fight(std::function cb); + void dump(); + }; + struct RitualAI { std::string script; ai::State start; diff --git a/systems.cpp b/systems.cpp index e0f1b97..1a2cccc 100644 --- a/systems.cpp +++ b/systems.cpp @@ -61,7 +61,7 @@ void System::enemy_ai_initialize(GameLevel &level) { auto ai_goal = ai::load_state(config.ai_goal_name); ai::EntityAI enemy(config.ai_script, ai_start, ai_goal); - auto&personality = world.get(ent); + auto& personality = world.get(ent); enemy.set_state("tough_personality", personality.tough); enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance); diff --git a/tests/ai.cpp b/tests/ai.cpp index 5ad2367..4e3c8be 100644 --- a/tests/ai.cpp +++ b/tests/ai.cpp @@ -131,14 +131,14 @@ TEST_CASE("ai as a module like sound/sprites", "[ai]") { TEST_CASE("ai autowalker ai test", "[ai]") { ai::reset(); ai::init("assets/ai.json"); - auto start = ai::load_state("Walker::initial_state"); - auto goal = ai::load_state("Walker::final_state"); + auto start = ai::load_state("Host::initial_state"); + auto goal = ai::load_state("Host::final_state"); int enemy_count = 5; ai::set(start, "no_more_enemies", enemy_count == 0); // find an enemy and kill them - auto a_plan = ai::plan("Walker::actions", start, goal); + auto a_plan = ai::plan("Host::actions", start, goal); REQUIRE(!a_plan.complete); auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script); @@ -154,7 +154,7 @@ TEST_CASE("ai autowalker ai test", "[ai]") { ai::set(result, "have_item", true); REQUIRE(!ai::test(result, "health_good")); - auto health_plan = ai::plan("Walker::actions", result, goal); + auto health_plan = ai::plan("Host::actions", result, goal); result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script); REQUIRE(!health_plan.complete); REQUIRE(ai::test(result, "health_good")); @@ -163,7 +163,7 @@ TEST_CASE("ai autowalker ai test", "[ai]") { ai::set(result, "no_more_enemies", true); REQUIRE(ai::test(result, "no_more_enemies")); - auto new_plan = ai::plan("Walker::actions", result, goal); + auto new_plan = ai::plan("Host::actions", result, goal); result = ai::dump_script("\n\nWALKER COMPLETE", result, new_plan.script); REQUIRE(new_plan.complete); diff --git a/tests/combat.cpp b/tests/combat.cpp index 4fcb887..b7a64aa 100644 --- a/tests/combat.cpp +++ b/tests/combat.cpp @@ -1,9 +1,39 @@ #include #include #include "rituals.hpp" +#include "fsm.hpp" +#include "dinkyecs.hpp" using namespace combat; -TEST_CASE("turn based combat engine sorted", "[combat]") { - dbc::log("does nothing."); + +TEST_CASE("cause scared rat won't run away bug", "[combat]") { + 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"); + BattleEngine battle; + + DinkyECS::Entity rat_id = 1; + ai::EntityAI rat("Enemy::actions", ai_start, ai_goal); + rat.set_state("tough_personality", false); + rat.set_state("health_good", true); + + battle.add_enemy(rat_id, rat); + + // first confirm that everyone stops fightings + bool active = battle.plan(); + REQUIRE(active); + + // this causes the plan to read END but if you set + // health_good to false it will run_away + + rat.set_state("health_good", false); + active = battle.plan(); + REQUIRE(rat.wants_to("run_away")); + + battle.fight([&](const auto entity, auto& ai) { + fmt::println("\n\n======= FIGHT! {}", entity); + ai.dump(); + }); } diff --git a/tests/rituals.cpp b/tests/rituals.cpp index a2172e2..e3c78f2 100644 --- a/tests/rituals.cpp +++ b/tests/rituals.cpp @@ -1,10 +1,12 @@ #include #include #include "rituals.hpp" +#include "fsm.hpp" +#include "dinkyecs.hpp" using namespace combat; -TEST_CASE("prototype combat system ideas", "[combat]") { +TEST_CASE("RitualEngine basic tests", "[rituals]") { RitualEngine re("assets/rituals.json"); auto ritual = re.start(); @@ -47,7 +49,7 @@ TEST_CASE("prototype combat system ideas", "[combat]") { ritual.dump(); } -TEST_CASE("confirm that cycles are avoided/detected", "[combat]") { +TEST_CASE("confirm that cycles are avoided/detected", "[rituals]") { RitualEngine re("assets/rituals.json"); auto ritual = re.start();