Fix Salvage Bay drop-off not working by adding config-driven buffer capacity
This commit is contained in:
@@ -81,6 +81,7 @@ id = "salvage_bay"
|
|||||||
cost = 25
|
cost = 25
|
||||||
player_placeable = true
|
player_placeable = true
|
||||||
construction_time_seconds = 1
|
construction_time_seconds = 1
|
||||||
|
output_buffer_capacity = 20
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"<AAS",
|
"<AAS",
|
||||||
" AAS",
|
" AAS",
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ id = "salvage_bay"
|
|||||||
cost = 25
|
cost = 25
|
||||||
player_placeable = true
|
player_placeable = true
|
||||||
construction_time_seconds = 15
|
construction_time_seconds = 15
|
||||||
|
output_buffer_capacity = 20
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"SAA",
|
"SAA",
|
||||||
"SAA>",
|
"SAA>",
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`. Only implicitly unlocked recipes are available for selection (REQ-LOCK-UI-RECIPE).
|
- REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`. Only implicitly unlocked recipes are available for selection (REQ-LOCK-UI-RECIPE).
|
||||||
- REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE); the pool of eligible outputs is restricted to implicitly unlocked item types (REQ-LOCK-REPROCESSING-POOL). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING.
|
- REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE); the pool of eligible outputs is restricted to implicitly unlocked item types (REQ-LOCK-REPROCESSING-POOL). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING.
|
||||||
- REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a schematic. When all required materials — the ship's base materials (`[ship.schematic].materials`) plus the materials of all modules in the configured layout (REQ-MOD-MATERIALS) — are present in its input buffer, the shipyard consumes them and begins a production cycle lasting the ship's base `[ship.schematic].production_time_seconds` plus the sum of production times contributed by all module instances in the configured layout (REQ-MOD-PRODUCTION-TIME). One ship of that type is spawned with the configured modules when the cycle completes. The shipyard cannot start a new cycle while one is in progress. If the player confirms a layout change (REQ-MOD-UI-DIALOG) while a production cycle is in progress, the current cycle is cancelled and all consumed materials are discarded; the shipyard returns to idle with the new layout configuration.
|
- REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a schematic. When all required materials — the ship's base materials (`[ship.schematic].materials`) plus the materials of all modules in the configured layout (REQ-MOD-MATERIALS) — are present in its input buffer, the shipyard consumes them and begins a production cycle lasting the ship's base `[ship.schematic].production_time_seconds` plus the sum of production times contributed by all module instances in the configured layout (REQ-MOD-PRODUCTION-TIME). One ship of that type is spawned with the configured modules when the cycle completes. The shipyard cannot start a new cycle while one is in progress. If the player confirms a layout change (REQ-MOD-UI-DIALOG) while a production cycle is in progress, the current cycle is cancelled and all consumed materials are discarded; the shipyard returns to idle with the new layout configuration.
|
||||||
- REQ-BLD-SALVAGE-BAY: **Salvage Bay** (3×2): A dedicated drop-off point for salvage ships. Scrap delivered here is placed onto connected output belts.
|
- REQ-BLD-SALVAGE-BAY: **Salvage Bay** (3×2): A dedicated drop-off point for salvage ships. It has an output buffer whose holding capacity is defined by the `output_buffer_capacity` field of the `salvage_bay` entry in `buildings.toml` (rather than by a production cycle, since the Salvage Bay has no recipe). A ship at the bay hands over one unit of scrap per tick while the buffer has free space; a full buffer blocks further drop-off until space frees up (consistent with the buffer-full semantics of REQ-MAT-OUTPUT-BUFFER). Held scrap is pushed onto connected output belts.
|
||||||
- REQ-BLD-BELT: **Belt** (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in `world.toml [world].belt_speed_tiles_per_second` (REQ-GW-BELT-SPEED).
|
- REQ-BLD-BELT: **Belt** (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in `world.toml [world].belt_speed_tiles_per_second` (REQ-GW-BELT-SPEED).
|
||||||
- REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel; only implicitly unlocked item types are available as filter options (REQ-LOCK-UI-SPLITTER). Routing rules:
|
- REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel; only implicitly unlocked item types are available as filter options (REQ-LOCK-UI-SPLITTER). Routing rules:
|
||||||
- An item matching only one output's filter is routed to that output.
|
- An item matching only one output's filter is routed to that output.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -18,6 +19,10 @@ struct BuildingDef
|
|||||||
// Stored as raw strings here; parsing into per-cell tiles + output ports
|
// Stored as raw strings here; parsing into per-cell tiles + output ports
|
||||||
// happens when buildings are placed, not at load time.
|
// happens when buildings are placed, not at load time.
|
||||||
std::vector<std::string> surfaceMask;
|
std::vector<std::string> surfaceMask;
|
||||||
|
|
||||||
|
// Output-buffer holding size for buildings without a recipe-driven buffer.
|
||||||
|
// Only the Salvage Bay sets this (REQ-BLD-SALVAGE-BAY).
|
||||||
|
std::optional<int> outputBufferCapacity;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BuildingsConfig
|
struct BuildingsConfig
|
||||||
|
|||||||
@@ -337,6 +337,12 @@ BuildingsConfig ConfigLoader::loadBuildings(const std::string& path)
|
|||||||
def.constructionTimeSeconds = requireDouble(mt["construction_time_seconds"], file, elemPath + ".construction_time_seconds");
|
def.constructionTimeSeconds = requireDouble(mt["construction_time_seconds"], file, elemPath + ".construction_time_seconds");
|
||||||
def.surfaceMask = requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
|
def.surfaceMask = requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
|
||||||
|
|
||||||
|
if (mt.contains("output_buffer_capacity"))
|
||||||
|
{
|
||||||
|
def.outputBufferCapacity = static_cast<int>(
|
||||||
|
requireInt(mt["output_buffer_capacity"], file, elemPath + ".output_buffer_capacity"));
|
||||||
|
}
|
||||||
|
|
||||||
const std::optional<BuildingType> parsedType = parseBuildingType(def.id);
|
const std::optional<BuildingType> parsedType = parseBuildingType(def.id);
|
||||||
if (!parsedType)
|
if (!parsedType)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -154,6 +154,16 @@ void BuildingSystem::initShipyardBuffers(Building& b) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuildingSystem::initSalvageBayBuffer(Building& b) const
|
||||||
|
{
|
||||||
|
// Salvage Bay has no recipe-driven buffer; its output-buffer holding size for
|
||||||
|
// ship drop-off is config-defined (REQ-BLD-SALVAGE-BAY).
|
||||||
|
b.outputBuffer.items.clear();
|
||||||
|
const BuildingDef* def = findBuildingDef(BuildingType::SalvageBay);
|
||||||
|
b.outputBuffer.capacity =
|
||||||
|
(def && def->outputBufferCapacity) ? *def->outputBufferCapacity : 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
|
std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
|
||||||
{
|
{
|
||||||
// Build lookup sets for quick membership checks.
|
// Build lookup sets for quick membership checks.
|
||||||
@@ -577,7 +587,11 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
|||||||
}
|
}
|
||||||
building.inputPorts = computeInputPorts(building);
|
building.inputPorts = computeInputPorts(building);
|
||||||
|
|
||||||
if (!building.recipeId.empty())
|
if (building.type == BuildingType::SalvageBay)
|
||||||
|
{
|
||||||
|
initSalvageBayBuffer(building);
|
||||||
|
}
|
||||||
|
else if (!building.recipeId.empty())
|
||||||
{
|
{
|
||||||
if (building.type == BuildingType::Shipyard)
|
if (building.type == BuildingType::Shipyard)
|
||||||
{
|
{
|
||||||
@@ -1238,6 +1252,11 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
|
|||||||
}
|
}
|
||||||
building.inputPorts = computeInputPorts(building);
|
building.inputPorts = computeInputPorts(building);
|
||||||
|
|
||||||
|
if (type == BuildingType::SalvageBay)
|
||||||
|
{
|
||||||
|
initSalvageBayBuffer(building);
|
||||||
|
}
|
||||||
|
|
||||||
m_buildings.push_back(std::move(building));
|
m_buildings.push_back(std::move(building));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ private:
|
|||||||
const ModuleDef* findModuleDef(const std::string& id) const;
|
const ModuleDef* findModuleDef(const std::string& id) const;
|
||||||
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
||||||
void initShipyardBuffers(Building& b) const;
|
void initShipyardBuffers(Building& b) const;
|
||||||
|
void initSalvageBayBuffer(Building& b) const;
|
||||||
std::vector<Port> computeInputPorts(const Building& b) const;
|
std::vector<Port> computeInputPorts(const Building& b) const;
|
||||||
std::vector<Item> rollReprocessingOutput(const RecipeDef& recipe);
|
std::vector<Item> rollReprocessingOutput(const RecipeDef& recipe);
|
||||||
bool bodyCellsWithinWorldBounds(
|
bool bodyCellsWithinWorldBounds(
|
||||||
|
|||||||
@@ -984,6 +984,45 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
|
|||||||
REQUIRE(i.target.x() < pos(f.admin, ship).value.x());
|
REQUIRE(i.target.x() < pos(f.admin, ship).value.x());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo", "[behavior]")
|
||||||
|
{
|
||||||
|
Fixture f;
|
||||||
|
|
||||||
|
const BuildingId bayId = f.buildings.place(BuildingType::SalvageBay,
|
||||||
|
QPoint(-4, 0), Rotation::East, 0);
|
||||||
|
Tick t = 0;
|
||||||
|
for (int i = 0; i < 500; ++i)
|
||||||
|
{
|
||||||
|
f.buildings.tickConstruction(t++);
|
||||||
|
if (f.buildings.findBuilding(bayId) != nullptr) { break; }
|
||||||
|
}
|
||||||
|
const Building* bay = f.buildings.findBuilding(bayId);
|
||||||
|
REQUIRE(bay != nullptr);
|
||||||
|
// Config-driven output-buffer capacity is applied on placement (REQ-BLD-SALVAGE-BAY).
|
||||||
|
REQUIRE(bay->outputBuffer.capacity == 20);
|
||||||
|
|
||||||
|
const QVector2D bayCenter(bay->anchor.x() + bay->footprint.width() / 2.0f,
|
||||||
|
bay->anchor.y() + bay->footprint.height() / 2.0f);
|
||||||
|
|
||||||
|
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvager");
|
||||||
|
const entt::entity ship = f.ships.spawn("salvage_ship", bayCenter, false, salvageLayout);
|
||||||
|
f.admin.get<PositionComponent>(ship).value = bayCenter;
|
||||||
|
CargoComponent& cargo = f.admin.get<CargoComponent>(ship);
|
||||||
|
cargo.current = cargo.maxCapacity; // full cargo
|
||||||
|
const int before = cargo.current;
|
||||||
|
REQUIRE(before > 0);
|
||||||
|
f.admin.get<DeliverScrapBehavior>(ship).deliveryBay = bayId;
|
||||||
|
|
||||||
|
f.salvageTick();
|
||||||
|
|
||||||
|
// One unit handed over from cargo into the bay's output buffer.
|
||||||
|
REQUIRE(f.admin.get<CargoComponent>(ship).current == before - 1);
|
||||||
|
const Building* bayAfter = f.buildings.findBuilding(bayId);
|
||||||
|
REQUIRE(bayAfter != nullptr);
|
||||||
|
REQUIRE(bayAfter->outputBuffer.items.size() == 1);
|
||||||
|
REQUIRE(bayAfter->outputBuffer.items.front().type.id == "scrap");
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Collection range (per-module)
|
// Collection range (per-module)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c
|
|||||||
REQUIRE(minerIt != cfg.buildings.buildings.end());
|
REQUIRE(minerIt != cfg.buildings.buildings.end());
|
||||||
REQUIRE(minerIt->cost == 15);
|
REQUIRE(minerIt->cost == 15);
|
||||||
REQUIRE(minerIt->surfaceMask.size() == 2);
|
REQUIRE(minerIt->surfaceMask.size() == 2);
|
||||||
|
// Miner has no output-buffer-capacity override; the Salvage Bay does.
|
||||||
|
REQUIRE_FALSE(minerIt->outputBufferCapacity.has_value());
|
||||||
|
|
||||||
|
const auto salvageBayIt = std::find_if(
|
||||||
|
cfg.buildings.buildings.begin(), cfg.buildings.buildings.end(),
|
||||||
|
[](const BuildingDef& b) { return b.type == BuildingType::SalvageBay; });
|
||||||
|
REQUIRE(salvageBayIt != cfg.buildings.buildings.end());
|
||||||
|
REQUIRE(salvageBayIt->outputBufferCapacity.has_value());
|
||||||
|
REQUIRE(*salvageBayIt->outputBufferCapacity == 20);
|
||||||
|
|
||||||
// recipes.toml — reprocessing cycle has three weighted outputs.
|
// recipes.toml — reprocessing cycle has three weighted outputs.
|
||||||
const auto reproIt = std::find_if(
|
const auto reproIt = std::find_if(
|
||||||
|
|||||||
Reference in New Issue
Block a user