BattleEngine is now connected to the boss::UI so as you click it'll continually run the plan and figure out the AI actions.

This commit is contained in:
Zed A. Shaw 2025-12-05 00:51:35 -05:00
parent a38bb5b691
commit f839edcd56
10 changed files with 112 additions and 56 deletions

View file

@ -6,6 +6,7 @@ namespace boss {
Fight::Fight(shared_ptr<World> world, Entity boss_id, Entity player_id) :
$world(world),
$boss_id(boss_id),
$battle(System::create_battle($world, $boss_id)),
$ui(world, boss_id, player_id)
{
$ui.init();
@ -96,7 +97,14 @@ namespace boss {
$ui.status(L"PLAYER TURN");
const std::string& player_pos = run % 10 < 5 ? "player1" : "player2";
$ui.move_actor("player", player_pos);
boss::System::combat($world, $boss_id, attack_id);
boss::System::plan_battle($battle, $world, $boss_id);
while(auto action = $battle.next()) {
fmt::println("*** combat turn run: eid={}",
action->enemy.entity);
boss::System::combat(*action, $world, $boss_id, attack_id);
}
$ui.update_stats();
state(State::PLAYER_TURN);
} break;
@ -125,7 +133,6 @@ namespace boss {
$ui.move_actor("boss", boss_at);
$ui.animate_actor("boss");
int attack_id = std::any_cast<int>(data);
boss::System::combat($world, $boss_id, attack_id);
state(State::BOSS_TURN);
} break;
case TICK:

View file

@ -4,6 +4,7 @@
#include "dinkyecs.hpp"
#include "boss/ui.hpp"
#include "gui/fsm_events.hpp"
#include "battle.hpp"
#include <memory>
#include <any>
@ -22,6 +23,7 @@ namespace boss {
public:
shared_ptr<World> $world = nullptr;
DinkyECS::Entity $boss_id = NONE;
combat::BattleEngine $battle;
boss::UI $ui;
sf::Vector2f mouse_pos{0,0};
int run = 0;

View file

@ -7,14 +7,15 @@
namespace boss {
using namespace components;
using namespace combat;
void System::load_config() {
fmt::println("load it");
}
void System::initialize_boss_ai(DinkyECS::World& world, DinkyECS::Entity boss_id) {
dbc::check(world.has<EnemyConfig>(boss_id), "boss doesn't have an AI EnemyConfig");
auto& config = world.get<EnemyConfig>(boss_id);
void System::initialize_actor_ai(DinkyECS::World& world, DinkyECS::Entity entity_id) {
dbc::check(world.has<EnemyConfig>(entity_id), "boss doesn't have an AI EnemyConfig");
auto& config = world.get<EnemyConfig>(entity_id);
auto ai_start = ai::load_state(config.ai_start_name);
auto ai_goal = ai::load_state(config.ai_goal_name);
@ -22,7 +23,7 @@ namespace boss {
boss_ai.set_state("tough_personality", true);
boss_ai.set_state("detect_enemy", true);
world.set<ai::EntityAI>(boss_id, boss_ai);
world.set<ai::EntityAI>(entity_id, boss_ai);
}
shared_ptr<boss::Fight> System::create_bossfight() {
@ -39,69 +40,89 @@ namespace boss {
auto boss_id = world->entity();
components::configure_entity(*world, boss_id, boss_data["components"]);
initialize_boss_ai(*world, boss_id);
initialize_actor_ai(*world, boss_id);
dbc::check(world->has<ai::EntityAI>(boss_id), "boss doesn't have an AI");
initialize_actor_ai(*world, level.player);
dbc::check(world->has<ai::EntityAI>(level.player), "player/host doesn't have an AI");
return make_shared<boss::Fight>(world, boss_id, level.player);
}
void System::combat(std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id, int attack_id) {
// get the player from the previous level, but should I just make the boss fights a level?
BattleEngine System::create_battle(std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id) {
auto& level = GameDB::current_level();
dbc::check(world->has<ai::EntityAI>(boss_id), "boss doesn't have an AI");
auto host_start = ai::load_state("Host::initial_state");
auto host_goal = ai::load_state("Host::final_state");
ai::EntityAI host_ai("Host::actions", host_start, host_goal);
auto player_combat = world->get_if<Combat>(level.player);
dbc::check(player_combat != nullptr, "No Combat for player.");
auto& player_combat = world->get<Combat>(level.player);
auto& boss_combat = world->get<Combat>(boss_id);
auto& boss_ai = world->get<ai::EntityAI>(boss_id);
auto boss_combat = world->get_if<Combat>(boss_id);
dbc::check(boss_combat != nullptr, "No Combat for Boss.");
combat::BattleEngine battle;
battle.add_enemy({boss_id, &boss_ai, &boss_combat});
battle.add_enemy({level.player, &host_ai, &player_combat});
// BUG: should I reset AP here?
player_combat->ap = player_combat->max_ap;
boss_combat->ap = boss_combat->max_ap;
auto boss_ai = world->get_if<ai::EntityAI>(boss_id);
dbc::check(boss_ai != nullptr, "boss doesn't have an AI");
auto host_ai = world->get_if<ai::EntityAI>(boss_id);
dbc::check(host_ai != nullptr, "host doesn't have an AI");
BattleEngine battle;
battle.add_enemy({boss_id, boss_ai, boss_combat, false});
battle.add_enemy({level.player, host_ai, player_combat, true});
battle.set_all("enemy_found", true);
battle.set_all("in_combat", true);
battle.set(boss_id, "tough_personality", true);
return battle;
}
void System::plan_battle(BattleEngine& battle, std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id) {
// REFACTOR: make this loop the list of entities in the battle then
// use their world state to configure the plan
auto& level = GameDB::current_level();
auto& player_combat = world->get<Combat>(level.player);
battle.set(level.player, "tough_personality", false);
battle.set(level.player, "have_healing", false);
battle.set(level.player, "health_good", player_combat.hp > 20);
battle.player_request("kill_enemy");
battle.plan();
}
while(auto act = battle.next()) {
auto [enemy, wants_to, cost, host_state] = *act;
Events::Combat result{};
void System::combat(BattleResult& action, std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id, int attack_id) {
switch(host_state) {
case combat::BattleHostState::agree:
result.player_did = player_combat.attack(*enemy.combat);
break;
case combat::BattleHostState::disagree:
fmt::println("HOST DISAGREES! {}", wants_to);
break;
case combat::BattleHostState::not_host:
if(wants_to == "kill_enemy") {
result.enemy_did = enemy.combat->attack(player_combat);
}
break;
case combat::BattleHostState::out_of_ap:
fmt::println("OUT OF AP {}", wants_to);
break;
}
auto& level = GameDB::current_level();
auto& player_combat = world->get<Combat>(level.player);
auto& boss_combat = world->get<Combat>(boss_id);
if(result.player_did > 0) {
auto& the_belt = world->get_the<ritual::Belt>();
dbc::check(the_belt.has(attack_id), "STOP passing invalid attack IDs to the system.");
}
auto& [enemy, wants_to, cost, host_state] = action;
// need to replicate this in the boss UI
world->send<Events::GUI>(Events::GUI::COMBAT, enemy.entity, result);
Events::Combat result{};
switch(host_state) {
case BattleHostState::agree:
// BUG: this is hard coding only one boss, how to select targets?
if(wants_to == "kill_enemy") {
result.player_did = player_combat.attack(boss_combat);
}
break;
case BattleHostState::disagree:
fmt::println("HOST DISAGREES! {}", wants_to);
break;
case BattleHostState::not_host:
if(wants_to == "kill_enemy") {
result.enemy_did = enemy.combat->attack(player_combat);
}
break;
case BattleHostState::out_of_ap:
fmt::println("OUT OF AP {}", wants_to);
break;
}
world->send<Events::GUI>(Events::GUI::COMBAT, enemy.entity, result);
}
}

View file

@ -8,6 +8,13 @@ namespace boss {
void load_config();
std::shared_ptr<boss::Fight> create_bossfight();
void combat(std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id, int attack_id);
void initialize_boss_ai(DinkyECS::World& world, DinkyECS::Entity boss_id);
void initialize_actor_ai(DinkyECS::World& world, DinkyECS::Entity boss_id);
combat::BattleEngine create_battle(std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id);
void plan_battle(combat::BattleEngine& battle, std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id);
void combat(combat::BattleResult& action, std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id, int attack_id);
}
}

View file

@ -51,7 +51,11 @@ namespace boss {
auto& boss_combat = $world->get<components::Combat>($boss_id);
std::wstring status = fmt::format(
L"PLAYER: {}\nBOSS: {}", player_combat.hp, boss_combat.hp);
L"--PLAYER--\nHP:{}/{}\nAP:{}/{}\n\n--BOSS--\nHP:{}/{}\nAP:{}/{}\n----\n",
player_combat.hp, player_combat.max_hp,
player_combat.ap, player_combat.max_ap,
boss_combat.hp, boss_combat.max_hp,
boss_combat.ap, boss_combat.max_ap);
if($world->has_event<Events::GUI>()) {
auto [evt, entity, data] = $world->recv<Events::GUI>();
@ -71,6 +75,7 @@ namespace boss {
status += L"\nBOSS MISSED!";
}
/*
if(result.player_did > 0) {
zoom("boss14", 1.8);
} else if(result.enemy_did > 0) {
@ -78,6 +83,7 @@ namespace boss {
} else {
zoom("", 0.0);
}
*/
}
$actions.show_text("stats", status);