From 360402cb3c6191133e1027b2cd05906803744711 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 30 Mar 2026 23:53:38 -0400 Subject: [PATCH] You now receive damage to multiple body parts and can heal them all too. --- assets/enemies.json | 24 ++++++++++++++++++++++-- src/combat/combat.cpp | 25 +++++++++++++++++-------- src/game/components.hpp | 22 +++++++++++++++------- src/gui/body_ui.cpp | 6 +++--- src/gui/fsm.cpp | 1 + tests/battle.cpp | 6 +++--- 6 files changed, 61 insertions(+), 23 deletions(-) diff --git a/assets/enemies.json b/assets/enemies.json index 57f5a2e..0577aae 100644 --- a/assets/enemies.json +++ b/assets/enemies.json @@ -6,7 +6,17 @@ "foreground": "enemies/fg:player", "background": "color:transparent" }, - {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false}, + {"_type": "Combat", "max_hp": 50, "max_ap": 12, "ap_delta": 6, "damage": 100, "dead": false, + "body_parts": { + "head": 200, + "chest": 200, + "stomach": 200, + "right_arm": 200, + "left_arm": 200, + "right_leg": 200, + "left_leg": 200 + } + }, {"_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 +30,17 @@ "foreground": "enemies/fg:rat_giant", "background": "color:transparent" }, - {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false}, + {"_type": "Combat", "max_hp": 50, "max_ap": 12, "ap_delta": 6,"damage": 10, "dead": false, + "body_parts": { + "head": 50, + "chest": 50, + "stomach": 50, + "right_arm": 50, + "left_arm": 50, + "right_leg": 50, + "left_leg": 50 + } + }, {"_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 23f3d7a..e72739a 100644 --- a/src/combat/combat.cpp +++ b/src/combat/combat.cpp @@ -8,30 +8,39 @@ namespace components { if(attack) { my_dmg = Random::uniform(1, damage); - target.hit_limb(my_dmg); + target.take_damage(my_dmg); } return my_dmg; } - void Combat::hit_limb(int my_dmg) { - body_parts["head"] -= my_dmg; + void Combat::take_damage(int my_dmg) { + for(auto& [key, hp] : body_parts) { + if(Random::uniform(0, 1) == 0) { + body_parts[key] = hp - my_dmg; + } + } + } + + bool Combat::less_than(int level) { + return body_parts["head"] < level || body_parts["stomach"] < level || body_parts["chest"] < level; } bool Combat::is_dead() { - return body_parts["head"] < 0; + return less_than(0); } bool Combat::almost_dead() { - return body_parts["head"] < 20; + return less_than(max_hp / 4); } bool Combat::can_heal() { - return body_parts["head"] < 50; + return less_than(max_hp / 2); } void Combat::apply_healing(Curative& cure) { - int new_hp = body_parts["head"] + cure.hp; - body_parts["head"] = std::min(new_hp, 50); + for(auto& [key, hp] : body_parts) { + body_parts[key] = std::min(hp + cure.hp, max_hp); + } } } diff --git a/src/game/components.hpp b/src/game/components.hpp index 9f31969..c90eac5 100644 --- a/src/game/components.hpp +++ b/src/game/components.hpp @@ -107,11 +107,18 @@ namespace components { }; struct Combat { - int ap_delta; - int max_ap; - int damage; + int max_hp=1; + int ap_delta=1; + int max_ap=1; + int damage=1; std::unordered_map body_parts{ - {"head", 50}, + {"head", 10}, + {"chest", 10}, + {"stomach", 10}, + {"right_arm", 10}, + {"left_arm", 10}, + {"right_leg", 10}, + {"left_leg", 10}, }; // everyone starts at 0 but ap_delta is added each round @@ -120,8 +127,9 @@ namespace components { /* NOTE: This is used to _mark_ entities as dead, to detect ones that have just died. Don't make attack automatically set it.*/ bool dead = false; + bool less_than(int level); int attack(Combat &target); - void hit_limb(int my_dmg); + void take_damage(int my_dmg); bool is_dead(); bool almost_dead(); bool can_heal(); @@ -162,7 +170,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, ap_delta, max_ap, damage, dead); + ENROLL_COMPONENT(Combat, max_hp, ap_delta, max_ap, damage, body_parts); ENROLL_COMPONENT(Device, config, events); ENROLL_COMPONENT(Storyboard, image, audio, layout, beats); ENROLL_COMPONENT(Sound, attack, death); @@ -186,7 +194,7 @@ namespace components { template void enroll(ComponentMap &m) { m[NameOf::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) { - COMPONENT c; + COMPONENT c{}; from_json(j, c); world.set(ent, c); }; diff --git a/src/gui/body_ui.cpp b/src/gui/body_ui.cpp index 2fa4f5b..e02705e 100644 --- a/src/gui/body_ui.cpp +++ b/src/gui/body_ui.cpp @@ -17,11 +17,11 @@ namespace gui { $gui.layout( "[head]" "[chest]" + "[stomach]" "[right_arm]" "[left_arm]" - "[stomach]" - "[left_leg]" - "[right_leg]"); + "[right_leg]" + "[left_leg]"); $gui.set($gui.MAIN, {$gui.$parser, }); diff --git a/src/gui/fsm.cpp b/src/gui/fsm.cpp index 9062526..15872a3 100644 --- a/src/gui/fsm.cpp +++ b/src/gui/fsm.cpp @@ -208,6 +208,7 @@ namespace gui { $main_ui.play_hands(); $main_ui.dirty(); sound::play("Sword_Hit_1"); + $status_ui.update(); state(State::ATTACKING); break; case ROTATE_LEFT: diff --git a/tests/battle.cpp b/tests/battle.cpp index d33ee05..f5a1bed 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -25,17 +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{.ap_delta=6, .max_ap=12, .damage=20}; + components::Combat host_combat{.max_hp=50, .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{.ap_delta=8, .max_ap=12, .damage=20}; + components::Combat axe_combat{.max_hp=50, .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{.ap_delta=2, .max_ap=10, .damage=10}; + components::Combat rat_combat{.max_hp=50, .ap_delta=2, .max_ap=10, .damage=10}; battle.add_enemy({rat, &rat_ai, &rat_combat}); battle.set_all("enemy_found", true);