From c4d696c79e9f01825b60d58cfaf4ba5634566fc4 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Sun, 12 Jul 2026 17:56:09 +0200 Subject: [PATCH] Add config-defined tooltips to build and module buttons Implement REQ-UI-BUILD-TOOLTIP and REQ-MOD-UI-MODULE-TOOLTIP: an optional tooltip string on BuildingDef and ModuleDef, parsed from buildings.toml and modules.toml and shown as the build/module button's hover tooltip. Omitted when unset. Author descriptions for all buildings and modules in the app config, and extend ConfigLoaderTest to cover the optional field. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01DZR44tA8sn4dPqDzAVXyps --- bin/app/data/config/buildings.toml | 10 ++++++++++ bin/app/data/config/modules.toml | 14 ++++++++++++++ bin/test/data/config/buildings.toml | 1 + bin/test/data/config/modules.toml | 1 + src/lib/config/BuildingsConfig.h | 3 +++ src/lib/config/ConfigLoader.cpp | 10 ++++++++++ src/lib/config/ModulesConfig.h | 4 ++++ src/test/ConfigLoaderTest.cpp | 21 +++++++++++++++++++++ src/ui/BuildButtonGrid.cpp | 4 ++++ src/ui/ShipLayoutDialog.cpp | 4 ++++ 10 files changed, 72 insertions(+) diff --git a/bin/app/data/config/buildings.toml b/bin/app/data/config/buildings.toml index 57ec733..c6956af 100644 --- a/bin/app/data/config/buildings.toml +++ b/bin/app/data/config/buildings.toml @@ -1,5 +1,6 @@ [[building]] id = "belt" +tooltip = "Transports items one tile at a time in the direction it faces." cost = 2 player_placeable = true construction_time_seconds = 0.2 @@ -7,6 +8,7 @@ surface_mask = ["A>"] [[building]] id = "splitter" +tooltip = "Splits an incoming item stream between two outputs, with optional per-output filters." cost = 3 player_placeable = true construction_time_seconds = 0.5 @@ -14,6 +16,7 @@ surface_mask = [""] [[building]] id = "tunnel_entry" +tooltip = "Sends items underground to a matching tunnel exit, letting belts cross." cost = 5 player_placeable = true construction_time_seconds = 0.5 @@ -21,6 +24,7 @@ surface_mask = ["A>"] [[building]] id = "tunnel_exit" +tooltip = "Receives items from a matching tunnel entry and pushes them onward." cost = 5 player_placeable = true construction_time_seconds = 0.5 @@ -28,6 +32,7 @@ surface_mask = ["A>"] [[building]] id = "miner" +tooltip = "Extracts a selected ore from the asteroid; every tile yields any ore." cost = 15 player_placeable = true construction_time_seconds = 1 @@ -37,6 +42,7 @@ surface_mask = [ [[building]] id = "smelter" +tooltip = "Melts ore or scrap into basic materials. No recipe selection needed." cost = 20 player_placeable = true construction_time_seconds = 1 @@ -47,6 +53,7 @@ surface_mask = [ [[building]] id = "assembler" +tooltip = "Crafts a selected recipe from the production tree into intermediate or final parts." cost = 35 player_placeable = true construction_time_seconds = 1 @@ -57,6 +64,7 @@ surface_mask = [ [[building]] id = "reprocessing_plant" +tooltip = "Consumes scrap and yields one random higher-tier product per cycle." cost = 40 player_placeable = true construction_time_seconds = 1 @@ -68,6 +76,7 @@ surface_mask = [ [[building]] id = "shipyard" +tooltip = "Builds autonomous combat ships from a selected schematic and module layout." cost = 60 player_placeable = true construction_time_seconds = 1 @@ -78,6 +87,7 @@ surface_mask = [ [[building]] id = "salvage_bay" +tooltip = "Drop-off point where salvage ships unload collected scrap onto belts." cost = 25 player_placeable = true construction_time_seconds = 1 diff --git a/bin/app/data/config/modules.toml b/bin/app/data/config/modules.toml index 252cb8e..66d39b7 100644 --- a/bin/app/data/config/modules.toml +++ b/bin/app/data/config/modules.toml @@ -32,6 +32,7 @@ [[module]] id = "railgun_s" +tooltip = "Small railgun. Fast-firing, short range, low damage; fits any hull." unlock_at_station_level = -1 surface_mask = ["O"] materials = [{item = "railgun_s_module", amount = 1}] @@ -47,6 +48,7 @@ attack_rate_hz = 2.0 [[module]] id = "railgun_m" +tooltip = "Medium railgun. Higher damage at longer range; needs a 2x2 slot." unlock_at_station_level = 2 surface_mask = [ "OO", @@ -64,6 +66,7 @@ attack_rate_hz = 1.5 [[module]] id = "railgun_l" +tooltip = "Large railgun. Heavy damage at long range; needs a 3x3 slot." unlock_at_station_level = 6 unlock_requires = ["railgun_m"] surface_mask = [ @@ -86,6 +89,7 @@ attack_rate_hz = 0.8 [[module]] id = "salvager" +tooltip = "Collects scrap from wrecks and stores it in the ship's cargo hold." unlock_at_station_level = -1 surface_mask = ["O"] materials = [{item = "salvager_module", amount = 1}] @@ -101,6 +105,7 @@ collection_rate_hz = 0.5 [[module]] id = "repair_tool" +tooltip = "Repairs damaged friendly ships and defence stations within range." unlock_at_station_level = 0 surface_mask = ["O"] materials = [{item = "repair_tool_module", amount = 1}] @@ -119,6 +124,7 @@ repair_range_m = 80 [[module]] id = "afterburner" +tooltip = "Greatly boosts top speed and forward acceleration." unlock_at_station_level = 2 surface_mask = ["OOO"] materials = [{item = "afterburner_module", amount = 1}] @@ -133,6 +139,7 @@ added_main_acceleration_mpss = 60 [[module]] id = "maneuvering_thrusters" +tooltip = "Improves top speed and lateral/braking acceleration." unlock_at_station_level = 1 surface_mask = ["OO"] materials = [{item = "maneuvering_thrusters_module", amount = 1}] @@ -150,6 +157,7 @@ added_maneuvering_acceleration_mpss = 10 [[module]] id = "armor_plates" +tooltip = "Adds a large flat bonus to the ship's hit points." unlock_at_station_level = 0 surface_mask = ["OO"] materials = [{item = "armor_plates_module", amount = 1}] @@ -163,6 +171,7 @@ added_hp = 1200 [[module]] id = "sensor_booster" +tooltip = "Extends the ship's sensor range." unlock_at_station_level = 1 surface_mask = ["OO"] materials = [{item = "sensor_booster_module", amount = 1}] @@ -179,6 +188,7 @@ added_sensor_range_m = 50 [[module]] id = "weapon_upgrade" +tooltip = "Increases the damage of all weapons on the ship." unlock_at_station_level = 4 surface_mask = [ "OO", @@ -195,6 +205,7 @@ multiplied_damage = 1.2 [[module]] id = "weapon_primer" +tooltip = "Increases the fire rate of all weapons on the ship." unlock_at_station_level = 4 surface_mask = [ "OO", @@ -211,6 +222,7 @@ multiplied_attack_rate_hz = 1.2 [[module]] id = "weapon_stabilizer" +tooltip = "Extends weapon range at the cost of some fire rate." unlock_at_station_level = 3 surface_mask = [ "OO", @@ -234,6 +246,7 @@ multiplied_attack_rate_hz = 0.8 [[module]] id = "drone_bay" +tooltip = "Drone launch bay (capability not yet implemented)." unlock_at_station_level = 5 surface_mask = [ "OO", @@ -246,6 +259,7 @@ glyph = "Db" [[module]] id = "drone_hangar" +tooltip = "Large drone hangar (capability not yet implemented)." unlock_at_station_level = 9 surface_mask = [ "OOOOOO", diff --git a/bin/test/data/config/buildings.toml b/bin/test/data/config/buildings.toml index 62ecca3..3bb2964 100644 --- a/bin/test/data/config/buildings.toml +++ b/bin/test/data/config/buildings.toml @@ -84,6 +84,7 @@ cost = 25 player_placeable = true construction_time_seconds = 15 output_buffer_capacity = 20 +tooltip = "Drop-off point for salvage ships." surface_mask = [ "SAA", "SAA>", diff --git a/bin/test/data/config/modules.toml b/bin/test/data/config/modules.toml index 3f8be23..d185d7a 100644 --- a/bin/test/data/config/modules.toml +++ b/bin/test/data/config/modules.toml @@ -1,5 +1,6 @@ [[module]] id = "armor_plate" +tooltip = "Adds a large flat bonus to hit points." unlock_at_station_level = -1 surface_mask = ["OO"] materials = [{item = "iron_ingot", amount = 2}] diff --git a/src/lib/config/BuildingsConfig.h b/src/lib/config/BuildingsConfig.h index 260d9b2..086de34 100644 --- a/src/lib/config/BuildingsConfig.h +++ b/src/lib/config/BuildingsConfig.h @@ -23,6 +23,9 @@ struct BuildingDef // Output-buffer holding size for buildings without a recipe-driven buffer. // Only the Salvage Bay sets this (REQ-BLD-SALVAGE-BAY). std::optional outputBufferCapacity; + + // Optional hover-tooltip text for the build button (REQ-UI-BUILD-TOOLTIP). + std::optional tooltip; }; struct BuildingsConfig diff --git a/src/lib/config/ConfigLoader.cpp b/src/lib/config/ConfigLoader.cpp index 3eb31de..6965066 100644 --- a/src/lib/config/ConfigLoader.cpp +++ b/src/lib/config/ConfigLoader.cpp @@ -343,6 +343,11 @@ BuildingsConfig ConfigLoader::loadBuildings(const std::string& path) requireInt(mt["output_buffer_capacity"], file, elemPath + ".output_buffer_capacity")); } + if (mt.contains("tooltip")) + { + def.tooltip = requireString(mt["tooltip"], file, elemPath + ".tooltip"); + } + const std::optional parsedType = parseBuildingType(def.id); if (!parsedType) { @@ -603,6 +608,11 @@ ModulesConfig ConfigLoader::loadModules(const std::string& path) def.fillColor = requireString(mt["fill_color"], file, elemPath + ".fill_color"); def.glyph = requireString(mt["glyph"], file, elemPath + ".glyph"); + if (mt.contains("tooltip")) + { + def.tooltip = requireString(mt["tooltip"], file, elemPath + ".tooltip"); + } + // Materials { const toml::array& materials = requireArray(mt["materials"], file, elemPath + ".materials"); diff --git a/src/lib/config/ModulesConfig.h b/src/lib/config/ModulesConfig.h index daf7dd0..ebf11d0 100644 --- a/src/lib/config/ModulesConfig.h +++ b/src/lib/config/ModulesConfig.h @@ -54,6 +54,10 @@ struct ModuleDef std::optional weaponCapability; std::optional salvageCapability; std::optional repairCapability; + + // Optional hover-tooltip text for the module selection button + // (REQ-MOD-UI-MODULE-TOOLTIP). + std::optional tooltip; }; struct ModulesConfig diff --git a/src/test/ConfigLoaderTest.cpp b/src/test/ConfigLoaderTest.cpp index 45c0d82..f06e8df 100644 --- a/src/test/ConfigLoaderTest.cpp +++ b/src/test/ConfigLoaderTest.cpp @@ -113,6 +113,12 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c REQUIRE(salvageBayIt->outputBufferCapacity.has_value()); REQUIRE(*salvageBayIt->outputBufferCapacity == 20); + // Optional per-building tooltip (REQ-UI-BUILD-TOOLTIP): the Salvage Bay + // defines one; the Miner leaves it unset. + REQUIRE(salvageBayIt->tooltip.has_value()); + REQUIRE(*salvageBayIt->tooltip == "Drop-off point for salvage ships."); + REQUIRE_FALSE(minerIt->tooltip.has_value()); + // recipes.toml — reprocessing cycle has three weighted outputs. const auto reproIt = std::find_if( cfg.recipes.recipes.begin(), cfg.recipes.recipes.end(), @@ -144,6 +150,21 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c REQUIRE(salvageShipIt != cfg.ships.ships.end()); REQUIRE(salvageShipIt->defaultModules.empty()); + // modules.toml — optional per-module tooltip (REQ-MOD-UI-MODULE-TOOLTIP): + // armor_plate defines one; salvager leaves it unset. + const auto armorPlateIt = std::find_if( + cfg.modules.modules.begin(), cfg.modules.modules.end(), + [](const ModuleDef& m) { return m.id == "armor_plate"; }); + REQUIRE(armorPlateIt != cfg.modules.modules.end()); + REQUIRE(armorPlateIt->tooltip.has_value()); + REQUIRE(*armorPlateIt->tooltip == "Adds a large flat bonus to hit points."); + + const auto salvagerModuleIt = std::find_if( + cfg.modules.modules.begin(), cfg.modules.modules.end(), + [](const ModuleDef& m) { return m.id == "salvager"; }); + REQUIRE(salvagerModuleIt != cfg.modules.modules.end()); + REQUIRE_FALSE(salvagerModuleIt->tooltip.has_value()); + // stations.toml REQUIRE(cfg.stations.playerStation.level == 5); REQUIRE(cfg.stations.playerStation.hpFormula.evaluate(5.0) == Approx(500.0)); // 300 + 40*5 diff --git a/src/ui/BuildButtonGrid.cpp b/src/ui/BuildButtonGrid.cpp index b70600f..1e01449 100644 --- a/src/ui/BuildButtonGrid.cpp +++ b/src/ui/BuildButtonGrid.cpp @@ -42,6 +42,10 @@ BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent) QPushButton* btn = new QPushButton(label, this); btn->setCheckable(true); btn->setFixedHeight(48); + if (def.tooltip) + { + btn->setToolTip(QString::fromStdString(*def.tooltip)); + } layout->addWidget(btn, row, col); const int idx = static_cast(m_buttons.size()); diff --git a/src/ui/ShipLayoutDialog.cpp b/src/ui/ShipLayoutDialog.cpp index 28e00dd..421c6f2 100644 --- a/src/ui/ShipLayoutDialog.cpp +++ b/src/ui/ShipLayoutDialog.cpp @@ -461,6 +461,10 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config, QPushButton* btn = new QPushButton(label, this); btn->setCheckable(true); btn->setFixedHeight(48); + if (def.tooltip) + { + btn->setToolTip(QString::fromStdString(*def.tooltip)); + } buttonGrid->addWidget(btn, row, col); m_moduleButtons.push_back(btn);