2023-04-30 17:35:15 +03:00
|
|
|
/*
|
|
|
|
|
* CBonusSystemNode.cpp, part of VCMI engine
|
|
|
|
|
*
|
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
|
*
|
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "StdInc.h"
|
|
|
|
|
|
|
|
|
|
#include "CBonusSystemNode.h"
|
|
|
|
|
#include "Limiters.h"
|
|
|
|
|
#include "Updaters.h"
|
|
|
|
|
#include "Propagators.h"
|
|
|
|
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
constexpr bool cachingEnabled = true;
|
2023-04-30 17:35:15 +03:00
|
|
|
|
2024-01-16 18:14:40 +02:00
|
|
|
std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector)
|
2024-01-01 12:49:17 +02:00
|
|
|
{
|
|
|
|
|
auto ret = bonuses.getFirst(selector);
|
|
|
|
|
if(ret)
|
|
|
|
|
return ret;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-16 18:14:40 +02:00
|
|
|
std::shared_ptr<const Bonus> CBonusSystemNode::getFirstBonus(const CSelector & selector) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
auto ret = bonuses.getFirst(selector);
|
|
|
|
|
if(ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
TCNodes lparents;
|
2024-01-01 00:44:09 +02:00
|
|
|
getParents(lparents);
|
2024-01-01 12:49:17 +02:00
|
|
|
for(const CBonusSystemNode *pname : lparents)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-01-16 18:14:40 +02:00
|
|
|
ret = pname->getFirstBonus(selector);
|
2023-04-30 17:35:15 +03:00
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::getParents(TCNodes & out) const /*retrieves list of parent nodes (nodes to inherit bonuses from) */
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
for(const auto * elem : parentsToInherit)
|
2023-05-03 16:09:21 +03:00
|
|
|
out.insert(elem);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of parent nodes (nodes to inherit bonuses from)
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
for(auto * parent : parentsToInherit)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
Better support for Adela specialty (+new modding functionality for it)
Fixes Adela specialty that was apparently broken back in #1518 and
replaced with logic that was clearly not tested - it was neither
functional, nor it was following H3 behavior.
- `HAS_ANOTHER_BONUS_LIMITER` now accepts `null` in place of bonus type,
for cases when limiting is needed by bonus source or bonus subtype. This
allows Adela Bless specialty to always work, irregardless of which
bonuses are provided by Bless.
- Implemented `DIVIDE_STACK_LEVEL` updater that functions same as
`TIMES_STACK_LEVEL`, but it divides bonus value, instead of multiplying
it (to make Adela specialty weaker for high-tier units, as in H3)
- Implemented `TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL` updater that
combines two existing updaters, to implement `val * heroLevel /
unitLevel` formula needed for Adela specialty
- Removed deprecated `ARMY_MOVEMENT` updater. Its functionality has
already been removed in 1.6.X releases, and it was remaining only as a
placeholder
- Updated modding documentation to account for these changes & to remove
some TODO's
Fixed regression from #777 that could led to either duplicated bonuses
or to multiple application of updaters. It introduced double-recursion -
node parents were gathered recursively, and then bonuses were also
collected recursively within each parent. This created situation where
updater could be applied different number of times. For example, hero
bonus that is propagated to unit in combat could be selected directly,
or via hero->combat unit chain, or via hero->garrison unit->combat unit
chains, leading to different calls to updaters if updater handles
garrison unit node type
2025-04-11 15:04:30 +03:00
|
|
|
// Diamond found! One of the parents of the targeted node can be discovered in two ways.
|
|
|
|
|
// For example, a hero has been attached to both the player node and the global node (to which the player node is also attached).
|
|
|
|
|
// This is illegal and can be a source of duplicate bonuses.
|
|
|
|
|
assert(out.count(parent) == 0);
|
2023-04-30 17:35:15 +03:00
|
|
|
out.insert(parent);
|
|
|
|
|
parent->getAllParents(out);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 11:27:34 +03:00
|
|
|
void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
BonusList beforeUpdate;
|
|
|
|
|
|
Better support for Adela specialty (+new modding functionality for it)
Fixes Adela specialty that was apparently broken back in #1518 and
replaced with logic that was clearly not tested - it was neither
functional, nor it was following H3 behavior.
- `HAS_ANOTHER_BONUS_LIMITER` now accepts `null` in place of bonus type,
for cases when limiting is needed by bonus source or bonus subtype. This
allows Adela Bless specialty to always work, irregardless of which
bonuses are provided by Bless.
- Implemented `DIVIDE_STACK_LEVEL` updater that functions same as
`TIMES_STACK_LEVEL`, but it divides bonus value, instead of multiplying
it (to make Adela specialty weaker for high-tier units, as in H3)
- Implemented `TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL` updater that
combines two existing updaters, to implement `val * heroLevel /
unitLevel` formula needed for Adela specialty
- Removed deprecated `ARMY_MOVEMENT` updater. Its functionality has
already been removed in 1.6.X releases, and it was remaining only as a
placeholder
- Updated modding documentation to account for these changes & to remove
some TODO's
Fixed regression from #777 that could led to either duplicated bonuses
or to multiple application of updaters. It introduced double-recursion -
node parents were gathered recursively, and then bonuses were also
collected recursively within each parent. This created situation where
updater could be applied different number of times. For example, hero
bonus that is propagated to unit in combat could be selected directly,
or via hero->combat unit chain, or via hero->garrison unit->combat unit
chains, leading to different calls to updaters if updater handles
garrison unit node type
2025-04-11 15:04:30 +03:00
|
|
|
for(const auto * parent : parentsToInherit)
|
2025-06-24 11:27:34 +03:00
|
|
|
parent->getAllBonusesRec(beforeUpdate);
|
Better support for Adela specialty (+new modding functionality for it)
Fixes Adela specialty that was apparently broken back in #1518 and
replaced with logic that was clearly not tested - it was neither
functional, nor it was following H3 behavior.
- `HAS_ANOTHER_BONUS_LIMITER` now accepts `null` in place of bonus type,
for cases when limiting is needed by bonus source or bonus subtype. This
allows Adela Bless specialty to always work, irregardless of which
bonuses are provided by Bless.
- Implemented `DIVIDE_STACK_LEVEL` updater that functions same as
`TIMES_STACK_LEVEL`, but it divides bonus value, instead of multiplying
it (to make Adela specialty weaker for high-tier units, as in H3)
- Implemented `TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL` updater that
combines two existing updaters, to implement `val * heroLevel /
unitLevel` formula needed for Adela specialty
- Removed deprecated `ARMY_MOVEMENT` updater. Its functionality has
already been removed in 1.6.X releases, and it was remaining only as a
placeholder
- Updated modding documentation to account for these changes & to remove
some TODO's
Fixed regression from #777 that could led to either duplicated bonuses
or to multiple application of updaters. It introduced double-recursion -
node parents were gathered recursively, and then bonuses were also
collected recursively within each parent. This created situation where
updater could be applied different number of times. For example, hero
bonus that is propagated to unit in combat could be selected directly,
or via hero->combat unit chain, or via hero->garrison unit->combat unit
chains, leading to different calls to updaters if updater handles
garrison unit node type
2025-04-11 15:04:30 +03:00
|
|
|
|
2023-04-30 17:35:15 +03:00
|
|
|
bonuses.getAllBonuses(beforeUpdate);
|
|
|
|
|
|
|
|
|
|
for(const auto & b : beforeUpdate)
|
|
|
|
|
{
|
2025-06-24 11:27:34 +03:00
|
|
|
auto updated = b->updater ? getUpdatedBonus(b, b->updater) : b;
|
Better support for Adela specialty (+new modding functionality for it)
Fixes Adela specialty that was apparently broken back in #1518 and
replaced with logic that was clearly not tested - it was neither
functional, nor it was following H3 behavior.
- `HAS_ANOTHER_BONUS_LIMITER` now accepts `null` in place of bonus type,
for cases when limiting is needed by bonus source or bonus subtype. This
allows Adela Bless specialty to always work, irregardless of which
bonuses are provided by Bless.
- Implemented `DIVIDE_STACK_LEVEL` updater that functions same as
`TIMES_STACK_LEVEL`, but it divides bonus value, instead of multiplying
it (to make Adela specialty weaker for high-tier units, as in H3)
- Implemented `TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL` updater that
combines two existing updaters, to implement `val * heroLevel /
unitLevel` formula needed for Adela specialty
- Removed deprecated `ARMY_MOVEMENT` updater. Its functionality has
already been removed in 1.6.X releases, and it was remaining only as a
placeholder
- Updated modding documentation to account for these changes & to remove
some TODO's
Fixed regression from #777 that could led to either duplicated bonuses
or to multiple application of updaters. It introduced double-recursion -
node parents were gathered recursively, and then bonuses were also
collected recursively within each parent. This created situation where
updater could be applied different number of times. For example, hero
bonus that is propagated to unit in combat could be selected directly,
or via hero->combat unit chain, or via hero->garrison unit->combat unit
chains, leading to different calls to updaters if updater handles
garrison unit node type
2025-04-11 15:04:30 +03:00
|
|
|
out.push_back(updated);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-25 12:44:11 +02:00
|
|
|
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2025-01-10 23:45:02 +00:00
|
|
|
if (cachingEnabled)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-12-18 14:01:31 +00:00
|
|
|
// If a bonus system request comes with a caching string then look up in the map if there are any
|
|
|
|
|
// pre-calculated bonus results. Limiters can't be cached so they have to be calculated.
|
2025-01-10 23:45:02 +00:00
|
|
|
if (cachedLast == nodeChanged && !cachingStr.empty())
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-12-18 14:01:31 +00:00
|
|
|
RequestsMap::const_accessor accessor;
|
2023-04-30 17:35:15 +03:00
|
|
|
|
2024-12-18 14:01:31 +00:00
|
|
|
//Cached list contains bonuses for our query with applied limiters
|
|
|
|
|
if (cachedRequests.find(accessor, cachingStr) && accessor->second.first == cachedLast)
|
|
|
|
|
return accessor->second.second;
|
|
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
|
2024-12-18 14:01:31 +00:00
|
|
|
//We still don't have the bonuses (didn't returned them from cache)
|
|
|
|
|
//Perform bonus selection
|
|
|
|
|
auto ret = std::make_shared<BonusList>();
|
2023-04-30 17:35:15 +03:00
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
if (cachedLast == nodeChanged)
|
2024-12-18 14:01:31 +00:00
|
|
|
{
|
|
|
|
|
// Cached bonuses are up-to-date - use shared/read access and compute results
|
|
|
|
|
std::shared_lock lock(sync);
|
|
|
|
|
cachedBonuses.getBonuses(*ret, selector, limit);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
2024-12-18 14:01:31 +00:00
|
|
|
else
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-12-18 14:01:31 +00:00
|
|
|
// If the bonus system tree changes(state of a single node or the relations to each other) then
|
|
|
|
|
// cache all bonus objects. Selector objects doesn't matter.
|
|
|
|
|
std::lock_guard lock(sync);
|
2025-01-10 23:45:02 +00:00
|
|
|
if (cachedLast == nodeChanged)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-12-18 14:01:31 +00:00
|
|
|
// While our thread was waiting, another one have updated bonus tree. Use cached bonuses.
|
|
|
|
|
cachedBonuses.getBonuses(*ret, selector, limit);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
2024-12-18 14:01:31 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Cached bonuses may be outdated - regenerate them
|
|
|
|
|
BonusList allBonuses;
|
2023-04-30 17:35:15 +03:00
|
|
|
|
2024-12-18 14:01:31 +00:00
|
|
|
cachedBonuses.clear();
|
|
|
|
|
|
2025-06-24 11:27:34 +03:00
|
|
|
getAllBonusesRec(allBonuses);
|
2024-12-18 14:01:31 +00:00
|
|
|
limitBonuses(allBonuses, cachedBonuses);
|
|
|
|
|
cachedBonuses.stackBonuses();
|
2025-01-10 23:45:02 +00:00
|
|
|
cachedLast = nodeChanged;
|
2024-12-18 14:01:31 +00:00
|
|
|
cachedBonuses.getBonuses(*ret, selector, limit);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
|
|
|
|
|
// Save the results in the cache
|
2024-12-18 14:01:31 +00:00
|
|
|
if (!cachingStr.empty())
|
|
|
|
|
{
|
|
|
|
|
RequestsMap::accessor accessor;
|
|
|
|
|
if (cachedRequests.find(accessor, cachingStr))
|
|
|
|
|
{
|
|
|
|
|
accessor->second.second = ret;
|
|
|
|
|
accessor->second.first = cachedLast;
|
|
|
|
|
}
|
|
|
|
|
else
|
2025-01-10 23:45:02 +00:00
|
|
|
cachedRequests.emplace(cachingStr, std::pair<int32_t, TBonusListPtr>{ cachedLast, ret });
|
2024-12-18 14:01:31 +00:00
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-08-25 12:44:11 +02:00
|
|
|
return getAllBonusesWithoutCaching(selector, limit);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-25 12:44:11 +02:00
|
|
|
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
auto ret = std::make_shared<BonusList>();
|
|
|
|
|
|
|
|
|
|
// Get bonus results without caching enabled.
|
|
|
|
|
BonusList beforeLimiting;
|
|
|
|
|
BonusList afterLimiting;
|
2025-06-24 11:27:34 +03:00
|
|
|
getAllBonusesRec(beforeLimiting);
|
2024-08-25 12:44:11 +02:00
|
|
|
limitBonuses(beforeLimiting, afterLimiting);
|
2023-04-30 17:35:15 +03:00
|
|
|
afterLimiting.getBonuses(*ret, selector, limit);
|
|
|
|
|
ret->stackBonuses();
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const
|
|
|
|
|
{
|
|
|
|
|
assert(updater);
|
|
|
|
|
return updater->createUpdatedBonus(b, * this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
|
|
|
|
|
nodeType(UNKNOWN),
|
|
|
|
|
cachedLast(0),
|
2025-02-06 19:22:04 +00:00
|
|
|
nodeChanged(0),
|
2023-04-30 17:35:15 +03:00
|
|
|
isHypotheticNode(isHypotetic)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
|
|
|
|
|
nodeType(NodeType),
|
|
|
|
|
cachedLast(0),
|
2025-02-06 19:22:04 +00:00
|
|
|
nodeChanged(0),
|
2023-04-30 17:35:15 +03:00
|
|
|
isHypotheticNode(false)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBonusSystemNode::~CBonusSystemNode()
|
|
|
|
|
{
|
|
|
|
|
detachFromAll();
|
|
|
|
|
|
|
|
|
|
if(!children.empty())
|
|
|
|
|
{
|
|
|
|
|
while(!children.empty())
|
|
|
|
|
children.front()->detachFrom(*this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
assert(!vstd::contains(parentsToPropagate, &parent));
|
|
|
|
|
parentsToPropagate.push_back(&parent);
|
|
|
|
|
|
|
|
|
|
attachToSource(parent);
|
2023-04-30 17:35:15 +03:00
|
|
|
|
|
|
|
|
if(!isHypothetic())
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
if(!parent.actsAsBonusSourceOnly())
|
2023-04-30 17:35:15 +03:00
|
|
|
newRedDescendant(parent);
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
assert(!vstd::contains(parent.children, this));
|
|
|
|
|
parent.children.push_back(this);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
assert(!vstd::contains(parentsToInherit, &parent));
|
|
|
|
|
parentsToInherit.push_back(&parent);
|
2023-04-30 17:35:15 +03:00
|
|
|
|
|
|
|
|
if(!isHypothetic())
|
|
|
|
|
{
|
|
|
|
|
if(parent.actsAsBonusSourceOnly())
|
2024-01-01 12:49:17 +02:00
|
|
|
parent.newRedDescendant(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
2024-01-01 12:49:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
|
|
|
|
|
{
|
|
|
|
|
assert(vstd::contains(parentsToPropagate, &parent));
|
|
|
|
|
|
|
|
|
|
if(!isHypothetic())
|
|
|
|
|
{
|
|
|
|
|
if(!parent.actsAsBonusSourceOnly())
|
2023-04-30 17:35:15 +03:00
|
|
|
removedRedDescendant(parent);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
detachFromSource(parent);
|
|
|
|
|
|
|
|
|
|
if (vstd::contains(parentsToPropagate, &parent))
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
parentsToPropagate -= &parent;
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
|
|
|
|
|
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!isHypothetic())
|
|
|
|
|
{
|
2025-01-10 23:45:02 +00:00
|
|
|
if(vstd::contains(parent.children, this))
|
|
|
|
|
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);
|
|
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
|
|
|
|
|
void CBonusSystemNode::detachFromSource(const CBonusSystemNode & parent)
|
|
|
|
|
{
|
|
|
|
|
assert(vstd::contains(parentsToInherit, &parent));
|
|
|
|
|
|
|
|
|
|
if(!isHypothetic())
|
|
|
|
|
{
|
|
|
|
|
if(parent.actsAsBonusSourceOnly())
|
|
|
|
|
parent.removedRedDescendant(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 19:43:34 +02:00
|
|
|
if (vstd::contains(parentsToInherit, &parent))
|
2024-01-01 12:49:17 +02:00
|
|
|
{
|
|
|
|
|
parentsToInherit -= &parent;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
|
|
|
|
|
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
2024-01-01 12:49:17 +02:00
|
|
|
}
|
|
|
|
|
|
2023-04-30 17:35:15 +03:00
|
|
|
void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
|
|
|
|
|
{
|
|
|
|
|
removeBonuses(s);
|
|
|
|
|
for(CBonusSystemNode * child : children)
|
|
|
|
|
child->removeBonusesRecursive(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
|
|
|
|
|
{
|
|
|
|
|
BonusList bl;
|
|
|
|
|
exportedBonuses.getBonuses(bl, s, Selector::all);
|
|
|
|
|
for(const auto & b : bl)
|
|
|
|
|
{
|
|
|
|
|
b->turnsRemain--;
|
|
|
|
|
if(b->turnsRemain <= 0)
|
|
|
|
|
removeBonus(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(CBonusSystemNode *child : children)
|
|
|
|
|
child->reduceBonusDurations(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
|
|
|
|
|
{
|
|
|
|
|
//turnsRemain shouldn't be zero for following durations
|
|
|
|
|
if(Bonus::NTurns(b.get()) || Bonus::NDays(b.get()) || Bonus::OneWeek(b.get()))
|
|
|
|
|
{
|
|
|
|
|
assert(b->turnsRemain);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(!vstd::contains(exportedBonuses, b));
|
|
|
|
|
exportedBonuses.push_back(b);
|
|
|
|
|
exportBonus(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b)
|
|
|
|
|
{
|
2023-12-23 20:16:29 +01:00
|
|
|
auto bonus = exportedBonuses.getFirst(Selector::typeSubtypeValueType(b->type, b->subtype, b->valType)); //only local bonuses are interesting
|
2023-04-30 17:35:15 +03:00
|
|
|
if(bonus)
|
|
|
|
|
bonus->val += b->val;
|
|
|
|
|
else
|
|
|
|
|
addNewBonus(std::make_shared<Bonus>(*b)); //duplicate needed, original may get destroyed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
|
|
|
|
|
{
|
|
|
|
|
exportedBonuses -= b;
|
|
|
|
|
if(b->propagator)
|
2025-01-10 23:45:02 +00:00
|
|
|
{
|
2023-04-30 17:35:15 +03:00
|
|
|
unpropagateBonus(b);
|
2025-01-10 23:45:02 +00:00
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
else
|
2025-01-10 23:45:02 +00:00
|
|
|
{
|
2023-04-30 17:35:15 +03:00
|
|
|
bonuses -= b;
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
|
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::removeBonuses(const CSelector & selector)
|
|
|
|
|
{
|
|
|
|
|
BonusList toRemove;
|
|
|
|
|
exportedBonuses.getBonuses(toRemove, selector, Selector::all);
|
|
|
|
|
for(const auto & bonus : toRemove)
|
|
|
|
|
removeBonus(bonus);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CBonusSystemNode::actsAsBonusSourceOnly() const
|
|
|
|
|
{
|
|
|
|
|
switch(nodeType)
|
|
|
|
|
{
|
|
|
|
|
case CREATURE:
|
|
|
|
|
case ARTIFACT:
|
|
|
|
|
case ARTIFACT_INSTANCE:
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source)
|
|
|
|
|
{
|
|
|
|
|
if(b->propagator->shouldBeAttached(this))
|
|
|
|
|
{
|
|
|
|
|
auto propagated = b->propagationUpdater
|
|
|
|
|
? source.getUpdatedBonus(b, b->propagationUpdater)
|
|
|
|
|
: b;
|
|
|
|
|
bonuses.push_back(propagated);
|
2024-10-25 18:36:02 +00:00
|
|
|
logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName());
|
2025-06-24 11:27:34 +03:00
|
|
|
invalidateChildrenNodes(nodeChanged);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
TNodes lchildren;
|
|
|
|
|
getRedChildren(lchildren);
|
|
|
|
|
for(CBonusSystemNode *pname : lchildren)
|
|
|
|
|
pname->propagateBonus(b, source);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
|
|
|
|
|
{
|
|
|
|
|
if(b->propagator->shouldBeAttached(this))
|
|
|
|
|
{
|
2024-04-26 19:22:20 +02:00
|
|
|
if (bonuses -= b)
|
2024-10-25 18:36:02 +00:00
|
|
|
logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(nullptr), nodeName());
|
2024-04-26 19:22:20 +02:00
|
|
|
else
|
2024-10-25 18:36:02 +00:00
|
|
|
logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
|
2024-04-26 19:22:20 +02:00
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
bonuses.remove_if([this, b](const auto & bonus)
|
2024-04-26 19:22:20 +02:00
|
|
|
{
|
|
|
|
|
if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater)
|
|
|
|
|
{
|
2025-06-24 11:27:34 +03:00
|
|
|
invalidateChildrenNodes(nodeChanged);
|
2024-04-26 19:22:20 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
TNodes lchildren;
|
|
|
|
|
getRedChildren(lchildren);
|
|
|
|
|
for(CBonusSystemNode *pname : lchildren)
|
|
|
|
|
pname->unpropagateBonus(b);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::detachFromAll()
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
while(!parentsToPropagate.empty())
|
|
|
|
|
detachFrom(*parentsToPropagate.front());
|
|
|
|
|
|
|
|
|
|
while(!parentsToInherit.empty())
|
|
|
|
|
detachFromSource(*parentsToInherit.front());
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CBonusSystemNode::isIndependentNode() const
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
return parentsToInherit.empty() && parentsToPropagate.empty() && children.empty();
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string CBonusSystemNode::nodeName() const
|
|
|
|
|
{
|
2023-05-03 16:09:21 +03:00
|
|
|
return std::string("Bonus system node of type ") + typeid(*this).name();
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string CBonusSystemNode::nodeShortInfo() const
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream str;
|
|
|
|
|
str << "'" << typeid(* this).name() << "'";
|
|
|
|
|
return str.str();
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
void CBonusSystemNode::getRedParents(TCNodes & out) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-01-01 00:44:09 +02:00
|
|
|
TCNodes lparents;
|
|
|
|
|
getParents(lparents);
|
|
|
|
|
for(const CBonusSystemNode *pname : lparents)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
if(pname->actsAsBonusSourceOnly())
|
|
|
|
|
{
|
|
|
|
|
out.insert(pname);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!actsAsBonusSourceOnly())
|
|
|
|
|
{
|
2024-01-19 23:02:00 +02:00
|
|
|
for(const CBonusSystemNode *child : children)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
out.insert(child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::getRedChildren(TNodes &out)
|
|
|
|
|
{
|
2024-01-01 12:49:17 +02:00
|
|
|
for(CBonusSystemNode *pname : parentsToPropagate)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
if(!pname->actsAsBonusSourceOnly())
|
|
|
|
|
{
|
|
|
|
|
out.insert(pname);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(actsAsBonusSourceOnly())
|
|
|
|
|
{
|
|
|
|
|
for(CBonusSystemNode *child : children)
|
|
|
|
|
{
|
|
|
|
|
out.insert(child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
for(const auto & b : exportedBonuses)
|
|
|
|
|
{
|
|
|
|
|
if(b->propagator)
|
|
|
|
|
descendant.propagateBonus(b, *this);
|
|
|
|
|
}
|
2024-01-01 00:44:09 +02:00
|
|
|
TCNodes redParents;
|
2023-04-30 17:35:15 +03:00
|
|
|
getRedAncestors(redParents); //get all red parents recursively
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
for(const auto * parent : redParents)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
for(const auto & b : parent->exportedBonuses)
|
|
|
|
|
{
|
|
|
|
|
if(b->propagator)
|
|
|
|
|
descendant.propagateBonus(b, *this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-01 12:49:17 +02:00
|
|
|
void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
for(const auto & b : exportedBonuses)
|
|
|
|
|
if(b->propagator)
|
|
|
|
|
descendant.unpropagateBonus(b);
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
TCNodes redParents;
|
2023-04-30 17:35:15 +03:00
|
|
|
getRedAncestors(redParents); //get all red parents recursively
|
|
|
|
|
|
|
|
|
|
for(auto * parent : redParents)
|
|
|
|
|
{
|
|
|
|
|
for(const auto & b : parent->exportedBonuses)
|
|
|
|
|
if(b->propagator)
|
|
|
|
|
descendant.unpropagateBonus(b);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
void CBonusSystemNode::getRedAncestors(TCNodes &out) const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
getRedParents(out);
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
TCNodes redParents;
|
2023-04-30 17:35:15 +03:00
|
|
|
getRedParents(redParents);
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
for(const CBonusSystemNode * parent : redParents)
|
2023-04-30 17:35:15 +03:00
|
|
|
parent->getRedAncestors(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b)
|
|
|
|
|
{
|
|
|
|
|
if(b->propagator)
|
2025-01-10 23:45:02 +00:00
|
|
|
{
|
2023-04-30 17:35:15 +03:00
|
|
|
propagateBonus(b, *this);
|
2025-01-10 23:45:02 +00:00
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
else
|
2025-01-10 23:45:02 +00:00
|
|
|
{
|
2023-04-30 17:35:15 +03:00
|
|
|
bonuses.push_back(b);
|
2025-01-10 23:45:02 +00:00
|
|
|
nodeHasChanged();
|
|
|
|
|
}
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::exportBonuses()
|
|
|
|
|
{
|
|
|
|
|
for(const auto & b : exportedBonuses)
|
|
|
|
|
exportBonus(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const
|
|
|
|
|
{
|
|
|
|
|
return nodeType;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 19:43:34 +02:00
|
|
|
const TCNodesVector& CBonusSystemNode::getParentNodes() const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2024-01-10 19:43:34 +02:00
|
|
|
return parentsToInherit;
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type)
|
|
|
|
|
{
|
|
|
|
|
nodeType = type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BonusList & CBonusSystemNode::getExportedBonusList()
|
|
|
|
|
{
|
|
|
|
|
return exportedBonuses;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BonusList & CBonusSystemNode::getExportedBonusList() const
|
|
|
|
|
{
|
|
|
|
|
return exportedBonuses;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out) const
|
|
|
|
|
{
|
|
|
|
|
assert(&allBonuses != &out); //todo should it work in-place?
|
|
|
|
|
|
|
|
|
|
BonusList undecided = allBonuses;
|
|
|
|
|
BonusList & accepted = out;
|
|
|
|
|
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
int undecidedCount = static_cast<int>(undecided.size());
|
|
|
|
|
for(int i = 0; i < undecided.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
auto b = undecided[i];
|
|
|
|
|
BonusLimitationContext context = {*b, *this, out, undecided};
|
|
|
|
|
auto decision = b->limiter ? b->limiter->limit(context) : ILimiter::EDecision::ACCEPT; //bonuses without limiters will be accepted by default
|
2025-04-13 23:31:37 +03:00
|
|
|
if(decision == ILimiter::EDecision::DISCARD || decision == ILimiter::EDecision::NOT_APPLICABLE)
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
|
|
|
|
undecided.erase(i);
|
|
|
|
|
i--; continue;
|
|
|
|
|
}
|
|
|
|
|
else if(decision == ILimiter::EDecision::ACCEPT)
|
|
|
|
|
{
|
|
|
|
|
accepted.push_back(b);
|
|
|
|
|
undecided.erase(i);
|
|
|
|
|
i--; continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
assert(decision == ILimiter::EDecision::NOT_SURE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(undecided.size() == undecidedCount) //we haven't moved a single bonus -> limiters reached a stable state
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
void CBonusSystemNode::nodeHasChanged()
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2025-01-10 23:45:02 +00:00
|
|
|
static std::atomic<int32_t> globalCounter = 1;
|
|
|
|
|
|
|
|
|
|
invalidateChildrenNodes(++globalCounter);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 11:27:34 +03:00
|
|
|
void CBonusSystemNode::recomputePropagationUpdaters(const CBonusSystemNode & source)
|
|
|
|
|
{
|
|
|
|
|
for(const auto & b : exportedBonuses)
|
|
|
|
|
{
|
|
|
|
|
if (b->propagator && b->propagationUpdater)
|
|
|
|
|
{
|
|
|
|
|
unpropagateBonus(b);
|
|
|
|
|
propagateBonus(b, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
|
|
|
|
|
{
|
|
|
|
|
if (nodeChanged == changeCounter)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
nodeChanged = changeCounter;
|
|
|
|
|
|
2025-06-24 11:27:34 +03:00
|
|
|
recomputePropagationUpdaters(*this);
|
|
|
|
|
for(const CBonusSystemNode * parent : parentsToInherit)
|
|
|
|
|
if (parent->actsAsBonusSourceOnly())
|
|
|
|
|
recomputePropagationUpdaters(*parent);
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
for(CBonusSystemNode * child : children)
|
|
|
|
|
child->invalidateChildrenNodes(changeCounter);
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:45:02 +00:00
|
|
|
int32_t CBonusSystemNode::getTreeVersion() const
|
2023-04-30 17:35:15 +03:00
|
|
|
{
|
2025-01-10 23:45:02 +00:00
|
|
|
return nodeChanged;
|
2023-04-30 17:35:15 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-01 00:44:09 +02:00
|
|
|
VCMI_LIB_NAMESPACE_END
|