From c2dc88bf06315e5e40cb97637bd8f1e4a58f5942 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Sep 2024 10:32:29 +0000 Subject: [PATCH 01/19] Fix refusing a reward, for example from Tree of Knowledge --- lib/mapObjects/CRewardableObject.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 19576e054..90c02aacb 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -246,6 +246,9 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int3 } else { + if (answer == 0) + return; //Player refused + if(answer > 0 && answer - 1 < configuration.info.size()) { auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT); From 1d7a89c79b0f4097b3ed6d786d14b55cab365804 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Sep 2024 10:36:31 +0000 Subject: [PATCH 02/19] Only visiting heroes can now activate manual rewardable buildings - This is more clear - in case if building generates queries - Avoids some edge cases, like what to do if building is single-use and town has 2 heroes - In line with hota version of this feature --- client/windows/CCastleInterface.cpp | 13 ++++++++++++- .../modders/Entities_Format/Town_Building_Format.md | 1 + server/CGameHandler.cpp | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 53214e050..418c5ffe4 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -845,7 +845,18 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu void CCastleBuildings::enterRewardable(BuildingID building) { - LOCPLINT->cb->visitTownBuilding(town, building); + if (town->visitingHero == nullptr) + { + MetaString message; + message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s + message.replaceTextID(town->town->buildings.at(building)->getNameTextID()); + + LOCPLINT->showInfoDialog(message.toString()); + } + else + { + LOCPLINT->cb->visitTownBuilding(town, building); + } } void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID) diff --git a/docs/modders/Entities_Format/Town_Building_Format.md b/docs/modders/Entities_Format/Town_Building_Format.md index 9cc14dd7d..67079fda4 100644 --- a/docs/modders/Entities_Format/Town_Building_Format.md +++ b/docs/modders/Entities_Format/Town_Building_Format.md @@ -197,6 +197,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config "bonuses" : [ BONUS_FORMAT ] // If set to true, this building will not automatically activate on new day or on entering town and needs to be activated manually on click + // Note that such building can only be activated by visiting hero, and not by garrisoned hero. "manualHeroVisit" : false, // Bonuses provided by this special building if this building or any of its upgrades are constructed in town diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1a97417fa..e450d698d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2175,7 +2175,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) if (t->rewardableBuildings.count(bid)) { - auto & hero = t->garrisonHero ? t->garrisonHero : t->visitingHero; + auto & hero = t->visitingHero; auto * building = t->rewardableBuildings.at(bid); if (hero && t->town->buildings.at(bid)->manualHeroVisit) From cbac4d7ea7a17e0e40c43890df42a33e01bce25e Mon Sep 17 00:00:00 2001 From: Simeon Manolov Date: Sun, 15 Sep 2024 17:37:11 +0300 Subject: [PATCH 03/19] fix error during ios build `onetbb/2021.10.0` adds a transitive dependency for `hwloc/2.9.1` The problem is that `hwloc` does not support mobile OS. ``` checking which OS support to include... Unsupported! (aarch64-apple-ios) configure: WARNING: *********************************************************** configure: WARNING: *** hwloc does not support this system. configure: WARNING: *** hwloc will *attempt* to build (but it may not work). configure: WARNING: *** hwloc run-time results may be reduced to showing just one processor, configure: WARNING: *** and binding will not be supported. configure: WARNING: *** You have been warned. configure: WARNING: *** Pausing to give you time to read this message... configure: WARNING: *********************************************************** checking which CPU support to include... unknown ``` ...which eventually leads to: ``` ninja: error: '/Users/simo/.conan/data/hwloc/2.9.3/_/_/package/81c684be1f19b5b5aefe294dd826377be1235f07/lib/libhwloc.so', needed by 'appleclang_15.0_cxx17_64_release/libtbbbind_2_5.3.12.dylib', missing and no known rule to make it onetbb/2021.12.0: onetbb/2021.12.0: ERROR: Package '9fcc0a90f088a5fd6ea9bf93b3534713b5635296' build failed ``` --- conanfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index fa140a728..c9c5df23d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -19,7 +19,7 @@ class VCMI(ConanFile): "sdl_image/[~2.0.5]", "sdl_mixer/[~2.0.4]", "sdl_ttf/[~2.0.18]", - "onetbb/[^2021.3]", + "onetbb/[^2021.7 <2021.10]", # 2021.10+ breaks mobile builds due to added hwloc dependency "xz_utils/[>=5.2.5]", # Required for innoextract ] @@ -39,7 +39,6 @@ class VCMI(ConanFile): "boost/*:shared": True, "minizip/*:shared": True, - "onetbb/*:shared": True, } def configure(self): From e09fbe5ea4e702d91968a2f1b376c3c29e2a8c2d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Sep 2024 20:14:08 +0000 Subject: [PATCH 04/19] Implement detection of typos in json using Levenshtein Distance --- lib/json/JsonValidator.cpp | 82 +++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 8a1de9137..0114f113d 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -21,6 +21,80 @@ VCMI_LIB_NAMESPACE_BEGIN +// Algorithm for detection of typos in words +// Determines how 'different' two strings are - how many changes must be done to turn one string into another one +// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows +static int getLevenshteinDistance(const std::string & s, const std::string & t) +{ + int n = t.size(); + int m = s.size(); + + // create two work vectors of integer distances + std::vector v0(n+1, 0); + std::vector v1(n+1, 0); + + // initialize v0 (the previous row of distances) + // this row is A[0][i]: edit distance from an empty s to t; + // that distance is the number of characters to append to s to make t. + for (int i = 0; i < n; ++i) + v0[i] = i; + + for (int i = 0; i < m; ++i) + { + // calculate v1 (current row distances) from the previous row v0 + + // first element of v1 is A[i + 1][0] + // edit distance is delete (i + 1) chars from s to match empty t + v1[0] = i + 1; + + // use formula to fill in the rest of the row + for (int j = 0; j < n; ++j) + { + // calculating costs for A[i + 1][j + 1] + int deletionCost = v0[j + 1] + 1; + int insertionCost = v1[j] + 1; + int substitutionCost; + + if (s[i] == t[j]) + substitutionCost = v0[j]; + else + substitutionCost = v0[j] + 1; + + v1[j + 1] = std::min({deletionCost, insertionCost, substitutionCost}); + } + + // copy v1 (current row) to v0 (previous row) for next iteration + // since data in v1 is always invalidated, a swap without copy could be more efficient + std::swap(v0, v1); + } + + // after the last swap, the results of v1 are now in v0 + return v0[n]; +} + +/// Searches for keys similar to 'target' in 'candidates' map +/// Returns closest match or empty string if no suitable candidates are found +static std::string findClosestMatch(JsonMap candidates, std::string target) +{ + // Maximum distance at which we can consider strings to be similar + // If strings have more different symbols than this number then it is not a typo, but a completely different word + static constexpr int maxDistance = 5; + int bestDistance = maxDistance; + std::string bestMatch; + + for (auto const & candidate : candidates) + { + int newDistance = getLevenshteinDistance(candidate.first, target); + + if (newDistance < bestDistance) + { + bestDistance = newDistance; + bestMatch = candidate.first; + } + } + return bestMatch; +} + static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { // check is not needed - e.g. incorporated into another check @@ -417,7 +491,13 @@ static std::string additionalPropertiesCheck(JsonValidator & validator, const Js // or, additionalItems field can be bool which indicates if such items are allowed else if(!schema.isNull() && !schema.Bool()) // present and set to false - error - errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); + { + std::string bestCandidate = findClosestMatch(baseSchema["properties"].Struct(), entry.first); + if (!bestCandidate.empty()) + errors += validator.makeErrorMessage("Unknown entry found: '" + entry.first + "'. Perhaps you meant '" + bestCandidate + "'?"); + else + errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); + } } } return errors; From 86c43565c0e8e10b80d4ee24838b1c8208dfce70 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 15 Sep 2024 19:32:12 -0300 Subject: [PATCH 05/19] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 7bce949ba..8206499d7 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -163,8 +163,8 @@ "vcmi.systemOptions.townsGroup" : "Tela da Cidade", "vcmi.statisticWindow.statistics" : "Estatísticas", - "vcmi.statisticWindow.tsvCopy" : "Copiar dados", - "vcmi.statisticWindow.selectView" : "Selecionar visualização", + "vcmi.statisticWindow.tsvCopy" : "Para a área de transf.", + "vcmi.statisticWindow.selectView" : "Selec. visualização", "vcmi.statisticWindow.value" : "Valor", "vcmi.statisticWindow.title.overview" : "Visão geral", "vcmi.statisticWindow.title.resources" : "Recursos", @@ -178,7 +178,7 @@ "vcmi.statisticWindow.title.experience" : "Experiência", "vcmi.statisticWindow.title.resourcesSpentArmy" : "Custo do exército", "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Custo de construção", - "vcmi.statisticWindow.title.mapExplored" : "Exploração do mapa", + "vcmi.statisticWindow.title.mapExplored" : "Mapa explorado", "vcmi.statisticWindow.param.playerName" : "Nome do jogador", "vcmi.statisticWindow.param.daysSurvived" : "Dias sobrevividos", "vcmi.statisticWindow.param.maxHeroLevel" : "Nível máximo do herói", @@ -325,8 +325,8 @@ "vcmi.townHall.missingBase" : "A construção base %s deve ser construída primeiro", "vcmi.townHall.noCreaturesToRecruit" : "Não há criaturas para recrutar!", - "vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode tomar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"", - "vcmi.townStructure.bank.payBack" : "Você entra no banco. Um banqueiro o vê e diz: \"Você já pegou um empréstimo. Pague-o antes de tomar um novo.\"", + "vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode pegar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"", + "vcmi.townStructure.bank.payBack" : "Você entra no banco. Um banqueiro o vê e diz: \"Você já pegou um empréstimo. Pague-o antes de pegar um novo.\"", "vcmi.logicalExpressions.anyOf" : "Qualquer um dos seguintes:", "vcmi.logicalExpressions.allOf" : "Todos os seguintes:", @@ -660,5 +660,7 @@ "core.bonus.WATER_IMMUNITY.name" : "Imunidade à Água", "core.bonus.WATER_IMMUNITY.description" : "Imune a todos os feitiços da escola de magia da Água", "core.bonus.WIDE_BREATH.name" : "Sopro Amplo", - "core.bonus.WIDE_BREATH.description" : "Ataque de sopro amplo (vários hexágonos)" + "core.bonus.WIDE_BREATH.description" : "Ataque de sopro amplo (vários hexágonos)", + "core.bonus.DISINTEGRATE.name": "Desintegrar", + "core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte" } From 9c36aabd26e62e991ce824ecce8009ee8d5f1984 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 15 Sep 2024 20:01:05 -0300 Subject: [PATCH 06/19] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 8206499d7..dceda7538 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -3,9 +3,9 @@ "vcmi.adventureMap.monsterThreat.levels.0" : "Sem Esforço", "vcmi.adventureMap.monsterThreat.levels.1" : "Muito Fraca", "vcmi.adventureMap.monsterThreat.levels.2" : "Fraca", - "vcmi.adventureMap.monsterThreat.levels.3" : "Um pouco mais fraca", + "vcmi.adventureMap.monsterThreat.levels.3" : "Um Pouco Mais Fraca", "vcmi.adventureMap.monsterThreat.levels.4" : "Igual", - "vcmi.adventureMap.monsterThreat.levels.5" : "Um pouco mais forte", + "vcmi.adventureMap.monsterThreat.levels.5" : "Um Pouco Mais Forte", "vcmi.adventureMap.monsterThreat.levels.6" : "Forte", "vcmi.adventureMap.monsterThreat.levels.7" : "Muito Forte", "vcmi.adventureMap.monsterThreat.levels.8" : "Desafiante", From 34e1a6c198553d80f0142120058318e836d8cf51 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 15 Sep 2024 20:11:31 -0300 Subject: [PATCH 07/19] Map Editor --- mapeditor/translation/portuguese.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index 499c0a730..1644feaf7 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -67,22 +67,22 @@ Author - + Autor Author contact (e.g. email) - + Contato do autor (ex.: e-mail) Map Creation Time - + Data de Criação do Mapa Map Version - + Versão do Mapa @@ -947,17 +947,17 @@ Can't place object - Não é possível colocar objeto + Não é possível colocar objeto There can only be one grail object on the map. - + Pode haver apenas um objeto graal no mapa. Hero %1 cannot be created as NEUTRAL. - + O herói %1 não pode ser criado como NEUTRO. @@ -1762,17 +1762,17 @@ Spell scroll %1 doesn't have instance assigned and must be removed - + O pergaminho de feitiço %1 não tem uma instância atribuída e deve ser removido Artifact %1 is prohibited by map settings - + O artefato %1 é proibido pelas configurações do mapa Player %1 has no towns and heroes assigned - + O jogador %1 não tem cidades e heróis atribuídos From 152f1e0a65ed5b9ff02a2bd391388d2cead05aeb Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Mon, 16 Sep 2024 13:31:17 +0300 Subject: [PATCH 08/19] Add editor forms to target_sources, so they become visible in Qt Creator's project tree --- mapeditor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 4fc4679e6..dc69ed286 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -196,6 +196,7 @@ endif() target_sources(vcmieditor PRIVATE ${editor_SRCS} ${editor_HEADERS} + ${editor_FORMS} ${editor_RESOURCES} ) From 28fea2e552f64741b92ae5617b286fca4ca67cf8 Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Mon, 16 Sep 2024 17:35:42 +0300 Subject: [PATCH 09/19] Map editor: Add road types to RMG options --- mapeditor/jsonutils.cpp | 5 +- mapeditor/windownewmap.cpp | 8 ++ mapeditor/windownewmap.ui | 175 ++++++++++++++++++++++++++++--------- 3 files changed, 147 insertions(+), 41 deletions(-) diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index 5ab2335af..45a20e51d 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -108,10 +108,11 @@ JsonNode toJson(QVariant object) if(object.canConvert()) ret.Struct() = VariantToMap(object.toMap()); - else if(object.canConvert()) - ret.Vector() = VariantToList(object.toList()); else if(object.userType() == QMetaType::QString) ret.String() = object.toString().toUtf8().data(); + //QStirng can be converted to QVariantList, need to check first + else if(object.canConvert()) + ret.Vector() = VariantToList(object.toList()); else if(object.userType() == QMetaType::Bool) ret.Bool() = object.toBool(); else if(object.canConvert()) diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 219a2da9c..f77b6e2f7 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -150,6 +150,10 @@ bool WindowNewMap::loadUserSettings() ui->monsterOpt4->setChecked(true); break; } + ui->roadDirt->setChecked(mapGenOptions.isRoadEnabled(Road::DIRT_ROAD)); + ui->roadGravel->setChecked(mapGenOptions.isRoadEnabled(Road::GRAVEL_ROAD)); + ui->roadCobblestone->setChecked(mapGenOptions.isRoadEnabled(Road::COBBLESTONE_ROAD)); + ret = true; } @@ -236,6 +240,10 @@ void WindowNewMap::on_okButton_clicked() mapGenOptions.setWaterContent(water); mapGenOptions.setMonsterStrength(monster); + + mapGenOptions.setRoadEnabled(Road::DIRT_ROAD, ui->roadDirt->isChecked()); + mapGenOptions.setRoadEnabled(Road::GRAVEL_ROAD, ui->roadGravel->isChecked()); + mapGenOptions.setRoadEnabled(Road::COBBLESTONE_ROAD, ui->roadCobblestone->isChecked()); saveUserSettings(); diff --git a/mapeditor/windownewmap.ui b/mapeditor/windownewmap.ui index 3b97e1a44..e4121de3c 100644 --- a/mapeditor/windownewmap.ui +++ b/mapeditor/windownewmap.ui @@ -7,7 +7,7 @@ 0 0 444 - 445 + 506 @@ -52,7 +52,7 @@ 0 20 281 - 68 + 73 @@ -72,7 +72,7 @@ - Qt::ImhDigitsOnly + Qt::InputMethodHint::ImhDigitsOnly 36 @@ -98,7 +98,7 @@ - Qt::ImhDigitsOnly + Qt::InputMethodHint::ImhDigitsOnly 36 @@ -132,10 +132,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -207,7 +207,7 @@ 10 140 431 - 301 + 361 @@ -237,7 +237,7 @@ 10 20 391 - 68 + 72 @@ -546,7 +546,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -675,7 +675,104 @@ - Qt::Horizontal + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 10 + 230 + 411 + 51 + + + + + 0 + 0 + + + + + 480 + 96 + + + + Roads + + + + + 0 + 20 + 411 + 26 + + + + + + + Dirt + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Gravel + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Cobblestone + + + + + + + Qt::Orientation::Horizontal @@ -692,9 +789,9 @@ 10 - 230 + 280 411 - 32 + 34 @@ -732,37 +829,37 @@ - - - false - + - 280 - 270 - 131 - 21 + 80 + 320 + 283 + 33 - - Qt::ImhDigitsOnly - - - 0 - - - - - - 110 - 270 - 161 - 20 - - - - Custom seed - + + + + + Custom seed + + + + + + + false + + + Qt::InputMethodHint::ImhDigitsOnly + + + 0 + + + + From 8917c753bd20d470fe07092d6c2719a4fe9effec Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 16 Sep 2024 20:16:27 +0200 Subject: [PATCH 10/19] adjust town event handling after introducing 8th creature in town --- mapeditor/inspector/towneventdialog.cpp | 9 +++++++-- server/processors/NewTurnProcessor.cpp | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 845f774c6..73e85ac09 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -155,7 +155,12 @@ void TownEventDialog::initCreatures() { auto creatures = params.value("creatures").toList(); auto * ctown = town.town; - for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i) + if (!ctown) + ui->creaturesTable->setRowCount(GameConstants::CREATURES_PER_TOWN); + else + ui->creaturesTable->setRowCount(ctown->creatures.size()); + + for (int i = 0; i < ui->creaturesTable->rowCount(); ++i) { QString creatureNames; if (!ctown) @@ -239,7 +244,7 @@ QVariantList TownEventDialog::buildingsToVariant() QVariantList TownEventDialog::creaturesToVariant() { QVariantList creaturesList; - for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i) + for (int i = 0; i < ui->creaturesTable->rowCount(); ++i) { auto * item = static_cast(ui->creaturesTable->cellWidget(i, 1)); creaturesList.push_back(item->value()); diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index 48c44edf6..4107720d5 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -110,7 +110,7 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town) for (si32 i=0;icreatures.at(i).second.empty() && event.creatures.at(i) > 0)//there is dwelling + if (i < town->creatures.size() && !town->creatures.at(i).second.empty() && event.creatures.at(i) > 0)//there is dwelling { sac.creatures[i].first += event.creatures.at(i); iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), event.creatures.at(i)); From 9baccdeeaed714ccf17e09cb9938bf10baa5f28e Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 16 Sep 2024 20:20:36 +0200 Subject: [PATCH 11/19] map editor: fix event players after conversion to std::set --- mapeditor/inspector/towneventdialog.cpp | 7 ++++--- mapeditor/mapsettings/timedevent.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 73e85ac09..b500bc8a9 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -63,9 +63,10 @@ TownEventDialog::~TownEventDialog() void TownEventDialog::initPlayers() { + auto playerList = params.value("players").toList(); for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) { - bool isAffected = (1 << i) & params.value("players").toInt(); + bool isAffected = playerList.contains(QString::fromStdString(PlayerColor(i).toString())); auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i])); item->setData(MapEditorRoles::PlayerIDRole, QVariant::fromValue(i)); item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked); @@ -213,12 +214,12 @@ void TownEventDialog::on_TownEventDialog_finished(int result) QVariant TownEventDialog::playersToVariant() { - int players = 0; + QVariantList players; for (int i = 0; i < ui->playersAffected->count(); ++i) { auto * item = ui->playersAffected->item(i); if (item->checkState() == Qt::Checked) - players |= 1 << i; + players.push_back(QString::fromStdString(PlayerColor(i).toString())); } return QVariant::fromValue(players); } diff --git a/mapeditor/mapsettings/timedevent.cpp b/mapeditor/mapsettings/timedevent.cpp index 94389efc7..b6d55c97e 100644 --- a/mapeditor/mapsettings/timedevent.cpp +++ b/mapeditor/mapsettings/timedevent.cpp @@ -30,9 +30,10 @@ TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) : ui->eventFirstOccurrence->setValue(params.value("firstOccurrence").toInt()); ui->eventRepeatAfter->setValue(params.value("nextOccurrence").toInt()); + auto playerList = params.value("players").toList(); for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) { - bool isAffected = (1 << i) & params.value("players").toInt(); + bool isAffected = playerList.contains(QString::fromStdString(PlayerColor(i).toString())); 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); @@ -69,12 +70,12 @@ void TimedEvent::on_TimedEvent_finished(int result) descriptor["firstOccurrence"] = QVariant::fromValue(ui->eventFirstOccurrence->value()); descriptor["nextOccurrence"] = QVariant::fromValue(ui->eventRepeatAfter->value()); - int players = 0; + QVariantList players; for(int i = 0; i < ui->playersAffected->count(); ++i) { auto * item = ui->playersAffected->item(i); if(item->checkState() == Qt::Checked) - players |= 1 << i; + players.push_back(QString::fromStdString(PlayerColor(i).toString())); } descriptor["players"] = QVariant::fromValue(players); From bff2e064fc39308983f8bd0405e5389d711d61a8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Sep 2024 20:09:06 +0000 Subject: [PATCH 12/19] Wait for queries generated by town building visit to end before visiting next building --- server/CGameHandler.cpp | 53 ++++++++++++++++++++++----------- server/CGameHandler.h | 1 + server/queries/VisitQueries.cpp | 32 ++++++++++++++++---- server/queries/VisitQueries.h | 32 +++++++++++++------- 4 files changed, 84 insertions(+), 34 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e450d698d..49f24ed35 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1171,7 +1171,6 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta sendAndApply(&vc); } visitCastleObjects(obj, hero); - giveSpells (obj, hero); if (obj->visitingHero && obj->garrisonHero) useScholarSkill(obj->visitingHero->id, obj->garrisonHero->id); @@ -1180,10 +1179,27 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h) { + std::vector visitors; + visitors.push_back(h); + visitCastleObjects(t, visitors); +} + +void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector visitors) +{ + std::vector buildingsToVisit; + for (auto const & hero : visitors) + giveSpells (t, hero); + for (auto & building : t->rewardableBuildings) { if (!t->town->buildings.at(building.first)->manualHeroVisit) - building.second->onHeroVisit(h); + buildingsToVisit.push_back(building.first); + } + + if (!buildingsToVisit.empty()) + { + auto visitQuery = std::make_shared(this, t, visitors, buildingsToVisit); + queries->addQuery(visitQuery); } } @@ -2144,10 +2160,15 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, if (!force) { - if(t->garrisonHero) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order - objectVisited(t, t->garrisonHero); - if(t->visitingHero) - objectVisited(t, t->visitingHero); + //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order + std::vector visitors; + if (t->garrisonHero) + visitors.push_back(t->garrisonHero); + if (t->visitingHero) + visitors.push_back(t->visitingHero); + + if (!visitors.empty()) + visitCastleObjects(t, visitors); } checkVictoryLossConditionsForPlayer(t->tempOwner); @@ -2173,19 +2194,15 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) return true; } - if (t->rewardableBuildings.count(bid)) + if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit) { - auto & hero = t->visitingHero; - auto * building = t->rewardableBuildings.at(bid); - - if (hero && t->town->buildings.at(bid)->manualHeroVisit) - { - auto visitQuery = std::make_shared(this, t, hero, bid); - queries->addQuery(visitQuery); - building->onHeroVisit(hero); - queries->popIfTop(visitQuery); - return true; - } + std::vector buildingsToVisit; + std::vector visitors; + buildingsToVisit.push_back(bid); + visitors.push_back(t->visitingHero); + auto visitQuery = std::make_shared(this, t, visitors, buildingsToVisit); + queries->addQuery(visitQuery); + return true; } return true; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index b7df98e05..cdb194377 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -182,6 +182,7 @@ public: void visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h); bool teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker = PlayerColor::NEUTRAL); void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override; + void visitCastleObjects(const CGTownInstance * obj, std::vector visitors); void levelUpHero(const CGHeroInstance * hero, SecondarySkill skill);//handle client respond and send one more request if needed void levelUpHero(const CGHeroInstance * hero);//initial call - check if hero have remaining levelups & handle them void levelUpCommander (const CCommanderInstance * c, int skill); //secondary skill 1 to 6, special skill : skill - 100 diff --git a/server/queries/VisitQueries.cpp b/server/queries/VisitQueries.cpp index 38beccbe6..cac6b873b 100644 --- a/server/queries/VisitQueries.cpp +++ b/server/queries/VisitQueries.cpp @@ -11,6 +11,8 @@ #include "VisitQueries.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/TownBuildingInstance.h" #include "../CGameHandler.h" #include "QueriesProcessor.h" @@ -29,7 +31,7 @@ bool VisitQuery::blocksPack(const CPack * pack) const return true; } -void VisitQuery::onExposure(QueryPtr topQuery) +void MapObjectVisitQuery::onExposure(QueryPtr topQuery) { //Object may have been removed and deleted. if(gh->isValidObject(visitedObject)) @@ -54,13 +56,31 @@ void MapObjectVisitQuery::onRemoval(PlayerColor color) gh->removeObject(visitedObject, color); } -TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, BuildingID buildingToVisit) - : VisitQuery(owner, Obj, Hero) - , visitedBuilding(buildingToVisit) +TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector heroes, std::vector buildingToVisit) + : VisitQuery(owner, Obj, heroes.front()) + , visitedTown(Obj) { + // generate in reverse order - first building-hero pair to handle must be in the end of vector + for (auto const * hero : boost::adaptors::reverse(heroes)) + for (auto const & building : boost::adaptors::reverse(buildingToVisit)) + visitedBuilding.push_back({ hero, building}); } -void TownBuildingVisitQuery::onRemoval(PlayerColor color) +void TownBuildingVisitQuery::onExposure(QueryPtr topQuery) { - + onAdded(players.front()); +} + +void TownBuildingVisitQuery::onAdded(PlayerColor color) +{ + while (!visitedBuilding.empty() && owner->topQuery(color).get() == this) + { + visitingHero = visitedBuilding.back().hero; + auto * building = visitedTown->rewardableBuildings.at(visitedBuilding.back().building); + building->onHeroVisit(visitingHero); + visitedBuilding.pop_back(); + } + + if (visitedBuilding.empty() && owner->topQuery(color).get() == this) + owner->popIfTop(*this); } diff --git a/server/queries/VisitQueries.h b/server/queries/VisitQueries.h index 0d7ce5cca..d2f554a49 100644 --- a/server/queries/VisitQueries.h +++ b/server/queries/VisitQueries.h @@ -11,19 +11,22 @@ #include "CQuery.h" +VCMI_LIB_NAMESPACE_BEGIN +class CGTownInstance; +VCMI_LIB_NAMESPACE_END + //Created when hero visits object. //Removed when query above is resolved (or immediately after visit if no queries were created) class VisitQuery : public CQuery { protected: - VisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero); + VisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero); public: - const CGObjectInstance *visitedObject; - const CGHeroInstance *visitingHero; + const CGObjectInstance * visitedObject; + const CGHeroInstance * visitingHero; - bool blocksPack(const CPack *pack) const final; - void onExposure(QueryPtr topQuery) final; + bool blocksPack(const CPack * pack) const final; }; class MapObjectVisitQuery final : public VisitQuery @@ -31,17 +34,26 @@ class MapObjectVisitQuery final : public VisitQuery public: bool removeObjectAfterVisit; - MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero); + MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero); void onRemoval(PlayerColor color) final; + void onExposure(QueryPtr topQuery) final; }; class TownBuildingVisitQuery final : public VisitQuery { + struct BuildingVisit + { + const CGHeroInstance * hero; + BuildingID building; + }; + + const CGTownInstance * visitedTown; + std::vector visitedBuilding; + public: - BuildingID visitedBuilding; + TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector heroes, std::vector buildingToVisit); - TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, BuildingID buildingToVisit); - - void onRemoval(PlayerColor color) final; + void onAdded(PlayerColor color) final; + void onExposure(QueryPtr topQuery) final; }; From 8b2821456a6a288a4152be37f8217a7094ce4936 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Sep 2024 19:51:10 +0000 Subject: [PATCH 13/19] Show generic dialog if building was already visited --- client/windows/CCastleInterface.cpp | 6 +++++- lib/mapObjects/TownBuildingInstance.cpp | 5 +++++ lib/mapObjects/TownBuildingInstance.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 418c5ffe4..e8086f830 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -54,6 +54,7 @@ #include "../../lib/entities/building/CBuilding.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/TownBuildingInstance.h" static bool useCompactCreatureBox() @@ -855,7 +856,10 @@ void CCastleBuildings::enterRewardable(BuildingID building) } else { - LOCPLINT->cb->visitTownBuilding(town, building); + if (town->rewardableBuildings.at(building)->wasVisited(town->visitingHero)) + enterBuilding(building); + else + LOCPLINT->cb->visitTownBuilding(town, building); } } diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index dd8b59b6d..a78366f18 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -165,6 +165,11 @@ void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInst } } +bool TownRewardableBuildingInstance::wasVisited(const CGHeroInstance * contextHero) const +{ + return wasVisitedBefore(contextHero); +} + bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * contextHero) const { switch (configuration.visitMode) diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index bf4dec596..2315566fa 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -70,6 +70,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance, public: void setProperty(ObjProperty what, ObjPropertyID identifier) override; void onHeroVisit(const CGHeroInstance * h) const override; + bool wasVisited(const CGHeroInstance * contextHero) const override; void newTurn(vstd::RNG & rand) const override; From b5bacb5c096b88a99531b48d39110b0844151110 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Sep 2024 14:48:28 +0000 Subject: [PATCH 14/19] Fix creatures with non-cyan background in battle when xbrz is not in use --- client/renderSDL/SDLImage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 57d62485f..83067b8a2 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -415,6 +415,10 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) { + // If shadow is enabled, following colors must be skipped unconditionally + if (shadowEnabled) + colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4); + // Note: here we skip first colors in the palette that are predefined in H3 images for(int i = 0; i < currentPalette->ncolors; i++) { From 514e81406df9aa5fd024929ebb4edee4dba3e706 Mon Sep 17 00:00:00 2001 From: godric3 Date: Tue, 17 Sep 2024 20:57:05 +0200 Subject: [PATCH 15/19] extract PlayerColor to QString conversion to helper function --- mapeditor/inspector/towneventdialog.cpp | 5 +++-- mapeditor/mapsettings/eventsettings.cpp | 7 ++++++- mapeditor/mapsettings/eventsettings.h | 1 + mapeditor/mapsettings/timedevent.cpp | 5 +++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index b500bc8a9..e56af191e 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -13,6 +13,7 @@ #include "towneventdialog.h" #include "ui_towneventdialog.h" #include "mapeditorroles.h" +#include "../mapsettings/eventsettings.h" #include "../../lib/entities/building/CBuilding.h" #include "../../lib/entities/faction/CTownHandler.h" #include "../../lib/constants/NumericConstants.h" @@ -66,7 +67,7 @@ void TownEventDialog::initPlayers() auto playerList = params.value("players").toList(); for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) { - bool isAffected = playerList.contains(QString::fromStdString(PlayerColor(i).toString())); + bool isAffected = playerList.contains(toQString(PlayerColor(i))); auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i])); item->setData(MapEditorRoles::PlayerIDRole, QVariant::fromValue(i)); item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked); @@ -219,7 +220,7 @@ QVariant TownEventDialog::playersToVariant() { auto * item = ui->playersAffected->item(i); if (item->checkState() == Qt::Checked) - players.push_back(QString::fromStdString(PlayerColor(i).toString())); + players.push_back(toQString(PlayerColor(i))); } return QVariant::fromValue(players); } diff --git a/mapeditor/mapsettings/eventsettings.cpp b/mapeditor/mapsettings/eventsettings.cpp index 7f127e45f..f7c2bbc76 100644 --- a/mapeditor/mapsettings/eventsettings.cpp +++ b/mapeditor/mapsettings/eventsettings.cpp @@ -16,11 +16,16 @@ #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" +QString toQString(const PlayerColor & player) +{ + return QString::fromStdString(player.toString()); +} + QVariant toVariant(const std::set & players) { QVariantList result; for(auto const id : players) - result.push_back(QString::fromStdString(id.toString())); + result.push_back(toQString(id)); return result; } diff --git a/mapeditor/mapsettings/eventsettings.h b/mapeditor/mapsettings/eventsettings.h index 2d1cee00c..c7be81f06 100644 --- a/mapeditor/mapsettings/eventsettings.h +++ b/mapeditor/mapsettings/eventsettings.h @@ -15,6 +15,7 @@ namespace Ui { class EventSettings; } +QString toQString(const PlayerColor & player); QVariant toVariant(const TResources & resources); QVariant toVariant(const std::set & players); diff --git a/mapeditor/mapsettings/timedevent.cpp b/mapeditor/mapsettings/timedevent.cpp index b6d55c97e..083bd1d72 100644 --- a/mapeditor/mapsettings/timedevent.cpp +++ b/mapeditor/mapsettings/timedevent.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "timedevent.h" #include "ui_timedevent.h" +#include "eventsettings.h" #include "../../lib/constants/EntityIdentifiers.h" #include "../../lib/constants/StringConstants.h" @@ -33,7 +34,7 @@ TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) : auto playerList = params.value("players").toList(); for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) { - bool isAffected = playerList.contains(QString::fromStdString(PlayerColor(i).toString())); + bool isAffected = playerList.contains(toQString(PlayerColor(i))); 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); @@ -75,7 +76,7 @@ void TimedEvent::on_TimedEvent_finished(int result) { auto * item = ui->playersAffected->item(i); if(item->checkState() == Qt::Checked) - players.push_back(QString::fromStdString(PlayerColor(i).toString())); + players.push_back(toQString(PlayerColor(i))); } descriptor["players"] = QVariant::fromValue(players); From c8f8a3fb6dc4947c1056f86c834867f4e02466fc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:03:30 +0200 Subject: [PATCH 16/19] missing asset for generateAll --- client/render/AssetGenerator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index dd31b12e2..ca03969f5 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -25,6 +25,7 @@ void AssetGenerator::generateAll() createAdventureOptionsCleanBackground(); for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) createPlayerColoredBackground(PlayerColor(i)); + createCombatUnitNumberWindow(); } void AssetGenerator::createAdventureOptionsCleanBackground() From c884566c4f61946997f192461298bbb31f577fd8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:58:56 +0200 Subject: [PATCH 17/19] fix xbrz shadow --- client/windows/CWindowObject.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index 2b88f6064..d60bf3a9d 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -20,6 +20,7 @@ #include "../windows/CMessage.h" #include "../renderSDL/SDL_PixelAccess.h" #include "../render/IImage.h" +#include "../render/IScreenHandler.h" #include "../render/IRenderHandler.h" #include "../render/Canvas.h" @@ -115,7 +116,8 @@ void CWindowObject::updateShadow() void CWindowObject::setShadow(bool on) { //size of shadow - static const int size = 8; + int sizeOriginal = 8; + int size = sizeOriginal * GH.screenHandler().getScalingFactor(); if(on == !shadowParts.empty()) return; @@ -180,9 +182,9 @@ void CWindowObject::setShadow(bool on) //FIXME: do something with this points Point shadowStart; if (options & BORDERED) - shadowStart = Point(size - 14, size - 14); + shadowStart = Point(sizeOriginal - 14, sizeOriginal - 14); else - shadowStart = Point(size, size); + shadowStart = Point(sizeOriginal, sizeOriginal); Point shadowPos; if (options & BORDERED) @@ -198,8 +200,8 @@ void CWindowObject::setShadow(bool on) //create base 8x8 piece of shadow SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); - SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, fullsize.x - size, size); - SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, fullsize.y - size); + SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, (fullsize.x - sizeOriginal) * GH.screenHandler().getScalingFactor(), size); + SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, (fullsize.y - sizeOriginal) * GH.screenHandler().getScalingFactor()); blitAlphaCol(shadowBottom, 0); blitAlphaRow(shadowRight, 0); From fd1f1001c5ea85eddf354c78413b5e0b7c412d93 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 18 Sep 2024 00:09:09 +0200 Subject: [PATCH 18/19] fix draw color --- client/render/Canvas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/render/Canvas.cpp b/client/render/Canvas.cpp index 971b6ebc9..682349bbe 100644 --- a/client/render/Canvas.cpp +++ b/client/render/Canvas.cpp @@ -191,14 +191,14 @@ void Canvas::drawText(const Point & position, const EFonts & font, const ColorRG void Canvas::drawColor(const Rect & target, const ColorRGBA & color) { - Rect realTarget = (target + renderArea.topLeft()) * getScalingFactor(); + Rect realTarget = target * getScalingFactor() + renderArea.topLeft(); CSDL_Ext::fillRect(surface, realTarget, CSDL_Ext::toSDL(color)); } void Canvas::drawColorBlended(const Rect & target, const ColorRGBA & color) { - Rect realTarget = (target + renderArea.topLeft()) * getScalingFactor(); + Rect realTarget = target * getScalingFactor() + renderArea.topLeft(); CSDL_Ext::fillRectBlended(surface, realTarget, CSDL_Ext::toSDL(color)); } From 70ef1d76fd30dfb150cbe8bb926326ee2e79a780 Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Wed, 18 Sep 2024 14:52:37 +0300 Subject: [PATCH 19/19] Update editor/jsonutils with launcher/jsonutils --- mapeditor/jsonutils.cpp | 46 +++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index 45a20e51d..e4eb884d6 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -17,7 +17,7 @@ static QVariantMap JsonToMap(const JsonMap & json) QVariantMap map; for(auto & entry : json) { - map.insert(QString::fromUtf8(entry.first.c_str()), JsonUtils::toVariant(entry.second)); + map.insert(QString::fromStdString(entry.first), JsonUtils::toVariant(entry.second)); } return map; } @@ -61,23 +61,18 @@ QVariant toVariant(const JsonNode & node) { switch(node.getType()) { - break; case JsonNode::JsonType::DATA_NULL: return QVariant(); - break; case JsonNode::JsonType::DATA_BOOL: return QVariant(node.Bool()); - break; case JsonNode::JsonType::DATA_FLOAT: - case JsonNode::JsonType::DATA_INTEGER: return QVariant(node.Float()); - break; + case JsonNode::JsonType::DATA_INTEGER: + return QVariant{static_cast(node.Integer())}; case JsonNode::JsonType::DATA_STRING: - return QVariant(QString::fromUtf8(node.String().c_str())); - break; + return QVariant(QString::fromStdString(node.String())); case JsonNode::JsonType::DATA_VECTOR: return JsonToList(node.Vector()); - break; case JsonNode::JsonType::DATA_STRUCT: return JsonToMap(node.Struct()); } @@ -87,34 +82,31 @@ QVariant toVariant(const JsonNode & node) QVariant JsonFromFile(QString filename) { QFile file(filename); - file.open(QFile::ReadOnly); - auto data = file.readAll(); + if(!file.open(QFile::ReadOnly)) + { + logGlobal->error("Failed to open file %s. Reason: %s", qUtf8Printable(filename), qUtf8Printable(file.errorString())); + return {}; + } - if(data.size() == 0) - { - logGlobal->error("Failed to open file %s", filename.toUtf8().data()); - return QVariant(); - } - else - { - JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); - return toVariant(node); - } + const auto data = file.readAll(); + JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); + return toVariant(node); } JsonNode toJson(QVariant object) { JsonNode ret; - if(object.canConvert()) - ret.Struct() = VariantToMap(object.toMap()); - else if(object.userType() == QMetaType::QString) + if(object.userType() == QMetaType::QString) ret.String() = object.toString().toUtf8().data(); - //QStirng can be converted to QVariantList, need to check first - else if(object.canConvert()) - ret.Vector() = VariantToList(object.toList()); else if(object.userType() == QMetaType::Bool) ret.Bool() = object.toBool(); + else if(object.canConvert()) + ret.Struct() = VariantToMap(object.toMap()); + else if(object.canConvert()) + ret.Vector() = VariantToList(object.toList()); + else if(object.canConvert()) + ret.Integer() = object.toInt(); else if(object.canConvert()) ret.Float() = object.toFloat();