From de7bfcaa8d8de5a86e5b3e6c7db8dc4f60741e20 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 May 2026 22:32:31 +0300 Subject: [PATCH] Fixes for crashes in 1.7.4 beta - Fixed crash in MP between 1.7.3 and 1.7.4 on opening exchange window - Fixed crash if creature with area attack and death blow attacks empty space (Magog in WoG) - Fixed crash on formatting of spell effect preview text - Fixed crash if hero has invalid path (e.g. blocked by another hero) and player attempts to end turn - Try to fix crash on opening battle-only mode in some cases(?) --- android/vcmi-app/build.gradle | 2 +- client/NetPacksLobbyClient.cpp | 2 +- client/PlayerLocalState.cpp | 8 +++----- client/PlayerLocalState.h | 2 +- client/adventureMap/AdventureMapShortcuts.cpp | 7 ++++--- client/battle/BattleActionsController.cpp | 17 ++++++++--------- client/battle/BattleStacksController.cpp | 11 +++++++---- lib/bonuses/BonusParameters.h | 4 ++-- lib/networkPacks/PacksForClient.h | 3 ++- lib/serializer/ESerializationVersion.h | 4 +++- 10 files changed, 32 insertions(+), 28 deletions(-) diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 91683727a..e97c47603 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -35,7 +35,7 @@ android { minSdk = qtMinSdkVersion as Integer targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project - versionCode 1754 + versionCode 1757 versionName "1.7.4" } diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 5f6645b9f..f34f40fd2 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -261,6 +261,6 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyShowMessage(LobbyShowMessage & void ApplyOnLobbyScreenNetPackVisitor::visitLobbySetBattleOnlyModeStartInfo(LobbySetBattleOnlyModeStartInfo & pack) { - if(lobby->tabBattleOnlyMode) + if(lobby && lobby->tabBattleOnlyMode) lobby->tabBattleOnlyMode->applyStartInfo(pack.startInfo); } diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index ee62d61eb..fc85b8d23 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -84,12 +84,10 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h) synchronizeState(); } -bool PlayerLocalState::verifyPath(const CGHeroInstance * h) +void PlayerLocalState::verifyPath(const CGHeroInstance * h) { - if(!hasPath(h)) - return false; - setPath(h, getPath(h).endPos()); - return true; + if (hasPath(h)) + setPath(h, getPath(h).endPos()); } SpellID PlayerLocalState::getCurrentSpell() const diff --git a/client/PlayerLocalState.h b/client/PlayerLocalState.h index 486489f58..275468218 100644 --- a/client/PlayerLocalState.h +++ b/client/PlayerLocalState.h @@ -84,7 +84,7 @@ public: void removeLastNode(const CGHeroInstance * h); void erasePath(const CGHeroInstance * h); - bool verifyPath(const CGHeroInstance * h); + void verifyPath(const CGHeroInstance * h); /// Returns currently selected object const CGHeroInstance * getCurrentHero() const; diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 53095052a..586efce01 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -307,11 +307,12 @@ void AdventureMapShortcuts::endTurn() { if(!GAME->interface()->localState->isHeroSleeping(hero) && hero->movementPointsRemaining() > 0) { + GAME->interface()->localState->verifyPath(hero); + // Only show hero reminder if conditions are met: // - There are still movement points - // - Hero doesn't have a path or there are no points for the first step on path - - if(!GAME->interface()->localState->verifyPath(hero)) + // - Hero doesn't have a path or there are enough points for the first step on path + if(!GAME->interface()->localState->hasPath(hero)) { showMoveReminderDialog(); return; diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 73ef26479..76f70d7f3 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -135,16 +135,15 @@ static std::string formatRetaliation(const DamageEstimation & estimation, bool m } static std::string prepareSpellEffectText(int gnrlTextID, const spells::effects::SpellEffectValue & value, - std::string_view spellName, std::string_view targetName) + const std::string & spellName, const std::string & targetName) { - auto const & templateText = LIBRARY->generaltexth->allTexts[gnrlTextID]; - std::string baseText; - if(!targetName.empty() && !spellName.empty()) - baseText = boost::str(boost::format(templateText) % spellName % targetName); - else if(targetName.empty()) - baseText = boost::str(boost::format(templateText) % spellName); - else - baseText = boost::str(boost::format(templateText) % targetName); + auto templateText = MetaString::createFromTextID( "core.genrltxt." + std::to_string(gnrlTextID)); + if (!spellName.empty()) + templateText.replaceRawString(spellName); + if (!targetName.empty()) + templateText.replaceRawString(targetName); + + std::string baseText = templateText.toString(); if(value.unitsDelta > 0) { diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 7995ec408..97e544678 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -614,10 +614,13 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if(info.deathBlow) { - owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [this, defender, info]() { - owner.appendBattleLog(info.attacker->formatGeneralMessage(365)); - owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, AudioPath::builtin("DEATHBLO"), defender->getPosition()); - }); + if (defender) + { + owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [this, defender, info]() { + owner.appendBattleLog(info.attacker->formatGeneralMessage(365)); + owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, AudioPath::builtin("DEATHBLO"), defender->getPosition()); + }); + } for(auto elem : info.secondaryDefender) { diff --git a/lib/bonuses/BonusParameters.h b/lib/bonuses/BonusParameters.h index 3b5e83d4c..e3b6c2d5d 100644 --- a/lib/bonuses/BonusParameters.h +++ b/lib/bonuses/BonusParameters.h @@ -97,7 +97,7 @@ public: auto * result = std::get_if(&data_); if (result) return *result; - throw std::runtime_error("Invalid addInfo type access!"); + throw std::runtime_error("Invalid addInfo type access! Stored type: " + std::to_string(data_.index())); } template @@ -106,7 +106,7 @@ public: auto * result = std::get_if(&data_); if (result) return *result; - throw std::runtime_error("Invalid addInfo type access!"); + throw std::runtime_error("Invalid addInfo type access! Stored type: " + std::to_string(data_.index())); } template diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 215b46cd1..364d1ee7d 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1402,7 +1402,8 @@ struct DLL_LINKAGE GarrisonDialog : public Query h & objid; h & hid; h & removableUnits; - h & customTitle; + if (h.hasFeature(Handler::Version::CUSTOM_GARRISON_TITLE)) + h & customTitle; } }; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 3011bf629..e2ea12008 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -62,9 +62,11 @@ enum class ESerializationVersion : int32_t DISABLE_TACTICS, // disable tactics REWARDABLE_EXTENSIONS_2, // movement points limiter for rewardables BONUS_TRIGGER, // bonus that allows triggered effects in combat + CUSTOM_GARRISON_TITLE, // GarrisonDialog pack now has custom title parameter RELEASE_170 = HOTA_MAP_STACK_COUNT, - CURRENT = BONUS_TRIGGER, + RELEASE_174 = CUSTOM_GARRISON_TITLE, + CURRENT = CUSTOM_GARRISON_TITLE, }; static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");