Bläddra i källkod

Merge pull request #125 from ArseniyShestakov/newMovementSystem

Okay, time to merge this.
DjWarmonger 10 år sedan
förälder
incheckning
17071c6ec8

+ 1 - 1
CCallback.cpp

@@ -292,7 +292,7 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
 
 int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest)
 {
-	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->hasBonusOfType (Bonus::FLYING_MOVEMENT), hero->movement);
+	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->movement);
 }
 
 const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h)

+ 2 - 4
config/artifacts.json

@@ -1058,10 +1058,9 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 1,
 				"type" : "FLYING_MOVEMENT",
 				"val" : 0,
-				"valueType" : "BASE_NUMBER"
+				"valueType" : "INDEPENDENT_MIN"
 			}
 		],
 		"index" : 72,
@@ -1276,10 +1275,9 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 1,
 				"type" : "WATER_WALKING",
 				"val" : 0,
-				"valueType" : "BASE_NUMBER"
+				"valueType" : "INDEPENDENT_MIN"
 			}
 		],
 		"index" : 90,

+ 8 - 8
config/spells/adventure.json

@@ -176,23 +176,23 @@
 				"effects" : {
 					"fly" : {
 						"type" : "FLYING_MOVEMENT",
-						"subtype" : 2,
 						"duration" : "ONE_DAY",
-						"val" : 0 //in fact unused 
+						"val" : 40,
+						"valueType" : "INDEPENDENT_MIN"
 					}
 				}				
 			},
 			"advanced":{
 				"effects" : {
 					"fly" : {
-						"subtype" : 1
+						"val" : 20
 					}
 				}			
 			},
 			"expert":{
 				"effects" : {
 					"fly" : {
-						"subtype" : 1
+						"val" : 0
 					}
 				}			
 			}
@@ -214,23 +214,23 @@
 				"effects" : {
 					"waterWalk" : {
 						"type" : "WATER_WALKING",
-						"subtype" : 2,
 						"duration" : "ONE_DAY",
-						"val" : 0 //in fact unused
+						"val" : 40,
+						"valueType" : "INDEPENDENT_MIN"
 					}
 				}				
 			},
 			"advanced":{
 				"effects" : {
 					"waterWalk" : {
-						"subtype" : 1
+						"val" : 20
 					}
 				}			
 			},
 			"expert":{
 				"effects" : {
 					"waterWalk" : {
-						"subtype" : 1
+						"val" : 0
 					}
 				}			
 			}

+ 11 - 449
lib/CGameState.cpp

@@ -27,6 +27,7 @@
 #include "rmg/CMapGenerator.h"
 #include "CStopWatch.h"
 #include "mapping/CMapEditManager.h"
+#include "CPathfinder.h"
 
 class CGObjectInstance;
 
@@ -2099,7 +2100,7 @@ void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<i
 	}
 }
 
