From df78c9c6f19e84345f957e8f1573d4ae554b705a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Dec 2023 18:08:22 +0200 Subject: [PATCH 01/15] Added workaround for crashes with outdated mods --- lib/mapObjectConstructors/CommonConstructors.cpp | 2 +- lib/spells/effects/Summon.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 13f286cbf..6a558b96a 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -133,7 +133,7 @@ void CHeroInstanceConstructor::afterLoadFinalization() { filters[entry.first] = LogicalExpression(entry.second, [](const JsonNode & node) { - return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value()); + return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value_or(-1)); }); } } diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index ac3c0206b..39b4dceca 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -42,6 +42,12 @@ void Summon::adjustTargetTypes(std::vector & types) const bool Summon::applicable(Problem & problem, const Mechanics * m) const { + if (creature == CreatureID::NONE) + { + logMod->error("Attempt to summon non-existing creature!"); + return m->adaptGenericProblem(problem); + } + if(exclusive) { //check if there are summoned creatures of other type From 902db091da25f4f2963678c87eb9bdb5819167ab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Dec 2023 18:09:10 +0200 Subject: [PATCH 02/15] Simple fix for slider activation when clicking on left/right buttons --- client/widgets/Slider.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index b8dfc98bf..8a37b6f46 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -156,6 +156,11 @@ void CSlider::clickPressed(const Point & cursorPosition) bool CSlider::receiveEvent(const Point &position, int eventType) const { + if (eventType == LCLICK) + { + return pos.isInside(position) && !left->pos.isInside(position) && !right->pos.isInside(position); + } + if(eventType != WHEEL && eventType != GESTURE) { return CIntObject::receiveEvent(position, eventType); From 2261298d09f54cd2f8030b5a85e6db914a5ab540 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Dec 2023 18:09:38 +0200 Subject: [PATCH 03/15] Revert U-turns block. Actually possible in H3 and has unintended side effects --- lib/pathfinder/CPathfinder.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 46d4f564b..28d510ee9 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -162,9 +162,6 @@ void CPathfinder::calculatePaths() if(neighbour->locked) continue; - if (source.node->theNodeBefore && source.node->theNodeBefore->coord == neighbour->coord ) - continue; // block U-turns - if(!hlp->isLayerAvailable(neighbour->layer)) continue; From 2de7a3939a4de12b59de0dc44e1e3314ef2ef7ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Dec 2023 18:09:57 +0200 Subject: [PATCH 04/15] Fix text identifier for generic signs without custom text --- lib/mapObjects/MiscObjects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 11c532f13..872e57ba4 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -937,7 +937,7 @@ void CGSignBottle::initObj(CRandomGenerator & rand) { auto vector = VLC->generaltexth->findStringsWithPrefix("core.randsign"); std::string messageIdentifier = *RandomGeneratorUtil::nextItem(vector, rand); - message.appendTextID(TextIdentifier("core", "randsign", messageIdentifier).get()); + message.appendTextID(messageIdentifier); } if(ID == Obj::OCEAN_BOTTLE) From 9a52131c82f48fd1a6c454cdca333e749f14a2e0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Dec 2023 18:48:53 +0200 Subject: [PATCH 05/15] Use battle side instead of player color for fire shield damage formula --- server/battles/BattleActionProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 56878c270..07180c913 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -1032,7 +1032,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const const CStack * actor = item.first; int64_t rawDamage = item.second; - const CGHeroInstance * actorOwner = battle.battleGetFightingHero(actor->unitOwner()); + const CGHeroInstance * actorOwner = battle.battleGetFightingHero(actor->unitSide()); if(actorOwner) { From ee7bd87b8d6512a7d63238f50ec4026e8dabbdab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 16:14:01 +0200 Subject: [PATCH 06/15] Fix crash on losing mission-critical hero in battle --- lib/gameState/CGameState.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 519b23e8f..5d49b350e 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1444,18 +1444,22 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio { // list of players that need to control object to fulfull condition // NOTE: cgameinfocallback specified explicitly in order to get const version - const auto & team = CGameInfoCallback::getPlayerTeam(player)->players; + const auto * team = CGameInfoCallback::getPlayerTeam(player); if (condition.objectID != ObjectInstanceID::NONE) // mode A - flag one specific object, like town { - return team.count(getObjInstance(condition.objectID)->tempOwner) != 0; + const auto * object = getObjInstance(condition.objectID); + + if (!object) + return false; + return team->players.count(object->getOwner()) != 0; } else { for(const auto & elem : map->objects) // mode B - flag all objects of this type { //check not flagged objs - if ( elem && elem->ID == condition.objectType.as() && team.count(elem->tempOwner) == 0 ) + if ( elem && elem->ID == condition.objectType.as() && team->players.count(elem->getOwner()) == 0 ) return false; } return true; From a7c838036df5d4131e73807bd211416e3c53e9cc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 16:32:43 +0200 Subject: [PATCH 07/15] Workaround to avoid crash on invalid bonus --- server/battles/BattleActionProcessor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 07180c913..f61d04548 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -1088,7 +1088,10 @@ void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bo TConstBonusListPtr spells = attacker->getBonuses(Selector::type()(attackMode)); for(const auto & sf : *spells) { - spellsToCast.insert(sf->subtype.as()); + if (sf->subtype.as() != SpellID()) + spellsToCast.insert(sf->subtype.as()); + else + logMod->error("Invalid spell to cast during attack!"); } for(SpellID spellID : spellsToCast) { From 999db2ed78c339000ec340cc21401792958cca76 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 16:33:22 +0200 Subject: [PATCH 08/15] Avoid boost::format that throws exception on invalid format string --- lib/MetaString.cpp | 5 ++++- lib/mapObjects/CGTownInstance.cpp | 13 +++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/MetaString.cpp b/lib/MetaString.cpp index 8fdb6d3a0..2845a6f38 100644 --- a/lib/MetaString.cpp +++ b/lib/MetaString.cpp @@ -168,7 +168,10 @@ DLL_LINKAGE std::string MetaString::toString() const boost::replace_first(dst, "%d", std::to_string(numbers[nums++])); break; case EMessage::REPLACE_POSITIVE_NUMBER: - boost::replace_first(dst, "%+d", '+' + std::to_string(numbers[nums++])); + if (dst.find("%+d") != std::string::npos) + boost::replace_first(dst, "%+d", '+' + std::to_string(numbers[nums++])); + else + boost::replace_first(dst, "%d", std::to_string(numbers[nums++])); break; default: logGlobal->error("MetaString processing error! Received message of type %d", static_cast(elem)); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index ab3fe7cd7..0f7c49bcf 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1224,12 +1224,21 @@ TerrainId CGTownInstance::getNativeTerrain() const GrowthInfo::Entry::Entry(const std::string &format, int _count) : count(_count) { - description = boost::str(boost::format(format) % count); + MetaString formatter; + formatter.appendRawString(format); + formatter.replacePositiveNumber(count); + + description = formatter.toString(); } GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): count(_count) { - description = boost::str(boost::format("%s %+d") % (*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated() % count); + MetaString formatter; + formatter.appendRawString("%s %+d"); + formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated()); + formatter.replacePositiveNumber(count); + + description = formatter.toString(); } GrowthInfo::Entry::Entry(int _count, std::string fullDescription): From 705718abc1cae4461018e35bab29ae97e0a4819e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:16:45 +0200 Subject: [PATCH 09/15] Do not alter case of mod description --- launcher/modManager/cmodlistview_moc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 23789707d..9bfffda43 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -313,7 +313,7 @@ QString CModListView::genModInfoText(CModEntry & mod) result += replaceIfNotEmpty(getModNames(mod.getDependencies()), lineTemplate.arg(tr("Required mods"))); result += replaceIfNotEmpty(getModNames(mod.getConflicts()), lineTemplate.arg(tr("Conflicting mods"))); - result += replaceIfNotEmpty(getModNames(mod.getValue("description").toStringList()), textTemplate.arg(tr("Description"))); + result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description"))); result += "

"; // to get some empty space From 3b6d3dee690b863a9c27a9f77d20510e26d2922e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:17:09 +0200 Subject: [PATCH 10/15] Slayer spell should only affect creatures with KING bonus --- lib/battle/DamageCalculator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index dfed47953..d735222f4 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -132,6 +132,9 @@ int DamageCalculator::getActorAttackSlayer() const const std::string cachingStrSlayer = "type_SLAYER"; static const auto selectorSlayer = Selector::type()(BonusType::SLAYER); + if (!info.defender->hasBonusOfType(BonusType::KING)) + return 0; + auto slayerEffects = info.attacker->getBonuses(selectorSlayer, cachingStrSlayer); auto slayerAffected = info.defender->unitType()->valOfBonuses(Selector::type()(BonusType::KING)); From c0572b061a71c88a08fa12dc8b336add5f72ca78 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:17:24 +0200 Subject: [PATCH 11/15] Fix compile on FreeBSD --- lib/modding/CModVersion.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modding/CModVersion.h b/lib/modding/CModVersion.h index e0ae7c8be..221decb88 100644 --- a/lib/modding/CModVersion.h +++ b/lib/modding/CModVersion.h @@ -10,7 +10,7 @@ #pragma once -#ifdef __UCLIBC__ +#if defined(__UCLIBC__) || defined(__FreeBSD__) #undef major #undef minor #undef patch From 65721123a149205e774ade32bd67451a27e4213e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:17:58 +0200 Subject: [PATCH 12/15] Partial fix for Coronius specialty bug --- lib/spells/CSpellHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 1acb58c6b..aeceb6f7f 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -107,8 +107,8 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int32_t level) const { if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS) { - logGlobal->error("CSpell::getLevelInfo: invalid school level %d", level); - return levels.at(0); + logGlobal->error("CSpell::getLevelInfo: invalid school mastery level %d", level); + return levels.at(MasteryLevel::EXPERT); } return levels.at(level); From e23cddac8c9caa2a07537031a38ffa6a523c933d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:18:15 +0200 Subject: [PATCH 13/15] Fix AI movement --- server/CGameHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4d989108f..c46241587 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1133,7 +1133,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo if (guardian && getVisitingHero(guardian) != nullptr) return complainRet("Cannot move hero, destination monster is busy!"); - if (objectToVisit && getVisitingHero(objectToVisit) != nullptr) + if (objectToVisit && getVisitingHero(objectToVisit) != nullptr && getVisitingHero(objectToVisit) != h) return complainRet("Cannot move hero, destination object is busy!"); if (objectToVisit && From b62e801530eab1d613ab082a05aafe983174ebcf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:48:27 +0200 Subject: [PATCH 14/15] Fix uninitialized variable in Seer Huts --- lib/rewardable/Limiter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index e6fd3b361..f4a03edfd 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -30,6 +30,7 @@ Rewardable::Limiter::Limiter() , heroLevel(-1) , manaPercentage(0) , manaPoints(0) + , canLearnSkills(false) , primary(GameConstants::PRIMARY_SKILLS, 0) { } @@ -45,6 +46,7 @@ bool operator==(const Rewardable::Limiter & l, const Rewardable::Limiter & r) && l.manaPoints == r.manaPoints && l.manaPercentage == r.manaPercentage && l.secondary == r.secondary + && l.canLearnSkills == r.canLearnSkills && l.creatures == r.creatures && l.spells == r.spells && l.artifacts == r.artifacts From 7187ba2d7928ba8f065391b49997a9b36967ec47 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 10 Dec 2023 19:48:44 +0200 Subject: [PATCH 15/15] Fix crash on visiting Seer Hut with no reward --- lib/mapObjects/CRewardableObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index c05fbd9aa..ce8399115 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -59,7 +59,7 @@ std::vector CRewardableObject::loadComponents(const CGHeroInstance * if (rewardIndices.empty()) return result; - if (configuration.selectMode != Rewardable::SELECT_FIRST) + if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1) { for (auto index : rewardIndices) result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));