1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-17 01:32:21 +02:00

Merge pull request #5844 from IvanSavenko/bonus_fixes

Fixes to bonus system functionality
This commit is contained in:
Ivan Savenko
2025-06-30 11:58:42 +03:00
committed by GitHub
92 changed files with 565 additions and 295 deletions

View File

@ -133,11 +133,10 @@ SlotID StackWithBonuses::unitSlot() const
return slot;
}
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
const std::string & cachingStr) const
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
{
auto ret = std::make_shared<BonusList>();
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, cachingStr);
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
{
@ -147,7 +146,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
for(const Bonus & bonus : bonusesToUpdate)
{
if(selector(&bonus) && (!limit || limit(&bonus)))
if(selector(&bonus))
{
if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
{
@ -164,7 +163,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
for(auto & bonus : bonusesToAdd)
{
auto b = std::make_shared<Bonus>(bonus);
if(selector(b.get()) && (!limit || !limit(b.get())))
if(selector(b.get()))
ret->push_back(b);
}
//TODO limiters?

View File

@ -90,8 +90,7 @@ public:
SlotID unitSlot() const override;
///IBonusBearer
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
const std::string & cachingStr = "") const override;
TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
int32_t getTreeVersion() const override;

View File

@ -271,7 +271,7 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_ptr<Bonus> & bonus)
{
if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
{
// assume that this is battle wide / other side propagator+limiter
// consider it as fully relevant since we don't know about future combat when equipping artifacts
@ -290,7 +290,7 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
for (const auto & slot : hero->Slots())
{
const auto allBonuses = slot.second->getAllBonuses(Selector::all, Selector::all);
const auto allBonuses = slot.second->getAllBonuses(Selector::all);
BonusLimitationContext context = {*bonus, *slot.second, *allBonuses, stillUndecided};
uint64_t unitStrength = slot.second->getPower();
@ -526,7 +526,7 @@ int32_t getArtifactBonusScoreImpl(const std::shared_ptr<Bonus> & bonus)
int32_t getArtifactBonusScore(const std::shared_ptr<Bonus> & bonus)
{
if (bonus->propagator && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
if (bonus->propagator && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
{
if (bonus->limiter)
{

View File

@ -172,7 +172,7 @@ class TemporaryArmy : public CArmedInstance
public:
void armyChanged() override {}
TemporaryArmy()
:CArmedInstance(nullptr, true)
:CArmedInstance(nullptr, BonusNodeType::UNKNOWN, true)
{
}
};

View File

@ -31,7 +31,7 @@ public:
bool needsLastStack() const override;
std::shared_ptr<SpecialAction> getActorAction() const;
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
HeroExchangeArmy(): CArmedInstance(nullptr, BonusNodeType::UNKNOWN, true), requireBuyArmy(false) {}
};
struct ExchangeResult

View File

@ -712,13 +712,16 @@ namespace vstd
return a + (b - a) * f;
}
/// Divides dividend by divisor and rounds result up
/// Divides dividend by divisor and rounds result away from zero
/// For use with integer-only arithmetic
template<typename Integer1, typename Integer2>
Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
{
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
return (dividend + divisor - 1) / divisor;
if (dividend >= 0)
return (dividend + divisor - 1) / divisor;
else
return (dividend - divisor + 1) / divisor;
}
/// Divides dividend by divisor and rounds result to nearest
@ -727,10 +730,13 @@ namespace vstd
Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
{
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
return (dividend + divisor / 2 - 1) / divisor;
if (dividend >= 0)
return (dividend + divisor / 2 - 1) / divisor;
else
return (dividend - divisor / 2 + 1) / divisor;
}
/// Divides dividend by divisor and rounds result down
/// Divides dividend by divisor and rounds result towards zero
/// For use with integer-only arithmetic
template<typename Integer1, typename Integer2>
Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)

View File

@ -730,6 +730,7 @@
"core.bonus.SUMMON_GUARDIANS.description" : "{Summon guardians}\nAt the start of battle summons ${subtype.creature} (${val}%)",
"core.bonus.THREE_HEADED_ATTACK.description" : "{Three-headed attack}\nAttacks three adjacent units",
"core.bonus.TRANSMUTATION.description" : "{Transmutation}\n${val}% chance to transform attacked unit to a different type",
"core.bonus.TRANSMUTATION_IMMUNITY.description" : "{Transmutation Immunity}\nThis unit cannot be transformed into another unit by enemy attack",
"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "{Breath Attack}\nAttacks by this unit will also hit any unit positioned immediately behind the target",
"core.bonus.UNDEAD.description" : "{Undead}\nCreature is Undead and is immune to effects that only affect living",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "{Unlimited retaliations}\nThis unit can retaliate against an unlimited number of attacks",

View File

@ -127,8 +127,8 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const
void ArtifactsUIController::artifactRemoved()
{
for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
artWin->update();
for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
artWin->updateArtifacts();
GAME->interface()->waitWhileDialog();
}
@ -139,10 +139,10 @@ void ArtifactsUIController::artifactMoved()
numOfMovedArts--;
if(numOfMovedArts == 0)
for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
{
artWin->update();
}
{
for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
artWin->updateArtifacts();
}
GAME->interface()->waitWhileDialog();
}
@ -160,12 +160,12 @@ void ArtifactsUIController::bulkArtMovementStart(size_t totalNumOfArts, size_t p
void ArtifactsUIController::artifactAssembled()
{
for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
artWin->update();
for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
artWin->updateArtifacts();
}
void ArtifactsUIController::artifactDisassembled()
{
for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
artWin->update();
for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
artWin->updateArtifacts();
}

View File

@ -440,14 +440,14 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
return ss.str();
};
printCommandMessage("Bonuses of " + GAME->interface()->localState->getCurrentArmy()->getObjectName() + "\n");
printCommandMessage(format(*GAME->interface()->localState->getCurrentArmy()->getAllBonuses(Selector::all, Selector::all)) + "\n");
printCommandMessage(format(*GAME->interface()->localState->getCurrentArmy()->getAllBonuses(Selector::all)) + "\n");
printCommandMessage("\nInherited bonuses:\n");
TCNodes parents;
GAME->interface()->localState->getCurrentArmy()->getParents(parents);
for(const CBonusSystemNode *parent : parents)
{
printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n");
printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all)) + "\n");
}
}

View File

@ -530,6 +530,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0;
DamageEstimation retaliation;
BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
attackInfo.attackerPos = attackFromHex;
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());

View File

@ -805,7 +805,7 @@ void BattleStacksController::removeExpiredColorFilters()
{
if (!filter.persistent)
{
if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id)), Selector::all))
if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id))))
return true;
if (filter.effectColor == Colors::TRANSPARENCY && filter.transparency == 255)
return true;

View File

@ -162,6 +162,12 @@ public:
virtual void updateGarrisons() = 0;
};
class IArtifactsHolder
{
public:
virtual void updateArtifacts() = 0;
};
class IMarketHolder
{
public:

View File

@ -1476,6 +1476,13 @@ CCastleInterface::~CCastleInterface()
GAME->interface()->castleInt = nullptr;
}
void CCastleInterface::updateArtifacts()
{
// handle equipping / unequipping Legion pieces
for(auto creatureInfoBox : creainfo)
creatureInfoBox->update();
}
void CCastleInterface::updateGarrisons()
{
garr->setArmy(town->getUpperArmy(), EGarrisonType::UPPER);

View File

@ -223,7 +223,7 @@ public:
};
/// Class which manages the castle window
class CCastleInterface : public CStatusbarWindow, public IGarrisonHolder
class CCastleInterface final : public CStatusbarWindow, public IGarrisonHolder, public IArtifactsHolder
{
std::shared_ptr<CLabel> title;
std::shared_ptr<CLabel> income;
@ -257,6 +257,7 @@ public:
CCastleInterface(const CGTownInstance * Town, const CGTownInstance * from = nullptr);
~CCastleInterface();
void updateArtifacts() override;
void updateGarrisons() override;
bool holdsGarrison(const CArmedInstance * army) override;

View File

@ -848,7 +848,7 @@ void CStackWindow::init()
void CStackWindow::initBonusesList()
{
BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all);
BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent));
BonusList abilities = info->creature->getExportedBonusList();
// remove all bonuses that are not propagated away
@ -900,9 +900,12 @@ void CStackWindow::initBonusesList()
BonusList groupIndepMin = group;
BonusList groupIndepMax = group;
BonusList groupNoMinMax = group;
BonusList groupBaseOnly = 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; });
groupBaseOnly.remove_if([](const Bonus * b) { return b->valType != BonusValueType::ADDITIVE_VALUE || b->valType == BonusValueType::BASE_NUMBER; });
int valIndepMin = groupIndepMin.totalValue();
int valIndepMax = groupIndepMax.totalValue();
@ -914,8 +917,8 @@ void CStackWindow::initBonusesList()
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
else if (!groupBaseOnly.empty())
usedGroup = groupNoMinMax; // bonus value is not limited and has bonuses other than percent to base / percent to all - 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

