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 5 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;
}
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);
}

@ -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();

@ -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",

@ -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();

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

@ -2,7 +2,7 @@
#include <iostream>
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;

@ -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();
}

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

@ -2,8 +2,20 @@
#include "goap.hpp"
#include "ai.hpp"
#include "config.hpp"
#include <functional>
#include "dinkyecs.hpp"
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 {
std::string script;
ai::State start;

@ -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<Personality>(ent);
auto& personality = world.get<Personality>(ent);
enemy.set_state("tough_personality", personality.tough);
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]") {
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);

@ -1,9 +1,39 @@
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#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();
});
}

@ -1,10 +1,12 @@
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#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();

Loading…
Cancel
Save