Skip to content

Commit

Permalink
Prioritize AI castles further from enemy territory (ihhub#5154)
Browse files Browse the repository at this point in the history
  • Loading branch information
idshibanov authored Mar 23, 2022
1 parent 825b158 commit 1d57e30
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 42 deletions.
23 changes: 23 additions & 0 deletions src/fheroes2/ai/normal/ai_normal.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "ai.h"
#include "world_pathfinding.h"

#include <set>

struct KingdomCastles;

namespace Battle
{
class Units;
Expand All @@ -46,6 +50,22 @@ namespace AI
std::vector<IndexObject> validObjects;
};

struct AICastle
{
Castle * castle = nullptr;
bool underThreat = false;
int safetyFactor = 0;
int buildingValue = 0;
AICastle( Castle * inCastle, bool inThreat, int inSafety, int inValue )
: castle( inCastle )
, underThreat( inThreat )
, safetyFactor( inSafety )
, buildingValue( inValue )
{
assert( castle != nullptr );
}
};

struct BattleTargetPair
{
int cell = -1;
Expand Down Expand Up @@ -142,6 +162,9 @@ namespace AI

bool recruitHero( Castle & castle, bool buyArmy, bool underThreat );
void evaluateRegionSafety();
std::set<int> findCastlesInDanger( const KingdomCastles & castles, const std::vector<std::pair<int, const Army *>> & enemyArmies, int myColor );
std::vector<AICastle> getSortedCastleList( const KingdomCastles & castles, const std::set<int> & castlesInDanger );

double getObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const;
int getPriorityTarget( const Heroes & hero, double & maxPriority, int patrolIndex = -1, uint32_t distanceLimit = 0 );
void resetPathfinder() override;
Expand Down
4 changes: 2 additions & 2 deletions src/fheroes2/ai/normal/ai_normal_battle.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2020 *
* Copyright (C) 2020 - 2022 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -487,7 +487,7 @@ namespace AI

const int32_t reachableCell = arena.GetNearestReachableCell( currentUnit, target.cell );

DEBUG_LOG( DBG_BATTLE, DBG_INFO, "Nearest reachable cell is " << reachableCell );
DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "Nearest reachable cell is " << reachableCell );

if ( currentUnit.GetHeadIndex() != reachableCell ) {
actions.emplace_back( CommandType::MSG_BATTLE_MOVE, currentUnit.GetUID(), reachableCell );
Expand Down
105 changes: 65 additions & 40 deletions src/fheroes2/ai/normal/ai_normal_kingdom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,63 @@ namespace AI
}
}

std::vector<AICastle> Normal::getSortedCastleList( const KingdomCastles & castles, const std::set<int> & castlesInDanger )
{
std::vector<AICastle> sortedCastleList;
for ( Castle * castle : castles ) {
if ( !castle )
continue;

const int32_t castleIndex = castle->GetIndex();
const uint32_t regionID = world.GetTiles( castleIndex ).GetRegion();
sortedCastleList.emplace_back( castle, castlesInDanger.count( castleIndex ) > 0, _regions[regionID].safetyFactor, castle->getBuildingValue() );
}

std::sort( sortedCastleList.begin(), sortedCastleList.end(), []( const AICastle & left, const AICastle & right ) {
if ( !left.underThreat && !right.underThreat ) {
return left.safetyFactor > right.safetyFactor;
}
return left.buildingValue > right.buildingValue;
} );

return sortedCastleList;
}