View File

@ -261,7 +261,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
}
}
CExchangeWindow::update();
CExchangeWindow::updateArtifacts();
}
void CExchangeWindow::creatureArrowButtonCallback(bool leftToRight, SlotID slotId)
@ -361,7 +361,7 @@ void CExchangeWindow::updateGarrisons()
{
garr->recreateSlots();
update();
updateArtifacts();
}
bool CExchangeWindow::holdsGarrison(const CArmedInstance * army)
@ -375,13 +375,13 @@ void CExchangeWindow::questLogShortcut()
GAME->interface()->showQuestLog();
}
void CExchangeWindow::update()
void CExchangeWindow::updateArtifacts()
{
const bool qeLayout = isQuickExchangeLayoutAvailable();
OBJECT_CONSTRUCTION;
CWindowWithArtifacts::update();
CWindowWithArtifacts::updateArtifacts();
for(size_t leftRight : {0, 1})
{

View File

@ -75,7 +75,7 @@ public:
void keyPressed(EShortcut key) override;
void update() override;
void updateArtifacts() override;
// IGarrisonHolder impl
void updateGarrisons() override;

View File

@ -46,7 +46,7 @@ void CHeroSwitcher::clickPressed(const Point & cursorPosition)
//TODO: do not recreate window
if (false)
{
owner->update();
owner->updateArtifacts();
}
else
{
@ -153,7 +153,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
{
auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
int lines = divisionRoundUp(hero->secSkills.size(), 2);
secSkillSlider = std::make_shared<CSlider>(Point(284, 276), 189, [this](int val){ CHeroWindow::update(); }, 4, lines, 0, Orientation::VERTICAL, CSlider::BROWN);
secSkillSlider = std::make_shared<CSlider>(Point(284, 276), 189, [this](int val){ CHeroWindow::updateArtifacts(); }, 4, lines, 0, Orientation::VERTICAL, CSlider::BROWN);
secSkillSlider->setPanningStep(48);
secSkillSlider->setScrollBounds(Rect(-266, 0, secSkillSlider->pos.x - pos.x + secSkillSlider->pos.w, secSkillSlider->pos.h));
}
@ -182,14 +182,14 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[6]));
labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[7]));
CHeroWindow::update();
CHeroWindow::updateArtifacts();
}
void CHeroWindow::update()
void CHeroWindow::updateArtifacts()
{
OBJECT_CONSTRUCTION;
CWindowWithArtifacts::update();
CWindowWithArtifacts::updateArtifacts();
auto & heroscrn = LIBRARY->generaltexth->heroscrn;
assert(curHero);

View File

@ -101,7 +101,7 @@ public:
CHeroWindow(const CGHeroInstance * hero);
void update() override;
void updateArtifacts() override;
void dismissCurrent(); //dismissed currently displayed hero (curHero)
void commanderWindow();

View File

@ -85,7 +85,7 @@ void CMarketWindow::updateExperience()
void CMarketWindow::update()
{
CWindowWithArtifacts::update();
CWindowWithArtifacts::updateArtifacts();
assert(marketWidget);
marketWidget->update();
}

View File

@ -20,7 +20,8 @@ public:
void updateArtifacts() override;
void updateGarrisons() override;
void updateExperience() override;
void update() override;
void update();
void close() override;
bool holdsGarrison(const CArmedInstance * army) override;

View File

@ -164,7 +164,7 @@ void CWindowWithArtifacts::enableKeyboardShortcuts() const
artSet->enableKeyboardShortcuts();
}
void CWindowWithArtifacts::update()
void CWindowWithArtifacts::updateArtifacts()
{
for(const auto & artSet : artSets)
{

View File

@ -16,7 +16,7 @@
#include "../widgets/CArtifactsOfHeroBackpack.h"
#include "CWindowObject.h"
class CWindowWithArtifacts : virtual public CWindowObject
class CWindowWithArtifacts : virtual public CWindowObject, public IArtifactsHolder
{
public:
using CArtifactsOfHeroPtr = std::shared_ptr<CArtifactsOfHeroBase>;
@ -36,7 +36,7 @@ public:
void deactivate() override;
void enableKeyboardShortcuts() const;
virtual void update();
void updateArtifacts() override;
protected:
void markPossibleSlots() const;

View File

@ -1733,7 +1733,7 @@
"type" : "CREATURE_GROWTH",
"subtype" : "creatureLevel2",
"val" : 5,
"propagator": "VISITED_TOWN_AND_VISITOR"
"propagator": "TOWN_AND_VISITOR"
}
}
},
@ -1746,7 +1746,7 @@
"type" : "CREATURE_GROWTH",
"subtype" : "creatureLevel3",
"val" : 4,
"propagator": "VISITED_TOWN_AND_VISITOR"
"propagator": "TOWN_AND_VISITOR"
}
}
},
@ -1759,7 +1759,7 @@
"type" : "CREATURE_GROWTH",
"subtype" : "creatureLevel4",
"val" : 3,
"propagator": "VISITED_TOWN_AND_VISITOR"
"propagator": "TOWN_AND_VISITOR"
}
}
},
@ -1772,7 +1772,7 @@
"type" : "CREATURE_GROWTH",
"subtype" : "creatureLevel5",
"val" : 2,
"propagator": "VISITED_TOWN_AND_VISITOR"
"propagator": "TOWN_AND_VISITOR"
}
}
},
@ -1785,7 +1785,7 @@
"type" : "CREATURE_GROWTH",
"subtype" : "creatureLevel6",
"val" : 1,
"propagator": "VISITED_TOWN_AND_VISITOR"
"propagator": "TOWN_AND_VISITOR"
}
}
},
@ -2094,7 +2094,7 @@
"growth" : {
"type" : "CREATURE_GROWTH_PERCENT",
"val" : 50,
"propagator": "PLAYER_PROPAGATOR"
"propagator": "PLAYER"
}
}
},

View File

