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:
parent
bcf8db3d05
commit
e3c87fb58d
@ -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)));
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user