From 16cfb51f3ea48f8c93626410709040f6131eeac0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 24 Dec 2024 23:11:20 +0000 Subject: [PATCH 01/12] Initial version of new bonus caching system --- include/vcmi/FactionMember.h | 4 - lib/BasicTypes.cpp | 8 -- lib/CMakeLists.txt | 2 + lib/battle/CUnitState.cpp | 100 +++++++++----- lib/battle/CUnitState.h | 70 ++++++---- lib/bonuses/BonusCache.cpp | 81 +++++++++++ lib/bonuses/BonusCache.h | 81 +++++++++++ lib/bonuses/CBonusProxy.cpp | 168 ----------------------- lib/bonuses/CBonusProxy.h | 56 -------- lib/mapObjects/CGHeroInstance.cpp | 36 ++--- lib/mapObjects/CGHeroInstance.h | 8 +- server/battles/BattleActionProcessor.cpp | 4 +- 12 files changed, 286 insertions(+), 332 deletions(-) create mode 100644 lib/bonuses/BonusCache.cpp create mode 100644 lib/bonuses/BonusCache.h diff --git a/include/vcmi/FactionMember.h b/include/vcmi/FactionMember.h index c87049ecb..28d86d355 100644 --- a/include/vcmi/FactionMember.h +++ b/include/vcmi/FactionMember.h @@ -44,10 +44,6 @@ public: Returns defence of creature or hero. */ virtual int getDefense(bool ranged) const; - /** - Returns primskill of creature or hero. - */ - int getPrimSkillLevel(PrimarySkill id) const; /** Returns morale of creature or hero. Taking absolute bonuses into account. For now, uses range from EGameSettings diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index 03bbe2a48..a696b20ac 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -69,14 +69,6 @@ int AFactionMember::getMaxDamage(bool ranged) const return getBonusBearer()->valOfBonuses(selector, cachingStr); } -int AFactionMember::getPrimSkillLevel(PrimarySkill id) const -{ - auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL); - int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id))); - int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, id.getNum()); - return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves -} - int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const { int32_t maxGoodMorale = VLC->engineSettings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0e94a25e3..45caf5f54 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -65,6 +65,7 @@ set(lib_MAIN_SRCS battle/Unit.cpp bonuses/Bonus.cpp + bonuses/BonusCache.cpp bonuses/BonusEnum.cpp bonuses/BonusList.cpp bonuses/BonusParams.cpp @@ -435,6 +436,7 @@ set(lib_MAIN_HEADERS battle/Unit.h bonuses/Bonus.h + bonuses/BonusCache.h bonuses/BonusEnum.h bonuses/BonusList.h bonuses/BonusParams.h diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 994705b25..53beb4aac 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -26,7 +26,7 @@ namespace battle CAmmo::CAmmo(const battle::Unit * Owner, CSelector totalSelector): used(0), owner(Owner), - totalProxy(Owner, std::move(totalSelector)) + totalProxy(Owner, totalSelector) { reset(); } @@ -34,7 +34,6 @@ CAmmo::CAmmo(const battle::Unit * Owner, CSelector totalSelector): CAmmo & CAmmo::operator= (const CAmmo & other) { used = other.used; - totalProxy = other.totalProxy; return *this; } @@ -60,7 +59,7 @@ void CAmmo::reset() int32_t CAmmo::total() const { - return totalProxy->totalValue(); + return totalProxy.getValue(); } void CAmmo::use(int32_t amount) @@ -89,13 +88,6 @@ CShots::CShots(const battle::Unit * Owner) { } -CShots & CShots::operator=(const CShots & other) -{ - CAmmo::operator=(other); - shooter = other.shooter; - return *this; -} - bool CShots::isLimited() const { return !shooter.getHasBonus() || !env->unitHasAmmoCart(owner); @@ -140,7 +132,7 @@ int32_t CRetaliations::total() const return 0; //after dispel bonus should remain during current round - int32_t val = 1 + totalProxy->totalValue(); + int32_t val = 1 + totalProxy.getValue(); vstd::amax(totalCache, val); return totalCache; } @@ -341,12 +333,7 @@ CUnitState::CUnitState(): counterAttacks(this), health(this), shots(this), - totalAttacks(this, Selector::type()(BonusType::ADDITIONAL_ATTACK), 1), - minDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)), 0), - maxDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)), 0), - attack(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)), 0), - defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)), 0), - inFrenzy(this, Selector::type()(BonusType::IN_FRENZY)), + bonusCache(this, generateBonusSelectors()), cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"), cloneID(-1) { @@ -374,12 +361,7 @@ CUnitState & CUnitState::operator=(const CUnitState & other) counterAttacks = other.counterAttacks; health = other.health; shots = other.shots; - totalAttacks = other.totalAttacks; - minDamage = other.minDamage; - maxDamage = other.maxDamage; - attack = other.attack; - defence = other.defence; - inFrenzy = other.inFrenzy; +// bonusCache = other.bonusCache; cloneLifetimeMarker = other.cloneLifetimeMarker; cloneID = other.cloneID; position = other.position; @@ -695,45 +677,62 @@ BattlePhases::Type CUnitState::battleQueuePhase(int turn) const int CUnitState::getTotalAttacks(bool ranged) const { - return ranged ? totalAttacks.getRangedValue() : totalAttacks.getMeleeValue(); + return 1 + (ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_MELEE)); } int CUnitState::getMinDamage(bool ranged) const { - return ranged ? minDamage.getRangedValue() : minDamage.getMeleeValue(); + return ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_MELEE); + } int CUnitState::getMaxDamage(bool ranged) const { - return ranged ? maxDamage.getRangedValue() : maxDamage.getMeleeValue(); + return ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_MELEE); } int CUnitState::getAttack(bool ranged) const { - int ret = ranged ? attack.getRangedValue() : attack.getMeleeValue(); + int attack = ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::ATTACK_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::ATTACK_MELEE); - if(!inFrenzy->empty()) + int frenzy = bonusCache.cache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); + if(frenzy != 0) { - double frenzyPower = static_cast(inFrenzy->totalValue()) / 100; - frenzyPower *= static_cast(ranged ? defence.getRangedValue() : defence.getMeleeValue()); - ret += static_cast(frenzyPower); + int defence = ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); + + int frenzyBonus = frenzy * defence / 100; + attack += frenzyBonus; } - vstd::amax(ret, 0); - return ret; + vstd::amax(attack, 0); + return attack; } int CUnitState::getDefense(bool ranged) const { - if(!inFrenzy->empty()) + int frenzy = bonusCache.cache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); + + if(frenzy != 0) { return 0; } else { - int ret = ranged ? defence.getRangedValue() : defence.getMeleeValue(); - vstd::amax(ret, 0); - return ret; + int defence = ranged ? + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); + vstd::amax(defence, 0); + return defence; } } @@ -911,6 +910,33 @@ void CUnitState::onRemoved() ghost = true; } +const UnitBonusValuesProxy::SelectorsArray * CUnitState::generateBonusSelectors() +{ + static const CSelector additionalAttack = Selector::type()(BonusType::ADDITIONAL_ATTACK); + static const CSelector selectorMelee = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT)); + static const CSelector selectorRanged = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)); + static const CSelector minDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); + static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); + static const CSelector attack = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); + static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); + + static const UnitBonusValuesProxy::SelectorsArray selectors = { + additionalAttack.And(selectorMelee), //TOTAL_ATTACKS_MELEE, + additionalAttack.And(selectorRanged), //TOTAL_ATTACKS_RANGED, + minDamage.And(selectorMelee), //MIN_DAMAGE_MELEE, + minDamage.And(selectorRanged), //MIN_DAMAGE_RANGED, + minDamage.And(selectorMelee), //MAX_DAMAGE_MELEE, + maxDamage.And(selectorRanged), //MAX_DAMAGE_RANGED, + attack.And(selectorRanged),//ATTACK_MELEE, + attack.And(selectorRanged),//ATTACK_RANGED, + defence.And(selectorRanged),//DEFENCE_MELEE, + defence.And(selectorRanged),//DEFENCE_RANGED, + Selector::type()(BonusType::IN_FRENZY),//IN_FRENZY, + }; + + return &selectors; +} + CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_): unit(unit_), bonus(bonus_) diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 9bb9570a1..f43474209 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -12,6 +12,7 @@ #include "Unit.h" #include "../bonuses/CBonusProxy.h" +#include "../bonuses/BonusCache.h" VCMI_LIB_NAMESPACE_BEGIN @@ -32,10 +33,6 @@ class DLL_LINKAGE CAmmo public: explicit CAmmo(const battle::Unit * Owner, CSelector totalSelector); - //only copy construction is allowed for acquire(), serializeJson should be used for any other "assignment" - CAmmo(const CAmmo & other) = default; - CAmmo(CAmmo && other) = delete; - CAmmo & operator=(const CAmmo & other); CAmmo & operator=(CAmmo && other) = delete; @@ -50,15 +47,14 @@ public: protected: int32_t used; const battle::Unit * owner; - CBonusProxy totalProxy; + BonusValueCache totalProxy; }; class DLL_LINKAGE CShots : public CAmmo { public: explicit CShots(const battle::Unit * Owner); - CShots(const CShots & other) = default; - CShots & operator=(const CShots & other); + bool isLimited() const override; int32_t total() const override; @@ -73,16 +69,13 @@ class DLL_LINKAGE CCasts : public CAmmo { public: explicit CCasts(const battle::Unit * Owner); - CCasts(const CCasts & other) = default; - CCasts & operator=(const CCasts & other) = default; }; class DLL_LINKAGE CRetaliations : public CAmmo { public: explicit CRetaliations(const battle::Unit * Owner); - CRetaliations(const CRetaliations & other) = default; - CRetaliations & operator=(const CRetaliations & other) = default; + bool isLimited() const override; int32_t total() const override; void reset() override; @@ -132,6 +125,41 @@ private: int32_t resurrected; }; +class UnitBonusValuesProxy +{ +public: + enum ECacheKeys : uint8_t + { + TOTAL_ATTACKS_MELEE, + TOTAL_ATTACKS_RANGED, + + MIN_DAMAGE_MELEE, + MIN_DAMAGE_RANGED, + MAX_DAMAGE_MELEE, + MAX_DAMAGE_RANGED, + + ATTACK_MELEE, + ATTACK_RANGED, + + DEFENCE_MELEE, + DEFENCE_RANGED, + + IN_FRENZY, + + TOTAL_KEYS, + }; + + static constexpr size_t KEYS_COUNT = static_cast(ECacheKeys::TOTAL_KEYS); + + BonusValuesArrayCache cache; + + using SelectorsArray = BonusValuesArrayCache::SelectorsArray; + + UnitBonusValuesProxy(const IBonusBearer * Target, const SelectorsArray * selectors): + cache(Target, selectors) + {} +}; + class DLL_LINKAGE CUnitState : public Unit { public: @@ -154,11 +182,6 @@ public: CHealth health; CShots shots; - CTotalsProxy totalAttacks; - - CTotalsProxy minDamage; - CTotalsProxy maxDamage; - ///id of alive clone of this stack clone if any si32 cloneID; @@ -266,12 +289,11 @@ public: void onRemoved(); private: + static const UnitBonusValuesProxy::SelectorsArray * generateBonusSelectors(); + const IUnitEnvironment * env; - CTotalsProxy attack; - CTotalsProxy defence; - CBonusProxy inFrenzy; - + UnitBonusValuesProxy bonusCache; CCheckProxy cloneLifetimeMarker; void reset(); @@ -282,13 +304,12 @@ class DLL_LINKAGE CUnitStateDetached : public CUnitState public: explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_); - TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, - const std::string & cachingStr = "") const override; + CUnitStateDetached & operator= (const CUnitState & other); + + TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override; int64_t getTreeVersion() const override; - CUnitStateDetached & operator= (const CUnitState & other); - uint32_t unitId() const override; BattleSide unitSide() const override; @@ -297,7 +318,6 @@ public: SlotID unitSlot() const override; - int32_t unitBaseAmount() const override; void spendMana(ServerCallback * server, const int spellCost) const override; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp new file mode 100644 index 000000000..9dd594efb --- /dev/null +++ b/lib/bonuses/BonusCache.cpp @@ -0,0 +1,81 @@ +/* + * BonusCache.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 "BonusCache.h" +#include "IBonusBearer.h" + +#include "BonusSelector.h" +#include "BonusList.h" + +#include "../VCMI_Lib.h" +#include "../IGameSettings.h" + +int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector) const +{ + if (target->getTreeVersion() == currentValue.version) + { + return currentValue.value; + } + else + { + // NOTE: following code theoretically can fail if bonus tree was changed by another thread between two following lines + // However, this situation should not be possible - gamestate modification should only happen in single-treaded mode with locked gamestate mutex + int newValue = target->valOfBonuses(selector); + currentValue.value = newValue; + currentValue.version = target->getTreeVersion(); + + return newValue; + } +} + +BonusValueCache::BonusValueCache(const IBonusBearer * target, const CSelector selector) + :BonusCacheBase(target),selector(selector) +{} + +int BonusValueCache::getValue() const +{ + return getBonusValueImpl(value, selector); +} + +PrimarySkillsCache::PrimarySkillsCache(const IBonusBearer * target) + :target(target) +{} + +void PrimarySkillsCache::update() const +{ + static const CSelector primarySkillsSelector = Selector::type()(BonusType::PRIMARY_SKILL); + static const CSelector attackSelector = Selector::subtype()(PrimarySkill::ATTACK); + static const CSelector defenceSelector = Selector::subtype()(PrimarySkill::DEFENSE); + static const CSelector spellPowerSelector = Selector::subtype()(PrimarySkill::SPELL_POWER); + static const CSelector knowledgeSelector = Selector::subtype()(PrimarySkill::KNOWLEDGE); + + std::array minValues = { + VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::ATTACK), + VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::DEFENSE), + VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::SPELL_POWER), + VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::KNOWLEDGE) + }; + + auto list = target->getBonuses(primarySkillsSelector); + skills[PrimarySkill::ATTACK] = std::max(minValues[PrimarySkill::ATTACK], list->valOfBonuses(attackSelector)); + skills[PrimarySkill::DEFENSE] = std::max(minValues[PrimarySkill::DEFENSE], list->valOfBonuses(defenceSelector)); + skills[PrimarySkill::SPELL_POWER] = std::max(minValues[PrimarySkill::SPELL_POWER], list->valOfBonuses(spellPowerSelector)); + skills[PrimarySkill::KNOWLEDGE] = std::max(minValues[PrimarySkill::KNOWLEDGE], list->valOfBonuses(knowledgeSelector)); + + version = target->getTreeVersion(); +} + +const std::array, 4> & PrimarySkillsCache::getSkills() const +{ + if (target->getTreeVersion() != version) + update(); + return skills; +} diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h new file mode 100644 index 000000000..29ac26d39 --- /dev/null +++ b/lib/bonuses/BonusCache.h @@ -0,0 +1,81 @@ +/* + * BonusCache.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 "BonusSelector.h" + +/// Internal base class with no own cache +class BonusCacheBase +{ + const IBonusBearer * target; + +protected: + explicit BonusCacheBase(const IBonusBearer * target): + target(target) + {} + + struct BonusCacheEntry + { + std::atomic version = 0; + std::atomic value = 0; + }; + + int getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector) const; +}; + +/// Cache that tracks a single query to bonus system +class BonusValueCache : public BonusCacheBase +{ + CSelector selector; + mutable BonusCacheEntry value; +public: + BonusValueCache(const IBonusBearer * target, const CSelector selector); + int getValue() const; +}; + +/// Cache that can track a list of queries to bonus system +template +class BonusValuesArrayCache : public BonusCacheBase +{ +public: + using SelectorsArray = std::array; + + BonusValuesArrayCache(const IBonusBearer * target, const SelectorsArray * selectors) + : BonusCacheBase(target) + , selectors(selectors) + {} + + int getBonusValue(EnumType which) const + { + auto index = static_cast(which); + return getBonusValueImpl(cache[index], (*selectors)[index]); + } + +private: + using CacheArray = std::array; + + const SelectorsArray * selectors; + mutable CacheArray cache; +}; + +/// Cache that tracks values of primary skill values in bonus system +class PrimarySkillsCache +{ + const IBonusBearer * target; + mutable std::atomic version = 0; + mutable std::array, 4> skills; + + void update() const; +public: + PrimarySkillsCache(const IBonusBearer * target); + + const std::array, 4> & getSkills() const; +}; diff --git a/lib/bonuses/CBonusProxy.cpp b/lib/bonuses/CBonusProxy.cpp index aa99e9a7d..263f42ad8 100644 --- a/lib/bonuses/CBonusProxy.cpp +++ b/lib/bonuses/CBonusProxy.cpp @@ -9,179 +9,11 @@ */ #include "StdInc.h" -#include "BonusList.h" #include "CBonusProxy.h" #include "IBonusBearer.h" VCMI_LIB_NAMESPACE_BEGIN -///CBonusProxy -CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector): - bonusListCachedLast(0), - target(Target), - selector(std::move(Selector)), - currentBonusListIndex(0) -{ -} - -CBonusProxy::CBonusProxy(const CBonusProxy & other): - bonusListCachedLast(other.bonusListCachedLast), - target(other.target), - selector(other.selector), - currentBonusListIndex(other.currentBonusListIndex) -{ - bonusList[currentBonusListIndex] = other.bonusList[currentBonusListIndex]; -} - -CBonusProxy::CBonusProxy(CBonusProxy && other) noexcept: - bonusListCachedLast(0), - target(other.target), - currentBonusListIndex(0) -{ - std::swap(bonusListCachedLast, other.bonusListCachedLast); - std::swap(selector, other.selector); - std::swap(bonusList, other.bonusList); - std::swap(currentBonusListIndex, other.currentBonusListIndex); -} - -CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other) -{ - boost::lock_guard lock(swapGuard); - - selector = other.selector; - swapBonusList(other.bonusList[other.currentBonusListIndex]); - bonusListCachedLast = other.bonusListCachedLast; - - return *this; -} - -CBonusProxy & CBonusProxy::operator=(CBonusProxy && other) noexcept -{ - std::swap(bonusListCachedLast, other.bonusListCachedLast); - std::swap(selector, other.selector); - std::swap(bonusList, other.bonusList); - std::swap(currentBonusListIndex, other.currentBonusListIndex); - - return *this; -} - -void CBonusProxy::swapBonusList(TConstBonusListPtr other) const -{ - // The idea here is to avoid changing active bonusList while it can be read by a different thread. - // Because such use of shared ptr is not thread safe - // So to avoid this we change the second offline instance and swap active index - auto newCurrent = 1 - currentBonusListIndex; - bonusList[newCurrent] = std::move(other); - currentBonusListIndex = newCurrent; -} - -TConstBonusListPtr CBonusProxy::getBonusList() const -{ - auto needUpdateBonusList = [&]() -> bool - { - return target->getTreeVersion() != bonusListCachedLast || !bonusList[currentBonusListIndex]; - }; - - // avoid locking if everything is up-to-date - if(needUpdateBonusList()) - { - boost::lock_guardlock(swapGuard); - - if(needUpdateBonusList()) - { - //TODO: support limiters - swapBonusList(target->getAllBonuses(selector, Selector::all)); - bonusListCachedLast = target->getTreeVersion(); - } - } - - return bonusList[currentBonusListIndex]; -} - -const BonusList * CBonusProxy::operator->() const -{ - return getBonusList().get(); -} - -CTotalsProxy::CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue): - CBonusProxy(Target, std::move(Selector)), - initialValue(InitialValue), - meleeCachedLast(0), - meleeValue(0), - rangedCachedLast(0), - rangedValue(0) -{ -} - -CTotalsProxy::CTotalsProxy(const CTotalsProxy & other) - : CBonusProxy(other), - initialValue(other.initialValue), - meleeCachedLast(other.meleeCachedLast), - meleeValue(other.meleeValue), - rangedCachedLast(other.rangedCachedLast), - rangedValue(other.rangedValue) -{ -} - -int CTotalsProxy::getValue() const -{ - const auto treeVersion = target->getTreeVersion(); - - if(treeVersion != valueCachedLast) - { - auto bonuses = getBonusList(); - - value = initialValue + bonuses->totalValue(); - valueCachedLast = treeVersion; - } - return value; -} - -int CTotalsProxy::getValueAndList(TConstBonusListPtr & outBonusList) const -{ - const auto treeVersion = target->getTreeVersion(); - outBonusList = getBonusList(); - - if(treeVersion != valueCachedLast) - { - value = initialValue + outBonusList->totalValue(); - valueCachedLast = treeVersion; - } - return value; -} - -int CTotalsProxy::getMeleeValue() const -{ - static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT)); - - const auto treeVersion = target->getTreeVersion(); - - if(treeVersion != meleeCachedLast) - { - auto bonuses = target->getBonuses(selector, limit); - meleeValue = initialValue + bonuses->totalValue(); - meleeCachedLast = treeVersion; - } - - return meleeValue; -} - -int CTotalsProxy::getRangedValue() const -{ - static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)); - - const auto treeVersion = target->getTreeVersion(); - - if(treeVersion != rangedCachedLast) - { - auto bonuses = target->getBonuses(selector, limit); - rangedValue = initialValue + bonuses->totalValue(); - rangedCachedLast = treeVersion; - } - - return rangedValue; -} - ///CCheckProxy CCheckProxy::CCheckProxy(const IBonusBearer * Target, BonusType bonusType): target(Target), diff --git a/lib/bonuses/CBonusProxy.h b/lib/bonuses/CBonusProxy.h index f7e3d6cbf..68fc1ebe7 100644 --- a/lib/bonuses/CBonusProxy.h +++ b/lib/bonuses/CBonusProxy.h @@ -10,66 +10,10 @@ #pragma once -#include "Bonus.h" #include "BonusSelector.h" VCMI_LIB_NAMESPACE_BEGIN -class DLL_LINKAGE CBonusProxy -{ -public: - CBonusProxy(const IBonusBearer * Target, CSelector Selector); - CBonusProxy(const CBonusProxy & other); - CBonusProxy(CBonusProxy && other) noexcept; - - CBonusProxy & operator=(CBonusProxy && other) noexcept; - CBonusProxy & operator=(const CBonusProxy & other); - const BonusList * operator->() const; - TConstBonusListPtr getBonusList() const; - -protected: - CSelector selector; - const IBonusBearer * target; - mutable int64_t bonusListCachedLast; - mutable TConstBonusListPtr bonusList[2]; - mutable int currentBonusListIndex; - mutable boost::mutex swapGuard; - void swapBonusList(TConstBonusListPtr other) const; -}; - -class DLL_LINKAGE CTotalsProxy : public CBonusProxy -{ -public: - CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue); - CTotalsProxy(const CTotalsProxy & other); - CTotalsProxy(CTotalsProxy && other) = delete; - - CTotalsProxy & operator=(const CTotalsProxy & other) = default; - CTotalsProxy & operator=(CTotalsProxy && other) = delete; - - int getMeleeValue() const; - int getRangedValue() const; - int getValue() const; - /** - Returns total value of all selected bonuses and sets bonusList as a pointer to the list of selected bonuses - @param bonusList is the out list of all selected bonuses - @return total value of all selected bonuses and 0 otherwise - */ - int getValueAndList(TConstBonusListPtr & bonusList) const; - -private: - int initialValue; - - mutable int64_t valueCachedLast = 0; - mutable int value = 0; - - mutable int64_t meleeCachedLast; - mutable int meleeValue; - - mutable int64_t rangedCachedLast; - mutable int rangedValue; -}; - class DLL_LINKAGE CCheckProxy { public: diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 81ef94c21..c83418be9 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -293,6 +293,7 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb) level(1), exp(UNINITIALIZED_EXPERIENCE), gender(EHeroGender::DEFAULT), + primarySkills(this), lowestCreatureSpeed(0) { setNodeType(HERO); @@ -704,40 +705,20 @@ void CGHeroInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier) setStackCount(SlotID(0), identifier.getNum()); } -std::array CGHeroInstance::getPrimarySkills() const +int CGHeroInstance::getPrimSkillLevel(PrimarySkill id) const { - std::array result; - - auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL); - for (auto skill : PrimarySkill::ALL_SKILLS()) - { - int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(skill))); - int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, skill.getNum()); - result[skill] = std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect - } - - return result; + return primarySkills.getSkills()[id]; } double CGHeroInstance::getFightingStrength() const { - const auto & primarySkills = getPrimarySkills(); - return getFightingStrengthImpl(primarySkills); -} - -double CGHeroInstance::getFightingStrengthImpl(const std::array & primarySkills) const -{ - return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::ATTACK]) * (1.0 + 0.05*primarySkills[PrimarySkill::DEFENSE])); + const auto & skillValues = primarySkills.getSkills(); + return sqrt((1.0 + 0.05*skillValues[PrimarySkill::ATTACK]) * (1.0 + 0.05*skillValues[PrimarySkill::DEFENSE])); } double CGHeroInstance::getMagicStrength() const { - const auto & primarySkills = getPrimarySkills(); - return getMagicStrengthImpl(primarySkills); -} - -double CGHeroInstance::getMagicStrengthImpl(const std::array & primarySkills) const -{ + const auto & skillValues = primarySkills.getSkills(); if (!hasSpellbook()) return 1; bool atLeastOneCombatSpell = false; @@ -751,13 +732,12 @@ double CGHeroInstance::getMagicStrengthImpl(const std::array & primarySk } if (!atLeastOneCombatSpell) return 1; - return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*primarySkills[PrimarySkill::SPELL_POWER] * mana / manaLimit())); + return sqrt((1.0 + 0.05*skillValues[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*skillValues[PrimarySkill::SPELL_POWER] * mana / manaLimit())); } double CGHeroInstance::getHeroStrength() const { - const auto & primarySkills = getPrimarySkills(); - return getFightingStrengthImpl(primarySkills) * getMagicStrengthImpl(primarySkills); + return getFightingStrength() * getMagicStrength(); } uint64_t CGHeroInstance::getValueForDiplomacy() const diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 2a7494415..43117397b 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -14,6 +14,7 @@ #include "CArmedInstance.h" #include "IOwnableObject.h" +#include "../bonuses/BonusCache.h" #include "../entities/hero/EHeroGender.h" #include "../CArtHandler.h" // For CArtifactSet @@ -58,13 +59,12 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, friend class CMapFormatJson; private: + PrimarySkillsCache primarySkills; + std::set spells; //known spells (spell IDs) mutable int lowestCreatureSpeed; ui32 movement; //remaining movement points - double getFightingStrengthImpl(const std::array & primarySkills) const; - double getMagicStrengthImpl(const std::array & primarySkills) const; - public: ////////////////////////////////////////////////////////////////////////// @@ -204,7 +204,7 @@ public: std::vector getLevelUpProposedSecondarySkills(vstd::RNG & rand) const; ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill - std::array getPrimarySkills() const; + int getPrimSkillLevel(PrimarySkill id) const; /// Returns true if hero has free secondary skill slot. bool canLearnSkill() const; diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 767cfc065..cdfed2975 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -257,7 +257,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c } //attack - int totalAttacks = stack->totalAttacks.getMeleeValue(); + int totalAttacks = stack->getTotalAttacks(false); //TODO: move to CUnitState const auto * attackingHero = battle.battleGetFightingHero(ba.side); @@ -378,7 +378,7 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co } //allow more than one additional attack - int totalRangedAttacks = stack->totalAttacks.getRangedValue(); + int totalRangedAttacks = stack->getTotalAttacks(true); //TODO: move to CUnitState const auto * attackingHero = battle.battleGetFightingHero(ba.side); From 05397e2aaf5a241c1e158f4e701b865e466c50ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 25 Dec 2024 11:40:18 +0000 Subject: [PATCH 02/12] Caching of bonuses that are requested with different durations --- include/vcmi/Creature.h | 4 +-- lib/battle/CUnitState.cpp | 19 ++++++++++--- lib/battle/CUnitState.h | 5 ++++ lib/bonuses/BonusCache.cpp | 56 ++++++++++++++++++++++++++++++++++++++ lib/bonuses/BonusCache.h | 31 ++++++++++++++++++++- 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index a1334652d..ad74e0cab 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -23,8 +23,8 @@ class DLL_LINKAGE ACreature: public AFactionMember { public: bool isLiving() const; //non-undead, non-non living or alive - ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators - ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators + virtual ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators + virtual ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers }; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 53beb4aac..b09fea2e6 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -333,6 +333,8 @@ CUnitState::CUnitState(): counterAttacks(this), health(this), shots(this), + stackSpeedPerTurn(this, Selector::type()(BonusType::STACKS_SPEED), BonusCacheMode::VALUE), + immobilizedPerTurn(this, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::BIND_EFFECT)), BonusCacheMode::PRESENCE), bonusCache(this, generateBonusSelectors()), cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"), cloneID(-1) @@ -573,11 +575,20 @@ void CUnitState::setPosition(BattleHex hex) int32_t CUnitState::getInitiative(int turn) const { - if (turn == 0) - return valOfBonuses(BonusType::STACKS_SPEED); + return stackSpeedPerTurn.getValue(turn); +} - std::string cachingStr = "type_STACKS_SPEED_turns_" + std::to_string(turn); - return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStr); +ui32 CUnitState::getMovementRange(int turn) const +{ + if (immobilizedPerTurn.getValue(0) != 0) + return 0; + + return stackSpeedPerTurn.getValue(0); +} + +ui32 CUnitState::getMovementRange() const +{ + return getMovementRange(0); } uint8_t CUnitState::getRangedFullDamageDistance() const diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index f43474209..9c1a5649c 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -248,6 +248,9 @@ public: uint8_t getRangedFullDamageDistance() const; uint8_t getShootingRangeDistance() const; + ui32 getMovementRange(int turn) const override; + ui32 getMovementRange() const override; + bool canMove(int turn = 0) const override; bool defended(int turn = 0) const override; bool moved(int turn = 0) const override; @@ -293,6 +296,8 @@ private: const IUnitEnvironment * env; + BonusCachePerTurn immobilizedPerTurn; + BonusCachePerTurn stackSpeedPerTurn; UnitBonusValuesProxy bonusCache; CCheckProxy cloneLifetimeMarker; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 9dd594efb..cb4863c4c 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -79,3 +79,59 @@ const std::array, 4> & PrimarySkillsCache::getSkills() cons update(); return skills; } + +int BonusCachePerTurn::getValueUncached(int turns) const +{ + std::lock_guard lock(bonusListMutex); + + int nodeTreeVersion = target->getTreeVersion(); + + if (bonusListVersion != nodeTreeVersion) + { + bonusList = target->getBonuses(selector); + bonusListVersion = nodeTreeVersion; + } + + if (mode == BonusCacheMode::VALUE) + { + if (turns != 0) + return bonusList->valOfBonuses(Selector::turns(turns)); + else + return bonusList->totalValue(); + } + else + { + if (turns != 0) + return bonusList->getFirst(Selector::turns(turns)) != nullptr; + else + return !bonusList->empty(); + } +} + +int BonusCachePerTurn::getValue(int turns) const +{ + int nodeTreeVersion = target->getTreeVersion(); + + if (turns < cachedTurns) + { + auto & entry = cache[turns]; + if (entry.version == nodeTreeVersion) + { + // best case: value is in cache and up-to-date + return entry.value; + } + else + { + // else - compute value and update it in the cache + int newValue = getValueUncached(turns); + entry.value = newValue; + entry.version = nodeTreeVersion; + return newValue; + } + } + else + { + // non-cacheable value - compute and return (should be 0 / close to 0 calls) + return getValueUncached(turns); + } +} diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 29ac26d39..8f247b8f8 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -12,12 +12,18 @@ #include "BonusSelector.h" +enum class BonusCacheMode +{ + VALUE, // total value of bonus will be cached + PRESENCE, // presence of bonus will be cached +}; + /// Internal base class with no own cache class BonusCacheBase { +protected: const IBonusBearer * target; -protected: explicit BonusCacheBase(const IBonusBearer * target): target(target) {} @@ -79,3 +85,26 @@ public: const std::array, 4> & getSkills() const; }; + +/// Cache that tracks values for different values of bonus duration +class BonusCachePerTurn : public BonusCacheBase +{ + static constexpr int cachedTurns = 8; + + const CSelector selector; + mutable TConstBonusListPtr bonusList; + mutable std::mutex bonusListMutex; + mutable std::atomic bonusListVersion = 0; + mutable std::array cache; + const BonusCacheMode mode; + + int getValueUncached(int turns) const; +public: + BonusCachePerTurn(const IBonusBearer * target, const CSelector & selector, BonusCacheMode mode) + : BonusCacheBase(target) + , selector(selector) + , mode(mode) + {} + + int getValue(int turns) const; +}; From 157d6d30c86f4538b6e5f8fbd7fe47995be8c8e4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 25 Dec 2024 21:25:06 +0000 Subject: [PATCH 03/12] Move rest of commonly-accessed UnitState queries to bonus cache --- client/NetPacksClient.cpp | 2 +- lib/battle/CBattleInfoCallback.cpp | 16 +--------------- lib/battle/CBattleInfoEssentials.cpp | 2 +- lib/battle/CUnitState.cpp | 23 ++++++++++++++++++++++- lib/battle/CUnitState.h | 8 ++++++++ lib/battle/Unit.h | 3 +++ lib/bonuses/BonusCache.cpp | 11 ++++++++--- lib/bonuses/BonusCache.h | 12 +++++++++--- test/mock/mock_battle_Unit.h | 2 ++ 9 files changed, 55 insertions(+), 24 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index cf1c896a1..9b6482b47 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -821,7 +821,7 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack); PlayerColor playerToCall; //pack.player that will move activated stack - if(activated->hasBonusOfType(BonusType::HYPNOTIZED)) + if(activated->isHypnotized()) { playerToCall = gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color == activated->unitOwner() ? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index b14389fdc..a0bf78d14 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -714,18 +714,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const if (!attacker->canShoot()) return false; - //forgetfulness - TConstBonusListPtr forgetfulList = attacker->getBonusesOfType(BonusType::FORGETFULL); - if(!forgetfulList->empty()) - { - int forgetful = forgetfulList->totalValue(); - - //advanced+ level - if(forgetful > 1) - return false; - } - - return !battleIsUnitBlocked(attacker) || attacker->hasBonusOfType(BonusType::FREE_SHOOTING); + return attacker->canShootBlocked() || !battleIsUnitBlocked(attacker); } bool CBattleInfoCallback::battleCanTargetEmptyHex(const battle::Unit * attacker) const @@ -1732,9 +1721,6 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const { RETURN_IF_NOT_BATTLE(false); - if(unit->hasBonusOfType(BonusType::SIEGE_WEAPON)) //siege weapons cannot be blocked - return false; - for(const auto * adjacent : battleAdjacentUnits(unit)) { if(adjacent->unitOwner() != unit->unitOwner()) //blocked by enemy stack diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index b9f23468b..20f02120e 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -404,7 +404,7 @@ PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) con PlayerColor initialOwner = getBattle()->getSidePlayer(unit->unitSide()); - if(unit->hasBonusOfType(BonusType::HYPNOTIZED)) + if(unit->isHypnotized()) return otherPlayer(initialOwner); else return initialOwner; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index b09fea2e6..7602e8b55 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -526,9 +526,16 @@ bool CUnitState::isCaster() const return casts.total() > 0;//do not check specific cast abilities here } +bool CUnitState::canShootBlocked() const +{ + return bonusCache.cache.getBonusValue(UnitBonusValuesProxy::HAS_FREE_SHOOTING); +} + bool CUnitState::canShoot() const { - return shots.canUse(1); + return + shots.canUse(1) && + bonusCache.cache.getBonusValue(UnitBonusValuesProxy::FORGETFULL) <= 1; //advanced+ level } bool CUnitState::isShooter() const @@ -563,6 +570,11 @@ int64_t CUnitState::getTotalHealth() const return health.total(); } +int64_t CUnitState::getMaxHealth() const +{ + return std::max(1, bonusCache.cache.getBonusValue(UnitBonusValuesProxy::STACK_HEALTH)); +} + BattleHex CUnitState::getPosition() const { return position; @@ -686,6 +698,11 @@ BattlePhases::Type CUnitState::battleQueuePhase(int turn) const } } +bool CUnitState::isHypnotized() const +{ + return bonusCache.cache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); +} + int CUnitState::getTotalAttacks(bool ranged) const { return 1 + (ranged ? @@ -943,6 +960,10 @@ const UnitBonusValuesProxy::SelectorsArray * CUnitState::generateBonusSelectors( defence.And(selectorRanged),//DEFENCE_MELEE, defence.And(selectorRanged),//DEFENCE_RANGED, Selector::type()(BonusType::IN_FRENZY),//IN_FRENZY, + Selector::type()(BonusType::FORGETFULL),//FORGETFULL, + Selector::type()(BonusType::HYPNOTIZED),//HYPNOTIZED, + Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, + Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, }; return &selectors; diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 9c1a5649c..66c2ec0b5 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -145,6 +145,10 @@ public: DEFENCE_RANGED, IN_FRENZY, + HYPNOTIZED, + FORGETFULL, + HAS_FREE_SHOOTING, + STACK_HEALTH, TOTAL_KEYS, }; @@ -228,11 +232,14 @@ public: bool isFrozen() const override; bool isValidTarget(bool allowDead = false) const override; + bool isHypnotized() const override; + bool isClone() const override; bool hasClone() const override; bool canCast() const override; bool isCaster() const override; + bool canShootBlocked() const override; bool canShoot() const override; bool isShooter() const override; @@ -241,6 +248,7 @@ public: int32_t getFirstHPleft() const override; int64_t getAvailableHealth() const override; int64_t getTotalHealth() const override; + int64_t getMaxHealth() const override; BattleHex getPosition() const override; void setPosition(BattleHex hex) override; diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index cbe585593..e3b56847d 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -84,11 +84,14 @@ public: bool isTurret() const; virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect) + virtual bool isHypnotized() const = 0; + virtual bool isClone() const = 0; virtual bool hasClone() const = 0; virtual bool canCast() const = 0; virtual bool isCaster() const = 0; + virtual bool canShootBlocked() const = 0; virtual bool canShoot() const = 0; virtual bool isShooter() const = 0; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index cb4863c4c..f50dc4680 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -18,7 +18,7 @@ #include "../VCMI_Lib.h" #include "../IGameSettings.h" -int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector) const +int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector, BonusCacheMode mode) const { if (target->getTreeVersion() == currentValue.version) { @@ -28,7 +28,12 @@ int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSel { // NOTE: following code theoretically can fail if bonus tree was changed by another thread between two following lines // However, this situation should not be possible - gamestate modification should only happen in single-treaded mode with locked gamestate mutex - int newValue = target->valOfBonuses(selector); + int newValue; + + if (mode == BonusCacheMode::VALUE) + newValue = target->valOfBonuses(selector); + else + newValue = target->hasBonus(selector); currentValue.value = newValue; currentValue.version = target->getTreeVersion(); @@ -42,7 +47,7 @@ BonusValueCache::BonusValueCache(const IBonusBearer * target, const CSelector se int BonusValueCache::getValue() const { - return getBonusValueImpl(value, selector); + return getBonusValueImpl(value, selector, BonusCacheMode::VALUE); } PrimarySkillsCache::PrimarySkillsCache(const IBonusBearer * target) diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 8f247b8f8..714f6a40c 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -12,7 +12,7 @@ #include "BonusSelector.h" -enum class BonusCacheMode +enum class BonusCacheMode : int8_t { VALUE, // total value of bonus will be cached PRESENCE, // presence of bonus will be cached @@ -34,7 +34,7 @@ protected: std::atomic value = 0; }; - int getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector) const; + int getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector, BonusCacheMode) const; }; /// Cache that tracks a single query to bonus system @@ -62,7 +62,13 @@ public: int getBonusValue(EnumType which) const { auto index = static_cast(which); - return getBonusValueImpl(cache[index], (*selectors)[index]); + return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::VALUE); + } + + int hasBonus(EnumType which) const + { + auto index = static_cast(which); + return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::PRESENCE); } private: diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index 7560bfdc8..e7efca987 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -57,10 +57,12 @@ public: MOCK_CONST_METHOD0(isFrozen, bool()); MOCK_CONST_METHOD1(isValidTarget, bool(bool)); + MOCK_CONST_METHOD0(isHypnotized, bool()); MOCK_CONST_METHOD0(isClone, bool()); MOCK_CONST_METHOD0(hasClone, bool()); MOCK_CONST_METHOD0(canCast, bool()); MOCK_CONST_METHOD0(isCaster, bool()); + MOCK_CONST_METHOD0(canShootBlocked, bool()); MOCK_CONST_METHOD0(canShoot, bool()); MOCK_CONST_METHOD0(isShooter, bool()); From 919588f7ff76e6c4c5ca77384564e9e61be4e224 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 25 Dec 2024 23:04:15 +0000 Subject: [PATCH 04/12] Added cache for common hero-based bonuses --- lib/battle/CUnitState.cpp | 38 ++++++++-------- lib/battle/CUnitState.h | 41 +---------------- lib/bonuses/BonusCache.cpp | 29 ++++++++++++ lib/bonuses/BonusCache.h | 73 ++++++++++++++++++++++++++++--- lib/mapObjects/CGHeroInstance.cpp | 9 ++-- lib/mapObjects/CGHeroInstance.h | 2 + 6 files changed, 124 insertions(+), 68 deletions(-) diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 7602e8b55..f8d1430fa 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -528,14 +528,14 @@ bool CUnitState::isCaster() const bool CUnitState::canShootBlocked() const { - return bonusCache.cache.getBonusValue(UnitBonusValuesProxy::HAS_FREE_SHOOTING); + return bonusCache.getBonusValue(UnitBonusValuesProxy::HAS_FREE_SHOOTING); } bool CUnitState::canShoot() const { return shots.canUse(1) && - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::FORGETFULL) <= 1; //advanced+ level + bonusCache.getBonusValue(UnitBonusValuesProxy::FORGETFULL) <= 1; //advanced+ level } bool CUnitState::isShooter() const @@ -570,9 +570,9 @@ int64_t CUnitState::getTotalHealth() const return health.total(); } -int64_t CUnitState::getMaxHealth() const +uint32_t CUnitState::getMaxHealth() const { - return std::max(1, bonusCache.cache.getBonusValue(UnitBonusValuesProxy::STACK_HEALTH)); + return std::max(1, bonusCache.getBonusValue(UnitBonusValuesProxy::STACK_HEALTH)); } BattleHex CUnitState::getPosition() const @@ -700,43 +700,43 @@ BattlePhases::Type CUnitState::battleQueuePhase(int turn) const bool CUnitState::isHypnotized() const { - return bonusCache.cache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); + return bonusCache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); } int CUnitState::getTotalAttacks(bool ranged) const { return 1 + (ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_MELEE)); + bonusCache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_MELEE)); } int CUnitState::getMinDamage(bool ranged) const { return ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_MELEE); + bonusCache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_MELEE); } int CUnitState::getMaxDamage(bool ranged) const { return ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_MELEE); + bonusCache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_MELEE); } int CUnitState::getAttack(bool ranged) const { int attack = ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::ATTACK_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::ATTACK_MELEE); + bonusCache.getBonusValue(UnitBonusValuesProxy::ATTACK_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::ATTACK_MELEE); - int frenzy = bonusCache.cache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); + int frenzy = bonusCache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); if(frenzy != 0) { int defence = ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); + bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); int frenzyBonus = frenzy * defence / 100; attack += frenzyBonus; @@ -748,7 +748,7 @@ int CUnitState::getAttack(bool ranged) const int CUnitState::getDefense(bool ranged) const { - int frenzy = bonusCache.cache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); + int frenzy = bonusCache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY); if(frenzy != 0) { @@ -757,8 +757,8 @@ int CUnitState::getDefense(bool ranged) const else { int defence = ranged ? - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): - bonusCache.cache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); + bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED): + bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE); vstd::amax(defence, 0); return defence; } diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 66c2ec0b5..fe83f89ce 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -125,45 +125,6 @@ private: int32_t resurrected; }; -class UnitBonusValuesProxy -{ -public: - enum ECacheKeys : uint8_t - { - TOTAL_ATTACKS_MELEE, - TOTAL_ATTACKS_RANGED, - - MIN_DAMAGE_MELEE, - MIN_DAMAGE_RANGED, - MAX_DAMAGE_MELEE, - MAX_DAMAGE_RANGED, - - ATTACK_MELEE, - ATTACK_RANGED, - - DEFENCE_MELEE, - DEFENCE_RANGED, - - IN_FRENZY, - HYPNOTIZED, - FORGETFULL, - HAS_FREE_SHOOTING, - STACK_HEALTH, - - TOTAL_KEYS, - }; - - static constexpr size_t KEYS_COUNT = static_cast(ECacheKeys::TOTAL_KEYS); - - BonusValuesArrayCache cache; - - using SelectorsArray = BonusValuesArrayCache::SelectorsArray; - - UnitBonusValuesProxy(const IBonusBearer * Target, const SelectorsArray * selectors): - cache(Target, selectors) - {} -}; - class DLL_LINKAGE CUnitState : public Unit { public: @@ -248,7 +209,7 @@ public: int32_t getFirstHPleft() const override; int64_t getAvailableHealth() const override; int64_t getTotalHealth() const override; - int64_t getMaxHealth() const override; + uint32_t getMaxHealth() const override; BattleHex getPosition() const override; void setPosition(BattleHex hex) override; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index f50dc4680..6c07c59ec 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -50,6 +50,35 @@ int BonusValueCache::getValue() const return getBonusValueImpl(value, selector, BonusCacheMode::VALUE); } +MagicSchoolMasteryCache::MagicSchoolMasteryCache(const IBonusBearer * target) + :target(target) +{} + +void MagicSchoolMasteryCache::update() const +{ + static const CSelector allBonusesSelector = Selector::type()(BonusType::MAGIC_SCHOOL_SKILL); + static const std::array schoolsSelector = { + Selector::subtype()(SpellSchool::ANY), + Selector::subtype()(SpellSchool::AIR), + Selector::subtype()(SpellSchool::FIRE), + Selector::subtype()(SpellSchool::WATER), + Selector::subtype()(SpellSchool::EARTH), + }; + + auto list = target->getBonuses(allBonusesSelector); + for (int i = 0; i < schoolsSelector.size(); ++i) + schools[i] = list->valOfBonuses(schoolsSelector[i]); + + version = target->getTreeVersion(); +} + +int32_t MagicSchoolMasteryCache::getMastery(const SpellSchool & school) const +{ + if (target->getTreeVersion() != version) + update(); + return schools[school.num + 1]; +} + PrimarySkillsCache::PrimarySkillsCache(const IBonusBearer * target) :target(target) {} diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 714f6a40c..54f520119 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -48,7 +48,7 @@ public: }; /// Cache that can track a list of queries to bonus system -template +template class BonusValuesArrayCache : public BonusCacheBase { public: @@ -59,15 +59,13 @@ public: , selectors(selectors) {} - int getBonusValue(EnumType which) const + int getBonusValue(int index) const { - auto index = static_cast(which); return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::VALUE); } - int hasBonus(EnumType which) const + int hasBonus(int index) const { - auto index = static_cast(which); return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::PRESENCE); } @@ -78,6 +76,57 @@ private: mutable CacheArray cache; }; +class UnitBonusValuesProxy +{ +public: + enum ECacheKeys : int8_t + { + TOTAL_ATTACKS_MELEE, + TOTAL_ATTACKS_RANGED, + + MIN_DAMAGE_MELEE, + MIN_DAMAGE_RANGED, + MAX_DAMAGE_MELEE, + MAX_DAMAGE_RANGED, + + ATTACK_MELEE, + ATTACK_RANGED, + + DEFENCE_MELEE, + DEFENCE_RANGED, + + IN_FRENZY, + HYPNOTIZED, + FORGETFULL, + HAS_FREE_SHOOTING, + STACK_HEALTH, + + TOTAL_KEYS, + }; + static constexpr size_t KEYS_COUNT = static_cast(ECacheKeys::TOTAL_KEYS); + + using SelectorsArray = BonusValuesArrayCache::SelectorsArray; + + UnitBonusValuesProxy(const IBonusBearer * Target, const SelectorsArray * selectors): + cache(Target, selectors) + {} + + int getBonusValue(ECacheKeys which) const + { + auto index = static_cast(which); + return cache.getBonusValue(index); + } + + int hasBonus(ECacheKeys which) const + { + auto index = static_cast(which); + return cache.hasBonus(index); + } + +private: + BonusValuesArrayCache cache; +}; + /// Cache that tracks values of primary skill values in bonus system class PrimarySkillsCache { @@ -92,6 +141,20 @@ public: const std::array, 4> & getSkills() const; }; +/// Cache that tracks values of spell school mastery in bonus system +class MagicSchoolMasteryCache +{ + const IBonusBearer * target; + mutable std::atomic version = 0; + mutable std::array, 4+1> schools; + + void update() const; +public: + MagicSchoolMasteryCache(const IBonusBearer * target); + + int32_t getMastery(const SpellSchool & school) const; +}; + /// Cache that tracks values for different values of bonus duration class BonusCachePerTurn : public BonusCacheBase { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c83418be9..24c35b781 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -294,6 +294,8 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb) exp(UNINITIALIZED_EXPERIENCE), gender(EHeroGender::DEFAULT), primarySkills(this), + magicSchoolMastery(this), + manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)), lowestCreatureSpeed(0) { setNodeType(HERO); @@ -789,7 +791,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSc spell->forEachSchool([&, this](const SpellSchool & cnf, bool & stop) { - int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(cnf)); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies) + int32_t thisSchool = magicSchoolMastery.getMastery(cnf); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies) if(thisSchool > skill) { skill = thisSchool; @@ -798,7 +800,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSc } }); - vstd::amax(skill, valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(SpellSchool::ANY))); //any school bonus + vstd::amax(skill, magicSchoolMastery.getMastery(SpellSchool::ANY)); //any school bonus vstd::amax(skill, valOfBonuses(BonusType::SPELL, BonusSubtypeID(spell->getId()))); //given by artifact or other effect vstd::amax(skill, 0); //in case we don't know any school @@ -1187,8 +1189,7 @@ std::string CGHeroInstance::nodeName() const si32 CGHeroInstance::manaLimit() const { - return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) - * (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))) / 100; + return getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * manaPerKnowledgeCached.getValue() / 100; } HeroTypeID CGHeroInstance::getPortraitSource() const diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 43117397b..4ff89ec5b 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -60,6 +60,8 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, private: PrimarySkillsCache primarySkills; + MagicSchoolMasteryCache magicSchoolMastery; + BonusValueCache manaPerKnowledgeCached; std::set spells; //known spells (spell IDs) mutable int lowestCreatureSpeed; From 39d2f2758961524ddf89c2e6165c4cd8e0f33f1b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 26 Dec 2024 16:57:05 +0000 Subject: [PATCH 05/12] Remove remaining usages of CCheckProxy class --- lib/CMakeLists.txt | 2 -- lib/battle/CUnitState.cpp | 22 ++++++------- lib/battle/CUnitState.h | 8 ++--- lib/bonuses/BonusCache.cpp | 5 +++ lib/bonuses/BonusCache.h | 3 ++ lib/bonuses/CBonusProxy.cpp | 53 ------------------------------- lib/bonuses/CBonusProxy.h | 36 --------------------- lib/mapObjects/CArmedInstance.cpp | 4 +-- lib/mapObjects/CArmedInstance.h | 5 ++- 9 files changed, 24 insertions(+), 114 deletions(-) delete mode 100644 lib/bonuses/CBonusProxy.cpp delete mode 100644 lib/bonuses/CBonusProxy.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 45caf5f54..bd7d3ea1d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -71,7 +71,6 @@ set(lib_MAIN_SRCS bonuses/BonusParams.cpp bonuses/BonusSelector.cpp bonuses/BonusCustomTypes.cpp - bonuses/CBonusProxy.cpp bonuses/CBonusSystemNode.cpp bonuses/IBonusBearer.cpp bonuses/Limiters.cpp @@ -442,7 +441,6 @@ set(lib_MAIN_HEADERS bonuses/BonusParams.h bonuses/BonusSelector.h bonuses/BonusCustomTypes.h - bonuses/CBonusProxy.h bonuses/CBonusSystemNode.h bonuses/IBonusBearer.h bonuses/Limiters.h diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index f8d1430fa..f1b0f0f7d 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -84,13 +84,13 @@ void CAmmo::serializeJson(JsonSerializeFormat & handler) ///CShots CShots::CShots(const battle::Unit * Owner) : CAmmo(Owner, Selector::type()(BonusType::SHOTS)), - shooter(Owner, BonusType::SHOOTER) + shooter(Owner, Selector::type()(BonusType::SHOOTER)) { } bool CShots::isLimited() const { - return !shooter.getHasBonus() || !env->unitHasAmmoCart(owner); + return !shooter.hasBonus() || !env->unitHasAmmoCart(owner); } void CShots::setEnv(const IUnitEnvironment * env_) @@ -100,7 +100,7 @@ void CShots::setEnv(const IUnitEnvironment * env_) int32_t CShots::total() const { - if(shooter.getHasBonus()) + if(shooter.hasBonus()) return CAmmo::total(); else return 0; @@ -116,19 +116,19 @@ CCasts::CCasts(const battle::Unit * Owner): CRetaliations::CRetaliations(const battle::Unit * Owner) : CAmmo(Owner, Selector::type()(BonusType::ADDITIONAL_RETALIATION)), totalCache(0), - noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION)), "CRetaliations::noRetaliation"), - unlimited(Owner, BonusType::UNLIMITED_RETALIATIONS) + noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION))), + unlimited(Owner, Selector::type()(BonusType::UNLIMITED_RETALIATIONS)) { } bool CRetaliations::isLimited() const { - return !unlimited.getHasBonus() || noRetaliation.getHasBonus(); + return !unlimited.hasBonus() || noRetaliation.hasBonus(); } int32_t CRetaliations::total() const { - if(noRetaliation.getHasBonus()) + if(noRetaliation.hasBonus()) return 0; //after dispel bonus should remain during current round @@ -336,7 +336,6 @@ CUnitState::CUnitState(): stackSpeedPerTurn(this, Selector::type()(BonusType::STACKS_SPEED), BonusCacheMode::VALUE), immobilizedPerTurn(this, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::BIND_EFFECT)), BonusCacheMode::PRESENCE), bonusCache(this, generateBonusSelectors()), - cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"), cloneID(-1) { @@ -360,11 +359,7 @@ CUnitState & CUnitState::operator=(const CUnitState & other) waiting = other.waiting; waitedThisTurn = other.waitedThisTurn; casts = other.casts; - counterAttacks = other.counterAttacks; health = other.health; - shots = other.shots; -// bonusCache = other.bonusCache; - cloneLifetimeMarker = other.cloneLifetimeMarker; cloneID = other.cloneID; position = other.position; return *this; @@ -913,7 +908,7 @@ void CUnitState::afterNewRound() if(alive() && isClone()) { - if(!cloneLifetimeMarker.getHasBonus()) + if(!bonusCache.hasBonus(UnitBonusValuesProxy::CLONE_MARKER)) makeGhost(); } } @@ -964,6 +959,7 @@ const UnitBonusValuesProxy::SelectorsArray * CUnitState::generateBonusSelectors( Selector::type()(BonusType::HYPNOTIZED),//HYPNOTIZED, Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, + Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) }; return &selectors; diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index fe83f89ce..c2a323e7f 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -11,7 +11,6 @@ #pragma once #include "Unit.h" -#include "../bonuses/CBonusProxy.h" #include "../bonuses/BonusCache.h" VCMI_LIB_NAMESPACE_BEGIN @@ -62,7 +61,7 @@ public: private: const IUnitEnvironment * env; - CCheckProxy shooter; + BonusValueCache shooter; }; class DLL_LINKAGE CCasts : public CAmmo @@ -84,8 +83,8 @@ public: private: mutable int32_t totalCache; - CCheckProxy noRetaliation; - CCheckProxy unlimited; + BonusValueCache noRetaliation; + BonusValueCache unlimited; }; class DLL_LINKAGE CHealth @@ -268,7 +267,6 @@ private: BonusCachePerTurn immobilizedPerTurn; BonusCachePerTurn stackSpeedPerTurn; UnitBonusValuesProxy bonusCache; - CCheckProxy cloneLifetimeMarker; void reset(); }; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 6c07c59ec..5e037ee3c 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -50,6 +50,11 @@ int BonusValueCache::getValue() const return getBonusValueImpl(value, selector, BonusCacheMode::VALUE); } +bool BonusValueCache::hasBonus() const +{ + return getBonusValueImpl(value, selector, BonusCacheMode::PRESENCE); +} + MagicSchoolMasteryCache::MagicSchoolMasteryCache(const IBonusBearer * target) :target(target) {} diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 54f520119..b3209e880 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -45,6 +45,7 @@ class BonusValueCache : public BonusCacheBase public: BonusValueCache(const IBonusBearer * target, const CSelector selector); int getValue() const; + bool hasBonus() const; }; /// Cache that can track a list of queries to bonus system @@ -101,6 +102,8 @@ public: HAS_FREE_SHOOTING, STACK_HEALTH, + CLONE_MARKER, + TOTAL_KEYS, }; static constexpr size_t KEYS_COUNT = static_cast(ECacheKeys::TOTAL_KEYS); diff --git a/lib/bonuses/CBonusProxy.cpp b/lib/bonuses/CBonusProxy.cpp deleted file mode 100644 index 263f42ad8..000000000 --- a/lib/bonuses/CBonusProxy.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * CBonusProxy.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 "CBonusProxy.h" -#include "IBonusBearer.h" - -VCMI_LIB_NAMESPACE_BEGIN - -///CCheckProxy -CCheckProxy::CCheckProxy(const IBonusBearer * Target, BonusType bonusType): - target(Target), - selector(Selector::type()(bonusType)), - cachingStr("type_" + std::to_string(static_cast(bonusType))), - cachedLast(0), - hasBonus(false) -{ - -} - -CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr): - target(Target), - selector(std::move(Selector)), - cachedLast(0), - cachingStr(cachingStr), - hasBonus(false) -{ -} - -//This constructor should be placed here to avoid side effects -CCheckProxy::CCheckProxy(const CCheckProxy & other) = default; - -bool CCheckProxy::getHasBonus() const -{ - const auto treeVersion = target->getTreeVersion(); - - if(treeVersion != cachedLast) - { - hasBonus = target->hasBonus(selector, cachingStr); - cachedLast = treeVersion; - } - - return hasBonus; -} - -VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/CBonusProxy.h b/lib/bonuses/CBonusProxy.h deleted file mode 100644 index 68fc1ebe7..000000000 --- a/lib/bonuses/CBonusProxy.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * CBonusProxy.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 "BonusSelector.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class DLL_LINKAGE CCheckProxy -{ -public: - CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr); - CCheckProxy(const IBonusBearer * Target, BonusType bonusType); - CCheckProxy(const CCheckProxy & other); - CCheckProxy& operator= (const CCheckProxy & other) = default; - - bool getHasBonus() const; - -private: - const IBonusBearer * target; - std::string cachingStr; - CSelector selector; - - mutable int64_t cachedLast; - mutable bool hasBonus; -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index f2783662d..eddaf2d95 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -46,7 +46,7 @@ CArmedInstance::CArmedInstance(IGameCallback *cb) CArmedInstance::CArmedInstance(IGameCallback *cb, bool isHypothetic): CGObjectInstance(cb), CBonusSystemNode(isHypothetic), - nonEvilAlignmentMix(this, BonusType::NONEVIL_ALIGNMENT_MIX), // Take Angelic Alliance troop-mixing freedom of non-evil units into account. + nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account. battle(nullptr) { } @@ -86,7 +86,7 @@ void CArmedInstance::updateMoraleBonusFromArmy() size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account - if (nonEvilAlignmentMix.getHasBonus()) + if (nonEvilAlignmentMix.hasBonus()) { size_t mixableFactions = 0; diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h index 8c50cba16..cbd943ea0 100644 --- a/lib/mapObjects/CArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -11,8 +11,8 @@ #include "CGObjectInstance.h" #include "../CCreatureSet.h" -#include "../bonuses/CBonusProxy.h" #include "../bonuses/CBonusSystemNode.h" +#include "../bonuses/BonusCache.h" VCMI_LIB_NAMESPACE_BEGIN @@ -23,8 +23,7 @@ class JsonSerializeFormat; class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet, public IConstBonusProvider { private: - CCheckProxy nonEvilAlignmentMix; - static CSelector nonEvilAlignmentMixSelector; + BonusValueCache nonEvilAlignmentMix; public: BattleInfo *battle; //set to the current battle, if engaged From 579b64cd39504e6c952dd9a77421174b43530f5c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 26 Dec 2024 16:58:04 +0000 Subject: [PATCH 06/12] Fix build --- lib/bonuses/BonusCache.cpp | 4 ++++ lib/bonuses/BonusCache.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 5e037ee3c..3eac0290c 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -18,6 +18,8 @@ #include "../VCMI_Lib.h" #include "../IGameSettings.h" +VCMI_LIB_NAMESPACE_BEGIN + int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector, BonusCacheMode mode) const { if (target->getTreeVersion() == currentValue.version) @@ -174,3 +176,5 @@ int BonusCachePerTurn::getValue(int turns) const return getValueUncached(turns); } } + +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index b3209e880..fbc59f571 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -12,6 +12,8 @@ #include "BonusSelector.h" +VCMI_LIB_NAMESPACE_BEGIN + enum class BonusCacheMode : int8_t { VALUE, // total value of bonus will be cached @@ -180,3 +182,5 @@ public: int getValue(int turns) const; }; + +VCMI_LIB_NAMESPACE_END From dea1eba20bce5eadaec3faff7aee565851e43df7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Dec 2024 12:02:27 +0000 Subject: [PATCH 07/12] Rework and optimize turnInfo used by pathfinder --- AI/Nullkiller/Pathfinding/Actors.cpp | 6 +- AI/Nullkiller/Pathfinding/Actors.h | 1 - config/gameConfig.json | 27 +-- lib/GameSettings.cpp | 2 + lib/IGameSettings.h | 2 + lib/bonuses/BonusList.cpp | 9 +- lib/bonuses/BonusList.h | 4 +- lib/bonuses/Updaters.cpp | 12 -- lib/mapObjects/CGHeroInstance.cpp | 80 ++++----- lib/mapObjects/CGHeroInstance.h | 10 +- lib/pathfinder/CPathfinder.cpp | 86 +++++---- lib/pathfinder/CPathfinder.h | 6 +- lib/pathfinder/TurnInfo.cpp | 237 +++++++++++++++---------- lib/pathfinder/TurnInfo.h | 85 ++++++--- server/CGameHandler.cpp | 4 +- server/processors/NewTurnProcessor.cpp | 2 +- 16 files changed, 300 insertions(+), 273 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 8db9230cc..489f830ac 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -47,11 +47,10 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t initialTurn = 0; armyValue = getHeroArmyStrengthWithCommander(hero, hero); heroFightingStrength = hero->getHeroStrength(); - tiCache.reset(new TurnInfo(hero)); } ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy) - :hero(carrier->hero), tiCache(carrier->tiCache), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask), + :hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask), baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength), actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction() { @@ -75,7 +74,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer) throw std::logic_error("Asking movement points for static actor"); #endif - return hero->movementPointsLimitCached(layer, tiCache.get()); + return hero->movementPointsLimit(layer); } std::string ChainActor::toString() const @@ -133,7 +132,6 @@ void ChainActor::setBaseActor(HeroActor * base) heroFightingStrength = base->heroFightingStrength; armyCost = base->armyCost; actorAction = base->actorAction; - tiCache = base->tiCache; actorExchangeCount = base->actorExchangeCount; } diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 1f653fbd3..8caf02e03 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -73,7 +73,6 @@ public: float heroFightingStrength; uint8_t actorExchangeCount; TResources armyCost; - std::shared_ptr tiCache; ChainActor() = default; virtual ~ChainActor() = default; diff --git a/config/gameConfig.json b/config/gameConfig.json index ac40a419f..e962291a4 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -306,6 +306,10 @@ "tavernInvite" : false, // minimal primary skills for heroes "minimalPrimarySkills": [ 0, 0, 1, 1] + /// movement points hero can get on start of the turn when on land, depending on speed of slowest creature (0-based list) + "movementPointsLand" : [ 1500, 1500, 1500, 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 ], + /// movement points hero can get on start of the turn when on sea, depending on speed of slowest creature (0-based list) + "movementPointsSea" : [ 1500 ], }, "towns": @@ -560,29 +564,6 @@ "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge "val" : 1000, "valueType" : "BASE_NUMBER" - }, - "landMovement" : - { - "type" : "MOVEMENT", //Basic land movement - "subtype" : "heroMovementLand", - "val" : 1300, - "valueType" : "BASE_NUMBER", - "updater" : { - "type" : "ARMY_MOVEMENT", //Enable army movement bonus - "parameters" : [ - 20, // Movement points for lowest speed numerator - 3, // Movement points for lowest speed denominator - 10, // Resulting value, rounded down, will be multiplied by this number - 700 // All army movement bonus cannot be higher than this value (so, max movement will be 1300 + 700 for this settings) - ] - } - }, - "seaMovement" : - { - "type" : "MOVEMENT", //Basic sea movement - "subtype" : "heroMovementSea", - "val" : 1500, - "valueType" : "BASE_NUMBER" } } }, diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index a8df9add3..c1e688490 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -76,6 +76,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, + {EGameSettings::HEROES_MOVEMENT_POINTS_LAND, "heroes", "movementPointsLand" }, + {EGameSettings::HEROES_MOVEMENT_POINTS_SEA, "heroes", "movementPointsSea" }, {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 3cb92e127..b4a0f6f72 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -49,6 +49,8 @@ enum class EGameSettings HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_STARTING_STACKS_CHANCES, HEROES_TAVERN_INVITE, + HEROES_MOVEMENT_POINTS_LAND, + HEROES_MOVEMENT_POINTS_SEA, MAP_FORMAT_ARMAGEDDONS_BLADE, MAP_FORMAT_CHRONICLES, MAP_FORMAT_HORN_OF_THE_ABYSS, diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index 1739f560a..5e4048573 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -83,10 +83,10 @@ void BonusList::stackBonuses() } } -int BonusList::totalValue() const +int BonusList::totalValue(int baseValue) const { if (bonuses.empty()) - return 0; + return baseValue; struct BonusCollection { @@ -104,6 +104,7 @@ int BonusList::totalValue() const }; BonusCollection accumulated; + accumulated.base = baseValue; int indexMaxCount = 0; int indexMinCount = 0; @@ -208,12 +209,12 @@ void BonusList::getAllBonuses(BonusList &out) const out.push_back(b); } -int BonusList::valOfBonuses(const CSelector &select) const +int BonusList::valOfBonuses(const CSelector &select, int baseValue) const { BonusList ret; CSelector limit = nullptr; getBonuses(ret, select, limit); - return ret.totalValue(); + return ret.totalValue(baseValue); } JsonNode BonusList::toJsonNode() const diff --git a/lib/bonuses/BonusList.h b/lib/bonuses/BonusList.h index 66b4f2b42..cc266494f 100644 --- a/lib/bonuses/BonusList.h +++ b/lib/bonuses/BonusList.h @@ -58,14 +58,14 @@ public: // BonusList functions void stackBonuses(); - int totalValue() const; + int totalValue(int baseValue = 0) const; void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const; void getAllBonuses(BonusList &out) const; //special find functions std::shared_ptr getFirst(const CSelector &select); std::shared_ptr getFirst(const CSelector &select) const; - int valOfBonuses(const CSelector &select) const; + int valOfBonuses(const CSelector &select, int baseValue = 0) const; // conversion / output JsonNode toJsonNode() const; diff --git a/lib/bonuses/Updaters.cpp b/lib/bonuses/Updaters.cpp index 064818a58..7ce42981a 100644 --- a/lib/bonuses/Updaters.cpp +++ b/lib/bonuses/Updaters.cpp @@ -111,18 +111,6 @@ ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier, std::shared_ptr ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const { - if(b->type == BonusType::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO) - { - auto speed = static_cast(context).getLowestCreatureSpeed(); - si32 armySpeed = speed * base / divider; - auto counted = armySpeed * multiplier; - auto newBonus = std::make_shared(*b); - newBonus->source = BonusSource::ARMY; - newBonus->val += vstd::amin(counted, max); - return newBonus; - } - if(b->type != BonusType::MOVEMENT) - logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!"); return b; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 24c35b781..ffd94191c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -73,31 +73,6 @@ void CGHeroPlaceholder::serializeJsonOptions(JsonSerializeFormat & handler) handler.serializeInt("powerRank", powerRank.value()); } -static int lowestSpeed(const CGHeroInstance * chi) -{ - static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED); - static const std::string keySTACKS_SPEED = "type_" + std::to_string(static_cast(BonusType::STACKS_SPEED)); - - if(!chi->stacksCount()) - { - if(chi->commander && chi->commander->alive) - { - return chi->commander->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED); - } - - logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->getNameTranslated()); - return 20; - } - - auto i = chi->Slots().begin(); - //TODO? should speed modifiers (eg from artifacts) affect hero movement? - - int ret = (i++)->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED); - for(; i != chi->Slots().end(); i++) - ret = std::min(ret, i->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED)); - return ret; -} - ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const { int64_t ret = GameConstants::BASE_MOVEMENT_COST; @@ -107,13 +82,10 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain { ret = from.getRoad()->movementCost; } - else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native - ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus + else if(ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus { - ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost; - ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT); + ret -= ti->getRoughTerrainDiscountValue(); if(ret < GameConstants::BASE_MOVEMENT_COST) ret = GameConstants::BASE_MOVEMENT_COST; } @@ -257,30 +229,44 @@ void CGHeroInstance::setMovementPoints(int points) int CGHeroInstance::movementPointsLimit(bool onLand) const { - return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); + auto ti = getTurnInfo(0); + return onLand ? ti->getMovePointsLimitLand() : ti->getMovePointsLimitWater(); } int CGHeroInstance::getLowestCreatureSpeed() const { - return lowestCreatureSpeed; + static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED); + static const std::string cachingStr = "type_" + std::to_string(static_cast(BonusType::STACKS_SPEED)); + + if(stacksCount() != 0) + { + int minimalSpeed = std::numeric_limits::max(); + //TODO? should speed modifiers (eg from artifacts) affect hero movement? + for(const auto & slot : Slots()) + minimalSpeed = std::min(minimalSpeed, slot.second->valOfBonuses(selectorSTACKS_SPEED, cachingStr)); + + return minimalSpeed; + } + else + { + if(commander && commander->alive) + return commander->valOfBonuses(selectorSTACKS_SPEED, cachingStr); + } + + return 10; } -void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const +std::unique_ptr CGHeroInstance::getTurnInfo(int days) const { - auto realLowestSpeed = lowestSpeed(this); - if(lowestCreatureSpeed != realLowestSpeed) - { - lowestCreatureSpeed = realLowestSpeed; - //Let updaters run again - treeHasChanged(); - ti->updateHeroBonuses(BonusType::MOVEMENT); - } + return std::make_unique(turnInfoCache.get(), this, days); } int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const { - updateArmyMovementBonus(onLand, ti); - return ti->valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); + if (onLand) + return ti->getMovePointsLimitLand(); + else + return ti->getMovePointsLimitWater(); } CGHeroInstance::CGHeroInstance(IGameCallback * cb) @@ -295,8 +281,8 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb) gender(EHeroGender::DEFAULT), primarySkills(this), magicSchoolMastery(this), - manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)), - lowestCreatureSpeed(0) + turnInfoCache(std::make_unique(this)), + manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)) { setNodeType(HERO); ID = Obj::HERO; @@ -1365,11 +1351,11 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool std::unique_ptr turnInfoLocal; if(!ti) { - turnInfoLocal = std::make_unique(this); + turnInfoLocal = getTurnInfo(0); ti = turnInfoLocal.get(); } - if(!ti->hasBonusOfType(BonusType::FREE_SHIP_BOARDING)) + if(!ti->hasFreeShipBoarding()) return 0; // take all MPs by default auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 4ff89ec5b..569922ef5 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -25,8 +25,10 @@ class CGBoat; class CGTownInstance; class CMap; class UpgradeInfo; +class TurnInfo; + struct TerrainTile; -struct TurnInfo; +struct TurnInfoCache; class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance { @@ -62,9 +64,9 @@ private: PrimarySkillsCache primarySkills; MagicSchoolMasteryCache magicSchoolMastery; BonusValueCache manaPerKnowledgeCached; + std::unique_ptr turnInfoCache; std::set spells; //known spells (spell IDs) - mutable int lowestCreatureSpeed; ui32 movement; //remaining movement points public: @@ -224,11 +226,11 @@ public: int movementPointsLimit(bool onLand) const; //cached version is much faster, TurnInfo construction is costly int movementPointsLimitCached(bool onLand, const TurnInfo * ti) const; - //update army movement bonus - void updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const; int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; + std::unique_ptr getTurnInfo(int days) const; + double getFightingStrength() const; // takes attack / defense skill into account double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account double getHeroStrength() const; // includes fighting and magic strength diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index deeb3fa5c..87c073fca 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -514,11 +514,7 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell()); } -CPathfinderHelper::~CPathfinderHelper() -{ - for(auto * ti : turnsInfo) - delete ti; -} +CPathfinderHelper::~CPathfinderHelper() = default; void CPathfinderHelper::updateTurnInfo(const int Turn) { @@ -526,10 +522,7 @@ void CPathfinderHelper::updateTurnInfo(const int Turn) { turn = Turn; if(turn >= turnsInfo.size()) - { - auto * ti = new TurnInfo(hero, turn); - turnsInfo.push_back(ti); - } + turnsInfo.push_back(hero->getTurnInfo(turn)); } } @@ -561,12 +554,7 @@ bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer & layer) const const TurnInfo * CPathfinderHelper::getTurnInfo() const { - return turnsInfo[turn]; -} - -bool CPathfinderHelper::hasBonusOfType(const BonusType type) const -{ - return turnsInfo[turn]->hasBonusOfType(type); + return turnsInfo[turn].get(); } int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const @@ -575,15 +563,16 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const } void CPathfinderHelper::getNeighbours( - const TerrainTile & srcTile, + const TerrainTile & sourceTile, const int3 & srcCoord, NeighbourTilesVector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) const { CMap * map = gs->map; + const TerrainType * sourceTerrain = sourceTile.getTerrain(); - static const int3 dirs[] = { + constexpr std::array dirs = { int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0), int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0), int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) @@ -596,12 +585,12 @@ void CPathfinderHelper::getNeighbours( continue; const TerrainTile & destTile = map->getTile(destCoord); - const TerrainType* terrain = destTile.getTerrain(); - if(!terrain->isPassable()) + const TerrainType * destTerrain = destTile.getTerrain(); + if(!destTerrain->isPassable()) continue; /// Following condition let us avoid diagonal movement over coast when sailing - if(srcTile.isWater() && limitCoastSailing && terrain->isWater() && dir.x && dir.y) //diagonal move through water + if(sourceTerrain->isWater() && limitCoastSailing && destTerrain->isWater() && dir.x && dir.y) //diagonal move through water { const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; @@ -609,7 +598,7 @@ void CPathfinderHelper::getNeighbours( continue; } - if(indeterminate(onLand) || onLand == terrain->isLand()) + if(indeterminate(onLand) || onLand == destTerrain->isLand()) { vec.push_back(destCoord); } @@ -663,54 +652,59 @@ int CPathfinderHelper::getMovementCost( bool isWaterLayer; if(indeterminate(isDstWaterLayer)) - isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->isWater(); + isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasWaterWalking()) && dt->isWater(); else isWaterLayer = static_cast(isDstWaterLayer); - bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT); + bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasFlyingMovement(); - int ret = hero->getTileMovementCost(*dt, *ct, ti); + int movementCost = hero->getTileMovementCost(*dt, *ct, ti); if(isSailLayer) { if(ct->hasFavorableWinds()) - ret = static_cast(ret * 2.0 / 3); + movementCost = static_cast(movementCost * 2.0 / 3); } else if(isAirLayer) - vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT)); - else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING)) - ret = static_cast(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0); + vstd::amin(movementCost, GameConstants::BASE_MOVEMENT_COST + ti->getFlyingMovementValue()); + else if(isWaterLayer && ti->hasWaterWalking()) + movementCost = static_cast(movementCost * (100.0 + ti->getWaterWalkingValue()) / 100.0); if(src.x != dst.x && src.y != dst.y) //it's diagonal move { - int old = ret; - ret = static_cast(ret * M_SQRT2); + int old = movementCost; + movementCost = static_cast(movementCost * M_SQRT2); //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points // https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception - if(ret > remainingMovePoints && remainingMovePoints >= old) + if(movementCost > remainingMovePoints && remainingMovePoints >= old) { return remainingMovePoints; } } - const int left = remainingMovePoints - ret; - constexpr auto maxCostOfOneStep = static_cast(175 * M_SQRT2); // diagonal move on Swamp - 247 MP - if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points + //it might be the last tile - if no further move possible we take all move points + const int pointsLeft = remainingMovePoints - movementCost; + if(checkLast && pointsLeft > 0) { - NeighbourTilesVector vec; + int minimalNextMoveCost = hero->getTileMovementCost(*dt, *ct, ti); - getNeighbours(*dt, dst, vec, ct->isLand(), true); - for(const auto & elem : vec) - { - int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; + if (pointsLeft < minimalNextMoveCost) + return remainingMovePoints; + +// NeighbourTilesVector vec; +// +// getNeighbours(*dt, dst, vec, ct->isLand(), true); +// for(const auto & elem : vec) +// { +// int fcost = getMovementCost(dst, elem, nullptr, nullptr, pointsLeft, false); +// if(fcost <= pointsLeft) +// { +// return movementCost; +// } +// } +// movementCost = remainingMovePoints; } - return ret; + return movementCost; } VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h index 9a903ced1..6fe8b17f2 100644 --- a/lib/pathfinder/CPathfinder.h +++ b/lib/pathfinder/CPathfinder.h @@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGWhirlpool; -struct TurnInfo; +class TurnInfo; struct PathfinderOptions; // Optimized storage - tile can have 0-8 neighbour tiles @@ -78,7 +78,7 @@ public: int turn; PlayerColor owner; const CGHeroInstance * hero; - std::vector turnsInfo; + std::vector> turnsInfo; const PathfinderOptions & options; bool canCastFly; bool canCastWaterWalk; @@ -93,7 +93,7 @@ public: void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer & layer) const; const TurnInfo * getTurnInfo() const; - bool hasBonusOfType(BonusType type) const; + //bool hasBonusOfType(BonusType type) const; int getMaxMovePoints(const EPathfindingLayer & layer) const; TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const; diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp index 17258de4e..9ef4489e3 100644 --- a/lib/pathfinder/TurnInfo.cpp +++ b/lib/pathfinder/TurnInfo.cpp @@ -10,40 +10,154 @@ #include "StdInc.h" #include "TurnInfo.h" +#include "../IGameCallback.h" +#include "../IGameSettings.h" #include "../TerrainHandler.h" #include "../VCMI_Lib.h" #include "../bonuses/BonusList.h" +#include "../json/JsonNode.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/MiscObjects.h" VCMI_LIB_NAMESPACE_BEGIN -TurnInfo::BonusCache::BonusCache(const TConstBonusListPtr & bl) +TConstBonusListPtr TurnInfoBonusList::getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector) { - for(const auto & terrain : VLC->terrainTypeHandler->objects) - { - auto selector = Selector::typeSubtype(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(terrain->getId())); - if (bl->getFirst(selector)) - noTerrainPenalty.insert(terrain->getId()); - } + std::lock_guard guard(bonusListMutex); - freeShipBoarding = static_cast(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - flyingMovement = static_cast(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); - flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); - waterWalking = static_cast(bl->getFirst(Selector::type()(BonusType::WATER_WALKING))); - waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); - pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); + if (target->getTreeVersion() == bonusListVersion) + return bonusList; + + bonusList = target->getBonuses(bonusSelector); + bonusListVersion = target->getTreeVersion(); + + return bonusList; } -TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): - hero(Hero), - maxMovePointsLand(-1), - maxMovePointsWater(-1), - turn(turn) +int TurnInfo::hasWaterWalking() const { - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); - bonusCache = std::make_unique(bonuses); - nativeTerrain = hero->getNativeTerrain(); + return waterWalkingTest; +} + +int TurnInfo::hasFlyingMovement() const +{ + return flyingMovementTest; +} + +int TurnInfo::hasNoTerrainPenalty(const TerrainId &terrain) const +{ + return noterrainPenalty[terrain.num]; +} + +int TurnInfo::hasFreeShipBoarding() const +{ + return freeShipBoardingTest; +} + +int TurnInfo::getFlyingMovementValue() const +{ + return flyingMovementValue; +} + +int TurnInfo::getWaterWalkingValue() const +{ + return waterWalkingValue; +} + +int TurnInfo::getRoughTerrainDiscountValue() const +{ + return roughTerrainDiscountValue; +} + +int TurnInfo::getMovePointsLimitLand() const +{ + return movePointsLimitLand; +} + +int TurnInfo::getMovePointsLimitWater() const +{ + return movePointsLimitWater; +} + +TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn) + : noterrainPenalty(VLC->terrainTypeHandler->size()) + , target(target) +{ + CSelector daySelector = Selector::days(Turn); + + int lowestSpeed; + if (target->getTreeVersion() == sharedCache->heroLowestSpeedVersion) + { + lowestSpeed = sharedCache->heroLowestSpeedValue; + } + else + { + lowestSpeed = target->getLowestCreatureSpeed(); + sharedCache->heroLowestSpeedValue = lowestSpeed; + sharedCache->heroLowestSpeedVersion = target->getTreeVersion(); + } + + { + static const CSelector selector = Selector::type()(BonusType::WATER_WALKING); + const auto & bonuses = sharedCache->waterWalking.getBonusList(target, selector); + waterWalkingTest = bonuses->getFirst(selector) != nullptr; + waterWalkingValue = bonuses->valOfBonuses(selector); + } + + { + static const CSelector selector = Selector::type()(BonusType::FLYING_MOVEMENT); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + flyingMovementTest = bonuses->getFirst(selector) != nullptr; + flyingMovementValue = bonuses->valOfBonuses(selector); + } + + { + static const CSelector selector = Selector::type()(BonusType::FREE_SHIP_BOARDING); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + freeShipBoardingTest = bonuses->getFirst(selector) != nullptr; + } + + { + static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + roughTerrainDiscountValue = bonuses->getFirst(selector) != nullptr; + } + + { + static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea); + const auto & vectorSea = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_SEA).Vector(); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + int baseMovementPointsSea; + if (lowestSpeed < vectorSea.size()) + baseMovementPointsSea = vectorSea[lowestSpeed].Integer(); + else + baseMovementPointsSea = vectorSea.back().Integer(); + + movePointsLimitWater = bonuses->valOfBonuses(selector, baseMovementPointsSea); + } + + { + static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea); + const auto & vectorLand = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_LAND).Vector(); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + int baseMovementPointsLand; + if (lowestSpeed < vectorLand.size()) + baseMovementPointsLand = vectorLand[lowestSpeed].Integer(); + else + baseMovementPointsLand = vectorLand.back().Integer(); + + movePointsLimitLand = bonuses->valOfBonuses(selector, baseMovementPointsLand); + } + + { + static const CSelector selector = Selector::type()(BonusType::NO_TERRAIN_PENALTY); + const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + for (const auto & bonus : *bonuses) + { + TerrainId affectedTerrain = bonus->subtype.as(); + noterrainPenalty.at(affectedTerrain.num) = true; + } + } } bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const @@ -51,19 +165,19 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const switch(layer.toEnum()) { case EPathfindingLayer::AIR: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR) + if(target && target->boat && target->boat->layer == EPathfindingLayer::AIR) break; - if(!hasBonusOfType(BonusType::FLYING_MOVEMENT)) + if(!hasFlyingMovement()) return false; break; case EPathfindingLayer::WATER: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER) + if(target && target->boat && target->boat->layer == EPathfindingLayer::WATER) break; - if(!hasBonusOfType(BonusType::WATER_WALKING)) + if(!hasWaterWalking()) return false; break; @@ -72,80 +186,9 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const return true; } -bool TurnInfo::hasBonusOfType(BonusType type) const -{ - return hasBonusOfType(type, BonusSubtypeID()); -} - -bool TurnInfo::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const -{ - switch(type) - { - case BonusType::FREE_SHIP_BOARDING: - return bonusCache->freeShipBoarding; - case BonusType::FLYING_MOVEMENT: - return bonusCache->flyingMovement; - case BonusType::WATER_WALKING: - return bonusCache->waterWalking; - case BonusType::NO_TERRAIN_PENALTY: - return bonusCache->noTerrainPenalty.count(subtype.as()); - } - - return static_cast( - bonuses->getFirst(Selector::type()(type).And(Selector::subtype()(subtype)))); -} - -int TurnInfo::valOfBonuses(BonusType type) const -{ - return valOfBonuses(type, BonusSubtypeID()); -} - -int TurnInfo::valOfBonuses(BonusType type, BonusSubtypeID subtype) const -{ - switch(type) - { - case BonusType::FLYING_MOVEMENT: - return bonusCache->flyingMovementVal; - case BonusType::WATER_WALKING: - return bonusCache->waterWalkingVal; - case BonusType::ROUGH_TERRAIN_DISCOUNT: - return bonusCache->pathfindingVal; - } - - return bonuses->valOfBonuses(Selector::type()(type).And(Selector::subtype()(subtype))); -} - int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const { - if(maxMovePointsLand == -1) - maxMovePointsLand = hero->movementPointsLimitCached(true, this); - if(maxMovePointsWater == -1) - maxMovePointsWater = hero->movementPointsLimitCached(false, this); - - return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; -} - -void TurnInfo::updateHeroBonuses(BonusType type) const -{ - switch(type) - { - case BonusType::FREE_SHIP_BOARDING: - bonusCache->freeShipBoarding = static_cast(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - break; - case BonusType::FLYING_MOVEMENT: - bonusCache->flyingMovement = static_cast(bonuses->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); - bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); - break; - case BonusType::WATER_WALKING: - bonusCache->waterWalking = static_cast(bonuses->getFirst(Selector::type()(BonusType::WATER_WALKING))); - bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); - break; - case BonusType::ROUGH_TERRAIN_DISCOUNT: - bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); - break; - default: - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); - } + return layer == EPathfindingLayer::SAIL ? getMovePointsLimitWater() : getMovePointsLimitLand(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/TurnInfo.h b/lib/pathfinder/TurnInfo.h index 02e4597df..70e7a16e1 100644 --- a/lib/pathfinder/TurnInfo.h +++ b/lib/pathfinder/TurnInfo.h @@ -10,43 +10,74 @@ #pragma once #include "../bonuses/Bonus.h" -#include "../GameConstants.h" +#include "../bonuses/BonusSelector.h" +#include "../bonuses/BonusCache.h" VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; -struct DLL_LINKAGE TurnInfo +class TurnInfoBonusList { - /// This is certainly not the best design ever and certainly can be improved - /// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead - struct BonusCache { - std::set noTerrainPenalty; - bool freeShipBoarding; - bool flyingMovement; - int flyingMovementVal; - bool waterWalking; - int waterWalkingVal; - int pathfindingVal; + TConstBonusListPtr bonusList; + std::mutex bonusListMutex; + std::atomic bonusListVersion = 0; +public: + TConstBonusListPtr getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector); +}; - BonusCache(const TConstBonusListPtr & bonusList); - }; - std::unique_ptr bonusCache; +struct TurnInfoCache +{ + TurnInfoBonusList waterWalking; + TurnInfoBonusList flyingMovement; + TurnInfoBonusList noTerrainPenalty; + TurnInfoBonusList freeShipBoarding; + TurnInfoBonusList roughTerrainDiscount; + TurnInfoBonusList movementPointsLimitLand; + TurnInfoBonusList movementPointsLimitWater; - const CGHeroInstance * hero; - mutable TConstBonusListPtr bonuses; - mutable int maxMovePointsLand; - mutable int maxMovePointsWater; - TerrainId nativeTerrain; - int turn; + const CGHeroInstance * target; - TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + mutable std::atomic heroLowestSpeedVersion = 0; + mutable std::atomic heroLowestSpeedValue = 0; + + explicit TurnInfoCache(const CGHeroInstance * target): + target(target) + {} +}; + +class DLL_LINKAGE TurnInfo +{ +private: + const CGHeroInstance * target; + + // stores cached values per each terrain + std::vector noterrainPenalty; + + int flyingMovementValue; + int waterWalkingValue; + int roughTerrainDiscountValue; + int movePointsLimitLand; + int movePointsLimitWater; + + bool waterWalkingTest; + bool flyingMovementTest; + bool freeShipBoardingTest; + +public: + int hasWaterWalking() const; + int hasFlyingMovement() const; + int hasNoTerrainPenalty(const TerrainId & terrain) const; + int hasFreeShipBoarding() const; + + int getFlyingMovementValue() const; + int getWaterWalkingValue() const; + int getRoughTerrainDiscountValue() const; + int getMovePointsLimitLand() const; + int getMovePointsLimitWater() const; + + TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn); bool isLayerAvailable(const EPathfindingLayer & layer) const; - bool hasBonusOfType(const BonusType type) const; - bool hasBonusOfType(const BonusType type, const BonusSubtypeID subtype) const; - int valOfBonuses(const BonusType type) const; - int valOfBonuses(const BonusType type, const BonusSubtypeID subtype) const; - void updateHeroBonuses(BonusType type) const; int getMaxMovePoints(const EPathfindingLayer & layer) const; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 07c8f50db..0e4688db0 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -846,8 +846,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme auto pathfinderHelper = std::make_unique(gs, h, PathfinderOptions(this)); auto ti = pathfinderHelper->getTurnInfo(); - const bool canFly = pathfinderHelper->hasBonusOfType(BonusType::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR); - const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(BonusType::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER); + const bool canFly = ti->hasFlyingMovement() || (h->boat && h->boat->layer == EPathfindingLayer::AIR); + const bool canWalkOnSea = ti->hasWaterWalking() || (h->boat && h->boat->layer == EPathfindingLayer::WATER); const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining()); const bool movingOntoObstacle = t.blocked() && !t.visitable(); diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index abd130793..41dc0bdc4 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -584,7 +584,7 @@ std::vector NewTurnProcessor::updateHeroesMovementPoints() { for (CGHeroInstance *h : elem.second.getHeroes()) { - auto ti = std::make_unique(h, 1); + auto ti = h->getTurnInfo(1); // NOTE: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356 int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get()); From ea368c5176ec8d821357dc47441ae16107191df5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Dec 2024 12:28:14 +0000 Subject: [PATCH 08/12] Inlined methods commonly used by pathfinder for better optimization --- lib/TerrainHandler.cpp | 30 ------------- lib/TerrainHandler.h | 30 +++++++++++++ lib/mapping/CMap.cpp | 95 +-------------------------------------- lib/mapping/CMap.h | 31 +++++++++---- lib/mapping/CMapDefines.h | 80 ++++++++++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 134 deletions(-) diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index f3431394a..1473051db 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -151,36 +151,6 @@ std::vector TerrainTypeHandler::loadLegacyData() return result; } -bool TerrainType::isLand() const -{ - return !isWater(); -} - -bool TerrainType::isWater() const -{ - return passabilityType & PassabilityType::WATER; -} - -bool TerrainType::isRock() const -{ - return passabilityType & PassabilityType::ROCK; -} - -bool TerrainType::isPassable() const -{ - return !isRock(); -} - -bool TerrainType::isSurface() const -{ - return passabilityType & PassabilityType::SURFACE; -} - -bool TerrainType::isUnderground() const -{ - return passabilityType & PassabilityType::SUBTERRANEAN; -} - bool TerrainType::isTransitionRequired() const { return transitionRequired; diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index 3d0715cb2..201dde29a 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -112,4 +112,34 @@ public: std::vector loadLegacyData() override; }; +inline bool TerrainType::isLand() const +{ + return !isWater(); +} + +inline bool TerrainType::isWater() const +{ + return passabilityType & PassabilityType::WATER; +} + +inline bool TerrainType::isRock() const +{ + return passabilityType & PassabilityType::ROCK; +} + +inline bool TerrainType::isPassable() const +{ + return !isRock(); +} + +inline bool TerrainType::isSurface() const +{ + return passabilityType & PassabilityType::SURFACE; +} + +inline bool TerrainType::isUnderground() const +{ + return passabilityType & PassabilityType::SUBTERRANEAN; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 3760c3bc1..8d389cf38 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -143,17 +143,6 @@ TerrainTile::TerrainTile(): { } -bool TerrainTile::entrableTerrain(const TerrainTile * from) const -{ - return entrableTerrain(from ? from->isLand() : true, from ? from->isWater() : true); -} - -bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const -{ - return getTerrain()->isPassable() - && ((allowSea && isWater()) || (allowLand && isLand())); -} - bool TerrainTile::isClear(const TerrainTile * from) const { return entrableTerrain(from) && !blocked(); @@ -187,72 +176,6 @@ EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const return EDiggingStatus::CAN_DIG; } -bool TerrainTile::hasFavorableWinds() const -{ - return extTileFlags & 128; -} - -bool TerrainTile::isWater() const -{ - return getTerrain()->isWater(); -} - -bool TerrainTile::isLand() const -{ - return getTerrain()->isLand(); -} - -bool TerrainTile::visitable() const -{ - return !visitableObjects.empty(); -} - -bool TerrainTile::blocked() const -{ - return !blockingObjects.empty(); -} - -bool TerrainTile::hasRiver() const -{ - return getRiverID() != RiverId::NO_RIVER; -} - -bool TerrainTile::hasRoad() const -{ - return getRoadID() != RoadId::NO_ROAD; -} - -const TerrainType * TerrainTile::getTerrain() const -{ - return terrainType.toEntity(VLC); -} - -const RiverType * TerrainTile::getRiver() const -{ - return riverType.toEntity(VLC); -} - -const RoadType * TerrainTile::getRoad() const -{ - return roadType.toEntity(VLC); -} - -TerrainId TerrainTile::getTerrainID() const -{ - return terrainType; -} - -RiverId TerrainTile::getRiverID() const -{ - return riverType; -} - -RoadId TerrainTile::getRoadID() const -{ - return roadType; -} - - CMap::CMap(IGameCallback * cb) : GameCallbackHolder(cb) , checksum(0) @@ -365,7 +288,7 @@ bool CMap::isCoastalTile(const int3 & pos) const return false; } - if(isWaterTile(pos)) + if(getTile(pos).isWater()) return false; for(const auto & dir : dirs) @@ -382,22 +305,6 @@ bool CMap::isCoastalTile(const int3 & pos) const return false; } -TerrainTile & CMap::getTile(const int3 & tile) -{ - assert(isInTheMap(tile)); - return terrain[tile.z][tile.x][tile.y]; -} - -const TerrainTile & CMap::getTile(const int3 & tile) const -{ - assert(isInTheMap(tile)); - return terrain[tile.z][tile.x][tile.y]; -} - -bool CMap::isWaterTile(const int3 &pos) const -{ - return isInTheMap(pos) && getTile(pos).isWater(); -} bool CMap::canMoveBetween(const int3 &src, const int3 &dst) const { const TerrainTile * dstTile = &getTile(dst); diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 5192f7c1a..94d36b724 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -89,15 +89,7 @@ public: TerrainTile & getTile(const int3 & tile); const TerrainTile & getTile(const int3 & tile) const; bool isCoastalTile(const int3 & pos) const; - bool isWaterTile(const int3 & pos) const; - inline bool isInTheMap(const int3 & pos) const - { - // Check whether coord < 0 is done implicitly. Negative signed int overflows to unsigned number larger than all signed ints. - return - static_cast(pos.x) < static_cast(width) && - static_cast(pos.y) < static_cast(height) && - static_cast(pos.z) <= (twoLevel ? 1 : 0); - } + bool isInTheMap(const int3 & pos) const; bool canMoveBetween(const int3 &src, const int3 &dst) const; bool checkForVisitableDir(const int3 & src, const TerrainTile * pom, const int3 & dst) const; @@ -250,4 +242,25 @@ public: } }; +inline bool CMap::isInTheMap(const int3 & pos) const +{ + // Check whether coord < 0 is done implicitly. Negative signed int overflows to unsigned number larger than all signed ints. + return + static_cast(pos.x) < static_cast(width) && + static_cast(pos.y) < static_cast(height) && + static_cast(pos.z) <= (twoLevel ? 1 : 0); +} + +inline TerrainTile & CMap::getTile(const int3 & tile) +{ + assert(isInTheMap(tile)); + return terrain[tile.z][tile.x][tile.y]; +} + +inline const TerrainTile & CMap::getTile(const int3 & tile) const +{ + assert(isInTheMap(tile)); + return terrain[tile.z][tile.x][tile.y]; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index b8da3b405..0317ac2b6 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -12,7 +12,8 @@ #include "../ResourceSet.h" #include "../texts/MetaString.h" -#include "../int3.h" +#include "../VCMI_Lib.h" +#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -193,4 +194,81 @@ struct DLL_LINKAGE TerrainTile } }; +inline bool TerrainTile::hasFavorableWinds() const +{ + return extTileFlags & 128; +} + +inline bool TerrainTile::isWater() const +{ + return getTerrain()->isWater(); +} + +inline bool TerrainTile::isLand() const +{ + return getTerrain()->isLand(); +} + +inline bool TerrainTile::visitable() const +{ + return !visitableObjects.empty(); +} + +inline bool TerrainTile::blocked() const +{ + return !blockingObjects.empty(); +} + +inline bool TerrainTile::hasRiver() const +{ + return getRiverID() != RiverId::NO_RIVER; +} + +inline bool TerrainTile::hasRoad() const +{ + return getRoadID() != RoadId::NO_ROAD; +} + +inline const TerrainType * TerrainTile::getTerrain() const +{ + return terrainType.toEntity(VLC); +} + +inline const RiverType * TerrainTile::getRiver() const +{ + return riverType.toEntity(VLC); +} + +inline const RoadType * TerrainTile::getRoad() const +{ + return roadType.toEntity(VLC); +} + +inline TerrainId TerrainTile::getTerrainID() const +{ + return terrainType; +} + +inline RiverId TerrainTile::getRiverID() const +{ + return riverType; +} + +inline RoadId TerrainTile::getRoadID() const +{ + return roadType; +} + +inline bool TerrainTile::entrableTerrain(const TerrainTile * from) const +{ + const TerrainType * terrainFrom = from->getTerrain(); + return entrableTerrain(terrainFrom->isLand(), terrainFrom->isWater()); +} + +inline bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const +{ + const TerrainType * terrain = getTerrain(); + return terrain->isPassable() && ((allowSea && terrain->isWater()) || (allowLand && terrain->isLand())); +} + VCMI_LIB_NAMESPACE_END From 78fc6d0e416d46f894e7a193a0907798fe901fba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Dec 2024 20:37:52 +0000 Subject: [PATCH 09/12] Remove unused code --- AI/BattleAI/BattleExchangeVariant.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index c4675afc0..60b6b4b02 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -857,15 +857,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange( exchangeBattle->nextRound(); } - // avoid blocking path for stronger stack by weaker stack - // the method checks if all stacks can be placed around enemy - std::map reachabilityMap; - - auto hexes = ap.attack.defender->getSurroundingHexes(); - - for(auto hex : hexes) - reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex); - auto score = v.getScore(); if(simulationTurnsCount < totalTurnsCount) From dfe8a95f8c6efac56349cb74ffd08f6d0eb3c9c8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Dec 2024 21:11:50 +0000 Subject: [PATCH 10/12] Fix build --- config/gameConfig.json | 4 ++-- lib/TerrainHandler.h | 13 ++++++------- lib/mapping/CMap.h | 6 +++--- lib/mapping/CMapDefines.h | 36 +++++++++++++++++++++--------------- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index e962291a4..92e869a0b 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -305,11 +305,11 @@ // if heroes are invitable in tavern "tavernInvite" : false, // minimal primary skills for heroes - "minimalPrimarySkills": [ 0, 0, 1, 1] + "minimalPrimarySkills": [ 0, 0, 1, 1], /// movement points hero can get on start of the turn when on land, depending on speed of slowest creature (0-based list) "movementPointsLand" : [ 1500, 1500, 1500, 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 ], /// movement points hero can get on start of the turn when on sea, depending on speed of slowest creature (0-based list) - "movementPointsSea" : [ 1500 ], + "movementPointsSea" : [ 1500 ] }, "towns": diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index 201dde29a..2af6db3ca 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -83,14 +83,13 @@ public: TerrainType() = default; - bool isLand() const; - bool isWater() const; - bool isRock() const; + inline bool isLand() const; + inline bool isWater() const; + inline bool isRock() const; + inline bool isPassable() const; + inline bool isSurface() const; + inline bool isUnderground() const; - bool isPassable() const; - - bool isSurface() const; - bool isUnderground() const; bool isTransitionRequired() const; }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 94d36b724..4fa4c2744 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -86,10 +86,10 @@ public: void initTerrain(); CMapEditManager * getEditManager(); - TerrainTile & getTile(const int3 & tile); - const TerrainTile & getTile(const int3 & tile) const; + inline TerrainTile & getTile(const int3 & tile); + inline const TerrainTile & getTile(const int3 & tile) const; bool isCoastalTile(const int3 & pos) const; - bool isInTheMap(const int3 & pos) const; + inline bool isInTheMap(const int3 & pos) const; bool canMoveBetween(const int3 &src, const int3 &dst) const; bool checkForVisitableDir(const int3 & src, const TerrainTile * pom, const int3 & dst) const; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 0317ac2b6..c34256506 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -104,31 +104,32 @@ struct DLL_LINKAGE TerrainTile TerrainTile(); /// Gets true if the terrain is not a rock. If from is water/land, same type is also required. - bool entrableTerrain(const TerrainTile * from = nullptr) const; - bool entrableTerrain(bool allowLand, bool allowSea) const; + inline bool entrableTerrain() const; + inline bool entrableTerrain(const TerrainTile * from) const; + inline bool entrableTerrain(bool allowLand, bool allowSea) const; /// Checks for blocking objects and terraint type (water / land). bool isClear(const TerrainTile * from = nullptr) const; /// Gets the ID of the top visitable object or -1 if there is none. Obj topVisitableId(bool excludeTop = false) const; CGObjectInstance * topVisitableObj(bool excludeTop = false) const; - bool isWater() const; - bool isLand() const; + inline bool isWater() const; + inline bool isLand() const; EDiggingStatus getDiggingStatus(bool excludeTop = true) const; - bool hasFavorableWinds() const; + inline bool hasFavorableWinds() const; - bool visitable() const; - bool blocked() const; + inline bool visitable() const; + inline bool blocked() const; - const TerrainType * getTerrain() const; - const RiverType * getRiver() const; - const RoadType * getRoad() const; + inline const TerrainType * getTerrain() const; + inline const RiverType * getRiver() const; + inline const RoadType * getRoad() const; - TerrainId getTerrainID() const; - RiverId getRiverID() const; - RoadId getRoadID() const; + inline TerrainId getTerrainID() const; + inline RiverId getRiverID() const; + inline RoadId getRoadID() const; - bool hasRiver() const; - bool hasRoad() const; + inline bool hasRiver() const; + inline bool hasRoad() const; TerrainId terrainType; RiverId riverType; @@ -259,6 +260,11 @@ inline RoadId TerrainTile::getRoadID() const return roadType; } +inline bool TerrainTile::entrableTerrain() const +{ + return entrableTerrain(true, true); +} + inline bool TerrainTile::entrableTerrain(const TerrainTile * from) const { const TerrainType * terrainFrom = from->getTerrain(); From 83b9a8d7502f86474b77e4e9134d76347e277567 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 30 Dec 2024 12:40:53 +0000 Subject: [PATCH 11/12] Fixes for regressions --- lib/battle/CUnitState.cpp | 6 +++--- lib/bonuses/IBonusBearer.cpp | 2 +- lib/pathfinder/TurnInfo.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index f1b0f0f7d..02fa3a178 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -939,16 +939,16 @@ const UnitBonusValuesProxy::SelectorsArray * CUnitState::generateBonusSelectors( static const CSelector selectorMelee = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT)); static const CSelector selectorRanged = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)); static const CSelector minDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); - static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); + static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)); static const CSelector attack = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); - static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); + static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)); static const UnitBonusValuesProxy::SelectorsArray selectors = { additionalAttack.And(selectorMelee), //TOTAL_ATTACKS_MELEE, additionalAttack.And(selectorRanged), //TOTAL_ATTACKS_RANGED, minDamage.And(selectorMelee), //MIN_DAMAGE_MELEE, minDamage.And(selectorRanged), //MIN_DAMAGE_RANGED, - minDamage.And(selectorMelee), //MAX_DAMAGE_MELEE, + maxDamage.And(selectorMelee), //MAX_DAMAGE_MELEE, maxDamage.And(selectorRanged), //MAX_DAMAGE_RANGED, attack.And(selectorRanged),//ATTACK_MELEE, attack.And(selectorRanged),//ATTACK_RANGED, diff --git a/lib/bonuses/IBonusBearer.cpp b/lib/bonuses/IBonusBearer.cpp index 2e6f29d12..532e6ef14 100644 --- a/lib/bonuses/IBonusBearer.cpp +++ b/lib/bonuses/IBonusBearer.cpp @@ -105,7 +105,7 @@ bool IBonusBearer::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const bool IBonusBearer::hasBonusFrom(BonusSource source, BonusSourceID sourceID) const { - std::string cachingStr = "source_" + std::to_string(static_cast(source)) + "_" + sourceID.toString(); + std::string cachingStr = "source_" + std::to_string(static_cast(source)) + "_" + std::to_string(sourceID.getNum()); return hasBonus(Selector::source(source,sourceID), cachingStr); } diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp index 9ef4489e3..b9d8f1f54 100644 --- a/lib/pathfinder/TurnInfo.cpp +++ b/lib/pathfinder/TurnInfo.cpp @@ -113,20 +113,20 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i { static const CSelector selector = Selector::type()(BonusType::FREE_SHIP_BOARDING); - const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + const auto & bonuses = sharedCache->freeShipBoarding.getBonusList(target, selector); freeShipBoardingTest = bonuses->getFirst(selector) != nullptr; } { static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT); - const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector); roughTerrainDiscountValue = bonuses->getFirst(selector) != nullptr; } { static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea); const auto & vectorSea = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_SEA).Vector(); - const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + const auto & bonuses = sharedCache->movementPointsLimitWater.getBonusList(target, selector); int baseMovementPointsSea; if (lowestSpeed < vectorSea.size()) baseMovementPointsSea = vectorSea[lowestSpeed].Integer(); @@ -139,7 +139,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i { static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea); const auto & vectorLand = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_LAND).Vector(); - const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + const auto & bonuses = sharedCache->movementPointsLimitLand.getBonusList(target, selector); int baseMovementPointsLand; if (lowestSpeed < vectorLand.size()) baseMovementPointsLand = vectorLand[lowestSpeed].Integer(); @@ -151,7 +151,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i { static const CSelector selector = Selector::type()(BonusType::NO_TERRAIN_PENALTY); - const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); + const auto & bonuses = sharedCache->noTerrainPenalty.getBonusList(target, selector); for (const auto & bonus : *bonuses) { TerrainId affectedTerrain = bonus->subtype.as(); From 73d1675ae30935c856478a728c77a0f6547ff210 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 5 Jan 2025 15:41:42 +0000 Subject: [PATCH 12/12] Code cleanup, fixes to discovered regressions --- include/vcmi/Creature.h | 1 + lib/BasicTypes.cpp | 13 ++++++++++++ lib/battle/CUnitState.cpp | 34 +------------------------------ lib/battle/CUnitState.h | 2 -- lib/battle/Unit.h | 2 -- lib/bonuses/BonusCache.cpp | 34 ++++++++++++++++++++++++++++++- lib/bonuses/BonusCache.h | 8 +++++--- lib/mapObjects/CGHeroInstance.cpp | 16 +++------------ lib/mapObjects/CGHeroInstance.h | 2 +- lib/pathfinder/CPathfinder.cpp | 15 +------------- lib/pathfinder/CPathfinder.h | 1 - lib/pathfinder/TurnInfo.cpp | 27 +++++++++++++++--------- 12 files changed, 75 insertions(+), 80 deletions(-) diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index ad74e0cab..6e493905a 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -26,6 +26,7 @@ public: virtual ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators virtual ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers + virtual int32_t getInitiative(int turn = 0) const; }; template diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index a696b20ac..d64fa0ff7 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -152,6 +152,19 @@ ui32 ACreature::getMovementRange() const return getBonusBearer()->valOfBonuses(BonusType::STACKS_SPEED); } +int32_t ACreature::getInitiative(int turn) const +{ + if (turn == 0) + { + return getBonusBearer()->valOfBonuses(BonusType::STACKS_SPEED); + } + else + { + const std::string cachingStrSS = "type_STACKS_SPEED_turns_" + std::to_string(turn); + return getBonusBearer()->valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStrSS); + } +} + ui32 ACreature::getMovementRange(int turn) const { if (turn == 0) diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 02fa3a178..8c40875e7 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -335,7 +335,7 @@ CUnitState::CUnitState(): shots(this), stackSpeedPerTurn(this, Selector::type()(BonusType::STACKS_SPEED), BonusCacheMode::VALUE), immobilizedPerTurn(this, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::BIND_EFFECT)), BonusCacheMode::PRESENCE), - bonusCache(this, generateBonusSelectors()), + bonusCache(this), cloneID(-1) { @@ -933,38 +933,6 @@ void CUnitState::onRemoved() ghost = true; } -const UnitBonusValuesProxy::SelectorsArray * CUnitState::generateBonusSelectors() -{ - static const CSelector additionalAttack = Selector::type()(BonusType::ADDITIONAL_ATTACK); - static const CSelector selectorMelee = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT)); - static const CSelector selectorRanged = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)); - static const CSelector minDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); - static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)); - static const CSelector attack = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); - static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)); - - static const UnitBonusValuesProxy::SelectorsArray selectors = { - additionalAttack.And(selectorMelee), //TOTAL_ATTACKS_MELEE, - additionalAttack.And(selectorRanged), //TOTAL_ATTACKS_RANGED, - minDamage.And(selectorMelee), //MIN_DAMAGE_MELEE, - minDamage.And(selectorRanged), //MIN_DAMAGE_RANGED, - maxDamage.And(selectorMelee), //MAX_DAMAGE_MELEE, - maxDamage.And(selectorRanged), //MAX_DAMAGE_RANGED, - attack.And(selectorRanged),//ATTACK_MELEE, - attack.And(selectorRanged),//ATTACK_RANGED, - defence.And(selectorRanged),//DEFENCE_MELEE, - defence.And(selectorRanged),//DEFENCE_RANGED, - Selector::type()(BonusType::IN_FRENZY),//IN_FRENZY, - Selector::type()(BonusType::FORGETFULL),//FORGETFULL, - Selector::type()(BonusType::HYPNOTIZED),//HYPNOTIZED, - Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, - Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, - Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) - }; - - return &selectors; -} - CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_): unit(unit_), bonus(bonus_) diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index c2a323e7f..d018c6625 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -260,8 +260,6 @@ public: void onRemoved(); private: - static const UnitBonusValuesProxy::SelectorsArray * generateBonusSelectors(); - const IUnitEnvironment * env; BonusCachePerTurn immobilizedPerTurn; diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index e3b56847d..574a111be 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -115,8 +115,6 @@ public: virtual BattleHex getPosition() const = 0; virtual void setPosition(BattleHex hex) = 0; - virtual int32_t getInitiative(int turn = 0) const = 0; - virtual bool canMove(int turn = 0) const = 0; //if stack can move virtual bool defended(int turn = 0) const = 0; virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 3eac0290c..cd51755db 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -43,7 +43,7 @@ int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSel } } -BonusValueCache::BonusValueCache(const IBonusBearer * target, const CSelector selector) +BonusValueCache::BonusValueCache(const IBonusBearer * target, const CSelector & selector) :BonusCacheBase(target),selector(selector) {} @@ -177,4 +177,36 @@ int BonusCachePerTurn::getValue(int turns) const } } +const UnitBonusValuesProxy::SelectorsArray * UnitBonusValuesProxy::generateSelectors() +{ + static const CSelector additionalAttack = Selector::type()(BonusType::ADDITIONAL_ATTACK); + static const CSelector selectorMelee = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT)); + static const CSelector selectorRanged = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)); + static const CSelector minDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)); + static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)); + static const CSelector attack = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); + static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)); + + static const UnitBonusValuesProxy::SelectorsArray selectors = { + additionalAttack.And(selectorMelee), //TOTAL_ATTACKS_MELEE, + additionalAttack.And(selectorRanged), //TOTAL_ATTACKS_RANGED, + minDamage.And(selectorMelee), //MIN_DAMAGE_MELEE, + minDamage.And(selectorRanged), //MIN_DAMAGE_RANGED, + maxDamage.And(selectorMelee), //MAX_DAMAGE_MELEE, + maxDamage.And(selectorRanged), //MAX_DAMAGE_RANGED, + attack.And(selectorRanged),//ATTACK_MELEE, + attack.And(selectorRanged),//ATTACK_RANGED, + defence.And(selectorRanged),//DEFENCE_MELEE, + defence.And(selectorRanged),//DEFENCE_RANGED, + Selector::type()(BonusType::IN_FRENZY),//IN_FRENZY, + Selector::type()(BonusType::FORGETFULL),//FORGETFULL, + Selector::type()(BonusType::HYPNOTIZED),//HYPNOTIZED, + Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, + Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, + Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) + }; + + return &selectors; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index fbc59f571..b791d3aa3 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -45,7 +45,7 @@ class BonusValueCache : public BonusCacheBase CSelector selector; mutable BonusCacheEntry value; public: - BonusValueCache(const IBonusBearer * target, const CSelector selector); + BonusValueCache(const IBonusBearer * target, const CSelector & selector); int getValue() const; bool hasBonus() const; }; @@ -112,8 +112,8 @@ public: using SelectorsArray = BonusValuesArrayCache::SelectorsArray; - UnitBonusValuesProxy(const IBonusBearer * Target, const SelectorsArray * selectors): - cache(Target, selectors) + UnitBonusValuesProxy(const IBonusBearer * Target): + cache(Target, generateSelectors()) {} int getBonusValue(ECacheKeys which) const @@ -129,6 +129,8 @@ public: } private: + const SelectorsArray * generateSelectors(); + BonusValuesArrayCache cache; }; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index ffd94191c..472834210 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -82,7 +82,7 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain { ret = from.getRoad()->movementCost; } - else if(ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus + else if(!ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus { ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost; ret -= ti->getRoughTerrainDiscountValue(); @@ -235,22 +235,19 @@ int CGHeroInstance::movementPointsLimit(bool onLand) const int CGHeroInstance::getLowestCreatureSpeed() const { - static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED); - static const std::string cachingStr = "type_" + std::to_string(static_cast(BonusType::STACKS_SPEED)); - if(stacksCount() != 0) { int minimalSpeed = std::numeric_limits::max(); //TODO? should speed modifiers (eg from artifacts) affect hero movement? for(const auto & slot : Slots()) - minimalSpeed = std::min(minimalSpeed, slot.second->valOfBonuses(selectorSTACKS_SPEED, cachingStr)); + minimalSpeed = std::min(minimalSpeed, slot.second->getInitiative()); return minimalSpeed; } else { if(commander && commander->alive) - return commander->valOfBonuses(selectorSTACKS_SPEED, cachingStr); + return commander->getInitiative(); } return 10; @@ -1348,13 +1345,6 @@ CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState * gs) int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const { - std::unique_ptr turnInfoLocal; - if(!ti) - { - turnInfoLocal = getTurnInfo(0); - ti = turnInfoLocal.get(); - } - if(!ti->hasFreeShipBoarding()) return 0; // take all MPs by default diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 569922ef5..1f66d678b 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -227,7 +227,7 @@ public: //cached version is much faster, TurnInfo construction is costly int movementPointsLimitCached(bool onLand, const TurnInfo * ti) const; - int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; + int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const; std::unique_ptr getTurnInfo(int days) const; diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 87c073fca..c7dfcdcdf 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -572,7 +572,7 @@ void CPathfinderHelper::getNeighbours( CMap * map = gs->map; const TerrainType * sourceTerrain = sourceTile.getTerrain(); - constexpr std::array dirs = { + static constexpr std::array dirs = { int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0), int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0), int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) @@ -689,19 +689,6 @@ int CPathfinderHelper::getMovementCost( if (pointsLeft < minimalNextMoveCost) return remainingMovePoints; - -// NeighbourTilesVector vec; -// -// getNeighbours(*dt, dst, vec, ct->isLand(), true); -// for(const auto & elem : vec) -// { -// int fcost = getMovementCost(dst, elem, nullptr, nullptr, pointsLeft, false); -// if(fcost <= pointsLeft) -// { -// return movementCost; -// } -// } -// movementCost = remainingMovePoints; } return movementCost; diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h index 6fe8b17f2..49d2ced24 100644 --- a/lib/pathfinder/CPathfinder.h +++ b/lib/pathfinder/CPathfinder.h @@ -93,7 +93,6 @@ public: void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer & layer) const; const TurnInfo * getTurnInfo() const; - //bool hasBonusOfType(BonusType type) const; int getMaxMovePoints(const EPathfindingLayer & layer) const; TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const; diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp index b9d8f1f54..c54e720c1 100644 --- a/lib/pathfinder/TurnInfo.cpp +++ b/lib/pathfinder/TurnInfo.cpp @@ -80,8 +80,8 @@ int TurnInfo::getMovePointsLimitWater() const } TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn) - : noterrainPenalty(VLC->terrainTypeHandler->size()) - , target(target) + : target(target) + , noterrainPenalty(VLC->terrainTypeHandler->size()) { CSelector daySelector = Selector::days(Turn); @@ -100,27 +100,27 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i { static const CSelector selector = Selector::type()(BonusType::WATER_WALKING); const auto & bonuses = sharedCache->waterWalking.getBonusList(target, selector); - waterWalkingTest = bonuses->getFirst(selector) != nullptr; - waterWalkingValue = bonuses->valOfBonuses(selector); + waterWalkingTest = bonuses->getFirst(daySelector) != nullptr; + waterWalkingValue = bonuses->valOfBonuses(daySelector); } { static const CSelector selector = Selector::type()(BonusType::FLYING_MOVEMENT); const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector); - flyingMovementTest = bonuses->getFirst(selector) != nullptr; - flyingMovementValue = bonuses->valOfBonuses(selector); + flyingMovementTest = bonuses->getFirst(daySelector) != nullptr; + flyingMovementValue = bonuses->valOfBonuses(daySelector); } { static const CSelector selector = Selector::type()(BonusType::FREE_SHIP_BOARDING); const auto & bonuses = sharedCache->freeShipBoarding.getBonusList(target, selector); - freeShipBoardingTest = bonuses->getFirst(selector) != nullptr; + freeShipBoardingTest = bonuses->getFirst(daySelector) != nullptr; } { static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT); const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector); - roughTerrainDiscountValue = bonuses->getFirst(selector) != nullptr; + roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr; } { @@ -133,7 +133,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i else baseMovementPointsSea = vectorSea.back().Integer(); - movePointsLimitWater = bonuses->valOfBonuses(selector, baseMovementPointsSea); + movePointsLimitWater = bonuses->valOfBonuses(daySelector, baseMovementPointsSea); } { @@ -146,7 +146,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i else baseMovementPointsLand = vectorLand.back().Integer(); - movePointsLimitLand = bonuses->valOfBonuses(selector, baseMovementPointsLand); + movePointsLimitLand = bonuses->valOfBonuses(daySelector, baseMovementPointsLand); } { @@ -157,6 +157,13 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i TerrainId affectedTerrain = bonus->subtype.as(); noterrainPenalty.at(affectedTerrain.num) = true; } + + const auto nativeTerrain = target->getNativeTerrain(); + if (nativeTerrain.hasValue()) + noterrainPenalty.at(nativeTerrain.num) = true; + + if (nativeTerrain == ETerrainId::ANY_TERRAIN) + boost::range::fill(noterrainPenalty, true); } }