@ -12,7 +12,7 @@
"val": 1
},
{
"propagator": "PLAYER_PROPAGATOR",
"propagator": "PLAYER",
"type": "THIEVES_GUILD_ACCESS",
"val": 1
}
@ -130,7 +130,7 @@
"lighthouse" : {
"bonuses": [
{
"propagator": "PLAYER_PROPAGATOR",
"propagator": "PLAYER",
"type": "MOVEMENT",
"subtype": "heroMovementSea",
"val": 500
@ -257,7 +257,7 @@
"thievesGuild" : {
"bonuses": [
{
"propagator": "PLAYER_PROPAGATOR",
"propagator": "PLAYER",
"type": "THIEVES_GUILD_ACCESS",
"val": 2
}

View File

@ -383,7 +383,7 @@
{
"type" : "MORALE",
"val" : 1,
"propagator" : "HERO",
"propagator" : "ARMY",
"description" : "PLACEHOLDER",
"stacking" : "Angels"
},
@ -453,7 +453,7 @@
"raisesMorale" : {
"type" : "MORALE",
"val" : 1,
"propagator" : "HERO",
"propagator" : "ARMY",
"description" : "@creatures.core.angel.bonus.raisesMorale",
"stacking" : "Angels"
},

View File

@ -179,7 +179,7 @@
"special1": {
"bonuses": [
{
"propagator": "PLAYER_PROPAGATOR",
"propagator": "PLAYER",
"type": "MOVEMENT",
"subtype": "heroMovementSea",
"val": 500
@ -212,14 +212,14 @@
"val": 2
},
{
"propagator": "PLAYER_PROPAGATOR",
"propagator": "PLAYER",
"type": "THIEVES_GUILD_ACCESS",
"val": 1
}
],
"upgrades" : "tavern"
},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER" } ] },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },

View File

@ -188,10 +188,10 @@
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "requires" : [ "mageGuild1" ],
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER" } ] },
"special3": { "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER" } ] },
"extraTownHall": { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
"extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

View File

@ -196,7 +196,7 @@
"special3": { "type" : "treasury", "requires" : [ "horde1" ] },
"horde2": { "id" : 24, "upgrades" : "dwellingLvl5" },
"horde2Upgr": { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER" } ] },
"extraTownHall": { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
"extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

View File

@ -21,7 +21,7 @@
"type" : "MOVEMENT",
"subtype" : "heroMovementSea",
"val" : 500,
"propagator": "PLAYER_PROPAGATOR"
"propagator": "PLAYER"
}
}
}

View File

@ -43,7 +43,7 @@
"anyOf" : [
{
"type" : "string",
"enum" : [ "TIMES_HERO_LEVEL", "TIMES_STACK_LEVEL", "DIVIDE_STACK_LEVEL", "BONUS_OWNER_UPDATER", "TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL" ]
"enum" : [ "TIMES_HERO_LEVEL", "TIMES_STACK_LEVEL", "DIVIDE_STACK_LEVEL", "BONUS_OWNER_UPDATER", "TIMES_STACK_SIZE", "TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL" ]
},
{
"description" : "GROWS_WITH_LEVEL updater",
@ -107,6 +107,43 @@
"description" : "Maximal bonus value"
}
}
},
{
"description" : "TIMES_ARMY_SIZE updater",
"type" : "object",
"required" : ["type"],
"additionalProperties" : false,
"properties" : {
"type" : {
"type" : "string",
"const" : "TIMES_ARMY_SIZE",
},
"stepSize" : {
"type" : "integer",
"minimum" : 1,
"description" : "Size of each step, in levels"
},
"minimum" : {
"type" : "integer",
"description" : "Minimal bonus value"
},
"maximum" : {
"type" : "integer",
"description" : "Maximal bonus value"
},
"filteredLevel" : {
"type" : "integer",
"description" : "Level of units to count"
},
"filteredFaction" : {
"type" : "string",
"description" : "Faction of units to count"
},
"filteredCreature" : {
"type" : "string",
"description" : "Specific unit to count"
}
}
}
]
}
@ -138,7 +175,7 @@
"propagator" : {
"description" : "propagator",
"type" : "string",
"enum" : [ "BATTLE_WIDE", "VISITED_TOWN_AND_VISITOR", "PLAYER_PROPAGATOR", "HERO", "TEAM_PROPAGATOR", "GLOBAL_EFFECT" ]
"enum" : [ "BATTLE_WIDE", "TOWN_AND_VISITOR", "PLAYER", "HERO", "TOWN", "ARMY", "TEAM", "GLOBAL_EFFECT" ]
},
"updater" : {
"$ref" : "#/definitions/updater"

View File

@ -1,10 +1,22 @@
# Bonus Propagators
Propagators allow to propagate bonus effect "upwards". For example, they can be used to make unit ability battle-wide, or to provide some bonuses to hero. See [Bonus System Guide](../Guides/Bonus_System.md) for more information.
## Available propagators
- BATTLE_WIDE: Affects both sides during battle
- VISITED_TOWN_AND_VISITOR: Used with Legion artifacts (town visited by hero)
- PLAYER_PROPAGATOR: Bonus will affect all objects owned by player. Used by Statue of Legion.
- HERO: Bonus will be transferred to hero (for example from stacks in his army).
- TEAM_PROPAGATOR: Bonus will affect all objects owned by player and his allies.
- GLOBAL_EFFECT: This effect will influence all creatures, heroes and towns on the map.
- `ARMY`: Propagators that allow bonuses to be transferred to an army. It is typically used by creature abilities to affect the army that the creature is part of.
- `HERO`: Similar to `ARMY`, but works only with armies led by heroes.
- `TOWN`: Similar to `ARMY`, but only affects units that are part of the town garrison.
- `TOWN_AND_VISITOR`: Propagator that allows the town and the visiting hero to interact. It can be used to propagate the effects of town buildings to the visiting hero outside of combat or the effects of the hero to the town (e.g. Legion artifacts)
- `BATTLE_WIDE` - Propagator that allows bonuses to affect all entities in battles. It is typically used for creature abilities or artifacts that need to affect either both sides or only the enemy side in combat.
- `PLAYER`: The bonus affects all objects owned by the player. Used by the Statue of Legion.
- `TEAM`: The bonus affects all objects owned by the player and their allies.
- `GLOBAL_EFFECT`: This effect influences all creatures, towns and recruited heroes on the map.
## Deprecated propagators
These propagators are still supported, but in future they may be removed.
- `VISITED_TOWN_AND_VISITOR`: Replaced by `TOWN_AND_VISITOR`
- `PLAYER_PROPAGATOR`: Replaced by `PLAYER`
- `TEAM_PROPAGATOR`: Replaced by `TEAM`

View File

@ -84,19 +84,78 @@ Usage:
Effect: Updates val to `val = clamp(val * floor(stackSize / stepSize), minimum, maximum)`, where stackSize is total number of creatures in current unit stack
Parameters `minimum` and `maximum` are optional and can be dropped if not needed
Example of short form with default parameters:
Example:
```json
"updater" : "TIMES_STACK_SIZE"
```
Example of long form with custom parameters:
```json
"updater" : {
"type" : "TIMES_STACK_SIZE",
// Optional, by default - unlimited
"minimum" : 0,
// Optional, by default - unlimited
"maximum" : 100,
// Optional, by default - 1
"stepSize" : 2
}
```
## TIMES_ARMY_SIZE
Effect: Updates val to `val = clamp(val * floor(stackSize / stepSize), minimum, maximum)`, where stackSize is total number of creatures in hero army that fulful filter
Parameters:
- `minimum`: minimum possible value of the bonus value. Unlimited by default
- `maximum`: maximum possible value of the bonus value. Unlimited by default
- `stepSize`: number of units needed to increase updater multiplier by 1
- `filteredCreature`: identifier of specific unit to filter
- `filteredLevel`: level of units that need to be counted. Redundant if `filteredCreature` is used
- `filteredFaction`: faction of units that need to be counted. Redundant if `filteredCreature` is used
Filtering for specific unit:
```json
"updater" : {
"type" : "TIMES_ARMY_SIZE",
"filteredCreature" : "pikeman",
// Optional, by default - unlimited
"minimum" : 0,
// Optional, by default - unlimited
"maximum" : 100,
// Optional, by default - 1
"stepSize" : 2
}
```
Filtering for specific faction:
```json
"updater" : {
"type" : "TIMES_STACK_SIZE",
"filteredFaction" : "castle"
}
```
Filtering for specific unit level:
```json
"updater" : {
"type" : "TIMES_STACK_SIZE",
"filteredLevel" : 2
}
```
## BONUS_OWNER_UPDATER
Helper updater for proper functionality of `OPPOSITE_SIDE` limiter

View File

@ -313,8 +313,8 @@ si32 CCreature::maxAmount(const TResources &res) const //how many creatures can
}
CCreature::CCreature()
:CBonusSystemNode(BonusNodeType::CREATURE)
{
setNodeType(CBonusSystemNode::CREATURE);
fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
}
@ -329,7 +329,7 @@ void CCreature::addBonus(int val, BonusType type, BonusSubtypeID subtype)
BonusList & exported = getExportedBonusList();
BonusList existing;
exported.getBonuses(existing, selector, Selector::all);
exported.getBonuses(existing, selector);
if(existing.empty())
{

View File

@ -707,20 +707,22 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin
}
}
CStackInstance::CStackInstance(IGameInfoCallback *cb, bool isHypothetic)
: CBonusSystemNode(isHypothetic)
CStackInstance::CStackInstance(IGameInfoCallback *cb)
: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
{}
CStackInstance::CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic)
: CBonusSystemNode(nodeType, isHypothetic)
, CStackBasicDescriptor(nullptr, 0)
, CArtifactSet(cb)
, GameCallbackHolder(cb)
, nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE))
, initiative(this, Selector::type()(BonusType::STACKS_SPEED))
, totalExperience(0)
{
setNodeType(STACK_INSTANCE);
}
{}
CStackInstance::CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity Count, bool isHypothetic)
: CStackInstance(cb, false)
: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
{
setType(id);
setCount(Count);
@ -833,6 +835,7 @@ void CStackInstance::setCount(TQuantity newCount)
}
CStackBasicDescriptor::setCount(newCount);
nodeHasChanged();
}
std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus>& bonus) const
@ -1039,14 +1042,13 @@ CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb)
{}
CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb, const CreatureID & id)
: CStackInstance(cb)
: CStackInstance(cb, BonusNodeType::COMMANDER, false)
, name("Commando")
{
alive = true;
level = 1;
setCount(1);
setType(nullptr);
setNodeType (CBonusSystemNode::COMMANDER);
secondarySkills.resize (ECommander::SPELL_POWER + 1);
setType(id);
//TODO - parse them

View File

@ -146,7 +146,8 @@ public:
virtual int getLevel() const; //different for regular stack and commander
CreatureID getCreatureID() const; //-1 if not available
std::string getName() const; //plural or singular
CStackInstance(IGameInfoCallback *cb, bool isHypothetic = false);
CStackInstance(IGameInfoCallback *cb);
CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic = false);
CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity count, bool isHypothetic = false);
virtual ~CStackInstance() = default;

View File

@ -22,7 +22,7 @@
VCMI_LIB_NAMESPACE_BEGIN
PlayerState::PlayerState(IGameInfoCallback *cb)
: CBonusSystemNode(PLAYER)
: CBonusSystemNode(BonusNodeType::PLAYER)
, GameCallbackHolder(cb)
, color(-1)
, human(false)

View File

