Skip to content

Commit

Permalink
Fix the retaliation of archers attacked by an ally under the influenc…
Browse files Browse the repository at this point in the history
…e of Berserker or Hypnotize spells, improve the AI usage of Hypnotize spell in general
  • Loading branch information
oleg-derevenetz committed Jan 25, 2024
1 parent 9675b2a commit 29a4dff
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 166 deletions.
47 changes: 35 additions & 12 deletions src/fheroes2/ai/normal/ai_normal_battle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ namespace AI
struct MeleeAttackOutcome
{
int32_t fromIndex = -1;
double attackValue = -INT32_MAX;
double positionValue = -INT32_MAX;
double attackValue = INT32_MIN;
double positionValue = INT32_MIN;
bool canAttackImmediately = false;
};

Expand Down Expand Up @@ -394,7 +394,8 @@ namespace AI
continue;
}

stepThreatLevel += enemy->evaluateThreatForUnit( currentUnit, stepPos );
// Rough estimate: the threat assessment is performed for the current position of the unit, not its new position at this step
stepThreatLevel += enemy->evaluateThreatForUnit( currentUnit );
}
}

Expand Down Expand Up @@ -1175,10 +1176,16 @@ namespace AI
BattleTargetPair target;
int32_t bestOutcome = INT32_MIN;

