mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Nullkiller: tbb and hero chain calculation optimization and parallel cpathfinder initialization
This commit is contained in:
committed by
Andrii Danylchenko
parent
3480f17a68
commit
fb3cda666f
@@ -189,7 +189,7 @@ bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectIns
|
|||||||
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
|
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
|
||||||
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
|
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
|
||||||
|
|
||||||
return ln->cost < rn->cost;
|
return ln->getCost() < rn->getCost();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
||||||
|
@@ -137,6 +137,8 @@ else()
|
|||||||
target_link_libraries(VCAI PRIVATE fl-static vcmi)
|
target_link_libraries(VCAI PRIVATE fl-static vcmi)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(VCAI PRIVATE TBB::tbb)
|
||||||
|
|
||||||
vcmi_set_output_dir(VCAI "AI")
|
vcmi_set_output_dir(VCAI "AI")
|
||||||
|
|
||||||
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
|
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
|
||||||
|
@@ -44,19 +44,19 @@ struct armyStructure
|
|||||||
|
|
||||||
armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
||||||
{
|
{
|
||||||
ui64 totalStrenght = army->getArmyStrength();
|
ui64 totalStrength = army->getArmyStrength();
|
||||||
double walkersStrenght = 0;
|
double walkersStrength = 0;
|
||||||
double flyersStrenght = 0;
|
double flyersStrength = 0;
|
||||||
double shootersStrenght = 0;
|
double shootersStrength = 0;
|
||||||
ui32 maxSpeed = 0;
|
ui32 maxSpeed = 0;
|
||||||
|
|
||||||
static const CSelector selectorSHOOTER = Selector::type(Bonus::SHOOTER);
|
static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
|
||||||
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
|
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
|
||||||
|
|
||||||
static const CSelector selectorFLYING = Selector::type(Bonus::FLYING);
|
static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
|
||||||
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
|
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
|
||||||
|
|
||||||
static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED);
|
static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
|
||||||
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
|
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
|
||||||
|
|
||||||
for(auto s : army->Slots())
|
for(auto s : army->Slots())
|
||||||
@@ -65,23 +65,23 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
|||||||
const CCreature * creature = s.second->type;
|
const CCreature * creature = s.second->type;
|
||||||
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
|
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
|
||||||
{
|
{
|
||||||
shootersStrenght += s.second->getPower();
|
shootersStrength += s.second->getPower();
|
||||||
walker = false;
|
walker = false;
|
||||||
}
|
}
|
||||||
if(creature->hasBonus(selectorFLYING, keyFLYING))
|
if(creature->hasBonus(selectorFLYING, keyFLYING))
|
||||||
{
|
{
|
||||||
flyersStrenght += s.second->getPower();
|
flyersStrength += s.second->getPower();
|
||||||
walker = false;
|
walker = false;
|
||||||
}
|
}
|
||||||
if(walker)
|
if(walker)
|
||||||
walkersStrenght += s.second->getPower();
|
walkersStrength += s.second->getPower();
|
||||||
|
|
||||||
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
|
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
|
||||||
}
|
}
|
||||||
armyStructure as;
|
armyStructure as;
|
||||||
as.walkers = walkersStrenght / totalStrenght;
|
as.walkers = static_cast<float>(walkersStrength / totalStrength);
|
||||||
as.shooters = shootersStrenght / totalStrenght;
|
as.shooters = static_cast<float>(shootersStrength / totalStrength);
|
||||||
as.flyers = flyersStrenght / totalStrenght;
|
as.flyers = static_cast<float>(flyersStrength / totalStrength);
|
||||||
as.maxSpeed = maxSpeed;
|
as.maxSpeed = maxSpeed;
|
||||||
assert(as.walkers || as.flyers || as.shooters);
|
assert(as.walkers || as.flyers || as.shooters);
|
||||||
return as;
|
return as;
|
||||||
|
@@ -262,4 +262,4 @@ void Nullkiller::makeTurn()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
#include "PriorityEvaluator.h"
|
#include "PriorityEvaluator.h"
|
||||||
#include "FuzzyHelper.h"
|
#include "FuzzyHelper.h"
|
||||||
#include "AIMemory.h"
|
#include "AIMemory.h"
|
||||||
|
@@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
{
|
{
|
||||||
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
|
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
|
||||||
|
|
||||||
ui32 val = std::min<ui32>(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
|
ui32 val = std::min((ui32)value, (ui32)ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
|
||||||
|
|
||||||
if(val)
|
if(val)
|
||||||
{
|
{
|
||||||
|
@@ -95,11 +95,11 @@ TSubgoal Win::whatToDoToAchieve()
|
|||||||
{
|
{
|
||||||
auto towns = cb->getTownsInfo();
|
auto towns = cb->getTownsInfo();
|
||||||
towns.erase(boost::remove_if(towns,
|
towns.erase(boost::remove_if(towns,
|
||||||
[](const CGTownInstance * t) -> bool
|
[](const CGTownInstance * t) -> bool
|
||||||
{
|
{
|
||||||
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
|
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
|
||||||
}),
|
}),
|
||||||
towns.end());
|
towns.end());
|
||||||
boost::sort(towns, CDistanceSorter(h.get()));
|
boost::sort(towns, CDistanceSorter(h.get()));
|
||||||
if(towns.size())
|
if(towns.size())
|
||||||
{
|
{
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
|
#include <tbb/tbb.h>
|
||||||
#include "AINodeStorage.h"
|
#include "AINodeStorage.h"
|
||||||
#include "Actions/TownPortalAction.h"
|
#include "Actions/TownPortalAction.h"
|
||||||
#include "../Goals/Goals.h"
|
#include "../Goals/Goals.h"
|
||||||
@@ -19,6 +20,8 @@
|
|||||||
#include "../../../lib/PathfinderUtil.h"
|
#include "../../../lib/PathfinderUtil.h"
|
||||||
#include "../../../lib/CPlayerState.h"
|
#include "../../../lib/CPlayerState.h"
|
||||||
|
|
||||||
|
using namespace tbb;
|
||||||
|
|
||||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||||
std::set<int3> commitedTiles;
|
std::set<int3> commitedTiles;
|
||||||
std::set<int3> commitedTilesInitial;
|
std::set<int3> commitedTilesInitial;
|
||||||
@@ -61,50 +64,56 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
|
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
|
||||||
int3 pos;
|
|
||||||
const PlayerColor player = playerID;
|
|
||||||
const PlayerColor fowPlayer = ai->playerID;
|
const PlayerColor fowPlayer = ai->playerID;
|
||||||
const int3 sizes = gs->getMapSize();
|
|
||||||
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
|
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
|
||||||
|
const int3 sizes = gs->getMapSize();
|
||||||
|
|
||||||
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
|
parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
|
||||||
const bool useFlying = options.useFlying;
|
|
||||||
const bool useWaterWalking = options.useWaterWalking;
|
|
||||||
|
|
||||||
for(pos.x=0; pos.x < sizes.x; ++pos.x)
|
|
||||||
{
|
{
|
||||||
for(pos.y=0; pos.y < sizes.y; ++pos.y)
|
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
|
||||||
|
const bool useFlying = options.useFlying;
|
||||||
|
const bool useWaterWalking = options.useWaterWalking;
|
||||||
|
const PlayerColor player = playerID;
|
||||||
|
|
||||||
|
int3 pos;
|
||||||
|
|
||||||
|
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
|
||||||
{
|
{
|
||||||
for(pos.z=0; pos.z < sizes.z; ++pos.z)
|
for(pos.y = 0; pos.y < sizes.y; ++pos.y)
|
||||||
{
|
{
|
||||||
const TerrainTile * tile = &gs->map->getTile(pos);
|
for(pos.z = 0; pos.z < sizes.z; ++pos.z)
|
||||||
switch(tile->terType)
|
|
||||||
{
|
{
|
||||||
case ETerrainType::ROCK:
|
const TerrainTile * tile = &gs->map->getTile(pos);
|
||||||
break;
|
switch(tile->terType)
|
||||||
|
{
|
||||||
|
case ETerrainType::ROCK:
|
||||||
|
break;
|
||||||
|
|
||||||
case ETerrainType::WATER:
|
case ETerrainType::WATER:
|
||||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||||
if(useFlying)
|
if(useFlying)
|
||||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||||
if(useWaterWalking)
|
if(useWaterWalking)
|
||||||
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
|
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
|
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
|
||||||
if(useFlying)
|
if(useFlying)
|
||||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AINodeStorage::clear()
|
void AINodeStorage::clear()
|
||||||
{
|
{
|
||||||
|
CCreature::DisableChildLinkage = true;
|
||||||
actors.clear();
|
actors.clear();
|
||||||
|
CCreature::DisableChildLinkage = false;
|
||||||
heroChainPass = EHeroChainPass::INITIAL;
|
heroChainPass = EHeroChainPass::INITIAL;
|
||||||
heroChainTurn = 0;
|
heroChainTurn = 0;
|
||||||
heroChainMaxTurns = 1;
|
heroChainMaxTurns = 1;
|
||||||
@@ -161,7 +170,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
|||||||
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||||
{
|
{
|
||||||
if(heroChainPass)
|
if(heroChainPass)
|
||||||
{
|
{
|
||||||
calculateTownPortalTeleportations(heroChain);
|
calculateTownPortalTeleportations(heroChain);
|
||||||
|
|
||||||
return heroChain;
|
return heroChain;
|
||||||
@@ -184,7 +193,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
|||||||
initialNode->turns = actor->initialTurn;
|
initialNode->turns = actor->initialTurn;
|
||||||
initialNode->moveRemains = actor->initialMovement;
|
initialNode->moveRemains = actor->initialMovement;
|
||||||
initialNode->danger = 0;
|
initialNode->danger = 0;
|
||||||
initialNode->cost = actor->initialTurn;
|
initialNode->setCost(actor->initialTurn);
|
||||||
initialNode->action = CGPathNode::ENodeAction::NORMAL;
|
initialNode->action = CGPathNode::ENodeAction::NORMAL;
|
||||||
|
|
||||||
if(actor->isMovable)
|
if(actor->isMovable)
|
||||||
@@ -205,7 +214,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
|||||||
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
||||||
{
|
{
|
||||||
for(AIPathNode & heroNode : nodes.get(coord, layer))
|
for(AIPathNode & heroNode : nodes.get(coord, layer))
|
||||||
{
|
{
|
||||||
heroNode.actor = nullptr;
|
heroNode.actor = nullptr;
|
||||||
heroNode.danger = 0;
|
heroNode.danger = 0;
|
||||||
heroNode.manaCost = 0;
|
heroNode.manaCost = 0;
|
||||||
@@ -246,7 +255,7 @@ void AINodeStorage::commit(
|
|||||||
float cost) const
|
float cost) const
|
||||||
{
|
{
|
||||||
destination->action = action;
|
destination->action = action;
|
||||||
destination->cost = cost;
|
destination->setCost(cost);
|
||||||
destination->moveRemains = movementLeft;
|
destination->moveRemains = movementLeft;
|
||||||
destination->turns = turn;
|
destination->turns = turn;
|
||||||
destination->armyLoss = source->armyLoss;
|
destination->armyLoss = source->armyLoss;
|
||||||
@@ -360,61 +369,133 @@ bool AINodeStorage::calculateHeroChainFinal()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return heroChain.size();
|
return heroChain.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DelayedWork
|
||||||
|
{
|
||||||
|
AIPathNode * carrier;
|
||||||
|
AIPathNode * other;
|
||||||
|
|
||||||
|
DelayedWork()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DelayedWork(AIPathNode * carrier, AIPathNode * other) : carrier(carrier), other(other)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeroChainCalculationTask
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
AISharedStorage & nodes;
|
||||||
|
AINodeStorage & storage;
|
||||||
|
std::vector<AIPathNode *> existingChains;
|
||||||
|
std::vector<ExchangeCandidate> newChains;
|
||||||
|
uint64_t chainMask;
|
||||||
|
int heroChainTurn;
|
||||||
|
std::vector<CGPathNode *> heroChain;
|
||||||
|
const std::vector<int3> & tiles;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HeroChainCalculationTask(
|
||||||
|
AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||||
|
:existingChains(), newChains(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||||
|
{
|
||||||
|
existingChains.reserve(NUM_CHAINS);
|
||||||
|
newChains.reserve(NUM_CHAINS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(const blocked_range<size_t>& r)
|
||||||
|
{
|
||||||
|
for(int i = r.begin(); i != r.end(); i++)
|
||||||
|
{
|
||||||
|
auto & pos = tiles[i];
|
||||||
|
|
||||||
|
for(auto layer : phisycalLayers)
|
||||||
|
{
|
||||||
|
auto chains = nodes.get(pos, layer);
|
||||||
|
|
||||||
|
// fast cut inactive nodes
|
||||||
|
if(chains[0].blocked())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
existingChains.clear();
|
||||||
|
newChains.clear();
|
||||||
|
|
||||||
|
for(AIPathNode & node : chains)
|
||||||
|
{
|
||||||
|
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
|
||||||
|
existingChains.push_back(&node);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::random_shuffle(existingChains.begin(), existingChains.end());
|
||||||
|
|
||||||
|
for(AIPathNode * node : existingChains)
|
||||||
|
{
|
||||||
|
if(node->actor->isMovable)
|
||||||
|
{
|
||||||
|
calculateHeroChain(node, existingChains, newChains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupInefectiveChains(newChains);
|
||||||
|
addHeroChain(newChains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateHeroChain(
|
||||||
|
AIPathNode * srcNode,
|
||||||
|
const std::vector<AIPathNode *> & variants,
|
||||||
|
std::vector<ExchangeCandidate> & result);
|
||||||
|
|
||||||
|
void calculateHeroChain(
|
||||||
|
AIPathNode * carrier,
|
||||||
|
AIPathNode * other,
|
||||||
|
std::vector<ExchangeCandidate> & result);
|
||||||
|
|
||||||
|
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
|
||||||
|
void addHeroChain(const std::vector<ExchangeCandidate> & result);
|
||||||
|
|
||||||
|
ExchangeCandidate calculateExchange(
|
||||||
|
ChainActor * exchangeActor,
|
||||||
|
AIPathNode * carrierParentNode,
|
||||||
|
AIPathNode * otherParentNode) const;
|
||||||
|
|
||||||
|
void flushResult(std::vector<CGPathNode *> & result)
|
||||||
|
{
|
||||||
|
vstd::concatenate(result, heroChain);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool AINodeStorage::calculateHeroChain()
|
bool AINodeStorage::calculateHeroChain()
|
||||||
{
|
{
|
||||||
heroChainPass = EHeroChainPass::CHAIN;
|
heroChainPass = EHeroChainPass::CHAIN;
|
||||||
heroChain.resize(0);
|
heroChain.clear();
|
||||||
|
|
||||||
std::vector<AIPathNode *> existingChains;
|
std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
|
||||||
std::vector<ExchangeCandidate> newChains;
|
|
||||||
|
|
||||||
existingChains.reserve(NUM_CHAINS);
|
CCreature::DisableChildLinkage = true;
|
||||||
newChains.reserve(NUM_CHAINS);
|
|
||||||
|
|
||||||
for(auto & pos : commitedTiles)
|
auto r = blocked_range<size_t>(0, data.size());
|
||||||
{
|
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
|
||||||
for(auto layer : phisycalLayers)
|
|
||||||
{
|
|
||||||
auto chains = nodes.get(pos, layer);
|
|
||||||
|
|
||||||
// fast cut inactive nodes
|
task.execute(r);
|
||||||
if(chains[0].blocked())
|
task.flushResult(heroChain);
|
||||||
continue;
|
|
||||||
|
|
||||||
existingChains.clear();
|
CCreature::DisableChildLinkage = false;
|
||||||
newChains.clear();
|
|
||||||
|
|
||||||
for(AIPathNode & node : chains)
|
|
||||||
{
|
|
||||||
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
|
|
||||||
existingChains.push_back(&node);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(AIPathNode * node : existingChains)
|
|
||||||
{
|
|
||||||
if(node->actor->isMovable)
|
|
||||||
{
|
|
||||||
calculateHeroChain(node, existingChains, newChains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupInefectiveChains(newChains);
|
|
||||||
addHeroChain(newChains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commitedTiles.clear();
|
commitedTiles.clear();
|
||||||
|
|
||||||
return heroChain.size();
|
return !heroChain.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AINodeStorage::selectFirstActor()
|
bool AINodeStorage::selectFirstActor()
|
||||||
{
|
{
|
||||||
if(!actors.size())
|
if(actors.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
|
auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
|
||||||
@@ -454,6 +535,9 @@ bool AINodeStorage::selectNextActor()
|
|||||||
|
|
||||||
if(nextActor != actors.end())
|
if(nextActor != actors.end())
|
||||||
{
|
{
|
||||||
|
if(nextActor->get()->armyValue < 1000)
|
||||||
|
return false;
|
||||||
|
|
||||||
chainMask = nextActor->get()->chainMask;
|
chainMask = nextActor->get()->chainMask;
|
||||||
commitedTiles = commitedTilesInitial;
|
commitedTiles = commitedTilesInitial;
|
||||||
|
|
||||||
@@ -463,22 +547,36 @@ bool AINodeStorage::selectNextActor()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
|
void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
|
||||||
{
|
{
|
||||||
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
|
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
|
||||||
{
|
{
|
||||||
auto pos = chainInfo.coord;
|
auto pos = chainInfo.coord;
|
||||||
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
||||||
|
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|
||||||
|
|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
|
||||||
|
|
||||||
return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
|
if(isNotEffective)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Skip exchange %s[%x] -> %s[%x] at %s is ineficient",
|
||||||
|
chainInfo.otherParent->actor->toString(),
|
||||||
|
chainInfo.otherParent->actor->chainMask,
|
||||||
|
chainInfo.carrierParent->actor->toString(),
|
||||||
|
chainInfo.carrierParent->actor->chainMask,
|
||||||
|
chainInfo.carrierParent->coord.toString());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return isNotEffective;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AINodeStorage::calculateHeroChain(
|
void HeroChainCalculationTask::calculateHeroChain(
|
||||||
AIPathNode * srcNode,
|
AIPathNode * srcNode,
|
||||||
const std::vector<AIPathNode *> & variants,
|
const std::vector<AIPathNode *> & variants,
|
||||||
std::vector<ExchangeCandidate> & result) const
|
std::vector<ExchangeCandidate> & result)
|
||||||
{
|
{
|
||||||
for(AIPathNode * node : variants)
|
for(AIPathNode * node : variants)
|
||||||
{
|
{
|
||||||
@@ -531,16 +629,15 @@ void AINodeStorage::calculateHeroChain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AINodeStorage::calculateHeroChain(
|
void HeroChainCalculationTask::calculateHeroChain(
|
||||||
AIPathNode * carrier,
|
AIPathNode * carrier,
|
||||||
AIPathNode * other,
|
AIPathNode * other,
|
||||||
std::vector<ExchangeCandidate> & result) const
|
std::vector<ExchangeCandidate> & result)
|
||||||
{
|
{
|
||||||
if(carrier->armyLoss < carrier->actor->armyValue
|
if(carrier->armyLoss < carrier->actor->armyValue
|
||||||
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
|
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
|
||||||
&& carrier->action != CGPathNode::BLOCKING_VISIT
|
&& carrier->action != CGPathNode::BLOCKING_VISIT
|
||||||
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
|
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
|
||||||
&& carrier->actor->canExchange(other->actor))
|
|
||||||
{
|
{
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
@@ -566,20 +663,20 @@ void AINodeStorage::calculateHeroChain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newActor = carrier->actor->exchange(other->actor);
|
auto newActor = carrier->actor->tryExchange(other->actor);
|
||||||
|
|
||||||
result.push_back(calculateExchange(newActor, carrier, other));
|
if(newActor) result.push_back(calculateExchange(newActor, carrier, other));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
||||||
{
|
{
|
||||||
for(const ExchangeCandidate & chainInfo : result)
|
for(const ExchangeCandidate & chainInfo : result)
|
||||||
{
|
{
|
||||||
auto carrier = chainInfo.carrierParent;
|
auto carrier = chainInfo.carrierParent;
|
||||||
auto newActor = chainInfo.actor;
|
auto newActor = chainInfo.actor;
|
||||||
auto other = chainInfo.otherParent;
|
auto other = chainInfo.otherParent;
|
||||||
auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
|
auto chainNodeOptional = storage.getOrCreateNode(carrier->coord, carrier->layer, newActor);
|
||||||
|
|
||||||
if(!chainNodeOptional)
|
if(!chainNodeOptional)
|
||||||
{
|
{
|
||||||
@@ -594,24 +691,34 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|||||||
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
|
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
|
||||||
{
|
{
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
|
logAi->trace(
|
||||||
|
"Skip exchange %s[%x] -> %s[%x] at %s because node is in use",
|
||||||
|
other->actor->toString(),
|
||||||
|
other->actor->chainMask,
|
||||||
|
carrier->actor->toString(),
|
||||||
|
carrier->actor->chainMask,
|
||||||
|
carrier->coord.toString());
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
|
if(exchangeNode->turns != 0xFF && exchangeNode->getCost() < chainInfo.getCost())
|
||||||
{
|
{
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Exchange at %s is is not effective enough. %f < %f",
|
"Skip exchange %s[%x] -> %s[%x] at %s because not effective enough. %f < %f",
|
||||||
exchangeNode->coord.toString(),
|
other->actor->toString(),
|
||||||
exchangeNode->getCost(),
|
other->actor->chainMask,
|
||||||
|
carrier->actor->toString(),
|
||||||
|
carrier->actor->chainMask,
|
||||||
|
carrier->coord.toString(),
|
||||||
|
exchangeNode->getCost(),
|
||||||
chainInfo.getCost());
|
chainInfo.getCost());
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
|
storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
|
||||||
|
|
||||||
if(carrier->specialAction || carrier->chainOther)
|
if(carrier->specialAction || carrier->chainOther)
|
||||||
{
|
{
|
||||||
@@ -644,7 +751,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExchangeCandidate AINodeStorage::calculateExchange(
|
ExchangeCandidate HeroChainCalculationTask::calculateExchange(
|
||||||
ChainActor * exchangeActor,
|
ChainActor * exchangeActor,
|
||||||
AIPathNode * carrierParentNode,
|
AIPathNode * carrierParentNode,
|
||||||
AIPathNode * otherParentNode) const
|
AIPathNode * otherParentNode) const
|
||||||
@@ -658,7 +765,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
|
|||||||
candidate.actor = exchangeActor;
|
candidate.actor = exchangeActor;
|
||||||
candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
|
candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
|
||||||
candidate.turns = carrierParentNode->turns;
|
candidate.turns = carrierParentNode->turns;
|
||||||
candidate.cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
|
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
|
||||||
candidate.moveRemains = carrierParentNode->moveRemains;
|
candidate.moveRemains = carrierParentNode->moveRemains;
|
||||||
|
|
||||||
if(carrierParentNode->turns < otherParentNode->turns)
|
if(carrierParentNode->turns < otherParentNode->turns)
|
||||||
@@ -668,7 +775,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
|
|||||||
+ carrierParentNode->moveRemains / (float)moveRemains;
|
+ carrierParentNode->moveRemains / (float)moveRemains;
|
||||||
|
|
||||||
candidate.turns = otherParentNode->turns;
|
candidate.turns = otherParentNode->turns;
|
||||||
candidate.cost += waitingCost;
|
candidate.setCost(candidate.getCost() + waitingCost);
|
||||||
candidate.moveRemains = moveRemains;
|
candidate.moveRemains = moveRemains;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +980,7 @@ struct TowmPortalFinder
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!bestNode || bestNode->cost > node->cost)
|
if(!bestNode || bestNode->getCost() > node->getCost())
|
||||||
bestNode = node;
|
bestNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,9 +1002,9 @@ struct TowmPortalFinder
|
|||||||
AIPathNode * node = nodeOptional.get();
|
AIPathNode * node = nodeOptional.get();
|
||||||
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
||||||
|
|
||||||
movementCost += bestNode->cost;
|
movementCost += bestNode->getCost();
|
||||||
|
|
||||||
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
|
if(node->action == CGPathNode::UNKNOWN || node->getCost() > movementCost)
|
||||||
{
|
{
|
||||||
nodeStorage->commit(
|
nodeStorage->commit(
|
||||||
node,
|
node,
|
||||||
@@ -1009,7 +1116,7 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
|
|
||||||
if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
|
if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
|
||||||
{
|
{
|
||||||
if(node.cost < candidateNode->cost)
|
if(node.getCost() < candidateNode->getCost())
|
||||||
{
|
{
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
@@ -1033,7 +1140,7 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
|
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
|
||||||
|
|
||||||
if(nodeArmyValue > candidateArmyValue
|
if(nodeArmyValue > candidateArmyValue
|
||||||
&& node.cost <= candidateNode->cost)
|
&& node.getCost() <= candidateNode->getCost())
|
||||||
{
|
{
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
@@ -1052,10 +1159,10 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
{
|
{
|
||||||
if(nodeArmyValue == candidateArmyValue
|
if(nodeArmyValue == candidateArmyValue
|
||||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||||
&& node.cost <= candidateNode->cost)
|
&& node.getCost() <= candidateNode->getCost())
|
||||||
{
|
{
|
||||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||||
&& node.cost == candidateNode->cost
|
&& node.getCost() == candidateNode->getCost()
|
||||||
&& &node < candidateNode)
|
&& &node < candidateNode)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -1141,7 +1248,8 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
|||||||
//if(node->actor->hero->visitablePos() != node->coord)
|
//if(node->actor->hero->visitablePos() != node->coord)
|
||||||
{
|
{
|
||||||
AIPathNodeInfo pathNode;
|
AIPathNodeInfo pathNode;
|
||||||
pathNode.cost = node->cost;
|
|
||||||
|
pathNode.cost = node->getCost();
|
||||||
pathNode.targetHero = node->actor->hero;
|
pathNode.targetHero = node->actor->hero;
|
||||||
pathNode.chainMask = node->actor->chainMask;
|
pathNode.chainMask = node->actor->chainMask;
|
||||||
pathNode.specialAction = node->specialAction;
|
pathNode.specialAction = node->specialAction;
|
||||||
|
@@ -23,6 +23,14 @@
|
|||||||
#include "Actions/SpecialAction.h"
|
#include "Actions/SpecialAction.h"
|
||||||
#include "Actors.h"
|
#include "Actors.h"
|
||||||
|
|
||||||
|
namespace AIPathfinding
|
||||||
|
{
|
||||||
|
const int BUCKET_COUNT = 11;
|
||||||
|
const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
|
||||||
|
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||||
|
const int THREAD_COUNT = 8;
|
||||||
|
}
|
||||||
|
|
||||||
struct AIPathNode : public CGPathNode
|
struct AIPathNode : public CGPathNode
|
||||||
{
|
{
|
||||||
uint64_t danger;
|
uint64_t danger;
|
||||||
@@ -228,28 +236,14 @@ public:
|
|||||||
return (uint64_t)(armyValue * ratio * ratio * ratio);
|
return (uint64_t)(armyValue * ratio * ratio * ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
STRONG_INLINE
|
STRONG_INLINE
|
||||||
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
|
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
|
||||||
|
STRONG_INLINE int getBucket(const ChainActor * actor) const
|
||||||
|
{
|
||||||
|
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
void calculateHeroChain(
|
|
||||||
AIPathNode * srcNode,
|
|
||||||
const std::vector<AIPathNode *> & variants,
|
|
||||||
std::vector<ExchangeCandidate> & result) const;
|
|
||||||
|
|
||||||
void calculateHeroChain(
|
|
||||||
AIPathNode * carrier,
|
|
||||||
AIPathNode * other,
|
|
||||||
std::vector<ExchangeCandidate> & result) const;
|
|
||||||
|
|
||||||
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
|
|
||||||
void addHeroChain(const std::vector<ExchangeCandidate> & result);
|
|
||||||
|
|
||||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||||
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
|
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
|
||||||
|
|
||||||
ExchangeCandidate calculateExchange(
|
|
||||||
ChainActor * exchangeActor,
|
|
||||||
AIPathNode * carrierParentNode,
|
|
||||||
AIPathNode * otherParentNode) const;
|
|
||||||
};
|
};
|
||||||
|
@@ -114,7 +114,7 @@ namespace AIPathfinding
|
|||||||
source->manaCost);
|
source->manaCost);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return hero->mana >= source->manaCost + getManaCost(hero);
|
return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SummonBoatAction::toString() const
|
std::string SummonBoatAction::toString() const
|
||||||
|
@@ -145,14 +145,11 @@ void HeroActor::setupSpecialActors()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChainActor * ChainActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
|
ChainActor * ChainActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
|
||||||
{
|
{
|
||||||
return baseActor->exchange(specialActor, other);
|
if(!isMovable) return nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
bool ChainActor::canExchange(const ChainActor * other) const
|
return baseActor->tryExchange(specialActor, other);
|
||||||
{
|
|
||||||
return isMovable && baseActor->canExchange(other);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace vstd
|
namespace vstd
|
||||||
@@ -172,71 +169,12 @@ namespace vstd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HeroActor::canExchange(const ChainActor * other) const
|
ChainActor * HeroActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
|
||||||
{
|
|
||||||
return exchangeMap->canExchange(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HeroExchangeMap::canExchange(const ChainActor * other)
|
|
||||||
{
|
|
||||||
return vstd::getOrCompute(canExchangeCache, other, [&](bool & result) {
|
|
||||||
result = (actor->chainMask & other->chainMask) == 0;
|
|
||||||
|
|
||||||
if(result)
|
|
||||||
{
|
|
||||||
TResources resources = ai->cb->getResourceAmount();
|
|
||||||
|
|
||||||
if(!resources.canAfford(actor->armyCost + other->armyCost))
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
|
||||||
logAi->trace(
|
|
||||||
"Can not afford exchange because of total cost %s but we have %s",
|
|
||||||
(actor->armyCost + other->armyCost).toString(),
|
|
||||||
resources.toString());
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TResources availableResources = resources - actor->armyCost - other->armyCost;
|
|
||||||
|
|
||||||
auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(
|
|
||||||
actor->creatureSet,
|
|
||||||
other->getActorObject(),
|
|
||||||
availableResources);
|
|
||||||
|
|
||||||
uint64_t reinforcment = upgradeInfo.upgradeValue;
|
|
||||||
|
|
||||||
if(other->creatureSet->Slots().size())
|
|
||||||
reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->hero, actor->creatureSet, other->creatureSet);
|
|
||||||
|
|
||||||
auto obj = other->getActorObject();
|
|
||||||
if(obj && obj->ID == Obj::TOWN)
|
|
||||||
{
|
|
||||||
reinforcment += ai->armyManager->howManyReinforcementsCanBuy(
|
|
||||||
actor->creatureSet,
|
|
||||||
ai->cb->getTown(obj->id),
|
|
||||||
availableResources - upgradeInfo.upgradeCost);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
|
||||||
logAi->trace(
|
|
||||||
"Exchange %s->%s reinforcement: %d, %f%%",
|
|
||||||
actor->toString(),
|
|
||||||
other->toString(),
|
|
||||||
reinforcment,
|
|
||||||
100.0f * reinforcment / actor->armyValue);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
result = reinforcment > actor->armyValue / 10 || reinforcment > 1000;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
|
|
||||||
{
|
{
|
||||||
const ChainActor * otherBase = other->baseActor;
|
const ChainActor * otherBase = other->baseActor;
|
||||||
HeroActor * result = exchangeMap->exchange(otherBase);
|
HeroActor * result = exchangeMap->tryExchange(otherBase);
|
||||||
|
|
||||||
|
if(!result) return nullptr;
|
||||||
|
|
||||||
if(specialActor == this)
|
if(specialActor == this)
|
||||||
return result;
|
return result;
|
||||||
@@ -250,7 +188,7 @@ ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainAct
|
|||||||
}
|
}
|
||||||
|
|
||||||
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
|
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
|
||||||
:actor(actor), ai(ai)
|
:actor(actor), ai(ai), sync()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +196,8 @@ HeroExchangeMap::~HeroExchangeMap()
|
|||||||
{
|
{
|
||||||
for(auto & exchange : exchangeMap)
|
for(auto & exchange : exchangeMap)
|
||||||
{
|
{
|
||||||
|
if(!exchange.second) continue;
|
||||||
|
|
||||||
delete exchange.second->creatureSet;
|
delete exchange.second->creatureSet;
|
||||||
delete exchange.second;
|
delete exchange.second;
|
||||||
}
|
}
|
||||||
@@ -265,44 +205,91 @@ HeroExchangeMap::~HeroExchangeMap()
|
|||||||
exchangeMap.clear();
|
exchangeMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
|
HeroActor * HeroExchangeMap::tryExchange(const ChainActor * other)
|
||||||
{
|
{
|
||||||
HeroActor * result;
|
auto position = exchangeMap.find(other);
|
||||||
|
|
||||||
if(vstd::contains(exchangeMap, other))
|
if(position != exchangeMap.end())
|
||||||
result = exchangeMap.at(other);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost;
|
return position->second;
|
||||||
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
|
}
|
||||||
HeroExchangeArmy * newArmy;
|
|
||||||
|
|
||||||
if(other->creatureSet->Slots().size())
|
|
||||||
{
|
|
||||||
if(upgradedInitialArmy)
|
|
||||||
{
|
|
||||||
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
|
|
||||||
newArmy->armyCost = upgradedInitialArmy->armyCost;
|
|
||||||
newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
|
|
||||||
|
|
||||||
delete upgradedInitialArmy;
|
auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
|
||||||
}
|
|
||||||
else
|
if(!inserted.second)
|
||||||
{
|
{
|
||||||
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
|
return inserted.first->second; // already inserted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
position = inserted.first;
|
||||||
|
|
||||||
|
auto differentMasks = (actor->chainMask & other->chainMask) == 0;
|
||||||
|
|
||||||
|
if(!differentMasks) return nullptr;
|
||||||
|
|
||||||
|
TResources resources = ai->cb->getResourceAmount();
|
||||||
|
|
||||||
|
if(!resources.canAfford(actor->armyCost + other->armyCost))
|
||||||
|
{
|
||||||
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
|
logAi->trace(
|
||||||
|
"Can not afford exchange because of total cost %s but we have %s",
|
||||||
|
(actor->armyCost + other->armyCost).toString(),
|
||||||
|
resources.toString());
|
||||||
|
#endif
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TResources availableResources = resources - actor->armyCost - other->armyCost;
|
||||||
|
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
|
||||||
|
HeroExchangeArmy * newArmy;
|
||||||
|
|
||||||
|
if(other->creatureSet->Slots().size())
|
||||||
|
{
|
||||||
|
if(upgradedInitialArmy)
|
||||||
|
{
|
||||||
|
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
|
||||||
|
newArmy->armyCost = upgradedInitialArmy->armyCost;
|
||||||
|
newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
|
||||||
|
|
||||||
|
delete upgradedInitialArmy;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newArmy = upgradedInitialArmy;
|
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
result = new HeroActor(actor, other, newArmy, ai);
|
else
|
||||||
result->armyCost += newArmy->armyCost;
|
{
|
||||||
exchangeMap[other] = result;
|
newArmy = upgradedInitialArmy;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
if(!newArmy) return nullptr;
|
||||||
|
|
||||||
|
auto reinforcement = newArmy->getArmyStrength() - actor->creatureSet->getArmyStrength();
|
||||||
|
|
||||||
|
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||||
|
logAi->trace(
|
||||||
|
"Exchange %s->%s reinforcement: %d, %f%%",
|
||||||
|
actor->toString(),
|
||||||
|
other->toString(),
|
||||||
|
reinforcement,
|
||||||
|
100.0f * reinforcement / actor->armyValue);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(reinforcement <= actor->armyValue / 10 && reinforcement < 1000)
|
||||||
|
{
|
||||||
|
delete newArmy;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
|
||||||
|
|
||||||
|
exchanged->armyCost += newArmy->armyCost;
|
||||||
|
position->second = exchanged;
|
||||||
|
|
||||||
|
return exchanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||||
|
@@ -65,14 +65,13 @@ public:
|
|||||||
|
|
||||||
ChainActor(){}
|
ChainActor(){}
|
||||||
|
|
||||||
virtual bool canExchange(const ChainActor * other) const;
|
|
||||||
virtual std::string toString() const;
|
virtual std::string toString() const;
|
||||||
ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); }
|
ChainActor * tryExchange(const ChainActor * other) const { return tryExchange(this, other); }
|
||||||
void setBaseActor(HeroActor * base);
|
void setBaseActor(HeroActor * base);
|
||||||
virtual const CGObjectInstance * getActorObject() const { return hero; }
|
virtual const CGObjectInstance * getActorObject() const { return hero; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
|
virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HeroExchangeMap
|
class HeroExchangeMap
|
||||||
@@ -80,15 +79,14 @@ class HeroExchangeMap
|
|||||||
private:
|
private:
|
||||||
const HeroActor * actor;
|
const HeroActor * actor;
|
||||||
std::map<const ChainActor *, HeroActor *> exchangeMap;
|
std::map<const ChainActor *, HeroActor *> exchangeMap;
|
||||||
std::map<const ChainActor *, bool> canExchangeCache;
|
|
||||||
const Nullkiller * ai;
|
const Nullkiller * ai;
|
||||||
|
boost::shared_mutex sync;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
|
HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
|
||||||
~HeroExchangeMap();
|
~HeroExchangeMap();
|
||||||
|
|
||||||
HeroActor * exchange(const ChainActor * other);
|
HeroActor * tryExchange(const ChainActor * other);
|
||||||
bool canExchange(const ChainActor * other);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HeroExchangeArmy * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
|
HeroExchangeArmy * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
|
||||||
@@ -113,10 +111,8 @@ public:
|
|||||||
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
|
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
|
||||||
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
|
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
|
||||||
|
|
||||||
virtual bool canExchange(const ChainActor * other) const override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const override;
|
virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObjectActor : public ChainActor
|
class ObjectActor : public ChainActor
|
||||||
|
@@ -143,7 +143,7 @@ namespace AIPathfinding
|
|||||||
destination.node->layer,
|
destination.node->layer,
|
||||||
destinationNode->actor->resourceActor);
|
destinationNode->actor->resourceActor);
|
||||||
|
|
||||||
if(!questNode || questNode.get()->cost < destination.cost)
|
if(!questNode || questNode.get()->getCost() < destination.cost)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -502,7 +502,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
|
|||||||
myCb->unlockGsWhenWaiting = true;
|
myCb->unlockGsWhenWaiting = true;
|
||||||
|
|
||||||
nullkiller->init(CB, playerID);
|
nullkiller->init(CB, playerID);
|
||||||
|
|
||||||
retrieveVisitableObjs();
|
retrieveVisitableObjs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,6 +764,7 @@ void VCAI::makeTurn()
|
|||||||
}
|
}
|
||||||
catch (boost::thread_interrupted & e)
|
catch (boost::thread_interrupted & e)
|
||||||
{
|
{
|
||||||
|
(void)e;
|
||||||
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
|
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1195,7 +1196,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
|||||||
logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
|
logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int i = path.nodes.size() - 1;
|
int i = (int)path.nodes.size() - 1;
|
||||||
|
|
||||||
auto getObj = [&](int3 coord, bool ignoreHero)
|
auto getObj = [&](int3 coord, bool ignoreHero)
|
||||||
{
|
{
|
||||||
@@ -1391,12 +1392,12 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
|
|||||||
|
|
||||||
int toGive, toGet;
|
int toGive, toGet;
|
||||||
m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
||||||
toGive = toGive * (it->resVal / toGive); //round down
|
toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
|
||||||
//TODO trade only as much as needed
|
//TODO trade only as much as needed
|
||||||
if (toGive) //don't try to sell 0 resources
|
if (toGive) //don't try to sell 0 resources
|
||||||
{
|
{
|
||||||
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
|
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
|
||||||
accquiredResources = toGet * (it->resVal / toGive);
|
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
|
||||||
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
|
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
|
||||||
}
|
}
|
||||||
if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
|
if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
|
||||||
@@ -1482,6 +1483,7 @@ void VCAI::finish()
|
|||||||
{
|
{
|
||||||
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
|
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
|
||||||
boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
|
boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
|
||||||
|
|
||||||
if(makingTurn)
|
if(makingTurn)
|
||||||
{
|
{
|
||||||
makingTurn->interrupt();
|
makingTurn->interrupt();
|
||||||
@@ -1617,7 +1619,7 @@ void AIStatus::removeQuery(QueryID ID)
|
|||||||
int AIStatus::getQueriesCount()
|
int AIStatus::getQueriesCount()
|
||||||
{
|
{
|
||||||
boost::unique_lock<boost::mutex> lock(mx);
|
boost::unique_lock<boost::mutex> lock(mx);
|
||||||
return remainingQueries.size();
|
return static_cast<int>(remainingQueries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIStatus::startedTurn()
|
void AIStatus::startedTurn()
|
||||||
|
Reference in New Issue
Block a user