@ -26,7 +26,7 @@ VCMI_LIB_NAMESPACE_BEGIN
///CStack
CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
CBonusSystemNode(STACK_BATTLE),
CBonusSystemNode(BonusNodeType::STACK_BATTLE),
base(Base),
ID(I),
typeID(Base->getId()),
@ -40,7 +40,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle
}
CStack::CStack():
CBonusSystemNode(STACK_BATTLE),
CBonusSystemNode(BonusNodeType::STACK_BATTLE),
owner(PlayerColor::NEUTRAL),
slot(SlotID(255)),
initialPosition(BattleHex())
@ -48,7 +48,7 @@ CStack::CStack():
}
CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
CBonusSystemNode(STACK_BATTLE),
CBonusSystemNode(BonusNodeType::STACK_BATTLE),
ID(I),
typeID(stack->getId()),
baseAmount(stack->getCount()),
@ -134,7 +134,7 @@ std::vector<SpellID> CStack::activeSpells() const
return b->type != BonusType::NONE && b->sid.as<SpellID>().toSpell() && !b->sid.as<SpellID>().toSpell()->isAdventure();
}));
TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
TConstBonusListPtr spellEffects = getBonuses(selector, cachingStr.str());
for(const auto & it : *spellEffects)
{
if(!vstd::contains(ret, it->sid.as<SpellID>())) //do not duplicate spells with multiple effects
@ -155,7 +155,7 @@ const CGHeroInstance * CStack::getMyHero() const
return dynamic_cast<const CGHeroInstance *>(base->getArmy());
else //we are attached directly?
for(const CBonusSystemNode * n : getParentNodes())
if(n->getNodeType() == HERO)
if(n->getNodeType() == BonusNodeType::HERO)
return dynamic_cast<const CGHeroInstance *>(n);
return nullptr;

View File

@ -467,7 +467,8 @@ BattleInfo::BattleInfo(IGameInfoCallback *cb, const BattleLayout & layout):
}
BattleInfo::BattleInfo(IGameInfoCallback *cb)
:GameCallbackHolder(cb),
:CBonusSystemNode(BonusNodeType::BATTLE_WIDE),
GameCallbackHolder(cb),
sides({SideInBattle(cb), SideInBattle(cb)}),
layout(std::make_unique<BattleLayout>()),
round(-1),
@ -477,7 +478,6 @@ BattleInfo::BattleInfo(IGameInfoCallback *cb)
tacticsSide(BattleSide::NONE),
tacticDistance(0)
{
setNodeType(BATTLE);
}
BattleLayout BattleInfo::getLayout() const

View File

@ -1797,7 +1797,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const ba
std::stringstream cachingStr;
cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), Selector::all, cachingStr.str()))
if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), cachingStr.str()))
continue;
auto spellPtr = spellID.toSpell();

View File

@ -495,7 +495,7 @@ bool CUnitState::isGhost() const
bool CUnitState::isFrozen() const
{
return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::STONE_GAZE))), Selector::all);
return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::STONE_GAZE))));
}
bool CUnitState::isValidTarget(bool allowDead) const
@ -948,9 +948,9 @@ CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBear
{
}
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
{
return bonus->getAllBonuses(selector, limit, cachingStr);
return bonus->getAllBonuses(selector, cachingStr);
}
int32_t CUnitStateDetached::getTreeVersion() const

View File

@ -278,7 +278,7 @@ public:
CUnitStateDetached & operator= (const CUnitState & other);
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
int32_t getTreeVersion() const override;

View File

@ -523,7 +523,7 @@ int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelec
: Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT);
//any regular bonuses or just ones for melee/ranged
return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
return bearer->getBonuses(selector)->valOfBonuses(noLimit.Or(limitMatches));
};
DamageEstimation DamageCalculator::calculateDmgRange() const

View File

@ -276,6 +276,29 @@ enum class BonusValueType : uint8_t
#undef BONUS_VALUE
};
enum class BonusNodeType
{
NONE = -1,
UNKNOWN,
STACK_INSTANCE,
STACK_BATTLE,
ARMY,
ARTIFACT,
CREATURE,
ARTIFACT_INSTANCE,
HERO,
PLAYER,
TEAM,
TOWN_AND_VISITOR,
BATTLE_WIDE,
COMMANDER,
GLOBAL_EFFECTS,
BOAT,
TOWN
};
extern DLL_LINKAGE const std::map<std::string, BonusValueType> bonusValueMap;
extern DLL_LINKAGE const std::map<std::string, BonusSource> bonusSourceMap;
extern DLL_LINKAGE const std::map<std::string, BonusDuration::Type> bonusDurationMap;

View File

@ -70,7 +70,10 @@ int BonusList::totalValue(int baseValue) const
};
auto applyPercentageRoundUp = [](int base, int percent) -> int {
return (static_cast<int64_t>(base) * (100 + percent) + 99) / 100;
if (base >= 0)
return (static_cast<int64_t>(base) * (100 + percent) + 99) / 100;
else
return (static_cast<int64_t>(base) * (100 + percent) - 99) / 100;
};
auto applyPercentageRoundDown = [](int base, int percent) -> int {
@ -172,11 +175,11 @@ std::shared_ptr<const Bonus> BonusList::getFirst(const CSelector &selector) cons
return nullptr;
}
void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSelector &limit) const
void BonusList::getBonuses(BonusList & out, const CSelector &selector) const
{
for(const auto & b : bonuses)
{
if(selector(b.get()) && (!limit || ((bool)limit && limit(b.get()))))
if(selector(b.get()))
out.push_back(b);
}
}
@ -190,8 +193,7 @@ void BonusList::getAllBonuses(BonusList &out) const
int BonusList::valOfBonuses(const CSelector &select, int baseValue) const
{
BonusList ret;
CSelector limit = nullptr;
getBonuses(ret, select, limit);
getBonuses(ret, select);
return ret.totalValue(baseValue);
}

View File

@ -54,7 +54,7 @@ public:
// BonusList functions
void stackBonuses();
int totalValue(int baseValue = 0) const;
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
void getBonuses(BonusList &out, const CSelector &selector) const;
void getAllBonuses(BonusList &out) const;
//special find functions

View File

