1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Improve selection of bonus when bonus grouping is active

This commit is contained in:
Ivan Savenko
2025-05-22 14:56:22 +03:00
parent 9975852d33
commit 5e02d2a3be
2 changed files with 45 additions and 7 deletions

View File

@@ -855,7 +855,7 @@ void CStackWindow::initBonusesList()
BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all); BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all);
BonusList abilities = info->creature->getExportedBonusList(); BonusList abilities = info->creature->getExportedBonusList();
std::sort(receivedBonuses.begin(), receivedBonuses.end(), [this](const std::shared_ptr<Bonus> & v1, const std::shared_ptr<Bonus> & v2){ const auto & bonusSortingPredicate = [this](const std::shared_ptr<Bonus> & v1, const std::shared_ptr<Bonus> & v2){
if (v1->source != v2->source) if (v1->source != v2->source)
{ {
int priorityV1 = v1->source == BonusSource::CREATURE_ABILITY ? -1 : static_cast<int>(v1->source); int priorityV1 = v1->source == BonusSource::CREATURE_ABILITY ? -1 : static_cast<int>(v1->source);
@@ -864,7 +864,12 @@ void CStackWindow::initBonusesList()
} }
else else
return info->stackNode->bonusToString(v1, false) < info->stackNode->bonusToString(v2, false); return info->stackNode->bonusToString(v1, false) < info->stackNode->bonusToString(v2, false);
}); };
// these bonuses require special handling. For example they come with own descriptions, for use in morale/luck description
// also, this information is already available in creature window
receivedBonuses.remove_if(Selector::type()(BonusType::MORALE));
receivedBonuses.remove_if(Selector::type()(BonusType::LUCK));
std::vector<BonusList> groupedBonuses; std::vector<BonusList> groupedBonuses;
while (!receivedBonuses.empty()) while (!receivedBonuses.empty())
@@ -876,17 +881,49 @@ void CStackWindow::initBonusesList()
return currentBonus->type == b->type && currentBonus->subtype == b->subtype; return currentBonus->type == b->type && currentBonus->subtype == b->subtype;
}; };
groupedBonuses.push_back({}); groupedBonuses.emplace_back();
std::copy_if(receivedBonuses.begin(), receivedBonuses.end(), std::back_inserter(groupedBonuses.back()), sameBonusPredicate); std::copy_if(receivedBonuses.begin(), receivedBonuses.end(), std::back_inserter(groupedBonuses.back()), sameBonusPredicate);
receivedBonuses.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype)); receivedBonuses.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype));
// FIXME: potential edge case: unit has ability that is propagated away (and needs to be displayed), but also receives same bonus from someplace else
abilities.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype)); abilities.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype));
} }
// Add any remaining abilities of this unit that don't affect it at the moment, such as abilities that are propagated away, e.g. to other side in combat
BonusList visibleBonuses = abilities; BonusList visibleBonuses = abilities;
for (const auto & group : groupedBonuses) for (auto & group : groupedBonuses)
visibleBonuses.push_back(group.front()); {
// Try to find the bonus in the group that represents the final effect in the best way.
std::sort(group.begin(), group.end(), bonusSortingPredicate);
BonusList groupIndepMin = group;
BonusList groupIndepMax = group;
BonusList groupNoMinMax = group;
groupIndepMin.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MIN; });
groupIndepMax.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MAX; });
groupNoMinMax.remove_if([](const Bonus * b) { return b->valType == BonusValueType::INDEPENDENT_MAX || b->valType == BonusValueType::INDEPENDENT_MIN; });
int valIndepMin = groupIndepMin.totalValue();
int valIndepMax = groupIndepMax.totalValue();
int valNoMinMax = group.totalValue();
BonusList usedGroup;
if (!groupIndepMin.empty() && valNoMinMax != valIndepMin)
usedGroup = groupIndepMin; // bonus value was limited due to INDEPENDENT_MIN bonus -> show this bonus
else if (!groupIndepMax.empty() && valNoMinMax != valIndepMax)
usedGroup = groupIndepMax; // bonus value was limited due to INDEPENDENT_MAX bonus -> show this bonus
else
usedGroup = groupNoMinMax; // bonus value is not limited - show first non-independent bonus
// It is possible that empty group was selected. For example, there is only INDEPENDENT effect with value of 0, which does not actually has any effect on this unit
// For example, orb of vulnerability on unit without any resistances
if (!usedGroup.empty())
visibleBonuses.push_back(usedGroup.front());
}
std::sort(visibleBonuses.begin(), visibleBonuses.end(), bonusSortingPredicate);
BonusInfo bonusInfo; BonusInfo bonusInfo;
for(auto b : visibleBonuses) for(auto b : visibleBonuses)
@@ -898,7 +935,7 @@ void CStackWindow::initBonusesList()
//if it's possible to give any description or image for this kind of bonus //if it's possible to give any description or image for this kind of bonus
//TODO: figure out why half of bonuses don't have proper description //TODO: figure out why half of bonuses don't have proper description
if(!bonusInfo.name.empty() || !bonusInfo.imagePath.empty()) if(!bonusInfo.name.empty() || !bonusInfo.description.empty())
activeBonuses.push_back(bonusInfo); activeBonuses.push_back(bonusInfo);
} }
} }

View File

@@ -247,7 +247,8 @@ void CTownHandler::loadBuildingBonuses(const JsonNode & source, BonusList & bonu
if(!JsonUtils::parseBonus(b, bonus.get())) if(!JsonUtils::parseBonus(b, bonus.get()))
continue; continue;
bonus->description.appendTextID(building->getNameTextID()); if (bonus->description.empty() && (bonus->type == BonusType::MORALE || bonus->type == BonusType::LUCK))
bonus->description.appendTextID(building->getNameTextID());
//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty. //JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
assert(bonus->propagator == nullptr || bonus->propagator->getPropagatorType() != CBonusSystemNode::ENodeTypes::UNKNOWN); assert(bonus->propagator == nullptr || bonus->propagator->getPropagatorType() != CBonusSystemNode::ENodeTypes::UNKNOWN);