schematic selection dialog

This commit is contained in:
2026-06-13 12:00:05 +02:00
parent 1641189b75
commit 49f7129bd5
25 changed files with 453 additions and 268 deletions

View File

@@ -1,6 +1,5 @@
#include "BuildButtonGrid.h"
#include <cctype>
#include <string>
#include <QGridLayout>
@@ -8,35 +7,7 @@
#include <QSignalMapper>
#include "BuildingType.h"
namespace
{
QString displayName(const std::string& id)
{
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;
}
} // namespace
#include "DisplayName.h"
BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent)
@@ -62,7 +33,7 @@ BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent)
m_types.push_back(def.type);
m_costs[def.type] = def.cost;
const QString label = displayName(def.id)
const QString label = QString::fromStdString(toDisplayName(def.id))
+ "\n" + tr("%1 Blocks").arg(def.cost);
QPushButton* btn = new QPushButton(label, this);
btn->setCheckable(true);

View File

@@ -11,6 +11,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutDialog.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.h
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoiceDialog.h
PARENT_SCOPE
)
@@ -26,5 +27,6 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutDialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoiceDialog.cpp
PARENT_SCOPE
)

View File

@@ -39,6 +39,7 @@
#include "BossWaveUpdatedEvent.h"
#include "BuildingBlocksChangedEvent.h"
#include "GameSpeedChangedEvent.h"
#include "SchematicChoicesAvailableEvent.h"
#include "TickAdvancedEvent.h"
namespace
@@ -68,31 +69,6 @@ Rotation rotateCounterClockwise(Rotation r)
return Rotation::East;
}
QString toDisplayName(const std::string& id)
{
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;
}
QPoint portBodyTile(QPoint portTile, Rotation direction)
{
switch (direction)
@@ -129,6 +105,7 @@ GameWorldView::GameWorldView(Simulation* sim, const GameConfig* config,
, m_scrollLeft(false)
, m_scrollRight(false)
, m_gameOverShown(false)
, m_schematicChoiceShown(false)
{
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
@@ -188,31 +165,6 @@ void GameWorldView::onFrame()
}
}
// Drain schematic drop events → toasts
{
const std::vector<SchematicDropEvent> drops =
m_sim->drainSchematicDropEvents();
for (const SchematicDropEvent& ev : drops)
{
const QString name = toDisplayName(ev.schematicId);
ToastEntry toast;
if (ev.isModuleSchematic)
{
toast.text = ev.wasNewUnlock
? tr("Module unlocked: ") + name
: name + tr(" production level -> ") + QString::number(ev.newLevel);
}
else
{
toast.text = ev.wasNewUnlock
? tr("Schematic unlocked: ") + name
: name + tr(" production level -> ") + QString::number(ev.newLevel);
}
toast.createdWallMs = m_wallMs;
m_toasts.push_back(toast);
}
}
// Expire old beams
{
std::vector<ActiveBeam> live;
@@ -226,19 +178,6 @@ void GameWorldView::onFrame()
m_activeBeams = std::move(live);
}
// Expire old toasts
{
std::vector<ToastEntry> live;
for (const ToastEntry& t : m_toasts)
{
if (m_wallMs - t.createdWallMs < kToastLifetimeMs)
{
live.push_back(t);
}
}
m_toasts = std::move(live);
}
// Apply held scroll
{
const float delta = kScrollSpeedTilesPerSec
@@ -276,6 +215,18 @@ void GameWorldView::onFrame()
}
}
// Schematic choice available
if (m_sim->hasSchematicChoicesPending() && !m_schematicChoiceShown)
{
m_schematicChoiceShown = true;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SchematicChoicesAvailableEvent>(m_sim->getPendingSchematicChoices()));
}
if (!m_sim->hasSchematicChoicesPending())
{
m_schematicChoiceShown = false;
}
// Game over check
if (m_sim->isGameOver() && !m_gameOverShown)
{
@@ -1097,41 +1048,8 @@ void GameWorldView::drawOverlays(QPainter& painter)
}
}
void GameWorldView::drawScreenSpace(QPainter& painter)
void GameWorldView::drawScreenSpace(QPainter& /*painter*/)
{
painter.resetTransform();
const int margin = 8;
const int toastW = 320;
const int toastH = 36;
const int spacing = 4;
QFont toastFont = painter.font();
toastFont.setPointSize(m_visuals->toast.fontSize);
painter.setFont(toastFont);
int y = margin;
for (const ToastEntry& toast : m_toasts)
{
const qint64 age = m_wallMs - toast.createdWallMs;
double opacity = 1.0;
if (age > kToastFadeStartMs)
{
opacity = 1.0 - static_cast<double>(age - kToastFadeStartMs)
/ static_cast<double>(kToastLifetimeMs - kToastFadeStartMs);
opacity = std::max(0.0, opacity);
}
painter.setOpacity(opacity);
const int x = width() - toastW - margin;
const QRect toastRect(x, y, toastW, toastH);
painter.fillRect(toastRect, m_visuals->toast.bg);
painter.setPen(m_visuals->toast.fg);
painter.drawText(toastRect.adjusted(8, 0, -8, 0),
Qt::AlignVCenter | Qt::AlignLeft, toast.text);
painter.setOpacity(1.0);
y += toastH + spacing;
}
}
// ---------------------------------------------------------------------------
@@ -1526,7 +1444,7 @@ void GameWorldView::resetForNewGame()
exitBuilderMode();
exitBlueprintMode();
m_activeBeams.clear();
m_toasts.clear();
m_schematicChoiceShown = false;
m_ghostRotation = Rotation::East;
m_ghostValid = false;
m_demolishMode = false;

