allow to set the recipe already for construction sites

This commit is contained in:
2026-06-22 21:32:24 +02:00
parent e5017ab3c5
commit d271d65678
10 changed files with 233 additions and 89 deletions

View File

@@ -104,6 +104,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
- REQ-BLD-BELT-DRAG: For belts, the player can click and drag across multiple tiles to place a construction site on each tile in one gesture. - REQ-BLD-BELT-DRAG: For belts, the player can click and drag across multiple tiles to place a construction site on each tile in one gesture.
- REQ-BLD-TUNNEL-AUTO-SWITCH: After the player successfully places a Tunnel Entry construction site, builder mode automatically switches to Tunnel Exit (and vice versa), preserving the current ghost rotation. This makes it easy to immediately place the paired end without manually selecting the complementary type. - REQ-BLD-TUNNEL-AUTO-SWITCH: After the player successfully places a Tunnel Entry construction site, builder mode automatically switches to Tunnel Exit (and vice versa), preserving the current ghost rotation. This makes it easy to immediately place the paired end without manually selecting the complementary type.
- REQ-BLD-DEMOLISH: The player can demolish a placed factory building. Demolition returns `world.toml [world].refund_percentage` percent of the original building block cost (default 75%) to the global stock. Exception: if the building is still in the construction queue (not yet fully built, including the one currently being constructed), it is removed from the queue and the **full** building block cost is refunded. The HQ and player defence stations cannot be demolished. - REQ-BLD-DEMOLISH: The player can demolish a placed factory building. Demolition returns `world.toml [world].refund_percentage` percent of the original building block cost (default 75%) to the global stock. Exception: if the building is still in the construction queue (not yet fully built, including the one currently being constructed), it is removed from the queue and the **full** building block cost is refunded. The HQ and player defence stations cannot be demolished.
- REQ-BLD-SITE-CONFIG: A construction site — a building that has been placed but is still queued or under construction (REQ-BLD-QUEUE) — can be selected and configured exactly like the equivalent operational building, before it finishes building. Whatever configuration the building type supports is available on the site: the recipe for a Miner or Assembler (REQ-UI-SELECT-BUTTON), the produced-ship schematic and its module layout for a Shipyard (REQ-UI-SELECT-BUTTON, REQ-MOD-UI-PREVIEW, REQ-MOD-UI-DIALOG), and the output filters for a Splitter (REQ-BLD-SPLITTER) — all set through the same Selected Building Panel controls (REQ-UI-CONFIG-INLINE). Only currently unlocked recipes and schematics are offered, exactly as for operational buildings (REQ-LOCK-UI-RECIPE, REQ-LOCK-UI-SCHEMATIC, REQ-LOCK-UI-SPLITTER). The configuration is stored on the construction site and carries over unchanged when construction completes, so the building becomes operational already configured. A construction site has no input/output buffers and runs no production cycle, so the buffer and production-progress portions of the panel (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS) are not shown for it; only its construction progress (REQ-UI-CONSTRUCTION-PROGRESS) and its configuration controls appear. (Blueprint placement already applies a stored recipe or schematic to a construction site on placement per REQ-UI-BLUEPRINT-PLACE; this requirement additionally lets the player set or change that configuration directly on an existing site.)
## Building Types ## Building Types
@@ -164,17 +165,14 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
- REQ-SHP-SENSOR: A ship perceives only entities within its sensor range. Behavior is driven by what is in sensor range; entities outside sensor range are ignored. - REQ-SHP-SENSOR: A ship perceives only entities within its sensor range. Behavior is driven by what is in sensor range; entities outside sensor range are ignored.
- REQ-SHP-FIRING: All weapons — on ships and on defence stations — fire when off cooldown and the target is within attack range. Firing emits a fire event and starts a 0.15-second damage delay (half the beam duration). When that delay expires, damage is applied to the target — unless the target has already been destroyed, in which case the damage is silently dropped. If the shooter is destroyed before the delay expires, damage is still applied when the delay expires. There is no projectile entity and no intervening collision. The weapon's cooldown begins at the moment of firing, not at damage application. - REQ-SHP-FIRING: All weapons — on ships and on defence stations — fire when off cooldown and the target is within attack range. Firing emits a fire event and starts a 0.15-second damage delay (half the beam duration). When that delay expires, damage is applied to the target — unless the target has already been destroyed, in which case the damage is silently dropped. If the shooter is destroyed before the delay expires, damage is still applied when the delay expires. There is no projectile entity and no intervening collision. The weapon's cooldown begins at the moment of firing, not at damage application.
- REQ-SHP-FIRING-BEAM: Each weapon fire event (REQ-SHP-FIRING), repair-tool activation (REQ-SHP-REPAIR), and salvage activation (REQ-SHP-SALVAGE) produces a visual beam drawn from the acting ship's position to the target for 0.3 seconds; repair and salvage beams have the same duration as weapon beams. The beam is rendered in the tool type's beam color from `visuals.toml` (a distinct color for weapon, repair, and salvage beams). The beam endpoint is not the target's center but a point randomly offset from it: the offset direction is uniformly random and the offset magnitude is uniformly random up to half the target's visual size (for ships: half their rendered radius; for buildings/stations: half the shorter side of their tile footprint, in world units; for a scrap pile: half its rendered size). The offset is chosen once per activation event and held fixed for the beam's lifetime. The beam is a pure rendering effect and has no simulation state (does not block movement, does not re-apply its effect over its lifetime). Beams follow the acting ship and target positions if either moves during the 0.3-second window. The beam is rendered for its full 0.3-second duration even if the acting ship or target is destroyed before it expires. - REQ-SHP-FIRING-BEAM: Each weapon fire event (REQ-SHP-FIRING), repair-tool activation (REQ-SHP-REPAIR), and salvage activation (REQ-SHP-SALVAGE) produces a visual beam drawn from the acting ship's position to the target for 0.3 seconds; repair and salvage beams have the same duration as weapon beams. The beam is rendered in the tool type's beam color from `visuals.toml` (a distinct color for weapon, repair, and salvage beams). The beam endpoint is not the target's center but a point randomly offset from it: the offset direction is uniformly random and the offset magnitude is uniformly random up to half the target's visual size (for ships: half their rendered radius; for buildings/stations: half the shorter side of their tile footprint, in world units; for a scrap pile: half its rendered size). The offset is chosen once per activation event and held fixed for the beam's lifetime. The beam is a pure rendering effect and has no simulation state (does not block movement, does not re-apply its effect over its lifetime). Beams follow the acting ship and target positions if either moves during the 0.3-second window. The beam is rendered for its full 0.3-second duration even if the acting ship or target is destroyed before it expires.
- REQ-SHP-COMBAT: Ships with at least one **weapon module** (player) — engage enemy ships within sensor range. When engaging an enemy, the ship orbits it at the combat orbit radius (REQ-SHP-ORBIT) rather than approaching its center. The player can configure the following per shipyard (applied to all ships produced by that shipyard): - REQ-SHP-COMBAT: Ships with at least one **weapon module** (player) — engage enemy ships within sensor range. When engaging an enemy, the ship orbits it at the combat orbit radius (REQ-SHP-ORBIT) rather than approaching its center.
- Stance: aggressive (advance toward enemies) / defensive (hold position near asteroid). - REQ-SHP-RALLY: After spawning, ships with weapon modules move to and orbit the **rally point** — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position) — at the rally orbit radius (REQ-SHP-ORBIT). While orbiting the rally point, ships still engage any enemy that enters sensor range (switching to the combat orbit per REQ-SHP-COMBAT). Every `world.toml [world].departure_interval_seconds` seconds (default 20), all ships with weapon modules currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point.
- Target priority: closest / highest HP / structures first.
- REQ-SHP-RALLY: After spawning, aggressive-stance ships with weapon modules move to and orbit the **rally point** — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position) — at the rally orbit radius (REQ-SHP-ORBIT). While orbiting the rally point, ships still engage any enemy that enters sensor range (switching to the combat orbit per REQ-SHP-COMBAT). Every `world.toml [world].departure_interval_seconds` seconds (default 20), all ships with weapon modules currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point.
- REQ-SHP-SALVAGE: Ships with at least one **salvage module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, navigate toward it by orbiting it at the salvage orbit radius (REQ-SHP-ORBIT); when it is within a module's `collection_range`, that module begins collecting from it, one scrap per cycle (see below). Once the ship's cargo pool is full, fly to a Salvage Bay and deliver (a direct approach, not an orbit — the ship must reach the bay); after delivery, resume patrol. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range, then resumes patrol — this applies regardless of whether the ship is targeting or carrying scrap. Ships with salvage modules are vulnerable to enemy ships while operating. - REQ-SHP-SALVAGE: Ships with at least one **salvage module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, navigate toward it by orbiting it at the salvage orbit radius (REQ-SHP-ORBIT); when it is within a module's `collection_range`, that module begins collecting from it, one scrap per cycle (see below). Once the ship's cargo pool is full, fly to a Salvage Bay and deliver (a direct approach, not an orbit — the ship must reach the bay); after delivery, resume patrol. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range, then resumes patrol — this applies regardless of whether the ship is targeting or carrying scrap. Ships with salvage modules are vulnerable to enemy ships while operating.
All salvage modules on a ship deposit into a single shared **cargo pool** whose size is the ship's cargo capacity stat (REQ-MOD-CARGO-CAPACITY). Each salvage module instance still runs its own collection cycle independently, with its own collection range (`collection_range`) and collection rate (`collection_rate`, in collection cycles per second). A module starts a collection cycle when it is off cooldown, the shared cargo pool has free space, and a scrap pile is within its `collection_range`. Free space is measured against the pool's current contents **plus the collection cycles already in flight toward the pool** (scrap claimed by cycles whose effect delay has not yet elapsed); each in-flight cycle is registered against the ship so that concurrent modules on the same ship never start more cycles than the remaining capacity can hold. Starting a cycle emits a collection beam toward that scrap pile (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of `1 / collection_rate` seconds begins at cycle start, not at effect application. When the delay expires, exactly 1 scrap is removed from the targeted pile and added to the ship's cargo pool — unless the pile has already been fully depleted or despawned, or the pool is now full, in which case the collection is silently dropped. A scrap pile worth more than 1 (REQ-RES-SCRAP-DROP) is depleted one scrap per cycle and persists, with its remaining amount decremented, until it is fully collected or despawns. A ship with multiple salvage modules can therefore run multiple collection cycles concurrently (one per ready module), and instances of different module types may have different ranges and rates. The ship navigates based on the maximum collection range across all installed salvage modules. All salvage modules on a ship deposit into a single shared **cargo pool** whose size is the ship's cargo capacity stat (REQ-MOD-CARGO-CAPACITY). Each salvage module instance still runs its own collection cycle independently, with its own collection range (`collection_range`) and collection rate (`collection_rate`, in collection cycles per second). A module starts a collection cycle when it is off cooldown, the shared cargo pool has free space, and a scrap pile is within its `collection_range`. Free space is measured against the pool's current contents **plus the collection cycles already in flight toward the pool** (scrap claimed by cycles whose effect delay has not yet elapsed); each in-flight cycle is registered against the ship so that concurrent modules on the same ship never start more cycles than the remaining capacity can hold. Starting a cycle emits a collection beam toward that scrap pile (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of `1 / collection_rate` seconds begins at cycle start, not at effect application. When the delay expires, exactly 1 scrap is removed from the targeted pile and added to the ship's cargo pool — unless the pile has already been fully depleted or despawned, or the pool is now full, in which case the collection is silently dropped. A scrap pile worth more than 1 (REQ-RES-SCRAP-DROP) is depleted one scrap per cycle and persists, with its remaining amount decremented, until it is fully collected or despawns. A ship with multiple salvage modules can therefore run multiple collection cycles concurrently (one per ready module), and instances of different module types may have different ranges and rates. The ship navigates based on the maximum collection range across all installed salvage modules.
Salvage collection cycles and delivery are processed regardless of which behavior the ship is currently executing; the salvage behavior only governs where the ship navigates (toward scrap, toward a Salvage Bay, or — when retreating — toward the rally point). Salvage collection cycles and delivery are processed regardless of which behavior the ship is currently executing; the salvage behavior only governs where the ship navigates (toward scrap, toward a Salvage Bay, or — when retreating — toward the rally point).
- REQ-SHP-REPAIR: Ships with at least one **repair module** (player) — when no more urgent behavior applies, hold with the fleet (REQ-SHP-STANDBY) rather than charging the enemy, so damaged allies stay within sensor range. If a damaged player defence station or player ship enters sensor range, navigate toward it by orbiting it at the repair orbit radius (REQ-SHP-ORBIT) and repair. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range — except that it holds its ground and keeps repairing while a damaged friendly remains within sensor range (REQ-SHP-RETREAT), retreating only once there is nothing left to repair — then resumes patrol. The player can configure the target priority per shipyard: - REQ-SHP-REPAIR: Ships with at least one **repair module** (player) — when no more urgent behavior applies, hold with the fleet (REQ-SHP-STANDBY) rather than charging the enemy, so damaged allies stay within sensor range. If a damaged player defence station or player ship enters sensor range, navigate toward it by orbiting it at the repair orbit radius (REQ-SHP-ORBIT) and repair. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range — except that it holds its ground and keeps repairing while a damaged friendly remains within sensor range (REQ-SHP-RETREAT), retreating only once there is nothing left to repair — then resumes patrol.
- Defence stations first / ships first / nearest target.
Each repair module instance operates independently: it has its own repair rate (`repair_rate`, in repair cycles per second), per-cycle heal amount (`repair_amount_hp`), and repair range (`repair_range`). A module starts a repair cycle when it is off cooldown and a valid repair target is in range. To choose the target, the module first considers the ship's current behavior-level navigation target if that target is within the module's `repair_range` and is damaged (HP above zero and below maximum HP). If those conditions are not met — because the target is out of the module's `repair_range`, already at full health, or destroyed — the module independently searches for the nearest damaged friendly (player ship or player defence station) within its own `repair_range`. If no valid target is found within range, the module idles and starts no cycle. On starting a cycle, the module emits a repair beam toward the chosen target (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of `1 / repair_rate` seconds begins at cycle start, not at effect application. When the delay expires, `repair_amount_hp` HP is restored to the targeted entity, clamped to its maximum HP — unless that entity is no longer damaged or has been destroyed, in which case the heal is silently dropped. A ship with multiple repair modules can therefore run multiple repair cycles concurrently, healing different targets. Navigation is driven solely by the behavior-level target; individual module fallback targets do not affect which direction the ship moves. Repair cycles are processed regardless of which behavior the ship is currently executing. Each repair module instance operates independently: it has its own repair rate (`repair_rate`, in repair cycles per second), per-cycle heal amount (`repair_amount_hp`), and repair range (`repair_range`). A module starts a repair cycle when it is off cooldown and a valid repair target is in range. To choose the target, the module first considers the ship's current behavior-level navigation target if that target is within the module's `repair_range` and is damaged (HP above zero and below maximum HP). If those conditions are not met — because the target is out of the module's `repair_range`, already at full health, or destroyed — the module independently searches for the nearest damaged friendly (player ship or player defence station) within its own `repair_range`. If no valid target is found within range, the module idles and starts no cycle. On starting a cycle, the module emits a repair beam toward the chosen target (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of `1 / repair_rate` seconds begins at cycle start, not at effect application. When the delay expires, `repair_amount_hp` HP is restored to the targeted entity, clamped to its maximum HP — unless that entity is no longer damaged or has been destroyed, in which case the heal is silently dropped. A ship with multiple repair modules can therefore run multiple repair cycles concurrently, healing different targets. Navigation is driven solely by the behavior-level target; individual module fallback targets do not affect which direction the ship moves. Repair cycles are processed regardless of which behavior the ship is currently executing.
- REQ-SHP-STANDBY: **Ships with at least one repair module hold with their fleet when idle**, whether or not they also carry weapon modules. Standby is a low-priority fallback — above the baseline forward advance (REQ-SHP-COMBAT/REQ-SHP-ENEMY-AI advance) but below rally (REQ-SHP-RALLY), so it only wins when no attack, repair, salvage, rally, or retreat behavior applies. A standing-by ship navigates toward the centroid of its other same-faction ships, falling back to the centroid of its own defence stations, and holding position when it has no allies. This keeps repair ships among the allies they exist to heal instead of advancing alone into the enemy. Armed repair ships therefore still rally and depart on the normal schedule (REQ-SHP-RALLY); standby only governs them once rally no longer applies. - REQ-SHP-STANDBY: **Ships with at least one repair module hold with their fleet when idle**, whether or not they also carry weapon modules. Standby is a low-priority fallback — above the baseline forward advance (REQ-SHP-COMBAT/REQ-SHP-ENEMY-AI advance) but below rally (REQ-SHP-RALLY), so it only wins when no attack, repair, salvage, rally, or retreat behavior applies. A standing-by ship navigates toward the centroid of its other same-faction ships, falling back to the centroid of its own defence stations, and holding position when it has no allies. This keeps repair ships among the allies they exist to heal instead of advancing alone into the enemy. Armed repair ships therefore still rally and depart on the normal schedule (REQ-SHP-RALLY); standby only governs them once rally no longer applies.
@@ -430,11 +428,11 @@ The screen is divided into two columns: a main column (75% width) containing the
### Selected Building Panel ### Selected Building Panel
- REQ-UI-EMPTY-SELECTION: When no building is selected, the panel is empty. - REQ-UI-EMPTY-SELECTION: When no building is selected, the panel is empty.
- REQ-UI-SINGLE-SELECTION: When one building is selected, the panel shows: building name, current recipe or schematic selection, input buffer contents, and output buffer contents. Buffer counts are displayed as `a/b` where `a` is the current item count and `b` is the per-cycle amount (items consumed per run for inputs; items produced per run for outputs). - REQ-UI-SINGLE-SELECTION: When one building is selected, the panel shows: building name, current recipe or schematic selection, input buffer contents, and output buffer contents. Buffer counts are displayed as `a/b` where `a` is the current item count and `b` is the per-cycle amount (items consumed per run for inputs; items produced per run for outputs). For a selected construction site, the recipe/schematic selection (and, for a shipyard, the layout preview and "Configure" button) are shown but the buffer rows are omitted (REQ-BLD-SITE-CONFIG).
- REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the selected building panel also shows: (a) the cycle time of the currently selected recipe or schematic in seconds, and (b) the completion percentage of the active production cycle as an integer (e.g. `42%`), or the text `idle` when no production cycle is active. When no recipe or schematic is selected, neither the cycle time nor the progress indicator is shown. - REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the selected building panel also shows: (a) the cycle time of the currently selected recipe or schematic in seconds, and (b) the completion percentage of the active production cycle as an integer (e.g. `42%`), or the text `idle` when no production cycle is active. When no recipe or schematic is selected, neither the cycle time nor the progress indicator is shown.
- REQ-UI-MULTI-SELECT: The player selects multiple buildings by box-drag or by Ctrl+clicking individual buildings to add or remove them from the selection. - REQ-UI-MULTI-SELECT: The player selects multiple buildings by box-drag or by Ctrl+clicking individual buildings to add or remove them from the selection.
- REQ-UI-MULTI-SELECTION: When multiple buildings are selected, the panel shows how many of each building type are selected. No per-building detail is shown. - REQ-UI-MULTI-SELECTION: When multiple buildings are selected, the panel shows how many of each building type are selected. No per-building detail is shown.
- REQ-UI-CONFIG-INLINE: Recipe, schematic, ship stance, and target priority configuration for a selected building is shown within this panel. Ship stance and target priority are selected and changed inline. Recipe selection (miner, assembler) and schematic selection (shipyard) use the selection button and dialog (REQ-UI-SELECT-BUTTON) rather than an inline control. For shipyards, the panel additionally shows the ship layout preview and "Configure" button below the schematic selection button (REQ-MOD-UI-PREVIEW). - REQ-UI-CONFIG-INLINE: Recipe and schematic configuration for a selected building is shown within this panel. Recipe selection (miner, assembler) and schematic selection (shipyard) use the selection button and dialog (REQ-UI-SELECT-BUTTON) rather than an inline control. For shipyards, the panel additionally shows the ship layout preview and "Configure" button below the schematic selection button (REQ-MOD-UI-PREVIEW).
- REQ-UI-SELECT-BUTTON: **Recipe and schematic selection control.** Recipe selection (Miner ore type, Assembler recipe) and schematic selection (Shipyard) are each presented in the selected building panel as a single **selection button** whose caption is the name of the currently selected recipe or schematic, or a placeholder ("Select recipe" / "Select schematic") when none is selected. Clicking the button opens a modal **selection dialog** that pauses the game (speed set to 0×; on close, the speed is restored to what it was before the dialog was opened). The dialog contains a grid of option buttons, one per selectable option — only options that are currently unlocked are shown (REQ-LOCK-UI-RECIPE for recipes, REQ-LOCK-UI-SCHEMATIC for schematics). Hovering an option button shows the selection info tooltip (REQ-UI-SELECT-TOOLTIP). Clicking an option button selects that recipe/schematic, closes the dialog, and updates the selection button's caption in the selected building panel. The dialog can be dismissed without changing the current selection (e.g. closing it without clicking an option). Selecting a new recipe or schematic has the same effects as before (REQ-MAT-INPUT-BUFFER, REQ-MAT-OUTPUT-BUFFER, REQ-BLD-SHIPYARD). - REQ-UI-SELECT-BUTTON: **Recipe and schematic selection control.** Recipe selection (Miner ore type, Assembler recipe) and schematic selection (Shipyard) are each presented in the selected building panel as a single **selection button** whose caption is the name of the currently selected recipe or schematic, or a placeholder ("Select recipe" / "Select schematic") when none is selected. Clicking the button opens a modal **selection dialog** that pauses the game (speed set to 0×; on close, the speed is restored to what it was before the dialog was opened). The dialog contains a grid of option buttons, one per selectable option — only options that are currently unlocked are shown (REQ-LOCK-UI-RECIPE for recipes, REQ-LOCK-UI-SCHEMATIC for schematics). Hovering an option button shows the selection info tooltip (REQ-UI-SELECT-TOOLTIP). Clicking an option button selects that recipe/schematic, closes the dialog, and updates the selection button's caption in the selected building panel. The dialog can be dismissed without changing the current selection (e.g. closing it without clicking an option). Selecting a new recipe or schematic has the same effects as before (REQ-MAT-INPUT-BUFFER, REQ-MAT-OUTPUT-BUFFER, REQ-BLD-SHIPYARD).
- REQ-UI-SELECT-TOOLTIP: **Selection info tooltip.** Hovering an option button in the selection dialog (REQ-UI-SELECT-BUTTON), and hovering the selection button in the selected building panel when a selection is set, displays an info tooltip: - REQ-UI-SELECT-TOOLTIP: **Selection info tooltip.** Hovering an option button in the selection dialog (REQ-UI-SELECT-BUTTON), and hovering the selection button in the selected building panel when a selection is set, displays an info tooltip:
- For a **recipe** (Miner or Assembler): the recipe name; the name and quantity of each input item (no inputs are listed for miner recipes, which consume nothing); the completion time (`duration_seconds`); and the name and quantity of the produced output item. - For a **recipe** (Miner or Assembler): the recipe name; the name and quantity of each input item (no inputs are listed for miner recipes, which consume nothing); the completion time (`duration_seconds`); and the name and quantity of the produced output item.
@@ -496,7 +494,7 @@ A separate executable target (`balancing`) that links against `lib` but contains
### Simulation ### Simulation
- REQ-BAL-SIM-ENV: Each arena simulates a pure-space environment using the same tick-based simulation as the main game. There is no asteroid, no buildings, no belts, no wave system, and no threat accumulation. Only ships, HQs, defence stations, and combat are active. - REQ-BAL-SIM-ENV: Each arena simulates a pure-space environment using the same tick-based simulation as the main game. There is no asteroid, no buildings, no belts, no wave system, and no threat accumulation. Only ships, HQs, defence stations, and combat are active.
- REQ-BAL-SIM-AI: Ships use the same AI and stats as in the main game. All ships use aggressive stance and closest-target priority. Ships with no target in sensor range advance toward the enemy team's HQ. Ships that detect an enemy in sensor range engage it as in the normal game (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI). - REQ-BAL-SIM-AI: Ships use the same AI and stats as in the main game. Ships with no target in sensor range advance toward the enemy team's HQ. Ships that detect an enemy in sensor range engage it as in the normal game (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI).
- REQ-BAL-SIM-SPEED: Each arena that is not being inspected runs its simulation at maximum tick rate (as many ticks per second as the hardware allows), with no rendering. An inspected arena runs at a player-controllable game speed (same speed steps as the main game: 0×, 0.5×, 1×, 2×, 4×) with full rendering in the inspect window, defaulting to 1× on open. - REQ-BAL-SIM-SPEED: Each arena that is not being inspected runs its simulation at maximum tick rate (as many ticks per second as the hardware allows), with no rendering. An inspected arena runs at a player-controllable game speed (same speed steps as the main game: 0×, 0.5×, 1×, 2×, 4×) with full rendering in the inspect window, defaulting to 1× on open.
- REQ-BAL-SIM-PARALLEL: All arenas are simulated in parallel, each on its own thread. - REQ-BAL-SIM-PARALLEL: All arenas are simulated in parallel, each on its own thread.
- REQ-BAL-SIM-END: An arena fight ends when either team's HQ is destroyed or all ships and defence stations of one team have been destroyed. If a team has no defence stations, destroying all its ships is sufficient. When the fight ends, the simulation for that arena stops. - REQ-BAL-SIM-END: An arena fight ends when either team's HQ is destroyed or all ships and defence stations of one team have been destroyed. If a team has no defence stations, destroying all its ships is sufficient. When the fight ends, the simulation for that arena stops.

View File

@@ -54,6 +54,11 @@ struct ConstructionSite
std::string recipeId; // may be configured before completion std::string recipeId; // may be configured before completion
Tick completesAt = 0; // 0 = queued but not yet started Tick completesAt = 0; // 0 = queued but not yet started
std::optional<ShipLayoutConfig> shipLayout; std::optional<ShipLayoutConfig> shipLayout;
// Splitter output filters configured before completion (REQ-BLD-SITE-CONFIG).
// Empty = accept all; applied to the BeltSystem when the splitter is built.
std::vector<ItemType> splitterFilterA;
std::vector<ItemType> splitterFilterB;
}; };
// A fully constructed, operational building. // A fully constructed, operational building.

