From 5375d61d1bb57bad9ca455f54e78da0de5003fc1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 14:27:12 +0000 Subject: [PATCH 01/10] Optimize battleAdjacentUnits method --- lib/battle/CBattleInfoCallback.cpp | 20 ++++++++++++-------- lib/battle/CBattleInfoCallback.h | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 32a3ec0e5..c3a946c1e 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1716,18 +1716,22 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const return false; } -std::set CBattleInfoCallback::battleAdjacentUnits(const battle::Unit * unit) const +battle::Units CBattleInfoCallback::battleAdjacentUnits(const battle::Unit * unit) const { - std::set ret; - RETURN_IF_NOT_BATTLE(ret); + RETURN_IF_NOT_BATTLE({}); - for(auto hex : unit->getSurroundingHexes()) + const auto & hexes = unit->getSurroundingHexes(); + + const auto & units = battleGetUnitsIf([=](const battle::Unit * unit) { - if(const auto * neighbour = battleGetUnitByPos(hex, true)) - ret.insert(neighbour); - } + const auto & unitHexes = unit->getHexes(); + for (const auto & hex : unitHexes) + if (hexes.contains(hex)) + return true; + return false; + }); - return ret; + return units; } SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const battle::Unit * caster, const battle::Unit * subject) const diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index e7c11d5da..626242a0d 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -95,7 +95,7 @@ public: bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack - std::set battleAdjacentUnits(const battle::Unit * unit) const; + battle::Units battleAdjacentUnits(const battle::Unit * unit) const; DamageEstimation calculateDmgRange(const BattleAttackInfo & info) const; @@ -173,4 +173,4 @@ protected: BattleHexArray getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END From 3b35c679ce1cdd4331b64dcd2911312b082899ff Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 15:07:59 +0000 Subject: [PATCH 02/10] Optimize Unit::getHexes method --- lib/CStack.cpp | 28 ++++++++++++++++++++++++++++ lib/CStack.h | 29 +++++++---------------------- lib/battle/CUnitState.h | 2 +- lib/battle/Unit.cpp | 40 +++++++++++++++++++++++++--------------- lib/battle/Unit.h | 2 ++ 5 files changed, 63 insertions(+), 38 deletions(-) diff --git a/lib/CStack.cpp b/lib/CStack.cpp index df2a38fa2..c788d7be3 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -35,6 +35,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle side(Side) { health.init(); //??? + doubleWideCached = battle::CUnitState::doubleWide(); } CStack::CStack(): @@ -55,6 +56,7 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I side(Side) { health.init(); //??? + doubleWideCached = battle::CUnitState::doubleWide(); } void CStack::localInit(BattleInfo * battleInfo) @@ -404,4 +406,30 @@ void CStack::spendMana(ServerCallback * server, const int spellCost) const server->apply(ssp); } +void CStack::postDeserialize(const CArmedInstance * army, const SlotID & extSlot) +{ + if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) + { + const auto * hero = dynamic_cast(army); + assert(hero); + base = hero->commander; + } + else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT) + { + //no external slot possible, so no base stack + base = nullptr; + } + else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) + { + base = nullptr; + logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated()); + } + else + { + base = &army->getStack(extSlot); + } + + doubleWideCached = battle::CUnitState::doubleWide(); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/CStack.h b/lib/CStack.h index 23f22cf4f..1faca6104 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -23,7 +23,7 @@ struct BattleStackAttacked; class BattleInfo; //Represents STACK_BATTLE nodes -class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment +class DLL_LINKAGE CStack final : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment { private: ui32 ID = -1; //unique ID of stack @@ -36,6 +36,9 @@ private: SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures) + bool doubleWideCached = false; + + void postDeserialize(const CArmedInstance * army, const SlotID & extSlot); public: const CStackInstance * base = nullptr; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) @@ -77,6 +80,7 @@ public: BattleSide unitSide() const override; PlayerColor unitOwner() const override; SlotID unitSlot() const override; + bool doubleWide() const override { return doubleWideCached;}; std::string getDescription() const override; @@ -119,26 +123,7 @@ public: h & army; h & extSlot; - if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) - { - const auto * hero = dynamic_cast(army); - assert(hero); - base = hero->commander; - } - else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT) - { - //no external slot possible, so no base stack - base = nullptr; - } - else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) - { - base = nullptr; - logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated()); - } - else - { - base = &army->getStack(extSlot); - } + postDeserialize(army, extSlot); } } @@ -146,4 +131,4 @@ private: const BattleInfo * battle; //do not serialize }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index d018c6625..f07ba27f3 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -269,7 +269,7 @@ private: void reset(); }; -class DLL_LINKAGE CUnitStateDetached : public CUnitState +class DLL_LINKAGE CUnitStateDetached final : public CUnitState { public: explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_); diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 57a36eb6a..b33eda0f4 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -107,24 +107,34 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos) const return getHexes(assumedPos, doubleWide(), unitSide()); } +BattleHexArray::ArrayOfBattleHexArrays Unit::precomputeUnitHexes(BattleSide side, bool twoHex) +{ + BattleHexArray::ArrayOfBattleHexArrays result; + + for (BattleHex assumedPos = 0; assumedPos < GameConstants::BFIELD_SIZE; ++assumedPos) + { + BattleHexArray hexes; + hexes.insert(assumedPos); + + if(twoHex) + hexes.insert(occupiedHex(assumedPos, twoHex, side)); + + result[assumedPos.toInt()] = std::move(hexes); + } + + return result; +} + const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) { - static BattleHexArray::ArrayOfBattleHexArrays precomputed[4]; + static const std::array precomputed = { + precomputeUnitHexes(BattleSide::ATTACKER, false), + precomputeUnitHexes(BattleSide::ATTACKER, true), + precomputeUnitHexes(BattleSide::DEFENDER, false), + precomputeUnitHexes(BattleSide::DEFENDER, true), + }; + int index = side == BattleSide::ATTACKER ? 0 : 2; - - if(!precomputed[index + twoHex][assumedPos.toInt()].empty()) - return precomputed[index + twoHex][assumedPos.toInt()]; - - // first run, compute - - BattleHexArray hexes; - hexes.insert(assumedPos); - - if(twoHex) - hexes.insert(occupiedHex(assumedPos, twoHex, side)); - - precomputed[index + twoHex][assumedPos.toInt()] = std::move(hexes); - return precomputed[index + twoHex][assumedPos.toInt()]; } diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index 9708b124f..794e14b94 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -64,6 +64,8 @@ class CUnitState; class DLL_LINKAGE Unit : public IUnitInfo, public spells::Caster, public virtual IBonusBearer, public ACreature { + static BattleHexArray::ArrayOfBattleHexArrays precomputeUnitHexes(BattleSide side, bool twoHex); + public: virtual ~Unit(); From 2d5b5d94e7da0e8ada0322295a3b8e21c82cf390 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 15:38:28 +0000 Subject: [PATCH 03/10] Optimize computation of reachability map --- AI/BattleAI/BattleExchangeVariant.cpp | 19 +++++++++++-------- AI/BattleAI/BattleExchangeVariant.h | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 49be0c470..cdb65a697 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -11,6 +11,7 @@ #include "BattleExchangeVariant.h" #include "BattleEvaluator.h" #include "../../lib/CStack.h" +#include "tbb/parallel_for.h" AttackerValue::AttackerValue() : value(0), @@ -518,14 +519,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( for(auto hex : hexes) { - vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex)); + vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex.toInt()) : getOneTurnReachableUnits(turn, hex)); } if(!ap.attack.attacker->isTurret()) { for(auto hex : ap.attack.attacker->getHexes()) { - auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); + auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex.toInt()) : getOneTurnReachableUnits(turn, hex); for(auto unit : unitsReachingAttacker) { if(unit->unitSide() != ap.attack.attacker->unitSide()) @@ -799,7 +800,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange( if(!u->getPosition().isValid()) return false; // e.g. tower shooters - return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool + return vstd::contains_if(reachabilityMap.at(u->getPosition().toInt()), [&attacker](const battle::Unit * other) -> bool { return attacker->unitId() == other->unitId(); }); @@ -886,7 +887,7 @@ bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) { for(auto pos : ap.attack.attacker->getSurroundingHexes()) { - for(auto u : reachabilityMap[pos]) + for(auto u : reachabilityMap.at(pos.toInt())) { if(u->unitSide() != ap.attack.attacker->unitSide()) { @@ -916,10 +917,12 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr(0, reachabilityMap.size()), [&](const tbb::blocked_range & r) { - reachabilityMap[hex] = getOneTurnReachableUnits(0, hex); - } + for(auto i = r.begin(); i != r.end(); i++) + reachabilityMap[i] = getOneTurnReachableUnits(0, BattleHex(i)); + }); } std::vector BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const @@ -1029,7 +1032,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb } } - if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1) + if(!reachable && std::count(reachabilityMap[hex.toInt()].begin(), reachabilityMap[hex.toInt()].end(), unit) > 1) { blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); } diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index 0aba34749..8518a51df 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -129,7 +129,7 @@ private: std::shared_ptr cb; std::shared_ptr env; std::map reachabilityCache; - std::map> reachabilityMap; + std::array, GameConstants::BFIELD_SIZE> reachabilityMap; std::vector turnOrder; float negativeEffectMultiplier; int simulationTurnsCount; From 797b62fd463ae3e9b05f9ac6c54c4ce4eb51eb14 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 17:43:45 +0000 Subject: [PATCH 04/10] Try to implement lazy evaluation for reachability map --- AI/BattleAI/BattleExchangeVariant.cpp | 78 +++++++++++++++++---------- AI/BattleAI/BattleExchangeVariant.h | 22 ++++++-- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index cdb65a697..5e71bc4a9 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -519,14 +519,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( for(auto hex : hexes) { - vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex.toInt()) : getOneTurnReachableUnits(turn, hex)); + vstd::concatenate(allReachableUnits, getOneTurnReachableUnits(turn, hex)); } if(!ap.attack.attacker->isTurret()) { for(auto hex : ap.attack.attacker->getHexes()) { - auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex.toInt()) : getOneTurnReachableUnits(turn, hex); + auto unitsReachingAttacker = getOneTurnReachableUnits(turn, hex); for(auto unit : unitsReachingAttacker) { if(unit->unitSide() != ap.attack.attacker->unitSide()) @@ -800,7 +800,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange( if(!u->getPosition().isValid()) return false; // e.g. tower shooters - return vstd::contains_if(reachabilityMap.at(u->getPosition().toInt()), [&attacker](const battle::Unit * other) -> bool + const auto & reachableUnits = getOneTurnReachableUnits(0, u->getPosition()); + + return vstd::contains_if(reachableUnits, [&attacker](const battle::Unit * other) -> bool { return attacker->unitId() == other->unitId(); }); @@ -887,7 +889,7 @@ bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) { for(auto pos : ap.attack.attacker->getSurroundingHexes()) { - for(auto u : reachabilityMap.at(pos.toInt())) + for(auto u : getOneTurnReachableUnits(0, pos)) { if(u->unitSide() != ap.attack.attacker->unitSide()) { @@ -899,6 +901,22 @@ bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) return false; } +void ReachabilityMapCache::update(const std::vector & turnOrder, std::shared_ptr hb) +{ + for(auto turn : turnOrder) + { + for(auto u : turn) + { + if(!vstd::contains(unitReachabilityMap, u->unitId())) + { + unitReachabilityMap[u->unitId()] = hb->getReachability(u); + } + } + } + + hexReachabilityPerTurn.clear(); +} + void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr hb) { const int TURN_DEPTH = 2; @@ -906,28 +924,25 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptrbattleGetTurnOrder(turnOrder, std::numeric_limits::max(), TURN_DEPTH); - - for(auto turn : turnOrder) - { - for(auto u : turn) - { - if(!vstd::contains(reachabilityCache, u->unitId())) - { - reachabilityCache[u->unitId()] = hb->getReachability(u); - } - } - } - - tbb::parallel_for(tbb::blocked_range(0, reachabilityMap.size()), [&](const tbb::blocked_range & r) - { - for(auto i = r.begin(); i != r.end(); i++) - reachabilityMap[i] = getOneTurnReachableUnits(0, BattleHex(i)); - }); + reachabilityMap.update(turnOrder, hb); } -std::vector BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const +const battle::Units & ReachabilityMapCache::getOneTurnReachableUnits(std::shared_ptr cb, std::shared_ptr env, const std::vector & turnOrder, uint8_t turn, BattleHex hex) { - std::vector result; + auto & turnData = hexReachabilityPerTurn[turn]; + + if (!turnData.isValid[hex.toInt()]) + { + turnData.hexes[hex.toInt()] = computeOneTurnReachableUnits(cb, env, turnOrder, turn, hex); + turnData.isValid.set(hex.toInt()); + } + + return turnData.hexes[hex.toInt()]; +} + +battle::Units ReachabilityMapCache::computeOneTurnReachableUnits(std::shared_ptr cb, std::shared_ptr env, const std::vector & turnOrder, uint8_t turn, BattleHex hex) +{ + battle::Units result; for(int i = 0; i < turnOrder.size(); i++, turn++) { @@ -949,10 +964,10 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn auto unitSpeed = unit->getMovementRange(turn); auto radius = unitSpeed * (turn + 1); - auto reachabilityIter = reachabilityCache.find(unit->unitId()); - assert(reachabilityIter != reachabilityCache.end()); // missing updateReachabilityMap call? + auto reachabilityIter = unitReachabilityMap.find(unit->unitId()); + assert(reachabilityIter != unitReachabilityMap.end()); // missing updateReachabilityMap call? - ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit); + ReachabilityInfo unitReachability = reachabilityIter != unitReachabilityMap.end() ? reachabilityIter->second : turnBattle.getReachability(unit); bool reachable = unitReachability.distances.at(hex.toInt()) <= radius; @@ -981,6 +996,11 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn return result; } +const battle::Units & BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const +{ + return reachabilityMap.getOneTurnReachableUnits(cb, env, turnOrder, turn, hex); +} + // avoid blocking path for stronger stack by weaker stack bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * activeUnit, BattleHex position) { @@ -1032,9 +1052,11 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb } } - if(!reachable && std::count(reachabilityMap[hex.toInt()].begin(), reachabilityMap[hex.toInt()].end(), unit) > 1) + if(!reachable) { - blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); + auto reachableUnits = getOneTurnReachableUnits(0, hex); + if (std::count(reachableUnits.begin(), reachableUnits.end(), unit) > 1) + blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); } } } diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index 8518a51df..de8577d5a 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -123,13 +123,29 @@ struct ReachabilityData std::set enemyUnitsReachingAttacker; }; +class ReachabilityMapCache +{ + struct PerTurnData{ + std::bitset isValid; + std::array hexes; + }; + + std::map unitReachabilityMap; // unit ID -> reachability + std::map hexReachabilityPerTurn; + + //const ReachabilityInfo & update(); + battle::Units computeOneTurnReachableUnits(std::shared_ptr cb, std::shared_ptr env, const std::vector & turnOrder, uint8_t turn, BattleHex hex); +public: + const battle::Units & getOneTurnReachableUnits(std::shared_ptr cb, std::shared_ptr env, const std::vector & turnOrder, uint8_t turn, BattleHex hex); + void update(const std::vector & turnOrder, std::shared_ptr hb); +}; + class BattleExchangeEvaluator { private: std::shared_ptr cb; std::shared_ptr env; - std::map reachabilityCache; - std::array, GameConstants::BFIELD_SIZE> reachabilityMap; + mutable ReachabilityMapCache reachabilityMap; std::vector turnOrder; float negativeEffectMultiplier; int simulationTurnsCount; @@ -169,7 +185,7 @@ public: DamageCache & damageCache, std::shared_ptr hb) const; - std::vector getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const; + const battle::Units & getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const; void updateReachabilityMap(std::shared_ptr hb); ReachabilityData getExchangeUnits( From 40bff741953e58fa409e4ecb3d8959d92f8a8ef6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 18:58:46 +0000 Subject: [PATCH 05/10] Use small vector for unit list --- AI/BattleAI/AttackPossibility.cpp | 10 +++++----- AI/BattleAI/BattleExchangeVariant.cpp | 14 +++++++------- AI/BattleAI/BattleExchangeVariant.h | 12 ++++++------ AI/BattleAI/PotentialTargets.h | 2 +- Global.h | 8 ++++---- include/vcmi/spells/Caster.h | 3 ++- lib/battle/BattleStateInfoForRetreat.cpp | 2 +- lib/battle/BattleStateInfoForRetreat.h | 5 +++-- lib/battle/CBattleInfoCallback.cpp | 12 +++++------- lib/battle/CBattleInfoCallback.h | 2 +- lib/battle/CUnitState.cpp | 2 +- lib/battle/CUnitState.h | 2 +- lib/battle/IBattleInfoCallback.h | 2 +- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/CGHeroInstance.h | 2 +- lib/spells/AbilityCaster.cpp | 2 +- lib/spells/AbilityCaster.h | 2 +- lib/spells/BattleSpellMechanics.cpp | 2 +- lib/spells/BattleSpellMechanics.h | 9 +++++++-- lib/spells/BonusCaster.cpp | 2 +- lib/spells/BonusCaster.h | 2 +- lib/spells/ObstacleCasterProxy.cpp | 2 +- lib/spells/ObstacleCasterProxy.h | 2 +- lib/spells/ProxyCaster.cpp | 2 +- lib/spells/ProxyCaster.h | 2 +- test/mock/mock_battle_Unit.h | 2 +- 26 files changed, 57 insertions(+), 52 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index bc14014c9..009f21b5e 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -92,8 +92,8 @@ void DamageCache::buildDamageCache(std::shared_ptr hb, BattleS return u->isValidTarget(); }); - std::vector ourUnits; - std::vector enemyUnits; + battle::Units ourUnits; + battle::Units enemyUnits; for(auto stack : stacks) { @@ -346,9 +346,9 @@ AttackPossibility AttackPossibility::evaluate( if (!attackInfo.shooting) ap.attackerState->setPosition(hex); - std::vector defenderUnits; - std::vector retaliatedUnits = {attacker}; - std::vector affectedUnits; + battle::Units defenderUnits; + battle::Units retaliatedUnits = {attacker}; + battle::Units affectedUnits; if (attackInfo.shooting) defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition()); diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 5e71bc4a9..2bf42449d 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -471,10 +471,10 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( return result; } -std::vector BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit) const +battle::Units BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit) const { std::queue queue; - std::vector checkedStacks; + battle::Units checkedStacks; queue.push(blockerUnit); @@ -506,7 +506,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( uint8_t turn, PotentialTargets & targets, std::shared_ptr hb, - std::vector additionalUnits) const + battle::Units additionalUnits) const { ReachabilityData result; @@ -515,7 +515,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( if(!ap.attack.shooting) hexes.insert(ap.from); - std::vector allReachableUnits = additionalUnits; + battle::Units allReachableUnits = additionalUnits; for(auto hex : hexes) { @@ -636,7 +636,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange( PotentialTargets & targets, DamageCache & damageCache, std::shared_ptr hb, - std::vector additionalUnits) const + battle::Units additionalUnits) const { #if BATTLE_TRACE_LEVEL>=1 logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex); @@ -649,8 +649,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange( return BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0); } - std::vector ourStacks; - std::vector enemyStacks; + battle::Units ourStacks; + battle::Units enemyStacks; if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive()) enemyStacks.push_back(ap.attack.defender); diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index de8577d5a..38481249f 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -112,13 +112,13 @@ private: struct ReachabilityData { - std::map> units; + std::map units; // shooters which are within mellee attack and mellee units - std::vector melleeAccessible; + battle::Units melleeAccessible; // far shooters - std::vector shooters; + battle::Units shooters; std::set enemyUnitsReachingAttacker; }; @@ -158,7 +158,7 @@ private: PotentialTargets & targets, DamageCache & damageCache, std::shared_ptr hb, - std::vector additionalUnits = {}) const; + battle::Units additionalUnits = {}) const; bool canBeHitThisTurn(const AttackPossibility & ap); @@ -193,7 +193,7 @@ public: uint8_t turn, PotentialTargets & targets, std::shared_ptr hb, - std::vector additionalUnits = {}) const; + battle::Units additionalUnits = {}) const; bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position); @@ -203,7 +203,7 @@ public: DamageCache & damageCache, std::shared_ptr hb); - std::vector getAdjacentUnits(const battle::Unit * unit) const; + battle::Units getAdjacentUnits(const battle::Unit * unit) const; float getPositiveEffectMultiplier() const { return 1; } float getNegativeEffectMultiplier() const { return negativeEffectMultiplier; } diff --git a/AI/BattleAI/PotentialTargets.h b/AI/BattleAI/PotentialTargets.h index e7b622026..141c1b95a 100644 --- a/AI/BattleAI/PotentialTargets.h +++ b/AI/BattleAI/PotentialTargets.h @@ -14,7 +14,7 @@ class PotentialTargets { public: std::vector possibleAttacks; - std::vector unreachableEnemies; + battle::Units unreachableEnemies; PotentialTargets(){}; PotentialTargets( diff --git a/Global.h b/Global.h index 2856abdcc..ce4a072d9 100644 --- a/Global.h +++ b/Global.h @@ -670,15 +670,15 @@ namespace vstd return false; } - template - void removeDuplicates(std::vector &vec) + template + void removeDuplicates(Container &vec) { std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } - template - void concatenate(std::vector &dest, const std::vector &src) + template + void concatenate(Container &dest, const Container &src) { dest.reserve(dest.size() + src.size()); dest.insert(dest.end(), src.begin(), src.end()); diff --git a/include/vcmi/spells/Caster.h b/include/vcmi/spells/Caster.h index 4158c0c53..a925dd629 100644 --- a/include/vcmi/spells/Caster.h +++ b/include/vcmi/spells/Caster.h @@ -22,6 +22,7 @@ class SpellSchool; namespace battle { class Unit; + using Units = boost::container::small_vector; } namespace spells @@ -65,7 +66,7 @@ public: virtual void getCasterName(MetaString & text) const = 0; ///full default text - virtual void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const = 0; + virtual void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const = 0; virtual void spendMana(ServerCallback * server, const int32_t spellCost) const = 0; diff --git a/lib/battle/BattleStateInfoForRetreat.cpp b/lib/battle/BattleStateInfoForRetreat.cpp index d517f242a..a6f0118af 100644 --- a/lib/battle/BattleStateInfoForRetreat.cpp +++ b/lib/battle/BattleStateInfoForRetreat.cpp @@ -27,7 +27,7 @@ BattleStateInfoForRetreat::BattleStateInfoForRetreat(): { } -uint64_t getFightingStrength(const std::vector & stacks, const CGHeroInstance * hero = nullptr) +uint64_t getFightingStrength(const battle::Units & stacks, const CGHeroInstance * hero = nullptr) { uint64_t result = 0; diff --git a/lib/battle/BattleStateInfoForRetreat.h b/lib/battle/BattleStateInfoForRetreat.h index 42cd8b7a6..e128942db 100644 --- a/lib/battle/BattleStateInfoForRetreat.h +++ b/lib/battle/BattleStateInfoForRetreat.h @@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN namespace battle { class Unit; + using Units = boost::container::small_vector; } class CGHeroInstance; @@ -27,8 +28,8 @@ public: bool canSurrender; bool isLastTurnBeforeDie; BattleSide ourSide; - std::vector ourStacks; - std::vector enemyStacks; + battle::Units ourStacks; + battle::Units enemyStacks; const CGHeroInstance * ourHero; const CGHeroInstance * enemyHero; int turnsSkippedByDefense; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index c3a946c1e..12205bffe 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -383,11 +383,9 @@ battle::Units CBattleInfoCallback::battleAliveUnits(BattleSide side) const using namespace battle; -//T is battle::Unit descendant -template -const T * takeOneUnit(std::vector & allUnits, const int turn, BattleSide & sideThatLastMoved, int phase) +static const battle::Unit * takeOneUnit(battle::Units & allUnits, const int turn, BattleSide & sideThatLastMoved, int phase) { - const T * returnedUnit = nullptr; + const battle::Unit * returnedUnit = nullptr; size_t currentUnitIndex = 0; for(size_t i = 0; i < allUnits.size(); i++) @@ -1168,7 +1166,7 @@ std::pair CBattleInfoCallback::getNearestStack( std::vector stackPairs; - std::vector possible = battleGetUnitsIf([=](const battle::Unit * unit) + battle::Units possible = battleGetUnitsIf([=](const battle::Unit * unit) { return unit->isValidTarget(false) && unit != closest; }); @@ -1436,7 +1434,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: return at; } -std::vector CBattleInfoCallback::getAttackedBattleUnits( +battle::Units CBattleInfoCallback::getAttackedBattleUnits( const battle::Unit * attacker, const battle::Unit * defender, BattleHex destinationTile, @@ -1444,7 +1442,7 @@ std::vector CBattleInfoCallback::getAttackedBattleUnits( BattleHex attackerPos, BattleHex defenderPos) const { - std::vector units; + battle::Units units; RETURN_IF_NOT_BATTLE(units); if(attackerPos == BattleHex::INVALID) diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 626242a0d..1b413dca7 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -147,7 +147,7 @@ public: AttackableTiles getPotentiallyShootableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const; - std::vector getAttackedBattleUnits( + battle::Units getAttackedBattleUnits( const battle::Unit* attacker, const battle::Unit * defender, BattleHex destinationTile, diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index dfeec02b9..9f38fe9cc 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -464,7 +464,7 @@ void CUnitState::getCasterName(MetaString & text) const addNameReplacement(text, true); } -void CUnitState::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const +void CUnitState::getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const { text.appendLocalString(EMetaText::GENERAL_TXT, 565);//The %s casts %s //todo: use text 566 for single creature diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index f07ba27f3..23d67590d 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -183,7 +183,7 @@ public: PlayerColor getCasterOwner() const override; const CGHeroInstance * getHeroCaster() const override; void getCasterName(MetaString & text) const override; - void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const override; int32_t manaLimit() const override; bool ableToRetaliate() const override; diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index 546eb9a7e..680dcbd88 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -27,7 +27,7 @@ namespace battle { class IUnitInfo; class Unit; - using Units = std::vector; + using Units = boost::container::small_vector; using UnitFilter = std::function; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 472834210..7027cd9dc 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -856,7 +856,7 @@ void CGHeroInstance::getCasterName(MetaString & text) const text.replaceRawString(getNameTranslated()); } -void CGHeroInstance::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const +void CGHeroInstance::getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const { const bool singleTarget = attacked.size() == 1; const int textIndex = singleTarget ? 195 : 196; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 1f66d678b..5c9a5e723 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -309,7 +309,7 @@ public: const CGHeroInstance * getHeroCaster() const override; void getCasterName(MetaString & text) const override; - void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int spellCost) const override; void attachToBoat(CGBoat* newBoat); diff --git a/lib/spells/AbilityCaster.cpp b/lib/spells/AbilityCaster.cpp index f6365684d..8bf527979 100644 --- a/lib/spells/AbilityCaster.cpp +++ b/lib/spells/AbilityCaster.cpp @@ -49,7 +49,7 @@ int32_t AbilityCaster::getEffectLevel(const Spell * spell) const return getSpellSchoolLevel(spell); } -void AbilityCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +void AbilityCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const { //do nothing } diff --git a/lib/spells/AbilityCaster.h b/lib/spells/AbilityCaster.h index 35685c409..e5e7695e0 100644 --- a/lib/spells/AbilityCaster.h +++ b/lib/spells/AbilityCaster.h @@ -25,7 +25,7 @@ public: int32_t getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override; int32_t getEffectLevel(const Spell * spell) const override; - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int32_t spellCost) const override; private: diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 0b02d5002..fb10a9b21 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -473,7 +473,7 @@ std::set BattleSpellMechanics::collectTargets() const return result; } -void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector) +void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const battle::Units & targets, const CSelector & selector) { SetStackEffect sse; sse.battleID = battle()->getBattle()->getBattleID(); diff --git a/lib/spells/BattleSpellMechanics.h b/lib/spells/BattleSpellMechanics.h index 860a1487c..73766dbaa 100644 --- a/lib/spells/BattleSpellMechanics.h +++ b/lib/spells/BattleSpellMechanics.h @@ -18,6 +18,11 @@ VCMI_LIB_NAMESPACE_BEGIN struct BattleSpellCast; +namespace battle +{ + using Units = boost::container::small_vector; +} + namespace spells { @@ -66,14 +71,14 @@ private: std::shared_ptr effects; std::shared_ptr targetCondition; - std::vector affectedUnits; + battle::Units affectedUnits; effects::Effects::EffectsToApply effectsToApply; void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); std::set collectTargets() const; - void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); + void doRemoveEffects(ServerCallback * server, const battle::Units & targets, const CSelector & selector); BattleHexArray spellRangeInHexes(BattleHex centralHex) const; diff --git a/lib/spells/BonusCaster.cpp b/lib/spells/BonusCaster.cpp index a2d8523b1..8a2a988fb 100644 --- a/lib/spells/BonusCaster.cpp +++ b/lib/spells/BonusCaster.cpp @@ -57,7 +57,7 @@ void BonusCaster::getCasterName(MetaString & text) const } } -void BonusCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +void BonusCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const { const bool singleTarget = attacked.size() == 1; const int textIndex = singleTarget ? 195 : 196; diff --git a/lib/spells/BonusCaster.h b/lib/spells/BonusCaster.h index a8e55f276..2c1c824a6 100644 --- a/lib/spells/BonusCaster.h +++ b/lib/spells/BonusCaster.h @@ -26,7 +26,7 @@ public: virtual ~BonusCaster(); void getCasterName(MetaString & text) const override; - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int spellCost) const override; private: diff --git a/lib/spells/ObstacleCasterProxy.cpp b/lib/spells/ObstacleCasterProxy.cpp index 8909fd17e..375a29db0 100644 --- a/lib/spells/ObstacleCasterProxy.cpp +++ b/lib/spells/ObstacleCasterProxy.cpp @@ -75,7 +75,7 @@ void SilentCaster::getCasterName(MetaString & text) const logGlobal->debug("Unexpected call to SilentCaster::getCasterName"); } -void SilentCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +void SilentCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const { //do nothing } diff --git a/lib/spells/ObstacleCasterProxy.h b/lib/spells/ObstacleCasterProxy.h index 890264d5e..6de0fbd30 100644 --- a/lib/spells/ObstacleCasterProxy.h +++ b/lib/spells/ObstacleCasterProxy.h @@ -25,7 +25,7 @@ public: SilentCaster(PlayerColor owner_, const Caster * caster); void getCasterName(MetaString & text) const override; - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int spellCost) const override; PlayerColor getCasterOwner() const override; int32_t manaLimit() const override; diff --git a/lib/spells/ProxyCaster.cpp b/lib/spells/ProxyCaster.cpp index 7622392bb..043901292 100644 --- a/lib/spells/ProxyCaster.cpp +++ b/lib/spells/ProxyCaster.cpp @@ -106,7 +106,7 @@ void ProxyCaster::getCasterName(MetaString & text) const actualCaster->getCasterName(text); } -void ProxyCaster::getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const +void ProxyCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const { if(actualCaster) actualCaster->getCastDescription(spell, attacked, text); diff --git a/lib/spells/ProxyCaster.h b/lib/spells/ProxyCaster.h index 67557dc02..7ce7234f8 100644 --- a/lib/spells/ProxyCaster.h +++ b/lib/spells/ProxyCaster.h @@ -33,7 +33,7 @@ public: int64_t getEffectValue(const Spell * spell) const override; PlayerColor getCasterOwner() const override; void getCasterName(MetaString & text) const override; - void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; + void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int32_t spellCost) const override; const CGHeroInstance * getHeroCaster() const override; int32_t manaLimit() const override; diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index e7efca987..72bf1bcda 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -28,7 +28,7 @@ public: MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *)); MOCK_CONST_METHOD0(getCasterOwner, PlayerColor()); MOCK_CONST_METHOD1(getCasterName, void(MetaString &)); - MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const std::vector &, MetaString &)); + MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const battle::Units &, MetaString &)); MOCK_CONST_METHOD2(spendMana, void(ServerCallback *, const int32_t)); MOCK_CONST_METHOD0(manaLimit, int32_t()); MOCK_CONST_METHOD0(getHeroCaster, CGHeroInstance*()); From 5cff9af236f6bee8cbf530c40002fb1904e56794 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 19:45:35 +0000 Subject: [PATCH 06/10] Reduce BattleAI logging --- AI/BattleAI/AttackPossibility.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 009f21b5e..cd9823ffa 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -384,7 +384,9 @@ AttackPossibility AttackPossibility::evaluate( affectedUnits = defenderUnits; vstd::concatenate(affectedUnits, retaliatedUnits); +#if BATTLE_TRACE_LEVEL>=1 logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex); +#endif std::map> defenderStates; From 48473b18f6f5f112f34d6ae7527fa12da85527cc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Jan 2025 21:15:37 +0000 Subject: [PATCH 07/10] move checks for invincible bonus to UnitState & cache --- lib/CStack.cpp | 2 +- lib/battle/CBattleInfoCallback.cpp | 6 +++--- lib/battle/CUnitState.cpp | 5 +++++ lib/battle/CUnitState.h | 1 + lib/battle/Unit.h | 1 + lib/bonuses/BonusCache.cpp | 1 + lib/bonuses/BonusCache.h | 1 + lib/spells/BattleSpellMechanics.cpp | 4 ++-- lib/spells/CSpellHandler.cpp | 2 +- server/battles/BattleActionProcessor.cpp | 4 ++-- test/battle/CBattleInfoCallbackTest.cpp | 5 +++++ test/mock/mock_battle_Unit.h | 1 + 12 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/CStack.cpp b/lib/CStack.cpp index c788d7be3..921d1fcb3 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -298,7 +298,7 @@ BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const bat bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) { - if(defender->hasBonusOfType(BonusType::INVINCIBLE)) + if(defender->isInvincible()) return false; return !meleeAttackHexes(attacker, defender, attackerPos, defenderPos).empty(); diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 12205bffe..05779c5ef 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -675,7 +675,7 @@ bool CBattleInfoCallback::battleCanAttack(const battle::Unit * stack, const batt if (!stack || !target) return false; - if(target->hasBonusOfType(BonusType::INVINCIBLE)) + if(target->isInvincible()) return false; if(!battleMatchOwner(stack, target)) @@ -744,7 +744,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe if(!defender) return false; - if(defender->hasBonusOfType(BonusType::INVINCIBLE)) + if(defender->isInvincible()) return false; } @@ -810,7 +810,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInf if (!bai.defender->ableToRetaliate()) return ret; - if (bai.attacker->hasBonusOfType(BonusType::BLOCKS_RETALIATION) || bai.attacker->hasBonusOfType(BonusType::INVINCIBLE)) + if (bai.attacker->hasBonusOfType(BonusType::BLOCKS_RETALIATION) || bai.attacker->isInvincible()) return ret; //TODO: rewrite using boost::numeric::interval diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 9f38fe9cc..d357bed30 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -700,6 +700,11 @@ bool CUnitState::isHypnotized() const return bonusCache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); } +bool CUnitState::isInvincible() const +{ + return bonusCache.getBonusValue(UnitBonusValuesProxy::INVINCIBLE); +} + int CUnitState::getTotalAttacks(bool ranged) const { return 1 + (ranged ? diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 23d67590d..110773023 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -193,6 +193,7 @@ public: bool isValidTarget(bool allowDead = false) const override; bool isHypnotized() const override; + bool isInvincible() const override; bool isClone() const override; bool hasClone() const override; diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index 794e14b94..a072f1663 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -87,6 +87,7 @@ public: 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 isInvincible() const = 0; virtual bool isClone() const = 0; virtual bool hasClone() const = 0; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 8be61dd69..02ce3e21d 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -203,6 +203,7 @@ const UnitBonusValuesProxy::SelectorsArray * UnitBonusValuesProxy::generateSelec Selector::type()(BonusType::FORGETFULL),//FORGETFULL, Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, + Selector::type()(BonusType::INVINCIBLE),//INVINCIBLE, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) }; diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 753c54905..34a07a304 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -116,6 +116,7 @@ public: FORGETFULL, HAS_FREE_SHOOTING, STACK_HEALTH, + INVINCIBLE, CLONE_MARKER, diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index fb10a9b21..cc388afd6 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -231,7 +231,7 @@ bool BattleSpellMechanics::canBeCastAt(const Target & target, Problem & problem) if(mainTarget && mainTarget == caster) return false; // can't cast on self - if(mainTarget && mainTarget->hasBonusOfType(BonusType::INVINCIBLE) && !getSpell()->getPositiveness()) + if(mainTarget && mainTarget->isInvincible() && !getSpell()->getPositiveness()) return false; } else if(getSpell()->canCastOnlyOnSelf()) @@ -259,7 +259,7 @@ std::vector BattleSpellMechanics::getAffectedStacks(const Target for(const Destination & dest : all) { - if(dest.unitValue && !dest.unitValue->hasBonusOfType(BonusType::INVINCIBLE)) + if(dest.unitValue && !dest.unitValue->isInvincible()) { //FIXME: remove and return battle::Unit stacks.insert(battle()->battleGetStackByID(dest.unitValue->unitId(), false)); diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index f357f2c9b..943da874e 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -434,7 +434,7 @@ int64_t CSpell::adjustRawDamage(const spells::Caster * caster, const battle::Uni } //invincible - if(bearer->hasBonusOfType(BonusType::INVINCIBLE)) + if(affectedCreature->isInvincible()) ret = 0; } ret = caster->getSpellBonus(this, ret, affectedCreature); diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 1ba3a3cdc..0d951c006 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -276,7 +276,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c for (int i = 0; i < totalAttacks; ++i) { //first strike - if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) && !stack->hasBonusOfType(BonusType::INVINCIBLE)) + if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) && !stack->isInvincible()) { makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true); } @@ -303,7 +303,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c //we check retaliation twice, so if it unblocked during attack it will work only on next attack if(stack->alive() && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) - && !stack->hasBonusOfType(BonusType::INVINCIBLE) + && !stack->isInvincible() && (i == 0 && !firstStrike) && retaliation && destinationStack->ableToRetaliate()) { diff --git a/test/battle/CBattleInfoCallbackTest.cpp b/test/battle/CBattleInfoCallbackTest.cpp index 9e7a68c7b..d2efc7396 100644 --- a/test/battle/CBattleInfoCallbackTest.cpp +++ b/test/battle/CBattleInfoCallbackTest.cpp @@ -76,6 +76,11 @@ public: return hasBonusOfType(BonusType::HYPNOTIZED); } + bool isInvincible() const override + { + return hasBonusOfType(BonusType::INVINCIBLE); + } + void redirectBonusesToFake() { ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses)); diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index 72bf1bcda..ffe99b877 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -58,6 +58,7 @@ public: MOCK_CONST_METHOD1(isValidTarget, bool(bool)); MOCK_CONST_METHOD0(isHypnotized, bool()); + MOCK_CONST_METHOD0(isInvincible, bool()); MOCK_CONST_METHOD0(isClone, bool()); MOCK_CONST_METHOD0(hasClone, bool()); MOCK_CONST_METHOD0(canCast, bool()); From 3d1bf03a2544e6512e259620c2ce4a73c5297b36 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Jan 2025 11:06:00 +0000 Subject: [PATCH 08/10] Fix potential concurrent access --- AI/BattleAI/BattleEvaluator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 75f39baaa..7e2275b33 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -756,7 +756,9 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) auto updatedAttack = AttackPossibility::evaluate(updatedBai, cachedAttack.ap->from, innerCache, state); - stackActionScore = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); + BattleExchangeEvaluator innerEvaluator(scoreEvaluator); + + stackActionScore = innerEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); } for(const auto & unit : allUnits) { From 14320fb100f4d23aa9414cf515c7990ada2d96b7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Jan 2025 11:06:45 +0000 Subject: [PATCH 09/10] Fix spawned wandering monsters not correctly attached to bonus system --- lib/CCreatureSet.h | 4 +++- lib/networkPacks/NetPacksLib.cpp | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index 9e86dfe5e..e6b80d4d2 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -93,7 +93,9 @@ public: h & static_cast(*this); h & _armyObj; h & experience; - BONUS_TREE_DESERIALIZATION_FIX + + if(!h.saving) + deserializationFix(); } void serializeJson(JsonSerializeFormat & handler); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index a187f48e6..4bfc8c80e 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1503,6 +1503,11 @@ void NewObject::applyGs(CGameState *gs) gs->map->addBlockVisTiles(newObject); gs->map->calculateGuardingGreaturePositions(); + // attach newly spawned wandering monster to global bonus system node + auto newArmy = dynamic_cast(newObject); + if (newArmy) + newArmy->whatShouldBeAttached().attachTo(newArmy->whereShouldBeAttached(gs)); + logGlobal->debug("Added object id=%d; name=%s", newObject->id, newObject->getObjectName()); } From 5bfc837a8f7991f1a314f4dabb8f8284828d747f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Jan 2025 11:07:50 +0000 Subject: [PATCH 10/10] Fix regressions from battlehex PR (mostly related to towers) --- lib/battle/BattleHexArray.h | 22 ++++++++++++++-------- lib/battle/CBattleInfoCallback.cpp | 11 +++-------- lib/battle/Unit.cpp | 20 ++++++++++++++++++-- lib/spells/effects/UnitEffect.cpp | 5 +++-- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 1cc19f601..35cea1f47 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -113,11 +113,11 @@ public: } void clear() noexcept; - inline void erase(size_type index) noexcept + inline void erase(BattleHex target) noexcept { - assert(index < totalSize); - internalStorage[index] = BattleHex::INVALID; - presenceFlags[index] = 0; + assert(contains(target)); + vstd::erase(internalStorage, target); + presenceFlags[target.toInt()] = 0; } void erase(iterator first, iterator last) noexcept; inline void pop_back() noexcept @@ -160,17 +160,23 @@ public: /// get (precomputed) all possible surrounding tiles static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept { - assert(hex.isValid()); + static const BattleHexArray invalid; - return allNeighbouringTiles[hex.toInt()]; + if (hex.isValid()) + return allNeighbouringTiles[hex.toInt()]; + else + return invalid; } /// get (precomputed) only valid and available surrounding tiles static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept { - assert(hex.isValid()); + static const BattleHexArray invalid; - return neighbouringTiles[hex.toInt()]; + if (hex.isValid()) + return neighbouringTiles[hex.toInt()]; + else + return invalid; } /// get (precomputed) only valid and available surrounding tiles for double wide creatures diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 05779c5ef..68afa7d9d 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1353,14 +1353,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) { BattleHexArray hexes = destinationTile.getNeighbouringTiles(); - for(int i = 0; i < hexes.size(); i++) - { - if(hexes.at(i) == attackOriginHex) - { - hexes.erase(i); - i = 0; - } - } + if (hexes.contains(attackOriginHex)) + hexes.erase(attackOriginHex); + for(BattleHex tile : hexes) { //friendly stacks can also be damaged by Dragon Breath diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index b33eda0f4..9f3fc730c 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -134,8 +134,24 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS precomputeUnitHexes(BattleSide::DEFENDER, true), }; - int index = side == BattleSide::ATTACKER ? 0 : 2; - return precomputed[index + twoHex][assumedPos.toInt()]; + static const std::array invalidHexes = { + BattleHexArray({BattleHex( 0)}), + BattleHexArray({BattleHex(-1)}), + BattleHexArray({BattleHex(-2)}), + BattleHexArray({BattleHex(-3)}), + BattleHexArray({BattleHex(-4)}) + }; + + if (assumedPos.isValid()) + { + int index = side == BattleSide::ATTACKER ? 0 : 2; + return precomputed[index + twoHex][assumedPos.toInt()]; + } + else + { + // Towers and such + return invalidHexes.at(-assumedPos.toInt()); + } } BattleHex Unit::occupiedHex() const diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 8a3a543d1..c4dce633a 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -223,7 +223,8 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe effectTarget.emplace_back(); for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide())) - possibleHexes.erase(hex.toInt()); + if (possibleHexes.contains(hex)) + possibleHexes.erase(hex); if(possibleHexes.empty()) break; @@ -278,4 +279,4 @@ void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler) } } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END