diff --git a/Makefile b/Makefile index c8197e1..907b977 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,8 @@ tracy_build: meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true meson compile -j 10 -C builddir -test: - ./builddir/runtests -d yes +test: build + ./builddir/runtests -d yes "[combat-battle]" run: build test ifeq '$(OS)' 'Windows_NT' @@ -60,7 +60,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests -e + gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests -e "[combat-battle]" win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/assets/ai.json b/assets/ai.json index 446231e..b398d2a 100644 --- a/assets/ai.json +++ b/assets/ai.json @@ -82,7 +82,6 @@ "name": "use_healing", "cost": 1, "needs": { - "have_item": true, "have_healing": true, "health_good": false }, @@ -133,6 +132,7 @@ "kill_enemy", "collect_items", "find_healing", + "run_away", "use_healing"], "Enemy::actions": ["find_enemy", "run_away", "kill_enemy", "use_healing"] diff --git a/battle.cpp b/battle.cpp index 9808ce8..cd774db 100644 --- a/battle.cpp +++ b/battle.cpp @@ -10,18 +10,30 @@ namespace combat { int active = 0; for(auto& [entity, enemy] : combatants) { - enemy.ai.update(); - active += enemy.ai.active(); + enemy.ai->update(); + active += enemy.ai->active(); - if(enemy.ai.active()) { - if(enemy.ai.wants_to("kill_enemy")) { - pending_actions.emplace_back(enemy, BattleAction::ATTACK); - } else if(enemy.ai.wants_to("run_away")) { - pending_actions.emplace_back(enemy, BattleAction::ESCAPE); + if(enemy.ai->active()) { + for(auto& action : enemy.ai->plan.script) { + if(enemy.ai->wants_to("kill_enemy")) { + pending_actions.emplace_back(enemy, action, BattleAction::ATTACK); + } else if(enemy.ai->wants_to("run_away")) { + pending_actions.emplace_back(enemy, action, BattleAction::ESCAPE); + } else { + pending_actions.emplace_back(enemy, action, BattleAction::OTHER); + } } } } + if(pending_actions.size() > 0) { + std::sort(pending_actions.begin(), pending_actions.end(), + [](const auto& a, const auto& b) -> bool + { + return a.wants_to.cost > b.wants_to.cost; + }); + } + return active > 0; } @@ -36,25 +48,25 @@ namespace combat { void BattleEngine::dump() { for(auto& [entity, enemy] : combatants) { fmt::println("\n\n###### ENTITY #{}", entity); - enemy.ai.dump(); + enemy.ai->dump(); } } void BattleEngine::set(DinkyECS::Entity entity, const std::string& state, bool setting) { dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine"); auto& action = combatants.at(entity); - action.ai.set_state(state, setting); + action.ai->set_state(state, setting); } void BattleEngine::set_all(const std::string& state, bool setting) { for(auto& [ent, action] : combatants) { - action.ai.set_state(state, setting); + action.ai->set_state(state, setting); } } - void BattleEngine::queue(DinkyECS::Entity entity, BattleAction action) { + Combatant& BattleEngine::get_enemy(DinkyECS::Entity entity) { dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine"); - auto& enemy = combatants.at(entity); - pending_actions.emplace_back(enemy, action); + + return combatants.at(entity); } } diff --git a/battle.hpp b/battle.hpp index e01d3d6..133c162 100644 --- a/battle.hpp +++ b/battle.hpp @@ -8,17 +8,18 @@ namespace combat { struct Combatant { - DinkyECS::Entity entity; - ai::EntityAI &ai; - components::Combat &combat; + DinkyECS::Entity entity = DinkyECS::NONE; + ai::EntityAI* ai = nullptr; + components::Combat* combat = nullptr; }; enum class BattleAction { - ATTACK, BLOCK, ESCAPE + ATTACK, BLOCK, ESCAPE, OTHER }; struct BattleResult { - Combatant &state; + Combatant state; + ai::Action wants_to; BattleAction action; }; @@ -27,11 +28,11 @@ namespace combat { std::vector pending_actions; void add_enemy(Combatant ba); + Combatant& get_enemy(DinkyECS::Entity entity); bool plan(); std::optional next(); void dump(); void set(DinkyECS::Entity entity, const std::string& state, bool setting); void set_all(const std::string& state, bool setting); - void queue(DinkyECS::Entity entity, BattleAction action); }; } diff --git a/boss/fight.cpp b/boss/fight.cpp index 62771bf..1697f11 100644 --- a/boss/fight.cpp +++ b/boss/fight.cpp @@ -33,11 +33,10 @@ namespace boss { switch(ev) { case MOUSE_CLICK: { $ui.mouse(mouse_pos.x, mouse_pos.y, guecs::NO_MODS); - } break; + } break; case MOUSE_MOVE: { - $ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover}); - } - break; + $ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover}); + } break; case MOUSE_DRAG: dbc::log("mouse drag"); break; @@ -64,12 +63,15 @@ namespace boss { 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; default: - // fmt::println("BOSS_FIGHT unknown event {}", (int)ev); + fmt::println("BOSS_FIGHT:START unknown event {}", (int)ev); break; } } @@ -82,11 +84,16 @@ namespace boss { 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); - int attack_id = std::any_cast(data); boss::System::combat($world, $boss_id, attack_id); $ui.update_stats(); state(State::PLAYER_TURN); @@ -105,6 +112,9 @@ namespace boss { 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"; @@ -120,10 +130,8 @@ namespace boss { } } - void Fight::END(gui::Event ev, std::any data) { - // We need to clean up that world I think, but not sure how - (void)ev; - (void)data; + void Fight::END(gui::Event ev, std::any) { + fmt::println("BOSS_FIGHT:END event {}", (int)ev); } void Fight::run_systems() { diff --git a/boss/system.cpp b/boss/system.cpp index 92606e8..7776608 100644 --- a/boss/system.cpp +++ b/boss/system.cpp @@ -56,17 +56,17 @@ namespace boss { auto& boss_ai = world->get(boss_id); combat::BattleEngine battle; - battle.add_enemy({boss_id, boss_ai, boss_combat}); + battle.add_enemy({boss_id, &boss_ai, &boss_combat}); battle.set_all("enemy_found", true); battle.set_all("in_combat", true); battle.plan(); while(auto act = battle.next()) { - auto [enemy, enemy_action] = *act; + auto [enemy, ai_action, enemy_action] = *act; Events::Combat result { - player_combat.attack(enemy.combat), 0 + player_combat.attack(*enemy.combat), 0 }; if(result.player_did > 0) { @@ -76,7 +76,7 @@ namespace boss { } if(enemy_action == combat::BattleAction::ATTACK) { - result.enemy_did = enemy.combat.attack(player_combat); + result.enemy_did = enemy.combat->attack(player_combat); } // need to replicate this in the boss UI diff --git a/meson.build b/meson.build index a978f59..966e509 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,11 @@ project('raycaster', 'cpp', ]) # use this for common options only for our executables -cpp_args=[] +cpp_args=[ + '-Wno-unused-parameter', + '-Wno-unused-function', + '-Wno-unused-variable', +] link_args=[] # these are passed as override_defaults exe_defaults = [ 'warning_level=2' ] diff --git a/systems.cpp b/systems.cpp index 176cdd9..202b5a8 100644 --- a/systems.cpp +++ b/systems.cpp @@ -244,7 +244,7 @@ void System::combat(int attack_id) { if(world.has(entity)) { auto& enemy_ai = world.get(entity); auto& enemy_combat = world.get(entity); - battle.add_enemy({entity, enemy_ai, enemy_combat}); + battle.add_enemy({entity, &enemy_ai, &enemy_combat}); } } @@ -256,10 +256,10 @@ void System::combat(int attack_id) { battle.dump(); while(auto act = battle.next()) { - auto [enemy, enemy_action] = *act; + auto [enemy, ai_action, enemy_action] = *act; Events::Combat result { - player_combat.attack(enemy.combat), 0 + player_combat.attack(*enemy.combat), 0 }; if(result.player_did > 0) { @@ -267,7 +267,7 @@ void System::combat(int attack_id) { } if(enemy_action == combat::BattleAction::ATTACK) { - result.enemy_did = enemy.combat.attack(player_combat); + result.enemy_did = enemy.combat->attack(player_combat); animation::animate_entity(world, enemy.entity); } diff --git a/tests/battle.cpp b/tests/battle.cpp index a479b55..8bb980f 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "rituals.hpp" #include "battle.hpp" #include "simplefsm.hpp" @@ -17,41 +18,60 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { auto host_goal = ai::load_state("Host::final_state"); BattleEngine battle; - DinkyECS::Entity player = 0; - ai::EntityAI player_ai("Host::actions", host_start, host_goal); - components::Combat player_combat{100, 100, 20}; - battle.add_enemy({player, player_ai, player_combat}); + + DinkyECS::Entity host = 0; + ai::EntityAI host_ai("Host::actions", host_start, host_goal); + components::Combat host_combat{100, 100, 20}; + battle.add_enemy({host, &host_ai, &host_combat}); DinkyECS::Entity axe_ranger = 1; ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); components::Combat axe_combat{100, 100, 20}; - battle.add_enemy({axe_ranger, axe_ai, axe_combat}); + battle.add_enemy({axe_ranger, &axe_ai, &axe_combat}); DinkyECS::Entity rat = 2; ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal); components::Combat rat_combat{10, 10, 2}; - battle.add_enemy({rat, rat_ai, rat_combat}); + battle.add_enemy({rat, &rat_ai, &rat_combat}); battle.set_all("enemy_found", true); battle.set_all("in_combat", true); battle.set_all("tough_personality", true); battle.set_all("health_good", true); + battle.set(rat, "tough_personality", false); - battle.queue(player, BattleAction::ATTACK); - battle.queue(player, BattleAction::BLOCK); - - battle.queue(player, BattleAction::ESCAPE); + battle.set(host, "have_healing", false); + battle.set(host, "health_good", false); + battle.set(host, "tough_personality", false); battle.plan(); - while(auto act = battle.next()) { - auto& [enemy, action] = *act; + std::set requests{ + "use_healing", + "kill_enemy" + }; - fmt::println("entity: {} wants to {} action={} and has {} HP and {} damage", - enemy.entity, enemy.ai.wants_to(), - int(action), enemy.combat.hp, - enemy.combat.damage); + while(auto act = battle.next()) { + auto& [enemy, wants_to, action] = *act; + + fmt::println(">>>>> entity: {} wants to {} cost={} and has {} HP and {} damage", + enemy.entity, wants_to.name, + wants_to.cost, enemy.combat->hp, + enemy.combat->damage); + + if(enemy.entity == host) { + // negotiate between the player requested actions and the AI action + if(requests.contains(wants_to.name)) { + fmt::println("HOST and PLAYER requests match {}, doing it.", + wants_to.name); + requests.erase(wants_to.name); + } else { + fmt::println("REBELIOUS ACT: {}", wants_to.name); + } + } + + fmt::println("<<<<<<<<<<<<<<<<"); } REQUIRE(!battle.next()); diff --git a/tools/arena.cpp b/tools/arena.cpp index 6a0dabd..e418c73 100644 --- a/tools/arena.cpp +++ b/tools/arena.cpp @@ -33,6 +33,7 @@ int main(int, char*[]) { sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Bossfight Testing Arena"); window.setVerticalSyncEnabled(VSYNC); + if(FRAME_LIMIT) window.setFramerateLimit(FRAME_LIMIT); window.setPosition({0,0}); @@ -63,7 +64,6 @@ int main(int, char*[]) { while(world->has_event()) { auto [evt, entity, data] = world->recv(); - // FIX YOUR DAMN EVENTS switch(evt) { case Events::GUI::ATTACK: main->event(gui::Event::ATTACK, data);