Refactored rituals so they can be used in different situations.

master
Zed A. Shaw 1 week ago
parent 5af9a6664e
commit da273cbee6
  1. 4
      Makefile
  2. 5
      ai.hpp
  3. 1
      meson.build
  4. 70
      rituals.cpp
  5. 22
      rituals.hpp
  6. 21
      tests/ai.cpp
  7. 9
      tests/combat.cpp
  8. 55
      tests/rituals.cpp

@ -22,7 +22,7 @@ tracy_build:
meson compile -j 10 -C builddir
test: build
./builddir/runtests
./builddir/runtests "[combat]"
run: build test
powershell "cp ./builddir/zedcaster.exe ."
@ -41,7 +41,7 @@ clean:
meson compile --clean -C builddir
debug_test: build
gdb --nx -x .gdbinit --ex run --args builddir/runtests.exe -e
gdb --nx -x .gdbinit --ex run --args builddir/runtests.exe -e "[combat]"
win_installer:
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" win_installer.ifp'

@ -33,7 +33,6 @@ namespace ai {
struct AIManager {
AIProfile profile;
std::unordered_map<std::string, Action> actions;
std::unordered_map<std::string, State> states;
std::unordered_map<std::string, std::vector<Action>> scripts;
@ -43,8 +42,8 @@ namespace ai {
void reset();
void init(std::string config_path);
Action config_action(nlohmann::json& profile, nlohmann::json& config);
State config_state(nlohmann::json& profile, nlohmann::json& config);
Action config_action(AIProfile& profile, nlohmann::json& config);
State config_state(AIProfile& profile, nlohmann::json& config);
int state_id(std::string name);
State load_state(std::string state_name);

@ -141,6 +141,7 @@ executable('runtests', sources + [
'tests/matrix.cpp',
'tests/pathing.cpp',
'tests/rituals.cpp',
'tests/combat.cpp',
'tests/sound.cpp',
'tests/spatialmap.cpp',
'tests/animation.cpp',

@ -1,33 +1,75 @@
#include "rituals.hpp"
#include "ai_debug.hpp"
#include "ai.hpp"
namespace combat {
void RitualAI::reset() {
start = original;
RitualEngine::RitualEngine(std::string config_path) :
$config(config_path)
{
$profile = $config["profile"];
auto& actions = $config["actions"];
for(auto& ac : actions) {
auto action = ai::config_action($profile, ac);
$actions.insert_or_assign(action.name, action);
}
for(auto& [name, sc] : $config["states"].items()) {
auto state = ai::config_state($profile, sc);
$states.insert_or_assign(name, state);
}
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));
}
$scripts.insert_or_assign(script_name, the_script);
}
}
bool RitualAI::will_do(std::string name) {
ai::check_valid_action(name, "RitualAI::is_able_to");
return plan.script[0].name == name;
ai::State RitualEngine::load_state(std::string name) {
return $states.at(name);
}
ai::Action RitualEngine::load_action(std::string name) {
return $actions.at(name);
}
RitualAI RitualEngine::start() {
auto start = load_state("initial");
auto goal = load_state("final");
return {"actions", start, goal};
}
void RitualAI::set_state(std::string name, bool setting) {
ai::set(start, name, setting);
void RitualEngine::set_state(RitualAI& ritual, std::string name, bool setting) {
ritual.start.set($profile.at(name), setting);
}
void RitualEngine::reset(RitualAI& ritual) {
ritual.start = ritual.original;
}
void RitualEngine::plan(RitualAI& ritual) {
ritual.plan = ai::plan_actions($scripts.at(ritual.script), ritual.start, ritual.goal);
}
bool RitualAI::will_do(std::string name) {
if(plan.script.size() == 0) return false;
return plan.script[0].name == name;
}
/*
* BUG: I don't like this, maybe an iterator is better?
*/
ai::Action RitualAI::pop() {
auto result = plan.script.front();
plan.script.pop_front();
return result;
}
void RitualAI::update() {
plan = ai::plan(script, start, goal);
}
void RitualAI::dump() {
ai::dump_script(script, start, plan.script);
}

@ -1,5 +1,7 @@
#pragma once
#include "goap.hpp"
#include "ai.hpp"
#include "config.hpp"
namespace combat {
struct RitualAI {
@ -16,11 +18,25 @@ namespace combat {
RitualAI() {};
void reset();
bool will_do(std::string name);
void set_state(std::string name, bool setting);
void update();
void dump();
ai::Action pop();
};
struct RitualEngine {
Config $config;
ai::AIProfile $profile;
std::unordered_map<std::string, ai::Action> $actions;
std::unordered_map<std::string, ai::State> $states;
std::unordered_map<std::string, std::vector<ai::Action>> $scripts;
RitualEngine(std::string config_path);
ai::State load_state(std::string name);
ai::Action load_action(std::string name);
RitualAI start();
void reset(RitualAI& ritual);
void set_state(RitualAI& ritual, std::string name, bool setting);
void plan(RitualAI& ritual);
};
}

@ -209,24 +209,3 @@ TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") {
enemy.update();
REQUIRE(enemy.wants_to("run_away"));
}
TEST_CASE("confirm that cycles are avoided/detected", "[ai]") {
ai::reset();
ai::init("tests/cyclic_rituals.json");
auto start = ai::load_state("initial");
auto goal = ai::load_state("final");
combat::RitualAI ritual("actions", start, goal);
ritual.reset();
ritual.set_state("has_magick", true);
ritual.set_state("cursed_item", true);
ritual.set_state("shiny_bauble", true);
bool it_throws = false;
try { ritual.update(); } catch(...) { it_throws = true; }
REQUIRE(it_throws);
fmt::println("\n\n------------ CYCLES AVOIDED");
ritual.dump();
}

@ -0,0 +1,9 @@
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#include "rituals.hpp"
using namespace combat;
TEST_CASE("turn based combat engine sorted", "[combat]") {
dbc::log("does nothing.");
}

@ -5,24 +5,24 @@
using namespace combat;
TEST_CASE("prototype combat system ideas", "[combat]") {
ai::reset();
ai::init("assets/rituals.json");
RitualEngine re("assets/rituals.json");
auto ritual = re.start();
auto start = ai::load_state("initial");
auto goal = ai::load_state("final");
re.set_state(ritual, "has_spikes", true);
re.plan(ritual);
RitualAI ritual("actions", start, goal);
ritual.set_state("has_spikes", true);
ritual.update();
fmt::println("\n\n------------ TEST WILL DO PIERCE");
ritual.dump();
REQUIRE(ritual.will_do("pierce_type"));
ritual.reset();
ritual.set_state("has_magick", true);
ritual.set_state("has_spikes", true);
ritual.update();
REQUIRE(ritual.start != ritual.original);
re.reset(ritual);
REQUIRE(ritual.start == ritual.original);
re.set_state(ritual, "has_magick", true);
re.set_state(ritual, "has_spikes", true);
re.plan(ritual);
fmt::println("\n\n------------ TEST WILL DO MAGICK TOO");
ritual.dump();
REQUIRE(ritual.will_do("magick_type"));
@ -30,20 +30,31 @@ TEST_CASE("prototype combat system ideas", "[combat]") {
ritual.pop();
REQUIRE(ritual.will_do("pierce_type"));
ritual.reset();
ritual.set_state("has_magick", true);
ritual.set_state("has_spikes", true);
ritual.set_state("shiny_bauble", true);
ritual.update();
re.reset(ritual);
re.set_state(ritual, "has_magick", true);
re.set_state(ritual, "has_spikes", true);
re.set_state(ritual, "shiny_bauble", true);
re.plan(ritual);
fmt::println("\n\n------------ TEST WILL DO DAMAGE BOOST");
ritual.dump();
ritual.reset();
ritual.set_state("has_magick", true);
ritual.set_state("cursed_item", true);
ritual.set_state("shiny_bauble", true);
ritual.update();
re.reset(ritual);
re.set_state(ritual, "has_magick", true);
re.set_state(ritual, "cursed_item", true);
re.set_state(ritual, "shiny_bauble", true);
re.plan(ritual);
fmt::println("\n\n------------ TEST WILL DO LARGE DAMAGE BOOST");
ritual.dump();
}
TEST_CASE("confirm that cycles are avoided/detected", "[combat]") {
RitualEngine re("assets/rituals.json");
auto ritual = re.start();
re.set_state(ritual, "has_magick", true);
re.set_state(ritual, "cursed_item", true);
re.set_state(ritual, "shiny_bauble", true);
fmt::println("\n\n------------ CYCLES AVOIDED");
ritual.dump();
}

Loading…
Cancel
Save