View File

@@ -479,6 +479,44 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
} }
} }
std::optional<BeltSystem::SplitterInfo>
BuildingSystem::getSiteSplitterInfo(BuildingId id) const
{
for (const ConstructionSite& site : m_constructionQueue)
{
if (site.id != id) { continue; }
if (site.type != BuildingType::Splitter) { return std::nullopt; }
const BuildingDef* def = findBuildingDef(site.type);
const ParsedSurfaceMask mask = parseSurfaceMask(
def ? def->surfaceMask : std::vector<std::string>{}, site.rotation);
if (mask.outputPorts.size() < 2) { return std::nullopt; }
BeltSystem::SplitterInfo info;
info.outputA = mask.outputPorts[0].direction;
info.outputB = mask.outputPorts[1].direction;
info.filterA = site.splitterFilterA;
info.filterB = site.splitterFilterB;
return info;
}
return std::nullopt;
}
void BuildingSystem::setSiteSplitterFilters(BuildingId id,
const std::vector<ItemType>& filterA,
const std::vector<ItemType>& filterB)
{
for (ConstructionSite& site : m_constructionQueue)
{
if (site.id == id && site.type == BuildingType::Splitter)
{
site.splitterFilterA = filterA;
site.splitterFilterB = filterB;
return;
}
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Tick hooks // Tick hooks
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -564,6 +602,11 @@ void BuildingSystem::tickConstruction(Tick currentTick)
m_belts.placeSplitter(front.anchor, m_belts.placeSplitter(front.anchor,
mask.outputPorts[0].direction, mask.outputPorts[0].direction,
mask.outputPorts[1].direction); mask.outputPorts[1].direction);
// Carry over any filters configured while under construction
// (REQ-BLD-SITE-CONFIG).
m_belts.setSplitterFilters(front.anchor,
front.splitterFilterA,
front.splitterFilterB);
} }
else if (front.type == BuildingType::TunnelEntry) else if (front.type == BuildingType::TunnelEntry)
{ {

View File

@@ -70,6 +70,18 @@ public:
// (materials discarded) and reinitializes input buffers (REQ-BLD-SHIPYARD). // (materials discarded) and reinitializes input buffers (REQ-BLD-SHIPYARD).
void setShipLayout(BuildingId id, const ShipLayoutConfig& layout); void setShipLayout(BuildingId id, const ShipLayoutConfig& layout);
// Splitter filter configuration for a queued/under-construction Splitter
// site (REQ-BLD-SITE-CONFIG). Operational splitters are configured through
// BeltSystem by tile; these mirror that for sites, which are not yet
// registered with BeltSystem. getSiteSplitterInfo returns the site's two
// output directions (derived from its surface mask) and stored filters, or
// nullopt if the id is not a Splitter site. The stored filters are applied
// to BeltSystem when the splitter finishes building (tickConstruction).
std::optional<BeltSystem::SplitterInfo> getSiteSplitterInfo(BuildingId id) const;
void setSiteSplitterFilters(BuildingId id,
const std::vector<ItemType>& filterA,
const std::vector<ItemType>& filterB);
// -- Tick hooks (called from Simulation::tick in the documented order) --- // -- Tick hooks (called from Simulation::tick in the documented order) ---
void tickConstruction(Tick currentTick); void tickConstruction(Tick currentTick);
void tickBeltPull(); void tickBeltPull();

View File

@@ -984,3 +984,42 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
// Belt tile must still be registered after rotation — items can be placed on it. // Belt tile must still be registered after rotation — items can be placed on it.
REQUIRE(belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore"))); REQUIRE(belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore")));
} }
TEST_CASE("BuildingSystem: splitter filters configured on a construction site carry over "
"to the built splitter (REQ-BLD-SITE-CONFIG)", "[building]")
{
PlacementFixture f;
const QPoint tile(5, 5);
const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0);
REQUIRE(id != kInvalidBuildingId);
REQUIRE(f.bs.findSite(id) != nullptr);
// Configure an output filter on the still-queued splitter site.
const std::vector<ItemType> filterA{ ItemType{"iron_ore"} };
const std::vector<ItemType> filterB{};
f.bs.setSiteSplitterFilters(id, filterA, filterB);
// The site reports its two output directions and the stored filters before
// it is built; it is not yet registered with BeltSystem.
const std::optional<BeltSystem::SplitterInfo> siteInfo = f.bs.getSiteSplitterInfo(id);
REQUIRE(siteInfo.has_value());
REQUIRE(siteInfo->filterA == filterA);
REQUIRE(siteInfo->filterB.empty());
REQUIRE_FALSE(f.belts.getSplitterInfo(tile).has_value());
// Run until construction completes.
Tick tick = 0;
while (f.bs.allBuildings().empty() && tick < 100000)
{
runTicks(f.bs, f.belts, 1, tick);
}
REQUIRE(f.bs.allBuildings().size() == 1);
REQUIRE(f.bs.allBuildings()[0].type == BuildingType::Splitter);
// The built splitter is registered with BeltSystem carrying the filters.
const std::optional<BeltSystem::SplitterInfo> builtInfo = f.belts.getSplitterInfo(tile);
REQUIRE(builtInfo.has_value());
REQUIRE(builtInfo->filterA == filterA);
REQUIRE(builtInfo->filterB.empty());
}

