mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
Merge branch 'master' into 'develop'
This commit is contained in:
@@ -411,6 +411,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
nullkiller->invalidatePathfinderData(); // new hero needs to look around
|
||||
}
|
||||
|
||||
void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
|
||||
@@ -929,7 +930,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
|
||||
|
||||
const CArmedInstance * armies[] = {destinationArmy, source};
|
||||
|
||||
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
|
||||
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source, myCb->getTile(source->visitablePos())->getTerrainID());
|
||||
|
||||
for(auto army : armies)
|
||||
{
|
||||
@@ -983,7 +984,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
|
||||
&& source->stacksCount() == 1
|
||||
&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
|
||||
{
|
||||
auto weakest = nullkiller->armyManager->getWeakestCreature(bestArmy);
|
||||
auto weakest = nullkiller->armyManager->getBestUnitForScout(bestArmy, myCb->getTile(source->visitablePos())->getTerrainID());
|
||||
|
||||
if(weakest->creature == targetCreature)
|
||||
{
|
||||
|
||||
@@ -774,9 +774,9 @@ bool townHasFreeTavern(const CGTownInstance * town)
|
||||
return canMoveVisitingHeroToGarrison;
|
||||
}
|
||||
|
||||
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy)
|
||||
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy, int fortLevel)
|
||||
{
|
||||
auto armyStrength = heroArmy->getArmyStrength();
|
||||
auto armyStrength = heroArmy->getArmyStrength(fortLevel);
|
||||
|
||||
if(hero && hero->commander && hero->commander->alive)
|
||||
{
|
||||
|
||||
@@ -217,7 +217,7 @@ int64_t getArtifactScoreForHero(const CGHeroInstance * hero, const CArtifactInst
|
||||
int64_t getPotentialArtifactScore(const CArtifact * art);
|
||||
bool townHasFreeTavern(const CGTownInstance * town);
|
||||
|
||||
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy);
|
||||
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy, int fortLevel = 0);
|
||||
|
||||
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start);
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/TerrainHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@@ -76,7 +78,7 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
|
||||
|
||||
uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
|
||||
{
|
||||
return howManyReinforcementsCanGet(hero, hero, source);
|
||||
return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
|
||||
}
|
||||
|
||||
std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
|
||||
@@ -111,17 +113,59 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
return resultingArmy;
|
||||
}
|
||||
|
||||
std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<SlotInfo> & army) const
|
||||
std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
|
||||
{
|
||||
auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
|
||||
uint64_t totalPower = 0;
|
||||
|
||||
for (const auto & unit : army)
|
||||
totalPower += unit.power;
|
||||
|
||||
int baseMovementCost = cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
|
||||
bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(VLC)->moveCost != baseMovementCost;
|
||||
|
||||
// arbitrary threshold - don't give scout more than specified part of total AI value of our army
|
||||
uint64_t maxUnitValue = totalPower / 100;
|
||||
|
||||
const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
|
||||
|
||||
auto fastest = boost::min_element(army, [&](const SlotInfo & left, const SlotInfo & right) -> bool
|
||||
{
|
||||
if(left.creature->getLevel() != right.creature->getLevel())
|
||||
return left.creature->getLevel() < right.creature->getLevel();
|
||||
|
||||
return left.creature->getMovementRange() > right.creature->getMovementRange();
|
||||
uint64_t leftUnitPower = left.power / left.count;
|
||||
uint64_t rightUnitPower = left.power / left.count;
|
||||
bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
|
||||
bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
|
||||
|
||||
if (leftUnitIsWeak != rightUnitIsWeak)
|
||||
return leftUnitIsWeak;
|
||||
|
||||
if (terrainHasPenalty)
|
||||
{
|
||||
auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
|
||||
auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
|
||||
|
||||
if (leftNativeTerrain != rightNativeTerrain)
|
||||
{
|
||||
if (leftNativeTerrain == armyTerrain)
|
||||
return true;
|
||||
|
||||
if (rightNativeTerrain == armyTerrain)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
|
||||
int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
|
||||
|
||||
int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
|
||||
int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
|
||||
|
||||
if (leftMovementPointsLimit != rightMovementPointsLimit)
|
||||
return leftMovementPointsLimit > rightMovementPointsLimit;
|
||||
|
||||
return leftUnitPower < rightUnitPower;
|
||||
});
|
||||
|
||||
return weakest;
|
||||
return fastest;
|
||||
}
|
||||
|
||||
class TemporaryArmy : public CArmedInstance
|
||||
@@ -134,7 +178,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
|
||||
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
|
||||
{
|
||||
auto sortedSlots = getSortedSlots(target, source);
|
||||
|
||||
@@ -218,7 +262,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
&& allowedFactions.size() == alignmentMap.size()
|
||||
&& source->needsLastStack())
|
||||
{
|
||||
auto weakest = getWeakestCreature(resultingArmy);
|
||||
auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
|
||||
|
||||
if(weakest->count == 1)
|
||||
{
|
||||
@@ -398,14 +442,14 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
return creaturesInDwellings;
|
||||
}
|
||||
|
||||
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
|
||||
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
|
||||
{
|
||||
if(source->stacksCount() == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto bestArmy = getBestArmy(armyCarrier, target, source);
|
||||
auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
|
||||
uint64_t newArmy = 0;
|
||||
uint64_t oldArmy = target->getArmyStrength();
|
||||
|
||||
|
||||
@@ -53,10 +53,11 @@ public:
|
||||
virtual ui64 howManyReinforcementsCanGet(
|
||||
const IBonusBearer * armyCarrier,
|
||||
const CCreatureSet * target,
|
||||
const CCreatureSet * source) const = 0;
|
||||
const CCreatureSet * source,
|
||||
const TerrainId & armyTerrain) const = 0;
|
||||
|
||||
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
|
||||
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const = 0;
|
||||
virtual std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const = 0;
|
||||
virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
|
||||
|
||||
@@ -97,9 +98,9 @@ public:
|
||||
uint8_t turn = 0) const override;
|
||||
|
||||
ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
|
||||
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
|
||||
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;
|
||||
|
||||
|
||||
@@ -291,6 +291,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
prerequisite.baseCreatureID = info.baseCreatureID;
|
||||
prerequisite.prerequisitesCount++;
|
||||
prerequisite.armyCost = info.armyCost;
|
||||
prerequisite.armyStrength = info.armyStrength;
|
||||
bool haveSameOrBetterFort = false;
|
||||
if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT)
|
||||
haveSameOrBetterFort = true;
|
||||
|
||||
@@ -459,6 +459,8 @@ void ObjectClusterizer::clusterizeObject(
|
||||
continue;
|
||||
}
|
||||
|
||||
float priority = 0;
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
@@ -475,7 +477,10 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
for (int prio = PriorityEvaluator::PriorityTier::BUILDINGS; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
||||
{
|
||||
priority = std::max(priority, priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), prio));
|
||||
}
|
||||
|
||||
if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
@@ -498,7 +503,10 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
for (int prio = PriorityEvaluator::PriorityTier::BUILDINGS; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
||||
{
|
||||
priority = std::max(priority, priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), prio));
|
||||
}
|
||||
|
||||
if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
@@ -57,7 +57,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
|
||||
targetHero,
|
||||
targetHero,
|
||||
&*townArmyAvailableToBuy);
|
||||
&*townArmyAvailableToBuy,
|
||||
TerrainId::NONE);
|
||||
|
||||
if(reinforcement)
|
||||
vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));
|
||||
|
||||
@@ -214,11 +214,15 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
std::vector<int> pathsToDefend;
|
||||
std::map<const CGHeroInstance *, std::vector<int>> defferedPaths;
|
||||
AIPath* closestWay = nullptr;
|
||||
|
||||
for(int i = 0; i < paths.size(); i++)
|
||||
{
|
||||
auto & path = paths[i];
|
||||
|
||||
if (!closestWay || path.movementCost() < closestWay->movementCost())
|
||||
closestWay = &path;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
|
||||
@@ -382,7 +386,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
town->getObjectName());
|
||||
#endif
|
||||
|
||||
sequence.push_back(sptr(ExecuteHeroChain(path, town)));
|
||||
ExecuteHeroChain heroChain = ExecuteHeroChain(path, town);
|
||||
|
||||
if (closestWay)
|
||||
{
|
||||
heroChain.closestWayRatio = closestWay->movementCost() / heroChain.getPath().movementCost();
|
||||
}
|
||||
|
||||
sequence.push_back(sptr(heroChain));
|
||||
composition.addNextSequence(sequence);
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
|
||||
@@ -300,7 +300,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
ai->armyManager->getBestArmy(
|
||||
path.targetHero,
|
||||
path.heroArmy,
|
||||
upgrader->getUpperArmy()));
|
||||
upgrader->getUpperArmy(),
|
||||
TerrainId::NONE));
|
||||
|
||||
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
int treasureSourcesCount = 0;
|
||||
int bestClosestThreat = UINT8_MAX;
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
@@ -118,6 +119,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
bestScore = score;
|
||||
bestHeroToHire = hero;
|
||||
bestTownToHireFrom = town;
|
||||
bestClosestThreat = closestThreat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +130,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
if (ai->cb->getHeroesInfo().size() == 0
|
||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||
|| bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0
|
||||
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
{
|
||||
|
||||
@@ -149,7 +149,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
if(!startupTown->visitingHero)
|
||||
{
|
||||
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
|
||||
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero, TerrainId::NONE) > 200)
|
||||
{
|
||||
auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos());
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
auto fortLevel = town->fortLevel();
|
||||
|
||||
if (fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||
danger = std::max(danger * 2, danger + 10000);
|
||||
danger += 10000;
|
||||
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
|
||||
danger = std::max(ui64(danger * 1.4), danger + 4000);
|
||||
danger += 4000;
|
||||
}
|
||||
|
||||
return danger;
|
||||
|
||||
@@ -446,7 +446,7 @@ void Nullkiller::makeTurn()
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
int prioOfTask = 0;
|
||||
#endif
|
||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio)
|
||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
prioOfTask = prio;
|
||||
@@ -535,7 +535,10 @@ void Nullkiller::makeTurn()
|
||||
else
|
||||
return;
|
||||
}
|
||||
hasAnySuccess = true;
|
||||
else
|
||||
{
|
||||
hasAnySuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
hasAnySuccess |= handleTrading();
|
||||
@@ -721,7 +724,7 @@ bool Nullkiller::handleTrading()
|
||||
if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive);
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName());
|
||||
#endif
|
||||
haveTraded = true;
|
||||
|
||||
@@ -66,7 +66,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai)
|
||||
isArmyUpgrade(false),
|
||||
isHero(false),
|
||||
isEnemy(false),
|
||||
explorePriority(0)
|
||||
explorePriority(0),
|
||||
powerRatio(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -609,9 +610,6 @@ float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
|
||||
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
|
||||
: 0;
|
||||
|
||||
case Obj::KEYMASTER:
|
||||
return 0.6f;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -889,7 +887,14 @@ public:
|
||||
|
||||
Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task);
|
||||
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
|
||||
if (stayAtTown.getHero() != nullptr && stayAtTown.getHero()->movementPointsRemaining() < 100)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(stayAtTown.town->mageGuildLevel() > 0)
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
|
||||
|
||||
if (evaluationContext.armyReward == 0)
|
||||
evaluationContext.isDefend = true;
|
||||
else
|
||||
@@ -1018,6 +1023,45 @@ public:
|
||||
if(heroRole == HeroRole::MAIN)
|
||||
evaluationContext.heroRole = heroRole;
|
||||
|
||||
if (hero)
|
||||
{
|
||||
// Assuming Slots() returns a collection of slots with slot.second->getCreatureID() and slot.second->getPower()
|
||||
float heroPower = 0;
|
||||
float totalPower = 0;
|
||||
|
||||
// Map to store the aggregated power of creatures by CreatureID
|
||||
std::map<int, float> totalPowerByCreatureID;
|
||||
|
||||
// Calculate hero power and total power by CreatureID
|
||||
for (auto slot : hero->Slots())
|
||||
{
|
||||
int creatureID = slot.second->getCreatureID();
|
||||
float slotPower = slot.second->getPower();
|
||||
|
||||
// Add the power of this slot to the heroPower
|
||||
heroPower += slotPower;
|
||||
|
||||
// Accumulate the total power for the specific CreatureID
|
||||
if (totalPowerByCreatureID.find(creatureID) == totalPowerByCreatureID.end())
|
||||
{
|
||||
// First time encountering this CreatureID, retrieve total creatures' power
|
||||
totalPowerByCreatureID[creatureID] = ai->armyManager->getTotalCreaturesAvailable(creatureID).power;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total power based on unique CreatureIDs
|
||||
for (const auto& entry : totalPowerByCreatureID)
|
||||
{
|
||||
totalPower += entry.second;
|
||||
}
|
||||
|
||||
// Compute the power ratio if total power is greater than zero
|
||||
if (totalPower > 0)
|
||||
{
|
||||
evaluationContext.powerRatio = heroPower / totalPower;
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
{
|
||||
evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
|
||||
@@ -1030,6 +1074,8 @@ public:
|
||||
evaluationContext.isHero = true;
|
||||
if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
|
||||
evaluationContext.isEnemy = true;
|
||||
if (target->ID == Obj::TOWN)
|
||||
evaluationContext.defenseValue = dynamic_cast<const CGTownInstance*>(target)->fortLevel();
|
||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
||||
if(evaluationContext.danger > 0)
|
||||
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
||||
@@ -1169,6 +1215,19 @@ public:
|
||||
evaluationContext.goldCost += cost;
|
||||
evaluationContext.closestWayRatio = 1;
|
||||
evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
|
||||
|
||||
bool alreadyOwn = false;
|
||||
int highestMageGuildPossible = BuildingID::MAGES_GUILD_3;
|
||||
for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo())
|
||||
{
|
||||
if (town->hasBuilt(bi.id))
|
||||
alreadyOwn = true;
|
||||
if (evaluationContext.evaluator.ai->cb->canBuildStructure(town, BuildingID::MAGES_GUILD_5) != EBuildingState::FORBIDDEN)
|
||||
highestMageGuildPossible = BuildingID::MAGES_GUILD_5;
|
||||
else if (evaluationContext.evaluator.ai->cb->canBuildStructure(town, BuildingID::MAGES_GUILD_4) != EBuildingState::FORBIDDEN)
|
||||
highestMageGuildPossible = BuildingID::MAGES_GUILD_4;
|
||||
}
|
||||
|
||||
if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
|
||||
evaluationContext.isTradeBuilding = true;
|
||||
|
||||
@@ -1183,14 +1242,19 @@ public:
|
||||
if(bi.baseCreatureID == bi.creatureID)
|
||||
{
|
||||
evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
|
||||
evaluationContext.armyReward += bi.armyStrength;
|
||||
evaluationContext.armyReward += bi.armyStrength * 1.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
|
||||
evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
|
||||
if(bi.id.IsDwelling())
|
||||
evaluationContext.armyReward += bi.armyStrength - evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
|
||||
else //This is for prerequisite-buildings
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
|
||||
if(alreadyOwn)
|
||||
evaluationContext.armyReward /= bi.buildCostWithPrerequisites.marketValue();
|
||||
}
|
||||
}
|
||||
else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
|
||||
@@ -1201,9 +1265,14 @@ public:
|
||||
else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
|
||||
{
|
||||
evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
|
||||
for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
|
||||
if (!alreadyOwn && evaluationContext.evaluator.ai->cb->canBuildStructure(buildThis.town, highestMageGuildPossible) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
evaluationContext.armyInvolvement += hero->getArmyCost();
|
||||
for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
|
||||
{
|
||||
if(hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + hero->getPrimSkillLevel(PrimarySkill::KNOWLEDGE) > hero->getPrimSkillLevel(PrimarySkill::ATTACK) + hero->getPrimSkillLevel(PrimarySkill::DEFENSE)
|
||||
&& hero->manaLimit() > 30)
|
||||
evaluationContext.armyReward += hero->getArmyCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
int sameTownBonus = 0;
|
||||
@@ -1333,18 +1402,35 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
else
|
||||
{
|
||||
float score = 0;
|
||||
const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0);
|
||||
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
||||
bool currentPositionThreatened = false;
|
||||
if (task->hero)
|
||||
{
|
||||
auto currentTileThreat = ai->dangerHitMap->getTileThreat(task->hero->visitablePos());
|
||||
if (currentTileThreat.fastestDanger.turn < 1 && currentTileThreat.fastestDanger.danger > task->hero->getTotalStrength())
|
||||
currentPositionThreatened = true;
|
||||
}
|
||||
if (priorityTier == PriorityTier::FAR_HUNTER_GATHER && currentPositionThreatened == false)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Skip FAR_HUNTER_GATHER because hero is not threatened.");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
const bool amIInDanger = ai->cb->getTownsInfo().empty();
|
||||
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio > 0 ? ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio : 1.0;
|
||||
float dangerThreshold = 1;
|
||||
dangerThreshold *= evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0;
|
||||
|
||||
bool arriveNextWeek = false;
|
||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
|
||||
arriveNextWeek = true;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d",
|
||||
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, maxWillingToLose: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, dangerThreshold: %f explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d powerRatio: %f",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
maxWillingToLose,
|
||||
(int)evaluationContext.turn,
|
||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||
@@ -1362,23 +1448,27 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
evaluationContext.conquestValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
dangerThreshold,
|
||||
evaluationContext.explorePriority,
|
||||
evaluationContext.isDefend,
|
||||
evaluationContext.isEnemy,
|
||||
arriveNextWeek);
|
||||
arriveNextWeek,
|
||||
evaluationContext.powerRatio);
|
||||
#endif
|
||||
|
||||
switch (priorityTier)
|
||||
{
|
||||
case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach
|
||||
{
|
||||
if (evaluationContext.turn > 0)
|
||||
if (evaluationContext.turn > 0 || evaluationContext.isExchange)
|
||||
return 0;
|
||||
if (evaluationContext.movementCost >= 1)
|
||||
return 0;
|
||||
if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
|
||||
return 0;
|
||||
if(evaluationContext.conquestValue > 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
@@ -1388,23 +1478,47 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
}
|
||||
case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
|
||||
{
|
||||
if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
//No point defending if we don't have defensive-structures
|
||||
if (evaluationContext.defenseValue < 2)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
if (evaluationContext.isEnemy && evaluationContext.turn > 0)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn)
|
||||
{
|
||||
const float OPTIMAL_PERCENTAGE = 0.75f; // We want army to be 75% of the threat
|
||||
float optimalStrength = evaluationContext.threat * OPTIMAL_PERCENTAGE;
|
||||
|
||||
// Calculate how far the army is from optimal strength
|
||||
float deviation = std::abs(evaluationContext.armyInvolvement - optimalStrength);
|
||||
|
||||
// Convert deviation to a percentage of the threat to normalize it
|
||||
float deviationPercentage = deviation / evaluationContext.threat;
|
||||
|
||||
// Calculate score: 1.0 is perfect, decreasing as deviation increases
|
||||
score = 1.0f / (1.0f + deviationPercentage);
|
||||
|
||||
// Apply turn penalty to still prefer earlier moves when scores are close
|
||||
score = score / (evaluationContext.turn + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PriorityTier::KILL: //Take towns / kill heroes that are further away
|
||||
//FALL_THROUGH
|
||||
case PriorityTier::FAR_KILL:
|
||||
{
|
||||
if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
|
||||
return 0;
|
||||
if (evaluationContext.turn > 0 && evaluationContext.isHero)
|
||||
return 0;
|
||||
if (arriveNextWeek && evaluationContext.isEnemy)
|
||||
return 0;
|
||||
if (evaluationContext.conquestValue > 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
@@ -1413,24 +1527,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::UPGRADE:
|
||||
{
|
||||
if (!evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::HIGH_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 1)
|
||||
return 0;
|
||||
@@ -1447,17 +1546,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
//FALL_THROUGH
|
||||
case PriorityTier::FAR_HUNTER_GATHER:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
||||
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && !evaluationContext.isDefend && priorityTier != PriorityTier::FAR_HUNTER_GATHER)
|
||||
return 0;
|
||||
if (evaluationContext.buildingCost.marketValue() > 0)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
|
||||
if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio > dangerThreshold || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority == 3)
|
||||
return 0;
|
||||
if (evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1)
|
||||
if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > dangerThreshold))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
@@ -1475,12 +1572,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
if(priorityTier == PriorityTier::FAR_HUNTER_GATHER && evaluationContext.enemyHeroDangerRatio > 0)
|
||||
score /= evaluationContext.enemyHeroDangerRatio;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PriorityTier::LOW_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 3)
|
||||
return 0;
|
||||
@@ -1495,7 +1594,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
}
|
||||
case PriorityTier::DEFEND: //Defend whatever if nothing else is to do
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
||||
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
@@ -1536,9 +1635,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
TResources needed = evaluationContext.buildingCost - resourcesAvailable;
|
||||
needed.positive();
|
||||
int turnsTo = needed.maxPurchasableCount(income);
|
||||
bool haveEverythingButGold = true;
|
||||
for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
|
||||
{
|
||||
if (i != GameResID::GOLD && resourcesAvailable[i] < evaluationContext.buildingCost[i])
|
||||
haveEverythingButGold = false;
|
||||
}
|
||||
if (turnsTo == INT_MAX)
|
||||
return 0;
|
||||
else
|
||||
if (!haveEverythingButGold)
|
||||
score /= turnsTo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ struct DLL_EXPORT EvaluationContext
|
||||
bool isHero;
|
||||
bool isEnemy;
|
||||
int explorePriority;
|
||||
float powerRatio;
|
||||
|
||||
EvaluationContext(const Nullkiller * ai);
|
||||
|
||||
@@ -114,13 +115,13 @@ public:
|
||||
INSTAKILL,
|
||||
INSTADEFEND,
|
||||
KILL,
|
||||
UPGRADE,
|
||||
HIGH_PRIO_EXPLORE,
|
||||
HUNTER_GATHER,
|
||||
LOW_PRIO_EXPLORE,
|
||||
FAR_KILL,
|
||||
DEFEND,
|
||||
FAR_HUNTER_GATHER,
|
||||
DEFEND
|
||||
MAX_PRIORITY_TIER = FAR_HUNTER_GATHER
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
@@ -583,42 +583,28 @@ public:
|
||||
|
||||
bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 randomEngine(randomDevice());
|
||||
|
||||
heroChainPass = EHeroChainPass::CHAIN;
|
||||
heroChain.clear();
|
||||
|
||||
std::vector<int3> data(committedTiles.begin(), committedTiles.end());
|
||||
|
||||
if(data.size() > 100)
|
||||
int maxConcurrency = tbb::this_task_arena::max_concurrency();
|
||||
std::vector<std::vector<CGPathNode *>> results(maxConcurrency);
|
||||
|
||||
logAi->trace("Caculating hero chain for %d items", data.size());
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
|
||||
{
|
||||
boost::mutex resultMutex;
|
||||
|
||||
std::shuffle(data.begin(), data.end(), randomEngine);
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
|
||||
{
|
||||
//auto r = blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
|
||||
|
||||
task.execute(r);
|
||||
|
||||
{
|
||||
boost::lock_guard<boost::mutex> resultLock(resultMutex);
|
||||
|
||||
task.flushResult(heroChain);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto r = tbb::blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
|
||||
|
||||
int ourThread = tbb::this_task_arena::current_thread_index();
|
||||
task.execute(r);
|
||||
task.flushResult(heroChain);
|
||||
}
|
||||
task.flushResult(results.at(ourThread));
|
||||
});
|
||||
|
||||
// FIXME: potentially non-deterministic behavior due to parallel_for
|
||||
for (const auto & result : results)
|
||||
vstd::concatenate(heroChain, result);
|
||||
|
||||
committedTiles.clear();
|
||||
|
||||
@@ -1464,9 +1450,20 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
|
||||
}
|
||||
}
|
||||
|
||||
int fortLevel = 0;
|
||||
auto visitableObjects = cb->getVisitableObjs(pos);
|
||||
for (auto obj : visitableObjects)
|
||||
{
|
||||
if (objWithID<Obj::TOWN>(obj))
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance*>(obj);
|
||||
fortLevel = town->fortLevel();
|
||||
}
|
||||
}
|
||||
|
||||
path.targetObjectArmyLoss = evaluateArmyLoss(
|
||||
path.targetHero,
|
||||
getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy),
|
||||
getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy, fortLevel),
|
||||
path.targetObjectDanger);
|
||||
|
||||
path.chainMask = node.actor->chainMask;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/pathfinder/TurnInfo.h"
|
||||
#include "Actions/BuyArmyAction.h"
|
||||
|
||||
@@ -394,7 +395,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
|
||||
{
|
||||
auto * target = new HeroExchangeArmy();
|
||||
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
|
||||
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2, ai->cb->getTile(actor->hero->visitablePos())->getTerrainID());
|
||||
|
||||
for(auto & slotInfo : bestArmy)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user