AI engine is working and I have a little BattleEngine going but the AI is working better than it should in systems.cpp. Need to find out why then make the BattleEngine avoid running entities that have END in action lists.

master
Zed A. Shaw 6 days ago
parent da273cbee6
commit 47c6bfd531
  1. 12
      ai.cpp
  2. 3
      ai.hpp
  3. 11
      assets/ai.json
  4. 6
      autowalker.cpp
  5. 2
      combat.cpp
  6. 2
      dbc.cpp
  7. 3
      inventory.cpp
  8. 55
      rituals.cpp
  9. 12
      rituals.hpp
  10. 2
      systems.cpp
  11. 10
      tests/ai.cpp
  12. 34
      tests/combat.cpp
  13. 6
      tests/rituals.cpp

@ -171,10 +171,22 @@ namespace ai {
return plan.script[0].name == name; 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) { void EntityAI::set_state(std::string name, bool setting) {
ai::set(start, name, setting); ai::set(start, name, setting);
} }
bool EntityAI::get_state(std::string name) {
return ai::test(start, name);
}
void EntityAI::update() { void EntityAI::update() {
plan = ai::plan(script, start, goal); plan = ai::plan(script, start, goal);
} }

@ -24,7 +24,10 @@ namespace ai {
bool wants_to(std::string name); bool wants_to(std::string name);
bool active();
void set_state(std::string name, bool setting); void set_state(std::string name, bool setting);
bool get_state(std::string name);
void update(); void update();

@ -27,10 +27,11 @@
}, },
{ {
"name": "kill_enemy", "name": "kill_enemy",
"cost": 5, "cost": 10,
"needs": { "needs": {
"tough_personality": true, "health_good": true,
"no_more_enemies": false, "no_more_enemies": false,
"in_combat": true,
"enemy_found": true, "enemy_found": true,
"enemy_dead": false "enemy_dead": false
}, },
@ -78,7 +79,7 @@
} }
], ],
"states": { "states": {
"Walker::initial_state": { "Host::initial_state": {
"tough_personality": true, "tough_personality": true,
"enemy_found": false, "enemy_found": false,
"enemy_dead": false, "enemy_dead": false,
@ -90,7 +91,7 @@
"have_healing": false, "have_healing": false,
"detect_enemy": true "detect_enemy": true
}, },
"Walker::final_state": { "Host::final_state": {
"enemy_found": true, "enemy_found": true,
"enemy_dead": true, "enemy_dead": true,
"health_good": true, "health_good": true,
@ -114,7 +115,7 @@
} }
}, },
"scripts": { "scripts": {
"Walker::actions": "Host::actions":
["find_enemy", ["find_enemy",
"kill_enemy", "kill_enemy",
"collect_items", "collect_items",

@ -220,7 +220,7 @@ void Autowalker::handle_boss_fight() {
void Autowalker::handle_player_walk(ai::State& start, ai::State& goal) { void Autowalker::handle_player_walk(ai::State& start, ai::State& goal) {
start = update_state(start); 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(); auto action = a_plan.script.front();
if(action.name == "find_enemy") { if(action.name == "find_enemy") {
@ -269,8 +269,8 @@ void Autowalker::autowalk() {
int move_attempts = 0; int move_attempts = 0;
auto start = ai::load_state("Walker::initial_state"); auto start = ai::load_state("Host::initial_state");
auto goal = ai::load_state("Walker::final_state"); auto goal = ai::load_state("Host::final_state");
do { do {
handle_window_events(); handle_window_events();

@ -13,4 +13,6 @@ namespace components {
return my_dmg; return my_dmg;
} }
} }

@ -2,7 +2,7 @@
#include <iostream> #include <iostream>
void dbc::log(const string &message, const std::source_location location) { void dbc::log(const string &message, const std::source_location location) {
std::clog << '[' << location.file_name() << ':' std::cout << '[' << location.file_name() << ':'
<< location.line() << "|" << location.line() << "|"
<< location.function_name() << "] " << location.function_name() << "] "
<< message << std::endl; << message << std::endl;

@ -15,9 +15,6 @@ namespace components {
} }
bool Inventory::has_item(size_t at) { 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(); return at < items.size();
} }

@ -4,6 +4,51 @@
namespace combat { 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<void(DinkyECS::Entity, ai::EntityAI &)> 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) : RitualEngine::RitualEngine(std::string config_path) :
$config(config_path) $config(config_path)
{ {
@ -23,12 +68,12 @@ namespace combat {
auto& scripts = $config["scripts"]; auto& scripts = $config["scripts"];
for(auto& [script_name, action_names] : scripts.items()) { for(auto& [script_name, action_names] : scripts.items()) {
std::vector<ai::Action> the_script; std::vector<ai::Action> the_script;
for(auto name : action_names) { for(auto name : action_names) {
the_script.push_back($actions.at(name)); the_script.push_back($actions.at(name));
} }
$scripts.insert_or_assign(script_name, the_script); $scripts.insert_or_assign(script_name, the_script);
} }
} }

@ -2,8 +2,20 @@
#include "goap.hpp" #include "goap.hpp"
#include "ai.hpp" #include "ai.hpp"
#include "config.hpp" #include "config.hpp"
#include <functional>
#include "dinkyecs.hpp"
namespace combat { namespace combat {
struct BattleEngine {
std::unordered_map<DinkyECS::Entity, ai::EntityAI&> combatants;
void add_enemy(DinkyECS::Entity enemy_id, ai::EntityAI& enemy);
bool plan();
void fight(std::function<void(DinkyECS::Entity, ai::EntityAI &)> cb);
void dump();
};
struct RitualAI { struct RitualAI {
std::string script; std::string script;
ai::State start; ai::State start;

@ -61,7 +61,7 @@ void System::enemy_ai_initialize(GameLevel &level) {
auto ai_goal = ai::load_state(config.ai_goal_name); auto ai_goal = ai::load_state(config.ai_goal_name);
ai::EntityAI enemy(config.ai_script, ai_start, ai_goal); ai::EntityAI enemy(config.ai_script, ai_start, ai_goal);
auto&personality = world.get<Personality>(ent); auto& personality = world.get<Personality>(ent);
enemy.set_state("tough_personality", personality.tough); enemy.set_state("tough_personality", personality.tough);
enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance); enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance);

@ -131,14 +131,14 @@ 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");
auto start = ai::load_state("Walker::initial_state"); auto start = ai::load_state("Host::initial_state");
auto goal = ai::load_state("Walker::final_state"); auto goal = ai::load_state("Host::final_state");
int enemy_count = 5; int enemy_count = 5;
ai::set(start, "no_more_enemies", enemy_count == 0); ai::set(start, "no_more_enemies", enemy_count == 0);
// find an enemy and kill them // 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); REQUIRE(!a_plan.complete);
auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script); 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); 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("Host::actions", result, goal);
result = ai::dump_script("\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"));
@ -163,7 +163,7 @@ TEST_CASE("ai autowalker ai test", "[ai]") {
ai::set(result, "no_more_enemies", true); ai::set(result, "no_more_enemies", true);
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("Host::actions", result, goal);
result = ai::dump_script("\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);

@ -1,9 +1,39 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <iostream> #include <iostream>
#include "rituals.hpp" #include "rituals.hpp"
#include "fsm.hpp"
#include "dinkyecs.hpp"
using namespace combat; 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();
});
} }

@ -1,10 +1,12 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <iostream> #include <iostream>
#include "rituals.hpp" #include "rituals.hpp"
#include "fsm.hpp"
#include "dinkyecs.hpp"
using namespace combat; using namespace combat;
TEST_CASE("prototype combat system ideas", "[combat]") { TEST_CASE("RitualEngine basic tests", "[rituals]") {
RitualEngine re("assets/rituals.json"); RitualEngine re("assets/rituals.json");
auto ritual = re.start(); auto ritual = re.start();
@ -47,7 +49,7 @@ TEST_CASE("prototype combat system ideas", "[combat]") {
ritual.dump(); 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"); RitualEngine re("assets/rituals.json");
auto ritual = re.start(); auto ritual = re.start();

Loading…
Cancel
Save