2
0
Эх сурвалжийг харах

Merge pull request #133 from vcmi/feature/pathfinderLayers

Pathfinder layers and related changes: fly and water walking implemented
ArseniyShestakov 10 жил өмнө
parent
commit
a851062891

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -372,7 +372,7 @@ int3 whereToExplore(HeroPtr h)
 			{
 				int3 op = obj->visitablePos();
 				CGPath p;
-				ai->myCb->getPathsInfo(h.get())->getPath(op, p);
+				ai->myCb->getPathsInfo(h.get())->getPath(p, op);
 				if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT)
 					if (ai->isGoodForVisit(obj, h, sm))
 						nearbyVisitableObjs.push_back(obj);

+ 2 - 2
AI/VCAI/Fuzzy.cpp

@@ -421,7 +421,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
 
 	//assert(cb->isInTheMap(g.tile));
 	float turns = 0;
-	float distance = cb->getMovementCost(g.hero.h, g.tile);
+	float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile);
 	if (!distance) //we stand on that tile
 		turns = 0;
 	else
@@ -530,4 +530,4 @@ float FuzzyHelper::evaluate (Goals::AbstractGoal & g)
 void FuzzyHelper::setPriority (Goals::TSubgoal & g)
 {
 	g->setpriority(g->accept(this)); //this enforces returned value is set
-}
+}

+ 4 - 2
AI/VCAI/VCAI.cpp

@@ -1840,7 +1840,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 	else
 	{
 		CGPath path;
-		cb->getPathsInfo(h.get())->getPath(dst, path);
+		cb->getPathsInfo(h.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
             logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst;
@@ -1915,6 +1915,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			{ // Hero should be able to go through object if it's allow transit
 				doMovement(endpos, true);
 			}
+			else if(path.nodes[i-1].layer == EPathfindingLayer::AIR)
+				doMovement(endpos, true);
 			else
 				doMovement(endpos, false);
 
@@ -2519,7 +2521,7 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
 				continue;
 
 			CGPath path;
-			cb->getPathsInfo(hero)->getPath(tile, path);
+			cb->getPathsInfo(hero)->getPath(path, tile);
 			float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps
 
 			if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much

+ 0 - 5
CCallback.cpp

@@ -290,11 +290,6 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
 	return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a);
 }
 
-int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest)
-{
-	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->movement);
-}
-
 const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h)
 {
 	return cl->getPathsInfo(h);

+ 0 - 1
CCallback.h

@@ -104,7 +104,6 @@ public:
 
 	//client-specific functionalities (pathfinding)
 	virtual bool canMoveBetween(const int3 &a, const int3 &b);
-	virtual int getMovementCost(const CGHeroInstance * hero, int3 dest);
 	virtual int3 getGuardingCreaturePosition(int3 tile);
 	virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
 

+ 12 - 0
Global.h

@@ -95,6 +95,18 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #  define NOMINMAX					// Exclude min/max macros from <Windows.h>. Use std::[min/max] from <algorithm> instead.
 #endif
 
+/* ---------------------------------------------------------------------------- */
+/* A macro to force inlining some of our functions */
+/* ---------------------------------------------------------------------------- */
+// Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower
+#ifdef _MSC_VER
+#  define STRONG_INLINE __forceinline
+#elif __GNUC__
+#  define STRONG_INLINE inline __attribute__((always_inline))
+#else
+#  define STRONG_INLINE inline
+#endif
+
 #define _USE_MATH_DEFINES
 
 #include <cstdio>

+ 21 - 7
client/CPlayerInterface.cpp

@@ -1288,7 +1288,7 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 			for(auto &p : pathsMap)
 			{
 				CGPath path;
-				cb->getPathsInfo(p.first)->getPath(p.second, path);
+				cb->getPathsInfo(p.first)->getPath(path, p.second);
 				paths[p.first] = path;
 				logGlobal->traceStream() << boost::format("Restored path for hero %s leading to %s with %d nodes")
 					% p.first->nodeName() % p.second % path.nodes.size();
@@ -2226,7 +2226,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
 		{
 			assert(h->getPosition(false) == path.startPos());
 			//update the hero path in case of something has changed on map
-			if(LOCPLINT->cb->getPathsInfo(h)->getPath(path.endPos(), path))
+			if(LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos()))
 				return &path;
 			else
 				paths.erase(h);
@@ -2642,7 +2642,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		ETerrainType newTerrain;
 		int sh = -1;
 
-		for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE); i--)
+		auto canStop = [&](CGPathNode * node) -> bool
+		{
+			if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL)
+				return true;
+
+			if(node->accessible == CGPathNode::ACCESSIBLE)
+				return true;
+
+			return false;
+		};
+
+		for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--)
 		{
 			int3 currentCoord = path.nodes[i].coord;
 			int3 nextCoord = path.nodes[i-1].coord;
@@ -2683,18 +2694,21 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 			int3 endpos(nextCoord.x, nextCoord.y, h->pos.z);
 			logGlobal->traceStream() << "Requesting hero movement to " << endpos;
 
+			bool useTransit = false;
 			if((i-2 >= 0) // Check there is node after next one; otherwise transit is pointless
 				&& (CGTeleport::isConnected(nextObject, getObj(path.nodes[i-2].coord, false))
 					|| CGTeleport::isTeleport(nextObject)))
 			{ // Hero should be able to go through object if it's allow transit
-				doMovement(endpos, true);
+				useTransit = true;
 			}
-			else
-				doMovement(endpos, false);
+			else if(path.nodes[i-1].layer == EPathfindingLayer::AIR)
+				useTransit = true;
+
+			doMovement(endpos, useTransit);
 
 			logGlobal->traceStream() << "Resuming " << __FUNCTION__;
 			bool guarded = cb->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0)));