-int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, bool flying, int remainingMovePoints, bool checkLast)
+int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast)
 {
 	if(src == dest) //same tile
 		return 0;
@@ -2110,21 +2111,18 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
 	//get basic cost
 	int ret = h->getTileCost(d,s);
 
-	if(d.blocked && flying)
+	if(d.blocked && h->canFly())
 	{
-		bool freeFlying = h->getBonusesCount(Selector::typeSubtype(Bonus::FLYING_MOVEMENT, 1)) > 0;
-
-		if(!freeFlying)
-		{
-			ret *= 1.4; //40% penalty for movement over blocked tile
-		}
+		ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0;
 	}
-    else if (d.terType == ETerrainType::WATER)
+	else if(d.terType == ETerrainType::WATER)
 	{
 		if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds
 			ret *= 0.666;
-		else if (!h->boat && h->getBonusesCount(Selector::typeSubtype(Bonus::WATER_WALKING, 1)) > 0)
-			ret *= 1.4; //40% penalty for water walking
+		else if(!h->boat && h->canWalkOnSea())
+		{
+			ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0;
+		}
 	}
 
 	if(src.x != dest.x  &&  src.y != dest.y) //it's diagonal move
@@ -2144,10 +2142,10 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
 	{
 		std::vector<int3> vec;
 		vec.reserve(8); //optimization
-        getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true);
+		getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true);
 		for(auto & elem : vec)
 		{
-			int fcost = getMovementCost(h,dest, elem, flying, left, false);
+			int fcost = getMovementCost(h, dest, elem, left, false);
 			if(fcost <= left)
 			{
 				return ret;
@@ -2884,109 +2882,6 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const
 	return nullptr;
 }
 
-CGPathNode::CGPathNode()
-:coord(-1,-1,-1)
-{
-	accessible = NOT_SET;
-	land = 0;
-	moveRemains = 0;
-	turns = 255;
-	theNodeBefore = nullptr;
-}
-
-bool CGPathNode::reachable() const
-{
-	return turns < 255;
-}
-
-const CGPathNode * CPathsInfo::getPathInfo( int3 tile ) const
-{
-	boost::unique_lock<boost::mutex> pathLock(pathMx);
-
-	if (tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z)
-		return nullptr;
-	return &nodes[tile.x][tile.y][tile.z];
-}
-
-int CPathsInfo::getDistance( int3 tile ) const
-{
-	boost::unique_lock<boost::mutex> pathLock(pathMx);
-
-	CGPath ret;
-	if (getPath(tile, ret))
-		return ret.nodes.size();
-	else
-		return 255;
-}
-
-bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const
-{
-	boost::unique_lock<boost::mutex> pathLock(pathMx);
-
-	out.nodes.clear();
-	const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z];
-	if(!curnode->theNodeBefore)
-		return false;
-
-
-	while(curnode)
-	{
-		CGPathNode cpn = *curnode;
-		curnode = curnode->theNodeBefore;
-		out.nodes.push_back(cpn);
-	}
-	return true;
-}
-
-CPathsInfo::CPathsInfo( const int3 &Sizes )
-:sizes(Sizes)
-{
-	hero = nullptr;
-	nodes = new CGPathNode**[sizes.x];
-	for(int i = 0; i < sizes.x; i++)
-	{
-		nodes[i] = new CGPathNode*[sizes.y];
-		for (int j = 0; j < sizes.y; j++)
-		{
-			nodes[i][j] = new CGPathNode[sizes.z];
-		}
-	}
-}
-
-CPathsInfo::~CPathsInfo()
-{
-	for(int i = 0; i < sizes.x; i++)
-	{
-		for (int j = 0; j < sizes.y; j++)
-		{
-			delete [] nodes[i][j];
-		}
-		delete [] nodes[i];
-	}
-	delete [] nodes;
-}
-
-int3 CGPath::startPos() const
-{
-	return nodes[nodes.size()-1].coord;
-}
-
-int3 CGPath::endPos() const
-{
-	return nodes[0].coord;
-}
-
-void CGPath::convert( ui8 mode )
-{
-	if(mode==0)
-	{
-		for(auto & elem : nodes)
-		{
-			elem.coord = CGHeroInstance::convertPosition(elem.coord,true);
-		}
-	}
-}
-
 PlayerState::PlayerState()
  : color(-1), enteredWinningCheatCode(0),
    enteredLosingCheatCode(0), status(EPlayerStatus::INGAME)
@@ -3274,341 +3169,8 @@ TeamState::TeamState()
 	setNodeType(TEAM);
 }
 
