implement save and load blueprints

This commit is contained in:
2026-04-28 21:31:29 +02:00
parent 550f46009f
commit ced4ab5fe3
5 changed files with 240 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
#include "BlueprintSerializer.h"
#include <sstream>
#include <stdexcept>
#include "toml.hpp"
#include "BuildingType.h"
#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 BlueprintSerializer
{
std::string serialize(const std::vector<Blueprint>& blueprints)
{
toml::array bpArr;
for (const Blueprint& bp : blueprints)
{
toml::array bldArr;
for (const BlueprintBuilding& b : bp.buildings)
{
toml::table bldTbl;
bldTbl.insert("type", buildingTypeId(b.type));
bldTbl.insert("rotation", rotationToString(b.rotation));
bldTbl.insert("offset_x", static_cast<int64_t>(b.offset.x()));
bldTbl.insert("offset_y", static_cast<int64_t>(b.offset.y()));
bldTbl.insert("recipe_id", b.recipeId);
bldArr.push_back(std::move(bldTbl));
}
toml::table bpTbl;
bpTbl.insert("name", bp.name.toStdString());
bpTbl.insert("buildings", std::move(bldArr));
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<Blueprint> 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<Blueprint> 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)
{
throw std::runtime_error("blueprint[" + std::to_string(i) + "] is not a table");
}
Blueprint bp;
bp.name = QString::fromStdString((*bpTbl)["name"].value_or(std::string{}));
const toml::array* bldArr = (*bpTbl)["buildings"].as_array();
if (bldArr)
{
for (std::size_t j = 0; j < bldArr->size(); ++j)
{
const toml::table* bldTbl = (*bldArr)[j].as_table();
if (!bldTbl)
{
throw std::runtime_error(
"blueprint[" + std::to_string(i) + "].buildings[" +
std::to_string(j) + "] is not a table");
}
const std::string typeStr = (*bldTbl)["type"].value_or(std::string{});
const std::optional<BuildingType> parsedType = parseBuildingType(typeStr);
if (!parsedType)
{
throw std::runtime_error("unknown building type: '" + typeStr + "'");
}
BlueprintBuilding bb;
bb.type = *parsedType;
bb.rotation = parseRotation((*bldTbl)["rotation"].value_or(std::string{}));
bb.offset.setX(static_cast<int>((*bldTbl)["offset_x"].value_or(int64_t{0})));
bb.offset.setY(static_cast<int>((*bldTbl)["offset_y"].value_or(int64_t{0})));
bb.recipeId = (*bldTbl)["recipe_id"].value_or(std::string{});
bp.buildings.push_back(std::move(bb));
}
}
result.push_back(std::move(bp));
}
return result;
}
} // namespace BlueprintSerializer

View File

@@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
#include "Blueprint.h"
namespace BlueprintSerializer
{
std::string serialize(const std::vector<Blueprint>& blueprints);
std::vector<Blueprint> deserialize(const std::string& tomlContent);
} // namespace BlueprintSerializer

View File

@@ -9,6 +9,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/GameConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/GameConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.h ${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.h ${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.h
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintSerializer.h
PARENT_SCOPE PARENT_SCOPE
) )
@@ -17,6 +18,7 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/Formula.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Formula.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintSerializer.cpp
PARENT_SCOPE PARENT_SCOPE
) )

View File

@@ -3,11 +3,17 @@
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
#include <QCoreApplication>
#include <QFile>
#include <QHBoxLayout>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QScrollArea> #include <QScrollArea>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "BlueprintSerializer.h"
#include "Building.h" #include "Building.h"
#include "BuildingSystem.h" #include "BuildingSystem.h"
#include "Simulation.h" #include "Simulation.h"
@@ -49,6 +55,18 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge
connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked); connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked);
connect(m_deleteBtn, &QPushButton::clicked, this, &BlueprintPanel::onDeleteClicked); connect(m_deleteBtn, &QPushButton::clicked, this, &BlueprintPanel::onDeleteClicked);
QHBoxLayout* ioLayout = new QHBoxLayout();
m_saveBtn = new QPushButton("Save", this);
m_loadBtn = new QPushButton("Load", this);
m_saveBtn->setFixedHeight(36);
m_loadBtn->setFixedHeight(36);
ioLayout->addWidget(m_saveBtn);
ioLayout->addWidget(m_loadBtn);
layout->addLayout(ioLayout);
connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked);
connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked);
} }
void BlueprintPanel::onSelectionChanged(const std::vector<EntityId>& ids) void BlueprintPanel::onSelectionChanged(const std::vector<EntityId>& ids)
@@ -242,6 +260,72 @@ void BlueprintPanel::rebuildButtons()
refreshButtonStates(); refreshButtonStates();
} }
void BlueprintPanel::onSaveClicked()
{
const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
try
{
const std::string content = BlueprintSerializer::serialize(m_blueprints);
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, "Save Failed",
QString("Could not open file for writing:\n%1").arg(path));
return;
}
file.write(QByteArray::fromStdString(content));
}
catch (const std::exception& e)
{
QMessageBox::critical(this, "Save Failed",
QString("Failed to save blueprints:\n%1").arg(e.what()));
}
}
void BlueprintPanel::onLoadClicked()
{
QMessageBox box(this);
box.setWindowTitle("Load Blueprints");
box.setText("Load blueprints? This will replace all current blueprints.");
QPushButton* confirmBtn = box.addButton("Confirm", QMessageBox::AcceptRole);
box.addButton("Cancel", QMessageBox::RejectRole);
box.exec();
if (box.clickedButton() != confirmBtn) { return; }
const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
try
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::critical(this, "Load Failed",
QString("Could not open file:\n%1").arg(path));
return;
}
const std::string content = file.readAll().toStdString();
std::vector<Blueprint> loaded = BlueprintSerializer::deserialize(content);
if (m_activeIndex >= 0)
{
emit exitBlueprintModeRequested();
m_activeIndex = -1;
}
if (m_deleteMode)
{
m_deleteMode = false;
m_deleteBtn->setChecked(false);
}
m_blueprints = std::move(loaded);
rebuildButtons();
}
catch (const std::exception& e)
{
QMessageBox::critical(this, "Load Failed",
QString("Failed to load blueprints:\n%1").arg(e.what()));
}
}
void BlueprintPanel::refreshButtonStates() void BlueprintPanel::refreshButtonStates()
{ {
const bool anyPlaceable = [&]() { const bool anyPlaceable = [&]() {

View File

@@ -34,6 +34,8 @@ private slots:
void onCreateClicked(); void onCreateClicked();
void onDeleteClicked(); void onDeleteClicked();
void onBlueprintButtonClicked(int index); void onBlueprintButtonClicked(int index);
void onSaveClicked();
void onLoadClicked();
private: private:
Blueprint createBlueprintFromSelection() const; Blueprint createBlueprintFromSelection() const;
@@ -51,6 +53,8 @@ private:
std::vector<QPushButton*> m_blueprintButtons; std::vector<QPushButton*> m_blueprintButtons;
QPushButton* m_createBtn; QPushButton* m_createBtn;
QPushButton* m_deleteBtn; QPushButton* m_deleteBtn;
QPushButton* m_saveBtn;
QPushButton* m_loadBtn;
QWidget* m_buttonsContainer; QWidget* m_buttonsContainer;
QVBoxLayout* m_buttonsLayout; QVBoxLayout* m_buttonsLayout;
}; };