View File

@@ -13,7 +13,7 @@
#include <QVector2D>
#include "Blueprint.h"
#include "SchematicDropEvent.h"
#include "SchematicChoiceOption.h"
#include "BuildingType.h"
#include "BuildingId.h"
#include "FireEvent.h"
@@ -125,15 +125,7 @@ private:
QVector2D targetOffset;
};
struct ToastEntry
{
QString text;
qint64 createdWallMs;
};
static constexpr qint64 kBeamLifetimeMs = 300;
static constexpr qint64 kToastLifetimeMs = 4000;
static constexpr qint64 kToastFadeStartMs = 3500;
static constexpr float kScrollSpeedTilesPerSec = 10.0f;
Simulation* m_sim;
@@ -151,7 +143,6 @@ private:
QTimer* m_renderTimer;
std::vector<ActiveBeam> m_activeBeams;
std::vector<ToastEntry> m_toasts;
std::optional<BuildingType> m_builderType;
Rotation m_ghostRotation;
@@ -176,6 +167,7 @@ private:
bool m_scrollLeft;
bool m_scrollRight;
bool m_gameOverShown;
bool m_schematicChoiceShown;
Tick m_lastTick = Tick(-1);
int m_lastBlocks = -1;

View File

@@ -18,6 +18,7 @@
#include "BuildingSystem.h"
#include "ConfigLoader.h"
#include "GameWorldView.h"
#include "SchematicChoiceDialog.h"
#include "HeaderBar.h"
#include "SelectedBuildingPanel.h"
#include "ShipLayoutBlueprintSerializer.h"
@@ -125,12 +126,12 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
}
}
registerForEvent();
registerForEvents();
}
MainWindow::~MainWindow()
{
unregisterForEvent();
unregisterForEvents();
}
void MainWindow::resizeEvent(QResizeEvent* event)
@@ -176,6 +177,19 @@ void MainWindow::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> e
m_buildButtonGrid->updateAffordability(event->blocks);
}
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
{
const double prevSpeed = m_gameWorldView->gameSpeed();
m_gameWorldView->setGameSpeed(0.0);
SchematicChoiceDialog dialog(event->choices, this);
dialog.exec();
m_sim->applySchematicChoice(dialog.getChosenIndex());
m_gameWorldView->setGameSpeed(prevSpeed);
}
void MainWindow::onEscapeMenuRequested()
{
const double prevSpeed = m_gameWorldView->gameSpeed();

View File

@@ -8,6 +8,7 @@
#include "BuildingBlocksChangedEvent.h"
#include "BuildingId.h"
#include "EventHandler.h"
#include "SchematicChoicesAvailableEvent.h"
#include "ShipLayoutBlueprint.h"
#include "Tick.h"
#include "VisualsConfig.h"
@@ -22,7 +23,8 @@ class QCloseEvent;
class QResizeEvent;
class MainWindow : public QWidget,
public EventHandler<BuildingBlocksChangedEvent>
public CombinedEventHandler<BuildingBlocksChangedEvent,
SchematicChoicesAvailableEvent>
{
Q_OBJECT
@@ -36,6 +38,7 @@ protected:
private:
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
void layoutPanels();
private slots:

View File

@@ -0,0 +1,99 @@
#include "SchematicChoiceDialog.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
SchematicChoiceDialog::SchematicChoiceDialog(
const std::vector<SchematicChoiceOption>& options,
QWidget* parent)
: QDialog(parent)
, m_chosenIndex(0)
{
setWindowTitle(tr("Schematic Drop"));
setWindowFlags(windowFlags() & ~Qt::WindowCloseButtonHint);
setModal(true);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
QLabel* titleLabel = new QLabel(tr("Choose a schematic to unlock:"), this);
QFont titleFont = titleLabel->font();
titleFont.setPointSize(titleFont.pointSize() + 2);
titleFont.setBold(true);
titleLabel->setFont(titleFont);
titleLabel->setAlignment(Qt::AlignCenter);
mainLayout->addWidget(titleLabel);
QHBoxLayout* optionsLayout = new QHBoxLayout();
mainLayout->addLayout(optionsLayout);
for (int i = 0; i < static_cast<int>(options.size()); ++i)
{
const SchematicChoiceOption& option = options[static_cast<std::size_t>(i)];
QWidget* card = new QWidget(this);
QVBoxLayout* cardLayout = new QVBoxLayout(card);
card->setStyleSheet("QWidget { border: 1px solid gray; padding: 8px; }");
QLabel* nameLabel = new QLabel(QString::fromStdString(option.displayName), card);
QFont nameFont = nameLabel->font();
nameFont.setPointSize(nameFont.pointSize() + 1);
nameFont.setBold(true);
nameLabel->setFont(nameFont);
nameLabel->setAlignment(Qt::AlignCenter);
cardLayout->addWidget(nameLabel);
QString typeText;
if (option.type == SchematicType::Ship)
{
typeText = tr("Ship");
}
else if (option.type == SchematicType::Module)
{
typeText = tr("Module");
}
else
{
typeText = tr("Recipe");
}
QLabel* typeLabel = new QLabel(typeText, card);
typeLabel->setAlignment(Qt::AlignCenter);
cardLayout->addWidget(typeLabel);
QString statusText;
if (option.isNewUnlock)
{
statusText = tr("New unlock");
}
else
{
statusText = tr("Level up -> %1").arg(option.targetLevel);
}
QLabel* statusLabel = new QLabel(statusText, card);
statusLabel->setAlignment(Qt::AlignCenter);
cardLayout->addWidget(statusLabel);
QPushButton* selectButton = new QPushButton(tr("Select"), card);
cardLayout->addWidget(selectButton);
const int index = i;
connect(selectButton, &QPushButton::clicked, this, [this, index]()
{
onOptionClicked(index);
});
optionsLayout->addWidget(card);
}
}
int SchematicChoiceDialog::getChosenIndex() const
{
return m_chosenIndex;
}
void SchematicChoiceDialog::onOptionClicked(int index)
{
m_chosenIndex = index;
accept();
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <vector>
#include <QDialog>
#include "SchematicChoiceOption.h"
class SchematicChoiceDialog : public QDialog
{
Q_OBJECT
public:
SchematicChoiceDialog(const std::vector<SchematicChoiceOption>& options,
QWidget* parent = nullptr);
int getChosenIndex() const;
private:
void onOptionClicked(int index);
int m_chosenIndex;
};

View File

@@ -1,9 +1,10 @@
#include "ShipLayoutDialog.h"
#include "ShipStatsPanel.h"
#include <cctype>
#include <functional>
#include "DisplayName.h"
#include <QGridLayout>
#include <QHBoxLayout>
#include <QInputDialog>
@@ -20,30 +21,6 @@ namespace
const int kCellSize = 32;
QString displayName(const std::string& id)
{
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;
}
std::vector<std::string> rotateMaskCW(const std::vector<std::string>& grid)
{
if (grid.empty())
@@ -478,7 +455,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
m_moduleButtons.push_back(nullptr);
continue;
}
const QString label = displayName(def.id)
const QString label = QString::fromStdString(toDisplayName(def.id))
+ "\n" + QString::fromStdString(def.glyph);
QPushButton* btn = new QPushButton(label, this);
btn->setCheckable(true);