for ( const int32_t cellIdx : Board::GetAdjacentEnemiesIndexes( currentUnit ) ) {
const Unit * enemy = Board::GetCell( cellIdx )->GetUnit();
for ( const Unit * enemy : enemies ) {
assert( enemy != nullptr );

const uint32_t dist = Board::GetDistance( currentUnit.GetPosition(), enemy->GetPosition() );
assert( dist > 0 );

if ( dist != 1 ) {
continue;
}

const int32_t archerMeleeDmg = [&currentUnit, enemy]() {
if ( currentUnit.Modes( SP_CURSE ) ) {
return currentUnit.CalculateMinDamage( *enemy );
Expand Down Expand Up @@ -1551,21 +1558,38 @@ namespace AI
return {};
}();

const Indexes adjacentEnemiesIndexes = Board::GetAdjacentEnemiesIndexes( *frnd );
const std::vector<const Unit *> adjacentEnemies = [&enemies, frnd]() {
std::vector<const Unit *> result;
result.reserve( enemies.size() );

for ( const Unit * enemy : enemies ) {
assert( enemy != nullptr );

const uint32_t dist = Board::GetDistance( frnd->GetPosition(), enemy->GetPosition() );
assert( dist > 0 );

if ( dist != 1 ) {
continue;
}

result.push_back( enemy );
}

return result;
}();

// If our archer is not blocked by enemy units, but the unit nevertheless cannot cover that archer, then ignore that archer
if ( bestCoverCellInfo.idx == -1 && adjacentEnemiesIndexes.empty() ) {
if ( bestCoverCellInfo.idx == -1 && adjacentEnemies.empty() ) {
continue;
}

// Either the unit can cover the friendly archer, or the archer is blocked by enemy units, which do not allow our unit to approach. As the distance to
// estimate the archer's value, we take the smallest of the distance that must be overcome to cover the archer and the distance that must be overcome to
// approach the nearest of the enemies blocking him.
const auto [eitherFrndOrAdjEnemyIsReachable, dist] = [&arena, &currentUnit, &bestCoverCellInfo, &adjacentEnemiesIndexes]() {
const auto [eitherFrndOrAdjEnemyIsReachable, dist] = [&arena, &currentUnit, &bestCoverCellInfo, &adjacentEnemies]() {
std::pair<bool, uint32_t> result{ bestCoverCellInfo.idx != -1, bestCoverCellInfo.dist };

for ( const int idx : adjacentEnemiesIndexes ) {
const Unit * enemy = Board::GetCell( idx )->GetUnit();
for ( const Unit * enemy : adjacentEnemies ) {
assert( enemy != nullptr );

const CellDistanceInfo nearestToEnemyCellInfo = findNearestCellNextToUnit( arena, currentUnit, *enemy );
Expand Down Expand Up @@ -1608,8 +1632,7 @@ namespace AI
{
MeleeAttackOutcome bestOutcome;

for ( const int idx : adjacentEnemiesIndexes ) {
const Unit * enemy = Board::GetCell( idx )->GetUnit();
for ( const Unit * enemy : adjacentEnemies ) {
assert( enemy != nullptr );

const MeleeAttackOutcome outcome = BestAttackOutcome( currentUnit, *enemy, valuesOfAttackPositions );
Expand Down
4 changes: 2 additions & 2 deletions src/fheroes2/battle/battle_action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ void Battle::Arena::BattleProcess( Unit & attacker, Unit & defender, int32_t tgt
}
}

attacker.PostAttackAction();
attacker.PostAttackAction( defender );
}

void Battle::Arena::moveUnit( Unit * unit, const int32_t dst )
Expand Down Expand Up @@ -959,7 +959,7 @@ Battle::TargetsInfo Battle::Arena::GetTargetsForDamage( const Unit & attacker, U
}
}
// lich cloud damage
else if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) && !attacker.isHandFighting() ) {
else if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) && !Unit::isHandFighting( attacker, defender ) ) {
for ( const int32_t nearbyIdx : Board::GetAroundIndexes( dst ) ) {
assert( Board::GetCell( nearbyIdx ) != nullptr );

Expand Down
51 changes: 0 additions & 51 deletions src/fheroes2/battle/battle_board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -991,57 +991,6 @@ bool Battle::Board::CanAttackTargetFromPosition( const Unit & attacker, const Un
return false;
}

Battle::Indexes Battle::Board::GetAdjacentEnemiesIndexes( const Unit & unit )
{
Indexes result;
const bool isWide = unit.isWide();
const int armyColor = unit.GetArmyColor();
result.reserve( isWide ? 8 : 6 );

const int leftmostIndex = ( isWide && !unit.isReflect() ) ? unit.GetTailIndex() : unit.GetHeadIndex();
const int x = leftmostIndex % ARENAW;
const int y = leftmostIndex / ARENAW;
const int mod = y % 2;

const auto validateAndInsert = [&result, armyColor]( const int index ) {
const Unit * vUnit = GetCell( index )->GetUnit();
if ( vUnit && armyColor != vUnit->GetArmyColor() )
result.push_back( index );
};

if ( y > 0 ) {
const int topRowIndex = ( y - 1 ) * ARENAW + x - mod;
if ( x - mod >= 0 )
validateAndInsert( topRowIndex );

if ( x < ARENAW - 1 )
validateAndInsert( topRowIndex + 1 );

if ( isWide && x < ARENAW - 2 )
validateAndInsert( topRowIndex + 2 );
}

if ( x > 0 )
validateAndInsert( leftmostIndex - 1 );

if ( x < ARENAW - ( isWide ? 2 : 1 ) )
validateAndInsert( leftmostIndex + ( isWide ? 2 : 1 ) );

if ( y < ARENAH - 1 ) {
const int bottomRowIndex = ( y + 1 ) * ARENAW + x - mod;
if ( x - mod >= 0 )
validateAndInsert( bottomRowIndex );

if ( x < ARENAW - 1 )
validateAndInsert( bottomRowIndex + 1 );

if ( isWide && x < ARENAW - 2 )
validateAndInsert( bottomRowIndex + 2 );
}

return result;
}

std::string Battle::Board::GetMoatInfo()
{
std::string msg = _( "The Moat reduces by -%{count} the defense skill of any unit and slows to half movement rate." );
Expand Down
3 changes: 0 additions & 3 deletions src/fheroes2/battle/battle_board.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,6 @@ namespace Battle
// to the given index
static bool CanAttackTargetFromPosition( const Unit & attacker, const Unit & target, const int32_t dst );

// Returns the indexes of the cells closest to the given unit occupied by enemies
static Indexes GetAdjacentEnemiesIndexes( const Unit & unit );

private:
void SetCobjObject( const int icn, const uint32_t dst );
};
Expand Down
6 changes: 3 additions & 3 deletions src/fheroes2/battle/battle_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3481,15 +3481,15 @@ void Battle::Interface::RedrawActionAttackPart1( Unit & attacker, const Unit & d
const fheroes2::Rect & pos1 = attacker.GetRectPosition();
const fheroes2::Rect & pos2 = defender.GetRectPosition();

const bool archer = attacker.isArchers() && !attacker.isHandFighting();
const bool archer = attacker.isArchers() && !Unit::isHandFighting( attacker, defender );
const bool isDoubleCell = attacker.isDoubleCellAttack() && 2 == targets.size();

// redraw luck animation
if ( attacker.Modes( LUCK_GOOD | LUCK_BAD ) ) {
RedrawActionLuck( attacker );
}

AudioManager::PlaySound( attacker.M82Attk() );
AudioManager::PlaySound( attacker.M82Attk( defender ) );

// long distance attack animation
if ( archer ) {
Expand Down Expand Up @@ -3660,7 +3660,7 @@ void Battle::Interface::RedrawActionWincesKills( const TargetsInfo & targets, Un

// If this was a Lich attack, we should render an explosion cloud over the target unit immediately after the projectile hits the target,
// along with the unit kill/wince animation.
const bool drawLichCloud = ( attacker != nullptr ) && ( defender != nullptr ) && attacker->isArchers() && !attacker->isHandFighting()
const bool drawLichCloud = ( attacker != nullptr ) && ( defender != nullptr ) && attacker->isArchers() && !Unit::isHandFighting( *attacker, *defender )
&& attacker->isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT );

// Play sound only if it is not already playing.
Expand Down
Loading

0 comments on commit 29a4dff

Please sign in to comment.