mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Merge pull request #1678 from vcmi/nkai-fix-build
NKAI: more fixes to defense and pandora
This commit is contained in:
@@ -596,9 +596,18 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
|
|
||||||
size_t ourUnits = 0;
|
size_t ourUnits = 0;
|
||||||
|
|
||||||
for(auto unit : all)
|
std::set<uint32_t> unitIds;
|
||||||
|
|
||||||
|
state.battleGetUnitsIf([&](const battle::Unit * u)->bool
|
||||||
|
{
|
||||||
|
if(!u->isGhost() && !u->isTurret())
|
||||||
|
unitIds.insert(u->unitId());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(auto unitId : unitIds)
|
||||||
{
|
{
|
||||||
auto unitId = unit->unitId();
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
auto localUnit = state.battleGetUnitByID(unitId);
|
||||||
|
|
||||||
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
||||||
@@ -620,9 +629,8 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
{
|
{
|
||||||
int64_t totalGain = 0;
|
int64_t totalGain = 0;
|
||||||
|
|
||||||
for(auto unit : all)
|
for(auto unitId : unitIds)
|
||||||
{
|
{
|
||||||
auto unitId = unit->unitId();
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
auto localUnit = state.battleGetUnitByID(unitId);
|
||||||
|
|
||||||
auto newValue = getValOr(newValueOfStack, unitId, 0);
|
auto newValue = getValOr(newValueOfStack, unitId, 0);
|
||||||
|
@@ -70,7 +70,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
|||||||
auto turn = path.turn();
|
auto turn = path.turn();
|
||||||
auto & node = hitMap[pos.x][pos.y][pos.z];
|
auto & node = hitMap[pos.x][pos.y][pos.z];
|
||||||
|
|
||||||
if(tileDanger > node.maximumDanger.danger
|
if(tileDanger / (turn / 3 + 1) > node.maximumDanger.danger / (node.maximumDanger.turn / 3 + 1)
|
||||||
|| (tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn))
|
|| (tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn))
|
||||||
{
|
{
|
||||||
node.maximumDanger.danger = tileDanger;
|
node.maximumDanger.danger = tileDanger;
|
||||||
|
@@ -30,7 +30,7 @@ namespace NKAI
|
|||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
extern boost::thread_specific_ptr<CCallback> cb;
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
extern boost::thread_specific_ptr<AIGateway> ai;
|
||||||
|
|
||||||
const double TREAT_IGNORE_RATIO = 0.5;
|
const float TREAT_IGNORE_RATIO = 2;
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
tasks.push_back(Goals::sptr(composition));
|
tasks.push_back(Goals::sptr(composition));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool treatIsWeak = path.getHeroStrength() / treat.danger > TREAT_IGNORE_RATIO;
|
bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
|
||||||
bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
|
bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
|
||||||
|
|
||||||
if(treatIsWeak && !needToSaveGrowth)
|
if(treatIsWeak && !needToSaveGrowth)
|
||||||
@@ -244,7 +244,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
|
if(path.targetHero == town->visitingHero.get() && path.exchangeCount == 1)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Put %s to garrison of town %s",
|
logAi->trace("Put %s to garrison of town %s",
|
||||||
@@ -266,6 +266,24 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// main without army and visiting scout with army, very specific case
|
||||||
|
if(town->visitingHero && town->getUpperArmy()->stacksCount() == 0
|
||||||
|
&& path.targetHero != town->visitingHero.get() && path.exchangeCount == 1 && path.turn() == 0
|
||||||
|
&& ai->nullkiller->heroManager->evaluateHero(path.targetHero) > ai->nullkiller->heroManager->evaluateHero(town->visitingHero.get())
|
||||||
|
&& 10 * path.targetHero->getTotalStrength() < town->visitingHero->getTotalStrength())
|
||||||
|
{
|
||||||
|
path.heroArmy = town->visitingHero.get();
|
||||||
|
|
||||||
|
tasks.push_back(
|
||||||
|
Goals::sptr(Composition()
|
||||||
|
.addNext(DefendTown(town, treat, path))
|
||||||
|
.addNext(ExchangeSwapTownHeroes(town, town->visitingHero.get()))
|
||||||
|
.addNext(ExecuteHeroChain(path, town))
|
||||||
|
.addNext(ExchangeSwapTownHeroes(town, path.targetHero, HeroLockedReason::DEFENCE))));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(treat.turn == 0 || (path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger))
|
if(treat.turn == 0 || (path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger))
|
||||||
{
|
{
|
||||||
if(ai->nullkiller->arePathHeroesLocked(path))
|
if(ai->nullkiller->arePathHeroesLocked(path))
|
||||||
|
@@ -65,6 +65,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
{
|
{
|
||||||
Goals::TGoalVec tasks;
|
Goals::TGoalVec tasks;
|
||||||
const int3 pos = hero->visitablePos();
|
const int3 pos = hero->visitablePos();
|
||||||
|
auto targetHeroScore = ai->nullkiller->heroManager->evaluateHero(hero);
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
||||||
@@ -113,7 +114,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
float armyValue = (float)heroExchange.getReinforcementArmyStrength() / hero->getArmyStrength();
|
float armyValue = (float)heroExchange.getReinforcementArmyStrength() / hero->getArmyStrength();
|
||||||
|
|
||||||
// avoid transferring very small amount of army
|
// avoid transferring very small amount of army
|
||||||
if(armyValue < 0.1f)
|
if(armyValue < 0.1f && armyValue < 20000)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Army value is too small.");
|
logAi->trace("Army value is too small.");
|
||||||
@@ -122,8 +123,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
}
|
}
|
||||||
|
|
||||||
// avoid trying to move bigger army to the weaker one.
|
// avoid trying to move bigger army to the weaker one.
|
||||||
if(armyValue > 1)
|
|
||||||
{
|
|
||||||
bool hasOtherMainInPath = false;
|
bool hasOtherMainInPath = false;
|
||||||
|
|
||||||
for(auto node : path.nodes)
|
for(auto node : path.nodes)
|
||||||
@@ -133,12 +132,17 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
|
auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
|
||||||
|
|
||||||
if(heroRole == HeroRole::MAIN)
|
if(heroRole == HeroRole::MAIN)
|
||||||
|
{
|
||||||
|
auto score = ai->nullkiller->heroManager->evaluateHero(node.targetHero);
|
||||||
|
|
||||||
|
if(score >= targetHeroScore)
|
||||||
{
|
{
|
||||||
hasOtherMainInPath = true;
|
hasOtherMainInPath = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(hasOtherMainInPath)
|
if(hasOtherMainInPath)
|
||||||
{
|
{
|
||||||
@@ -147,7 +151,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto danger = path.getTotalDanger();
|
auto danger = path.getTotalDanger();
|
||||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||||
@@ -180,7 +183,17 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Action is blocked. Considering decomposition.");
|
logAi->trace("Action is blocked. Considering decomposition.");
|
||||||
#endif
|
#endif
|
||||||
composition.addNext(blockedAction->decompose(path.targetHero));
|
auto subGoal = blockedAction->decompose(path.targetHero);
|
||||||
|
|
||||||
|
if(subGoal->invalid())
|
||||||
|
{
|
||||||
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Path is invalid. Skipping");
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
composition.addNext(subGoal);
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.push_back(sptr(composition));
|
tasks.push_back(sptr(composition));
|
||||||
@@ -261,7 +274,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
|
|
||||||
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
|
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
|
||||||
|
|
||||||
if(armyValue < 0.25f || upgrade.upgradeValue < 300) // avoid small upgrades
|
if((armyValue < 0.1f && armyValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
|
logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
|
||||||
|
@@ -130,8 +130,6 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
|||||||
|
|
||||||
return danger;
|
return danger;
|
||||||
}
|
}
|
||||||
case Obj::PANDORAS_BOX:
|
|
||||||
return 10000; //Who knows what awaits us there
|
|
||||||
|
|
||||||
case Obj::ARTIFACT:
|
case Obj::ARTIFACT:
|
||||||
case Obj::RESOURCE:
|
case Obj::RESOURCE:
|
||||||
@@ -148,6 +146,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
|||||||
case Obj::CREATURE_GENERATOR4:
|
case Obj::CREATURE_GENERATOR4:
|
||||||
case Obj::MINE:
|
case Obj::MINE:
|
||||||
case Obj::ABANDONED_MINE:
|
case Obj::ABANDONED_MINE:
|
||||||
|
case Obj::PANDORAS_BOX:
|
||||||
{
|
{
|
||||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||||
return a->getArmyStrength();
|
return a->getArmyStrength();
|
||||||
|
@@ -299,6 +299,7 @@ void Nullkiller::makeTurn()
|
|||||||
|
|
||||||
void Nullkiller::executeTask(Goals::TTask task)
|
void Nullkiller::executeTask(Goals::TTask task)
|
||||||
{
|
{
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
std::string taskDescr = task->toString();
|
std::string taskDescr = task->toString();
|
||||||
|
|
||||||
boost::this_thread::interruption_point();
|
boost::this_thread::interruption_point();
|
||||||
@@ -307,10 +308,11 @@ void Nullkiller::executeTask(Goals::TTask task)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
task->accept(ai.get());
|
task->accept(ai.get());
|
||||||
|
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
|
||||||
}
|
}
|
||||||
catch(goalFulfilledException &)
|
catch(goalFulfilledException &)
|
||||||
{
|
{
|
||||||
logAi->trace("Task %s completed", task->toString());
|
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
|
||||||
}
|
}
|
||||||
catch(cannotFulfillGoalException & e)
|
catch(cannotFulfillGoalException & e)
|
||||||
{
|
{
|
||||||
|
@@ -591,7 +591,7 @@ public:
|
|||||||
uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
|
uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
|
||||||
|
|
||||||
evaluationContext.armyReward += upgradeValue;
|
evaluationContext.armyReward += upgradeValue;
|
||||||
evaluationContext.strategicalValue += upgradeValue / armyUpgrade.hero->getTotalStrength();
|
evaluationContext.strategicalValue += upgradeValue / (float)armyUpgrade.hero->getArmyStrength();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -627,7 +627,7 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto creature = creatureInfo.second.back().toCreature();
|
auto creature = creatureInfo.second.back().toCreature();
|
||||||
result += creature->AIValue * town->getGrowthInfo(creature->level).totalGrowth();
|
result += creature->AIValue * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -648,6 +648,9 @@ public:
|
|||||||
|
|
||||||
auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
|
auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
|
||||||
|
|
||||||
|
if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
|
||||||
|
strategicalValue = 1;
|
||||||
|
|
||||||
float multiplier = 1;
|
float multiplier = 1;
|
||||||
|
|
||||||
if(treat.turn < defendTown.getTurn())
|
if(treat.turn < defendTown.getTurn())
|
||||||
@@ -781,9 +784,11 @@ public:
|
|||||||
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
||||||
{
|
{
|
||||||
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
||||||
|
auto mpLeft = garrisonHero->movement / (float)garrisonHero->maxMovePoints(true);
|
||||||
|
|
||||||
evaluationContext.movementCost += garrisonHero->movement;
|
evaluationContext.movementCost += mpLeft;
|
||||||
evaluationContext.movementCostByRole[defenderRole] += garrisonHero->movement;
|
evaluationContext.movementCostByRole[defenderRole] += mpLeft;
|
||||||
|
evaluationContext.heroRole = defenderRole;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -191,7 +191,10 @@ RuleBlock: gold reward
|
|||||||
rule: if armyReward is LOW and heroRole is MAIN and danger is NONE and mainTurnDistance is LOWEST then Value is HIGH
|
rule: if armyReward is LOW and heroRole is MAIN and danger is NONE and mainTurnDistance is LOWEST then Value is HIGH
|
||||||
rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
|
rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
|
||||||
rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is BITHIGH
|
rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is BITHIGH
|
||||||
|
rule: if skillReward is MEDIUM and heroRole is MAIN and rewardType is MIXED and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5
|
||||||
rule: if skillReward is HIGH and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH
|
rule: if skillReward is HIGH and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH
|
||||||
|
rule: if skillReward is MEDIUM and heroRole is SCOUT then Value is LOWEST
|
||||||
|
rule: if skillReward is HIGH and heroRole is SCOUT then Value is LOWEST
|
||||||
rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
|
rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
|
||||||
rule: if strategicalValue is LOWEST and heroRole is MAIN and armyLoss is LOW then Value is LOW
|
rule: if strategicalValue is LOWEST and heroRole is MAIN and armyLoss is LOW then Value is LOW
|
||||||
rule: if strategicalValue is LOW and heroRole is SCOUT and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5
|
rule: if strategicalValue is LOW and heroRole is SCOUT and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5
|
||||||
|
Reference in New Issue
Block a user