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);
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
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/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));
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;
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/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):
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));
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)
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
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;
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
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);
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
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 &&
diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp
index 56878c270..f61d04548 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)
{
@@ -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)
{