mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-13 01:20:34 +02:00
BattleAI: fix dragonbreath retaliation
This commit is contained in:
@ -268,11 +268,11 @@ AttackPossibility AttackPossibility::evaluate(
|
|||||||
std::vector<const battle::Unit *> affectedUnits;
|
std::vector<const battle::Unit *> affectedUnits;
|
||||||
|
|
||||||
if (attackInfo.shooting)
|
if (attackInfo.shooting)
|
||||||
defenderUnits = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
|
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition());
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defenderUnits = state->getAttackedBattleUnits(attacker, defHex, false, hex);
|
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, false, hex, defender->getPosition());
|
||||||
retaliatedUnits = state->getAttackedBattleUnits(defender, hex, false, defender->getPosition());
|
retaliatedUnits = state->getAttackedBattleUnits(defender, attacker, hex, false, defender->getPosition(), hex);
|
||||||
|
|
||||||
// attacker can not melle-attack itself but still can hit that place where it was before moving
|
// attacker can not melle-attack itself but still can hit that place where it was before moving
|
||||||
vstd::erase_if(defenderUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); });
|
vstd::erase_if(defenderUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); });
|
||||||
|
@ -1235,19 +1235,40 @@ ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityIn
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const
|
AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
|
||||||
|
const battle::Unit * attacker,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
BattleHex attackerPos) const
|
||||||
|
{
|
||||||
|
const auto * defender = battleGetUnitByPos(destinationTile, true);
|
||||||
|
|
||||||
|
if(!defender)
|
||||||
|
return AttackableTiles(); // can't attack thin air
|
||||||
|
|
||||||
|
return getPotentiallyAttackableHexes(
|
||||||
|
attacker,
|
||||||
|
defender,
|
||||||
|
destinationTile,
|
||||||
|
attackerPos,
|
||||||
|
defender->getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
|
||||||
|
const battle::Unit* attacker,
|
||||||
|
const battle::Unit * defender,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
BattleHex attackerPos,
|
||||||
|
BattleHex defenderPos) const
|
||||||
{
|
{
|
||||||
//does not return hex attacked directly
|
//does not return hex attacked directly
|
||||||
AttackableTiles at;
|
AttackableTiles at;
|
||||||
RETURN_IF_NOT_BATTLE(at);
|
RETURN_IF_NOT_BATTLE(at);
|
||||||
|
|
||||||
BattleHex attackOriginHex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position
|
BattleHex attackOriginHex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position
|
||||||
|
|
||||||
const auto * defender = battleGetUnitByPos(destinationTile, true);
|
defenderPos = (defenderPos != BattleHex::INVALID) ? defenderPos : defender->getPosition(); //real or hypothetical (cursor) position
|
||||||
if (!defender)
|
|
||||||
return at; // can't attack thin air
|
bool reverse = isToReverse(attacker, defender, attackerPos, defenderPos);
|
||||||
|
|
||||||
bool reverse = isToReverse(attacker, defender, attackOriginHex, destinationTile);
|
|
||||||
if(reverse && attacker->doubleWide())
|
if(reverse && attacker->doubleWide())
|
||||||
{
|
{
|
||||||
attackOriginHex = attacker->occupiedHex(attackOriginHex); //the other hex stack stands on
|
attackOriginHex = attacker->occupiedHex(attackOriginHex); //the other hex stack stands on
|
||||||
@ -1291,20 +1312,27 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle:
|
|||||||
else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH))
|
else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH))
|
||||||
{
|
{
|
||||||
auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile);
|
auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile);
|
||||||
|
|
||||||
|
if(direction == BattleHex::NONE
|
||||||
|
&& defender->doubleWide()
|
||||||
|
&& attacker->doubleWide()
|
||||||
|
&& defenderPos == destinationTile)
|
||||||
|
{
|
||||||
|
direction = BattleHex::mutualPosition(attackOriginHex, defender->occupiedHex(defenderPos));
|
||||||
|
}
|
||||||
|
|
||||||
if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation
|
if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation
|
||||||
{
|
{
|
||||||
BattleHex nextHex = destinationTile.cloneInDirection(direction, false);
|
BattleHex nextHex = destinationTile.cloneInDirection(direction, false);
|
||||||
|
|
||||||
if ( defender->doubleWide() )
|
if ( defender->doubleWide() )
|
||||||
{
|
{
|
||||||
auto secondHex = destinationTile == defender->getPosition() ?
|
auto secondHex = destinationTile == defenderPos ? defender->occupiedHex(defenderPos) : defenderPos;
|
||||||
defender->occupiedHex():
|
|
||||||
defender->getPosition();
|
|
||||||
|
|
||||||
// if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin)
|
// if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin)
|
||||||
// then dragon breath should target tile on the opposite side of targeted creature
|
// then dragon breath should target tile on the opposite side of targeted creature
|
||||||
/*if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE)
|
if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE)
|
||||||
nextHex = secondHex.cloneInDirection(direction, false);*/
|
nextHex = secondHex.cloneInDirection(direction, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextHex.isValid())
|
if (nextHex.isValid())
|
||||||
@ -1335,17 +1363,29 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::
|
|||||||
return at;
|
return at;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits(const battle::Unit* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos) const
|
std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits(
|
||||||
|
const battle::Unit * attacker,
|
||||||
|
const battle::Unit * defender,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
bool rangedAttack,
|
||||||
|
BattleHex attackerPos,
|
||||||
|
BattleHex defenderPos) const
|
||||||
{
|
{
|
||||||
std::vector<const battle::Unit*> units;
|
std::vector<const battle::Unit*> units;
|
||||||
RETURN_IF_NOT_BATTLE(units);
|
RETURN_IF_NOT_BATTLE(units);
|
||||||
|
|
||||||
|
if(attackerPos == BattleHex::INVALID)
|
||||||
|
attackerPos = attacker->getPosition();
|
||||||
|
|
||||||
|
if(defenderPos == BattleHex::INVALID)
|
||||||
|
defenderPos = defender->getPosition();
|
||||||
|
|
||||||
AttackableTiles at;
|
AttackableTiles at;
|
||||||
|
|
||||||
if (rangedAttack)
|
if (rangedAttack)
|
||||||
at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
|
at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
|
||||||
else
|
else
|
||||||
at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
|
at = getPotentiallyAttackableHexes(attacker, defender, destinationTile, attackerPos, defenderPos);
|
||||||
|
|
||||||
units = battleGetUnitsIf([=](const battle::Unit * unit)
|
units = battleGetUnitsIf([=](const battle::Unit * unit)
|
||||||
{
|
{
|
||||||
@ -1371,7 +1411,7 @@ std::set<const CStack*> CBattleInfoCallback::getAttackedCreatures(const CStack*
|
|||||||
RETURN_IF_NOT_BATTLE(attackedCres);
|
RETURN_IF_NOT_BATTLE(attackedCres);
|
||||||
|
|
||||||
AttackableTiles at;
|
AttackableTiles at;
|
||||||
|
|
||||||
if(rangedAttack)
|
if(rangedAttack)
|
||||||
at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
|
at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
|
||||||
else
|
else
|
||||||
@ -1424,15 +1464,22 @@ bool CBattleInfoCallback::isToReverse(const battle::Unit * attacker, const battl
|
|||||||
if(isHexInFront(attackerHex, defenderHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
if(isHexInFront(attackerHex, defenderHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
auto defenderOtherHex = defenderHex;
|
||||||
|
auto attackerOtherHex = defenderHex;
|
||||||
|
|
||||||
if (defender->doubleWide())
|
if (defender->doubleWide())
|
||||||
{
|
{
|
||||||
if(isHexInFront(attackerHex, defender->occupiedHex(), static_cast<BattleSide::Type>(attacker->unitSide())))
|
defenderOtherHex = battle::Unit::occupiedHex(defenderHex, true, defender->unitSide());
|
||||||
|
|
||||||
|
if(isHexInFront(attackerHex, defenderOtherHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker->doubleWide())
|
if (attacker->doubleWide())
|
||||||
{
|
{
|
||||||
if(isHexInFront(attacker->occupiedHex(), defenderHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
attackerOtherHex = battle::Unit::occupiedHex(attackerHex, true, attacker->unitSide());
|
||||||
|
|
||||||
|
if(isHexInFront(attackerOtherHex, defenderHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1440,7 +1487,7 @@ bool CBattleInfoCallback::isToReverse(const battle::Unit * attacker, const battl
|
|||||||
// but this is how H3 handles it which is important, e.g. for direction of dragon breath attacks
|
// but this is how H3 handles it which is important, e.g. for direction of dragon breath attacks
|
||||||
if (attacker->doubleWide() && defender->doubleWide())
|
if (attacker->doubleWide() && defender->doubleWide())
|
||||||
{
|
{
|
||||||
if(isHexInFront(attacker->occupiedHex(), defender->occupiedHex(), static_cast<BattleSide::Type>(attacker->unitSide())))
|
if(isHexInFront(attackerOtherHex, defenderOtherHex, static_cast<BattleSide::Type>(attacker->unitSide())))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -130,9 +130,28 @@ public:
|
|||||||
bool isInTacticRange(BattleHex dest) const;
|
bool isInTacticRange(BattleHex dest) const;
|
||||||
si8 battleGetTacticDist() const; //returns tactic distance for calling player or 0 if this player is not in tactic phase (for ALL_KNOWING actual distance for tactic side)
|
si8 battleGetTacticDist() const; //returns tactic distance for calling player or 0 if this player is not in tactic phase (for ALL_KNOWING actual distance for tactic side)
|
||||||
|
|
||||||
AttackableTiles getPotentiallyAttackableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const; //TODO: apply rotation to two-hex attacker
|
AttackableTiles getPotentiallyAttackableHexes(
|
||||||
|
const battle::Unit* attacker,
|
||||||
|
const battle::Unit* defender,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
BattleHex attackerPos,
|
||||||
|
BattleHex defenderPos) const; //TODO: apply rotation to two-hex attacker
|
||||||
|
|
||||||
|
AttackableTiles getPotentiallyAttackableHexes(
|
||||||
|
const battle::Unit * attacker,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
BattleHex attackerPos) const;
|
||||||
|
|
||||||
AttackableTiles getPotentiallyShootableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const;
|
AttackableTiles getPotentiallyShootableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const;
|
||||||
std::vector<const battle::Unit *> getAttackedBattleUnits(const battle::Unit* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks
|
|
||||||
|
std::vector<const battle::Unit *> getAttackedBattleUnits(
|
||||||
|
const battle::Unit* attacker,
|
||||||
|
const battle::Unit * defender,
|
||||||
|
BattleHex destinationTile,
|
||||||
|
bool rangedAttack,
|
||||||
|
BattleHex attackerPos = BattleHex::INVALID,
|
||||||
|
BattleHex defenderPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks
|
||||||
|
|
||||||
std::set<const CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks
|
std::set<const CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks
|
||||||
bool isToReverse(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerHex = BattleHex::INVALID, BattleHex defenderHex = BattleHex::INVALID) const; //determines if attacker standing at attackerHex should reverse in order to attack defender
|
bool isToReverse(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerHex = BattleHex::INVALID, BattleHex defenderHex = BattleHex::INVALID) const; //determines if attacker standing at attackerHex should reverse in order to attack defender
|
||||||
|
|
||||||
|
@ -40,11 +40,32 @@ public:
|
|||||||
bonusFake.addNewBonus(b);
|
bonusFake.addNewBonus(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addCreatureAbility(BonusType bonusType)
|
||||||
|
{
|
||||||
|
addNewBonus(
|
||||||
|
std::make_shared<Bonus>(
|
||||||
|
BonusDuration::PERMANENT,
|
||||||
|
bonusType,
|
||||||
|
BonusSource::CREATURE_ABILITY,
|
||||||
|
0,
|
||||||
|
CreatureID(0)));
|
||||||
|
}
|
||||||
|
|
||||||
void makeAlive()
|
void makeAlive()
|
||||||
{
|
{
|
||||||
EXPECT_CALL(*this, alive()).WillRepeatedly(Return(true));
|
EXPECT_CALL(*this, alive()).WillRepeatedly(Return(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setupPoisition(BattleHex pos)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(*this, getPosition()).WillRepeatedly(Return(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeDoubleWide()
|
||||||
|
{
|
||||||
|
EXPECT_CALL(*this, doubleWide()).WillRepeatedly(Return(true));
|
||||||
|
}
|
||||||
|
|
||||||
void makeWarMachine()
|
void makeWarMachine()
|
||||||
{
|
{
|
||||||
addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::SIEGE_WEAPON, BonusSource::CREATURE_ABILITY, 1, BonusSourceID()));
|
addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::SIEGE_WEAPON, BonusSource::CREATURE_ABILITY, 1, BonusSourceID()));
|
||||||
@ -183,6 +204,190 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AttackableHexesTest : public CBattleInfoCallbackTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnitFake & addRegularMelee(BattleHex hex, uint8_t side)
|
||||||
|
{
|
||||||
|
auto & unit = unitsFake.add(side);
|
||||||
|
|
||||||
|
unit.makeAlive();
|
||||||
|
unit.setDefaultState();
|
||||||
|
unit.setupPoisition(hex);
|
||||||
|
unit.redirectBonusesToFake();
|
||||||
|
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitFake & addDragon(BattleHex hex, uint8_t side)
|
||||||
|
{
|
||||||
|
auto & unit = addRegularMelee(hex, side);
|
||||||
|
|
||||||
|
unit.addCreatureAbility(BonusType::TWO_HEX_ATTACK_BREATH);
|
||||||
|
unit.makeDoubleWide();
|
||||||
|
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Units getAttackedUnits(UnitFake & attacker, UnitFake & defender, BattleHex defenderHex)
|
||||||
|
{
|
||||||
|
startBattle();
|
||||||
|
redirectUnitsToFake();
|
||||||
|
|
||||||
|
return subject.getAttackedBattleUnits(
|
||||||
|
&attacker, &defender,
|
||||||
|
defenderHex, false,
|
||||||
|
attacker.getPosition(),
|
||||||
|
defender.getPosition());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonRightRegular_RightHorithontalBreath)
|
||||||
|
{
|
||||||
|
// X A D #
|
||||||
|
UnitFake & attacker = addDragon(35, 0);
|
||||||
|
UnitFake & defender = addRegularMelee(36, 1);
|
||||||
|
UnitFake & next = addRegularMelee(37, 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonDragonBottomRightHead_BottomRightBreathFromHead)
|
||||||
|
{
|
||||||
|
// X A
|
||||||
|
// D X target D
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(35, 0);
|
||||||
|
UnitFake & defender = addDragon(attacker.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonDragonVerticalDownHead_VerticalDownBreathFromHead)
|
||||||
|
{
|
||||||
|
// X A
|
||||||
|
// D X target D
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(35, 0);
|
||||||
|
UnitFake & defender = addDragon(attacker.getPosition().cloneInDirection(BattleHex::BOTTOM_LEFT), 1);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonDragonVerticalDownHeadReverse_VerticalDownBreathFromHead)
|
||||||
|
{
|
||||||
|
// A X
|
||||||
|
// X D target D
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(36, 1);
|
||||||
|
UnitFake & defender = addDragon(attacker.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 0);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_LEFT), 0);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonDragonVerticalDownBack_VerticalDownBreath)
|
||||||
|
{
|
||||||
|
// X A
|
||||||
|
// D X target X
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(37, 0);
|
||||||
|
UnitFake & defender = addDragon(attacker.occupiedHex().cloneInDirection(BattleHex::BOTTOM_LEFT), 1);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.occupiedHex());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonDragonHeadBottomRight_BottomRightBreathFromHead)
|
||||||
|
{
|
||||||
|
// X A
|
||||||
|
// D X target D
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(37, 0);
|
||||||
|
UnitFake & defender = addDragon(attacker.occupiedHex().cloneInDirection(BattleHex::BOTTOM_LEFT), 1);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonVerticalDownDragonBackReverse_VerticalDownBreath)
|
||||||
|
{
|
||||||
|
// A X
|
||||||
|
// X D target X
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(36, 1);
|
||||||
|
UnitFake & defender = addDragon(attacker.occupiedHex().cloneInDirection(BattleHex::BOTTOM_RIGHT), 0);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_LEFT), 0);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.occupiedHex());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonRightBottomDragonHeadReverse_RightBottomBreathFromHeadHex)
|
||||||
|
{
|
||||||
|
// A X
|
||||||
|
// X D target D
|
||||||
|
UnitFake & attacker = addDragon(36, 1);
|
||||||
|
UnitFake & defender = addDragon(attacker.occupiedHex().cloneInDirection(BattleHex::BOTTOM_RIGHT), 0);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_LEFT), 0);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DragonLeftBottomDragonBackToBack_LeftBottomBreathFromBackHex)
|
||||||
|
{
|
||||||
|
// X A
|
||||||
|
// D X target X
|
||||||
|
// #
|
||||||
|
UnitFake & attacker = addDragon(8, 0);
|
||||||
|
UnitFake & defender = addDragon(attacker.occupiedHex().cloneInDirection(BattleHex::BOTTOM_LEFT).cloneInDirection(BattleHex::LEFT), 1);
|
||||||
|
UnitFake & next = addRegularMelee(defender.getPosition().cloneInDirection(BattleHex::BOTTOM_RIGHT), 1);
|
||||||
|
|
||||||
|
auto attacked = getAttackedUnits(attacker, defender, defender.occupiedHex());
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, DefenderPositionOverride_BreathCountsHypoteticDefenderPosition)
|
||||||
|
{
|
||||||
|
// # N
|
||||||
|
// X D target D
|
||||||
|
// A X
|
||||||
|
UnitFake & attacker = addDragon(35, 1);
|
||||||
|
UnitFake & defender = addDragon(8, 0);
|
||||||
|
UnitFake & next = addDragon(2, 0);
|
||||||
|
|
||||||
|
startBattle();
|
||||||
|
redirectUnitsToFake();
|
||||||
|
|
||||||
|
auto attacked = subject.getAttackedBattleUnits(
|
||||||
|
&attacker,
|
||||||
|
&defender,
|
||||||
|
19,
|
||||||
|
false,
|
||||||
|
attacker.getPosition(),
|
||||||
|
19);
|
||||||
|
|
||||||
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
|
}
|
||||||
|
|
||||||
class BattleFinishedTest : public CBattleInfoCallbackTest
|
class BattleFinishedTest : public CBattleInfoCallbackTest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Reference in New Issue
Block a user