From 831b46fa3fb4434ddb89359f8ff7fb1383e61051 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Fri, 3 Apr 2026 23:42:49 -0400 Subject: [PATCH] Now the body_ui handles the toughness and attack rating, and applies colors to show your body part status. --- assets/enemies.json | 9 ++++++--- src/combat/combat.cpp | 9 ++++++--- src/events.hpp | 1 + src/game/components.cpp | 1 + src/game/components.hpp | 13 ++++++++++++- src/game/systems.cpp | 29 +++++++++++++++++++++++++++++ src/game/systems.hpp | 1 + src/game/worldbuilder.cpp | 2 +- src/gui/body_ui.cpp | 31 +++++++++++++++++++++++++++---- src/gui/fsm.cpp | 10 +++++----- src/gui/status_ui.cpp | 6 +++--- 11 files changed, 92 insertions(+), 20 deletions(-) diff --git a/assets/enemies.json b/assets/enemies.json index 48ffc40..87f8cc2 100644 --- a/assets/enemies.json +++ b/assets/enemies.json @@ -6,7 +6,8 @@ "foreground": "enemies/fg:player", "background": "color:transparent" }, - {"_type": "Combat", "max_hp": 200, "max_ap": 12, "ap_delta": 6, "damage": 20, + {"_type": "Combat", "max_hp": 200, "max_ap": 12, + "ap_delta": 6, "damage": 20, "attack_rating": 0.5, "toughness_rating": 0.1, "body_parts": { "head": 200, "chest": 200, @@ -21,7 +22,8 @@ {"_type": "Collision", "has": true}, {"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": false}, - {"_type": "LightSource", "strength": 35, "radius": 2.0} + {"_type": "LightSource", "strength": 35, "radius": 2.0}, + {"_type": "LearnByDoing", "attacks": 0, "hits": 0, "next_attack_level": 10, "next_toughness_level": 10} ] }, "SPIDER_BOT": { @@ -30,7 +32,8 @@ "foreground": "enemies/fg:rat_giant", "background": "color:transparent" }, - {"_type": "Combat", "max_hp": 50, "max_ap": 12, "ap_delta": 6,"damage": 30, + {"_type": "Combat", "max_hp": 50, "max_ap": 12, + "ap_delta": 6,"damage": 20, "attack_rating": 0.6, "toughness_rating": 0.2, "body_parts": { "head": 50, "chest": 50, diff --git a/src/combat/combat.cpp b/src/combat/combat.cpp index 9ba9217..31be018 100644 --- a/src/combat/combat.cpp +++ b/src/combat/combat.cpp @@ -4,11 +4,14 @@ namespace components { int Combat::attack(Combat &target) { - int attack = 1; + float hit_prob = Random::uniform_real(0.0f, 1.0f); + int my_dmg = 0; - my_dmg = Random::uniform(1, damage); - target.take_damage(my_dmg); + if(hit_prob < attack_rating) { + my_dmg = std::ceil(Random::uniform_real(1.0f, float(damage)) * (1.0f - toughness_rating)); + target.take_damage(my_dmg); + } return my_dmg; } diff --git a/src/events.hpp b/src/events.hpp index 55efbb3..7460aa2 100644 --- a/src/events.hpp +++ b/src/events.hpp @@ -45,5 +45,6 @@ namespace game { TRAP=__LINE__, UPDATE_SPRITE=__LINE__, USE_ITEM=__LINE__, + LEVEL_UP=__LINE__, }; } diff --git a/src/game/components.cpp b/src/game/components.cpp index 42304fe..ec9e507 100644 --- a/src/game/components.cpp +++ b/src/game/components.cpp @@ -30,6 +30,7 @@ namespace components { components::enroll(MAP); components::enroll(MAP); components::enroll(MAP); + components::enroll(MAP); MAP_configured = true; } } diff --git a/src/game/components.hpp b/src/game/components.hpp index 84d4e64..c1f77ff 100644 --- a/src/game/components.hpp +++ b/src/game/components.hpp @@ -108,11 +108,21 @@ namespace components { int hp = 10; }; + struct LearnByDoing { + int attacks=0; + int hits=0; + int next_attack_level=10; + int next_toughness_level=10; + }; + struct Combat { int max_hp=1; int ap_delta=1; int max_ap=1; int damage=1; + float attack_rating=0.01; + float toughness_rating=0.01; + std::unordered_map body_parts{ {"head", 10}, {"chest", 10}, @@ -177,11 +187,12 @@ 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, max_hp, ap_delta, max_ap, damage, body_parts); + ENROLL_COMPONENT(Combat, max_hp, ap_delta, max_ap, damage, attack_rating, toughness_rating, body_parts); ENROLL_COMPONENT(Device, config, events); ENROLL_COMPONENT(Storyboard, image, audio, layout, beats); ENROLL_COMPONENT(Sound, attack, death); ENROLL_COMPONENT(Collision, has); + ENROLL_COMPONENT(LearnByDoing, attacks, hits, next_attack_level, next_toughness_level); template COMPONENT convert(nlohmann::json &data) { COMPONENT result; diff --git a/src/game/systems.cpp b/src/game/systems.cpp index ea896e8..95942cb 100644 --- a/src/game/systems.cpp +++ b/src/game/systems.cpp @@ -270,6 +270,7 @@ void System::combat(int attack_id) { // player shouldn't hit theirself if(host_state != BattleHostState::not_host) continue; + // BUG: really this should be attacker and receiver so I don't need the above line components::CombatResult result { .attacker=enemy.entity, .host_state=host_state, @@ -286,10 +287,38 @@ void System::combat(int attack_id) { animation::animate_entity(world, enemy.entity); } + System::learn_by_doing(result); world.send(game::Event::COMBAT, enemy.entity, result); } } +void System::learn_by_doing(components::CombatResult& result) { + auto& level = GameDB::current_level(); + auto& world = *level.world; + auto& combat = world.get(level.player); + auto& lbd = world.get(level.player); + + if(result.player_did > 0) { + lbd.attacks++; + } + + if(result.enemy_did > 0) { + lbd.hits++; + } + + if(lbd.attacks > lbd.next_attack_level) { + // they leveled up + combat.attack_rating += 0.01; + lbd.next_attack_level = lbd.next_attack_level * 4; + } + + if(lbd.hits > lbd.next_toughness_level) { + // they leveled up + combat.toughness_rating += 0.01; + lbd.next_toughness_level = lbd.next_toughness_level * 4; + } +} + void System::collision() { auto& level = GameDB::current_level(); diff --git a/src/game/systems.hpp b/src/game/systems.hpp index b2cc47e..737ad37 100644 --- a/src/game/systems.hpp +++ b/src/game/systems.hpp @@ -31,6 +31,7 @@ namespace System { void enemy_ai(); void combat(int attack_id); + void learn_by_doing(components::CombatResult& result); std::shared_ptr sprite_effect(Entity entity); void distribute_loot(Position target_pos); diff --git a/src/game/worldbuilder.cpp b/src/game/worldbuilder.cpp index 5132226..59adfd3 100644 --- a/src/game/worldbuilder.cpp +++ b/src/game/worldbuilder.cpp @@ -209,7 +209,7 @@ void WorldBuilder::configure_starting_items(DinkyECS::World &world) { world.make_constant(healing); auto sword = System::spawn_item(world, "SWORD_1"); - inventory.add("hand_main", sword); + inventory.add("weapon", sword); world.make_constant(sword); } diff --git a/src/gui/body_ui.cpp b/src/gui/body_ui.cpp index ffea011..fadfeb3 100644 --- a/src/gui/body_ui.cpp +++ b/src/gui/body_ui.cpp @@ -21,15 +21,25 @@ namespace gui { "[right_arm]" "[left_arm]" "[right_leg]" - "[left_leg]"); + "[left_leg]" + "[=*%(200,100)attack|_|attack_rating]" + "[=*%(200,100)toughness|_|toughness_rating]"); $gui.set($gui.MAIN, {$gui.$parser, }); for(auto& [name, cell] : $gui.cells()) { auto gui_id = $gui.entity(name); - $gui.set(gui_id, {guecs::to_wstring(name)}); - $gui.set(gui_id, {1.0f, THEME.DARK_MID, {}}); + if(name.starts_with("attack")) { + $gui.set(gui_id, {}); + $gui.set(gui_id, {L"Attack"}); + } else if(name.starts_with("toughness")) { + $gui.set(gui_id, {}); + $gui.set(gui_id, {L"Toughness"}); + } else { + $gui.set(gui_id, {guecs::to_wstring(name)}); + $gui.set(gui_id, {1.0f, THEME.DARK_MID, {}}); + } } $gui.init(); @@ -50,10 +60,23 @@ namespace gui { if(auto meter = $gui.get_if(gui_id)) { meter->percent = float(value) / float(player_combat.max_hp); + + if(meter->percent < 0.2) { + meter->color = sf::Color(220, 10, 10, 255); + } else { + meter->color = THEME.DARK_MID; + } + + // BUG: gross, make it stop + meter->bar.shape->setFillColor(meter->color); } - $gui.show_text(key, fmt::format(L"{}: {}", guecs::to_wstring(key), value)); + $gui.show_text(key, fmt::format(L"{}", guecs::to_wstring(key), value)); } + + // update learn by doing + $gui.show_text("attack_rating", fmt::format(L"{}", int(player_combat.attack_rating * 100.0f))); + $gui.show_text("toughness_rating", fmt::format(L"{}", int(player_combat.toughness_rating * 100.0f))); } void BodyUI::render(sf::RenderWindow &window) { diff --git a/src/gui/fsm.cpp b/src/gui/fsm.cpp index 7af7625..d47e0d3 100644 --- a/src/gui/fsm.cpp +++ b/src/gui/fsm.cpp @@ -66,11 +66,11 @@ namespace gui { void FSM::INTRO(Event ev) { dbc::check($story != nullptr, "you forgot the stroy"); - if($story->playing()) { - if(ev == game::Event::MOUSE_CLICK) { - $story->stop(); - } - } else { + if(ev == game::Event::MOUSE_CLICK) { + $story->stop(); + } + + if(!$story->playing()) { $story = nullptr; state(State::IDLE); } diff --git a/src/gui/status_ui.cpp b/src/gui/status_ui.cpp index b542d3b..8d63a0e 100644 --- a/src/gui/status_ui.cpp +++ b/src/gui/status_ui.cpp @@ -16,14 +16,14 @@ namespace gui { { $gui.position(x, y, width, height); $gui.layout( - "[*%(100, 300)body_ui]" + "[*%(100, 400)body_ui]" + "[_]" "[_]" "[_]" "[=inv0|=inv1|=inv2]" "[=inv3|=inv4|=inv5]" "[=inv6|=inv7|=inv8]" - "[=hand_main]" - "[=hand_off]"); + "[=weapon]"); } void StatusUI::init() {