From 28169b051e84971d1171ca235a924c1ff3602340 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 23 Mar 2025 19:52:21 +0100 Subject: [PATCH 01/11] campaign editor --- lib/campaign/CampaignState.cpp | 4 + lib/campaign/CampaignState.h | 21 + lib/texts/MetaString.cpp | 6 + lib/texts/MetaString.h | 2 + mapeditor/BitmapHandler.cpp | 8 +- mapeditor/CMakeLists.txt | 16 + mapeditor/StdInc.h | 1 + mapeditor/campaigneditor/campaigneditor.cpp | 251 ++++++++ mapeditor/campaigneditor/campaigneditor.h | 60 ++ mapeditor/campaigneditor/campaigneditor.ui | 157 +++++ .../campaigneditor/campaignproperties.cpp | 193 ++++++ mapeditor/campaigneditor/campaignproperties.h | 46 ++ .../campaigneditor/campaignproperties.ui | 332 ++++++++++ mapeditor/campaigneditor/campaignview.cpp | 48 ++ mapeditor/campaigneditor/campaignview.h | 44 ++ .../campaigneditor/scenarioproperties.cpp | 529 ++++++++++++++++ mapeditor/campaigneditor/scenarioproperties.h | 65 ++ .../campaigneditor/scenarioproperties.ui | 569 ++++++++++++++++++ mapeditor/campaigneditor/startingbonus.cpp | 370 ++++++++++++ mapeditor/campaigneditor/startingbonus.h | 55 ++ mapeditor/campaigneditor/startingbonus.ui | 468 ++++++++++++++ mapeditor/helper.cpp | 109 ++++ mapeditor/helper.h | 21 + mapeditor/mainwindow.cpp | 103 +--- mapeditor/mainwindow.h | 7 +- mapeditor/mainwindow.ui | 13 + mapeditor/mapcontroller.cpp | 2 +- mapeditor/mapcontroller.h | 2 +- 28 files changed, 3404 insertions(+), 98 deletions(-) create mode 100644 mapeditor/campaigneditor/campaigneditor.cpp create mode 100644 mapeditor/campaigneditor/campaigneditor.h create mode 100644 mapeditor/campaigneditor/campaigneditor.ui create mode 100644 mapeditor/campaigneditor/campaignproperties.cpp create mode 100644 mapeditor/campaigneditor/campaignproperties.h create mode 100644 mapeditor/campaigneditor/campaignproperties.ui create mode 100644 mapeditor/campaigneditor/campaignview.cpp create mode 100644 mapeditor/campaigneditor/campaignview.h create mode 100644 mapeditor/campaigneditor/scenarioproperties.cpp create mode 100644 mapeditor/campaigneditor/scenarioproperties.h create mode 100644 mapeditor/campaigneditor/scenarioproperties.ui create mode 100644 mapeditor/campaigneditor/startingbonus.cpp create mode 100644 mapeditor/campaigneditor/startingbonus.h create mode 100644 mapeditor/campaigneditor/startingbonus.ui create mode 100644 mapeditor/helper.cpp create mode 100644 mapeditor/helper.h diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index 8932da92f..81d7488c4 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -431,6 +431,10 @@ std::unique_ptr CampaignState::getMap(CampaignScenarioID scenarioId, IGame std::string scenarioName = getFilename().substr(0, getFilename().find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId.getNum()); + + if(!mapPieces.count(scenarioId) || !mapPieces.find(scenarioId)->second.size()) + return nullptr; + const auto & mapContent = mapPieces.find(scenarioId)->second; auto result = mapService.loadMap(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding(), cb); diff --git a/lib/campaign/CampaignState.h b/lib/campaign/CampaignState.h index bcff8a883..40911b5df 100644 --- a/lib/campaign/CampaignState.h +++ b/lib/campaign/CampaignState.h @@ -32,6 +32,11 @@ class IGameCallback; class DLL_LINKAGE CampaignRegions { + // Campaign editor + friend class CampaignEditor; + friend class CampaignProperties; + friend class ScenarioProperties; + std::string campPrefix; std::vector campSuffix; std::string campBackground; @@ -85,6 +90,11 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable friend class CampaignHandler; friend class Campaign; + // Campaign editor + friend class CampaignEditor; + friend class CampaignProperties; + friend class ScenarioProperties; + CampaignVersion version = CampaignVersion::NONE; CampaignRegions campaignRegions; MetaString name; @@ -251,6 +261,11 @@ class DLL_LINKAGE Campaign : public CampaignHeader, public Serializeable { friend class CampaignHandler; + // Campaign editor + friend class CampaignEditor; + friend class CampaignProperties; + friend class ScenarioProperties; + std::map scenarios; public: @@ -273,6 +288,12 @@ public: class DLL_LINKAGE CampaignState : public Campaign { friend class CampaignHandler; + + // Campaign editor + friend class CampaignEditor; + friend class CampaignProperties; + friend class ScenarioProperties; + using ScenarioPoolType = std::vector; using CampaignPoolType = std::map; using GlobalPoolType = std::map; diff --git a/lib/texts/MetaString.cpp b/lib/texts/MetaString.cpp index 068017378..5bc5875f9 100644 --- a/lib/texts/MetaString.cpp +++ b/lib/texts/MetaString.cpp @@ -14,6 +14,7 @@ #include "CCreatureHandler.h" #include "CCreatureSet.h" #include "entities/faction/CFaction.h" +#include "entities/hero/CHero.h" #include "texts/CGeneralTextHandler.h" #include "CSkillHandler.h" #include "GameConstants.h" @@ -350,6 +351,11 @@ void MetaString::appendName(const ArtifactID & id) appendTextID(id.toEntity(LIBRARY)->getNameTextID()); } +void MetaString::appendName(const HeroTypeID & id) +{ + appendTextID(id.toEntity(LIBRARY)->getNameTextID()); +} + void MetaString::appendName(const SpellID & id) { appendTextID(id.toEntity(LIBRARY)->getNameTextID()); diff --git a/lib/texts/MetaString.h b/lib/texts/MetaString.h index fd002aef7..68230a027 100644 --- a/lib/texts/MetaString.h +++ b/lib/texts/MetaString.h @@ -23,6 +23,7 @@ class SecondarySkill; class SpellID; class FactionID; class GameResID; +class HeroTypeID; using TQuantity = si32; /// Strings classes that can be used as replacement in MetaString @@ -78,6 +79,7 @@ public: void appendNumber(int64_t value); void appendName(const ArtifactID& id); + void appendName(const HeroTypeID& id); void appendName(const SpellID& id); void appendName(const PlayerColor& id); void appendName(const CreatureID & id, TQuantity count); diff --git a/mapeditor/BitmapHandler.cpp b/mapeditor/BitmapHandler.cpp index 67c5d4224..dd971b4d7 100644 --- a/mapeditor/BitmapHandler.cpp +++ b/mapeditor/BitmapHandler.cpp @@ -69,18 +69,20 @@ namespace BitmapHandler it = (int)size - 256 * 3; for(int i = 0; i < 256; i++) { - char bytes[3]; + unsigned char bytes[4]; bytes[0] = pcx[it++]; bytes[1] = pcx[it++]; bytes[2] = pcx[it++]; - colorTable.append(qRgb(bytes[0], bytes[1], bytes[2])); + bytes[3] = (bytes[0] == 0 && bytes[1] == 255 && bytes[2] == 255) ? 0 : 255; + + colorTable.append(qRgba(bytes[0], bytes[1], bytes[2], bytes[3])); } image.setColorTable(colorTable); return image; } else { - QImage image(pcx + it, width, height, width * 3, QImage::Format_RGB888); + QImage image(pcx + it, width, height, width * 3, QImage::Format_BGR888); return image; } } diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index e60fb9aaf..79f1fce36 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -41,6 +41,12 @@ set(editor_SRCS inspector/PickObjectDelegate.cpp inspector/portraitwidget.cpp resourceExtractor/ResourceConverter.cpp + helper.cpp + campaigneditor/campaigneditor.cpp + campaigneditor/campaignproperties.cpp + campaigneditor/scenarioproperties.cpp + campaigneditor/startingbonus.cpp + campaigneditor/campaignview.cpp ) set(editor_HEADERS @@ -87,6 +93,12 @@ set(editor_HEADERS inspector/baseinspectoritemdelegate.h resourceExtractor/ResourceConverter.h mapeditorroles.h + helper.h + campaigneditor/campaigneditor.h + campaigneditor/campaignproperties.h + campaigneditor/scenarioproperties.h + campaigneditor/startingbonus.h + campaigneditor/campaignview.h ) set(editor_FORMS @@ -118,6 +130,10 @@ set(editor_FORMS inspector/heroskillswidget.ui inspector/herospellwidget.ui inspector/portraitwidget.ui + campaigneditor/campaigneditor.ui + campaigneditor/campaignproperties.ui + campaigneditor/scenarioproperties.ui + campaigneditor/startingbonus.ui ) set(editor_RESOURCES diff --git a/mapeditor/StdInc.h b/mapeditor/StdInc.h index 4ff74c6b0..fd30130ef 100644 --- a/mapeditor/StdInc.h +++ b/mapeditor/StdInc.h @@ -12,6 +12,7 @@ #include "../Global.h" #define VCMI_EDITOR_NAME "VCMI Map Editor" +#define VCMI_CAMP_EDITOR_NAME "VCMI Campaign Editor" #include #include diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp new file mode 100644 index 000000000..5818906a0 --- /dev/null +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -0,0 +1,251 @@ +/* + * campaigneditor.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "campaigneditor.h" +#include "ui_campaigneditor.h" + +#include "campaignproperties.h" +#include "scenarioproperties.h" + +#include "../BitmapHandler.h" +#include "../helper.h" + +#include "../../lib/VCMIDirs.h" +#include "../../lib/json/JsonNode.h" +#include "../../lib/campaign/CampaignState.h" +#include "../../lib/mapping/CMap.h" + +CampaignEditor::CampaignEditor(): + ui(new Ui::CampaignEditor), + selectedScenario(CampaignScenarioID::NONE) +{ + ui->setupUi(this); + + setWindowIcon(QIcon{":/icons/menu-game.png"}); + ui->actionOpen->setIcon(QIcon{":/icons/document-open.png"}); + ui->actionSave->setIcon(QIcon{":/icons/document-save.png"}); + ui->actionNew->setIcon(QIcon{":/icons/document-new.png"}); + ui->actionScenarioProperties->setIcon(QIcon{":/icons/menu-settings.png"}); + ui->actionCampaignProperties->setIcon(QIcon{":/icons/menu-mods.png"}); + + campaignScene.reset(new CampaignScene()); + ui->campaignView->setScene(campaignScene.get()); + + redraw(); + + setTitle(); + + setWindowModality(Qt::ApplicationModal); + + show(); +} + +CampaignEditor::~CampaignEditor() +{ + delete ui; +} + +void CampaignEditor::redraw() +{ + ui->actionSave->setEnabled(campaignState != nullptr); + ui->actionSave_as->setEnabled(campaignState != nullptr); + ui->actionScenarioProperties->setEnabled(campaignState != nullptr && campaignState->scenarios.count(selectedScenario)); + ui->actionCampaignProperties->setEnabled(campaignState != nullptr); + + if(!campaignState) + return; + + campaignScene->clear(); + + auto background = BitmapHandler::loadBitmap(campaignState->getRegions().getBackgroundName().getName()); + campaignScene->addItem(new QGraphicsPixmapItem(QPixmap::fromImage(background))); + for (auto & s : campaignState->scenarios) + { + auto scenario = s.first; + auto color = campaignState->scenarios.at(scenario).regionColor; + auto image = BitmapHandler::loadBitmap(campaignState->getRegions().getAvailableName(scenario, color).getName()); + if(selectedScenario == scenario) + image = BitmapHandler::loadBitmap(campaignState->getRegions().getSelectedName(scenario, color).getName()); + else if(campaignState->scenarios.at(scenario).mapName == "") + image = BitmapHandler::loadBitmap(campaignState->getRegions().getConqueredName(scenario, color).getName()); + auto pixmap = new ClickablePixmapItem(QPixmap::fromImage(image), [this, scenario]() + { + bool redrawRequired = selectedScenario != scenario; + selectedScenario = scenario; + + if(redrawRequired) + redraw(); + }, [this, scenario]() + { + if(ScenarioProperties::showScenarioProperties(campaignState, scenario)) + changed(); + redraw(); + }, [this, scenario](QGraphicsSceneContextMenuEvent * event) + { + QMenu contextMenu(this); + QAction *actionScenarioProperties = contextMenu.addAction(tr("Scenario editor")); + actionScenarioProperties->setIcon(ui->actionScenarioProperties->icon()); + connect(actionScenarioProperties, &QAction::triggered, this, [this, scenario]() { + if(ScenarioProperties::showScenarioProperties(campaignState, scenario)) + changed(); + redraw(); + }); + contextMenu.exec(event->screenPos()); + }); + auto pos = campaignState->getRegions().getPosition(scenario); + pixmap->setPos(pos.x, pos.y); + pixmap->setToolTip(QString::fromStdString(campaignState->scenarios.at(scenario).mapName)); + campaignScene->addItem(pixmap); + } + + ui->campaignView->show(); +} + +bool CampaignEditor::getAnswerAboutUnsavedChanges() +{ + if(unsaved) + { + auto sure = QMessageBox::question(this, tr("Confirmation"), tr("Unsaved changes will be lost, are you sure?")); + if(sure == QMessageBox::No) + { + return false; + } + } + return true; +} + +void CampaignEditor::setTitle() +{ + QFileInfo fileInfo(filename); + QString title = QString("%1%2 - %3 (%4)").arg(fileInfo.fileName(), unsaved ? "*" : "", VCMI_CAMP_EDITOR_NAME, GameConstants::VCMI_VERSION.c_str()); + setWindowTitle(title); +} + +void CampaignEditor::changed() +{ + unsaved = true; + setTitle(); +} + +bool CampaignEditor::saveCampaign() +{ + if(campaignState->mapPieces.size() != campaignState->campaignRegions.regions.size()) + { + QMessageBox::critical(nullptr, tr("Maps missing"), tr("Not all Regions have a map. Please add them in Scenario Properties.")); + return false; + } + + Helper::saveCampaign(campaignState, filename); + return true; +} + +void CampaignEditor::showCampaignEditor() +{ + auto * dialog = new CampaignEditor(); + + dialog->setAttribute(Qt::WA_DeleteOnClose); +} + +void CampaignEditor::on_actionOpen_triggered() +{ + if(!getAnswerAboutUnsavedChanges()) + return; + + auto filenameSelect = QFileDialog::getOpenFileName(this, tr("Open map"), + QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()), + tr("All supported campaigns (*.vcmp *.h3c);;VCMI campaigns(*.vcmp);;HoMM3 campaigns(*.h3c)")); + if(filenameSelect.isEmpty()) + return; + + campaignState = Helper::openCampaignInternal(filenameSelect); + selectedScenario = *campaignState->allScenarios().begin(); + + redraw(); +} + +void CampaignEditor::on_actionSave_as_triggered() +{ + if(!campaignState) + return; + + auto filenameSelect = QFileDialog::getSaveFileName(this, tr("Save campaign"), "", tr("VCMI campaigns (*.vcmp)")); + + if(filenameSelect.isNull()) + return; + + QFileInfo fileInfo(filenameSelect); + + if(fileInfo.suffix().toLower() != "vcmp") + filenameSelect += ".vcmp"; + + filename = filenameSelect; + if(saveCampaign()) + unsaved = false; + setTitle(); +} + +void CampaignEditor::on_actionNew_triggered() +{ + if(!getAnswerAboutUnsavedChanges()) + return; + + campaignState = std::make_unique(); + campaignState->campaignRegions = CampaignRegions::getLegacy(0); + for (int i = 0; i < campaignState->campaignRegions.regions.size(); i++) + { + CampaignScenario s; + s.travelOptions.startOptions = CampaignStartOptions::START_BONUS; + campaignState->scenarios.emplace(CampaignScenarioID(i), s); + } + campaignState->modName = "mapEditor"; + + changed(); + redraw(); +} + +void CampaignEditor::on_actionSave_triggered() +{ + if(!campaignState) + return; + + if(filename.isNull()) + on_actionSave_as_triggered(); + else if(saveCampaign()) + unsaved = false; + setTitle(); +} + +void CampaignEditor::on_actionCampaignProperties_triggered() +{ + if(!campaignState) + return; + + if(CampaignProperties::showCampaignProperties(campaignState)) + changed(); + redraw(); +} + +void CampaignEditor::on_actionScenarioProperties_triggered() +{ + if(!campaignState || selectedScenario == CampaignScenarioID::NONE) + return; + + if(ScenarioProperties::showScenarioProperties(campaignState, selectedScenario)) + changed(); + redraw(); +} + +void CampaignEditor::closeEvent(QCloseEvent *event) +{ + if(getAnswerAboutUnsavedChanges()) + QDialog::closeEvent(event); + else + event->ignore(); +} diff --git a/mapeditor/campaigneditor/campaigneditor.h b/mapeditor/campaigneditor/campaigneditor.h new file mode 100644 index 000000000..cf28ae41c --- /dev/null +++ b/mapeditor/campaigneditor/campaigneditor.h @@ -0,0 +1,60 @@ +/* + * campaigneditor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include + +#include "campaignview.h" + +#include "../StdInc.h" +#include "../../lib/constants/EntityIdentifiers.h" + +class CampaignState; + +namespace Ui { +class CampaignEditor; +} + +class CampaignEditor : public QDialog +{ + Q_OBJECT + +public: + explicit CampaignEditor(); + ~CampaignEditor(); + + void redraw(); + + static void showCampaignEditor(); + +private slots: + void on_actionOpen_triggered(); + void on_actionSave_as_triggered(); + void on_actionNew_triggered(); + void on_actionSave_triggered(); + void on_actionCampaignProperties_triggered(); + void on_actionScenarioProperties_triggered(); + +private: + bool getAnswerAboutUnsavedChanges(); + void setTitle(); + void changed(); + bool saveCampaign(); + + void closeEvent(QCloseEvent *event) override; + + Ui::CampaignEditor *ui; + + std::unique_ptr campaignScene; + + QString filename; + bool unsaved = false; + CampaignScenarioID selectedScenario; + std::shared_ptr campaignState; +}; diff --git a/mapeditor/campaigneditor/campaigneditor.ui b/mapeditor/campaigneditor/campaigneditor.ui new file mode 100644 index 000000000..e23e36e0d --- /dev/null +++ b/mapeditor/campaigneditor/campaigneditor.ui @@ -0,0 +1,157 @@ + + + CampaignEditor + + + + 0 + 0 + 820 + 720 + + + + VCMI Campaign Editor + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + File + + + + + + + + + Edit + + + + + + + + + + + + Toolbar + + + 0 + + + 0 + + + + + + + + + + + + + + 0 + 0 + + + + true + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + Open + + + Ctrl+O + + + + + Save + + + Ctrl+S + + + + + New + + + Ctrl+N + + + + + Save as... + + + Ctrl+Shift+S + + + + + Campaign Properties + + + Campaign Properties + + + Ctrl+Enter + + + + + Scenario Properties + + + Scenario Properties + + + Enter + + + + + + CampaignView + QGraphicsView +
campaigneditor/campaignview.h
+
+
+ + +
diff --git a/mapeditor/campaigneditor/campaignproperties.cpp b/mapeditor/campaigneditor/campaignproperties.cpp new file mode 100644 index 000000000..a03e101dd --- /dev/null +++ b/mapeditor/campaigneditor/campaignproperties.cpp @@ -0,0 +1,193 @@ +/* + * campaignproperties.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "campaignproperties.h" +#include "ui_campaignproperties.h" + +#include "../../lib/GameLibrary.h" +#include "../../lib/texts/CGeneralTextHandler.h" +#include "../../lib/campaign/CampaignState.h" +#include "../../lib/constants/StringConstants.h" + +CampaignProperties::CampaignProperties(std::shared_ptr campaignState): + ui(new Ui::CampaignProperties), + campaignState(campaignState), + regions(campaignState->campaignRegions) +{ + ui->setupUi(this); + + setWindowTitle(tr("Campaign Properties")); + + setWindowModality(Qt::ApplicationModal); + + ui->lineEditName->setText(QString::fromStdString(campaignState->name.toString())); + ui->textEditDescription->setText(QString::fromStdString(campaignState->description.toString())); + ui->lineEditAuthor->setText(QString::fromStdString(campaignState->author.toString())); + ui->lineEditAuthorContact->setText(QString::fromStdString(campaignState->authorContact.toString())); + ui->dateTimeEditCreationDateTime->setDateTime(QDateTime::fromSecsSinceEpoch(campaignState->creationDateTime)); + ui->lineEditCampaignVersion->setText(QString::fromStdString(campaignState->campaignVersion.toString())); + ui->lineEditMusic->setText(QString::fromStdString(campaignState->music.getName())); + ui->checkBoxScenarioDifficulty->setChecked(campaignState->difficultyChosenByPlayer); + + for (int i = 0; i < 20; i++) + ui->comboBoxRegionPreset->insertItem(i, QString::fromStdString(LIBRARY->generaltexth->translate("core.camptext.names", i))); + ui->comboBoxRegionPreset->insertItem(20, tr("Custom")); + ui->comboBoxRegionPreset->setCurrentIndex(20); + + loadRegion(); + + show(); +} + +CampaignProperties::~CampaignProperties() +{ + delete ui; +} + +bool CampaignProperties::showCampaignProperties(std::shared_ptr campaignState) +{ + if(!campaignState) + return false; + + auto * dialog = new CampaignProperties(campaignState); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + + return dialog->exec() == QDialog::Accepted; +} + +void CampaignProperties::on_buttonBox_clicked(QAbstractButton * button) +{ + if(button == ui->buttonBox->button(QDialogButtonBox::Ok)) + { + if(!saveRegion()) + return; + campaignState->name = MetaString::createFromRawString(ui->lineEditName->text().toStdString()); + campaignState->description = MetaString::createFromRawString(ui->textEditDescription->toPlainText().toStdString()); + campaignState->author = MetaString::createFromRawString(ui->lineEditAuthor->text().toStdString()); + campaignState->authorContact = MetaString::createFromRawString(ui->lineEditAuthorContact->text().toStdString()); + campaignState->creationDateTime = ui->dateTimeEditCreationDateTime->dateTime().toSecsSinceEpoch(); + campaignState->campaignVersion = MetaString::createFromRawString(ui->lineEditCampaignVersion->text().toStdString()); + campaignState->music = AudioPath::builtin(ui->lineEditMusic->text().toStdString()); + campaignState->difficultyChosenByPlayer = ui->checkBoxScenarioDifficulty->isChecked(); + accept(); + } + close(); +} + +void CampaignProperties::on_comboBoxRegionPreset_currentIndexChanged(int index) +{ + if(ui->comboBoxRegionPreset->count() == 21 && ui->comboBoxRegionPreset->currentIndex() != 20) + regions = CampaignRegions::getLegacy(ui->comboBoxRegionPreset->currentIndex()); + + loadRegion(); +} + +void CampaignProperties::on_pushButtonRegionAdd_clicked() +{ + int row = ui->tableWidgetRegions->rowCount(); + ui->tableWidgetRegions->insertRow(row); + ui->tableWidgetRegions->setItem(row, 0, new QTableWidgetItem("INFIX")); + ui->tableWidgetRegions->setItem(row, 1, new QTableWidgetItem(QString::number(0))); + ui->tableWidgetRegions->setItem(row, 2, new QTableWidgetItem(QString::number(0))); + ui->tableWidgetRegions->setItem(row, 3, new QTableWidgetItem(QString::number(-1))); + ui->tableWidgetRegions->setItem(row, 4, new QTableWidgetItem(QString::number(-1))); +} + +void CampaignProperties::on_pushButtonRegionRemove_clicked() +{ + int rows = ui->tableWidgetRegions->rowCount() - 1; + ui->tableWidgetRegions->removeRow(rows); + ui->tableWidgetRegions->setRowCount(rows); +} + +void CampaignProperties::loadRegion() +{ + ui->lineEditBackground->setText(QString::fromStdString(regions.campBackground.empty() ? regions.campPrefix + "_BG" : regions.campBackground)); + ui->lineEditSuffix1->setText(QString::fromStdString(regions.campSuffix.size() ? regions.campSuffix[0] : "En")); + ui->lineEditSuffix2->setText(QString::fromStdString(regions.campSuffix.size() ? regions.campSuffix[1] : "Se")); + ui->lineEditSuffix3->setText(QString::fromStdString(regions.campSuffix.size() ? regions.campSuffix[2] : "Co")); + ui->lineEditPrefix->setText(QString::fromStdString(regions.campPrefix)); + ui->spinBoxColorSuffixLength->setValue(regions.colorSuffixLength); + + ui->tableWidgetRegions->clearContents(); + ui->tableWidgetRegions->setRowCount(0); + ui->tableWidgetRegions->setColumnCount(5); + ui->tableWidgetRegions->setHorizontalHeaderLabels({tr("Infix"), tr("X"), tr("Y"), tr("Label Pos X"), tr("Label Pos Y")}); + for (int i = 0; i < regions.regions.size(); ++i) + { + ui->tableWidgetRegions->insertRow(ui->tableWidgetRegions->rowCount()); + ui->tableWidgetRegions->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(regions.regions[i].infix))); + ui->tableWidgetRegions->setItem(i, 1, new QTableWidgetItem(QString::number(regions.regions[i].pos.x))); + ui->tableWidgetRegions->setItem(i, 2, new QTableWidgetItem(QString::number(regions.regions[i].pos.y))); + ui->tableWidgetRegions->setItem(i, 3, new QTableWidgetItem(QString::number(regions.regions[i].labelPos.has_value() ? (*regions.regions[i].labelPos).x : -1))); + ui->tableWidgetRegions->setItem(i, 4, new QTableWidgetItem(QString::number(regions.regions[i].labelPos.has_value() ? (*regions.regions[i].labelPos).y : -1))); + } + ui->tableWidgetRegions->resizeColumnsToContents(); +} + +bool CampaignProperties::saveRegion() +{ + regions.campBackground = ui->lineEditBackground->text().toStdString(); + if(regions.campSuffix.size() == 3) + { + regions.campSuffix[0] = ui->lineEditSuffix1->text().toStdString(); + regions.campSuffix[1] = ui->lineEditSuffix2->text().toStdString(); + regions.campSuffix[2] = ui->lineEditSuffix3->text().toStdString(); + } + else + { + regions.campSuffix.push_back(ui->lineEditSuffix1->text().toStdString()); + regions.campSuffix.push_back(ui->lineEditSuffix2->text().toStdString()); + regions.campSuffix.push_back(ui->lineEditSuffix3->text().toStdString()); + } + regions.campPrefix = ui->lineEditPrefix->text().toStdString(); + regions.colorSuffixLength = ui->spinBoxColorSuffixLength->value(); + + regions.regions.clear(); + for (int i = 0; i < ui->tableWidgetRegions->rowCount(); ++i) + { + CampaignRegions::RegionDescription rd; + + rd.infix = ui->tableWidgetRegions->item(i, 0)->text().toStdString(); + rd.pos.x = ui->tableWidgetRegions->item(i, 1)->text().toInt(); + rd.pos.y = ui->tableWidgetRegions->item(i, 2)->text().toInt(); + auto labelX = ui->tableWidgetRegions->item(i, 3)->text().toInt(); + auto labelY = ui->tableWidgetRegions->item(i, 4)->text().toInt(); + if(labelX == -1 || labelY == -1) + rd.labelPos = std::nullopt; + else + Point(labelX, labelY); + + regions.regions.push_back(rd); + } + + if(campaignState->campaignRegions.regions.size() > regions.regions.size()) + { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, tr("Fewer Scenarios"), tr("New Region setup supports fewer scenarios than before. Some will removed. Continue?"), QMessageBox::Yes|QMessageBox::No); + if (reply != QMessageBox::Yes) + return false; + } + + campaignState->campaignRegions = regions; + + while(campaignState->scenarios.size() < campaignState->campaignRegions.regions.size()) + campaignState->scenarios.emplace(CampaignScenarioID(std::prev(campaignState->scenarios.end())->first + 1), CampaignScenario()); + while(campaignState->scenarios.size() > campaignState->campaignRegions.regions.size()) + { + auto elem = std::prev(campaignState->scenarios.end()); + campaignState->mapPieces.erase(elem->first); + campaignState->scenarios.erase(elem); + } + + + return true; +} diff --git a/mapeditor/campaigneditor/campaignproperties.h b/mapeditor/campaigneditor/campaignproperties.h new file mode 100644 index 000000000..f749ca3ec --- /dev/null +++ b/mapeditor/campaigneditor/campaignproperties.h @@ -0,0 +1,46 @@ +/* + * campaignproperties.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include + +#include "../../lib/campaign/CampaignState.h" + +class CampaignState; +class QAbstractButton; + +namespace Ui { +class CampaignProperties; +} + +class CampaignProperties : public QDialog +{ + Q_OBJECT + +public: + explicit CampaignProperties(std::shared_ptr campaignState); + ~CampaignProperties(); + + static bool showCampaignProperties(std::shared_ptr campaignState); + +private slots: + void on_buttonBox_clicked(QAbstractButton * button); + void on_comboBoxRegionPreset_currentIndexChanged(int index); + void on_pushButtonRegionAdd_clicked(); + void on_pushButtonRegionRemove_clicked(); + +private: + Ui::CampaignProperties *ui; + + std::shared_ptr campaignState; + CampaignRegions regions; + + void loadRegion(); + bool saveRegion(); +}; diff --git a/mapeditor/campaigneditor/campaignproperties.ui b/mapeditor/campaigneditor/campaignproperties.ui new file mode 100644 index 000000000..4241236f4 --- /dev/null +++ b/mapeditor/campaigneditor/campaignproperties.ui @@ -0,0 +1,332 @@ + + + CampaignProperties + + + + 0 + 0 + 499 + 580 + + + + Campaign Properties + + + + + + 0 + + + + + 0 + 0 + + + + General + + + + + + Campaign name + + + + + + + + + + Campaign description + + + + + + + + + + Author + + + + + + + + + + Author contact (e.g. e-mail) + + + + + + + + + + Campaign creation date + + + + + + + + + + Campaign version + + + + + + + + + + Music + + + + + + + + + + Scenario difficulty is user selectable + + + + + + + + Regions + + + + + + Regions Preset + + + + + + + + + + Qt::Horizontal + + + + + + + Background + + + + + + + + + + Suffix + + + + + + + 0 + + + 0 + + + + + + + + + + + + + + + + Prefix + + + + + + + + + + Color suffix length + + + + + + + 2 + + + + + + + Regions + + + + + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + + + 0 + + + + + Add + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Misc + + + + + + Loading background image + + + + + + + + + + Video rim image + + + + + + + + + + Intro video + + + + + + + + + + Outro video + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/mapeditor/campaigneditor/campaignview.cpp b/mapeditor/campaigneditor/campaignview.cpp new file mode 100644 index 000000000..947a891b5 --- /dev/null +++ b/mapeditor/campaigneditor/campaignview.cpp @@ -0,0 +1,48 @@ +/* + * campaignview.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "campaignview.h" + +CampaignScene::CampaignScene(): + QGraphicsScene(nullptr) +{ +} + +CampaignView::CampaignView(QWidget * parent): + QGraphicsView(parent) +{ +} + +ClickablePixmapItem::ClickablePixmapItem(const QPixmap &pixmap, std::function clickedCallback, std::function doubleClickedCallback, std::function contextMenuCallback): + QGraphicsPixmapItem(pixmap), + clickedCallback(clickedCallback), + doubleClickedCallback(doubleClickedCallback), + contextMenuCallback(contextMenuCallback) +{ +} + +void ClickablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if(clickedCallback) + clickedCallback(); +} + +void ClickablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if(doubleClickedCallback) + doubleClickedCallback(); +} + +void ClickablePixmapItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + if(contextMenuCallback) + contextMenuCallback(event); +} diff --git a/mapeditor/campaigneditor/campaignview.h b/mapeditor/campaigneditor/campaignview.h new file mode 100644 index 000000000..30016957d --- /dev/null +++ b/mapeditor/campaigneditor/campaignview.h @@ -0,0 +1,44 @@ +/* + * campaignview.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include + +class CampaignScene : public QGraphicsScene +{ + Q_OBJECT; +public: + CampaignScene(); +}; + +class CampaignView : public QGraphicsView +{ + Q_OBJECT + +public: + CampaignView(QWidget * parent); +}; + +class ClickablePixmapItem : public QGraphicsPixmapItem { +public: + ClickablePixmapItem(const QPixmap &pixmap, std::function clickedCallback, std::function doubleClickedCallback, std::function contextMenuCallback); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + std::function clickedCallback; + std::function doubleClickedCallback; + std::function contextMenuCallback; +}; \ No newline at end of file diff --git a/mapeditor/campaigneditor/scenarioproperties.cpp b/mapeditor/campaigneditor/scenarioproperties.cpp new file mode 100644 index 000000000..e61863517 --- /dev/null +++ b/mapeditor/campaigneditor/scenarioproperties.cpp @@ -0,0 +1,529 @@ +/* + * scenarioproperties.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "scenarioproperties.h" +#include "ui_scenarioproperties.h" +#include "startingbonus.h" + +#include "../../lib/GameLibrary.h" +#include "../../lib/CArtHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/texts/CGeneralTextHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" +#include "../../lib/campaign/CampaignState.h" +#include "../../lib/mapping/CMap.h" +#include "../../lib/constants/StringConstants.h" + +ScenarioProperties::ScenarioProperties(std::shared_ptr campaignState, CampaignScenarioID scenario): + ui(new Ui::ScenarioProperties), + campaignState(campaignState), + map(campaignState->getMap(scenario, nullptr)), + scenario(scenario) +{ + ui->setupUi(this); + + setWindowTitle(tr("Scenario Properties")); + + setWindowModality(Qt::ApplicationModal); + + ui->lineEditRegionName->setText(getRegionChar(scenario.getNum())); + ui->plainTextEditRightClickText->setPlainText(QString::fromStdString(campaignState->scenarios.at(scenario).regionText.toString())); + + for(int i = 0, index = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) + { + MetaString str; + str.appendName(PlayerColor(i)); + ui->comboBoxRegionColor->addItem(QString::fromStdString(str.toString()), QVariant(i)); + if(i == campaignState->scenarios.at(scenario).regionColor) + ui->comboBoxRegionColor->setCurrentIndex(index); + ++index; + } + + for(int i = 0, index = 0; i < 5; ++i) + { + ui->comboBoxDefaultDifficulty->addItem(QString::fromStdString(LIBRARY->generaltexth->arraytxt[142 + i]), QVariant(i)); + if(i == campaignState->scenarios.at(scenario).difficulty) + ui->comboBoxDefaultDifficulty->setCurrentIndex(index); + ++index; + } + + for(int i = 0; i < scenario.getNum(); ++i) + { + auto tmpScenario = CampaignScenarioID(i); + auto * item = new QListWidgetItem(getRegionChar(tmpScenario.getNum()) + " - " + QString::fromStdString(campaignState->scenarios.at(tmpScenario).mapName)); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(campaignState->scenarios.at(scenario).preconditionRegions.count(tmpScenario) ? Qt::Checked : Qt::Unchecked); + ui->listWidgetPrerequisites->addItem(item); + } + + ui->checkBoxPrologueEnabled->setChecked(campaignState->scenarios.at(scenario).prolog.hasPrologEpilog); + ui->lineEditPrologueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVideo.getName())); + ui->lineEditPrologueMusic->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologMusic.getName())); + ui->lineEditPrologueVoice->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVoice.getName())); + ui->plainTextEditPrologueText->setPlainText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologText.toString())); + ui->checkBoxEpilogueEnabled->setChecked(campaignState->scenarios.at(scenario).epilog.hasPrologEpilog); + ui->lineEditEpilogueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVideo.getName())); + ui->lineEditEpilogueMusic->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologMusic.getName())); + ui->lineEditEpilogueVoice->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVoice.getName())); + ui->plainTextEditEpilogueText->setPlainText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologText.toString())); + + ui->checkBoxCrossoverExperience->setChecked(campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.experience); + ui->checkBoxCrossoverPrimarySkills->setChecked(campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.primarySkills); + ui->checkBoxCrossoverSecondarySkills->setChecked(campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.secondarySkills); + ui->checkBoxCrossoverSpells->setChecked(campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.spells); + ui->checkBoxCrossoverArtifacts->setChecked(campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.artifacts); + + ui->radioButtonStartingOptionBonus->setChecked(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::START_BONUS); + ui->radioButtonStartingOptionHeroCrossover->setChecked(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_CROSSOVER); + ui->radioButtonStartingOptionStartingHero->setChecked(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_OPTIONS); + + for(auto const & objectPtr : LIBRARY->arth->objects) + { + auto * item = new QListWidgetItem(QString::fromStdString(objectPtr->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(objectPtr->getIndex())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(campaignState->scenarios.at(scenario).travelOptions.artifactsKeptByHero.count(objectPtr->getId()) ? Qt::Checked : Qt::Unchecked); + ui->listWidgetCrossoverArtifacts->addItem(item); + } + + for(auto const & objectPtr : LIBRARY->creh->objects) + { + auto * item = new QListWidgetItem(QString::fromStdString(objectPtr->getNameSingularTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(objectPtr->getIndex())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(campaignState->scenarios.at(scenario).travelOptions.monstersKeptByHero.count(objectPtr->getId()) ? Qt::Checked : Qt::Unchecked); + ui->listWidgetCrossoverCreatures->addItem(item); + } + + ui->tabWidgetStartOptions->tabBar()->hide(); + ui->tabWidgetStartOptions->setStyleSheet("QTabWidget::pane { border: 0; }"); + + ui->tableWidgetStartingCrossover->setColumnCount(2); + + LIBRARY->heroTypes()->forEach([this](const HeroType * hero, bool &) + { + heroSelection.emplace(hero->getId().getNum(), hero->getNameTranslated()); + }); + heroSelection.emplace(0xFFFD, tr("Strongest").toStdString()); + heroSelection.emplace(0xFFFE, tr("Generated").toStdString()); + heroSelection.emplace(0xFFFF, tr("Random").toStdString()); + + reloadMapRelatedUi(); + + show(); +} + +ScenarioProperties::~ScenarioProperties() +{ + delete ui; +} + +void ScenarioProperties::reloadMapRelatedUi() +{ + map = campaignState->getMap(scenario, nullptr); + + ui->lineEditMapFile->setText(QString::fromStdString(campaignState->scenarios.at(scenario).mapName)); + ui->lineEditScenarioName->setText(map ? QString::fromStdString(map->name.toString()) : tr("No map")); + if(!map) + ui->lineEditScenarioName->setStyleSheet("background-color: red;"); + else + ui->lineEditScenarioName->setStyleSheet(""); + + ui->pushButtonExport->setEnabled(map != nullptr); + ui->pushButtonRemove->setEnabled(map != nullptr); + + ui->radioButtonStartingOptionHeroCrossover->setEnabled(scenario.getNum() > 0); + bool allowSelectingStartHero = false; + if(map != nullptr) + for(auto & player : map->players) + if(player.generateHeroAtMainTown) + allowSelectingStartHero = true; + ui->radioButtonStartingOptionStartingHero->setEnabled(allowSelectingStartHero); + + ui->tabPrologueEpilogue->setEnabled(map != nullptr); + ui->tabCrossover->setEnabled(map != nullptr); + ui->tabStarting->setEnabled(map != nullptr); + + ui->comboBoxDefaultDifficulty->setEnabled(map != nullptr); + ui->listWidgetPrerequisites->setEnabled(map != nullptr); + ui->plainTextEditRightClickText->setEnabled(map != nullptr); + + ui->tableWidgetStartingCrossover->clearContents(); + ui->tableWidgetStartingCrossover->setRowCount(0); + ui->listWidgetStartingBonusOption->clear(); + + if(map != nullptr) + { + std::vector selectableColors; + for(int i = 0; i < map->players.size(); i++) + if(map->players[i].canHumanPlay) + selectableColors.push_back(PlayerColor(i)); + + ui->comboBoxStartingBonusPlayerPosition->clear(); + for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) + { + if(!vstd::contains(selectableColors, PlayerColor(i))) + continue; + + MetaString str; + str.appendRawString((tr("Player") + " ").toStdString()); + str.appendNumber(i + 1); + str.appendRawString(" ("); + str.appendName(PlayerColor(i)); + str.appendRawString(")"); + ui->comboBoxStartingBonusPlayerPosition->addItem(QString::fromStdString(str.toString()), QVariant(i)); + } + int index = ui->comboBoxStartingBonusPlayerPosition->findData(campaignState->scenarios.at(scenario).travelOptions.playerColor.getNum()); + if(index != -1) + ui->comboBoxStartingBonusPlayerPosition->setCurrentIndex(index); + } + + if(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_CROSSOVER || campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_OPTIONS) + { + if(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_OPTIONS) + on_radioButtonStartingOptionStartingHero_toggled(); + else + on_radioButtonStartingOptionHeroCrossover_toggled(); + + for(int i = 0; i < campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.size(); i++) + { + auto bonus = campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose[i]; + + ui->tableWidgetStartingCrossover->insertRow(i); + QComboBox* comboBoxOption = new QComboBox(); + if(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::HERO_OPTIONS) + { + for(auto & hero : heroSelection) + comboBoxOption->addItem(QString::fromStdString(hero.second), hero.first); + } + else + { + for(int i = 0; i < scenario.getNum(); ++i) + { + auto tmpScenario = CampaignScenarioID(i); + auto text = getRegionChar(tmpScenario.getNum()) + " - " + QString::fromStdString(campaignState->scenarios.at(tmpScenario).mapName); + comboBoxOption->addItem(text, i); + } + } + + QComboBox* comboBoxPlayer = new QComboBox(); + for(int i = 0; i < ui->comboBoxStartingBonusPlayerPosition->count(); ++i) // copy from player dropdown + comboBoxPlayer->addItem(ui->comboBoxStartingBonusPlayerPosition->itemText(i), ui->comboBoxStartingBonusPlayerPosition->itemData(i)); + + // set selected + int index = comboBoxPlayer->findData(bonus.info1); + if(index != -1) + comboBoxPlayer->setCurrentIndex(index); + index = comboBoxOption->findData(bonus.info2); + if(index != -1) + comboBoxOption->setCurrentIndex(index); + + ui->tableWidgetStartingCrossover->setCellWidget(i, 0, comboBoxOption); + ui->tableWidgetStartingCrossover->setCellWidget(i, 1, comboBoxPlayer); + } + + ui->tableWidgetStartingCrossover->resizeColumnsToContents(); + } + else if(campaignState->scenarios.at(scenario).travelOptions.startOptions == CampaignStartOptions::START_BONUS) + { + ui->listWidgetStartingBonusOption->clear(); + for(auto & bonus : campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose) + { + QListWidgetItem * item = new QListWidgetItem(StartingBonus::getBonusListTitle(bonus, map)); + item->setData(Qt::UserRole, QVariant::fromValue(bonus)); + ui->listWidgetStartingBonusOption->addItem(item); + } + } + reloadEnableState(); +} + +void ScenarioProperties::reloadEnableState() +{ + ui->pushButtonStartingAdd->setEnabled(ui->tableWidgetStartingCrossover->rowCount() < 3 && ui->listWidgetStartingBonusOption->count() < 3); + ui->pushButtonStartingRemove->setEnabled(ui->tableWidgetStartingCrossover->rowCount() > 0 || ui->listWidgetStartingBonusOption->count() > 0); +} + +bool ScenarioProperties::showScenarioProperties(std::shared_ptr campaignState, CampaignScenarioID scenario) +{ + if(!campaignState || scenario == CampaignScenarioID::NONE) + return false; + + auto * dialog = new ScenarioProperties(campaignState, scenario); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + + return dialog->exec() == QDialog::Accepted; +} + + +void ScenarioProperties::on_buttonBox_clicked(QAbstractButton * button) +{ + if(button == ui->buttonBox->button(QDialogButtonBox::Ok)) + { + campaignState->scenarios.at(scenario).regionText = MetaString::createFromRawString(ui->plainTextEditRightClickText->toPlainText().toStdString()); + + campaignState->scenarios.at(scenario).regionColor = ui->comboBoxRegionColor->currentData().toInt(); + campaignState->scenarios.at(scenario).difficulty = ui->comboBoxDefaultDifficulty->currentData().toInt(); + + campaignState->scenarios.at(scenario).prolog.hasPrologEpilog = ui->checkBoxPrologueEnabled->isChecked(); + campaignState->scenarios.at(scenario).prolog.prologVideo = VideoPath::builtin(ui->lineEditPrologueVideo->text().toStdString()); + campaignState->scenarios.at(scenario).prolog.prologMusic = AudioPath::builtin(ui->lineEditPrologueMusic->text().toStdString()); + campaignState->scenarios.at(scenario).prolog.prologVoice = AudioPath::builtin(ui->lineEditPrologueVoice->text().toStdString()); + campaignState->scenarios.at(scenario).prolog.prologText = MetaString::createFromRawString(ui->plainTextEditPrologueText->toPlainText().toStdString()); + campaignState->scenarios.at(scenario).epilog.hasPrologEpilog = ui->checkBoxEpilogueEnabled->isChecked(); + campaignState->scenarios.at(scenario).epilog.prologVideo = VideoPath::builtin(ui->lineEditEpilogueVideo->text().toStdString()); + campaignState->scenarios.at(scenario).epilog.prologMusic = AudioPath::builtin(ui->lineEditEpilogueMusic->text().toStdString()); + campaignState->scenarios.at(scenario).epilog.prologVoice = AudioPath::builtin(ui->lineEditEpilogueVoice->text().toStdString()); + campaignState->scenarios.at(scenario).epilog.prologText = MetaString::createFromRawString(ui->plainTextEditEpilogueText->toPlainText().toStdString()); + + campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.experience = ui->checkBoxCrossoverExperience->isChecked(); + campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.primarySkills = ui->checkBoxCrossoverPrimarySkills->isChecked(); + campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.secondarySkills = ui->checkBoxCrossoverSecondarySkills->isChecked(); + campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.spells = ui->checkBoxCrossoverSpells->isChecked(); + campaignState->scenarios.at(scenario).travelOptions.whatHeroKeeps.artifacts = ui->checkBoxCrossoverArtifacts->isChecked(); + + CampaignStartOptions startOption = CampaignStartOptions::NONE; + if(ui->radioButtonStartingOptionBonus->isChecked()) + startOption = CampaignStartOptions::START_BONUS; + else if(ui->radioButtonStartingOptionHeroCrossover->isChecked()) + startOption = CampaignStartOptions::HERO_CROSSOVER; + else if(ui->radioButtonStartingOptionStartingHero->isChecked()) + startOption = CampaignStartOptions::HERO_OPTIONS; + campaignState->scenarios.at(scenario).travelOptions.startOptions = startOption; + + campaignState->scenarios.at(scenario).travelOptions.artifactsKeptByHero.clear(); + for(int i = 0; i < ui->listWidgetCrossoverArtifacts->count(); ++i) + { + auto * item = ui->listWidgetCrossoverArtifacts->item(i); + if(item->checkState() == Qt::Checked) + campaignState->scenarios.at(scenario).travelOptions.artifactsKeptByHero.emplace(i); + } + + campaignState->scenarios.at(scenario).travelOptions.monstersKeptByHero.clear(); + for(int i = 0; i < ui->listWidgetCrossoverCreatures->count(); ++i) + { + auto * item = ui->listWidgetCrossoverCreatures->item(i); + if(item->checkState() == Qt::Checked) + campaignState->scenarios.at(scenario).travelOptions.monstersKeptByHero.emplace(i); + } + + campaignState->scenarios.at(scenario).preconditionRegions.clear(); + for(int i = 0; i < ui->listWidgetPrerequisites->count(); ++i) + { + auto * item = ui->listWidgetPrerequisites->item(i); + if(item->checkState() == Qt::Checked) + campaignState->scenarios.at(scenario).preconditionRegions.emplace(i); + } + + campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.clear(); + if(ui->radioButtonStartingOptionBonus->isChecked()) + { + campaignState->scenarios.at(scenario).travelOptions.playerColor = PlayerColor(ui->comboBoxStartingBonusPlayerPosition->currentData().toInt()); + campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.clear(); + for (int i = 0; i < ui->listWidgetStartingBonusOption->count(); ++i) { + QListWidgetItem *item = ui->listWidgetStartingBonusOption->item(i); + campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.push_back(item->data(Qt::UserRole).value()); + } + } + else if(ui->radioButtonStartingOptionHeroCrossover->isChecked() || ui->radioButtonStartingOptionStartingHero->isChecked()) + { + for (int i = 0; i < ui->tableWidgetStartingCrossover->rowCount(); ++i) + { + CampaignBonus bonus; + bonus.type = ui->radioButtonStartingOptionHeroCrossover->isChecked() ? CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO : CampaignBonusType::HERO; + QComboBox* comboBoxOption = qobject_cast(ui->tableWidgetStartingCrossover->cellWidget(i, 0)); + QComboBox* comboBoxPlayer = qobject_cast(ui->tableWidgetStartingCrossover->cellWidget(i, 1)); + bonus.info1 = comboBoxPlayer->currentData().toInt(); + bonus.info2 = comboBoxOption->currentData().toInt(); + campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.push_back(bonus); + } + } + + accept(); + } + close(); +} + +void ScenarioProperties::on_pushButtonCreatureTypeAll_clicked() +{ + for(int i = 0; i < ui->listWidgetCrossoverCreatures->count(); ++i) + ui->listWidgetCrossoverCreatures->item(i)->setCheckState(Qt::CheckState::Checked); +} + +void ScenarioProperties::on_pushButtonCreatureTypeNone_clicked() +{ + for(int i = 0; i < ui->listWidgetCrossoverCreatures->count(); ++i) + ui->listWidgetCrossoverCreatures->item(i)->setCheckState(Qt::CheckState::Unchecked); +} + +void ScenarioProperties::on_pushButtonImport_clicked() +{ + auto filename = QFileDialog::getOpenFileName(this, tr("Open map"), "", tr("All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m)")); + if(filename.isEmpty()) + return; + + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Could not open the file.")); + return; + } + + QByteArray fileData = file.readAll(); + std::vector byteArray(fileData.begin(), fileData.end()); + campaignState->mapPieces[scenario] = byteArray; + + QFileInfo fileInfo(filename); + QString baseName = fileInfo.fileName(); + campaignState->scenarios.at(scenario).mapName = baseName.toStdString(); + + reloadMapRelatedUi(); +} + +void ScenarioProperties::on_pushButtonExport_clicked() +{ + auto mapName = QString::fromStdString(campaignState->scenarios.at(scenario).mapName); + bool isVmap = mapName.toLower().endsWith(".vmap"); + QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Save map"), mapName, isVmap ? tr("VCMI maps (*.vmap);") : tr("HoMM3 maps (*.h3m);")); + if (fileName.isEmpty()) + return; + + QFile file(fileName); + + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Could not open the file.")); + return; + } + + auto byteArray = campaignState->mapPieces[scenario]; + QByteArray fileData(reinterpret_cast(byteArray.data()), byteArray.size()); + + file.write(fileData); +} + +void ScenarioProperties::on_pushButtonRemove_clicked() +{ + campaignState->mapPieces.erase(scenario); + campaignState->scenarios.at(scenario).mapName = ""; + reloadMapRelatedUi(); +} + +void ScenarioProperties::on_radioButtonStartingOptionBonus_toggled() +{ + ui->tabWidgetStartOptions->setCurrentIndex(0); + ui->listWidgetStartingBonusOption->clear(); + ui->pushButtonStartingEdit->setEnabled(true); +} + +void ScenarioProperties::on_radioButtonStartingOptionHeroCrossover_toggled() +{ + ui->tabWidgetStartOptions->setCurrentIndex(1); + ui->tableWidgetStartingCrossover->clearContents(); + ui->tableWidgetStartingCrossover->setRowCount(0); + ui->tableWidgetStartingCrossover->setHorizontalHeaderLabels({tr("Source scenario"), tr("Player position")}); + ui->tableWidgetStartingCrossover->resizeColumnsToContents(); + ui->pushButtonStartingEdit->setEnabled(false); +} + +void ScenarioProperties::on_radioButtonStartingOptionStartingHero_toggled() +{ + ui->tabWidgetStartOptions->setCurrentIndex(1); + ui->tableWidgetStartingCrossover->clearContents(); + ui->tableWidgetStartingCrossover->setRowCount(0); + ui->tableWidgetStartingCrossover->setHorizontalHeaderLabels({tr("Hero"), tr("Player position")}); + ui->tableWidgetStartingCrossover->resizeColumnsToContents(); + ui->pushButtonStartingEdit->setEnabled(false); +} + +void ScenarioProperties::on_pushButtonStartingAdd_clicked() +{ + if(ui->radioButtonStartingOptionHeroCrossover->isChecked() || ui->radioButtonStartingOptionStartingHero->isChecked()) + { + int row = ui->tableWidgetStartingCrossover->rowCount(); + ui->tableWidgetStartingCrossover->insertRow(row); + + QComboBox* comboBoxOption = new QComboBox(); + if(ui->radioButtonStartingOptionStartingHero->isChecked()) + { + for(auto & hero : heroSelection) + comboBoxOption->addItem(QString::fromStdString(hero.second), hero.first); + } + else + { + for(int i = 0; i < scenario.getNum(); ++i) + { + auto tmpScenario = CampaignScenarioID(i); + auto text = getRegionChar(tmpScenario.getNum()) + " - " + QString::fromStdString(campaignState->scenarios.at(tmpScenario).mapName); + comboBoxOption->addItem(text, i); + } + } + QComboBox* comboBoxPlayer = new QComboBox(); + for(int i = 0; i < ui->comboBoxStartingBonusPlayerPosition->count(); ++i) // copy from player dropdown + comboBoxPlayer->addItem(ui->comboBoxStartingBonusPlayerPosition->itemText(i), ui->comboBoxStartingBonusPlayerPosition->itemData(i)); + + ui->tableWidgetStartingCrossover->setCellWidget(row, 0, comboBoxOption); + ui->tableWidgetStartingCrossover->setCellWidget(row, 1, comboBoxPlayer); + + ui->tableWidgetStartingCrossover->resizeColumnsToContents(); + } + else + { + CampaignBonus bonus; + bonus.type = CampaignBonusType::SPELL; + if(StartingBonus::showStartingBonus(PlayerColor(ui->comboBoxStartingBonusPlayerPosition->currentData().toInt()), map, bonus)) + { + QListWidgetItem * item = new QListWidgetItem(StartingBonus::getBonusListTitle(bonus, map)); + item->setData(Qt::UserRole, QVariant::fromValue(bonus)); + ui->listWidgetStartingBonusOption->addItem(item); + } + } + reloadEnableState(); +} + +void ScenarioProperties::on_pushButtonStartingEdit_clicked() +{ + if(ui->radioButtonStartingOptionBonus->isChecked()) + { + QList selectedItems = ui->listWidgetStartingBonusOption->selectedItems(); + QListWidgetItem * item = selectedItems.takeFirst(); + + CampaignBonus bonus = item->data(Qt::UserRole).value(); + if(StartingBonus::showStartingBonus(PlayerColor(ui->comboBoxStartingBonusPlayerPosition->currentData().toInt()), map, bonus)) + { + item->setText(StartingBonus::getBonusListTitle(bonus, map)); + item->setData(Qt::UserRole, QVariant::fromValue(bonus)); + } + } +} + +void ScenarioProperties::on_pushButtonStartingRemove_clicked() +{ + if(ui->radioButtonStartingOptionHeroCrossover->isChecked() || ui->radioButtonStartingOptionStartingHero->isChecked()) + { + int rows = ui->tableWidgetStartingCrossover->rowCount() - 1; + ui->tableWidgetStartingCrossover->removeRow(rows); + ui->tableWidgetStartingCrossover->setRowCount(rows); + + ui->tableWidgetStartingCrossover->resizeColumnsToContents(); + } + else if(ui->radioButtonStartingOptionBonus->isChecked()) + { + QList selectedItems = ui->listWidgetStartingBonusOption->selectedItems(); + for (QListWidgetItem *item : selectedItems) + delete ui->listWidgetStartingBonusOption->takeItem(ui->listWidgetStartingBonusOption->row(item)); + } + reloadEnableState(); +} + +QString ScenarioProperties::getRegionChar(int no) +{ + return QString(static_cast('A' + no)); +} diff --git a/mapeditor/campaigneditor/scenarioproperties.h b/mapeditor/campaigneditor/scenarioproperties.h new file mode 100644 index 000000000..f7c399ff3 --- /dev/null +++ b/mapeditor/campaigneditor/scenarioproperties.h @@ -0,0 +1,65 @@ +/* + * scenarioproperties.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include + +#include "lib/constants/EntityIdentifiers.h" +#include "lib/campaign/CampaignState.h" + +class CMap; +class CampaignState; +class QAbstractButton; + +namespace Ui { +class ScenarioProperties; +} + +Q_DECLARE_METATYPE(CampaignBonus) + +class ScenarioProperties : public QDialog +{ + Q_OBJECT + +public: + explicit ScenarioProperties(std::shared_ptr campaignState, CampaignScenarioID scenario); + ~ScenarioProperties(); + + static bool showScenarioProperties(std::shared_ptr campaignState, CampaignScenarioID scenario); + +private slots: + void on_buttonBox_clicked(QAbstractButton * button); + void on_pushButtonCreatureTypeAll_clicked(); + void on_pushButtonCreatureTypeNone_clicked(); + + void on_pushButtonImport_clicked(); + void on_pushButtonExport_clicked(); + void on_pushButtonRemove_clicked(); + + void on_radioButtonStartingOptionBonus_toggled(); + void on_radioButtonStartingOptionHeroCrossover_toggled(); + void on_radioButtonStartingOptionStartingHero_toggled(); + + void on_pushButtonStartingAdd_clicked(); + void on_pushButtonStartingEdit_clicked(); + void on_pushButtonStartingRemove_clicked(); + +private: + Ui::ScenarioProperties *ui; + + std::shared_ptr map; + std::shared_ptr campaignState; + CampaignScenarioID scenario; + + std::map heroSelection; + + void reloadMapRelatedUi(); + void reloadEnableState(); + QString getRegionChar(int no); +}; diff --git a/mapeditor/campaigneditor/scenarioproperties.ui b/mapeditor/campaigneditor/scenarioproperties.ui new file mode 100644 index 000000000..a3702fcd6 --- /dev/null +++ b/mapeditor/campaigneditor/scenarioproperties.ui @@ -0,0 +1,569 @@ + + + ScenarioProperties + + + + 0 + 0 + 499 + 660 + + + + Scenario Properties + + + + + + 0 + + + + + 0 + 0 + + + + General + + + + + + Region name + + + + + + + true + + + + + + + Region color + + + + + + + + + + Scenario name + + + + + + + true + + + + + + + Map file + + + + + + + true + + + + + + + + + Import... + + + + + + + Export... + + + + + + + Remove + + + + + + + + + Default difficulty + + + + + + + + + + Prerequisites + + + + + + + + + + Region right-click text + + + + + + + + + + + Prologue/Epilogue + + + + + + Prologue + + + + + + Enabled + + + + + + + 0 + + + + + Video + + + + + + + + + + + + 0 + + + + + Music + + + + + + + + + + + + 0 + + + + + Voice + + + + + + + + + + + + Text + + + + + + + + + + + + + Epilogue + + + + + + Enabled + + + + + + + 0 + + + + + Video + + + + + + + + + + + + 0 + + + + + Music + + + + + + + + + + + + 0 + + + + + Voice + + + + + + + + + + + + Text + + + + + + + + + + + + + + Crossover + + + + + + Crossover heroes retain + + + + + + Experience + + + + + + + Primary skills + + + + + + + Secondary skills + + + + + + + Spells + + + + + + + Artifacts + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ScrollPerItem + + + true + + + QListView::Batched + + + 30 + + + + + + + 0 + + + + + All + + + + + + + None + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + Crossover artifacts + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ScrollPerItem + + + true + + + QListView::Batched + + + 30 + + + + + + + + + + + Starting + + + + + + Starting options are + + + + + + + Starting bonus options + + + + + + + Hero crossover options + + + + + + + Starting hero options + + + + + + + Qt::Horizontal + + + + + + + 0 + + + + Bonus + + + + + + Player position + + + + + + + + + + Starting bonus option + + + + + + + + + + + Crossover/ Starting hero + + + + + + + + + + + + + 0 + + + + + Add... + + + + + + + Edit... + + + + + + + Remove + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp new file mode 100644 index 000000000..ca6e2007c --- /dev/null +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -0,0 +1,370 @@ +/* + * startingbonus.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "startingbonus.h" +#include "ui_startingbonus.h" + +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/campaign/CampaignState.h" +#include "../../lib/entities/building/CBuilding.h" +#include "../../lib/entities/hero/CHero.h" +#include "../../lib/mapping/CMap.h" +#include "../../lib/CArtHandler.h" +#include "../../lib/CSkillHandler.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/spells/CSpellHandler.h" + +StartingBonus::StartingBonus(PlayerColor color, std::shared_ptr map, CampaignBonus bonus): + ui(new Ui::StartingBonus), + bonus(bonus), + map(map), + color(color) +{ + ui->setupUi(this); + + setWindowTitle(tr("Edit Starting Bonus")); + + setWindowModality(Qt::ApplicationModal); + + ui->tabWidget->tabBar()->hide(); + ui->tabWidget->setStyleSheet("QTabWidget::pane { border: 0; }"); + + initControls(); + + loadBonus(); + + show(); +} + +StartingBonus::~StartingBonus() +{ + delete ui; +} + +void StartingBonus::initControls() +{ + std::map heroSelection; + for(auto & hero : map->heroesOnMap) + if(hero->getOwner() == color || color == PlayerColor::CANNOT_DETERMINE) + heroSelection.emplace(hero->getHeroTypeID(), hero->getNameTranslated()); + heroSelection.emplace(0xFFFD, tr("Strongest").toStdString()); + heroSelection.emplace(0xFFFE, tr("Generated").toStdString()); + heroSelection.emplace(0xFFFF, tr("Random").toStdString()); + + for(auto & hero : heroSelection) + { + ui->comboBoxSpellRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + ui->comboBoxCreatureRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + ui->comboBoxArtifactRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + ui->comboBoxSpellScrollRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + ui->comboBoxPrimarySkillRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + ui->comboBoxSecondarySkillRecipient->addItem(QString::fromStdString(hero.second), QVariant(hero.first)); + } + + for(auto const & objectPtr : LIBRARY->spellh->objects) + { + ui->comboBoxSpellSpell->addItem(QString::fromStdString(objectPtr->getNameTranslated()), QVariant(objectPtr->getId())); + ui->comboBoxSpellScrollSpell->addItem(QString::fromStdString(objectPtr->getNameTranslated()), QVariant(objectPtr->getId())); + } + + for(auto const & objectPtr : LIBRARY->creh->objects) + ui->comboBoxCreatureCreatureType->addItem(QString::fromStdString(objectPtr->getNameSingularTranslated()), QVariant(objectPtr->getId())); + + if(map->players[color].hasMainTown) + for(auto & town : map->towns) + if(town->pos == map->players[color].posOfMainTown) + { + for(auto & building : town->getTown()->buildings) + ui->comboBoxBuildingBuilding->addItem(QString::fromStdString(building.second->getNameTranslated()), QVariant(building.first.getNum())); + break; + } + + for(auto const & objectPtr : LIBRARY->arth->objects) + if(!vstd::contains(std::vector({ + ArtifactID::SPELL_SCROLL, + ArtifactID::SPELLBOOK, + ArtifactID::GRAIL, + ArtifactID::AMMO_CART, + ArtifactID::BALLISTA, + ArtifactID::CATAPULT, + ArtifactID::FIRST_AID_TENT, + }), objectPtr->getId())) + ui->comboBoxArtifactArtifact->addItem(QString::fromStdString(objectPtr->getNameTranslated()), QVariant(objectPtr->getId())); + + for(auto const & objectPtr : LIBRARY->skillh->objects) + ui->comboBoxSecondarySkillSecondarySkill->addItem(QString::fromStdString(objectPtr->getNameTranslated()), QVariant(objectPtr->getId())); + + ui->comboBoxSecondarySkillMastery->addItem(tr("Basic"), QVariant(0)); + ui->comboBoxSecondarySkillMastery->addItem(tr("Advanced"), QVariant(1)); + ui->comboBoxSecondarySkillMastery->addItem(tr("Expert"), QVariant(2)); + + ui->comboBoxResourceResourceType->addItem(tr("Wood"), QVariant(0)); + ui->comboBoxResourceResourceType->addItem(tr("Mercury"), QVariant(1)); + ui->comboBoxResourceResourceType->addItem(tr("Ore"), QVariant(2)); + ui->comboBoxResourceResourceType->addItem(tr("Sulfur"), QVariant(3)); + ui->comboBoxResourceResourceType->addItem(tr("Crystal"), QVariant(4)); + ui->comboBoxResourceResourceType->addItem(tr("Gems"), QVariant(5)); + ui->comboBoxResourceResourceType->addItem(tr("Gold"), QVariant(6)); + ui->comboBoxResourceResourceType->addItem(tr("Common (Wood and Ore)"), QVariant(0xFD)); + ui->comboBoxResourceResourceType->addItem(tr("Rare (Mercury, Sulfur, Crystal, Gems)"), QVariant(0xFE)); +} + +void StartingBonus::loadBonus() +{ + auto setComboBoxValue = [](QComboBox * comboBox, QVariant data){ + int index = comboBox->findData(data); + if(index != -1) + comboBox->setCurrentIndex(index); + }; + + switch (bonus.type) + { + case CampaignBonusType::SPELL: + ui->radioButtonSpell->setChecked(true); + on_radioButtonSpell_toggled(); + setComboBoxValue(ui->comboBoxSpellRecipient, bonus.info1); + setComboBoxValue(ui->comboBoxSpellSpell, bonus.info2); + break; + case CampaignBonusType::MONSTER: + ui->radioButtonCreature->setChecked(true); + on_radioButtonCreature_toggled(); + setComboBoxValue(ui->comboBoxCreatureRecipient, bonus.info1); + setComboBoxValue(ui->comboBoxCreatureCreatureType, bonus.info2); + ui->spinBoxCreatureQuantity->setValue(bonus.info3); + break; + case CampaignBonusType::BUILDING: + ui->radioButtonBuilding->setChecked(true); + on_radioButtonBuilding_toggled(); + setComboBoxValue(ui->comboBoxBuildingBuilding, bonus.info1); + break; + case CampaignBonusType::ARTIFACT: + ui->radioButtonArtifact->setChecked(true); + on_radioButtonArtifact_toggled(); + setComboBoxValue(ui->comboBoxArtifactRecipient, bonus.info1); + setComboBoxValue(ui->comboBoxArtifactArtifact, bonus.info2); + break; + case CampaignBonusType::SPELL_SCROLL: + ui->radioButtonSpellScroll->setChecked(true); + on_radioButtonSpellScroll_toggled(); + setComboBoxValue(ui->comboBoxSpellScrollRecipient, bonus.info1); + setComboBoxValue(ui->comboBoxSpellScrollSpell, bonus.info2); + break; + case CampaignBonusType::PRIMARY_SKILL: + ui->radioButtonPrimarySkill->setChecked(true); + on_radioButtonPrimarySkill_toggled(); + setComboBoxValue(ui->comboBoxPrimarySkillRecipient, bonus.info1); + ui->spinBoxPrimarySkillAttack->setValue((bonus.info2 >> 0) & 0xff); + ui->spinBoxPrimarySkillDefense->setValue((bonus.info2 >> 8) & 0xff); + ui->spinBoxPrimarySkillSpell->setValue((bonus.info2 >> 16) & 0xff); + ui->spinBoxPrimarySkillKnowledge->setValue((bonus.info2 >> 24) & 0xff); + break; + case CampaignBonusType::SECONDARY_SKILL: + ui->radioButtonSecondarySkill->setChecked(true); + on_radioButtonSecondarySkill_toggled(); + setComboBoxValue(ui->comboBoxSecondarySkillRecipient, bonus.info1); + setComboBoxValue(ui->comboBoxSecondarySkillSecondarySkill, bonus.info2); + setComboBoxValue(ui->comboBoxSecondarySkillMastery, bonus.info3); + break; + case CampaignBonusType::RESOURCE: + ui->radioButtonResource->setChecked(true); + on_radioButtonResource_toggled(); + setComboBoxValue(ui->comboBoxResourceResourceType, bonus.info1); + ui->spinBoxResourceQuantity->setValue(bonus.info2); + break; + + default: + break; + } +} + +void StartingBonus::saveBonus() +{ + if(ui->radioButtonSpell->isChecked()) + bonus.type = CampaignBonusType::SPELL; + else if(ui->radioButtonCreature->isChecked()) + bonus.type = CampaignBonusType::MONSTER; + else if(ui->radioButtonBuilding->isChecked()) + bonus.type = CampaignBonusType::BUILDING; + else if(ui->radioButtonArtifact->isChecked()) + bonus.type = CampaignBonusType::ARTIFACT; + else if(ui->radioButtonSpellScroll->isChecked()) + bonus.type = CampaignBonusType::SPELL_SCROLL; + else if(ui->radioButtonPrimarySkill->isChecked()) + bonus.type = CampaignBonusType::PRIMARY_SKILL; + else if(ui->radioButtonSecondarySkill->isChecked()) + bonus.type = CampaignBonusType::SECONDARY_SKILL; + else if(ui->radioButtonResource->isChecked()) + bonus.type = CampaignBonusType::RESOURCE; + + bonus.info1 = 0; + bonus.info2 = 0; + bonus.info3 = 0; + + switch (bonus.type) + { + case CampaignBonusType::SPELL: + bonus.info1 = ui->comboBoxSpellRecipient->currentData().toInt(); + bonus.info2 = ui->comboBoxSpellSpell->currentData().toInt(); + break; + case CampaignBonusType::MONSTER: + bonus.info1 = ui->comboBoxCreatureRecipient->currentData().toInt(); + bonus.info2 = ui->comboBoxCreatureCreatureType->currentData().toInt(); + bonus.info3 = ui->spinBoxCreatureQuantity->value(); + break; + case CampaignBonusType::BUILDING: + bonus.info1 = ui->comboBoxBuildingBuilding->currentData().toInt(); + break; + case CampaignBonusType::ARTIFACT: + bonus.info1 = ui->comboBoxArtifactRecipient->currentData().toInt(); + bonus.info2 = ui->comboBoxArtifactArtifact->currentData().toInt(); + break; + case CampaignBonusType::SPELL_SCROLL: + bonus.info1 = ui->comboBoxSpellScrollRecipient->currentData().toInt(); + bonus.info2 = ui->comboBoxSpellScrollSpell->currentData().toInt(); + break; + case CampaignBonusType::PRIMARY_SKILL: + bonus.info1 = ui->comboBoxPrimarySkillRecipient->currentData().toInt(); + bonus.info2 |= ui->spinBoxPrimarySkillAttack->value() << 0; + bonus.info2 |= ui->spinBoxPrimarySkillDefense->value() << 8; + bonus.info2 |= ui->spinBoxPrimarySkillSpell->value() << 16; + bonus.info2 |= ui->spinBoxPrimarySkillKnowledge->value() << 24; + break; + case CampaignBonusType::SECONDARY_SKILL: + bonus.info1 = ui->comboBoxSecondarySkillRecipient->currentData().toInt(); + bonus.info2 = ui->comboBoxSecondarySkillSecondarySkill->currentData().toInt(); + bonus.info3 = ui->comboBoxSecondarySkillMastery->currentData().toInt(); + break; + case CampaignBonusType::RESOURCE: + bonus.info1 = ui->comboBoxResourceResourceType->currentData().toInt(); + bonus.info2 = ui->spinBoxResourceQuantity->value(); + break; + + default: + break; + } +} + +void StartingBonus::on_buttonBox_clicked(QAbstractButton * button) +{ + if(button == ui->buttonBox->button(QDialogButtonBox::Ok)) + { + saveBonus(); + accept(); + } + close(); +} + +bool StartingBonus::showStartingBonus(PlayerColor color, std::shared_ptr map, CampaignBonus & bonus) +{ + auto * dialog = new StartingBonus(color, map, bonus); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + + auto result = dialog->exec(); + + if(result == QDialog::Accepted) + bonus = dialog->bonus; + + return result == QDialog::Accepted; +} + +QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr map) +{ + auto getHeroName = [](int id){ + MetaString tmp; + if(0xFFFD) + tmp.appendRawString(tr("strongest hero").toStdString()); + else if(0xFFFE) + tmp.appendRawString(tr("generated hero").toStdString()); + else if(0xFFFF) + tmp.appendRawString(tr("random hero").toStdString()); + else + tmp.appendName(HeroTypeID(id)); + return QString::fromStdString(tmp.toString()); + }; + auto getSpellName = [](int id){ + MetaString tmp; + tmp.appendName(SpellID(id)); + return QString::fromStdString(tmp.toString()); + }; + auto getMonsterName = [](int id, int amount){ + MetaString tmp; + tmp.appendName(CreatureID(id), amount); + return QString::fromStdString(tmp.toString()); + }; + auto getArtifactName = [](int id){ + MetaString tmp; + tmp.appendName(ArtifactID(id)); + return QString::fromStdString(tmp.toString()); + }; + + switch (bonus.type) + { + case CampaignBonusType::SPELL: + return tr("%1 spell for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1)); + case CampaignBonusType::MONSTER: + return tr("%1 %2 for %3").arg(bonus.info3).arg(getMonsterName(bonus.info2, bonus.info3)).arg(getHeroName(bonus.info1)); + case CampaignBonusType::BUILDING: + return tr("Building"); + case CampaignBonusType::ARTIFACT: + return tr("%1 artifact for %2").arg(getArtifactName(bonus.info2)).arg(getHeroName(bonus.info1)); + case CampaignBonusType::SPELL_SCROLL: + return tr("%1 spell scroll for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1)); + case CampaignBonusType::PRIMARY_SKILL: + return tr("Primary skill (Attack: %1, Defense: %2, Spell: %3, Knowledge: %4) for %5").arg((bonus.info2 >> 0) & 0xff).arg((bonus.info2 >> 8) & 0xff).arg((bonus.info2 >> 16) & 0xff).arg((bonus.info2 >> 24) & 0xff).arg(getHeroName(bonus.info1)); + case CampaignBonusType::SECONDARY_SKILL: + return tr("Secondary skill"); + case CampaignBonusType::RESOURCE: + return tr("Resource"); + } + return {}; +} + +void StartingBonus::on_radioButtonSpell_toggled() +{ + ui->tabWidget->setCurrentIndex(0); +} + +void StartingBonus::on_radioButtonCreature_toggled() +{ + ui->tabWidget->setCurrentIndex(1); +} + +void StartingBonus::on_radioButtonBuilding_toggled() +{ + ui->tabWidget->setCurrentIndex(2); +} + +void StartingBonus::on_radioButtonArtifact_toggled() +{ + ui->tabWidget->setCurrentIndex(3); +} + +void StartingBonus::on_radioButtonSpellScroll_toggled() +{ + ui->tabWidget->setCurrentIndex(4); +} + +void StartingBonus::on_radioButtonPrimarySkill_toggled() +{ + ui->tabWidget->setCurrentIndex(5); +} + +void StartingBonus::on_radioButtonSecondarySkill_toggled() +{ + ui->tabWidget->setCurrentIndex(6); +} + +void StartingBonus::on_radioButtonResource_toggled() +{ + ui->tabWidget->setCurrentIndex(7); +} + diff --git a/mapeditor/campaigneditor/startingbonus.h b/mapeditor/campaigneditor/startingbonus.h new file mode 100644 index 000000000..5c46be185 --- /dev/null +++ b/mapeditor/campaigneditor/startingbonus.h @@ -0,0 +1,55 @@ +/* + * startingbonus.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include + +#include "lib/constants/EntityIdentifiers.h" +#include "lib/campaign/CampaignState.h" + +struct CampaignBonus; +class CMap; + +namespace Ui { +class StartingBonus; +} + +class StartingBonus : public QDialog +{ + Q_OBJECT + +public: + explicit StartingBonus(PlayerColor color, std::shared_ptr map, CampaignBonus bonus); + ~StartingBonus(); + + static bool showStartingBonus(PlayerColor color, std::shared_ptr map, CampaignBonus & bonus); + static QString getBonusListTitle(CampaignBonus bonus, std::shared_ptr map); + +private slots: + void on_radioButtonSpell_toggled(); + void on_radioButtonCreature_toggled(); + void on_radioButtonBuilding_toggled(); + void on_radioButtonArtifact_toggled(); + void on_radioButtonSpellScroll_toggled(); + void on_radioButtonPrimarySkill_toggled(); + void on_radioButtonSecondarySkill_toggled(); + void on_radioButtonResource_toggled(); + void on_buttonBox_clicked(QAbstractButton * button); + +private: + Ui::StartingBonus *ui; + + CampaignBonus bonus; + std::shared_ptr map; + PlayerColor color; + + void initControls(); + void loadBonus(); + void saveBonus(); +}; diff --git a/mapeditor/campaigneditor/startingbonus.ui b/mapeditor/campaigneditor/startingbonus.ui new file mode 100644 index 000000000..66cd449d9 --- /dev/null +++ b/mapeditor/campaigneditor/startingbonus.ui @@ -0,0 +1,468 @@ + + + StartingBonus + + + + 0 + 0 + 499 + 644 + + + + Scenario Properties + + + + + + Select a bonus type + + + + + + + Spell + + + + + + + Creature + + + + + + + Building + + + + + + + Artifact + + + + + + + Spell scroll + + + + + + + Primary skill + + + + + + + Secondary skill + + + + + + + Resource + + + + + + + Qt::Horizontal + + + + + + + 0 + + + + Spell + + + + + + Recipient + + + + + + + + + + Spell + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Creature + + + + + + Recipient + + + + + + + + + + Creature type + + + + + + + + + + Quantity + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Building + + + + + + Building + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Artifact + + + + + + Recipient + + + + + + + + + + Artifact + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Spell scroll + + + + + + Recipient + + + + + + + + + + Spell + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Primary skill + + + + + + Recipient + + + + + + + + + + Attack skill + + + + + + + + + + Defense skill + + + + + + + + + + Spell power + + + + + + + + + + Knowledge + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Scondary skill + + + + + + Recipient + + + + + + + + + + Secondary skill + + + + + + + + + + Mastery + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Resource + + + + + + Resource type + + + + + + + + + + Quantity + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/mapeditor/helper.cpp b/mapeditor/helper.cpp new file mode 100644 index 000000000..10be29f47 --- /dev/null +++ b/mapeditor/helper.cpp @@ -0,0 +1,109 @@ +/* + * helper.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "helper.h" +#include "mapcontroller.h" + +#include "../lib/filesystem/Filesystem.h" +#include "../lib/filesystem/CMemoryBuffer.h" +#include "../lib/filesystem/CFilesystemLoader.h" +#include "../lib/filesystem/CZipSaver.h" +#include "../lib/campaign/CampaignHandler.h" +#include "../lib/mapping/CMapService.h" +#include "../lib/mapping/CMap.h" +#include "../lib/mapping/CMap.h" +#include "../lib/mapping/MapFormatJson.h" +#include "../lib/modding/ModIncompatibility.h" + +std::unique_ptr Helper::openMapInternal(const QString & filenameSelect) +{ + QFileInfo fi(filenameSelect); + std::string fname = fi.fileName().toStdString(); + std::string fdir = fi.dir().path().toStdString(); + + ResourcePath resId("MAPEDITOR/" + fname, EResType::MAP); + + //addFilesystem takes care about memory deallocation if case of failure, no memory leak here + auto * mapEditorFilesystem = new CFilesystemLoader("MAPEDITOR/", fdir, 0); + CResourceHandler::removeFilesystem("local", "mapEditor"); + CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem); + + if(!CResourceHandler::get("mapEditor")->existsResource(resId)) + throw std::runtime_error("Cannot open map from this folder"); + + CMapService mapService; + if(auto header = mapService.loadMapHeader(resId)) + { + auto missingMods = CMapService::verifyMapHeaderMods(*header); + ModIncompatibility::ModList modList; + for(const auto & m : missingMods) + modList.push_back(m.second.name); + + if(!modList.empty()) + throw ModIncompatibility(modList); + + return mapService.loadMap(resId, nullptr); + } + else + throw std::runtime_error("Corrupted map"); +} + +std::shared_ptr Helper::openCampaignInternal(const QString & filenameSelect) +{ + QFileInfo fi(filenameSelect); + std::string fname = fi.fileName().toStdString(); + std::string fdir = fi.dir().path().toStdString(); + + ResourcePath resId("MAPEDITOR/" + fname, EResType::CAMPAIGN); + + //addFilesystem takes care about memory deallocation if case of failure, no memory leak here + auto * mapEditorFilesystem = new CFilesystemLoader("MAPEDITOR/", fdir, 0); + CResourceHandler::removeFilesystem("local", "mapEditor"); + CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem); + + if(!CResourceHandler::get("mapEditor")->existsResource(resId)) + throw std::runtime_error("Cannot open campaign from this folder"); + if(auto campaign = CampaignHandler::getCampaign(resId.getName())) + return campaign; + else + throw std::runtime_error("Corrupted campaign"); +} + +void Helper::saveCampaign(std::shared_ptr campaignState, const QString & filename) +{ + auto jsonCampaign = CampaignHandler::writeHeaderToJson(*campaignState); + + std::shared_ptr io(new CDefaultIOApi()); + auto saver = std::make_shared(io, filename.toStdString()); + for(auto & scenario : campaignState->allScenarios()) + { + auto map = campaignState->getMap(scenario, nullptr); + MapController::repairMap(map.get()); + CMemoryBuffer serializeBuffer; + { + CMapSaverJson jsonSaver(&serializeBuffer); + jsonSaver.saveMap(map); + } + + auto mapName = boost::algorithm::to_lower_copy(campaignState->scenario(scenario).mapName); + if(!boost::ends_with(mapName, ".vmap")) + mapName = boost::replace_all_copy(mapName, ".h3m", std::string("")) + ".vmap"; + + auto stream = saver->addFile(mapName); + stream->write(reinterpret_cast(serializeBuffer.getBuffer().data()), serializeBuffer.getSize()); + + jsonCampaign["scenarios"].Vector().push_back(CampaignHandler::writeScenarioToJson(campaignState->scenario(scenario))); + jsonCampaign["scenarios"].Vector().back()["map"].String() = mapName; + } + + auto jsonCampaignStr = jsonCampaign.toString(); + saver->addFile("header.json")->write(reinterpret_cast(jsonCampaignStr.data()), jsonCampaignStr.length()); +} diff --git a/mapeditor/helper.h b/mapeditor/helper.h new file mode 100644 index 000000000..90a8b2259 --- /dev/null +++ b/mapeditor/helper.h @@ -0,0 +1,21 @@ +/* + * helper.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class CMap; +class CampaignState; + +namespace Helper +{ + std::unique_ptr openMapInternal(const QString &); + std::shared_ptr openCampaignInternal(const QString &); + void saveCampaign(std::shared_ptr campaignState, const QString & filename); +} \ No newline at end of file diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 7d43e05b4..50b1b70e7 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -25,14 +25,11 @@ #include "../lib/CConfigHandler.h" #include "../lib/CConsoleHandler.h" #include "../lib/filesystem/Filesystem.h" -#include "../lib/filesystem/CMemoryBuffer.h" #include "../lib/GameConstants.h" #include "../lib/campaign/CampaignHandler.h" #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjects/ObjectTemplate.h" -#include "../lib/mapping/CMapService.h" -#include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapEditManager.h" #include "../lib/mapping/MapFormat.h" #include "../lib/mapping/MapFormatJson.h" @@ -40,7 +37,6 @@ #include "../lib/RoadHandler.h" #include "../lib/RiverHandler.h" #include "../lib/TerrainHandler.h" -#include "../lib/filesystem/CFilesystemLoader.h" #include "maphandler.h" #include "graphics.h" @@ -51,6 +47,8 @@ #include "mapsettings/translations.h" #include "playersettings.h" #include "validator.h" +#include "helper.h" +#include "campaigneditor/campaigneditor.h" QJsonValue jsonFromPixmap(const QPixmap &p) { @@ -237,6 +235,7 @@ MainWindow::MainWindow(QWidget* parent) : ui->actionZoom_in->setIcon(QIcon{":/icons/zoom_plus.png"}); ui->actionZoom_out->setIcon(QIcon{":/icons/zoom_minus.png"}); ui->actionZoom_reset->setIcon(QIcon{":/icons/zoom_zero.png"}); + ui->actionCampaignEditor->setIcon(QIcon{":/icons/mapeditor.64x64.png"}); loadUserSettings(); //For example window size setTitle(); @@ -363,65 +362,11 @@ void MainWindow::initializeMap(bool isNew) onPlayersChanged(); } -std::unique_ptr MainWindow::openMapInternal(const QString & filenameSelect) -{ - QFileInfo fi(filenameSelect); - std::string fname = fi.fileName().toStdString(); - std::string fdir = fi.dir().path().toStdString(); - - ResourcePath resId("MAPEDITOR/" + fname, EResType::MAP); - - //addFilesystem takes care about memory deallocation if case of failure, no memory leak here - auto * mapEditorFilesystem = new CFilesystemLoader("MAPEDITOR/", fdir, 0); - CResourceHandler::removeFilesystem("local", "mapEditor"); - CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem); - - if(!CResourceHandler::get("mapEditor")->existsResource(resId)) - throw std::runtime_error("Cannot open map from this folder"); - - CMapService mapService; - if(auto header = mapService.loadMapHeader(resId)) - { - auto missingMods = CMapService::verifyMapHeaderMods(*header); - ModIncompatibility::ModList modList; - for(const auto & m : missingMods) - modList.push_back(m.second.name); - - if(!modList.empty()) - throw ModIncompatibility(modList); - - return mapService.loadMap(resId, nullptr); - } - else - throw std::runtime_error("Corrupted map"); -} - -std::shared_ptr MainWindow::openCampaignInternal(const QString & filenameSelect) -{ - QFileInfo fi(filenameSelect); - std::string fname = fi.fileName().toStdString(); - std::string fdir = fi.dir().path().toStdString(); - - ResourcePath resId("MAPEDITOR/" + fname, EResType::CAMPAIGN); - - //addFilesystem takes care about memory deallocation if case of failure, no memory leak here - auto * mapEditorFilesystem = new CFilesystemLoader("MAPEDITOR/", fdir, 0); - CResourceHandler::removeFilesystem("local", "mapEditor"); - CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem); - - if(!CResourceHandler::get("mapEditor")->existsResource(resId)) - throw std::runtime_error("Cannot open campaign from this folder"); - if(auto campaign = CampaignHandler::getCampaign(resId.getName())) - return campaign; - else - throw std::runtime_error("Corrupted campaign"); -} - bool MainWindow::openMap(const QString & filenameSelect) { try { - controller.setMap(openMapInternal(filenameSelect)); + controller.setMap(Helper::openMapInternal(filenameSelect)); } catch(const ModIncompatibility & e) { @@ -623,6 +568,14 @@ void MainWindow::on_actionSave_as_triggered() saveMap(); } +void MainWindow::on_actionCampaignEditor_triggered() +{ + if(!getAnswerAboutUnsavedChanges()) + return; + + hide(); + CampaignEditor::showCampaignEditor(); +} void MainWindow::on_actionNew_triggered() { @@ -1382,7 +1335,7 @@ void MainWindow::on_actionh3m_converter_triggered() for(auto & m : mapFiles) { CMapService mapService; - auto map = openMapInternal(m); + auto map = Helper::openMapInternal(m); controller.repairMap(map.get()); mapService.saveMap(map, (saveDirectory + '/' + QFileInfo(m).completeBaseName() + ".vmap").toStdString()); } @@ -1411,35 +1364,9 @@ void MainWindow::on_actionh3c_converter_triggered() QFileInfo fileInfo(campaignFileDest); if(fileInfo.suffix().toLower() != "vcmp") campaignFileDest += ".vcmp"; - auto campaign = openCampaignInternal(campaignFile); + auto campaign = Helper::openCampaignInternal(campaignFile); - auto jsonCampaign = CampaignHandler::writeHeaderToJson(*campaign); - - std::shared_ptr io(new CDefaultIOApi()); - auto saver = std::make_shared(io, campaignFileDest.toStdString()); - for(auto & scenario : campaign->allScenarios()) - { - CMapService mapService; - auto map = campaign->getMap(scenario, nullptr); - controller.repairMap(map.get()); - CMemoryBuffer serializeBuffer; - { - CMapSaverJson jsonSaver(&serializeBuffer); - jsonSaver.saveMap(map); - } - - auto mapName = boost::algorithm::to_lower_copy(campaign->scenario(scenario).mapName); - mapName = boost::replace_all_copy(mapName, ".h3m", std::string("")) + ".vmap"; - - auto stream = saver->addFile(mapName); - stream->write(reinterpret_cast(serializeBuffer.getBuffer().data()), serializeBuffer.getSize()); - - jsonCampaign["scenarios"].Vector().push_back(CampaignHandler::writeScenarioToJson(campaign->scenario(scenario))); - jsonCampaign["scenarios"].Vector().back()["map"].String() = mapName; - } - - auto jsonCampaignStr = jsonCampaign.toString(); - saver->addFile("header.json")->write(reinterpret_cast(jsonCampaignStr.data()), jsonCampaignStr.length()); + Helper::saveCampaign(campaign, campaignFileDest); } void MainWindow::on_actionLock_triggered() diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index b16f342ba..dfe3a8be7 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -10,8 +10,6 @@ class ObjectBrowser; class ObjectBrowserProxyModel; VCMI_LIB_NAMESPACE_BEGIN -class CMap; -class CampaignState; class CConsoleHandler; class CBasicLogConfigurator; class CGObjectInstance; @@ -42,9 +40,6 @@ class MainWindow : public QMainWindow #endif std::unique_ptr logConfig; - std::unique_ptr openMapInternal(const QString &); - std::shared_ptr openCampaignInternal(const QString &); - public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); @@ -75,6 +70,8 @@ private slots: void on_actionSave_as_triggered(); + void on_actionCampaignEditor_triggered(); + void on_actionNew_triggered(); void on_actionLevel_triggered(); diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index c0cc0382a..cd482d8d3 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -70,6 +70,9 @@ + + + @@ -144,6 +147,8 @@ + + @@ -1065,6 +1070,14 @@ Ctrl+Shift+S + + + Campaign editor + + + Campaign editor + + View underground diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index ede018962..e34030b18 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -93,7 +93,7 @@ void MapController::repairMap() repairMap(map()); } -void MapController::repairMap(CMap * map) const +void MapController::repairMap(CMap * map) { if(!map) return; diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 0fdc4025d..89c6ec337 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -31,7 +31,7 @@ public: void setMap(std::unique_ptr); void initObstaclePainters(CMap * map); - void repairMap(CMap * map) const; + static void repairMap(CMap * map); void repairMap(); const std::unique_ptr & getMapUniquePtr() const; //to be used for map saving From 8dc11702156e2cc61c4c3a1af7a1d3ff3911cebf Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 5 Apr 2025 23:09:58 +0200 Subject: [PATCH 02/11] set creation date --- mapeditor/campaigneditor/campaigneditor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp index 5818906a0..077115419 100644 --- a/mapeditor/campaigneditor/campaigneditor.cpp +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -205,6 +205,7 @@ void CampaignEditor::on_actionNew_triggered() campaignState->scenarios.emplace(CampaignScenarioID(i), s); } campaignState->modName = "mapEditor"; + campaignState->creationDateTime = std::time(nullptr); changed(); redraw(); From 42673c1bab122447c6cc4607f2bdd2fd0f3f32cf Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 5 Apr 2025 23:11:30 +0200 Subject: [PATCH 03/11] fix error --- mapeditor/campaigneditor/startingbonus.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp index ca6e2007c..fcbdc1a4d 100644 --- a/mapeditor/campaigneditor/startingbonus.cpp +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -280,11 +280,11 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr Date: Thu, 10 Apr 2025 21:04:24 +0200 Subject: [PATCH 04/11] code review (first batch) --- client/lobby/CBonusSelection.cpp | 6 ++-- lib/campaign/CampaignHandler.cpp | 26 ++++++++-------- lib/constants/EntityIdentifiers.cpp | 4 ++- lib/constants/EntityIdentifiers.h | 7 ++++- lib/gameState/CGameStateCampaign.cpp | 10 +++---- mapeditor/BitmapHandler.cpp | 2 +- mapeditor/StdInc.h | 3 -- mapeditor/campaigneditor/campaigneditor.cpp | 7 +++-- .../campaigneditor/campaignproperties.cpp | 8 +++-- .../campaigneditor/scenarioproperties.cpp | 6 ++-- mapeditor/campaigneditor/startingbonus.cpp | 30 +++++++++---------- mapeditor/mainwindow.cpp | 2 +- 12 files changed, 59 insertions(+), 52 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 7dedec842..d5a0ade19 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -271,13 +271,13 @@ void CBonusSelection::createBonusesIcons() switch(bonDescs[i].info1) { - case 0xFD: //wood + ore + case EGameResID::COMMON: //wood + ore { desc.replaceLocalString(EMetaText::GENERAL_TXT, 721); picNumber = 7; break; } - case 0xFE: //wood + ore + case EGameResID::RARE : //mercury + sulfur + crystal + gems { desc.replaceLocalString(EMetaText::GENERAL_TXT, 722); picNumber = 8; @@ -305,7 +305,7 @@ void CBonusSelection::createBonusesIcons() } case CampaignBonusType::HERO: - if(bonDescs[i].info2 == 0xFFFF) + if(bonDescs[i].info2 == HeroTypeID::RANDOM) { desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero picNumber = -1; diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 0e2a1802c..37cd0aa70 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -281,23 +281,21 @@ static const std::map primarySkillsMap = { }; static const std::map heroSpecialMap = { - {"strongest", 0xFFFD}, - {"generated", 0xFFFE}, - {"random", 0xFFFF} + {"strongest", HeroTypeID::STRONGEST}, + {"generated", HeroTypeID::GENERATED}, + {"random", HeroTypeID::RANDOM} }; static const std::map resourceTypeMap = { - //FD - wood+ore - //FE - mercury+sulfur+crystal+gem - {"wood", 0}, - {"mercury", 1}, - {"ore", 2}, - {"sulfur", 3}, - {"crystal", 4}, - {"gems", 5}, - {"gold", 6}, - {"common", 0xFD}, - {"rare", 0xFE} + {"wood", EGameResID::WOOD}, + {"mercury", EGameResID::MERCURY}, + {"ore", EGameResID::ORE}, + {"sulfur", EGameResID::SULFUR}, + {"crystal", EGameResID::CRYSTAL}, + {"gems", EGameResID::GEMS}, + {"gold", EGameResID::GOLD}, + {"common", EGameResID::COMMON}, + {"rare", EGameResID::RARE} }; CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader) diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index d57571f32..5731ff261 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -53,9 +53,11 @@ const BattleID BattleID::NONE(-1); const QueryID QueryID::NONE(-1); const QueryID QueryID::CLIENT(-2); const HeroTypeID HeroTypeID::NONE(-1); -const HeroTypeID HeroTypeID::RANDOM(-2); const HeroTypeID HeroTypeID::GEM(27); const HeroTypeID HeroTypeID::SOLMYR(45); +const HeroTypeID HeroTypeID::STRONGEST(0xFFFD); +const HeroTypeID HeroTypeID::GENERATED(0xFFFE); +const HeroTypeID HeroTypeID::RANDOM(0xFFFF); const ObjectInstanceID ObjectInstanceID::NONE(-1); diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index ac89388b1..c22298ef3 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -102,10 +102,13 @@ public: const HeroType * toEntity(const Services * services) const; static const HeroTypeID NONE; - static const HeroTypeID RANDOM; static const HeroTypeID GEM; // aka Gem, Sorceress in campaign static const HeroTypeID SOLMYR; // aka Young Yog in campaigns + static const HeroTypeID STRONGEST; + static const HeroTypeID GENERATED; + static const HeroTypeID RANDOM; + bool isValid() const { return getNum() >= 0; @@ -1043,6 +1046,8 @@ public: COUNT, WOOD_AND_ORE = 127, // special case for town bonus resource + COMMON = 0xFD, // campaign bonus + RARE = 0xFE, // campaign bonus NONE = -1 }; }; diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 3cc6cc9ab..21e7ac413 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -209,7 +209,7 @@ void CGameStateCampaign::placeCampaignHeroes() if(it != gameState->scenarioOps->playerInfos.end()) { HeroTypeID heroTypeId = HeroTypeID(campaignBonus->info2); - if(heroTypeId.getNum() == 0xffff) // random bonus hero + if(heroTypeId.getNum() == HeroTypeID::RANDOM) // random bonus hero { heroTypeId = gameState->pickUnusedHeroTypeRandomly(playerColor); } @@ -529,7 +529,7 @@ void CGameStateCampaign::generateCampaignHeroesToReplace() void CGameStateCampaign::initHeroes() { auto chosenBonus = currentBonus(); - if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes + if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != HeroTypeID::GENERATED) //exclude generated heroes { //find human player PlayerColor humanPlayer=PlayerColor::NEUTRAL; @@ -545,7 +545,7 @@ void CGameStateCampaign::initHeroes() const auto & heroes = gameState->players[humanPlayer].getHeroes(); - if (chosenBonus->info1 == 0xFFFD) //most powerful + if (chosenBonus->info1 == HeroTypeID::STRONGEST) //most powerful { int maxB = -1; for (int b=0; binfo1); break; - case 0xFD: //wood+ore + case EGameResID::COMMON: //wood+ore res.push_back(GameResID(EGameResID::WOOD)); res.push_back(GameResID(EGameResID::ORE)); break; - case 0xFE: //rare + case EGameResID::RARE: //rare res.push_back(GameResID(EGameResID::MERCURY)); res.push_back(GameResID(EGameResID::SULFUR)); res.push_back(GameResID(EGameResID::CRYSTAL)); diff --git a/mapeditor/BitmapHandler.cpp b/mapeditor/BitmapHandler.cpp index dd971b4d7..8347a8c7b 100644 --- a/mapeditor/BitmapHandler.cpp +++ b/mapeditor/BitmapHandler.cpp @@ -69,7 +69,7 @@ namespace BitmapHandler it = (int)size - 256 * 3; for(int i = 0; i < 256; i++) { - unsigned char bytes[4]; + std::array bytes; bytes[0] = pcx[it++]; bytes[1] = pcx[it++]; bytes[2] = pcx[it++]; diff --git a/mapeditor/StdInc.h b/mapeditor/StdInc.h index fd30130ef..97f5147da 100644 --- a/mapeditor/StdInc.h +++ b/mapeditor/StdInc.h @@ -11,9 +11,6 @@ #include "../Global.h" -#define VCMI_EDITOR_NAME "VCMI Map Editor" -#define VCMI_CAMP_EDITOR_NAME "VCMI Campaign Editor" - #include #include #include diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp index 077115419..fe9f61431 100644 --- a/mapeditor/campaigneditor/campaigneditor.cpp +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -124,7 +124,7 @@ bool CampaignEditor::getAnswerAboutUnsavedChanges() void CampaignEditor::setTitle() { QFileInfo fileInfo(filename); - QString title = QString("%1%2 - %3 (%4)").arg(fileInfo.fileName(), unsaved ? "*" : "", VCMI_CAMP_EDITOR_NAME, GameConstants::VCMI_VERSION.c_str()); + QString title = QString("%1%2 - %3 (%4)").arg(fileInfo.fileName(), unsaved ? "*" : "", tr("VCMI Campaign Editor"), GameConstants::VCMI_VERSION.c_str()); setWindowTitle(title); } @@ -138,8 +138,9 @@ bool CampaignEditor::saveCampaign() { if(campaignState->mapPieces.size() != campaignState->campaignRegions.regions.size()) { - QMessageBox::critical(nullptr, tr("Maps missing"), tr("Not all Regions have a map. Please add them in Scenario Properties.")); - return false; + auto reply = QMessageBox::question(nullptr, tr("Maps missing"), tr("Not all regions have a map. Do you want to continue?"), QMessageBox::Yes|QMessageBox::No); + if (reply != QMessageBox::Yes) + return false; } Helper::saveCampaign(campaignState, filename); diff --git a/mapeditor/campaigneditor/campaignproperties.cpp b/mapeditor/campaigneditor/campaignproperties.cpp index a03e101dd..1f1120dde 100644 --- a/mapeditor/campaigneditor/campaignproperties.cpp +++ b/mapeditor/campaigneditor/campaignproperties.cpp @@ -15,6 +15,7 @@ #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/campaign/CampaignState.h" #include "../../lib/constants/StringConstants.h" +#include "../../lib/json/JsonNode.h" CampaignProperties::CampaignProperties(std::shared_ptr campaignState): ui(new Ui::CampaignProperties), @@ -36,9 +37,12 @@ CampaignProperties::CampaignProperties(std::shared_ptr campaignSt ui->lineEditMusic->setText(QString::fromStdString(campaignState->music.getName())); ui->checkBoxScenarioDifficulty->setChecked(campaignState->difficultyChosenByPlayer); - for (int i = 0; i < 20; i++) + const JsonNode legacyRegionConfig(JsonPath::builtin("config/campaign_regions.json")); + int legacyRegionNumber = legacyRegionConfig["campaign_regions"].Vector().size(); + + for (int i = 0; i < legacyRegionNumber; i++) ui->comboBoxRegionPreset->insertItem(i, QString::fromStdString(LIBRARY->generaltexth->translate("core.camptext.names", i))); - ui->comboBoxRegionPreset->insertItem(20, tr("Custom")); + ui->comboBoxRegionPreset->insertItem(legacyRegionNumber, tr("Custom")); ui->comboBoxRegionPreset->setCurrentIndex(20); loadRegion(); diff --git a/mapeditor/campaigneditor/scenarioproperties.cpp b/mapeditor/campaigneditor/scenarioproperties.cpp index e61863517..314355e02 100644 --- a/mapeditor/campaigneditor/scenarioproperties.cpp +++ b/mapeditor/campaigneditor/scenarioproperties.cpp @@ -112,9 +112,9 @@ ScenarioProperties::ScenarioProperties(std::shared_ptr campaignSt { heroSelection.emplace(hero->getId().getNum(), hero->getNameTranslated()); }); - heroSelection.emplace(0xFFFD, tr("Strongest").toStdString()); - heroSelection.emplace(0xFFFE, tr("Generated").toStdString()); - heroSelection.emplace(0xFFFF, tr("Random").toStdString()); + heroSelection.emplace(HeroTypeID::STRONGEST, tr("Strongest").toStdString()); + heroSelection.emplace(HeroTypeID::GENERATED, tr("Generated").toStdString()); + heroSelection.emplace(HeroTypeID::RANDOM, tr("Random").toStdString()); reloadMapRelatedUi(); diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp index fcbdc1a4d..a1a9ea976 100644 --- a/mapeditor/campaigneditor/startingbonus.cpp +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -55,9 +55,9 @@ void StartingBonus::initControls() for(auto & hero : map->heroesOnMap) if(hero->getOwner() == color || color == PlayerColor::CANNOT_DETERMINE) heroSelection.emplace(hero->getHeroTypeID(), hero->getNameTranslated()); - heroSelection.emplace(0xFFFD, tr("Strongest").toStdString()); - heroSelection.emplace(0xFFFE, tr("Generated").toStdString()); - heroSelection.emplace(0xFFFF, tr("Random").toStdString()); + heroSelection.emplace(HeroTypeID::STRONGEST, tr("Strongest").toStdString()); + heroSelection.emplace(HeroTypeID::GENERATED, tr("Generated").toStdString()); + heroSelection.emplace(HeroTypeID::RANDOM, tr("Random").toStdString()); for(auto & hero : heroSelection) { @@ -106,15 +106,15 @@ void StartingBonus::initControls() ui->comboBoxSecondarySkillMastery->addItem(tr("Advanced"), QVariant(1)); ui->comboBoxSecondarySkillMastery->addItem(tr("Expert"), QVariant(2)); - ui->comboBoxResourceResourceType->addItem(tr("Wood"), QVariant(0)); - ui->comboBoxResourceResourceType->addItem(tr("Mercury"), QVariant(1)); - ui->comboBoxResourceResourceType->addItem(tr("Ore"), QVariant(2)); - ui->comboBoxResourceResourceType->addItem(tr("Sulfur"), QVariant(3)); - ui->comboBoxResourceResourceType->addItem(tr("Crystal"), QVariant(4)); - ui->comboBoxResourceResourceType->addItem(tr("Gems"), QVariant(5)); - ui->comboBoxResourceResourceType->addItem(tr("Gold"), QVariant(6)); - ui->comboBoxResourceResourceType->addItem(tr("Common (Wood and Ore)"), QVariant(0xFD)); - ui->comboBoxResourceResourceType->addItem(tr("Rare (Mercury, Sulfur, Crystal, Gems)"), QVariant(0xFE)); + ui->comboBoxResourceResourceType->addItem(tr("Wood"), QVariant(EGameResID::WOOD)); + ui->comboBoxResourceResourceType->addItem(tr("Mercury"), QVariant(EGameResID::MERCURY)); + ui->comboBoxResourceResourceType->addItem(tr("Ore"), QVariant(EGameResID::ORE)); + ui->comboBoxResourceResourceType->addItem(tr("Sulfur"), QVariant(EGameResID::SULFUR)); + ui->comboBoxResourceResourceType->addItem(tr("Crystal"), QVariant(EGameResID::CRYSTAL)); + ui->comboBoxResourceResourceType->addItem(tr("Gems"), QVariant(EGameResID::GEMS)); + ui->comboBoxResourceResourceType->addItem(tr("Gold"), QVariant(EGameResID::GOLD)); + ui->comboBoxResourceResourceType->addItem(tr("Common (Wood and Ore)"), QVariant(EGameResID::COMMON)); + ui->comboBoxResourceResourceType->addItem(tr("Rare (Mercury, Sulfur, Crystal, Gems)"), QVariant(EGameResID::RARE)); } void StartingBonus::loadBonus() @@ -280,11 +280,11 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr Date: Thu, 10 Apr 2025 21:41:22 +0200 Subject: [PATCH 05/11] code review (second batch) --- lib/campaign/CampaignState.cpp | 2 +- lib/texts/MetaString.cpp | 7 ++++++ lib/texts/MetaString.h | 2 ++ mapeditor/campaigneditor/startingbonus.cpp | 29 +++++++++++++--------- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index 81d7488c4..6aaa99769 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -432,7 +432,7 @@ std::unique_ptr CampaignState::getMap(CampaignScenarioID scenarioId, IGame boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId.getNum()); - if(!mapPieces.count(scenarioId) || !mapPieces.find(scenarioId)->second.size()) + if(!mapPieces.count(scenarioId)) return nullptr; const auto & mapContent = mapPieces.find(scenarioId)->second; diff --git a/lib/texts/MetaString.cpp b/lib/texts/MetaString.cpp index 5bc5875f9..5eb89f304 100644 --- a/lib/texts/MetaString.cpp +++ b/lib/texts/MetaString.cpp @@ -39,6 +39,13 @@ MetaString MetaString::createFromTextID(const std::string & value) return result; } +MetaString MetaString::createFromName(const GameResID& id) +{ + MetaString result; + result.appendName(id); + return result; +} + void MetaString::appendLocalString(EMetaText type, ui32 serial) { message.push_back(EMessage::APPEND_LOCAL_STRING); diff --git a/lib/texts/MetaString.h b/lib/texts/MetaString.h index 68230a027..3ee360200 100644 --- a/lib/texts/MetaString.h +++ b/lib/texts/MetaString.h @@ -68,6 +68,8 @@ public: static MetaString createFromRawString(const std::string & value); /// Creates MetaString and appends provided text ID string to it static MetaString createFromTextID(const std::string & value); + /// Creates MetaString and appends provided name string to it + static MetaString createFromName(const GameResID& id); /// Appends local string to resulting string void appendLocalString(EMetaText type, ui32 serial); diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp index a1a9ea976..f1aa50e35 100644 --- a/mapeditor/campaigneditor/startingbonus.cpp +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -21,6 +21,7 @@ #include "../../lib/CSkillHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/spells/CSpellHandler.h" +#include "../../lib/texts/CGeneralTextHandler.h" StartingBonus::StartingBonus(PlayerColor color, std::shared_ptr map, CampaignBonus bonus): ui(new Ui::StartingBonus), @@ -102,19 +103,23 @@ void StartingBonus::initControls() for(auto const & objectPtr : LIBRARY->skillh->objects) ui->comboBoxSecondarySkillSecondarySkill->addItem(QString::fromStdString(objectPtr->getNameTranslated()), QVariant(objectPtr->getId())); - ui->comboBoxSecondarySkillMastery->addItem(tr("Basic"), QVariant(0)); - ui->comboBoxSecondarySkillMastery->addItem(tr("Advanced"), QVariant(1)); - ui->comboBoxSecondarySkillMastery->addItem(tr("Expert"), QVariant(2)); + for(int i = 0; i < 3; i++) // Basic, Advanced, Expert + ui->comboBoxSecondarySkillMastery->addItem(QString::fromStdString(LIBRARY->generaltexth->translate("core.skilllev", i)), QVariant(i)); - ui->comboBoxResourceResourceType->addItem(tr("Wood"), QVariant(EGameResID::WOOD)); - ui->comboBoxResourceResourceType->addItem(tr("Mercury"), QVariant(EGameResID::MERCURY)); - ui->comboBoxResourceResourceType->addItem(tr("Ore"), QVariant(EGameResID::ORE)); - ui->comboBoxResourceResourceType->addItem(tr("Sulfur"), QVariant(EGameResID::SULFUR)); - ui->comboBoxResourceResourceType->addItem(tr("Crystal"), QVariant(EGameResID::CRYSTAL)); - ui->comboBoxResourceResourceType->addItem(tr("Gems"), QVariant(EGameResID::GEMS)); - ui->comboBoxResourceResourceType->addItem(tr("Gold"), QVariant(EGameResID::GOLD)); - ui->comboBoxResourceResourceType->addItem(tr("Common (Wood and Ore)"), QVariant(EGameResID::COMMON)); - ui->comboBoxResourceResourceType->addItem(tr("Rare (Mercury, Sulfur, Crystal, Gems)"), QVariant(EGameResID::RARE)); + for(auto & res : std::vector({EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS, EGameResID::GOLD})) + ui->comboBoxResourceResourceType->addItem(QString::fromStdString(MetaString::createFromName(res).toString()), QVariant(res)); + ui->comboBoxResourceResourceType->addItem( + tr("Common (%1 and %2)") + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::WOOD).toString())) + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::ORE).toString())) + , QVariant(EGameResID::COMMON)); + ui->comboBoxResourceResourceType->addItem( + tr("Rare (%1, %2, %3, %4)") + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::MERCURY).toString())) + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::SULFUR).toString())) + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::CRYSTAL).toString())) + .arg(QString::fromStdString(MetaString::createFromName(EGameResID::GEMS).toString())) + , QVariant(EGameResID::RARE)); } void StartingBonus::loadBonus() From af5e95feeef8b0ee5287b6ce290cd7edf694b617 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:07:58 +0200 Subject: [PATCH 06/11] init fix --- mapeditor/campaigneditor/scenarioproperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapeditor/campaigneditor/scenarioproperties.cpp b/mapeditor/campaigneditor/scenarioproperties.cpp index 314355e02..7374ee5fe 100644 --- a/mapeditor/campaigneditor/scenarioproperties.cpp +++ b/mapeditor/campaigneditor/scenarioproperties.cpp @@ -412,7 +412,7 @@ void ScenarioProperties::on_pushButtonExport_clicked() void ScenarioProperties::on_pushButtonRemove_clicked() { campaignState->mapPieces.erase(scenario); - campaignState->scenarios.at(scenario).mapName = ""; + campaignState->scenarios.at(scenario) = CampaignScenario(); reloadMapRelatedUi(); } From 47cfe01141b0817a88329469c458ce3c4a7a71af Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:49:26 +0200 Subject: [PATCH 07/11] seperate identifier --- client/lobby/CBonusSelection.cpp | 2 +- lib/campaign/CampaignHandler.cpp | 6 +++--- lib/constants/EntityIdentifiers.cpp | 7 ++++--- lib/constants/EntityIdentifiers.h | 7 ++++--- lib/gameState/CGameStateCampaign.cpp | 6 +++--- mapeditor/campaigneditor/scenarioproperties.cpp | 6 +++--- mapeditor/campaigneditor/startingbonus.cpp | 12 ++++++------ 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index d5a0ade19..f10f64ccb 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -305,7 +305,7 @@ void CBonusSelection::createBonusesIcons() } case CampaignBonusType::HERO: - if(bonDescs[i].info2 == HeroTypeID::RANDOM) + if(bonDescs[i].info2 == HeroTypeID::CAMP_RANDOM) { desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero picNumber = -1; diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 37cd0aa70..aa18bf026 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -281,9 +281,9 @@ static const std::map primarySkillsMap = { }; static const std::map heroSpecialMap = { - {"strongest", HeroTypeID::STRONGEST}, - {"generated", HeroTypeID::GENERATED}, - {"random", HeroTypeID::RANDOM} + {"strongest", HeroTypeID::CAMP_STRONGEST}, + {"generated", HeroTypeID::CAMP_GENERATED}, + {"random", HeroTypeID::CAMP_RANDOM} }; static const std::map resourceTypeMap = { diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 5731ff261..e52013ed3 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -53,11 +53,12 @@ const BattleID BattleID::NONE(-1); const QueryID QueryID::NONE(-1); const QueryID QueryID::CLIENT(-2); const HeroTypeID HeroTypeID::NONE(-1); +const HeroTypeID HeroTypeID::RANDOM(-2); const HeroTypeID HeroTypeID::GEM(27); const HeroTypeID HeroTypeID::SOLMYR(45); -const HeroTypeID HeroTypeID::STRONGEST(0xFFFD); -const HeroTypeID HeroTypeID::GENERATED(0xFFFE); -const HeroTypeID HeroTypeID::RANDOM(0xFFFF); +const HeroTypeID HeroTypeID::CAMP_STRONGEST(0xFFFD); +const HeroTypeID HeroTypeID::CAMP_GENERATED(0xFFFE); +const HeroTypeID HeroTypeID::CAMP_RANDOM(0xFFFF); const ObjectInstanceID ObjectInstanceID::NONE(-1); diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index c22298ef3..d48719a05 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -102,12 +102,13 @@ public: const HeroType * toEntity(const Services * services) const; static const HeroTypeID NONE; + static const HeroTypeID RANDOM; static const HeroTypeID GEM; // aka Gem, Sorceress in campaign static const HeroTypeID SOLMYR; // aka Young Yog in campaigns - static const HeroTypeID STRONGEST; - static const HeroTypeID GENERATED; - static const HeroTypeID RANDOM; + static const HeroTypeID CAMP_STRONGEST; + static const HeroTypeID CAMP_GENERATED; + static const HeroTypeID CAMP_RANDOM; bool isValid() const { diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 21e7ac413..0d56f0477 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -209,7 +209,7 @@ void CGameStateCampaign::placeCampaignHeroes() if(it != gameState->scenarioOps->playerInfos.end()) { HeroTypeID heroTypeId = HeroTypeID(campaignBonus->info2); - if(heroTypeId.getNum() == HeroTypeID::RANDOM) // random bonus hero + if(heroTypeId.getNum() == HeroTypeID::CAMP_RANDOM) // random bonus hero { heroTypeId = gameState->pickUnusedHeroTypeRandomly(playerColor); } @@ -529,7 +529,7 @@ void CGameStateCampaign::generateCampaignHeroesToReplace() void CGameStateCampaign::initHeroes() { auto chosenBonus = currentBonus(); - if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != HeroTypeID::GENERATED) //exclude generated heroes + if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != HeroTypeID::CAMP_GENERATED) //exclude generated heroes { //find human player PlayerColor humanPlayer=PlayerColor::NEUTRAL; @@ -545,7 +545,7 @@ void CGameStateCampaign::initHeroes() const auto & heroes = gameState->players[humanPlayer].getHeroes(); - if (chosenBonus->info1 == HeroTypeID::STRONGEST) //most powerful + if (chosenBonus->info1 == HeroTypeID::CAMP_STRONGEST) //most powerful { int maxB = -1; for (int b=0; b campaignSt { heroSelection.emplace(hero->getId().getNum(), hero->getNameTranslated()); }); - heroSelection.emplace(HeroTypeID::STRONGEST, tr("Strongest").toStdString()); - heroSelection.emplace(HeroTypeID::GENERATED, tr("Generated").toStdString()); - heroSelection.emplace(HeroTypeID::RANDOM, tr("Random").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_STRONGEST, tr("Strongest").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_GENERATED, tr("Generated").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_RANDOM, tr("Random").toStdString()); reloadMapRelatedUi(); diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp index f1aa50e35..679e4413b 100644 --- a/mapeditor/campaigneditor/startingbonus.cpp +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -56,9 +56,9 @@ void StartingBonus::initControls() for(auto & hero : map->heroesOnMap) if(hero->getOwner() == color || color == PlayerColor::CANNOT_DETERMINE) heroSelection.emplace(hero->getHeroTypeID(), hero->getNameTranslated()); - heroSelection.emplace(HeroTypeID::STRONGEST, tr("Strongest").toStdString()); - heroSelection.emplace(HeroTypeID::GENERATED, tr("Generated").toStdString()); - heroSelection.emplace(HeroTypeID::RANDOM, tr("Random").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_STRONGEST, tr("Strongest").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_GENERATED, tr("Generated").toStdString()); + heroSelection.emplace(HeroTypeID::CAMP_RANDOM, tr("Random").toStdString()); for(auto & hero : heroSelection) { @@ -285,11 +285,11 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr Date: Fri, 11 Apr 2025 22:58:49 +0200 Subject: [PATCH 08/11] option to show full background --- mapeditor/campaigneditor/campaigneditor.cpp | 7 +++++++ mapeditor/campaigneditor/campaigneditor.ui | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp index fe9f61431..ec5ce2efe 100644 --- a/mapeditor/campaigneditor/campaigneditor.cpp +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -34,6 +34,10 @@ CampaignEditor::CampaignEditor(): ui->actionNew->setIcon(QIcon{":/icons/document-new.png"}); ui->actionScenarioProperties->setIcon(QIcon{":/icons/menu-settings.png"}); ui->actionCampaignProperties->setIcon(QIcon{":/icons/menu-mods.png"}); + ui->actionShowFullBackground->setIcon(QIcon{":/icons/tool-area.png"}); + + ui->actionShowFullBackground->setCheckable(true); + connect(ui->actionShowFullBackground, &QAction::triggered, [this](){ redraw(); }); campaignScene.reset(new CampaignScene()); ui->campaignView->setScene(campaignScene.get()); @@ -65,6 +69,8 @@ void CampaignEditor::redraw() campaignScene->clear(); auto background = BitmapHandler::loadBitmap(campaignState->getRegions().getBackgroundName().getName()); + if(!ui->actionShowFullBackground->isChecked()) + background = background.copy(0, 0, 456, 600); campaignScene->addItem(new QGraphicsPixmapItem(QPixmap::fromImage(background))); for (auto & s : campaignState->scenarios) { @@ -105,6 +111,7 @@ void CampaignEditor::redraw() campaignScene->addItem(pixmap); } + campaignScene->setSceneRect(background.rect()); ui->campaignView->show(); } diff --git a/mapeditor/campaigneditor/campaigneditor.ui b/mapeditor/campaigneditor/campaigneditor.ui index e23e36e0d..204d7f1a8 100644 --- a/mapeditor/campaigneditor/campaigneditor.ui +++ b/mapeditor/campaigneditor/campaigneditor.ui @@ -47,8 +47,15 @@ + + + View + + + + @@ -68,6 +75,8 @@ + + @@ -144,6 +153,17 @@ Enter + + + Show full background + + + Show full background + + + F + + From 100f3606db076d8cb361aa3337eccad8a0f3cf32 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:39:08 +0200 Subject: [PATCH 09/11] code review --- lib/texts/MetaString.cpp | 5 ----- lib/texts/MetaString.h | 2 -- mapeditor/campaigneditor/campaigneditor.cpp | 17 ++++++----------- mapeditor/campaigneditor/campaigneditor.h | 2 +- mapeditor/campaigneditor/startingbonus.cpp | 9 +++++++-- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/lib/texts/MetaString.cpp b/lib/texts/MetaString.cpp index 5eb89f304..86ee43574 100644 --- a/lib/texts/MetaString.cpp +++ b/lib/texts/MetaString.cpp @@ -358,11 +358,6 @@ void MetaString::appendName(const ArtifactID & id) appendTextID(id.toEntity(LIBRARY)->getNameTextID()); } -void MetaString::appendName(const HeroTypeID & id) -{ - appendTextID(id.toEntity(LIBRARY)->getNameTextID()); -} - void MetaString::appendName(const SpellID & id) { appendTextID(id.toEntity(LIBRARY)->getNameTextID()); diff --git a/lib/texts/MetaString.h b/lib/texts/MetaString.h index 3ee360200..1601665e6 100644 --- a/lib/texts/MetaString.h +++ b/lib/texts/MetaString.h @@ -23,7 +23,6 @@ class SecondarySkill; class SpellID; class FactionID; class GameResID; -class HeroTypeID; using TQuantity = si32; /// Strings classes that can be used as replacement in MetaString @@ -81,7 +80,6 @@ public: void appendNumber(int64_t value); void appendName(const ArtifactID& id); - void appendName(const HeroTypeID& id); void appendName(const SpellID& id); void appendName(const PlayerColor& id); void appendName(const CreatureID & id, TQuantity count); diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp index ec5ce2efe..d681d0759 100644 --- a/mapeditor/campaigneditor/campaigneditor.cpp +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -141,17 +141,13 @@ void CampaignEditor::changed() setTitle(); } -bool CampaignEditor::saveCampaign() +void CampaignEditor::saveCampaign() { if(campaignState->mapPieces.size() != campaignState->campaignRegions.regions.size()) - { - auto reply = QMessageBox::question(nullptr, tr("Maps missing"), tr("Not all regions have a map. Do you want to continue?"), QMessageBox::Yes|QMessageBox::No); - if (reply != QMessageBox::Yes) - return false; - } + logGlobal->trace("Not all regions have a map"); Helper::saveCampaign(campaignState, filename); - return true; + unsaved = false; } void CampaignEditor::showCampaignEditor() @@ -194,8 +190,7 @@ void CampaignEditor::on_actionSave_as_triggered() filenameSelect += ".vcmp"; filename = filenameSelect; - if(saveCampaign()) - unsaved = false; + saveCampaign(); setTitle(); } @@ -226,8 +221,8 @@ void CampaignEditor::on_actionSave_triggered() if(filename.isNull()) on_actionSave_as_triggered(); - else if(saveCampaign()) - unsaved = false; + else + saveCampaign(); setTitle(); } diff --git a/mapeditor/campaigneditor/campaigneditor.h b/mapeditor/campaigneditor/campaigneditor.h index cf28ae41c..064f34a15 100644 --- a/mapeditor/campaigneditor/campaigneditor.h +++ b/mapeditor/campaigneditor/campaigneditor.h @@ -45,7 +45,7 @@ private: bool getAnswerAboutUnsavedChanges(); void setTitle(); void changed(); - bool saveCampaign(); + void saveCampaign(); void closeEvent(QCloseEvent *event) override; diff --git a/mapeditor/campaigneditor/startingbonus.cpp b/mapeditor/campaigneditor/startingbonus.cpp index 679e4413b..dae2d187c 100644 --- a/mapeditor/campaigneditor/startingbonus.cpp +++ b/mapeditor/campaigneditor/startingbonus.cpp @@ -283,7 +283,7 @@ bool StartingBonus::showStartingBonus(PlayerColor color, std::shared_ptr m QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr map) { - auto getHeroName = [](int id){ + auto getHeroName = [map](int id){ MetaString tmp; if(id == HeroTypeID::CAMP_STRONGEST) tmp.appendRawString(tr("strongest hero").toStdString()); @@ -292,7 +292,12 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptrobjects) + if(auto * ins = dynamic_cast(o.get())) + if(ins->getHeroTypeID().getNum() == id) + tmp.appendTextID(ins->getNameTextID()); + } return QString::fromStdString(tmp.toString()); }; auto getSpellName = [](int id){ From 44bf6bdf487557f1293f36a9bc1449fa712f4c4a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:01:21 +0200 Subject: [PATCH 10/11] fix shortcut --- mapeditor/campaigneditor/campaigneditor.ui | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mapeditor/campaigneditor/campaigneditor.ui b/mapeditor/campaigneditor/campaigneditor.ui index 204d7f1a8..ebc8b4601 100644 --- a/mapeditor/campaigneditor/campaigneditor.ui +++ b/mapeditor/campaigneditor/campaigneditor.ui @@ -139,7 +139,10 @@ Campaign Properties - Ctrl+Enter + Ctrl+Return + + + Qt::ApplicationShortcut @@ -150,7 +153,10 @@ Scenario Properties - Enter + Return + + + Qt::ApplicationShortcut From c9f64993c1f461a9dda90456c33a79d196957543 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 12 Apr 2025 20:46:33 +0200 Subject: [PATCH 11/11] code review --- mapeditor/campaigneditor/campaignview.cpp | 2 +- mapeditor/campaigneditor/campaignview.h | 2 +- mapeditor/campaigneditor/scenarioproperties.cpp | 6 +++--- mapeditor/helper.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mapeditor/campaigneditor/campaignview.cpp b/mapeditor/campaigneditor/campaignview.cpp index 947a891b5..08f25fbcf 100644 --- a/mapeditor/campaigneditor/campaignview.cpp +++ b/mapeditor/campaigneditor/campaignview.cpp @@ -21,7 +21,7 @@ CampaignView::CampaignView(QWidget * parent): { } -ClickablePixmapItem::ClickablePixmapItem(const QPixmap &pixmap, std::function clickedCallback, std::function doubleClickedCallback, std::function contextMenuCallback): +ClickablePixmapItem::ClickablePixmapItem(const QPixmap &pixmap, const std::function & clickedCallback, const std::function & doubleClickedCallback, const std::function & contextMenuCallback): QGraphicsPixmapItem(pixmap), clickedCallback(clickedCallback), doubleClickedCallback(doubleClickedCallback), diff --git a/mapeditor/campaigneditor/campaignview.h b/mapeditor/campaigneditor/campaignview.h index 30016957d..03ab5ab0b 100644 --- a/mapeditor/campaigneditor/campaignview.h +++ b/mapeditor/campaigneditor/campaignview.h @@ -30,7 +30,7 @@ public: class ClickablePixmapItem : public QGraphicsPixmapItem { public: - ClickablePixmapItem(const QPixmap &pixmap, std::function clickedCallback, std::function doubleClickedCallback, std::function contextMenuCallback); + ClickablePixmapItem(const QPixmap &pixmap, const std::function & clickedCallback, const std::function & doubleClickedCallback, const std::function & contextMenuCallback); protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/mapeditor/campaigneditor/scenarioproperties.cpp b/mapeditor/campaigneditor/scenarioproperties.cpp index 22612ffb1..eb0642dac 100644 --- a/mapeditor/campaigneditor/scenarioproperties.cpp +++ b/mapeditor/campaigneditor/scenarioproperties.cpp @@ -206,11 +206,11 @@ void ScenarioProperties::reloadMapRelatedUi() } else { - for(int i = 0; i < scenario.getNum(); ++i) + for(int j = 0; j < scenario.getNum(); ++j) { - auto tmpScenario = CampaignScenarioID(i); + auto tmpScenario = CampaignScenarioID(j); auto text = getRegionChar(tmpScenario.getNum()) + " - " + QString::fromStdString(campaignState->scenarios.at(tmpScenario).mapName); - comboBoxOption->addItem(text, i); + comboBoxOption->addItem(text, j); } } diff --git a/mapeditor/helper.cpp b/mapeditor/helper.cpp index 10be29f47..4d183299d 100644 --- a/mapeditor/helper.cpp +++ b/mapeditor/helper.cpp @@ -81,7 +81,7 @@ void Helper::saveCampaign(std::shared_ptr campaignState, const QS { auto jsonCampaign = CampaignHandler::writeHeaderToJson(*campaignState); - std::shared_ptr io(new CDefaultIOApi()); + auto io = std::make_shared(); auto saver = std::make_shared(io, filename.toStdString()); for(auto & scenario : campaignState->allScenarios()) {