Files
dota_factory/src/ui/selection/BufferedBuildingContent.cpp

214 lines
7.5 KiB
C++

#include "BufferedBuildingContent.h"
#include <map>
#include <set>
#include <QVBoxLayout>
#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<RecipeLineRow::Amount> toAmounts(const std::map<std::string, int>& map)
{
std::vector<RecipeLineRow::Amount> amounts;
amounts.reserve(map.size());
for (const std::pair<const std::string, int>& 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<std::string> collectItemIds(const std::map<std::string, int>& buffered,
const std::map<std::string, int>& perCycle,
const std::vector<std::string>& handled)
{
std::set<std::string> itemIds;
for (const std::pair<const std::string, int>& entry : buffered)
{
itemIds.insert(entry.first);
}
for (const std::pair<const std::string, int>& entry : perCycle)
{
itemIds.insert(entry.first);
}
itemIds.insert(handled.begin(), handled.end());
return itemIds;
}
int lookUp(const std::map<std::string, int>& map, const std::string& key)
{
const std::map<std::string, int>::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<ItemChipRow::Entry> inputs =
buildInputEntries(*target.building, cycle);
const std::vector<ItemChipRow::Entry> 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<ItemChipRow::Entry> BufferedBuildingContent::buildInputEntries(
const Building& building, const CycleInfo& cycle) const
{
std::map<std::string, int> buffered;
for (const std::pair<const ItemType, int>& entry : building.inputBuffer.counts)
{
buffered[entry.first.id] = entry.second;
}
std::vector<ItemChipRow::Entry> 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<ItemChipRow::Entry> 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<std::string, int> buffered;
for (const Item& item : building.outputBuffer.items)
{
buffered[item.type.id]++;
}
for (const std::vector<BeltItemSlot>& lane : building.emergingItems)
{
for (const BeltItemSlot& slot : lane)
{
buffered[slot.item.type.id]++;
}
}
std::vector<ItemChipRow::Entry> 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;
}