std::set<int> Normal::findCastlesInDanger( const KingdomCastles & castles, const std::vector<std::pair<int, const Army *>> & enemyArmies, int myColor )
{
const uint32_t threatDistanceLimit = 2500; // 25 tiles, roughly how much maxed out hero can move in a turn
std::set<int> castlesInDanger;

for ( const std::pair<int, const Army *> & enemy : enemyArmies ) {
if ( enemy.second == nullptr )
continue;

const double attackerStrength = enemy.second->GetStrength();

for ( const Castle * castle : castles ) {
if ( !castle )
continue;

const int castleIndex = castle->GetIndex();
// skip precise distance check if army is too far to be a threat
if ( Maps::GetApproximateDistance( enemy.first, castleIndex ) * Maps::Ground::roadPenalty > threatDistanceLimit )
continue;

const double defenders = castle->GetArmy().GetStrength();

const double attackerThreat = attackerStrength - defenders;
if ( attackerThreat > 0 ) {
const uint32_t dist = _pathfinder.getDistance( enemy.first, castleIndex, myColor, attackerStrength );
if ( dist && dist < threatDistanceLimit ) {
// castle is under threat
castlesInDanger.insert( castleIndex );
}
}
}
}
return castlesInDanger;
}

void Normal::KingdomTurn( Kingdom & kingdom )
{
const int myColor = kingdom.GetColor();
Expand Down Expand Up @@ -282,36 +339,7 @@ namespace AI
++availableHeroCount;
}

const uint32_t threatDistanceLimit = 2500; // 25 tiles, roughly how much maxed out hero can move in a turn
std::set<int> castlesInDanger;

for ( auto enemy = enemyArmies.begin(); enemy != enemyArmies.end(); ++enemy ) {
if ( enemy->second == nullptr )
continue;

const double attackerStrength = enemy->second->GetStrength();

for ( size_t idx = 0; idx < castles.size(); ++idx ) {
const Castle * castle = castles[idx];
if ( castle ) {
const int castleIndex = castle->GetIndex();
// skip precise distance check if army is too far away to be a threat
if ( Maps::GetApproximateDistance( enemy->first, castleIndex ) * Maps::Ground::roadPenalty > threatDistanceLimit )
continue;

const double defenders = castle->GetArmy().GetStrength();

const double attackerThreat = attackerStrength - defenders;
if ( attackerThreat > 0 ) {
const uint32_t dist = _pathfinder.getDistance( enemy->first, castleIndex, myColor, attackerStrength );
if ( dist && dist < threatDistanceLimit ) {
// castle is under threat
castlesInDanger.insert( castleIndex );
}
}
}
}
}
const std::set<int> castlesInDanger = findCastlesInDanger( castles, enemyArmies, myColor );

int32_t heroLimit = world.w() / Maps::SMALL + 1;
if ( _personality == EXPLORER )
Expand All @@ -333,17 +361,15 @@ namespace AI
status.RedrawTurnProgress( 6 );

// Step 4. Buy new heroes, adjust roles, sort heroes based on priority or strength

// sort castles by value: best first
VecCastles sortedCastleList( castles );
sortedCastleList.SortByBuildingValue();
std::vector<AICastle> sortedCastleList = getSortedCastleList( castles, castlesInDanger );

if ( availableHeroCount < heroLimit ) {
Castle * recruitmentCastle = nullptr;
double bestArmyAvailable = -1.0;

// search for best castle to recruit hero from
for ( Castle * castle : sortedCastleList ) {
for ( const AICastle & entry : sortedCastleList ) {
Castle * castle = entry.castle;
if ( castle && castle->isCastle() ) {
const Heroes * hero = castle->GetHeroes().Guest();
const int mapIndex = castle->GetIndex();
Expand Down Expand Up @@ -393,14 +419,13 @@ namespace AI
if ( castles.size() != sortedCastleList.size() ) {
evaluateRegionSafety();

sortedCastleList = castles;
sortedCastleList.SortByBuildingValue();
sortedCastleList = getSortedCastleList( castles, castlesInDanger );
}

// Step 6. Castle development according to kingdom budget
for ( Castle * castle : sortedCastleList ) {
if ( castle != nullptr ) {
CastleTurn( *castle, castlesInDanger.find( castle->GetIndex() ) != castlesInDanger.end() );
for ( const AICastle & entry : sortedCastleList ) {
if ( entry.castle != nullptr ) {
CastleTurn( *entry.castle, entry.underThreat );
}
}
}
Expand Down

0 comments on commit 1d57e30

Please sign in to comment.