-void CPathfinder::initializeGraph()
-{
-	CGPathNode ***graph = out.nodes;
-	for(size_t i=0; i < out.sizes.x; ++i)
-	{
-		for(size_t j=0; j < out.sizes.y; ++j)
-		{
-			for(size_t k=0; k < out.sizes.z; ++k)
-			{
-				curPos = int3(i,j,k);
-				const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k));
-				CGPathNode &node = graph[i][j][k];
-
-				node.accessible = evaluateAccessibility(tinfo);
-				node.turns = 0xff;
-				node.moveRemains = 0;
-				node.coord.x = i;
-				node.coord.y = j;
-				node.coord.z = k;
-                node.land = tinfo->terType != ETerrainType::WATER;
-				node.theNodeBefore = nullptr;
-			}
-		}
-	}
-}
-
-void CPathfinder::calculatePaths()
-{
-	bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
-	int maxMovePointsLand = hero->maxMovePoints(true);
-	int maxMovePointsWater = hero->maxMovePoints(false);
-	int3 src = hero->getPosition(false);
-
-	auto maxMovePoints = [&](CGPathNode *cp) -> int
-	{
-		return cp->land ? maxMovePointsLand : maxMovePointsWater;
-	};
-
-	out.hero = hero;
-	out.hpos = hero->getPosition(false);
-
-	if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
-	{
-		logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
-		return;
-	}
-
-	//logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress  %d) of player %d") % hero->name % hero % hero->tempOwner;
-	initializeGraph();
-
-	//initial tile - set cost on 0 and add to the queue
-	CGPathNode &initialNode = *getNode(out.hpos);
-	initialNode.turns = 0;
-	initialNode.moveRemains = hero->movement;
-	mq.push_back(&initialNode);
-
-	std::vector<int3> neighbours;
-	neighbours.reserve(16);
-	while(!mq.empty())
-	{
-		cp = mq.front();
-		mq.pop_front();
-
-		const int3 sourceGuardPosition = gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z];
-		bool guardedSource = (sourceGuardPosition != int3(-1, -1, -1) && cp->coord != src);
-		ct = &gs->map->getTile(cp->coord);
-
-		int movement = cp->moveRemains, turn = cp->turns;
-		if(!movement)
-		{
-			movement = maxMovePoints(cp);
-			turn++;
-		}
-
-		//add accessible neighbouring nodes to the queue
-		neighbours.clear();
-
-		auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool
-		{
-			if(!gs->isTeleportEntrancePassable(obj, hero->tempOwner))
-				return false;
-
-			auto whirlpool = dynamic_cast<const CGWhirlpool *>(obj);
-			if(whirlpool)
-			{
-				if(addTeleportWhirlpool(whirlpool))
-					return true;
-			}
-			else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj))
-				return true;
-
-			return false;
-		};
-
-		auto sObj = ct->topVisitableObj(cp->coord == CGHeroInstance::convertPosition(hero->pos, false));
-		auto cObj = dynamic_cast<const CGTeleport *>(sObj);
-		if(isAllowedTeleportEntrance(cObj))
-		{
-			for(auto objId : gs->getTeleportChannelExits(cObj->channel, hero->tempOwner))
-			{
-				auto obj = getObj(objId);
-				if(CGTeleport::isExitPassable(gs, hero, obj))
-					neighbours.push_back(obj->visitablePos());
-			}
-		}
-
-		std::vector<int3> neighbour_tiles;
-		gs->getNeighbours(*ct, cp->coord, neighbour_tiles, boost::logic::indeterminate, !cp->land);
-		if(sObj)
-		{
-			for(int3 neighbour_tile: neighbour_tiles)
-			{
-				if(canMoveBetween(neighbour_tile, sObj->visitablePos()))
-					neighbours.push_back(neighbour_tile);
-			}
-		}
-		else
-			vstd::concatenate(neighbours, neighbour_tiles);
-
-		for(auto & neighbour : neighbours)
-		{
-			const int3 &n = neighbour; //current neighbor
-			dp = getNode(n);
-			dt = &gs->map->getTile(n);
-			destTopVisObjID = dt->topVisitableId();
-
-			useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
-			const bool destIsGuardian = sourceGuardPosition == n;
-
-			auto dObj = dynamic_cast<const CGTeleport*>(dt->topVisitableObj());
-			if(!goodForLandSeaTransition()
-			   || (!canMoveBetween(cp->coord, dp->coord) && !CGTeleport::isConnected(cObj, dObj))
-			   || dp->accessible == CGPathNode::BLOCKED)
-			{
-				continue;
-			}
-
-			//special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
-			if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT)
-				guardedSource = false;
-
-			int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement);
-			//special case -> moving from src Subterranean gate to dest gate -> it's free
-			if(CGTeleport::isConnected(cObj, dObj))
-				cost = 0;
-
-			int remains = movement - cost;
-			if(useEmbarkCost)
-			{
-				remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
-				cost = movement - remains;
-			}
-
-			int turnAtNextTile = turn;
-			if(remains < 0)
-			{
-				//occurs rarely, when hero with low movepoints tries to leave the road
-				turnAtNextTile++;
-				int moveAtNextTile = maxMovePoints(cp);
-				cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, moveAtNextTile); //cost must be updated, movement points changed :(
-				remains = moveAtNextTile - cost;
-			}
-
-			if((dp->turns==0xff		//we haven't been here before
-				|| dp->turns > turnAtNextTile
-				|| (dp->turns >= turnAtNextTile  &&  dp->moveRemains < remains)) //this route is faster
-				&& (!guardedSource || destIsGuardian)) // Can step into tile of guard
-			{
-				assert(dp != cp->theNodeBefore); //two tiles can't point to each other
-				dp->moveRemains = remains;
-				dp->turns = turnAtNextTile;
-				dp->theNodeBefore = cp;
-
-				const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid()
-										&& dp->accessible == CGPathNode::BLOCKVIS;
-
-				auto checkDestinationTile = [&]() -> bool
-				{
-					if(dp->accessible == CGPathNode::ACCESSIBLE)
-						return true;
-					if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false))
-						return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation
-					if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj()))
-						return true; // For now we'll walways allos transit for teleports
-					if(useEmbarkCost && allowEmbarkAndDisembark)
-						return true;
-					if(gs->isTeleportEntrancePassable(dObj, hero->tempOwner))
-						return true; // Always add entry teleport with non-dummy channel
-					if(CGTeleport::isConnected(cObj, dObj))
-						return true; // Always add exit points of teleport
-					if(guardedDst && !guardedSource)
-						return true; // Can step into a hostile tile once
-
-					return false;
-				};
-
-				if(checkDestinationTile())
-					mq.push_back(dp);
-			}
-		} //neighbours loop
-	} //queue loop
-}
-
-CGPathNode *CPathfinder::getNode(const int3 &coord)
-{
-	return &out.nodes[coord.x][coord.y][coord.z];
-}
-
-bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const
-{
-	return gs->checkForVisitableDir(a, b);
-}
-
-CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const
-{
-	CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE);
-
-
-    if(tinfo->terType == ETerrainType::ROCK || !FoW[curPos.x][curPos.y][curPos.z])
-		return CGPathNode::BLOCKED;
-
-	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->passableFor(hero->tempOwner))
-				{
-					ret = CGPathNode::ACCESSIBLE;
-				}
-				else if(obj->blockVisit)
-				{
-					return CGPathNode::BLOCKVIS;
-				}
-				else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events
-				{
-					ret =  CGPathNode::VISITABLE;
-				}
-			}
-		}
-	}
-	else if (gs->map->guardingCreaturePositions[curPos.x][curPos.y][curPos.z].valid()
-		&& !tinfo->blocked)
-	{
-		// Monster close by; blocked visit for battle.
-		return CGPathNode::BLOCKVIS;
-	}
-
-	return ret;
-}
-
-bool CPathfinder::goodForLandSeaTransition()
-{
-	if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances
-	{
-		if(cp->land) //from land to sea -> embark or assault hero on boat
-		{
-			if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable
-				return false;
-			if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land
-				return false;
-			if(destTopVisObjID == Obj::BOAT)
-				useEmbarkCost = 1;
-		}
-		else //disembark
-		{
-			//can disembark only on coastal tiles
-			if(!dt->isCoastal())
-				return false;
-
-			//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
-			if( (dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked))
-				|| dt->visitable)  //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate
-				return false;;
-
-			useEmbarkCost = 2;
-		}
-	}
-
-	return true;
-}
-
-CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
-{
-	assert(hero);
-	assert(hero == getHero(hero->id));
-
-	allowEmbarkAndDisembark = true;
-	allowTeleportTwoWay = true;
-	allowTeleportOneWay = true;
-	allowTeleportOneWayRandom = false;
-	allowTeleportWhirlpool = false;
-	if (CGWhirlpool::isProtected(hero))
-		allowTeleportWhirlpool = true;
-}
-
 CRandomGenerator & CGameState::getRandomGenerator()
 {
 	//logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt();
 	return rand;
 }