@ -18,6 +18,7 @@
VCMI_LIB_NAMESPACE_BEGIN
constexpr bool cachingEnabled = true;
static std::atomic<int32_t> globalCounter = 1;
std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector)
{
@ -64,27 +65,23 @@ void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of pa
}
}
void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const
void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
{
BonusList beforeUpdate;
for(const auto * parent : parentsToInherit)
parent->getAllBonusesRec(beforeUpdate, selector);
parent->getAllBonusesRec(beforeUpdate);
bonuses.getAllBonuses(beforeUpdate);
for(const auto & b : beforeUpdate)
{
//We should not run updaters on non-selected bonuses
auto updated = selector(b.get()) && b->updater
? getUpdatedBonus(b, b->updater)
: b;
auto updated = b->updater ? getUpdatedBonus(b, b->updater) : b;
out.push_back(updated);
}
}
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const std::string &cachingStr) const
{
if (cachingEnabled)
{
@ -107,7 +104,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
{
// Cached bonuses are up-to-date - use shared/read access and compute results
std::shared_lock lock(sync);
cachedBonuses.getBonuses(*ret, selector, limit);
cachedBonuses.getBonuses(*ret, selector);
}
else
{
@ -117,7 +114,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
if (cachedLast == nodeChanged)
{
// While our thread was waiting, another one have updated bonus tree. Use cached bonuses.
cachedBonuses.getBonuses(*ret, selector, limit);
cachedBonuses.getBonuses(*ret, selector);
}
else
{
@ -126,11 +123,11 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
cachedBonuses.clear();
getAllBonusesRec(allBonuses, Selector::all);
getAllBonusesRec(allBonuses);
limitBonuses(allBonuses, cachedBonuses);
cachedBonuses.stackBonuses();
cachedLast = nodeChanged;
cachedBonuses.getBonuses(*ret, selector, limit);
cachedBonuses.getBonuses(*ret, selector);
}
}
@ -151,20 +148,20 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
}
else
{
return getAllBonusesWithoutCaching(selector, limit);
return getAllBonusesWithoutCaching(selector);
}
}
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector) const
{
auto ret = std::make_shared<BonusList>();
// Get bonus results without caching enabled.
BonusList beforeLimiting;
BonusList afterLimiting;
getAllBonusesRec(beforeLimiting, selector);
getAllBonusesRec(beforeLimiting);
limitBonuses(beforeLimiting, afterLimiting);
afterLimiting.getBonuses(*ret, selector, limit);
afterLimiting.getBonuses(*ret, selector);
ret->stackBonuses();
return ret;
}
@ -175,21 +172,17 @@ std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<B
return updater->createUpdatedBonus(b, * this);
}
CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
nodeType(UNKNOWN),
CBonusSystemNode::CBonusSystemNode(BonusNodeType NodeType, bool isHypotetic):
nodeType(NodeType),
cachedLast(0),
nodeChanged(0),
isHypotheticNode(isHypotetic)
{
}
CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
nodeType(NodeType),
cachedLast(0),
nodeChanged(0),
isHypotheticNode(false)
{
}
CBonusSystemNode::CBonusSystemNode(BonusNodeType NodeType):
CBonusSystemNode(NodeType, false)
{}
CBonusSystemNode::~CBonusSystemNode()
{
@ -218,7 +211,7 @@ void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
parent.children.push_back(this);
}
nodeHasChanged();
parent.nodeHasChanged();
}
void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
@ -253,8 +246,8 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
}
else
{
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)",
nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
}
if(!isHypothetic())
@ -263,11 +256,11 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
parent.children -= this;
else
{
logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)",
nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
}
}
nodeHasChanged();
parent.nodeHasChanged();
}
@ -287,8 +280,8 @@ void CBonusSystemNode::detachFromSource(const CBonusSystemNode & parent)
}
else
{
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)",
nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
}
nodeHasChanged();
@ -304,7 +297,7 @@ void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
{
BonusList bl;
exportedBonuses.getBonuses(bl, s, Selector::all);
exportedBonuses.getBonuses(bl, s);
for(const auto & b : bl)
{
b->turnsRemain--;
@ -355,7 +348,7 @@ void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
void CBonusSystemNode::removeBonuses(const CSelector & selector)
{
BonusList toRemove;
exportedBonuses.getBonuses(toRemove, selector, Selector::all);
exportedBonuses.getBonuses(toRemove, selector);
for(const auto & bonus : toRemove)
removeBonus(bonus);
}
@ -364,9 +357,10 @@ bool CBonusSystemNode::actsAsBonusSourceOnly() const
{
switch(nodeType)
{
case CREATURE:
case ARTIFACT:
case ARTIFACT_INSTANCE:
case BonusNodeType::CREATURE:
case BonusNodeType::ARTIFACT:
case BonusNodeType::ARTIFACT_INSTANCE:
case BonusNodeType::BOAT:
return true;
default:
return false;
@ -382,7 +376,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB
: b;
bonuses.push_back(propagated);
logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName());
nodeHasChanged();
invalidateChildrenNodes(globalCounter);
}
TNodes lchildren;
@ -400,15 +394,17 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
else
logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
bonuses.remove_if([this, b](const auto & bonus)
bonuses.remove_if([b](const auto & bonus)
{
if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater)
{
nodeHasChanged();
return true;
}
return false;
});
invalidateChildrenNodes(globalCounter);
}
TNodes lchildren;
@ -550,7 +546,7 @@ void CBonusSystemNode::exportBonuses()
exportBonus(b);
}
CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const
BonusNodeType CBonusSystemNode::getNodeType() const
{
return nodeType;
}
@ -560,11 +556,6 @@ const TCNodesVector& CBonusSystemNode::getParentNodes() const
return parentsToInherit;
}
void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type)
{
nodeType = type;
}
BonusList & CBonusSystemNode::getExportedBonusList()
{
return exportedBonuses;
@ -612,11 +603,26 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
void CBonusSystemNode::nodeHasChanged()
{
static std::atomic<int32_t> globalCounter = 1;
invalidateChildrenNodes(++globalCounter);
}
void CBonusSystemNode::recomputePropagationUpdaters(const CBonusSystemNode & source)
{
for(const auto & b : exportedBonuses)
{
if (b->propagator && b->propagationUpdater)
{
unpropagateBonus(b);
propagateBonus(b, source);
}
}
for(const CBonusSystemNode * parent : source.parentsToInherit)
if (parent->actsAsBonusSourceOnly())
recomputePropagationUpdaters(*parent);
}
void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
{
if (nodeChanged == changeCounter)
@ -624,6 +630,8 @@ void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
nodeChanged = changeCounter;
recomputePropagationUpdaters(*this);
for(CBonusSystemNode * child : children)
child->invalidateChildrenNodes(changeCounter);
}

View File

@ -26,13 +26,6 @@ using TCNodesVector = std::vector<const CBonusSystemNode *>;
class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public virtual Serializeable, public boost::noncopyable
{
public:
enum ENodeTypes
{
NONE = -1,
UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM,
TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES, TOWN
};
struct HashStringCompare {
static size_t hash(const std::string& data)
{
@ -53,7 +46,7 @@ private:
TNodesVector parentsToPropagate; // we may attach our bonuses to them
TNodesVector children;
ENodeTypes nodeType;
BonusNodeType nodeType;
bool isHypotheticNode;
mutable BonusList cachedBonuses;
@ -69,8 +62,8 @@ private:
mutable RequestsMap cachedRequests;
mutable std::shared_mutex sync;
void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
void getAllBonusesRec(BonusList &out) const;
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector) const;
std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
@ -82,6 +75,7 @@ private:
void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
void unpropagateBonus(const std::shared_ptr<Bonus> & b);
void recomputePropagationUpdaters(const CBonusSystemNode & source);
bool actsAsBonusSourceOnly() const;
void newRedDescendant(CBonusSystemNode & descendant) const; //propagation needed
@ -96,11 +90,11 @@ protected:
void exportBonuses();
public:
explicit CBonusSystemNode(bool isHypotetic = false);
explicit CBonusSystemNode(ENodeTypes NodeType);
explicit CBonusSystemNode(BonusNodeType nodeType, bool isHypotetic);
explicit CBonusSystemNode(BonusNodeType nodeType);
virtual ~CBonusSystemNode();
TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
TConstBonusListPtr getAllBonuses(const CSelector &selector, const std::string &cachingStr = "") const override;
void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from),
/// Returns first bonus matching selector
@ -129,8 +123,7 @@ public:
BonusList & getExportedBonusList();
const BonusList & getExportedBonusList() const;
CBonusSystemNode::ENodeTypes getNodeType() const;
void setNodeType(CBonusSystemNode::ENodeTypes type);
BonusNodeType getNodeType() const;
const TCNodesVector & getParentNodes() const;
void nodeHasChanged();

View File

@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr, int baseValue) const
{
TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, cachingStr);
TConstBonusListPtr hlp = getAllBonuses(selector, cachingStr);
return hlp->totalValue(baseValue);
}
@ -27,19 +27,9 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const std::string &cachin
return !getBonuses(selector, cachingStr)->empty();
}
bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
{
return !getBonuses(selector, limit, cachingStr)->empty();
}
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
{
return getAllBonuses(selector, nullptr, cachingStr);
}
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
{
return getAllBonuses(selector, limit, cachingStr);
return getAllBonuses(selector, cachingStr);
}
TConstBonusListPtr IBonusBearer::getBonusesFrom(BonusSource source) const
@ -120,7 +110,7 @@ bool IBonusBearer::hasBonusFrom(BonusSource source) const
std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) const
{
auto bonuses = getAllBonuses(selector, Selector::all);
auto bonuses = getAllBonuses(selector);
return bonuses->getFirst(Selector::all);
}

View File

@ -20,11 +20,9 @@ public:
// * selector is predicate that tests if Bonus matches our criteria
IBonusBearer() = default;
virtual ~IBonusBearer() = default;
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const = 0;
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const std::string &cachingStr = {}) const = 0;
int valOfBonuses(const CSelector &selector, const std::string &cachingStr = {}, int baseValue = 0) const;
bool hasBonus(const CSelector &selector, const std::string &cachingStr = {}) const;
bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const;
TConstBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const;
TConstBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = {}) const;
std::shared_ptr<const Bonus> getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)

View File

