1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

VCAI performance improvements

* Node graph initialization optimized.
* Fixed "Unathorized obstacle access".
* Pathfinding tracing disabled with ifdef.
* Misc bonus calculation optimizations.
* Removed timestamp from log lines. Date formatting eats too much CPU.
* Paths for all heroes in Client are now cached
This commit is contained in:
AlexVinS 2019-01-15 06:00:00 +03:00
parent 8fee46de7c
commit 4b5910c2f4
25 changed files with 472 additions and 347 deletions

View File

@ -50,15 +50,25 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
double shootersStrenght = 0;
ui32 maxSpeed = 0;
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 std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
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())
{
bool walker = true;
if(s.second->type->hasBonusOfType(Bonus::SHOOTER))
const CCreature * creature = s.second->type;
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
{
shootersStrenght += s.second->getPower();
walker = false;
}
if(s.second->type->hasBonusOfType(Bonus::FLYING))
if(creature->hasBonus(selectorFLYING, keyFLYING))
{
flyersStrenght += s.second->getPower();
walker = false;
@ -66,7 +76,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
if(walker)
walkersStrenght += s.second->getPower();
vstd::amax(maxSpeed, s.second->type->valOfBonuses(Bonus::STACKS_SPEED));
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
}
armyStructure as;
as.walkers = walkersStrenght / totalStrenght;
@ -79,12 +89,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const
{
const float maxMovePoints = (float)goal.hero->maxMovePoints(true);
if(goal.evaluationContext.movementCost != 0)
{
return goal.evaluationContext.movementCost / (float)goal.hero->maxMovePoints(true);
return goal.evaluationContext.movementCost / maxMovePoints;
}
return distanceToTile(goal.hero.h, goal.tile) / (float)goal.hero->maxMovePoints(true);
return distanceToTile(goal.hero.h, goal.tile) / maxMovePoints;
}
TacticalAdvantageEngine::TacticalAdvantageEngine()

View File

@ -244,6 +244,7 @@ TSubgoal Explore::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) con
auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp
int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h);
if(tilesDiscovered > 0)
dstToRevealedTiles[tile] = tilesDiscovered / distance;
}
}
@ -371,9 +372,9 @@ TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const
int radius = h->getSightRadius();
int3 hpos = h->visitablePos();
//look for nearby objs -> visit them if they're close enouh
//look for nearby objs -> visit them if they're close enough
const int DIST_LIMIT = 3;
const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement
const int MP_LIMIT = DIST_LIMIT * 150; // approximate cost of diagonal movement
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map

View File