-			if(guarded || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136)
+			if((!useTransit && guarded) || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136)
 				break;
 		}
 

+ 0 - 9
client/gui/SDL_Extensions.h

@@ -18,15 +18,6 @@
 #include "../../lib/GameConstants.h"
 
 
-//A macro to force inlining some of our functions. Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower
-#ifdef _MSC_VER
-	#define STRONG_INLINE __forceinline
-#elif __GNUC__
-	#define STRONG_INLINE inline __attribute__((always_inline))
-#else
-	#define STRONG_INLINE inline
-#endif
-
 extern SDL_Window * mainWindow;
 extern SDL_Renderer * mainRenderer;
 extern SDL_Texture * screenTexture;

+ 54 - 126
client/windows/CAdvmapInterface.cpp

@@ -114,7 +114,7 @@ void CTerrainRect::clickRight(tribool down, bool previousState)
 		adventureInt->tileRClicked(mp);
 }
 
-void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
+void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 {
 	int3 tHovered = whichTileIsIt(sEvent.x,sEvent.y);
 	int3 pom = adventureInt->verifyPos(tHovered);
@@ -126,11 +126,11 @@ void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
 	}
 
 	if (pom != curHoveredTile)
-		curHoveredTile=pom;
+		curHoveredTile = pom;
 	else
 		return;
 
-	adventureInt->tileHovered(curHoveredTile);
+	adventureInt->tileHovered(pom);
 }
 void CTerrainRect::hover(bool on)
 {
@@ -188,7 +188,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to)
 			 * is id1=7, id2=5 (pns[7][5])
 			*/
 			bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
-			if(pathContinuous && cv[i].land == cv[i+1].land)
+			if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK)
 			{
 				int id1=(curPos.x-nextPos.x+1)+3*(curPos.y-nextPos.y+1);   //Direction of entering vector
 				int id2=(cv[i-1].coord.x-curPos.x+1)+3*(cv[i-1].coord.y-curPos.y+1); //Direction of exiting vector
@@ -1190,7 +1190,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 			CGPath &path = LOCPLINT->paths[h];
 			terrain.currentPath = &path;
 			int3 dst = h->getPosition(false) + dir;
-			if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(dst, path))
+			if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst))
 			{
 				terrain.currentPath = nullptr;
 				return;
@@ -1445,7 +1445,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 			{
 				CGPath &path = LOCPLINT->paths[currentHero];
 				terrain.currentPath = &path;
-				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(mapPos, path); //try getting path, erase if failed
+				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(path, mapPos); //try getting path, erase if failed
 				updateMoveHero(currentHero);
 				if (!gotPath)
 					LOCPLINT->eraseCurrentPathOf(currentHero);
@@ -1467,7 +1467,8 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 
 void CAdvMapInt::tileHovered(const int3 &mapPos)
 {
-	if(mode != EAdvMapMode::NORMAL)
+	if(mode != EAdvMapMode::NORMAL //disable in world view
+		|| !selection) //may occur just at the start of game (fake move before full intiialization)
 		return;
 	if(!LOCPLINT->cb->isVisible(mapPos))
 	{
@@ -1475,10 +1476,11 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		statusbar.clear();
 		return;
 	}
+	auto objRelations = PlayerRelations::ALLIES;
 	const CGObjectInstance *objAtTile = getActiveObject(mapPos);
-
-	if (objAtTile)
+	if(objAtTile)
 	{
+		objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
 		std::string text = curHero() ? objAtTile->getHoverText(curHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
 		boost::replace_all(text,"\n"," ");
 		statusbar.setText(text);
@@ -1490,9 +1492,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		statusbar.setText(hlp);
 	}
 
-	if(!selection) //may occur just at the start of game (fake move before full intiialization)
-		return;
-
 	if(spellBeingCasted)
 	{
 		switch(spellBeingCasted->id)
@@ -1505,9 +1504,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 			return;
 		case SpellID::DIMENSION_DOOR:
 			{
-				const TerrainTile *t = LOCPLINT->cb->getTile(mapPos, false);
+				const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
 				int3 hpos = selection->getSightCenter();
-				if((!t  ||  t->isClear(LOCPLINT->cb->getTile(hpos)))   &&   isInScreenRange(hpos, mapPos))
+				if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
 					CCS->curh->changeGraphic(ECursor::ADVENTURE, 41);
 				else
 					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
@@ -1516,15 +1515,13 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		}
 	}
 
-	const bool guardingCreature = CGI->mh->map->isInTheMap(LOCPLINT->cb->getGuardingCreaturePosition(mapPos));
-
 	if(selection->ID == Obj::TOWN)
 	{
 		if(objAtTile)
 		{
-			if(objAtTile->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner) != PlayerRelations::ENEMIES)
+			if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
 				CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
-			else if(objAtTile->ID == Obj::HERO && objAtTile->tempOwner == LOCPLINT->playerID)
+			else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
 				CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
 			else
 				CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
@@ -1532,127 +1529,58 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		else
 			CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
 	}
-	else if(const CGHeroInstance *h = curHero())
+	else if(const CGHeroInstance * h = curHero())
 	{
-		const CGPathNode *pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
-
+		const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
 		int turns = pnode->turns;
 		vstd::amin(turns, 3);
-		bool accessible  =  pnode->turns < 255;
-
-		if(objAtTile)
+		switch(pnode->action)
 		{
-			if(objAtTile->ID == Obj::HERO)
-			{
-				if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy hero
-				{
-					if(accessible)
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-				}
-				else //our or ally hero
-				{
-					if(selection == objAtTile)
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
-					else if(accessible)
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6);
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
-				}
-			}
-			else if(objAtTile->ID == Obj::TOWN)
-			{
-				if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy town
-				{
-					if(accessible)
-					{
-						const CGTownInstance* townObj = dynamic_cast<const CGTownInstance*>(objAtTile);
-
-						// Show movement cursor for unguarded enemy towns, otherwise attack cursor.
-						if (townObj && !townObj->armedGarrison())
-							CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
-						else
-							CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
+		case CGPathNode::NORMAL:
+			if(pnode->layer == EPathfindingLayer::LAND)
+				CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6);
+			else
+				CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
+			break;
 
-					}
-					else
-					{
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-					}
-				}
-				else //our or ally town
-				{
-					if(accessible)
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
-				}
-			}
-			else if(objAtTile->ID == Obj::BOAT)
-			{
-				if(accessible)
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6);
-				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-			}
-			else if (objAtTile->ID == Obj::GARRISON || objAtTile->ID == Obj::GARRISON2)
+		case CGPathNode::VISIT:
+		case CGPathNode::BLOCKING_VISIT:
+			if(objAtTile && objAtTile->ID == Obj::HERO)
 			{
-				if (accessible)
-				{
-					const CGGarrison* garrObj = dynamic_cast<const CGGarrison*>(objAtTile); //TODO evil evil cast!
-
-					// Show battle cursor for guarded enemy garrisons or garrisons have guarding creature behind, otherwise movement cursor.
-					if (garrObj  &&  ((garrObj->stacksCount()
-						&& !LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, garrObj->tempOwner))
-						|| guardingCreature))
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
-				}
+				if(selection == objAtTile)
+					CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
 				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-			}
-			else if (guardingCreature && accessible) //(objAtTile->ID == 54) //monster
-			{
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
+					CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6);
 			}
+			else if(pnode->layer == EPathfindingLayer::LAND)
+				CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
 			else
+				CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
+			break;
+
+		case CGPathNode::BATTLE:
+			CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
+			break;
+
+		case CGPathNode::EMBARK:
+			CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6);
+			break;
+
+		case CGPathNode::DISEMBARK:
+			CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6);
+			break;
+
+		default:
+			if(objAtTile && objRelations != PlayerRelations::ENEMIES)
 			{
-				if(accessible)
-				{
-					if(pnode->land)
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
-				}
-				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-			}
-		}
-		else //no objs
-		{
-			if(accessible/* && pnode->accessible != CGPathNode::FLYABLE*/)
-			{
-				if (guardingCreature)
-				{
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
-				}
-				else
-				{
-					if(pnode->land)
-					{
-						if(LOCPLINT->cb->getTile(h->getPosition(false))->terType != ETerrainType::WATER)
-							CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6);
-						else
-							CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6); //anchor
-					}
-					else
-						CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6);
-				}
+				if(objAtTile->ID == Obj::TOWN)
+					CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
+				else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
+					CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
 			}
 			else
 				CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+			break;
 		}
 	}
 

