From 136f33f950e5a53893c64e98810b2cb759457e6b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 26 Jul 2024 20:11:14 +0200 Subject: [PATCH 01/76] quickspell improvements --- client/battle/BattleInterfaceClasses.cpp | 65 +++++++++++++++++++----- client/battle/BattleInterfaceClasses.h | 5 ++ client/battle/BattleWindow.cpp | 13 ++--- client/windows/CSpellWindow.cpp | 17 +++++-- client/windows/CSpellWindow.h | 4 ++ 5 files changed, 80 insertions(+), 24 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index d10956326..b9775eba8 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -433,6 +433,49 @@ QuickSpellPanel::QuickSpellPanel(BattleInterface & owner) create(); } +std::vector> QuickSpellPanel::getSpells() +{ + std::vector spellIds; + std::vector spellIdsFromSetting; + for(int i = 0; i < QUICKSPELL_SLOTS; i++) + { + std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String(); + SpellID id; + try + { + id = SpellID::decode(spellIdentifier); + } + catch(const IdentifierResolutionException& e) + { + id = SpellID::NONE; + } + spellIds.push_back(id); + spellIdsFromSetting.push_back(id != SpellID::NONE); + } + + // autofill empty slots with spells if possible + auto hero = owner.getBattle()->battleGetMyHero(); + for(int i = 0; i < QUICKSPELL_SLOTS; i++) + { + if(spellIds[i] != SpellID::NONE) + continue; + + for(auto & availableSpell : CGI->spellh->objects) + { + if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell.get()) && !vstd::contains(spellIds, availableSpell->getId())) + { + spellIds[i] = availableSpell->getId(); + break; + } + } + } + + std::vector> ret; + for(int i = 0; i < QUICKSPELL_SLOTS; i++) + ret.push_back(std::make_tuple(spellIds[i], spellIdsFromSetting[i])); + return ret; +} + void QuickSpellPanel::create() { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); @@ -447,18 +490,11 @@ void QuickSpellPanel::create() if(!hero) return; - for(int i = 0; i < 12; i++) { - std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String(); - + auto spells = getSpells(); + for(int i = 0; i < QUICKSPELL_SLOTS; i++) { SpellID id; - try - { - id = SpellID::decode(spellIdentifier); - } - catch(const IdentifierResolutionException& e) - { - id = SpellID::NONE; - } + bool fromSettings; + std::tie(id, fromSettings) = spells[i]; auto button = std::make_shared(Point(2, 7 + 50 * i), AnimationPath::builtin("spellint"), CButton::tooltip(), [this, id, hero](){ if(id.hasValue() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero)) @@ -466,16 +502,19 @@ void QuickSpellPanel::create() owner.castThisSpell(id); } }); - button->setOverlay(std::make_shared(AnimationPath::builtin("spellint"), !spellIdentifier.empty() ? id.num + 1 : 0)); + button->setOverlay(std::make_shared(AnimationPath::builtin("spellint"), id != SpellID::NONE ? id.num + 1 : 0)); button->addPopupCallback([this, i, hero](){ GH.input().hapticFeedback(); GH.windows().createAndPushWindow(hero, owner.curInt.get(), true, [this, i](SpellID spell){ Settings configID = persistentStorage.write["quickSpell"][std::to_string(i)]; - configID->String() = spell.toSpell()->identifier; + configID->String() = spell == SpellID::NONE ? "" : spell.toSpell()->identifier; create(); }); }); + if(fromSettings) + buttonsIsAutoGenerated.push_back(std::make_shared(Rect(45, 37 + 50 * i, 5, 5), Colors::ORANGE)); + if(!id.hasValue() || !id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero)) { buttonsDisabled.push_back(std::make_shared(Rect(2, 7 + 50 * i, 48, 36), ColorRGBA(0, 0, 0, 172))); diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index a1a16121a..2af8c6bbd 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -155,17 +155,22 @@ private: std::shared_ptr background; std::shared_ptr rect; std::vector> buttons; + std::vector> buttonsIsAutoGenerated; std::vector> buttonsDisabled; std::vector> labels; BattleInterface & owner; public: + int QUICKSPELL_SLOTS = 12; + bool isEnabled; // isActive() is not working on multiple conditions, because of this we need a seperate flag QuickSpellPanel(BattleInterface & owner); void create(); + std::vector> getSpells(); + void show(Canvas & to) override; void inputModeChanged(InputMode modi) override; }; diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 15216c811..26330cfda 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -273,16 +273,13 @@ std::shared_ptr BattleWindow::buildBattleConsole(const JsonNode & void BattleWindow::useSpellIfPossible(int slot) { - std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(slot)].String(); SpellID id; - try - { - id = SpellID::decode(spellIdentifier); - } - catch(const IdentifierResolutionException& e) - { + bool fromSettings; + std::tie(id, fromSettings) = quickSpellWindow->getSpells()[slot]; + + if(id == SpellID::NONE) return; - } + if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero())) { owner.castThisSpell(id); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 6c562e191..ad2f4e4bb 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -29,6 +29,7 @@ #include "../widgets/CComponent.h" #include "../widgets/CTextInput.h" #include "../widgets/TextControls.h" +#include "../widgets/Buttons.h" #include "../adventureMap/AdventureMapInterface.h" #include "../render/IRenderHandler.h" #include "../render/IImage.h" @@ -130,9 +131,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y)); + Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16); if(settings["general"]["enableUiEnhancements"].Bool()) { - Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16); const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75); const ColorRGBA borderColor = ColorRGBA(128, 100, 75); const ColorRGBA grayedColor = ColorRGBA(158, 130, 105); @@ -143,6 +144,13 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m searchBox->setCallback(std::bind(&CSpellWindow::searchInput, this)); } + if(onSpellSelect) + { + Point boxPos = r.bottomLeft() + Point(-2, 5); + showAllSpells = std::make_shared(boxPos, AnimationPath::builtin("sysopchk.def"), CButton::tooltip(CGI->generaltexth->translate("core.help.458.hover"), CGI->generaltexth->translate("core.help.458.hover")), [this](bool state){ searchInput(); }); + showAllSpellsDescription = std::make_shared(boxPos.x + 40, boxPos.y + 12, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, CGI->generaltexth->translate("core.help.458.hover")); + } + processSpells(); //numbers of spell pages computed @@ -288,7 +296,7 @@ void CSpellWindow::processSpells() if(onSpellSelect) { - if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility() && searchTextFound) + if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility() && searchTextFound && (showAllSpells->isSelected() || myHero->canCastThisSpell(spell.get()))) mySpells.push_back(spell.get()); continue; } @@ -359,6 +367,9 @@ void CSpellWindow::fexitb() (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab; (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage; + if(onSpellSelect) + onSpellSelect(SpellID::NONE); + close(); } @@ -605,7 +616,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition) if(owner->onSpellSelect) { owner->onSpellSelect(mySpell->id); - owner->fexitb(); + owner->close(); return; } diff --git a/client/windows/CSpellWindow.h b/client/windows/CSpellWindow.h index 73dee4e4c..3119860ca 100644 --- a/client/windows/CSpellWindow.h +++ b/client/windows/CSpellWindow.h @@ -27,6 +27,7 @@ class CPlayerInterface; class CSpellWindow; class CTextInput; class TransparentFilledRectangle; +class CToggleButton; /// The spell window class CSpellWindow : public CWindowObject @@ -82,6 +83,9 @@ class CSpellWindow : public CWindowObject std::shared_ptr searchBoxRectangle; std::shared_ptr searchBoxDescription; + std::shared_ptr showAllSpells; + std::shared_ptr showAllSpellsDescription; + bool isBigSpellbook; int spellsPerPage; int offL; From 4d1c8bb1b716a63f7506f21bec5f331775760853 Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 8 Jun 2024 16:33:55 +0200 Subject: [PATCH 02/76] map editor: auto update checkboxes of base/upgrade buildings in TownBulidingsWidget --- mapeditor/inspector/townbuildingswidget.cpp | 37 +++++++++++++++++++-- mapeditor/inspector/townbuildingswidget.h | 11 ++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index f14af11a6..7c7c99b48 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -77,7 +77,7 @@ TownBuildingsWidget::TownBuildingsWidget(CGTownInstance & t, QWidget *parent) : ui->treeView->setModel(&model); //ui->treeView->setColumnCount(3); model.setHorizontalHeaderLabels(QStringList() << QStringLiteral("Type") << QStringLiteral("Enabled") << QStringLiteral("Built")); - + connect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); //setAttribute(Qt::WA_DeleteOnClose); } @@ -196,12 +196,12 @@ std::set TownBuildingsWidget::getBuildingsFromModel(int modelColumn, std::set TownBuildingsWidget::getForbiddenBuildings() { - return getBuildingsFromModel(1, Qt::Unchecked); + return getBuildingsFromModel(Column::ENABLED, Qt::Unchecked); } std::set TownBuildingsWidget::getBuiltBuildings() { - return getBuildingsFromModel(2, Qt::Checked); + return getBuildingsFromModel(Column::BUILT, Qt::Checked); } void TownBuildingsWidget::on_treeView_expanded(const QModelIndex &index) @@ -215,6 +215,37 @@ void TownBuildingsWidget::on_treeView_collapsed(const QModelIndex &index) } +void TownBuildingsWidget::setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState) { + auto sibling = item->model()->sibling(item->row(), column, item->index()); + model.itemFromIndex(sibling)->setCheckState(checkState); +} + +void TownBuildingsWidget::onItemChanged(QStandardItem * item) { + disconnect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); + auto rowFirstColumnIndex = item->model()->sibling(item->row(), Column::TYPE, item->index()); + QStandardItem * nextRow = model.itemFromIndex(rowFirstColumnIndex); + if (item->checkState() == Qt::Checked) { + while (nextRow) { + setRowColumnCheckState(nextRow, Column(item->column()), Qt::Checked); + if (item->column() == Column::BUILT) { + setRowColumnCheckState(nextRow, Column::ENABLED, Qt::Checked); + } + nextRow = nextRow->parent(); + + } + } + else if (item->checkState() == Qt::Unchecked) { + while (nextRow) { + setRowColumnCheckState(nextRow, Column(item->column()), Qt::Unchecked); + if (item->column() == Column::ENABLED) { + setRowColumnCheckState(nextRow, Column::BUILT, Qt::Unchecked); + } + nextRow = nextRow->child(0, Column::TYPE); + } + } + connect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); +} + TownBuildingsDelegate::TownBuildingsDelegate(CGTownInstance & t): town(t), QStyledItemDelegate() { } diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index 90629f8be..eedbe6c4d 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -26,9 +26,13 @@ class TownBuildingsWidget : public QDialog QStandardItem * addBuilding(const CTown & ctown, int bId, std::set & remaining); public: + enum Column + { + TYPE, ENABLED, BUILT + }; explicit TownBuildingsWidget(CGTownInstance &, QWidget *parent = nullptr); ~TownBuildingsWidget(); - + void addBuildings(const CTown & ctown); std::set getForbiddenBuildings(); std::set getBuiltBuildings(); @@ -38,9 +42,12 @@ private slots: void on_treeView_collapsed(const QModelIndex &index); + void onItemChanged(QStandardItem * item); + private: std::set getBuildingsFromModel(int modelColumn, Qt::CheckState checkState); - + void setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState); + Ui::TownBuildingsWidget *ui; CGTownInstance & town; mutable QStandardItemModel model; From 5c3c6d4baaa554bce5b1894205136a5d40e19e9c Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 8 Jun 2024 23:13:17 +0200 Subject: [PATCH 03/76] map editor: Add bulk actions for buildings in TownBulidingsWidget --- mapeditor/inspector/townbuildingswidget.cpp | 40 +++++++++++++++++++++ mapeditor/inspector/townbuildingswidget.h | 9 +++++ mapeditor/inspector/townbuildingswidget.ui | 36 +++++++++++++++++-- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 7c7c99b48..4318c331b 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -214,12 +214,52 @@ void TownBuildingsWidget::on_treeView_collapsed(const QModelIndex &index) ui->treeView->resizeColumnToContents(0); } +void TownBuildingsWidget::on_buildAll_clicked() +{ + setAllRowsColumnCheckState(Column::BUILT, Qt::Checked); +} + +void TownBuildingsWidget::on_demolishAll_clicked() +{ + setAllRowsColumnCheckState(Column::BUILT, Qt::Unchecked); +} + +void TownBuildingsWidget::on_enableAll_clicked() +{ + setAllRowsColumnCheckState(Column::ENABLED, Qt::Checked); +} + +void TownBuildingsWidget::on_disableAll_clicked() +{ + setAllRowsColumnCheckState(Column::ENABLED, Qt::Unchecked); +} + void TownBuildingsWidget::setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState) { auto sibling = item->model()->sibling(item->row(), column, item->index()); model.itemFromIndex(sibling)->setCheckState(checkState); } +void TownBuildingsWidget::setAllRowsColumnCheckState(Column column, Qt::CheckState checkState) +{ + std::vector stack; + stack.push_back(QModelIndex()); + while (!stack.empty()) + { + auto parentIndex = stack.back(); + stack.pop_back(); + for (int i = 0; i < model.rowCount(parentIndex); ++i) + { + QModelIndex index = model.index(i, column, parentIndex); + if (auto* item = model.itemFromIndex(index)) + item->setCheckState(checkState); + index = model.index(i, 0, parentIndex); + if (model.hasChildren(index)) + stack.push_back(index); + } + } +} + void TownBuildingsWidget::onItemChanged(QStandardItem * item) { disconnect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); auto rowFirstColumnIndex = item->model()->sibling(item->row(), Column::TYPE, item->index()); diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index eedbe6c4d..d0e5d0e96 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -42,11 +42,20 @@ private slots: void on_treeView_collapsed(const QModelIndex &index); + void on_buildAll_clicked(); + + void on_demolishAll_clicked(); + + void on_enableAll_clicked(); + + void on_disableAll_clicked(); + void onItemChanged(QStandardItem * item); private: std::set getBuildingsFromModel(int modelColumn, Qt::CheckState checkState); void setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState); + void setAllRowsColumnCheckState(Column column, Qt::CheckState checkState); Ui::TownBuildingsWidget *ui; CGTownInstance & town; diff --git a/mapeditor/inspector/townbuildingswidget.ui b/mapeditor/inspector/townbuildingswidget.ui index 86bbaf54e..472093960 100644 --- a/mapeditor/inspector/townbuildingswidget.ui +++ b/mapeditor/inspector/townbuildingswidget.ui @@ -9,7 +9,7 @@ 0 0 - 480 + 580 280 @@ -21,7 +21,7 @@ - 480 + 580 280 @@ -45,6 +45,38 @@ + + + + + + Build all + + + + + + + Demolish all + + + + + + + Enable all + + + + + + + Disable all + + + + + From db1a78003000d3f16b0c05f9a8453b2e55308d2e Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 8 Jun 2024 23:32:58 +0200 Subject: [PATCH 04/76] map editor: fix unchecking when there is more then one child biulding --- mapeditor/inspector/townbuildingswidget.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 4318c331b..96c0b297d 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -275,12 +275,21 @@ void TownBuildingsWidget::onItemChanged(QStandardItem * item) { } } else if (item->checkState() == Qt::Unchecked) { - while (nextRow) { + std::vector stack; + stack.push_back(nextRow); + while (!stack.empty()) { + nextRow = stack.back(); + stack.pop_back(); setRowColumnCheckState(nextRow, Column(item->column()), Qt::Unchecked); if (item->column() == Column::ENABLED) { setRowColumnCheckState(nextRow, Column::BUILT, Qt::Unchecked); } - nextRow = nextRow->child(0, Column::TYPE); + if (nextRow->hasChildren()) { + for (int i = 0; i < nextRow->rowCount(); ++i) { + stack.push_back(nextRow->child(i, Column::TYPE)); + } + } + } } connect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); From 7aca2efb3577f1ecfe1fb2f304db8f4d4ad69738 Mon Sep 17 00:00:00 2001 From: godric3 Date: Fri, 12 Jul 2024 21:18:43 +0200 Subject: [PATCH 05/76] map editor: Allow to customize town spells --- lib/gameState/CGameState.cpp | 2 +- mapeditor/CMakeLists.txt | 3 + mapeditor/inspector/inspector.cpp | 2 + mapeditor/inspector/townspellswidget.cpp | 172 +++++++++++++ mapeditor/inspector/townspellswidget.h | 57 +++++ mapeditor/inspector/townspellswidget.ui | 304 +++++++++++++++++++++++ 6 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 mapeditor/inspector/townspellswidget.cpp create mode 100644 mapeditor/inspector/townspellswidget.h create mode 100644 mapeditor/inspector/townspellswidget.ui diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index fca3b3073..0c503effd 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -871,7 +871,7 @@ void CGameState::initTowns() } //init spells vti->spells.resize(GameConstants::SPELL_LEVELS); - + vti->possibleSpells -= SpellID::PRESET; for(ui32 z=0; zobligatorySpells.size();z++) { const auto * s = vti->obligatorySpells[z].toSpell(); diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index cd0ca7b35..eff5dfde7 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -29,6 +29,7 @@ set(editor_SRCS validator.cpp inspector/inspector.cpp inspector/townbuildingswidget.cpp + inspector/townspellswidget.cpp inspector/armywidget.cpp inspector/messagewidget.cpp inspector/rewardswidget.cpp @@ -70,6 +71,7 @@ set(editor_HEADERS validator.h inspector/inspector.h inspector/townbuildingswidget.h + inspector/townspellswidget.h inspector/armywidget.h inspector/messagewidget.h inspector/rewardswidget.h @@ -98,6 +100,7 @@ set(editor_FORMS playerparams.ui validator.ui inspector/townbuildingswidget.ui + inspector/townspellswidget.ui inspector/armywidget.ui inspector/messagewidget.ui inspector/rewardswidget.ui diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index e878f06fa..3400e1579 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -21,6 +21,7 @@ #include "../lib/constants/StringConstants.h" #include "townbuildingswidget.h" +#include "townspellswidget.h" #include "armywidget.h" #include "messagewidget.h" #include "rewardswidget.h" @@ -342,6 +343,7 @@ void Inspector::updateProperties(CGTownInstance * o) auto * delegate = new TownBuildingsDelegate(*o); addProperty("Buildings", PropertyEditorPlaceholder(), delegate, false); + addProperty("Spells", PropertyEditorPlaceholder(), new TownSpellsDelegate(*o), false); } void Inspector::updateProperties(CGArtifact * o) diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp new file mode 100644 index 000000000..f4c7388b4 --- /dev/null +++ b/mapeditor/inspector/townspellswidget.cpp @@ -0,0 +1,172 @@ +/* + * townspellswidget.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 "townspellswidget.h" +#include "ui_townspellswidget.h" +#include "inspector.h" +#include "../../lib/constants/StringConstants.h" +#include "../../lib/spells/CSpellHandler.h" + +TownSpellsWidget::TownSpellsWidget(CGTownInstance & town, QWidget * parent) : + QDialog(parent), + ui(new Ui::TownSpellsWidget), + town(town) +{ + ui->setupUi(this); + BuildingID mageGuilds[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; + for (int i = 0; i < GameConstants::SPELL_LEVELS; i++) + { + ui->tabWidget->setTabEnabled(i, vstd::contains(town.getTown()->buildings, mageGuilds[i])); + } +} + +TownSpellsWidget::~TownSpellsWidget() +{ + delete ui; +} + + +void TownSpellsWidget::obtainData() +{ + initSpellLists(); + if (vstd::contains(town.possibleSpells, SpellID::PRESET)) { + ui->customizeSpells->setChecked(true); + } + else + { + ui->customizeSpells->setChecked(false); + ui->tabWidget->setEnabled(false); + } +} + +void TownSpellsWidget::resetSpells() +{ + town.possibleSpells.clear(); + town.obligatorySpells.clear(); + for (auto spell : VLC->spellh->objects) + { + if (!spell->isSpecial() && !spell->isCreatureAbility()) + town.possibleSpells.push_back(spell->id); + } +} + +void TownSpellsWidget::initSpellLists() +{ + QListWidget * possibleSpellLists[] = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; + QListWidget * requiredSpellLists[] = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; + auto spells = VLC->spellh->objects; + for (int i = 0; i < GameConstants::SPELL_LEVELS; i++) + { + std::vector> spellsByLevel; + auto getSpellsByLevel = [i](auto spell) { + return spell->getLevel() == i + 1 && !spell->isSpecial() && !spell->isCreatureAbility(); + }; + vstd::copy_if(spells, std::back_inserter(spellsByLevel), getSpellsByLevel); + possibleSpellLists[i]->clear(); + requiredSpellLists[i]->clear(); + for (auto spell : spellsByLevel) + { + auto * possibleItem = new QListWidgetItem(QString::fromStdString(spell->getNameTranslated())); + possibleItem->setData(Qt::UserRole, QVariant::fromValue(spell->getIndex())); + possibleItem->setFlags(possibleItem->flags() | Qt::ItemIsUserCheckable); + possibleItem->setCheckState(vstd::contains(town.possibleSpells, spell->getId()) ? Qt::Checked : Qt::Unchecked); + possibleSpellLists[i]->addItem(possibleItem); + + auto * requiredItem = new QListWidgetItem(QString::fromStdString(spell->getNameTranslated())); + requiredItem->setData(Qt::UserRole, QVariant::fromValue(spell->getIndex())); + requiredItem->setFlags(requiredItem->flags() | Qt::ItemIsUserCheckable); + requiredItem->setCheckState(vstd::contains(town.obligatorySpells, spell->getId()) ? Qt::Checked : Qt::Unchecked); + requiredSpellLists[i]->addItem(requiredItem); + } + } +} + +void TownSpellsWidget::commitChanges() +{ + if (!ui->tabWidget->isEnabled()) + { + resetSpells(); + return; + } + town.possibleSpells.clear(); + town.obligatorySpells.clear(); + town.possibleSpells.push_back(SpellID::PRESET); + QListWidget * possibleSpellLists[] = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; + QListWidget * requiredSpellLists[] = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; + for (auto spellList : possibleSpellLists) + { + for (int i = 0; i < spellList->count(); i++) + { + auto * item = spellList->item(i); + if (item->checkState() == Qt::Checked) + { + town.possibleSpells.push_back(item->data(Qt::UserRole).toInt()); + } + } + } + for (auto spellList : requiredSpellLists) + { + for (int i = 0; i < spellList->count(); i++) + { + auto * item = spellList->item(i); + if (item->checkState() == Qt::Checked) + { + town.obligatorySpells.push_back(item->data(Qt::UserRole).toInt()); + } + } + } +} + +void TownSpellsWidget::on_customizeSpells_toggled(bool checked) +{ + if (checked) + { + town.possibleSpells.push_back(SpellID::PRESET); + } + else + { + resetSpells(); + } + ui->tabWidget->setEnabled(checked); + initSpellLists(); +} + +TownSpellsDelegate::TownSpellsDelegate(CGTownInstance & town) : town(town), QStyledItemDelegate() +{ +} + +QWidget * TownSpellsDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + return new TownSpellsWidget(town, parent); +} + +void TownSpellsDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const +{ + if (auto * ed = qobject_cast(editor)) + { + ed->obtainData(); + } + else + { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void TownSpellsDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const +{ + if (auto * ed = qobject_cast(editor)) + { + ed->commitChanges(); + } + else + { + QStyledItemDelegate::setModelData(editor, model, index); + } +} \ No newline at end of file diff --git a/mapeditor/inspector/townspellswidget.h b/mapeditor/inspector/townspellswidget.h new file mode 100644 index 000000000..de7018bed --- /dev/null +++ b/mapeditor/inspector/townspellswidget.h @@ -0,0 +1,57 @@ +/* + * townspellswidget.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/mapObjects/CGTownInstance.h" + +namespace Ui { + class TownSpellsWidget; +} + + +class TownSpellsWidget : public QDialog +{ + Q_OBJECT + +public: + explicit TownSpellsWidget(CGTownInstance &, QWidget * parent = nullptr); + ~TownSpellsWidget(); + + void obtainData(); + void commitChanges(); + +private slots: + void on_customizeSpells_toggled(bool checked); + +private: + Ui::TownSpellsWidget * ui; + + CGTownInstance & town; + + void resetSpells(); + void initSpellLists(); +}; + +class TownSpellsDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + using QStyledItemDelegate::QStyledItemDelegate; + + TownSpellsDelegate(CGTownInstance&); + + QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex& index) const override; + void setEditorData(QWidget * editor, const QModelIndex & index) const override; + void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; + +private: + CGTownInstance& town; +}; diff --git a/mapeditor/inspector/townspellswidget.ui b/mapeditor/inspector/townspellswidget.ui new file mode 100644 index 000000000..40156178b --- /dev/null +++ b/mapeditor/inspector/townspellswidget.ui @@ -0,0 +1,304 @@ + + + TownSpellsWidget + + + Qt::NonModal + + + + 0 + 0 + 600 + 480 + + + + + 0 + 0 + + + + + 600 + 480 + + + + Spells + + + Qt::LeftToRight + + + true + + + + 10 + + + 5 + + + + + Customize spells + + + false + + + + + + + + 0 + 0 + + + + 0 + + + true + + + + + 0 + 0 + + + + Level 1 + + + + 12 + + + 12 + + + 12 + + + + + + + Spell that may appear in mage guild + + + + + + + Spell that must appear in mage guild + + + + + + + + + + + + + + + + + 0 + 0 + + + + Level 2 + + + + 12 + + + 12 + + + 12 + + + + + + + Spell that may appear in mage guild + + + + + + + Spell that must appear in mage guild + + + + + + + + + + + + + + + + + 0 + 0 + + + + Level 3 + + + + 12 + + + 12 + + + 12 + + + + + + + Spell that may appear in mage guild + + + + + + + Spell that must appear in mage guild + + + + + + + + + + + + + + + + + 0 + 0 + + + + Level 4 + + + + 12 + + + 12 + + + 12 + + + + + + + Spell that may appear in mage guild + + + + + + + Spell that must appear in mage guild + + + + + + + + + + + + + + + + + 0 + 0 + + + + Level 5 + + + + 12 + + + 12 + + + 12 + + + + + + + Spell that may appear in mage guild + + + + + + + Spell that must appear in mage guild + + + + + + + + + + + + + + + + + + + + From 5578346dac2de9f987f22e45852b9d47d36cffaf Mon Sep 17 00:00:00 2001 From: godric3 Date: Sun, 14 Jul 2024 19:06:39 +0200 Subject: [PATCH 06/76] map editor: Allow to customize town events --- lib/mapObjects/CGTownInstance.cpp | 16 ++ mapeditor/CMakeLists.txt | 6 + mapeditor/inspector/inspector.cpp | 2 + mapeditor/inspector/townevent.cpp | 320 +++++++++++++++++++++++ mapeditor/inspector/townevent.h | 53 ++++ mapeditor/inspector/townevent.ui | 263 +++++++++++++++++++ mapeditor/inspector/towneventswidget.cpp | 179 +++++++++++++ mapeditor/inspector/towneventswidget.h | 59 +++++ mapeditor/inspector/towneventswidget.ui | 93 +++++++ mapeditor/mapsettings/eventsettings.h | 3 + 10 files changed, 994 insertions(+) create mode 100644 mapeditor/inspector/townevent.cpp create mode 100644 mapeditor/inspector/townevent.h create mode 100644 mapeditor/inspector/townevent.ui create mode 100644 mapeditor/inspector/towneventswidget.cpp create mode 100644 mapeditor/inspector/towneventswidget.h create mode 100644 mapeditor/inspector/towneventswidget.ui diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 268664c50..386cc08ba 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1207,6 +1207,22 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) { handler.serializeIdArray( "possibleSpells", possibleSpells); handler.serializeIdArray( "obligatorySpells", obligatorySpells); + + if (handler.saving) + { + auto eventsHandler = handler.enterArray("events"); + std::vector temp(events.begin(), events.end()); + eventsHandler.serializeStruct(temp); + } + else + { + auto eventsHandler = handler.enterArray("events"); + std::vector temp; + eventsHandler.serializeStruct(temp); + events.clear(); + events.insert(events.begin(), temp.begin(), temp.end()); + } + } } diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index eff5dfde7..c6e5ff0a3 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -29,6 +29,8 @@ set(editor_SRCS validator.cpp inspector/inspector.cpp inspector/townbuildingswidget.cpp + inspector/townevent.cpp + inspector/towneventswidget.cpp inspector/townspellswidget.cpp inspector/armywidget.cpp inspector/messagewidget.cpp @@ -71,6 +73,8 @@ set(editor_HEADERS validator.h inspector/inspector.h inspector/townbuildingswidget.h + inspector/townevent.h + inspector/towneventswidget.h inspector/townspellswidget.h inspector/armywidget.h inspector/messagewidget.h @@ -100,6 +104,8 @@ set(editor_FORMS playerparams.ui validator.ui inspector/townbuildingswidget.ui + inspector/townevent.ui + inspector/towneventswidget.ui inspector/townspellswidget.ui inspector/armywidget.ui inspector/messagewidget.ui diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 3400e1579..971c6676d 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -21,6 +21,7 @@ #include "../lib/constants/StringConstants.h" #include "townbuildingswidget.h" +#include "towneventswidget.h" #include "townspellswidget.h" #include "armywidget.h" #include "messagewidget.h" @@ -344,6 +345,7 @@ void Inspector::updateProperties(CGTownInstance * o) auto * delegate = new TownBuildingsDelegate(*o); addProperty("Buildings", PropertyEditorPlaceholder(), delegate, false); addProperty("Spells", PropertyEditorPlaceholder(), new TownSpellsDelegate(*o), false); + addProperty("Events", PropertyEditorPlaceholder(), new TownEventsDelegate(*o, controller), false); } void Inspector::updateProperties(CGArtifact * o) diff --git a/mapeditor/inspector/townevent.cpp b/mapeditor/inspector/townevent.cpp new file mode 100644 index 000000000..dee472736 --- /dev/null +++ b/mapeditor/inspector/townevent.cpp @@ -0,0 +1,320 @@ +/* + * townevent.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 "townbuildingswidget.h" +#include "townevent.h" +#include "ui_townevent.h" +#include "../../lib/constants/NumericConstants.h" +#include "../../lib/constants/StringConstants.h" + +TownEvent::TownEvent(CGTownInstance & t, QListWidgetItem * item, QWidget * parent) : + QDialog(parent), + ui(new Ui::TownEvent), + town(t), + item(item) +{ + ui->setupUi(this); + + ui->buildingsTree->setModel(&buildingsModel); + + params = item->data(Qt::UserRole).toMap(); + ui->eventFirstOccurrence->setMinimum(1); + ui->eventFirstOccurrence->setMaximum(999); + ui->eventRepeatAfter->setMaximum(999); + ui->eventNameText->setText(params.value("name").toString()); + ui->eventMessageText->setPlainText(params.value("message").toString()); + ui->eventAffectsCpu->setChecked(params.value("computerAffected").toBool()); + ui->eventAffectsHuman->setChecked(params.value("humanAffected").toBool()); + ui->eventFirstOccurrence->setValue(params.value("firstOccurrence").toInt()+1); + ui->eventRepeatAfter->setValue(params.value("nextOccurrence").toInt()); + + initPlayers(); + initResources(); + initBuildings(); + initCreatures(); + + show(); +} + +TownEvent::~TownEvent() +{ + delete ui; +} + +void TownEvent::initPlayers() +{ + for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) + { + bool isAffected = (1 << i) & params.value("players").toInt(); + auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i])); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked); + ui->playersAffected->addItem(item); + } +} + +void TownEvent::initResources() +{ + ui->resourcesTable->setRowCount(GameConstants::RESOURCE_QUANTITY); + auto resourcesMap = params.value("resources").toMap(); + for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) + { + auto name = QString::fromStdString(GameConstants::RESOURCE_NAMES[i]); + int val = resourcesMap.value(name).toInt(); + ui->resourcesTable->setItem(i, 0, new QTableWidgetItem(name)); + + QSpinBox * edit = new QSpinBox(ui->resourcesTable); + edit->setMaximum(i == GameResID::GOLD ? 999999 : 999); + edit->setMinimum(i == GameResID::GOLD ? -999999 : -999); + edit->setSingleStep(i == GameResID::GOLD ? 100 : 1); + edit->setValue(val); + + ui->resourcesTable->setCellWidget(i, 1, edit); + } +} + +void TownEvent::initBuildings() +{ + auto * ctown = town.town; + if (!ctown) + ctown = VLC->townh->randomTown; + if (!ctown) + throw std::runtime_error("No Town defined for type selected"); + auto allBuildings = ctown->getAllBuildings(); + while (!allBuildings.empty()) + { + addBuilding(*ctown, *allBuildings.begin(), allBuildings); + } + ui->buildingsTree->resizeColumnToContents(0); + + connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); +} + +QStandardItem * TownEvent::addBuilding(const CTown& ctown, BuildingID buildingId, std::set& remaining) +{ + auto bId = buildingId.num; + const CBuilding * building = ctown.buildings.at(buildingId); + if (!building) + { + remaining.erase(bId); + return nullptr; + } + + QString name = tr(building->getNameTranslated().c_str()); + + if (name.isEmpty()) + name = QString::fromStdString(defaultBuildingIdConversion(buildingId)); + + QList checks; + + checks << new QStandardItem(name); + checks.back()->setData(bId, Qt::UserRole); + + checks << new QStandardItem; + checks.back()->setCheckable(true); + checks.back()->setCheckState(params["buildings"].toList().contains(bId) ? Qt::Checked : Qt::Unchecked); + checks.back()->setData(bId, Qt::UserRole); + + if (building->getBase() == buildingId) + { + buildingsModel.appendRow(checks); + } + else + { + QStandardItem * parent = nullptr; + std::vector stack; + stack.push_back(QModelIndex()); + while (!parent && !stack.empty()) + { + auto pindex = stack.back(); + stack.pop_back(); + for (int i = 0; i < buildingsModel.rowCount(pindex); ++i) + { + QModelIndex index = buildingsModel.index(i, 0, pindex); + if (building->upgrade.getNum() == buildingsModel.itemFromIndex(index)->data(Qt::UserRole).toInt()) + { + parent = buildingsModel.itemFromIndex(index); + break; + } + if (buildingsModel.hasChildren(index)) + stack.push_back(index); + } + } + + if (!parent) + parent = addBuilding(ctown, building->upgrade.getNum(), remaining); + + if (!parent) + { + remaining.erase(bId); + return nullptr; + } + + parent->appendRow(checks); + } + + remaining.erase(bId); + return checks.front(); +} + +void TownEvent::initCreatures() +{ + auto creatures = params.value("creatures").toList(); + auto * ctown = town.town; + for (int i = 0; i < 7; ++i) + { + QString creatureNames; + if (!ctown) + { + creatureNames.append(QString("Creature %1 / Creature %1 Upgrade").arg(i + 1)); + } + else + { + auto creaturesOnLevel = ctown->creatures.at(i); + for (auto& creature : creaturesOnLevel) + { + auto cre = VLC->creatures()->getById(creature); + auto creatureName = QString::fromStdString(cre->getNameSingularTranslated()); + creatureNames.append(creatureNames.isEmpty() ? creatureName : " / " + creatureName); + } + } + auto * item = new QTableWidgetItem(); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + item->setText(creatureNames); + ui->creaturesTable->setItem(i, 0, item); + + auto creatureNumber = creatures.size() > i ? creatures.at(i).toInt() : 0; + QSpinBox* edit = new QSpinBox(ui->creaturesTable); + edit->setValue(creatureNumber); + edit->setMaximum(999999); + ui->creaturesTable->setCellWidget(i, 1, edit); + + } + ui->creaturesTable->resizeColumnToContents(0); +} + +void TownEvent::on_TownEvent_finished(int result) +{ + QVariantMap descriptor; + descriptor["name"] = ui->eventNameText->text(); + descriptor["message"] = ui->eventMessageText->toPlainText(); + descriptor["humanAffected"] = QVariant::fromValue(ui->eventAffectsHuman->isChecked()); + descriptor["computerAffected"] = QVariant::fromValue(ui->eventAffectsCpu->isChecked()); + descriptor["firstOccurrence"] = QVariant::fromValue(ui->eventFirstOccurrence->value()-1); + descriptor["nextOccurrence"] = QVariant::fromValue(ui->eventRepeatAfter->value()); + descriptor["players"] = playersToVariant(); + descriptor["resources"] = resourcesToVariant(); + descriptor["buildings"] = buildingsToVariant(); + descriptor["creatures"] = creaturesToVariant(); + + item->setData(Qt::UserRole, descriptor); + auto itemText = QString::fromStdString("Day %1 - %2").arg(ui->eventFirstOccurrence->value(), 3).arg(ui->eventNameText->text()); + item->setText(itemText); +} + +QVariant TownEvent::playersToVariant() +{ + int players = 0; + for (int i = 0; i < ui->playersAffected->count(); ++i) + { + auto * item = ui->playersAffected->item(i); + if (item->checkState() == Qt::Checked) + players |= 1 << i; + } + return QVariant::fromValue(players); +} + +QVariantMap TownEvent::resourcesToVariant() +{ + auto res = item->data(Qt::UserRole).toMap().value("resources").toMap(); + for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) + { + auto * itemType = ui->resourcesTable->item(i, 0); + auto * itemQty = static_cast (ui->resourcesTable->cellWidget(i, 1)); + + res[itemType->text()] = QVariant::fromValue(itemQty->value()); + } + return res; +} + +QVariantList TownEvent::buildingsToVariant() +{ + QVariantList buildingsList; + std::vector stack; + stack.push_back(QModelIndex()); + while (!stack.empty()) + { + auto pindex = stack.back(); + stack.pop_back(); + for (int i = 0; i < buildingsModel.rowCount(pindex); ++i) + { + QModelIndex index = buildingsModel.index(i, 1, pindex); + if (auto * item = buildingsModel.itemFromIndex(index)) + if (item->checkState() == Qt::Checked) + buildingsList.push_back(item->data(Qt::UserRole)); + index = buildingsModel.index(i, 0, pindex); + if (buildingsModel.hasChildren(index)) + stack.push_back(index); + } + } + return buildingsList; +} + +QVariantList TownEvent::creaturesToVariant() +{ + QVariantList creaturesList; + for (int i = 0; i < 7; ++i) + { + auto * item = static_cast(ui->creaturesTable->cellWidget(i, 1)); + creaturesList.push_back(item->value()); + } + return creaturesList; +} + +void TownEvent::on_okButton_clicked() +{ + close(); +} + +void TownEvent::setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState) { + auto sibling = item->model()->sibling(item->row(), column, item->index()); + buildingsModel.itemFromIndex(sibling)->setCheckState(checkState); +} + +void TownEvent::onItemChanged(QStandardItem * item) +{ + disconnect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); + auto rowFirstColumnIndex = item->model()->sibling(item->row(), 0, item->index()); + QStandardItem * nextRow = buildingsModel.itemFromIndex(rowFirstColumnIndex); + if (item->checkState() == Qt::Checked) { + while (nextRow) { + setRowColumnCheckState(nextRow,item->column(), Qt::Checked); + nextRow = nextRow->parent(); + + } + } + else if (item->checkState() == Qt::Unchecked) { + std::vector stack; + stack.push_back(nextRow); + while (!stack.empty()) { + nextRow = stack.back(); + stack.pop_back(); + setRowColumnCheckState(nextRow, item->column(), Qt::Unchecked); + if (nextRow->hasChildren()) { + for (int i = 0; i < nextRow->rowCount(); ++i) { + stack.push_back(nextRow->child(i, 0)); + } + } + + } + } + connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); +} diff --git a/mapeditor/inspector/townevent.h b/mapeditor/inspector/townevent.h new file mode 100644 index 000000000..e614dc76b --- /dev/null +++ b/mapeditor/inspector/townevent.h @@ -0,0 +1,53 @@ +/* + * townevent.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 "../StdInc.h" +#include +#include "../lib/mapObjects/CGTownInstance.h" + +namespace Ui { + class TownEvent; +} + +class TownEvent : public QDialog +{ + Q_OBJECT + +public: + explicit TownEvent(CGTownInstance & town, QListWidgetItem * item, QWidget * parent); + ~TownEvent(); + + +private slots: + void onItemChanged(QStandardItem * item); + void on_TownEvent_finished(int result); + void on_okButton_clicked(); + void setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState); + +private: + void initPlayers(); + void initResources(); + void initBuildings(); + void initCreatures(); + + QVariant playersToVariant(); + QVariantMap resourcesToVariant(); + QVariantList buildingsToVariant(); + QVariantList creaturesToVariant(); + + QStandardItem * addBuilding(const CTown & ctown, BuildingID bId, std::set & remaining); + + Ui::TownEvent * ui; + CGTownInstance & town; + QListWidgetItem * item; + QMap params; + mutable QStandardItemModel buildingsModel; +}; \ No newline at end of file diff --git a/mapeditor/inspector/townevent.ui b/mapeditor/inspector/townevent.ui new file mode 100644 index 000000000..3e7070d1d --- /dev/null +++ b/mapeditor/inspector/townevent.ui @@ -0,0 +1,263 @@ + + + TownEvent + + + Qt::ApplicationModal + + + + 0 + 0 + 693 + 525 + + + + + 0 + 0 + + + + Town event + + + + 0 + + + 3 + + + 3 + + + + + 0 + + + + General + + + + + 9 + 9 + 511 + 351 + + + + + + + Event name + + + + + + + Type event message text + + + + + + + + + 10 + 370 + 511 + 61 + + + + + + + + + Day of first occurrence + + + + + + + + + + + + + + Repeat after (0 = no repeat) + + + + + + + + + + + + + + 529 + 9 + 141 + 421 + + + + + + + Affected players + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + + + + + affects human + + + + + + + + + affects AI + + + + + + + + + + + Resources + + + + + 10 + 10 + 661 + 421 + + + + + 0 + 0 + + + + 2 + + + false + + + false + + + + + + + + Buildings + + + + + 10 + 10 + 661 + 421 + + + + false + + + + + + Creatures + + + + + 10 + 10 + 661 + 421 + + + + 7 + + + 2 + + + false + + + false + + + + + + + + + + + + + + + + + + OK + + + + + + + + diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp new file mode 100644 index 000000000..5ff87ce9c --- /dev/null +++ b/mapeditor/inspector/towneventswidget.cpp @@ -0,0 +1,179 @@ +/* + * towneventswidget.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 "towneventswidget.h" +#include "ui_towneventswidget.h" +#include "townevent.h" +#include "mapsettings/eventsettings.h" +#include "../../lib/constants/NumericConstants.h" +#include "../../lib/constants/StringConstants.h" + +TownEventsWidget::TownEventsWidget(CGTownInstance & town, QWidget * parent) : + QDialog(parent), + ui(new Ui::TownEventsWidget), + town(town) +{ + ui->setupUi(this); +} + +TownEventsWidget::~TownEventsWidget() +{ + delete ui; +} + +QVariant toVariant(const std::set & buildings) +{ + QVariantList result; + for (auto b : buildings) + result.push_back(QVariant::fromValue(b.num)); + return result; +} + +QVariant toVariant(const std::vector & creatures) +{ + QVariantList result; + for (auto c : creatures) + result.push_back(QVariant::fromValue(c)); + return result; +} + +std::set buildingsFromVariant(const QVariant& v) +{ + std::set result; + for (auto r : v.toList()) { + result.insert(BuildingID(r.toInt())); + } + return result; +} + +std::vector creaturesFromVariant(const QVariant& v) +{ + std::vector result; + for (auto r : v.toList()) { + result.push_back(r.toInt()); + } + return result; +} + +QVariant toVariant(const CCastleEvent& event) +{ + QVariantMap result; + result["name"] = QString::fromStdString(event.name); + result["message"] = QString::fromStdString(event.message.toString()); + result["players"] = QVariant::fromValue(event.players); + result["humanAffected"] = QVariant::fromValue(event.humanAffected); + result["computerAffected"] = QVariant::fromValue(event.computerAffected); + result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence); + result["nextOccurrence"] = QVariant::fromValue(event.nextOccurrence); + result["resources"] = toVariant(event.resources); + result["buildings"] = toVariant(event.buildings); + result["creatures"] = toVariant(event.creatures); + + return QVariant(result); +} + +CCastleEvent eventFromVariant(CMapHeader& map, CGTownInstance& town, const QVariant& variant) +{ + CCastleEvent result; + auto v = variant.toMap(); + result.name = v.value("name").toString().toStdString(); + result.message.appendTextID(mapRegisterLocalizedString("map", map, TextIdentifier("town", town.instanceName, "event", result.name, "message"), v.value("message").toString().toStdString())); + result.players = v.value("players").toInt(); + result.humanAffected = v.value("humanAffected").toInt(); + result.computerAffected = v.value("computerAffected").toInt(); + result.firstOccurrence = v.value("firstOccurrence").toInt(); + result.nextOccurrence = v.value("nextOccurrence").toInt(); + result.resources = resourcesFromVariant(v.value("resources")); + result.buildings = buildingsFromVariant(v.value("buildings")); + result.creatures = creaturesFromVariant(v.value("creatures")); + return result; +} + +void TownEventsWidget::obtainData() +{ + for (const auto & event : town.events) + { + auto eventName = QString::fromStdString(event.name); + auto itemText = QString::fromStdString("Day %1 - %2").arg(event.firstOccurrence+1, 3).arg(eventName); + + auto * item = new QListWidgetItem(itemText); + item->setData(Qt::UserRole, toVariant(event)); + ui->eventsList->addItem(item); + } +} + +void TownEventsWidget::commitChanges(MapController& controller) +{ + town.events.clear(); + for (int i = 0; i < ui->eventsList->count(); ++i) + { + const auto * item = ui->eventsList->item(i); + town.events.push_back(eventFromVariant(*controller.map(), town, item->data(Qt::UserRole))); + } +} + +void TownEventsWidget::on_timedEventAdd_clicked() +{ + CCastleEvent event; + event.name = tr("New event").toStdString(); + auto* item = new QListWidgetItem(QString::fromStdString(event.name)); + item->setData(Qt::UserRole, toVariant(event)); + ui->eventsList->addItem(item); + on_eventsList_itemActivated(item); +} + +void TownEventsWidget::on_timedEventRemove_clicked() +{ + if (auto* item = ui->eventsList->currentItem()) + ui->eventsList->takeItem(ui->eventsList->row(item)); +} + +void TownEventsWidget::on_eventsList_itemActivated(QListWidgetItem* item) +{ + new TownEvent(town, item, parentWidget()); +} + +void TownEventsWidget::onItemChanged(QStandardItem * item) +{ +} + +TownEventsDelegate::TownEventsDelegate(CGTownInstance & town, MapController & c) : town(town), controller(c), QStyledItemDelegate() +{ +} + +QWidget* TownEventsDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + return new TownEventsWidget(town, parent);; +} + +void TownEventsDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const +{ + if (auto * ed = qobject_cast(editor)) + { + ed->obtainData(); + } + else + { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void TownEventsDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const +{ + if (auto * ed = qobject_cast(editor)) + { + ed->commitChanges(controller); + } + else + { + QStyledItemDelegate::setModelData(editor, model, index); + } +} diff --git a/mapeditor/inspector/towneventswidget.h b/mapeditor/inspector/towneventswidget.h new file mode 100644 index 000000000..ada1ee7fd --- /dev/null +++ b/mapeditor/inspector/towneventswidget.h @@ -0,0 +1,59 @@ +/* + * towneventswidget.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 "../StdInc.h" +#include +#include "../lib/mapping/CMapDefines.h" +#include "../lib/mapObjects/CGTownInstance.h" +#include "../mapcontroller.h" + +namespace Ui { + class TownEventsWidget; +} + +class TownEventsWidget : public QDialog +{ + Q_OBJECT + +public: + explicit TownEventsWidget(CGTownInstance &, QWidget * parent = nullptr); + ~TownEventsWidget(); + + void obtainData(); + void commitChanges(MapController & controller); +private slots: + void onItemChanged(QStandardItem * item); + void on_timedEventAdd_clicked(); + void on_timedEventRemove_clicked(); + void on_eventsList_itemActivated(QListWidgetItem * item); + +private: + + Ui::TownEventsWidget * ui; + CGTownInstance & town; +}; + +class TownEventsDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + using QStyledItemDelegate::QStyledItemDelegate; + + TownEventsDelegate(CGTownInstance &, MapController &); + + QWidget* createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + void setEditorData(QWidget * editor, const QModelIndex & index) const override; + void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; + +private: + CGTownInstance & town; + MapController & controller; +}; \ No newline at end of file diff --git a/mapeditor/inspector/towneventswidget.ui b/mapeditor/inspector/towneventswidget.ui new file mode 100644 index 000000000..cecc34149 --- /dev/null +++ b/mapeditor/inspector/towneventswidget.ui @@ -0,0 +1,93 @@ + + + TownEventsWidget + + + Qt::ApplicationModal + + + + 0 + 0 + 691 + 462 + + + + + 0 + 0 + + + + + 400 + 400 + + + + Town events + + + + + + + + Timed events + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 90 + 0 + + + + Add + + + + + + + + 90 + 0 + + + + Remove + + + + + + + + + true + + + + + + + + diff --git a/mapeditor/mapsettings/eventsettings.h b/mapeditor/mapsettings/eventsettings.h index ef29f0308..991e09037 100644 --- a/mapeditor/mapsettings/eventsettings.h +++ b/mapeditor/mapsettings/eventsettings.h @@ -15,6 +15,9 @@ namespace Ui { class EventSettings; } +QVariant toVariant(const TResources & resources); +TResources resourcesFromVariant(const QVariant & v); + class EventSettings : public AbstractSettings { Q_OBJECT From c212b1bf36012b08ef97ef0c9f88145cb19c8b27 Mon Sep 17 00:00:00 2001 From: godric3 Date: Sun, 14 Jul 2024 21:00:08 +0200 Subject: [PATCH 07/76] map editor: update translation + polish translation --- mapeditor/inspector/townbuildingswidget.cpp | 2 +- mapeditor/inspector/townevent.cpp | 4 +- mapeditor/inspector/towneventswidget.cpp | 2 +- mapeditor/translation/chinese.ts | 232 +++++++++++++++++-- mapeditor/translation/czech.ts | 232 +++++++++++++++++-- mapeditor/translation/english.ts | 220 +++++++++++++++++- mapeditor/translation/french.ts | 236 ++++++++++++++++++-- mapeditor/translation/german.ts | 232 +++++++++++++++++-- mapeditor/translation/polish.ts | 232 +++++++++++++++++-- mapeditor/translation/portuguese.ts | 232 +++++++++++++++++-- mapeditor/translation/russian.ts | 232 +++++++++++++++++-- mapeditor/translation/spanish.ts | 232 +++++++++++++++++-- mapeditor/translation/ukrainian.ts | 232 +++++++++++++++++-- mapeditor/translation/vietnamese.ts | 232 +++++++++++++++++-- 14 files changed, 2327 insertions(+), 225 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 96c0b297d..6ceab8baf 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -76,7 +76,7 @@ TownBuildingsWidget::TownBuildingsWidget(CGTownInstance & t, QWidget *parent) : ui->setupUi(this); ui->treeView->setModel(&model); //ui->treeView->setColumnCount(3); - model.setHorizontalHeaderLabels(QStringList() << QStringLiteral("Type") << QStringLiteral("Enabled") << QStringLiteral("Built")); + model.setHorizontalHeaderLabels(QStringList() << tr("Type") << tr("Enabled") << tr("Built")); connect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); //setAttribute(Qt::WA_DeleteOnClose); } diff --git a/mapeditor/inspector/townevent.cpp b/mapeditor/inspector/townevent.cpp index dee472736..9516e0a2a 100644 --- a/mapeditor/inspector/townevent.cpp +++ b/mapeditor/inspector/townevent.cpp @@ -174,7 +174,7 @@ void TownEvent::initCreatures() QString creatureNames; if (!ctown) { - creatureNames.append(QString("Creature %1 / Creature %1 Upgrade").arg(i + 1)); + creatureNames.append(tr("Creature level %1 / Creature level %1 Upgrade").arg(i + 1)); } else { @@ -216,7 +216,7 @@ void TownEvent::on_TownEvent_finished(int result) descriptor["creatures"] = creaturesToVariant(); item->setData(Qt::UserRole, descriptor); - auto itemText = QString::fromStdString("Day %1 - %2").arg(ui->eventFirstOccurrence->value(), 3).arg(ui->eventNameText->text()); + auto itemText = tr("Day %1 - %2").arg(ui->eventFirstOccurrence->value(), 3).arg(ui->eventNameText->text()); item->setText(itemText); } diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index 5ff87ce9c..5a3967597 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -102,7 +102,7 @@ void TownEventsWidget::obtainData() for (const auto & event : town.events) { auto eventName = QString::fromStdString(event.name); - auto itemText = QString::fromStdString("Day %1 - %2").arg(event.firstOccurrence+1, 3).arg(eventName); + auto itemText = tr("Day %1 - %2").arg(event.firstOccurrence+1, 3).arg(eventName); auto * item = new QListWidgetItem(itemText); item->setData(Qt::UserRole, toVariant(event)); diff --git a/mapeditor/translation/chinese.ts b/mapeditor/translation/chinese.ts index 10de74c60..19b776ee9 100644 --- a/mapeditor/translation/chinese.ts +++ b/mapeditor/translation/chinese.ts @@ -715,7 +715,7 @@ MapView - + Can't place object 无法放置物体 @@ -889,38 +889,38 @@ 高级 - + Compliant 屈服的 - + Friendly 友善的 - + Aggressive 好斗的 - + Hostile 有敌意的 - + Savage 野蛮的 - - + + neutral 中立 - + UNFLAGGABLE 没有旗帜 @@ -1435,6 +1435,208 @@ Buildings 建筑 + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + 类型 + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + 通用 + + + + Event name + 事件名 + + + + Type event message text + 输入事件信息文本 + + + + Day of first occurrence + 首次发生天数 + + + + Repeat after (0 = no repeat) + 重复周期 (0 = 不重复) + + + + Affected players + 生效玩家 + + + + affects human + 人类玩家生效 + + + + affects AI + AI玩家生效 + + + + Resources + 资源 + + + + Buildings + 建筑 + + + + Creatures + 生物 + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + 计时事件 + + + + Add + 添加 + + + + Remove + 移除 + + + + Day %1 - %2 + + + + + New event + 新事件 + + + + TownSpellsWidget + + + Spells + 魔法 + + + + Customize spells + 自定义魔法 + + + + Level 1 + 1级 + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + 2级 + + + + Level 3 + 3级 + + + + Level 4 + 4级 + + + + Level 5 + 5级 + Translations @@ -1698,18 +1900,6 @@ Width 宽度 - - S (36x36) - 小(36x36) - - - M (72x72) - 中(72x72) - - - L (108x108) - 大(108x108) - XL (144x144) diff --git a/mapeditor/translation/czech.ts b/mapeditor/translation/czech.ts index c5ea0c7c5..285f83f78 100644 --- a/mapeditor/translation/czech.ts +++ b/mapeditor/translation/czech.ts @@ -715,7 +715,7 @@ MapView - + Can't place object Nelze umístit objekt @@ -889,38 +889,38 @@ Expert - + Compliant Ochotná - + Friendly Přátelská - + Aggressive Agresivní - + Hostile Nepřátelská - + Savage Brutální - - + + neutral neutrální - + UNFLAGGABLE NEOZNAČITELNÝ @@ -1435,6 +1435,208 @@ Buildings Budovy + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Druh + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + + + + + Event name + Název události + + + + Type event message text + Zadejte text zprávy události + + + + Day of first occurrence + Den prvního výskytu + + + + Repeat after (0 = no repeat) + Opakovat po (0 = bez opak.) + + + + Affected players + Ovlivnění hráči + + + + affects human + ovlivňuje lidi + + + + affects AI + ovlivňuje AI + + + + Resources + Zdroje + + + + Buildings + Budovy + + + + Creatures + Jednotky + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + Načasované události + + + + Add + Přidat + + + + Remove + Odebrat + + + + Day %1 - %2 + + + + + New event + Nová událost + + + + TownSpellsWidget + + + Spells + Kouzla + + + + Customize spells + Přizpůsobit kouzla + + + + Level 1 + Úroveň 1 + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + Úroveň 2 + + + + Level 3 + Úroveň 3 + + + + Level 4 + Úroveň 4 + + + + Level 5 + Úroveň 5 + Translations @@ -1698,18 +1900,6 @@ Width Šířka - - S (36x36) - S (36x36) - - - M (72x72) - M (72x72) - - - L (108x108) - L (108x108) - XL (144x144) diff --git a/mapeditor/translation/english.ts b/mapeditor/translation/english.ts index 33f48c1ce..f12d12250 100644 --- a/mapeditor/translation/english.ts +++ b/mapeditor/translation/english.ts @@ -715,7 +715,7 @@ MapView - + Can't place object @@ -889,38 +889,38 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + neutral - + UNFLAGGABLE @@ -1435,6 +1435,208 @@ Buildings + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + + + + + Event name + + + + + Type event message text + + + + + Day of first occurrence + + + + + Repeat after (0 = no repeat) + + + + + Affected players + + + + + affects human + + + + + affects AI + + + + + Resources + + + + + Buildings + + + + + Creatures + + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + + + + + Add + + + + + Remove + + + + + Day %1 - %2 + + + + + New event + + + + + TownSpellsWidget + + + Spells + + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations diff --git a/mapeditor/translation/french.ts b/mapeditor/translation/french.ts index 3e165b41b..2f0af6fdc 100644 --- a/mapeditor/translation/french.ts +++ b/mapeditor/translation/french.ts @@ -560,8 +560,8 @@ - Unsaved changes will be lost, are you sur? - Des modifications non sauvegardées vont être perdues. Êtes-vous sûr ? + Unsaved changes will be lost, are you sure? + @@ -715,7 +715,7 @@ MapView - + Can't place object Impossible de placer l'objet @@ -889,38 +889,38 @@ Expert - + Compliant Compérhensif - + Friendly Amical - + Aggressive Aggressif - + Hostile Hostile - + Savage Sauvage - - + + neutral neutre - + UNFLAGGABLE INCLASSABLE @@ -1435,6 +1435,208 @@ Buildings Bâtiments + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Type + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Général + + + + Event name + Nom de l'évènement + + + + Type event message text + Taper le message d'évènement + + + + Day of first occurrence + Jour de la première occurrence + + + + Repeat after (0 = no repeat) + Récurrence (0 = pas de récurrence) + + + + Affected players + Joueurs affectés + + + + affects human + afttecte les joueurs + + + + affects AI + affecte l'ordinateur + + + + Resources + Resources + + + + Buildings + Bâtiments + + + + Creatures + Créatures + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + Evenements timés + + + + Add + Ajouter + + + + Remove + Supprimer + + + + Day %1 - %2 + + + + + New event + Nouvel évènement + + + + TownSpellsWidget + + + Spells + Sorts + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations @@ -1698,18 +1900,6 @@ Width Largeur - - S (36x36) - Petite (36x36) - - - M (72x72) - Moyenne (72x72) - - - L (108x108) - Grande (108x108) - XL (144x144) diff --git a/mapeditor/translation/german.ts b/mapeditor/translation/german.ts index 21ca1043e..57543e6d5 100644 --- a/mapeditor/translation/german.ts +++ b/mapeditor/translation/german.ts @@ -715,7 +715,7 @@ MapView - + Can't place object Objekt kann nicht platziert werden @@ -889,38 +889,38 @@ Experte - + Compliant Konform - + Friendly Freundlich - + Aggressive Aggressiv - + Hostile Feindlich - + Savage Wild - - + + neutral neutral - + UNFLAGGABLE UNFLAGGBAR @@ -1435,6 +1435,208 @@ Buildings Gebäude + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Typ + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Allgemein + + + + Event name + Name des Ereignisses + + + + Type event message text + Ereignistext eingeben + + + + Day of first occurrence + Tag des ersten Auftretens + + + + Repeat after (0 = no repeat) + Wiederholung nach (0 = keine Wiederholung) + + + + Affected players + Betroffene Spieler + + + + affects human + beeinflusst Menschen + + + + affects AI + beeinflusst KI + + + + Resources + Ressourcen + + + + Buildings + Gebäude + + + + Creatures + Kreaturen + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + Zeitlich begrenzte Ereignisse + + + + Add + Hinzufügen + + + + Remove + Entfernen + + + + Day %1 - %2 + + + + + New event + Neues Ereignis + + + + TownSpellsWidget + + + Spells + Zaubersprüche + + + + Customize spells + Zaubersprüche anpassen + + + + Level 1 + Level 1 + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + Level 2 + + + + Level 3 + Level 3 + + + + Level 4 + Level 4 + + + + Level 5 + Level 5 + Translations @@ -1698,18 +1900,6 @@ Width Breite - - S (36x36) - S (36x36) - - - M (72x72) - M (72x72) - - - L (108x108) - L (108x108) - XL (144x144) diff --git a/mapeditor/translation/polish.ts b/mapeditor/translation/polish.ts index faf60b6cb..f879b9c04 100644 --- a/mapeditor/translation/polish.ts +++ b/mapeditor/translation/polish.ts @@ -715,7 +715,7 @@ MapView - + Can't place object Nie można umieścić obiektu @@ -889,38 +889,38 @@ Ekspert - + Compliant Przyjazny - + Friendly Przychylny - + Aggressive Agresywny - + Hostile Wrogi - + Savage Nienawistny - - + + neutral neutralny - + UNFLAGGABLE NIEOFLAGOWYWALNY @@ -1435,6 +1435,208 @@ Buildings Budynki + + + Build all + Zbuduj wsyzstkie + + + + Demolish all + Zburz wszystkie + + + + Enable all + Włącz wszystkie + + + + Disable all + Wyłącz wszystkie + + + + Type + Typ + + + + Enabled + Włączony + + + + Built + Zbudowany + + + + TownEvent + + + Town event + Zdarzenie miasta + + + + General + Ogólne + + + + Event name + Nazwa zdarzenia + + + + Type event message text + Wpisz treść komunikatu zdarzenia + + + + Day of first occurrence + Dzień pierwszego wystąpienia + + + + Repeat after (0 = no repeat) + Powtórz po... (0 = nigdy) + + + + Affected players + Dotyczy graczy + + + + affects human + dotyczy graczy ludzkich + + + + affects AI + dotyczy graczy AI + + + + Resources + Zasoby + + + + Buildings + Budynki + + + + Creatures + Stworzenia + + + + OK + OK + + + + Creature level %1 / Creature level %1 Upgrade + Stworzenie poziomu %1 / Ulepszone stworzenie poziomu %1 + + + + Day %1 - %2 + Dzień %1 - %2 + + + + TownEventsWidget + + + Town events + Zdarzenia miasta + + + + Timed events + Zdarzenia czasowe + + + + Add + Dodaj + + + + Remove + Usuń + + + + Day %1 - %2 + Dzień %1 - %2 + + + + New event + Nowe zdarzenie + + + + TownSpellsWidget + + + Spells + Zaklęcia + + + + Customize spells + Własne zaklęcia + + + + Level 1 + Poziom 1 + + + + + + + + Spell that may appear in mage guild + Zaklecia, które mogą pojawić się w gildii magów + + + + + + + + Spell that must appear in mage guild + Zaklecia, które muszą pojawić się w gildii magów + + + + Level 2 + Poziom 2 + + + + Level 3 + Poziom 3 + + + + Level 4 + Poziom 4 + + + + Level 5 + Poziom 5 + Translations @@ -1698,18 +1900,6 @@ Width Szerokość - - S (36x36) - S (36x36) - - - M (72x72) - M (72x72) - - - L (108x108) - L (108x108) - XL (144x144) diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index e6ca56dd1..3af685286 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -715,7 +715,7 @@ MapView - + Can't place object Não é possível colocar objeto @@ -889,38 +889,38 @@ Experiente - + Compliant Conformista - + Friendly Amigável - + Aggressive Agressivo - + Hostile Hostil - + Savage Selvagem - - + + neutral neutro - + UNFLAGGABLE NÃO TEM BANDEIRA @@ -1435,6 +1435,208 @@ Buildings Estruturas + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Tipo + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Geral + + + + Event name + Nome do evento + + + + Type event message text + Introduza o texto da mensagem do evento + + + + Day of first occurrence + Dia da primeira ocorrência + + + + Repeat after (0 = no repeat) + Repetir após (0 = não repetir) + + + + Affected players + Jogadores afetados + + + + affects human + afeta humano + + + + affects AI + afeta IA + + + + Resources + Recursos + + + + Buildings + Estruturas + + + + Creatures + Criaturas + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + Eventos Temporizados + + + + Add + Adicionar + + + + Remove + Remover + + + + Day %1 - %2 + + + + + New event + Novo Evento + + + + TownSpellsWidget + + + Spells + Feitiços + + + + Customize spells + Personalizar feitiços + + + + Level 1 + Nível 1 + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + Nível 2 + + + + Level 3 + Nível 3 + + + + Level 4 + Nível 4 + + + + Level 5 + Nível 5 + Translations @@ -1698,18 +1900,6 @@ Width Largura - - S (36x36) - P (36x36) - - - M (72x72) - M (72x72) - - - L (108x108) - G (108x108) - XL (144x144) diff --git a/mapeditor/translation/russian.ts b/mapeditor/translation/russian.ts index b32fe31d8..6c4d58192 100644 --- a/mapeditor/translation/russian.ts +++ b/mapeditor/translation/russian.ts @@ -715,7 +715,7 @@ MapView - + Can't place object @@ -889,38 +889,38 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + neutral - + UNFLAGGABLE @@ -1435,6 +1435,208 @@ Buildings Постройки + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Тип + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Общее + + + + Event name + + + + + Type event message text + + + + + Day of first occurrence + + + + + Repeat after (0 = no repeat) + + + + + Affected players + + + + + affects human + + + + + affects AI + + + + + Resources + + + + + Buildings + Постройки + + + + Creatures + + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + + + + + Add + + + + + Remove + + + + + Day %1 - %2 + + + + + New event + + + + + TownSpellsWidget + + + Spells + Заклинания + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations @@ -1698,18 +1900,6 @@ Width Ширина - - S (36x36) - Мал. (36x36) - - - M (72x72) - Ср. (72x72) - - - L (108x108) - Бол. (108x108) - XL (144x144) diff --git a/mapeditor/translation/spanish.ts b/mapeditor/translation/spanish.ts index 17ad2c24b..e9a2f0d43 100644 --- a/mapeditor/translation/spanish.ts +++ b/mapeditor/translation/spanish.ts @@ -715,7 +715,7 @@ MapView - + Can't place object @@ -889,38 +889,38 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + neutral - + UNFLAGGABLE @@ -1435,6 +1435,208 @@ Buildings Edificios + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Tipo + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + General + + + + Event name + + + + + Type event message text + + + + + Day of first occurrence + + + + + Repeat after (0 = no repeat) + + + + + Affected players + + + + + affects human + + + + + affects AI + + + + + Resources + + + + + Buildings + Edificios + + + + Creatures + + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + + + + + Add + + + + + Remove + + + + + Day %1 - %2 + + + + + New event + + + + + TownSpellsWidget + + + Spells + Hechizos + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations @@ -1698,18 +1900,6 @@ Width Ancho - - S (36x36) - S (36x36) - - - M (72x72) - M (72x72) - - - L (108x108) - L (108x108) - XL (144x144) diff --git a/mapeditor/translation/ukrainian.ts b/mapeditor/translation/ukrainian.ts index f280967f8..7f8558cda 100644 --- a/mapeditor/translation/ukrainian.ts +++ b/mapeditor/translation/ukrainian.ts @@ -715,7 +715,7 @@ MapView - + Can't place object @@ -889,38 +889,38 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + neutral - + UNFLAGGABLE @@ -1435,6 +1435,208 @@ Buildings Будівлі + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Тип + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Загальний + + + + Event name + + + + + Type event message text + + + + + Day of first occurrence + + + + + Repeat after (0 = no repeat) + + + + + Affected players + + + + + affects human + + + + + affects AI + + + + + Resources + + + + + Buildings + Будівлі + + + + Creatures + + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + + + + + Add + + + + + Remove + + + + + Day %1 - %2 + + + + + New event + + + + + TownSpellsWidget + + + Spells + Закляття + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations @@ -1698,18 +1900,6 @@ Width Ширина - - S (36x36) - М (36x36) - - - M (72x72) - С (72x72) - - - L (108x108) - В (108x108) - XL (144x144) diff --git a/mapeditor/translation/vietnamese.ts b/mapeditor/translation/vietnamese.ts index 32f286fae..ea255d154 100644 --- a/mapeditor/translation/vietnamese.ts +++ b/mapeditor/translation/vietnamese.ts @@ -715,7 +715,7 @@ MapView - + Can't place object Không thể đặt vật thể @@ -889,38 +889,38 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + neutral - + UNFLAGGABLE @@ -1435,6 +1435,208 @@ Buildings Công trình + + + Build all + + + + + Demolish all + + + + + Enable all + + + + + Disable all + + + + + Type + Loại + + + + Enabled + + + + + Built + + + + + TownEvent + + + Town event + + + + + General + Chung + + + + Event name + + + + + Type event message text + + + + + Day of first occurrence + + + + + Repeat after (0 = no repeat) + + + + + Affected players + + + + + affects human + + + + + affects AI + + + + + Resources + + + + + Buildings + Công trình + + + + Creatures + + + + + OK + + + + + Creature level %1 / Creature level %1 Upgrade + + + + + Day %1 - %2 + + + + + TownEventsWidget + + + Town events + + + + + Timed events + + + + + Add + + + + + Remove + + + + + Day %1 - %2 + + + + + New event + + + + + TownSpellsWidget + + + Spells + Phép + + + + Customize spells + + + + + Level 1 + + + + + + + + + Spell that may appear in mage guild + + + + + + + + + Spell that must appear in mage guild + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + Translations @@ -1698,18 +1900,6 @@ Width Rộng - - S (36x36) - Nhỏ (36x36) - - - M (72x72) - Vừa (72x72) - - - L (108x108) - Lớn (108x108) - XL (144x144) From fa3fef8a0fa741351d749ac0bb81ce51d12af38b Mon Sep 17 00:00:00 2001 From: godric3 Date: Tue, 16 Jul 2024 21:16:26 +0200 Subject: [PATCH 08/76] change town events to vector + use getDefaultAllowed for spells --- lib/mapObjects/CGTownInstance.cpp | 20 +++++--------------- lib/mapObjects/CGTownInstance.h | 2 +- lib/networkPacks/PacksForClient.h | 2 +- mapeditor/inspector/townspellswidget.cpp | 18 ++++++++---------- server/CGameHandler.cpp | 6 +++--- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 386cc08ba..79fec4d7e 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1207,22 +1207,12 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) { handler.serializeIdArray( "possibleSpells", possibleSpells); handler.serializeIdArray( "obligatorySpells", obligatorySpells); + } - if (handler.saving) - { - auto eventsHandler = handler.enterArray("events"); - std::vector temp(events.begin(), events.end()); - eventsHandler.serializeStruct(temp); - } - else - { - auto eventsHandler = handler.enterArray("events"); - std::vector temp; - eventsHandler.serializeStruct(temp); - events.clear(); - events.insert(events.begin(), temp.begin(), temp.end()); - } - + { + auto eventsHandler = handler.enterArray("events"); + eventsHandler.syncSize(events, JsonNode::JsonType::DATA_VECTOR); + eventsHandler.serializeStruct(events); } } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 5f3dc4509..545c42bc7 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -64,7 +64,7 @@ public: std::vector bonusingBuildings; std::vector possibleSpells, obligatorySpells; std::vector > spells; //spells[level] -> vector of spells, first will be available in guild - std::list events; + std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond); ////////////////////////////////////////////////////////////////////////// diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 8ef2de96e..48f3c0b6f 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -561,7 +561,7 @@ struct DLL_LINKAGE UpdateMapEvents : public CPackForClient struct DLL_LINKAGE UpdateCastleEvents : public CPackForClient { ObjectInstanceID town; - std::list events; + std::vector events; void applyGs(CGameState * gs) const; void visitTyped(ICPackVisitor & visitor) override; diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp index f4c7388b4..3dfbb6f0f 100644 --- a/mapeditor/inspector/townspellswidget.cpp +++ b/mapeditor/inspector/townspellswidget.cpp @@ -50,29 +50,27 @@ void TownSpellsWidget::resetSpells() { town.possibleSpells.clear(); town.obligatorySpells.clear(); - for (auto spell : VLC->spellh->objects) - { - if (!spell->isSpecial() && !spell->isCreatureAbility()) - town.possibleSpells.push_back(spell->id); - } + for (auto spellID : VLC->spellh->getDefaultAllowed()) + town.possibleSpells.push_back(spellID); } void TownSpellsWidget::initSpellLists() { QListWidget * possibleSpellLists[] = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; QListWidget * requiredSpellLists[] = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; - auto spells = VLC->spellh->objects; + auto spells = VLC->spellh->getDefaultAllowed(); for (int i = 0; i < GameConstants::SPELL_LEVELS; i++) { - std::vector> spellsByLevel; - auto getSpellsByLevel = [i](auto spell) { - return spell->getLevel() == i + 1 && !spell->isSpecial() && !spell->isCreatureAbility(); + std::vector spellsByLevel; + auto getSpellsByLevel = [i](auto spellID) { + return spellID.toEntity(VLC)->getLevel() == i + 1; }; vstd::copy_if(spells, std::back_inserter(spellsByLevel), getSpellsByLevel); possibleSpellLists[i]->clear(); requiredSpellLists[i]->clear(); - for (auto spell : spellsByLevel) + for (auto spellID : spellsByLevel) { + auto spell = spellID.toEntity(VLC); auto * possibleItem = new QListWidgetItem(QString::fromStdString(spell->getNameTranslated())); possibleItem->setData(Qt::UserRole, QVariant::fromValue(spell->getIndex())); possibleItem->setFlags(possibleItem->flags() | Qt::ItemIsUserCheckable); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a72f327ff..1f7e13bba 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3413,7 +3413,7 @@ void CGameHandler::handleTimeEvents() void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n) { - town->events.sort(evntCmp); + std::sort(town->events.begin(), town->events.end(), evntCmp); while(town->events.size() && town->events.front().firstOccurrence == gs->day) { PlayerColor player = town->tempOwner; @@ -3474,7 +3474,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n) if (ev.nextOccurrence) { - town->events.pop_front(); + town->events.erase(town->events.begin()); ev.firstOccurrence += ev.nextOccurrence; auto it = town->events.begin(); @@ -3484,7 +3484,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n) } else { - town->events.pop_front(); + town->events.erase(town->events.begin()); } } From 3d3f388fb81d379a14bc57026cc720a324607682 Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 22 Jul 2024 21:09:57 +0200 Subject: [PATCH 09/76] rename TownEvent to TownEventDialog --- mapeditor/CMakeLists.txt | 6 +-- .../{townevent.cpp => towneventdialog.cpp} | 44 +++++++++---------- .../{townevent.h => towneventdialog.h} | 14 +++--- .../{townevent.ui => towneventdialog.ui} | 4 +- mapeditor/inspector/towneventswidget.cpp | 4 +- mapeditor/translation/chinese.ts | 32 +++++++------- mapeditor/translation/czech.ts | 32 +++++++------- mapeditor/translation/english.ts | 32 +++++++------- mapeditor/translation/french.ts | 32 +++++++------- mapeditor/translation/german.ts | 32 +++++++------- mapeditor/translation/polish.ts | 32 +++++++------- mapeditor/translation/portuguese.ts | 32 +++++++------- mapeditor/translation/russian.ts | 32 +++++++------- mapeditor/translation/spanish.ts | 32 +++++++------- mapeditor/translation/ukrainian.ts | 32 +++++++------- mapeditor/translation/vietnamese.ts | 32 +++++++------- 16 files changed, 212 insertions(+), 212 deletions(-) rename mapeditor/inspector/{townevent.cpp => towneventdialog.cpp} (88%) rename mapeditor/inspector/{townevent.h => towneventdialog.h} (76%) rename mapeditor/inspector/{townevent.ui => towneventdialog.ui} (98%) diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index c6e5ff0a3..736a00fe0 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -29,7 +29,7 @@ set(editor_SRCS validator.cpp inspector/inspector.cpp inspector/townbuildingswidget.cpp - inspector/townevent.cpp + inspector/towneventdialog.cpp inspector/towneventswidget.cpp inspector/townspellswidget.cpp inspector/armywidget.cpp @@ -73,7 +73,7 @@ set(editor_HEADERS validator.h inspector/inspector.h inspector/townbuildingswidget.h - inspector/townevent.h + inspector/towneventdialog.h inspector/towneventswidget.h inspector/townspellswidget.h inspector/armywidget.h @@ -104,7 +104,7 @@ set(editor_FORMS playerparams.ui validator.ui inspector/townbuildingswidget.ui - inspector/townevent.ui + inspector/towneventdialog.ui inspector/towneventswidget.ui inspector/townspellswidget.ui inspector/armywidget.ui diff --git a/mapeditor/inspector/townevent.cpp b/mapeditor/inspector/towneventdialog.cpp similarity index 88% rename from mapeditor/inspector/townevent.cpp rename to mapeditor/inspector/towneventdialog.cpp index 9516e0a2a..108ba7c63 100644 --- a/mapeditor/inspector/townevent.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -1,5 +1,5 @@ /* - * townevent.cpp, part of VCMI engine + * towneventdialog.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -10,14 +10,14 @@ #include "../StdInc.h" #include "townbuildingswidget.h" -#include "townevent.h" -#include "ui_townevent.h" +#include "towneventdialog.h" +#include "ui_towneventdialog.h" #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" -TownEvent::TownEvent(CGTownInstance & t, QListWidgetItem * item, QWidget * parent) : +TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWidget * parent) : QDialog(parent), - ui(new Ui::TownEvent), + ui(new Ui::TownEventDialog), town(t), item(item) { @@ -44,12 +44,12 @@ TownEvent::TownEvent(CGTownInstance & t, QListWidgetItem * item, QWidget * paren show(); } -TownEvent::~TownEvent() +TownEventDialog::~TownEventDialog() { delete ui; } -void TownEvent::initPlayers() +void TownEventDialog::initPlayers() { for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) { @@ -61,7 +61,7 @@ void TownEvent::initPlayers() } } -void TownEvent::initResources() +void TownEventDialog::initResources() { ui->resourcesTable->setRowCount(GameConstants::RESOURCE_QUANTITY); auto resourcesMap = params.value("resources").toMap(); @@ -81,7 +81,7 @@ void TownEvent::initResources() } } -void TownEvent::initBuildings() +void TownEventDialog::initBuildings() { auto * ctown = town.town; if (!ctown) @@ -95,10 +95,10 @@ void TownEvent::initBuildings() } ui->buildingsTree->resizeColumnToContents(0); - connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); + connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEventDialog::onItemChanged); } -QStandardItem * TownEvent::addBuilding(const CTown& ctown, BuildingID buildingId, std::set& remaining) +QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buildingId, std::set& remaining) { auto bId = buildingId.num; const CBuilding * building = ctown.buildings.at(buildingId); @@ -165,7 +165,7 @@ QStandardItem * TownEvent::addBuilding(const CTown& ctown, BuildingID buildingId return checks.front(); } -void TownEvent::initCreatures() +void TownEventDialog::initCreatures() { auto creatures = params.value("creatures").toList(); auto * ctown = town.town; @@ -201,7 +201,7 @@ void TownEvent::initCreatures() ui->creaturesTable->resizeColumnToContents(0); } -void TownEvent::on_TownEvent_finished(int result) +void TownEventDialog::on_TownEventDialog_finished(int result) { QVariantMap descriptor; descriptor["name"] = ui->eventNameText->text(); @@ -220,7 +220,7 @@ void TownEvent::on_TownEvent_finished(int result) item->setText(itemText); } -QVariant TownEvent::playersToVariant() +QVariant TownEventDialog::playersToVariant() { int players = 0; for (int i = 0; i < ui->playersAffected->count(); ++i) @@ -232,7 +232,7 @@ QVariant TownEvent::playersToVariant() return QVariant::fromValue(players); } -QVariantMap TownEvent::resourcesToVariant() +QVariantMap TownEventDialog::resourcesToVariant() { auto res = item->data(Qt::UserRole).toMap().value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) @@ -245,7 +245,7 @@ QVariantMap TownEvent::resourcesToVariant() return res; } -QVariantList TownEvent::buildingsToVariant() +QVariantList TownEventDialog::buildingsToVariant() { QVariantList buildingsList; std::vector stack; @@ -268,7 +268,7 @@ QVariantList TownEvent::buildingsToVariant() return buildingsList; } -QVariantList TownEvent::creaturesToVariant() +QVariantList TownEventDialog::creaturesToVariant() { QVariantList creaturesList; for (int i = 0; i < 7; ++i) @@ -279,19 +279,19 @@ QVariantList TownEvent::creaturesToVariant() return creaturesList; } -void TownEvent::on_okButton_clicked() +void TownEventDialog::on_okButton_clicked() { close(); } -void TownEvent::setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState) { +void TownEventDialog::setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState) { auto sibling = item->model()->sibling(item->row(), column, item->index()); buildingsModel.itemFromIndex(sibling)->setCheckState(checkState); } -void TownEvent::onItemChanged(QStandardItem * item) +void TownEventDialog::onItemChanged(QStandardItem * item) { - disconnect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); + disconnect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEventDialog::onItemChanged); auto rowFirstColumnIndex = item->model()->sibling(item->row(), 0, item->index()); QStandardItem * nextRow = buildingsModel.itemFromIndex(rowFirstColumnIndex); if (item->checkState() == Qt::Checked) { @@ -316,5 +316,5 @@ void TownEvent::onItemChanged(QStandardItem * item) } } - connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEvent::onItemChanged); + connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEventDialog::onItemChanged); } diff --git a/mapeditor/inspector/townevent.h b/mapeditor/inspector/towneventdialog.h similarity index 76% rename from mapeditor/inspector/townevent.h rename to mapeditor/inspector/towneventdialog.h index e614dc76b..746ea317b 100644 --- a/mapeditor/inspector/townevent.h +++ b/mapeditor/inspector/towneventdialog.h @@ -1,5 +1,5 @@ /* - * townevent.h, part of VCMI engine + * towneventdialog.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -14,21 +14,21 @@ #include "../lib/mapObjects/CGTownInstance.h" namespace Ui { - class TownEvent; + class TownEventDialog; } -class TownEvent : public QDialog +class TownEventDialog : public QDialog { Q_OBJECT public: - explicit TownEvent(CGTownInstance & town, QListWidgetItem * item, QWidget * parent); - ~TownEvent(); + explicit TownEventDialog(CGTownInstance & town, QListWidgetItem * item, QWidget * parent); + ~TownEventDialog(); private slots: void onItemChanged(QStandardItem * item); - void on_TownEvent_finished(int result); + void on_TownEventDialog_finished(int result); void on_okButton_clicked(); void setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState); @@ -45,7 +45,7 @@ private: QStandardItem * addBuilding(const CTown & ctown, BuildingID bId, std::set & remaining); - Ui::TownEvent * ui; + Ui::TownEventDialog * ui; CGTownInstance & town; QListWidgetItem * item; QMap params; diff --git a/mapeditor/inspector/townevent.ui b/mapeditor/inspector/towneventdialog.ui similarity index 98% rename from mapeditor/inspector/townevent.ui rename to mapeditor/inspector/towneventdialog.ui index 3e7070d1d..0f9b2c032 100644 --- a/mapeditor/inspector/townevent.ui +++ b/mapeditor/inspector/towneventdialog.ui @@ -1,7 +1,7 @@ - TownEvent - + TownEventDialog + Qt::ApplicationModal diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index 5a3967597..e8d89f03d 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -11,7 +11,7 @@ #include "../StdInc.h" #include "towneventswidget.h" #include "ui_towneventswidget.h" -#include "townevent.h" +#include "towneventdialog.h" #include "mapsettings/eventsettings.h" #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" @@ -138,7 +138,7 @@ void TownEventsWidget::on_timedEventRemove_clicked() void TownEventsWidget::on_eventsList_itemActivated(QListWidgetItem* item) { - new TownEvent(town, item, parentWidget()); + new TownEventDialog(town, item, parentWidget()); } void TownEventsWidget::onItemChanged(QStandardItem * item) diff --git a/mapeditor/translation/chinese.ts b/mapeditor/translation/chinese.ts index 19b776ee9..55b329e4f 100644 --- a/mapeditor/translation/chinese.ts +++ b/mapeditor/translation/chinese.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General 通用 - + Event name 事件名 - + Type event message text 输入事件信息文本 - + Day of first occurrence 首次发生天数 - + Repeat after (0 = no repeat) 重复周期 (0 = 不重复) - + Affected players 生效玩家 - + affects human 人类玩家生效 - + affects AI AI玩家生效 - + Resources 资源 - + Buildings 建筑 - + Creatures 生物 - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/czech.ts b/mapeditor/translation/czech.ts index 285f83f78..95c43ba55 100644 --- a/mapeditor/translation/czech.ts +++ b/mapeditor/translation/czech.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General - + Event name Název události - + Type event message text Zadejte text zprávy události - + Day of first occurrence Den prvního výskytu - + Repeat after (0 = no repeat) Opakovat po (0 = bez opak.) - + Affected players Ovlivnění hráči - + affects human ovlivňuje lidi - + affects AI ovlivňuje AI - + Resources Zdroje - + Buildings Budovy - + Creatures Jednotky - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/english.ts b/mapeditor/translation/english.ts index f12d12250..115f79aed 100644 --- a/mapeditor/translation/english.ts +++ b/mapeditor/translation/english.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General - + Event name - + Type event message text - + Day of first occurrence - + Repeat after (0 = no repeat) - + Affected players - + affects human - + affects AI - + Resources - + Buildings - + Creatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/french.ts b/mapeditor/translation/french.ts index 2f0af6fdc..5e725a3bd 100644 --- a/mapeditor/translation/french.ts +++ b/mapeditor/translation/french.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Général - + Event name Nom de l'évènement - + Type event message text Taper le message d'évènement - + Day of first occurrence Jour de la première occurrence - + Repeat after (0 = no repeat) Récurrence (0 = pas de récurrence) - + Affected players Joueurs affectés - + affects human afttecte les joueurs - + affects AI affecte l'ordinateur - + Resources Resources - + Buildings Bâtiments - + Creatures Créatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/german.ts b/mapeditor/translation/german.ts index 57543e6d5..1d5d186be 100644 --- a/mapeditor/translation/german.ts +++ b/mapeditor/translation/german.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Allgemein - + Event name Name des Ereignisses - + Type event message text Ereignistext eingeben - + Day of first occurrence Tag des ersten Auftretens - + Repeat after (0 = no repeat) Wiederholung nach (0 = keine Wiederholung) - + Affected players Betroffene Spieler - + affects human beeinflusst Menschen - + affects AI beeinflusst KI - + Resources Ressourcen - + Buildings Gebäude - + Creatures Kreaturen - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/polish.ts b/mapeditor/translation/polish.ts index f879b9c04..9b57bcf7a 100644 --- a/mapeditor/translation/polish.ts +++ b/mapeditor/translation/polish.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event Zdarzenie miasta - + General Ogólne - + Event name Nazwa zdarzenia - + Type event message text Wpisz treść komunikatu zdarzenia - + Day of first occurrence Dzień pierwszego wystąpienia - + Repeat after (0 = no repeat) Powtórz po... (0 = nigdy) - + Affected players Dotyczy graczy - + affects human dotyczy graczy ludzkich - + affects AI dotyczy graczy AI - + Resources Zasoby - + Buildings Budynki - + Creatures Stworzenia - + OK OK - + Creature level %1 / Creature level %1 Upgrade Stworzenie poziomu %1 / Ulepszone stworzenie poziomu %1 - + Day %1 - %2 Dzień %1 - %2 diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index 3af685286..a5069bb28 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Geral - + Event name Nome do evento - + Type event message text Introduza o texto da mensagem do evento - + Day of first occurrence Dia da primeira ocorrência - + Repeat after (0 = no repeat) Repetir após (0 = não repetir) - + Affected players Jogadores afetados - + affects human afeta humano - + affects AI afeta IA - + Resources Recursos - + Buildings Estruturas - + Creatures Criaturas - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/russian.ts b/mapeditor/translation/russian.ts index 6c4d58192..db0e4ee99 100644 --- a/mapeditor/translation/russian.ts +++ b/mapeditor/translation/russian.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Общее - + Event name - + Type event message text - + Day of first occurrence - + Repeat after (0 = no repeat) - + Affected players - + affects human - + affects AI - + Resources - + Buildings Постройки - + Creatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/spanish.ts b/mapeditor/translation/spanish.ts index e9a2f0d43..ae0b04a25 100644 --- a/mapeditor/translation/spanish.ts +++ b/mapeditor/translation/spanish.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General General - + Event name - + Type event message text - + Day of first occurrence - + Repeat after (0 = no repeat) - + Affected players - + affects human - + affects AI - + Resources - + Buildings Edificios - + Creatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/ukrainian.ts b/mapeditor/translation/ukrainian.ts index 7f8558cda..7ffd2aa0b 100644 --- a/mapeditor/translation/ukrainian.ts +++ b/mapeditor/translation/ukrainian.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Загальний - + Event name - + Type event message text - + Day of first occurrence - + Repeat after (0 = no repeat) - + Affected players - + affects human - + affects AI - + Resources - + Buildings Будівлі - + Creatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 diff --git a/mapeditor/translation/vietnamese.ts b/mapeditor/translation/vietnamese.ts index ea255d154..4d1b78974 100644 --- a/mapeditor/translation/vietnamese.ts +++ b/mapeditor/translation/vietnamese.ts @@ -1472,79 +1472,79 @@ - TownEvent + TownEventDialog - + Town event - + General Chung - + Event name - + Type event message text - + Day of first occurrence - + Repeat after (0 = no repeat) - + Affected players - + affects human - + affects AI - + Resources - + Buildings Công trình - + Creatures - + OK - + Creature level %1 / Creature level %1 Upgrade - + Day %1 - %2 From 67ab43526f51e63616a3da429d904b8b74a2accf Mon Sep 17 00:00:00 2001 From: godric3 Date: Fri, 26 Jul 2024 20:42:16 +0200 Subject: [PATCH 10/76] reduce code duplication --- mapeditor/inspector/townbuildingswidget.cpp | 97 ++++++++++++--------- mapeditor/inspector/townbuildingswidget.h | 4 + mapeditor/inspector/towneventdialog.cpp | 54 ++---------- mapeditor/inspector/towneventdialog.h | 6 +- mapeditor/inspector/towneventswidget.cpp | 9 +- mapeditor/inspector/towneventswidget.h | 3 +- mapeditor/inspector/townspellswidget.cpp | 60 +++++++------ mapeditor/inspector/townspellswidget.h | 3 + 8 files changed, 113 insertions(+), 123 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 6ceab8baf..14c3645bd 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -68,6 +68,56 @@ std::string defaultBuildingIdConversion(BuildingID bId) } } +QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model) +{ + QStandardItem * parent = nullptr; + std::vector stack(1); + while (!parent && !stack.empty()) + { + auto pindex = stack.back(); + stack.pop_back(); + auto rowCount = model.rowCount(pindex); + for (int i = 0; i < rowCount; ++i) + { + QModelIndex index = model.index(i, 0, pindex); + if (building->upgrade.getNum() == model.itemFromIndex(index)->data(Qt::UserRole).toInt()) + { + parent = model.itemFromIndex(index); + break; + } + if (model.hasChildren(index)) + stack.push_back(index); + } + } + return parent; +} + +std::set getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) +{ + std::set result; + std::vector stack(1); + while (!stack.empty()) + { + auto pindex = stack.back(); + stack.pop_back(); + auto rowCount = model.rowCount(pindex); + for (int i = 0; i < rowCount; ++i) + { + QModelIndex index = model.index(i, modelColumn, pindex); + if (auto * item = model.itemFromIndex(index)) + if (item->checkState() == checkState) + result.emplace(item->data(Qt::UserRole)); + index = model.index(i, 0, pindex); + if (model.hasChildren(index)) + stack.push_back(index); + } + } + + return result; +} + + + TownBuildingsWidget::TownBuildingsWidget(CGTownInstance & t, QWidget *parent) : town(t), QDialog(parent), @@ -96,7 +146,7 @@ QStandardItem * TownBuildingsWidget::addBuilding(const CTown & ctown, int bId, s return nullptr; } - QString name = tr(building->getNameTranslated().c_str()); + QString name = QString::fromStdString(building->getNameTranslated()); if(name.isEmpty()) name = QString::fromStdString(defaultBuildingIdConversion(buildingId)); @@ -122,25 +172,7 @@ QStandardItem * TownBuildingsWidget::addBuilding(const CTown & ctown, int bId, s } else { - QStandardItem * parent = nullptr; - std::vector stack; - stack.push_back(QModelIndex()); - while(!parent && !stack.empty()) - { - auto pindex = stack.back(); - stack.pop_back(); - for(int i = 0; i < model.rowCount(pindex); ++i) - { - QModelIndex index = model.index(i, 0, pindex); - if(building->upgrade.getNum() == model.itemFromIndex(index)->data(Qt::UserRole).toInt()) - { - parent = model.itemFromIndex(index); - break; - } - if(model.hasChildren(index)) - stack.push_back(index); - } - } + QStandardItem * parent = getBuildingParentFromTreeModel(building, model); if(!parent) parent = addBuilding(ctown, building->upgrade.getNum(), remaining); @@ -172,25 +204,12 @@ void TownBuildingsWidget::addBuildings(const CTown & ctown) std::set TownBuildingsWidget::getBuildingsFromModel(int modelColumn, Qt::CheckState checkState) { + auto buildingVariants = getBuildingVariantsFromModel(model, modelColumn, checkState); std::set result; - std::vector stack; - stack.push_back(QModelIndex()); - while(!stack.empty()) + for (auto buildingId : buildingVariants) { - auto pindex = stack.back(); - stack.pop_back(); - for(int i = 0; i < model.rowCount(pindex); ++i) - { - QModelIndex index = model.index(i, modelColumn, pindex); - if(auto * item = model.itemFromIndex(index)) - if(item->checkState() == checkState) - result.emplace(item->data(Qt::UserRole).toInt()); - index = model.index(i, 0, pindex); //children are linked to first column of the model - if(model.hasChildren(index)) - stack.push_back(index); - } + result.insert(buildingId.toInt()); } - return result; } @@ -242,13 +261,13 @@ void TownBuildingsWidget::setRowColumnCheckState(QStandardItem * item, Column co void TownBuildingsWidget::setAllRowsColumnCheckState(Column column, Qt::CheckState checkState) { - std::vector stack; - stack.push_back(QModelIndex()); + std::vector stack(1); while (!stack.empty()) { auto parentIndex = stack.back(); stack.pop_back(); - for (int i = 0; i < model.rowCount(parentIndex); ++i) + auto rowCount = model.rowCount(parentIndex); + for (int i = 0; i < rowCount; ++i) { QModelIndex index = model.index(i, column, parentIndex); if (auto* item = model.itemFromIndex(index)) diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index d0e5d0e96..84a3186de 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -19,6 +19,10 @@ class TownBuildingsWidget; std::string defaultBuildingIdConversion(BuildingID bId); +QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model); + +std::set getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); + class TownBuildingsWidget : public QDialog { Q_OBJECT diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 108ba7c63..70ab76bc4 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -19,13 +19,13 @@ TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWi QDialog(parent), ui(new Ui::TownEventDialog), town(t), - item(item) + townEventListItem(item) { ui->setupUi(this); ui->buildingsTree->setModel(&buildingsModel); - params = item->data(Qt::UserRole).toMap(); + params = townEventListItem->data(Qt::UserRole).toMap(); ui->eventFirstOccurrence->setMinimum(1); ui->eventFirstOccurrence->setMaximum(999); ui->eventRepeatAfter->setMaximum(999); @@ -40,8 +40,6 @@ TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWi initResources(); initBuildings(); initCreatures(); - - show(); } TownEventDialog::~TownEventDialog() @@ -108,7 +106,7 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil return nullptr; } - QString name = tr(building->getNameTranslated().c_str()); + QString name = QString::fromStdString(building->getNameTranslated()); if (name.isEmpty()) name = QString::fromStdString(defaultBuildingIdConversion(buildingId)); @@ -129,25 +127,7 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil } else { - QStandardItem * parent = nullptr; - std::vector stack; - stack.push_back(QModelIndex()); - while (!parent && !stack.empty()) - { - auto pindex = stack.back(); - stack.pop_back(); - for (int i = 0; i < buildingsModel.rowCount(pindex); ++i) - { - QModelIndex index = buildingsModel.index(i, 0, pindex); - if (building->upgrade.getNum() == buildingsModel.itemFromIndex(index)->data(Qt::UserRole).toInt()) - { - parent = buildingsModel.itemFromIndex(index); - break; - } - if (buildingsModel.hasChildren(index)) - stack.push_back(index); - } - } + QStandardItem * parent = getBuildingParentFromTreeModel(building, buildingsModel); if (!parent) parent = addBuilding(ctown, building->upgrade.getNum(), remaining); @@ -215,9 +195,9 @@ void TownEventDialog::on_TownEventDialog_finished(int result) descriptor["buildings"] = buildingsToVariant(); descriptor["creatures"] = creaturesToVariant(); - item->setData(Qt::UserRole, descriptor); + townEventListItem->setData(Qt::UserRole, descriptor); auto itemText = tr("Day %1 - %2").arg(ui->eventFirstOccurrence->value(), 3).arg(ui->eventNameText->text()); - item->setText(itemText); + townEventListItem->setText(itemText); } QVariant TownEventDialog::playersToVariant() @@ -234,7 +214,7 @@ QVariant TownEventDialog::playersToVariant() QVariantMap TownEventDialog::resourcesToVariant() { - auto res = item->data(Qt::UserRole).toMap().value("resources").toMap(); + auto res = townEventListItem->data(Qt::UserRole).toMap().value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) { auto * itemType = ui->resourcesTable->item(i, 0); @@ -247,24 +227,8 @@ QVariantMap TownEventDialog::resourcesToVariant() QVariantList TownEventDialog::buildingsToVariant() { - QVariantList buildingsList; - std::vector stack; - stack.push_back(QModelIndex()); - while (!stack.empty()) - { - auto pindex = stack.back(); - stack.pop_back(); - for (int i = 0; i < buildingsModel.rowCount(pindex); ++i) - { - QModelIndex index = buildingsModel.index(i, 1, pindex); - if (auto * item = buildingsModel.itemFromIndex(index)) - if (item->checkState() == Qt::Checked) - buildingsList.push_back(item->data(Qt::UserRole)); - index = buildingsModel.index(i, 0, pindex); - if (buildingsModel.hasChildren(index)) - stack.push_back(index); - } - } + auto buildings = getBuildingVariantsFromModel(buildingsModel, 1, Qt::Checked); + QVariantList buildingsList(buildings.begin(), buildings.end()); return buildingsList; } diff --git a/mapeditor/inspector/towneventdialog.h b/mapeditor/inspector/towneventdialog.h index 746ea317b..008bb89d2 100644 --- a/mapeditor/inspector/towneventdialog.h +++ b/mapeditor/inspector/towneventdialog.h @@ -47,7 +47,7 @@ private: Ui::TownEventDialog * ui; CGTownInstance & town; - QListWidgetItem * item; + QListWidgetItem * townEventListItem; QMap params; - mutable QStandardItemModel buildingsModel; -}; \ No newline at end of file + QStandardItemModel buildingsModel; +}; diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index e8d89f03d..16c2f7c8e 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -132,18 +132,15 @@ void TownEventsWidget::on_timedEventAdd_clicked() void TownEventsWidget::on_timedEventRemove_clicked() { - if (auto* item = ui->eventsList->currentItem()) - ui->eventsList->takeItem(ui->eventsList->row(item)); + delete ui->eventsList->takeItem(ui->eventsList->currentRow()); } void TownEventsWidget::on_eventsList_itemActivated(QListWidgetItem* item) { - new TownEventDialog(town, item, parentWidget()); + TownEventDialog dlg{ town, item, parentWidget() }; + dlg.exec(); } -void TownEventsWidget::onItemChanged(QStandardItem * item) -{ -} TownEventsDelegate::TownEventsDelegate(CGTownInstance & town, MapController & c) : town(town), controller(c), QStyledItemDelegate() { diff --git a/mapeditor/inspector/towneventswidget.h b/mapeditor/inspector/towneventswidget.h index ada1ee7fd..7bc85f6ca 100644 --- a/mapeditor/inspector/towneventswidget.h +++ b/mapeditor/inspector/towneventswidget.h @@ -30,7 +30,6 @@ public: void obtainData(); void commitChanges(MapController & controller); private slots: - void onItemChanged(QStandardItem * item); void on_timedEventAdd_clicked(); void on_timedEventRemove_clicked(); void on_eventsList_itemActivated(QListWidgetItem * item); @@ -56,4 +55,4 @@ public: private: CGTownInstance & town; MapController & controller; -}; \ No newline at end of file +}; diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp index 3dfbb6f0f..2403c986f 100644 --- a/mapeditor/inspector/townspellswidget.cpp +++ b/mapeditor/inspector/townspellswidget.cpp @@ -20,8 +20,21 @@ TownSpellsWidget::TownSpellsWidget(CGTownInstance & town, QWidget * parent) : town(town) { ui->setupUi(this); - BuildingID mageGuilds[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; - for (int i = 0; i < GameConstants::SPELL_LEVELS; i++) + + possibleSpellLists[0] = ui->possibleSpellList1; + possibleSpellLists[1] = ui->possibleSpellList2; + possibleSpellLists[2] = ui->possibleSpellList3; + possibleSpellLists[3] = ui->possibleSpellList4; + possibleSpellLists[4] = ui->possibleSpellList5; + + requiredSpellLists[0] = ui->requiredSpellList1; + requiredSpellLists[1] = ui->requiredSpellList2; + requiredSpellLists[2] = ui->requiredSpellList3; + requiredSpellLists[3] = ui->requiredSpellList4; + requiredSpellLists[4] = ui->requiredSpellList5; + + std::array mageGuilds = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; + for (int i = 0; i < mageGuilds.size(); i++) { ui->tabWidget->setTabEnabled(i, vstd::contains(town.getTown()->buildings, mageGuilds[i])); } @@ -56,8 +69,6 @@ void TownSpellsWidget::resetSpells() void TownSpellsWidget::initSpellLists() { - QListWidget * possibleSpellLists[] = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; - QListWidget * requiredSpellLists[] = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; auto spells = VLC->spellh->getDefaultAllowed(); for (int i = 0; i < GameConstants::SPELL_LEVELS; i++) { @@ -93,33 +104,26 @@ void TownSpellsWidget::commitChanges() resetSpells(); return; } + + auto updateTownSpellList = [](auto uiSpellLists, auto & townSpellList) { + for (QListWidget * spellList : uiSpellLists) + { + for (int i = 0; i < spellList->count(); ++i) + { + QListWidgetItem * item = spellList->item(i); + if (item->checkState() == Qt::Checked) + { + townSpellList.push_back(item->data(Qt::UserRole).toInt()); + } + } + } + }; + town.possibleSpells.clear(); town.obligatorySpells.clear(); town.possibleSpells.push_back(SpellID::PRESET); - QListWidget * possibleSpellLists[] = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; - QListWidget * requiredSpellLists[] = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; - for (auto spellList : possibleSpellLists) - { - for (int i = 0; i < spellList->count(); i++) - { - auto * item = spellList->item(i); - if (item->checkState() == Qt::Checked) - { - town.possibleSpells.push_back(item->data(Qt::UserRole).toInt()); - } - } - } - for (auto spellList : requiredSpellLists) - { - for (int i = 0; i < spellList->count(); i++) - { - auto * item = spellList->item(i); - if (item->checkState() == Qt::Checked) - { - town.obligatorySpells.push_back(item->data(Qt::UserRole).toInt()); - } - } - } + updateTownSpellList(possibleSpellLists, town.possibleSpells); + updateTownSpellList(requiredSpellLists, town.obligatorySpells); } void TownSpellsWidget::on_customizeSpells_toggled(bool checked) diff --git a/mapeditor/inspector/townspellswidget.h b/mapeditor/inspector/townspellswidget.h index de7018bed..1548c25f7 100644 --- a/mapeditor/inspector/townspellswidget.h +++ b/mapeditor/inspector/townspellswidget.h @@ -36,6 +36,9 @@ private: CGTownInstance & town; + std::array possibleSpellLists; + std::array requiredSpellLists; + void resetSpells(); void initSpellLists(); }; From d540ee1887e4b6924bdfd697b789112ecaa324f1 Mon Sep 17 00:00:00 2001 From: godric3 Date: Fri, 26 Jul 2024 22:30:51 +0200 Subject: [PATCH 11/76] extract magic numbers to constants --- mapeditor/inspector/towneventdialog.cpp | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 70ab76bc4..00dfc30f7 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -21,14 +21,17 @@ TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWi town(t), townEventListItem(item) { + static const int FIRST_DAY_FOR_EVENT = 1; + static const int LAST_DAY_FOR_EVENT = 999; + static const int MAXIMUM_REPEAT_AFTER = 999; ui->setupUi(this); ui->buildingsTree->setModel(&buildingsModel); params = townEventListItem->data(Qt::UserRole).toMap(); - ui->eventFirstOccurrence->setMinimum(1); - ui->eventFirstOccurrence->setMaximum(999); - ui->eventRepeatAfter->setMaximum(999); + ui->eventFirstOccurrence->setMinimum(FIRST_DAY_FOR_EVENT); + ui->eventFirstOccurrence->setMaximum(LAST_DAY_FOR_EVENT); + ui->eventRepeatAfter->setMaximum(MAXIMUM_REPEAT_AFTER); ui->eventNameText->setText(params.value("name").toString()); ui->eventMessageText->setPlainText(params.value("message").toString()); ui->eventAffectsCpu->setChecked(params.value("computerAffected").toBool()); @@ -61,6 +64,10 @@ void TownEventDialog::initPlayers() void TownEventDialog::initResources() { + static const int MAXIUMUM_GOLD_CHANGE = 999999; + static const int MAXIUMUM_RESOURCE_CHANGE = 999; + static const int GOLD_STEP = 100; + static const int RESOURCE_STEP = 1; ui->resourcesTable->setRowCount(GameConstants::RESOURCE_QUANTITY); auto resourcesMap = params.value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) @@ -70,9 +77,9 @@ void TownEventDialog::initResources() ui->resourcesTable->setItem(i, 0, new QTableWidgetItem(name)); QSpinBox * edit = new QSpinBox(ui->resourcesTable); - edit->setMaximum(i == GameResID::GOLD ? 999999 : 999); - edit->setMinimum(i == GameResID::GOLD ? -999999 : -999); - edit->setSingleStep(i == GameResID::GOLD ? 100 : 1); + edit->setMaximum(i == GameResID::GOLD ? MAXIUMUM_GOLD_CHANGE : MAXIUMUM_RESOURCE_CHANGE); + edit->setMinimum(i == GameResID::GOLD ? -MAXIUMUM_GOLD_CHANGE : -MAXIUMUM_RESOURCE_CHANGE); + edit->setSingleStep(i == GameResID::GOLD ? GOLD_STEP : RESOURCE_STEP); edit->setValue(val); ui->resourcesTable->setCellWidget(i, 1, edit); @@ -147,9 +154,10 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil void TownEventDialog::initCreatures() { + static const int MAXIUMUM_CREATURES_CHANGE = 999999; auto creatures = params.value("creatures").toList(); auto * ctown = town.town; - for (int i = 0; i < 7; ++i) + for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i) { QString creatureNames; if (!ctown) @@ -174,7 +182,7 @@ void TownEventDialog::initCreatures() auto creatureNumber = creatures.size() > i ? creatures.at(i).toInt() : 0; QSpinBox* edit = new QSpinBox(ui->creaturesTable); edit->setValue(creatureNumber); - edit->setMaximum(999999); + edit->setMaximum(MAXIUMUM_CREATURES_CHANGE); ui->creaturesTable->setCellWidget(i, 1, edit); } @@ -235,7 +243,7 @@ QVariantList TownEventDialog::buildingsToVariant() QVariantList TownEventDialog::creaturesToVariant() { QVariantList creaturesList; - for (int i = 0; i < 7; ++i) + for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i) { auto * item = static_cast(ui->creaturesTable->cellWidget(i, 1)); creaturesList.push_back(item->value()); From 2b9461ed6ef90d0ce8f95a13b1c16489477d5e36 Mon Sep 17 00:00:00 2001 From: godric3 Date: Sun, 28 Jul 2024 16:07:52 +0200 Subject: [PATCH 12/76] use custom roles instead of Qt::UserRole --- mapeditor/CMakeLists.txt | 1 + mapeditor/inspector/townbuildingswidget.cpp | 11 ++++++----- mapeditor/inspector/towneventdialog.cpp | 15 +++++++++------ mapeditor/inspector/towneventswidget.cpp | 7 ++++--- mapeditor/inspector/townspellswidget.cpp | 7 ++++--- mapeditor/mapeditorroles.h | 20 ++++++++++++++++++++ 6 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 mapeditor/mapeditorroles.h diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 736a00fe0..4fc4679e6 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -85,6 +85,7 @@ set(editor_HEADERS inspector/PickObjectDelegate.h inspector/portraitwidget.h resourceExtractor/ResourceConverter.h + mapeditorroles.h ) set(editor_FORMS diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 14c3645bd..9cea3ddc6 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "townbuildingswidget.h" #include "ui_townbuildingswidget.h" +#include "mapeditorroles.h" #include "../lib/entities/building/CBuilding.h" #include "../lib/entities/faction/CTownHandler.h" #include "../lib/texts/CGeneralTextHandler.h" @@ -80,7 +81,7 @@ QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStan for (int i = 0; i < rowCount; ++i) { QModelIndex index = model.index(i, 0, pindex); - if (building->upgrade.getNum() == model.itemFromIndex(index)->data(Qt::UserRole).toInt()) + if (building->upgrade.getNum() == model.itemFromIndex(index)->data(MapEditorRoles::BuildingIDRole).toInt()) { parent = model.itemFromIndex(index); break; @@ -106,7 +107,7 @@ std::set getBuildingVariantsFromModel(QStandardItemModel & model, int QModelIndex index = model.index(i, modelColumn, pindex); if (auto * item = model.itemFromIndex(index)) if (item->checkState() == checkState) - result.emplace(item->data(Qt::UserRole)); + result.emplace(item->data(MapEditorRoles::BuildingIDRole)); index = model.index(i, 0, pindex); if (model.hasChildren(index)) stack.push_back(index); @@ -154,17 +155,17 @@ QStandardItem * TownBuildingsWidget::addBuilding(const CTown & ctown, int bId, s QList checks; checks << new QStandardItem(name); - checks.back()->setData(bId, Qt::UserRole); + checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); checks << new QStandardItem; checks.back()->setCheckable(true); checks.back()->setCheckState(town.forbiddenBuildings.count(buildingId) ? Qt::Unchecked : Qt::Checked); - checks.back()->setData(bId, Qt::UserRole); + checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); checks << new QStandardItem; checks.back()->setCheckable(true); checks.back()->setCheckState(town.builtBuildings.count(buildingId) ? Qt::Checked : Qt::Unchecked); - checks.back()->setData(bId, Qt::UserRole); + checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); if(building->getBase() == buildingId) { diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 00dfc30f7..6f5ecd2b1 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -12,6 +12,9 @@ #include "townbuildingswidget.h" #include "towneventdialog.h" #include "ui_towneventdialog.h" +#include "mapeditorroles.h" +#include "../../lib/entities/building/CBuilding.h" +#include "../../lib/entities/faction/CTownHandler.h" #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" @@ -28,7 +31,7 @@ TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWi ui->buildingsTree->setModel(&buildingsModel); - params = townEventListItem->data(Qt::UserRole).toMap(); + params = townEventListItem->data(MapEditorRoles::TownEventRole).toMap(); ui->eventFirstOccurrence->setMinimum(FIRST_DAY_FOR_EVENT); ui->eventFirstOccurrence->setMaximum(LAST_DAY_FOR_EVENT); ui->eventRepeatAfter->setMaximum(MAXIMUM_REPEAT_AFTER); @@ -56,7 +59,7 @@ void TownEventDialog::initPlayers() { bool isAffected = (1 << i) & params.value("players").toInt(); auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i])); - item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setData(MapEditorRoles::PlayerIDRole, QVariant::fromValue(i)); item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked); ui->playersAffected->addItem(item); } @@ -121,12 +124,12 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil QList checks; checks << new QStandardItem(name); - checks.back()->setData(bId, Qt::UserRole); + checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); checks << new QStandardItem; checks.back()->setCheckable(true); checks.back()->setCheckState(params["buildings"].toList().contains(bId) ? Qt::Checked : Qt::Unchecked); - checks.back()->setData(bId, Qt::UserRole); + checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); if (building->getBase() == buildingId) { @@ -203,7 +206,7 @@ void TownEventDialog::on_TownEventDialog_finished(int result) descriptor["buildings"] = buildingsToVariant(); descriptor["creatures"] = creaturesToVariant(); - townEventListItem->setData(Qt::UserRole, descriptor); + townEventListItem->setData(MapEditorRoles::TownEventRole, descriptor); auto itemText = tr("Day %1 - %2").arg(ui->eventFirstOccurrence->value(), 3).arg(ui->eventNameText->text()); townEventListItem->setText(itemText); } @@ -222,7 +225,7 @@ QVariant TownEventDialog::playersToVariant() QVariantMap TownEventDialog::resourcesToVariant() { - auto res = townEventListItem->data(Qt::UserRole).toMap().value("resources").toMap(); + auto res = params.value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) { auto * itemType = ui->resourcesTable->item(i, 0); diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index 16c2f7c8e..89f600a2b 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -12,6 +12,7 @@ #include "towneventswidget.h" #include "ui_towneventswidget.h" #include "towneventdialog.h" +#include "mapeditorroles.h" #include "mapsettings/eventsettings.h" #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" @@ -105,7 +106,7 @@ void TownEventsWidget::obtainData() auto itemText = tr("Day %1 - %2").arg(event.firstOccurrence+1, 3).arg(eventName); auto * item = new QListWidgetItem(itemText); - item->setData(Qt::UserRole, toVariant(event)); + item->setData(MapEditorRoles::TownEventRole, toVariant(event)); ui->eventsList->addItem(item); } } @@ -116,7 +117,7 @@ void TownEventsWidget::commitChanges(MapController& controller) for (int i = 0; i < ui->eventsList->count(); ++i) { const auto * item = ui->eventsList->item(i); - town.events.push_back(eventFromVariant(*controller.map(), town, item->data(Qt::UserRole))); + town.events.push_back(eventFromVariant(*controller.map(), town, item->data(MapEditorRoles::TownEventRole))); } } @@ -125,7 +126,7 @@ void TownEventsWidget::on_timedEventAdd_clicked() CCastleEvent event; event.name = tr("New event").toStdString(); auto* item = new QListWidgetItem(QString::fromStdString(event.name)); - item->setData(Qt::UserRole, toVariant(event)); + item->setData(MapEditorRoles::TownEventRole, toVariant(event)); ui->eventsList->addItem(item); on_eventsList_itemActivated(item); } diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp index 2403c986f..6bd8d0c99 100644 --- a/mapeditor/inspector/townspellswidget.cpp +++ b/mapeditor/inspector/townspellswidget.cpp @@ -11,6 +11,7 @@ #include "townspellswidget.h" #include "ui_townspellswidget.h" #include "inspector.h" +#include "mapeditorroles.h" #include "../../lib/constants/StringConstants.h" #include "../../lib/spells/CSpellHandler.h" @@ -83,13 +84,13 @@ void TownSpellsWidget::initSpellLists() { auto spell = spellID.toEntity(VLC); auto * possibleItem = new QListWidgetItem(QString::fromStdString(spell->getNameTranslated())); - possibleItem->setData(Qt::UserRole, QVariant::fromValue(spell->getIndex())); + possibleItem->setData(MapEditorRoles::SpellIDRole, QVariant::fromValue(spell->getIndex())); possibleItem->setFlags(possibleItem->flags() | Qt::ItemIsUserCheckable); possibleItem->setCheckState(vstd::contains(town.possibleSpells, spell->getId()) ? Qt::Checked : Qt::Unchecked); possibleSpellLists[i]->addItem(possibleItem); auto * requiredItem = new QListWidgetItem(QString::fromStdString(spell->getNameTranslated())); - requiredItem->setData(Qt::UserRole, QVariant::fromValue(spell->getIndex())); + requiredItem->setData(MapEditorRoles::SpellIDRole, QVariant::fromValue(spell->getIndex())); requiredItem->setFlags(requiredItem->flags() | Qt::ItemIsUserCheckable); requiredItem->setCheckState(vstd::contains(town.obligatorySpells, spell->getId()) ? Qt::Checked : Qt::Unchecked); requiredSpellLists[i]->addItem(requiredItem); @@ -113,7 +114,7 @@ void TownSpellsWidget::commitChanges() QListWidgetItem * item = spellList->item(i); if (item->checkState() == Qt::Checked) { - townSpellList.push_back(item->data(Qt::UserRole).toInt()); + townSpellList.push_back(item->data(MapEditorRoles::SpellIDRole).toInt()); } } } diff --git a/mapeditor/mapeditorroles.h b/mapeditor/mapeditorroles.h new file mode 100644 index 000000000..b0ea4e4a2 --- /dev/null +++ b/mapeditor/mapeditorroles.h @@ -0,0 +1,20 @@ +/* + * mapeditorroles.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 "StdInc.h" + +enum MapEditorRoles +{ + TownEventRole = Qt::UserRole +1, + PlayerIDRole, + BuildingIDRole, + SpellIDRole +}; From 3fb3fef16b8dcdb67d8dd7197b0bcbc7ffe67c38 Mon Sep 17 00:00:00 2001 From: godric3 Date: Sun, 28 Jul 2024 17:39:58 +0200 Subject: [PATCH 13/76] use range constructor or QVariantList::fromStdList based on Qt version --- mapeditor/inspector/townbuildingswidget.cpp | 6 +++--- mapeditor/inspector/townbuildingswidget.h | 2 +- mapeditor/inspector/towneventdialog.cpp | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 9cea3ddc6..eaa4719dd 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -93,9 +93,9 @@ QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStan return parent; } -std::set getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) +std::list getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) { - std::set result; + std::list result; std::vector stack(1); while (!stack.empty()) { @@ -107,7 +107,7 @@ std::set getBuildingVariantsFromModel(QStandardItemModel & model, int QModelIndex index = model.index(i, modelColumn, pindex); if (auto * item = model.itemFromIndex(index)) if (item->checkState() == checkState) - result.emplace(item->data(MapEditorRoles::BuildingIDRole)); + result.push_back(item->data(MapEditorRoles::BuildingIDRole)); index = model.index(i, 0, pindex); if (model.hasChildren(index)) stack.push_back(index); diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index 84a3186de..5e279c4ae 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -21,7 +21,7 @@ std::string defaultBuildingIdConversion(BuildingID bId); QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model); -std::set getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); +std::list getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); class TownBuildingsWidget : public QDialog { diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 6f5ecd2b1..937ffa2f6 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -239,7 +239,11 @@ QVariantMap TownEventDialog::resourcesToVariant() QVariantList TownEventDialog::buildingsToVariant() { auto buildings = getBuildingVariantsFromModel(buildingsModel, 1, Qt::Checked); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QVariantList buildingsList(buildings.begin(), buildings.end()); +#else + QVariantList buildingsList = QVariantList::fromStdList(buildings); +#endif return buildingsList; } From b6c22b2053f977b6dc869bc4c7179e787a18f36d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jul 2024 16:46:46 +0000 Subject: [PATCH 14/76] Fix typo --- lib/mapObjects/CGDwelling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 5375a39ec..058139e39 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -424,7 +424,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const if(count) //there are available creatures { - if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED)) + if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_MERGE_ON_RECRUIT)) { SlotID testSlot = h->getSlotFor(crid); if(!testSlot.validSlot()) //no available slot - try merging army of visiting hero From f9348fc84a2c3d8a101f797b7f350f75b708285a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jul 2024 16:58:22 +0000 Subject: [PATCH 15/76] Do not hide spells from reward if hero can't learn them --- lib/rewardable/Reward.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 0bdd5fb5d..11830b3c0 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -115,8 +115,7 @@ void Rewardable::Reward::loadComponents(std::vector & comps, const CG comps.emplace_back(ComponentType::ARTIFACT, entry); for(const auto & entry : spells) - if (!h || h->canLearnSpell(entry.toEntity(VLC), true)) - comps.emplace_back(ComponentType::SPELL, entry); + comps.emplace_back(ComponentType::SPELL, entry); for(const auto & entry : creatures) comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count); From 74fea5109ba818d30a882f0fe3344a24e6bd9d9e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jul 2024 18:09:12 +0000 Subject: [PATCH 16/76] Show non-learnable spells from rewardable object as greyed-out --- client/widgets/CComponent.cpp | 5 ++++- lib/rewardable/Reward.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 043dde6fa..0325e9ca9 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -290,7 +290,10 @@ std::string CComponent::getSubtitle() const return CGI->artifacts()->getById(data.subType.as())->getNameTranslated(); case ComponentType::SPELL_SCROLL: case ComponentType::SPELL: - return CGI->spells()->getById(data.subType.as())->getNameTranslated(); + if (data.value < 0) + return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as())->getNameTranslated() + "}"; + else + return CGI->spells()->getById(data.subType.as())->getNameTranslated(); case ComponentType::NONE: case ComponentType::MORALE: case ComponentType::LUCK: diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 11830b3c0..c52501a6b 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -115,7 +115,10 @@ void Rewardable::Reward::loadComponents(std::vector & comps, const CG comps.emplace_back(ComponentType::ARTIFACT, entry); for(const auto & entry : spells) - comps.emplace_back(ComponentType::SPELL, entry); + { + bool learnable = !h || h->canLearnSpell(entry.toEntity(VLC), true); + comps.emplace_back(ComponentType::SPELL, entry, learnable ? 0 : -1); + } for(const auto & entry : creatures) comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count); From 87b5f955d1a8efc8208d04f4fa10569a0c2fdabe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jul 2024 18:12:08 +0000 Subject: [PATCH 17/76] Fix some shortcuts not active during enemy turn in multiplayer --- client/adventureMap/AdventureMapShortcuts.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 5cc33fc28..269183d0e 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -553,12 +553,12 @@ bool AdventureMapShortcuts::optionSpellcasting() bool AdventureMapShortcuts::optionInMapView() { - return state == EAdventureState::MAKING_TURN; + return state == EAdventureState::MAKING_TURN || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; } bool AdventureMapShortcuts::optionInWorldView() { - return state == EAdventureState::WORLD_VIEW; + return state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; } bool AdventureMapShortcuts::optionSidePanelActive() From 434a2fb0fb143cc4f0e28948bf0b8b0ea4832478 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jul 2024 18:45:27 +0000 Subject: [PATCH 18/76] Explicitly specify to use ranged or melee attack for damage estimation --- client/battle/BattleActionsController.cpp | 8 ++++++-- lib/battle/CBattleInfoCallback.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index a4fe689a1..b66eea8f2 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -499,9 +499,12 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle case PossiblePlayerBattleAction::WALK_AND_ATTACK: case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return { + const auto * attacker = owner.stacksController->getActiveStack(); BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex); + int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0; DamageEstimation retaliation; - DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex, &retaliation); + BattleAttackInfo attackInfo(attacker, targetStack, distance, false ); + DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation); estimation.kills.max = std::min(estimation.kills.max, targetStack->getCount()); estimation.kills.min = std::min(estimation.kills.min, targetStack->getCount()); bool enemyMayBeKilled = estimation.kills.max == targetStack->getCount(); @@ -514,7 +517,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle const auto * shooter = owner.stacksController->getActiveStack(); DamageEstimation retaliation; - DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition(), &retaliation); + BattleAttackInfo attackInfo(shooter, targetStack, 0, true ); + DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation); estimation.kills.max = std::min(estimation.kills.max, targetStack->getCount()); estimation.kills.min = std::min(estimation.kills.min, targetStack->getCount()); return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available()); diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index c5f578412..914c20e0e 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -742,15 +742,15 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * { RETURN_IF_NOT_BATTLE({}); auto reachability = battleGetDistances(attacker, attacker->getPosition()); - int getMovementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0; - return battleEstimateDamage(attacker, defender, getMovementRange, retaliationDmg); + int movementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0; + return battleEstimateDamage(attacker, defender, movementRange, retaliationDmg); } -DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg) const +DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementRange, DamageEstimation * retaliationDmg) const { RETURN_IF_NOT_BATTLE({}); const bool shooting = battleCanShoot(attacker, defender->getPosition()); - const BattleAttackInfo bai(attacker, defender, getMovementRange, shooting); + const BattleAttackInfo bai(attacker, defender, movementRange, shooting); return battleEstimateDamage(bai, retaliationDmg); } From 5f0e6f7ce18fa6de10eb8cecf3096ac73ba4cfa6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 29 Jul 2024 15:57:49 +0000 Subject: [PATCH 19/76] Close all dialogs on start of new turn in MP --- client/CPlayerInterface.cpp | 61 ++++++++++++++++++++----------------- client/CPlayerInterface.h | 1 + 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 0129cf9c6..ad44404ce 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -169,40 +169,44 @@ void CPlayerInterface::initGameInterface(std::shared_ptr ENV, std:: adventureInt.reset(new AdventureMapInterface()); } +void CPlayerInterface::closeAllDialogs() +{ + // remove all active dialogs that do not expect query answer + for (;;) + { + auto adventureWindow = GH.windows().topWindow(); + auto infoWindow = GH.windows().topWindow(); + + if(adventureWindow != nullptr) + break; + + if(infoWindow && infoWindow->ID != QueryID::NONE) + break; + + if (infoWindow) + infoWindow->close(); + else + GH.windows().popWindows(1); + } + + if(castleInt) + castleInt->close(); + + castleInt = nullptr; + + // remove all pending dialogs that do not expect query answer + vstd::erase_if(dialogs, [](const std::shared_ptr & window){ + return window->ID == QueryID::NONE; + }); +} + void CPlayerInterface::playerEndsTurn(PlayerColor player) { EVENT_HANDLER_CALLED_BY_CLIENT; if (player == playerID) { makingTurn = false; - - // remove all active dialogs that do not expect query answer - for (;;) - { - auto adventureWindow = GH.windows().topWindow(); - auto infoWindow = GH.windows().topWindow(); - - if(adventureWindow != nullptr) - break; - - if(infoWindow && infoWindow->ID != QueryID::NONE) - break; - - if (infoWindow) - infoWindow->close(); - else - GH.windows().popWindows(1); - } - - if(castleInt) - castleInt->close(); - - castleInt = nullptr; - - // remove all pending dialogs that do not expect query answer - vstd::erase_if(dialogs, [](const std::shared_ptr & window){ - return window->ID == QueryID::NONE; - }); + closeAllDialogs(); } } @@ -284,6 +288,7 @@ void CPlayerInterface::gamePause(bool pause) void CPlayerInterface::yourTurn(QueryID queryID) { + closeAllDialogs(); CTutorialWindow::openWindowFirstTime(TutorialMode::TOUCH_ADVENTUREMAP); EVENT_HANDLER_CALLED_BY_CLIENT; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index fd03f76ed..1d1dcd01f 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -203,6 +203,7 @@ public: // public interface for use by client via LOCPLINT access void performAutosave(); void gamePause(bool pause); void endNetwork(); + void closeAllDialogs(); ///returns true if all events are processed internally bool capturedAllEvents(); From 15f37f8c4b55639c89ab1be99072ec8791b9aa18 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 29 Jul 2024 15:58:07 +0000 Subject: [PATCH 20/76] Fix possible crash on invalid SPELL_LIKE_ATTACK ability --- lib/constants/VariantIdentifier.h | 11 +++++++---- server/battles/BattleActionProcessor.cpp | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/constants/VariantIdentifier.h b/lib/constants/VariantIdentifier.h index 5e3002391..e1b9f83b2 100644 --- a/lib/constants/VariantIdentifier.h +++ b/lib/constants/VariantIdentifier.h @@ -32,18 +32,14 @@ public: int32_t getNum() const { int32_t result; - std::visit([&result] (const auto& v) { result = v.getNum(); }, value); - return result; } std::string toString() const { std::string result; - std::visit([&result] (const auto& v) { result = v.encode(v.getNum()); }, value); - return result; } @@ -58,6 +54,13 @@ public: return IdentifierType(); } + bool hasValue() const + { + bool result = false; + std::visit([&result] (const auto& v) { result = v.hasValue(); }, value); + return result; + } + template void serialize(Handler &h) { h & value; diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 44157097e..2867c5bed 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -494,7 +494,7 @@ bool BattleActionProcessor::doHealAction(const CBattleInfoCallback & battle, con else destStack = battle.battleGetUnitByPos(target.at(0).hexValue); - if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype == BonusSubtypeID()) + if(stack == nullptr || destStack == nullptr || !healerAbility || !healerAbility->subtype.hasValue()) { gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P"); } @@ -971,7 +971,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const } std::shared_ptr bonus = attacker->getFirstBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK)); - if(bonus && ranged) //TODO: make it work in melee? + if(bonus && ranged && bonus->subtype.hasValue()) //TODO: make it work in melee? { //this is need for displaying hit animation bat.flags |= BattleAttack::SPELL_LIKE; From 789e370950847f8c4e0e08650f8e5e308bfe8a55 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 29 Jul 2024 16:24:58 +0000 Subject: [PATCH 21/76] Add compatibility for old vcmi maps --- config/objects/moddables.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/objects/moddables.json b/config/objects/moddables.json index b97606eb9..422fe3532 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -155,18 +155,21 @@ "types" : { "boatNecropolis" : { "index" : 0, + "compatibilityIdentifiers" : [ "evil" ], "actualAnimation" : "AB01_.def", "overlayAnimation" : "ABM01_.def", "flagAnimations" : ["ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"] }, "boatCastle" : { "index" : 1, + "compatibilityIdentifiers" : [ "good" ], "actualAnimation" : "AB02_.def", "overlayAnimation" : "ABM02_.def", "flagAnimations" : ["ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"] }, "boatFortress" : { "index" : 2, + "compatibilityIdentifiers" : [ "neutral" ], "actualAnimation" : "AB03_.def", "overlayAnimation" : "ABM03_.def", "flagAnimations" : ["ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"] From f4f416eb16f252b4118b367e5aaf5b3f2ff6885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 29 Jul 2024 20:36:23 +0200 Subject: [PATCH 22/76] Add RMG option "forcePortal" --- config/schemas/template.json | 2 +- docs/modders/Random_Map_Template.md | 2 +- lib/rmg/CRmgTemplate.cpp | 3 +- lib/rmg/CRmgTemplate.h | 3 +- lib/rmg/modificators/ConnectionsPlacer.cpp | 54 ++++++++++++++++------ lib/rmg/modificators/ConnectionsPlacer.h | 3 +- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index a1a353a6d..564210632 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -99,7 +99,7 @@ "type": { "type" : "string", - "enum" : ["wide", "fictive", "repulsive"] + "enum" : ["wide", "fictive", "repulsive", "forcePortal"] } } }, diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index c133b80d8..bf43cb106 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -38,7 +38,7 @@ { "a" : "zoneA", "b" : "zoneB", "guard" : 5000, "road" : "false" }, { "a" : "zoneA", "b" : "zoneC", "guard" : 5000, "road" : "random" }, { "a" : "zoneB", "b" : "zoneC", "type" : "wide" } - //"type" can be "guarded" (default), "wide", "fictive" or "repulsive" + //"type" can be "guarded" (default), "wide", "fictive", "repulsive" or "forcePortal" //"wide" connections have no border, or guard. "fictive" and "repulsive" connections are virtual - //they do not create actual path, but only attract or repulse zones, respectively ] diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index e5fb4836b..f678b47d3 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -463,7 +463,8 @@ void ZoneConnection::serializeJson(JsonSerializeFormat & handler) "guarded", "fictive", "repulsive", - "wide" + "wide", + "forcePortal" }; static const std::vector roadOptions = diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 1d0382ad1..ba5e49f9b 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -75,7 +75,8 @@ enum class EConnectionType GUARDED = 0, //default FICTIVE, REPULSIVE, - WIDE + WIDE, + FORCE_PORTAL }; enum class ERoadOption diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index d6bc3c2c5..ab53acce0 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -74,6 +74,11 @@ void ConnectionsPlacer::process() } }; + diningPhilosophers([this](const rmg::ZoneConnection& c) + { + forcePortalConnection(c); + }); + diningPhilosophers([this](const rmg::ZoneConnection& c) { selfSideDirectConnection(c); @@ -115,6 +120,16 @@ void ConnectionsPlacer::otherSideConnection(const rmg::ZoneConnection & connecti dCompleted.push_back(connection); } +void ConnectionsPlacer::forcePortalConnection(const rmg::ZoneConnection & connection) +{ + // This should always succeed + if (connection.getConnectionType() == rmg::EConnectionType::FORCE_PORTAL) + { + placeMonolithConnection(connection); + dCompleted.push_back(connection); + } +} + void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & connection) { bool success = false; @@ -410,23 +425,32 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c //4. place monoliths/portals if(!success) { - auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex()); - auto * teleport1 = factory->create(map.mapInstance->cb, nullptr); - auto * teleport2 = factory->create(map.mapInstance->cb, nullptr); - - RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad); - RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad); - zone.getModificator()->addRequiredObject(obj1); - otherZone->getModificator()->addRequiredObject(obj2); - - assert(otherZone->getModificator()); - otherZone->getModificator()->otherSideConnection(connection); - - success = true; + placeMonolithConnection(connection); } - - if(success) + else + { dCompleted.push_back(connection); + } +} + +void ConnectionsPlacer::placeMonolithConnection(const rmg::ZoneConnection & connection) +{ + auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA()); + auto & otherZone = map.getZones().at(otherZoneId); + + bool allowRoad = shouldGenerateRoad(connection); + + auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex()); + auto * teleport1 = factory->create(map.mapInstance->cb, nullptr); + auto * teleport2 = factory->create(map.mapInstance->cb, nullptr); + + RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad); + RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad); + zone.getModificator()->addRequiredObject(obj1); + otherZone->getModificator()->addRequiredObject(obj2); + + assert(otherZone->getModificator()); + otherZone->getModificator()->otherSideConnection(connection); } void ConnectionsPlacer::collectNeighbourZones() diff --git a/lib/rmg/modificators/ConnectionsPlacer.h b/lib/rmg/modificators/ConnectionsPlacer.h index 0350d6d92..ad31609b1 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.h +++ b/lib/rmg/modificators/ConnectionsPlacer.h @@ -23,7 +23,8 @@ public: void init() override; void addConnection(const rmg::ZoneConnection& connection); - + void placeMonolithConnection(const rmg::ZoneConnection& connection); + void forcePortalConnection(const rmg::ZoneConnection & connection); void selfSideDirectConnection(const rmg::ZoneConnection & connection); void selfSideIndirectConnection(const rmg::ZoneConnection & connection); void otherSideConnection(const rmg::ZoneConnection & connection); From d10711928fd87a6cb3f163c22ed926d8935b3c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 29 Jul 2024 21:56:07 +0200 Subject: [PATCH 23/76] Allow connecting zone to itself (always through the portal) --- lib/rmg/modificators/ConnectionsPlacer.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index ab53acce0..f585cb60d 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -55,6 +55,18 @@ void ConnectionsPlacer::process() { for (auto& c : dConnections) { + if (c.getZoneA() == c.getZoneB()) + { + // Zone can always be connected to itself, but only by monolith pair + RecursiveLock lock(externalAccessMutex); + if (!vstd::contains(dCompleted, c)) + { + placeMonolithConnection(c); + // FIXME: Two pair of monoliths are added instead of 1 + continue; + } + } + auto otherZone = map.getZones().at(c.getZoneB()); auto* cp = otherZone->getModificator(); @@ -126,7 +138,6 @@ void ConnectionsPlacer::forcePortalConnection(const rmg::ZoneConnection & connec if (connection.getConnectionType() == rmg::EConnectionType::FORCE_PORTAL) { placeMonolithConnection(connection); - dCompleted.push_back(connection); } } @@ -427,10 +438,6 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c { placeMonolithConnection(connection); } - else - { - dCompleted.push_back(connection); - } } void ConnectionsPlacer::placeMonolithConnection(const rmg::ZoneConnection & connection) @@ -448,6 +455,8 @@ void ConnectionsPlacer::placeMonolithConnection(const rmg::ZoneConnection & conn RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad); zone.getModificator()->addRequiredObject(obj1); otherZone->getModificator()->addRequiredObject(obj2); + + dCompleted.push_back(connection); assert(otherZone->getModificator()); otherZone->getModificator()->otherSideConnection(connection); From a7fa3c7d8b625a954f25ebbb67c739263b64b0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 30 Jul 2024 05:07:05 +0200 Subject: [PATCH 24/76] Ignore new connections for zone placement. --- lib/rmg/CZonePlacer.cpp | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 94719a983..29b1acfc6 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -80,12 +80,21 @@ void CZonePlacer::findPathsBetweenZones() for (auto & connection : connectedZoneIds) { - if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) + switch (connection.getConnectionType()) { //Do not consider virtual connections for graph distance - continue; + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; } auto neighbor = connection.getOtherZoneId(current); + + if (current == neighbor) + { + //Do not consider self-connections + continue; + } + if (!visited[neighbor]) { visited[neighbor] = true; @@ -552,8 +561,16 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, for (const auto & connection : zone.second->getConnections()) { - if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) + switch (connection.getConnectionType()) { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections continue; } @@ -710,11 +727,19 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista std::set connectedZones; for (const auto& connection : firstZone->getConnections()) { - //FIXME: Should we also exclude fictive connections? - if (connection.getConnectionType() != rmg::EConnectionType::REPULSIVE) + switch (connection.getConnectionType()) { - connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); } auto level = firstZone->getCenter().z; From 4413f03276d230b23ae8068c306a8ece2022b054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 30 Jul 2024 08:50:33 +0200 Subject: [PATCH 25/76] Cleanup --- lib/rmg/modificators/ConnectionsPlacer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index f585cb60d..79c4ff438 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -62,8 +62,7 @@ void ConnectionsPlacer::process() if (!vstd::contains(dCompleted, c)) { placeMonolithConnection(c); - // FIXME: Two pair of monoliths are added instead of 1 - continue; + continue; } } From 0d1f744ba474ce0ff1d9bc57d99dc09204ebfdd5 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:40:49 +0200 Subject: [PATCH 26/76] limit text sizes --- client/globalLobby/GlobalLobbyLoginWindow.cpp | 2 +- client/globalLobby/GlobalLobbyRoomWindow.cpp | 6 +++--- client/globalLobby/GlobalLobbyWidget.cpp | 6 +++--- client/gui/InterfaceObjectConfigurable.cpp | 3 ++- config/widgets/lobbyWindow.json | 3 ++- docs/modders/Configurable_Widgets.md | 4 +++- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 1c8f98e85..b34c4d587 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -43,7 +43,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title")); labelUsernameTitle = std::make_shared( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username")); - labelUsername = std::make_shared( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString()); + labelUsername = std::make_shared( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString(), 265); backgroundUsername = std::make_shared(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); inputUsername = std::make_shared(Rect(15, 93, 260, 16), FONT_SMALL, ETextAlignment::CENTERLEFT, true); buttonLogin = std::make_shared(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }, EShortcut::GLOBAL_ACCEPT); diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index 65ec73857..8dc7988be 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -37,7 +37,7 @@ GlobalLobbyRoomAccountCard::GlobalLobbyRoomAccountCard(const GlobalLobbyAccount pos.w = 130; pos.h = 40; backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1); - labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName); + labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120); labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status); } @@ -56,7 +56,7 @@ GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & mo pos.h = 40; backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1); - labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName); + labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName, 190); labelVersion = std::make_shared(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version); labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); } @@ -142,7 +142,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.title").toString()); - labelSubtitle = std::make_shared( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString()); + labelSubtitle = std::make_shared( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString(), 400); labelVersionTitle = std::make_shared( 10, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.version").toString()); labelVersionValue = std::make_shared( 10, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.gameVersion); diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index a6af670db..7b71937b6 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -207,7 +207,7 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const : GlobalLobbyChannelCardBase(window, Point(130, 40), "player", accountDescription.accountID, accountDescription.displayName) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName); + labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120); labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status); } @@ -238,8 +238,8 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa else backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1); - labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName); - labelDescription = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description); + labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName, 180); + labelDescription = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description, 160); labelRoomSize = std::make_shared(212, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString()); labelRoomStatus = std::make_shared(225, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString()); iconRoomSize = std::make_shared(ImagePath::builtin("lobby/iconPlayer"), Point(214, 5)); diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 5bcf45e17..11a2e4ade 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -345,7 +345,8 @@ std::shared_ptr InterfaceObjectConfigurable::buildLabel(const JsonNode & auto color = readColor(config["color"]); auto text = readText(config["text"]); auto position = readPosition(config["position"]); - return std::make_shared(position.x, position.y, font, alignment, color, text); + auto maxWidth = config["maxWidth"].Integer(); + return std::make_shared(position.x, position.y, font, alignment, color, text, maxWidth); } std::shared_ptr InterfaceObjectConfigurable::buildMultiLineLabel(const JsonNode & config) const diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index 529d26ce5..118a60317 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -43,7 +43,8 @@ { "name" : "accountNameLabel", "type": "labelTitleMain", - "position": {"x": 15, "y": 10} + "position": {"x": 15, "y": 10}, + "maxWidth": 230 }, { diff --git a/docs/modders/Configurable_Widgets.md b/docs/modders/Configurable_Widgets.md index 0649b866b..a1d40f76a 100644 --- a/docs/modders/Configurable_Widgets.md +++ b/docs/modders/Configurable_Widgets.md @@ -461,7 +461,9 @@ Configurable object has following structure: `"text"`: [text](#text), -`"position"`: [position](#position) +`"position"`: [position](#position), + +`"maxWidth"`: int` optional, trim longer text ### [VCMI-1.4] Multi-line label From 0cc743f7caa366eb3e76a0cdb46aa7a16f5a2129 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:10:37 +0200 Subject: [PATCH 27/76] mod compatibility improvements --- client/globalLobby/GlobalLobbyRoomWindow.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index 65ec73857..282b1a2f2 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -58,7 +58,7 @@ GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & mo labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName); labelVersion = std::make_shared(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version); - labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); + labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, modInfo.status == ModVerificationStatus::FULL_MATCH ? Colors::YELLOW : Colors::RED, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); } static const std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDescription, const std::vector & modVerificationList) @@ -134,6 +134,13 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s modVerificationList.push_back(modInfo); } + std::sort(modVerificationList.begin(), modVerificationList.end(), [](const GlobalLobbyRoomModInfo &a, const GlobalLobbyRoomModInfo &b) + { + if(a.status == b.status) + return a.modName < b.modName; + + return a.status < b.status; + }); MetaString subtitleText; subtitleText.appendTextID("vcmi.lobby.preview.subtitle"); From 12af016eaca4dbdbe9770d6df28e73b20424d1ae Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:25:05 +0200 Subject: [PATCH 28/76] more colors for status --- client/globalLobby/GlobalLobbyRoomWindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index 282b1a2f2..20cde882c 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -58,7 +58,12 @@ GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & mo labelName = std::make_shared(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName); labelVersion = std::make_shared(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version); - labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, modInfo.status == ModVerificationStatus::FULL_MATCH ? Colors::YELLOW : Colors::RED, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); + auto statusColor = Colors::RED; + if(modInfo.status == ModVerificationStatus::FULL_MATCH) + statusColor = ColorRGBA(128, 128, 128); + else if(modInfo.status == ModVerificationStatus::VERSION_MISMATCH) + statusColor = Colors::YELLOW; + labelStatus = std::make_shared(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, statusColor, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); } static const std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDescription, const std::vector & modVerificationList) From fb171ab3a21d61e94d7f1f004a1d014580e10600 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 27 Jul 2024 02:11:26 +0200 Subject: [PATCH 29/76] statisic basic --- client/ClientCommandManager.cpp | 19 ++++++++++++ client/ClientCommandManager.h | 3 ++ lib/CMakeLists.txt | 2 ++ lib/gameState/CGameState.h | 5 +++ lib/gameState/GameStatistics.cpp | 34 +++++++++++++++++++++ lib/gameState/GameStatistics.h | 42 ++++++++++++++++++++++++++ lib/serializer/ESerializationVersion.h | 3 +- server/CGameHandler.cpp | 18 +++++++++++ server/CGameHandler.h | 1 + 9 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 lib/gameState/GameStatistics.cpp create mode 100644 lib/gameState/GameStatistics.h diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 79ef59688..283c5e1cd 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -355,6 +355,22 @@ void ClientCommandManager::handleGetScriptsCommand() #endif } +void ClientCommandManager::handleGetStatistic() +{ + printCommandMessage("Command accepted.\t"); + + const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "statistic"; + boost::filesystem::create_directories(outPath); + + const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv"); + std::ofstream file(filePath.c_str()); + std::string csv = CSH->client->gameState()->statistic.toCsv(); + file << csv; + + printCommandMessage("Writing statistic done :)\n"); + printCommandMessage("Statistic files can be found in " + outPath.string() + " directory\n"); +} + void ClientCommandManager::handleGetTextCommand() { printCommandMessage("Command accepted.\t"); @@ -597,6 +613,9 @@ void ClientCommandManager::processCommand(const std::string & message, bool call else if(message=="get scripts") handleGetScriptsCommand(); + else if(message=="get statistic") + handleGetStatistic(); + else if(message=="get txt") handleGetTextCommand(); diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 63c5a96dd..c5190d599 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -57,6 +57,9 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a // Dumps all scripts in Extracted/Scripts void handleGetScriptsCommand(); + // Dumps statistic in file + void handleGetStatistic(); + // Dumps all .txt files from DATA into Extracted/DATA void handleGetTextCommand(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6c462a53b..13e17faca 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -102,6 +102,7 @@ set(lib_MAIN_SRCS gameState/InfoAboutArmy.cpp gameState/RumorState.cpp gameState/TavernHeroesPool.cpp + gameState/GameStatistics.cpp mapObjectConstructors/AObjectTypeHandler.cpp mapObjectConstructors/CBankInstanceConstructor.cpp @@ -468,6 +469,7 @@ set(lib_MAIN_HEADERS gameState/RumorState.h gameState/SThievesGuildInfo.h gameState/TavernHeroesPool.h + gameState/GameStatistics.h gameState/TavernSlot.h gameState/QuestInfo.h diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index f7814fde9..c63f5cc52 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -15,6 +15,7 @@ #include "../ConstTransitivePtr.h" #include "RumorState.h" +#include "GameStatistics.h" namespace boost { @@ -90,6 +91,8 @@ public: CBonusSystemNode globalEffects; RumorState currentRumor; + StatisticDataSet statistic; + static boost::shared_mutex mutex; void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override; @@ -167,6 +170,8 @@ public: h & currentRumor; h & campaign; h & allocatedArtifacts; + if (h.version >= Handler::Version::STATISTICS) + h & statistic; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp new file mode 100644 index 000000000..a9d909e68 --- /dev/null +++ b/lib/gameState/GameStatistics.cpp @@ -0,0 +1,34 @@ +/* + * GameStatistics.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 "GameStatistics.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void StatisticDataSet::add(StatisticDataSetEntry entry) +{ + data.push_back(entry); +} + +std::string StatisticDataSet::toCsv() +{ + std::stringstream ss; + + ss << "Day" << ";" << "Player" << "\r\n"; + + for(auto & entry : data) + { + ss << entry.day << ";" << entry.player.getNum() << "\r\n"; + } + + return ss.str(); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h new file mode 100644 index 000000000..370b8aad8 --- /dev/null +++ b/lib/gameState/GameStatistics.h @@ -0,0 +1,42 @@ +/* + * GameSTatistics.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 "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE StatisticDataSetEntry +{ + int day; + PlayerColor player; + + template void serialize(Handler &h) + { + h & day; + h & player; + } +}; + +class DLL_LINKAGE StatisticDataSet +{ + std::vector data; + +public: + void add(StatisticDataSetEntry entry); + std::string toCsv(); + + template void serialize(Handler &h) + { + h & data; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7c546b71e..0fd7291b1 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -58,6 +58,7 @@ enum class ESerializationVersion : int32_t MAP_FORMAT_ADDITIONAL_INFOS, // 848 - serialize new infos in map format REMOVE_LIB_RNG, // 849 - removed random number generators from library classes HIGHSCORE_PARAMETERS, // 850 - saves parameter for campaign + STATISTICS, // 851 - removed random number generators from library classes - CURRENT = HIGHSCORE_PARAMETERS + CURRENT = STATISTICS }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a72f327ff..613f92f07 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -669,6 +669,22 @@ void CGameHandler::onPlayerTurnEnded(PlayerColor which) heroPool->onNewWeek(which); } +void CGameHandler::addStatistics() +{ + for (auto & elem : gs->players) + { + if (elem.first == PlayerColor::NEUTRAL || !elem.first.isValidPlayer()) + continue; + + StatisticDataSetEntry data; + + data.day = getDate(Date::DAY); + data.player = elem.first; + + gameState()->statistic.add(data); + } +} + void CGameHandler::onNewTurn() { logGlobal->trace("Turn %d", gs->day+1); @@ -1002,6 +1018,8 @@ void CGameHandler::onNewTurn() } synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that + + addStatistics(); } void CGameHandler::start(bool resume) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index ad90a0bbe..00a1c0738 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -226,6 +226,7 @@ public: void onPlayerTurnStarted(PlayerColor which); void onPlayerTurnEnded(PlayerColor which); void onNewTurn(); + void addStatistics(); void handleTimeEvents(); void handleTownEvents(CGTownInstance *town, NewTurn &n); From 490f1bfee676941cc036e607403d077d91f4d0ab Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:30:53 +0200 Subject: [PATCH 30/76] rework --- client/ClientCommandManager.cpp | 19 ------------- client/ClientCommandManager.h | 3 -- lib/gameState/GameStatistics.cpp | 29 ++++++++++++++++++-- lib/gameState/GameStatistics.h | 13 +++++++++ server/CGameHandler.cpp | 5 +--- server/processors/PlayerMessageProcessor.cpp | 21 ++++++++++++++ server/processors/PlayerMessageProcessor.h | 1 + 7 files changed, 63 insertions(+), 28 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 283c5e1cd..79ef59688 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -355,22 +355,6 @@ void ClientCommandManager::handleGetScriptsCommand() #endif } -void ClientCommandManager::handleGetStatistic() -{ - printCommandMessage("Command accepted.\t"); - - const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "statistic"; - boost::filesystem::create_directories(outPath); - - const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv"); - std::ofstream file(filePath.c_str()); - std::string csv = CSH->client->gameState()->statistic.toCsv(); - file << csv; - - printCommandMessage("Writing statistic done :)\n"); - printCommandMessage("Statistic files can be found in " + outPath.string() + " directory\n"); -} - void ClientCommandManager::handleGetTextCommand() { printCommandMessage("Command accepted.\t"); @@ -613,9 +597,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call else if(message=="get scripts") handleGetScriptsCommand(); - else if(message=="get statistic") - handleGetStatistic(); - else if(message=="get txt") handleGetTextCommand(); diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index c5190d599..63c5a96dd 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -57,9 +57,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a // Dumps all scripts in Extracted/Scripts void handleGetScriptsCommand(); - // Dumps statistic in file - void handleGetStatistic(); - // Dumps all .txt files from DATA into Extracted/DATA void handleGetTextCommand(); diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index a9d909e68..49c489ccc 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -9,6 +9,9 @@ */ #include "StdInc.h" #include "GameStatistics.h" +#include "../CPlayerState.h" +#include "../constants/StringConstants.h" +#include "CGameState.h" VCMI_LIB_NAMESPACE_BEGIN @@ -17,15 +20,37 @@ void StatisticDataSet::add(StatisticDataSetEntry entry) data.push_back(entry); } +StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs) +{ + StatisticDataSetEntry data; + + data.day = gs->getDate(Date::DAY); + data.player = ps->color; + data.team = ps->team; + data.resources = ps->resources; + data.heroesCount = ps->heroes.size(); + data.townCount = ps->towns.size(); + + return data; +} + std::string StatisticDataSet::toCsv() { std::stringstream ss; - ss << "Day" << ";" << "Player" << "\r\n"; + auto resources = std::vector{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS}; + + ss << "Day" << ";" << "Player" << ";" << "Team" << ";" << "HeroesCount" << ";" << "TownCount"; + for(auto & resource : resources) + ss << ";" << GameConstants::RESOURCE_NAMES[resource]; + ss << "\r\n"; for(auto & entry : data) { - ss << entry.day << ";" << entry.player.getNum() << "\r\n"; + ss << entry.day << ";" << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";" << entry.team.getNum() << ";" << entry.heroesCount << ";" << entry.townCount; + for(auto & resource : resources) + ss << ";" << entry.resources[resource]; + ss << "\r\n"; } return ss.str(); diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 370b8aad8..3edf366d8 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -10,18 +10,30 @@ #pragma once #include "../GameConstants.h" +#include "../ResourceSet.h" VCMI_LIB_NAMESPACE_BEGIN +struct PlayerState; +class CGameState; + struct DLL_LINKAGE StatisticDataSetEntry { int day; PlayerColor player; + TeamID team; + TResources resources; + int heroesCount; + int townCount; template void serialize(Handler &h) { h & day; h & player; + h & team; + h & resources; + h & heroesCount; + h & townCount; } }; @@ -31,6 +43,7 @@ class DLL_LINKAGE StatisticDataSet public: void add(StatisticDataSetEntry entry); + static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs); std::string toCsv(); template void serialize(Handler &h) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 613f92f07..12b3fb3d7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -676,10 +676,7 @@ void CGameHandler::addStatistics() if (elem.first == PlayerColor::NEUTRAL || !elem.first.isValidPlayer()) continue; - StatisticDataSetEntry data; - - data.day = getDate(Date::DAY); - data.player = elem.first; + auto data = StatisticDataSet::createEntry(&elem.second, gs); gameState()->statistic.add(data); } diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 4ed115559..6ff2b93f5 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -29,6 +29,7 @@ #include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/StackLocation.h" #include "../../lib/serializer/Connection.h" +#include "../lib/VCMIDirs.h" PlayerMessageProcessor::PlayerMessageProcessor(CGameHandler * gameHandler) :gameHandler(gameHandler) @@ -133,12 +134,30 @@ void PlayerMessageProcessor::commandCheaters(PlayerColor player, const std::vect broadcastSystemMessage("No cheaters registered!"); } +void PlayerMessageProcessor::commandStatistic(PlayerColor player, const std::vector & words) +{ + bool isHost = gameHandler->gameLobby()->isPlayerHost(player); + if(!isHost) + return; + + const boost::filesystem::path outPath = VCMIDirs::get().userCachePath() / "statistic"; + boost::filesystem::create_directories(outPath); + + const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv"); + std::ofstream file(filePath.c_str()); + std::string csv = gameHandler->gameState()->statistic.toCsv(); + file << csv; + + broadcastSystemMessage("Statistic files can be found in " + outPath.string() + " directory\n"); +} + void PlayerMessageProcessor::commandHelp(PlayerColor player, const std::vector & words) { broadcastSystemMessage("Available commands to host:"); broadcastSystemMessage("'!exit' - immediately ends current game"); broadcastSystemMessage("'!kick ' - kick specified player from the game"); broadcastSystemMessage("'!save ' - save game under specified filename"); + broadcastSystemMessage("'!statistic' - save game statistics as csv file"); broadcastSystemMessage("Available commands to all players:"); broadcastSystemMessage("'!help' - display this help"); broadcastSystemMessage("'!cheaters' - list players that entered cheat command during game"); @@ -319,6 +338,8 @@ void PlayerMessageProcessor::handleCommand(PlayerColor player, const std::string commandSave(player, words); if(words[0] == "!cheaters") commandCheaters(player, words); + if(words[0] == "!statistic") + commandStatistic(player, words); } void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero) diff --git a/server/processors/PlayerMessageProcessor.h b/server/processors/PlayerMessageProcessor.h index 810c25188..8671817e8 100644 --- a/server/processors/PlayerMessageProcessor.h +++ b/server/processors/PlayerMessageProcessor.h @@ -62,6 +62,7 @@ class PlayerMessageProcessor void commandKick(PlayerColor player, const std::vector & words); void commandSave(PlayerColor player, const std::vector & words); void commandCheaters(PlayerColor player, const std::vector & words); + void commandStatistic(PlayerColor player, const std::vector & words); void commandHelp(PlayerColor player, const std::vector & words); void commandVote(PlayerColor player, const std::vector & words); From 405bc09780f545a2819293b1cca66f52a4ada53f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:42:32 +0200 Subject: [PATCH 31/76] Update client/battle/BattleInterfaceClasses.cpp Co-authored-by: Ivan Savenko --- client/battle/BattleInterfaceClasses.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index b9775eba8..a440af1ba 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -460,8 +460,9 @@ std::vector> QuickSpellPanel::getSpells() if(spellIds[i] != SpellID::NONE) continue; - for(auto & availableSpell : CGI->spellh->objects) + for(const auto & availableSpellID : CGI->spellh->getDefaultAllowed()) { + const auto * availableSpell = availableSpellID->toSpell(); if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell.get()) && !vstd::contains(spellIds, availableSpell->getId())) { spellIds[i] = availableSpell->getId(); From 27f83449f23c0c786faccd6d07a22d2cb8a4f722 Mon Sep 17 00:00:00 2001 From: godric3 Date: Thu, 1 Aug 2024 22:28:23 +0200 Subject: [PATCH 32/76] PR review fixes: - change return of `getBuildingVariantsFromModel` to `QVariantList` - change while to do-while where it makes sense - moved constants outside of methods - made building and resources name non editable --- mapeditor/inspector/townbuildingswidget.cpp | 27 +++++----- mapeditor/inspector/townbuildingswidget.h | 2 +- mapeditor/inspector/towneventdialog.cpp | 58 +++++++++------------ mapeditor/inspector/towneventdialog.ui | 3 ++ mapeditor/inspector/townspellswidget.cpp | 15 ++---- mapeditor/mapeditorroles.h | 6 +-- 6 files changed, 48 insertions(+), 63 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index eaa4719dd..5ade9c09b 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -73,7 +73,7 @@ QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStan { QStandardItem * parent = nullptr; std::vector stack(1); - while (!parent && !stack.empty()) + do { auto pindex = stack.back(); stack.pop_back(); @@ -89,15 +89,15 @@ QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStan if (model.hasChildren(index)) stack.push_back(index); } - } + } while(!parent && !stack.empty()); return parent; } -std::list getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) +QVariantList getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) { - std::list result; + QVariantList result; std::vector stack(1); - while (!stack.empty()) + do { auto pindex = stack.back(); stack.pop_back(); @@ -105,14 +105,14 @@ std::list getBuildingVariantsFromModel(QStandardItemModel & model, int for (int i = 0; i < rowCount; ++i) { QModelIndex index = model.index(i, modelColumn, pindex); - if (auto * item = model.itemFromIndex(index)) - if (item->checkState() == checkState) - result.push_back(item->data(MapEditorRoles::BuildingIDRole)); + auto * item = model.itemFromIndex(index); + if(item && item->checkState() == checkState) + result.push_back(item->data(MapEditorRoles::BuildingIDRole)); index = model.index(i, 0, pindex); if (model.hasChildren(index)) stack.push_back(index); } - } + } while(!stack.empty()); return result; } @@ -263,7 +263,7 @@ void TownBuildingsWidget::setRowColumnCheckState(QStandardItem * item, Column co void TownBuildingsWidget::setAllRowsColumnCheckState(Column column, Qt::CheckState checkState) { std::vector stack(1); - while (!stack.empty()) + do { auto parentIndex = stack.back(); stack.pop_back(); @@ -277,7 +277,7 @@ void TownBuildingsWidget::setAllRowsColumnCheckState(Column column, Qt::CheckSta if (model.hasChildren(index)) stack.push_back(index); } - } + } while(!stack.empty()); } void TownBuildingsWidget::onItemChanged(QStandardItem * item) { @@ -297,7 +297,8 @@ void TownBuildingsWidget::onItemChanged(QStandardItem * item) { else if (item->checkState() == Qt::Unchecked) { std::vector stack; stack.push_back(nextRow); - while (!stack.empty()) { + do + { nextRow = stack.back(); stack.pop_back(); setRowColumnCheckState(nextRow, Column(item->column()), Qt::Unchecked); @@ -310,7 +311,7 @@ void TownBuildingsWidget::onItemChanged(QStandardItem * item) { } } - } + } while(!stack.empty()); } connect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); } diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index 5e279c4ae..029effe3f 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -21,7 +21,7 @@ std::string defaultBuildingIdConversion(BuildingID bId); QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model); -std::list getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); +QVariantList getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); class TownBuildingsWidget : public QDialog { diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 937ffa2f6..5ad22366c 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -18,15 +18,23 @@ #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" +static const int FIRST_DAY_FOR_EVENT = 1; +static const int LAST_DAY_FOR_EVENT = 999; +static const int MAXIMUM_EVENT_REPEAT_AFTER = 999; + +static const int MAXIMUM_GOLD_CHANGE = 999999; +static const int MAXIMUM_RESOURCE_CHANGE = 999; +static const int GOLD_STEP = 100; +static const int RESOURCE_STEP = 1; + +static const int MAXIMUM_CREATURES_CHANGE = 999999; + TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWidget * parent) : QDialog(parent), ui(new Ui::TownEventDialog), town(t), townEventListItem(item) { - static const int FIRST_DAY_FOR_EVENT = 1; - static const int LAST_DAY_FOR_EVENT = 999; - static const int MAXIMUM_REPEAT_AFTER = 999; ui->setupUi(this); ui->buildingsTree->setModel(&buildingsModel); @@ -34,7 +42,7 @@ TownEventDialog::TownEventDialog(CGTownInstance & t, QListWidgetItem * item, QWi params = townEventListItem->data(MapEditorRoles::TownEventRole).toMap(); ui->eventFirstOccurrence->setMinimum(FIRST_DAY_FOR_EVENT); ui->eventFirstOccurrence->setMaximum(LAST_DAY_FOR_EVENT); - ui->eventRepeatAfter->setMaximum(MAXIMUM_REPEAT_AFTER); + ui->eventRepeatAfter->setMaximum(MAXIMUM_EVENT_REPEAT_AFTER); ui->eventNameText->setText(params.value("name").toString()); ui->eventMessageText->setPlainText(params.value("message").toString()); ui->eventAffectsCpu->setChecked(params.value("computerAffected").toBool()); @@ -67,21 +75,20 @@ void TownEventDialog::initPlayers() void TownEventDialog::initResources() { - static const int MAXIUMUM_GOLD_CHANGE = 999999; - static const int MAXIUMUM_RESOURCE_CHANGE = 999; - static const int GOLD_STEP = 100; - static const int RESOURCE_STEP = 1; ui->resourcesTable->setRowCount(GameConstants::RESOURCE_QUANTITY); auto resourcesMap = params.value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) { auto name = QString::fromStdString(GameConstants::RESOURCE_NAMES[i]); - int val = resourcesMap.value(name).toInt(); - ui->resourcesTable->setItem(i, 0, new QTableWidgetItem(name)); + auto * item = new QTableWidgetItem(); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + item->setText(name); + ui->resourcesTable->setItem(i, 0, item); + int val = resourcesMap.value(name).toInt(); QSpinBox * edit = new QSpinBox(ui->resourcesTable); - edit->setMaximum(i == GameResID::GOLD ? MAXIUMUM_GOLD_CHANGE : MAXIUMUM_RESOURCE_CHANGE); - edit->setMinimum(i == GameResID::GOLD ? -MAXIUMUM_GOLD_CHANGE : -MAXIUMUM_RESOURCE_CHANGE); + edit->setMaximum(i == GameResID::GOLD ? MAXIMUM_GOLD_CHANGE : MAXIMUM_RESOURCE_CHANGE); + edit->setMinimum(i == GameResID::GOLD ? -MAXIMUM_GOLD_CHANGE : -MAXIMUM_RESOURCE_CHANGE); edit->setSingleStep(i == GameResID::GOLD ? GOLD_STEP : RESOURCE_STEP); edit->setValue(val); @@ -110,11 +117,6 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil { auto bId = buildingId.num; const CBuilding * building = ctown.buildings.at(buildingId); - if (!building) - { - remaining.erase(bId); - return nullptr; - } QString name = QString::fromStdString(building->getNameTranslated()); @@ -142,12 +144,6 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil if (!parent) parent = addBuilding(ctown, building->upgrade.getNum(), remaining); - if (!parent) - { - remaining.erase(bId); - return nullptr; - } - parent->appendRow(checks); } @@ -157,7 +153,6 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil void TownEventDialog::initCreatures() { - static const int MAXIUMUM_CREATURES_CHANGE = 999999; auto creatures = params.value("creatures").toList(); auto * ctown = town.town; for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i) @@ -185,7 +180,7 @@ void TownEventDialog::initCreatures() auto creatureNumber = creatures.size() > i ? creatures.at(i).toInt() : 0; QSpinBox* edit = new QSpinBox(ui->creaturesTable); edit->setValue(creatureNumber); - edit->setMaximum(MAXIUMUM_CREATURES_CHANGE); + edit->setMaximum(MAXIMUM_CREATURES_CHANGE); ui->creaturesTable->setCellWidget(i, 1, edit); } @@ -238,13 +233,7 @@ QVariantMap TownEventDialog::resourcesToVariant() QVariantList TownEventDialog::buildingsToVariant() { - auto buildings = getBuildingVariantsFromModel(buildingsModel, 1, Qt::Checked); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - QVariantList buildingsList(buildings.begin(), buildings.end()); -#else - QVariantList buildingsList = QVariantList::fromStdList(buildings); -#endif - return buildingsList; + return getBuildingVariantsFromModel(buildingsModel, 1, Qt::Checked); } QVariantList TownEventDialog::creaturesToVariant() @@ -283,7 +272,8 @@ void TownEventDialog::onItemChanged(QStandardItem * item) else if (item->checkState() == Qt::Unchecked) { std::vector stack; stack.push_back(nextRow); - while (!stack.empty()) { + do + { nextRow = stack.back(); stack.pop_back(); setRowColumnCheckState(nextRow, item->column(), Qt::Unchecked); @@ -293,7 +283,7 @@ void TownEventDialog::onItemChanged(QStandardItem * item) } } - } + } while(!stack.empty()); } connect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEventDialog::onItemChanged); } diff --git a/mapeditor/inspector/towneventdialog.ui b/mapeditor/inspector/towneventdialog.ui index 0f9b2c032..9d698afc2 100644 --- a/mapeditor/inspector/towneventdialog.ui +++ b/mapeditor/inspector/towneventdialog.ui @@ -206,6 +206,9 @@ 421 + + QAbstractItemView::NoEditTriggers + false diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp index 6bd8d0c99..c92e208f0 100644 --- a/mapeditor/inspector/townspellswidget.cpp +++ b/mapeditor/inspector/townspellswidget.cpp @@ -22,17 +22,8 @@ TownSpellsWidget::TownSpellsWidget(CGTownInstance & town, QWidget * parent) : { ui->setupUi(this); - possibleSpellLists[0] = ui->possibleSpellList1; - possibleSpellLists[1] = ui->possibleSpellList2; - possibleSpellLists[2] = ui->possibleSpellList3; - possibleSpellLists[3] = ui->possibleSpellList4; - possibleSpellLists[4] = ui->possibleSpellList5; - - requiredSpellLists[0] = ui->requiredSpellList1; - requiredSpellLists[1] = ui->requiredSpellList2; - requiredSpellLists[2] = ui->requiredSpellList3; - requiredSpellLists[3] = ui->requiredSpellList4; - requiredSpellLists[4] = ui->requiredSpellList5; + possibleSpellLists = { ui->possibleSpellList1, ui->possibleSpellList2, ui->possibleSpellList3, ui->possibleSpellList4, ui->possibleSpellList5 }; + requiredSpellLists = { ui->requiredSpellList1, ui->requiredSpellList2, ui->requiredSpellList3, ui->requiredSpellList4, ui->requiredSpellList5 }; std::array mageGuilds = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; for (int i = 0; i < mageGuilds.size(); i++) @@ -172,4 +163,4 @@ void TownSpellsDelegate::setModelData(QWidget * editor, QAbstractItemModel * mod { QStyledItemDelegate::setModelData(editor, model, index); } -} \ No newline at end of file +} diff --git a/mapeditor/mapeditorroles.h b/mapeditor/mapeditorroles.h index b0ea4e4a2..3564ad598 100644 --- a/mapeditor/mapeditorroles.h +++ b/mapeditor/mapeditorroles.h @@ -13,8 +13,8 @@ enum MapEditorRoles { - TownEventRole = Qt::UserRole +1, - PlayerIDRole, - BuildingIDRole, + TownEventRole = Qt::UserRole + 1, + PlayerIDRole, + BuildingIDRole, SpellIDRole }; From 6f604444c6b63aae041eeb4ff66d1b5c6f713898 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:36:11 +0200 Subject: [PATCH 33/76] fix --- client/battle/BattleInterfaceClasses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index a440af1ba..d36741cdf 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -462,7 +462,7 @@ std::vector> QuickSpellPanel::getSpells() for(const auto & availableSpellID : CGI->spellh->getDefaultAllowed()) { - const auto * availableSpell = availableSpellID->toSpell(); + const auto * availableSpell = availableSpellID.toSpell(); if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell.get()) && !vstd::contains(spellIds, availableSpell->getId())) { spellIds[i] = availableSpell->getId(); From 380d5bb05b0818bf334200de2b3342f68d6ca157 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:36:32 +0200 Subject: [PATCH 34/76] refactoring & expanding --- lib/gameState/CGameState.cpp | 81 ++-------------------------- lib/gameState/GameStatistics.cpp | 91 ++++++++++++++++++++++++++++++-- lib/gameState/GameStatistics.h | 22 ++++++-- 3 files changed, 108 insertions(+), 86 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index fca3b3073..c028f78e7 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1588,81 +1588,6 @@ struct statsHLP } return h[best]; } - - //calculates total number of artifacts that belong to given player - static int getNumberOfArts(const PlayerState * ps) - { - int ret = 0; - for(auto h : ps->heroes) - { - ret += (int)h->artifactsInBackpack.size() + (int)h->artifactsWorn.size(); - } - return ret; - } - - // get total strength of player army - static si64 getArmyStrength(const PlayerState * ps) - { - si64 str = 0; - - for(auto h : ps->heroes) - { - if(!h->inTownGarrison) //original h3 behavior - str += h->getArmyStrength(); - } - return str; - } - - // get total gold income - static int getIncome(const PlayerState * ps) - { - int totalIncome = 0; - const CGObjectInstance * heroOrTown = nullptr; - - //Heroes can produce gold as well - skill, specialty or arts - for(const auto & h : ps->heroes) - { - totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))); - - if(!heroOrTown) - heroOrTown = h; - } - - //Add town income of all towns - for(const auto & t : ps->towns) - { - totalIncome += t->dailyIncome()[EGameResID::GOLD]; - - if(!heroOrTown) - heroOrTown = t; - } - - /// FIXME: Dirty dirty hack - /// Stats helper need some access to gamestate. - std::vector ownedObjects; - for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) - { - if(obj && obj->tempOwner == ps->color) - ownedObjects.push_back(obj); - } - /// This is code from CPlayerSpecificInfoCallback::getMyObjects - /// I'm really need to find out about callback interface design... - - for(const auto * object : ownedObjects) - { - //Mines - if ( object->ID == Obj::MINE ) - { - const auto * mine = dynamic_cast(object); - assert(mine); - - if (mine->producedResource == EGameResID::GOLD) - totalIncome += mine->producedQuantity; - } - } - - return totalIncome; - } }; void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) @@ -1739,15 +1664,15 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } if(level >= 4) //artifacts { - FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) + FILL_FIELD(artifacts, Statistic::getNumberOfArts(&g->second)) } if(level >= 4) //army strength { - FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) + FILL_FIELD(army, Statistic::getArmyStrength(&g->second)) } if(level >= 5) //income { - FILL_FIELD(income, statsHLP::getIncome(&g->second)) + FILL_FIELD(income, Statistic::getIncome(&g->second)) } if(level >= 2) //best hero's stats { diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 49c489ccc..69c4e01f6 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -12,6 +12,11 @@ #include "../CPlayerState.h" #include "../constants/StringConstants.h" #include "CGameState.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/CGTownInstance.h" +#include "../mapObjects/CGObjectInstance.h" +#include "../mapObjects/MiscObjects.h" +#include "../mapping/CMap.h" VCMI_LIB_NAMESPACE_BEGIN @@ -28,8 +33,11 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.player = ps->color; data.team = ps->team; data.resources = ps->resources; - data.heroesCount = ps->heroes.size(); - data.townCount = ps->towns.size(); + data.numberHeroes = ps->heroes.size(); + data.numberTowns = ps->towns.size(); + data.numberArtifacts = Statistic::getNumberOfArts(ps); + data.armyStrength = Statistic::getArmyStrength(ps); + data.income = Statistic::getIncome(ps); return data; } @@ -40,14 +48,14 @@ std::string StatisticDataSet::toCsv() auto resources = std::vector{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS}; - ss << "Day" << ";" << "Player" << ";" << "Team" << ";" << "HeroesCount" << ";" << "TownCount"; + ss << "Day" << ";" << "Player" << ";" << "Team" << ";" << "NumberHeroes" << ";" << "NumberTowns" << ";" << "NumberArtifacts" << ";" << "ArmyStrength" << ";" << "Income"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; ss << "\r\n"; for(auto & entry : data) { - ss << entry.day << ";" << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";" << entry.team.getNum() << ";" << entry.heroesCount << ";" << entry.townCount; + ss << entry.day << ";" << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";" << entry.team.getNum() << ";" << entry.numberHeroes << ";" << entry.numberTowns << ";" << entry.numberArtifacts << ";" << entry.armyStrength << ";" << entry.income; for(auto & resource : resources) ss << ";" << entry.resources[resource]; ss << "\r\n"; @@ -56,4 +64,79 @@ std::string StatisticDataSet::toCsv() return ss.str(); } +//calculates total number of artifacts that belong to given player +int Statistic::getNumberOfArts(const PlayerState * ps) +{ + int ret = 0; + for(auto h : ps->heroes) + { + ret += (int)h->artifactsInBackpack.size() + (int)h->artifactsWorn.size(); + } + return ret; +} + +// get total strength of player army +si64 Statistic::getArmyStrength(const PlayerState * ps) +{ + si64 str = 0; + + for(auto h : ps->heroes) + { + if(!h->inTownGarrison) //original h3 behavior + str += h->getArmyStrength(); + } + return str; +} + +// get total gold income +int Statistic::getIncome(const PlayerState * ps) +{ + int totalIncome = 0; + const CGObjectInstance * heroOrTown = nullptr; + + //Heroes can produce gold as well - skill, specialty or arts + for(const auto & h : ps->heroes) + { + totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))); + + if(!heroOrTown) + heroOrTown = h; + } + + //Add town income of all towns + for(const auto & t : ps->towns) + { + totalIncome += t->dailyIncome()[EGameResID::GOLD]; + + if(!heroOrTown) + heroOrTown = t; + } + + /// FIXME: Dirty dirty hack + /// Stats helper need some access to gamestate. + std::vector ownedObjects; + for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) + { + if(obj && obj->tempOwner == ps->color) + ownedObjects.push_back(obj); + } + /// This is code from CPlayerSpecificInfoCallback::getMyObjects + /// I'm really need to find out about callback interface design... + + for(const auto * object : ownedObjects) + { + //Mines + if ( object->ID == Obj::MINE ) + { + const auto * mine = dynamic_cast(object); + assert(mine); + + if (mine->producedResource == EGameResID::GOLD) + totalIncome += mine->producedQuantity; + } + } + + return totalIncome; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 3edf366d8..133e24c57 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -23,8 +23,11 @@ struct DLL_LINKAGE StatisticDataSetEntry PlayerColor player; TeamID team; TResources resources; - int heroesCount; - int townCount; + int numberHeroes; + int numberTowns; + int numberArtifacts; + si64 armyStrength; + int income; template void serialize(Handler &h) { @@ -32,8 +35,11 @@ struct DLL_LINKAGE StatisticDataSetEntry h & player; h & team; h & resources; - h & heroesCount; - h & townCount; + h & numberHeroes; + h & numberTowns; + h & numberArtifacts; + h & armyStrength; + h & income; } }; @@ -52,4 +58,12 @@ public: } }; +class DLL_LINKAGE Statistic +{ +public: + static int getNumberOfArts(const PlayerState * ps); + static si64 getArmyStrength(const PlayerState * ps); + static int getIncome(const PlayerState * ps); +}; + VCMI_LIB_NAMESPACE_END From 2979bf1976b7b98d3e3c6d95cb4240055eca843d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:21:41 +0200 Subject: [PATCH 35/76] visitedRatio --- lib/gameState/GameStatistics.cpp | 50 ++++++++++++++++++++++++++++++-- lib/gameState/GameStatistics.h | 7 +++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 69c4e01f6..2df7c1673 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -12,6 +12,7 @@ #include "../CPlayerState.h" #include "../constants/StringConstants.h" #include "CGameState.h" +#include "TerrainHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGObjectInstance.h" @@ -32,12 +33,15 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.day = gs->getDate(Date::DAY); data.player = ps->color; data.team = ps->team; + data.isHuman = ps->isHuman(); + data.status = ps->status; data.resources = ps->resources; data.numberHeroes = ps->heroes.size(); data.numberTowns = ps->towns.size(); data.numberArtifacts = Statistic::getNumberOfArts(ps); data.armyStrength = Statistic::getArmyStrength(ps); data.income = Statistic::getIncome(ps); + data.mapVisitedRatio = Statistic::getMapVisitedRatio(gs, ps->color); return data; } @@ -48,14 +52,34 @@ std::string StatisticDataSet::toCsv() auto resources = std::vector{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS}; - ss << "Day" << ";" << "Player" << ";" << "Team" << ";" << "NumberHeroes" << ";" << "NumberTowns" << ";" << "NumberArtifacts" << ";" << "ArmyStrength" << ";" << "Income"; + ss << "Day" << ";"; + ss << "Player" << ";"; + ss << "Team" << ";"; + ss << "IsHuman" << ";"; + ss << "Status" << ";"; + ss << "NumberHeroes" << ";"; + ss << "NumberTowns" << ";"; + ss << "NumberArtifacts" << ";"; + ss << "ArmyStrength" << ";"; + ss << "Income" << ";"; + ss << "MapVisitedRatio"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; ss << "\r\n"; for(auto & entry : data) { - ss << entry.day << ";" << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";" << entry.team.getNum() << ";" << entry.numberHeroes << ";" << entry.numberTowns << ";" << entry.numberArtifacts << ";" << entry.armyStrength << ";" << entry.income; + ss << entry.day << ";"; + ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";"; + ss << entry.team.getNum() << ";"; + ss << entry.isHuman << ";"; + ss << (int)entry.status << ";"; + ss << entry.numberHeroes << ";"; + ss << entry.numberTowns << ";"; + ss << entry.numberArtifacts << ";"; + ss << entry.armyStrength << ";"; + ss << entry.income << ";"; + ss << entry.mapVisitedRatio; for(auto & resource : resources) ss << ";" << entry.resources[resource]; ss << "\r\n"; @@ -139,4 +163,26 @@ int Statistic::getIncome(const PlayerState * ps) return totalIncome; } +double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) +{ + double visible = 0.0; + double numTiles = 0.0; + + for(int layer = 0; layer < (gs->map->twoLevel ? 2 : 1); layer++) + for (int y = 0; y < gs->map->height; ++y) + for (int x = 0; x < gs->map->width; ++x) + { + TerrainTile tile = gs->map->getTile(int3(x, y, layer)); + + if (tile.blocked && (!tile.visitable)) + continue; + + if(gs->isVisible(int3(x, y, layer), player)) + visible++; + numTiles++; + } + + return visible / (numTiles); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 133e24c57..df685c01b 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -22,24 +22,30 @@ struct DLL_LINKAGE StatisticDataSetEntry int day; PlayerColor player; TeamID team; + bool isHuman; + EPlayerStatus status; TResources resources; int numberHeroes; int numberTowns; int numberArtifacts; si64 armyStrength; int income; + double mapVisitedRatio; template void serialize(Handler &h) { h & day; h & player; h & team; + h & isHuman; + h & status; h & resources; h & numberHeroes; h & numberTowns; h & numberArtifacts; h & armyStrength; h & income; + h & mapVisitedRatio; } }; @@ -64,6 +70,7 @@ public: static int getNumberOfArts(const PlayerState * ps); static si64 getArmyStrength(const PlayerState * ps); static int getIncome(const PlayerState * ps); + static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); }; VCMI_LIB_NAMESPACE_END From e782d3984f966a822446037e721607dba44001b4 Mon Sep 17 00:00:00 2001 From: godric3 Date: Thu, 1 Aug 2024 23:15:24 +0200 Subject: [PATCH 36/76] fix some sonarcloud issues --- mapeditor/inspector/townbuildingswidget.cpp | 10 +++++----- mapeditor/inspector/townbuildingswidget.h | 8 ++++---- mapeditor/inspector/towneventdialog.cpp | 8 ++++---- mapeditor/inspector/towneventdialog.h | 4 ++-- mapeditor/inspector/towneventswidget.cpp | 10 +++++----- mapeditor/inspector/townspellswidget.cpp | 6 +++--- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index 5ade9c09b..775c735f9 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -69,7 +69,7 @@ std::string defaultBuildingIdConversion(BuildingID bId) } } -QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model) +QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, const QStandardItemModel & model) { QStandardItem * parent = nullptr; std::vector stack(1); @@ -93,7 +93,7 @@ QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStan return parent; } -QVariantList getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) +QVariantList getBuildingVariantsFromModel(const QStandardItemModel & model, int modelColumn, Qt::CheckState checkState) { QVariantList result; std::vector stack(1); @@ -207,7 +207,7 @@ std::set TownBuildingsWidget::getBuildingsFromModel(int modelColumn, { auto buildingVariants = getBuildingVariantsFromModel(model, modelColumn, checkState); std::set result; - for (auto buildingId : buildingVariants) + for (const auto & buildingId : buildingVariants) { result.insert(buildingId.toInt()); } @@ -255,7 +255,7 @@ void TownBuildingsWidget::on_disableAll_clicked() } -void TownBuildingsWidget::setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState) { +void TownBuildingsWidget::setRowColumnCheckState(const QStandardItem * item, Column column, Qt::CheckState checkState) { auto sibling = item->model()->sibling(item->row(), column, item->index()); model.itemFromIndex(sibling)->setCheckState(checkState); } @@ -280,7 +280,7 @@ void TownBuildingsWidget::setAllRowsColumnCheckState(Column column, Qt::CheckSta } while(!stack.empty()); } -void TownBuildingsWidget::onItemChanged(QStandardItem * item) { +void TownBuildingsWidget::onItemChanged(const QStandardItem * item) { disconnect(&model, &QStandardItemModel::itemChanged, this, &TownBuildingsWidget::onItemChanged); auto rowFirstColumnIndex = item->model()->sibling(item->row(), Column::TYPE, item->index()); QStandardItem * nextRow = model.itemFromIndex(rowFirstColumnIndex); diff --git a/mapeditor/inspector/townbuildingswidget.h b/mapeditor/inspector/townbuildingswidget.h index 029effe3f..2b3cf1dd2 100644 --- a/mapeditor/inspector/townbuildingswidget.h +++ b/mapeditor/inspector/townbuildingswidget.h @@ -19,9 +19,9 @@ class TownBuildingsWidget; std::string defaultBuildingIdConversion(BuildingID bId); -QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, QStandardItemModel & model); +QStandardItem * getBuildingParentFromTreeModel(const CBuilding * building, const QStandardItemModel & model); -QVariantList getBuildingVariantsFromModel(QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); +QVariantList getBuildingVariantsFromModel(const QStandardItemModel & model, int modelColumn, Qt::CheckState checkState); class TownBuildingsWidget : public QDialog { @@ -54,11 +54,11 @@ private slots: void on_disableAll_clicked(); - void onItemChanged(QStandardItem * item); + void onItemChanged(const QStandardItem * item); private: std::set getBuildingsFromModel(int modelColumn, Qt::CheckState checkState); - void setRowColumnCheckState(QStandardItem * item, Column column, Qt::CheckState checkState); + void setRowColumnCheckState(const QStandardItem * item, Column column, Qt::CheckState checkState); void setAllRowsColumnCheckState(Column column, Qt::CheckState checkState); Ui::TownBuildingsWidget *ui; diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 5ad22366c..845f774c6 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -86,7 +86,7 @@ void TownEventDialog::initResources() ui->resourcesTable->setItem(i, 0, item); int val = resourcesMap.value(name).toInt(); - QSpinBox * edit = new QSpinBox(ui->resourcesTable); + auto * edit = new QSpinBox(ui->resourcesTable); edit->setMaximum(i == GameResID::GOLD ? MAXIMUM_GOLD_CHANGE : MAXIMUM_RESOURCE_CHANGE); edit->setMinimum(i == GameResID::GOLD ? -MAXIMUM_GOLD_CHANGE : -MAXIMUM_RESOURCE_CHANGE); edit->setSingleStep(i == GameResID::GOLD ? GOLD_STEP : RESOURCE_STEP); @@ -178,7 +178,7 @@ void TownEventDialog::initCreatures() ui->creaturesTable->setItem(i, 0, item); auto creatureNumber = creatures.size() > i ? creatures.at(i).toInt() : 0; - QSpinBox* edit = new QSpinBox(ui->creaturesTable); + auto * edit = new QSpinBox(ui->creaturesTable); edit->setValue(creatureNumber); edit->setMaximum(MAXIMUM_CREATURES_CHANGE); ui->creaturesTable->setCellWidget(i, 1, edit); @@ -252,12 +252,12 @@ void TownEventDialog::on_okButton_clicked() close(); } -void TownEventDialog::setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState) { +void TownEventDialog::setRowColumnCheckState(const QStandardItem * item, int column, Qt::CheckState checkState) { auto sibling = item->model()->sibling(item->row(), column, item->index()); buildingsModel.itemFromIndex(sibling)->setCheckState(checkState); } -void TownEventDialog::onItemChanged(QStandardItem * item) +void TownEventDialog::onItemChanged(const QStandardItem * item) { disconnect(&buildingsModel, &QStandardItemModel::itemChanged, this, &TownEventDialog::onItemChanged); auto rowFirstColumnIndex = item->model()->sibling(item->row(), 0, item->index()); diff --git a/mapeditor/inspector/towneventdialog.h b/mapeditor/inspector/towneventdialog.h index 008bb89d2..635939a36 100644 --- a/mapeditor/inspector/towneventdialog.h +++ b/mapeditor/inspector/towneventdialog.h @@ -27,10 +27,10 @@ public: private slots: - void onItemChanged(QStandardItem * item); + void onItemChanged(const QStandardItem * item); void on_TownEventDialog_finished(int result); void on_okButton_clicked(); - void setRowColumnCheckState(QStandardItem * item, int column, Qt::CheckState checkState); + void setRowColumnCheckState(const QStandardItem * item, int column, Qt::CheckState checkState); private: void initPlayers(); diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index 89f600a2b..f963a2ae3 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -49,7 +49,7 @@ QVariant toVariant(const std::vector & creatures) std::set buildingsFromVariant(const QVariant& v) { std::set result; - for (auto r : v.toList()) { + for (const auto & r : v.toList()) { result.insert(BuildingID(r.toInt())); } return result; @@ -58,7 +58,7 @@ std::set buildingsFromVariant(const QVariant& v) std::vector creaturesFromVariant(const QVariant& v) { std::vector result; - for (auto r : v.toList()) { + for (const auto & r : v.toList()) { result.push_back(r.toInt()); } return result; @@ -81,7 +81,7 @@ QVariant toVariant(const CCastleEvent& event) return QVariant(result); } -CCastleEvent eventFromVariant(CMapHeader& map, CGTownInstance& town, const QVariant& variant) +CCastleEvent eventFromVariant(CMapHeader& map, const CGTownInstance& town, const QVariant& variant) { CCastleEvent result; auto v = variant.toMap(); @@ -143,13 +143,13 @@ void TownEventsWidget::on_eventsList_itemActivated(QListWidgetItem* item) } -TownEventsDelegate::TownEventsDelegate(CGTownInstance & town, MapController & c) : town(town), controller(c), QStyledItemDelegate() +TownEventsDelegate::TownEventsDelegate(CGTownInstance & town, MapController & c) : QStyledItemDelegate(), town(town), controller(c) { } QWidget* TownEventsDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { - return new TownEventsWidget(town, parent);; + return new TownEventsWidget(town, parent); } void TownEventsDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const diff --git a/mapeditor/inspector/townspellswidget.cpp b/mapeditor/inspector/townspellswidget.cpp index c92e208f0..abfd4def2 100644 --- a/mapeditor/inspector/townspellswidget.cpp +++ b/mapeditor/inspector/townspellswidget.cpp @@ -98,11 +98,11 @@ void TownSpellsWidget::commitChanges() } auto updateTownSpellList = [](auto uiSpellLists, auto & townSpellList) { - for (QListWidget * spellList : uiSpellLists) + for (const QListWidget * spellList : uiSpellLists) { for (int i = 0; i < spellList->count(); ++i) { - QListWidgetItem * item = spellList->item(i); + const auto * item = spellList->item(i); if (item->checkState() == Qt::Checked) { townSpellList.push_back(item->data(MapEditorRoles::SpellIDRole).toInt()); @@ -132,7 +132,7 @@ void TownSpellsWidget::on_customizeSpells_toggled(bool checked) initSpellLists(); } -TownSpellsDelegate::TownSpellsDelegate(CGTownInstance & town) : town(town), QStyledItemDelegate() +TownSpellsDelegate::TownSpellsDelegate(CGTownInstance & town) : QStyledItemDelegate(), town(town) { } From 52ae6a56f1fe93c217f2a102569698199905035f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:35:07 +0200 Subject: [PATCH 37/76] fix --- client/battle/BattleInterfaceClasses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index d36741cdf..4d43dd452 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -463,7 +463,7 @@ std::vector> QuickSpellPanel::getSpells() for(const auto & availableSpellID : CGI->spellh->getDefaultAllowed()) { const auto * availableSpell = availableSpellID.toSpell(); - if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell.get()) && !vstd::contains(spellIds, availableSpell->getId())) + if(!availableSpell->isAdventure() && !availableSpell->isCreatureAbility() && hero->canCastThisSpell(availableSpell) && !vstd::contains(spellIds, availableSpell->getId())) { spellIds[i] = availableSpell->getId(); break; From f695f7038ec10e72469cfdc48971f6aebee075ed Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:56:06 +0200 Subject: [PATCH 38/76] cleanup --- lib/gameState/CGameState.cpp | 2 +- lib/gameState/GameStatistics.cpp | 18 ++++++++++-------- lib/gameState/GameStatistics.h | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 66705c7b8..08fe1bb5b 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1676,7 +1676,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } if(level >= 5) //income { - FILL_FIELD(income, Statistic::getIncome(&g->second)) + FILL_FIELD(income, Statistic::getIncome(gs, &g->second)) } if(level >= 2) //best hero's stats { diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 2df7c1673..d80cbc902 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -13,6 +13,7 @@ #include "../constants/StringConstants.h" #include "CGameState.h" #include "TerrainHandler.h" +#include "StartInfo.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGObjectInstance.h" @@ -40,7 +41,7 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numberTowns = ps->towns.size(); data.numberArtifacts = Statistic::getNumberOfArts(ps); data.armyStrength = Statistic::getArmyStrength(ps); - data.income = Statistic::getIncome(ps); + data.income = Statistic::getIncome(gs, ps); data.mapVisitedRatio = Statistic::getMapVisitedRatio(gs, ps->color); return data; @@ -113,15 +114,16 @@ si64 Statistic::getArmyStrength(const PlayerState * ps) } // get total gold income -int Statistic::getIncome(const PlayerState * ps) +int Statistic::getIncome(const CGameState * gs, const PlayerState * ps) { + int percentIncome = gs->getStartInfo()->getIthPlayersSettings(ps->color).handicap.percentIncome; int totalIncome = 0; const CGObjectInstance * heroOrTown = nullptr; //Heroes can produce gold as well - skill, specialty or arts for(const auto & h : ps->heroes) { - totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))); + totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))) * percentIncome / 100; if(!heroOrTown) heroOrTown = h; @@ -156,7 +158,7 @@ int Statistic::getIncome(const PlayerState * ps) assert(mine); if (mine->producedResource == EGameResID::GOLD) - totalIncome += mine->producedQuantity; + totalIncome += mine->getProducedQuantity(); } } @@ -169,12 +171,12 @@ double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) double numTiles = 0.0; for(int layer = 0; layer < (gs->map->twoLevel ? 2 : 1); layer++) - for (int y = 0; y < gs->map->height; ++y) - for (int x = 0; x < gs->map->width; ++x) + for(int y = 0; y < gs->map->height; ++y) + for(int x = 0; x < gs->map->width; ++x) { TerrainTile tile = gs->map->getTile(int3(x, y, layer)); - if (tile.blocked && (!tile.visitable)) + if(tile.blocked && (!tile.visitable)) continue; if(gs->isVisible(int3(x, y, layer), player)) @@ -182,7 +184,7 @@ double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) numTiles++; } - return visible / (numTiles); + return visible / numTiles; } VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index df685c01b..7fda48fac 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -69,7 +69,7 @@ class DLL_LINKAGE Statistic public: static int getNumberOfArts(const PlayerState * ps); static si64 getArmyStrength(const PlayerState * ps); - static int getIncome(const PlayerState * ps); + static int getIncome(const CGameState * gs, const PlayerState * ps); static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); }; From 33b2633775b90f5560de3dc00856430ac103c79f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 00:04:41 +0200 Subject: [PATCH 39/76] refectoring: remove statsHLP --- lib/gameState/CGameState.cpp | 60 ++------------------------------ lib/gameState/GameStatistics.cpp | 46 ++++++++++++++++++++++++ lib/gameState/GameStatistics.h | 5 +++ 3 files changed, 53 insertions(+), 58 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 08fe1bb5b..4a38a36f0 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1538,62 +1538,6 @@ bool CGameState::checkForStandardLoss(const PlayerColor & player) const return pState.checkVanquished(); } -struct statsHLP -{ - using TStat = std::pair; - //converts [] to vec[place] -> platers - static std::vector< std::vector< PlayerColor > > getRank( std::vector stats ) - { - std::sort(stats.begin(), stats.end(), statsHLP()); - - //put first element - std::vector< std::vector > ret; - std::vector tmp; - tmp.push_back( stats[0].first ); - ret.push_back( tmp ); - - //the rest of elements - for(int g=1; gpush_back( stats[g].first ); - } - else - { - //create next occupied rank - std::vector tmp; - tmp.push_back(stats[g].first); - ret.push_back(tmp); - } - } - - return ret; - } - - bool operator()(const TStat & a, const TStat & b) const - { - return a.second > b.second; - } - - static const CGHeroInstance * findBestHero(CGameState * gs, const PlayerColor & color) - { - std::vector > &h = gs->players[color].heroes; - if(h.empty()) - return nullptr; - //best hero will be that with highest exp - int best = 0; - for(int b=1; bexp > h[best]->exp) - { - best = b; - } - } - return h[best]; - } -}; - void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) { auto playerInactive = [&](const PlayerColor & color) @@ -1613,7 +1557,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) stat.second = VAL_GETTER; \ stats.push_back(stat); \ } \ - tgi.FIELD = statsHLP::getRank(stats); \ + tgi.FIELD = Statistic::getRank(stats); \ } for(auto & elem : players) @@ -1635,7 +1579,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) { if(playerInactive(player.second.color)) continue; - const CGHeroInstance * best = statsHLP::findBestHero(this, player.second.color); + const CGHeroInstance * best = Statistic::findBestHero(this, player.second.color); InfoAboutHero iah; iah.initFromHero(best, (level >= 2) ? InfoAboutHero::EInfoLevel::DETAILED : InfoAboutHero::EInfoLevel::BASIC); iah.army.clear(); diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index d80cbc902..2ffea42f6 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -187,4 +187,50 @@ double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) return visible / numTiles; } +const CGHeroInstance * Statistic::findBestHero(CGameState * gs, const PlayerColor & color) +{ + std::vector > &h = gs->players[color].heroes; + if(h.empty()) + return nullptr; + //best hero will be that with highest exp + int best = 0; + for(int b=1; bexp > h[best]->exp) + { + best = b; + } + } + return h[best]; +} + +std::vector> Statistic::getRank(std::vector stats) +{ + std::sort(stats.begin(), stats.end(), [](const TStat & a, const TStat & b) { return a.second > b.second; }); + + //put first element + std::vector< std::vector > ret; + std::vector tmp; + tmp.push_back( stats[0].first ); + ret.push_back( tmp ); + + //the rest of elements + for(int g=1; gpush_back( stats[g].first ); + } + else + { + //create next occupied rank + std::vector tmp; + tmp.push_back(stats[g].first); + ret.push_back(tmp); + } + } + + return ret; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 7fda48fac..c8f4e5d38 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN struct PlayerState; class CGameState; +class CGHeroInstance; struct DLL_LINKAGE StatisticDataSetEntry { @@ -67,10 +68,14 @@ public: class DLL_LINKAGE Statistic { public: + using TStat = std::pair; + static int getNumberOfArts(const PlayerState * ps); static si64 getArmyStrength(const PlayerState * ps); static int getIncome(const CGameState * gs, const PlayerState * ps); static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); + static const CGHeroInstance * findBestHero(CGameState * gs, const PlayerColor & color); + static std::vector> getRank(std::vector stats); }; VCMI_LIB_NAMESPACE_END From 86477c7b04477a2b942fedac44142ffba77c3e6d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 01:18:39 +0200 Subject: [PATCH 40/76] refactoring + values added --- lib/gameState/CGameState.cpp | 10 +-- lib/gameState/GameStatistics.cpp | 108 +++++++++++++++++++++++-------- lib/gameState/GameStatistics.h | 15 ++++- 3 files changed, 96 insertions(+), 37 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 4a38a36f0..90adf8c43 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1600,15 +1600,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } if(level >= 3) //obelisks found { - auto getObeliskVisited = [&](const TeamID & t) - { - if(map->obelisksVisited.count(t)) - return map->obelisksVisited[t]; - else - return ui8(0); - }; - - FILL_FIELD(obelisks, getObeliskVisited(gs->getPlayerTeam(g->second.color)->id)) + FILL_FIELD(obelisks, Statistic::getObeliskVisited(gs, gs->getPlayerTeam(g->second.color)->id)) } if(level >= 4) //artifacts { diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 2ffea42f6..0b502ee3a 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -13,6 +13,7 @@ #include "../constants/StringConstants.h" #include "CGameState.h" #include "TerrainHandler.h" +#include "CHeroHandler.h" #include "StartInfo.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" @@ -40,9 +41,12 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numberHeroes = ps->heroes.size(); data.numberTowns = ps->towns.size(); data.numberArtifacts = Statistic::getNumberOfArts(ps); - data.armyStrength = Statistic::getArmyStrength(ps); + data.armyStrength = Statistic::getArmyStrength(ps, true); data.income = Statistic::getIncome(gs, ps); data.mapVisitedRatio = Statistic::getMapVisitedRatio(gs, ps->color); + data.obeliskVisited = Statistic::getObeliskVisited(gs, ps->team); + data.mightMagicRatio = Statistic::getMightMagicRatio(ps); + data.numMines = Statistic::getNumMines(gs, ps); return data; } @@ -63,9 +67,13 @@ std::string StatisticDataSet::toCsv() ss << "NumberArtifacts" << ";"; ss << "ArmyStrength" << ";"; ss << "Income" << ";"; - ss << "MapVisitedRatio"; + ss << "MapVisitedRatio" << ";"; + ss << "ObeliskVisited" << ";"; + ss << "MightMagicRatio"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; + for(auto & resource : resources) + ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "Mines"; ss << "\r\n"; for(auto & entry : data) @@ -80,15 +88,49 @@ std::string StatisticDataSet::toCsv() ss << entry.numberArtifacts << ";"; ss << entry.armyStrength << ";"; ss << entry.income << ";"; - ss << entry.mapVisitedRatio; + ss << entry.mapVisitedRatio << ";"; + ss << entry.obeliskVisited << ";"; + ss << entry.mightMagicRatio; for(auto & resource : resources) ss << ";" << entry.resources[resource]; + for(auto & resource : resources) + ss << ";" << entry.numMines[resource]; ss << "\r\n"; } return ss.str(); } +std::vector Statistic::getMines(const CGameState * gs, const PlayerState * ps) +{ + std::vector tmp; + + /// FIXME: Dirty dirty hack + /// Stats helper need some access to gamestate. + std::vector ownedObjects; + for(const CGObjectInstance * obj : gs->map->objects) + { + if(obj && obj->tempOwner == ps->color) + ownedObjects.push_back(obj); + } + /// This is code from CPlayerSpecificInfoCallback::getMyObjects + /// I'm really need to find out about callback interface design... + + for(const auto * object : ownedObjects) + { + //Mines + if ( object->ID == Obj::MINE ) + { + const auto * mine = dynamic_cast(object); + assert(mine); + + tmp.push_back(mine); + } + } + + return tmp; +} + //calculates total number of artifacts that belong to given player int Statistic::getNumberOfArts(const PlayerState * ps) { @@ -101,13 +143,13 @@ int Statistic::getNumberOfArts(const PlayerState * ps) } // get total strength of player army -si64 Statistic::getArmyStrength(const PlayerState * ps) +si64 Statistic::getArmyStrength(const PlayerState * ps, bool withTownGarrison) { si64 str = 0; for(auto h : ps->heroes) { - if(!h->inTownGarrison) //original h3 behavior + if(!h->inTownGarrison || withTownGarrison) //original h3 behavior str += h->getArmyStrength(); } return str; @@ -138,28 +180,10 @@ int Statistic::getIncome(const CGameState * gs, const PlayerState * ps) heroOrTown = t; } - /// FIXME: Dirty dirty hack - /// Stats helper need some access to gamestate. - std::vector ownedObjects; - for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) + for(const CGMine * mine : getMines(gs, ps)) { - if(obj && obj->tempOwner == ps->color) - ownedObjects.push_back(obj); - } - /// This is code from CPlayerSpecificInfoCallback::getMyObjects - /// I'm really need to find out about callback interface design... - - for(const auto * object : ownedObjects) - { - //Mines - if ( object->ID == Obj::MINE ) - { - const auto * mine = dynamic_cast(object); - assert(mine); - - if (mine->producedResource == EGameResID::GOLD) - totalIncome += mine->getProducedQuantity(); - } + if (mine->producedResource == EGameResID::GOLD) + totalIncome += mine->getProducedQuantity(); } return totalIncome; @@ -233,4 +257,36 @@ std::vector> Statistic::getRank(std::vector stat return ret; } +int Statistic::getObeliskVisited(const CGameState * gs, const TeamID & t) +{ + if(gs->map->obelisksVisited.count(t)) + return gs->map->obelisksVisited.at(t); + else + return 0; +} + +double Statistic::getMightMagicRatio(const PlayerState * ps) +{ + double numMight = 0; + + for(auto h : ps->heroes) + if(h->type->heroClass->affinity == CHeroClass::EClassAffinity::MIGHT) + numMight++; + + return numMight / ps->heroes.size(); +} + +std::map Statistic::getNumMines(const CGameState * gs, const PlayerState * ps) +{ + std::map tmp; + + for(auto & res : EGameResID::ALL_RESOURCES()) + tmp[res] = 0; + + for(const CGMine * mine : getMines(gs, ps)) + tmp[mine->producedResource]++; + + return tmp; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index c8f4e5d38..2161e7e42 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN struct PlayerState; class CGameState; class CGHeroInstance; +class CGMine; struct DLL_LINKAGE StatisticDataSetEntry { @@ -32,6 +33,9 @@ struct DLL_LINKAGE StatisticDataSetEntry si64 armyStrength; int income; double mapVisitedRatio; + int obeliskVisited; + double mightMagicRatio; + std::map numMines; template void serialize(Handler &h) { @@ -47,6 +51,9 @@ struct DLL_LINKAGE StatisticDataSetEntry h & armyStrength; h & income; h & mapVisitedRatio; + h & obeliskVisited; + h & mightMagicRatio; + h & numMines; } }; @@ -67,15 +74,19 @@ public: class DLL_LINKAGE Statistic { + static std::vector getMines(const CGameState * gs, const PlayerState * ps); public: using TStat = std::pair; - static int getNumberOfArts(const PlayerState * ps); - static si64 getArmyStrength(const PlayerState * ps); + static int getNumberOfArts(const PlayerState * ps); + static si64 getArmyStrength(const PlayerState * ps, bool withTownGarrison = false); static int getIncome(const CGameState * gs, const PlayerState * ps); static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); static const CGHeroInstance * findBestHero(CGameState * gs, const PlayerColor & color); static std::vector> getRank(std::vector stats); + static int getObeliskVisited(const CGameState * gs, const TeamID & t); + static double getMightMagicRatio(const PlayerState * ps); + static std::map getNumMines(const CGameState * gs, const PlayerState * ps); }; VCMI_LIB_NAMESPACE_END From 2c42737b28f6970520647aae8d1a5c6db96afbaf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 31 Jul 2024 21:03:24 +0000 Subject: [PATCH 41/76] Fix infinitely updating simultaneous turns slider --- client/lobby/OptionsTabBase.cpp | 6 +++--- client/widgets/Slider.cpp | 11 ++++++----- client/widgets/Slider.h | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index c4d728d00..17936a6d0 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -340,10 +340,10 @@ void OptionsTabBase::recreate(bool campaign) //Simultaneous turns if(auto turnSlider = widget("simturnsDurationMin")) - turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns); + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns, false); if(auto turnSlider = widget("simturnsDurationMax")) - turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns); + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns, false); if(auto w = widget("labelSimturnsDurationValueMin")) w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns)); @@ -388,7 +388,7 @@ void OptionsTabBase::recreate(bool campaign) auto & tpreset = variables["timerPresets"].Vector()[idx]; if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000) { - turnSlider->scrollTo(idx); + turnSlider->scrollTo(idx, false); if(auto w = widget("labelTurnDurationValue")) w->setText(CGI->generaltexth->turnDurations[idx]); } diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index 2c866ff47..e51896cdc 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -70,7 +70,7 @@ int CSlider::getValue() const return value; } -void CSlider::setValue(int to) +void CSlider::setValue(int to, bool callCallbacks) { scrollTo(value); } @@ -113,7 +113,7 @@ void CSlider::updateSliderPos() } } -void CSlider::scrollTo(int to) +void CSlider::scrollTo(int to, bool callCallbacks) { vstd::amax(to, 0); vstd::amin(to, positions); @@ -125,7 +125,8 @@ void CSlider::scrollTo(int to) updateSliderPos(); - moved(getValue()); + if (callCallbacks) + moved(getValue()); } void CSlider::clickPressed(const Point & cursorPosition) @@ -321,7 +322,7 @@ int SliderNonlinear::getValue() const return scaledValues.at(CSlider::getValue()); } -void SliderNonlinear::setValue(int to) +void SliderNonlinear::setValue(int to, bool callCallbacks) { size_t nearest = 0; @@ -334,5 +335,5 @@ void SliderNonlinear::setValue(int to) nearest = i; } - scrollTo(nearest); + scrollTo(nearest, callCallbacks); } diff --git a/client/widgets/Slider.h b/client/widgets/Slider.h index 88a580187..6cbba6823 100644 --- a/client/widgets/Slider.h +++ b/client/widgets/Slider.h @@ -52,14 +52,14 @@ public: void clearScrollBounds(); /// Value modifiers - void scrollTo(int value); + void scrollTo(int value, bool callCallbacks = true); void scrollBy(int amount) override; void scrollToMin(); void scrollToMax(); /// Amount modifier void setAmount(int to); - virtual void setValue(int to); + virtual void setValue(int to, bool callCallbacks = true); /// Accessors int getAmount() const; @@ -95,7 +95,7 @@ class SliderNonlinear : public CSlider using CSlider::setAmount; // make private public: - void setValue(int to) override; + void setValue(int to, bool callCallbacks) override; int getValue() const override; SliderNonlinear(Point position, int length, const std::function & Moved, const std::vector & values, int Value, Orientation orientation, EStyle style); From 16b28c28e47dac5741e3c1480e3fc983f76bd477 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 1 Aug 2024 19:15:17 +0000 Subject: [PATCH 42/76] Version bump to 1.5.6 --- android/vcmi-app/build.gradle | 4 ++-- cmake_modules/VersionDefinition.cmake | 2 +- debian/changelog | 6 ++++++ launcher/eu.vcmi.VCMI.metainfo.xml | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index f8f7596b8..c6429c6cb 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -26,8 +26,8 @@ android { minSdk = qtMinSdkVersion as Integer targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project - versionCode 1550 - versionName "1.5.5" + versionCode 1560 + versionName "1.5.6" setProperty("archivesBaseName", "vcmi") } diff --git a/cmake_modules/VersionDefinition.cmake b/cmake_modules/VersionDefinition.cmake index 67002ded8..959837e8b 100644 --- a/cmake_modules/VersionDefinition.cmake +++ b/cmake_modules/VersionDefinition.cmake @@ -1,6 +1,6 @@ set(VCMI_VERSION_MAJOR 1) set(VCMI_VERSION_MINOR 5) -set(VCMI_VERSION_PATCH 5) +set(VCMI_VERSION_PATCH 6) add_definitions( -DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR} -DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR} diff --git a/debian/changelog b/debian/changelog index 592ea716b..52247d334 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vcmi (1.5.6) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Sun, 4 Aug 2024 12:00:00 +0200 + vcmi (1.5.5) jammy; urgency=medium * New upstream release diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index 94e15aeb3..5e54c6620 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -90,6 +90,7 @@ vcmilauncher.desktop + From 46669e78e8206b7f3668ee97f28e7a5d30e6e335 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:02:37 +0000 Subject: [PATCH 43/76] Fix possible crash on deletion of adventureInt after GH on shutdown --- client/gui/CGuiHandler.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 675130e46..f5dd29eab 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -19,6 +19,7 @@ #include "../eventsSDL/InputHandler.h" #include "../CGameInfo.h" +#include "../adventureMap/AdventureMapInterface.h" #include "../render/Colors.h" #include "../render/Graphics.h" #include "../render/IFont.h" @@ -145,7 +146,13 @@ CGuiHandler::CGuiHandler() { } -CGuiHandler::~CGuiHandler() = default; +CGuiHandler::~CGuiHandler() +{ + // enforce deletion order on shutdown + // all UI elements including adventure map must be destroyed before Gui Handler + // proper solution would be removal of adventureInt global + adventureInt.reset(); +} ShortcutHandler & CGuiHandler::shortcuts() { From 8ce6bcdf3b8c9c3353130ad971297276624fed8b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:03:25 +0000 Subject: [PATCH 44/76] Right-clicking dwellings will now show recruitable creatures (but not their number) for all players --- lib/mapObjects/CGDwelling.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 058139e39..023abd77e 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -346,15 +346,19 @@ void CGDwelling::newTurn(CRandomGenerator & rand) const std::vector CGDwelling::getPopupComponents(PlayerColor player) const { - if (getOwner() != player) - return {}; + bool visitedByOwner = getOwner() == player; std::vector result; if (ID == Obj::CREATURE_GENERATOR1 && !creatures.empty()) { for (auto const & creature : creatures.front().second) - result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first); + { + if (visitedByOwner) + result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first); + else + result.emplace_back(ComponentType::CREATURE, creature); + } } if (ID == Obj::CREATURE_GENERATOR4) @@ -362,7 +366,12 @@ std::vector CGDwelling::getPopupComponents(PlayerColor player) const for (auto const & creatureLevel : creatures) { if (!creatureLevel.second.empty()) - result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first); + { + if (visitedByOwner) + result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first); + else + result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back()); + } } } return result; From 6678aafbbbdc1f5d2373ea2ba999fe78bd51a7dd Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 23 Jul 2024 01:47:10 +0200 Subject: [PATCH 45/76] Update googletest submodule Fixes the following warning, occurring during the linux-clang-test CI builds: [824/832] Building CXX object test/googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o In file included from /home/runner/work/vcmi/vcmi/test/googletest/googletest/src/gtest-all.cc:38: In file included from /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest.h:65: In file included from /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest-death-test.h:43: In file included from /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/internal/gtest-death-test-internal.h:47: In file included from /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest-matchers.h:48: /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest-printers.h:532:9: warning: implicit conversion from 'int32_t' (aka 'int') to 'float' may lose precision [-Wimplicit-int-float-conversion] 532 | if (static_cast(val * mulfor6 + 0.5) / mulfor6 == val) return 6; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest-printers.h:551:17: note: in instantiation of function template specialization 'testing::internal::AppropriateResolution' requested here 551 | os->precision(AppropriateResolution(f)); | ^ /home/runner/work/vcmi/vcmi/test/googletest/googletest/include/gtest/gtest-printers.h:544:9: warning: implicit conversion from 'int32_t' (aka 'int') to 'float' may lose precision [-Wimplicit-int-float-conversion] 544 | if (static_cast(val / divfor6 + 0.5) * divfor6 == val) return 6; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ 2 warnings generated. --- .gitmodules | 2 +- test/googletest | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index b484aea8e..d6b206fe1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "test/googletest"] path = test/googletest url = https://github.com/google/googletest - branch = v1.13.x + branch = v1.15.x [submodule "AI/FuzzyLite"] path = AI/FuzzyLite url = https://github.com/fuzzylite/fuzzylite.git diff --git a/test/googletest b/test/googletest index b796f7d44..b514bdc89 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 From 0ac1ef077dcdb56d1fede9f69daf121915aca9d9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:03:48 +0000 Subject: [PATCH 46/76] Workaround for hota witch hut preview --- lib/mapObjects/CRewardableObject.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 0e671b028..c1f9d546c 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -181,7 +181,26 @@ void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { if(answer == 0) + { + switch (configuration.visitMode) + { + case Rewardable::VISIT_UNLIMITED: + case Rewardable::VISIT_BONUS: + case Rewardable::VISIT_HERO: + case Rewardable::VISIT_LIMITER: + { + // workaround for object with refusable reward not getting marked as visited + // TODO: better solution that would also work for player-visitable objects + if (!wasScouted(hero->getOwner())) + { + ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, hero->id); + cb->sendAndApply(&cov); + } + } + } + return; // player refused + } if(answer > 0 && answer-1 < configuration.info.size()) { From c9b6b17422831f97ca4e400b1872e9df9dee1de8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:04:07 +0000 Subject: [PATCH 47/76] Add more logging for weird crash on game start --- server/CGameHandler.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2b8ec21da..18b831759 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -682,6 +682,15 @@ void CGameHandler::onNewTurn() } } + for (auto & player : gs->players) + { + if (player.second.status != EPlayerStatus::INGAME) + continue; + + if (player.second.heroes.empty() && player.second.towns.empty()) + throw std::runtime_error("Invalid player in player state! Player " + std::to_string(player.first.getNum()) + ", map name: " + gs->map->name.toString() + ", map description: " + gs->map->description.toString()); + } + if (newWeek && !firstTurn) { n.specialWeek = NewTurn::NORMAL; From 13108849caf50f9addc8dd7b7cc2ff6613b45dcf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:56:05 +0000 Subject: [PATCH 48/76] Try to fix crash on transferring component of a composite artifact --- lib/gameState/CGameStateCampaign.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index a016cc6ee..7f11c92fb 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -130,13 +130,15 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr if(!art) return false; - bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId()); + ArtifactLocation al(hero.hero->id, artifactPosition); - if (takeable) + bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId()); + bool locked = hero.hero->getSlot(al.slot)->locked; + + if (!locked && takeable) hero.transferrableArtifacts.push_back(artifactPosition); - ArtifactLocation al(hero.hero->id, artifactPosition); - if(!takeable && !hero.hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719 + if (!locked && !takeable) { hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot); return true; From 5023e08ae8a3f72d911e22aaa4c8710da428fdec Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Aug 2024 15:56:34 +0000 Subject: [PATCH 49/76] Fix crash on testing for hero faction before deserializing hero type --- lib/mapObjects/CGHeroInstance.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c99667f58..0a9a127c7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1709,6 +1709,16 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) setHeroTypeName(typeName); } + if(!handler.saving) + { + if(!appearance) + { + // crossoverDeserialize + type = getHeroType().toHeroType(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); + } + } + CArmedInstance::serializeJsonOptions(handler); { @@ -1724,13 +1734,6 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) if(!handler.saving) { - if(!appearance) - { - // crossoverDeserialize - type = getHeroType().toHeroType(); - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); - } - patrol.patrolling = (rawPatrolRadius > NO_PATROLING); patrol.initialPos = visitablePos(); patrol.patrolRadius = (rawPatrolRadius > NO_PATROLING) ? rawPatrolRadius : 0; From 9ceb1c567d0670933c630acc308b372f558b2403 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:37:46 +0200 Subject: [PATCH 50/76] highscore refactoring --- client/CServerHandler.cpp | 33 +------- client/CServerHandler.h | 2 - client/mainmenu/CHighScoreScreen.cpp | 68 ---------------- client/mainmenu/CHighScoreScreen.h | 10 --- lib/CMakeLists.txt | 1 + lib/gameState/GameStatistics.cpp | 15 +++- lib/gameState/GameStatistics.h | 2 + lib/gameState/HighScore.cpp | 111 +++++++++++++++++++++++++++ lib/gameState/HighScore.h | 27 +++++++ 9 files changed, 155 insertions(+), 114 deletions(-) create mode 100644 lib/gameState/HighScore.cpp diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 58b94a8be..3e1553df3 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -35,6 +35,7 @@ #include "../lib/TurnTimerInfo.h" #include "../lib/VCMIDirs.h" #include "../lib/campaign/CampaignState.h" +#include "../lib/gameState/HighScore.h" #include "../lib/CPlayerState.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/mapObjects/CGTownInstance.h" @@ -672,39 +673,9 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta setState(EClientState::GAMEPLAY); } -HighScoreParameter CServerHandler::prepareHighScores(PlayerColor player, bool victory) -{ - const auto * gs = client->gameState(); - const auto * playerState = gs->getPlayerState(player); - - HighScoreParameter param; - param.difficulty = gs->getStartInfo()->difficulty; - param.day = gs->getDate(); - param.townAmount = gs->howManyTowns(player); - param.usedCheat = gs->getPlayerState(player)->cheated; - param.hasGrail = false; - for(const CGHeroInstance * h : playerState->heroes) - if(h->hasArt(ArtifactID::GRAIL)) - param.hasGrail = true; - for(const CGTownInstance * t : playerState->towns) - if(t->builtBuildings.count(BuildingID::GRAIL)) - param.hasGrail = true; - param.allEnemiesDefeated = true; - for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer) - { - auto ps = gs->getPlayerState(otherPlayer, false); - if(ps && otherPlayer != player && !ps->checkVanquished()) - param.allEnemiesDefeated = false; - } - param.scenarioName = gs->getMapHeader()->name.toString(); - param.playerName = gs->getStartInfo()->playerInfos.find(player)->second.name; - - return param; -} - void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victory) { - HighScoreParameter param = prepareHighScores(player, victory); + HighScoreParameter param = HighScore::prepareHighScores(client->gameState(), player, victory); if(victory && client->gameState()->getStartInfo()->campState) { diff --git a/client/CServerHandler.h b/client/CServerHandler.h index e96496af6..55b441bcd 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -128,8 +128,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor bool isServerLocal() const; - HighScoreParameter prepareHighScores(PlayerColor player, bool victory); - public: /// High-level connection overlay that is capable of (de)serializing network data std::shared_ptr logicConnection; diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index fdf59d2a9..141ed7416 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -34,74 +34,6 @@ #include "../../lib/constants/EntityIdentifiers.h" #include "../../lib/gameState/HighScore.h" -auto HighScoreCalculation::calculate() -{ - struct Result - { - int basic = 0; - int total = 0; - int sumDays = 0; - bool cheater = false; - }; - - Result firstResult; - Result summary; - const std::array difficultyMultipliers{0.8, 1.0, 1.3, 1.6, 2.0}; - for(auto & param : parameters) - { - double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allEnemiesDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); - firstResult = Result{static_cast(tmp), static_cast(tmp * difficultyMultipliers.at(param.difficulty)), param.day, param.usedCheat}; - summary.basic += firstResult.basic * 5.0 / parameters.size(); - summary.total += firstResult.total * 5.0 / parameters.size(); - summary.sumDays += firstResult.sumDays; - summary.cheater |= firstResult.cheater; - } - - if(parameters.size() == 1) - return firstResult; - - return summary; -} - -struct HighScoreCreature -{ - CreatureID creature; - int min; - int max; -}; - -static std::vector getHighscoreCreaturesList() -{ - JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); - - std::vector ret; - - for(auto & json : configCreatures["creatures"].Vector()) - { - HighScoreCreature entry; - entry.creature = CreatureID::decode(json["creature"].String()); - entry.max = json["max"].isNull() ? std::numeric_limits::max() : json["max"].Integer(); - entry.min = json["min"].isNull() ? std::numeric_limits::min() : json["min"].Integer(); - - ret.push_back(entry); - } - - return ret; -} - -CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) -{ - static const std::vector creatures = getHighscoreCreaturesList(); - - int divide = campaign ? 5 : 1; - - for(auto & creature : creatures) - if(points / divide <= creature.max && points / divide >= creature.min) - return creature.creature; - - throw std::runtime_error("Unable to find creature for score " + std::to_string(points)); -} - CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted) : CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted) { diff --git a/client/mainmenu/CHighScoreScreen.h b/client/mainmenu/CHighScoreScreen.h index 3a963a88e..93f79e42d 100644 --- a/client/mainmenu/CHighScoreScreen.h +++ b/client/mainmenu/CHighScoreScreen.h @@ -21,16 +21,6 @@ class CFilledTexture; class TransparentFilledRectangle; -class HighScoreCalculation -{ -public: - std::vector parameters; - bool isCampaign = false; - - auto calculate(); - static CreatureID getCreatureForPoints(int points, bool campaign); -}; - class CHighScoreScreen : public CWindowObject { public: diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 13e17faca..e598a4389 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -99,6 +99,7 @@ set(lib_MAIN_SRCS gameState/CGameState.cpp gameState/CGameStateCampaign.cpp + gameState/HighScore.cpp gameState/InfoAboutArmy.cpp gameState/RumorState.cpp gameState/TavernHeroesPool.cpp diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 0b502ee3a..bb1568ab3 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -15,6 +15,7 @@ #include "TerrainHandler.h" #include "CHeroHandler.h" #include "StartInfo.h" +#include "HighScore.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGObjectInstance.h" @@ -32,6 +33,11 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons { StatisticDataSetEntry data; + HighScoreParameter param = HighScore::prepareHighScores(gs, ps->color, false); + HighScoreCalculation scenarioHighScores; + scenarioHighScores.parameters.push_back(param); + scenarioHighScores.isCampaign = false; + data.day = gs->getDate(Date::DAY); data.player = ps->color; data.team = ps->team; @@ -39,7 +45,7 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.status = ps->status; data.resources = ps->resources; data.numberHeroes = ps->heroes.size(); - data.numberTowns = ps->towns.size(); + data.numberTowns = gs->howManyTowns(ps->color); data.numberArtifacts = Statistic::getNumberOfArts(ps); data.armyStrength = Statistic::getArmyStrength(ps, true); data.income = Statistic::getIncome(gs, ps); @@ -47,6 +53,7 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.obeliskVisited = Statistic::getObeliskVisited(gs, ps->team); data.mightMagicRatio = Statistic::getMightMagicRatio(ps); data.numMines = Statistic::getNumMines(gs, ps); + data.score = scenarioHighScores.calculate().total; return data; } @@ -69,7 +76,8 @@ std::string StatisticDataSet::toCsv() ss << "Income" << ";"; ss << "MapVisitedRatio" << ";"; ss << "ObeliskVisited" << ";"; - ss << "MightMagicRatio"; + ss << "MightMagicRatio" << ";"; + ss << "Score"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; for(auto & resource : resources) @@ -90,7 +98,8 @@ std::string StatisticDataSet::toCsv() ss << entry.income << ";"; ss << entry.mapVisitedRatio << ";"; ss << entry.obeliskVisited << ";"; - ss << entry.mightMagicRatio; + ss << entry.mightMagicRatio << ";"; + ss << entry.score; for(auto & resource : resources) ss << ";" << entry.resources[resource]; for(auto & resource : resources) diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 2161e7e42..059046b99 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -36,6 +36,7 @@ struct DLL_LINKAGE StatisticDataSetEntry int obeliskVisited; double mightMagicRatio; std::map numMines; + int score; template void serialize(Handler &h) { @@ -54,6 +55,7 @@ struct DLL_LINKAGE StatisticDataSetEntry h & obeliskVisited; h & mightMagicRatio; h & numMines; + h & score; } }; diff --git a/lib/gameState/HighScore.cpp b/lib/gameState/HighScore.cpp new file mode 100644 index 000000000..51759629e --- /dev/null +++ b/lib/gameState/HighScore.cpp @@ -0,0 +1,111 @@ +/* + * HighScore.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 "HighScore.h" +#include "../CPlayerState.h" +#include "../constants/StringConstants.h" +#include "CGameState.h" +#include "StartInfo.h" +#include "../mapping/CMapHeader.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/CGTownInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +HighScoreParameter HighScore::prepareHighScores(const CGameState * gs, PlayerColor player, bool victory) +{ + const auto * playerState = gs->getPlayerState(player); + + HighScoreParameter param; + param.difficulty = gs->getStartInfo()->difficulty; + param.day = gs->getDate(); + param.townAmount = gs->howManyTowns(player); + param.usedCheat = gs->getPlayerState(player)->cheated; + param.hasGrail = false; + for(const CGHeroInstance * h : playerState->heroes) + if(h->hasArt(ArtifactID::GRAIL)) + param.hasGrail = true; + for(const CGTownInstance * t : playerState->towns) + if(t->builtBuildings.count(BuildingID::GRAIL)) + param.hasGrail = true; + param.allEnemiesDefeated = true; + for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer) + { + auto ps = gs->getPlayerState(otherPlayer, false); + if(ps && otherPlayer != player && !ps->checkVanquished()) + param.allEnemiesDefeated = false; + } + param.scenarioName = gs->getMapHeader()->name.toString(); + param.playerName = gs->getStartInfo()->playerInfos.find(player)->second.name; + + return param; +} + +HighScoreCalculation::Result HighScoreCalculation::calculate() +{ + Result firstResult; + Result summary; + const std::array difficultyMultipliers{0.8, 1.0, 1.3, 1.6, 2.0}; + for(auto & param : parameters) + { + double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allEnemiesDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0); + firstResult = Result{static_cast(tmp), static_cast(tmp * difficultyMultipliers.at(param.difficulty)), param.day, param.usedCheat}; + summary.basic += firstResult.basic * 5.0 / parameters.size(); + summary.total += firstResult.total * 5.0 / parameters.size(); + summary.sumDays += firstResult.sumDays; + summary.cheater |= firstResult.cheater; + } + + if(parameters.size() == 1) + return firstResult; + + return summary; +} + +struct HighScoreCreature +{ + CreatureID creature; + int min; + int max; +}; + +static std::vector getHighscoreCreaturesList() +{ + JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json")); + + std::vector ret; + + for(auto & json : configCreatures["creatures"].Vector()) + { + HighScoreCreature entry; + entry.creature = CreatureID::decode(json["creature"].String()); + entry.max = json["max"].isNull() ? std::numeric_limits::max() : json["max"].Integer(); + entry.min = json["min"].isNull() ? std::numeric_limits::min() : json["min"].Integer(); + + ret.push_back(entry); + } + + return ret; +} + +CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign) +{ + static const std::vector creatures = getHighscoreCreaturesList(); + + int divide = campaign ? 5 : 1; + + for(auto & creature : creatures) + if(points / divide <= creature.max && points / divide >= creature.min) + return creature.creature; + + throw std::runtime_error("Unable to find creature for score " + std::to_string(points)); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/HighScore.h b/lib/gameState/HighScore.h index 74e2dcd76..b031ed1b4 100644 --- a/lib/gameState/HighScore.h +++ b/lib/gameState/HighScore.h @@ -9,8 +9,12 @@ */ #pragma once +#include "../GameConstants.h" + VCMI_LIB_NAMESPACE_BEGIN +class CGameState; + class DLL_LINKAGE HighScoreParameter { public: @@ -37,5 +41,28 @@ public: h & playerName; } }; +class DLL_LINKAGE HighScore +{ +public: + static HighScoreParameter prepareHighScores(const CGameState * gs, PlayerColor player, bool victory); +}; + +class DLL_LINKAGE HighScoreCalculation +{ +public: + struct Result + { + int basic = 0; + int total = 0; + int sumDays = 0; + bool cheater = false; + }; + + std::vector parameters; + bool isCampaign = false; + + Result calculate(); + static CreatureID getCreatureForPoints(int points, bool campaign); +}; VCMI_LIB_NAMESPACE_END From 80dd97364ad460abff06c746c03276d0023ec47c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:38:33 +0200 Subject: [PATCH 51/76] add additional statistic --- lib/gameState/GameStatistics.cpp | 23 ++++++++++++++---- lib/gameState/GameStatistics.h | 30 +++++++++++++++++++++++- server/battles/BattleResultProcessor.cpp | 16 +++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index bb1568ab3..ebc6f9e1e 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -54,6 +54,11 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.mightMagicRatio = Statistic::getMightMagicRatio(ps); data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; + data.maxHeroLevel = Statistic::findBestHero(gs, ps->color)->level; + data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.at(ps->color); + data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.at(ps->color); + data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.at(ps->color); + data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.at(ps->color); return data; } @@ -77,7 +82,12 @@ std::string StatisticDataSet::toCsv() ss << "MapVisitedRatio" << ";"; ss << "ObeliskVisited" << ";"; ss << "MightMagicRatio" << ";"; - ss << "Score"; + ss << "Score" << ";"; + ss << "MaxHeroLevel" << ";"; + ss << "NumBattlesNeutral" << ";"; + ss << "NumBattlesPlayer" << ";"; + ss << "NumWinBattlesNeutral" << ";"; + ss << "NumWinBattlesPlayer"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; for(auto & resource : resources) @@ -99,7 +109,12 @@ std::string StatisticDataSet::toCsv() ss << entry.mapVisitedRatio << ";"; ss << entry.obeliskVisited << ";"; ss << entry.mightMagicRatio << ";"; - ss << entry.score; + ss << entry.score << ";"; + ss << entry.maxHeroLevel << ";"; + ss << entry.numBattlesNeutral << ";"; + ss << entry.numBattlesPlayer << ";"; + ss << entry.numWinBattlesNeutral << ";"; + ss << entry.numWinBattlesPlayer; for(auto & resource : resources) ss << ";" << entry.resources[resource]; for(auto & resource : resources) @@ -220,9 +235,9 @@ double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) return visible / numTiles; } -const CGHeroInstance * Statistic::findBestHero(CGameState * gs, const PlayerColor & color) +const CGHeroInstance * Statistic::findBestHero(const CGameState * gs, const PlayerColor & color) { - std::vector > &h = gs->players[color].heroes; + auto &h = gs->players.at(color).heroes; if(h.empty()) return nullptr; //best hero will be that with highest exp diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 059046b99..70d34f512 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -37,6 +37,11 @@ struct DLL_LINKAGE StatisticDataSetEntry double mightMagicRatio; std::map numMines; int score; + int maxHeroLevel; + int numBattlesNeutral; + int numBattlesPlayer; + int numWinBattlesNeutral; + int numWinBattlesPlayer; template void serialize(Handler &h) { @@ -56,6 +61,11 @@ struct DLL_LINKAGE StatisticDataSetEntry h & mightMagicRatio; h & numMines; h & score; + h & maxHeroLevel; + h & numBattlesNeutral; + h & numBattlesPlayer; + h & numWinBattlesNeutral; + h & numWinBattlesPlayer; } }; @@ -68,9 +78,27 @@ public: static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs); std::string toCsv(); + struct ValueStorage + { + std::map numBattlesNeutral; + std::map numBattlesPlayer; + std::map numWinBattlesNeutral; + std::map numWinBattlesPlayer; + + template void serialize(Handler &h) + { + h & numBattlesNeutral; + h & numBattlesPlayer; + h & numWinBattlesNeutral; + h & numWinBattlesPlayer; + } + }; + ValueStorage values; + template void serialize(Handler &h) { h & data; + h & values; } }; @@ -84,7 +112,7 @@ public: static si64 getArmyStrength(const PlayerState * ps, bool withTownGarrison = false); static int getIncome(const CGameState * gs, const PlayerState * ps); static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); - static const CGHeroInstance * findBestHero(CGameState * gs, const PlayerColor & color); + static const CGHeroInstance * findBestHero(const CGameState * gs, const PlayerColor & color); static std::vector> getRank(std::vector stats); static int getObeliskVisited(const CGameState * gs, const TeamID & t); static double getMightMagicRatio(const PlayerState * ps); diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 122ec9a7b..8deb2d4fb 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -497,6 +497,22 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) gameHandler->sendAndApply(&ro); } + // add statistic + if(battle.sideToPlayer(0) == PlayerColor::NEUTRAL || battle.sideToPlayer(1) == PlayerColor::NEUTRAL) + { + gameHandler->gameState()->statistic.values.numBattlesNeutral[battle.sideToPlayer(0)]++; + gameHandler->gameState()->statistic.values.numBattlesNeutral[battle.sideToPlayer(1)]++; + if(!finishingBattle->isDraw()) + gameHandler->gameState()->statistic.values.numWinBattlesNeutral[battle.sideToPlayer(finishingBattle->winnerSide)]++; + } + else + { + gameHandler->gameState()->statistic.values.numBattlesPlayer[battle.sideToPlayer(0)]++; + gameHandler->gameState()->statistic.values.numBattlesPlayer[battle.sideToPlayer(1)]++; + if(!finishingBattle->isDraw()) + gameHandler->gameState()->statistic.values.numWinBattlesPlayer[battle.sideToPlayer(finishingBattle->winnerSide)]++; + } + BattleResultAccepted raccepted; raccepted.battleID = battle.getBattle()->getBattleID(); raccepted.heroResult[0].army = const_cast(battle.battleGetArmyObject(BattleSide::ATTACKER)); From 13b2008f796a2f6ad60607851a50533fae4ace0d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 1 Aug 2024 21:54:24 +0000 Subject: [PATCH 52/76] Enabled terminate handler and call stack writing on all platforms Attempt to get a bit more debug info from crashes. VCMI will now: - use c++ terminate handler on any platform in release builds - attempt to write call stack to log file using boost::callstack Since I use std::set_terminate this will only affect c++ exceptions, e.g. std::runtime_error and will not affect OS signals, e.g. SIGSEGV. Handling signals is OS-specific and has a lot of limitations that I don't want to investigate. Besides - most of our crashes are now caused by c++ exceptions. Haven't tested on other platforms, but should at the very least write exception information (`e.what()`) for all exceptions and function names for methods exported from dll's (libvcmi.so & AI's). Possibly more, if debug information is present. --- Global.h | 6 ++++ lib/CConsoleHandler.cpp | 62 ++++++++++++++++++++++++----------------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Global.h b/Global.h index 57c4466ef..cfa07cc90 100644 --- a/Global.h +++ b/Global.h @@ -102,6 +102,12 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); # define STRONG_INLINE inline #endif +// Required for building boost::stacktrace on macOS. +// See https://github.com/boostorg/stacktrace/issues/88 +#if defined(VCMI_APPLE) +#define _GNU_SOURCE +#endif + #define _USE_MATH_DEFINES #include diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 05ea6f428..8f1b0096e 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -13,6 +13,8 @@ #include "CThreadHelper.h" +#include + VCMI_LIB_NAMESPACE_BEGIN std::mutex CConsoleHandler::smx; @@ -142,6 +144,30 @@ static void createMemoryDump(MINIDUMP_EXCEPTION_INFORMATION * meinfo) MessageBoxA(0, "VCMI has crashed. We are sorry. File with information about encountered problem has been created.", "VCMI Crashhandler", MB_OK | MB_ICONERROR); } +LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception) +{ + logGlobal->error("Disaster happened."); + + PEXCEPTION_RECORD einfo = exception->ExceptionRecord; + logGlobal->error("Reason: 0x%x - %s at %04x:%x", einfo->ExceptionCode, exceptionName(einfo->ExceptionCode), exception->ContextRecord->SegCs, (void*)einfo->ExceptionAddress); + + if (einfo->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) + { + logGlobal->error("Attempt to %s 0x%8x", (einfo->ExceptionInformation[0] == 1 ? "write to" : "read from"), (void*)einfo->ExceptionInformation[1]); + } + const DWORD threadId = ::GetCurrentThreadId(); + logGlobal->error("Thread ID: %d", threadId); + + //exception info to be placed in the dump + MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE}; + + createMemoryDump(&meinfo); + + return EXCEPTION_EXECUTE_HANDLER; +} + +#endif + [[noreturn]] static void onTerminate() { logGlobal->error("Disaster happened."); @@ -166,37 +192,20 @@ static void createMemoryDump(MINIDUMP_EXCEPTION_INFORMATION * meinfo) logGlobal->error("Reason: unknown exception!"); } + logGlobal->error("Call stack information:"); + std::stringstream stream; + stream << boost::stacktrace::stacktrace(); + logGlobal->error("%s", stream.str()); + +#ifdef VCMI_WINDOWS const DWORD threadId = ::GetCurrentThreadId(); logGlobal->error("Thread ID: %d", threadId); createMemoryDump(nullptr); +#endif std::abort(); } -LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception) -{ - logGlobal->error("Disaster happened."); - - PEXCEPTION_RECORD einfo = exception->ExceptionRecord; - logGlobal->error("Reason: 0x%x - %s at %04x:%x", einfo->ExceptionCode, exceptionName(einfo->ExceptionCode), exception->ContextRecord->SegCs, (void*)einfo->ExceptionAddress); - - if (einfo->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) - { - logGlobal->error("Attempt to %s 0x%8x", (einfo->ExceptionInformation[0] == 1 ? "write to" : "read from"), (void*)einfo->ExceptionInformation[1]); - } - const DWORD threadId = ::GetCurrentThreadId(); - logGlobal->error("Thread ID: %d", threadId); - - //exception info to be placed in the dump - MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE}; - - createMemoryDump(&meinfo); - - return EXCEPTION_EXECUTE_HANDLER; -} -#endif - - void CConsoleHandler::setColor(EConsoleTextColor::EConsoleTextColor color) { TColor colorCode; @@ -289,11 +298,14 @@ CConsoleHandler::CConsoleHandler(): defErrColor = csbi.wAttributes; #ifndef _DEBUG SetUnhandledExceptionFilter(onUnhandledException); - std::set_terminate(onTerminate); #endif #else defColor = "\x1b[0m"; #endif + +#ifndef _DEBUG + std::set_terminate(onTerminate); +#endif } CConsoleHandler::~CConsoleHandler() { From acf2e0eef16fef83359eaa78538e0e1065ced515 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 20:01:00 +0200 Subject: [PATCH 53/76] fix --- client/CServerHandler.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 55b441bcd..ace71bd68 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -40,8 +40,6 @@ class GlobalLobbyClient; class GameChatHandler; class IServerRunner; -class HighScoreCalculation; - enum class ESelectionScreen : ui8; enum class ELoadMode : ui8; From b3b7729a6c23b3cc8bb48d8e074b54f568b8d528 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 20:06:30 +0200 Subject: [PATCH 54/76] fix --- lib/gameState/GameStatistics.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index ebc6f9e1e..efd14c079 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -55,10 +55,10 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; data.maxHeroLevel = Statistic::findBestHero(gs, ps->color)->level; - data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.at(ps->color); - data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.at(ps->color); - data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.at(ps->color); - data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.at(ps->color); + data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.count(ps->color) ? gs->statistic.values.numBattlesNeutral.at(ps->color) : 0; + data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.count(ps->color) ? gs->statistic.values.numBattlesPlayer.at(ps->color) : 0; + data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.count(ps->color) ? gs->statistic.values.numWinBattlesNeutral.at(ps->color) : 0; + data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.count(ps->color) ? gs->statistic.values.numWinBattlesPlayer.at(ps->color) : 0; return data; } From 58bfd27aaddfaa74ee81844b0ea153e9bc1c39cc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 2 Aug 2024 20:40:24 +0200 Subject: [PATCH 55/76] add some stats --- lib/gameState/GameStatistics.cpp | 18 +++++++++++++++--- lib/gameState/GameStatistics.h | 12 ++++++++++++ server/battles/BattleResultProcessor.cpp | 6 ++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index efd14c079..e0ffbaae1 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -38,6 +38,8 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons scenarioHighScores.parameters.push_back(param); scenarioHighScores.isCampaign = false; + data.map = gs->map->name.toString(); + data.timestamp = std::time(0); data.day = gs->getDate(Date::DAY); data.player = ps->color; data.team = ps->team; @@ -54,11 +56,13 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.mightMagicRatio = Statistic::getMightMagicRatio(ps); data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; - data.maxHeroLevel = Statistic::findBestHero(gs, ps->color)->level; + data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0; data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.count(ps->color) ? gs->statistic.values.numBattlesNeutral.at(ps->color) : 0; data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.count(ps->color) ? gs->statistic.values.numBattlesPlayer.at(ps->color) : 0; data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.count(ps->color) ? gs->statistic.values.numWinBattlesNeutral.at(ps->color) : 0; data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.count(ps->color) ? gs->statistic.values.numWinBattlesPlayer.at(ps->color) : 0; + data.numHeroSurrendered = gs->statistic.values.numHeroSurrendered.count(ps->color) ? gs->statistic.values.numHeroSurrendered.at(ps->color) : 0; + data.numHeroEscaped = gs->statistic.values.numHeroEscaped.count(ps->color) ? gs->statistic.values.numHeroEscaped.at(ps->color) : 0; return data; } @@ -69,6 +73,8 @@ std::string StatisticDataSet::toCsv() auto resources = std::vector{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS}; + ss << "Map" << ";"; + ss << "Timestamp" << ";"; ss << "Day" << ";"; ss << "Player" << ";"; ss << "Team" << ";"; @@ -87,7 +93,9 @@ std::string StatisticDataSet::toCsv() ss << "NumBattlesNeutral" << ";"; ss << "NumBattlesPlayer" << ";"; ss << "NumWinBattlesNeutral" << ";"; - ss << "NumWinBattlesPlayer"; + ss << "NumWinBattlesPlayer" << ";"; + ss << "NumHeroSurrendered" << ";"; + ss << "NumHeroEscaped"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; for(auto & resource : resources) @@ -96,6 +104,8 @@ std::string StatisticDataSet::toCsv() for(auto & entry : data) { + ss << entry.map << ";"; + ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H-%M-%S") << ";"; ss << entry.day << ";"; ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";"; ss << entry.team.getNum() << ";"; @@ -114,7 +124,9 @@ std::string StatisticDataSet::toCsv() ss << entry.numBattlesNeutral << ";"; ss << entry.numBattlesPlayer << ";"; ss << entry.numWinBattlesNeutral << ";"; - ss << entry.numWinBattlesPlayer; + ss << entry.numWinBattlesPlayer << ";"; + ss << entry.numHeroSurrendered << ";"; + ss << entry.numHeroEscaped; for(auto & resource : resources) ss << ";" << entry.resources[resource]; for(auto & resource : resources) diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 70d34f512..20387e774 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -21,6 +21,8 @@ class CGMine; struct DLL_LINKAGE StatisticDataSetEntry { + std::string map; + time_t timestamp; int day; PlayerColor player; TeamID team; @@ -42,9 +44,13 @@ struct DLL_LINKAGE StatisticDataSetEntry int numBattlesPlayer; int numWinBattlesNeutral; int numWinBattlesPlayer; + int numHeroSurrendered; + int numHeroEscaped; template void serialize(Handler &h) { + h & map; + h & timestamp; h & day; h & player; h & team; @@ -66,6 +72,8 @@ struct DLL_LINKAGE StatisticDataSetEntry h & numBattlesPlayer; h & numWinBattlesNeutral; h & numWinBattlesPlayer; + h & numHeroSurrendered; + h & numHeroEscaped; } }; @@ -84,6 +92,8 @@ public: std::map numBattlesPlayer; std::map numWinBattlesNeutral; std::map numWinBattlesPlayer; + std::map numHeroSurrendered; + std::map numHeroEscaped; template void serialize(Handler &h) { @@ -91,6 +101,8 @@ public: h & numBattlesPlayer; h & numWinBattlesNeutral; h & numWinBattlesPlayer; + h & numHeroSurrendered; + h & numHeroEscaped; } }; ValueStorage values; diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 8deb2d4fb..c8c8dce7d 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -572,10 +572,16 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const gameHandler->checkVictoryLossConditions(playerColors); if (result.result == EBattleResult::SURRENDER) + { + gameHandler->gameState()->statistic.values.numHeroSurrendered[finishingBattle->loser]++; gameHandler->heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero); + } if (result.result == EBattleResult::ESCAPE) + { + gameHandler->gameState()->statistic.values.numHeroEscaped[finishingBattle->loser]++; gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero); + } if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty() && (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive)) From a4e4ec565b765c7eacc73f6ad32d9cc302f1480d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 1 Aug 2024 19:27:35 +0000 Subject: [PATCH 56/76] Changelog for 1.5.6 --- ChangeLog.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 4124de750..2932b8fc4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,31 @@ +# 1.5.5 -> 1.5.6 + +# Stability +* Fixed possible crash on transferring hero to next campaign scenario if hero has combined artifact some components of which can be transferred +* Fixed possible crash on transferring hero to next campaign scenario that has creature with faction limiter in his army +* Fixed possible crash on application shutdown due to incorrect destruction order of UI entities + +# Multiplayer +* Mod compatibility issues when joining a lobby room now use color coding to make them less easy to miss. +* Incompatible mods are now placed before compatible mods when joining lobby room. +* Fixed text overflow in online lobby interface +* Fixed jittering simultaneous turns slider after moving it twice over short period + +# Interface +* Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild. +* Game now correctly uses melee damage calculation when forcing a melee attack with a shooter. +* Game will now close all open dialogs on start of our turn, to avoid bugs like locked right-click popups + +# Map Objects +* Spells the hero can't learn are no longer hidden when received from a rewardable object, such as the Pandora Box +* Spells that cannot be learned are now displayed with gray text in the name of the spell. +* Configurable objects with scouted state such as Witch Hut in HotA now correctly show their reward on right click after vising them but refusing to accept reward +* Right-click tooltip on map dwelling now always shows produced creatures. Player that owns the dwelling can also see number of creatures available for recruit + +# Modding +* Fixed possible crash on invalid SPELL_LIKE_ATTACK bonus +* Added compatibility check when loading maps with old names for boats + # 1.5.4 -> 1.5.5 * Fixed crash when advancing to the next scenario in campaigns when the hero not transferring has a combination artefact that can be transferred to the next scenario. From 81e6207df06d33c660a487598c44fcfb6e95c63f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 1 Aug 2024 20:48:55 +0000 Subject: [PATCH 57/76] Add helper functions for integer division rounding Added set of functions that perform integer division with different rounding modes: - divideAndCeil - rounds up to next integer - divideAndRound - rounds to nearest integer - divideAndFloor - rounds to previous integer (equivalent to default division) Intended for use in library, where usage of floating point might lead to desync in multiplayer games. Replaced some cases that I knew of, including recent handicap PR --- Global.h | 27 ++++++++++++++++++++++++ lib/battle/DamageCalculator.cpp | 3 ++- lib/mapObjects/CGTownInstance.cpp | 4 ++-- lib/mapObjects/MiscObjects.cpp | 2 +- server/battles/BattleActionProcessor.cpp | 2 +- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Global.h b/Global.h index 57c4466ef..1f9274e90 100644 --- a/Global.h +++ b/Global.h @@ -700,6 +700,33 @@ namespace vstd return a + (b - a) * f; } + /// Divides dividend by divisor and rounds result up + /// For use with integer-only arithmetic + template + Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor) + { + static_assert(std::is_integral_v && std::is_integral_v, "This function should only be used with integral types"); + return (dividend + divisor - 1) / divisor; + } + + /// Divides dividend by divisor and rounds result to nearest + /// For use with integer-only arithmetic + template + Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor) + { + static_assert(std::is_integral_v && std::is_integral_v, "This function should only be used with integral types"); + return (dividend + divisor / 2 - 1) / divisor; + } + + /// Divides dividend by divisor and rounds result down + /// For use with integer-only arithmetic + template + Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor) + { + static_assert(std::is_integral_v && std::is_integral_v, "This function should only be used with integral types"); + return dividend / divisor; + } + template bool isAlmostZero(const Floating & value) { diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index 99bce45e6..0bcc88ab0 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -145,7 +145,8 @@ int DamageCalculator::getActorAttackIgnored() const if(multAttackReductionPercent > 0) { - int reduction = (getActorAttackBase() * multAttackReductionPercent + 49) / 100; //using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix) + //using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix) + int reduction = vstd::divideAndRound( getActorAttackBase() * multAttackReductionPercent, 100); return -std::min(reduction, getActorAttackBase()); } return 0; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index bec0aebc7..6f646bf87 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -227,7 +227,7 @@ TResources CGTownInstance::dailyIncome() const auto playerSettings = cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner()); for(TResources::nziterator it(ret); it.valid(); it++) // always round up income - we don't want to always produce zero if handicap in use - ret[it->resType] = (ret[it->resType] * playerSettings.handicap.percentIncome + 99) / 100; + ret[it->resType] = vstd::divideAndCeil(ret[it->resType] * playerSettings.handicap.percentIncome, 100); return ret; } @@ -1271,7 +1271,7 @@ int GrowthInfo::totalGrowth() const ret += entry.count; // always round up income - we don't want buildings to always produce zero if handicap in use - return (ret * handicapPercentage + 99) / 100; + return vstd::divideAndCeil(ret * handicapPercentage, 100); } void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index b0eff7592..33e96cbd7 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -200,7 +200,7 @@ ui32 CGMine::getProducedQuantity() const { auto * playerSettings = cb->getPlayerSettings(getOwner()); // always round up income - we don't want mines to always produce zero if handicap in use - return (producedQuantity * playerSettings->handicap.percentIncome + 99) / 100; + return vstd::divideAndCeil(producedQuantity * playerSettings->handicap.percentIncome, 100); } void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 4a8e9b4f0..c8978a1ea 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -1267,7 +1267,7 @@ void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle, vstd::amin(chanceToKill, 1); //cap at 100% int killedCreatures = gameHandler->getRandomGenerator().nextBinomialInt(attacker->getCount(), chanceToKill); - int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100; + int maxToKill = vstd::divideAndCeil(attacker->getCount() * singleCreatureKillChancePercent, 100); vstd::amin(killedCreatures, maxToKill); killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level(); From 34b824f9ea0502029932eaced237285612e76da9 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:04:27 +0300 Subject: [PATCH 58/76] moveChildForeground --- client/gui/CIntObject.cpp | 9 +++++++++ client/gui/CIntObject.h | 2 ++ client/widgets/CArtPlace.cpp | 1 + client/widgets/MiscWidgets.cpp | 5 +++++ client/widgets/MiscWidgets.h | 1 + 5 files changed, 18 insertions(+) diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 3f136151e..cd768b033 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -258,6 +258,15 @@ void CIntObject::redraw() } } +void CIntObject::moveChildForeground(const CIntObject * childToMove) +{ + for(auto child = children.begin(); child != children.end(); child++) + if(*child == childToMove && child != children.end()) + { + std::rotate(child, child + 1, children.end()); + } +} + bool CIntObject::receiveEvent(const Point & position, int eventType) const { return pos.isInside(position); diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index a2afaea54..3a8760c4c 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -102,6 +102,8 @@ public: void showAll(Canvas & to) override; //request complete redraw of this object void redraw() override; + // Move child object to foreground + void moveChildForeground(const CIntObject * childToMove); /// returns true if this element is a popup window /// called only for windows diff --git a/client/widgets/CArtPlace.cpp b/client/widgets/CArtPlace.cpp index d4d57d785..bd0a39cc3 100644 --- a/client/widgets/CArtPlace.cpp +++ b/client/widgets/CArtPlace.cpp @@ -90,6 +90,7 @@ CArtPlace::CArtPlace(Point position, const CArtifactInstance * art) image = std::make_shared(AnimationPath::builtin("artifact"), imageIndex); image->disable(); + moveSelectionForeground(); } const CArtifactInstance * CArtPlace::getArt() const diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index fcaefaa86..403dba07d 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -714,3 +714,8 @@ void SelectableSlot::setSelectionWidth(int width) selection = std::make_shared( selection->pos - pos.topLeft(), Colors::TRANSPARENCY, Colors::YELLOW, width); selectSlot(selected); } + +void SelectableSlot::moveSelectionForeground() +{ + moveChildForeground(selection.get()); +} diff --git a/client/widgets/MiscWidgets.h b/client/widgets/MiscWidgets.h index 36a741755..926d54f32 100644 --- a/client/widgets/MiscWidgets.h +++ b/client/widgets/MiscWidgets.h @@ -261,4 +261,5 @@ public: void selectSlot(bool on); bool isSelected() const; void setSelectionWidth(int width); + void moveSelectionForeground(); }; From f0c0beb9e0442a845f8fcc3695ccb4fa94a9ee24 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 3 Aug 2024 16:39:46 +0200 Subject: [PATCH 59/76] date according to iso8601 --- lib/gameState/GameStatistics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index e0ffbaae1..c5b2f1e86 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -105,7 +105,7 @@ std::string StatisticDataSet::toCsv() for(auto & entry : data) { ss << entry.map << ";"; - ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H-%M-%S") << ";"; + ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H:%M:%S") << ";"; ss << entry.day << ";"; ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";"; ss << entry.team.getNum() << ";"; From 9d64059496208fabef1559e34392acc018675d19 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:55:43 +0200 Subject: [PATCH 60/76] code review --- lib/gameState/GameStatistics.cpp | 37 +++++++++++++------------------- lib/gameState/GameStatistics.h | 18 ++++++---------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index c5b2f1e86..9f85c75d6 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -51,9 +51,8 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numberArtifacts = Statistic::getNumberOfArts(ps); data.armyStrength = Statistic::getArmyStrength(ps, true); data.income = Statistic::getIncome(gs, ps); - data.mapVisitedRatio = Statistic::getMapVisitedRatio(gs, ps->color); - data.obeliskVisited = Statistic::getObeliskVisited(gs, ps->team); - data.mightMagicRatio = Statistic::getMightMagicRatio(ps); + data.mapExploredRatio = Statistic::getMapExploredRatio(gs, ps->color); + data.obeliskVisitedRatio = Statistic::getObeliskVisitedRatio(gs, ps->team); data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0; @@ -85,9 +84,8 @@ std::string StatisticDataSet::toCsv() ss << "NumberArtifacts" << ";"; ss << "ArmyStrength" << ";"; ss << "Income" << ";"; - ss << "MapVisitedRatio" << ";"; - ss << "ObeliskVisited" << ";"; - ss << "MightMagicRatio" << ";"; + ss << "MapExploredRatio" << ";"; + ss << "ObeliskVisitedRatio" << ";"; ss << "Score" << ";"; ss << "MaxHeroLevel" << ";"; ss << "NumBattlesNeutral" << ";"; @@ -116,9 +114,8 @@ std::string StatisticDataSet::toCsv() ss << entry.numberArtifacts << ";"; ss << entry.armyStrength << ";"; ss << entry.income << ";"; - ss << entry.mapVisitedRatio << ";"; - ss << entry.obeliskVisited << ";"; - ss << entry.mightMagicRatio << ";"; + ss << entry.mapExploredRatio << ";"; + ss << entry.obeliskVisitedRatio << ";"; ss << entry.score << ";"; ss << entry.maxHeroLevel << ";"; ss << entry.numBattlesNeutral << ";"; @@ -225,10 +222,10 @@ int Statistic::getIncome(const CGameState * gs, const PlayerState * ps) return totalIncome; } -double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player) +float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player) { - double visible = 0.0; - double numTiles = 0.0; + float visible = 0.0; + float numTiles = 0.0; for(int layer = 0; layer < (gs->map->twoLevel ? 2 : 1); layer++) for(int y = 0; y < gs->map->height; ++y) @@ -264,9 +261,9 @@ const CGHeroInstance * Statistic::findBestHero(const CGameState * gs, const Play return h[best]; } -std::vector> Statistic::getRank(std::vector stats) +std::vector> Statistic::getRank(std::vector> stats) { - std::sort(stats.begin(), stats.end(), [](const TStat & a, const TStat & b) { return a.second > b.second; }); + std::sort(stats.begin(), stats.end(), [](const std::pair & a, const std::pair & b) { return a.second > b.second; }); //put first element std::vector< std::vector > ret; @@ -301,15 +298,11 @@ int Statistic::getObeliskVisited(const CGameState * gs, const TeamID & t) return 0; } -double Statistic::getMightMagicRatio(const PlayerState * ps) +float Statistic::getObeliskVisitedRatio(const CGameState * gs, const TeamID & t) { - double numMight = 0; - - for(auto h : ps->heroes) - if(h->type->heroClass->affinity == CHeroClass::EClassAffinity::MIGHT) - numMight++; - - return numMight / ps->heroes.size(); + if(!gs->map->obeliskCount) + return 0; + return (float)getObeliskVisited(gs, t) / (float)gs->map->obeliskCount; } std::map Statistic::getNumMines(const CGameState * gs, const PlayerState * ps) diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 20387e774..c40c89e16 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -34,9 +34,8 @@ struct DLL_LINKAGE StatisticDataSetEntry int numberArtifacts; si64 armyStrength; int income; - double mapVisitedRatio; - int obeliskVisited; - double mightMagicRatio; + float mapExploredRatio; + float obeliskVisitedRatio; std::map numMines; int score; int maxHeroLevel; @@ -62,9 +61,8 @@ struct DLL_LINKAGE StatisticDataSetEntry h & numberArtifacts; h & armyStrength; h & income; - h & mapVisitedRatio; - h & obeliskVisited; - h & mightMagicRatio; + h & mapExploredRatio; + h & obeliskVisitedRatio; h & numMines; h & score; h & maxHeroLevel; @@ -118,16 +116,14 @@ class DLL_LINKAGE Statistic { static std::vector getMines(const CGameState * gs, const PlayerState * ps); public: - using TStat = std::pair; - static int getNumberOfArts(const PlayerState * ps); static si64 getArmyStrength(const PlayerState * ps, bool withTownGarrison = false); static int getIncome(const CGameState * gs, const PlayerState * ps); - static double getMapVisitedRatio(const CGameState * gs, PlayerColor player); + static float getMapExploredRatio(const CGameState * gs, PlayerColor player); static const CGHeroInstance * findBestHero(const CGameState * gs, const PlayerColor & color); - static std::vector> getRank(std::vector stats); + static std::vector> getRank(std::vector> stats); static int getObeliskVisited(const CGameState * gs, const TeamID & t); - static double getMightMagicRatio(const PlayerState * ps); + static float getObeliskVisitedRatio(const CGameState * gs, const TeamID & t); static std::map getNumMines(const CGameState * gs, const PlayerState * ps); }; From de128eb4719a17008a589c52eb5204561fc4b1c0 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:48:45 +0200 Subject: [PATCH 61/76] add dwellings & expirience --- lib/gameState/GameStatistics.cpp | 17 +++++++++++++++++ lib/gameState/GameStatistics.h | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 9f85c75d6..d4f84d83a 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -49,7 +49,9 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numberHeroes = ps->heroes.size(); data.numberTowns = gs->howManyTowns(ps->color); data.numberArtifacts = Statistic::getNumberOfArts(ps); + data.numberDwellings = gs->getPlayerState(ps->color)->dwellings.size(); data.armyStrength = Statistic::getArmyStrength(ps, true); + data.totalExperience = Statistic::getTotalExperience(ps); data.income = Statistic::getIncome(gs, ps); data.mapExploredRatio = Statistic::getMapExploredRatio(gs, ps->color); data.obeliskVisitedRatio = Statistic::getObeliskVisitedRatio(gs, ps->team); @@ -82,7 +84,9 @@ std::string StatisticDataSet::toCsv() ss << "NumberHeroes" << ";"; ss << "NumberTowns" << ";"; ss << "NumberArtifacts" << ";"; + ss << "NumberDwellings" << ";"; ss << "ArmyStrength" << ";"; + ss << "TotalExperience" << ";"; ss << "Income" << ";"; ss << "MapExploredRatio" << ";"; ss << "ObeliskVisitedRatio" << ";"; @@ -112,7 +116,9 @@ std::string StatisticDataSet::toCsv() ss << entry.numberHeroes << ";"; ss << entry.numberTowns << ";"; ss << entry.numberArtifacts << ";"; + ss << entry.numberDwellings << ";"; ss << entry.armyStrength << ";"; + ss << entry.totalExperience << ";"; ss << entry.income << ";"; ss << entry.mapExploredRatio << ";"; ss << entry.obeliskVisitedRatio << ";"; @@ -188,6 +194,17 @@ si64 Statistic::getArmyStrength(const PlayerState * ps, bool withTownGarrison) return str; } +// get total experience of all heroes +si64 Statistic::getTotalExperience(const PlayerState * ps) +{ + si64 tmp = 0; + + for(auto h : ps->heroes) + tmp += h->exp; + + return tmp; +} + // get total gold income int Statistic::getIncome(const CGameState * gs, const PlayerState * ps) { diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index c40c89e16..d8b7fdd60 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -32,7 +32,9 @@ struct DLL_LINKAGE StatisticDataSetEntry int numberHeroes; int numberTowns; int numberArtifacts; + int numberDwellings; si64 armyStrength; + si64 totalExperience; int income; float mapExploredRatio; float obeliskVisitedRatio; @@ -59,7 +61,9 @@ struct DLL_LINKAGE StatisticDataSetEntry h & numberHeroes; h & numberTowns; h & numberArtifacts; + h & numberDwellings; h & armyStrength; + h & totalExperience; h & income; h & mapExploredRatio; h & obeliskVisitedRatio; @@ -118,6 +122,7 @@ class DLL_LINKAGE Statistic public: static int getNumberOfArts(const PlayerState * ps); static si64 getArmyStrength(const PlayerState * ps, bool withTownGarrison = false); + static si64 getTotalExperience(const PlayerState * ps); static int getIncome(const CGameState * gs, const PlayerState * ps); static float getMapExploredRatio(const CGameState * gs, PlayerColor player); static const CGHeroInstance * findBestHero(const CGameState * gs, const PlayerColor & color); From eca04cbc5431e9981264422c592235b0f2ae7247 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:53:05 +0200 Subject: [PATCH 62/76] added 5 values --- lib/gameState/GameStatistics.cpp | 42 ++++++++++++++++++++++++++++++++ lib/gameState/GameStatistics.h | 16 +++++++++++- server/CGameHandler.cpp | 11 ++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index d4f84d83a..ec3ae5653 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -21,6 +21,8 @@ #include "../mapObjects/CGObjectInstance.h" #include "../mapObjects/MiscObjects.h" #include "../mapping/CMap.h" +#include "../entities/building/CBuilding.h" + VCMI_LIB_NAMESPACE_BEGIN @@ -55,6 +57,8 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.income = Statistic::getIncome(gs, ps); data.mapExploredRatio = Statistic::getMapExploredRatio(gs, ps->color); data.obeliskVisitedRatio = Statistic::getObeliskVisitedRatio(gs, ps->team); + data.townBuiltRatio = Statistic::getTownBuiltRatio(ps); + data.hasGrail = param.hasGrail; data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0; @@ -64,6 +68,9 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.count(ps->color) ? gs->statistic.values.numWinBattlesPlayer.at(ps->color) : 0; data.numHeroSurrendered = gs->statistic.values.numHeroSurrendered.count(ps->color) ? gs->statistic.values.numHeroSurrendered.at(ps->color) : 0; data.numHeroEscaped = gs->statistic.values.numHeroEscaped.count(ps->color) ? gs->statistic.values.numHeroEscaped.at(ps->color) : 0; + data.spentResourcesForArmy = gs->statistic.values.spentResourcesForArmy.count(ps->color) ? gs->statistic.values.spentResourcesForArmy.at(ps->color) : TResources(); + data.spentResourcesForBuildings = gs->statistic.values.spentResourcesForBuildings.count(ps->color) ? gs->statistic.values.spentResourcesForBuildings.at(ps->color) : TResources(); + data.tradeVolume = gs->statistic.values.tradeVolume.count(ps->color) ? gs->statistic.values.tradeVolume.at(ps->color) : TResources(); return data; } @@ -90,6 +97,8 @@ std::string StatisticDataSet::toCsv() ss << "Income" << ";"; ss << "MapExploredRatio" << ";"; ss << "ObeliskVisitedRatio" << ";"; + ss << "TownBuiltRatio" << ";"; + ss << "HasGrail" << ";"; ss << "Score" << ";"; ss << "MaxHeroLevel" << ";"; ss << "NumBattlesNeutral" << ";"; @@ -102,6 +111,12 @@ std::string StatisticDataSet::toCsv() ss << ";" << GameConstants::RESOURCE_NAMES[resource]; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "Mines"; + for(auto & resource : resources) + ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForArmy"; + for(auto & resource : resources) + ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForBuildings"; + for(auto & resource : resources) + ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "TradeVolume"; ss << "\r\n"; for(auto & entry : data) @@ -122,6 +137,8 @@ std::string StatisticDataSet::toCsv() ss << entry.income << ";"; ss << entry.mapExploredRatio << ";"; ss << entry.obeliskVisitedRatio << ";"; + ss << entry.townBuiltRatio << ";"; + ss << entry.hasGrail << ";"; ss << entry.score << ";"; ss << entry.maxHeroLevel << ";"; ss << entry.numBattlesNeutral << ";"; @@ -134,6 +151,12 @@ std::string StatisticDataSet::toCsv() ss << ";" << entry.resources[resource]; for(auto & resource : resources) ss << ";" << entry.numMines[resource]; + for(auto & resource : resources) + ss << ";" << entry.spentResourcesForArmy[resource]; + for(auto & resource : resources) + ss << ";" << entry.spentResourcesForBuildings[resource]; + for(auto & resource : resources) + ss << ";" << entry.tradeVolume[resource]; ss << "\r\n"; } @@ -335,4 +358,23 @@ std::map Statistic::getNumMines(const CGameState * gs, const Pl return tmp; } +float Statistic::getTownBuiltRatio(const PlayerState * ps) +{ + float built = 0.0; + float total = 0.0; + + for(const auto & t : ps->towns) + { + built += t->builtBuildings.size(); + for(const auto & b : t->town->buildings) + if(!t->forbiddenBuildings.count(b.first)) + total += 1; + } + + if(total < 1) + return 0; + + return built / total; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index d8b7fdd60..7734f7977 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -38,6 +38,8 @@ struct DLL_LINKAGE StatisticDataSetEntry int income; float mapExploredRatio; float obeliskVisitedRatio; + float townBuiltRatio; + bool hasGrail; std::map numMines; int score; int maxHeroLevel; @@ -47,6 +49,9 @@ struct DLL_LINKAGE StatisticDataSetEntry int numWinBattlesPlayer; int numHeroSurrendered; int numHeroEscaped; + TResources spentResourcesForArmy; + TResources spentResourcesForBuildings; + TResources tradeVolume; template void serialize(Handler &h) { @@ -67,6 +72,8 @@ struct DLL_LINKAGE StatisticDataSetEntry h & income; h & mapExploredRatio; h & obeliskVisitedRatio; + h & townBuiltRatio; + h & hasGrail; h & numMines; h & score; h & maxHeroLevel; @@ -76,6 +83,9 @@ struct DLL_LINKAGE StatisticDataSetEntry h & numWinBattlesPlayer; h & numHeroSurrendered; h & numHeroEscaped; + h & spentResourcesForArmy; + h & spentResourcesForBuildings; + h & tradeVolume; } }; @@ -88,7 +98,7 @@ public: static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs); std::string toCsv(); - struct ValueStorage + struct ValueStorage // holds some actual values needed for stats { std::map numBattlesNeutral; std::map numBattlesPlayer; @@ -96,6 +106,9 @@ public: std::map numWinBattlesPlayer; std::map numHeroSurrendered; std::map numHeroEscaped; + std::map spentResourcesForArmy; + std::map spentResourcesForBuildings; + std::map tradeVolume; template void serialize(Handler &h) { @@ -130,6 +143,7 @@ public: static int getObeliskVisited(const CGameState * gs, const TeamID & t); static float getObeliskVisitedRatio(const CGameState * gs, const TeamID & t); static std::map getNumMines(const CGameState * gs, const PlayerState * ps); + static float getTownBuiltRatio(const PlayerState * ps); }; VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 60f9e0ec4..1c0994af1 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2463,7 +2463,10 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, //Take cost if(!force) + { giveResources(t->tempOwner, -requestedBuilding->resources); + gs->statistic.values.spentResourcesForBuildings[t->tempOwner] += requestedBuilding->resources; + } //We know what has been built, apply changes. Do this as final step to properly update town window sendAndApply(&ns); @@ -2565,7 +2568,9 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst } //recruit - giveResources(army->tempOwner, -(c->getFullRecruitCost() * cram)); + TResources cost = (c->getFullRecruitCost() * cram); + giveResources(army->tempOwner, -cost); + gs->statistic.values.spentResourcesForArmy[army->tempOwner] += cost; SetAvailableCreatures sac; sac.tid = objid; @@ -2618,6 +2623,7 @@ bool CGameHandler::upgradeCreature(ObjectInstanceID objid, SlotID pos, CreatureI //take resources giveResources(player, -totalCost); + gs->statistic.values.spentResourcesForArmy[player] += totalCost; //upgrade creature changeStackType(StackLocation(obj, pos), upgID.toCreature()); @@ -3242,6 +3248,9 @@ bool CGameHandler::tradeResources(const IMarket *market, ui32 amountToSell, Play giveResource(player, toSell, -b1 * amountToBoy); giveResource(player, toBuy, b2 * amountToBoy); + gs->statistic.values.tradeVolume[player][toSell] += -b1 * amountToBoy; + gs->statistic.values.tradeVolume[player][toBuy] += b2 * amountToBoy; + return true; } From 611d5daa0f20969d4fb8880829b1c9df516bd5c4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:47:20 +0200 Subject: [PATCH 63/76] fix missing code + movement points used --- lib/gameState/GameStatistics.cpp | 7 +++++-- lib/gameState/GameStatistics.h | 7 +++++++ server/CGameHandler.cpp | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index ec3ae5653..b29e2cc1c 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -71,6 +71,7 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.spentResourcesForArmy = gs->statistic.values.spentResourcesForArmy.count(ps->color) ? gs->statistic.values.spentResourcesForArmy.at(ps->color) : TResources(); data.spentResourcesForBuildings = gs->statistic.values.spentResourcesForBuildings.count(ps->color) ? gs->statistic.values.spentResourcesForBuildings.at(ps->color) : TResources(); data.tradeVolume = gs->statistic.values.tradeVolume.count(ps->color) ? gs->statistic.values.tradeVolume.at(ps->color) : TResources(); + data.movementPointsUsed = gs->statistic.values.movementPointsUsed.count(ps->color) ? gs->statistic.values.movementPointsUsed.at(ps->color) : 0; return data; } @@ -106,7 +107,8 @@ std::string StatisticDataSet::toCsv() ss << "NumWinBattlesNeutral" << ";"; ss << "NumWinBattlesPlayer" << ";"; ss << "NumHeroSurrendered" << ";"; - ss << "NumHeroEscaped"; + ss << "NumHeroEscaped" << ";"; + ss << "MovementPointsUsed"; for(auto & resource : resources) ss << ";" << GameConstants::RESOURCE_NAMES[resource]; for(auto & resource : resources) @@ -146,7 +148,8 @@ std::string StatisticDataSet::toCsv() ss << entry.numWinBattlesNeutral << ";"; ss << entry.numWinBattlesPlayer << ";"; ss << entry.numHeroSurrendered << ";"; - ss << entry.numHeroEscaped; + ss << entry.numHeroEscaped << ";"; + ss << entry.movementPointsUsed; for(auto & resource : resources) ss << ";" << entry.resources[resource]; for(auto & resource : resources) diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 7734f7977..95e22527e 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -52,6 +52,7 @@ struct DLL_LINKAGE StatisticDataSetEntry TResources spentResourcesForArmy; TResources spentResourcesForBuildings; TResources tradeVolume; + si64 movementPointsUsed; template void serialize(Handler &h) { @@ -86,6 +87,7 @@ struct DLL_LINKAGE StatisticDataSetEntry h & spentResourcesForArmy; h & spentResourcesForBuildings; h & tradeVolume; + h & movementPointsUsed; } }; @@ -109,6 +111,7 @@ public: std::map spentResourcesForArmy; std::map spentResourcesForBuildings; std::map tradeVolume; + std::map movementPointsUsed; template void serialize(Handler &h) { @@ -118,6 +121,10 @@ public: h & numWinBattlesPlayer; h & numHeroSurrendered; h & numHeroEscaped; + h & spentResourcesForArmy; + h & spentResourcesForBuildings; + h & tradeVolume; + h & movementPointsUsed; } }; ValueStorage values; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1c0994af1..424df96a3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1351,6 +1351,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme turnTimerHandler->setEndTurnAllowed(h->getOwner(), !movingOntoWater && !movingOntoObstacle); doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); + gs->statistic.values.movementPointsUsed[asker] += tmh.movePoints; return true; } } From 500e9a864539e0dfaddbc1156f2a4b67f0399dd6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Aug 2024 09:10:22 +0000 Subject: [PATCH 64/76] Added downloads counter for 1.5.6 --- docs/Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Readme.md b/docs/Readme.md index 8ceb7a55d..b3122ef24 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,7 +1,7 @@ [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.4/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.4) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.4/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases) # VCMI Project From d36f90e6b645c9c71cd654ffbd888f962df85572 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Aug 2024 09:18:43 +0000 Subject: [PATCH 65/76] Fixed heading in 1.5.6 changelog --- ChangeLog.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2932b8fc4..df1aae550 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,28 +1,28 @@ # 1.5.5 -> 1.5.6 -# Stability +### Stability * Fixed possible crash on transferring hero to next campaign scenario if hero has combined artifact some components of which can be transferred * Fixed possible crash on transferring hero to next campaign scenario that has creature with faction limiter in his army * Fixed possible crash on application shutdown due to incorrect destruction order of UI entities -# Multiplayer +### Multiplayer * Mod compatibility issues when joining a lobby room now use color coding to make them less easy to miss. * Incompatible mods are now placed before compatible mods when joining lobby room. * Fixed text overflow in online lobby interface * Fixed jittering simultaneous turns slider after moving it twice over short period -# Interface +### Interface * Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild. * Game now correctly uses melee damage calculation when forcing a melee attack with a shooter. * Game will now close all open dialogs on start of our turn, to avoid bugs like locked right-click popups -# Map Objects +### Map Objects * Spells the hero can't learn are no longer hidden when received from a rewardable object, such as the Pandora Box * Spells that cannot be learned are now displayed with gray text in the name of the spell. * Configurable objects with scouted state such as Witch Hut in HotA now correctly show their reward on right click after vising them but refusing to accept reward * Right-click tooltip on map dwelling now always shows produced creatures. Player that owns the dwelling can also see number of creatures available for recruit -# Modding +### Modding * Fixed possible crash on invalid SPELL_LIKE_ATTACK bonus * Added compatibility check when loading maps with old names for boats From e8c32522142ab4338d3aaf07309d8c06555f9c05 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Aug 2024 13:14:28 +0000 Subject: [PATCH 66/76] Do not auto-remove dialogs such as new week --- client/CPlayerInterface.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ad44404ce..5db827d4b 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -193,11 +193,6 @@ void CPlayerInterface::closeAllDialogs() castleInt->close(); castleInt = nullptr; - - // remove all pending dialogs that do not expect query answer - vstd::erase_if(dialogs, [](const std::shared_ptr & window){ - return window->ID == QueryID::NONE; - }); } void CPlayerInterface::playerEndsTurn(PlayerColor player) @@ -1515,7 +1510,7 @@ void CPlayerInterface::update() return; //if there are any waiting dialogs, show them - if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy()) + if (makingTurn && !dialogs.empty() && !showingDialog->isBusy()) { showingDialog->setBusy(); GH.windows().pushWindow(dialogs.front()); From dde5cea601a9491673866c077061f602cc0710d0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Aug 2024 13:19:12 +0000 Subject: [PATCH 67/76] Fix activation of world view on end turn in multiplayer --- client/adventureMap/AdventureMapInterface.cpp | 2 +- client/adventureMap/AdventureMapShortcuts.cpp | 13 ++++++------- client/adventureMap/AdventureState.h | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 2a4dbe0be..381e26c86 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -374,7 +374,7 @@ void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID, bool isHuma mapAudio->onEnemyTurnStarted(); widget->getMinimap()->setAIRadar(!isHuman); widget->getInfoBar()->startEnemyTurn(playerID); - setState(isHuman ? EAdventureState::OTHER_HUMAN_PLAYER_TURN : EAdventureState::AI_PLAYER_TURN); + setState(isHuman ? EAdventureState::MAKING_TURN : EAdventureState::AI_PLAYER_TURN); } void AdventureMapInterface::setState(EAdventureState state) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 269183d0e..ee196e0b8 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -518,7 +518,7 @@ bool AdventureMapShortcuts::optionCanVisitObject() auto * hero = LOCPLINT->localState->getCurrentHero(); auto objects = LOCPLINT->cb->getVisitableObjs(hero->visitablePos()); - assert(vstd::contains(objects,hero)); + //assert(vstd::contains(objects,hero)); return objects.size() > 1; // there is object other than our hero } @@ -553,26 +553,25 @@ bool AdventureMapShortcuts::optionSpellcasting() bool AdventureMapShortcuts::optionInMapView() { - return state == EAdventureState::MAKING_TURN || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; + return state == EAdventureState::MAKING_TURN; } bool AdventureMapShortcuts::optionInWorldView() { - return state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; + return state == EAdventureState::WORLD_VIEW; } bool AdventureMapShortcuts::optionSidePanelActive() { -return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; +return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW; } bool AdventureMapShortcuts::optionMapScrollingActive() { - return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; + return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW; } bool AdventureMapShortcuts::optionMapViewActive() { - return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL - || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN; + return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL; } diff --git a/client/adventureMap/AdventureState.h b/client/adventureMap/AdventureState.h index 32bfc2e2d..6fb2ec6e3 100644 --- a/client/adventureMap/AdventureState.h +++ b/client/adventureMap/AdventureState.h @@ -15,7 +15,6 @@ enum class EAdventureState HOTSEAT_WAIT, MAKING_TURN, AI_PLAYER_TURN, - OTHER_HUMAN_PLAYER_TURN, CASTING_SPELL, WORLD_VIEW }; From edad8889157e4c291ea3405c06541d401b48b94e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Aug 2024 13:33:30 +0000 Subject: [PATCH 68/76] Fixed non-scrollable invite to private room widget --- ChangeLog.md | 1 + client/globalLobby/GlobalLobbyInviteWindow.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index df1aae550..3ec0620fe 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,7 @@ * Incompatible mods are now placed before compatible mods when joining lobby room. * Fixed text overflow in online lobby interface * Fixed jittering simultaneous turns slider after moving it twice over short period +* Fixed non-functioning slider in invite to game room dialog ### Interface * Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild. diff --git a/client/globalLobby/GlobalLobbyInviteWindow.cpp b/client/globalLobby/GlobalLobbyInviteWindow.cpp index ec436fea7..64a4e0026 100644 --- a/client/globalLobby/GlobalLobbyInviteWindow.cpp +++ b/client/globalLobby/GlobalLobbyInviteWindow.cpp @@ -94,7 +94,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow() }; listBackground = std::make_shared(Rect(8, 48, 220, 324), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1); - accountList = std::make_shared(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, 0, 0, 1 | 4, Rect(200, 0, 320, 320)); + accountList = std::make_shared(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, CSH->getGlobalLobby().getActiveAccounts().size(), 0, 1 | 4, Rect(200, 0, 320, 320)); buttonClose = std::make_shared(Point(86, 384), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); }, EShortcut::GLOBAL_RETURN ); From ae652f8e59cfd69a2cdd2581e670bda99e291bd2 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 4 Aug 2024 11:16:17 -0300 Subject: [PATCH 69/76] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index de4b67adf..9965d1201 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -72,6 +72,11 @@ "vcmi.lobby.noUnderground" : "sem subterrâneo", "vcmi.lobby.sortDate" : "Classifica mapas por data de alteração", "vcmi.lobby.backToLobby" : "Voltar para a sala de espera", + "vcmi.lobby.author" : "Autor", + "vcmi.lobby.handicap" : "Desvantagem", + "vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).", + "vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.", + "vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.", "vcmi.lobby.login.title" : "Sala de Espera Online do VCMI", "vcmi.lobby.login.username" : "Nome de usuário:", From 70f3397a35b118de4459aad0607cd6637d75bb31 Mon Sep 17 00:00:00 2001 From: K Date: Sun, 4 Aug 2024 16:49:53 +0200 Subject: [PATCH 70/76] fix crash when my city without hero is attacked --- client/battle/BattleWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 9bc8806cd..490400d18 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -218,7 +218,9 @@ void BattleWindow::showStickyQuickSpellWindow() Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"]; showStickyQuickSpellWindow->Bool() = true; - if(GH.screenDimensions().x >= 1050 && owner.getBattle()->battleGetMyHero()->hasSpellbook()) + auto hero = owner.getBattle()->battleGetMyHero(); + + if(GH.screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook()) { quickSpellWindow->enable(); quickSpellWindow->isEnabled = true; From 2813f5cccc2d3ed869b107f7690855954ae42f77 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 4 Aug 2024 12:13:11 -0300 Subject: [PATCH 71/76] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 9965d1201..5e1da5196 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -73,7 +73,7 @@ "vcmi.lobby.sortDate" : "Classifica mapas por data de alteração", "vcmi.lobby.backToLobby" : "Voltar para a sala de espera", "vcmi.lobby.author" : "Autor", - "vcmi.lobby.handicap" : "Desvantagem", + "vcmi.lobby.handicap" : "Desvant.", "vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).", "vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.", "vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.", From 78542ebe31f3849c67a307733134029bfc9bc231 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 5 Aug 2024 18:40:56 +0200 Subject: [PATCH 72/76] Fix github download counters --- docs/Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Readme.md b/docs/Readme.md index b3122ef24..074f07bf6 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,7 +1,7 @@ [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.4/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases) # VCMI Project From 0e287910754678ad378199ee935e490863de6b02 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:47:29 +0200 Subject: [PATCH 73/76] scroll only if in focus --- client/adventureMap/AdventureMapInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index e602365fe..647ca18f7 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -33,6 +33,7 @@ #include "../render/Canvas.h" #include "../render/IImage.h" #include "../render/IRenderHandler.h" +#include "../render/IScreenHandler.h" #include "../CMT.h" #include "../PlayerLocalState.h" #include "../CPlayerInterface.h" @@ -232,7 +233,7 @@ void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed) bool cursorInScrollArea = scrollDelta != Point(0,0); bool scrollingActive = cursorInScrollArea && shortcuts->optionMapScrollingActive() && !scrollingWasBlocked; - bool scrollingBlocked = GH.isKeyboardCtrlDown() || !settings["adventure"]["borderScroll"].Bool(); + bool scrollingBlocked = GH.isKeyboardCtrlDown() || !settings["adventure"]["borderScroll"].Bool() || !GH.screenHandler().hasFocus(); if (!scrollingWasActive && scrollingBlocked) { From e0ab760a2f9376f5dc960ded3d7be39a01032c5d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:26:22 +0200 Subject: [PATCH 74/76] code review --- lib/gameState/GameStatistics.cpp | 35 ++++++++---------------- lib/gameState/GameStatistics.h | 26 +++++++++--------- server/CGameHandler.cpp | 12 ++++---- server/battles/BattleResultProcessor.cpp | 16 +++++------ 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index b29e2cc1c..5daf0e075 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -62,16 +62,16 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons data.numMines = Statistic::getNumMines(gs, ps); data.score = scenarioHighScores.calculate().total; data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0; - data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.count(ps->color) ? gs->statistic.values.numBattlesNeutral.at(ps->color) : 0; - data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.count(ps->color) ? gs->statistic.values.numBattlesPlayer.at(ps->color) : 0; - data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.count(ps->color) ? gs->statistic.values.numWinBattlesNeutral.at(ps->color) : 0; - data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.count(ps->color) ? gs->statistic.values.numWinBattlesPlayer.at(ps->color) : 0; - data.numHeroSurrendered = gs->statistic.values.numHeroSurrendered.count(ps->color) ? gs->statistic.values.numHeroSurrendered.at(ps->color) : 0; - data.numHeroEscaped = gs->statistic.values.numHeroEscaped.count(ps->color) ? gs->statistic.values.numHeroEscaped.at(ps->color) : 0; - data.spentResourcesForArmy = gs->statistic.values.spentResourcesForArmy.count(ps->color) ? gs->statistic.values.spentResourcesForArmy.at(ps->color) : TResources(); - data.spentResourcesForBuildings = gs->statistic.values.spentResourcesForBuildings.count(ps->color) ? gs->statistic.values.spentResourcesForBuildings.at(ps->color) : TResources(); - data.tradeVolume = gs->statistic.values.tradeVolume.count(ps->color) ? gs->statistic.values.tradeVolume.at(ps->color) : TResources(); - data.movementPointsUsed = gs->statistic.values.movementPointsUsed.count(ps->color) ? gs->statistic.values.movementPointsUsed.at(ps->color) : 0; + data.numBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesNeutral : 0; + data.numBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesPlayer : 0; + data.numWinBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesNeutral : 0; + data.numWinBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesPlayer : 0; + data.numHeroSurrendered = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroSurrendered : 0; + data.numHeroEscaped = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroEscaped : 0; + data.spentResourcesForArmy = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources(); + data.spentResourcesForBuildings = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources(); + data.tradeVolume = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).tradeVolume : TResources(); + data.movementPointsUsed = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).movementPointsUsed : 0; return data; } @@ -236,31 +236,18 @@ int Statistic::getIncome(const CGameState * gs, const PlayerState * ps) { int percentIncome = gs->getStartInfo()->getIthPlayersSettings(ps->color).handicap.percentIncome; int totalIncome = 0; - const CGObjectInstance * heroOrTown = nullptr; //Heroes can produce gold as well - skill, specialty or arts for(const auto & h : ps->heroes) - { totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))) * percentIncome / 100; - if(!heroOrTown) - heroOrTown = h; - } - //Add town income of all towns for(const auto & t : ps->towns) - { totalIncome += t->dailyIncome()[EGameResID::GOLD]; - if(!heroOrTown) - heroOrTown = t; - } - for(const CGMine * mine : getMines(gs, ps)) - { - if (mine->producedResource == EGameResID::GOLD) + if(mine->producedResource == EGameResID::GOLD) totalIncome += mine->getProducedQuantity(); - } return totalIncome; } diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 95e22527e..daec71e4a 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -100,18 +100,18 @@ public: static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs); std::string toCsv(); - struct ValueStorage // holds some actual values needed for stats + struct PlayerAccumulatedValueStorage // holds some actual values needed for stats { - std::map numBattlesNeutral; - std::map numBattlesPlayer; - std::map numWinBattlesNeutral; - std::map numWinBattlesPlayer; - std::map numHeroSurrendered; - std::map numHeroEscaped; - std::map spentResourcesForArmy; - std::map spentResourcesForBuildings; - std::map tradeVolume; - std::map movementPointsUsed; + int numBattlesNeutral; + int numBattlesPlayer; + int numWinBattlesNeutral; + int numWinBattlesPlayer; + int numHeroSurrendered; + int numHeroEscaped; + TResources spentResourcesForArmy; + TResources spentResourcesForBuildings; + TResources tradeVolume; + si64 movementPointsUsed; template void serialize(Handler &h) { @@ -127,12 +127,12 @@ public: h & movementPointsUsed; } }; - ValueStorage values; + std::map accumulatedValues; template void serialize(Handler &h) { h & data; - h & values; + h & accumulatedValues; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 424df96a3..62407d2a4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1351,7 +1351,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme turnTimerHandler->setEndTurnAllowed(h->getOwner(), !movingOntoWater && !movingOntoObstacle); doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); - gs->statistic.values.movementPointsUsed[asker] += tmh.movePoints; + gs->statistic.accumulatedValues[asker].movementPointsUsed += tmh.movePoints; return true; } } @@ -2466,7 +2466,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, if(!force) { giveResources(t->tempOwner, -requestedBuilding->resources); - gs->statistic.values.spentResourcesForBuildings[t->tempOwner] += requestedBuilding->resources; + gs->statistic.accumulatedValues[t->tempOwner].spentResourcesForBuildings += requestedBuilding->resources; } //We know what has been built, apply changes. Do this as final step to properly update town window @@ -2571,7 +2571,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst //recruit TResources cost = (c->getFullRecruitCost() * cram); giveResources(army->tempOwner, -cost); - gs->statistic.values.spentResourcesForArmy[army->tempOwner] += cost; + gs->statistic.accumulatedValues[army->tempOwner].spentResourcesForArmy += cost; SetAvailableCreatures sac; sac.tid = objid; @@ -2624,7 +2624,7 @@ bool CGameHandler::upgradeCreature(ObjectInstanceID objid, SlotID pos, CreatureI //take resources giveResources(player, -totalCost); - gs->statistic.values.spentResourcesForArmy[player] += totalCost; + gs->statistic.accumulatedValues[player].spentResourcesForArmy += totalCost; //upgrade creature changeStackType(StackLocation(obj, pos), upgID.toCreature()); @@ -3249,8 +3249,8 @@ bool CGameHandler::tradeResources(const IMarket *market, ui32 amountToSell, Play giveResource(player, toSell, -b1 * amountToBoy); giveResource(player, toBuy, b2 * amountToBoy); - gs->statistic.values.tradeVolume[player][toSell] += -b1 * amountToBoy; - gs->statistic.values.tradeVolume[player][toBuy] += b2 * amountToBoy; + gs->statistic.accumulatedValues[player].tradeVolume[toSell] += -b1 * amountToBoy; + gs->statistic.accumulatedValues[player].tradeVolume[toBuy] += b2 * amountToBoy; return true; } diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index c8c8dce7d..715075899 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -500,17 +500,17 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) // add statistic if(battle.sideToPlayer(0) == PlayerColor::NEUTRAL || battle.sideToPlayer(1) == PlayerColor::NEUTRAL) { - gameHandler->gameState()->statistic.values.numBattlesNeutral[battle.sideToPlayer(0)]++; - gameHandler->gameState()->statistic.values.numBattlesNeutral[battle.sideToPlayer(1)]++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(0)].numBattlesNeutral++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(1)].numBattlesNeutral++; if(!finishingBattle->isDraw()) - gameHandler->gameState()->statistic.values.numWinBattlesNeutral[battle.sideToPlayer(finishingBattle->winnerSide)]++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesNeutral++; } else { - gameHandler->gameState()->statistic.values.numBattlesPlayer[battle.sideToPlayer(0)]++; - gameHandler->gameState()->statistic.values.numBattlesPlayer[battle.sideToPlayer(1)]++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(0)].numBattlesPlayer++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(1)].numBattlesPlayer++; if(!finishingBattle->isDraw()) - gameHandler->gameState()->statistic.values.numWinBattlesPlayer[battle.sideToPlayer(finishingBattle->winnerSide)]++; + gameHandler->gameState()->statistic.accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesPlayer++; } BattleResultAccepted raccepted; @@ -573,13 +573,13 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const if (result.result == EBattleResult::SURRENDER) { - gameHandler->gameState()->statistic.values.numHeroSurrendered[finishingBattle->loser]++; + gameHandler->gameState()->statistic.accumulatedValues[finishingBattle->loser].numHeroSurrendered++; gameHandler->heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero); } if (result.result == EBattleResult::ESCAPE) { - gameHandler->gameState()->statistic.values.numHeroEscaped[finishingBattle->loser]++; + gameHandler->gameState()->statistic.accumulatedValues[finishingBattle->loser].numHeroEscaped++; gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero); } From 5903b3b42cbbb16a465715b33e5a27413076162d Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:57:06 +0300 Subject: [PATCH 75/76] endBattleConfirm crash fix --- server/battles/BattleResultProcessor.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 122ec9a7b..7d68d9ca4 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -447,16 +447,16 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) addArtifactToTransfer(packCommander, artSlot.first, artSlot.second.getArt()); sendArtifacts(packCommander); } - } - auto armyObj = battle.battleGetArmyObject(battle.otherSide(battleResult->winner)); - for(const auto & armySlot : armyObj->stacks) - { - BulkMoveArtifacts packsArmy(finishingBattle->winnerHero->getOwner(), finishingBattle->loserHero->id, finishingBattle->winnerHero->id, false); - packsArmy.srcArtHolder = armyObj->id; - packsArmy.srcCreature = armySlot.first; - for(const auto & artSlot : armySlot.second->artifactsWorn) - addArtifactToTransfer(packsArmy, artSlot.first, armySlot.second->getArt(artSlot.first)); - sendArtifacts(packsArmy); + auto armyObj = battle.battleGetArmyObject(battle.otherSide(battleResult->winner)); + for(const auto & armySlot : armyObj->stacks) + { + BulkMoveArtifacts packsArmy(finishingBattle->winnerHero->getOwner(), finishingBattle->loserHero->id, finishingBattle->winnerHero->id, false); + packsArmy.srcArtHolder = armyObj->id; + packsArmy.srcCreature = armySlot.first; + for(const auto & artSlot : armySlot.second->artifactsWorn) + addArtifactToTransfer(packsArmy, artSlot.first, armySlot.second->getArt(artSlot.first)); + sendArtifacts(packsArmy); + } } // Display loot if(!arts.empty()) From ffaaa7da15b42b059258ee9cd9c23b714cfc4ecc Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 9 Aug 2024 00:28:28 +0200 Subject: [PATCH 76/76] blockingDialog functions: the `answer` parameter can be negative --- lib/mapObjects/CBank.cpp | 2 +- lib/mapObjects/CBank.h | 2 +- lib/mapObjects/CGCreature.cpp | 2 +- lib/mapObjects/CGCreature.h | 2 +- lib/mapObjects/CGDwelling.cpp | 2 +- lib/mapObjects/CGDwelling.h | 2 +- lib/mapObjects/CGPandoraBox.cpp | 2 +- lib/mapObjects/CGPandoraBox.h | 2 +- lib/mapObjects/CGTownBuilding.cpp | 2 +- lib/mapObjects/CGTownBuilding.h | 2 +- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/CGTownInstance.h | 2 +- lib/mapObjects/CQuest.cpp | 4 ++-- lib/mapObjects/CQuest.h | 4 ++-- lib/mapObjects/CRewardableObject.cpp | 2 +- lib/mapObjects/CRewardableObject.h | 2 +- lib/mapObjects/IObjectInterface.cpp | 2 +- lib/mapObjects/IObjectInterface.h | 2 +- lib/mapObjects/MiscObjects.cpp | 6 +++--- lib/mapObjects/MiscObjects.h | 6 +++--- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 8ac6fec76..552237825 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -393,7 +393,7 @@ void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &resul } } -void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CBank::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if (answer) { diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h index d2fe226f1..b61563421 100644 --- a/lib/mapObjects/CBank.h +++ b/lib/mapObjects/CBank.h @@ -40,7 +40,7 @@ public: bool isCoastVisitable() const override; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; std::vector getPopupComponents(PlayerColor player) const override; diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index 94ad2ad84..efba48004 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -523,7 +523,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & } } -void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { auto action = takenAction(hero); if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index 588e26d0f..2b88d367d 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -48,7 +48,7 @@ public: void pickRandomObject(vstd::RNG & rand) override; void newTurn(vstd::RNG & rand) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; CreatureID getCreature() const; //stack formation depends on position, diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index a85d412c0..37a717960 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -516,7 +516,7 @@ void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult & } } -void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present diff --git a/lib/mapObjects/CGDwelling.h b/lib/mapObjects/CGDwelling.h index 4afa3907e..feb4b9bc5 100644 --- a/lib/mapObjects/CGDwelling.h +++ b/lib/mapObjects/CGDwelling.h @@ -54,7 +54,7 @@ private: void newTurn(vstd::RNG & rand) const override; void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; std::vector getPopupComponents(PlayerColor player) const override; void updateGuards() const; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index faa49360b..c9ede1335 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -186,7 +186,7 @@ void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult } } -void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) { diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index a1e5e8394..7277a792d 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -26,7 +26,7 @@ public: void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; template void serialize(Handler &h) { diff --git a/lib/mapObjects/CGTownBuilding.cpp b/lib/mapObjects/CGTownBuilding.cpp index 367641c1f..3d84843ea 100644 --- a/lib/mapObjects/CGTownBuilding.cpp +++ b/lib/mapObjects/CGTownBuilding.cpp @@ -385,7 +385,7 @@ void CTownRewardableBuilding::heroLevelUpDone(const CGHeroInstance *hero) const grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero); } -void CTownRewardableBuilding::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CTownRewardableBuilding::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer == 0) return; // player refused diff --git a/lib/mapObjects/CGTownBuilding.h b/lib/mapObjects/CGTownBuilding.h index 9ddf4e616..b8617d784 100644 --- a/lib/mapObjects/CGTownBuilding.h +++ b/lib/mapObjects/CGTownBuilding.h @@ -133,7 +133,7 @@ public: void initObj(vstd::RNG & rand) override; /// applies player selection of reward - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * town, vstd::RNG & rand); CTownRewardableBuilding(IGameCallback *cb); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 6f646bf87..6ef9f9eda 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -281,7 +281,7 @@ void CGTownInstance::setOwner(const PlayerColor & player) const cb->setOwner(this, player); } -void CGTownInstance::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGTownInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { for (auto building : bonusingBuildings) building->blockingDialogAnswered(hero, answer); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 57b8c7d26..4804a02a9 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -223,7 +223,7 @@ public: protected: void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; void serializeJsonOptions(JsonSerializeFormat & handler) override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; private: FactionID randomizeFaction(vstd::RNG & rand); diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 98179cd05..ca06e4d49 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -660,7 +660,7 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const return dynamic_cast(o); } -void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { CRewardableObject::blockingDialogAnswered(hero, answer); if(answer) @@ -865,7 +865,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const } } -void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if (answer) cb->removeObject(this, hero->getOwner()); diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 281a087ba..407c46134 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -150,7 +150,7 @@ public: std::vector getPopupComponents(const CGHeroInstance * hero) const override; void newTurn(vstd::RNG & rand) const override; void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; void getVisitText (MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override; virtual void init(vstd::RNG & rand); @@ -229,7 +229,7 @@ public: void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; void getVisitText (MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override; void getRolloverText (MetaString &text, bool onHover) const; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index b9f122dc4..4fedb7f09 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -181,7 +181,7 @@ void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), this, hero); } -void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer == 0) { diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index a3fb7c2e2..9e1735a5f 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -63,7 +63,7 @@ public: void heroLevelUpDone(const CGHeroInstance *hero) const override; /// applies player selection of reward - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; void initObj(vstd::RNG & rand) override; diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index b3101155d..11d9e294c 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -68,7 +68,7 @@ void IObjectInterface::preInit() void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const {} -void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const {} void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 82a798e72..c528dbc37 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -61,7 +61,7 @@ public: //Called when queries created DURING HERO VISIT are resolved //First parameter is always hero that visited object and triggered the query virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const; - virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; + virtual void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const; virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; virtual void heroLevelUpDone(const CGHeroInstance *hero) const; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 33e96cbd7..95059c339 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -215,7 +215,7 @@ void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &resu } } -void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) cb->startBattleI(hero, this); @@ -348,7 +348,7 @@ void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult & collectRes(hero->getOwner()); } -void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) cb->startBattleI(hero, this); @@ -915,7 +915,7 @@ void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult & pick(hero); } -void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) cb->startBattleI(hero, this); diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 4f15210fc..542894a2f 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -91,7 +91,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; std::string getObjectName() const override; std::string getPopupText(PlayerColor player) const override; @@ -132,7 +132,7 @@ public: void initObj(vstd::RNG & rand) override; void pickRandomObject(vstd::RNG & rand) override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; std::string getHoverText(PlayerColor player) const override; void collectRes(const PlayerColor & player) const; @@ -163,7 +163,7 @@ private: void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; void flagMine(const PlayerColor & player) const; void newTurn(vstd::RNG & rand) const override;