From 31b04780c99918b002db61110ccb5bc833523f33 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 16:40:51 +0200 Subject: [PATCH 01/21] Added hasValue method to identifiers --- lib/constants/IdentifierBase.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/constants/IdentifierBase.h b/lib/constants/IdentifierBase.h index 62c2e671b..8b1628abe 100644 --- a/lib/constants/IdentifierBase.h +++ b/lib/constants/IdentifierBase.h @@ -52,6 +52,11 @@ public: num = value; } + constexpr bool hasValue() const + { + return num >= 0; + } + struct hash { size_t operator()(const IdentifierBase & id) const From 24d25730ad58dec12099afeef72227a257042855 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 16:41:10 +0200 Subject: [PATCH 02/21] Changed checks for null with checks for hasValue --- client/battle/BattleActionsController.cpp | 10 +++++----- client/battle/BattleInterface.cpp | 8 ++++---- lib/battle/CBattleInfoCallback.cpp | 5 +++-- server/CGameHandler.cpp | 4 ++-- server/NetPacksServer.cpp | 5 +++-- server/battles/BattleActionProcessor.cpp | 4 ++-- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index a6e4f5784..3484689c1 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -750,7 +750,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B if (!spellcastingModeActive()) { - if (action.spell().toSpell()) + if (action.spell().hasValue()) { owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell()); } @@ -887,17 +887,17 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS { // faerie dragon can cast only one, randomly selected spell until their next move //TODO: faerie dragon type spell should be selected by server - const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell(); + const auto spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack); - if (spellToCast) - creatureSpells.push_back(spellToCast); + if (spellToCast.hasValue()) + creatureSpells.push_back(spellToCast.toSpell()); } TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER)); for(const auto & bonus : *bl) { - if (bonus->additionalInfo[0] <= 0) + if (bonus->additionalInfo[0] <= 0 && bonus->subtype.as().hasValue()) creatureSpells.push_back(bonus->subtype.as().toSpell()); } } diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index c2255b6f5..d534f0556 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -352,13 +352,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) CCS->curh->set(Cursor::Combat::BLOCKED); const SpellID spellID = sc->spellID; + + if(!spellID.hasValue()) + return; + const CSpell * spell = spellID.toSpell(); auto targetedTile = sc->tile; - assert(spell); - if(!spell) - return; - const AudioPath & castSoundPath = spell->getCastSound(); if (!castSoundPath.empty()) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 7db5cc7aa..14509999d 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -867,9 +867,10 @@ bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & s auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side); const auto * hero = battleGetFightingHero(spellObstacle->casterSide); auto caster = spells::ObstacleCasterProxy(getBattle()->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle); - const auto * sp = obstacle->getTrigger().toSpell(); - if(obstacle->triggersEffects() && sp) + + if(obstacle->triggersEffects() && obstacle->getTrigger().hasValue()) { + const auto * sp = obstacle->getTrigger().toSpell(); auto cast = spells::BattleCast(this, &caster, spells::Mode::PASSIVE, sp); spells::detail::ProblemImpl ignored; auto target = spells::Target(1, spells::Destination(&unit)); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ad9baed9e..dc9bcb999 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3955,14 +3955,14 @@ bool CGameHandler::moveStack(const StackLocation &src, const StackLocation &dst, void CGameHandler::castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) { - const CSpell * s = spellID.toSpell(); - if(!s) + if (!spellID.hasValue()) return; AdventureSpellCastParameters p; p.caster = caster; p.pos = pos; + const CSpell * s = spellID.toSpell(); s->adventureCast(spellEnv, p); } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index a3d77db31..846ddcd42 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -327,9 +327,9 @@ void ApplyGhNetPackVisitor::visitCastAdvSpell(CastAdvSpell & pack) { gh.throwIfWrongOwner(&pack, pack.hid); - const CSpell * s = pack.sid.toSpell(); - if(!s) + if (!pack.sid.hasValue()) gh.throwNotAllowedAction(&pack); + const CGHeroInstance * h = gh.getHero(pack.hid); if(!h) gh.throwNotAllowedAction(&pack); @@ -338,6 +338,7 @@ void ApplyGhNetPackVisitor::visitCastAdvSpell(CastAdvSpell & pack) p.caster = h; p.pos = pack.pos; + const CSpell * s = pack.sid.toSpell(); result = s->adventureCast(gh.spellEnv, p); } diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index af8e88994..8a706a8c1 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -102,13 +102,13 @@ bool BattleActionProcessor::doHeroSpellAction(const CBattleInfoCallback & battle return false; } - const CSpell * s = ba.spell.toSpell(); - if (!s) + if (!ba.spell.hasValue()) { logGlobal->error("Wrong spell id (%d)!", ba.spell.getNum()); return false; } + const CSpell * s = ba.spell.toSpell(); spells::BattleCast parameters(&battle, h, spells::Mode::HERO, s); spells::detail::ProblemImpl problem; From dffa1746059cbec7efb833f1200e92bbe997e18c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 16:48:05 +0200 Subject: [PATCH 03/21] Attempt to fix crash --- server/TurnTimerHandler.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 9aa3e5dc7..84a05c740 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -88,21 +88,21 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) void TurnTimerHandler::update(int waitTime) { std::lock_guard guard(mx); - if(const auto * gs = gameHandler.gameState()) - { - for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) - if(gs->isPlayerMakingTurn(player)) - onPlayerMakingTurn(player, waitTime); - - // create copy for iterations - battle might end during onBattleLoop call - std::vector ongoingBattles; + if(!gameHandler.getStartInfo()->turnTimerInfo.isEnabled()) + return; - for (auto & battle : gs->currentBattles) - ongoingBattles.push_back(battle->battleID); + for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) + if(gameHandler.gameState()->isPlayerMakingTurn(player)) + onPlayerMakingTurn(player, waitTime); - for (auto & battleID : ongoingBattles) - onBattleLoop(battleID, waitTime); - } + // create copy for iterations - battle might end during onBattleLoop call + std::vector ongoingBattles; + + for (auto & battle : gameHandler.gameState()->currentBattles) + ongoingBattles.push_back(battle->battleID); + + for (auto & battleID : ongoingBattles) + onBattleLoop(battleID, waitTime); } bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor player, int waitTime) From 62935b4ba4f5ad65e7a71cecb932fffdbb49bcdd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 17:15:51 +0200 Subject: [PATCH 04/21] Fix crash on using StupidAI for player actions --- AI/StupidAI/StupidAI.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 3888e5a6b..b24cf3285 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -16,8 +16,6 @@ #include "../../lib/battle/BattleAction.h" #include "../../lib/battle/BattleInfo.h" -static std::shared_ptr cbc; - CStupidAI::CStupidAI() : side(-1) , wasWaitingForRealize(false) @@ -41,7 +39,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr ENV, std::share { print("init called, saving ptr to IBattleCallback"); env = ENV; - cbc = cb = CB; + cb = CB; wasWaitingForRealize = CB->waitTillRealize; wasUnlockingGs = CB->unlockGsWhenWaiting; @@ -72,11 +70,11 @@ public: std::vector attackFrom; //for melee fight EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0) {} - void calcDmg(const BattleID & battleID, const CStack * ourStack) + void calcDmg(std::shared_ptr cb, const BattleID & battleID, const CStack * ourStack) { // FIXME: provide distance info for Jousting bonus DamageEstimation retal; - DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal); + DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal); adi = static_cast((dmg.damage.min + dmg.damage.max) / 2); adr = static_cast((retal.damage.min + retal.damage.max) / 2); } @@ -92,14 +90,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2) return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr); } -static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2) +static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2) { int shooters[2] = {0}; //count of shooters on hexes for(int i = 0; i < 2; i++) { for (auto & neighbour : (i ? h2 : h1).neighbouringTiles()) - if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour)) + if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour)) if(s->isShooter()) shooters[i]++; } @@ -169,10 +167,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack) } for ( auto & enemy : enemiesReachable ) - enemy.calcDmg(battleID, stack); + enemy.calcDmg(cb, battleID, stack); for ( auto & enemy : enemiesShootable ) - enemy.calcDmg(battleID, stack); + enemy.calcDmg(cb, battleID, stack); if(enemiesShootable.size()) { @@ -183,7 +181,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack) else if(enemiesReachable.size()) { const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable); - BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);}); + BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);}); cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex)); return; From 06517acd67228b4e2d518e5941f8742d63c4da88 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 17:31:11 +0200 Subject: [PATCH 05/21] Version bump for Android --- android/vcmi-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index f4c216501..7a88a69fd 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,7 +10,7 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 33 - versionCode 1440 + versionCode 1441 versionName "1.4.4" setProperty("archivesBaseName", "vcmi") } From 912262e826ce155bc8dfe8c8ed34e9fd9cbd3ce6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 18:17:23 +0200 Subject: [PATCH 06/21] Avoid crash on invalid artifact in bank config --- lib/JsonRandom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/JsonRandom.cpp b/lib/JsonRandom.cpp index ca068c973..142edd2a1 100644 --- a/lib/JsonRandom.cpp +++ b/lib/JsonRandom.cpp @@ -82,7 +82,7 @@ namespace JsonRandom IdentifierType decodeKey(const std::string & modScope, const std::string & value, const Variables & variables) { if (value.empty() || value[0] != '@') - return IdentifierType(*VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value)); + return IdentifierType(VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value).value_or(-1)); else return loadVariable(IdentifierType::entityType(), value, variables, IdentifierType::NONE); } @@ -91,7 +91,7 @@ namespace JsonRandom IdentifierType decodeKey(const JsonNode & value, const Variables & variables) { if (value.String().empty() || value.String()[0] != '@') - return IdentifierType(*VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value)); + return IdentifierType(VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value).value_or(-1)); else return loadVariable(IdentifierType::entityType(), value.String(), variables, IdentifierType::NONE); } From 8e8d42bfa29252ce18343c15a51f53bfdc4fe350 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 18:40:03 +0200 Subject: [PATCH 07/21] Try to simplify / cleanup code to localize crash --- .../AObjectTypeHandler.cpp | 21 ++++++------------- .../CObjectClassesHandler.cpp | 4 ++-- .../DwellingInstanceConstructor.cpp | 8 +++---- lib/mapping/MapFormatJson.cpp | 4 ++-- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.cpp b/lib/mapObjectConstructors/AObjectTypeHandler.cpp index b5ead3f79..a3ed75ef4 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.cpp +++ b/lib/mapObjectConstructors/AObjectTypeHandler.cpp @@ -21,17 +21,7 @@ VCMI_LIB_NAMESPACE_BEGIN AObjectTypeHandler::AObjectTypeHandler() = default; - -AObjectTypeHandler::~AObjectTypeHandler() -{ - // FIXME: currently on Android there is a weird crash in destructor of 'base' member - // this code attempts to localize and fix this crash - if (base) - { - base->clear(); - base.reset(); - } -} +AObjectTypeHandler::~AObjectTypeHandler() = default; std::string AObjectTypeHandler::getJsonKey() const { @@ -89,12 +79,12 @@ void AObjectTypeHandler::init(const JsonNode & input) if (base) JsonUtils::inherit(entry.second, *base); - auto * tmpl = new ObjectTemplate; + auto tmpl = std::make_shared(); tmpl->id = Obj(type); tmpl->subid = subtype; tmpl->stringID = entry.first; // FIXME: create "fullID" - type.object.template? tmpl->readJson(entry.second); - templates.push_back(std::shared_ptr(tmpl)); + templates.push_back(tmpl); } for(const JsonNode & node : input["sounds"]["ambient"].Vector()) @@ -188,12 +178,13 @@ void AObjectTypeHandler::addTemplate(JsonNode config) config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not null if (base) JsonUtils::inherit(config, *base); - auto * tmpl = new ObjectTemplate; + + auto tmpl = std::make_shared(); tmpl->id = Obj(type); tmpl->subid = subtype; tmpl->stringID.clear(); // TODO? tmpl->readJson(config); - templates.emplace_back(tmpl); + templates.push_back(tmpl); } std::vector> AObjectTypeHandler::getTemplates() const diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 2c758b0f6..de608e33d 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -109,13 +109,13 @@ std::vector CObjectClassesHandler::loadLegacyData() for (size_t i = 0; i < totalNumber; i++) { - auto * tmpl = new ObjectTemplate; + auto tmpl = std::make_shared(); tmpl->readTxt(parser); parser.endLine(); std::pair key(tmpl->id, tmpl->subid); - legacyTemplates.insert(std::make_pair(key, std::shared_ptr(tmpl))); + legacyTemplates.insert(std::make_pair(key, tmpl)); } objects.resize(256); diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index 6a137b976..ea86a5cb8 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -35,17 +35,17 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input) const auto totalLevels = levels.size(); availableCreatures.resize(totalLevels); - for(auto currentLevel = 0; currentLevel < totalLevels; currentLevel++) + for(int currentLevel = 0; currentLevel < totalLevels; currentLevel++) { const JsonVector & creaturesOnLevel = levels[currentLevel].Vector(); const auto creaturesNumber = creaturesOnLevel.size(); availableCreatures[currentLevel].resize(creaturesNumber); - for(auto currentCreature = 0; currentCreature < creaturesNumber; currentCreature++) + for(int currentCreature = 0; currentCreature < creaturesNumber; currentCreature++) { - VLC->identifiers()->requestIdentifier("creature", creaturesOnLevel[currentCreature], [=] (si32 index) + VLC->identifiers()->requestIdentifier("creature", creaturesOnLevel[currentCreature], [this, currentLevel, currentCreature] (si32 index) { - availableCreatures[currentLevel][currentCreature] = VLC->creh->objects[index]; + availableCreatures.at(currentLevel).at(currentCreature) = VLC->creh->objects[index]; }); } assert(!availableCreatures[currentLevel].empty()); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 7f6059400..ca4b706fb 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1074,14 +1074,14 @@ void CMapLoaderJson::MapObjectLoader::construct() auto handler = VLC->objtypeh->getHandlerFor( ModScope::scopeMap(), typeName, subtypeName); - auto * appearance = new ObjectTemplate; + auto appearance = std::make_shared(); appearance->id = Obj(handler->getIndex()); appearance->subid = handler->getSubIndex(); appearance->readJson(configuration["template"], false); // Will be destroyed soon and replaced with shared template - instance = handler->create(std::shared_ptr(appearance)); + instance = handler->create(appearance); instance->id = ObjectInstanceID(static_cast(owner->map->objects.size())); instance->instanceName = jsonKey; From 99207836289d9de69f56d1bf281e295494280c45 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 18:50:59 +0200 Subject: [PATCH 08/21] Avoid crash on invalid H3 data --- client/renderSDL/SDLImage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index e8930abd3..4ce8e869c 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -205,6 +205,8 @@ void SDLImage::exportBitmap(const boost::filesystem::path& path) const void SDLImage::playerColored(PlayerColor player) { + if (!surf) + return; graphics->blueToPlayersAdv(surf, player); } From 6a42494b125f6d1888b2c8d46ec36e4623c2bdf6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 22 Jan 2024 21:48:03 +0200 Subject: [PATCH 09/21] Merge CKeyboardFocusListener and IFocusListener into CFocusable --- client/widgets/TextControls.cpp | 33 +++++++++--------------------- client/widgets/TextControls.h | 36 ++++++--------------------------- 2 files changed, 16 insertions(+), 53 deletions(-) diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 344d20757..6fbab6605 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -553,8 +553,7 @@ Point CGStatusBar::getBorderSize() CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB, bool giveFocusToInput) : CLabel(Pos.x, Pos.y, font, ETextAlignment::CENTER), - cb(CB), - CFocusable(std::make_shared(this)) + cb(CB) { setRedrawParent(true); pos.h = Pos.h; @@ -570,7 +569,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB) - :cb(CB), CFocusable(std::make_shared(this)) + :cb(CB) { pos += Pos.topLeft(); pos.h = Pos.h; @@ -587,7 +586,6 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath } CTextInput::CTextInput(const Rect & Pos, std::shared_ptr srf) - :CFocusable(std::make_shared(this)) { pos += Pos.topLeft(); OBJ_CONSTRUCTION; @@ -603,20 +601,15 @@ CTextInput::CTextInput(const Rect & Pos, std::shared_ptr srf) #endif } -std::atomic CKeyboardFocusListener::usageIndex(0); +std::atomic CFocusable::usageIndex(0); -CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput) - :textInput(textInput) +void CFocusable::focusGot() { -} - -void CKeyboardFocusListener::focusGot() -{ - GH.startTextInput(textInput->pos); + GH.startTextInput(pos); usageIndex++; } -void CKeyboardFocusListener::focusLost() +void CFocusable::focusLost() { if(0 == --usageIndex) { @@ -769,12 +762,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i } CFocusable::CFocusable() - :CFocusable(std::make_shared()) -{ -} - -CFocusable::CFocusable(std::shared_ptr focusListener) - : focusListener(focusListener) { focus = false; focusables.push_back(this); @@ -785,7 +772,7 @@ CFocusable::~CFocusable() if(hasFocus()) { inputWithFocus = nullptr; - focusListener->focusLost(); + focusLost(); } focusables -= this; @@ -799,13 +786,13 @@ bool CFocusable::hasFocus() const void CFocusable::giveFocus() { focus = true; - focusListener->focusGot(); + focusGot(); redraw(); if(inputWithFocus) { inputWithFocus->focus = false; - inputWithFocus->focusListener->focusLost(); + inputWithFocus->focusLost(); inputWithFocus->redraw(); } @@ -837,7 +824,7 @@ void CFocusable::removeFocus() if(this == inputWithFocus) { focus = false; - focusListener->focusLost(); + focusLost(); redraw(); inputWithFocus = nullptr; diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 7c6b5266b..d922d0f87 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -163,25 +163,12 @@ public: void clear() override; void setEnteringMode(bool on) override; void setEnteredText(const std::string & text) override; - -}; - -class CFocusable; - -class IFocusListener -{ -public: - virtual void focusGot() {}; - virtual void focusLost() {}; - virtual ~IFocusListener() = default; }; /// UIElement which can get input focus class CFocusable : public virtual CIntObject { -private: - std::shared_ptr focusListener; - + static std::atomic usageIndex; public: bool focus; //only one focusable control can have focus at one moment @@ -190,38 +177,27 @@ public: void removeFocus(); //remove focus bool hasFocus() const; + void focusGot(); + void focusLost(); + static std::list focusables; //all existing objs static CFocusable * inputWithFocus; //who has focus now CFocusable(); - CFocusable(std::shared_ptr focusListener); ~CFocusable(); }; -class CTextInput; -class CKeyboardFocusListener : public IFocusListener -{ -private: - static std::atomic usageIndex; - CTextInput * textInput; - -public: - CKeyboardFocusListener(CTextInput * textInput); - void focusGot() override; - void focusLost() override; -}; - /// Text input box where players can enter text class CTextInput : public CLabel, public CFocusable { std::string newText; std::string helpBox; //for right-click help - + protected: std::string visibleText() override; public: - + CFunctionList cb; CFunctionList filters; void setText(const std::string & nText) override; From ea439ad17580a0c639621f7fbc337f0d395ce505 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 22 Jan 2024 22:19:52 +0200 Subject: [PATCH 10/21] Dispatch text input SDL calls in main thread on all platforms --- client/eventsSDL/InputSourceText.cpp | 43 +++++++++------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/client/eventsSDL/InputSourceText.cpp b/client/eventsSDL/InputSourceText.cpp index 72afc978d..96edf5fe1 100644 --- a/client/eventsSDL/InputSourceText.cpp +++ b/client/eventsSDL/InputSourceText.cpp @@ -21,10 +21,6 @@ #include -#ifdef VCMI_APPLE -# include -#endif - void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text) { GH.events().dispatchTextInput(text.text); @@ -37,38 +33,27 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text) void InputSourceText::startTextInput(const Rect & whereInput) { - -#ifdef VCMI_APPLE - dispatch_async(dispatch_get_main_queue(), ^{ -#endif - - Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput); - SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates); - - SDL_SetTextInputRect(&textInputRect); - - if (SDL_IsTextInputActive() == SDL_FALSE) + GH.dispatchMainThread([whereInput]() { - SDL_StartTextInput(); - } + Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput); + SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates); -#ifdef VCMI_APPLE + SDL_SetTextInputRect(&textInputRect); + + if (SDL_IsTextInputActive() == SDL_FALSE) + { + SDL_StartTextInput(); + } }); -#endif } void InputSourceText::stopTextInput() { -#ifdef VCMI_APPLE - dispatch_async(dispatch_get_main_queue(), ^{ -#endif - - if (SDL_IsTextInputActive() == SDL_TRUE) + GH.dispatchMainThread([]() { - SDL_StopTextInput(); - } - -#ifdef VCMI_APPLE + if (SDL_IsTextInputActive() == SDL_TRUE) + { + SDL_StopTextInput(); + } }); -#endif } From f83a004b54c097d901bcc1b1e26a13749a26ed34 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 23 Jan 2024 00:49:18 +0200 Subject: [PATCH 11/21] Added workaround to prevent multithreaded access to bonus system in RMG --- lib/bonuses/CBonusSystemNode.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index 8306e3ae1..92634975d 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -273,6 +273,10 @@ CBonusSystemNode::~CBonusSystemNode() void CBonusSystemNode::attachTo(CBonusSystemNode & parent) { + // quick workaround to prevent multithreaded access to bonus system in RMG + static boost::mutex attachMutex; + boost::lock_guard lock(attachMutex); + assert(!vstd::contains(parents, &parent)); parents.push_back(&parent); @@ -646,4 +650,4 @@ int64_t CBonusSystemNode::getTreeVersion() const return treeChanged; } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END From be47c66c575d2c312c4e2336864511f1335524aa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 23 Jan 2024 12:57:38 +0200 Subject: [PATCH 12/21] Ignore hidden mods when checking mod dependencies --- launcher/modManager/cmodlist.cpp | 3 +++ launcher/modManager/cmodlistview_moc.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index b36d4b198..6facca796 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -38,6 +38,9 @@ bool CModEntry::isEnabled() const if(!isInstalled()) return false; + if (!isVisible()) + return false; + return modSettings["active"].toBool(); } diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index f27573aef..556b7945b 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -521,7 +521,7 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled) { auto current = modModel->getMod(modName); - if(!current.isInstalled()) + if(!current.isInstalled() || !current.isVisible()) continue; if(current.getDependencies().contains(mod, Qt::CaseInsensitive)) From 5836e390f1711c293b25420167723d17e4df9339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 22 Jan 2024 09:27:30 +0100 Subject: [PATCH 13/21] Do not place zone guards adjacent to 3rd zone --- lib/rmg/modificators/ConnectionsPlacer.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index 73e9b5837..f765eeb00 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -177,6 +177,21 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con int3 potentialPos = zone.areaPossible().nearest(borderPos); assert(borderPos != potentialPos); + //Check if guard pos doesn't touch any 3rd zone. This would create unwanted passage to 3rd zone + bool adjacentZone = false; + map.foreach_neighbour(potentialPos, [this, &adjacentZone, otherZoneId](int3 & pos) + { + auto zoneId = map.getZoneID(pos); + if (zoneId != zone.getId() && zoneId != otherZoneId) + { + adjacentZone = true; + } + }); + if (adjacentZone) + { + continue; + } + //Take into account distance to objects from both sides float dist = std::min(map.getTileInfo(potentialPos).getNearestObjectDistance(), map.getTileInfo(borderPos).getNearestObjectDistance()); From ccc8d28043329f7f860a2b3a1b08c562ce0644c1 Mon Sep 17 00:00:00 2001 From: godric3 Date: Sun, 21 Jan 2024 21:07:06 +0100 Subject: [PATCH 14/21] Fix inspector using wrong editor for some values --- mapeditor/inspector/inspector.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 432e40ab0..94c2b80c2 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -128,7 +128,6 @@ protected: { itemKey = keyItems[key]; table->setItem(table->row(itemKey), 1, itemValue); - if(delegate) table->setItemDelegateForRow(table->row(itemKey), delegate); } else @@ -139,7 +138,6 @@ protected: table->setRowCount(row + 1); table->setItem(row, 0, itemKey); table->setItem(row, 1, itemValue); - if(delegate) table->setItemDelegateForRow(row, delegate); ++row; } From 183612d32b645a9433ec4dc36af8d1b943fb9fdd Mon Sep 17 00:00:00 2001 From: Asirome <66029412+macomarivas@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:11:35 +0100 Subject: [PATCH 15/21] Updated spanish.ts translation --- launcher/translation/spanish.ts | 143 +++++++++++++++++--------------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index 10c94498e..ae2c67cbf 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -6,84 +6,84 @@ VCMI on Discord - VCMI en Discord + VCMI en Discord Have a question? Found a bug? Want to help? Join us! - ¿Tienes alguna pregunta? ¿Encontraste algún error? ¿Quieres ayudar? ¡Únete a nosotros! + ¿Tienes alguna pregunta? ¿Encontraste algún error? ¿Quieres ayudar? ¡Únete a nosotros! VCMI on Github - VCMI en Github + VCMI en Github Our Community - + Nuestra comunidad VCMI on Slack - VCMI en Slack + VCMI en Slack Build Information - + Información de la versión User data directory - Directorio de datos del usuario + Directorio de datos del usuario Open - Abrir + Abrir Check for updates - + Comprobar actualizaciones Game version - + Versión del juego Log files directory - Directorio de archivos de registro + Directorio de archivos de registro Data Directories - Directorios de datos + Directorios de datos Game data directory - + Directorio de los datos del juego Operating System - + Sistema operativo Project homepage - + Página web del proyecto Report a bug - + Informar de un error @@ -121,7 +121,7 @@ Maps - + Mapas @@ -180,7 +180,7 @@ Compatibility - Compatibilidad + Compatibilidad @@ -319,7 +319,7 @@ Size - + Tamaño @@ -410,12 +410,12 @@ Downloading %s%. %p% (%v MB out of %m MB) finished - + Descargando %s%. %p% (%v MB de %m MB) completado Download failed - + Descarga fallida @@ -424,30 +424,37 @@ Encountered errors: - + No se han podido descargar todos los ficheros. + +Errores encontrados: + + Install successfully downloaded? - + + +Instalar lo correctamente descargado? Installing mod %1 - + Instalando mod %1 Operation failed - + Operación fallida Encountered errors: - + Errores encontrados: + @@ -482,94 +489,94 @@ Install successfully downloaded? Interface Scaling - + Escala de la interfaz Neutral AI in battles - + IA neutral en batallas Enemy AI in battles - + IA enemiga en batallas Additional repository - + Repositorio adicional Adventure Map Allies - + Aliados en el Mapa de aventuras Adventure Map Enemies - + Enemigos en el Mapa de aventuras Windowed - + Ventana Borderless fullscreen - + Ventana completa sin bordes Exclusive fullscreen - + Pantalla completa Autosave limit (0 = off) - + Límite de autosaves (0 = sin límite) Friendly AI in battles - + IA amistosa en batallas Framerate Limit - + Límite de fotogramas Autosave prefix - + Prefijo autoguardado empty = map name prefix - + Vacio = prefijo del mapa Refresh now - + Actualizar Default repository - + Repositorio por defecto Renderer - + Render On - Encendido + Activado @@ -584,7 +591,7 @@ Install successfully downloaded? Reserved screen area - + Área de pantalla reservada @@ -614,7 +621,7 @@ Install successfully downloaded? VSync - + Sincronización vertical @@ -640,7 +647,13 @@ Windowed - game will run inside a window that covers part of your screen Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen. Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution. - + Selecciona el modo de visualización del juego + +En ventana - el juego se ejecutará dentro de una ventana que forma parte de tu pantalla. + +Ventana sin bordes - el juego se ejecutará en una ventana que cubre completamente tu pantalla, usando la misma resolución. + +Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará la resolución seleccionada. @@ -698,27 +711,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Form - + Formulario Users in lobby - + Usuarios en la sala Global chat - + Chat general type you message - + Escribe tu mensaje send - + Enviar @@ -792,12 +805,12 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Interface Improvements - + Mejora de la interfaz Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles - + Instalar mod que proporciona varias mejoras en la interfaz, como mejor interacción en los mapas aleatorios y más opciones en las batallas @@ -921,7 +934,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Czech - + Czech (Checo) @@ -936,7 +949,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Finnish - + Finnish (Finlandés) @@ -951,12 +964,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Hungarian - + Hungarian (Húngaro) Italian - + Italian (Italiano) @@ -971,7 +984,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Portuguese - + Portuguese (Portugués) @@ -986,12 +999,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Swedish - + Swedish (Sueco) Turkish - + Turkish (Turco) @@ -1001,7 +1014,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Vietnamese - + Vietnamese (Vietnamita) @@ -1011,12 +1024,12 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Other (Cyrillic Script) - + Otro (Escritura cirílica) Other (West European) - + Otro (Europa del Este) @@ -1151,7 +1164,7 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Help - + Ayuda From aff9fddd030e1d3462842ea8367eea4539c8793b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 23 Jan 2024 13:56:28 +0200 Subject: [PATCH 16/21] Version bump to 1.4.5 --- ChangeLog.md | 19 +++++++++++++++++++ android/vcmi-app/build.gradle | 4 ++-- cmake_modules/VersionDefinition.cmake | 2 +- debian/changelog | 6 ++++++ launcher/eu.vcmi.VCMI.metainfo.xml | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 20db36fee..cd7468304 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,22 @@ +# 1.4.4 -> 1.4.5 + +### Stability +* Fixed crash on creature spellcasting +* Fixed crash on unit entering magical obstacles such as quicksands +* Fixed freeze on map loading on some systems + +### Random Maps Generator +* Fixed placement of guards sometimes resulting into open connection into third zone +* Fixed rare crash on multithreaded access during placement of artifacts or wandering monsters + +### Map Editor +* Fixed inspector using wrong editor for some values + +# 1.4.3 -> 1.4.4 + +### General +* Fixed crash on generation of random maps + # 1.4.2 -> 1.4.3 ### General diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 7a88a69fd..b8ab1c6ef 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 33 - versionCode 1441 - versionName "1.4.4" + versionCode 1450 + versionName "1.4.5" setProperty("archivesBaseName", "vcmi") } diff --git a/cmake_modules/VersionDefinition.cmake b/cmake_modules/VersionDefinition.cmake index 14867224a..1e8c6240a 100644 --- a/cmake_modules/VersionDefinition.cmake +++ b/cmake_modules/VersionDefinition.cmake @@ -1,6 +1,6 @@ set(VCMI_VERSION_MAJOR 1) set(VCMI_VERSION_MINOR 4) -set(VCMI_VERSION_PATCH 4) +set(VCMI_VERSION_PATCH 5) add_definitions( -DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR} -DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR} diff --git a/debian/changelog b/debian/changelog index 144b17e94..a2ad89bfa 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vcmi (1.4.5) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Tue, 23 Jan 2024 12:00:00 +0200 + vcmi (1.4.4) jammy; urgency=medium * New upstream release diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index c043968f3..d1e30f3ad 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -76,6 +76,7 @@ + From b7662c5ec670756be122505b32ed19be1f7c2663 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 24 Jan 2024 13:43:16 +0200 Subject: [PATCH 17/21] Android build ID bump --- android/vcmi-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index b8ab1c6ef..538fc9a4c 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,7 +10,7 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 33 - versionCode 1450 + versionCode 1451 versionName "1.4.5" setProperty("archivesBaseName", "vcmi") } From 702fc8077dcf5320f0dd7042364e17a0d2f8d372 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 24 Jan 2024 13:43:35 +0200 Subject: [PATCH 18/21] Fix possible crash on invalid spell --- lib/CBonusTypeHandler.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 7244c62d2..ba86a2d8e 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -76,10 +76,10 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr & bonu if (text.find("${val}") != std::string::npos) boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)))); - if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as() != CreatureID::NONE) + if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as().hasValue()) boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as().toCreature()->getNamePluralTranslated()); - if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as() != SpellID::NONE) + if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as().hasValue()) boost::algorithm::replace_all(text, "${subtype.spell}", bonus->subtype.as().toSpell()->getNameTranslated()); return text; @@ -95,8 +95,11 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr & bonu case BonusType::SPELL_IMMUNITY: { fullPath = true; - const CSpell * sp = bonus->subtype.as().toSpell(); - fileName = sp->getIconImmune(); + if (bonus->subtype.as().hasValue()) + { + const CSpell * sp = bonus->subtype.as().toSpell(); + fileName = sp->getIconImmune(); + } break; } case BonusType::SPELL_DAMAGE_REDUCTION: //Spell damage reduction for all schools From 2f5bf6434004ac44e24460660b868517583aae22 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 24 Jan 2024 13:44:22 +0200 Subject: [PATCH 19/21] Do not crash on attempt to load campaign with unsupported maps --- Mods/vcmi/config/vcmi/english.json | 1 + client/lobby/CLobbyScreen.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 690e34700..010c2429d 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -72,6 +72,7 @@ "vcmi.lobby.noUnderground" : "no underground", "vcmi.lobby.sortDate" : "Sorts maps by change date", + "vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.modsToEnable" : "{Following mods are required}", diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 0c5a6670e..730f5e2ee 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -127,11 +127,23 @@ void CLobbyScreen::toggleTab(std::shared_ptr tab) void CLobbyScreen::startCampaign() { - if(CSH->mi) - { + if(!CSH->mi) + return; + + try { auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI); CSH->setCampaignState(ourCampaign); } + catch (const std::runtime_error &e) + { + // handle possible exception on map loading. For example campaign that contains map in unsupported format + // for example, wog campaigns or hota campaigns without hota map support mod + MetaString message; + message.appendTextID("vcmi.client.errors.invalidMap"); + message.replaceRawString(e.what()); + + CInfoWindow::showInfoDialog(message.toString(), {}); + } } void CLobbyScreen::startScenario(bool allowOnlyAI) From a2e0742ffb038a82d4bd04d29375f24b47494d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 24 Jan 2024 06:20:46 +0100 Subject: [PATCH 20/21] Fix invalid logic which made AI not attack guards --- lib/pathfinder/PathfindingRules.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pathfinder/PathfindingRules.cpp b/lib/pathfinder/PathfindingRules.cpp index 49de31ff7..ca71db460 100644 --- a/lib/pathfinder/PathfindingRules.cpp +++ b/lib/pathfinder/PathfindingRules.cpp @@ -273,9 +273,9 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking if(destination.guarded) { if (pathfinderHelper->options.ignoreGuards) - return BlockingReason::DESTINATION_GUARDED; - else return BlockingReason::NONE; + else + return BlockingReason::DESTINATION_GUARDED; } break; From d69e0b23e897c166329271389def64e57c4f4e0a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 24 Jan 2024 14:09:40 +0200 Subject: [PATCH 21/21] Update changelog --- ChangeLog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index cd7468304..78379b875 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,8 @@ * Fixed crash on creature spellcasting * Fixed crash on unit entering magical obstacles such as quicksands * Fixed freeze on map loading on some systems +* Fixed crash on attempt to start campaign with unsupported map +* Fixed crash on opening creature information window with invalid SPELL_IMMUNITY bonus ### Random Maps Generator * Fixed placement of guards sometimes resulting into open connection into third zone @@ -12,6 +14,10 @@ ### Map Editor * Fixed inspector using wrong editor for some values +### AI +* Fixed bug leading to AI not attacking wandering monsters in some cases +* Fixed crash on using StupidAI for autocombat or for enemy players + # 1.4.3 -> 1.4.4 ### General