864 lines
24 KiB
C++
864 lines
24 KiB
C++
#include "SelectedBuildingPanel.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include <QComboBox>
|
|
#include <QLabel>
|
|
#include <QListWidget>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "BeltSystem.h"
|
|
#include "DynamicBodyComponent.h"
|
|
#include "EntityAdmin.h"
|
|
#include "EntitySelectedEvent.h"
|
|
#include "FactionComponent.h"
|
|
#include "HealthComponent.h"
|
|
#include "ModuleOwnerComponent.h"
|
|
#include "ShipIdentityComponent.h"
|
|
#include "ShipStatsCalculator.h"
|
|
#include "ShipStatsPanel.h"
|
|
#include "StationBodyComponent.h"
|
|
#include "TickAdvancedEvent.h"
|
|
#include "Building.h"
|
|
#include "BuildingSystem.h"
|
|
#include "BuildingType.h"
|
|
#include "ItemType.h"
|
|
#include "ModulesConfig.h"
|
|
#include "Rotation.h"
|
|
#include "ShipLayoutPreview.h"
|
|
#include "Simulation.h"
|
|
#include "WeaponComponent.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
QString buildingTypeName(BuildingType type)
|
|
{
|
|
const std::string id = buildingTypeId(type);
|
|
QString result;
|
|
bool nextUpper = true;
|
|
for (char c : id)
|
|
{
|
|
if (c == '_')
|
|
{
|
|
result += ' ';
|
|
nextUpper = true;
|
|
}
|
|
else if (nextUpper)
|
|
{
|
|
result += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
|
nextUpper = false;
|
|
}
|
|
else
|
|
{
|
|
result += c;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool isProductionBuilding(BuildingType type)
|
|
{
|
|
return type == BuildingType::Miner
|
|
|| type == BuildingType::Smelter
|
|
|| type == BuildingType::Assembler
|
|
|| type == BuildingType::ReprocessingPlant
|
|
|| type == BuildingType::Shipyard;
|
|
}
|
|
|
|
bool isBeltLike(BuildingType type)
|
|
{
|
|
return type == BuildingType::Belt || type == BuildingType::Splitter
|
|
|| type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit;
|
|
}
|
|
|
|
QString rotationLabel(Rotation r)
|
|
{
|
|
switch (r)
|
|
{
|
|
case Rotation::North: return QObject::tr("North (↑)");
|
|
case Rotation::East: return QObject::tr("East (→)");
|
|
case Rotation::South: return QObject::tr("South (↓)");
|
|
case Rotation::West: return QObject::tr("West (←)");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
|
const GameConfig* config,
|
|
QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_sim(sim)
|
|
, m_config(config)
|
|
, m_singleBuildingId(kInvalidBuildingId)
|
|
, m_splitterTile(0, 0)
|
|
{
|
|
m_layout = new QVBoxLayout(this);
|
|
m_layout->setContentsMargins(8, 8, 8, 8);
|
|
m_layout->setSpacing(4);
|
|
m_layout->setAlignment(Qt::AlignTop);
|
|
|
|
m_titleLabel = new QLabel(this);
|
|
m_recipeCombo = new QComboBox(this);
|
|
m_clearBeltBtn = new QPushButton(tr("Clear Items"), this);
|
|
m_filterALabel = new QLabel(this);
|
|
m_filterAList = new QListWidget(this);
|
|
m_filterBLabel = new QLabel(this);
|
|
m_filterBList = new QListWidget(this);
|
|
m_layoutPreview = new ShipLayoutPreview(this);
|
|
m_configureLayoutBtn = new QPushButton(tr("Configure Layout"), this);
|
|
m_buffersLabel = new QLabel(this);
|
|
m_buffersLabel->setWordWrap(true);
|
|
|
|
m_filterAList->setMaximumHeight(100);
|
|
m_filterBList->setMaximumHeight(100);
|
|
|
|
m_layout->addWidget(m_titleLabel);
|
|
m_layout->addWidget(m_recipeCombo);
|
|
m_layout->addWidget(m_layoutPreview);
|
|
m_layout->addWidget(m_configureLayoutBtn);
|
|
m_layout->addWidget(m_clearBeltBtn);
|
|
m_layout->addWidget(m_filterALabel);
|
|
m_layout->addWidget(m_filterAList);
|
|
m_layout->addWidget(m_filterBLabel);
|
|
m_layout->addWidget(m_filterBList);
|
|
m_layout->addWidget(m_buffersLabel);
|
|
|
|
connect(m_recipeCombo, qOverload<int>(&QComboBox::currentIndexChanged),
|
|
this, &SelectedBuildingPanel::onRecipeChanged);
|
|
connect(m_clearBeltBtn, &QPushButton::clicked,
|
|
this, &SelectedBuildingPanel::onClearBelt);
|
|
connect(m_configureLayoutBtn, &QPushButton::clicked, this, [this]() {
|
|
if (m_singleBuildingId != kInvalidBuildingId)
|
|
{
|
|
emit layoutDialogRequested(m_singleBuildingId);
|
|
}
|
|
});
|
|
connect(m_filterAList, &QListWidget::itemChanged,
|
|
this, &SelectedBuildingPanel::onSplitterFilterChanged);
|
|
connect(m_filterBList, &QListWidget::itemChanged,
|
|
this, &SelectedBuildingPanel::onSplitterFilterChanged);
|
|
|
|
m_entityTitleLabel = new QLabel(this);
|
|
QFont titleFont = m_entityTitleLabel->font();
|
|
titleFont.setBold(true);
|
|
m_entityTitleLabel->setFont(titleFont);
|
|
m_layout->addWidget(m_entityTitleLabel);
|
|
m_entityTitleLabel->hide();
|
|
|
|
m_entityStatsPanel = new ShipStatsPanel(config, this);
|
|
m_layout->addWidget(m_entityStatsPanel);
|
|
m_entityStatsPanel->hide();
|
|
|
|
m_stationStatsLabel = new QLabel(this);
|
|
m_stationStatsLabel->setWordWrap(true);
|
|
m_layout->addWidget(m_stationStatsLabel);
|
|
m_stationStatsLabel->hide();
|
|
|
|
buildEmpty();
|
|
|
|
registerForEvents();
|
|
}
|
|
|
|
SelectedBuildingPanel::~SelectedBuildingPanel()
|
|
{
|
|
unregisterForEvents();
|
|
}
|
|
|
|
void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
|
{
|
|
m_selectedBuildingIds = ids;
|
|
if (!ids.empty())
|
|
{
|
|
clearEntityDisplay();
|
|
}
|
|
rebuild();
|
|
}
|
|
|
|
void SelectedBuildingPanel::rebuild()
|
|
{
|
|
if (m_selectedBuildingIds.empty())
|
|
{
|
|
buildEmpty();
|
|
}
|
|
else if (m_selectedBuildingIds.size() == 1)
|
|
{
|
|
buildSingle(m_selectedBuildingIds[0]);
|
|
}
|
|
else
|
|
{
|
|
buildMulti(m_selectedBuildingIds);
|
|
}
|
|
}
|
|
|
|
void SelectedBuildingPanel::clearContent()
|
|
{
|
|
m_singleBuildingId = kInvalidBuildingId;
|
|
m_titleLabel->hide();
|
|
m_recipeCombo->hide();
|
|
m_layoutPreview->hide();
|
|
m_configureLayoutBtn->hide();
|
|
m_clearBeltBtn->hide();
|
|
m_filterALabel->hide();
|
|
m_filterAList->hide();
|
|
m_filterBLabel->hide();
|
|
m_filterBList->hide();
|
|
m_buffersLabel->hide();
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildEmpty()
|
|
{
|
|
clearContent();
|
|
m_entityTitleLabel->hide();
|
|
m_entityStatsPanel->hide();
|
|
m_stationStatsLabel->hide();
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildSingle(BuildingId id)
|
|
{
|
|
m_singleBuildingId = id;
|
|
|
|
const Building* b = m_sim->buildings().findBuilding(id);
|
|
if (!b)
|
|
{
|
|
const ConstructionSite* s = m_sim->buildings().findSite(id);
|
|
if (!s)
|
|
{
|
|
buildEmpty();
|
|
return;
|
|
}
|
|
|
|
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_titleLabel->setText(tr("(Building) %1").arg(buildingTypeName(s->type)));
|
|
m_titleLabel->show();
|
|
m_buffersLabel->setText(progress);
|
|
m_buffersLabel->show();
|
|
return;
|
|
}
|
|
|
|
m_titleLabel->setText(buildingTypeName(b->type));
|
|
m_titleLabel->show();
|
|
m_buffersLabel->show();
|
|
|
|
if (isProductionBuilding(b->type))
|
|
{
|
|
m_recipeCombo->blockSignals(true);
|
|
m_recipeCombo->clear();
|
|
|
|
m_recipeCombo->addItem(tr("(none)"), QString());
|
|
|
|
if (b->type == BuildingType::Shipyard)
|
|
{
|
|
for (const ShipDef& def : m_config->ships.ships)
|
|
{
|
|
if (m_sim->isSchematicUnlocked(def.id))
|
|
{
|
|
m_recipeCombo->addItem(
|
|
QString::fromStdString(def.id),
|
|
QString::fromStdString(def.id));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const RecipeDef& recipe : m_config->recipes.recipes)
|
|
{
|
|
if (recipe.building != b->type) { continue; }
|
|
if ((b->type == BuildingType::Miner || b->type == BuildingType::Assembler)
|
|
&& !m_sim->isRecipeUnlocked(recipe.id))
|
|
{
|
|
continue;
|
|
}
|
|
m_recipeCombo->addItem(
|
|
QString::fromStdString(recipe.id),
|
|
QString::fromStdString(recipe.id));
|
|
}
|
|
}
|
|
|
|
const int currentIdx = m_recipeCombo->findData(QString::fromStdString(b->recipeId));
|
|
m_recipeCombo->setCurrentIndex(currentIdx >= 0 ? currentIdx : 0);
|
|
m_recipeCombo->blockSignals(false);
|
|
m_recipeCombo->show();
|
|
|
|
if (b->type == BuildingType::Shipyard && !b->recipeId.empty())
|
|
{
|
|
const ShipDef* sDef = findShipDef(b->recipeId);
|
|
if (sDef && !sDef->layout.empty())
|
|
{
|
|
ShipLayoutConfig layout;
|
|
if (b->shipLayout.has_value())
|
|
{
|
|
layout = *b->shipLayout;
|
|
}
|
|
m_layoutPreview->setShipAndLayout(
|
|
sDef->layout, layout, &m_config->modules.modules);
|
|
m_layoutPreview->show();
|
|
m_configureLayoutBtn->show();
|
|
}
|
|
else
|
|
{
|
|
m_layoutPreview->hide();
|
|
m_configureLayoutBtn->hide();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_layoutPreview->hide();
|
|
m_configureLayoutBtn->hide();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_recipeCombo->hide();
|
|
m_layoutPreview->hide();
|
|
m_configureLayoutBtn->hide();
|
|
}
|
|
|
|
if (isBeltLike(b->type))
|
|
{
|
|
m_clearBeltBtn->show();
|
|
}
|
|
else
|
|
{
|
|
m_clearBeltBtn->hide();
|
|
}
|
|
|
|
if (b->type == BuildingType::Splitter)
|
|
{
|
|
m_splitterTile = b->anchor;
|
|
buildSplitterFilters(m_splitterTile);
|
|
}
|
|
else
|
|
{
|
|
m_filterALabel->hide();
|
|
m_filterAList->hide();
|
|
m_filterBLabel->hide();
|
|
m_filterBList->hide();
|
|
}
|
|
|
|
refreshBuffers(b);
|
|
}
|
|
|
|
void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|
{
|
|
const RecipeDef* recipe = findRecipe(b);
|
|
const ShipDef* shipDef = (b->type == BuildingType::Shipyard)
|
|
? findShipDef(b->recipeId)
|
|
: nullptr;
|
|
|
|
QString bufText;
|
|
|
|
if (!b->inputBuffer.counts.empty())
|
|
{
|
|
bufText += tr("Input: ");
|
|
for (const std::pair<const ItemType, int>& entry : b->inputBuffer.counts)
|
|
{
|
|
int perCycle = 0;
|
|
if (recipe)
|
|
{
|
|
for (const RecipeIngredient& ing : recipe->inputs)
|
|
{
|
|
if (ing.item == entry.first.id) { perCycle = ing.amount; break; }
|
|
}
|
|
}
|
|
else if (shipDef)
|
|
{
|
|
for (const RecipeIngredient& mat : shipDef->schematic.materials)
|
|
{
|
|
if (mat.item == entry.first.id) { perCycle = mat.amount; break; }
|
|
}
|
|
if (b->shipLayout.has_value())
|
|
{
|
|
for (const PlacedModule& pm : b->shipLayout->placedModules)
|
|
{
|
|
for (const ModuleDef& modDef : m_config->modules.modules)
|
|
{
|
|
if (modDef.id == pm.moduleId)
|
|
{
|
|
for (const RecipeIngredient& ing : modDef.materials)
|
|
{
|
|
if (ing.item == entry.first.id)
|
|
{
|
|
perCycle += ing.amount;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bufText += QString::fromStdString(entry.first.id)
|
|
+ ": " + QString::number(entry.second);
|
|
if (perCycle > 0)
|
|
{
|
|
bufText += "/" + QString::number(perCycle);
|
|
}
|
|
bufText += " ";
|
|
}
|
|
bufText += "\n";
|
|
}
|
|
|
|
if (recipe && !recipe->outputs.empty())
|
|
{
|
|
std::map<std::string, int> outCounts;
|
|
for (const Item& item : b->outputBuffer.items)
|
|
{
|
|
outCounts[item.type.id]++;
|
|
}
|
|
bufText += tr("Output: ");
|
|
for (const RecipeOutput& out : recipe->outputs)
|
|
{
|
|
const std::map<std::string, int>::const_iterator it =
|
|
outCounts.find(out.item);
|
|
const int count = (it != outCounts.end()) ? it->second : 0;
|
|
bufText += QString::fromStdString(out.item)
|
|
+ ": " + QString::number(count)
|
|
+ "/" + QString::number(out.amount) + " ";
|
|
}
|
|
}
|
|
else if (!b->outputBuffer.items.empty())
|
|
{
|
|
std::map<std::string, int> outCounts;
|
|
for (const Item& item : b->outputBuffer.items)
|
|
{
|
|
outCounts[item.type.id]++;
|
|
}
|
|
bufText += tr("Output: ");
|
|
for (const std::pair<const std::string, int>& entry : outCounts)
|
|
{
|
|
bufText += QString::fromStdString(entry.first)
|
|
+ ": " + QString::number(entry.second) + " ";
|
|
}
|
|
}
|
|
|
|
if (isProductionBuilding(b->type) && (recipe || shipDef))
|
|
{
|
|
double durationSeconds = recipe
|
|
? recipe->durationSeconds
|
|
: shipDef->schematic.productionTimeSeconds;
|
|
|
|
if (shipDef && b->shipLayout.has_value())
|
|
{
|
|
for (const PlacedModule& pm : b->shipLayout->placedModules)
|
|
{
|
|
for (const ModuleDef& modDef : m_config->modules.modules)
|
|
{
|
|
if (modDef.id == pm.moduleId)
|
|
{
|
|
durationSeconds += modDef.productionTimeSeconds;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bufText += tr("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);
|
|
|
|
if (b->production.has_value())
|
|
{
|
|
const Tick cycleTicks = secondsToTicks(durationSeconds);
|
|
const Tick completesAt = b->production->completesAt;
|
|
const Tick currentTick = m_sim->currentTick();
|
|
const Tick elapsed = currentTick - (completesAt - cycleTicks);
|
|
const int pct = static_cast<int>(
|
|
std::max(Tick(0), std::min(cycleTicks, elapsed)) * 100 / cycleTicks);
|
|
bufText += tr("Progress: %1%\n").arg(pct);
|
|
}
|
|
else
|
|
{
|
|
bufText += tr("Progress: idle\n");
|
|
}
|
|
}
|
|
|
|
m_buffersLabel->setText(bufText);
|
|
|
|
if (b->type == BuildingType::Shipyard && shipDef && !shipDef->layout.empty())
|
|
{
|
|
ShipLayoutConfig layout;
|
|
if (b->shipLayout.has_value())
|
|
{
|
|
layout = *b->shipLayout;
|
|
}
|
|
m_layoutPreview->setShipAndLayout(
|
|
shipDef->layout, layout, &m_config->modules.modules);
|
|
}
|
|
}
|
|
|
|
const RecipeDef* SelectedBuildingPanel::findRecipe(const Building* b) const
|
|
{
|
|
if (b->recipeId.empty()) { return nullptr; }
|
|
for (const RecipeDef& r : m_config->recipes.recipes)
|
|
{
|
|
if (r.id == b->recipeId && r.building == b->type) { return &r; }
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
|
|
{
|
|
if (id.empty()) { return nullptr; }
|
|
for (const ShipDef& s : m_config->ships.ships)
|
|
{
|
|
if (s.id == id) { return &s; }
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
|
|
{
|
|
if (m_selectedEntity.has_value())
|
|
{
|
|
refreshEntityStats();
|
|
return;
|
|
}
|
|
|
|
if (m_singleBuildingId == kInvalidBuildingId) { return; }
|
|
const Building* b = m_sim->buildings().findBuilding(m_singleBuildingId);
|
|
if (b)
|
|
{
|
|
if (m_titleLabel->text().startsWith(tr("(Building) ")))
|
|
{
|
|
rebuild();
|
|
}
|
|
else
|
|
{
|
|
refreshBuffers(b);
|
|
}
|
|
return;
|
|
}
|
|
const ConstructionSite* s = m_sim->buildings().findSite(m_singleBuildingId);
|
|
if (s)
|
|
{
|
|
rebuild();
|
|
return;
|
|
}
|
|
buildEmpty();
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildMulti(const std::vector<BuildingId>& ids)
|
|
{
|
|
m_singleBuildingId = kInvalidBuildingId;
|
|
m_recipeCombo->hide();
|
|
m_clearBeltBtn->hide();
|
|
m_filterALabel->hide();
|
|
m_filterAList->hide();
|
|
m_filterBLabel->hide();
|
|
m_filterBList->hide();
|
|
m_buffersLabel->hide();
|
|
|
|
std::map<BuildingType, int> counts;
|
|
for (BuildingId id : ids)
|
|
{
|
|
const Building* b = m_sim->buildings().findBuilding(id);
|
|
if (b)
|
|
{
|
|
counts[b->type]++;
|
|
continue;
|
|
}
|
|
const ConstructionSite* s = m_sim->buildings().findSite(id);
|
|
if (s)
|
|
{
|
|
counts[s->type]++;
|
|
}
|
|
}
|
|
|
|
bool hasBelt = false;
|
|
QString text;
|
|
for (const std::pair<const BuildingType, int>& entry : counts)
|
|
{
|
|
text += buildingTypeName(entry.first) + ": "
|
|
+ QString::number(entry.second) + "\n";
|
|
if (isBeltLike(entry.first))
|
|
{
|
|
hasBelt = true;
|
|
}
|
|
}
|
|
m_titleLabel->setText(text.trimmed());
|
|
m_titleLabel->show();
|
|
|
|
if (hasBelt)
|
|
{
|
|
m_clearBeltBtn->show();
|
|
}
|
|
}
|
|
|
|
void SelectedBuildingPanel::onRecipeChanged(int comboIndex)
|
|
{
|
|
if (m_singleBuildingId == kInvalidBuildingId)
|
|
{
|
|
return;
|
|
}
|
|
const QString recipeId = m_recipeCombo->itemData(comboIndex).toString();
|
|
m_sim->buildings().setRecipe(m_singleBuildingId, recipeId.toStdString());
|
|
rebuild();
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildSplitterFilters(QPoint splitterTile)
|
|
{
|
|
const std::optional<BeltSystem::SplitterInfo> info =
|
|
m_sim->belts().getSplitterInfo(splitterTile);
|
|
if (!info.has_value())
|
|
{
|
|
m_filterALabel->hide();
|
|
m_filterAList->hide();
|
|
m_filterBLabel->hide();
|
|
m_filterBList->hide();
|
|
return;
|
|
}
|
|
|
|
const std::vector<std::string> items = allItemIds();
|
|
|
|
auto populateList = [&](QListWidget* list, QLabel* label,
|
|
const QString& dirLabel,
|
|
const std::vector<ItemType>& filter)
|
|
{
|
|
label->setText(tr("%1 filter (empty = all):").arg(dirLabel));
|
|
list->blockSignals(true);
|
|
list->clear();
|
|
for (const std::string& itemId : items)
|
|
{
|
|
if (!m_sim->isItemUnlocked(itemId)) { continue; }
|
|
QListWidgetItem* row = new QListWidgetItem(
|
|
QString::fromStdString(itemId), list);
|
|
const bool checked = filter.empty()
|
|
? false
|
|
: std::find(filter.begin(), filter.end(),
|
|
ItemType{itemId}) != filter.end();
|
|
row->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
|
|
row->setFlags(row->flags() | Qt::ItemIsUserCheckable);
|
|
}
|
|
list->blockSignals(false);
|
|
label->show();
|
|
list->show();
|
|
};
|
|
|
|
populateList(m_filterAList, m_filterALabel,
|
|
rotationLabel(info->outputA), info->filterA);
|
|
populateList(m_filterBList, m_filterBLabel,
|
|
rotationLabel(info->outputB), info->filterB);
|
|
}
|
|
|
|
void SelectedBuildingPanel::onSplitterFilterChanged()
|
|
{
|
|
if (m_singleBuildingId == kInvalidBuildingId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto collectFilter = [](QListWidget* list) -> std::vector<ItemType>
|
|
{
|
|
std::vector<ItemType> filter;
|
|
for (int i = 0; i < list->count(); ++i)
|
|
{
|
|
const QListWidgetItem* row = list->item(i);
|
|
if (row->checkState() == Qt::Checked)
|
|
{
|
|
filter.push_back(ItemType{row->text().toStdString()});
|
|
}
|
|
}
|
|
return filter;
|
|
};
|
|
|
|
m_sim->belts().setSplitterFilters(
|
|
m_splitterTile,
|
|
collectFilter(m_filterAList),
|
|
collectFilter(m_filterBList));
|
|
}
|
|
|
|
std::vector<std::string> SelectedBuildingPanel::allItemIds() const
|
|
{
|
|
std::set<std::string> seen;
|
|
for (const RecipeDef& recipe : m_config->recipes.recipes)
|
|
{
|
|
for (const RecipeIngredient& ing : recipe.inputs)
|
|
{
|
|
seen.insert(ing.item);
|
|
}
|
|
for (const RecipeOutput& out : recipe.outputs)
|
|
{
|
|
seen.insert(out.item);
|
|
}
|
|
}
|
|
return std::vector<std::string>(seen.begin(), seen.end());
|
|
}
|
|
|
|
void SelectedBuildingPanel::onClearBelt()
|
|
{
|
|
std::vector<QPoint> tiles;
|
|
for (BuildingId id : m_selectedBuildingIds)
|
|
{
|
|
const Building* b = m_sim->buildings().findBuilding(id);
|
|
if (b && isBeltLike(b->type))
|
|
{
|
|
for (const QPoint& cell : b->bodyCells)
|
|
{
|
|
tiles.push_back(cell);
|
|
}
|
|
}
|
|
}
|
|
if (!tiles.empty())
|
|
{
|
|
m_sim->belts().clearTiles(tiles);
|
|
}
|
|
}
|
|
|
|
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const EntitySelectedEvent> event)
|
|
{
|
|
if (event->entity.has_value())
|
|
{
|
|
m_selectedEntity = event->entity;
|
|
m_selectedBuildingIds.clear();
|
|
clearContent();
|
|
|
|
EntityAdmin& admin = m_sim->admin();
|
|
entt::entity entity = *m_selectedEntity;
|
|
|
|
if (!admin.isValid(entity))
|
|
{
|
|
clearEntityDisplay();
|
|
return;
|
|
}
|
|
|
|
if (admin.hasAll<ShipIdentityComponent>(entity))
|
|
{
|
|
buildEntityShip(entity);
|
|
}
|
|
else if (admin.hasAll<StationBodyComponent>(entity))
|
|
{
|
|
buildEntityStation(entity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clearEntityDisplay();
|
|
}
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildEntityShip(entt::entity entity)
|
|
{
|
|
EntityAdmin& admin = m_sim->admin();
|
|
const ShipIdentityComponent& identity = admin.get<ShipIdentityComponent>(entity);
|
|
const HealthComponent& health = admin.get<HealthComponent>(entity);
|
|
|
|
m_entityTitleLabel->setText(tr("Ship: %1 (Lv %2)")
|
|
.arg(QString::fromStdString(identity.schematicId))
|
|
.arg(identity.level));
|
|
m_entityTitleLabel->show();
|
|
|
|
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
|
|
m_entityStatsPanel->refreshFromLive(stats, health.hp);
|
|
m_entityStatsPanel->show();
|
|
|
|
m_stationStatsLabel->hide();
|
|
}
|
|
|
|
void SelectedBuildingPanel::buildEntityStation(entt::entity entity)
|
|
{
|
|
EntityAdmin& admin = m_sim->admin();
|
|
const HealthComponent& health = admin.get<HealthComponent>(entity);
|
|
|
|
m_entityTitleLabel->setText(tr("Defence Station"));
|
|
m_entityTitleLabel->show();
|
|
|
|
float totalDps = 0.0f;
|
|
float maxRange = 0.0f;
|
|
bool hasWeapons = false;
|
|
|
|
admin.forEach<ModuleOwnerComponent, WeaponComponent>(
|
|
[&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
|
|
{
|
|
if (owner.owner != entity) { return; }
|
|
hasWeapons = true;
|
|
totalDps += w.damage * w.fireRateHz;
|
|
if (w.range_tiles > maxRange) { maxRange = w.range_tiles; }
|
|
});
|
|
|
|
QString statsText = tr("HP: %1 / %2")
|
|
.arg(static_cast<int>(health.hp + 0.5f))
|
|
.arg(static_cast<int>(health.maxHp + 0.5f));
|
|
|
|
if (hasWeapons)
|
|
{
|
|
statsText += tr("\nDPS: %1").arg(QString::number(static_cast<double>(totalDps), 'f', 1));
|
|
statsText += tr("\nRange: %1 tiles").arg(QString::number(static_cast<double>(maxRange), 'f', 1));
|
|
}
|
|
|
|
m_stationStatsLabel->setText(statsText);
|
|
m_stationStatsLabel->show();
|
|
|
|
m_entityStatsPanel->hide();
|
|
}
|
|
|
|
void SelectedBuildingPanel::refreshEntityStats()
|
|
{
|
|
if (!m_selectedEntity.has_value()) { return; }
|
|
|
|
EntityAdmin& admin = m_sim->admin();
|
|
entt::entity entity = *m_selectedEntity;
|
|
|
|
if (!admin.isValid(entity))
|
|
{
|
|
clearEntityDisplay();
|
|
return;
|
|
}
|
|
|
|
const HealthComponent& health = admin.get<HealthComponent>(entity);
|
|
if (health.hp <= 0.0f)
|
|
{
|
|
clearEntityDisplay();
|
|
return;
|
|
}
|
|
|
|
if (admin.hasAll<ShipIdentityComponent>(entity))
|
|
{
|
|
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
|
|
m_entityStatsPanel->refreshFromLive(stats, health.hp);
|
|
}
|
|
else if (admin.hasAll<StationBodyComponent>(entity))
|
|
{
|
|
buildEntityStation(entity);
|
|
}
|
|
}
|
|
|
|
void SelectedBuildingPanel::clearEntityDisplay()
|
|
{
|
|
m_selectedEntity = std::nullopt;
|
|
m_entityTitleLabel->hide();
|
|
m_entityStatsPanel->hide();
|
|
m_stationStatsLabel->hide();
|
|
}
|