@ -14,16 +14,61 @@
#include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/PathfinderUtil.h"
#include "../../../lib/CPlayerState.h"
extern boost::thread_specific_ptr<CCallback> cb;
AINodeStorage::AINodeStorage(const int3 & Sizes)
: sizes(Sizes)
{
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
}
AINodeStorage::~AINodeStorage()
AINodeStorage::~AINodeStorage() = default;
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
{
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
const PlayerColor player = hero->tempOwner;
//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)
{
for(pos.y=0; pos.y < sizes.y; ++pos.y)
{
for(pos.z=0; pos.z < sizes.z; ++pos.z)
{
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;
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;
}
}
}
}
}
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
@ -97,7 +142,8 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
{
const AIPathNode * srcNode = getAINode(source.node);
updateAINode(destination.node, [&](AIPathNode * dstNode) {
updateAINode(destination.node, [&](AIPathNode * dstNode)
{
dstNode->moveRemains = destination.movementLeft;
dstNode->turns = destination.turn;
dstNode->danger = srcNode->danger;
@ -287,7 +333,6 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
destinationNode->chainMask,
node.moveRemains - destinationNode->moveRemains);
#endif
return true;
}
}

View File

@ -76,6 +76,9 @@ private:
boost::multi_array<AIPathNode, 5> nodes;
const CGHeroInstance * hero;
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
public:
/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
static const int NUM_CHAINS = 3;
@ -89,8 +92,9 @@ public:
AINodeStorage(const int3 & sizes);
~AINodeStorage();
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
virtual CGPathNode * getInitialNode() override;
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,

View File

@ -76,12 +76,14 @@ namespace AIPathfinding
bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Hero %s has %d mana and needed %d and already spent %d",
hero->name,
hero->mana,
getManaCost(hero),
source->manaCost);
#endif
return hero->mana >= source->manaCost + getManaCost(hero);
}
@ -148,7 +150,9 @@ namespace AIPathfinding
if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat))
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
#endif
}
}
}
@ -242,15 +246,17 @@ namespace AIPathfinding
}
else
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Special transition node already allocated. Blocked moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
}
}
else
{
logAi->trace(
logAi->debug(
"Can not allocate special transition node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
@ -330,10 +336,12 @@ namespace AIPathfinding
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
{
//logAi->trace(
// "Bypass guard at destination while moving %s -> %s",
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
@ -346,10 +354,12 @@ namespace AIPathfinding
if(!battleNodeOptional)
{
//logAi->trace(
// "Can not allocate battle node while moving %s -> %s",
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Can not allocate battle node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
@ -360,11 +370,12 @@ namespace AIPathfinding
if(battleNode->locked)
{
//logAi->trace(
// "Block bypass guard at destination while moving %s -> %s",
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Block bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
return;
@ -382,13 +393,13 @@ namespace AIPathfinding
}
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
//logAi->trace(
// "Begin bypass guard at destination with danger %s while moving %s -> %s",
// std::to_string(danger),
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Begin bypass guard at destination with danger %s while moving %s -> %s",
std::to_string(danger),
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
@ -428,11 +439,12 @@ namespace AIPathfinding
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
{
//logAi->trace(
// "Bypass src guard while moving from %s to %s",
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Bypass src guard while moving from %s to %s",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
@ -462,12 +474,12 @@ namespace AIPathfinding
{
// we can not directly bypass objects, we need to interact with them first
destination.node->theNodeBefore = source.node;
//logAi->trace(
// "Link src node %s to destination node %s while bypassing visitable obj",
// source.coord.toString(),
// destination.coord.toString());
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Link src node %s to destination node %s while bypassing visitable obj",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}

View File

@ -124,14 +124,16 @@ Goals::TGoalVec PathfindingManager::findPath(
std::vector<AIPath> chainInfo = pathfinder->getPathInfo(hero, dest);
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());
#endif
for(auto path : chainInfo)
{
int3 firstTileToGet = path.firstTileToGet();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
#endif
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
{
danger = path.getTotalDanger(hero);
@ -158,9 +160,9 @@ Goals::TGoalVec PathfindingManager::findPath(
solution->evaluationContext.danger = danger;
solution->evaluationContext.movementCost += path.movementCost();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name());
#endif
result.push_back(solution);
continue;
@ -178,7 +180,9 @@ Goals::TGoalVec PathfindingManager::findPath(
if(allowGatherArmy && danger > 0)
{
//we need to get army in order to conquer that place
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger));
#endif
result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)));
}

View File

@ -1313,7 +1313,7 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::option
if(movementCostLimit && movementCostLimit.get() < path.movementCost())
return false;
if(ai->isGoodForVisit(obj, h, path))
if(isGoodForVisit(obj, h, path))
return true;
}
@ -1330,7 +1330,7 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath
return false;
if (obj->wasVisited(playerID))
return false;
if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
if (cb->getPlayerRelations(playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
return false; // Otherwise we flag or get weekly resources / creatures
if (!isSafeToVisit(h, pos))
return false;

View File

@ -286,7 +286,7 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
return gs->map->canMoveBetween(a, b);
}
const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h)
std::shared_ptr<const CPathsInfo> CCallback::getPathsInfo(const CGHeroInstance * h)
{
return cl->getPathsInfo(h);
}

View File

@ -106,7 +106,7 @@ public:
//client-specific functionalities (pathfinding)
virtual bool canMoveBetween(const int3 &a, const int3 &b);
virtual int3 getGuardingCreaturePosition(int3 tile);
virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
virtual std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);

View File

@ -109,7 +109,6 @@ public:
CClient::CClient()
{
waitingRequest.clear();
pathInfo = nullptr;
applier = std::make_shared<CApplier<CBaseForCLApply>>();
registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier);
@ -316,7 +315,8 @@ void CClient::initMapHandler()
CGI->mh->init();
logNetwork->trace("Initializing mapHandler (together): %d ms", CSH->th->getDiff());
}
pathInfo = make_unique<CPathsInfo>(getMapSize());
pathCache.clear();
}
void CClient::initPlayerInterfaces()
@ -612,20 +612,30 @@ void CClient::waitForMoveAndSend(PlayerColor color)
void CClient::invalidatePaths()
{
// turn pathfinding info into invalid. It will be regenerated later
boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
pathInfo->hero = nullptr;
boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
pathCache.clear();
}
const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance * h)
std::shared_ptr<const CPathsInfo> CClient::getPathsInfo(const CGHeroInstance * h)
{
assert(h);
boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
if(pathInfo->hero != h)
boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
auto iter = pathCache.find(h);
if(iter == std::end(pathCache))
{
gs->calculatePaths(h, *pathInfo.get());
std::shared_ptr<CPathsInfo> paths = std::make_shared<CPathsInfo>(getMapSize(), h);
gs->calculatePaths(h, *paths.get());
pathCache[h] = paths;
return paths;
}
else
{
return iter->second;
}
return pathInfo.get();
}
PlayerColor CClient::getLocalPlayer() const

