diff --git a/assets/ai.json b/assets/ai.json index 7762e01..c58acb7 100644 --- a/assets/ai.json +++ b/assets/ai.json @@ -79,7 +79,6 @@ ], "states": { "Host::initial_state": { - "tough_personality": true, "enemy_found": false, "enemy_dead": false, "health_good": true, @@ -88,7 +87,8 @@ "in_combat": false, "have_item": false, "have_healing": false, - "detect_enemy": true + "detect_enemy": true, + "tough_personality": true }, "Host::final_state": { "enemy_found": true, diff --git a/assets/config.json b/assets/config.json index 59bf92e..ac5067a 100644 --- a/assets/config.json +++ b/assets/config.json @@ -20,6 +20,11 @@ "ambient_1": "assets/sounds/ambient_1.ogg" }, "sprites": { + "gold_savior": + {"path": "assets/gold_savior-256.png", + "frame_width": 256, + "frame_height": 256 + }, "armored_knight": {"path": "assets/armored_knight_1-256.png", "frame_width": 256, diff --git a/assets/gold_savior-256.png b/assets/gold_savior-256.png new file mode 100644 index 0000000..c2da3eb Binary files /dev/null and b/assets/gold_savior-256.png differ diff --git a/battle.cpp b/battle.cpp index 4d46d88..e6066e7 100644 --- a/battle.cpp +++ b/battle.cpp @@ -2,7 +2,7 @@ #include "battle.hpp" namespace combat { - void BattleEngine::add_enemy(BattleAction enemy) { + void BattleEngine::add_enemy(Combatant enemy) { combatants.try_emplace(enemy.entity, enemy); } @@ -10,19 +10,22 @@ namespace combat { int active = 0; for(auto& [entity, enemy] : combatants) { - enemy.ai.set_state("enemy_found", true); - enemy.ai.set_state("in_combat", true); enemy.ai.update(); - active += enemy.ai.active(); - // yes, copy it out of the combatants list - pending_actions.push_back(enemy); + + 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); + } + } } return active > 0; } - std::optional BattleEngine::next() { + std::optional BattleEngine::next() { if(pending_actions.size() == 0) return std::nullopt; auto ba = pending_actions.back(); @@ -36,4 +39,22 @@ namespace combat { enemy.ai.dump(); } } + + void BattleEngine::set(DinkyECS::Entity entity, 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); + } + + void BattleEngine::set_all(std::string state, bool setting) { + for(auto& [ent, action] : combatants) { + action.ai.set_state(state, setting); + } + } + + void BattleEngine::queue(DinkyECS::Entity entity, BattleAction action) { + dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine"); + auto& enemy = combatants.at(entity); + pending_actions.emplace_back(enemy, action); + } } diff --git a/battle.hpp b/battle.hpp index d9ebb64..2aeb6f5 100644 --- a/battle.hpp +++ b/battle.hpp @@ -7,19 +7,31 @@ namespace combat { - struct BattleAction { + struct Combatant { DinkyECS::Entity entity; ai::EntityAI &ai; components::Combat &combat; }; + enum class BattleAction { + ATTACK, BLOCK, ESCAPE + }; + + struct BattleResult { + Combatant &state; + BattleAction action; + }; + struct BattleEngine { - std::unordered_map combatants; - std::vector pending_actions; + std::unordered_map combatants; + std::vector pending_actions; - void add_enemy(BattleAction ba); + void add_enemy(Combatant ba); bool plan(); - std::optional next(); + std::optional next(); void dump(); + void set(DinkyECS::Entity entity, std::string state, bool setting); + void set_all(std::string state, bool setting); + void queue(DinkyECS::Entity entity, BattleAction action); }; } diff --git a/systems.cpp b/systems.cpp index cf92fbf..d06588b 100644 --- a/systems.cpp +++ b/systems.cpp @@ -223,20 +223,24 @@ void System::combat(GameLevel &level) { } } + battle.set_all("enemy_found", true); + battle.set_all("in_combat", true); battle.plan(); } - while(auto enemy = battle.next()) { + while(auto act = battle.next()) { + auto [enemy, action] = *act; + Events::Combat result { - player_combat.attack(enemy->combat), 0 + player_combat.attack(enemy.combat), 0 }; - if(enemy->ai.wants_to("kill_enemy")) { - result.enemy_did = enemy->combat.attack(player_combat); - animate_entity(world, enemy->entity); + if(enemy.ai.wants_to("kill_enemy")) { + result.enemy_did = enemy.combat.attack(player_combat); + animate_entity(world, enemy.entity); } - world.send(Events::GUI::COMBAT, enemy->entity, result); + world.send(Events::GUI::COMBAT, enemy.entity, result); } } diff --git a/tests/battle.cpp b/tests/battle.cpp index 6eb4f4b..c98d25e 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -13,31 +13,45 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { auto ai_start = ai::load_state("Enemy::initial_state"); auto ai_goal = ai::load_state("Enemy::final_state"); + auto host_start = ai::load_state("Host::initial_state"); + auto host_goal = ai::load_state("Host::final_state"); BattleEngine battle; - DinkyECS::Entity axe_ranger = 0; + 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 axe_ranger = 1; ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); - axe_ai.set_state("tough_personality", true); - axe_ai.set_state("health_good", true); components::Combat axe_combat{100, 100, 20}; battle.add_enemy({axe_ranger, axe_ai, axe_combat}); - DinkyECS::Entity rat = 1; + DinkyECS::Entity rat = 2; ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal); - rat_ai.set_state("tough_personality", false); - rat_ai.set_state("health_good", true); components::Combat rat_combat{10, 10, 2}; 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.plan(); while(auto act = battle.next()) { - auto& [entity, enemy_ai, combat] = *act; + auto& [enemy, action] = *act; - fmt::println("entity: {} wants to {} and has {} HP and {} damage", - entity, - enemy_ai.wants_to(), - combat.hp, combat.damage); + 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); } REQUIRE(!battle.next());