-
-bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const
-{
-	return allowTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner);
-}
-
-bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
-{
-	if(allowTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
-	{
-		auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
-		if(passableExits.size() == 1)
-			return true;
-	}
-	return false;
-}
-
-bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
-{
-	if(allowTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
-	{
-		auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
-		if(passableExits.size() > 1)
-			return true;
-	}
-	return false;
-}
-
-bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const
-{
-   return allowTeleportWhirlpool && obj;
-}

+ 2 - 42
lib/CGameState.h

@@ -16,6 +16,7 @@
 #include "int3.h"
 #include "CRandomGenerator.h"
 #include "CGameStateFwd.h"
+#include "CPathfinder.h"
 
 /*
  * CGameState.h, part of VCMI engine
@@ -44,7 +45,6 @@ class CMap;
 struct StartInfo;
 struct SDL_Surface;
 class CMapHandler;
-class CPathfinder;
 struct SetObjectProperty;
 struct MetaString;
 struct CPack;
@@ -276,46 +276,6 @@ struct DLL_EXPORT DuelParameters
 	}
 };
 
-class CPathfinder : private CGameInfoCallback
-{
-private:
-	bool allowEmbarkAndDisembark;
-	bool allowTeleportTwoWay; // Two-way monoliths and Subterranean Gate
-	bool allowTeleportOneWay; // One-way monoliths with one known exit only
-	bool allowTeleportOneWayRandom; // One-way monoliths with more than one known exit
-	bool allowTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
-	CPathsInfo &out;
-	const CGHeroInstance *hero;
-	const std::vector<std::vector<std::vector<ui8> > > &FoW;
-
-	std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked
-
-
-	int3 curPos;
-	CGPathNode *cp; //current (source) path node -> we took it from the queue
-	CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider
-	const TerrainTile *ct, *dt; //tile info for both nodes
-	ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
-	Obj destTopVisObjID;
-
-
-	CGPathNode *getNode(const int3 &coord);
-	void initializeGraph();
-	bool goodForLandSeaTransition(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost
-
-	CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) const;
-	bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
-
-	bool addTeleportTwoWay(const CGTeleport * obj) const;
-	bool addTeleportOneWay(const CGTeleport * obj) const;
-	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
-	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
-
-public:
-	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
-	void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
-};
-
 
 struct BattleInfo;
 
@@ -380,7 +340,7 @@ public:
 	bool isVisible(const CGObjectInstance *obj, boost::optional<PlayerColor> player);
 
 	void getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing);
-	int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, bool flying, int remainingMovePoints=-1, bool checkLast=true);
+	int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints=-1, bool checkLast=true);
 	int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
 
 	// ----- getters, setters -----

+ 0 - 47
lib/CGameStateFwd.h

@@ -117,50 +117,3 @@ struct DLL_LINKAGE QuestInfo //universal interface for human and AI
 		h & quest & obj & tile;
 	}
 };
-
-struct DLL_LINKAGE CGPathNode
-{
-	enum EAccessibility
-	{
-		NOT_SET = 0,
-		ACCESSIBLE = 1, //tile can be entered and passed
-		VISITABLE, //tile can be entered as the last tile in path
-		BLOCKVIS,  //visitable from neighbouring tile but not passable
-		BLOCKED //tile can't be entered nor visited
-	};
-
-	EAccessibility accessible;
-	ui8 land;
-	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
-	ui32 moveRemains; //remaining tiles after hero reaches the tile
-	CGPathNode * theNodeBefore;
-	int3 coord; //coordinates
-
-	CGPathNode();
-	bool reachable() const;
-};
-
-struct DLL_LINKAGE CGPath
-{
-	std::vector<CGPathNode> nodes; //just get node by node
-
-	int3 startPos() const; // start point
-	int3 endPos() const; //destination point
-	void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object'
-};
-
-struct DLL_LINKAGE CPathsInfo
-{
-	mutable boost::mutex pathMx;
-
-	const CGHeroInstance *hero;
-	int3 hpos;
-	int3 sizes;
-	CGPathNode ***nodes; //[w][h][level]
-
-	const CGPathNode * getPathInfo( int3 tile ) const;
-	bool getPath(const int3 &dst, CGPath &out) const;
-	int getDistance( int3 tile ) const;
-	CPathsInfo(const int3 &Sizes);
-	~CPathsInfo();
-};

+ 1 - 0
lib/CMakeLists.txt

@@ -95,6 +95,7 @@ set(lib_SRCS
 
 		IGameCallback.cpp
 		CGameInfoCallback.cpp
+		CPathfinder.cpp
 		CGameState.cpp
 		Connection.cpp
 		NetPacksLib.cpp

+ 519 - 0
lib/CPathfinder.cpp

@@ -0,0 +1,519 @@
+#include "StdInc.h"
+#include "CPathfinder.h"
+
+#include "CHeroHandler.h"
+#include "mapping/CMap.h"
+#include "CGameState.h"
+#include "mapObjects/CGHeroInstance.h"
+#include "GameConstants.h"
+#include "CStopWatch.h"
+
+/*
+ * CPathfinder.cpp, 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
+ *
+ */
+
+CPathfinder::PathfinderOptions::PathfinderOptions()
+{
+	useFlying = false;
+	useWaterWalking = false;
+	useEmbarkAndDisembark = true;
+	useTeleportTwoWay = true;
+	useTeleportOneWay = true;
+	useTeleportOneWayRandom = false;
+	useTeleportWhirlpool = false;
+}
+
+CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
+{
+	assert(hero);
+	assert(hero == getHero(hero->id));
+
+	out.hero = hero;
+	out.hpos = hero->getPosition(false);
+	if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
+	{
+		logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
+		throw std::runtime_error("Wrong checksum");
+	}
+
+	initializeGraph();
+
+	if(hero->canFly())
+		options.useFlying = true;
+	if(hero->canWalkOnSea())
+		options.useWaterWalking = true;
+	if(CGWhirlpool::isProtected(hero))
+		options.useTeleportWhirlpool = true;
+
+	neighbours.reserve(16);
+}
+
+void CPathfinder::calculatePaths()
+{
+	int maxMovePointsLand = hero->maxMovePoints(true);
+	int maxMovePointsWater = hero->maxMovePoints(false);
+
+	auto maxMovePoints = [&](CGPathNode *cp) -> int
+	{
+		return cp->land ? maxMovePointsLand : maxMovePointsWater;
+	};
+
+	auto isBetterWay = [&](int remains, int turn) -> bool
+	{
+		if(dp->turns == 0xff) //we haven't been here before
+			return true;
+		else if(dp->turns > turn)
+			return true;
+		else if(dp->turns >= turn  &&  dp->moveRemains < remains) //this route is faster
+			return true;
+
+		return false;
+	};
+
+	//logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress  %d) of player %d") % hero->name % hero % hero->tempOwner;
+
+	//initial tile - set cost on 0 and add to the queue
+	CGPathNode &initialNode = *getNode(out.hpos);
+	initialNode.turns = 0;
+	initialNode.moveRemains = hero->movement;
+	mq.push_back(&initialNode);
+
+	while(!mq.empty())
+	{
+		cp = mq.front();
+		mq.pop_front();
+
+		int movement = cp->moveRemains, turn = cp->turns;
+		if(!movement)
+		{
+			movement = maxMovePoints(cp);
+			turn++;
+		}
+
+		//add accessible neighbouring nodes to the queue
+		addNeighbours(cp->coord);
+		for(auto & neighbour : neighbours)
+		{
+			dp = getNode(neighbour);
+			dt = &gs->map->getTile(neighbour);
+			useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
+
+			if(!isMovementPossible())
+				continue;
+
+			int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement);
+			int remains = movement - cost;
+			if(useEmbarkCost)
+			{
+				remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
+				cost = movement - remains;
+			}
+
+			int turnAtNextTile = turn;
+			if(remains < 0)
+			{
+				//occurs rarely, when hero with low movepoints tries to leave the road
+				turnAtNextTile++;
+				int moveAtNextTile = maxMovePoints(cp);
+				cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :(
+				remains = moveAtNextTile - cost;
+			}
+
+			if(isBetterWay(remains, turnAtNextTile))
+			{
+				assert(dp != cp->theNodeBefore); //two tiles can't point to each other
+				dp->moveRemains = remains;
+				dp->turns = turnAtNextTile;
+				dp->theNodeBefore = cp;
+
+				if(checkDestinationTile())
+					mq.push_back(dp);
+			}
+		} //neighbours loop
+
+		//just add all passable teleport exits
+		if(sTileObj)
+		{
+			addTeleportExits();
+			for(auto & neighbour : neighbours)
+			{
+				dp = getNode(neighbour);
+				if(isBetterWay(movement, turn))
+				{
+					dp->moveRemains = movement;
+					dp->turns = turn;
+					dp->theNodeBefore = cp;
+					mq.push_back(dp);
+				}
+			}
+		}
+	} //queue loop
+}
+
+void CPathfinder::addNeighbours(const int3 &coord)
+{
+	neighbours.clear();
+	ct = &gs->map->getTile(coord);
+
+	std::vector<int3> tiles;
+	gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, !cp->land);
+	sTileObj = ct->topVisitableObj(coord == CGHeroInstance::convertPosition(hero->pos, false));
+	if(sTileObj)
+	{
+		for(int3 tile: tiles)
+		{
+			if(canMoveBetween(tile, sTileObj->visitablePos()))
+				neighbours.push_back(tile);
+		}
+	}
+	else
+		vstd::concatenate(neighbours, tiles);
+}
+
+void CPathfinder::addTeleportExits(bool noTeleportExcludes)
+{
+	assert(sTileObj);
+
+	neighbours.clear();
+	auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool
+	{
+		if(!gs->isTeleportEntrancePassable(obj, hero->tempOwner))
+			return false;
+
+		if(noTeleportExcludes)
+			return true;
+
+		auto whirlpool = dynamic_cast<const CGWhirlpool *>(obj);
+		if(whirlpool)
+		{
+			if(addTeleportWhirlpool(whirlpool))
+				return true;
+		}
+		else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj))
+			return true;
+
+		return false;
+	};
+
+	const CGTeleport *sTileTeleport = dynamic_cast<const CGTeleport *>(sTileObj);
+	if(isAllowedTeleportEntrance(sTileTeleport))
+	{
+		for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner))
+		{
+			auto obj = getObj(objId);
+			if(CGTeleport::isExitPassable(gs, hero, obj))
+				neighbours.push_back(obj->visitablePos());
+		}
+	}
+}
+
+bool CPathfinder::isMovementPossible()
+{
+	if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED)
+		return false;
+
+	Obj destTopVisObjID = dt->topVisitableId();
+	if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances
+	{
+		if(cp->land) //from land to sea -> embark or assault hero on boat
+		{
+			if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable
+				return false;
+			if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land
+				return false;
+			if(destTopVisObjID == Obj::BOAT)
+				useEmbarkCost = 1;
+		}
+		else //disembark
+		{
+			//can disembark only on coastal tiles
+			if(!dt->isCoastal())
+				return false;
+
+			//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
+			if((dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked))
+				|| dt->visitable)  //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate
+				return false;;
+
+			useEmbarkCost = 2;
+		}
+	}
+
+	if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard
+		return false;
+
+	return true;
+}
+
+bool CPathfinder::checkDestinationTile()
+{
+	if(dp->accessible == CGPathNode::ACCESSIBLE)
+		return true;
+	if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false))
+		return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation
+	if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj()))
+		return true; // For now we'll always allow transit for teleporters
+	if(useEmbarkCost && options.useEmbarkAndDisembark)
+		return true;
+	if(isDestinationGuarded() && !isSourceGuarded())
+		return true; // Can step into a hostile tile once
+
+	return false;
+}
+
+int3 CPathfinder::getSourceGuardPosition()
+{
+	return gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z];
+}
+
+bool CPathfinder::isSourceGuarded()
+{
+	//map can start with hero on guarded tile or teleport there using dimension door
+	//so threat tile hero standing on like it's not guarded because it's should be possible to move out of here
+	if(getSourceGuardPosition() != int3(-1, -1, -1)
+		&& cp->coord != hero->getPosition(false))
+	{
+		//special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
+		if(cp->accessible != CGPathNode::VISITABLE
+		   || !cp->theNodeBefore->land
+		   || ct->topVisitableId() != Obj::BOAT)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool CPathfinder::isDestinationGuarded()
+{
+	if(gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid()
+		&& dp->accessible == CGPathNode::BLOCKVIS)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+bool CPathfinder::isDestinationGuardian()
+{
+	return getSourceGuardPosition() == dp->coord;
+}
+
+void CPathfinder::initializeGraph()
+{
+	int3 pos;
+	CGPathNode ***graph = out.nodes;
+	for(pos.x=0; pos.x < out.sizes.x; ++pos.x)
+	{
+		for(pos.y=0; pos.y < out.sizes.y; ++pos.y)
+		{
+			for(pos.z=0; pos.z < out.sizes.z; ++pos.z)
+			{
+				const TerrainTile *tinfo = &gs->map->getTile(pos);
+				CGPathNode &node = graph[pos.x][pos.y][pos.z];
+				node.accessible = evaluateAccessibility(pos, tinfo);
+				node.turns = 0xff;
+				node.moveRemains = 0;
+				node.coord = pos;
+				node.land = tinfo->terType != ETerrainType::WATER;
+				node.theNodeBefore = nullptr;
+			}
+		}
+	}
+}
+
+CGPathNode *CPathfinder::getNode(const int3 &coord)
+{
+	return &out.nodes[coord.x][coord.y][coord.z];
+}
+
+CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const
+{
+	CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE);
+
+
+	if(tinfo->terType == ETerrainType::ROCK || !FoW[pos.x][pos.y][pos.z])
+		return CGPathNode::BLOCKED;
+
+	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->passableFor(hero->tempOwner))
+				{
+					ret = CGPathNode::ACCESSIBLE;
+				}
+				else if(obj->blockVisit)
+				{
+					return CGPathNode::BLOCKVIS;
+				}
+				else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events
+				{
+					ret =  CGPathNode::VISITABLE;
+				}
+			}
+		}
+	}
+	else if(gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z].valid()
+		&& !tinfo->blocked)
+	{
+		// Monster close by; blocked visit for battle.
+		return CGPathNode::BLOCKVIS;
+	}
+
+	return ret;
+}
+
+bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const
+{
+	return gs->checkForVisitableDir(a, b);
+}
+
+bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const
+{
+	return options.useTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner);
+}
+
+bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
+{
+	if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
+	{
+		auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
+		if(passableExits.size() == 1)
+			return true;
+	}
+	return false;
+}
+
+bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
+{
+	if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
+	{
+		auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
+		if(passableExits.size() > 1)
+			return true;
+	}
+	return false;
+}
+
+bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const
+{
+	return options.useTeleportWhirlpool && obj;
+}
+
+CGPathNode::CGPathNode()
+:coord(-1,-1,-1)
+{
+	accessible = NOT_SET;
+	land = 0;
+	moveRemains = 0;
+	turns = 255;
+	theNodeBefore = nullptr;
+}
+
+bool CGPathNode::reachable() const
+{
+	return turns < 255;
+}
+
+int3 CGPath::startPos() const
+{
+	return nodes[nodes.size()-1].coord;
+}
+
+int3 CGPath::endPos() const
+{
+	return nodes[0].coord;
+}
+
+void CGPath::convert( ui8 mode )
+{
+	if(mode==0)
+	{
+		for(auto & elem : nodes)
+		{
+			elem.coord = CGHeroInstance::convertPosition(elem.coord,true);
+		}
+	}
+}
+
+CPathsInfo::CPathsInfo( const int3 &Sizes )
+:sizes(Sizes)
+{
+	hero = nullptr;
+	nodes = new CGPathNode**[sizes.x];
+	for(int i = 0; i < sizes.x; i++)
+	{
+		nodes[i] = new CGPathNode*[sizes.y];
+		for(int j = 0; j < sizes.y; j++)
+		{
+			nodes[i][j] = new CGPathNode[sizes.z];
+		}
+	}
+}
+
+CPathsInfo::~CPathsInfo()
+{
+	for(int i = 0; i < sizes.x; i++)
+	{
+		for(int j = 0; j < sizes.y; j++)
+		{
+			delete [] nodes[i][j];
+		}
+		delete [] nodes[i];
+	}
+	delete [] nodes;
+}
+
+const CGPathNode * CPathsInfo::getPathInfo( int3 tile ) const
+{
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
+
+	if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z)
+		return nullptr;
+	return &nodes[tile.x][tile.y][tile.z];
+}
+
+bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const
+{
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
+
+	out.nodes.clear();
+	const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z];
+	if(!curnode->theNodeBefore)
+		return false;
+
+
+	while(curnode)
+	{
+		CGPathNode cpn = *curnode;
+		curnode = curnode->theNodeBefore;
+		out.nodes.push_back(cpn);
+	}
+	return true;
+}
+
+int CPathsInfo::getDistance( int3 tile ) const
+{
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
+
+	CGPath ret;
+	if(getPath(tile, ret))
+		return ret.nodes.size();
+	else
+		return 255;
+}