View File

@ -99,7 +99,9 @@ public:
class CClient : public IGameCallback
{
std::shared_ptr<CApplier<CBaseForCLApply>> applier;
std::unique_ptr<CPathsInfo> pathInfo;
mutable boost::mutex pathCacheMutex;
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
void waitForMoveAndSend(PlayerColor color);
@ -150,7 +152,7 @@ public:
void stopAllBattleActions();
void invalidatePaths();
const CPathsInfo * getPathsInfo(const CGHeroInstance * h);
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
virtual PlayerColor getLocalPlayer() const override;
friend class CCallback; //handling players actions

View File

@ -13,6 +13,7 @@
<Option object_output="../obj/Client/Debug/x86" />
<Option type="1" />
<Option compiler="gcc" />
<Option parameters="--testmap=Maps/Reclamation --spectate --spectate-skip-battle --spectate-ignore-hero" />
<Compiler>
<Add option="-Og" />
<Add option="-g" />

View File

@ -3644,7 +3644,7 @@ std::shared_ptr<IImage> CBattleInterface::getObstacleImage(const CObstacleInstan
if(cacheIter == animationsCache.end())
{
logAi->trace("Creating obstacle animation %s", animationName);
logAnim->trace("Creating obstacle animation %s", animationName);
animation = std::make_shared<CAnimation>(animationName);
animation->preload();

View File

@ -331,7 +331,7 @@
"properties" : {
"format" : {
"type" : "string",
"default" : "%d %l %n [%t] - %m"
"default" : "%l %n [%t] - %m"
}
}
},

View File

@ -341,6 +341,7 @@ set(lib_HEADERS
NetPacksBase.h
NetPacks.h
NetPacksLobby.h
PathfinderUtil.h
ResourceSet.h
ScopeGuard.h
StartInfo.h

View File

@ -17,7 +17,8 @@
#include "GameConstants.h"
#include "CStopWatch.h"
#include "CConfigHandler.h"
#include "../lib/CPlayerState.h"
#include "CPlayerState.h"
#include "PathfinderUtil.h"
bool canSeeObj(const CGObjectInstance * obj)
{
@ -25,6 +26,50 @@ bool canSeeObj(const CGObjectInstance * obj)
return obj != nullptr && obj->ID != Obj::EVENT;
}
void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
{
//TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
const PlayerColor player = hero->tempOwner;
//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)
{
for(pos.y=0; pos.y < sizes.y; ++pos.y)
{
for(pos.z=0; pos.z < sizes.z; ++pos.z)
{
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;
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;
}
}
}
}
}
std::vector<CGPathNode *> NodeStorage::calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
@ -102,11 +147,6 @@ NodeStorage::NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero)
out.hpos = hero->getPosition(false);
}
CGPathNode * NodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer)
{
return out.getNode(coord, layer);
}
void NodeStorage::resetTile(
const int3 & tile,
EPathfindingLayer layer,
@ -221,7 +261,7 @@ CPathfinder::CPathfinder(
std::shared_ptr<PathfinderConfig> config)
: CGameInfoCallback(_gs, boost::optional<PlayerColor>())
, hero(_hero)
, FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({})
, patrolTiles({})
, config(config)
, source()
, destination()
@ -702,7 +742,9 @@ void DestinationActionRule::process(
{
if(destination.action != CGPathNode::ENodeAction::UNKNOWN)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Accepted precalculated action at %s", destination.coord.toString());
#endif
return;
}
@ -851,106 +893,8 @@ void CPathfinder::initializePatrol()
void CPathfinder::initializeGraph()
{
auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo)
{
auto accessibility = evaluateAccessibility(pos, tinfo, layer);
config->nodeStorage->resetTile(pos, layer, accessibility);
};
int3 pos;
int3 sizes = gs->getMapSize();
for(pos.x=0; pos.x < sizes.x; ++pos.x)
{
for(pos.y=0; pos.y < sizes.y; ++pos.y)
{
for(pos.z=0; pos.z < sizes.z; ++pos.z)
{
const TerrainTile * tinfo = &gs->map->getTile(pos);
switch(tinfo->terType)
{
case ETerrainType::ROCK:
break;
case ETerrainType::WATER:
updateNode(pos, ELayer::SAIL, tinfo);
if(config->options.useFlying)
updateNode(pos, ELayer::AIR, tinfo);
if(config->options.useWaterWalking)
updateNode(pos, ELayer::WATER, tinfo);
break;
default:
updateNode(pos, ELayer::LAND, tinfo);
if(config->options.useFlying)
updateNode(pos, ELayer::AIR, tinfo);
break;
}
}
}
}
}
CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const
{
if(tinfo->terType == ETerrainType::ROCK || !FoW[pos.x][pos.y][pos.z])
return CGPathNode::BLOCKED;
switch(layer)
{
case ELayer::LAND:
case ELayer::SAIL:
if(tinfo->visitable)
{
if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary
{
return CGPathNode::BLOCKED;
}
else
{
for(const CGObjectInstance * obj : tinfo->visitableObjects)
{
if(obj->blockVisit)
{
return CGPathNode::BLOCKVIS;
}
else if(obj->passableFor(hero->tempOwner))
{
return CGPathNode::ACCESSIBLE;
}
else if(canSeeObj(obj))
{
return CGPathNode::VISITABLE;
}
}
}
}
else if(tinfo->blocked)
{
return CGPathNode::BLOCKED;
}
else if(gs->guardingCreaturePosition(pos).valid())
{
// Monster close by; blocked visit for battle
return CGPathNode::BLOCKVIS;
}
break;
case ELayer::WATER:
if(tinfo->blocked || tinfo->terType != ETerrainType::WATER)
return CGPathNode::BLOCKED;
break;
case ELayer::AIR:
if(tinfo->blocked || tinfo->terType == ETerrainType::WATER)
return CGPathNode::FLYABLE;
break;
}
return CGPathNode::ACCESSIBLE;
INodeStorage * nodeStorage = config->nodeStorage.get();
nodeStorage->initialize(config->options, gs, hero);
}
bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const
@ -1055,10 +999,7 @@ TurnInfo::BonusCache::BonusCache(TBonusListPtr bl)
TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn)
: hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1)
{
std::stringstream cachingStr;
cachingStr << "days_" << turn;
bonuses = hero->getAllBonuses(Selector::days(turn), nullptr, nullptr, cachingStr.str());
bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, "");
bonusCache = make_unique<BonusCache>(bonuses);
nativeTerrain = hero->getNativeTerrain();
}
@ -1309,40 +1250,6 @@ int CPathfinderHelper::getMovementCost(
return ret;
}
CGPathNode::CGPathNode()
: coord(int3(-1, -1, -1)), layer(ELayer::WRONG)
{
reset();
}
void CGPathNode::reset()
{
locked = false;
accessible = NOT_SET;
moveRemains = 0;
turns = 255;
theNodeBefore = nullptr;
action = UNKNOWN;
}
void CGPathNode::update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible)
{
if(layer == ELayer::WRONG)
{
coord = Coord;
layer = Layer;
}
else
reset();
accessible = Accessible;
}
bool CGPathNode::reachable() const
{
return turns < 255;
}
int3 CGPath::startPos() const
{
return nodes[nodes.size()-1].coord;
@ -1364,16 +1271,13 @@ void CGPath::convert(ui8 mode)
}
}
CPathsInfo::CPathsInfo(const int3 & Sizes)
: sizes(Sizes)
CPathsInfo::CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_)
: sizes(Sizes), hero(hero_)
{
hero = nullptr;
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][ELayer::NUM_LAYERS]);
}
CPathsInfo::~CPathsInfo()
{
}
CPathsInfo::~CPathsInfo() = default;
const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const
{
@ -1381,14 +1285,11 @@ const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const
assert(vstd::iswithin(tile.y, 0, sizes.y));
assert(vstd::iswithin(tile.z, 0, sizes.z));
boost::unique_lock<boost::mutex> pathLock(pathMx);
return getNode(tile);
}
bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const
{
boost::unique_lock<boost::mutex> pathLock(pathMx);
out.nodes.clear();
const CGPathNode * curnode = getNode(dst);
if(!curnode->theNodeBefore)
@ -1405,8 +1306,6 @@ bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const
int CPathsInfo::getDistance(const int3 & tile) const
{
boost::unique_lock<boost::mutex> pathLock(pathMx);
CGPath ret;
if(getPath(ret, tile))
return ret.nodes.size();
@ -1423,11 +1322,6 @@ const CGPathNode * CPathsInfo::getNode(const int3 & coord) const
return &nodes[coord.x][coord.y][coord.z][ELayer::SAIL];
}
CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer)
{
return &nodes[coord.x][coord.y][coord.z][layer];
}
PathNodeInfo::PathNodeInfo()
: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false)
{

View File

@ -63,10 +63,45 @@ struct DLL_LINKAGE CGPathNode
ENodeAction action;
bool locked;
CGPathNode();
void reset();
void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible);
bool reachable() const;
CGPathNode()
: coord(int3(-1, -1, -1)),
layer(ELayer::WRONG)
{
reset();
}
STRONG_INLINE
void reset()
{
locked = false;
accessible = NOT_SET;
moveRemains = 0;
turns = 255;
theNodeBefore = nullptr;
action = UNKNOWN;
}
STRONG_INLINE
void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible)
{
if(layer == ELayer::WRONG)
{
coord = Coord;
layer = Layer;
}
else
{
reset();
}
accessible = Accessible;
}
STRONG_INLINE
bool reachable() const
{
return turns < 255;
}
};
struct DLL_LINKAGE CGPath
@ -82,21 +117,23 @@ struct DLL_LINKAGE CPathsInfo
{
typedef EPathfindingLayer ELayer;
mutable boost::mutex pathMx;
const CGHeroInstance * hero;
int3 hpos;
int3 sizes;
boost::multi_array<CGPathNode, 4> nodes; //[w][h][level][layer]
CPathsInfo(const int3 & Sizes);
CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_);
~CPathsInfo();
const CGPathNode * getPathInfo(const int3 & tile) const;
bool getPath(CGPath & out, const int3 & dst) const;
int getDistance(const int3 & tile) const;
const CGPathNode * getNode(const int3 & coord) const;
CGPathNode * getNode(const int3 & coord, const ELayer layer);
STRONG_INLINE
CGPathNode * getNode(const int3 & coord, const ELayer layer)
{
return &nodes[coord.x][coord.y][coord.z][layer];
}
};
struct DLL_LINKAGE PathNodeInfo
@ -230,55 +267,6 @@ protected:
const CPathfinderHelper * pathfinderHelper) const override;
};
class INodeStorage
{
public:
virtual CGPathNode * getInitialNode() = 0;
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) = 0;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) = 0;
virtual std::vector<CGPathNode *> calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) = 0;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0;
};
class DLL_LINKAGE NodeStorage : public INodeStorage
{
private:
CPathsInfo & out;
public:
NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero);
CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer);
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual std::vector<CGPathNode *> calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual void resetTile(
const int3 & tile,
EPathfindingLayer layer,
CGPathNode::EAccessibility accessibility) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
};
struct DLL_LINKAGE PathfinderOptions
{
bool useFlying;
@ -330,6 +318,61 @@ struct DLL_LINKAGE PathfinderOptions
PathfinderOptions();
};
class DLL_LINKAGE INodeStorage
{
public:
using ELayer = EPathfindingLayer;
virtual CGPathNode * getInitialNode() = 0;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) = 0;
virtual std::vector<CGPathNode *> calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) = 0;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0;
virtual void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) = 0;
};
class DLL_LINKAGE NodeStorage : public INodeStorage
{
private:
CPathsInfo & out;
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
public:
NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero);
STRONG_INLINE
CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer)
{
return out.getNode(coord, layer);
}
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual std::vector<CGPathNode *> calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
};
class DLL_LINKAGE PathfinderConfig
{
public:
@ -359,7 +402,6 @@ private:
typedef EPathfindingLayer ELayer;
const CGHeroInstance * hero;
const std::vector<std::vector<std::vector<ui8> > > &FoW;
std::unique_ptr<CPathfinderHelper> hlp;
std::shared_ptr<PathfinderConfig> config;
@ -400,8 +442,6 @@ private:
void initializePatrol();
void initializeGraph();
CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
};
struct DLL_LINKAGE TurnInfo