@ -45,7 +45,7 @@ static const CStack * retrieveStackBattle(const CBonusSystemNode * node)
{
switch(node->getNodeType())
{
case CBonusSystemNode::STACK_BATTLE:
case BonusNodeType::STACK_BATTLE:
return dynamic_cast<const CStack *>(node);
default:
return nullptr;
@ -56,9 +56,9 @@ static const CStackInstance * retrieveStackInstance(const CBonusSystemNode * nod
{
switch(node->getNodeType())
{
case CBonusSystemNode::STACK_INSTANCE:
case BonusNodeType::STACK_INSTANCE:
return (dynamic_cast<const CStackInstance *>(node));
case CBonusSystemNode::STACK_BATTLE:
case BonusNodeType::STACK_BATTLE:
return (dynamic_cast<const CStack *>(node))->base;
default:
return nullptr;
@ -69,9 +69,9 @@ static const CCreature * retrieveCreature(const CBonusSystemNode *node)
{
switch(node->getNodeType())
{
case CBonusSystemNode::CREATURE:
case BonusNodeType::CREATURE:
return (dynamic_cast<const CCreature *>(node));
case CBonusSystemNode::STACK_BATTLE:
case BonusNodeType::STACK_BATTLE:
return (dynamic_cast<const CStack *>(node))->unitType();
default:
const CStackInstance * csi = retrieveStackInstance(node);
@ -262,7 +262,7 @@ CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
{
if (context.node.getNodeType() != CBonusSystemNode::STACK_BATTLE && context.node.getNodeType() != CBonusSystemNode::STACK_INSTANCE)
if (context.node.getNodeType() != BonusNodeType::STACK_BATTLE && context.node.getNodeType() != BonusNodeType::STACK_INSTANCE)
return ILimiter::EDecision::NOT_APPLICABLE;
if (terrainType == ETerrainId::NATIVE_TERRAIN)
@ -277,7 +277,7 @@ ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &
// TODO: CStack and CStackInstance need some common base type that represents any stack
// Closest existing class is ACreature, however it is also used as base for CCreature, which is not a stack
if (context.node.getNodeType() == CBonusSystemNode::STACK_BATTLE)
if (context.node.getNodeType() == BonusNodeType::STACK_BATTLE)
{
const auto * unit = dynamic_cast<const CStack *>(&context.node);
auto unitNativeTerrain = unit->getFactionID().toEntity(LIBRARY)->getNativeTerrain();
@ -294,7 +294,7 @@ ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &
}
else
{
if (context.node.getNodeType() == CBonusSystemNode::STACK_BATTLE)
if (context.node.getNodeType() == BonusNodeType::STACK_BATTLE)
{
const auto * unit = dynamic_cast<const CStack *>(&context.node);
if (unit->getCurrentTerrain() == terrainType)
@ -462,7 +462,7 @@ ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &contex
const CStackInstance * csi = retrieveStackInstance(&context.node);
if(csi)
{
if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures
if (csi->getNodeType() == BonusNodeType::COMMANDER) //no stack exp bonuses for commander creatures
return ILimiter::EDecision::DISCARD;
if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
return ILimiter::EDecision::ACCEPT;

View File

@ -16,37 +16,51 @@ VCMI_LIB_NAMESPACE_BEGIN
const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
{
{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE)},
{"VISITED_TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TOWN_AND_VISITOR)},
{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER)},
{"HERO", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO)},
{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TEAM)}, //untested
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
}; //untested
{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE)},
{"TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN_AND_VISITOR)},
{"PLAYER", std::make_shared<CPropagatorNodeType>(BonusNodeType::PLAYER)},
{"HERO", std::make_shared<CPropagatorNodeType>(BonusNodeType::HERO)},
{"TOWN", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN)},
{"ARMY", std::make_shared<CPropagatorNodeType>(BonusNodeType::ARMY)},
{"TEAM", std::make_shared<CPropagatorNodeType>(BonusNodeType::TEAM)},
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(BonusNodeType::GLOBAL_EFFECTS)},
// deprecated, for compatibility
{"VISITED_TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN_AND_VISITOR)},
{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::PLAYER)},
{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TEAM)},
};
bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) const
{
return false;
}
CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
BonusNodeType IPropagator::getPropagatorType() const
{
return CBonusSystemNode::ENodeTypes::NONE;
return BonusNodeType::NONE;
}
CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
CPropagatorNodeType::CPropagatorNodeType(BonusNodeType NodeType)
: nodeType(NodeType)
{
}
CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
BonusNodeType CPropagatorNodeType::getPropagatorType() const
{
return nodeType;
}
bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) const
{
return nodeType == dest->getNodeType();
if (nodeType == dest->getNodeType())
return true;
if (nodeType == BonusNodeType::ARMY)
return dest->getNodeType() == BonusNodeType::HERO || dest->getNodeType() == BonusNodeType::TOWN;
return false;
}
VCMI_LIB_NAMESPACE_END

View File

@ -23,7 +23,7 @@ class DLL_LINKAGE IPropagator : public Serializeable
public:
virtual ~IPropagator() = default;
virtual bool shouldBeAttached(CBonusSystemNode *dest) const;
virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
virtual BonusNodeType getPropagatorType() const;
template <typename Handler> void serialize(Handler &h)
{}
@ -31,12 +31,12 @@ public:
class DLL_LINKAGE CPropagatorNodeType : public IPropagator
{
CBonusSystemNode::ENodeTypes nodeType;
BonusNodeType nodeType;
public:
CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN);
CPropagatorNodeType(BonusNodeType NodeType = BonusNodeType::UNKNOWN);
bool shouldBeAttached(CBonusSystemNode *dest) const override;
CBonusSystemNode::ENodeTypes getPropagatorType() const override;
BonusNodeType getPropagatorType() const override;
template <typename Handler> void serialize(Handler &h)
{

View File

@ -41,7 +41,7 @@ GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize)
std::shared_ptr<Bonus> GrowsWithLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::HERO)
if(context.getNodeType() == BonusNodeType::HERO)
{
int level = dynamic_cast<const CGHeroInstance &>(context).level;
int steps = stepSize ? level / stepSize : level;
@ -74,7 +74,7 @@ JsonNode GrowsWithLevelUpdater::toJsonNode() const
std::shared_ptr<Bonus> TimesHeroLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::HERO)
if(context.getNodeType() == BonusNodeType::HERO)
{
int level = dynamic_cast<const CGHeroInstance &>(context).level;
auto newBonus = std::make_shared<Bonus>(*b);
@ -96,7 +96,7 @@ JsonNode TimesHeroLevelUpdater::toJsonNode() const
std::shared_ptr<Bonus> TimesHeroLevelDivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::HERO)
if(context.getNodeType() == BonusNodeType::HERO)
{
auto newBonus = TimesHeroLevelUpdater::createUpdatedBonus(b, context);
newBonus->updater = divideStackLevel;
@ -119,19 +119,18 @@ std::shared_ptr<Bonus> TimesStackSizeUpdater::apply(const std::shared_ptr<Bonus>
{
auto newBonus = std::make_shared<Bonus>(*b);
newBonus->val *= std::clamp(count / stepSize, minimum, maximum);
newBonus->updater = nullptr; // prevent double-apply
return newBonus;
}
std::shared_ptr<Bonus> TimesStackSizeUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
{
int count = dynamic_cast<const CStackInstance &>(context).getCount();
return apply(b, count);
}
if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
{
const auto & stack = dynamic_cast<const CStack &>(context);
return apply(b, stack.getCount());
@ -149,6 +148,44 @@ JsonNode TimesStackSizeUpdater::toJsonNode() const
return JsonNode("TIMES_STACK_SIZE");
}
std::shared_ptr<Bonus> TimesArmySizeUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == BonusNodeType::ARMY || context.getNodeType() == BonusNodeType::HERO || context.getNodeType() == BonusNodeType::TOWN)
{
const auto & army = dynamic_cast<const CArmedInstance &>(context);
int totalSize = 0;
for (const auto & unit : army.Slots())
{
if (filteredCreature.hasValue() && filteredCreature != unit.second->getCreatureID())
continue;
if (filteredFaction.hasValue() && filteredFaction != unit.second->getFactionID())
continue;
if (filteredLevel != -1 && filteredLevel != unit.second->getLevel())
continue;
totalSize += unit.second->getCount();
}
auto newBonus = std::make_shared<Bonus>(*b);
newBonus->val *= std::clamp(totalSize / stepSize, minimum, maximum);
return newBonus;
}
return b;
}
std::string TimesArmySizeUpdater::toString() const
{
return "TimesArmySizeUpdater";
}
JsonNode TimesArmySizeUpdater::toJsonNode() const
{
return JsonNode("TIMES_ARMY_SIZE");
}
std::shared_ptr<Bonus> TimesStackLevelUpdater::apply(const std::shared_ptr<Bonus> & b, int level) const
{
auto newBonus = std::make_shared<Bonus>(*b);
@ -159,13 +196,13 @@ std::shared_ptr<Bonus> TimesStackLevelUpdater::apply(const std::shared_ptr<Bonus
std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
{
int level = dynamic_cast<const CStackInstance &>(context).getLevel();
return apply(b, level);
}
if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
{
const auto & stack = dynamic_cast<const CStack &>(context);
//update if stack doesn't have an instance (summons, war machines)
@ -203,13 +240,13 @@ std::shared_ptr<Bonus> DivideStackLevelUpdater::apply(const std::shared_ptr<Bonu
std::shared_ptr<Bonus> DivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
{
int level = dynamic_cast<const CStackInstance &>(context).getLevel();
return apply(b, level);
}
if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
{
const auto & stack = dynamic_cast<const CStack &>(context);
//update if stack doesn't have an instance (summons, war machines)

View File

@ -89,9 +89,9 @@ class DLL_LINKAGE TimesStackSizeUpdater : public IUpdater
{
std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int count) const;
int minimum;
int maximum;
int stepSize;
int minimum = std::numeric_limits<int>::min();
int maximum = std::numeric_limits<int>::max();
int stepSize = 1;
public:
TimesStackSizeUpdater() = default;
TimesStackSizeUpdater(int minimum, int maximum, int stepSize)
@ -113,6 +113,33 @@ public:
}
};
class DLL_LINKAGE TimesArmySizeUpdater : public IUpdater
{
public:
int minimum = std::numeric_limits<int>::min();
int maximum = std::numeric_limits<int>::max();
int stepSize = 1;
int filteredLevel = -1;
CreatureID filteredCreature;
FactionID filteredFaction;
TimesArmySizeUpdater() = default;
std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
std::string toString() const override;
JsonNode toJsonNode() const override;
template <typename Handler> void serialize(Handler & h)
{
h & static_cast<IUpdater &>(*this);
h & minimum;
h & maximum;
h & stepSize;
h & filteredLevel;
h & filteredCreature;
h & filteredFaction;
}
};
class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
{
std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int level) const;

View File

@ -293,10 +293,10 @@ bool CChargedArtifact::getRemoveOnDepletion() const
}
CArtifact::CArtifact()
: iconIndex(ArtifactID::NONE),
: CBonusSystemNode(BonusNodeType::ARTIFACT),
iconIndex(ArtifactID::NONE),
price(0)
{
setNodeType(ARTIFACT);
possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty
possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty
possibleSlots[ArtBearer::COMMANDER];

View File

@ -224,7 +224,7 @@ CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb, const CArtifact * ar
}
CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb)
: CBonusSystemNode(ARTIFACT_INSTANCE)
: CBonusSystemNode(BonusNodeType::ARTIFACT_INSTANCE)
, CCombinedArtifactInstance(cb)
{
}

View File

@ -251,10 +251,10 @@ void CTownHandler::loadBuildingBonuses(const JsonNode & source, BonusList & bonu
bonus->description.appendTextID(building->getNameTextID());
//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() != BonusNodeType::UNKNOWN);
if(bonus->propagator != nullptr
&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
&& bonus->propagator->getPropagatorType() == BonusNodeType::UNKNOWN)
bonus->addPropagator(emptyPropagator());
building->addNewBonus(bonus, bonusList);
}

View File

@ -152,9 +152,9 @@ int CGameState::getDate(Date mode) const
}
CGameState::CGameState()
:globalEffects(BonusNodeType::GLOBAL_EFFECTS)
{
heroesPool = std::make_unique<TavernHeroesPool>(this);
globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
}
CGameState::~CGameState()
@ -1596,9 +1596,8 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
}
TeamState::TeamState()
{
setNodeType(TEAM);
}
:CBonusSystemNode(BonusNodeType::TEAM)
{}
CArtifactInstance * CGameState::createScroll(const SpellID & spellId)
{

View File

@ -386,6 +386,7 @@ static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
{"TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL", std::make_shared<TimesHeroLevelDivideStackLevelUpdater>()},
{"DIVIDE_STACK_LEVEL", std::make_shared<DivideStackLevelUpdater>()},
{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
{"TIMES_STACK_SIZE", std::make_shared<TimesStackSizeUpdater>()},
{"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
};
@ -427,6 +428,35 @@ static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
}
return std::make_shared<TimesStackSizeUpdater>(minimum, maximum, std::max(1, stepSize));
}
if(updaterJson["type"].String() == "TIMES_ARMY_SIZE")
{
auto result = std::make_shared<TimesArmySizeUpdater>();
result->minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
result->maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
result->stepSize = updaterJson["stepSize"].isNull() ? 1 : updaterJson["stepSize"].Integer();
result->filteredLevel = updaterJson["filteredLevel"].isNull() ? -1 : updaterJson["filteredLevel"].Integer();
if (result->minimum > result->maximum)
{
logMod->warn("TIMES_ARMY_SIZE updater: minimum value (%d) is above maximum value(%d)!", result->minimum, result->maximum);
std::swap(result->minimum, result->maximum);
}
if (!updaterJson["filteredFaction"].isNull())
{
LIBRARY->identifiers()->requestIdentifier( "faction", updaterJson["filteredFaction"], [result](int32_t identifier)
{
result->filteredFaction = FactionID(identifier);
});
}
if (!updaterJson["filteredCreature"].isNull())
{
LIBRARY->identifiers()->requestIdentifier( "creature", updaterJson["filteredCreature"], [result](int32_t identifier)
{
result->filteredCreature = CreatureID(identifier);
});
}
return result;
}
else
logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
break;