+ 124 - 0
lib/CPathfinder.h

@@ -0,0 +1,124 @@
+#pragma once
+
+#include "VCMI_Lib.h"
+#include "mapping/CMap.h"
+#include "IGameCallback.h"
+#include "int3.h"
+
+/*
+ * CPathfinder.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
+ *
+ */
+
+class CGHeroInstance;
+class CGObjectInstance;
+struct TerrainTile;
+
+struct DLL_LINKAGE CGPathNode
+{
+	enum EAccessibility
+	{
+		NOT_SET = 0,
+		ACCESSIBLE = 1, //tile can be entered and passed
+		VISITABLE, //tile can be entered as the last tile in path
+		BLOCKVIS,  //visitable from neighbouring tile but not passable
+		BLOCKED //tile can't be entered nor visited
+	};
+
+	EAccessibility accessible;
+	ui8 land;
+	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
+	ui32 moveRemains; //remaining tiles after hero reaches the tile
+	CGPathNode * theNodeBefore;
+	int3 coord; //coordinates
+
+	CGPathNode();
+	bool reachable() const;
+};
+
+struct DLL_LINKAGE CGPath
+{
+	std::vector<CGPathNode> nodes; //just get node by node
+
+	int3 startPos() const; // start point
+	int3 endPos() const; //destination point
+	void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object'
+};
+
+struct DLL_LINKAGE CPathsInfo
+{
+	mutable boost::mutex pathMx;
+
+	const CGHeroInstance *hero;
+	int3 hpos;
+	int3 sizes;
+	CGPathNode ***nodes; //[w][h][level]
+
+	CPathsInfo(const int3 &Sizes);
+	~CPathsInfo();
+	const CGPathNode * getPathInfo( int3 tile ) const;
+	bool getPath(const int3 &dst, CGPath &out) const;
+	int getDistance( int3 tile ) const;
+};
+
+class CPathfinder : private CGameInfoCallback
+{
+public:
+	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
+	void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
+
+private:
+	struct PathfinderOptions
+	{
+		bool useFlying;
+		bool useWaterWalking;
+		bool useEmbarkAndDisembark;
+		bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
+		bool useTeleportOneWay; // One-way monoliths with one known exit only
+		bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
+		bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
+
+		PathfinderOptions();
+	} options;
+
+	CPathsInfo &out;
+	const CGHeroInstance *hero;
+	const std::vector<std::vector<std::vector<ui8> > > &FoW;
+
+	std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked
+
+	std::vector<int3> neighbours;
+
+	CGPathNode *cp; //current (source) path node -> we took it from the queue
+	CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider
+	const TerrainTile *ct, *dt; //tile info for both nodes
+	const CGObjectInstance *sTileObj;
+	ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
+
+	void addNeighbours(const int3 &coord);
+	void addTeleportExits(bool noTeleportExcludes = false);
+
+	bool isMovementPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost
+	bool checkDestinationTile();
+
+	int3 getSourceGuardPosition();
+	bool isSourceGuarded();
+	bool isDestinationGuarded();
+	bool isDestinationGuardian();
+
+	void initializeGraph();
+
+	CGPathNode *getNode(const int3 &coord);
+	CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const;
+	bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
+
+	bool addTeleportTwoWay(const CGTeleport * obj) const;
+	bool addTeleportOneWay(const CGTeleport * obj) const;
+	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
+	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
+};

