diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index c1a3491ed..9ed4411d0 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -22,8 +22,10 @@ #include "../../lib/CGameState.h" #include "../../lib/NetPacksBase.h" #include "../../lib/NetPacks.h" +#include "../../lib/bonuses/CBonusSystemNode.h" #include "../../lib/bonuses/Limiters.h" #include "../../lib/bonuses/Updaters.h" +#include "../../lib/bonuses/Propagators.h" #include "../../lib/serializer/CTypeList.h" #include "../../lib/serializer/BinarySerializer.h" #include "../../lib/serializer/BinaryDeserializer.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index eabfc9429..339bf6819 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -43,8 +43,10 @@ #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" +#include "../lib/bonuses/CBonusSystemNode.h" #include "../lib/bonuses/Limiters.h" #include "../lib/bonuses/Updaters.h" +#include "../lib/bonuses/Propagators.h" #include "../lib/serializer/CTypeList.h" #include "../lib/serializer/BinaryDeserializer.h" #include "../lib/serializer/BinarySerializer.h" diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index e58b11838..5e5726281 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -27,9 +27,12 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/SiegeInfo.cpp ${MAIN_LIB_DIR}/battle/Unit.cpp + ${MAIN_LIB_DIR}/bonuses/BonusParams.cpp ${MAIN_LIB_DIR}/bonuses/CBonusProxy.cpp + ${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.cpp ${MAIN_LIB_DIR}/bonuses/HeroBonus.cpp ${MAIN_LIB_DIR}/bonuses/Limiters.cpp + ${MAIN_LIB_DIR}/bonuses/Propagators.cpp ${MAIN_LIB_DIR}/bonuses/Updaters.cpp ${MAIN_LIB_DIR}/events/ApplyDamage.cpp @@ -304,9 +307,12 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/SiegeInfo.h ${MAIN_LIB_DIR}/battle/Unit.h + ${MAIN_LIB_DIR}/bonuses/BonusParams.h ${MAIN_LIB_DIR}/bonuses/CBonusProxy.h + ${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.h ${MAIN_LIB_DIR}/bonuses/HeroBonus.h ${MAIN_LIB_DIR}/bonuses/Limiters.h + ${MAIN_LIB_DIR}/bonuses/Propagators.h ${MAIN_LIB_DIR}/bonuses/Updaters.h ${MAIN_LIB_DIR}/events/ApplyDamage.h diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 1a2f55260..3d5f26ea4 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -13,6 +13,7 @@ #include #include "bonuses/HeroBonus.h" +#include "bonuses/CBonusSystemNode.h" #include "GameConstants.h" #include "IHandlerBase.h" diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 58075e1d0..5d285eda4 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -10,6 +10,7 @@ #pragma once #include "bonuses/HeroBonus.h" +#include "bonuses/CBonusSystemNode.h" #include "ConstTransitivePtr.h" #include "ResourceSet.h" #include "GameConstants.h" diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index f8eb1a15e..8014ed839 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -10,6 +10,7 @@ #pragma once #include "bonuses/HeroBonus.h" +#include "bonuses/CBonusSystemNode.h" #include "GameConstants.h" #include "CArtHandler.h" #include "CCreatureHandler.h" diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 193e80ccc..b735bcc43 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -13,6 +13,7 @@ #include #include "bonuses/HeroBonus.h" +#include "bonuses/CBonusSystemNode.h" #include "ResourceSet.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CStack.h b/lib/CStack.h index bcdf5a291..939d8191a 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -11,6 +11,7 @@ #pragma once #include "JsonNode.h" #include "bonuses/HeroBonus.h" +#include "bonuses/CBonusSystemNode.h" #include "CCreatureHandler.h" //todo: remove #include "battle/BattleHex.h" #include "mapObjects/CGHeroInstance.h" // for commander serialization diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 9d19c4892..40c34c8ab 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -24,6 +24,7 @@ #include "mapObjects/CObjectClassesHandler.h" #include "mapObjects/CObjectHandler.h" #include "bonuses/HeroBonus.h" +#include "bonuses/Propagators.h" #include "ResourceSet.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 4702d0dd7..b89a577af 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -18,7 +18,9 @@ #include "CModHandler.h" #include "BattleFieldHandler.h" #include "ObstacleHandler.h" +#include "bonuses/CBonusSystemNode.h" #include "bonuses/Limiters.h" +#include "bonuses/Propagators.h" #include "bonuses/Updaters.h" #include "serializer/CSerializer.h" // for SAVEGAME_MAGIC diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 214ed85d9..36f81dd89 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -13,8 +13,10 @@ #include "ScopeGuard.h" +#include "bonuses/BonusParams.h" #include "bonuses/HeroBonus.h" #include "bonuses/Limiters.h" +#include "bonuses/Propagators.h" #include "bonuses/Updaters.h" #include "filesystem/Filesystem.h" #include "VCMI_Lib.h" //for identifier resolution diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index f596e52f1..4d3dcc08b 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -10,6 +10,7 @@ #pragma once #include "../int3.h" #include "../bonuses/HeroBonus.h" +#include "../bonuses/CBonusSystemNode.h" #include "CBattleInfoCallback.h" #include "IBattleState.h" #include "SiegeInfo.h" diff --git a/lib/bonuses/BonusParams.cpp b/lib/bonuses/BonusParams.cpp new file mode 100644 index 000000000..edb94ffe9 --- /dev/null +++ b/lib/bonuses/BonusParams.cpp @@ -0,0 +1,259 @@ +/* + * BonusParams.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 "BonusParams.h" + +#include "../ResourceSet.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr, int deprecatedSubtype): + isConverted(true) +{ + if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL") + { + if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding") + type = Bonus::ROUGH_TERRAIN_DISCOUNT; + else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy") + type = Bonus::WANDERING_CREATURES_JOIN_BONUS; + else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom") + type = Bonus::MAX_LEARNABLE_SPELL_LEVEL; + else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism") + type = Bonus::MANA_REGENERATION; + else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy") + type = Bonus::UNDEAD_RAISE_PERCENTAGE; + else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning") + type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT; + else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance") + type = Bonus::MAGIC_RESISTANCE; + else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") + type = Bonus::LEARN_BATTLE_SPELL_CHANCE; + else if(deprecatedSubtype == SecondarySkill::SCOUTING || deprecatedSubtypeStr == "skill.scouting") + type = Bonus::SIGHT_RADIUS; + else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence") + { + type = Bonus::MANA_PER_KNOWLEDGE; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + } + else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery") + type = Bonus::SPELL_DAMAGE; + else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar") + type = Bonus::LEARN_MEETING_SPELL_LIMIT; + else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery") + { + subtype = 1; + subtypeRelevant = true; + type = Bonus::PERCENTAGE_DAMAGE_BOOST; + } + else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence") + { + subtype = 0; + subtypeRelevant = true; + type = Bonus::PERCENTAGE_DAMAGE_BOOST; + } + else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer") + { + subtype = -1; + subtypeRelevant = true; + type = Bonus::GENERAL_DAMAGE_REDUCTION; + } + else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation") + { + subtype = 0; + subtypeRelevant = true; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics") + { + subtype = 1; + subtypeRelevant = true; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates") + { + type = Bonus::GENERATE_RESOURCE; + subtype = GameResID(EGameResID::GOLD); + subtypeRelevant = true; + } + else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 4; + } + else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 1; + } + else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 2; + } + else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 8; + } + else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") + { + type = Bonus::BONUS_DAMAGE_CHANCE; + subtypeRelevant = true; + subtypeStr = "core:creature.ballista"; + } + else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid") + { + type = Bonus::SPECIFIC_SPELL_POWER; + subtypeRelevant = true; + subtypeStr = "core:spell.firstAid"; + } + else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics") + { + type = Bonus::CATAPULT_EXTRA_SHOTS; + subtypeRelevant = true; + subtypeStr = "core:spell.catapultShot"; + } + else + isConverted = false; + } + else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2") + { + if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") + type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT; + else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") + { + type = Bonus::HERO_GRANTS_ATTACKS; + subtypeRelevant = true; + subtypeStr = "core:creature.ballista"; + } + else + isConverted = false; + } + else if (deprecatedTypeStr == "SEA_MOVEMENT") + { + subtype = 0; + subtypeRelevant = true; + valueType = Bonus::ADDITIVE_VALUE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if (deprecatedTypeStr == "LAND_MOVEMENT") + { + subtype = 1; + subtypeRelevant = true; + valueType = Bonus::ADDITIVE_VALUE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if (deprecatedTypeStr == "MAXED_SPELL") + { + type = Bonus::SPELL; + subtypeStr = deprecatedSubtypeStr; + subtypeRelevant = true; + valueType = Bonus::INDEPENDENT_MAX; + valueTypeRelevant = true; + val = 3; + valRelevant = true; + } + else if (deprecatedTypeStr == "FULL_HP_REGENERATION") + { + type = Bonus::HP_REGENERATION; + val = 100000; //very high value to always chose stack health + valRelevant = true; + } + else if (deprecatedTypeStr == "KING1") + { + type = Bonus::KING; + val = 0; + valRelevant = true; + } + else if (deprecatedTypeStr == "KING2") + { + type = Bonus::KING; + val = 2; + valRelevant = true; + } + else if (deprecatedTypeStr == "KING3") + { + type = Bonus::KING; + val = 3; + valRelevant = true; + } + else if (deprecatedTypeStr == "SIGHT_RADIOUS") + type = Bonus::SIGHT_RADIUS; + else if (deprecatedTypeStr == "SELF_MORALE") + { + type = Bonus::MORALE; + val = 1; + valRelevant = true; + valueType = Bonus::INDEPENDENT_MAX; + valueTypeRelevant = true; + } + else if (deprecatedTypeStr == "SELF_LUCK") + { + type = Bonus::LUCK; + val = 1; + valRelevant = true; + valueType = Bonus::INDEPENDENT_MAX; + valueTypeRelevant = true; + } + else + isConverted = false; +} + +const JsonNode & BonusParams::toJson() +{ + assert(isConverted); + if(ret.isNull()) + { + ret["type"].String() = vstd::findKey(bonusNameMap, type); + if(subtypeRelevant && !subtypeStr.empty()) + ret["subtype"].String() = subtypeStr; + else if(subtypeRelevant) + ret["subtype"].Integer() = subtype; + if(valueTypeRelevant) + ret["valueType"].String() = vstd::findKey(bonusValueMap, valueType); + if(valRelevant) + ret["val"].Float() = val; + if(targetTypeRelevant) + ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetType); + jsonCreated = true; + } + return ret; +}; + +CSelector BonusParams::toSelector() +{ + assert(isConverted); + if(subtypeRelevant && !subtypeStr.empty()) + JsonUtils::resolveIdentifier(subtype, toJson(), "subtype"); + + auto ret = Selector::type()(type); + if(subtypeRelevant) + ret = ret.And(Selector::subtype()(subtype)); + if(valueTypeRelevant) + ret = ret.And(Selector::valueType(valueType)); + if(targetTypeRelevant) + ret = ret.And(Selector::targetSourceType()(targetType)); + return ret; +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/bonuses/BonusParams.h b/lib/bonuses/BonusParams.h new file mode 100644 index 000000000..aeb5cd150 --- /dev/null +++ b/lib/bonuses/BonusParams.h @@ -0,0 +1,41 @@ +/* + * BonusParams.h, 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 + * + */ +#pragma once + +#include "HeroBonus.h" + +#include "../GameConstants.h" +#include "../JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE BonusParams { + bool isConverted; + Bonus::BonusType type = Bonus::NONE; + TBonusSubtype subtype = -1; + std::string subtypeStr; + bool subtypeRelevant = false; + Bonus::ValueType valueType = Bonus::BASE_NUMBER; + bool valueTypeRelevant = false; + si32 val = 0; + bool valRelevant = false; + Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL; + bool targetTypeRelevant = false; + + BonusParams(bool isConverted = true) : isConverted(isConverted) {}; + BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr = "", int deprecatedSubtype = 0); + const JsonNode & toJson(); + CSelector toSelector(); +private: + JsonNode ret; + bool jsonCreated = false; +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp new file mode 100644 index 000000000..f05ea231f --- /dev/null +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -0,0 +1,689 @@ +/* + * 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 + +std::atomic CBonusSystemNode::treeChanged(1); +constexpr bool CBonusSystemNode::cachingEnabled = true; + +#define FOREACH_PARENT(pname) TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents) +#define FOREACH_RED_CHILD(pname) TNodes lchildren; getRedChildren(lchildren); for(CBonusSystemNode *pname : lchildren) + +PlayerColor CBonusSystemNode::retrieveNodeOwner(const CBonusSystemNode * node) +{ + return node ? node->getOwner() : PlayerColor::CANNOT_DETERMINE; +} + +std::shared_ptr CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) +{ + auto ret = bonuses.getFirst(selector); + if(ret) + return ret; + + FOREACH_PARENT(pname) + { + ret = pname->getBonusLocalFirst(selector); + if (ret) + return ret; + } + + return nullptr; +} + +std::shared_ptr CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) const +{ + return (const_cast(this))->getBonusLocalFirst(selector); +} + +void CBonusSystemNode::getParents(TCNodes & out) const /*retrieves list of parent nodes (nodes to inherit bonuses from) */ +{ + for(const auto & elem : parents) + { + const CBonusSystemNode *parent = elem; + out.insert(parent); + } +} + +void CBonusSystemNode::getParents(TNodes &out) +{ + for (auto & elem : parents) + { + const CBonusSystemNode *parent = elem; + out.insert(const_cast(parent)); + } +} + +void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of parent nodes (nodes to inherit bonuses from) +{ + for(auto * parent : parents) + { + out.insert(parent); + parent->getAllParents(out); + } +} + +void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const +{ + //out has been reserved sufficient capacity at getAllBonuses() call + + BonusList beforeUpdate; + TCNodes lparents; + getAllParents(lparents); + + if(!lparents.empty()) + { + //estimate on how many bonuses are missing yet - must be positive + beforeUpdate.reserve(std::max(out.capacity() - out.size(), bonuses.size())); + } + else + { + beforeUpdate.reserve(bonuses.size()); //at most all local bonuses + } + + for(const auto * parent : lparents) + { + parent->getAllBonusesRec(beforeUpdate, selector); + } + 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; + + //do not add bonus with updater + bool bonusExists = false; + for(const auto & bonus : out) + { + if (bonus == updated) + bonusExists = true; + if (bonus->updater && bonus->updater == updated->updater) + bonusExists = true; + } + + if (!bonusExists) + out.push_back(updated); + } +} + +TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const +{ + bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node + if (CBonusSystemNode::cachingEnabled && limitOnUs) + { + // Exclusive access for one thread + boost::lock_guard lock(sync); + + // 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. + if (cachedLast != treeChanged) + { + BonusList allBonuses; + allBonuses.reserve(cachedBonuses.capacity()); //we assume we'll get about the same number of bonuses + + cachedBonuses.clear(); + cachedRequests.clear(); + + getAllBonusesRec(allBonuses, Selector::all); + limitBonuses(allBonuses, cachedBonuses); + cachedBonuses.stackBonuses(); + + cachedLast = treeChanged; + } + + // 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. + if(!cachingStr.empty()) + { + auto it = cachedRequests.find(cachingStr); + if(it != cachedRequests.end()) + { + //Cached list contains bonuses for our query with applied limiters + return it->second; + } + } + + //We still don't have the bonuses (didn't returned them from cache) + //Perform bonus selection + auto ret = std::make_shared(); + cachedBonuses.getBonuses(*ret, selector, limit); + + // Save the results in the cache + if(!cachingStr.empty()) + cachedRequests[cachingStr] = ret; + + return ret; + } + else + { + return getAllBonusesWithoutCaching(selector, limit, root); + } +} + +TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const +{ + auto ret = std::make_shared(); + + // Get bonus results without caching enabled. + BonusList beforeLimiting; + BonusList afterLimiting; + getAllBonusesRec(beforeLimiting, selector); + + if(!root || root == this) + { + limitBonuses(beforeLimiting, afterLimiting); + } + else if(root) + { + //We want to limit our query against an external node. We get all its bonuses, + // add the ones we're considering and see if they're cut out by limiters + BonusList rootBonuses; + BonusList limitedRootBonuses; + getAllBonusesRec(rootBonuses, selector); + + for(const auto & b : beforeLimiting) + rootBonuses.push_back(b); + + root->limitBonuses(rootBonuses, limitedRootBonuses); + + for(const auto & b : beforeLimiting) + if(vstd::contains(limitedRootBonuses, b)) + afterLimiting.push_back(b); + + } + afterLimiting.getBonuses(*ret, selector, limit); + ret->stackBonuses(); + return ret; +} + +std::shared_ptr CBonusSystemNode::getUpdatedBonus(const std::shared_ptr & b, const TUpdaterPtr & updater) const +{ + assert(updater); + return updater->createUpdatedBonus(b, * this); +} + +CBonusSystemNode::CBonusSystemNode() + :CBonusSystemNode(false) +{ +} + +CBonusSystemNode::CBonusSystemNode(bool isHypotetic): + bonuses(true), + exportedBonuses(true), + nodeType(UNKNOWN), + cachedLast(0), + isHypotheticNode(isHypotetic) +{ +} + +CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType): + bonuses(true), + exportedBonuses(true), + nodeType(NodeType), + cachedLast(0), + isHypotheticNode(false) +{ +} + +CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other) noexcept: + bonuses(std::move(other.bonuses)), + exportedBonuses(std::move(other.exportedBonuses)), + nodeType(other.nodeType), + description(other.description), + cachedLast(0), + isHypotheticNode(other.isHypotheticNode) +{ + std::swap(parents, other.parents); + std::swap(children, other.children); + + //fixing bonus tree without recalculation + + if(!isHypothetic()) + { + for(CBonusSystemNode * n : parents) + { + n->children -= &other; + n->children.push_back(this); + } + } + + for(CBonusSystemNode * n : children) + { + n->parents -= &other; + n->parents.push_back(this); + } + + //cache ignored + + //cachedBonuses + //cachedRequests +} + +CBonusSystemNode::~CBonusSystemNode() +{ + detachFromAll(); + + if(!children.empty()) + { + while(!children.empty()) + children.front()->detachFrom(*this); + } +} + +void CBonusSystemNode::attachTo(CBonusSystemNode & parent) +{ + assert(!vstd::contains(parents, &parent)); + parents.push_back(&parent); + + if(!isHypothetic()) + { + if(parent.actsAsBonusSourceOnly()) + parent.newRedDescendant(*this); + else + newRedDescendant(parent); + + parent.newChildAttached(*this); + } + + CBonusSystemNode::treeHasChanged(); +} + +void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) +{ + assert(vstd::contains(parents, &parent)); + + if(!isHypothetic()) + { + if(parent.actsAsBonusSourceOnly()) + parent.removedRedDescendant(*this); + else + removedRedDescendant(parent); + } + + if (vstd::contains(parents, &parent)) + { + parents -= &parent; + } + else + { + logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)" + , nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType); + } + + if(!isHypothetic()) + { + parent.childDetached(*this); + } + CBonusSystemNode::treeHasChanged(); +} + +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& 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); + CBonusSystemNode::treeHasChanged(); +} + +void CBonusSystemNode::accumulateBonus(const std::shared_ptr& b) +{ + auto bonus = exportedBonuses.getFirst(Selector::typeSubtype(b->type, b->subtype)); //only local bonuses are interesting //TODO: what about value type? + if(bonus) + bonus->val += b->val; + else + addNewBonus(std::make_shared(*b)); //duplicate needed, original may get destroyed +} + +void CBonusSystemNode::removeBonus(const std::shared_ptr& b) +{ + exportedBonuses -= b; + if(b->propagator) + unpropagateBonus(b); + else + bonuses -= b; + CBonusSystemNode::treeHasChanged(); +} + +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 & b, const CBonusSystemNode & source) +{ + if(b->propagator->shouldBeAttached(this)) + { + auto propagated = b->propagationUpdater + ? source.getUpdatedBonus(b, b->propagationUpdater) + : b; + bonuses.push_back(propagated); + logBonus->trace("#$# %s #propagated to# %s", propagated->Description(), nodeName()); + } + + FOREACH_RED_CHILD(child) + child->propagateBonus(b, source); +} + +void CBonusSystemNode::unpropagateBonus(const std::shared_ptr & b) +{ + if(b->propagator->shouldBeAttached(this)) + { + bonuses -= b; + logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName()); + } + + FOREACH_RED_CHILD(child) + child->unpropagateBonus(b); +} + +void CBonusSystemNode::newChildAttached(CBonusSystemNode & child) +{ + assert(!vstd::contains(children, &child)); + children.push_back(&child); +} + +void CBonusSystemNode::childDetached(CBonusSystemNode & child) +{ + if(vstd::contains(children, &child)) + children -= &child; + else + { + logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)" + , child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType); + } +} + +void CBonusSystemNode::detachFromAll() +{ + while(!parents.empty()) + detachFrom(*parents.front()); +} + +bool CBonusSystemNode::isIndependentNode() const +{ + return parents.empty() && children.empty(); +} + +std::string CBonusSystemNode::nodeName() const +{ + return !description.empty() + ? description + : std::string("Bonus system node of type ") + typeid(*this).name(); +} + +std::string CBonusSystemNode::nodeShortInfo() const +{ + std::ostringstream str; + str << "'" << typeid(* this).name() << "'"; + description.length() > 0 + ? str << " (" << description << ")" + : str << " (no description)"; + return str.str(); +} + +void CBonusSystemNode::deserializationFix() +{ + exportBonuses(); + +} + +void CBonusSystemNode::getRedParents(TNodes & out) +{ + FOREACH_PARENT(pname) + { + if(pname->actsAsBonusSourceOnly()) + { + out.insert(pname); + } + } + + if(!actsAsBonusSourceOnly()) + { + for(CBonusSystemNode *child : children) + { + out.insert(child); + } + } +} + +void CBonusSystemNode::getRedChildren(TNodes &out) +{ + FOREACH_PARENT(pname) + { + if(!pname->actsAsBonusSourceOnly()) + { + out.insert(pname); + } + } + + if(actsAsBonusSourceOnly()) + { + for(CBonusSystemNode *child : children) + { + out.insert(child); + } + } +} + +void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant) +{ + for(const auto & b : exportedBonuses) + { + if(b->propagator) + descendant.propagateBonus(b, *this); + } + TNodes redParents; + getRedAncestors(redParents); //get all red parents recursively + + for(auto * parent : redParents) + { + for(const auto & b : parent->exportedBonuses) + { + if(b->propagator) + descendant.propagateBonus(b, *this); + } + } +} + +void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant) +{ + for(const auto & b : exportedBonuses) + if(b->propagator) + descendant.unpropagateBonus(b); + + TNodes redParents; + getRedAncestors(redParents); //get all red parents recursively + + for(auto * parent : redParents) + { + for(const auto & b : parent->exportedBonuses) + if(b->propagator) + descendant.unpropagateBonus(b); + } +} + +void CBonusSystemNode::getRedAncestors(TNodes &out) +{ + getRedParents(out); + + TNodes redParents; + getRedParents(redParents); + + for(CBonusSystemNode * parent : redParents) + parent->getRedAncestors(out); +} + +void CBonusSystemNode::exportBonus(const std::shared_ptr & b) +{ + if(b->propagator) + propagateBonus(b, *this); + else + bonuses.push_back(b); + + CBonusSystemNode::treeHasChanged(); +} + +void CBonusSystemNode::exportBonuses() +{ + for(const auto & b : exportedBonuses) + exportBonus(b); +} + +CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const +{ + return nodeType; +} + +const BonusList& CBonusSystemNode::getBonusList() const +{ + return bonuses; +} + +const TNodesVector& CBonusSystemNode::getParentNodes() const +{ + return parents; +} + +const TNodesVector& CBonusSystemNode::getChildrenNodes() const +{ + return children; +} + +void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type) +{ + nodeType = type; +} + +BonusList & CBonusSystemNode::getExportedBonusList() +{ + return exportedBonuses; +} + +const BonusList & CBonusSystemNode::getExportedBonusList() const +{ + return exportedBonuses; +} + +const std::string& CBonusSystemNode::getDescription() const +{ + return description; +} + +void CBonusSystemNode::setDescription(const std::string &description) +{ + this->description = description; +} + +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(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 + if(decision == ILimiter::EDecision::DISCARD) + { + 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; + } +} + +TBonusListPtr CBonusSystemNode::limitBonuses(const BonusList &allBonuses) const +{ + auto ret = std::make_shared(); + limitBonuses(allBonuses, *ret); + return ret; +} + +void CBonusSystemNode::treeHasChanged() +{ + treeChanged++; +} + +int64_t CBonusSystemNode::getTreeVersion() const +{ + return treeChanged; +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/bonuses/CBonusSystemNode.h b/lib/bonuses/CBonusSystemNode.h new file mode 100644 index 000000000..54fac89d6 --- /dev/null +++ b/lib/bonuses/CBonusSystemNode.h @@ -0,0 +1,139 @@ +/* + * CBonusSystemNode.h, 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 + * + */ +#pragma once + +#include "HeroBonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +using TNodes = std::set; +using TCNodes = std::set; +using TNodesVector = std::vector; + +class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, 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 + }; +private: + BonusList bonuses; //wielded bonuses (local or up-propagated here) + BonusList exportedBonuses; //bonuses coming from this node (wielded or propagated away) + + TNodesVector parents; //parents -> we inherit bonuses from them, we may attach our bonuses to them + TNodesVector children; + + ENodeTypes nodeType; + std::string description; + bool isHypotheticNode; + + static const bool cachingEnabled; + mutable BonusList cachedBonuses; + mutable int64_t cachedLast; + static std::atomic treeChanged; + + // Setting a value to cachingStr before getting any bonuses caches the result for later requests. + // This string needs to be unique, that's why it has to be setted in the following manner: + // [property key]_[value] => only for selector + mutable std::map cachedRequests; + mutable boost::mutex sync; + + void getAllBonusesRec(BonusList &out, const CSelector & selector) const; + TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const; + std::shared_ptr getUpdatedBonus(const std::shared_ptr & b, const TUpdaterPtr & updater) const; + +public: + explicit CBonusSystemNode(); + explicit CBonusSystemNode(bool isHypotetic); + explicit CBonusSystemNode(ENodeTypes NodeType); + CBonusSystemNode(CBonusSystemNode && other) noexcept; + virtual ~CBonusSystemNode(); + + void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here + TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence + TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; + void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from), + std::shared_ptr getBonusLocalFirst(const CSelector & selector) const; + + //non-const interface + void getParents(TNodes &out); //retrieves list of parent nodes (nodes to inherit bonuses from) + + void getRedParents(TNodes &out); //retrieves list of red parent nodes (nodes bonuses propagate from) + void getRedAncestors(TNodes &out); + void getRedChildren(TNodes &out); + void getAllParents(TCNodes & out) const; + static PlayerColor retrieveNodeOwner(const CBonusSystemNode * node); + std::shared_ptr getBonusLocalFirst(const CSelector & selector); + + void attachTo(CBonusSystemNode & parent); + void detachFrom(CBonusSystemNode & parent); + void detachFromAll(); + virtual void addNewBonus(const std::shared_ptr& b); + void accumulateBonus(const std::shared_ptr& b); //add value of bonus with same type/subtype or create new + + void newChildAttached(CBonusSystemNode & child); + void childDetached(CBonusSystemNode & child); + void propagateBonus(const std::shared_ptr & b, const CBonusSystemNode & source); + void unpropagateBonus(const std::shared_ptr & b); + void removeBonus(const std::shared_ptr& b); + void removeBonuses(const CSelector & selector); + void removeBonusesRecursive(const CSelector & s); + void newRedDescendant(CBonusSystemNode & descendant); //propagation needed + void removedRedDescendant(CBonusSystemNode & descendant); //de-propagation needed + + bool isIndependentNode() const; //node is independent when it has no parents nor children + bool actsAsBonusSourceOnly() const; + ///updates count of remaining turns and removes outdated bonuses by selector + void reduceBonusDurations(const CSelector &s); + virtual std::string bonusToString(const std::shared_ptr& bonus, bool description) const {return "";}; //description or bonus name + virtual std::string nodeName() const; + virtual std::string nodeShortInfo() const; + bool isHypothetic() const { return isHypotheticNode; } + + void deserializationFix(); + void exportBonus(const std::shared_ptr & b); + void exportBonuses(); + + const BonusList &getBonusList() const; + BonusList & getExportedBonusList(); + const BonusList & getExportedBonusList() const; + CBonusSystemNode::ENodeTypes getNodeType() const; + void setNodeType(CBonusSystemNode::ENodeTypes type); + const TNodesVector &getParentNodes() const; + const TNodesVector &getChildrenNodes() const; + const std::string &getDescription() const; + void setDescription(const std::string &description); + + static void treeHasChanged(); + + int64_t getTreeVersion() const override; + + virtual PlayerColor getOwner() const + { + return PlayerColor::NEUTRAL; + } + + template void serialize(Handler &h, const int version) + { +// h & bonuses; + h & nodeType; + h & exportedBonuses; + h & description; + BONUS_TREE_DESERIALIZATION_FIX + //h & parents & children; + } + + friend class CBonusProxy; +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/bonuses/HeroBonus.cpp b/lib/bonuses/HeroBonus.cpp index b6285a3d7..fc1f370e2 100644 --- a/lib/bonuses/HeroBonus.cpp +++ b/lib/bonuses/HeroBonus.cpp @@ -10,8 +10,10 @@ #include "StdInc.h" #include "HeroBonus.h" +#include "CBonusSystemNode.h" #include "Limiters.h" #include "Updaters.h" +#include "Propagators.h" #include "../VCMI_Lib.h" #include "../spells/CSpellHandler.h" @@ -30,9 +32,6 @@ VCMI_LIB_NAMESPACE_BEGIN -#define FOREACH_PARENT(pname) TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents) -#define FOREACH_RED_CHILD(pname) TNodes lchildren; getRedChildren(lchildren); for(CBonusSystemNode *pname : lchildren) - #define BONUS_NAME(x) { #x, Bonus::x }, const std::map bonusNameMap = { BONUS_LIST @@ -71,16 +70,6 @@ const std::map bonusLimitEffect = BONUS_ITEM(ONLY_MELEE_FIGHT) }; -const std::map bonusPropagatorMap = -{ - {"BATTLE_WIDE", std::make_shared(CBonusSystemNode::BATTLE)}, - {"VISITED_TOWN_AND_VISITOR", std::make_shared(CBonusSystemNode::TOWN_AND_VISITOR)}, - {"PLAYER_PROPAGATOR", std::make_shared(CBonusSystemNode::PLAYER)}, - {"HERO", std::make_shared(CBonusSystemNode::HERO)}, - {"TEAM_PROPAGATOR", std::make_shared(CBonusSystemNode::TEAM)}, //untested - {"GLOBAL_EFFECT", std::make_shared(CBonusSystemNode::GLOBAL_EFFECTS)} -}; //untested - const std::set deprecatedBonusSet = { "SECONDARY_SKILL_PREMY", "SECONDARY_SKILL_VAL2", @@ -159,9 +148,6 @@ JsonNode CAddInfo::toJsonNode() const } } -std::atomic CBonusSystemNode::treeChanged(1); -constexpr bool CBonusSystemNode::cachingEnabled = true; - BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree) { @@ -477,669 +463,6 @@ std::shared_ptr IBonusBearer::getBonus(const CSelector &selector) c return bonuses->getFirst(Selector::all); } -PlayerColor CBonusSystemNode::retrieveNodeOwner(const CBonusSystemNode * node) -{ - return node ? node->getOwner() : PlayerColor::CANNOT_DETERMINE; -} - -std::shared_ptr CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) -{ - auto ret = bonuses.getFirst(selector); - if(ret) - return ret; - - FOREACH_PARENT(pname) - { - ret = pname->getBonusLocalFirst(selector); - if (ret) - return ret; - } - - return nullptr; -} - -std::shared_ptr CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) const -{ - return (const_cast(this))->getBonusLocalFirst(selector); -} - -void CBonusSystemNode::getParents(TCNodes & out) const /*retrieves list of parent nodes (nodes to inherit bonuses from) */ -{ - for(const auto & elem : parents) - { - const CBonusSystemNode *parent = elem; - out.insert(parent); - } -} - -void CBonusSystemNode::getParents(TNodes &out) -{ - for (auto & elem : parents) - { - const CBonusSystemNode *parent = elem; - out.insert(const_cast(parent)); - } -} - -void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of parent nodes (nodes to inherit bonuses from) -{ - for(auto * parent : parents) - { - out.insert(parent); - parent->getAllParents(out); - } -} - -void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const -{ - //out has been reserved sufficient capacity at getAllBonuses() call - - BonusList beforeUpdate; - TCNodes lparents; - getAllParents(lparents); - - if(!lparents.empty()) - { - //estimate on how many bonuses are missing yet - must be positive - beforeUpdate.reserve(std::max(out.capacity() - out.size(), bonuses.size())); - } - else - { - beforeUpdate.reserve(bonuses.size()); //at most all local bonuses - } - - for(const auto * parent : lparents) - { - parent->getAllBonusesRec(beforeUpdate, selector); - } - 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; - - //do not add bonus with updater - bool bonusExists = false; - for(const auto & bonus : out) - { - if (bonus == updated) - bonusExists = true; - if (bonus->updater && bonus->updater == updated->updater) - bonusExists = true; - } - - if (!bonusExists) - out.push_back(updated); - } -} - -TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const -{ - bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node - if (CBonusSystemNode::cachingEnabled && limitOnUs) - { - // Exclusive access for one thread - boost::lock_guard lock(sync); - - // 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. - if (cachedLast != treeChanged) - { - BonusList allBonuses; - allBonuses.reserve(cachedBonuses.capacity()); //we assume we'll get about the same number of bonuses - - cachedBonuses.clear(); - cachedRequests.clear(); - - getAllBonusesRec(allBonuses, Selector::all); - limitBonuses(allBonuses, cachedBonuses); - cachedBonuses.stackBonuses(); - - cachedLast = treeChanged; - } - - // 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. - if(!cachingStr.empty()) - { - auto it = cachedRequests.find(cachingStr); - if(it != cachedRequests.end()) - { - //Cached list contains bonuses for our query with applied limiters - return it->second; - } - } - - //We still don't have the bonuses (didn't returned them from cache) - //Perform bonus selection - auto ret = std::make_shared(); - cachedBonuses.getBonuses(*ret, selector, limit); - - // Save the results in the cache - if(!cachingStr.empty()) - cachedRequests[cachingStr] = ret; - - return ret; - } - else - { - return getAllBonusesWithoutCaching(selector, limit, root); - } -} - -TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const -{ - auto ret = std::make_shared(); - - // Get bonus results without caching enabled. - BonusList beforeLimiting; - BonusList afterLimiting; - getAllBonusesRec(beforeLimiting, selector); - - if(!root || root == this) - { - limitBonuses(beforeLimiting, afterLimiting); - } - else if(root) - { - //We want to limit our query against an external node. We get all its bonuses, - // add the ones we're considering and see if they're cut out by limiters - BonusList rootBonuses; - BonusList limitedRootBonuses; - getAllBonusesRec(rootBonuses, selector); - - for(const auto & b : beforeLimiting) - rootBonuses.push_back(b); - - root->limitBonuses(rootBonuses, limitedRootBonuses); - - for(const auto & b : beforeLimiting) - if(vstd::contains(limitedRootBonuses, b)) - afterLimiting.push_back(b); - - } - afterLimiting.getBonuses(*ret, selector, limit); - ret->stackBonuses(); - return ret; -} - -std::shared_ptr CBonusSystemNode::getUpdatedBonus(const std::shared_ptr & b, const TUpdaterPtr & updater) const -{ - assert(updater); - return updater->createUpdatedBonus(b, * this); -} - -CBonusSystemNode::CBonusSystemNode() - :CBonusSystemNode(false) -{ -} - -CBonusSystemNode::CBonusSystemNode(bool isHypotetic): - bonuses(true), - exportedBonuses(true), - nodeType(UNKNOWN), - cachedLast(0), - isHypotheticNode(isHypotetic) -{ -} - -CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType): - bonuses(true), - exportedBonuses(true), - nodeType(NodeType), - cachedLast(0), - isHypotheticNode(false) -{ -} - -CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other) noexcept: - bonuses(std::move(other.bonuses)), - exportedBonuses(std::move(other.exportedBonuses)), - nodeType(other.nodeType), - description(other.description), - cachedLast(0), - isHypotheticNode(other.isHypotheticNode) -{ - std::swap(parents, other.parents); - std::swap(children, other.children); - - //fixing bonus tree without recalculation - - if(!isHypothetic()) - { - for(CBonusSystemNode * n : parents) - { - n->children -= &other; - n->children.push_back(this); - } - } - - for(CBonusSystemNode * n : children) - { - n->parents -= &other; - n->parents.push_back(this); - } - - //cache ignored - - //cachedBonuses - //cachedRequests -} - -CBonusSystemNode::~CBonusSystemNode() -{ - detachFromAll(); - - if(!children.empty()) - { - while(!children.empty()) - children.front()->detachFrom(*this); - } -} - -void CBonusSystemNode::attachTo(CBonusSystemNode & parent) -{ - assert(!vstd::contains(parents, &parent)); - parents.push_back(&parent); - - if(!isHypothetic()) - { - if(parent.actsAsBonusSourceOnly()) - parent.newRedDescendant(*this); - else - newRedDescendant(parent); - - parent.newChildAttached(*this); - } - - CBonusSystemNode::treeHasChanged(); -} - -void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) -{ - assert(vstd::contains(parents, &parent)); - - if(!isHypothetic()) - { - if(parent.actsAsBonusSourceOnly()) - parent.removedRedDescendant(*this); - else - removedRedDescendant(parent); - } - - if (vstd::contains(parents, &parent)) - { - parents -= &parent; - } - else - { - logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)" - , nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType); - } - - if(!isHypothetic()) - { - parent.childDetached(*this); - } - CBonusSystemNode::treeHasChanged(); -} - -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& 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); - CBonusSystemNode::treeHasChanged(); -} - -void CBonusSystemNode::accumulateBonus(const std::shared_ptr& b) -{ - auto bonus = exportedBonuses.getFirst(Selector::typeSubtype(b->type, b->subtype)); //only local bonuses are interesting //TODO: what about value type? - if(bonus) - bonus->val += b->val; - else - addNewBonus(std::make_shared(*b)); //duplicate needed, original may get destroyed -} - -void CBonusSystemNode::removeBonus(const std::shared_ptr& b) -{ - exportedBonuses -= b; - if(b->propagator) - unpropagateBonus(b); - else - bonuses -= b; - CBonusSystemNode::treeHasChanged(); -} - -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 & b, const CBonusSystemNode & source) -{ - if(b->propagator->shouldBeAttached(this)) - { - auto propagated = b->propagationUpdater - ? source.getUpdatedBonus(b, b->propagationUpdater) - : b; - bonuses.push_back(propagated); - logBonus->trace("#$# %s #propagated to# %s", propagated->Description(), nodeName()); - } - - FOREACH_RED_CHILD(child) - child->propagateBonus(b, source); -} - -void CBonusSystemNode::unpropagateBonus(const std::shared_ptr & b) -{ - if(b->propagator->shouldBeAttached(this)) - { - bonuses -= b; - logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName()); - } - - FOREACH_RED_CHILD(child) - child->unpropagateBonus(b); -} - -void CBonusSystemNode::newChildAttached(CBonusSystemNode & child) -{ - assert(!vstd::contains(children, &child)); - children.push_back(&child); -} - -void CBonusSystemNode::childDetached(CBonusSystemNode & child) -{ - if(vstd::contains(children, &child)) - children -= &child; - else - { - logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)" - , child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType); - } -} - -void CBonusSystemNode::detachFromAll() -{ - while(!parents.empty()) - detachFrom(*parents.front()); -} - -bool CBonusSystemNode::isIndependentNode() const -{ - return parents.empty() && children.empty(); -} - -std::string CBonusSystemNode::nodeName() const -{ - return !description.empty() - ? description - : std::string("Bonus system node of type ") + typeid(*this).name(); -} - -std::string CBonusSystemNode::nodeShortInfo() const -{ - std::ostringstream str; - str << "'" << typeid(* this).name() << "'"; - description.length() > 0 - ? str << " (" << description << ")" - : str << " (no description)"; - return str.str(); -} - -void CBonusSystemNode::deserializationFix() -{ - exportBonuses(); - -} - -void CBonusSystemNode::getRedParents(TNodes & out) -{ - FOREACH_PARENT(pname) - { - if(pname->actsAsBonusSourceOnly()) - { - out.insert(pname); - } - } - - if(!actsAsBonusSourceOnly()) - { - for(CBonusSystemNode *child : children) - { - out.insert(child); - } - } -} - -void CBonusSystemNode::getRedChildren(TNodes &out) -{ - FOREACH_PARENT(pname) - { - if(!pname->actsAsBonusSourceOnly()) - { - out.insert(pname); - } - } - - if(actsAsBonusSourceOnly()) - { - for(CBonusSystemNode *child : children) - { - out.insert(child); - } - } -} - -void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant) -{ - for(const auto & b : exportedBonuses) - { - if(b->propagator) - descendant.propagateBonus(b, *this); - } - TNodes redParents; - getRedAncestors(redParents); //get all red parents recursively - - for(auto * parent : redParents) - { - for(const auto & b : parent->exportedBonuses) - { - if(b->propagator) - descendant.propagateBonus(b, *this); - } - } -} - -void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant) -{ - for(const auto & b : exportedBonuses) - if(b->propagator) - descendant.unpropagateBonus(b); - - TNodes redParents; - getRedAncestors(redParents); //get all red parents recursively - - for(auto * parent : redParents) - { - for(const auto & b : parent->exportedBonuses) - if(b->propagator) - descendant.unpropagateBonus(b); - } -} - -void CBonusSystemNode::getRedAncestors(TNodes &out) -{ - getRedParents(out); - - TNodes redParents; - getRedParents(redParents); - - for(CBonusSystemNode * parent : redParents) - parent->getRedAncestors(out); -} - -void CBonusSystemNode::exportBonus(const std::shared_ptr & b) -{ - if(b->propagator) - propagateBonus(b, *this); - else - bonuses.push_back(b); - - CBonusSystemNode::treeHasChanged(); -} - -void CBonusSystemNode::exportBonuses() -{ - for(const auto & b : exportedBonuses) - exportBonus(b); -} - -CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const -{ - return nodeType; -} - -const BonusList& CBonusSystemNode::getBonusList() const -{ - return bonuses; -} - -const TNodesVector& CBonusSystemNode::getParentNodes() const -{ - return parents; -} - -const TNodesVector& CBonusSystemNode::getChildrenNodes() const -{ - return children; -} - -void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type) -{ - nodeType = type; -} - -BonusList & CBonusSystemNode::getExportedBonusList() -{ - return exportedBonuses; -} - -const BonusList & CBonusSystemNode::getExportedBonusList() const -{ - return exportedBonuses; -} - -const std::string& CBonusSystemNode::getDescription() const -{ - return description; -} - -void CBonusSystemNode::setDescription(const std::string &description) -{ - this->description = description; -} - -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(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 - if(decision == ILimiter::EDecision::DISCARD) - { - 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; - } -} - -TBonusListPtr CBonusSystemNode::limitBonuses(const BonusList &allBonuses) const -{ - auto ret = std::make_shared(); - limitBonuses(allBonuses, *ret); - return ret; -} - -void CBonusSystemNode::treeHasChanged() -{ - treeChanged++; -} - -int64_t CBonusSystemNode::getTreeVersion() const -{ - return treeChanged; -} - std::string Bonus::Description(std::optional customValue) const { std::ostringstream str; @@ -1314,246 +637,6 @@ std::string Bonus::nameForBonus() const } } -BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr, int deprecatedSubtype): - isConverted(true) -{ - if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL") - { - if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding") - type = Bonus::ROUGH_TERRAIN_DISCOUNT; - else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy") - type = Bonus::WANDERING_CREATURES_JOIN_BONUS; - else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom") - type = Bonus::MAX_LEARNABLE_SPELL_LEVEL; - else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism") - type = Bonus::MANA_REGENERATION; - else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy") - type = Bonus::UNDEAD_RAISE_PERCENTAGE; - else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning") - type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT; - else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance") - type = Bonus::MAGIC_RESISTANCE; - else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") - type = Bonus::LEARN_BATTLE_SPELL_CHANCE; - else if(deprecatedSubtype == SecondarySkill::SCOUTING || deprecatedSubtypeStr == "skill.scouting") - type = Bonus::SIGHT_RADIUS; - else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence") - { - type = Bonus::MANA_PER_KNOWLEDGE; - valueType = Bonus::PERCENT_TO_BASE; - valueTypeRelevant = true; - } - else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery") - type = Bonus::SPELL_DAMAGE; - else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar") - type = Bonus::LEARN_MEETING_SPELL_LIMIT; - else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery") - { - subtype = 1; - subtypeRelevant = true; - type = Bonus::PERCENTAGE_DAMAGE_BOOST; - } - else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence") - { - subtype = 0; - subtypeRelevant = true; - type = Bonus::PERCENTAGE_DAMAGE_BOOST; - } - else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer") - { - subtype = -1; - subtypeRelevant = true; - type = Bonus::GENERAL_DAMAGE_REDUCTION; - } - else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation") - { - subtype = 0; - subtypeRelevant = true; - valueType = Bonus::PERCENT_TO_BASE; - valueTypeRelevant = true; - type = Bonus::MOVEMENT; - } - else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics") - { - subtype = 1; - subtypeRelevant = true; - valueType = Bonus::PERCENT_TO_BASE; - valueTypeRelevant = true; - type = Bonus::MOVEMENT; - } - else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates") - { - type = Bonus::GENERATE_RESOURCE; - subtype = GameResID(EGameResID::GOLD); - subtypeRelevant = true; - } - else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic") - { - type = Bonus::MAGIC_SCHOOL_SKILL; - subtypeRelevant = true; - subtype = 4; - } - else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic") - { - type = Bonus::MAGIC_SCHOOL_SKILL; - subtypeRelevant = true; - subtype = 1; - } - else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic") - { - type = Bonus::MAGIC_SCHOOL_SKILL; - subtypeRelevant = true; - subtype = 2; - } - else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic") - { - type = Bonus::MAGIC_SCHOOL_SKILL; - subtypeRelevant = true; - subtype = 8; - } - else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") - { - type = Bonus::BONUS_DAMAGE_CHANCE; - subtypeRelevant = true; - subtypeStr = "core:creature.ballista"; - } - else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid") - { - type = Bonus::SPECIFIC_SPELL_POWER; - subtypeRelevant = true; - subtypeStr = "core:spell.firstAid"; - } - else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics") - { - type = Bonus::CATAPULT_EXTRA_SHOTS; - subtypeRelevant = true; - subtypeStr = "core:spell.catapultShot"; - } - else - isConverted = false; - } - else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2") - { - if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") - type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT; - else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") - { - type = Bonus::HERO_GRANTS_ATTACKS; - subtypeRelevant = true; - subtypeStr = "core:creature.ballista"; - } - else - isConverted = false; - } - else if (deprecatedTypeStr == "SEA_MOVEMENT") - { - subtype = 0; - subtypeRelevant = true; - valueType = Bonus::ADDITIVE_VALUE; - valueTypeRelevant = true; - type = Bonus::MOVEMENT; - } - else if (deprecatedTypeStr == "LAND_MOVEMENT") - { - subtype = 1; - subtypeRelevant = true; - valueType = Bonus::ADDITIVE_VALUE; - valueTypeRelevant = true; - type = Bonus::MOVEMENT; - } - else if (deprecatedTypeStr == "MAXED_SPELL") - { - type = Bonus::SPELL; - subtypeStr = deprecatedSubtypeStr; - subtypeRelevant = true; - valueType = Bonus::INDEPENDENT_MAX; - valueTypeRelevant = true; - val = 3; - valRelevant = true; - } - else if (deprecatedTypeStr == "FULL_HP_REGENERATION") - { - type = Bonus::HP_REGENERATION; - val = 100000; //very high value to always chose stack health - valRelevant = true; - } - else if (deprecatedTypeStr == "KING1") - { - type = Bonus::KING; - val = 0; - valRelevant = true; - } - else if (deprecatedTypeStr == "KING2") - { - type = Bonus::KING; - val = 2; - valRelevant = true; - } - else if (deprecatedTypeStr == "KING3") - { - type = Bonus::KING; - val = 3; - valRelevant = true; - } - else if (deprecatedTypeStr == "SIGHT_RADIOUS") - type = Bonus::SIGHT_RADIUS; - else if (deprecatedTypeStr == "SELF_MORALE") - { - type = Bonus::MORALE; - val = 1; - valRelevant = true; - valueType = Bonus::INDEPENDENT_MAX; - valueTypeRelevant = true; - } - else if (deprecatedTypeStr == "SELF_LUCK") - { - type = Bonus::LUCK; - val = 1; - valRelevant = true; - valueType = Bonus::INDEPENDENT_MAX; - valueTypeRelevant = true; - } - else - isConverted = false; -} - -const JsonNode & BonusParams::toJson() -{ - assert(isConverted); - if(ret.isNull()) - { - ret["type"].String() = vstd::findKey(bonusNameMap, type); - if(subtypeRelevant && !subtypeStr.empty()) - ret["subtype"].String() = subtypeStr; - else if(subtypeRelevant) - ret["subtype"].Integer() = subtype; - if(valueTypeRelevant) - ret["valueType"].String() = vstd::findKey(bonusValueMap, valueType); - if(valRelevant) - ret["val"].Float() = val; - if(targetTypeRelevant) - ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetType); - jsonCreated = true; - } - return ret; -}; - -CSelector BonusParams::toSelector() -{ - assert(isConverted); - if(subtypeRelevant && !subtypeStr.empty()) - JsonUtils::resolveIdentifier(subtype, toJson(), "subtype"); - - auto ret = Selector::type()(type); - if(subtypeRelevant) - ret = ret.And(Selector::subtype()(subtype)); - if(valueTypeRelevant) - ret = ret.And(Selector::valueType(valueType)); - if(targetTypeRelevant) - ret = ret.And(Selector::targetSourceType()(targetType)); - return ret; -} - Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype): duration(static_cast(Duration)), type(Type), @@ -1722,31 +805,6 @@ std::shared_ptr Bonus::addLimiter(const TLimiterPtr & Limiter) return this->shared_from_this(); } -bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) -{ - return false; -} - -CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const -{ - return CBonusSystemNode::ENodeTypes::NONE; -} - -CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType) - : nodeType(NodeType) -{ -} - -CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const -{ - return nodeType; -} - -bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) -{ - return nodeType == dest->getNodeType(); -} - // Updaters std::shared_ptr Bonus::addUpdater(const TUpdaterPtr & Updater) diff --git a/lib/bonuses/HeroBonus.h b/lib/bonuses/HeroBonus.h index dc12d3b01..5d1307352 100644 --- a/lib/bonuses/HeroBonus.h +++ b/lib/bonuses/HeroBonus.h @@ -27,9 +27,6 @@ using TConstBonusListPtr = std::shared_ptr; using TLimiterPtr = std::shared_ptr; using TPropagatorPtr = std::shared_ptr; using TUpdaterPtr = std::shared_ptr; -using TNodes = std::set; -using TCNodes = std::set; -using TNodesVector = std::vector; class CSelector : std::function { @@ -469,28 +466,6 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus); -struct DLL_LINKAGE BonusParams { - bool isConverted; - Bonus::BonusType type = Bonus::NONE; - TBonusSubtype subtype = -1; - std::string subtypeStr; - bool subtypeRelevant = false; - Bonus::ValueType valueType = Bonus::BASE_NUMBER; - bool valueTypeRelevant = false; - si32 val = 0; - bool valRelevant = false; - Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL; - bool targetTypeRelevant = false; - - BonusParams(bool isConverted = true) : isConverted(isConverted) {}; - BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr = "", int deprecatedSubtype = 0); - const JsonNode & toJson(); - CSelector toSelector(); -private: - JsonNode ret; - bool jsonCreated = false; -}; - class DLL_LINKAGE BonusList { public: @@ -614,151 +589,6 @@ public: virtual int64_t getTreeVersion() const = 0; }; -class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, 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 - }; -private: - BonusList bonuses; //wielded bonuses (local or up-propagated here) - BonusList exportedBonuses; //bonuses coming from this node (wielded or propagated away) - - TNodesVector parents; //parents -> we inherit bonuses from them, we may attach our bonuses to them - TNodesVector children; - - ENodeTypes nodeType; - std::string description; - bool isHypotheticNode; - - static const bool cachingEnabled; - mutable BonusList cachedBonuses; - mutable int64_t cachedLast; - static std::atomic treeChanged; - - // Setting a value to cachingStr before getting any bonuses caches the result for later requests. - // This string needs to be unique, that's why it has to be setted in the following manner: - // [property key]_[value] => only for selector - mutable std::map cachedRequests; - mutable boost::mutex sync; - - void getAllBonusesRec(BonusList &out, const CSelector & selector) const; - TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const; - std::shared_ptr getUpdatedBonus(const std::shared_ptr & b, const TUpdaterPtr & updater) const; - -public: - explicit CBonusSystemNode(); - explicit CBonusSystemNode(bool isHypotetic); - explicit CBonusSystemNode(ENodeTypes NodeType); - CBonusSystemNode(CBonusSystemNode && other) noexcept; - virtual ~CBonusSystemNode(); - - void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here - TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence - TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; - void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from), - std::shared_ptr getBonusLocalFirst(const CSelector & selector) const; - - //non-const interface - void getParents(TNodes &out); //retrieves list of parent nodes (nodes to inherit bonuses from) - - void getRedParents(TNodes &out); //retrieves list of red parent nodes (nodes bonuses propagate from) - void getRedAncestors(TNodes &out); - void getRedChildren(TNodes &out); - void getAllParents(TCNodes & out) const; - static PlayerColor retrieveNodeOwner(const CBonusSystemNode * node); - std::shared_ptr getBonusLocalFirst(const CSelector & selector); - - void attachTo(CBonusSystemNode & parent); - void detachFrom(CBonusSystemNode & parent); - void detachFromAll(); - virtual void addNewBonus(const std::shared_ptr& b); - void accumulateBonus(const std::shared_ptr& b); //add value of bonus with same type/subtype or create new - - void newChildAttached(CBonusSystemNode & child); - void childDetached(CBonusSystemNode & child); - void propagateBonus(const std::shared_ptr & b, const CBonusSystemNode & source); - void unpropagateBonus(const std::shared_ptr & b); - void removeBonus(const std::shared_ptr& b); - void removeBonuses(const CSelector & selector); - void removeBonusesRecursive(const CSelector & s); - void newRedDescendant(CBonusSystemNode & descendant); //propagation needed - void removedRedDescendant(CBonusSystemNode & descendant); //de-propagation needed - - bool isIndependentNode() const; //node is independent when it has no parents nor children - bool actsAsBonusSourceOnly() const; - ///updates count of remaining turns and removes outdated bonuses by selector - void reduceBonusDurations(const CSelector &s); - virtual std::string bonusToString(const std::shared_ptr& bonus, bool description) const {return "";}; //description or bonus name - virtual std::string nodeName() const; - virtual std::string nodeShortInfo() const; - bool isHypothetic() const { return isHypotheticNode; } - - void deserializationFix(); - void exportBonus(const std::shared_ptr & b); - void exportBonuses(); - - const BonusList &getBonusList() const; - BonusList & getExportedBonusList(); - const BonusList & getExportedBonusList() const; - CBonusSystemNode::ENodeTypes getNodeType() const; - void setNodeType(CBonusSystemNode::ENodeTypes type); - const TNodesVector &getParentNodes() const; - const TNodesVector &getChildrenNodes() const; - const std::string &getDescription() const; - void setDescription(const std::string &description); - - static void treeHasChanged(); - - int64_t getTreeVersion() const override; - - virtual PlayerColor getOwner() const - { - return PlayerColor::NEUTRAL; - } - - template void serialize(Handler &h, const int version) - { -// h & bonuses; - h & nodeType; - h & exportedBonuses; - h & description; - BONUS_TREE_DESERIALIZATION_FIX - //h & parents & children; - } - - friend class CBonusProxy; -}; - -class DLL_LINKAGE IPropagator -{ -public: - virtual ~IPropagator() = default; - virtual bool shouldBeAttached(CBonusSystemNode *dest); - virtual CBonusSystemNode::ENodeTypes getPropagatorType() const; - - template void serialize(Handler &h, const int version) - {} -}; - -class DLL_LINKAGE CPropagatorNodeType : public IPropagator -{ - CBonusSystemNode::ENodeTypes nodeType; - -public: - CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN); - bool shouldBeAttached(CBonusSystemNode *dest) override; - CBonusSystemNode::ENodeTypes getPropagatorType() const override; - - template void serialize(Handler &h, const int version) - { - h & nodeType; - } -}; - template class CSelectFieldEqual { @@ -880,7 +710,6 @@ extern DLL_LINKAGE const std::map bonusValueMap; extern DLL_LINKAGE const std::map bonusSourceMap; extern DLL_LINKAGE const std::map bonusDurationMap; extern DLL_LINKAGE const std::map bonusLimitEffect; -extern DLL_LINKAGE const std::map bonusPropagatorMap; extern DLL_LINKAGE const std::set deprecatedBonusSet; // BonusList template that requires full interface of CBonusSystemNode diff --git a/lib/bonuses/Propagators.cpp b/lib/bonuses/Propagators.cpp new file mode 100644 index 000000000..1d140f4d1 --- /dev/null +++ b/lib/bonuses/Propagators.cpp @@ -0,0 +1,52 @@ +/* + * Propagators.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 "Propagators.h" + +VCMI_LIB_NAMESPACE_BEGIN + +const std::map bonusPropagatorMap = +{ + {"BATTLE_WIDE", std::make_shared(CBonusSystemNode::BATTLE)}, + {"VISITED_TOWN_AND_VISITOR", std::make_shared(CBonusSystemNode::TOWN_AND_VISITOR)}, + {"PLAYER_PROPAGATOR", std::make_shared(CBonusSystemNode::PLAYER)}, + {"HERO", std::make_shared(CBonusSystemNode::HERO)}, + {"TEAM_PROPAGATOR", std::make_shared(CBonusSystemNode::TEAM)}, //untested + {"GLOBAL_EFFECT", std::make_shared(CBonusSystemNode::GLOBAL_EFFECTS)} +}; //untested + +bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) +{ + return false; +} + +CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const +{ + return CBonusSystemNode::ENodeTypes::NONE; +} + +CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType) + : nodeType(NodeType) +{ +} + +CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const +{ + return nodeType; +} + +bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) +{ + return nodeType == dest->getNodeType(); +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/bonuses/Propagators.h b/lib/bonuses/Propagators.h new file mode 100644 index 000000000..8cf4757bb --- /dev/null +++ b/lib/bonuses/Propagators.h @@ -0,0 +1,45 @@ +/* + * Propagators.h, 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 + * + */ +#pragma once + +#include "HeroBonus.h" +#include "CBonusSystemNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +extern DLL_LINKAGE const std::map bonusPropagatorMap; + +class DLL_LINKAGE IPropagator +{ +public: + virtual ~IPropagator() = default; + virtual bool shouldBeAttached(CBonusSystemNode *dest); + virtual CBonusSystemNode::ENodeTypes getPropagatorType() const; + + template void serialize(Handler &h, const int version) + {} +}; + +class DLL_LINKAGE CPropagatorNodeType : public IPropagator +{ + CBonusSystemNode::ENodeTypes nodeType; + +public: + CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN); + bool shouldBeAttached(CBonusSystemNode *dest) override; + CBonusSystemNode::ENodeTypes getPropagatorType() const override; + + template void serialize(Handler &h, const int version) + { + h & nodeType; + } +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h index 5a99dd80e..817d5f4e4 100644 --- a/lib/mapObjects/CArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -12,6 +12,7 @@ #include "CObjectHandler.h" #include "../CCreatureSet.h" #include "../bonuses/CBonusProxy.h" +#include "../bonuses/CBonusSystemNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 2d879197e..dc1115bb0 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -22,8 +22,10 @@ #include "../mapObjects/CommonConstructors.h" #include "../mapObjects/MapObjects.h" #include "../battle/CObstacleInstance.h" +#include "../bonuses/CBonusSystemNode.h" #include "../bonuses/Limiters.h" #include "../bonuses/Updaters.h" +#include "../bonuses/Propagators.h" #include "../CStack.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/serializer/JsonUpdater.cpp b/lib/serializer/JsonUpdater.cpp index eba52d2e2..e7edc7d75 100644 --- a/lib/serializer/JsonUpdater.cpp +++ b/lib/serializer/JsonUpdater.cpp @@ -12,6 +12,7 @@ #include "../JsonNode.h" +#include "../bonuses/CBonusSystemNode.h" #include "../bonuses/HeroBonus.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index 14e42ac41..bc80de751 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -15,6 +15,7 @@ #include "../CBonusTypeHandler.h" #include "../battle/CBattleInfoCallback.h" #include "../battle/Unit.h" +#include "../bonuses/BonusParams.h" #include "../serializer/JsonSerializeFormat.h" #include "../VCMI_Lib.h"