View File

@ -42,13 +42,13 @@ void CArmedInstance::randomizeArmy(FactionID type)
}
CArmedInstance::CArmedInstance(IGameInfoCallback *cb)
:CArmedInstance(cb, false)
:CArmedInstance(cb, BonusNodeType::ARMY, false)
{
}
CArmedInstance::CArmedInstance(IGameInfoCallback *cb, bool isHypothetic):
CArmedInstance::CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic):
CGObjectInstance(cb),
CBonusSystemNode(isHypothetic),
CBonusSystemNode(nodeType, isHypothetic),
nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
battle(nullptr)
{

View File

@ -51,7 +51,7 @@ public:
//////////////////////////////////////////////////////////////////////////
CArmedInstance(IGameInfoCallback *cb);
CArmedInstance(IGameInfoCallback *cb, bool isHypothetic);
CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic);
PlayerColor getOwner() const override
{

View File

@ -50,7 +50,11 @@ void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler)
}
CGDwelling::CGDwelling(IGameInfoCallback *cb):
CArmedInstance(cb)
CGDwelling(cb, BonusNodeType::ARMY)
{}
CGDwelling::CGDwelling(IGameInfoCallback *cb, BonusNodeType nodeType):
CArmedInstance(cb, nodeType, false)
{}
CGDwelling::~CGDwelling() = default;

View File

@ -39,6 +39,7 @@ public:
std::optional<CGDwellingRandomizationInfo> randomizationInfo; //random dwelling options; not serialized
TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
CGDwelling(IGameInfoCallback *cb, BonusNodeType nodeType);
CGDwelling(IGameInfoCallback *cb);
~CGDwelling() override;

View File

@ -244,7 +244,7 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti)
}
CGHeroInstance::CGHeroInstance(IGameInfoCallback * cb)
: CArmedInstance(cb),
: CArmedInstance(cb, BonusNodeType::HERO, false),
CArtifactSet(cb),
tacticFormationEnabled(false),
inTownGarrison(false),
@ -259,7 +259,6 @@ CGHeroInstance::CGHeroInstance(IGameInfoCallback * cb)
turnInfoCache(std::make_unique<TurnInfoCache>(this)),
manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))
{
setNodeType(HERO);
ID = Obj::HERO;
secSkills.emplace_back(SecondarySkill::NONE, -1);
}

View File

