implement blueprints

This commit is contained in:
2026-04-26 21:56:49 +02:00
parent 4605c2e443
commit 71677b806a
8 changed files with 453 additions and 3 deletions

247
src/ui/BlueprintPanel.cpp Normal file
View File

@@ -0,0 +1,247 @@
#include "BlueprintPanel.h"
#include <algorithm>
#include <climits>
#include <QInputDialog>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#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);
}
void BlueprintPanel::onSelectionChanged(const std::vector<EntityId>& 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<int>(m_blueprintButtons.size()))
{
m_blueprintButtons[static_cast<std::size_t>(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<int>(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<int>(m_blueprintButtons.size()))
{
m_blueprintButtons[static_cast<std::size_t>(m_activeIndex)]->setChecked(false);
}
m_activeIndex = index;
m_blueprintButtons[static_cast<std::size_t>(index)]->setChecked(true);
emit blueprintPlacementRequested(m_blueprints[static_cast<std::size_t>(index)]);
}
Blueprint BlueprintPanel::createBlueprintFromSelection() const
{
struct Entry
{
const Building* building;
};
std::vector<Entry> entries;
entries.reserve(m_selectedIds.size());
for (const EntityId id : m_selectedIds)
{
const Building* b = m_sim->buildings().findBuilding(id);
if (b) { 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;
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<int>(m_blueprints.size()); ++i)
{
const Blueprint& bp = m_blueprints[static_cast<std::size_t>(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::refreshButtonStates()
{
m_createBtn->setEnabled(!m_selectedIds.empty());
for (int i = 0; i < static_cast<int>(m_blueprintButtons.size()); ++i)
{
const int cost = computeBlueprintCost(m_blueprints[static_cast<std::size_t>(i)]);
const bool canAfford = m_currentBlocks >= cost;
m_blueprintButtons[static_cast<std::size_t>(i)]->setEnabled(
canAfford || m_activeIndex == i);
}
}