mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-08 22:26:51 +02:00
Merge pull request #5021 from Xilmi/develop
Fix for AI not defending in some cases
This commit is contained in:
@ -167,14 +167,12 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
|||||||
|
|
||||||
result = evaluator.selectStackAction(stack);
|
result = evaluator.selectStackAction(stack);
|
||||||
|
|
||||||
if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
|
if(autobattlePreferences.enableSpellsUsage && evaluator.canCastSpell())
|
||||||
{
|
{
|
||||||
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
||||||
|
|
||||||
if(spelCasted)
|
if(spelCasted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
skipCastUntilNextBattle = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
||||||
@ -256,8 +254,6 @@ void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1
|
|||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
side = Side;
|
side = Side;
|
||||||
|
|
||||||
skipCastUntilNextBattle = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::print(const std::string &text) const
|
void CBattleAI::print(const std::string &text) const
|
||||||
|
@ -62,7 +62,6 @@ class CBattleAI : public CBattleGameInterface
|
|||||||
bool wasWaitingForRealize;
|
bool wasWaitingForRealize;
|
||||||
bool wasUnlockingGs;
|
bool wasUnlockingGs;
|
||||||
int movesSkippedByDefense;
|
int movesSkippedByDefense;
|
||||||
bool skipCastUntilNextBattle;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CBattleAI();
|
CBattleAI();
|
||||||
|
@ -119,6 +119,58 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<BattleHex> BattleEvaluator::getCastleHexes()
|
||||||
|
{
|
||||||
|
std::vector<BattleHex> result;
|
||||||
|
|
||||||
|
// Loop through all wall parts
|
||||||
|
|
||||||
|
std::vector<BattleHex> wallHexes;
|
||||||
|
wallHexes.push_back(50);
|
||||||
|
wallHexes.push_back(183);
|
||||||
|
wallHexes.push_back(182);
|
||||||
|
wallHexes.push_back(130);
|
||||||
|
wallHexes.push_back(78);
|
||||||
|
wallHexes.push_back(29);
|
||||||
|
wallHexes.push_back(12);
|
||||||
|
wallHexes.push_back(97);
|
||||||
|
wallHexes.push_back(45);
|
||||||
|
wallHexes.push_back(62);
|
||||||
|
wallHexes.push_back(112);
|
||||||
|
wallHexes.push_back(147);
|
||||||
|
wallHexes.push_back(165);
|
||||||
|
|
||||||
|
for (BattleHex wallHex : wallHexes) {
|
||||||
|
// Get the starting x-coordinate of the wall hex
|
||||||
|
int startX = wallHex.getX();
|
||||||
|
|
||||||
|
// Initialize current hex with the wall hex
|
||||||
|
BattleHex currentHex = wallHex;
|
||||||
|
while (currentHex.isValid()) {
|
||||||
|
// Check if the x-coordinate has wrapped (smaller than the starting x)
|
||||||
|
if (currentHex.getX() < startX) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the hex to the result
|
||||||
|
result.push_back(currentHex);
|
||||||
|
|
||||||
|
// Move to the next hex to the right
|
||||||
|
currentHex = currentHex.cloneInDirection(BattleHex::RIGHT, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleEvaluator::hasWorkingTowers() const
|
||||||
|
{
|
||||||
|
bool keepIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
|
||||||
|
bool upperIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
|
||||||
|
bool bottomIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
|
||||||
|
return keepIntact || upperIntact || bottomIntact;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||||
{
|
{
|
||||||
//TODO: faerie dragon type spell should be selected by server
|
//TODO: faerie dragon type spell should be selected by server
|
||||||
@ -161,6 +213,19 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
|
|
||||||
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
||||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
|
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||||
|
{
|
||||||
|
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||||
|
});
|
||||||
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
|
&& !stack->canShoot()
|
||||||
|
&& hasWorkingTowers()
|
||||||
|
&& !enemyMellee.empty();
|
||||||
|
std::vector<BattleHex> castleHexes = getCastleHexes();
|
||||||
|
for (auto hex : castleHexes)
|
||||||
|
{
|
||||||
|
logAi->trace("Castlehex ID: %d Y: %d X: %d", hex, hex.getY(), hex.getX());
|
||||||
|
}
|
||||||
|
|
||||||
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
||||||
{
|
{
|
||||||
@ -174,7 +239,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb, siegeDefense);
|
||||||
auto & bestAttack = evaluationResult.bestAttack;
|
auto & bestAttack = evaluationResult.bestAttack;
|
||||||
|
|
||||||
cachedAttack.ap = bestAttack;
|
cachedAttack.ap = bestAttack;
|
||||||
@ -227,15 +292,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool
|
bool isTargetOutsideFort = std::none_of(castleHexes.begin(), castleHexes.end(),
|
||||||
{
|
[&](const BattleHex& hex) {
|
||||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
return hex == bestAttack.from;
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4;
|
|
||||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
&& !bestAttack.attack.shooting
|
&& !bestAttack.attack.shooting
|
||||||
&& hb->battleGetFortifications().hasMoat
|
&& hasWorkingTowers()
|
||||||
&& !enemyMellee.empty()
|
&& !enemyMellee.empty()
|
||||||
&& isTargetOutsideFort;
|
&& isTargetOutsideFort;
|
||||||
|
|
||||||
@ -349,6 +412,28 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
|||||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||||
|
|
||||||
|
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||||
|
{
|
||||||
|
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
|
&& hasWorkingTowers()
|
||||||
|
&& !enemyMellee.empty();
|
||||||
|
|
||||||
|
if (siegeDefense)
|
||||||
|
{
|
||||||
|
vstd::erase_if(avHexes, [&](const BattleHex& hex) {
|
||||||
|
std::vector<BattleHex> castleHexes = getCastleHexes();
|
||||||
|
|
||||||
|
bool isOutsideWall = std::none_of(castleHexes.begin(), castleHexes.end(),
|
||||||
|
[&](const BattleHex& checkhex) {
|
||||||
|
return checkhex == hex;
|
||||||
|
});
|
||||||
|
return isOutsideWall;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||||
{
|
{
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
|
@ -53,6 +53,8 @@ public:
|
|||||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||||
|
static std::vector<BattleHex> getCastleHexes();
|
||||||
|
bool hasWorkingTowers() const;
|
||||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||||
void print(const std::string & text) const;
|
void print(const std::string & text) const;
|
||||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "BattleExchangeVariant.h"
|
#include "BattleExchangeVariant.h"
|
||||||
|
#include "BattleEvaluator.h"
|
||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
|
|
||||||
AttackerValue::AttackerValue()
|
AttackerValue::AttackerValue()
|
||||||
@ -213,9 +214,11 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
const battle::Unit * activeStack,
|
const battle::Unit * activeStack,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
DamageCache & damageCache,
|
DamageCache & damageCache,
|
||||||
std::shared_ptr<HypotheticBattle> hb)
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
bool siegeDefense)
|
||||||
{
|
{
|
||||||
EvaluationResult result(targets.bestAction());
|
EvaluationResult result(targets.bestAction());
|
||||||
|
std::vector<BattleHex> castleHexes = BattleEvaluator::getCastleHexes();
|
||||||
|
|
||||||
if(!activeStack->waited() && !activeStack->acquireState()->hadMorale)
|
if(!activeStack->waited() && !activeStack->acquireState()->hadMorale)
|
||||||
{
|
{
|
||||||
@ -231,6 +234,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
for(auto & ap : targets.possibleAttacks)
|
||||||
{
|
{
|
||||||
|
if (siegeDefense && std::find(castleHexes.begin(), castleHexes.end(), ap.from) == castleHexes.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
||||||
|
|
||||||
if(score > result.score)
|
if(score > result.score)
|
||||||
@ -263,6 +269,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
for(auto & ap : targets.possibleAttacks)
|
||||||
{
|
{
|
||||||
|
if (siegeDefense && std::find(castleHexes.begin(), castleHexes.end(), ap.from) == castleHexes.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||||
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
||||||
|
|
||||||
@ -350,11 +359,32 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
if(distance <= speed)
|
if(distance <= speed)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
float penaltyMultiplier = 1.0f; // Default multiplier, no penalty
|
||||||
|
float closestAllyDistance = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
for (const battle::Unit* ally : hb->battleAliveUnits()) {
|
||||||
|
if (ally == activeStack)
|
||||||
|
continue;
|
||||||
|
if (ally->unitSide() != activeStack->unitSide())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float allyDistance = dists.distToNearestNeighbour(ally, enemy);
|
||||||
|
if (allyDistance < closestAllyDistance)
|
||||||
|
{
|
||||||
|
closestAllyDistance = allyDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an ally is closer to the enemy, compute the penaltyMultiplier
|
||||||
|
if (closestAllyDistance < distance) {
|
||||||
|
penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances
|
||||||
|
}
|
||||||
|
|
||||||
auto turnsToRich = (distance - 1) / speed + 1;
|
auto turnsToRich = (distance - 1) / speed + 1;
|
||||||
auto hexes = enemy->getSurroundingHexes();
|
auto hexes = enemy->getSurroundingHexes();
|
||||||
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;
|
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
||||||
|
|
||||||
for(auto & hex : hexes)
|
for(auto & hex : hexes)
|
||||||
{
|
{
|
||||||
|
@ -159,7 +159,8 @@ public:
|
|||||||
const battle::Unit * activeStack,
|
const battle::Unit * activeStack,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
DamageCache & damageCache,
|
DamageCache & damageCache,
|
||||||
std::shared_ptr<HypotheticBattle> hb);
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
bool siegeDefense = false);
|
||||||
|
|
||||||
float evaluateExchange(
|
float evaluateExchange(
|
||||||
const AttackPossibility & ap,
|
const AttackPossibility & ap,
|
||||||
|
@ -309,6 +309,8 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
|||||||
? dynamic_cast<const CGTownInstance *>(dwelling)
|
? dynamic_cast<const CGTownInstance *>(dwelling)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
|
std::set<SlotID> alreadyDisbanded;
|
||||||
|
|
||||||
for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
|
for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
auto ci = infoFromDC(dwelling->creatures[i]);
|
auto ci = infoFromDC(dwelling->creatures[i]);
|
||||||
@ -322,18 +324,71 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
|||||||
|
|
||||||
if(!ci.count) continue;
|
if(!ci.count) continue;
|
||||||
|
|
||||||
|
// Calculate the market value of the new stack
|
||||||
|
TResources newStackValue = ci.creID.toCreature()->getFullRecruitCost() * ci.count;
|
||||||
|
|
||||||
SlotID dst = hero->getSlotFor(ci.creID);
|
SlotID dst = hero->getSlotFor(ci.creID);
|
||||||
|
|
||||||
|
// Keep track of the least valuable slot in the hero's army
|
||||||
|
SlotID leastValuableSlot;
|
||||||
|
TResources leastValuableStackValue;
|
||||||
|
leastValuableStackValue[6] = std::numeric_limits<int>::max();
|
||||||
|
bool shouldDisband = false;
|
||||||
if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
|
if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
|
||||||
{
|
{
|
||||||
if(!freeHeroSlots) //no more place for stacks
|
if(!freeHeroSlots) // No free slots; consider replacing
|
||||||
continue;
|
{
|
||||||
|
// Check for the least valuable existing stack
|
||||||
|
for (auto& slot : hero->Slots())
|
||||||
|
{
|
||||||
|
if (alreadyDisbanded.find(slot.first) != alreadyDisbanded.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(slot.second->getCreatureID() != CreatureID::NONE)
|
||||||
|
{
|
||||||
|
TResources currentStackValue = slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
|
||||||
|
|
||||||
|
if (town && slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(currentStackValue.marketValue() < leastValuableStackValue.marketValue())
|
||||||
|
{
|
||||||
|
leastValuableStackValue = currentStackValue;
|
||||||
|
leastValuableSlot = slot.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide whether to replace the least valuable stack
|
||||||
|
if(newStackValue.marketValue() <= leastValuableStackValue.marketValue())
|
||||||
|
{
|
||||||
|
continue; // Skip if the new stack isn't worth replacing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shouldDisband = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
freeHeroSlots--; //new slot will be occupied
|
freeHeroSlots--; //new slot will be occupied
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
||||||
|
|
||||||
if(!ci.count) continue;
|
int disbandMalus = 0;
|
||||||
|
|
||||||
|
if (shouldDisband)
|
||||||
|
{
|
||||||
|
disbandMalus = leastValuableStackValue / ci.creID.toCreature()->getFullRecruitCost();
|
||||||
|
alreadyDisbanded.insert(leastValuableSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.count -= disbandMalus;
|
||||||
|
|
||||||
|
if(ci.count <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
ci.level = i; //this is important for Dungeon Summoning Portal
|
ci.level = i; //this is important for Dungeon Summoning Portal
|
||||||
creaturesInDwellings.push_back(ci);
|
creaturesInDwellings.push_back(ci);
|
||||||
|
@ -505,7 +505,7 @@ void ObjectClusterizer::clusterizeObject(
|
|||||||
else if (priority <= 0)
|
else if (priority <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
bool interestingObject = path.turn() <= 2 || priority > (ai->settings->isUseFuzzy() ? 0.5f : 0);
|
||||||
|
|
||||||
if(interestingObject)
|
if(interestingObject)
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
|||||||
|
|
||||||
if(reinforcement)
|
if(reinforcement)
|
||||||
{
|
{
|
||||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5)));
|
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(reinforcement)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,6 @@ Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * ai) const
|
|||||||
for(auto town : ai->cb->getTownsInfo())
|
for(auto town : ai->cb->getTownsInfo())
|
||||||
{
|
{
|
||||||
evaluateDefence(tasks, town, ai);
|
evaluateDefence(tasks, town, ai);
|
||||||
//Let's do only one defence-task per pass since otherwise it can try to hire the same hero twice
|
|
||||||
if (!tasks.empty())
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
@ -422,6 +419,21 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
|||||||
if(hero->getTotalStrength() < threat.danger)
|
if(hero->getTotalStrength() < threat.danger)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
bool heroAlreadyHiredInOtherTown = false;
|
||||||
|
for (const auto& task : tasks)
|
||||||
|
{
|
||||||
|
if (auto recruitGoal = dynamic_cast<Goals::RecruitHero*>(task.get()))
|
||||||
|
{
|
||||||
|
if (recruitGoal->getHero() == hero)
|
||||||
|
{
|
||||||
|
heroAlreadyHiredInOtherTown = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (heroAlreadyHiredInOtherTown)
|
||||||
|
continue;
|
||||||
|
|
||||||
auto myHeroes = ai->cb->getHeroesInfo();
|
auto myHeroes = ai->cb->getHeroesInfo();
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
@ -124,6 +124,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
|||||||
{
|
{
|
||||||
if (ai->cb->getHeroesInfo().size() == 0
|
if (ai->cb->getHeroesInfo().size() == 0
|
||||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||||
|
|| bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0
|
||||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
||||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||||
{
|
{
|
||||||
|
@ -397,7 +397,12 @@ void Nullkiller::makeTurn()
|
|||||||
if(!executeTask(bestTask))
|
if(!executeTask(bestTask))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateAiState(i, true);
|
bool fastUpdate = true;
|
||||||
|
|
||||||
|
if (bestTask->getHero() != nullptr)
|
||||||
|
fastUpdate = false;
|
||||||
|
|
||||||
|
updateAiState(i, fastUpdate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1006,6 +1006,9 @@ public:
|
|||||||
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
|
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
|
||||||
const AIPath & path = chain.getPath();
|
const AIPath & path = chain.getPath();
|
||||||
|
|
||||||
|
if (vstd::isAlmostZero(path.movementCost()))
|
||||||
|
return;
|
||||||
|
|
||||||
vstd::amax(evaluationContext.danger, path.getTotalDanger());
|
vstd::amax(evaluationContext.danger, path.getTotalDanger());
|
||||||
evaluationContext.movementCost += path.movementCost();
|
evaluationContext.movementCost += path.movementCost();
|
||||||
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
||||||
@ -1019,12 +1022,20 @@ public:
|
|||||||
evaluationContext.involvesSailing = true;
|
evaluationContext.involvesSailing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float highestCostForSingleHero = 0;
|
||||||
for(auto pair : costsPerHero)
|
for(auto pair : costsPerHero)
|
||||||
{
|
{
|
||||||
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
|
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
|
||||||
|
|
||||||
evaluationContext.movementCostByRole[role] += pair.second;
|
evaluationContext.movementCostByRole[role] += pair.second;
|
||||||
|
if (pair.second > highestCostForSingleHero)
|
||||||
|
highestCostForSingleHero = pair.second;
|
||||||
}
|
}
|
||||||
|
if (highestCostForSingleHero > 1 && costsPerHero.size() > 1)
|
||||||
|
{
|
||||||
|
//Chains that involve more than 1 hero doing something for more than a turn are too expensive in my book. They often involved heroes doing nothing just standing there waiting to fulfill their part of the chain.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
evaluationContext.movementCost *= costsPerHero.size(); //further deincentivise chaining as it often involves bringing back the army afterwards
|
||||||
|
|
||||||
auto hero = task->hero;
|
auto hero = task->hero;
|
||||||
bool checkGold = evaluationContext.danger == 0;
|
bool checkGold = evaluationContext.danger == 0;
|
||||||
@ -1046,13 +1057,13 @@ public:
|
|||||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||||
if (target->ID == Obj::HERO)
|
if (target->ID == Obj::HERO)
|
||||||
evaluationContext.isHero = true;
|
evaluationContext.isHero = true;
|
||||||
if (target->getOwner() != PlayerColor::NEUTRAL && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
|
if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
|
||||||
evaluationContext.isEnemy = true;
|
evaluationContext.isEnemy = true;
|
||||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
||||||
evaluationContext.armyInvolvement += army->getArmyCost();
|
|
||||||
if(evaluationContext.danger > 0)
|
if(evaluationContext.danger > 0)
|
||||||
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
||||||
}
|
}
|
||||||
|
evaluationContext.armyInvolvement += army->getArmyCost();
|
||||||
|
|
||||||
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
||||||
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
||||||
@ -1353,17 +1364,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
||||||
|
|
||||||
bool arriveNextWeek = false;
|
bool arriveNextWeek = false;
|
||||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7)
|
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
|
||||||
arriveNextWeek = true;
|
arriveNextWeek = true;
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %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",
|
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",
|
||||||
priorityTier,
|
priorityTier,
|
||||||
task->toString(),
|
task->toString(),
|
||||||
evaluationContext.armyLossPersentage,
|
evaluationContext.armyLossPersentage,
|
||||||
(int)evaluationContext.turn,
|
(int)evaluationContext.turn,
|
||||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||||
|
evaluationContext.armyInvolvement,
|
||||||
goldRewardPerTurn,
|
goldRewardPerTurn,
|
||||||
evaluationContext.goldCost,
|
evaluationContext.goldCost,
|
||||||
evaluationContext.armyReward,
|
evaluationContext.armyReward,
|
||||||
@ -1378,7 +1390,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
evaluationContext.closestWayRatio,
|
evaluationContext.closestWayRatio,
|
||||||
evaluationContext.enemyHeroDangerRatio,
|
evaluationContext.enemyHeroDangerRatio,
|
||||||
evaluationContext.explorePriority,
|
evaluationContext.explorePriority,
|
||||||
evaluationContext.isDefend);
|
evaluationContext.isDefend,
|
||||||
|
evaluationContext.isEnemy,
|
||||||
|
arriveNextWeek);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (priorityTier)
|
switch (priorityTier)
|
||||||
@ -1387,13 +1401,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
{
|
{
|
||||||
if (evaluationContext.turn > 0)
|
if (evaluationContext.turn > 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (evaluationContext.movementCost >= 1)
|
||||||
|
return 0;
|
||||||
if(evaluationContext.conquestValue > 0)
|
if(evaluationContext.conquestValue > 0)
|
||||||
score = 1000;
|
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 > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1404,17 +1419,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
score = evaluationContext.armyInvolvement;
|
score = evaluationContext.armyInvolvement;
|
||||||
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PriorityTier::KILL: //Take towns / kill heroes that are further away
|
case PriorityTier::KILL: //Take towns / kill heroes that are further away
|
||||||
|
//FALL_THROUGH
|
||||||
|
case PriorityTier::FAR_KILL:
|
||||||
{
|
{
|
||||||
if (evaluationContext.turn > 0 && evaluationContext.isHero)
|
if (evaluationContext.turn > 0 && evaluationContext.isHero)
|
||||||
return 0;
|
return 0;
|
||||||
if (arriveNextWeek && evaluationContext.isEnemy)
|
if (arriveNextWeek && evaluationContext.isEnemy)
|
||||||
return 0;
|
return 0;
|
||||||
if (evaluationContext.conquestValue > 0)
|
if (evaluationContext.conquestValue > 0)
|
||||||
score = 1000;
|
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 > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
@ -1432,8 +1448,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1446,13 +1463,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
||||||
|
//FALL_THROUGH
|
||||||
|
case PriorityTier::FAR_HUNTER_GATHER:
|
||||||
{
|
{
|
||||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
||||||
return 0;
|
return 0;
|
||||||
@ -1468,6 +1488,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score += evaluationContext.strategicalValue * 1000;
|
score += evaluationContext.strategicalValue * 1000;
|
||||||
score += evaluationContext.goldReward;
|
score += evaluationContext.goldReward;
|
||||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||||
@ -1478,7 +1500,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
if (score > 0)
|
if (score > 0)
|
||||||
{
|
{
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
}
|
}
|
||||||
@ -1492,8 +1513,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1503,8 +1525,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
||||||
return 0;
|
return 0;
|
||||||
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
||||||
score = 1000;
|
score = evaluationContext.armyInvolvement;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
score /= (evaluationContext.turn + 1);
|
score /= (evaluationContext.turn + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1563,13 +1584,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %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, result %f",
|
logAi->trace("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, result %f",
|
||||||
priorityTier,
|
priorityTier,
|
||||||
task->toString(),
|
task->toString(),
|
||||||
evaluationContext.armyLossPersentage,
|
evaluationContext.armyLossPersentage,
|
||||||
(int)evaluationContext.turn,
|
(int)evaluationContext.turn,
|
||||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||||
|
evaluationContext.armyInvolvement,
|
||||||
goldRewardPerTurn,
|
goldRewardPerTurn,
|
||||||
evaluationContext.goldCost,
|
evaluationContext.goldCost,
|
||||||
evaluationContext.armyReward,
|
evaluationContext.armyReward,
|
||||||
|
@ -118,6 +118,8 @@ public:
|
|||||||
HIGH_PRIO_EXPLORE,
|
HIGH_PRIO_EXPLORE,
|
||||||
HUNTER_GATHER,
|
HUNTER_GATHER,
|
||||||
LOW_PRIO_EXPLORE,
|
LOW_PRIO_EXPLORE,
|
||||||
|
FAR_KILL,
|
||||||
|
FAR_HUNTER_GATHER,
|
||||||
DEFEND
|
DEFEND
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +58,36 @@ void BuyArmy::accept(AIGateway * ai)
|
|||||||
|
|
||||||
if(ci.count)
|
if(ci.count)
|
||||||
{
|
{
|
||||||
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
if (town->getUpperArmy()->stacksCount() == GameConstants::ARMY_SIZE)
|
||||||
|
{
|
||||||
|
SlotID lowestValueSlot;
|
||||||
|
int lowestValue = std::numeric_limits<int>::max();
|
||||||
|
for (auto slot : town->getUpperArmy()->Slots())
|
||||||
|
{
|
||||||
|
if (slot.second->getCreatureID() != CreatureID::NONE)
|
||||||
|
{
|
||||||
|
int currentStackMarketValue =
|
||||||
|
slot.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * slot.second->getCount();
|
||||||
|
|
||||||
|
if (slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (currentStackMarketValue < lowestValue)
|
||||||
|
{
|
||||||
|
lowestValue = currentStackMarketValue;
|
||||||
|
lowestValueSlot = slot.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowestValueSlot.validSlot())
|
||||||
|
{
|
||||||
|
cb->dismissCreature(town->getUpperArmy(), lowestValueSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (town->getUpperArmy()->stacksCount() < GameConstants::ARMY_SIZE || town->getUpperArmy()->getSlotFor(ci.creID).validSlot()) //It is possible we don't scrap despite we wanted to due to not scrapping stacks that fit our faction
|
||||||
|
{
|
||||||
|
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||||
|
}
|
||||||
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,10 +374,12 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
|||||||
for(auto & creatureToBuy : buyArmy)
|
for(auto & creatureToBuy : buyArmy)
|
||||||
{
|
{
|
||||||
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
||||||
|
if (targetSlot.validSlot())
|
||||||
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
{
|
||||||
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
||||||
target->requireBuyArmy = true;
|
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
||||||
|
target->requireBuyArmy = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user