[BREAKING] Battle system now runs the turn based combat better, and lots of interesting things like if you don't choose an action the host AI rebels and does it for you.
This commit is contained in:
parent
9739441a9c
commit
986b2612d4
9 changed files with 100 additions and 32 deletions
2
Makefile
2
Makefile
|
|
@ -37,7 +37,7 @@ tracy_build:
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
./builddir/runtests -d yes "[combat-battle]"
|
./builddir/runtests -d yes
|
||||||
|
|
||||||
run: build test
|
run: build test
|
||||||
ifeq '$(OS)' 'Windows_NT'
|
ifeq '$(OS)' 'Windows_NT'
|
||||||
|
|
|
||||||
2
ai.cpp
2
ai.cpp
|
|
@ -126,7 +126,7 @@ namespace ai {
|
||||||
|
|
||||||
Action load_action(std::string action_name) {
|
Action load_action(std::string action_name) {
|
||||||
check(initialized, "you forgot to initialize the AI first.");
|
check(initialized, "you forgot to initialize the AI first.");
|
||||||
check(AIMGR.states.contains(action_name), fmt::format(
|
check(AIMGR.actions.contains(action_name), fmt::format(
|
||||||
"ai::load_action({}): action does not exist in config",
|
"ai::load_action({}): action does not exist in config",
|
||||||
action_name));
|
action_name));
|
||||||
return AIMGR.actions.at(action_name);
|
return AIMGR.actions.at(action_name);
|
||||||
|
|
|
||||||
50
battle.cpp
50
battle.cpp
|
|
@ -4,27 +4,30 @@
|
||||||
namespace combat {
|
namespace combat {
|
||||||
void BattleEngine::add_enemy(Combatant enemy) {
|
void BattleEngine::add_enemy(Combatant enemy) {
|
||||||
$combatants.try_emplace(enemy.entity, enemy);
|
$combatants.try_emplace(enemy.entity, enemy);
|
||||||
|
|
||||||
|
if(enemy.is_host) {
|
||||||
|
dbc::check($host_combat == nullptr, "added the host twice!");
|
||||||
|
$host_combat = enemy.combat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEngine::player_request(const std::string& request) {
|
bool BattleEngine::player_request(const std::string& request) {
|
||||||
$player_requests.emplace(request);
|
auto action = ai::load_action(request);
|
||||||
|
bool can_go = player_pending_ap() >= action.cost;
|
||||||
|
|
||||||
|
if(can_go) {
|
||||||
|
$player_requests.try_emplace(request, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
return can_go;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEngine::clear_requests() {
|
void BattleEngine::clear_requests() {
|
||||||
$player_requests.clear();
|
$player_requests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleEngine::plan() {
|
void BattleEngine::ap_refresh() {
|
||||||
dbc::check($player_requests.size() > 0, "Calling plan without any player reqeusts queued.");
|
|
||||||
using enum BattleHostState;
|
|
||||||
|
|
||||||
int active = 0;
|
|
||||||
bool had_host = false;
|
|
||||||
|
|
||||||
for(auto& [entity, enemy] : $combatants) {
|
for(auto& [entity, enemy] : $combatants) {
|
||||||
//NOTE: this is just for asserting I'm using things right
|
|
||||||
if(enemy.is_host) had_host = true;
|
|
||||||
|
|
||||||
if(enemy.combat->ap < enemy.combat->max_ap) {
|
if(enemy.combat->ap < enemy.combat->max_ap) {
|
||||||
int new_ap = std::min(enemy.combat->max_ap, enemy.combat->ap_delta + enemy.combat->ap);
|
int new_ap = std::min(enemy.combat->max_ap, enemy.combat->ap_delta + enemy.combat->ap);
|
||||||
|
|
||||||
|
|
@ -34,6 +37,29 @@ namespace combat {
|
||||||
|
|
||||||
enemy.combat->ap = new_ap;
|
enemy.combat->ap = new_ap;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int BattleEngine::player_pending_ap() {
|
||||||
|
dbc::check($host_combat != nullptr, "didn't set host before checking AP");
|
||||||
|
int pending_ap = $host_combat->ap;
|
||||||
|
|
||||||
|
for(auto& [name, action] : $player_requests) {
|
||||||
|
pending_ap -= action.cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pending_ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleEngine::plan() {
|
||||||
|
using enum BattleHostState;
|
||||||
|
|
||||||
|
int active = 0;
|
||||||
|
bool had_host = false;
|
||||||
|
|
||||||
|
for(auto& [entity, enemy] : $combatants) {
|
||||||
|
//NOTE: this is just for asserting I'm using things right
|
||||||
|
if(enemy.is_host) had_host = true;
|
||||||
|
|
||||||
enemy.ai->update();
|
enemy.ai->update();
|
||||||
active += enemy.ai->active();
|
active += enemy.ai->active();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include "dinkyecs.hpp"
|
#include "dinkyecs.hpp"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include "components.hpp"
|
#include "components.hpp"
|
||||||
#include <set>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace combat {
|
namespace combat {
|
||||||
|
|
||||||
|
|
@ -32,7 +32,8 @@ namespace combat {
|
||||||
struct BattleEngine {
|
struct BattleEngine {
|
||||||
std::unordered_map<DinkyECS::Entity, Combatant> $combatants;
|
std::unordered_map<DinkyECS::Entity, Combatant> $combatants;
|
||||||
std::vector<BattleResult> $pending_actions;
|
std::vector<BattleResult> $pending_actions;
|
||||||
std::set<std::string> $player_requests;
|
std::unordered_map<std::string, ai::Action> $player_requests;
|
||||||
|
components::Combat* $host_combat = nullptr;
|
||||||
|
|
||||||
void add_enemy(Combatant ba);
|
void add_enemy(Combatant ba);
|
||||||
Combatant& get_enemy(DinkyECS::Entity entity);
|
Combatant& get_enemy(DinkyECS::Entity entity);
|
||||||
|
|
@ -41,7 +42,9 @@ namespace combat {
|
||||||
void dump();
|
void dump();
|
||||||
void set(DinkyECS::Entity entity, const std::string& state, bool setting);
|
void set(DinkyECS::Entity entity, const std::string& state, bool setting);
|
||||||
void set_all(const std::string& state, bool setting);
|
void set_all(const std::string& state, bool setting);
|
||||||
void player_request(const std::string& request);
|
bool player_request(const std::string& request);
|
||||||
|
int player_pending_ap();
|
||||||
void clear_requests();
|
void clear_requests();
|
||||||
|
void ap_refresh();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
#define FSM_DEBUG 1
|
|
||||||
#include "boss/fight.hpp"
|
#include "boss/fight.hpp"
|
||||||
#include "boss/system.hpp"
|
#include "boss/system.hpp"
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
|
|
@ -9,7 +8,8 @@ namespace boss {
|
||||||
$boss_id(boss_id),
|
$boss_id(boss_id),
|
||||||
$battle(System::create_battle($world, $boss_id)),
|
$battle(System::create_battle($world, $boss_id)),
|
||||||
$ui(world, boss_id, player_id),
|
$ui(world, boss_id, player_id),
|
||||||
$player_id(player_id)
|
$host(player_id),
|
||||||
|
$host_combat($world->get<components::Combat>(player_id))
|
||||||
{
|
{
|
||||||
$ui.init();
|
$ui.init();
|
||||||
}
|
}
|
||||||
|
|
@ -31,8 +31,7 @@ namespace boss {
|
||||||
return in_state(State::END);
|
return in_state(State::END);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fight::START(gui::Event ev, std::any data) {
|
void Fight::START(gui::Event ev, std::any) {
|
||||||
(void)data;
|
|
||||||
using enum gui::Event;
|
using enum gui::Event;
|
||||||
|
|
||||||
switch(ev) {
|
switch(ev) {
|
||||||
|
|
@ -42,6 +41,7 @@ namespace boss {
|
||||||
break;
|
break;
|
||||||
case TICK:
|
case TICK:
|
||||||
$ui.status(L"PLAYER REQUESTS");
|
$ui.status(L"PLAYER REQUESTS");
|
||||||
|
$battle.ap_refresh();
|
||||||
state(State::PLAYER_REQUESTS);
|
state(State::PLAYER_REQUESTS);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -58,8 +58,18 @@ namespace boss {
|
||||||
state(State::END);
|
state(State::END);
|
||||||
break;
|
break;
|
||||||
case START_COMBAT:
|
case START_COMBAT:
|
||||||
|
System::plan_battle($battle, $world, $boss_id);
|
||||||
$ui.status(L"PLANNING BATTLE");
|
$ui.status(L"PLANNING BATTLE");
|
||||||
state(State::PLAN_BATTLE);
|
state(State::PLAN_BATTLE);
|
||||||
|
break;
|
||||||
|
case ATTACK:
|
||||||
|
if($battle.player_request("kill_enemy")) {
|
||||||
|
fmt::println("player requests kill_enemy {} vs. {}",
|
||||||
|
$host_combat.ap, $battle.player_pending_ap());
|
||||||
|
} else {
|
||||||
|
fmt::println("NO MORE ACTION!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
case TICK:
|
case TICK:
|
||||||
break; // ignore tick
|
break; // ignore tick
|
||||||
default:
|
default:
|
||||||
|
|
@ -77,6 +87,10 @@ namespace boss {
|
||||||
break;
|
break;
|
||||||
case START_COMBAT:
|
case START_COMBAT:
|
||||||
$ui.status(L"EXEC PLAN");
|
$ui.status(L"EXEC PLAN");
|
||||||
|
while(auto action = $battle.next()) {
|
||||||
|
System::combat(*action, $world, $boss_id, 0);
|
||||||
|
run_systems();
|
||||||
|
}
|
||||||
state(State::EXEC_PLAN);
|
state(State::EXEC_PLAN);
|
||||||
break;
|
break;
|
||||||
case TICK:
|
case TICK:
|
||||||
|
|
@ -100,6 +114,8 @@ namespace boss {
|
||||||
state(State::END);
|
state(State::END);
|
||||||
} else {
|
} else {
|
||||||
$ui.status(L"PLAYER REQUESTS");
|
$ui.status(L"PLAYER REQUESTS");
|
||||||
|
$battle.ap_refresh();
|
||||||
|
$battle.clear_requests();
|
||||||
state(State::PLAYER_REQUESTS);
|
state(State::PLAYER_REQUESTS);
|
||||||
}
|
}
|
||||||
break; // ignore tick
|
break; // ignore tick
|
||||||
|
|
@ -113,7 +129,10 @@ namespace boss {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fight::run_systems() {
|
void Fight::run_systems() {
|
||||||
run++;
|
$ui.update_stats();
|
||||||
|
$battle.set($host, "tough_personality", false);
|
||||||
|
$battle.set($host, "have_healing", false);
|
||||||
|
$battle.set($host, "health_good", $host_combat.hp > 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fight::render(sf::RenderWindow& window) {
|
void Fight::render(sf::RenderWindow& window) {
|
||||||
|
|
@ -150,7 +169,11 @@ namespace boss {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Fight::player_dead() {
|
bool Fight::player_dead() {
|
||||||
auto& combat = $world->get<components::Combat>($player_id);
|
return $host_combat.hp <= 0;
|
||||||
return combat.hp <= 0;
|
}
|
||||||
|
|
||||||
|
void Fight::init_fight() {
|
||||||
|
System::initialize_actor_ai(*$world, $boss_id);
|
||||||
|
run_systems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ namespace boss {
|
||||||
combat::BattleEngine $battle;
|
combat::BattleEngine $battle;
|
||||||
boss::UI $ui;
|
boss::UI $ui;
|
||||||
sf::Vector2f mouse_pos{0,0};
|
sf::Vector2f mouse_pos{0,0};
|
||||||
Entity $player_id = NONE;
|
Entity $host = NONE;
|
||||||
int run = 0;
|
components::Combat& $host_combat;
|
||||||
|
|
||||||
Fight(shared_ptr<World> world, Entity boss_id, Entity player_id);
|
Fight(shared_ptr<World> world, Entity boss_id, Entity player_id);
|
||||||
|
|
||||||
|
|
@ -44,5 +44,6 @@ namespace boss {
|
||||||
|
|
||||||
void run_systems();
|
void run_systems();
|
||||||
bool player_dead();
|
bool player_dead();
|
||||||
|
void init_fight();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@ namespace boss {
|
||||||
auto ai_goal = ai::load_state(config.ai_goal_name);
|
auto ai_goal = ai::load_state(config.ai_goal_name);
|
||||||
|
|
||||||
ai::EntityAI boss_ai(config.ai_script, ai_start, ai_goal);
|
ai::EntityAI boss_ai(config.ai_script, ai_start, ai_goal);
|
||||||
|
boss_ai.set_state("enemy_found", true);
|
||||||
|
boss_ai.set_state("in_combat", true);
|
||||||
boss_ai.set_state("tough_personality", true);
|
boss_ai.set_state("tough_personality", true);
|
||||||
boss_ai.set_state("detect_enemy", true);
|
boss_ai.set_state("health_good", true);
|
||||||
|
|
||||||
world.set<ai::EntityAI>(entity_id, boss_ai);
|
world.set<ai::EntityAI>(entity_id, boss_ai);
|
||||||
}
|
}
|
||||||
|
|
@ -97,15 +99,17 @@ namespace boss {
|
||||||
|
|
||||||
switch(host_state) {
|
switch(host_state) {
|
||||||
case BattleHostState::agree:
|
case BattleHostState::agree:
|
||||||
dbc::log("host_agrees");
|
|
||||||
// BUG: this is hard coding only one boss, how to select targets?
|
// BUG: this is hard coding only one boss, how to select targets?
|
||||||
if(wants_to == "kill_enemy") {
|
if(wants_to == "kill_enemy") {
|
||||||
result.player_did = player_combat.attack(boss_combat);
|
result.player_did = player_combat.attack(boss_combat);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BattleHostState::disagree:
|
case BattleHostState::disagree:
|
||||||
dbc::log("host_DISagrees");
|
|
||||||
fmt::println("HOST DISAGREES! {}", wants_to);
|
fmt::println("HOST DISAGREES! {}", wants_to);
|
||||||
|
|
||||||
|
if(wants_to == "kill_enemy") {
|
||||||
|
result.player_did = player_combat.attack(boss_combat);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case BattleHostState::not_host:
|
case BattleHostState::not_host:
|
||||||
dbc::log("kill_enemy");
|
dbc::log("kill_enemy");
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,7 @@ void System::combat(int attack_id) {
|
||||||
battle.set_all("enemy_found", true);
|
battle.set_all("enemy_found", true);
|
||||||
battle.set_all("in_combat", true);
|
battle.set_all("in_combat", true);
|
||||||
battle.player_request("kill_enemy");
|
battle.player_request("kill_enemy");
|
||||||
|
battle.ap_refresh();
|
||||||
battle.plan();
|
battle.plan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
battle.player_request("use_healing");
|
battle.player_request("use_healing");
|
||||||
battle.player_request("kill_enemy");
|
battle.player_request("kill_enemy");
|
||||||
|
|
||||||
|
battle.ap_refresh();
|
||||||
battle.plan();
|
battle.plan();
|
||||||
|
|
||||||
while(auto act = battle.next()) {
|
while(auto act = battle.next()) {
|
||||||
|
|
@ -112,11 +113,18 @@ TEST_CASE("boss/systems.cpp works", "[combat-battle]") {
|
||||||
|
|
||||||
System::initialize_actor_ai(*fight->$world, fight->$boss_id);
|
System::initialize_actor_ai(*fight->$world, fight->$boss_id);
|
||||||
|
|
||||||
battle.set(host, "tough_personality", false);
|
// NEED UPDATE STATE
|
||||||
battle.set(host, "have_healing", false);
|
battle.set_all("enemy_found", true);
|
||||||
battle.set(host, "health_good", host_combat.hp > 20);
|
battle.set_all("in_combat", true);
|
||||||
|
battle.set_all("tough_personality", true);
|
||||||
|
battle.set_all("health_good", true);
|
||||||
|
|
||||||
|
battle.ap_refresh();
|
||||||
|
|
||||||
battle.player_request("kill_enemy");
|
battle.player_request("kill_enemy");
|
||||||
|
int pending_ap = battle.player_pending_ap();
|
||||||
|
|
||||||
|
REQUIRE(pending_ap < host_combat.ap);
|
||||||
|
|
||||||
System::plan_battle(battle, fight->$world, fight->$boss_id);
|
System::plan_battle(battle, fight->$world, fight->$boss_id);
|
||||||
|
|
||||||
|
|
@ -124,4 +132,6 @@ TEST_CASE("boss/systems.cpp works", "[combat-battle]") {
|
||||||
dbc::log("ACTION!");
|
dbc::log("ACTION!");
|
||||||
System::combat(*action, fight->$world, fight->$boss_id, 0);
|
System::combat(*action, fight->$world, fight->$boss_id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
REQUIRE(host_combat.ap == pending_ap);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue