mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-24 03:47:18 +02:00
Nullkiller: tbb and hero chain calculation optimization and parallel cpathfinder initialization
This commit is contained in:
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 * 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)
|
||||
|
@ -137,6 +137,8 @@ else()
|
||||
target_link_libraries(VCAI PRIVATE fl-static vcmi)
|
||||
endif()
|
||||
|
||||
target_link_libraries(VCAI PRIVATE TBB::tbb)
|
||||
|
||||
vcmi_set_output_dir(VCAI "AI")
|
||||
|
||||
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
|
||||
|
@ -44,19 +44,19 @@ struct armyStructure
|
||||
|
||||
armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
||||
{
|
||||
ui64 totalStrenght = army->getArmyStrength();
|
||||
double walkersStrenght = 0;
|
||||
double flyersStrenght = 0;
|
||||
double shootersStrenght = 0;
|
||||
ui64 totalStrength = army->getArmyStrength();
|
||||
double walkersStrength = 0;
|
||||
double flyersStrength = 0;
|
||||
double shootersStrength = 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 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 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);
|
||||
|
||||
for(auto s : army->Slots())
|
||||
@ -65,23 +65,23 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
||||
const CCreature * creature = s.second->type;
|
||||
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
|
||||
{
|
||||
shootersStrenght += s.second->getPower();
|
||||
shootersStrength += s.second->getPower();
|
||||
walker = false;
|
||||
}
|
||||
if(creature->hasBonus(selectorFLYING, keyFLYING))
|
||||
{
|
||||
flyersStrenght += s.second->getPower();
|
||||
flyersStrength += s.second->getPower();
|
||||
walker = false;
|
||||
}
|
||||
if(walker)
|
||||
walkersStrenght += s.second->getPower();
|
||||
walkersStrength += s.second->getPower();
|
||||
|
||||
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
|
||||
}
|
||||
armyStructure as;
|
||||
as.walkers = walkersStrenght / totalStrenght;
|
||||
as.shooters = shootersStrenght / totalStrenght;
|
||||
as.flyers = flyersStrenght / totalStrenght;
|
||||
as.walkers = static_cast<float>(walkersStrength / totalStrength);
|
||||
as.shooters = static_cast<float>(shootersStrength / totalStrength);
|
||||
as.flyers = static_cast<float>(flyersStrength / totalStrength);
|
||||
as.maxSpeed = maxSpeed;
|
||||
assert(as.walkers || as.flyers || as.shooters);
|
||||
return as;
|
||||
|
@ -262,4 +262,4 @@ void Nullkiller::makeTurn()
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "PriorityEvaluator.h"
|
||||
#include "FuzzyHelper.h"
|
||||
#include "AIMemory.h"
|
||||
|
@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -95,11 +95,11 @@ TSubgoal Win::whatToDoToAchieve()
|
||||
{
|
||||
auto towns = cb->getTownsInfo();
|
||||
towns.erase(boost::remove_if(towns,
|
||||
[](const CGTownInstance * t) -> bool
|
||||
[](const CGTownInstance * t) -> bool
|
||||
{
|
||||
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
|
||||
}),
|
||||
towns.end());
|
||||
towns.end());
|
||||
boost::sort(towns, CDistanceSorter(h.get()));
|
||||
if(towns.size())
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include <tbb/tbb.h>
|
||||
#include "AINodeStorage.h"
|
||||
#include "Actions/TownPortalAction.h"
|
||||
#include "../Goals/Goals.h"
|
||||
@ -19,6 +20,8 @@
|
||||
#include "../../../lib/PathfinderUtil.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
|
||||
using namespace tbb;
|
||||
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||
std::set<int3> commitedTiles;
|
||||
std::set<int3> commitedTilesInitial;
|
||||
@ -61,50 +64,56 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
return;
|
||||
|
||||
//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 int3 sizes = gs->getMapSize();
|
||||
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)
|
||||
const bool useFlying = options.useFlying;
|
||||
const bool useWaterWalking = options.useWaterWalking;
|
||||
|
||||
for(pos.x=0; pos.x < sizes.x; ++pos.x)
|
||||
parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
|
||||
{
|
||||
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);
|
||||
switch(tile->terType)
|
||||
for(pos.z = 0; pos.z < sizes.z; ++pos.z)
|
||||
{
|
||||
case ETerrainType::ROCK:
|
||||
break;
|
||||
const TerrainTile * tile = &gs->map->getTile(pos);
|
||||
switch(tile->terType)
|
||||
{
|
||||
case ETerrainType::ROCK:
|
||||
break;
|
||||
|
||||
case ETerrainType::WATER:
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
if(useWaterWalking)
|
||||
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
case ETerrainType::WATER:
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
if(useWaterWalking)
|
||||
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
|
||||
default:
|
||||
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
default:
|
||||
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AINodeStorage::clear()
|
||||
{
|
||||
CCreature::DisableChildLinkage = true;
|
||||
actors.clear();
|
||||
CCreature::DisableChildLinkage = false;
|
||||
heroChainPass = EHeroChainPass::INITIAL;
|
||||
heroChainTurn = 0;
|
||||
heroChainMaxTurns = 1;
|
||||
@ -161,7 +170,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
{
|
||||
if(heroChainPass)
|
||||
{
|
||||
{
|
||||
calculateTownPortalTeleportations(heroChain);
|
||||
|
||||
return heroChain;
|
||||
@ -184,7 +193,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
initialNode->turns = actor->initialTurn;
|
||||
initialNode->moveRemains = actor->initialMovement;
|
||||
initialNode->danger = 0;
|
||||
initialNode->cost = actor->initialTurn;
|
||||
initialNode->setCost(actor->initialTurn);
|
||||
initialNode->action = CGPathNode::ENodeAction::NORMAL;
|
||||
|
||||
if(actor->isMovable)
|
||||
@ -205,7 +214,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
||||
{
|
||||
for(AIPathNode & heroNode : nodes.get(coord, layer))
|
||||
{
|
||||
{
|
||||
heroNode.actor = nullptr;
|
||||
heroNode.danger = 0;
|
||||
heroNode.manaCost = 0;
|
||||
@ -246,7 +255,7 @@ void AINodeStorage::commit(
|
||||
float cost) const
|
||||
{
|
||||
destination->action = action;
|
||||
destination->cost = cost;
|
||||
destination->setCost(cost);
|
||||
destination->moveRemains = movementLeft;
|
||||
destination->turns = turn;
|
||||
destination->armyLoss = source->armyLoss;
|
||||
@ -360,61 +369,133 @@ bool AINodeStorage::calculateHeroChainFinal()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
{
|
||||
heroChainPass = EHeroChainPass::CHAIN;
|
||||
heroChain.resize(0);
|
||||
heroChain.clear();
|
||||
|
||||
std::vector<AIPathNode *> existingChains;
|
||||
std::vector<ExchangeCandidate> newChains;
|
||||
std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
|
||||
|
||||
existingChains.reserve(NUM_CHAINS);
|
||||
newChains.reserve(NUM_CHAINS);
|
||||
CCreature::DisableChildLinkage = true;
|
||||
|
||||
for(auto & pos : commitedTiles)
|
||||
{
|
||||
for(auto layer : phisycalLayers)
|
||||
{
|
||||
auto chains = nodes.get(pos, layer);
|
||||
auto r = blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
|
||||
|
||||
// fast cut inactive nodes
|
||||
if(chains[0].blocked())
|
||||
continue;
|
||||
task.execute(r);
|
||||
task.flushResult(heroChain);
|
||||
|
||||
existingChains.clear();
|
||||
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);
|
||||
}
|
||||
}
|
||||
CCreature::DisableChildLinkage = false;
|
||||
|
||||
commitedTiles.clear();
|
||||
|
||||
return heroChain.size();
|
||||
return !heroChain.empty();
|
||||
}
|
||||
|
||||
bool AINodeStorage::selectFirstActor()
|
||||
{
|
||||
if(!actors.size())
|
||||
if(actors.empty())
|
||||
return false;
|
||||
|
||||
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->get()->armyValue < 1000)
|
||||
return false;
|
||||
|
||||
chainMask = nextActor->get()->chainMask;
|
||||
commitedTiles = commitedTilesInitial;
|
||||
|
||||
@ -463,22 +547,36 @@ bool AINodeStorage::selectNextActor()
|
||||
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
|
||||
{
|
||||
auto pos = chainInfo.coord;
|
||||
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)
|
||||
|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
|
||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||
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,
|
||||
const std::vector<AIPathNode *> & variants,
|
||||
std::vector<ExchangeCandidate> & result) const
|
||||
std::vector<ExchangeCandidate> & result)
|
||||
{
|
||||
for(AIPathNode * node : variants)
|
||||
{
|
||||
@ -531,16 +629,15 @@ void AINodeStorage::calculateHeroChain(
|
||||
}
|
||||
}
|
||||
|
||||
void AINodeStorage::calculateHeroChain(
|
||||
void HeroChainCalculationTask::calculateHeroChain(
|
||||
AIPathNode * carrier,
|
||||
AIPathNode * other,
|
||||
std::vector<ExchangeCandidate> & result) const
|
||||
std::vector<ExchangeCandidate> & result)
|
||||
{
|
||||
if(carrier->armyLoss < carrier->actor->armyValue
|
||||
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
|
||||
&& carrier->action != CGPathNode::BLOCKING_VISIT
|
||||
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
|
||||
&& carrier->actor->canExchange(other->actor))
|
||||
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
|
||||
{
|
||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||
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)
|
||||
{
|
||||
auto carrier = chainInfo.carrierParent;
|
||||
auto newActor = chainInfo.actor;
|
||||
auto other = chainInfo.otherParent;
|
||||
auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
|
||||
auto chainNodeOptional = storage.getOrCreateNode(carrier->coord, carrier->layer, newActor);
|
||||
|
||||
if(!chainNodeOptional)
|
||||
{
|
||||
@ -594,24 +691,34 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
||||
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
|
||||
{
|
||||
#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
|
||||
continue;
|
||||
}
|
||||
|
||||
if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
|
||||
if(exchangeNode->turns != 0xFF && exchangeNode->getCost() < chainInfo.getCost())
|
||||
{
|
||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Exchange at %s is is not effective enough. %f < %f",
|
||||
exchangeNode->coord.toString(),
|
||||
exchangeNode->getCost(),
|
||||
"Skip exchange %s[%x] -> %s[%x] at %s because not effective enough. %f < %f",
|
||||
other->actor->toString(),
|
||||
other->actor->chainMask,
|
||||
carrier->actor->toString(),
|
||||
carrier->actor->chainMask,
|
||||
carrier->coord.toString(),
|
||||
exchangeNode->getCost(),
|
||||
chainInfo.getCost());
|
||||
#endif
|
||||
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)
|
||||
{
|
||||
@ -644,7 +751,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
||||
}
|
||||
}
|
||||
|
||||
ExchangeCandidate AINodeStorage::calculateExchange(
|
||||
ExchangeCandidate HeroChainCalculationTask::calculateExchange(
|
||||
ChainActor * exchangeActor,
|
||||
AIPathNode * carrierParentNode,
|
||||
AIPathNode * otherParentNode) const
|
||||
@ -658,7 +765,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
|
||||
candidate.actor = exchangeActor;
|
||||
candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
|
||||
candidate.turns = carrierParentNode->turns;
|
||||
candidate.cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
|
||||
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
|
||||
candidate.moveRemains = carrierParentNode->moveRemains;
|
||||
|
||||
if(carrierParentNode->turns < otherParentNode->turns)
|
||||
@ -668,7 +775,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
|
||||
+ carrierParentNode->moveRemains / (float)moveRemains;
|
||||
|
||||
candidate.turns = otherParentNode->turns;
|
||||
candidate.cost += waitingCost;
|
||||
candidate.setCost(candidate.getCost() + waitingCost);
|
||||
candidate.moveRemains = moveRemains;
|
||||
}
|
||||
|
||||
@ -873,7 +980,7 @@ struct TowmPortalFinder
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!bestNode || bestNode->cost > node->cost)
|
||||
if(!bestNode || bestNode->getCost() > node->getCost())
|
||||
bestNode = node;
|
||||
}
|
||||
|
||||
@ -895,9 +1002,9 @@ struct TowmPortalFinder
|
||||
AIPathNode * node = nodeOptional.get();
|
||||
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(
|
||||
node,
|
||||
@ -1009,7 +1116,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
|
||||
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
|
||||
logAi->trace(
|
||||
@ -1033,7 +1140,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
|
||||
|
||||
if(nodeArmyValue > candidateArmyValue
|
||||
&& node.cost <= candidateNode->cost)
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
{
|
||||
#if PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
@ -1052,10 +1159,10 @@ bool AINodeStorage::hasBetterChain(
|
||||
{
|
||||
if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.cost <= candidateNode->cost)
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
{
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.cost == candidateNode->cost
|
||||
&& node.getCost() == candidateNode->getCost()
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
@ -1141,7 +1248,8 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
||||
//if(node->actor->hero->visitablePos() != node->coord)
|
||||
{
|
||||
AIPathNodeInfo pathNode;
|
||||
pathNode.cost = node->cost;
|
||||
|
||||
pathNode.cost = node->getCost();
|
||||
pathNode.targetHero = node->actor->hero;
|
||||
pathNode.chainMask = node->actor->chainMask;
|
||||
pathNode.specialAction = node->specialAction;
|
||||
|
@ -23,6 +23,14 @@
|
||||
#include "Actions/SpecialAction.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
|
||||
{
|
||||
uint64_t danger;
|
||||
@ -228,28 +236,14 @@ public:
|
||||
return (uint64_t)(armyValue * ratio * ratio * ratio);
|
||||
}
|
||||
|
||||
private:
|
||||
STRONG_INLINE
|
||||
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 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);
|
||||
#endif
|
||||
|
||||
return hero->mana >= source->manaCost + getManaCost(hero);
|
||||
return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
|
||||
}
|
||||
|
||||
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 isMovable && baseActor->canExchange(other);
|
||||
return baseActor->tryExchange(specialActor, other);
|
||||
}
|
||||
|
||||
namespace vstd
|
||||
@ -172,71 +169,12 @@ namespace vstd
|
||||
}
|
||||
}
|
||||
|
||||
bool HeroActor::canExchange(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
|
||||
ChainActor * HeroActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
|
||||
{
|
||||
const ChainActor * otherBase = other->baseActor;
|
||||
HeroActor * result = exchangeMap->exchange(otherBase);
|
||||
HeroActor * result = exchangeMap->tryExchange(otherBase);
|
||||
|
||||
if(!result) return nullptr;
|
||||
|
||||
if(specialActor == this)
|
||||
return result;
|
||||
@ -250,7 +188,7 @@ ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainAct
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if(!exchange.second) continue;
|
||||
|
||||
delete exchange.second->creatureSet;
|
||||
delete exchange.second;
|
||||
}
|
||||
@ -265,44 +205,91 @@ HeroExchangeMap::~HeroExchangeMap()
|
||||
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))
|
||||
result = exchangeMap.at(other);
|
||||
else
|
||||
if(position != exchangeMap.end())
|
||||
{
|
||||
TResources availableResources = ai->cb->getResourceAmount() - 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;
|
||||
return position->second;
|
||||
}
|
||||
|
||||
delete upgradedInitialArmy;
|
||||
}
|
||||
else
|
||||
{
|
||||
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
|
||||
}
|
||||
auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
|
||||
|
||||
if(!inserted.second)
|
||||
{
|
||||
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
|
||||
{
|
||||
newArmy = upgradedInitialArmy;
|
||||
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
|
||||
}
|
||||
|
||||
result = new HeroActor(actor, other, newArmy, ai);
|
||||
result->armyCost += newArmy->armyCost;
|
||||
exchangeMap[other] = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
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(
|
||||
|
@ -65,14 +65,13 @@ public:
|
||||
|
||||
ChainActor(){}
|
||||
|
||||
virtual bool canExchange(const ChainActor * other) 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);
|
||||
virtual const CGObjectInstance * getActorObject() const { return hero; }
|
||||
|
||||
protected:
|
||||
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
|
||||
virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const;
|
||||
};
|
||||
|
||||
class HeroExchangeMap
|
||||
@ -80,15 +79,14 @@ class HeroExchangeMap
|
||||
private:
|
||||
const HeroActor * actor;
|
||||
std::map<const ChainActor *, HeroActor *> exchangeMap;
|
||||
std::map<const ChainActor *, bool> canExchangeCache;
|
||||
const Nullkiller * ai;
|
||||
boost::shared_mutex sync;
|
||||
|
||||
public:
|
||||
HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
|
||||
~HeroExchangeMap();
|
||||
|
||||
HeroActor * exchange(const ChainActor * other);
|
||||
bool canExchange(const ChainActor * other);
|
||||
HeroActor * tryExchange(const ChainActor * other);
|
||||
|
||||
private:
|
||||
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 ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
|
||||
|
||||
virtual bool canExchange(const ChainActor * other) const override;
|
||||
|
||||
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
|
||||
|
@ -143,7 +143,7 @@ namespace AIPathfinding
|
||||
destination.node->layer,
|
||||
destinationNode->actor->resourceActor);
|
||||
|
||||
if(!questNode || questNode.get()->cost < destination.cost)
|
||||
if(!questNode || questNode.get()->getCost() < destination.cost)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
|
||||
myCb->unlockGsWhenWaiting = true;
|
||||
|
||||
nullkiller->init(CB, playerID);
|
||||
|
||||
|
||||
retrieveVisitableObjs();
|
||||
}
|
||||
|
||||
@ -764,6 +764,7 @@ void VCAI::makeTurn()
|
||||
}
|
||||
catch (boost::thread_interrupted & e)
|
||||
{
|
||||
(void)e;
|
||||
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
|
||||
return;
|
||||
}
|
||||
@ -1195,7 +1196,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
|
||||
return true;
|
||||
}
|
||||
int i = path.nodes.size() - 1;
|
||||
int i = (int)path.nodes.size() - 1;
|
||||
|
||||
auto getObj = [&](int3 coord, bool ignoreHero)
|
||||
{
|
||||
@ -1391,12 +1392,12 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
|
||||
|
||||
int toGive, toGet;
|
||||
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
|
||||
if (toGive) //don't try to sell 0 resources
|
||||
{
|
||||
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());
|
||||
}
|
||||
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
|
||||
boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
|
||||
|
||||
if(makingTurn)
|
||||
{
|
||||
makingTurn->interrupt();
|
||||
@ -1617,7 +1619,7 @@ void AIStatus::removeQuery(QueryID ID)
|
||||
int AIStatus::getQueriesCount()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mx);
|
||||
return remainingQueries.size();
|
||||
return static_cast<int>(remainingQueries.size());
|
||||
}
|
||||
|
||||
void AIStatus::startedTurn()
|
||||
|
Loading…
x
Reference in New Issue
Block a user