+ 2 - 2
lib/HeroBonus.h

@@ -86,13 +86,13 @@ public:
 	BONUS_NAME(SECONDARY_SKILL_PREMY) /*%*/  \
 	BONUS_NAME(SURRENDER_DISCOUNT) /*%*/  \
 	BONUS_NAME(STACKS_SPEED)  /*additional info - percent of speed bonus applied after direct bonuses; >0 - added, <0 - subtracted to this part*/ \
-	BONUS_NAME(FLYING_MOVEMENT) /*subtype 1 - without penalty, 2 - with penalty*/ \
+	BONUS_NAME(FLYING_MOVEMENT) /*value - penalty percentage*/ \
 	BONUS_NAME(SPELL_DURATION) \
 	BONUS_NAME(AIR_SPELL_DMG_PREMY) \
 	BONUS_NAME(EARTH_SPELL_DMG_PREMY) \
 	BONUS_NAME(FIRE_SPELL_DMG_PREMY) \
 	BONUS_NAME(WATER_SPELL_DMG_PREMY) \
-	BONUS_NAME(WATER_WALKING) /*subtype 1 - without penalty, 2 - with penalty*/ \
+	BONUS_NAME(WATER_WALKING) /*value - penalty percentage*/ \
 	BONUS_NAME(NEGATE_ALL_NATURAL_IMMUNITIES) \
 	BONUS_NAME(STACK_HEALTH) \
 	BONUS_NAME(BLOCK_MORALE) \

+ 6 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -129,9 +129,14 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o
 	}
 }
 
+bool CGHeroInstance::canFly() const
+{
+	return hasBonusOfType(Bonus::FLYING_MOVEMENT);
+}
+
 bool CGHeroInstance::canWalkOnSea() const
 {
-	return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING);
+	return hasBonusOfType(Bonus::WATER_WALKING);
 }
 
 ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const

+ 1 - 0
lib/mapObjects/CGHeroInstance.h

@@ -133,6 +133,7 @@ public:
 	ui32 getLowestCreatureSpeed() const;
 	int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
+	bool canFly() const;
 	bool canWalkOnSea() const;
 	int getCurrentLuck(int stack=-1, bool town=false) const;
 	int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored

+ 1 - 1
server/CGameHandler.cpp

@@ -1764,7 +1764,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 	}
 
 	const TerrainTile t = *gs->getTile(hmpos);
-	const int cost = gs->getMovementCost(h, h->getPosition(), hmpos, h->hasBonusOfType(Bonus::FLYING_MOVEMENT), h->movement);
+	const int cost = gs->getMovementCost(h, h->getPosition(), hmpos, h->movement);
 	const int3 guardPos = gs->guardingCreaturePosition(hmpos);
 
 	const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;