+ 69 - 1
config/schemas/settings.json

@@ -3,7 +3,7 @@
 {
 	"type" : "object",
 	"$schema": "http://json-schema.org/draft-04/schema",
-	"required" : [ "general", "video", "adventure", "battle", "server", "logging", "launcher" ],
+	"required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ],
 	"definitions" : {
 		"logLevelEnum" : { 
 			"type" : "string", 
@@ -108,6 +108,74 @@
 				}
 			}
 		},
+		"pathfinder" : {
+			"type" : "object",
+			"additionalProperties" : false,
+			"default": {},
+			"required" : [ "teleports", "layers", "oneTurnSpecialLayersLimit", "originalMovementRules", "lightweightFlyingMode" ],
+			"properties" : {
+				"layers" : {
+					"type" : "object",
+					"additionalProperties" : false,
+					"default": {},
+					"required" : [ "sailing", "waterWalking", "flying" ],
+					"properties" : {
+						"sailing" : {
+							"type" : "boolean",
+							"default" : true
+						},
+						"waterWalking" : {
+							"type" : "boolean",
+							"default" : true
+						},
+						"flying" : {
+							"type" : "boolean",
+							"default" : true
+						}
+					}
+				},
+				"teleports" : {
+					"type" : "object",
+					"additionalProperties" : false,
+					"default": {},
+					"required" : [ "twoWay", "oneWay", "oneWayRandom", "whirlpool", "castleGate" ],
+					"properties" : {
+						"twoWay" : {
+							"type" : "boolean",
+							"default" : true
+						},
+						"oneWay" : {
+							"type" : "boolean",
+							"default" : true
+						},
+						"oneWayRandom" : {
+							"type" : "boolean",
+							"default" : false
+						},
+						"whirlpool" : {
+							"type" : "boolean",
+							"default" : true
+						},
+						"castleGate" : {
+							"type" : "boolean",
+							"default" : false
+						}
+					}
+				},
+				"oneTurnSpecialLayersLimit" : {
+					"type" : "boolean",
+					"default" : true
+				},
+				"originalMovementRules" : {
+					"type" : "boolean",
+					"default" : false
+				},
+				"lightweightFlyingMode" : {
+					"type" : "boolean",
+					"default" : false
+				}
+			}
+		},
 		"battle" : {
 			"type" : "object",
 			"additionalProperties" : false,

+ 0 - 95
lib/CGameState.cpp

@@ -2061,101 +2061,6 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col
 	return PlayerRelations::ENEMIES;
 }
 
