#include "BufferedBuildingContent.h" #include #include #include #include "Building.h" #include "BuildingTarget.h" #include "DisplayName.h" #include "FactoryQueries.h" #include "ProductionSection.h" #include "RecipeLineRow.h" #include "SectionBox.h" #include "SelectionNames.h" #include "Simulation.h" namespace { std::vector toAmounts(const std::map& map) { std::vector amounts; amounts.reserve(map.size()); for (const std::pair& entry : map) { amounts.push_back(RecipeLineRow::Amount{ entry.first, entry.second }); } return amounts; } // Every item one side of the card should list: what the buffer holds, what a cycle // moves, and what the building handles at all. A building's buffers can carry items it // is not currently making anything of, and an auto-recipe building between cycles names // nothing at all, so the three sources are unioned rather than one being picked. std::set collectItemIds(const std::map& buffered, const std::map& perCycle, const std::vector& handled) { std::set itemIds; for (const std::pair& entry : buffered) { itemIds.insert(entry.first); } for (const std::pair& entry : perCycle) { itemIds.insert(entry.first); } itemIds.insert(handled.begin(), handled.end()); return itemIds; } int lookUp(const std::map& map, const std::string& key) { const std::map::const_iterator it = map.find(key); return (it != map.end()) ? it->second : 0; } } // namespace BufferedBuildingContent::BufferedBuildingContent(const SelectionContext& context, BuildingId id, QWidget* parent) : SelectionContent(context, asConstructionSite(context, id), parent) , m_id(id) { // The summary is configuration -- what the building will do -- so it sits with the // selection control and is shown for a construction site too (REQ-UI-RECIPE-SUMMARY). m_recipeSummary = new RecipeLineRow(context.itemIcons, context.buildingIcons, this); getConfigurationLayout()->addWidget(m_recipeSummary); m_inputSection = new SectionBox(tr("Input buffers"), this); m_inputChips = new ItemChipRow(context, m_inputSection); m_inputSection->getContentLayout()->addWidget(m_inputChips); m_production = new ProductionSection(this); m_outputSection = new SectionBox(tr("Output buffer"), this); m_outputChips = new ItemChipRow(context, m_outputSection); m_outputSection->getContentLayout()->addWidget(m_outputChips); // In the direction the materials flow: what goes in, what is being made of it, what // has come out (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS). getRuntimeLayout()->addWidget(m_inputSection); getRuntimeLayout()->addWidget(m_production); getRuntimeLayout()->addWidget(m_outputSection); } void BufferedBuildingContent::refreshConfiguration() { const BuildingTarget target = resolveBuildingTarget(getContext(), m_id); if (!target.isValid()) { // Gone under the card. SelectionPanel rebuilds on the same refresh; this only // has to avoid reading it. return; } setBuildingIdentity(target.type, getBuildingTypeName(target.type)); // The card already names the building in its header, so the summary states the cycle // alone -- no building chip, no recipe name (REQ-UI-RECIPE-SUMMARY). const CycleInfo cycle = getCycleInfo(target); RecipeLineRow::Spec summary; summary.inputs = toAmounts(cycle.perCycleInputs); summary.outputs = toAmounts(cycle.perCycleOutputs); summary.durationSeconds = cycle.durationSeconds; m_recipeSummary->setLine(summary); refreshControls(target); } void BufferedBuildingContent::refreshRuntime() { const BuildingTarget target = resolveBuildingTarget(getContext(), m_id); if (!target.building) { return; } setProductionStatusSlot(*target.building); const CycleInfo cycle = getCycleInfo(target); const std::vector inputs = buildInputEntries(*target.building, cycle); const std::vector outputs = buildOutputEntries(*target.building, cycle); m_inputChips->setEntries(inputs); m_outputChips->setEntries(outputs); m_inputSection->setVisible(!inputs.empty()); m_outputSection->setVisible(!outputs.empty()); m_production->setProduction(cycle.runsProduction, *target.building, cycle.durationSeconds, getContext().sim->getCurrentTick()); } std::vector BufferedBuildingContent::buildInputEntries( const Building& building, const CycleInfo& cycle) const { std::map buffered; for (const std::pair& entry : building.inputBuffer.counts) { buffered[entry.first.id] = entry.second; } std::vector entries; for (const std::string& itemId : collectItemIds(buffered, cycle.perCycleInputs, cycle.handledInputs)) { // An auto-recipe building's buffers are sized over every recipe of its type, // including recipes still locked, so those entries are left out here // (REQ-UI-SINGLE-SELECTION, REQ-LOCK-UI-RECIPE). if (!getContext().sim->isItemUnlocked(itemId)) { continue; } ItemChipRow::Entry chip; chip.itemId = itemId; chip.countText = QString::number(lookUp(buffered, itemId)); const int perCycle = lookUp(cycle.perCycleInputs, itemId); if (perCycle > 0) { chip.subLine = tr("/ %1 per cycle").arg(perCycle); } else { chip.subLine = QString::fromStdString(toDisplayName(itemId)); } entries.push_back(chip); } return entries; } std::vector BufferedBuildingContent::buildOutputEntries( const Building& building, const CycleInfo& cycle) const { // The buffered items plus those still emerging onto the output belts: an emerging // item still belongs to the output buffer (REQ-MAT-OUTPUT-EMERGE), so leaving it out // would make it vanish from the panel while it animates. std::map buffered; for (const Item& item : building.outputBuffer.items) { buffered[item.type.id]++; } for (const std::vector& lane : building.emergingItems) { for (const BeltItemSlot& slot : lane) { buffered[slot.item.type.id]++; } } std::vector entries; for (const std::string& itemId : collectItemIds(buffered, cycle.perCycleOutputs, cycle.handledOutputs)) { if (!getContext().sim->isItemUnlocked(itemId)) { continue; } ItemChipRow::Entry chip; chip.itemId = itemId; // Counted against the buffer's capacity, which is what production stops at // (REQ-MAT-OUTPUT-BUFFER). chip.countText = building.outputBuffer.capacity > 0 ? tr("%1 / %2").arg(lookUp(buffered, itemId)) .arg(building.outputBuffer.capacity) : QString::number(lookUp(buffered, itemId)); chip.subLine = QString::fromStdString(toDisplayName(itemId)); entries.push_back(chip); } return entries; }