View File

@@ -193,18 +193,26 @@ void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> e
const double prevSpeed = m_gameWorldView->gameSpeed(); const double prevSpeed = m_gameWorldView->gameSpeed();
m_gameWorldView->setGameSpeed(0.0); m_gameWorldView->setGameSpeed(0.0);
// A construction site has no Building yet; fall back to its site record so
// the shipyard layout can be configured before it is built (REQ-BLD-SITE-CONFIG).
const Building* b = m_sim->buildings().findBuilding(event->shipyardId); const Building* b = m_sim->buildings().findBuilding(event->shipyardId);
if (!b) const ConstructionSite* s =
b ? nullptr : m_sim->buildings().findSite(event->shipyardId);
if (!b && !s)
{ {
m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->setGameSpeed(prevSpeed);
m_gameWorldView->resetFrameTimer(); m_gameWorldView->resetFrameTimer();
return; return;
} }
const std::string& schematicId = b ? b->recipeId : s->recipeId;
const std::optional<ShipLayoutConfig>& layoutOpt =
b ? b->shipLayout : s->shipLayout;
ShipLayoutConfig currentLayout; ShipLayoutConfig currentLayout;
if (b->shipLayout.has_value()) if (layoutOpt.has_value())
{ {
currentLayout = *b->shipLayout; currentLayout = *layoutOpt;
} }
std::set<std::string> unlockedModuleIds; std::set<std::string> unlockedModuleIds;
@@ -218,7 +226,7 @@ void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> e
moduleLevels[def.id] = m_sim->moduleSchematicLevel(def.id); moduleLevels[def.id] = m_sim->moduleSchematicLevel(def.id);
} }
ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout, ShipLayoutDialog dialog(&m_sim->config(), schematicId, currentLayout,
m_layoutBlueprints, m_layoutBlueprints,
std::move(unlockedModuleIds), std::move(unlockedModuleIds),
std::move(moduleLevels), std::move(moduleLevels),
@@ -238,17 +246,22 @@ void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent
const double prevSpeed = m_gameWorldView->gameSpeed(); const double prevSpeed = m_gameWorldView->gameSpeed();
m_gameWorldView->setGameSpeed(0.0); m_gameWorldView->setGameSpeed(0.0);
// A construction site has no Building yet; fall back to its site record so
// the recipe/schematic can be chosen before it is built (REQ-BLD-SITE-CONFIG).
const Building* b = m_sim->buildings().findBuilding(event->buildingId); const Building* b = m_sim->buildings().findBuilding(event->buildingId);
if (!b) const ConstructionSite* s =
b ? nullptr : m_sim->buildings().findSite(event->buildingId);
if (!b && !s)
{ {
m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->setGameSpeed(prevSpeed);
m_gameWorldView->resetFrameTimer(); m_gameWorldView->resetFrameTimer();
return; return;
} }
const BuildingType type = b ? b->type : s->type;
const std::vector<RecipeSelectionOption> options = const std::vector<RecipeSelectionOption> options =
buildRecipeSelectionOptions(*b, *m_sim, m_sim->config()); buildRecipeSelectionOptions(type, *m_sim, m_sim->config());
const QString title = (b->type == BuildingType::Shipyard) const QString title = (type == BuildingType::Shipyard)
? tr("Select Schematic") ? tr("Select Schematic")
: tr("Select Recipe"); : tr("Select Recipe");

View File

@@ -88,12 +88,12 @@ QString shipTooltip(const ShipDef& def)
} // namespace } // namespace
std::vector<RecipeSelectionOption> buildRecipeSelectionOptions( std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
const Building& building, Simulation& sim, const GameConfig& config) BuildingType type, Simulation& sim, const GameConfig& config)
{ {
std::vector<RecipeSelectionOption> options; std::vector<RecipeSelectionOption> options;
options.push_back({std::string(), QObject::tr("(None)"), QString()}); options.push_back({std::string(), QObject::tr("(None)"), QString()});
if (building.type == BuildingType::Shipyard) if (type == BuildingType::Shipyard)
{ {
for (const ShipDef& def : config.ships.ships) for (const ShipDef& def : config.ships.ships)
{ {
@@ -107,9 +107,9 @@ std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
{ {
for (const RecipeDef& recipe : config.recipes.recipes) for (const RecipeDef& recipe : config.recipes.recipes)
{ {
if (recipe.building != building.type) { continue; } if (recipe.building != type) { continue; }
if ((building.type == BuildingType::Miner if ((type == BuildingType::Miner
|| building.type == BuildingType::Assembler) || type == BuildingType::Assembler)
&& !sim.isRecipeUnlocked(recipe.id)) && !sim.isRecipeUnlocked(recipe.id))
{ {
continue; continue;

View File

@@ -7,7 +7,8 @@
#include <QDialog> #include <QDialog>
#include <QString> #include <QString>
struct Building; #include "BuildingType.h"
struct GameConfig; struct GameConfig;
class Simulation; class Simulation;
class QPushButton; class QPushButton;
@@ -21,13 +22,14 @@ struct RecipeSelectionOption
QString tooltip; QString tooltip;
}; };
// Builds the lock-aware list of selectable options for a production building, // Builds the lock-aware list of selectable options for a production building
// with display captions and info tooltips (REQ-UI-SELECT-BUTTON, // of the given type, with display captions and info tooltips
// REQ-UI-SELECT-TOOLTIP). The first entry is always a "(None)" option with an // (REQ-UI-SELECT-BUTTON, REQ-UI-SELECT-TOOLTIP). The first entry is always a
// empty id. Shared by the selection dialog and the panel selection button so // "(None)" option with an empty id. Shared by the selection dialog and the
// both render identical captions and tooltips. // panel selection button so both render identical captions and tooltips, and
// usable for construction sites which have no Building yet (REQ-BLD-SITE-CONFIG).
std::vector<RecipeSelectionOption> buildRecipeSelectionOptions( std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
const Building& building, Simulation& sim, const GameConfig& config); BuildingType type, Simulation& sim, const GameConfig& config);
// Modal dialog showing a grid of option buttons (REQ-UI-SELECT-BUTTON). The // Modal dialog showing a grid of option buttons (REQ-UI-SELECT-BUTTON). The
// game is paused by the caller while it is open. Clicking an option selects it // game is paused by the caller while it is open. Clicking an option selects it

View File

@@ -238,61 +238,39 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
hideAllWidgets(); hideAllWidgets();
const Building* b = m_sim->buildings().findBuilding(id); const Building* b = m_sim->buildings().findBuilding(id);
if (!b) const ConstructionSite* s = b ? nullptr : m_sim->buildings().findSite(id);
{ if (!b && !s)
const ConstructionSite* s = m_sim->buildings().findSite(id);
if (!s)
{ {
buildEmpty(); buildEmpty();
return; return;
} }
m_singleIsSite = (s != nullptr);
QString progress; // A construction site exposes the same configuration as the operational
if (s->completesAt == 0) // building it will become (REQ-BLD-SITE-CONFIG). The only difference is
{ // that its buffer/production rows are replaced by a construction-progress
progress = tr("Queued"); // line, since a site has no buffers and runs no production cycle.
} const BuildingType type = b ? b->type : s->type;
else const std::string& recipeId = b ? b->recipeId : s->recipeId;
{ const std::optional<ShipLayoutConfig>& shipLayout =
const BuildingDef* def = nullptr; b ? b->shipLayout : s->shipLayout;
for (const BuildingDef& d : m_config->buildings.buildings) const QPoint anchor = b ? b->anchor : s->anchor;
{
if (d.type == s->type) { def = &d; break; }
}
if (def && def->constructionTimeSeconds > 0)
{
const Tick duration = secondsToTicks(def->constructionTimeSeconds);
const Tick elapsed = m_sim->currentTick() - (s->completesAt - duration);
const int pct = static_cast<int>(
std::max(Tick(0), std::min(duration, elapsed)) * 100 / duration);
progress = tr("%1% complete").arg(pct);
}
else
{
progress = tr("Building...");
}
}
m_titleLabel->setText(tr("(Building) %1").arg(buildingTypeName(s->type))); m_titleLabel->setText(m_singleIsSite
m_titleLabel->show(); ? tr("(Building) %1").arg(buildingTypeName(type))
m_buffersLabel->setText(progress); : buildingTypeName(type));
m_buffersLabel->show();
return;
}
m_titleLabel->setText(buildingTypeName(b->type));
m_titleLabel->show(); m_titleLabel->show();
m_buffersLabel->show(); m_buffersLabel->show();
if (isProductionBuilding(b->type)) if (isProductionBuilding(type))
{ {
const std::vector<RecipeSelectionOption> options = const std::vector<RecipeSelectionOption> options =
buildRecipeSelectionOptions(*b, *m_sim, *m_config); buildRecipeSelectionOptions(type, *m_sim, *m_config);
const RecipeSelectionOption* current = nullptr; const RecipeSelectionOption* current = nullptr;
for (const RecipeSelectionOption& option : options) for (const RecipeSelectionOption& option : options)
{ {
if (option.id == b->recipeId) if (option.id == recipeId)
{ {
current = &option; current = &option;
break; break;
@@ -306,7 +284,7 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
} }
else else
{ {
const QString placeholder = (b->type == BuildingType::Shipyard) const QString placeholder = (type == BuildingType::Shipyard)
? tr("Select schematic") ? tr("Select schematic")
: tr("Select recipe"); : tr("Select recipe");
m_recipeSelectButton->setText(placeholder); m_recipeSelectButton->setText(placeholder);
@@ -314,15 +292,15 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
} }
m_recipeSelectButton->show(); m_recipeSelectButton->show();
if (b->type == BuildingType::Shipyard && !b->recipeId.empty()) if (type == BuildingType::Shipyard && !recipeId.empty())
{ {
const ShipDef* sDef = findShipDef(b->recipeId); const ShipDef* sDef = findShipDef(recipeId);
if (sDef && !sDef->layout.empty()) if (sDef && !sDef->layout.empty())
{ {
ShipLayoutConfig layout; ShipLayoutConfig layout;
if (b->shipLayout.has_value()) if (shipLayout.has_value())
{ {
layout = *b->shipLayout; layout = *shipLayout;
} }
m_layoutPreview->setShipAndLayout( m_layoutPreview->setShipAndLayout(
sDef->layout, layout, &m_config->modules.modules); sDef->layout, layout, &m_config->modules.modules);
@@ -348,7 +326,9 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
m_configureLayoutBtn->hide(); m_configureLayoutBtn->hide();
} }
if (isBeltLike(b->type)) // Belt "Clear" removes items from a live belt tile; a construction site has
// none and is not registered with BeltSystem yet, so hide it for sites.
if (isBeltLike(type) && !m_singleIsSite)
{ {
m_clearBeltBtn->show(); m_clearBeltBtn->show();
} }
@@ -357,10 +337,19 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
m_clearBeltBtn->hide(); m_clearBeltBtn->hide();
} }
if (b->type == BuildingType::Splitter) if (type == BuildingType::Splitter)
{ {
m_splitterTile = b->anchor; std::optional<BeltSystem::SplitterInfo> info;
buildSplitterFilters(m_splitterTile); if (m_singleIsSite)
{
info = m_sim->buildings().getSiteSplitterInfo(id);
}
else
{
m_splitterTile = anchor;
info = m_sim->belts().getSplitterInfo(m_splitterTile);
}
buildSplitterFilters(info);
} }
else else
{ {
@@ -370,7 +359,39 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
m_filterBList->hide(); m_filterBList->hide();
} }
if (m_singleIsSite)
{
QString progress;
if (s->completesAt == 0)
{
progress = tr("Queued");
}
else
{
const BuildingDef* def = nullptr;
for (const BuildingDef& d : m_config->buildings.buildings)
{
if (d.type == s->type) { def = &d; break; }
}
if (def && def->constructionTimeSeconds > 0)
{
const Tick duration = secondsToTicks(def->constructionTimeSeconds);
const Tick elapsed = m_sim->currentTick() - (s->completesAt - duration);
const int pct = static_cast<int>(
std::max(Tick(0), std::min(duration, elapsed)) * 100 / duration);
progress = tr("%1% complete").arg(pct);
}
else
{
progress = tr("Building...");
}
}
m_buffersLabel->setText(progress);
}
else
{
refreshBuffers(b); refreshBuffers(b);
}
} }
void SelectedBuildingPanel::refreshBuffers(const Building* b) void SelectedBuildingPanel::refreshBuffers(const Building* b)
@@ -632,10 +653,9 @@ void SelectedBuildingPanel::onSelectRecipeClicked()
rebuild(); rebuild();
} }
void SelectedBuildingPanel::buildSplitterFilters(QPoint splitterTile) void SelectedBuildingPanel::buildSplitterFilters(
const std::optional<BeltSystem::SplitterInfo>& info)
{ {
const std::optional<BeltSystem::SplitterInfo> info =
m_sim->belts().getSplitterInfo(splitterTile);
if (!info.has_value()) if (!info.has_value())
{ {
m_filterALabel->hide(); m_filterALabel->hide();
@@ -698,10 +718,20 @@ void SelectedBuildingPanel::onSplitterFilterChanged()
return filter; return filter;
}; };
if (m_singleIsSite)
{
m_sim->buildings().setSiteSplitterFilters(
m_singleBuildingId,
collectFilter(m_filterAList),
collectFilter(m_filterBList));
}
else
{
m_sim->belts().setSplitterFilters( m_sim->belts().setSplitterFilters(
m_splitterTile, m_splitterTile,
collectFilter(m_filterAList), collectFilter(m_filterAList),
collectFilter(m_filterBList)); collectFilter(m_filterBList));
}
} }
std::vector<std::string> SelectedBuildingPanel::allItemIds() const std::vector<std::string> SelectedBuildingPanel::allItemIds() const

View File

@@ -9,6 +9,7 @@
#include "entt/entity/entity.hpp" #include "entt/entity/entity.hpp"
#include "BeltSystem.h"
#include "Building.h" #include "Building.h"
#include "BuildingId.h" #include "BuildingId.h"
#include "DebugDrawToggledEvent.h" #include "DebugDrawToggledEvent.h"
@@ -63,7 +64,7 @@ private:
void buildSingle(BuildingId id); void buildSingle(BuildingId id);
void buildMulti(const std::vector<BuildingId>& ids); void buildMulti(const std::vector<BuildingId>& ids);
void refreshBuffers(const Building* b); void refreshBuffers(const Building* b);
void buildSplitterFilters(QPoint splitterTile); void buildSplitterFilters(const std::optional<BeltSystem::SplitterInfo>& info);
const RecipeDef* findRecipe(const Building* b) const; const RecipeDef* findRecipe(const Building* b) const;
const ShipDef* findShipDef(const std::string& id) const; const ShipDef* findShipDef(const std::string& id) const;
std::vector<std::string> allItemIds() const; std::vector<std::string> allItemIds() const;
@@ -86,6 +87,7 @@ private:
QPushButton* m_configureLayoutBtn; QPushButton* m_configureLayoutBtn;
BuildingId m_singleBuildingId; BuildingId m_singleBuildingId;
bool m_singleIsSite = false; // selected single entity is a construction site
QPoint m_splitterTile; QPoint m_splitterTile;
std::string m_currentRecipeId; std::string m_currentRecipeId;