diff --git a/Makefile b/Makefile index 47b9365..43de32a 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests -d yes + ./builddir/runtests -d yes "[combat-battle]" run: build test ifeq '$(OS)' 'Windows_NT' diff --git a/boss/fight.cpp b/boss/fight.cpp index c068469..26f56ce 100644 --- a/boss/fight.cpp +++ b/boss/fight.cpp @@ -1,3 +1,4 @@ +#define FSM_DEBUG 1 #include "boss/fight.hpp" #include "boss/system.hpp" #include "animation.hpp" @@ -7,7 +8,8 @@ namespace boss { $world(world), $boss_id(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) { $ui.init(); } @@ -18,8 +20,9 @@ namespace boss { switch($state) { FSM_STATE(State, START, ev, data); - FSM_STATE(State, BOSS_TURN, ev, data); - FSM_STATE(State, PLAYER_TURN, ev, data); + FSM_STATE(State, PLAYER_REQUESTS, ev, data); + FSM_STATE(State, PLAN_BATTLE, ev, data); + FSM_STATE(State, EXEC_PLAN, ev, data); FSM_STATE(State, END, ev, data); } @@ -28,6 +31,97 @@ namespace boss { return in_state(State::END); } + void Fight::START(gui::Event ev, std::any data) { + (void)data; + using enum gui::Event; + + switch(ev) { + // this is only if using the debug X key to skip it + case BOSS_START: + state(State::END); + break; + case TICK: + $ui.status(L"PLAYER REQUESTS"); + state(State::PLAYER_REQUESTS); + break; + default: + break; + } + } + + void Fight::PLAYER_REQUESTS(gui::Event ev, std::any data) { + using enum gui::Event; + + switch(ev) { + // this is only if using the debug X key to skip it + case BOSS_START: + state(State::END); + break; + case START_COMBAT: + $ui.status(L"PLANNING BATTLE"); + state(State::PLAN_BATTLE); + case TICK: + break; // ignore tick + default: + break; + } + } + + void Fight::PLAN_BATTLE(gui::Event ev, std::any data) { + using enum gui::Event; + + switch(ev) { + // this is only if using the debug X key to skip it + case BOSS_START: + state(State::END); + break; + case START_COMBAT: + $ui.status(L"EXEC PLAN"); + state(State::EXEC_PLAN); + break; + case TICK: + break; // ignore tick + default: + break; + } + } + + void Fight::EXEC_PLAN(gui::Event ev, std::any data) { + using enum gui::Event; + + switch(ev) { + // this is only if using the debug X key to skip it + case BOSS_START: + state(State::END); + break; + case TICK: + if(player_dead()) { + $ui.status(L"YOU DIED"); + state(State::END); + } else { + $ui.status(L"PLAYER REQUESTS"); + state(State::PLAYER_REQUESTS); + } + break; // ignore tick + default: + break; + } + } + + void Fight::END(gui::Event ev, std::any) { + fmt::println("BOSS_FIGHT:END event {}", (int)ev); + } + + void Fight::run_systems() { + run++; + } + + void Fight::render(sf::RenderWindow& window) { + window.clear(); + $ui.play_animations(); + $ui.render(window); + } + bool Fight::handle_mouse(gui::Event ev) { using enum gui::Event; @@ -55,105 +149,8 @@ namespace boss { return true; } - void Fight::START(gui::Event ev, std::any data) { - (void)data; - using enum gui::Event; - - switch(ev) { - // this is only if using the debug X key to skip it - case BOSS_START: - state(State::END); - break; - case KEY_PRESS: - fmt::println("KEY_PRESS"); - break; - case ATTACK: - $ui.status(L"PLAYER TURN"); - state(State::PLAYER_TURN); - break; - case TICK: - break; // ignore tick - default: - fmt::println("BOSS_FIGHT:START unknown event {}", (int)ev); - break; - } - } - - void Fight::BOSS_TURN(gui::Event ev, std::any data) { - using enum gui::Event; - - switch(ev) { - // this is only if using the debug X key to skip it - case BOSS_START: - state(State::END); - break; - case KEY_PRESS: - fmt::println("KEY_PRESS"); - break; - case ATTACK: { - int attack_id = std::any_cast(data); - fmt::println("Player attack: {}", attack_id); - - $ui.status(L"PLAYER TURN"); - const std::string& player_pos = run % 10 < 5 ? "player1" : "player2"; - $ui.move_actor("player", player_pos); - 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; - case TICK: - break; // ignore tick - default: - fmt::println("BOSS_FIGHT:BOSS_TURN unknown event {}", (int)ev); - break; - } - } - - void Fight::PLAYER_TURN(gui::Event ev, std::any data) { - using enum gui::Event; - - switch(ev) { - // this is only if using the debug X key to skip it - case BOSS_START: - state(State::END); - break; - case KEY_PRESS: - fmt::println("KEY_PRESS"); - break; - case ATTACK: { - $ui.status(L"BOSS TURN"); - const std::string &boss_at = run % 10 < 5 ? "boss5" : "boss6"; - $ui.move_actor("boss", boss_at); - $ui.animate_actor("boss"); - int attack_id = std::any_cast(data); - state(State::BOSS_TURN); - } break; - case TICK: - break; // ignore tick - default: - fmt::println("BOSS_FIGHT:PLAYER_TURN unknown event {}", (int)ev); - break; - } - } - - void Fight::END(gui::Event ev, std::any) { - fmt::println("BOSS_FIGHT:END event {}", (int)ev); - } - - void Fight::run_systems() { - run++; - } - - void Fight::render(sf::RenderWindow& window) { - window.clear(); - $ui.play_animations(); - $ui.render(window); + bool Fight::player_dead() { + auto& combat = $world->get($player_id); + return combat.hp <= 0; } } diff --git a/boss/fight.hpp b/boss/fight.hpp index 5931906..4064c96 100644 --- a/boss/fight.hpp +++ b/boss/fight.hpp @@ -14,9 +14,10 @@ namespace boss { enum class State { START=0, - BOSS_TURN=1, - PLAYER_TURN=2, - END=3 + PLAYER_REQUESTS=1, + PLAN_BATTLE=2, + EXEC_PLAN=3, + END=4 }; class Fight : public DeadSimpleFSM { @@ -26,6 +27,7 @@ namespace boss { combat::BattleEngine $battle; boss::UI $ui; sf::Vector2f mouse_pos{0,0}; + Entity $player_id = NONE; int run = 0; Fight(shared_ptr world, Entity boss_id, Entity player_id); @@ -34,11 +36,13 @@ namespace boss { bool event(gui::Event ev, std::any data); void START(gui::Event ev, std::any data); - void BOSS_TURN(gui::Event ev, std::any data); - void PLAYER_TURN(gui::Event ev, std::any data); + void PLAYER_REQUESTS(gui::Event ev, std::any data); + void PLAN_BATTLE(gui::Event ev, std::any data); + void EXEC_PLAN(gui::Event ev, std::any data); void END(gui::Event ev, std::any data); void render(sf::RenderWindow& window); void run_systems(); + bool player_dead(); }; } diff --git a/boss/system.cpp b/boss/system.cpp index a898a9c..0b1da2b 100644 --- a/boss/system.cpp +++ b/boss/system.cpp @@ -82,14 +82,6 @@ namespace boss { void System::plan_battle(BattleEngine& battle, std::shared_ptr 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(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(); } @@ -105,15 +97,18 @@ namespace boss { switch(host_state) { case BattleHostState::agree: + dbc::log("host_agrees"); // 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: + dbc::log("host_DISagrees"); fmt::println("HOST DISAGREES! {}", wants_to); break; case BattleHostState::not_host: + dbc::log("kill_enemy"); if(wants_to == "kill_enemy") { result.enemy_did = enemy.combat->attack(player_combat); } diff --git a/boss/system.hpp b/boss/system.hpp index fad3ae5..ad0af9a 100644 --- a/boss/system.hpp +++ b/boss/system.hpp @@ -7,9 +7,9 @@ namespace boss { namespace System { void load_config(); std::shared_ptr create_bossfight(); - void combat(std::shared_ptr world, DinkyECS::Entity boss_id, int attack_id); void initialize_actor_ai(DinkyECS::World& world, DinkyECS::Entity boss_id); + combat::BattleEngine create_battle(std::shared_ptr world, DinkyECS::Entity boss_id); void plan_battle(combat::BattleEngine& battle, std::shared_ptr world, DinkyECS::Entity boss_id); diff --git a/boss/ui.cpp b/boss/ui.cpp index f96272f..e75e9e8 100644 --- a/boss/ui.cpp +++ b/boss/ui.cpp @@ -5,6 +5,8 @@ #include "animation.hpp" #include #include "game_level.hpp" +#include "gui/guecstra.hpp" +#include "gui/fsm_events.hpp" namespace boss { using namespace guecs; @@ -28,16 +30,25 @@ namespace boss { $actions.position(0,0, SCREEN_WIDTH-BOSS_VIEW_WIDTH, SCREEN_HEIGHT); $actions.layout( - "[*(200,400)combat|_]" - "[_|_]" - "[_|_]" - "[_|_]" - "[*(200,300)stats]" + "[*%(100,400)combat]" + "[_]" + "[_]" + "[_]" + "[commit]" + "[*%(100,300)stats]" "[_]" "[_]"); + auto commit = $actions.entity("commit"); + $actions.set(commit, {}); + $actions.set(commit, {L"COMMIT"}); + $actions.set(commit, {}); + $actions.set(commit, + guecs::make_action(commit, Events::GUI::COMBAT_START, {})); + auto stats = $actions.entity("stats"); $actions.set(stats, {}); + update_stats(); $actions.init(); @@ -60,8 +71,6 @@ namespace boss { if($world->has_event()) { auto [evt, entity, data] = $world->recv(); auto result = std::any_cast(data); - auto& player_is = $arena.$actors.at($arena.$actor_name_ids.at("player")); - auto& boss_is = $arena.$actors.at($arena.$actor_name_ids.at("boss")); if(result.player_did > 0) { status += L"\nYOU HIT!"; @@ -74,16 +83,6 @@ namespace boss { } else { status += L"\nBOSS MISSED!"; } - - /* - if(result.player_did > 0) { - zoom("boss14", 1.8); - } else if(result.enemy_did > 0) { - zoom(player_is.cell, 2.0); - } else { - zoom("", 0.0); - } - */ } $actions.show_text("stats", status); diff --git a/tests/battle.cpp b/tests/battle.cpp index c20f588..c2f4270 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -5,8 +5,16 @@ #include "battle.hpp" #include "simplefsm.hpp" #include "dinkyecs.hpp" +#include "boss/system.hpp" +#include "backend.hpp" +#include "game_level.hpp" +#include "animation.hpp" +#include "components.hpp" +#include "ai.hpp" using namespace combat; +using namespace boss; +using namespace components; TEST_CASE("battle operations fantasy", "[combat-battle]") { ai::reset(); @@ -85,3 +93,35 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { REQUIRE(!battle.next()); } } + + +TEST_CASE("boss/systems.cpp works", "[combat-battle]") { + components::init(); + sfml::Backend backend; + guecs::init(&backend); + ai::reset(); + ai::init("ai"); + animation::init(); + GameDB::init(); + cinematic::init(); + auto host = GameDB::current_level().player; + + auto fight = System::create_bossfight(); + auto battle = System::create_battle(fight->$world, fight->$boss_id); + auto& host_combat = fight->$world->get(host); + + System::initialize_actor_ai(*fight->$world, fight->$boss_id); + + battle.set(host, "tough_personality", false); + battle.set(host, "have_healing", false); + battle.set(host, "health_good", host_combat.hp > 20); + + battle.player_request("kill_enemy"); + + System::plan_battle(battle, fight->$world, fight->$boss_id); + + while(auto action = battle.next()) { + dbc::log("ACTION!"); + System::combat(*action, fight->$world, fight->$boss_id, 0); + } +} diff --git a/tools/arena.cpp b/tools/arena.cpp index e418c73..ff7ab15 100644 --- a/tools/arena.cpp +++ b/tools/arena.cpp @@ -68,6 +68,9 @@ int main(int, char*[]) { case Events::GUI::ATTACK: main->event(gui::Event::ATTACK, data); break; + case Events::GUI::COMBAT_START: + main->event(gui::Event::START_COMBAT, data); + break; default: fmt::println("GUI EVENT: {} entity={}", int(evt), entity); }