implement blueprints
This commit is contained in:
22
src/ui/Blueprint.h
Normal file
22
src/ui/Blueprint.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "BuildingType.h"
|
||||||
|
#include "Rotation.h"
|
||||||
|
|
||||||
|
struct BlueprintBuilding
|
||||||
|
{
|
||||||
|
BuildingType type;
|
||||||
|
Rotation rotation;
|
||||||
|
QPoint offset; // tile offset from bounding-box center (floor for even sizes)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Blueprint
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
std::vector<BlueprintBuilding> buildings;
|
||||||
|
};
|
||||||
247
src/ui/BlueprintPanel.cpp
Normal file
247
src/ui/BlueprintPanel.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/ui/BlueprintPanel.h
Normal file
56
src/ui/BlueprintPanel.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "Blueprint.h"
|
||||||
|
#include "EntityId.h"
|
||||||
|
#include "GameConfig.h"
|
||||||
|
#include "Tick.h"
|
||||||
|
|
||||||
|
class Simulation;
|
||||||
|
class QPushButton;
|
||||||
|
class QScrollArea;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
class BlueprintPanel : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
BlueprintPanel(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onSelectionChanged(const std::vector<EntityId>& ids);
|
||||||
|
void onStateUpdated(Tick tick, int blocks, double speed);
|
||||||
|
void clearActiveBlueprintButton();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void blueprintPlacementRequested(Blueprint blueprint);
|
||||||
|
void exitBlueprintModeRequested();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCreateClicked();
|
||||||
|
void onDeleteClicked();
|
||||||
|
void onBlueprintButtonClicked(int index);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Blueprint createBlueprintFromSelection() const;
|
||||||
|
int computeBlueprintCost(const Blueprint& bp) const;
|
||||||
|
void rebuildButtons();
|
||||||
|
void refreshButtonStates();
|
||||||
|
|
||||||
|
Simulation* m_sim;
|
||||||
|
const GameConfig* m_config;
|
||||||
|
std::vector<EntityId> m_selectedIds;
|
||||||
|
int m_currentBlocks;
|
||||||
|
bool m_deleteMode;
|
||||||
|
int m_activeIndex;
|
||||||
|
std::vector<Blueprint> m_blueprints;
|
||||||
|
std::vector<QPushButton*> m_blueprintButtons;
|
||||||
|
QPushButton* m_createBtn;
|
||||||
|
QPushButton* m_deleteBtn;
|
||||||
|
QWidget* m_buttonsContainer;
|
||||||
|
QVBoxLayout* m_buttonsLayout;
|
||||||
|
};
|
||||||
@@ -7,6 +7,8 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintPanel.h
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,5 +20,6 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintPanel.cpp
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -452,6 +452,29 @@ void GameWorldView::stepSpeed(int delta)
|
|||||||
setGameSpeed(kSpeeds[next]);
|
setGameSpeed(kSpeeds[next]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorldView::placeBlueprintAtTile(QPoint center)
|
||||||
|
{
|
||||||
|
const Blueprint& bp = *m_blueprintMode;
|
||||||
|
|
||||||
|
for (const BlueprintBuilding& bb : bp.buildings)
|
||||||
|
{
|
||||||
|
if (!isValidPlacement(bb.type, center + bb.offset, bb.rotation)) { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCost = 0;
|
||||||
|
for (const BlueprintBuilding& bb : bp.buildings)
|
||||||
|
{
|
||||||
|
const BuildingDef* def = findBuildingDef(bb.type);
|
||||||
|
if (def) { totalCost += def->cost; }
|
||||||
|
}
|
||||||
|
if (m_sim->buildingBlocksStock() < totalCost) { return; }
|
||||||
|
|
||||||
|
for (const BlueprintBuilding& bb : bp.buildings)
|
||||||
|
{
|
||||||
|
m_sim->tryPlaceBuilding(bb.type, center + bb.offset, bb.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameWorldView::placeAtTile(QPoint tile)
|
void GameWorldView::placeAtTile(QPoint tile)
|
||||||
{
|
{
|
||||||
if (!m_builderType.has_value())
|
if (!m_builderType.has_value())
|
||||||
@@ -776,6 +799,32 @@ void GameWorldView::drawOverlays(QPainter& painter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blueprint placement ghost
|
||||||
|
if (m_blueprintMode.has_value())
|
||||||
|
{
|
||||||
|
for (const BlueprintBuilding& bb : m_blueprintMode->buildings)
|
||||||
|
{
|
||||||
|
const QPoint anchor = m_blueprintGhostTile + bb.offset;
|
||||||
|
const bool valid = isValidPlacement(bb.type, anchor, bb.rotation);
|
||||||
|
const QColor& ghostColor = valid
|
||||||
|
? m_visuals->overlays.ghostValid
|
||||||
|
: m_visuals->overlays.ghostInvalid;
|
||||||
|
const BuildingDef* def = findBuildingDef(bb.type);
|
||||||
|
if (!def) { continue; }
|
||||||
|
const ParsedSurfaceMask parsed = parseSurfaceMask(def->surfaceMask, bb.rotation);
|
||||||
|
for (const QPoint& cell : parsed.bodyCells)
|
||||||
|
{
|
||||||
|
painter.fillRect(tileRect(anchor + cell), ghostColor);
|
||||||
|
}
|
||||||
|
for (const Port& port : parsed.outputPorts)
|
||||||
|
{
|
||||||
|
drawPortGlyph(painter,
|
||||||
|
anchor + portBodyTile(port.tile, port.direction),
|
||||||
|
port.direction, Qt::white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Demolish hover tint
|
// Demolish hover tint
|
||||||
if (m_demolishMode && m_demolishHoverId != kInvalidEntityId)
|
if (m_demolishMode && m_demolishHoverId != kInvalidEntityId)
|
||||||
{
|
{
|
||||||
@@ -883,6 +932,14 @@ void GameWorldView::keyPressEvent(QKeyEvent* event)
|
|||||||
m_ghostRotation = rotateClockwise(m_ghostRotation);
|
m_ghostRotation = rotateClockwise(m_ghostRotation);
|
||||||
m_ghostValid = isValidPlacement(*m_builderType, m_ghostTile, m_ghostRotation);
|
m_ghostValid = isValidPlacement(*m_builderType, m_ghostTile, m_ghostRotation);
|
||||||
}
|
}
|
||||||
|
else if (m_blueprintMode.has_value())
|
||||||
|
{
|
||||||
|
for (BlueprintBuilding& bb : m_blueprintMode->buildings)
|
||||||
|
{
|
||||||
|
bb.offset = QPoint(-bb.offset.y(), bb.offset.x());
|
||||||
|
bb.rotation = rotateClockwise(bb.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Q:
|
case Qt::Key_Q:
|
||||||
if (m_builderType.has_value())
|
if (m_builderType.has_value())
|
||||||
@@ -890,6 +947,14 @@ void GameWorldView::keyPressEvent(QKeyEvent* event)
|
|||||||
m_ghostRotation = rotateCounterClockwise(m_ghostRotation);
|
m_ghostRotation = rotateCounterClockwise(m_ghostRotation);
|
||||||
m_ghostValid = isValidPlacement(*m_builderType, m_ghostTile, m_ghostRotation);
|
m_ghostValid = isValidPlacement(*m_builderType, m_ghostTile, m_ghostRotation);
|
||||||
}
|
}
|
||||||
|
else if (m_blueprintMode.has_value())
|
||||||
|
{
|
||||||
|
for (BlueprintBuilding& bb : m_blueprintMode->buildings)
|
||||||
|
{
|
||||||
|
bb.offset = QPoint(bb.offset.y(), -bb.offset.x());
|
||||||
|
bb.rotation = rotateCounterClockwise(bb.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Escape:
|
case Qt::Key_Escape:
|
||||||
emit escapeMenuRequested();
|
emit escapeMenuRequested();
|
||||||
@@ -919,9 +984,10 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
|||||||
{
|
{
|
||||||
if (event->button() != Qt::LeftButton)
|
if (event->button() != Qt::LeftButton)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::RightButton && m_builderType.has_value())
|
if (event->button() == Qt::RightButton)
|
||||||
{
|
{
|
||||||
exitBuilderMode();
|
if (m_builderType.has_value()) { exitBuilderMode(); }
|
||||||
|
else if (m_blueprintMode.has_value()) { exitBlueprintMode(); }
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -942,6 +1008,10 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
|||||||
placeAtTile(tile);
|
placeAtTile(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (m_blueprintMode.has_value())
|
||||||
|
{
|
||||||
|
placeBlueprintAtTile(tile);
|
||||||
|
}
|
||||||
else if (m_demolishMode)
|
else if (m_demolishMode)
|
||||||
{
|
{
|
||||||
EntityId hovered = buildingAtTile(tile);
|
EntityId hovered = buildingAtTile(tile);
|
||||||
@@ -1012,6 +1082,10 @@ void GameWorldView::mouseMoveEvent(QMouseEvent* event)
|
|||||||
placeAtTile(tile);
|
placeAtTile(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (m_blueprintMode.has_value())
|
||||||
|
{
|
||||||
|
m_blueprintGhostTile = tile;
|
||||||
|
}
|
||||||
else if (m_demolishMode)
|
else if (m_demolishMode)
|
||||||
{
|
{
|
||||||
m_demolishHoverId = buildingAtTile(tile);
|
m_demolishHoverId = buildingAtTile(tile);
|
||||||
@@ -1088,7 +1162,8 @@ void GameWorldView::toggleDemolishMode()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (m_builderType.has_value()) { exitBuilderMode(); }
|
if (m_builderType.has_value()) { exitBuilderMode(); }
|
||||||
|
if (m_blueprintMode.has_value()) { exitBlueprintMode(); }
|
||||||
m_demolishMode = true;
|
m_demolishMode = true;
|
||||||
}
|
}
|
||||||
emit demolishModeChanged(m_demolishMode);
|
emit demolishModeChanged(m_demolishMode);
|
||||||
@@ -1100,9 +1175,25 @@ void GameWorldView::enterBuilderMode(BuildingType type)
|
|||||||
m_ghostRotation = Rotation::East;
|
m_ghostRotation = Rotation::East;
|
||||||
m_ghostValid = false;
|
m_ghostValid = false;
|
||||||
m_demolishMode = false;
|
m_demolishMode = false;
|
||||||
|
m_blueprintMode.reset();
|
||||||
emit demolishModeChanged(false);
|
emit demolishModeChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorldView::enterBlueprintMode(Blueprint blueprint)
|
||||||
|
{
|
||||||
|
if (m_builderType.has_value()) { exitBuilderMode(); }
|
||||||
|
m_demolishMode = false;
|
||||||
|
emit demolishModeChanged(false);
|
||||||
|
m_blueprintGhostTile = m_ghostTile;
|
||||||
|
m_blueprintMode = std::move(blueprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameWorldView::exitBlueprintMode()
|
||||||
|
{
|
||||||
|
m_blueprintMode.reset();
|
||||||
|
emit blueprintModeExited();
|
||||||
|
}
|
||||||
|
|
||||||
void GameWorldView::exitBuilderMode()
|
void GameWorldView::exitBuilderMode()
|
||||||
{
|
{
|
||||||
m_builderType.reset();
|
m_builderType.reset();
|
||||||
@@ -1127,6 +1218,7 @@ void GameWorldView::setGameSpeed(double multiplier)
|
|||||||
void GameWorldView::resetForNewGame()
|
void GameWorldView::resetForNewGame()
|
||||||
{
|
{
|
||||||
exitBuilderMode();
|
exitBuilderMode();
|
||||||
|
exitBlueprintMode();
|
||||||
m_activeBeams.clear();
|
m_activeBeams.clear();
|
||||||
m_toasts.clear();
|
m_toasts.clear();
|
||||||
m_ghostRotation = Rotation::East;
|
m_ghostRotation = Rotation::East;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "Blueprint.h"
|
||||||
#include "SchematicDropEvent.h"
|
#include "SchematicDropEvent.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "EntityId.h"
|
#include "EntityId.h"
|
||||||
@@ -46,6 +47,7 @@ signals:
|
|||||||
void stateUpdated(Tick tick, int blocks, double speed);
|
void stateUpdated(Tick tick, int blocks, double speed);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
void builderModeExited();
|
void builderModeExited();
|
||||||
|
void blueprintModeExited();
|
||||||
void escapeMenuRequested();
|
void escapeMenuRequested();
|
||||||
void demolishModeChanged(bool active);
|
void demolishModeChanged(bool active);
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
void enterBuilderMode(BuildingType type);
|
void enterBuilderMode(BuildingType type);
|
||||||
void exitBuilderMode();
|
void exitBuilderMode();
|
||||||
|
void enterBlueprintMode(Blueprint blueprint);
|
||||||
|
void exitBlueprintMode();
|
||||||
void toggleDemolishMode();
|
void toggleDemolishMode();
|
||||||
void setGameSpeed(double multiplier);
|
void setGameSpeed(double multiplier);
|
||||||
void resetForNewGame();
|
void resetForNewGame();
|
||||||
@@ -101,6 +105,8 @@ private:
|
|||||||
void drawPortGlyph(QPainter& painter, QPoint bodyTile,
|
void drawPortGlyph(QPainter& painter, QPoint bodyTile,
|
||||||
Rotation direction, const QColor& color);
|
Rotation direction, const QColor& color);
|
||||||
|
|
||||||
|
void placeBlueprintAtTile(QPoint center);
|
||||||
|
|
||||||
std::optional<QVector2D> entityPosition(EntityId id) const;
|
std::optional<QVector2D> entityPosition(EntityId id) const;
|
||||||
void stepSpeed(int delta);
|
void stepSpeed(int delta);
|
||||||
void placeAtTile(QPoint tile);
|
void placeAtTile(QPoint tile);
|
||||||
@@ -145,6 +151,9 @@ private:
|
|||||||
std::set<QPoint, QPointCompare> m_beltDragTiles;
|
std::set<QPoint, QPointCompare> m_beltDragTiles;
|
||||||
bool m_dragging;
|
bool m_dragging;
|
||||||
|
|
||||||
|
std::optional<Blueprint> m_blueprintMode;
|
||||||
|
QPoint m_blueprintGhostTile;
|
||||||
|
|
||||||
bool m_demolishMode;
|
bool m_demolishMode;
|
||||||
EntityId m_demolishHoverId;
|
EntityId m_demolishHoverId;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "BlueprintPanel.h"
|
||||||
#include "BuildButtonGrid.h"
|
#include "BuildButtonGrid.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "GameWorldView.h"
|
#include "GameWorldView.h"
|
||||||
@@ -36,9 +37,11 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
|
|
||||||
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->config(), m_bottomPanel);
|
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->config(), m_bottomPanel);
|
||||||
m_buildButtonGrid = new BuildButtonGrid(&sim->config(), m_bottomPanel);
|
m_buildButtonGrid = new BuildButtonGrid(&sim->config(), m_bottomPanel);
|
||||||
|
m_blueprintPanel = new BlueprintPanel(sim, &sim->config(), m_bottomPanel);
|
||||||
|
|
||||||
bottomLayout->addWidget(m_selectedBuildingPanel, 1);
|
bottomLayout->addWidget(m_selectedBuildingPanel, 1);
|
||||||
bottomLayout->addWidget(m_buildButtonGrid, 1);
|
bottomLayout->addWidget(m_buildButtonGrid, 1);
|
||||||
|
bottomLayout->addWidget(m_blueprintPanel, 1);
|
||||||
|
|
||||||
// Signals: game world → other panels
|
// Signals: game world → other panels
|
||||||
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
||||||
@@ -75,6 +78,22 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
connect(m_gameWorldView, &GameWorldView::demolishModeChanged,
|
connect(m_gameWorldView, &GameWorldView::demolishModeChanged,
|
||||||
m_buildButtonGrid, &BuildButtonGrid::setDemolishModeActive);
|
m_buildButtonGrid, &BuildButtonGrid::setDemolishModeActive);
|
||||||
|
|
||||||
|
// Signals: blueprint panel ↔ game world
|
||||||
|
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
||||||
|
m_blueprintPanel, &BlueprintPanel::onSelectionChanged);
|
||||||
|
|
||||||
|
connect(m_gameWorldView, &GameWorldView::stateUpdated,
|
||||||
|
m_blueprintPanel, &BlueprintPanel::onStateUpdated);
|
||||||
|
|
||||||
|
connect(m_blueprintPanel, &BlueprintPanel::blueprintPlacementRequested,
|
||||||
|
m_gameWorldView, &GameWorldView::enterBlueprintMode);
|
||||||
|
|
||||||
|
connect(m_blueprintPanel, &BlueprintPanel::exitBlueprintModeRequested,
|
||||||
|
m_gameWorldView, &GameWorldView::exitBlueprintMode);
|
||||||
|
|
||||||
|
connect(m_gameWorldView, &GameWorldView::blueprintModeExited,
|
||||||
|
m_blueprintPanel, &BlueprintPanel::clearActiveBlueprintButton);
|
||||||
|
|
||||||
// Signals: header bar → game world
|
// Signals: header bar → game world
|
||||||
connect(m_headerBar, &HeaderBar::speedChanged,
|
connect(m_headerBar, &HeaderBar::speedChanged,
|
||||||
m_gameWorldView, &GameWorldView::setGameSpeed);
|
m_gameWorldView, &GameWorldView::setGameSpeed);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class GameWorldView;
|
|||||||
class HeaderBar;
|
class HeaderBar;
|
||||||
class SelectedBuildingPanel;
|
class SelectedBuildingPanel;
|
||||||
class BuildButtonGrid;
|
class BuildButtonGrid;
|
||||||
|
class BlueprintPanel;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class MainWindow : public QWidget
|
class MainWindow : public QWidget
|
||||||
@@ -39,5 +40,6 @@ private:
|
|||||||
HeaderBar* m_headerBar;
|
HeaderBar* m_headerBar;
|
||||||
SelectedBuildingPanel* m_selectedBuildingPanel;
|
SelectedBuildingPanel* m_selectedBuildingPanel;
|
||||||
BuildButtonGrid* m_buildButtonGrid;
|
BuildButtonGrid* m_buildButtonGrid;
|
||||||
|
BlueprintPanel* m_blueprintPanel;
|
||||||
QWidget* m_bottomPanel;
|
QWidget* m_bottomPanel;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user