1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Nullkiller: stabilisation and fixes

This commit is contained in:
Andrii Danylchenko 2021-05-15 22:02:57 +03:00 committed by Andrii Danylchenko
parent bcf8db3d05
commit e3c87fb58d
7 changed files with 111 additions and 39 deletions

View File

@ -39,14 +39,14 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
if(heroes.size())
{
auto mainHero = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t
auto mainArmy = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t
{
return hero->getFightingStrength();
return hero->getTotalStrength();
});
for(auto town : cb->getTownsInfo())
{
const CGHeroInstance * targetHero = *mainHero;
const CGHeroInstance * targetHero = *mainArmy;
/*if(town->visitingHero)
{
@ -62,6 +62,9 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
auto reinforcement = ai->ah->howManyReinforcementsCanBuy(targetHero, town);
if(reinforcement)
reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
if(reinforcement)
{
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5)));

View File

@ -79,6 +79,35 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town)
return shortestPath.targetHero;
}
bool needToRecruitHero(const CGTownInstance * startupTown)
{
if(!ai->canRecruitAnyHero(startupTown))
return false;
if(!startupTown->garrisonHero && !startupTown->visitingHero)
return false;
auto heroToCheck = startupTown->garrisonHero ? startupTown->garrisonHero.get() : startupTown->visitingHero.get();
auto paths = cb->getPathsInfo(heroToCheck);
for(auto obj : ai->visitableObjs)
{
if(obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD
|| obj->ID == Obj::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE
|| obj->ID == Obj::WATER_WHEEL)
{
auto path = paths->getPathInfo(obj->visitablePos());
if(path->accessible == CGPathNode::BLOCKVIS && path->turns != 255)
{
return true;
}
}
}
return false;
}
Goals::TGoalVec StartupBehavior::getTasks()
{
Goals::TGoalVec tasks;
@ -88,8 +117,8 @@ Goals::TGoalVec StartupBehavior::getTasks()
return tasks;
const CGTownInstance * startupTown = towns.front();
bool canRecruitHero = ai->canRecruitAnyHero(startupTown);
bool canRecruitHero = needToRecruitHero(startupTown);
if(towns.size() > 1)
{
startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float
@ -131,14 +160,17 @@ Goals::TGoalVec StartupBehavior::getTasks()
auto garrisonHero = startupTown->garrisonHero.get();
auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero);
if(garrisonHeroScore > visitingHeroScore)
if(visitingHeroScore > garrisonHeroScore
|| ai->ah->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->ah->getHeroRole(visitingHero) == HeroRole::MAIN)
{
if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
{
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
}
}
else
else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
{
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
}
}
else if(canRecruitHero)
@ -157,7 +189,7 @@ Goals::TGoalVec StartupBehavior::getTasks()
{
for(const CGTownInstance * town : towns)
{
if(town->garrisonHero)
if(town->garrisonHero && town->garrisonHero->movement)
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f)));
}
}

View File

@ -99,7 +99,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
return result;
}
uint64_t getDwellingScore(const CGObjectInstance * target)
uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
{
auto dwelling = dynamic_cast<const CGDwelling *>(target);
uint64_t score = 0;
@ -109,17 +109,17 @@ uint64_t getDwellingScore(const CGObjectInstance * target)
if(creLevel.first && creLevel.second.size())
{
auto creature = creLevel.second.back().toCreature();
if(cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
{
score += creature->AIValue * creLevel.first;
}
if(checkGold && !cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
continue;
score += creature->AIValue * creLevel.first;
}
}
return score;
}
uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, bool checkGold)
{
if(!target)
return 0;
@ -127,17 +127,20 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
switch(target->ID)
{
case Obj::TOWN:
return target->tempOwner == PlayerColor::NEUTRAL ? 5000 : 1000;
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
case Obj::CREATURE_BANK:
return getCreatureBankArmyReward(target, hero);
case Obj::CREATURE_GENERATOR1:
return getDwellingScore(target);
case Obj::CREATURE_GENERATOR2:
case Obj::CREATURE_GENERATOR3:
case Obj::CREATURE_GENERATOR4:
return getDwellingScore(target, checkGold);
case Obj::CRYPT:
case Obj::SHIPWRECK:
case Obj::SHIPWRECK_SURVIVOR:
return 1500;
case Obj::ARTIFACT:
return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() == CArtifact::ART_MAJOR ? 3000 : 1500;
return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() * 300;
case Obj::DRAGON_UTOPIA:
return 10000;
default:
@ -259,11 +262,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
auto hero = heroPtr.get();
auto armyTotal = task->evaluationContext.heroStrength;
double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal;
int32_t goldReward = getGoldReward(target, hero);
uint64_t armyReward = getArmyReward(target, hero);
HeroRole heroRole = ai->ah->getHeroRole(heroPtr);
float skillReward = getSkillReward(target, hero, heroRole);
uint64_t danger = task->evaluationContext.danger;
HeroRole heroRole = ai->ah->getHeroRole(heroPtr);
int32_t goldReward = getGoldReward(target, hero);
bool checkGold = danger == 0;
uint64_t armyReward = getArmyReward(target, hero, checkGold);
float skillReward = getSkillReward(target, hero, heroRole);
double result = 0;
int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0);

View File

@ -82,6 +82,14 @@ void ExecuteHeroChain::accept(VCAI * ai)
{
ai->nullkiller->setActive(hero);
if(node.specialAction)
{
auto specialGoal = node.specialAction->whatToDo(hero);
if(specialGoal->isElementar)
specialGoal->accept(ai);
}
Goals::VisitTile(node.coord).sethero(hero).accept(ai);
}
@ -138,15 +146,13 @@ TSubgoal ExchangeSwapTownHeroes::whatToDoToAchieve()
void ExchangeSwapTownHeroes::accept(VCAI * ai)
{
if(!garrisonHero && town->garrisonHero && town->visitingHero)
throw cannotFulfillGoalException("Invalid configuration. Garrison hero is null.");
if(!garrisonHero)
{
if(!town->garrisonHero)
throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison.");
cb->swapGarrisonHero(town);
ai->buildArmyIn(town);
ai->nullkiller->unlockHero(town->visitingHero.get());
logAi->debug("Extracted hero %s from garrison of %s", town->visitingHero->name, town->name);
@ -162,7 +168,10 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
ai->nullkiller->lockHero(town->garrisonHero.get());
if(town->visitingHero)
{
ai->nullkiller->unlockHero(town->visitingHero.get());
makePossibleUpgrades(town->visitingHero);
}
logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name);
}

View File

@ -501,7 +501,7 @@ void AINodeStorage::setTownsAndDwellings(
{
uint64_t mask = 1 << actors.size();
if(town->getUpperArmy()->getArmyStrength())
if(!town->garrisonHero && town->getUpperArmy()->getArmyStrength())
{
actors.push_back(std::make_shared<TownGarrisonActor>(town, mask));
}
@ -563,7 +563,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
if(source.isInitialPosition)
{
calculateTownPortalTeleportations(source, neighbours);
calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
}
return neighbours;
@ -571,7 +571,8 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
void AINodeStorage::calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours)
std::vector<CGPathNode *> & neighbours,
const CPathfinderHelper * pathfinderHelper)
{
SpellID spellID = SpellID::TOWN_PORTAL;
const CSpell * townPortal = spellID.toSpell();
@ -594,9 +595,12 @@ void AINodeStorage::calculateTownPortalTeleportations(
// TODO: Copy/Paste from TownPortalMechanics
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
if(hero->movement < movementCost)
movementCost += source.node->cost;
if(source.node->moveRemains < movementNeeded)
{
return;
}
@ -605,7 +609,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
{
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
{
return hero->visitablePos().dist2dSQ(t->visitablePos());
return source.coord.dist2dSQ(t->visitablePos());
});
towns = std::vector<const CGTownInstance *>{ nearestTown };
@ -613,6 +617,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
for(const CGTownInstance * targetTown : towns)
{
// TODO: allow to hide visiting hero in garrison
if(targetTown->visitingHero)
continue;
@ -626,9 +631,13 @@ void AINodeStorage::calculateTownPortalTeleportations(
AIPathNode * node = nodeOptional.get();
node->theNodeBefore = source.node;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
node->moveRemains = source.node->moveRemains;
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
{
node->theNodeBefore = source.node;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
node->moveRemains = source.node->moveRemains + movementNeeded;
node->cost = movementCost;
}
neighbours.push_back(node);
}
@ -764,6 +773,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
AIPathNodeInfo pathNode;
pathNode.cost = node->cost;
pathNode.targetHero = node->actor->hero;
pathNode.specialAction = node->specialAction;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;

View File

@ -10,7 +10,7 @@
#pragma once
#define VCMI_TRACE_PATHFINDER 1
#define VCMI_TRACE_PATHFINDER 0
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
@ -39,6 +39,7 @@ struct AIPathNodeInfo
uint64_t danger;
const CGHeroInstance * targetHero;
int parentIndex;
std::shared_ptr<const ISpecialAction> specialAction;
};
struct AIPath
@ -163,7 +164,10 @@ private:
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
void addHeroChain(const std::vector<ExchangeCandidate> & result);
void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
void calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours,
const CPathfinderHelper * pathfinderHelper);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
void commit(
AIPathNode * destination,

View File

@ -649,6 +649,12 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vector<Compon
if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
sel = 1;
// TODO: Find better way to understand it is Chest of Treasures
if(components.size() == 2 && components.front().id == Component::RESOURCE)
{
sel = 1; // for now lets pick gold from a chest.
}
requestActionASAP([=]()
{
answerQuery(askID, sel);
@ -1062,7 +1068,8 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
if(h->visitedTown) //we are inside, not just attacking
{
townVisitsThisWeek[h].insert(h->visitedTown);
if(!h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
ah->updateHeroRoles();
if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
{
if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
@ -1807,6 +1814,9 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies)
bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
{
if(h->inTownGarrison && h->visitedTown)
cb->swapGarrisonHero(h->visitedTown);
//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
auto afterMovementCheck = [&]() -> void