#include "BlueprintPanel.h" #include #include #include #include #include #include #include #include #include #include #include "BlueprintSerializer.h" #include "Building.h" #include "BuildingSystem.h" #include "Simulation.h" BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidget* parent) : QWidget(parent) , m_sim(sim) , m_config(config) , m_currentBlocks(0) , m_deleteMode(false) , m_activeIndex(-1) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(4, 4, 4, 4); layout->setSpacing(4); m_createBtn = new QPushButton("Create Blueprint", this); m_createBtn->setFixedHeight(48); m_createBtn->setEnabled(false); layout->addWidget(m_createBtn); m_deleteBtn = new QPushButton("Delete Blueprint", this); m_deleteBtn->setFixedHeight(48); m_deleteBtn->setCheckable(true); layout->addWidget(m_deleteBtn); QScrollArea* scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_buttonsContainer = new QWidget(scrollArea); m_buttonsLayout = new QVBoxLayout(m_buttonsContainer); m_buttonsLayout->setContentsMargins(0, 0, 0, 0); m_buttonsLayout->setSpacing(4); m_buttonsLayout->addStretch(); scrollArea->setWidget(m_buttonsContainer); layout->addWidget(scrollArea, 1); connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked); connect(m_deleteBtn, &QPushButton::clicked, this, &BlueprintPanel::onDeleteClicked); QHBoxLayout* ioLayout = new QHBoxLayout(); m_saveBtn = new QPushButton("Save", this); m_loadBtn = new QPushButton("Load", this); m_saveBtn->setFixedHeight(36); m_loadBtn->setFixedHeight(36); ioLayout->addWidget(m_saveBtn); ioLayout->addWidget(m_loadBtn); layout->addLayout(ioLayout); connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked); connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked); } void BlueprintPanel::onSelectionChanged(const std::vector& ids) { m_selectedIds = ids; refreshButtonStates(); } void BlueprintPanel::onStateUpdated(Tick /*tick*/, int blocks, double /*speed*/) { m_currentBlocks = blocks; refreshButtonStates(); } void BlueprintPanel::clearActiveBlueprintButton() { if (m_activeIndex >= 0 && m_activeIndex < static_cast(m_blueprintButtons.size())) { m_blueprintButtons[static_cast(m_activeIndex)]->setChecked(false); } m_activeIndex = -1; refreshButtonStates(); } void BlueprintPanel::onCreateClicked() { if (m_selectedIds.empty()) { return; } Blueprint bp = createBlueprintFromSelection(); if (bp.buildings.empty()) { return; } bool ok = false; const QString name = QInputDialog::getText( this, "Create Blueprint", "Blueprint name:", QLineEdit::Normal, QString(), &ok); if (!ok || name.trimmed().isEmpty()) { return; } bp.name = name.trimmed(); m_blueprints.push_back(bp); rebuildButtons(); } void BlueprintPanel::onDeleteClicked() { if (!m_deleteMode) { m_deleteMode = true; m_deleteBtn->setChecked(true); if (m_activeIndex >= 0) { clearActiveBlueprintButton(); emit exitBlueprintModeRequested(); } } else { m_deleteMode = false; m_deleteBtn->setChecked(false); } } void BlueprintPanel::onBlueprintButtonClicked(int index) { if (index < 0 || index >= static_cast(m_blueprints.size())) { return; } if (m_deleteMode) { m_blueprints.erase(m_blueprints.begin() + index); m_deleteMode = false; m_deleteBtn->setChecked(false); m_activeIndex = -1; rebuildButtons(); return; } if (m_activeIndex == index) { clearActiveBlueprintButton(); emit exitBlueprintModeRequested(); return; } if (m_activeIndex >= 0 && m_activeIndex < static_cast(m_blueprintButtons.size())) { m_blueprintButtons[static_cast(m_activeIndex)]->setChecked(false); } m_activeIndex = index; m_blueprintButtons[static_cast(index)]->setChecked(true); emit blueprintPlacementRequested(m_blueprints[static_cast(index)]); } Blueprint BlueprintPanel::createBlueprintFromSelection() const { struct Entry { const Building* building; }; std::vector entries; entries.reserve(m_selectedIds.size()); for (const EntityId id : m_selectedIds) { const Building* b = m_sim->buildings().findBuilding(id); if (!b) { continue; } const bool placeable = [&]() { for (const BuildingDef& def : m_config->buildings.buildings) { if (def.type == b->type) { return def.playerPlaceable; } } return false; }(); if (placeable) { entries.push_back({ b }); } } if (entries.empty()) { return Blueprint{}; } int minX = INT_MAX, maxX = INT_MIN; int minY = INT_MAX, maxY = INT_MIN; for (const Entry& e : entries) { for (const QPoint& cell : e.building->bodyCells) { minX = std::min(minX, cell.x()); maxX = std::max(maxX, cell.x()); minY = std::min(minY, cell.y()); maxY = std::max(maxY, cell.y()); } } const QPoint center((minX + maxX) / 2, (minY + maxY) / 2); Blueprint bp; bp.buildings.reserve(entries.size()); for (const Entry& e : entries) { BlueprintBuilding bb; bb.type = e.building->type; bb.rotation = e.building->rotation; bb.offset = e.building->anchor - center; bb.recipeId = e.building->recipeId; bp.buildings.push_back(bb); } return bp; } int BlueprintPanel::computeBlueprintCost(const Blueprint& bp) const { int total = 0; for (const BlueprintBuilding& bb : bp.buildings) { for (const BuildingDef& def : m_config->buildings.buildings) { if (def.type == bb.type) { total += def.cost; break; } } } return total; } void BlueprintPanel::rebuildButtons() { for (QPushButton* btn : m_blueprintButtons) { m_buttonsLayout->removeWidget(btn); delete btn; } m_blueprintButtons.clear(); for (int i = 0; i < static_cast(m_blueprints.size()); ++i) { const Blueprint& bp = m_blueprints[static_cast(i)]; const int cost = computeBlueprintCost(bp); const QString label = bp.name + "\n" + QString::number(cost) + " Blocks"; QPushButton* btn = new QPushButton(label, m_buttonsContainer); btn->setCheckable(true); btn->setFixedHeight(48); m_buttonsLayout->insertWidget(i, btn); const int capturedIndex = i; connect(btn, &QPushButton::clicked, this, [this, capturedIndex]() { onBlueprintButtonClicked(capturedIndex); }); m_blueprintButtons.push_back(btn); } refreshButtonStates(); } void BlueprintPanel::onSaveClicked() { const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml"; try { const std::string content = BlueprintSerializer::serialize(m_blueprints); QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, "Save Failed", QString("Could not open file for writing:\n%1").arg(path)); return; } file.write(QByteArray::fromStdString(content)); } catch (const std::exception& e) { QMessageBox::critical(this, "Save Failed", QString("Failed to save blueprints:\n%1").arg(e.what())); } } void BlueprintPanel::onLoadClicked() { QMessageBox box(this); box.setWindowTitle("Load Blueprints"); box.setText("Load blueprints? This will replace all current blueprints."); QPushButton* confirmBtn = box.addButton("Confirm", QMessageBox::AcceptRole); box.addButton("Cancel", QMessageBox::RejectRole); box.exec(); if (box.clickedButton() != confirmBtn) { return; } const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml"; try { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(this, "Load Failed", QString("Could not open file:\n%1").arg(path)); return; } const std::string content = file.readAll().toStdString(); std::vector loaded = BlueprintSerializer::deserialize(content); if (m_activeIndex >= 0) { emit exitBlueprintModeRequested(); m_activeIndex = -1; } if (m_deleteMode) { m_deleteMode = false; m_deleteBtn->setChecked(false); } m_blueprints = std::move(loaded); rebuildButtons(); } catch (const std::exception& e) { QMessageBox::critical(this, "Load Failed", QString("Failed to load blueprints:\n%1").arg(e.what())); } } void BlueprintPanel::refreshButtonStates() { const bool anyPlaceable = [&]() { for (const EntityId id : m_selectedIds) { const Building* b = m_sim->buildings().findBuilding(id); if (!b) { continue; } for (const BuildingDef& def : m_config->buildings.buildings) { if (def.type == b->type) { return def.playerPlaceable; } } } return false; }(); m_createBtn->setEnabled(anyPlaceable); for (int i = 0; i < static_cast(m_blueprintButtons.size()); ++i) { const int cost = computeBlueprintCost(m_blueprints[static_cast(i)]); const bool canAfford = m_currentBlocks >= cost; m_blueprintButtons[static_cast(i)]->setEnabled( canAfford || m_activeIndex == i); } }