mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-13 01:20:34 +02:00
Merge pull request #5739 from IvanSavenko/battleai_fix
Fix handling of double-wide creatures by BattleAI
This commit is contained in:
@ -385,7 +385,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto turnsToReach = (distance - 1) / speed + 1;
|
auto turnsToReach = (distance - 1) / speed + 1;
|
||||||
const BattleHexArray & hexes = enemy->getSurroundingHexes();
|
const BattleHexArray & hexes = enemy->getAttackableHexes(activeStack);
|
||||||
auto enemySpeed = enemy->getMovementRange();
|
auto enemySpeed = enemy->getMovementRange();
|
||||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||||
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
||||||
@ -416,8 +416,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
logAi->trace("New high score");
|
logAi->trace("New high score");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
|
BattleHex enemyHex = hex;
|
||||||
{
|
|
||||||
while(!flying && dists.distances[enemyHex.toInt()] > speed && dists.predecessors.at(enemyHex.toInt()).isValid())
|
while(!flying && dists.distances[enemyHex.toInt()] > speed && dists.predecessors.at(enemyHex.toInt()).isValid())
|
||||||
{
|
{
|
||||||
enemyHex = dists.predecessors.at(enemyHex.toInt());
|
enemyHex = dists.predecessors.at(enemyHex.toInt());
|
||||||
@ -425,14 +424,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
if(dists.accessibility[enemyHex.toInt()] == EAccessibility::ALIVE_STACK)
|
if(dists.accessibility[enemyHex.toInt()] == EAccessibility::ALIVE_STACK)
|
||||||
{
|
{
|
||||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||||
|
assert(defenderToBypass != nullptr);
|
||||||
|
auto attackHex = dists.predecessors[enemyHex.toInt()];
|
||||||
|
|
||||||
if(defenderToBypass)
|
if(defenderToBypass &&
|
||||||
|
defenderToBypass != enemy &&
|
||||||
|
vstd::contains(defenderToBypass->getAttackableHexes(activeStack), attackHex))
|
||||||
{
|
{
|
||||||
#if BATTLE_TRACE_LEVEL >= 1
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Found target to bypass at %d", enemyHex.toInt());
|
logAi->trace("Found target to bypass at %d", enemyHex.toInt());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto attackHex = dists.predecessors[enemyHex.toInt()];
|
|
||||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||||
|
|
||||||
@ -462,8 +464,6 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.positions.insert(enemyHex);
|
result.positions.insert(enemyHex);
|
||||||
}
|
|
||||||
|
|
||||||
result.cachedAttack = attack;
|
result.cachedAttack = attack;
|
||||||
result.turnsToReach = turnsToReach;
|
result.turnsToReach = turnsToReach;
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1338,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
|
|||||||
attackOriginHex = attacker->occupiedHex(attackOriginHex);
|
attackOriginHex = attacker->occupiedHex(attackOriginHex);
|
||||||
|
|
||||||
if (!vstd::contains(defender->getSurroundingHexes(defenderPos), attackOriginHex))
|
if (!vstd::contains(defender->getSurroundingHexes(defenderPos), attackOriginHex))
|
||||||
throw std::runtime_error("!!!");
|
throw std::runtime_error("Atempt to attack from invalid position!");
|
||||||
|
|
||||||
auto attackDirection = BattleHex::mutualPosition(attackOriginHex, defenderPos);
|
auto attackDirection = BattleHex::mutualPosition(attackOriginHex, defenderPos);
|
||||||
|
|
||||||
|
@ -68,22 +68,28 @@ const BattleHexArray & Unit::getSurroundingHexes(const BattleHex & position, boo
|
|||||||
|
|
||||||
BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
|
BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
|
||||||
{
|
{
|
||||||
const BattleHexArray & defenderHexes = getHexes();
|
if (!attacker->doubleWide())
|
||||||
|
|
||||||
BattleHexArray targetableHexes;
|
|
||||||
|
|
||||||
for(const auto & defenderHex : defenderHexes)
|
|
||||||
{
|
{
|
||||||
auto hexes = battle::Unit::getHexes(defenderHex);
|
return getSurroundingHexes();
|
||||||
|
|
||||||
if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1)
|
|
||||||
hexes.pop_back();
|
|
||||||
|
|
||||||
for(const auto & hex : hexes)
|
|
||||||
targetableHexes.insert(hex.getNeighbouringTiles());
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BattleHexArray result;
|
||||||
|
|
||||||
return targetableHexes;
|
for (const auto & attackOrigin : getSurroundingHexes())
|
||||||
|
{
|
||||||
|
if (!coversPos(attacker->occupiedHex(attackOrigin)) && attackOrigin.isAvailable())
|
||||||
|
result.insert(attackOrigin);
|
||||||
|
|
||||||
|
bool isAttacker = attacker->unitSide() == BattleSide::ATTACKER;
|
||||||
|
BattleHex::EDir headDirection = isAttacker ? BattleHex::RIGHT : BattleHex::LEFT;
|
||||||
|
BattleHex headHex = attackOrigin.cloneInDirection(headDirection);
|
||||||
|
|
||||||
|
if (!coversPos(headHex) && headHex.isAvailable())
|
||||||
|
result.insert(headHex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Unit::coversPos(const BattleHex & pos) const
|
bool Unit::coversPos(const BattleHex & pos) const
|
||||||
|
@ -132,6 +132,8 @@ public:
|
|||||||
virtual std::string getDescription() const;
|
virtual std::string getDescription() const;
|
||||||
|
|
||||||
const BattleHexArray & getSurroundingHexes(const BattleHex & assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
|
const BattleHexArray & getSurroundingHexes(const BattleHex & assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
|
||||||
|
|
||||||
|
/// Returns list of hexes from which attacker can attack this unit
|
||||||
BattleHexArray getAttackableHexes(const Unit * attacker) const;
|
BattleHexArray getAttackableHexes(const Unit * attacker) const;
|
||||||
static const BattleHexArray & getSurroundingHexes(const BattleHex & position, bool twoHex, BattleSide side);
|
static const BattleHexArray & getSurroundingHexes(const BattleHex & position, bool twoHex, BattleSide side);
|
||||||
|
|
||||||
|
@ -262,6 +262,96 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///// getAttackableHexes tests
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, getAttackableHexes_SingleWideAttacker_SingleWideDefender)
|
||||||
|
{
|
||||||
|
UnitFake & attacker = addRegularMelee(60, BattleSide::ATTACKER);
|
||||||
|
UnitFake & defender = addRegularMelee(90, BattleSide::DEFENDER);
|
||||||
|
|
||||||
|
static const BattleHexArray expectedDef =
|
||||||
|
{
|
||||||
|
72,
|
||||||
|
73,
|
||||||
|
89,
|
||||||
|
91,
|
||||||
|
106,
|
||||||
|
107
|
||||||
|
};
|
||||||
|
|
||||||
|
auto attackable = defender.getAttackableHexes(&attacker);
|
||||||
|
attackable.sort([](const auto & l, const auto & r) { return l < r; });
|
||||||
|
EXPECT_EQ(expectedDef, attackable);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, getAttackableHexes_SingleWideAttacker_DoubleWideDefender)
|
||||||
|
{
|
||||||
|
UnitFake & attacker = addRegularMelee(60, BattleSide::ATTACKER);
|
||||||
|
UnitFake & defender = addDragon(90, BattleSide::DEFENDER);
|
||||||
|
|
||||||
|
static const BattleHexArray expectedDef =
|
||||||
|
{
|
||||||
|
72,
|
||||||
|
73,
|
||||||
|
74,
|
||||||
|
89,
|
||||||
|
92,
|
||||||
|
106,
|
||||||
|
107,
|
||||||
|
108
|
||||||
|
};
|
||||||
|
|
||||||
|
auto attackable = defender.getAttackableHexes(&attacker);
|
||||||
|
attackable.sort([](const auto & l, const auto & r) { return l < r; });
|
||||||
|
EXPECT_EQ(expectedDef, attackable);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, getAttackableHexes_DoubleWideAttacker_SingleWideDefender)
|
||||||
|
{
|
||||||
|
UnitFake & attacker = addDragon(60, BattleSide::ATTACKER);
|
||||||
|
UnitFake & defender = addRegularMelee(90, BattleSide::DEFENDER);
|
||||||
|
|
||||||
|
static const BattleHexArray expectedDef =
|
||||||
|
{
|
||||||
|
72,
|
||||||
|
73,
|
||||||
|
74,
|
||||||
|
89,
|
||||||
|
92,
|
||||||
|
106,
|
||||||
|
107,
|
||||||
|
108
|
||||||
|
};
|
||||||
|
|
||||||
|
auto attackable = defender.getAttackableHexes(&attacker);
|
||||||
|
attackable.sort([](const auto & l, const auto & r) { return l < r; });
|
||||||
|
EXPECT_EQ(expectedDef, attackable);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AttackableHexesTest, getAttackableHexes_DoubleWideAttacker_DoubleWideDefender)
|
||||||
|
{
|
||||||
|
UnitFake & attacker = addDragon(60, BattleSide::ATTACKER);
|
||||||
|
UnitFake & defender = addDragon(90, BattleSide::DEFENDER);
|
||||||
|
|
||||||
|
static const BattleHexArray expectedDef =
|
||||||
|
{
|
||||||
|
72,
|
||||||
|
73,
|
||||||
|
74,
|
||||||
|
75,
|
||||||
|
89,
|
||||||
|
93,
|
||||||
|
106,
|
||||||
|
107,
|
||||||
|
108,
|
||||||
|
109
|
||||||
|
};
|
||||||
|
|
||||||
|
auto attackable = defender.getAttackableHexes(&attacker);
|
||||||
|
attackable.sort([](const auto & l, const auto & r) { return l < r; });
|
||||||
|
EXPECT_EQ(expectedDef, attackable);
|
||||||
|
}
|
||||||
|
|
||||||
//// CERBERI 3-HEADED ATTACKS
|
//// CERBERI 3-HEADED ATTACKS
|
||||||
|
|
||||||
TEST_F(AttackableHexesTest, CerberiAttackerRight)
|
TEST_F(AttackableHexesTest, CerberiAttackerRight)
|
||||||
@ -276,7 +366,6 @@ TEST_F(AttackableHexesTest, CerberiAttackerRight)
|
|||||||
|
|
||||||
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
EXPECT_TRUE(vstd::contains(attacked, &defender));
|
|
||||||
EXPECT_TRUE(vstd::contains(attacked, &right));
|
EXPECT_TRUE(vstd::contains(attacked, &right));
|
||||||
EXPECT_TRUE(vstd::contains(attacked, &left));
|
EXPECT_TRUE(vstd::contains(attacked, &left));
|
||||||
}
|
}
|
||||||
@ -356,7 +445,6 @@ TEST_F(AttackableHexesTest, DragonRightRegular_RightHorithontalBreath)
|
|||||||
|
|
||||||
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
auto attacked = getAttackedUnits(attacker, defender, defender.getPosition());
|
||||||
|
|
||||||
EXPECT_TRUE(vstd::contains(attacked, &defender));
|
|
||||||
EXPECT_TRUE(vstd::contains(attacked, &next));
|
EXPECT_TRUE(vstd::contains(attacked, &next));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user