diff --git a/assets/enemies.json b/assets/enemies.json index e529d38..57f5a2e 100644 --- a/assets/enemies.json +++ b/assets/enemies.json @@ -6,7 +6,7 @@ "foreground": "enemies/fg:player", "background": "color:transparent" }, - {"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false}, + {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false}, {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "Collision", "has": true}, {"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"}, @@ -20,7 +20,7 @@ "foreground": "enemies/fg:rat_giant", "background": "color:transparent" }, - {"_type": "Combat", "hp": 50, "max_hp": 50, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false}, + {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false}, {"_type": "Collision", "has": true}, {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, diff --git a/src/combat/combat.cpp b/src/combat/combat.cpp index 042895b..23f3d7a 100644 --- a/src/combat/combat.cpp +++ b/src/combat/combat.cpp @@ -8,9 +8,30 @@ namespace components { if(attack) { my_dmg = Random::uniform(1, damage); - target.hp -= my_dmg; + target.hit_limb(my_dmg); } return my_dmg; } + + void Combat::hit_limb(int my_dmg) { + body_parts["head"] -= my_dmg; + } + + bool Combat::is_dead() { + return body_parts["head"] < 0; + } + + bool Combat::almost_dead() { + return body_parts["head"] < 20; + } + + bool Combat::can_heal() { + return body_parts["head"] < 50; + } + + void Combat::apply_healing(Curative& cure) { + int new_hp = body_parts["head"] + cure.hp; + body_parts["head"] = std::min(new_hp, 50); + } } diff --git a/src/game/components.hpp b/src/game/components.hpp index de49c35..9f31969 100644 --- a/src/game/components.hpp +++ b/src/game/components.hpp @@ -83,10 +83,6 @@ namespace components { std::string ai_goal_name; }; - struct Curative { - int hp = 10; - }; - struct Sprite { string name; float scale; @@ -106,12 +102,17 @@ namespace components { std::vector> beats; }; + struct Curative { + int hp = 10; + }; + struct Combat { - int hp; - int max_hp; int ap_delta; int max_ap; int damage; + std::unordered_map body_parts{ + {"head", 50}, + }; // everyone starts at 0 but ap_delta is added each round int ap = 0; @@ -120,6 +121,11 @@ namespace components { bool dead = false; int attack(Combat &target); + void hit_limb(int my_dmg); + bool is_dead(); + bool almost_dead(); + bool can_heal(); + void apply_healing(Curative& cure); }; struct LightSource { @@ -156,7 +162,7 @@ namespace components { ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name); ENROLL_COMPONENT(Personality, hearing_distance, tough); ENROLL_COMPONENT(Motion, dx, dy, random); - ENROLL_COMPONENT(Combat, hp, max_hp, ap_delta, max_ap, damage, dead); + ENROLL_COMPONENT(Combat, ap_delta, max_ap, damage, dead); ENROLL_COMPONENT(Device, config, events); ENROLL_COMPONENT(Storyboard, image, audio, layout, beats); ENROLL_COMPONENT(Sound, attack, death); diff --git a/src/game/systems.cpp b/src/game/systems.cpp index 09bd01f..75b9e12 100644 --- a/src/game/systems.cpp +++ b/src/game/systems.cpp @@ -188,7 +188,7 @@ void System::death() { world.query([&](auto ent, auto &combat) { // bring out yer dead - if(combat.hp <= 0 && !combat.dead) { + if(combat.is_dead() && !combat.dead) { combat.dead = true; if(ent != player.entity) { // we won't change out the player's components later @@ -196,7 +196,7 @@ void System::death() { } // we need to send this event for everything that dies world.send(game::Event::DEATH, ent, {}); - } else if(float(combat.hp) / float(combat.max_hp) < 0.5f) { + } else if(combat.almost_dead()) { // if enemies are below 50% health they are marked with bad health if(world.has(ent)) { auto& enemy_ai = world.get(ent); @@ -479,23 +479,14 @@ void System::use_item(const string& slot_name) { auto& inventory = world.get(level.player); auto& player_combat = world.get(level.player); - if(player_combat.hp >= player_combat.max_hp) return; + if(!player_combat.can_heal()) return; if(!inventory.has(slot_name)) return; auto what = inventory.get(slot_name); if(auto curative = world.get_if(what)) { inventory.remove(what); - - player_combat.hp += curative->hp; - - if(player_combat.hp > player_combat.max_hp) { - player_combat.hp = player_combat.max_hp; - } - - dbc::log($F("player health now {}", - player_combat.hp)); - + player_combat.apply_healing(*curative); world.remove(what); } else { dbc::log($F("no usable item at {}", what)); diff --git a/src/gui/body_ui.cpp b/src/gui/body_ui.cpp index 0786c8f..2fa4f5b 100644 --- a/src/gui/body_ui.cpp +++ b/src/gui/body_ui.cpp @@ -15,13 +15,13 @@ namespace gui { void BodyUI::init(size_t x, size_t y, size_t width, size_t height) { $gui.position(x, y, width, height); $gui.layout( - "[body_head]" - "[body_chest]" - "[body_right_arm]" - "[body_left_arm]" - "[body_stomach]" - "[body_left_leg]" - "[body_right_leg]"); + "[head]" + "[chest]" + "[right_arm]" + "[left_arm]" + "[stomach]" + "[left_leg]" + "[right_leg]"); $gui.set($gui.MAIN, {$gui.$parser, }); @@ -42,7 +42,16 @@ namespace gui { void BodyUI::update() { auto world = GameDB::current_world(); - auto player = world->get_the(); + auto& player = world->get_the(); + auto& player_combat = world->get(player.entity); + + for(auto& [key, value] : player_combat.body_parts) { + auto gui_id = $gui.entity(key); + + if(auto meter = $gui.get_if(gui_id)) { + meter->percent = float(value) / 50.0; + } + } } void BodyUI::render(sf::RenderWindow &window) { diff --git a/src/gui/debug_ui.cpp b/src/gui/debug_ui.cpp index ce25a0d..c4f9fcc 100644 --- a/src/gui/debug_ui.cpp +++ b/src/gui/debug_ui.cpp @@ -48,7 +48,6 @@ namespace gui { auto map = level.map; std::wstring stats = $F(L"STATS\n" - L"HP: {}\n" L"mean:{:>8.5}\n" L"sdev: {:>8.5}\n" L"min: {:>8.5}\n" @@ -58,7 +57,7 @@ namespace gui { L"VSync? {}\n" L"FR Limit: {}\n" L"Debug? {}\n\n", - player_combat.hp, $stats.mean(), $stats.stddev(), $stats.min, + $stats.mean(), $stats.stddev(), $stats.min, $stats.max, $stats.n, level.index, map->width(), map->height(), VSYNC, FRAME_LIMIT, DEBUG_BUILD); @@ -74,9 +73,6 @@ namespace gui { if(active) { auto& level = GameDB::current_level(); // it's on now, enable things - auto player = level.world->get_the(); - auto& player_combat = level.world->get(player.entity); - player_combat.hp = player_combat.max_hp; $gui.show_text("debug_text", L"STATS"); } else { // it's off now, close it diff --git a/tests/battle.cpp b/tests/battle.cpp index eb46d09..d33ee05 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -25,20 +25,17 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { DinkyECS::Entity host = 0; ai::EntityAI host_ai("Host::actions", host_start, host_goal); - components::Combat host_combat{ - .hp=100, .max_hp=100, .ap_delta=6, .max_ap=12, .damage=20}; + components::Combat host_combat{.ap_delta=6, .max_ap=12, .damage=20}; battle.add_enemy({host, &host_ai, &host_combat, true}); DinkyECS::Entity axe_ranger = 1; ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); - components::Combat axe_combat{ - .hp=20, .max_hp=20, .ap_delta=8, .max_ap=12, .damage=20}; + components::Combat axe_combat{.ap_delta=8, .max_ap=12, .damage=20}; 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{ - .hp=10, .max_hp=10, .ap_delta=2, .max_ap=10, .damage=10}; + components::Combat rat_combat{.ap_delta=2, .max_ap=10, .damage=10}; battle.add_enemy({rat, &rat_ai, &rat_combat}); battle.set_all("enemy_found", true); @@ -50,8 +47,8 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { battle.set(host, "have_healing", false); battle.set(host, "tough_personality", false); - while(host_combat.hp > 0) { - battle.set(host, "health_good", host_combat.hp > 20); + while(!host_combat.is_dead()) { + battle.set(host, "health_good", host_combat.almost_dead()); battle.player_request("use_healing"); battle.player_request("kill_enemy"); @@ -62,11 +59,6 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { while(auto act = battle.next()) { auto& [enemy, wants_to, cost, enemy_state] = *act; - // fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap", - // enemy.entity, wants_to, - // cost, enemy.combat->hp, - // enemy.combat->ap); - switch(enemy_state) { case BattleHostState::agree: // fmt::println("HOST and PLAYER requests match {}, doing it.", wants_to);