@ -206,7 +206,11 @@ int CGTownInstance::getDwellingBonus(const std::vector<CreatureID>& creatureIds,
TResources CGTownInstance::dailyIncome() const
{
TResources ret;
ResourceSet ret;
for (GameResID k : GameResID::ALL_RESOURCES())
ret[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
for(const auto & p : getTown()->buildings)
{
BuildingID buildingUpgrade;
@ -261,8 +265,9 @@ TownFortifications CGTownInstance::fortificationsLevel() const
}
CGTownInstance::CGTownInstance(IGameInfoCallback *cb):
CGDwelling(cb),
CGDwelling(cb, BonusNodeType::TOWN),
IMarket(cb),
townAndVis(BonusNodeType::TOWN_AND_VISITOR),
built(0),
destroyed(0),
identifier(0),
@ -271,7 +276,6 @@ CGTownInstance::CGTownInstance(IGameInfoCallback *cb):
spellResearchAcceptedCounter(0),
spellResearchAllowed(true)
{
setNodeType(CBonusSystemNode::TOWN);
attachTo(townAndVis);
}
@ -1209,11 +1213,6 @@ GrowthInfo::Entry::Entry(int _count, std::string fullDescription):
{
}
CTownAndVisitingHero::CTownAndVisitingHero()
{
setNodeType(TOWN_AND_VISITOR);
}
int GrowthInfo::totalGrowth() const
{
int ret = 0;

View File

@ -26,12 +26,6 @@ struct DamageRange;
template<typename ContainedClass>
class LogicalExpression;
class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
{
public:
CTownAndVisitingHero();
};
struct DLL_LINKAGE GrowthInfo
{
struct Entry
@ -61,7 +55,7 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I
public:
enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
CTownAndVisitingHero townAndVis;
CBonusSystemNode townAndVis;
si32 built; //how many buildings has been built this turn
si32 destroyed; //how many buildings has been destroyed this turn
ui32 identifier; //special identifier from h3m (only > RoE maps)

View File

@ -22,6 +22,11 @@
VCMI_LIB_NAMESPACE_BEGIN
FlaggableMapObject::FlaggableMapObject(IGameInfoCallback *cb)
:CGObjectInstance(cb)
,CBonusSystemNode(BonusNodeType::UNKNOWN)
{}
const IOwnableObject * FlaggableMapObject::asOwnable() const
{
return this;

View File

@ -25,7 +25,7 @@ class DLL_LINKAGE FlaggableMapObject final : public CGObjectInstance, public IOw
void initBonuses();
public:
using CGObjectInstance::CGObjectInstance;
FlaggableMapObject(IGameInfoCallback *cb);
void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
void initObj(IGameRandomizer & gameRandomizer) override;

View File

@ -142,6 +142,10 @@ std::vector<CreatureID> CGMine::providedCreatures() const
ResourceSet CGMine::dailyIncome() const
{
ResourceSet result;
for (GameResID k : GameResID::ALL_RESOURCES())
result[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
result[producedResource] += defaultResProduction();
const auto & playerSettings = cb->getPlayerSettings(getOwner());
@ -867,7 +871,11 @@ const IOwnableObject * CGGarrison::asOwnable() const
ResourceSet CGGarrison::dailyIncome() const
{
return {};
ResourceSet result;
for (GameResID k : GameResID::ALL_RESOURCES())
result[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
return result;
}
std::vector<CreatureID> CGGarrison::providedCreatures() const
@ -930,7 +938,7 @@ void CGGarrison::addAntimagicGarrisonBonus()
bonus->type = BonusType::BLOCK_ALL_MAGIC;
bonus->source = BonusSource::OBJECT_TYPE;
bonus->sid = BonusSourceID(this->ID);
bonus->propagator = std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE);
bonus->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE);
bonus->duration = BonusDuration::PERMANENT;
this->addNewBonus(bonus);
}
@ -987,6 +995,7 @@ void CGMagi::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance *
CGBoat::CGBoat(IGameInfoCallback * cb)
: CGObjectInstance(cb)
, CBonusSystemNode(BonusNodeType::BOAT)
{
direction = 4;
layer = EPathfindingLayer::SAIL;

View File

@ -86,7 +86,7 @@ void registerTypes(Serializer &s)
s.template registerType<CGUniversity>(23);
s.template registerType<CGHeroPlaceholder>(24);
s.template registerType<CArmedInstance>(25);
s.template registerType<CBonusSystemNode>(26);
// s.template registerType<CBonusSystemNode>(26);
s.template registerType<CCreatureSet>(27);
s.template registerType<CGHeroInstance>(28);
s.template registerType<CGDwelling>(30);
@ -298,6 +298,8 @@ void registerTypes(Serializer &s)
s.template registerType<DivideStackLevelUpdater>(246);
s.template registerType<SetHeroExperience>(247);
s.template registerType<GiveStackExperience>(248);
s.template registerType<TimesStackSizeUpdater>(249);
s.template registerType<TimesArmySizeUpdater>(250);
}
VCMI_LIB_NAMESPACE_END

View File

@ -317,7 +317,7 @@ bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const IGam
std::stringstream cachingStr;
cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), Selector::all, cachingStr.str())->size();
int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
int castsLimit = owner->getLevelPower(schoolLevel);
bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);

View File

@ -246,21 +246,22 @@ int BonusBearerProxy::getBonuses(lua_State * L)
if(hasRangeSelector)
{
auto rangeSelector = [](const Bonus * b)
{
return false;//TODO: BonusBearerProxy::getBonuses rangeSelector
};
//TODO: BonusBearerProxy::getBonuses rangeSelector
//auto rangeSelector = [](const Bonus * b)
//{
// return false;
//};
ret = object->getBonuses(selector, rangeSelector);
ret = object->getBonuses(selector);
}
else
{
ret = object->getBonuses(selector, Selector::all);
ret = object->getBonuses(selector);
}
}
else
{
ret = object->getBonuses(Selector::all, Selector::all);
ret = object->getBonuses(Selector::all);
}
S.clear();

View File

@ -83,13 +83,13 @@ public:
void redirectBonusesToFake()
{
ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
ON_CALL(*this, getAllBonuses(_, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
}
void expectAnyBonusSystemCall()
{
EXPECT_CALL(*this, getAllBonuses(_, _, _)).Times(AtLeast(0));
EXPECT_CALL(*this, getAllBonuses(_, _)).Times(AtLeast(0));
EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -29,7 +29,7 @@ public:
void setDefaultExpectations()
{
EXPECT_CALL(mock, getAllBonuses(_, _, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
EXPECT_CALL(mock, getAllBonuses(_, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, UNIT_HEALTH, BonusSourceID()));
@ -235,7 +235,7 @@ TEST_F(HealthTest, singleUnitStack)
//one Titan
EXPECT_CALL(mock, getAllBonuses(_, _, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
EXPECT_CALL(mock, getAllBonuses(_, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, 300, BonusSourceID()));

View File

@ -34,13 +34,13 @@ void UnitFake::makeDead()
void UnitFake::redirectBonusesToFake()
{
ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
ON_CALL(*this, getAllBonuses(_, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
}
void UnitFake::expectAnyBonusSystemCall()
{
EXPECT_CALL(*this, getAllBonuses(_, _, _)).Times(AtLeast(0));
EXPECT_CALL(*this, getAllBonuses(_, _)).Times(AtLeast(0));
EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -25,7 +25,7 @@ void BonusBearerMock::addNewBonus(const std::shared_ptr<Bonus> & b)
treeVersion++;
}
TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
{
if(cachedLast != treeVersion)
{
@ -34,7 +34,7 @@ TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, co
}
auto ret = std::make_shared<BonusList>();
bonuses.getBonuses(*ret, selector, limit);
bonuses.getBonuses(*ret, selector);
return ret;
}

View File

@ -23,7 +23,7 @@ public:
void addNewBonus(const std::shared_ptr<Bonus> & b);
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
int32_t getTreeVersion() const override;
private:

View File

@ -15,7 +15,7 @@
class UnitMock : public battle::Unit
{
public:
MOCK_CONST_METHOD3(getAllBonuses, TConstBonusListPtr(const CSelector &, const CSelector &, const std::string &));
MOCK_CONST_METHOD2(getAllBonuses, TConstBonusListPtr(const CSelector &, const std::string &));
MOCK_CONST_METHOD0(getTreeVersion, int32_t());
MOCK_CONST_METHOD0(getCasterUnitId, int32_t());

View File

@ -33,7 +33,7 @@ public:
protected:
void SetUp() override
{
ON_CALL(actualCaster, getAllBonuses(_, _, _)).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getAllBonuses));
ON_CALL(actualCaster, getAllBonuses(_, _)).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getAllBonuses));
ON_CALL(actualCaster, getTreeVersion()).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getTreeVersion));
}
@ -57,7 +57,7 @@ TEST_F(AbilityCasterTest, MagicAbilityAffectedByGenericBonus)
casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::ANY)));
EXPECT_CALL(actualCaster, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(actualCaster, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
setupSubject(1);
@ -71,7 +71,7 @@ TEST_F(AbilityCasterTest, MagicAbilityIgnoresSchoolBonus)
casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::AIR)));
EXPECT_CALL(actualCaster, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(actualCaster, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
setupSubject(1);

View File

@ -24,7 +24,7 @@ public:
void setDefaultExpectations()
{
EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(true));
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -24,7 +24,7 @@ public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
}

View File

@ -21,7 +21,7 @@ class BonusConditionTest : public TargetConditionItemTest
public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -21,7 +21,7 @@ class CreatureConditionTest : public TargetConditionItemTest
public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(0);
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(0);
EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
}

View File

@ -23,7 +23,7 @@ public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
EXPECT_CALL(mechanicsMock, getSpell()).Times(AtLeast(1)).WillRepeatedly(Return(&spellMock));

View File

@ -23,7 +23,7 @@ public:
const int64_t EFFECT_VALUE = 101;
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(0);
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(0);
EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
EXPECT_CALL(unitMock, getAvailableHealth()).WillOnce(Return(UNIT_HP));
EXPECT_CALL(mechanicsMock, getEffectValue()).WillOnce(Return(EFFECT_VALUE));

View File

@ -30,7 +30,7 @@ public:
{
ownerMatches = ::testing::get<0>(GetParam());
isMagicalEffect = ::testing::get<1>(GetParam());
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(0));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(0));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
EXPECT_CALL(mechanicsMock, isMagicalEffect()).Times(AtLeast(0)).WillRepeatedly(Return(isMagicalEffect));
EXPECT_CALL(mechanicsMock, ownerMatches(Eq(&unitMock), Field(&boost::logic::tribool::value, boost::logic::tribool::false_value))).WillRepeatedly(Return(ownerMatches));

View File

@ -27,7 +27,7 @@ public:
isMagicalEffect = GetParam();
EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(isMagicalEffect));
if(isMagicalEffect)
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -24,7 +24,7 @@ public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
}

View File

@ -27,7 +27,7 @@ public:
isPositive = ::testing::get<0>(GetParam());
hasBonus = ::testing::get<1>(GetParam());
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(0));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(0));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
EXPECT_CALL(mechanicsMock, isPositiveSpell()).WillRepeatedly(Return(isPositive));
if(hasBonus)

View File

@ -21,7 +21,7 @@ class SpellEffectConditionTest : public TargetConditionItemTest
public:
void setDefaultExpectations()
{
EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
}

View File

@ -37,7 +37,7 @@ protected:
void SetUp() override
{
using namespace ::testing;
ON_CALL(unitMock, getAllBonuses(_, _, _)).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getAllBonuses));
ON_CALL(unitMock, getAllBonuses(_, _)).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getAllBonuses));
ON_CALL(unitMock, getTreeVersion()).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getTreeVersion));
}
};