839 lines
25 KiB
C++
839 lines
25 KiB
C++
#include "ShipLayoutDialog.h"
|
|
#include "ShipStatsPanel.h"
|
|
|
|
#include <cctype>
|
|
#include <functional>
|
|
|
|
#include <QGridLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QInputDialog>
|
|
#include <QKeyEvent>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPushButton>
|
|
#include <QScrollArea>
|
|
#include <QSignalMapper>
|
|
#include <QVBoxLayout>
|
|
|
|
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())
|
|
{
|
|
return {};
|
|
}
|
|
const int srcH = static_cast<int>(grid.size());
|
|
int srcW = 0;
|
|
for (const std::string& row : grid)
|
|
{
|
|
const int w = static_cast<int>(row.size());
|
|
if (w > srcW)
|
|
{
|
|
srcW = w;
|
|
}
|
|
}
|
|
const int dstW = srcH;
|
|
const int dstH = srcW;
|
|
std::vector<std::string> dst(dstH, std::string(dstW, 'X'));
|
|
for (int row = 0; row < srcH; ++row)
|
|
{
|
|
for (int col = 0; col < srcW; ++col)
|
|
{
|
|
const char ch = (col < static_cast<int>(grid[row].size()))
|
|
? grid[row][col]
|
|
: 'X';
|
|
dst[col][srcH - 1 - row] = ch;
|
|
}
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid rendering widget (nested inside dialog)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class LayoutGridWidget : public QWidget
|
|
{
|
|
public:
|
|
LayoutGridWidget(ShipLayoutDialog* dialog, QWidget* parent = nullptr)
|
|
: QWidget(parent)
|
|
, m_dialog(dialog)
|
|
{
|
|
setMouseTracking(true);
|
|
}
|
|
|
|
void setGridData(const std::vector<std::vector<ShipLayoutDialog::CellInfo>>* grid,
|
|
int rows, int cols,
|
|
const std::vector<PlacedModule>* placed,
|
|
const GameConfig* config)
|
|
{
|
|
m_grid = grid;
|
|
m_rows = rows;
|
|
m_cols = cols;
|
|
m_placed = placed;
|
|
m_config = config;
|
|
setFixedSize(cols * kCellSize + 1, rows * kCellSize + 1);
|
|
}
|
|
|
|
void setGhostData(int moduleIndex, Rotation rotation)
|
|
{
|
|
m_ghostModuleIdx = moduleIndex;
|
|
m_ghostRotation = rotation;
|
|
}
|
|
|
|
protected:
|
|
void paintEvent(QPaintEvent* /*event*/) override
|
|
{
|
|
if (!m_grid || m_rows == 0 || m_cols == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
for (int r = 0; r < m_rows; ++r)
|
|
{
|
|
for (int c = 0; c < m_cols; ++c)
|
|
{
|
|
const QRect cellRect(c * kCellSize, r * kCellSize, kCellSize, kCellSize);
|
|
const ShipLayoutDialog::CellInfo& cell = (*m_grid)[r][c];
|
|
|
|
if (!cell.buildable)
|
|
{
|
|
painter.fillRect(cellRect, QColor(30, 30, 30));
|
|
}
|
|
else if (cell.moduleIndex >= 0)
|
|
{
|
|
const PlacedModule& pm = (*m_placed)[cell.moduleIndex];
|
|
const ModuleDef* def = findModule(pm.moduleId);
|
|
QColor color(Qt::gray);
|
|
QString glyph;
|
|
if (def)
|
|
{
|
|
color = QColor(QString::fromStdString(def->fillColor));
|
|
glyph = QString::fromStdString(def->glyph);
|
|
}
|
|
painter.fillRect(cellRect, color);
|
|
if (!glyph.isEmpty())
|
|
{
|
|
painter.setPen(Qt::white);
|
|
painter.drawText(cellRect, Qt::AlignCenter, glyph);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
painter.fillRect(cellRect, QColor(240, 240, 240));
|
|
}
|
|
|
|
painter.setPen(QColor(100, 100, 100));
|
|
painter.drawRect(cellRect);
|
|
}
|
|
}
|
|
|
|
// Draw ghost
|
|
if (m_ghostModuleIdx >= 0 && m_hoverCell.x() >= 0 && m_config)
|
|
{
|
|
const ModuleDef& def = m_config->modules.modules[m_ghostModuleIdx];
|
|
const std::vector<std::string> mask = rotateMask(def.surfaceMask, m_ghostRotation);
|
|
QColor ghostColor(QString::fromStdString(def.fillColor));
|
|
ghostColor.setAlpha(100);
|
|
|
|
for (int mr = 0; mr < static_cast<int>(mask.size()); ++mr)
|
|
{
|
|
for (int mc = 0; mc < static_cast<int>(mask[mr].size()); ++mc)
|
|
{
|
|
if (mask[mr][mc] != 'O')
|
|
{
|
|
continue;
|
|
}
|
|
const int gr = m_hoverCell.y() + mr;
|
|
const int gc = m_hoverCell.x() + mc;
|
|
if (gr >= 0 && gr < m_rows && gc >= 0 && gc < m_cols)
|
|
{
|
|
const QRect cellRect(gc * kCellSize, gr * kCellSize,
|
|
kCellSize, kCellSize);
|
|
painter.fillRect(cellRect, ghostColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void mouseMoveEvent(QMouseEvent* event) override
|
|
{
|
|
const QPoint pos = event->pos();
|
|
const QPoint cell(pos.x() / kCellSize, pos.y() / kCellSize);
|
|
if (cell != m_hoverCell)
|
|
{
|
|
m_hoverCell = cell;
|
|
update();
|
|
}
|
|
}
|
|
|
|
void mousePressEvent(QMouseEvent* event) override
|
|
{
|
|
if (event->button() != Qt::LeftButton)
|
|
{
|
|
return;
|
|
}
|
|
const QPoint pos = event->pos();
|
|
const QPoint cell(pos.x() / kCellSize, pos.y() / kCellSize);
|
|
emit m_dialog->gridCellClicked(cell);
|
|
}
|
|
|
|
void leaveEvent(QEvent* /*event*/) override
|
|
{
|
|
m_hoverCell = QPoint(-1, -1);
|
|
update();
|
|
}
|
|
|
|
private:
|
|
const ModuleDef* findModule(const std::string& id) const
|
|
{
|
|
if (!m_config)
|
|
{
|
|
return nullptr;
|
|
}
|
|
for (const ModuleDef& def : m_config->modules.modules)
|
|
{
|
|
if (def.id == id)
|
|
{
|
|
return &def;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<std::string> rotateMask(const std::vector<std::string>& mask,
|
|
Rotation rotation) const
|
|
{
|
|
int steps = 0;
|
|
switch (rotation)
|
|
{
|
|
case Rotation::East: steps = 0; break;
|
|
case Rotation::South: steps = 1; break;
|
|
case Rotation::West: steps = 2; break;
|
|
case Rotation::North: steps = 3; break;
|
|
}
|
|
std::vector<std::string> result = mask;
|
|
for (int i = 0; i < steps; ++i)
|
|
{
|
|
result = rotateMaskCW(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ShipLayoutDialog* m_dialog;
|
|
const std::vector<std::vector<ShipLayoutDialog::CellInfo>>* m_grid = nullptr;
|
|
int m_rows = 0;
|
|
int m_cols = 0;
|
|
const std::vector<PlacedModule>* m_placed = nullptr;
|
|
const GameConfig* m_config = nullptr;
|
|
int m_ghostModuleIdx = -2;
|
|
Rotation m_ghostRotation = Rotation::East;
|
|
QPoint m_hoverCell = QPoint(-1, -1);
|
|
};
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Blueprint panel (third column of the layout dialog)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class ShipLayoutBlueprintPanel : public QWidget
|
|
{
|
|
public:
|
|
ShipLayoutBlueprintPanel(
|
|
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
|
const std::string& shipType,
|
|
std::function<std::vector<PlacedModule>()> getModules,
|
|
std::function<void(const std::vector<PlacedModule>&)> loadModules,
|
|
QWidget* parent = nullptr)
|
|
: QWidget(parent)
|
|
, m_allBlueprints(allBlueprints)
|
|
, m_shipType(shipType)
|
|
, m_getModules(std::move(getModules))
|
|
, m_loadModules(std::move(loadModules))
|
|
{
|
|
QVBoxLayout* layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(4, 4, 4, 4);
|
|
layout->setSpacing(4);
|
|
|
|
QPushButton* createBtn = new QPushButton(tr("Create Blueprint"), this);
|
|
createBtn->setFixedHeight(36);
|
|
layout->addWidget(createBtn);
|
|
|
|
m_scrollArea = new QScrollArea(this);
|
|
m_scrollArea->setWidgetResizable(true);
|
|
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
m_scrollContainer = new QWidget(m_scrollArea);
|
|
m_listLayout = new QVBoxLayout(m_scrollContainer);
|
|
m_listLayout->setContentsMargins(0, 0, 0, 0);
|
|
m_listLayout->setSpacing(2);
|
|
m_listLayout->addStretch();
|
|
m_scrollArea->setWidget(m_scrollContainer);
|
|
layout->addWidget(m_scrollArea, 1);
|
|
|
|
connect(createBtn, &QPushButton::clicked, this, [this]() {
|
|
bool ok = false;
|
|
const QString name = QInputDialog::getText(
|
|
this, tr("Create Blueprint"), tr("Blueprint name:"),
|
|
QLineEdit::Normal, QString(), &ok);
|
|
if (!ok || name.trimmed().isEmpty()) { return; }
|
|
|
|
ShipLayoutBlueprint bp;
|
|
bp.name = name.trimmed();
|
|
bp.shipType = m_shipType;
|
|
bp.modules = m_getModules();
|
|
m_allBlueprints.push_back(std::move(bp));
|
|
rebuildList();
|
|
});
|
|
|
|
rebuildList();
|
|
}
|
|
|
|
private:
|
|
void rebuildList()
|
|
{
|
|
// Remove all items except the trailing stretch.
|
|
while (m_listLayout->count() > 1)
|
|
{
|
|
QLayoutItem* item = m_listLayout->takeAt(0);
|
|
if (item->widget()) { delete item->widget(); }
|
|
delete item;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < m_allBlueprints.size(); ++i)
|
|
{
|
|
const ShipLayoutBlueprint& bp = m_allBlueprints[i];
|
|
if (bp.shipType != m_shipType) { continue; }
|
|
|
|
QWidget* row = new QWidget(m_scrollContainer);
|
|
QHBoxLayout* rowLayout = new QHBoxLayout(row);
|
|
rowLayout->setContentsMargins(0, 0, 0, 0);
|
|
rowLayout->setSpacing(2);
|
|
|
|
QPushButton* nameBtn = new QPushButton(bp.name, row);
|
|
nameBtn->setFixedHeight(36);
|
|
|
|
QPushButton* delBtn = new QPushButton("\xc3\x97", row);
|
|
delBtn->setFixedWidth(28);
|
|
delBtn->setFixedHeight(36);
|
|
|
|
rowLayout->addWidget(nameBtn, 1);
|
|
rowLayout->addWidget(delBtn, 0);
|
|
m_listLayout->insertWidget(m_listLayout->count() - 1, row);
|
|
|
|
const std::vector<PlacedModule> modules = bp.modules;
|
|
connect(nameBtn, &QPushButton::clicked, this, [this, modules]() {
|
|
m_loadModules(modules);
|
|
});
|
|
|
|
connect(delBtn, &QPushButton::clicked, this, [this, i]() {
|
|
m_allBlueprints.erase(m_allBlueprints.begin()
|
|
+ static_cast<std::ptrdiff_t>(i));
|
|
rebuildList();
|
|
});
|
|
}
|
|
}
|
|
|
|
std::vector<ShipLayoutBlueprint>& m_allBlueprints;
|
|
std::string m_shipType;
|
|
std::function<std::vector<PlacedModule>()> m_getModules;
|
|
std::function<void(const std::vector<PlacedModule>&)> m_loadModules;
|
|
QScrollArea* m_scrollArea = nullptr;
|
|
QWidget* m_scrollContainer = nullptr;
|
|
QVBoxLayout* m_listLayout = nullptr;
|
|
};
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ShipLayoutDialog implementation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|
const std::string& shipId,
|
|
const ShipLayoutConfig& currentLayout,
|
|
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
|
std::set<std::string> unlockedModuleIds,
|
|
std::map<std::string, int> moduleLevels,
|
|
QWidget* parent)
|
|
: QDialog(parent)
|
|
, m_config(config)
|
|
, m_shipId(shipId)
|
|
, m_unlockedModuleIds(std::move(unlockedModuleIds))
|
|
, m_moduleLevels(std::move(moduleLevels))
|
|
, m_rows(0)
|
|
, m_cols(0)
|
|
, m_placedModules(currentLayout.placedModules)
|
|
, m_activeModuleIndex(-2)
|
|
, m_currentRotation(Rotation::East)
|
|
, m_removeButton(nullptr)
|
|
, m_gridWidget(nullptr)
|
|
, m_statsPanel(nullptr)
|
|
{
|
|
setWindowTitle(tr("Configure Ship Layout"));
|
|
setModal(true);
|
|
|
|
// Find the ship's layout grid.
|
|
for (const ShipDef& def : config->ships.ships)
|
|
{
|
|
if (def.id == shipId)
|
|
{
|
|
m_shipLayout = def.layout;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_rows = static_cast<int>(m_shipLayout.size());
|
|
m_cols = 0;
|
|
for (const std::string& row : m_shipLayout)
|
|
{
|
|
const int w = static_cast<int>(row.size());
|
|
if (w > m_cols)
|
|
{
|
|
m_cols = w;
|
|
}
|
|
}
|
|
|
|
// Initialize grid.
|
|
m_grid.assign(m_rows, std::vector<CellInfo>(m_cols, {false, -1}));
|
|
for (int r = 0; r < m_rows; ++r)
|
|
{
|
|
for (int c = 0; c < static_cast<int>(m_shipLayout[r].size()); ++c)
|
|
{
|
|
if (m_shipLayout[r][c] == 'O')
|
|
{
|
|
m_grid[r][c].buildable = true;
|
|
}
|
|
}
|
|
}
|
|
rebuildOccupancy();
|
|
|
|
// --- UI layout ---
|
|
QVBoxLayout* outerLayout = new QVBoxLayout(this);
|
|
|
|
// Top: grid widget.
|
|
LayoutGridWidget* gridW = new LayoutGridWidget(this, this);
|
|
gridW->setGridData(&m_grid, m_rows, m_cols, &m_placedModules, m_config);
|
|
gridW->setGhostData(m_activeModuleIndex, m_currentRotation);
|
|
m_gridWidget = gridW;
|
|
outerLayout->addWidget(m_gridWidget, 0, Qt::AlignHCenter | Qt::AlignTop);
|
|
|
|
// Middle: three-column area (stats | module buttons | blueprints).
|
|
QHBoxLayout* columnsLayout = new QHBoxLayout();
|
|
|
|
// Left column: ship stats panel.
|
|
m_statsPanel = new ShipStatsPanel(config, this);
|
|
columnsLayout->addWidget(m_statsPanel);
|
|
|
|
// Center column: module selection buttons.
|
|
QVBoxLayout* centerLayout = new QVBoxLayout();
|
|
|
|
QGridLayout* buttonGrid = new QGridLayout();
|
|
buttonGrid->setSpacing(4);
|
|
|
|
QSignalMapper* mapper = new QSignalMapper(this);
|
|
int col = 0;
|
|
int row = 0;
|
|
const int kCols = 2;
|
|
|
|
for (int i = 0; i < static_cast<int>(config->modules.modules.size()); ++i)
|
|
{
|
|
const ModuleDef& def = config->modules.modules[i];
|
|
if (m_unlockedModuleIds.count(def.id) == 0)
|
|
{
|
|
m_moduleButtons.push_back(nullptr);
|
|
continue;
|
|
}
|
|
const QString label = displayName(def.id)
|
|
+ "\n" + QString::fromStdString(def.glyph);
|
|
QPushButton* btn = new QPushButton(label, this);
|
|
btn->setCheckable(true);
|
|
btn->setFixedHeight(48);
|
|
buttonGrid->addWidget(btn, row, col);
|
|
m_moduleButtons.push_back(btn);
|
|
|
|
mapper->setMapping(btn, i);
|
|
connect(btn, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));
|
|
|
|
++col;
|
|
if (col >= kCols)
|
|
{
|
|
col = 0;
|
|
++row;
|
|
}
|
|
}
|
|
connect(mapper, qOverload<int>(&QSignalMapper::mapped),
|
|
this, &ShipLayoutDialog::onModuleButtonClicked);
|
|
|
|
// Remove button.
|
|
m_removeButton = new QPushButton(tr("Remove"), this);
|
|
m_removeButton->setCheckable(true);
|
|
m_removeButton->setFixedHeight(48);
|
|
if (col > 0)
|
|
{
|
|
++row;
|
|
}
|
|
buttonGrid->addWidget(m_removeButton, row, 0, 1, kCols);
|
|
connect(m_removeButton, &QPushButton::clicked, this, [this]() {
|
|
if (m_activeModuleIndex == -1)
|
|
{
|
|
m_activeModuleIndex = -2;
|
|
m_removeButton->setChecked(false);
|
|
}
|
|
else
|
|
{
|
|
for (QPushButton* btn : m_moduleButtons)
|
|
{
|
|
if (btn) { btn->setChecked(false); }
|
|
}
|
|
m_activeModuleIndex = -1;
|
|
m_removeButton->setChecked(true);
|
|
}
|
|
updateGridWidget();
|
|
});
|
|
|
|
centerLayout->addLayout(buttonGrid);
|
|
centerLayout->addStretch();
|
|
|
|
columnsLayout->addLayout(centerLayout);
|
|
|
|
// Right column: blueprint panel.
|
|
ShipLayoutBlueprintPanel* bpPanel = new ShipLayoutBlueprintPanel(
|
|
allBlueprints,
|
|
m_shipId,
|
|
[this]() { return m_placedModules; },
|
|
[this](const std::vector<PlacedModule>& mods) { loadLayoutBlueprint(mods); },
|
|
this);
|
|
columnsLayout->addWidget(bpPanel);
|
|
|
|
outerLayout->addLayout(columnsLayout, 1);
|
|
|
|
// Bottom: confirm / cancel buttons.
|
|
QHBoxLayout* bottomBar = new QHBoxLayout();
|
|
QPushButton* confirmBtn = new QPushButton(tr("Confirm"), this);
|
|
QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);
|
|
bottomBar->addWidget(confirmBtn);
|
|
bottomBar->addWidget(cancelBtn);
|
|
outerLayout->addLayout(bottomBar);
|
|
|
|
connect(confirmBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onConfirm);
|
|
connect(cancelBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onCancel);
|
|
|
|
// Initial stats display.
|
|
updateStats();
|
|
|
|
// Grid click handler.
|
|
connect(this, &ShipLayoutDialog::gridCellClicked, this, [this](QPoint cell) {
|
|
if (m_activeModuleIndex == -2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_activeModuleIndex == -1)
|
|
{
|
|
// Remove mode: find and remove module at cell.
|
|
if (cell.y() >= 0 && cell.y() < m_rows && cell.x() >= 0 && cell.x() < m_cols)
|
|
{
|
|
const int idx = m_grid[cell.y()][cell.x()].moduleIndex;
|
|
if (idx >= 0)
|
|
{
|
|
m_placedModules.erase(m_placedModules.begin() + idx);
|
|
rebuildOccupancy();
|
|
updateGridWidget();
|
|
updateStats();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Place module.
|
|
const ModuleDef& def = m_config->modules.modules[m_activeModuleIndex];
|
|
if (canPlaceModule(def, cell, m_currentRotation))
|
|
{
|
|
PlacedModule pm;
|
|
pm.moduleId = def.id;
|
|
pm.position = cell;
|
|
pm.rotation = m_currentRotation;
|
|
m_placedModules.push_back(pm);
|
|
rebuildOccupancy();
|
|
updateGridWidget();
|
|
updateStats();
|
|
}
|
|
});
|
|
}
|
|
|
|
std::optional<ShipLayoutConfig> ShipLayoutDialog::result() const
|
|
{
|
|
return m_result;
|
|
}
|
|
|
|
void ShipLayoutDialog::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
if (event->key() == Qt::Key_Q)
|
|
{
|
|
// Rotate CCW = 3 CW steps.
|
|
switch (m_currentRotation)
|
|
{
|
|
case Rotation::East: m_currentRotation = Rotation::North; break;
|
|
case Rotation::North: m_currentRotation = Rotation::West; break;
|
|
case Rotation::West: m_currentRotation = Rotation::South; break;
|
|
case Rotation::South: m_currentRotation = Rotation::East; break;
|
|
}
|
|
updateGridWidget();
|
|
}
|
|
else if (event->key() == Qt::Key_E)
|
|
{
|
|
// Rotate CW.
|
|
switch (m_currentRotation)
|
|
{
|
|
case Rotation::East: m_currentRotation = Rotation::South; break;
|
|
case Rotation::South: m_currentRotation = Rotation::West; break;
|
|
case Rotation::West: m_currentRotation = Rotation::North; break;
|
|
case Rotation::North: m_currentRotation = Rotation::East; break;
|
|
}
|
|
updateGridWidget();
|
|
}
|
|
else
|
|
{
|
|
QDialog::keyPressEvent(event);
|
|
}
|
|
}
|
|
|
|
void ShipLayoutDialog::onModuleButtonClicked(int index)
|
|
{
|
|
if (m_activeModuleIndex == index)
|
|
{
|
|
if (m_moduleButtons[index]) { m_moduleButtons[index]->setChecked(false); }
|
|
m_activeModuleIndex = -2;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < static_cast<int>(m_moduleButtons.size()); ++i)
|
|
{
|
|
if (m_moduleButtons[i]) { m_moduleButtons[i]->setChecked(i == index); }
|
|
}
|
|
m_removeButton->setChecked(false);
|
|
m_activeModuleIndex = index;
|
|
}
|
|
updateGridWidget();
|
|
}
|
|
|
|
void ShipLayoutDialog::onConfirm()
|
|
{
|
|
ShipLayoutConfig layout;
|
|
layout.placedModules = m_placedModules;
|
|
m_result = layout;
|
|
accept();
|
|
}
|
|
|
|
void ShipLayoutDialog::onCancel()
|
|
{
|
|
m_result = std::nullopt;
|
|
reject();
|
|
}
|
|
|
|
void ShipLayoutDialog::rebuildOccupancy()
|
|
{
|
|
for (int r = 0; r < m_rows; ++r)
|
|
{
|
|
for (int c = 0; c < m_cols; ++c)
|
|
{
|
|
m_grid[r][c].moduleIndex = -1;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < static_cast<int>(m_placedModules.size()); ++i)
|
|
{
|
|
const PlacedModule& pm = m_placedModules[i];
|
|
const ModuleDef* def = nullptr;
|
|
for (const ModuleDef& d : m_config->modules.modules)
|
|
{
|
|
if (d.id == pm.moduleId) { def = &d; break; }
|
|
}
|
|
if (!def)
|
|
{
|
|
continue;
|
|
}
|
|
const std::vector<std::string> mask = rotatedMask(*def, pm.rotation);
|
|
for (int mr = 0; mr < static_cast<int>(mask.size()); ++mr)
|
|
{
|
|
for (int mc = 0; mc < static_cast<int>(mask[mr].size()); ++mc)
|
|
{
|
|
if (mask[mr][mc] != 'O')
|
|
{
|
|
continue;
|
|
}
|
|
const int gr = pm.position.y() + mr;
|
|
const int gc = pm.position.x() + mc;
|
|
if (gr >= 0 && gr < m_rows && gc >= 0 && gc < m_cols)
|
|
{
|
|
m_grid[gr][gc].moduleIndex = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShipLayoutDialog::updateGridWidget()
|
|
{
|
|
LayoutGridWidget* gridW = static_cast<LayoutGridWidget*>(m_gridWidget);
|
|
gridW->setGhostData(m_activeModuleIndex, m_currentRotation);
|
|
gridW->update();
|
|
}
|
|
|
|
void ShipLayoutDialog::updateStats()
|
|
{
|
|
int level = 1;
|
|
for (const ShipDef& def : m_config->ships.ships)
|
|
{
|
|
if (def.id == m_shipId)
|
|
{
|
|
level = def.schematic.playerProductionLevel;
|
|
break;
|
|
}
|
|
}
|
|
m_statsPanel->refresh(m_shipId, level, m_placedModules, m_moduleLevels);
|
|
}
|
|
|
|
bool ShipLayoutDialog::canPlaceModule(const ModuleDef& def, QPoint position,
|
|
Rotation rotation) const
|
|
{
|
|
const std::vector<std::string> mask = rotatedMask(def, rotation);
|
|
for (int mr = 0; mr < static_cast<int>(mask.size()); ++mr)
|
|
{
|
|
for (int mc = 0; mc < static_cast<int>(mask[mr].size()); ++mc)
|
|
{
|
|
if (mask[mr][mc] != 'O')
|
|
{
|
|
continue;
|
|
}
|
|
const int gr = position.y() + mr;
|
|
const int gc = position.x() + mc;
|
|
if (gr < 0 || gr >= m_rows || gc < 0 || gc >= m_cols)
|
|
{
|
|
return false;
|
|
}
|
|
if (!m_grid[gr][gc].buildable)
|
|
{
|
|
return false;
|
|
}
|
|
if (m_grid[gr][gc].moduleIndex >= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> ShipLayoutDialog::rotatedMask(const ModuleDef& def,
|
|
Rotation rotation) const
|
|
{
|
|
int steps = 0;
|
|
switch (rotation)
|
|
{
|
|
case Rotation::East: steps = 0; break;
|
|
case Rotation::South: steps = 1; break;
|
|
case Rotation::West: steps = 2; break;
|
|
case Rotation::North: steps = 3; break;
|
|
}
|
|
std::vector<std::string> result = def.surfaceMask;
|
|
for (int i = 0; i < steps; ++i)
|
|
{
|
|
result = rotateMaskCW(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ShipLayoutDialog::loadLayoutBlueprint(const std::vector<PlacedModule>& modules)
|
|
{
|
|
m_placedModules.clear();
|
|
|
|
// Build a temporary occupancy grid to detect overlaps within the blueprint.
|
|
std::vector<std::vector<bool>> occupied(m_rows, std::vector<bool>(m_cols, false));
|
|
|
|
for (const PlacedModule& pm : modules)
|
|
{
|
|
// Validate module type exists and is unlocked.
|
|
const ModuleDef* def = nullptr;
|
|
for (const ModuleDef& d : m_config->modules.modules)
|
|
{
|
|
if (d.id == pm.moduleId) { def = &d; break; }
|
|
}
|
|
if (!def || m_unlockedModuleIds.count(def->id) == 0) { continue; }
|
|
|
|
const std::vector<std::string> mask = rotatedMask(*def, pm.rotation);
|
|
bool valid = true;
|
|
|
|
for (int mr = 0; mr < static_cast<int>(mask.size()) && valid; ++mr)
|
|
{
|
|
for (int mc = 0; mc < static_cast<int>(mask[mr].size()) && valid; ++mc)
|
|
{
|
|
if (mask[mr][mc] != 'O') { continue; }
|
|
const int gr = pm.position.y() + mr;
|
|
const int gc = pm.position.x() + mc;
|
|
if (gr < 0 || gr >= m_rows || gc < 0 || gc >= m_cols) { valid = false; break; }
|
|
if (!m_grid[gr][gc].buildable) { valid = false; break; }
|
|
if (occupied[gr][gc]) { valid = false; break; }
|
|
}
|
|
}
|
|
|
|
if (!valid) { continue; }
|
|
|
|
// Mark cells as occupied.
|
|
for (int mr = 0; mr < static_cast<int>(mask.size()); ++mr)
|
|
{
|
|
for (int mc = 0; mc < static_cast<int>(mask[mr].size()); ++mc)
|
|
{
|
|
if (mask[mr][mc] != 'O') { continue; }
|
|
const int gr = pm.position.y() + mr;
|
|
const int gc = pm.position.x() + mc;
|
|
if (gr >= 0 && gr < m_rows && gc >= 0 && gc < m_cols)
|
|
{
|
|
occupied[gr][gc] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_placedModules.push_back(pm);
|
|
}
|
|
|
|
rebuildOccupancy();
|
|
updateGridWidget();
|
|
updateStats();
|
|
}
|