ship layout blueprints
This commit is contained in:
@@ -36,7 +36,7 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
m_beltSystem,
|
||||
[this]() { return allocateId(); },
|
||||
[](int) {},
|
||||
[](const std::string&, QVector2D) {},
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
m_rng);
|
||||
|
||||
m_shipSystem = std::make_unique<ShipSystem>(
|
||||
@@ -198,7 +198,8 @@ void ArenaSimulation::spawnShips()
|
||||
for (int i = 0; i < entry.count; ++i)
|
||||
{
|
||||
const QVector2D pos(xDist(m_rng), yDist(m_rng));
|
||||
m_shipSystem->spawn(entry.schematicId, entry.level, pos, false);
|
||||
m_shipSystem->spawn(entry.schematicId, entry.level, pos, false,
|
||||
entry.layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,7 +215,8 @@ void ArenaSimulation::spawnShips()
|
||||
for (int i = 0; i < entry.count; ++i)
|
||||
{
|
||||
const QVector2D pos(xDist(m_rng), yDist(m_rng));
|
||||
m_shipSystem->spawn(entry.schematicId, entry.level, pos, true);
|
||||
m_shipSystem->spawn(entry.schematicId, entry.level, pos, true,
|
||||
entry.layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "toml.hpp"
|
||||
|
||||
#include "Rotation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -35,6 +37,14 @@ std::string requireString(NodeView node, const std::string& path)
|
||||
return *value;
|
||||
}
|
||||
|
||||
Rotation parseRotation(const std::string& s)
|
||||
{
|
||||
if (s == "east") { return Rotation::East; }
|
||||
if (s == "south") { return Rotation::South; }
|
||||
if (s == "west") { return Rotation::West; }
|
||||
return Rotation::North;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BalancingConfig loadBalancingConfig(const std::string& path)
|
||||
@@ -119,6 +129,36 @@ BalancingConfig loadBalancingConfig(const std::string& path)
|
||||
requireInt((*shipTbl)["level"], sPrefix + ".level"));
|
||||
entry.count = static_cast<int>(
|
||||
requireInt((*shipTbl)["count"], sPrefix + ".count"));
|
||||
|
||||
const toml::array* modArray = (*shipTbl)["modules"].as_array();
|
||||
if (modArray && !modArray->empty())
|
||||
{
|
||||
ShipLayoutConfig layout;
|
||||
for (std::size_t mi = 0; mi < modArray->size(); ++mi)
|
||||
{
|
||||
const toml::table* modTbl = (*modArray)[mi].as_table();
|
||||
if (!modTbl) { continue; }
|
||||
|
||||
const std::optional<std::string> type =
|
||||
(*modTbl)["type"].value<std::string>();
|
||||
const std::optional<int64_t> x =
|
||||
(*modTbl)["x"].value<int64_t>();
|
||||
const std::optional<int64_t> y =
|
||||
(*modTbl)["y"].value<int64_t>();
|
||||
const std::optional<std::string> rotStr =
|
||||
(*modTbl)["rotation"].value<std::string>();
|
||||
if (!type || !x || !y || !rotStr) { continue; }
|
||||
|
||||
PlacedModule pm;
|
||||
pm.moduleId = *type;
|
||||
pm.position = QPoint(static_cast<int>(*x),
|
||||
static_cast<int>(*y));
|
||||
pm.rotation = parseRotation(*rotStr);
|
||||
layout.placedModules.push_back(std::move(pm));
|
||||
}
|
||||
entry.layout = std::move(layout);
|
||||
}
|
||||
|
||||
team.ships.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
#include "ShipLayout.h"
|
||||
|
||||
struct ArenaStationEntry
|
||||
{
|
||||
std::string stationType; // "player_station" or "enemy_station"
|
||||
@@ -17,6 +20,7 @@ struct ArenaShipEntry
|
||||
std::string schematicId;
|
||||
int level;
|
||||
int count;
|
||||
std::optional<ShipLayoutConfig> layout;
|
||||
};
|
||||
|
||||
struct ArenaTeamConfig
|
||||
|
||||
@@ -11,6 +11,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintSerializer.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprintSerializer.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -20,6 +21,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintSerializer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprintSerializer.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
|
||||
129
src/lib/config/ShipLayoutBlueprintSerializer.cpp
Normal file
129
src/lib/config/ShipLayoutBlueprintSerializer.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "ShipLayoutBlueprintSerializer.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "toml.hpp"
|
||||
|
||||
#include "Rotation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string rotationToString(Rotation r)
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case Rotation::North: return "north";
|
||||
case Rotation::East: return "east";
|
||||
case Rotation::South: return "south";
|
||||
case Rotation::West: return "west";
|
||||
}
|
||||
return "north";
|
||||
}
|
||||
|
||||
Rotation parseRotation(const std::string& s)
|
||||
{
|
||||
if (s == "east") { return Rotation::East; }
|
||||
if (s == "south") { return Rotation::South; }
|
||||
if (s == "west") { return Rotation::West; }
|
||||
return Rotation::North;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ShipLayoutBlueprintSerializer
|
||||
{
|
||||
|
||||
std::string serialize(const std::vector<ShipLayoutBlueprint>& blueprints)
|
||||
{
|
||||
toml::array bpArr;
|
||||
for (const ShipLayoutBlueprint& bp : blueprints)
|
||||
{
|
||||
toml::array modArr;
|
||||
for (const PlacedModule& pm : bp.modules)
|
||||
{
|
||||
toml::table modTbl;
|
||||
modTbl.insert("type", pm.moduleId);
|
||||
modTbl.insert("x", static_cast<int64_t>(pm.position.x()));
|
||||
modTbl.insert("y", static_cast<int64_t>(pm.position.y()));
|
||||
modTbl.insert("rotation", rotationToString(pm.rotation));
|
||||
modArr.push_back(std::move(modTbl));
|
||||
}
|
||||
|
||||
toml::table bpTbl;
|
||||
bpTbl.insert("name", bp.name.toStdString());
|
||||
bpTbl.insert("ship_type", bp.shipType);
|
||||
bpTbl.insert("modules", std::move(modArr));
|
||||
bpArr.push_back(std::move(bpTbl));
|
||||
}
|
||||
|
||||
toml::table root;
|
||||
root.insert("blueprint", std::move(bpArr));
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << root;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::vector<ShipLayoutBlueprint> deserialize(const std::string& tomlContent)
|
||||
{
|
||||
toml::table root;
|
||||
try
|
||||
{
|
||||
root = toml::parse(tomlContent);
|
||||
}
|
||||
catch (const toml::parse_error& e)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "TOML parse error: " << e.description() << " at " << e.source().begin;
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
|
||||
std::vector<ShipLayoutBlueprint> result;
|
||||
|
||||
const toml::array* bpArr = root["blueprint"].as_array();
|
||||
if (!bpArr) { return result; }
|
||||
|
||||
for (std::size_t i = 0; i < bpArr->size(); ++i)
|
||||
{
|
||||
const toml::table* bpTbl = (*bpArr)[i].as_table();
|
||||
if (!bpTbl) { continue; }
|
||||
|
||||
const std::optional<std::string> name = (*bpTbl)["name"].value<std::string>();
|
||||
const std::optional<std::string> shipType = (*bpTbl)["ship_type"].value<std::string>();
|
||||
if (!name || name->empty() || !shipType || shipType->empty()) { continue; }
|
||||
|
||||
ShipLayoutBlueprint bp;
|
||||
bp.name = QString::fromStdString(*name);
|
||||
bp.shipType = *shipType;
|
||||
|
||||
const toml::array* modArr = (*bpTbl)["modules"].as_array();
|
||||
if (modArr)
|
||||
{
|
||||
for (std::size_t j = 0; j < modArr->size(); ++j)
|
||||
{
|
||||
const toml::table* modTbl = (*modArr)[j].as_table();
|
||||
if (!modTbl) { continue; }
|
||||
|
||||
const std::optional<std::string> type = (*modTbl)["type"].value<std::string>();
|
||||
const std::optional<int64_t> x = (*modTbl)["x"].value<int64_t>();
|
||||
const std::optional<int64_t> y = (*modTbl)["y"].value<int64_t>();
|
||||
const std::optional<std::string> rotStr = (*modTbl)["rotation"].value<std::string>();
|
||||
if (!type || !x || !y || !rotStr) { continue; }
|
||||
|
||||
PlacedModule pm;
|
||||
pm.moduleId = *type;
|
||||
pm.position = QPoint(static_cast<int>(*x), static_cast<int>(*y));
|
||||
pm.rotation = parseRotation(*rotStr);
|
||||
bp.modules.push_back(std::move(pm));
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(std::move(bp));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ShipLayoutBlueprintSerializer
|
||||
14
src/lib/config/ShipLayoutBlueprintSerializer.h
Normal file
14
src/lib/config/ShipLayoutBlueprintSerializer.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ShipLayoutBlueprint.h"
|
||||
|
||||
namespace ShipLayoutBlueprintSerializer
|
||||
{
|
||||
|
||||
std::string serialize(const std::vector<ShipLayoutBlueprint>& blueprints);
|
||||
std::vector<ShipLayoutBlueprint> deserialize(const std::string& tomlContent);
|
||||
|
||||
} // namespace ShipLayoutBlueprintSerializer
|
||||
@@ -8,6 +8,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Scrap.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Ship.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h
|
||||
|
||||
15
src/lib/sim/ShipLayoutBlueprint.h
Normal file
15
src/lib/sim/ShipLayoutBlueprint.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "ShipLayout.h"
|
||||
|
||||
struct ShipLayoutBlueprint
|
||||
{
|
||||
QString name;
|
||||
std::string shipType;
|
||||
std::vector<PlacedModule> modules;
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QFile>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
@@ -14,6 +16,7 @@
|
||||
#include "GameWorldView.h"
|
||||
#include "HeaderBar.h"
|
||||
#include "SelectedBuildingPanel.h"
|
||||
#include "ShipLayoutBlueprintSerializer.h"
|
||||
#include "ShipLayoutDialog.h"
|
||||
#include "Simulation.h"
|
||||
#include "Tick.h"
|
||||
@@ -111,6 +114,24 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
||||
m_gameWorldView->setFocus();
|
||||
}
|
||||
});
|
||||
|
||||
// Load layout blueprints from disk. Missing file is silently ignored.
|
||||
const QString bpPath = QCoreApplication::applicationDirPath() + "/ship_layouts.toml";
|
||||
QFile bpFile(bpPath);
|
||||
if (bpFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
try
|
||||
{
|
||||
m_layoutBlueprints = ShipLayoutBlueprintSerializer::deserialize(
|
||||
bpFile.readAll().toStdString());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
QMessageBox::critical(this, "Load Error",
|
||||
QString("Failed to load ship_layouts.toml:\n%1").arg(e.what()));
|
||||
m_layoutBlueprints.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::resizeEvent(QResizeEvent* event)
|
||||
@@ -119,6 +140,23 @@ void MainWindow::resizeEvent(QResizeEvent* event)
|
||||
layoutPanels();
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
const QString path = QCoreApplication::applicationDirPath() + "/ship_layouts.toml";
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
try
|
||||
{
|
||||
const std::string content =
|
||||
ShipLayoutBlueprintSerializer::serialize(m_layoutBlueprints);
|
||||
file.write(QByteArray::fromStdString(content));
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::layoutPanels()
|
||||
{
|
||||
const int totalW = width();
|
||||
@@ -199,7 +237,8 @@ void MainWindow::onLayoutDialogRequested(EntityId shipyardId)
|
||||
currentLayout = *b->shipLayout;
|
||||
}
|
||||
|
||||
ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout, this);
|
||||
ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout,
|
||||
m_layoutBlueprints, this);
|
||||
if (dialog.exec() == QDialog::Accepted && dialog.result().has_value())
|
||||
{
|
||||
m_sim->buildings().setShipLayout(shipyardId, *dialog.result());
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "EntityId.h"
|
||||
#include "ShipLayoutBlueprint.h"
|
||||
#include "Tick.h"
|
||||
#include "VisualsConfig.h"
|
||||
|
||||
@@ -14,6 +16,7 @@ class HeaderBar;
|
||||
class SelectedBuildingPanel;
|
||||
class BuildButtonGrid;
|
||||
class BlueprintPanel;
|
||||
class QCloseEvent;
|
||||
class QResizeEvent;
|
||||
|
||||
class MainWindow : public QWidget
|
||||
@@ -25,6 +28,7 @@ public:
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void onGameOver();
|
||||
@@ -44,4 +48,6 @@ private:
|
||||
BuildButtonGrid* m_buildButtonGrid;
|
||||
BlueprintPanel* m_blueprintPanel;
|
||||
QWidget* m_bottomPanel;
|
||||
|
||||
std::vector<ShipLayoutBlueprint> m_layoutBlueprints;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "ShipLayoutDialog.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>
|
||||
|
||||
@@ -263,6 +266,118 @@ private:
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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("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, "Create Blueprint", "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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -270,6 +385,7 @@ private:
|
||||
ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
||||
const std::string& shipId,
|
||||
const ShipLayoutConfig& currentLayout,
|
||||
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
||||
QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_config(config)
|
||||
@@ -408,6 +524,15 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
||||
|
||||
mainLayout->addLayout(rightLayout);
|
||||
|
||||
// Right: blueprint panel (third column).
|
||||
ShipLayoutBlueprintPanel* bpPanel = new ShipLayoutBlueprintPanel(
|
||||
allBlueprints,
|
||||
m_shipId,
|
||||
[this]() { return m_placedModules; },
|
||||
[this](const std::vector<PlacedModule>& mods) { loadLayoutBlueprint(mods); },
|
||||
this);
|
||||
mainLayout->addWidget(bpPanel);
|
||||
|
||||
// Grid click handler.
|
||||
connect(this, &ShipLayoutDialog::gridCellClicked, this, [this](QPoint cell) {
|
||||
if (m_activeModuleIndex == -2)
|
||||
@@ -614,3 +739,60 @@ std::vector<std::string> ShipLayoutDialog::rotatedMask(const ModuleDef& def,
|
||||
}
|
||||
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.
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "GameConfig.h"
|
||||
#include "Rotation.h"
|
||||
#include "ShipLayout.h"
|
||||
#include "ShipLayoutBlueprint.h"
|
||||
|
||||
class QPushButton;
|
||||
|
||||
@@ -21,6 +22,7 @@ public:
|
||||
ShipLayoutDialog(const GameConfig* config,
|
||||
const std::string& shipId,
|
||||
const ShipLayoutConfig& currentLayout,
|
||||
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
std::optional<ShipLayoutConfig> result() const;
|
||||
@@ -49,6 +51,7 @@ private:
|
||||
void updateGridWidget();
|
||||
bool canPlaceModule(const ModuleDef& def, QPoint position, Rotation rotation) const;
|
||||
std::vector<std::string> rotatedMask(const ModuleDef& def, Rotation rotation) const;
|
||||
void loadLayoutBlueprint(const std::vector<PlacedModule>& modules);
|
||||
|
||||
const GameConfig* m_config;
|
||||
std::string m_shipId;
|
||||
|
||||
Reference in New Issue
Block a user