-void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing)
-{
-	static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
-					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
-
-	//vec.reserve(8); //optimization
-	for (auto & dir : dirs)
-	{
-		const int3 hlp = tile + dir;
-		if(!map->isInTheMap(hlp))
-			continue;
-
-		const TerrainTile &hlpt = map->getTile(hlp);
-
-// 		//we cannot visit things from blocked tiles
-// 		if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE)
-// 		{
-// 			continue;
-// 		}
-
-        if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water
-		{
-			int3 hlp1 = tile,
-				hlp2 = tile;
-			hlp1.x += dir.x;
-			hlp2.y += dir.y;
-
-            if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER)
-				continue;
-		}
-
-        if((indeterminate(onLand)  ||  onLand == (hlpt.terType!=ETerrainType::WATER) )
-            && hlpt.terType != ETerrainType::ROCK)
-		{
-			vec.push_back(hlp);
-		}
-	}
-}
-
-int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast)
-{
-	if(src == dest) //same tile
-		return 0;
-
-	TerrainTile &s = map->getTile(src),
-		&d = map->getTile(dest);
-
-	//get basic cost
-	int ret = h->getTileCost(d,s);
-
-	if(d.blocked && h->canFly())
-	{
-		ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0;
-	}
-	else if(d.terType == ETerrainType::WATER)
-	{
-		if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds
-			ret *= 0.666;
-		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
-	{
-		int old = ret;
-		ret *= 1.414213;
-		//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
-		if(ret > remainingMovePoints  &&  remainingMovePoints >= old)
-		{
-			return remainingMovePoints;
-		}
-	}
-
-
-	int left = remainingMovePoints-ret;
-	if(checkLast  &&  left > 0  &&  remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points
-	{
-		std::vector<int3> vec;
-		vec.reserve(8); //optimization
-		getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true);
-		for(auto & elem : vec)
-		{
-			int fcost = getMovementCost(h, dest, elem, left, false);
-			if(fcost <= left)
-			{
-				return ret;
-			}
-		}
-		ret = remainingMovePoints;
-	}
-	return ret;
-}
-
 void CGameState::apply(CPack *pack)
 {
 	ui16 typ = typeList.getTypeID(pack);

+ 0 - 2
lib/CGameState.h

@@ -339,8 +339,6 @@ public:
 	bool isVisible(int3 pos, PlayerColor player);
 	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, 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 -----

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 793 - 203
lib/CPathfinder.cpp


+ 172 - 31
lib/CPathfinder.h

@@ -5,6 +5,8 @@
 #include "IGameCallback.h"
 #include "int3.h"
 
+#include <boost/heap/priority_queue.hpp>
+
 /*
  * CPathfinder.h, part of VCMI engine
  *
@@ -18,26 +20,45 @@
 class CGHeroInstance;
 class CGObjectInstance;
 struct TerrainTile;
+class CPathfinderHelper;
 
 struct DLL_LINKAGE CGPathNode
 {
-	enum EAccessibility
+	typedef EPathfindingLayer ELayer;
+
+	enum ENodeAction : ui8
+	{
+		UNKNOWN = 0,
+		EMBARK = 1,
+		DISEMBARK,
+		NORMAL,
+		BATTLE,
+		VISIT,
+		BLOCKING_VISIT
+	};
+
+	enum EAccessibility : ui8
 	{
 		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
+		FLYABLE, //can only be accessed in air layer
 		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
+	ui32 moveRemains; //remaining tiles after hero reaches the tile
+	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
+	ELayer layer;
+	EAccessibility accessible;
+	ENodeAction action;
+	bool locked;
 
 	CGPathNode();
+	void reset();
+	void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible);
 	bool reachable() const;
 };
 
@@ -52,27 +73,36 @@ struct DLL_LINKAGE CGPath
 
 struct DLL_LINKAGE CPathsInfo
 {
+	typedef EPathfindingLayer ELayer;
+
 	mutable boost::mutex pathMx;
 
-	const CGHeroInstance *hero;
+	const CGHeroInstance * hero;
 	int3 hpos;
 	int3 sizes;
-	CGPathNode ***nodes; //[w][h][level]
+	boost::multi_array<CGPathNode, 4> nodes; //[w][h][level][layer]
 
-	CPathsInfo(const int3 &Sizes);
+	CPathsInfo(const int3 & Sizes);
 	~CPathsInfo();
-	const CGPathNode * getPathInfo( int3 tile ) const;
-	bool getPath(const int3 &dst, CGPath &out) const;
-	int getDistance( int3 tile ) const;
+	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);
 };
 
 class CPathfinder : private CGameInfoCallback
 {
 public:
-	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
+	friend class CPathfinderHelper;
+
+	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:
+	typedef EPathfindingLayer ELayer;
+
 	struct PathfinderOptions
 	{
 		bool useFlying;
@@ -83,42 +113,153 @@ private:
 		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)
 
+		/// TODO: Find out with client and server code, merge with normal teleporters.
+		/// Likely proper implementation would require some refactoring of CGTeleport.
+		/// So for now this is unfinished and disabled by default.
+		bool useCastleGate;
+
+		/// If true transition into air layer only possible from initial node.
+		/// This is drastically decrease path calculation complexity (and time).
+		/// Downside is less MP effective paths calculation.
+		///
+		/// TODO: If this option end up useful for slow devices it's can be improved:
+		/// - Allow transition into air layer not only from initial position, but also from teleporters.
+		///   Movement into air can be also allowed when hero disembarked.
+		/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
+		///   Patrol support need similar functionality so it's won't be ton of useless code.
+		///   Such limitation could be useful as it's can be scaled depend on device performance.
+		bool lightweightFlyingMode;
+
+		/// This option enable one turn limitation for flying and water walking.
+		/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
+		///
+		/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
+		/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
+		///
+		/// TODO:
+		/// - Behavior when option is disabled not implemented and will lead to crashes.
+		bool oneTurnSpecialLayersLimit;
+
+		/// VCMI have different movement rules to solve flaws original engine has.
+		/// If this option enabled you'll able to do following things in fly:
+		/// - Move from blocked tiles to visitable one
+		/// - Move from guarded tiles to blockvis tiles without being attacked
+		/// - Move from guarded tiles to guarded visitable tiles with being attacked after
+		/// TODO:
+		/// - Option should also allow same tile land <-> air layer transitions.
+		///   Current implementation only allow go into (from) air layer only to neighbour tiles.
+		///   I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
+		bool originalMovementRules;
+
 		PathfinderOptions();
 	} options;
 
-	CPathsInfo &out;
-	const CGHeroInstance *hero;
+	CPathsInfo & out;
+	const CGHeroInstance * hero;
 	const std::vector<std::vector<std::vector<ui8> > > &FoW;
+	unique_ptr<CPathfinderHelper> hlp;
 
-	std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked
+	struct NodeComparer
+	{
+		bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const
+		{
+			if(rhs->turns > lhs->turns)
+				return false;
+			else if(rhs->turns == lhs->turns && rhs->moveRemains < lhs->moveRemains)
+				return false;
+
+			return true;
+		}
+	};
+	boost::heap::priority_queue<CGPathNode *, boost::heap::compare<NodeComparer> > pq;
 
+	std::vector<int3> neighbourTiles;
 	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
+	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 * ctObj, * dtObj;
+	CGPathNode::ENodeAction destAction;
 
-	void addNeighbours(const int3 &coord);
-	void addTeleportExits(bool noTeleportExcludes = false);
+	void addNeighbours();
+	void addTeleportExits();
 
-	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();
+	bool isLayerTransitionPossible(const ELayer dstLayer) const;
+	bool isLayerTransitionPossible() const;
+	bool isMovementToDestPossible() const;
+	bool isMovementAfterDestPossible() const;
+	CGPathNode::ENodeAction getDestAction() const;
 
-	int3 getSourceGuardPosition();
-	bool isSourceGuarded();
-	bool isDestinationGuarded();
-	bool isDestinationGuardian();
+	bool isSourceInitialPosition() const;
+	bool isSourceVisitableObj() const;
+	bool isSourceGuarded() const;
+	bool isDestVisitableObj() const;
+	bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
+	bool isDestinationGuardian() const;
 
 	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)
+	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
+	bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const;
+	bool canSeeObj(const CGObjectInstance * obj) 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 isAllowedTeleportEntrance(const CGTeleport * obj) const;
 	bool addTeleportTwoWay(const CGTeleport * obj) const;
 	bool addTeleportOneWay(const CGTeleport * obj) const;
 	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
 	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
+
+};
+
+struct DLL_LINKAGE TurnInfo
+{
+	/// This is certainly not the best design ever and certainly can be improved
+	/// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead
+	struct BonusCache {
+		std::vector<bool> noTerrainPenalty;
+		bool freeShipBoarding;
+		bool flyingMovement;
+		int flyingMovementVal;
+		bool waterWalking;
+		int waterWalkingVal;
+
+		BonusCache(TBonusListPtr bonusList);
+	};
+	unique_ptr<BonusCache> bonusCache;
+
+	const CGHeroInstance * hero;
+	TBonusListPtr bonuses;
+	mutable int maxMovePointsLand;
+	mutable int maxMovePointsWater;
+	int nativeTerrain;
+
+	TurnInfo(const CGHeroInstance * Hero, const int Turn = 0);
+	bool isLayerAvailable(const EPathfindingLayer layer) const;
+	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
+	int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const;
+	int getMaxMovePoints(const EPathfindingLayer layer) const;
+};
+
+class DLL_LINKAGE CPathfinderHelper
+{
+public:
+	CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options);
+	void updateTurnInfo(const int turn = 0);
+	bool isLayerAvailable(const EPathfindingLayer layer) const;
+	const TurnInfo * getTurnInfo() const;
+	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
+	int getMaxMovePoints(const EPathfindingLayer layer) const;
+
+	static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
+
+	static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true);
+	static int getMovementCost(const CGHeroInstance * h, const int3 & dst);
+
+private:
+	int turn;
+	const CGHeroInstance * hero;
+	std::vector<TurnInfo *> turnsInfo;
+	const CPathfinder::PathfinderOptions & options;
 };

+ 23 - 52
lib/GameConstants.cpp

@@ -25,56 +25,6 @@ const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);
 const PlayerColor PlayerColor::PLAYER_LIMIT = PlayerColor(PLAYER_LIMIT_I);
 const TeamID TeamID::NO_TEAM = TeamID(255);
 
-#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN)	\
-bool operator==(const A & a, const B & b)			\
-{													\
-	return AN == BN ;								\
-}													\
-bool operator!=(const A & a, const B & b)			\
-{													\
-	return AN != BN ;								\
-}													\
-bool operator<(const A & a, const B & b)			\
-{													\
-	return AN < BN ;								\
-}													\
-bool operator<=(const A & a, const B & b)			\
-{													\
-	return AN <= BN ;								\
-}													\
-bool operator>(const A & a, const B & b)			\
-{													\
-	return AN > BN ;								\
-}													\
-bool operator>=(const A & a, const B & b)			\
-{													\
-	return AN >= BN ;								\
-}
-
-#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME)	\
-	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num)	\
-	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b)	\
-	ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num)
-
-
-ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill)
-
-ID_LIKE_OPERATORS(Obj, Obj::EObj)
-
-ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType)
-
-ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID)
-
-ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
-
-ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
-
-ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
-
-ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
-
-ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
-
 CArtifact * ArtifactID::toArtifact() const
 {
 	return VLC->arth->artifacts[*this];
@@ -130,7 +80,7 @@ std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType
 	else return os << it->second;
 }
 
-std::ostream & operator<<(std::ostream & os, const ETerrainType actionType)
+std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType)
 {
 	static const std::map<ETerrainType::EETerrainType, std::string> terrainTypeToString =
 	{
@@ -147,9 +97,10 @@ std::ostream & operator<<(std::ostream & os, const ETerrainType actionType)
 		DEFINE_ELEMENT(LAVA),
 		DEFINE_ELEMENT(WATER),
 		DEFINE_ELEMENT(ROCK)
+	#undef DEFINE_ELEMENT
 	};
 
-	auto it = terrainTypeToString.find(actionType.num);
+	auto it = terrainTypeToString.find(terrainType.num);
 	if (it == terrainTypeToString.end()) return os << "<Unknown type>";
 	else return os << it->second;
 }
@@ -160,3 +111,23 @@ std::string ETerrainType::toString() const
 	ss << *this;
 	return ss.str();
 }
+
+std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer)
+{
+	static const std::map<EPathfindingLayer::EEPathfindingLayer, std::string> pathfinderLayerToString
+	{
+	#define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element}
+		DEFINE_ELEMENT(WRONG),
+		DEFINE_ELEMENT(AUTO),
+		DEFINE_ELEMENT(LAND),
+		DEFINE_ELEMENT(SAIL),
+		DEFINE_ELEMENT(WATER),
+		DEFINE_ELEMENT(AIR),
+		DEFINE_ELEMENT(NUM_LAYERS)
+	#undef DEFINE_ELEMENT
+	};
+
+	auto it = pathfinderLayerToString.find(pathfindingLayer.num);
+	if (it == pathfinderLayerToString.end()) return os << "<Unknown type>";
+	else return os << it->second;
+}

+ 61 - 25
lib/GameConstants.h

@@ -93,18 +93,37 @@ CLASS_NAME & advance(int i)							\
 }
 
 
-#define ID_LIKE_OPERATORS_INTERNAL_DECLS(A, B)			\
-bool DLL_LINKAGE operator==(const A & a, const B & b);	\
-bool DLL_LINKAGE operator!=(const A & a, const B & b);	\
-bool DLL_LINKAGE operator<(const A & a, const B & b);	\
-bool DLL_LINKAGE operator<=(const A & a, const B & b);	\
-bool DLL_LINKAGE operator>(const A & a, const B & b);	\
-bool DLL_LINKAGE operator>=(const A & a, const B & b);
+// Operators are performance-critical and to be inlined they must be in header
+#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN)	\
+STRONG_INLINE bool operator==(const A & a, const B & b)			\
+{													\
+	return AN == BN ;								\
+}													\
+STRONG_INLINE bool operator!=(const A & a, const B & b)			\
+{													\
+	return AN != BN ;								\
+}													\
+STRONG_INLINE bool operator<(const A & a, const B & b)			\
+{													\
+	return AN < BN ;								\
+}													\
+STRONG_INLINE bool operator<=(const A & a, const B & b)			\
+{													\
+	return AN <= BN ;								\
+}													\
+STRONG_INLINE bool operator>(const A & a, const B & b)			\
+{													\
+	return AN > BN ;								\
+}													\
+STRONG_INLINE bool operator>=(const A & a, const B & b)			\
+{													\
+	return AN >= BN ;								\
+}
 
-#define ID_LIKE_OPERATORS_DECLS(CLASS_NAME, ENUM_NAME)			\
-	ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, CLASS_NAME)	\
-	ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, ENUM_NAME)		\
-	ID_LIKE_OPERATORS_INTERNAL_DECLS(ENUM_NAME, CLASS_NAME)
+#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME)	\
+	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num)	\
+	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b)	\
+	ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num)
 
 
 #define OP_DECL_INT(CLASS_NAME, OP)					\
@@ -296,7 +315,7 @@ public:
 	ESecondarySkill num;
 };
 
-ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill)
+ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill)
 
 namespace EAlignment
 {
@@ -384,7 +403,7 @@ public:
 	EBuildingID num;
 };
 
-ID_LIKE_OPERATORS_DECLS(BuildingID, BuildingID::EBuildingID)
+ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
 
 namespace EBuildingState
 {
@@ -664,7 +683,7 @@ public:
 	EObj num;
 };
 
-ID_LIKE_OPERATORS_DECLS(Obj, Obj::EObj)
+ID_LIKE_OPERATORS(Obj, Obj::EObj)
 
 namespace SecSkillLevel
 {
@@ -754,10 +773,29 @@ public:
 	std::string toString() const;
 };
 
-DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType actionType);
+DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType);
 
-ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType)
+ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType)
 
+class DLL_LINKAGE EPathfindingLayer
+{
+public:
+	enum EEPathfindingLayer : ui8
+	{
+		LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS, WRONG, AUTO
+	};
+
+	EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num)
+	{}
+
+	ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer)
+
+	EEPathfindingLayer num;
+};
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer);
+
+ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer)
 
 class BFieldType
 {
@@ -780,7 +818,7 @@ public:
 	EBFieldType num;
 };
 
-ID_LIKE_OPERATORS_DECLS(BFieldType, BFieldType::EBFieldType)
+ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
 
 namespace EPlayerStatus
 {
@@ -820,7 +858,7 @@ public:
 	EArtifactPosition num;
 };
 
-ID_LIKE_OPERATORS_DECLS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
+ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
 
 class ArtifactID
 {
@@ -865,7 +903,7 @@ public:
 	EArtifactID num;
 };
 
-ID_LIKE_OPERATORS_DECLS(ArtifactID, ArtifactID::EArtifactID)
+ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID)
 
 class CreatureID
 {
@@ -909,7 +947,7 @@ public:
 	ECreatureID num;
 };
 
-ID_LIKE_OPERATORS_DECLS(CreatureID, CreatureID::ECreatureID)
+ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
 
 class SpellID
 {
@@ -953,7 +991,7 @@ public:
 	ESpellID num;
 };
 
-ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
+ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
 
 enum class ESpellSchool: ui8
 {
@@ -963,8 +1001,6 @@ enum class ESpellSchool: ui8
 	EARTH 	= 3
 };
 
-ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
-
 // Typedef declarations
 typedef ui8 TFaction;
 typedef si64 TExpType;
@@ -975,8 +1011,8 @@ typedef si32 TQuantity;
 typedef int TRmgTemplateZoneId;
 
 #undef ID_LIKE_CLASS_COMMON
-#undef ID_LIKE_OPERATORS_DECLS
-#undef ID_LIKE_OPERATORS_INTERNAL_DECLS
+#undef ID_LIKE_OPERATORS
+#undef ID_LIKE_OPERATORS_INTERNAL
 #undef INSTID_LIKE_CLASS_COMMON
 #undef OP_DECL_INT
 

+ 43 - 35
lib/mapObjects/CGHeroInstance.cpp

@@ -56,7 +56,7 @@ static int lowestSpeed(const CGHeroInstance * chi)
 	return ret;
 }
 
-ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
+ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const
 {
 	unsigned ret = GameConstants::BASE_MOVEMENT_COST;
 
@@ -80,28 +80,38 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro
 			break;
 		}
 	}
-	else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
+	else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
 	{
-		// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
-		// This is clearly bug in H3 however intended behaviour is not clear.
-		// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
-		// will always have best penalty without any influence from player-defined stacks order
+		ret = VLC->heroh->terrCosts[from.terType];
+		ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25;
+		if(ret < GameConstants::BASE_MOVEMENT_COST)
+			ret = GameConstants::BASE_MOVEMENT_COST;
+	}
+	return ret;
+}
 
-		for(auto stack : stacks)
-		{
-			int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
-			if(nativeTerrain != -1 && nativeTerrain != from.terType)
-			{
-				ret = VLC->heroh->terrCosts[from.terType];
-				ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25;
-				if(ret < GameConstants::BASE_MOVEMENT_COST)
-					ret = GameConstants::BASE_MOVEMENT_COST;
+int CGHeroInstance::getNativeTerrain() const
+{
+	// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
+	// This is clearly bug in H3 however intended behaviour is not clear.
+	// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
+	// will always have best penalty without any influence from player-defined stacks order
 
-				break;
-			}
-		}
+	// TODO: What should we do if all hero stacks are neutral creatures?
+	int nativeTerrain = -1;
+	for(auto stack : stacks)
+	{
+		int stackNativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
+		if(stackNativeTerrain == -1)
+			continue;
+
+		if(nativeTerrain == -1)
+			nativeTerrain = stackNativeTerrain;
+		else if(nativeTerrain != stackNativeTerrain)
+			return -1;
 	}
-	return ret;
+
+	return nativeTerrain;
 }
 
 int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
@@ -129,16 +139,6 @@ 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::WATER_WALKING);
-}
-
 ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const
 {
 	for(auto & elem : secSkills)
@@ -181,8 +181,11 @@ bool CGHeroInstance::canLearnSkill() const
 	return secSkills.size() < GameConstants::SKILL_PER_HERO;
 }
 
-int CGHeroInstance::maxMovePoints(bool onLand) const
+int CGHeroInstance::maxMovePoints(bool onLand, const TurnInfo * ti) const
 {
+	if(!ti)
+		ti = new TurnInfo(this);
+
 	int base;
 
 	if(onLand)
@@ -201,10 +204,10 @@ int CGHeroInstance::maxMovePoints(bool onLand) const
 	}
 
 	const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT;
-	const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt);
+	const int bonus = ti->valOfBonuses(Bonus::MOVEMENT) + ti->valOfBonuses(bt);
 
 	const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION;
-	const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
+	const double modifier = ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
 
 	return int(base* (1+modifier)) + bonus;
 }
@@ -1171,10 +1174,15 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
 		return CArmedInstance::whereShouldBeAttached(gs);
 }
 
-int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const
+int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/, const TurnInfo * ti) const
 {
-	if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
-		return (MPsBefore - basicCost) * static_cast<double>(maxMovePoints(disembark)) / maxMovePoints(!disembark);
+	if(!ti)
+		ti = new TurnInfo(this);
+
+	int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
+	int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
+	if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
+		return (MPsBefore - basicCost) * static_cast<double>(mp1) / mp2;
 
 	return 0; //take all MPs otherwise
 }

+ 5 - 5
lib/mapObjects/CGHeroInstance.h

@@ -21,6 +21,7 @@ class CHero;
 class CGBoat;
 class CGTownInstance;
 struct TerrainTile;
+struct TurnInfo;
 
 class CGHeroPlaceholder : public CGObjectInstance
 {
@@ -129,12 +130,11 @@ public:
 	EAlignment::EAlignment getAlignment() const;
 	const std::string &getBiography() const;
 	bool needsLastStack()const override;
-	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
+	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
+	int getNativeTerrain() const;
 	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
 
@@ -161,8 +161,8 @@ public:
 	void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value
 	void levelUp(std::vector<SecondarySkill> skills);
 
-	int maxMovePoints(bool onLand) const;
-	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
+	int maxMovePoints(bool onLand, const TurnInfo * ti = nullptr) const;
+	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const;
 
 	static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
 	double getFightingStrength() const; // takes attack / defense skill into account

+ 6 - 4
lib/mapping/CMap.cpp

@@ -132,11 +132,13 @@ Obj TerrainTile::topVisitableId(bool excludeTop) const
 
 CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
 {
-	auto visitableObj = visitableObjects;
-	if(excludeTop && visitableObj.size())
-		visitableObj.pop_back();
+	if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1))
+		return nullptr;
 
-	return visitableObj.size() ? visitableObj.back() : nullptr;
+	if(excludeTop)
+		return visitableObjects[visitableObjects.size()-2];
+
+	return visitableObjects.back();
 }
 
 bool TerrainTile::isCoastal() const

+ 27 - 10
server/CGameHandler.cpp

@@ -1764,7 +1764,6 @@ 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->movement);
 	const int3 guardPos = gs->guardingCreaturePosition(hmpos);
 
 	const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;
@@ -1779,12 +1778,16 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 	tmh.movePoints = h->movement;
 
 	//check if destination tile is available
+	auto ti = new TurnInfo(h);
+	const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT);
+	const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING);
+	const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti);
 
 	//it's a rock or blocked and not visitable tile
 	//OR hero is on land and dest is water and (there is not present only one object - boat)
-	if(((t.terType == ETerrainType::ROCK  ||  (t.blocked && !t.visitable && !h->hasBonusOfType(Bonus::FLYING_MOVEMENT) ))
+	if(((t.terType == ETerrainType::ROCK  ||  (t.blocked && !t.visitable && !canFly))
 			&& complain("Cannot move hero, destination tile is blocked!"))
-		|| ((!h->boat && !h->canWalkOnSea() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
+		|| ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
 			&& complain("Cannot move hero, destination tile is on water!"))
 		|| ((h->boat && t.terType != ETerrainType::WATER && t.blocked)
 			&& complain("Cannot disembark hero, tile is blocked!"))
@@ -1794,6 +1797,8 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 			&& complain("Can not move garrisoned hero!"))
 		|| ((h->movement < cost  &&  dst != h->pos  &&  !teleporting)
 			&& complain("Hero doesn't have any movement points left!"))
+		|| ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj()))
+			&& complain("Hero cannot transit over this tile!"))
 		/*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle)
 			&& complain("Cannot move hero during the battle"))*/)
 	{
@@ -1843,8 +1848,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 		}
 		else if(visitDest == VISIT_DEST)
 		{
-			if(!transit || !CGTeleport::isTeleport(t.topVisitableObj()))
-				visitObjectOnTile(t, h);
+			visitObjectOnTile(t, h);
 		}
 
 		queries.popIfTop(moveQuery);
@@ -1867,16 +1871,16 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 	};
 
 
-	if(embarking)
+	if(!transit && embarking)
 	{
-		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false);
+		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti);
 		return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
 		//attack guards on embarking? In H3 creatures on water had no zone of control at all
 	}
 
 	if(disembarking)
 	{
-		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true);
+		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti);
 		return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
 	}
 
@@ -1905,10 +1909,23 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 						? h->movement - cost
 						: 0;
 
-		if(blockingVisit())
+		EGuardLook lookForGuards = CHECK_FOR_GUARDS;
+		EVisitDest visitDest = VISIT_DEST;
+		if(transit)
+		{
+			if(CGTeleport::isTeleport(t.topVisitableObj()))
+				visitDest = DONT_VISIT_DEST;
+
+			if(canFly)
+			{
+				lookForGuards = IGNORE_GUARDS;
+				visitDest = DONT_VISIT_DEST;
+			}
+		}
+		else if(blockingVisit())
 			return true;
 
-		doMove(TryMoveHero::SUCCESS, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
+		doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE);
 		return true;
 	}
 }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно