implement ship modules

This commit is contained in:
2026-05-18 08:49:51 +02:00
parent b59e392461
commit d08bf5d37b
33 changed files with 1911 additions and 56 deletions

View File

@@ -11,7 +11,8 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
BeltSystem& belts,
std::function<EntityId()> allocateId,
std::function<void(int)> addBuildingBlocks,
std::function<void(const std::string&, QVector2D)> spawnShip,
std::function<void(const std::string&, QVector2D,
const std::optional<ShipLayoutConfig>&)> spawnShip,
std::mt19937& rng)
: m_config(config)
, m_belts(belts)
@@ -63,6 +64,18 @@ const ShipDef* BuildingSystem::findShipDef(const std::string& id) const
return nullptr;
}
const ModuleDef* BuildingSystem::findModuleDef(const std::string& id) const
{
for (const ModuleDef& def : m_config.modules.modules)
{
if (def.id == id)
{
return &def;
}
}
return nullptr;
}
void BuildingSystem::initBuffers(Building& b, const RecipeDef& recipe) const
{
b.inputBuffer.counts.clear();
@@ -117,6 +130,23 @@ void BuildingSystem::initShipyardBuffers(Building& b) const
b.inputBuffer.counts[type] = 0;
b.inputBuffer.caps[type] = 2 * ing.amount;
}
if (b.shipLayout.has_value())
{
for (const PlacedModule& pm : b.shipLayout->placedModules)
{
const ModuleDef* modDef = findModuleDef(pm.moduleId);
if (!modDef)
{
continue;
}
for (const RecipeIngredient& ing : modDef->materials)
{
const ItemType type{ing.item};
b.inputBuffer.counts.try_emplace(type, 0);
b.inputBuffer.caps[type] += 2 * ing.amount;
}
}
}
}
std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
@@ -303,6 +333,7 @@ void BuildingSystem::setRecipe(EntityId id, const std::string& recipeId)
if (site.id == id)
{
site.recipeId = recipeId;
site.shipLayout = std::nullopt;
return;
}
}
@@ -313,6 +344,7 @@ void BuildingSystem::setRecipe(EntityId id, const std::string& recipeId)
if (building.id == id)
{
building.recipeId = recipeId;
building.shipLayout = std::nullopt;
building.inputBuffer.counts.clear();
building.inputBuffer.caps.clear();
building.outputBuffer.items.clear();
@@ -339,6 +371,39 @@ void BuildingSystem::setRecipe(EntityId id, const std::string& recipeId)
}
}
void BuildingSystem::setShipLayout(EntityId id, const ShipLayoutConfig& layout)
{
for (ConstructionSite& site : m_constructionQueue)
{
if (site.id == id)
{
site.shipLayout = layout;
return;
}
}
for (Building& building : m_buildings)
{
if (building.id == id)
{
if (building.production.has_value())
{
building.production = std::nullopt;
}
building.shipLayout = layout;
building.inputBuffer.counts.clear();
building.inputBuffer.caps.clear();
building.outputBuffer.items.clear();
building.outputBuffer.capacity = 0;
if (!building.recipeId.empty() && building.type == BuildingType::Shipyard)
{
initShipyardBuffers(building);
}
return;
}
}
}
// ---------------------------------------------------------------------------
// Tick hooks
// ---------------------------------------------------------------------------
@@ -383,6 +448,7 @@ void BuildingSystem::tickConstruction(Tick currentTick)
building.hp = 100.0f;
building.maxHp = 100.0f;
building.recipeId = front.recipeId;
building.shipLayout = front.shipLayout;
for (const QPoint& cell : mask.bodyCells)
{
@@ -657,22 +723,44 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
{
const Port& p = building.outputPorts[0];
const QVector2D spawnPos(p.tile.x() + 0.5f, p.tile.y() + 0.5f);
m_spawnShip(building.recipeId, spawnPos);
m_spawnShip(building.recipeId, spawnPos, building.shipLayout);
}
building.production = std::nullopt;
}
continue;
}
// Idle: check if all materials are available to start a new cycle.
bool inputsOk = true;
// Build combined materials list (base + modules).
std::map<std::string, int> requiredMaterials;
for (const RecipeIngredient& ing : shipDef->schematic.materials)
{
const ItemType type{ing.item};
requiredMaterials[ing.item] += ing.amount;
}
if (building.shipLayout.has_value())
{
for (const PlacedModule& pm : building.shipLayout->placedModules)
{
const ModuleDef* modDef = findModuleDef(pm.moduleId);
if (!modDef)
{
continue;
}
for (const RecipeIngredient& ing : modDef->materials)
{
requiredMaterials[ing.item] += ing.amount;
}
}
}
// Idle: check if all combined materials are available.
bool inputsOk = true;
for (const std::pair<const std::string, int>& req : requiredMaterials)
{
const ItemType type{req.first};
const std::map<ItemType, int>::const_iterator it =
building.inputBuffer.counts.find(type);
const int have = (it != building.inputBuffer.counts.end()) ? it->second : 0;
if (have < ing.amount)
if (have < req.second)
{
inputsOk = false;
break;
@@ -683,16 +771,28 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
continue;
}
// Consume materials and start the production cycle.
for (const RecipeIngredient& ing : shipDef->schematic.materials)
// Consume combined materials and start the production cycle.
for (const std::pair<const std::string, int>& req : requiredMaterials)
{
building.inputBuffer.counts[ItemType{ing.item}] -= ing.amount;
building.inputBuffer.counts[ItemType{req.first}] -= req.second;
}
double totalTime = shipDef->schematic.productionTimeSeconds;
if (building.shipLayout.has_value())
{
for (const PlacedModule& pm : building.shipLayout->placedModules)
{
const ModuleDef* modDef = findModuleDef(pm.moduleId);
if (modDef)
{
totalTime += modDef->productionTimeSeconds;
}
}
}
Production prod;
prod.recipeId = building.recipeId;
prod.completesAt = currentTick
+ secondsToTicks(shipDef->schematic.productionTimeSeconds);
prod.completesAt = currentTick + secondsToTicks(totalTime);
building.production = std::move(prod);
}
}