View File

@ -474,14 +474,14 @@ int IBonusBearer::valOfBonuses(Bonus::BonusType type, const CSelector &selector)
int IBonusBearer::valOfBonuses(Bonus::BonusType type, int subtype) const
{
std::stringstream cachingStr;
cachingStr << "type_" << type << "s_" << subtype;
boost::format fmt("type_%ds_%d");
fmt % (int)type % subtype;
CSelector s = Selector::type(type);
if(subtype != -1)
s = s.And(Selector::subtype(subtype));
return valOfBonuses(s, cachingStr.str());
return valOfBonuses(s, fmt.str());
}
int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
@ -502,14 +502,14 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, c
bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype) const
{
std::stringstream cachingStr;
cachingStr << "type_" << type << "s_" << subtype;
boost::format fmt("type_%ds_%d");
fmt % (int)type % subtype;
CSelector s = Selector::type(type);
if(subtype != -1)
s = s.And(Selector::subtype(subtype));
return hasBonus(s, cachingStr.str());
return hasBonus(s, fmt.str());
}
const TBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
@ -524,9 +524,10 @@ const TBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CS
bool IBonusBearer::hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const
{
std::stringstream cachingStr;
cachingStr << "source_" << source << "id_" << sourceID;
return hasBonus(Selector::source(source,sourceID), cachingStr.str());
boost::format fmt("source_%did_%d");
fmt % (int)source % sourceID;
return hasBonus(Selector::source(source,sourceID), fmt.str());
}
int IBonusBearer::MoraleVal() const
@ -605,7 +606,12 @@ si32 IBonusBearer::manaLimit() const
int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
{
int ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
static const CSelector selectorAllSkills = Selector::type(Bonus::PRIMARY_SKILL);
static const std::string keyAllSkills = "type_PRIMARY_SKILL";
auto allSkills = getBonuses(selectorAllSkills, keyAllSkills);
int ret = allSkills->valOfBonuses(Selector::subtype(id));
vstd::amax(ret, id/2); //minimal value is 0 for attack and defense and 1 for spell power and knowledge
return ret;
@ -634,11 +640,12 @@ ui32 IBonusBearer::Speed(int turn, bool useBind) const
bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" bonus in stack experience documentation
{
std::stringstream cachingStr;
cachingStr << "type_" << Bonus::UNDEAD << "s_-1Otype_" << Bonus::NON_LIVING << "s_-11type_" << Bonus::SIEGE_WEAPON; //I don't really get what string labels mean?
return !hasBonus(Selector::type(Bonus::UNDEAD)
static const std::string cachingStr = "IBonusBearer::isLiving";
static const CSelector selector = Selector::type(Bonus::UNDEAD)
.Or(Selector::type(Bonus::NON_LIVING))
.Or(Selector::type(Bonus::SIEGE_WEAPON)), cachingStr.str());
.Or(Selector::type(Bonus::SIEGE_WEAPON));
return !hasBonus(selector, cachingStr);
}
const std::shared_ptr<Bonus> IBonusBearer::getBonus(const CSelector &selector) const

76
lib/PathfinderUtil.h Normal file
View File

@ -0,0 +1,76 @@
/*
* PathfinderUtil.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "mapping/CMapDefines.h"
#include "CGameState.h"
namespace PathfinderUtil
{
using FoW = std::vector<std::vector<std::vector<ui8> > >;
using ELayer = EPathfindingLayer;
template<EPathfindingLayer::EEPathfindingLayer layer>
CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const FoW & fow, const PlayerColor player, const CGameState * gs)
{
if(!fow[pos.x][pos.y][pos.z])
return CGPathNode::BLOCKED;
switch(layer)
{
case ELayer::LAND:
case ELayer::SAIL:
if(tinfo->visitable)
{
if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary
{
return CGPathNode::BLOCKED;
}
else
{
for(const CGObjectInstance * obj : tinfo->visitableObjects)
{
if(obj->blockVisit)
return CGPathNode::BLOCKVIS;
else if(obj->passableFor(player))
return CGPathNode::ACCESSIBLE;
else if(obj->ID != Obj::EVENT)
return CGPathNode::VISITABLE;
}
}
}
else if(tinfo->blocked)
{
return CGPathNode::BLOCKED;
}
else if(gs->guardingCreaturePosition(pos).valid())
{
// Monster close by; blocked visit for battle
return CGPathNode::BLOCKVIS;
}
break;
case ELayer::WATER:
if(tinfo->blocked || tinfo->terType != ETerrainType::WATER)
return CGPathNode::BLOCKED;
break;
case ELayer::AIR:
if(tinfo->blocked || tinfo->terType == ETerrainType::WATER)
return CGPathNode::FLYABLE;
break;
}
return CGPathNode::ACCESSIBLE;
}
}

View File

@ -202,6 +202,7 @@
<Unit filename="NetPacksBase.h" />
<Unit filename="NetPacksLib.cpp" />
<Unit filename="NetPacksLobby.h" />
<Unit filename="PathfinderUtil.h" />
<Unit filename="ResourceSet.cpp" />
<Unit filename="ResourceSet.h" />
<Unit filename="ScopeGuard.h" />
@ -331,6 +332,7 @@
<Unit filename="mapping/CDrawRoadsOperation.h" />
<Unit filename="mapping/CMap.cpp" />
<Unit filename="mapping/CMap.h" />
<Unit filename="mapping/CMapDefines.h" />
<Unit filename="mapping/CMapEditManager.cpp" />
<Unit filename="mapping/CMapEditManager.h" />
<Unit filename="mapping/CMapInfo.cpp" />

View File

@ -231,12 +231,14 @@ std::vector<std::string> CLogManager::getRegisteredDomains() const
return std::move(domains);
}
CLogFormatter::CLogFormatter() : CLogFormatter("%m") { }
CLogFormatter::CLogFormatter(const std::string & pattern) : pattern(pattern)
CLogFormatter::CLogFormatter()
: CLogFormatter("%m")
{
}
CLogFormatter::CLogFormatter(const std::string & pattern)
: pattern(pattern)
{
boost::posix_time::time_facet * facet = new boost::posix_time::time_facet("%H:%M:%S.%f");
dateStream.imbue(std::locale(dateStream.getloc(), facet));
}
CLogFormatter::CLogFormatter(const CLogFormatter & c) : pattern(c.pattern) { }
@ -258,7 +260,7 @@ std::string CLogFormatter::format(const LogRecord & record) const
std::string message = pattern;
//Format date
boost::algorithm::replace_first(message, "%d", boost::posix_time::to_simple_string (record.timeStamp));
// boost::algorithm::replace_first(message, "%d", boost::posix_time::to_simple_string (record.timeStamp));
//Format log level
std::string level;
@ -387,7 +389,8 @@ void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) { th
CLogFileTarget::CLogFileTarget(boost::filesystem::path filePath, bool append)
: file(std::move(filePath), append ? std::ios_base::app : std::ios_base::out)
{
formatter.setPattern("%d %l %n [%t] - %m");
// formatter.setPattern("%d %l %n [%t] - %m");
formatter.setPattern("%l %n [%t] - %m");
}
void CLogFileTarget::write(const LogRecord & record)

View File

@ -149,7 +149,6 @@ public:
private:
std::string pattern;
mutable std::stringstream dateStream;
};
/// The interface ILogTarget is used by all log target implementations. It holds

View File

@ -55,9 +55,13 @@ static int lowestSpeed(const CGHeroInstance * chi)
}
auto i = chi->Slots().begin();
//TODO? should speed modifiers (eg from artifacts) affect hero movement?
int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED);
static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((si32)Bonus::STACKS_SPEED);
int ret = (i++)->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
for(; i != chi->Slots().end(); i++)
ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED));
ret = std::min(ret, i->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
return ret;
}
@ -87,8 +91,11 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro
}
else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
{
static const CSelector selectorPATHFINDING = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING);
static const std::string keyPATHFINDING = "type_"+std::to_string((si32)Bonus::SECONDARY_SKILL_PREMY)+"s_"+std::to_string((si32)SecondarySkill::PATHFINDING);
ret = VLC->heroh->terrCosts[from.terType];
ret -= valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING));
ret -= valOfBonuses(selectorPATHFINDING, keyPATHFINDING);
if(ret < GameConstants::BASE_MOVEMENT_COST)
ret = GameConstants::BASE_MOVEMENT_COST;
}

View File

@ -280,7 +280,12 @@ void Obstacle::placeObstacles(BattleStateProxy * battleState, const Mechanics *
BattleObstaclesChanged pack;
auto all = m->cb->battleGetAllObstacles(BattlePerspective::ALL_KNOWING);
boost::optional<BattlePerspective::BattlePerspective> perspective;
if(!m->cb->getPlayerID())
perspective = boost::make_optional(BattlePerspective::ALL_KNOWING);
auto all = m->cb->battleGetAllObstacles(perspective);
int obstacleIdToGive = 1;
for(auto & one : all)