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());