浏览代码

Refactoring of new code, multiple functionality changes:

- Implemented separate Map View contexts for different map modes
- Puzzle map now works
- Enemy movement now works as in H3
- Removed no longer used code
- Implemented missing embark/disembark sounds
- Fixed view focusing on embarking/disembarking
- Fixed focusing on movement near edge of terra incognita
- Fixed sea movement sound
Ivan Savenko 2 年之前
父节点
当前提交
387a7b421a

+ 2 - 0
client/CMakeLists.txt

@@ -48,6 +48,7 @@ set(client_SRCS
 
 	mapRenderer/MapRenderer.cpp
 	mapRenderer/MapRendererContext.cpp
+	mapRenderer/MapRendererContextState.cpp
 	mapRenderer/MapView.cpp
 	mapRenderer/MapViewActions.cpp
 	mapRenderer/MapViewCache.cpp
@@ -170,6 +171,7 @@ set(client_HEADERS
 	mapRenderer/IMapRendererObserver.h
 	mapRenderer/MapRenderer.h
 	mapRenderer/MapRendererContext.h
+	mapRenderer/MapRendererContextState.h
 	mapRenderer/MapView.h
 	mapRenderer/MapViewActions.h
 	mapRenderer/MapViewCache.h

+ 6 - 142
client/CPlayerInterface.cpp

@@ -328,6 +328,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 	if (!hero)
 		return;
 
+	if (details.result == TryMoveHero::EMBARK || details.result == TryMoveHero::DISEMBARK)
+	{
+		if (hero->getRemovalSound())
+			CCS->soundh->playSound(hero->getRemovalSound().get());
+	}
+
 	adventureInt->minimap->updateTile(hero->convertToVisitablePos(details.start));
 	adventureInt->minimap->updateTile(hero->convertToVisitablePos(details.end));
 
@@ -1603,149 +1609,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
 		return (--dates.end())->second; //return latest file number
 	return 0;
 }
-/*
-void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp )
-{
-	auto subArr = (CGI->mh->ttiles)[hp.z];
-
-	int heroWidth  = ho->appearance->getWidth();
-	int heroHeight = ho->appearance->getHeight();
-
-	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
-	int tileMaxX = std::max(details.start.x, details.end.x);
-	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
-	int tileMaxY = std::max(details.start.y, details.end.y);
-
-	// determine tiles on which hero will be visible during movement and add hero as visible object on these tiles where necessary
-	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
-	{
-		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
-		{
-			bool heroVisibleHere = false;
-			auto & tile = subArr[tileX][tileY];
-
-			for(const auto & obj : tile.objects)
-			{
-				if (obj.obj == ho)
-				{
-					heroVisibleHere = true;
-					break;
-				}
-			}
-
-			if ( !heroVisibleHere)
-			{
-				tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32}));
-				std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter);
-			}
-		}
-	}
-}
-
-void CPlayerInterface::movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho )
-{
-	auto subArr = (CGI->mh->ttiles)[hp.z];
-
-	int heroWidth  = ho->appearance->getWidth();
-	int heroHeight = ho->appearance->getHeight();
-
-	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
-	int tileMaxX = std::max(details.start.x, details.end.x);
-	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
-	int tileMaxY = std::max(details.start.y, details.end.y);
-
-	std::shared_ptr<CAnimation> animation = graphics->getAnimation(ho);
-
-	assert(animation);
-	assert(animation->size(0) != 0);
-	auto image = animation->getImage(0,0);
-
-	int heroImageOldX = details.start.x * 32;
-	int heroImageOldY = details.start.y * 32;
-
-	int heroImageNewX = details.end.x * 32;
-	int heroImageNewY = details.end.y * 32;
-
-	int heroImageCurrX = heroImageOldX + i*(heroImageNewX - heroImageOldX)/32;
-	int heroImageCurrY = heroImageOldY + i*(heroImageNewY - heroImageOldY)/32;
-
-	// recompute which part of hero sprite will be visible on each tile at this point of movement animation
-	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
-	{
-		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
-		{
-			auto & tile = subArr[tileX][tileY];
-			for ( auto & obj : tile.objects)
-			{
-				if (obj.obj == ho)
-				{
-					int tilePosX = tileX * 32;
-					int tilePosY = tileY * 32;
-
-					obj.rect.x = tilePosX - heroImageCurrX + image->width() - 32;
-					obj.rect.y = tilePosY - heroImageCurrY + image->height() - 32;
-				}
-			}
-		}
-	}
-
-	//adventureInt->terrain->moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32;
-	//adventureInt->terrain->moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32;
-}
 
-void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho )
-{
-	auto subArr = (CGI->mh->ttiles)[hp.z];
-
-	int heroWidth  = ho->appearance->getWidth();
-	int heroHeight = ho->appearance->getHeight();
-
-	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
-	int tileMaxX = std::max(details.start.x, details.end.x);
-	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
-	int tileMaxY = std::max(details.start.y, details.end.y);
-
-	// erase hero from all tiles on which he is currently visible
-	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
-	{
-		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
-		{
-			auto & tile = subArr[tileX][tileY];
-			for (size_t i = 0; i < tile.objects.size(); ++i)
-			{
-				if ( tile.objects[i].obj == ho)
-				{
-					tile.objects.erase(tile.objects.begin() + i);
-					break;
-				}
-			}
-		}
-	}
-
-	// re-add hero to all tiles on which he will still be visible after animation is over
-	for ( int tileX = details.end.x - heroWidth + 1; tileX <= details.end.x; ++tileX)
-	{
-		for ( int tileY = details.end.y - heroHeight + 1; tileY <= details.end.y; ++tileY)
-		{
-			auto & tile = subArr[tileX][tileY];
-			tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32}));
-		}
-	}
-
-	// update object list on all tiles that were affected during previous operations
-	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
-	{
-		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
-		{
-			auto & tile = subArr[tileX][tileY];
-			std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter);
-		}
-	}
-
-	//recompute hero sprite positioning using hero's final position
-	movementPxStep(details, 32, hp, ho);
-}
-*/
 void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;

+ 16 - 22
client/NetPacksClient.cpp

@@ -444,20 +444,20 @@ void ApplyFirstClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
 {
 	CGHeroInstance *h = gs.getHero(pack.id);
 
-	//check if playerint will have the knowledge about movement - if not, directly update maphandler
-	for(auto i=cl.playerint.begin(); i!=cl.playerint.end(); i++)
+	if(CGI->mh)
 	{
-		auto ps = gs.getPlayerState(i->first);
-		if(ps && (gs.isVisible(h->convertToVisitablePos(pack.start), i->first) || gs.isVisible(h->convertToVisitablePos(pack.end), i->first)))
+		switch (pack.result)
 		{
-			if(ps->human)
-				pack.humanKnows = true;
+			case TryMoveHero::EMBARK:
+				CGI->mh->onBeforeHeroEmbark(h, pack.start, pack.end);
+				break;
+			case TryMoveHero::TELEPORTATION:
+				CGI->mh->onBeforeHeroTeleported(h, pack.start, pack.end);
+				break;
+			case TryMoveHero::DISEMBARK:
+				CGI->mh->onBeforeHeroDisembark(h, pack.start, pack.end);
+				break;
 		}
-	}
-
-	if(CGI->mh && pack.result == TryMoveHero::EMBARK)
-	{
-		CGI->mh->onObjectFadeOut(h);
 		CGI->mh->waitForOngoingAnimations();
 	}
 }
@@ -471,23 +471,17 @@ void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
 	{
 		switch(pack.result)
 		{
-			case TryMoveHero::FAILED:
-				break; // no-op
 			case TryMoveHero::SUCCESS:
 				CGI->mh->onHeroMoved(h, pack.start, pack.end);
 				break;
-			case TryMoveHero::TELEPORTATION:
-				CGI->mh->onHeroTeleported(h, pack.start, pack.end);
-				break;
-			case TryMoveHero::BLOCKING_VISIT:
-				CGI->mh->onHeroRotated(h, pack.start, pack.end);
-				break;
 			case TryMoveHero::EMBARK:
-				// handled in ApplyFirst
-				//CGI->mh->onObjectFadeOut(h);
+				CGI->mh->onAfterHeroEmbark(h, pack.start, pack.end);
+				break;
+			case TryMoveHero::TELEPORTATION:
+				CGI->mh->onAfterHeroTeleported(h, pack.start, pack.end);
 				break;
 			case TryMoveHero::DISEMBARK:
-				CGI->mh->onObjectFadeIn(h);
+				CGI->mh->onAfterHeroDisembark(h, pack.start, pack.end);
 				break;
 		}
 	}

+ 9 - 5
client/mapRenderer/IMapRendererObserver.h

@@ -37,12 +37,16 @@ public:
 	/// Removes object from map instantly, with no animation
 	virtual void onObjectInstantRemove(const CGObjectInstance * obj) {}
 
-	/// Perform hero teleportation animation with terrain fade animation
-	virtual void onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
-
 	/// Perform hero movement animation, moving hero across terrain
 	virtual void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
 
-	/// Instantly rotates hero to face destination tile
-	virtual void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+	/// Perform initialization of hero teleportation animation with terrain fade animation
+	virtual void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+	virtual void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+
+	virtual void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {};
+	virtual void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {};
+
+	virtual void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {};
+	virtual void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {};
 };

+ 289 - 214
client/mapRenderer/MapRendererContext.cpp

@@ -1,5 +1,5 @@
 /*
- * MapRendererContext.cpp, part of VCMI engine
+ * MapRendererContextState.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -11,335 +11,323 @@
 #include "StdInc.h"
 #include "MapRendererContext.h"
 
+#include "MapRendererContextState.h"
 #include "mapHandler.h"
 
+#include "../../CCallback.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../adventureMap/CAdvMapInt.h"
-#include "../../CCallback.h"
 
+#include "../../lib/Point.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"
+#include "../../lib/CPathfinder.h"
 
-MapObjectsSorter::MapObjectsSorter(IMapRendererContext & context)
-	: context(context)
+MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState)
+	: viewState(viewState)
 {
 }
 
-bool MapObjectsSorter::operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const
+uint32_t MapRendererBaseContext::getObjectRotation(ObjectInstanceID objectID) const
 {
-	return (*this)(context.getObject(left), context.getObject(right));
-}
+	const CGObjectInstance * obj = getObject(objectID);
 
-bool MapObjectsSorter::operator()(const CGObjectInstance * left, const CGObjectInstance * right) const
-{
-	//FIXME: remove mh access
-	return CGI->mh->compareObjectBlitOrder(left, right);
+	if(obj->ID == Obj::HERO)
+	{
+		const auto * hero = dynamic_cast<const CGHeroInstance *>(obj);
+		return hero->moveDir;
+	}
+
+	if(obj->ID == Obj::BOAT)
+	{
+		const auto * boat = dynamic_cast<const CGBoat *>(obj);
+
+		if(boat->hero)
+			return boat->hero->moveDir;
+		return boat->direction;
+	}
+	return 0;
 }
 
-int3 MapRendererContext::getMapSize() const
+int3 MapRendererBaseContext::getMapSize() const
 {
 	return LOCPLINT->cb->getMapSize();
 }
 
-bool MapRendererContext::isInMap(const int3 & coordinates) const
+bool MapRendererBaseContext::isInMap(const int3 & coordinates) const
 {
 	return LOCPLINT->cb->isInTheMap(coordinates);
 }
 
-const TerrainTile & MapRendererContext::getMapTile(const int3 & coordinates) const
+bool MapRendererBaseContext::isVisible(const int3 & coordinates) const
 {
-	return CGI->mh->getMap()->getTile(coordinates);
+	if(settingsSessionSpectate)
+		return LOCPLINT->cb->isInTheMap(coordinates);
+	else
+		return LOCPLINT->cb->isVisible(coordinates);
 }
 
-const CGObjectInstance * MapRendererContext::getObject(ObjectInstanceID objectID) const
+bool MapRendererBaseContext::tileAnimated(const int3 & coordinates) const
 {
-	return CGI->mh->getMap()->objects.at(objectID.getNum());
+	return false;
 }
 
-bool MapRendererContext::isVisible(const int3 & coordinates) const
+const TerrainTile & MapRendererBaseContext::getMapTile(const int3 & coordinates) const
 {
-	if (settingsSessionSpectate || showAllTerrain)
-		return LOCPLINT->cb->isInTheMap(coordinates);
-	return LOCPLINT->cb->isVisible(coordinates);
+	return CGI->mh->getMap()->getTile(coordinates);
 }
 
-const CGPath * MapRendererContext::currentPath() const
+const MapRendererBaseContext::MapObjectsList & MapRendererBaseContext::getObjects(const int3 & coordinates) const
 {
-	if (worldViewModeActive)
-		return nullptr;
-
-	const auto * hero = adventureInt->curHero();
-
-	if(!hero)
-		return nullptr;
-
-	if(!LOCPLINT->paths.hasPath(hero))
-		return nullptr;
-
-	return &LOCPLINT->paths.getPath(hero);
+	assert(isInMap(coordinates));
+	return viewState.objects[coordinates.z][coordinates.x][coordinates.y];
 }
 
-size_t MapRendererContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+const CGObjectInstance * MapRendererBaseContext::getObject(ObjectInstanceID objectID) const
 {
-	assert(groupSize > 0);
-	if(groupSize == 0)
-		return 0;
-
-	if (!settingsAdventureObjectAnimation)
-		return 0;
-
-	if (worldViewModeActive)
-		return 0;
-
-	// H3 timing for adventure map objects animation is 180 ms
-	// Terrain animations also use identical interval, however those are only present in HotA and/or HD Mod
-	size_t baseFrameTime = 180;
-
-	// hero movement animation always plays at ~50ms / frame
-	// in-game setting only affect movement across screen
-	if(movementAnimation && movementAnimation->target == objectID)
-		baseFrameTime = 50;
-
-	size_t frameCounter = animationTime / baseFrameTime;
-	size_t frameIndex = frameCounter % groupSize;
-	return frameIndex;
+	return CGI->mh->getMap()->objects.at(objectID.getNum());
 }
 
-size_t MapRendererContext::terrainImageIndex(size_t groupSize) const
+const CGPath * MapRendererBaseContext::currentPath() const
 {
-	if (!settingsAdventureTerrainAnimation)
-		return 0;
+	return nullptr;
+}
 
-	if (worldViewModeActive)
-		return 0;
+size_t MapRendererBaseContext::objectGroupIndex(ObjectInstanceID objectID) const
+{
+	static const std::vector<size_t> idleGroups = {0, 13, 0, 1, 2, 3, 4, 15, 14};
+	return idleGroups[getObjectRotation(objectID)];
+}
 
-	size_t baseFrameTime = 180;
-	size_t frameCounter = animationTime / baseFrameTime;
-	size_t frameIndex = frameCounter % groupSize;
-	return frameIndex;
+Point MapRendererBaseContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	const CGObjectInstance * object = getObject(objectID);
+	int3 offsetTiles(object->getPosition() - coordinates);
+	return Point(offsetTiles) * Point(32, 32);
 }
 
-bool MapRendererContext::tileAnimated(const int3 & coordinates) const
+double MapRendererBaseContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
 {
-	if (!isInMap(coordinates))
-		return false;
+	const CGObjectInstance * object = getObject(objectID);
 
-	if(movementAnimation)
+	if(object->ID == Obj::HERO)
 	{
-		auto objects = getObjects(coordinates);
-
-		if(vstd::contains(objects, movementAnimation->target))
-			return true;
-	}
+		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
 
-	if(fadeInAnimation)
-	{
-		auto objects = getObjects(coordinates);
+		if(hero->inTownGarrison)
+			return 0;
 
-		if(vstd::contains(objects, fadeInAnimation->target))
-			return true;
+		if(hero->boat)
+			return 0;
 	}
+	return 1;
+}
 
-	if(fadeOutAnimation)
-	{
-		auto objects = getObjects(coordinates);
+size_t MapRendererBaseContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	return 0;
+}
 
-		if(vstd::contains(objects, fadeOutAnimation->target))
-			return true;
-	}
-	return false;
+size_t MapRendererBaseContext::terrainImageIndex(size_t groupSize) const
+{
+	return 0;
+}
+
+size_t MapRendererBaseContext::overlayImageIndex(const int3 & coordinates) const
+{
+	return std::numeric_limits<size_t>::max();
 }
 
-bool MapRendererContext::filterGrayscale() const
+bool MapRendererBaseContext::filterGrayscale() const
 {
 	return false;
 }
 
-bool MapRendererContext::showRoads() const
+bool MapRendererBaseContext::showRoads() const
 {
 	return true;
 }
 
-bool MapRendererContext::showRivers() const
+bool MapRendererBaseContext::showRivers() const
 {
 	return true;
 }
 
-bool MapRendererContext::showBorder() const
+bool MapRendererBaseContext::showBorder() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showOverlay() const
 {
-	return !worldViewModeActive;
+	return false;
 }
 
-bool MapRendererContext::showOverlay() const
+bool MapRendererBaseContext::showGrid() const
 {
-	return worldViewModeActive;
+	return false;
 }
 
-bool MapRendererContext::showGrid() const
+bool MapRendererBaseContext::showVisitable() const
 {
-	return settingsSessionShowGrid;
+	return false;
 }
 
-bool MapRendererContext::showVisitable() const
+bool MapRendererBaseContext::showBlockable() const
 {
-	return settingsSessionShowVisitable;
+	return false;
 }
 
-bool MapRendererContext::showBlockable() const
+MapRendererAdventureContext::MapRendererAdventureContext(const MapRendererContextState & viewState)
+	: MapRendererBaseContext(viewState)
 {
-	return settingsSessionShowBlockable;
 }
 
-MapRendererContext::MapRendererContext()
+const CGPath * MapRendererAdventureContext::currentPath() const
 {
-	auto mapSize = getMapSize();
+	const auto * hero = adventureInt->curHero();
+
+	if(!hero)
+		return nullptr;
 
-	objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
+	if(!LOCPLINT->paths.hasPath(hero))
+		return nullptr;
 
-	for(const auto & obj : CGI->mh->getMap()->objects)
-		addObject(obj);
+	return &LOCPLINT->paths.getPath(hero);
 }
 
-void MapRendererContext::addObject(const CGObjectInstance * obj)
+size_t MapRendererAdventureContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
 {
-	if(!obj)
-		return;
+	assert(groupSize > 0);
 
-	for(int fx = 0; fx < obj->getWidth(); ++fx)
-	{
-		for(int fy = 0; fy < obj->getHeight(); ++fy)
-		{
-			int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
-
-			if(isInMap(currTile) && obj->coveringAt(currTile.x, currTile.y))
-			{
-				auto & container = objects[currTile.z][currTile.x][currTile.y];
-
-				container.push_back(obj->id);
-				boost::range::sort(container, MapObjectsSorter(*this));
-			}
-		}
-	}
+	if(!settingsAdventureObjectAnimation)
+		return 0;
+
+	if(groupSize == 0)
+		return 0;
+
+	// usign objectID for frameCounter to add pseudo-random element per-object.
+	// Without it, animation of multiple visible objects of the same type will always be in sync
+	size_t baseFrameTime = 180;
+	size_t frameCounter = animationTime / baseFrameTime + objectID.getNum();
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
 }
 
-void MapRendererContext::addMovingObject(const CGObjectInstance * object, const int3 & tileFrom, const int3 & tileDest)
+size_t MapRendererAdventureContext::terrainImageIndex(size_t groupSize) const
 {
-	int xFrom = std::min(tileFrom.x, tileDest.x) - object->getWidth();
-	int xDest = std::max(tileFrom.x, tileDest.x);
-	int yFrom = std::min(tileFrom.y, tileDest.y) - object->getHeight();
-	int yDest = std::max(tileFrom.y, tileDest.y);
+	if(!settingsAdventureTerrainAnimation)
+		return 0;
 
-	for(int x = xFrom; x <= xDest; ++x)
-	{
-		for(int y = yFrom; y <= yDest; ++y)
-		{
-			int3 currTile(x, y, object->pos.z);
-
-			if(isInMap(currTile))
-			{
-				auto & container = objects[currTile.z][currTile.x][currTile.y];
-
-				container.push_back(object->id);
-				boost::range::sort(container, MapObjectsSorter(*this));
-			}
-		}
-	}
+	size_t baseFrameTime = 180;
+	size_t frameCounter = animationTime / baseFrameTime;
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
 }
 
-void MapRendererContext::removeObject(const CGObjectInstance * object)
+bool MapRendererAdventureContext::showBorder() const
 {
-	for(int z = 0; z < getMapSize().z; z++)
-		for(int x = 0; x < getMapSize().x; x++)
-			for(int y = 0; y < getMapSize().y; y++)
-				vstd::erase(objects[z][x][y], object->id);
+	return true;
 }
 
-const MapRendererContext::MapObjectsList & MapRendererContext::getObjects(const int3 & coordinates) const
+bool MapRendererAdventureContext::showGrid() const
 {
-	assert(isInMap(coordinates));
-	return objects[coordinates.z][coordinates.x][coordinates.y];
+	return settingShowGrid;
 }
 
-size_t MapRendererContext::objectGroupIndex(ObjectInstanceID objectID) const
+bool MapRendererAdventureContext::showVisitable() const
 {
-	const CGObjectInstance * obj = getObject(objectID);
-	// TODO
-	static const std::vector<size_t> moveGroups = {99, 10, 5, 6, 7, 8, 9, 12, 11};
-	static const std::vector<size_t> idleGroups = {99, 13, 0, 1, 2, 3, 4, 15, 14};
+	return settingShowVisitable;
+}
 
-	if(obj->ID == Obj::HERO)
-	{
-		const auto * hero = dynamic_cast<const CGHeroInstance *>(obj);
-		if(movementAnimation && movementAnimation->target == objectID)
-			return moveGroups[hero->moveDir];
-		return idleGroups[hero->moveDir];
-	}
+bool MapRendererAdventureContext::showBlockable() const
+{
+	return settingShowBlockable;
+}
 
-	if(obj->ID == Obj::BOAT)
-	{
-		const auto * boat = dynamic_cast<const CGBoat *>(obj);
+MapRendererAdventureFadingContext::MapRendererAdventureFadingContext(const MapRendererContextState & viewState)
+	: MapRendererAdventureContext(viewState)
+{
+}
+
+bool MapRendererAdventureFadingContext::tileAnimated(const int3 & coordinates) const
+{
+	if(!isInMap(coordinates))
+		return false;
 
-		uint8_t direction = boat->hero ? boat->hero->moveDir : boat->direction;
+	auto objects = getObjects(coordinates);
+	if(vstd::contains(objects, target))
+		return true;
 
-		if(movementAnimation && movementAnimation->target == objectID)
-			return moveGroups[direction];
-		return idleGroups[direction];
-	}
-	return 0;
+	return false;
 }
 
-Point MapRendererContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
+double MapRendererAdventureFadingContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
 {
-	if(movementAnimation && movementAnimation->target == objectID)
-	{
-		int3 offsetTilesFrom = movementAnimation->tileFrom - coordinates;
-		int3 offsetTilesDest = movementAnimation->tileDest - coordinates;
+	if(objectID == target)
+		return progress;
 
-		Point offsetPixelsFrom = Point(offsetTilesFrom) * Point(32, 32);
-		Point offsetPixelsDest = Point(offsetTilesDest) * Point(32, 32);
+	return 1.0;
+}
 
-		Point result = vstd::lerp(offsetPixelsFrom, offsetPixelsDest, movementAnimation->progress);
+MapRendererAdventureMovingContext::MapRendererAdventureMovingContext(const MapRendererContextState & viewState)
+	: MapRendererAdventureContext(viewState)
+{
+}
 
-		return result;
+size_t MapRendererAdventureMovingContext::objectGroupIndex(ObjectInstanceID objectID) const
+{
+	if(target == objectID)
+	{
+		static const std::vector<size_t> moveGroups = {0, 10, 5, 6, 7, 8, 9, 12, 11};
+		return moveGroups[getObjectRotation(objectID)];
 	}
-
-	const CGObjectInstance * object = getObject(objectID);
-	int3 offsetTiles(object->getPosition() - coordinates);
-	return Point(offsetTiles) * Point(32, 32);
+	return MapRendererAdventureContext::objectGroupIndex(objectID);
 }
 
-double MapRendererContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
+bool MapRendererAdventureMovingContext::tileAnimated(const int3 & coordinates) const
 {
-	const CGObjectInstance * object = getObject(objectID);
+	if(!isInMap(coordinates))
+		return false;
 
-	if(object->ID == Obj::HERO)
+	auto objects = getObjects(coordinates);
+	if(vstd::contains(objects, target))
+		return true;
+
+	return false;
+}
+
+Point MapRendererAdventureMovingContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	if(target == objectID)
 	{
-		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
+		int3 offsetTilesFrom = tileFrom - coordinates;
+		int3 offsetTilesDest = tileDest - coordinates;
 
-		if(hero->inTownGarrison)
-			return 0;
+		Point offsetPixelsFrom = Point(offsetTilesFrom) * Point(32, 32);
+		Point offsetPixelsDest = Point(offsetTilesDest) * Point(32, 32);
 
-		if(hero->boat)
-			return 0;
-	}
+		Point result = vstd::lerp(offsetPixelsFrom, offsetPixelsDest, progress);
 
-	if(showAllTerrain)
-	{
-		if(object->isVisitable() && !LOCPLINT->cb->isVisible(coordinates))
-			return 0;
+		return result;
 	}
 
-	if(fadeOutAnimation && objectID == fadeOutAnimation->target)
-		return 1.0 - fadeOutAnimation->progress;
+	return MapRendererAdventureContext::objectImageOffset(objectID, coordinates);
+}
 
-	if(fadeInAnimation && objectID == fadeInAnimation->target)
-		return fadeInAnimation->progress;
+size_t MapRendererAdventureMovingContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	if(target != objectID)
+		return MapRendererAdventureContext::objectImageIndex(objectID, groupSize);
 
-	return 1.0;
+	int32_t baseFrameTime = 50;
+	size_t frameCounter = animationTime / baseFrameTime;
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
 }
 
-size_t MapRendererContext::selectOverlayImageForObject(const ObjectPosInfo & object) const
+size_t MapRendererWorldViewContext::selectOverlayImageForObject(const ObjectPosInfo & object) const
 {
 	size_t ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
 
@@ -368,19 +356,18 @@ size_t MapRendererContext::selectOverlayImageForObject(const ObjectPosInfo & obj
 	return std::numeric_limits<size_t>::max();
 }
 
-size_t MapRendererContext::overlayImageIndex(const int3 & coordinates) const
+MapRendererWorldViewContext::MapRendererWorldViewContext(const MapRendererContextState & viewState)
+	: MapRendererBaseContext(viewState)
 {
-	for(const auto & entry : additionalOverlayIcons)
-	{
-		if(entry.pos != coordinates)
-			continue;
-
-		size_t iconIndex = selectOverlayImageForObject(entry);
+}
 
-		if(iconIndex != std::numeric_limits<size_t>::max())
-			return iconIndex;
-	}
+bool MapRendererWorldViewContext::showOverlay() const
+{
+	return true;
+}
 
+size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates) const
+{
 	if(!isVisible(coordinates))
 		return std::numeric_limits<size_t>::max();
 
@@ -402,5 +389,93 @@ size_t MapRendererContext::overlayImageIndex(const int3 & coordinates) const
 		if(iconIndex != std::numeric_limits<size_t>::max())
 			return iconIndex;
 	}
+
 	return std::numeric_limits<size_t>::max();
 }
+
+MapRendererSpellViewContext::MapRendererSpellViewContext(const MapRendererContextState & viewState)
+	: MapRendererWorldViewContext(viewState)
+{
+}
+
+double MapRendererSpellViewContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	if(showAllTerrain)
+	{
+		if(getObject(objectID)->isVisitable() && !MapRendererWorldViewContext::isVisible(coordinates))
+			return 0;
+	}
+
+	return MapRendererWorldViewContext::objectTransparency(objectID, coordinates);
+}
+
+bool MapRendererSpellViewContext::isVisible(const int3 & coordinates) const
+{
+	if (showAllTerrain)
+		return isInMap(coordinates);
+	return MapRendererBaseContext::isVisible(coordinates);
+}
+
+size_t MapRendererSpellViewContext::overlayImageIndex(const int3 & coordinates) const
+{
+	for(const auto & entry : additionalOverlayIcons)
+	{
+		if(entry.pos != coordinates)
+			continue;
+
+		size_t iconIndex = selectOverlayImageForObject(entry);
+
+		if(iconIndex != std::numeric_limits<size_t>::max())
+			return iconIndex;
+	}
+
+	return MapRendererWorldViewContext::overlayImageIndex(coordinates);
+}
+
+MapRendererPuzzleMapContext::MapRendererPuzzleMapContext(const MapRendererContextState & viewState)
+	: MapRendererBaseContext(viewState)
+{
+}
+
+MapRendererPuzzleMapContext::~MapRendererPuzzleMapContext() = default;
+
+const CGPath * MapRendererPuzzleMapContext::currentPath() const
+{
+	return grailPos.get();
+}
+
+double MapRendererPuzzleMapContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	const auto * object = getObject(objectID);
+
+	if(!object)
+		return 0;
+
+	if(object->isVisitable())
+		return 0;
+
+	if(object->ID == Obj::HOLE)
+		return 0;
+
+	return MapRendererBaseContext::objectTransparency(objectID, coordinates);
+}
+
+bool MapRendererPuzzleMapContext::isVisible(const int3 & coordinates) const
+{
+	return LOCPLINT->cb->isInTheMap(coordinates);
+}
+
+bool MapRendererPuzzleMapContext::filterGrayscale() const
+{
+	return true;
+}
+
+bool MapRendererPuzzleMapContext::showRoads() const
+{
+	return false;
+}
+
+bool MapRendererPuzzleMapContext::showRivers() const
+{
+	return false;
+}

+ 97 - 80
client/mapRenderer/MapRendererContext.h

@@ -11,122 +11,139 @@
 
 #include "IMapRendererContext.h"
 
-#include "../lib/int3.h"
 #include "../lib/GameConstants.h"
+#include "../lib/int3.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 struct ObjectPosInfo;
 VCMI_LIB_NAMESPACE_END
 
-class MapObjectsSorter
+struct MapRendererContextState;
+
+class MapRendererBaseContext : public IMapRendererContext
 {
-	IMapRendererContext & context;
+public:
+	const MapRendererContextState & viewState;
+	bool settingsSessionSpectate = false;
+
+	explicit MapRendererBaseContext(const MapRendererContextState & viewState);
+
+	uint32_t getObjectRotation(ObjectInstanceID objectID) const;
 
+	int3 getMapSize() const override;
+	bool isInMap(const int3 & coordinates) const override;
+	bool isVisible(const int3 & coordinates) const override;
+	bool tileAnimated(const int3 & coordinates) const override;
+
+	const TerrainTile & getMapTile(const int3 & coordinates) const override;
+	const MapObjectsList & getObjects(const int3 & coordinates) const override;
+	const CGObjectInstance * getObject(ObjectInstanceID objectID) const override;
+	const CGPath * currentPath() const override;
+
+	size_t objectGroupIndex(ObjectInstanceID objectID) const override;
+	Point objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
+	size_t terrainImageIndex(size_t groupSize) const override;
+	size_t overlayImageIndex(const int3 & coordinates) const override;
+
+	bool filterGrayscale() const override;
+	bool showRoads() const override;
+	bool showRivers() const override;
+	bool showBorder() const override;
+	bool showOverlay() const override;
+	bool showGrid() const override;
+	bool showVisitable() const override;
+	bool showBlockable() const override;
+};
+
+class MapRendererAdventureContext : public MapRendererBaseContext
+{
 public:
-	explicit MapObjectsSorter(IMapRendererContext & context);
+	uint32_t animationTime = 0;
+	bool settingShowGrid = false;
+	bool settingShowVisitable = false;
+	bool settingShowBlockable = false;
+	bool settingsAdventureObjectAnimation = true;
+	bool settingsAdventureTerrainAnimation = true;
+
+	explicit MapRendererAdventureContext(const MapRendererContextState & viewState);
+
+	const CGPath * currentPath() const override;
+	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
+	size_t terrainImageIndex(size_t groupSize) const override;
 
-	bool operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const;
-	bool operator()(const CGObjectInstance * left, const CGObjectInstance * right) const;
+	bool showBorder() const override;
+	bool showGrid() const override;
+	bool showVisitable() const override;
+	bool showBlockable() const override;
 };
 
-struct HeroAnimationState
+class MapRendererAdventureFadingContext : public MapRendererAdventureContext
 {
+public:
 	ObjectInstanceID target;
-	int3 tileFrom;
-	int3 tileDest;
 	double progress;
+
+	explicit MapRendererAdventureFadingContext(const MapRendererContextState & viewState);
+
+	bool tileAnimated(const int3 & coordinates) const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
 };
 
-struct FadingAnimationState
+class MapRendererAdventureMovingContext : public MapRendererAdventureContext
 {
+public:
 	ObjectInstanceID target;
+	int3 tileFrom;
+	int3 tileDest;
 	double progress;
-};
 
-// from VwSymbol.def
-enum class EWorldViewIcon
-{
-	TOWN = 0,
-	HERO = 1,
-	ARTIFACT = 2,
-	TELEPORT = 3,
-	GATE = 4,
-	MINE_WOOD = 5,
-	MINE_MERCURY = 6,
-	MINE_STONE = 7,
-	MINE_SULFUR = 8,
-	MINE_CRYSTAL = 9,
-	MINE_GEM = 10,
-	MINE_GOLD = 11,
-	RES_WOOD = 12,
-	RES_MERCURY = 13,
-	RES_STONE = 14,
-	RES_SULFUR = 15,
-	RES_CRYSTAL = 16,
-	RES_GEM = 17,
-	RES_GOLD = 18,
-
-	ICONS_PER_PLAYER = 19,
-	ICONS_TOTAL = 19 * 9 // 8 players + neutral set at the end
+	explicit MapRendererAdventureMovingContext(const MapRendererContextState & viewState);
+
+	bool tileAnimated(const int3 & coordinates) const override;
+	size_t objectGroupIndex(ObjectInstanceID objectID) const override;
+	Point objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
 };
 
-class MapRendererContext : public IMapRendererContext
+class MapRendererWorldViewContext : public MapRendererBaseContext
 {
-	friend class MapViewController;
-
-	boost::multi_array<MapObjectsList, 3> objects;
+protected:
+	size_t selectOverlayImageForObject(const ObjectPosInfo & object) const;
 
-	uint32_t animationTime = 0;
-
-	boost::optional<HeroAnimationState> movementAnimation;
-	boost::optional<HeroAnimationState> teleportAnimation;
+public:
+	explicit MapRendererWorldViewContext(const MapRendererContextState & viewState);
 
-	boost::optional<FadingAnimationState> fadeOutAnimation;
-	boost::optional<FadingAnimationState> fadeInAnimation;
+	size_t overlayImageIndex(const int3 & coordinates) const override;
+	bool showOverlay() const override;
+};
 
+class MapRendererSpellViewContext : public MapRendererWorldViewContext
+{
+public:
 	std::vector<ObjectPosInfo> additionalOverlayIcons;
-
-	bool worldViewModeActive = false;
 	bool showAllTerrain = false;
-	bool settingsSessionSpectate = false;
-	bool settingsAdventureObjectAnimation = true;
-	bool settingsAdventureTerrainAnimation = true;
-	bool settingsSessionShowGrid = false;
-	bool settingsSessionShowVisitable = false;
-	bool settingsSessionShowBlockable = false;
 
-	size_t selectOverlayImageForObject(const ObjectPosInfo & objectID) const;
+	explicit MapRendererSpellViewContext(const MapRendererContextState & viewState);
 
-public:
-	MapRendererContext();
-
-	void addObject(const CGObjectInstance * object);
-	void addMovingObject(const CGObjectInstance * object, const int3 & tileFrom, const int3 & tileDest);
-	void removeObject(const CGObjectInstance * object);
-
-	int3 getMapSize() const final;
-	bool isInMap(const int3 & coordinates) const final;
 	bool isVisible(const int3 & coordinates) const override;
-	bool tileAnimated(const int3 & coordinates) const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	size_t overlayImageIndex(const int3 & coordinates) const override;
+};
 
-	const TerrainTile & getMapTile(const int3 & coordinates) const override;
-	const MapObjectsList & getObjects(const int3 & coordinates) const override;
-	const CGObjectInstance * getObject(ObjectInstanceID objectID) const override;
-	const CGPath * currentPath() const override;
+class MapRendererPuzzleMapContext : public MapRendererBaseContext
+{
+public:
+	std::unique_ptr<CGPath> grailPos;
 
-	size_t objectGroupIndex(ObjectInstanceID objectID) const override;
-	Point objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const override;
-	double objectTransparency(ObjectInstanceID objectID, const int3 &coordinates) const override;
-	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
-	size_t terrainImageIndex(size_t groupSize) const override;
-	size_t overlayImageIndex(const int3 & coordinates) const override;
+	explicit MapRendererPuzzleMapContext(const MapRendererContextState & viewState);
+	~MapRendererPuzzleMapContext();
 
+	const CGPath * currentPath() const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	bool isVisible(const int3 & coordinates) const override;
 	bool filterGrayscale() const override;
 	bool showRoads() const override;
 	bool showRivers() const override;
-	bool showBorder() const override;
-	bool showOverlay() const override;
-	bool showGrid() const override;
-	bool showVisitable() const override;
-	bool showBlockable() const override;
 };

+ 93 - 0
client/mapRenderer/MapRendererContextState.cpp

@@ -0,0 +1,93 @@
+/*
+ * MapRendererContext.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
+ *
+ */
+
+#include "StdInc.h"
+#include "MapRendererContextState.h"
+
+#include "IMapRendererContext.h"
+#include "mapHandler.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CAdvMapInt.h"
+#include "../../CCallback.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/CMap.h"
+
+static bool compareObjectBlitOrder(ObjectInstanceID left, ObjectInstanceID right)
+{
+	//FIXME: remove mh access
+	return CGI->mh->compareObjectBlitOrder(CGI->mh->getMap()->objects[left.getNum()], CGI->mh->getMap()->objects[right.getNum()]);
+}
+
+MapRendererContextState::MapRendererContextState()
+{
+	auto mapSize = LOCPLINT->cb->getMapSize();
+
+	objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
+
+	for(const auto & obj : CGI->mh->getMap()->objects)
+		addObject(obj);
+}
+
+void MapRendererContextState::addObject(const CGObjectInstance * obj)
+{
+	if(!obj)
+		return;
+
+	for(int fx = 0; fx < obj->getWidth(); ++fx)
+	{
+		for(int fy = 0; fy < obj->getHeight(); ++fy)
+		{
+			int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
+
+			if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y))
+			{
+				auto & container = objects[currTile.z][currTile.x][currTile.y];
+
+				container.push_back(obj->id);
+				boost::range::sort(container, compareObjectBlitOrder);
+			}
+		}
+	}
+}
+
+void MapRendererContextState::addMovingObject(const CGObjectInstance * object, const int3 & tileFrom, const int3 & tileDest)
+{
+	int xFrom = std::min(tileFrom.x, tileDest.x) - object->getWidth();
+	int xDest = std::max(tileFrom.x, tileDest.x);
+	int yFrom = std::min(tileFrom.y, tileDest.y) - object->getHeight();
+	int yDest = std::max(tileFrom.y, tileDest.y);
+
+	for(int x = xFrom; x <= xDest; ++x)
+	{
+		for(int y = yFrom; y <= yDest; ++y)
+		{
+			int3 currTile(x, y, object->pos.z);
+
+			if(LOCPLINT->cb->isInTheMap(currTile))
+			{
+				auto & container = objects[currTile.z][currTile.x][currTile.y];
+
+				container.push_back(object->id);
+				boost::range::sort(container, compareObjectBlitOrder);
+			}
+		}
+	}
+}
+
+void MapRendererContextState::removeObject(const CGObjectInstance * object)
+{
+	for(int z = 0; z < LOCPLINT->cb->getMapSize().z; z++)
+		for(int x = 0; x < LOCPLINT->cb->getMapSize().x; x++)
+			for(int y = 0; y < LOCPLINT->cb->getMapSize().y; y++)
+				vstd::erase(objects[z][x][y], object->id);
+}

+ 62 - 0
client/mapRenderer/MapRendererContextState.h

@@ -0,0 +1,62 @@
+/*
+ * MapRendererContext.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../lib/int3.h"
+#include "../lib/GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct ObjectPosInfo;
+class CGObjectInstance;
+VCMI_LIB_NAMESPACE_END
+
+class IMapRendererContext;
+
+// from VwSymbol.def
+enum class EWorldViewIcon
+{
+	TOWN = 0,
+	HERO = 1,
+	ARTIFACT = 2,
+	TELEPORT = 3,
+	GATE = 4,
+	MINE_WOOD = 5,
+	MINE_MERCURY = 6,
+	MINE_STONE = 7,
+	MINE_SULFUR = 8,
+	MINE_CRYSTAL = 9,
+	MINE_GEM = 10,
+	MINE_GOLD = 11,
+	RES_WOOD = 12,
+	RES_MERCURY = 13,
+	RES_STONE = 14,
+	RES_SULFUR = 15,
+	RES_CRYSTAL = 16,
+	RES_GEM = 17,
+	RES_GOLD = 18,
+
+	ICONS_PER_PLAYER = 19,
+	ICONS_TOTAL = 19 * 9 // 8 players + neutral set at the end
+};
+
+struct MapRendererContextState
+{
+public:
+	MapRendererContextState();
+
+	using MapObject = ObjectInstanceID;
+	using MapObjectsList = std::vector<MapObject>;
+
+	boost::multi_array<MapObjectsList, 3> objects;
+
+	void addObject(const CGObjectInstance * object);
+	void addMovingObject(const CGObjectInstance * object, const int3 & tileFrom, const int3 & tileDest);
+	void removeObject(const CGObjectInstance * object);
+};

+ 32 - 13
client/mapRenderer/MapView.cpp

@@ -32,9 +32,9 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"
 
-MapView::~MapView() = default;
+BasicMapView::~BasicMapView() = default;
 
-std::shared_ptr<MapViewModel> MapView::createModel(const Point & dimensions) const
+std::shared_ptr<MapViewModel> BasicMapView::createModel(const Point & dimensions) const
 {
 	auto result = std::make_shared<MapViewModel>();
 
@@ -46,22 +46,18 @@ std::shared_ptr<MapViewModel> MapView::createModel(const Point & dimensions) con
 	return result;
 }
 
-MapView::MapView(const Point & offset, const Point & dimensions)
+BasicMapView::BasicMapView(const Point & offset, const Point & dimensions)
 	: model(createModel(dimensions))
-	, controller(new MapViewController(model))
 	, tilesCache(new MapViewCache(model))
-	, isSwiping(false)
+	, controller(new MapViewController(model, tilesCache))
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	pos += offset;
 	pos.w = dimensions.x;
 	pos.h = dimensions.y;
-
-	actions = std::make_shared<MapViewActions>(*this, model);
-	actions->setContext(controller->getContext());
 }
 
-void MapView::render(Canvas & target, bool fullUpdate)
+void BasicMapView::render(Canvas & target, bool fullUpdate)
 {
 	Canvas targetClipped(target, pos);
 
@@ -70,20 +66,35 @@ void MapView::render(Canvas & target, bool fullUpdate)
 	tilesCache->render(controller->getContext(), targetClipped, fullUpdate);
 }
 
-void MapView::show(SDL_Surface * to)
+void BasicMapView::show(SDL_Surface * to)
 {
 	Canvas target(to);
 	CSDL_Ext::CClipRectGuard guard(to, pos);
 	render(target, false);
 }
 
-void MapView::showAll(SDL_Surface * to)
+void BasicMapView::showAll(SDL_Surface * to)
 {
 	Canvas target(to);
 	CSDL_Ext::CClipRectGuard guard(to, pos);
 	render(target, true);
 }
 
+void MapView::show(SDL_Surface * to)
+{
+	actions->setContext(controller->getContext());
+	BasicMapView::show(to);
+}
+
+MapView::MapView(const Point & offset, const Point & dimensions)
+	: BasicMapView(offset, dimensions)
+	, isSwiping(false)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	actions = std::make_shared<MapViewActions>(*this, model);
+	actions->setContext(controller->getContext());
+}
+
 void MapView::onMapLevelSwitched()
 {
 	if(LOCPLINT->cb->getMapSize().z > 1)
@@ -124,6 +135,7 @@ void MapView::onCenteredObject(const CGObjectInstance * target)
 
 void MapView::onViewSpellActivated(uint32_t tileSize, const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
 {
+	controller->activateSpellViewContext();
 	controller->setTileSize(Point(tileSize, tileSize));
 	controller->setOverlayVisibility(objectPositions);
 	controller->setTerrainVisibility(showTerrain);
@@ -131,12 +143,19 @@ void MapView::onViewSpellActivated(uint32_t tileSize, const std::vector<ObjectPo
 
 void MapView::onViewWorldActivated(uint32_t tileSize)
 {
+	controller->activateWorldViewContext();
 	controller->setTileSize(Point(tileSize, tileSize));
 }
 
 void MapView::onViewMapActivated()
 {
+	controller->activateAdventureContext();
 	controller->setTileSize(Point(32, 32));
-	controller->setOverlayVisibility({});
-	controller->setTerrainVisibility(false);
+}
+
+PuzzleMapView::PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter)
+	: BasicMapView(offset, dimensions)
+{
+	controller->setViewCenter(tileToCenter);
+	controller->activatePuzzleMapContext(tileToCenter);
 }

+ 28 - 12
client/mapRenderer/MapView.h

@@ -21,24 +21,38 @@ class MapViewController;
 class MapViewModel;
 class MapViewCache;
 
-/// Main class that represents visible section of adventure map
-/// Contains all public interface of view and translates calls to internal model
-class MapView : public CIntObject
+/// Internal class that contains logic shared between all map views
+class BasicMapView : public CIntObject
 {
+protected:
 	std::shared_ptr<MapViewModel> model;
 	std::shared_ptr<MapViewCache> tilesCache;
 	std::shared_ptr<MapViewController> controller;
-	std::shared_ptr<MapViewActions> actions;
-
-	bool isSwiping;
 
 	std::shared_ptr<MapViewModel> createModel(const Point & dimensions) const;
 
 	void render(Canvas & target, bool fullUpdate);
 
 public:
+	BasicMapView(const Point & offset, const Point & dimensions);
+	~BasicMapView() override;
+
+	void show(SDL_Surface * to) override;
+	void showAll(SDL_Surface * to) override;
+};
+
+/// Main class that represents visible section of adventure map
+/// Contains all public interface of view and translates calls to internal model
+class MapView : public BasicMapView
+{
+	std::shared_ptr<MapViewActions> actions;
+
+	bool isSwiping;
+
+public:
+	void show(SDL_Surface * to) override;
+
 	MapView(const Point & offset, const Point & dimensions);
-	~MapView() override;
 
 	/// Moves current view to another level, preserving position
 	void onMapLevelSwitched();
@@ -55,9 +69,7 @@ public:
 	/// Moves current view to specified tile
 	void onCenteredTile(const int3 & tile);
 
-	/// Centers view on object and starts "tracking" it
-	/// Whenever object changes position, so will the object
-	/// Tracking will be disabled on any call that moves view
+	/// Moves current view to specified object
 	void onCenteredObject(const CGObjectInstance * target);
 
 	/// Switches view to "View Earth" / "View Air" mode, displaying downscaled map with overlay
@@ -68,7 +80,11 @@ public:
 
 	/// Switches view from View World mode back to standard view
 	void onViewMapActivated();
+};
 
-	void show(SDL_Surface * to) override;
-	void showAll(SDL_Surface * to) override;
+/// Main class that represents map view for puzzle map
+class PuzzleMapView : public BasicMapView
+{
+public:
+	PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter);
 };

+ 27 - 0
client/mapRenderer/MapViewCache.cpp

@@ -54,6 +54,33 @@ std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_p
 	return nullptr;
 }
 
+void MapViewCache::invalidate(const std::shared_ptr<IMapRendererContext> & context, const int3 & tile)
+{
+	int cacheX = (terrainChecksum.shape()[0] + tile.x) % terrainChecksum.shape()[0];
+	int cacheY = (terrainChecksum.shape()[1] + tile.y) % terrainChecksum.shape()[1];
+
+	auto & entry = terrainChecksum[cacheX][cacheY];
+
+	if (entry.tileX == tile.x && entry.tileY ==tile.y)
+		entry = TileChecksum{};
+}
+
+void MapViewCache::invalidate(const std::shared_ptr<IMapRendererContext> & context, const ObjectInstanceID & object)
+{
+	for (size_t cacheY = 0; cacheY < terrainChecksum.shape()[1]; ++cacheY)
+	{
+		for (size_t cacheX = 0; cacheX < terrainChecksum.shape()[0]; ++cacheX)
+		{
+			auto & entry = terrainChecksum[cacheX][cacheY];
+
+			int3 tile( entry.tileX, entry.tileY, cachedLevel);
+
+			if (context->isInMap(tile) && vstd::contains(context->getObjects(tile), object))
+				entry = TileChecksum{};
+		}
+	}
+}
+
 void MapViewCache::updateTile(const std::shared_ptr<IMapRendererContext> & context, const int3 & coordinates)
 {
 	int cacheX = (terrainChecksum.shape()[0] + coordinates.x) % terrainChecksum.shape()[0];

+ 4 - 0
client/mapRenderer/MapViewCache.h

@@ -17,6 +17,7 @@ class Canvas;
 class MapRenderer;
 class IMapRendererContext;
 class MapViewModel;
+class ObjectInstanceID;
 
 /// Class responsible for rendering of entire map view
 /// uses rendering parameters provided by owner class
@@ -57,6 +58,9 @@ public:
 	explicit MapViewCache(const std::shared_ptr<MapViewModel> & model);
 	~MapViewCache();
 
+	void invalidate(const std::shared_ptr<IMapRendererContext> & context, const int3 & tile);
+	void invalidate(const std::shared_ptr<IMapRendererContext> & context, const ObjectInstanceID & object);
+
 	/// updates internal terrain cache according to provided time delta
 	void update(const std::shared_ptr<IMapRendererContext> & context);
 

+ 280 - 93
client/mapRenderer/MapViewController.cpp

@@ -12,14 +12,18 @@
 #include "MapViewController.h"
 
 #include "MapRendererContext.h"
+#include "MapRendererContextState.h"
 #include "MapViewModel.h"
+#include "MapViewCache.h"
 
 #include "../adventureMap/CAdvMapInt.h"
+#include "../CPlayerInterface.h"
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
 #include "../../lib/spells/ViewSpellInt.h"
+#include "../../lib/CPathfinder.h"
 
 void MapViewController::setViewCenter(const int3 & position)
 {
@@ -32,7 +36,7 @@ void MapViewController::setViewCenter(const Point & position, int level)
 	Point upperLimit = Point(context->getMapSize()) * model->getSingleTileSize() + model->getSingleTileSize();
 	Point lowerLimit = Point(0,0);
 
-	if (context->worldViewModeActive)
+	if (worldViewContext)
 	{
 		Point area = model->getPixelsVisibleDimensions();
 		Point mapCenter = upperLimit / 2;
@@ -74,10 +78,13 @@ void MapViewController::setTileSize(const Point & tileSize)
 	setViewCenter(model->getMapViewCenter(), model->getLevel());
 }
 
-MapViewController::MapViewController(std::shared_ptr<MapViewModel> model)
-	: context(new MapRendererContext())
+MapViewController::MapViewController(std::shared_ptr<MapViewModel> model, std::shared_ptr<MapViewCache> view)
+	: state(new MapRendererContextState())
 	, model(std::move(model))
+	, view(view)
 {
+	adventureContext = std::make_shared<MapRendererAdventureContext>(*state);
+	context = adventureContext;
 }
 
 std::shared_ptr<IMapRendererContext> MapViewController::getContext() const
@@ -85,14 +92,6 @@ std::shared_ptr<IMapRendererContext> MapViewController::getContext() const
 	return context;
 }
 
-void MapViewController::moveFocusToSelection()
-{
-	const auto * army = adventureInt->curArmy();
-
-	if (army)
-		setViewCenter(army->getSightCenter());
-}
-
 void MapViewController::update(uint32_t timeDelta)
 {
 	// confirmed to match H3 for
@@ -103,13 +102,13 @@ void MapViewController::update(uint32_t timeDelta)
 	// - teleporting ( 250 ms)
 	static const double fadeOutDuration = 500;
 	static const double fadeInDuration = 500;
-	static const double heroTeleportDuration = 250;
+	//static const double heroTeleportDuration = 250;
 
 	//FIXME: remove code duplication?
 
-	if(context->movementAnimation)
+	if(movementContext)
 	{
-		const auto * object = context->getObject(context->movementAnimation->target);
+		const auto * object = context->getObject(movementContext->target);
 		const auto * hero = dynamic_cast<const CGHeroInstance*>(object);
 		const auto * boat = dynamic_cast<const CGBoat*>(object);
 
@@ -118,172 +117,360 @@ void MapViewController::update(uint32_t timeDelta)
 		if (!hero)
 			hero = boat->hero;
 
-		// TODO: enemyMoveTime
-		double heroMoveTime = settings["adventure"]["heroMoveTime"].Float();
+		double heroMoveTime =
+			LOCPLINT->makingTurn ?
+			settings["adventure"]["heroMoveTime"].Float():
+			settings["adventure"]["enemyMoveTime"].Float();
 
-		context->movementAnimation->progress += timeDelta / heroMoveTime;
+		movementContext->progress += timeDelta / heroMoveTime;
 
-		Point positionFrom = Point(hero->convertToVisitablePos(context->movementAnimation->tileFrom)) * model->getSingleTileSize() + model->getSingleTileSize() / 2;
-		Point positionDest = Point(hero->convertToVisitablePos(context->movementAnimation->tileDest)) * model->getSingleTileSize() + model->getSingleTileSize() / 2;
+		Point positionFrom = Point(hero->convertToVisitablePos(movementContext->tileFrom)) * model->getSingleTileSize() + model->getSingleTileSize() / 2;
+		Point positionDest = Point(hero->convertToVisitablePos(movementContext->tileDest)) * model->getSingleTileSize() + model->getSingleTileSize() / 2;
 
-		Point positionCurr = vstd::lerp(positionFrom, positionDest, context->movementAnimation->progress);
+		Point positionCurr = vstd::lerp(positionFrom, positionDest, movementContext->progress);
 
-		if(context->movementAnimation->progress >= 1.0)
+		if(movementContext->progress >= 1.0)
 		{
 			setViewCenter(hero->getSightCenter());
 
-			context->removeObject(context->getObject(context->movementAnimation->target));
-			context->addObject(context->getObject(context->movementAnimation->target));
-			context->movementAnimation.reset();
+			removeObject(context->getObject(movementContext->target));
+			addObject(context->getObject(movementContext->target));
+
+			activateAdventureContext(movementContext->animationTime);
 		}
 		else
 		{
-			setViewCenter(positionCurr, context->movementAnimation->tileDest.z);
+			setViewCenter(positionCurr, movementContext->tileDest.z);
 		}
 	}
 
-	if(context->teleportAnimation)
+	//if(teleportContext)
+	//{
+	//	teleportContext->progress += timeDelta / heroTeleportDuration;
+	//	moveFocusToSelection();
+	//	if(teleportContext->progress >= 1.0)
+	//		teleportContext.reset();
+	//}
+
+	if(fadingOutContext)
 	{
-		context->teleportAnimation->progress += timeDelta / heroTeleportDuration;
-		moveFocusToSelection();
-		if(context->teleportAnimation->progress >= 1.0)
-			context->teleportAnimation.reset();
+		fadingOutContext->progress -= timeDelta / fadeOutDuration;
+
+		if(fadingOutContext->progress <= 0.0)
+		{
+			removeObject(context->getObject(fadingOutContext->target));
+
+			activateAdventureContext(fadingOutContext->animationTime);
+		}
 	}
 
-	if(context->fadeOutAnimation)
+	if(fadingInContext)
 	{
-		context->fadeOutAnimation->progress += timeDelta / fadeOutDuration;
-		moveFocusToSelection();
-		if(context->fadeOutAnimation->progress >= 1.0)
+		fadingInContext->progress += timeDelta / fadeInDuration;
+
+		if(fadingInContext->progress >= 1.0)
 		{
-			context->removeObject(context->getObject(context->fadeOutAnimation->target));
-			context->fadeOutAnimation.reset();
+			activateAdventureContext(fadingInContext->animationTime);
 		}
 	}
 
-	if(context->fadeInAnimation)
+	if (adventureContext)
 	{
-		context->fadeInAnimation->progress += timeDelta / fadeInDuration;
-		moveFocusToSelection();
-		if(context->fadeInAnimation->progress >= 1.0)
-			context->fadeInAnimation.reset();
+		adventureContext->animationTime += timeDelta;
+		adventureContext->settingsSessionSpectate = settings["session"]["spectate"].Bool();
+		adventureContext->settingsAdventureObjectAnimation = settings["adventure"]["objectAnimation"].Bool();
+		adventureContext->settingsAdventureTerrainAnimation = settings["adventure"]["terrainAnimation"].Bool();
+		adventureContext->settingShowGrid = settings["gameTweaks"]["showGrid"].Bool();
+		adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool();
+		adventureContext->settingShowBlockable = settings["session"]["showBlockable"].Bool();
 	}
+}
+
+bool MapViewController::isEventVisible(const CGObjectInstance * obj)
+{
+	if (adventureContext == nullptr)
+		return false;
+
+	if (!LOCPLINT->makingTurn && settings["adventure"]["enemyMoveTime"].Float() < 0)
+		return false; // enemy move speed set to "hidden/none"
+
+	if (obj->isVisitable())
+		return context->isVisible(obj->visitablePos());
+	else
+		return context->isVisible(obj->pos);
+}
+
+bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if (adventureContext == nullptr)
+		return false;
 
-	context->animationTime += timeDelta;
-	context->worldViewModeActive = model->getSingleTileSize() != Point(32,32);
+	if (!LOCPLINT->makingTurn && settings["adventure"]["enemyMoveTime"].Float() < 0)
+		return false; // enemy move speed set to "hidden/none"
 
-	context->settingsSessionSpectate = settings["session"]["spectate"].Bool();
-	context->settingsAdventureObjectAnimation = settings["adventure"]["objectAnimation"].Bool();
-	context->settingsAdventureTerrainAnimation = settings["adventure"]["terrainAnimation"].Bool();
-	context->settingsSessionShowGrid = settings["gameTweaks"]["showGrid"].Bool();
-	context->settingsSessionShowVisitable = settings["session"]["showVisitable"].Bool();
-	context->settingsSessionShowBlockable = settings["session"]["showBlockable"].Bool();
+	if (context->isVisible(obj->convertToVisitablePos(from)))
+		return true;
+
+	if (context->isVisible(obj->convertToVisitablePos(dest)))
+		return true;
+
+	return false;
+}
+
+void MapViewController::fadeOutObject(const CGObjectInstance * obj)
+{
+	fadingOutContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
+	fadingOutContext->animationTime = adventureContext->animationTime;
+	adventureContext = fadingOutContext;
+	context = fadingOutContext;
+
+	fadingOutContext->target = obj->id;
+	fadingOutContext->progress = 1.0;
+}
+
+void MapViewController::fadeInObject(const CGObjectInstance * obj)
+{
+	fadingInContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
+	fadingInContext->animationTime = adventureContext->animationTime;
+	adventureContext = fadingInContext;
+	context = fadingInContext;
+
+	fadingInContext->target = obj->id;
+	fadingInContext->progress = 0.0;
+}
+
+void MapViewController::removeObject(const CGObjectInstance * obj)
+{
+	view->invalidate(context, obj->id);
+	state->removeObject(obj);
+}
+
+void MapViewController::addObject(const CGObjectInstance * obj)
+{
+	state->addObject(obj);
+	view->invalidate(context, obj->id);
+}
+
+void MapViewController::onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if (isEventVisible(obj, from, dest))
+	{
+		onObjectFadeOut(obj);
+		setViewCenter(obj->getSightCenter());
+	}
+	else
+		removeObject(obj);
+}
+
+void MapViewController::onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if (isEventVisible(obj, from, dest))
+		setViewCenter(obj->getSightCenter());
+}
+
+void MapViewController::onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if (isEventVisible(obj, from, dest))
+		setViewCenter(obj->getSightCenter());
+}
+
+void MapViewController::onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if (isEventVisible(obj, from, dest))
+	{
+		onObjectFadeIn(obj);
+		setViewCenter(obj->getSightCenter());
+	}
+	addObject(obj);
 }
 
 void MapViewController::onObjectFadeIn(const CGObjectInstance * obj)
 {
-	bool actionVisible = context->isVisible(obj->pos);
+	assert(!hasOngoingAnimations());
 
-	assert(!context->fadeInAnimation);
+	if (isEventVisible(obj))
+		fadeInObject(obj);
 
-	if (actionVisible)
-		context->fadeInAnimation = FadingAnimationState{obj->id, 0.0};
-	context->addObject(obj);
+	addObject(obj);
 }
 
 void MapViewController::onObjectFadeOut(const CGObjectInstance * obj)
 {
-	bool actionVisible = context->isVisible(obj->pos);
-
-	assert(!context->fadeOutAnimation);
+	assert(!hasOngoingAnimations());
 
-	if (actionVisible)
-		context->fadeOutAnimation = FadingAnimationState{obj->id, 0.0};
+	if (isEventVisible(obj))
+		fadeOutObject(obj);
 	else
-		context->removeObject(obj);
+		removeObject(obj);
 }
 
 void MapViewController::onObjectInstantAdd(const CGObjectInstance * obj)
 {
-	context->addObject(obj);
+	addObject(obj);
 };
 
 void MapViewController::onObjectInstantRemove(const CGObjectInstance * obj)
 {
-	context->removeObject(obj);
+	removeObject(obj);
 };
 
-void MapViewController::onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+void MapViewController::onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
 {
-	assert(!context->teleportAnimation);
-	bool actionVisible = context->isVisible(from) || context->isVisible(dest);
+	assert(!hasOngoingAnimations());
 
-	if (actionVisible)
+	if (isEventVisible(obj, from, dest))
 	{
-		context->teleportAnimation = HeroAnimationState{obj->id, from, dest, 0.0};
+		// TODO: generate view with old state
+		setViewCenter(obj->getSightCenter());
+	}
+}
+
+void MapViewController::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!hasOngoingAnimations());
+
+	if (isEventVisible(obj, from, dest))
+	{
+		// TODO: animation
+		setViewCenter(obj->getSightCenter());
 	}
 	else
 	{
-		context->removeObject(obj);
-		context->addObject(obj);
+		removeObject(obj);
+		addObject(obj);
 	}
 }
 
 void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
 {
-	assert(!context->movementAnimation);
-	bool actionVisible = context->isVisible(from) || context->isVisible(dest);
+	assert(!hasOngoingAnimations());
 
 	const CGObjectInstance * movingObject = obj;
 	if(obj->boat)
 		movingObject = obj->boat;
 
-	context->removeObject(movingObject);
+	removeObject(movingObject);
 
-	if(settings["adventure"]["heroMoveTime"].Float() > 1 && actionVisible)
+	if (!isEventVisible(obj, from, dest))
 	{
-		context->addMovingObject(movingObject, from, dest);
-		context->movementAnimation = HeroAnimationState{movingObject->id, from, dest, 0.0};
+		addObject(movingObject);
+		return;
 	}
-	else
+
+	double movementTime =
+		LOCPLINT->playerID == obj->tempOwner ?
+		settings["adventure"]["heroMoveTime"].Float():
+		settings["adventure"]["enemyMoveTime"].Float();
+
+	if(movementTime > 1)
 	{
-		// instant movement
-		context->addObject(movingObject);
+		movementContext = std::make_shared<MapRendererAdventureMovingContext>(*state);
+		movementContext->animationTime = adventureContext->animationTime;
+		adventureContext = movementContext;
+		context = movementContext;
 
-		if (actionVisible)
-			setViewCenter(movingObject->visitablePos());
-	}
-}
+		state->addMovingObject(movingObject, from, dest);
 
-void MapViewController::onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
-{
-	//TODO. Or no-op?
+		movementContext->target = movingObject->id;
+		movementContext->tileFrom = from;
+		movementContext->tileDest = dest;
+		movementContext->progress = 0.0;
+	}
+	else // instant movement
+	{
+		addObject(movingObject);
+		setViewCenter(movingObject->visitablePos());
+	}
 }
 
 bool MapViewController::hasOngoingAnimations()
 {
-	if(context->movementAnimation)
-		return true;
-
-	if(context->teleportAnimation)
+	if(movementContext)
 		return true;
 
-	if(context->fadeOutAnimation)
+	if(fadingOutContext)
 		return true;
 
-	if(context->fadeInAnimation)
+	if(fadingInContext)
 		return true;
 
 	return false;
 }
 
+void MapViewController::activateAdventureContext(uint32_t animationTime)
+{
+	resetContext();
+
+	adventureContext = std::make_shared<MapRendererAdventureContext>(*state);
+	adventureContext->animationTime = animationTime;
+	context = adventureContext;
+}
+
+void MapViewController::activateAdventureContext()
+{
+	activateAdventureContext(0);
+}
+
+void MapViewController::activateWorldViewContext()
+{
+	if (worldViewContext)
+		return;
+
+	resetContext();
+
+	worldViewContext = std::make_shared<MapRendererWorldViewContext>(*state);
+	context = worldViewContext;
+}
+
+void MapViewController::activateSpellViewContext()
+{
+	if (spellViewContext)
+		return;
+
+	resetContext();
+
+	spellViewContext = std::make_shared<MapRendererSpellViewContext>(*state);
+	worldViewContext = spellViewContext;
+	context = spellViewContext;
+}
+
+void MapViewController::activatePuzzleMapContext(const int3 & grailPosition)
+{
+	resetContext();
+
+	puzzleMapContext = std::make_shared<MapRendererPuzzleMapContext>(*state);
+	context = puzzleMapContext;
+
+	CGPathNode fakeNode;
+	fakeNode.coord = grailPosition;
+
+	puzzleMapContext->grailPos = std::make_unique<CGPath>();
+
+	// create two nodes since 1st one is normally not visible
+	puzzleMapContext->grailPos->nodes.push_back(fakeNode);
+	puzzleMapContext->grailPos->nodes.push_back(fakeNode);
+}
+
+void MapViewController::resetContext()
+{
+	adventureContext.reset();
+	movementContext.reset();
+	fadingOutContext.reset();
+	fadingInContext.reset();
+	worldViewContext.reset();
+	spellViewContext.reset();
+	puzzleMapContext.reset();
+}
+
 void MapViewController::setTerrainVisibility(bool showAllTerrain)
 {
-	context->showAllTerrain = showAllTerrain;
+	assert(spellViewContext);
+	spellViewContext->showAllTerrain = showAllTerrain;
 }
 
 void MapViewController::setOverlayVisibility(const std::vector<ObjectPosInfo> & objectPositions)
 {
-	context->additionalOverlayIcons = objectPositions;
+	assert(spellViewContext);
+	spellViewContext->additionalOverlayIcons = objectPositions;
 }
 

+ 47 - 6
client/mapRenderer/MapViewController.h

@@ -16,31 +16,66 @@ class Point;
 struct ObjectPosInfo;
 VCMI_LIB_NAMESPACE_END
 
+struct MapRendererContextState;
+
+class MapViewCache;
 class MapViewModel;
 class IMapRendererContext;
-class MapRendererContext;
+class MapRendererAdventureContext;
+class MapRendererAdventureFadingContext;
+class MapRendererAdventureMovingContext;
+class MapRendererWorldViewContext;
+class MapRendererSpellViewContext;
+class MapRendererPuzzleMapContext;
 
 /// Class responsible for updating view state,
 /// such as its position and any animations
 class MapViewController : public IMapObjectObserver
 {
-	std::shared_ptr<MapRendererContext> context;
+	std::shared_ptr<IMapRendererContext> context;
+	std::shared_ptr<MapRendererContextState> state;
 	std::shared_ptr<MapViewModel> model;
+	std::shared_ptr<MapViewCache> view;
+
+	// all potential contexts for view
+	// only some are present at any given moment, rest are nullptr's
+	std::shared_ptr<MapRendererAdventureContext> adventureContext;
+	std::shared_ptr<MapRendererAdventureMovingContext> movementContext;
+	//std::shared_ptr<IMapRendererContext> teleportContext;
+	std::shared_ptr<MapRendererAdventureFadingContext> fadingOutContext;
+	std::shared_ptr<MapRendererAdventureFadingContext> fadingInContext;
+	std::shared_ptr<MapRendererWorldViewContext> worldViewContext;
+	std::shared_ptr<MapRendererSpellViewContext> spellViewContext;
+	std::shared_ptr<MapRendererPuzzleMapContext> puzzleMapContext;
 
 private:
+	bool isEventVisible(const CGObjectInstance * obj);
+	bool isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+
+	void fadeOutObject(const CGObjectInstance * obj);
+	void fadeInObject(const CGObjectInstance * obj);
+
+	void removeObject(const CGObjectInstance * obj);
+	void addObject(const CGObjectInstance * obj);
+
 	// IMapObjectObserver impl
 	bool hasOngoingAnimations() override;
 	void onObjectFadeIn(const CGObjectInstance * obj) override;
 	void onObjectFadeOut(const CGObjectInstance * obj) override;
 	void onObjectInstantAdd(const CGObjectInstance * obj) override;
 	void onObjectInstantRemove(const CGObjectInstance * obj) override;
-	void onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
 	void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
-	void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+
+	void resetContext();
 
-	void moveFocusToSelection();
 public:
-	explicit MapViewController(std::shared_ptr<MapViewModel> model);
+	MapViewController(std::shared_ptr<MapViewModel> model, std::shared_ptr<MapViewCache> view);
 
 	std::shared_ptr<IMapRendererContext> getContext() const;
 
@@ -49,6 +84,12 @@ public:
 	void setTileSize(const Point & tileSize);
 	void update(uint32_t timeDelta);
 
+	void activateAdventureContext(uint32_t animationTime);
+	void activateAdventureContext();
+	void activateWorldViewContext();
+	void activateSpellViewContext();
+	void activatePuzzleMapContext(const int3 & grailPosition);
+
 	void setTerrainVisibility(bool showAllTerrain);
 	void setOverlayVisibility(const std::vector<ObjectPosInfo> & objectPositions);
 

+ 32 - 51
client/mapRenderer/mapHandler.cpp

@@ -22,49 +22,6 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/TerrainHandler.h"
 
-/*
-void CMapPuzzleViewBlitter::drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const
-{
-	CMapBlitter::drawObjects(targetSurf, tile);
-
-	// grail X mark
-	if(pos.x == info->grailPos.x && pos.y == info->grailPos.y)
-	{
-		const auto mark = graphics->heroMoveArrows->getImage(0);
-		mark->draw(targetSurf,realTileRect.x,realTileRect.y);
-	}
-}
-*/
-/*
-void CMapPuzzleViewBlitter::postProcessing(SDL_Surface * targetSurf) const
-{
-	CSDL_Ext::applyEffect(targetSurf, info->drawBounds, static_cast<int>(!ADVOPT.puzzleSepia));
-}
-*/
-/*
-bool CMapPuzzleViewBlitter::canDrawObject(const CGObjectInstance * obj) const
-{
-	if (!CMapBlitter::canDrawObject(obj))
-		return false;
-
-	//don't print flaggable objects in puzzle mode
-	if (obj->isVisitable())
-		return false;
-
-	if(std::find(unblittableObjects.begin(), unblittableObjects.end(), obj->ID) != unblittableObjects.end())
-		return false;
-
-	return true;
-}
-*/
-/*
-CMapPuzzleViewBlitter::CMapPuzzleViewBlitter(CMapHandler * parent)
-	: CMapNormalBlitter(parent)
-{
-	unblittableObjects.push_back(Obj::HOLE);
-}
-*/
-
 bool CMapHandler::hasOngoingAnimations()
 {
 	for (auto * observer : observers)
@@ -196,6 +153,30 @@ void CMapHandler::onObjectFadeOut(const CGObjectInstance * obj)
 		observer->onObjectFadeOut(obj);
 }
 
+void CMapHandler::onBeforeHeroEmbark(const CGHeroInstance *obj, const int3 &from, const int3 &dest)
+{
+	for (auto * observer : observers)
+		observer->onBeforeHeroEmbark(obj, from, dest);
+}
+
+void CMapHandler::onAfterHeroEmbark(const CGHeroInstance *obj, const int3 &from, const int3 &dest)
+{
+	for (auto * observer : observers)
+		observer->onAfterHeroEmbark(obj, from, dest);
+}
+
+void CMapHandler::onBeforeHeroDisembark(const CGHeroInstance *obj, const int3 &from, const int3 &dest)
+{
+	for (auto * observer : observers)
+		observer->onBeforeHeroDisembark(obj, from, dest);
+}
+
+void CMapHandler::onAfterHeroDisembark(const CGHeroInstance *obj, const int3 &from, const int3 &dest)
+{
+	for (auto * observer : observers)
+		observer->onAfterHeroDisembark(obj, from, dest);
+}
+
 void CMapHandler::onObjectInstantAdd(const CGObjectInstance * obj)
 {
 	for (auto * observer : observers)
@@ -208,25 +189,25 @@ void CMapHandler::onObjectInstantRemove(const CGObjectInstance * obj)
 		observer->onObjectInstantRemove(obj);
 }
 
-void CMapHandler::onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+void CMapHandler::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
 {
 	assert(obj->pos == dest);
 	for (auto * observer : observers)
-		observer->onHeroTeleported(obj, from, dest);
+		observer->onAfterHeroTeleported(obj, from, dest);
 }
 
-void CMapHandler::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+void CMapHandler::onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
 {
-	assert(obj->pos == dest);
+	assert(obj->pos == from);
 	for (auto * observer : observers)
-		observer->onHeroMoved(obj, from, dest);
+		observer->onBeforeHeroTeleported(obj, from, dest);
 }
 
-void CMapHandler::onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+void CMapHandler::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
 {
-	assert(obj->pos == from);
+	assert(obj->pos == dest);
 	for (auto * observer : observers)
-		observer->onHeroRotated(obj, from, dest);
+		observer->onHeroMoved(obj, from, dest);
 }
 
 void CMapHandler::addMapObserver(IMapObjectObserver * object)

+ 6 - 2
client/mapRenderer/mapHandler.h

@@ -59,9 +59,13 @@ public:
 	void onObjectFadeOut(const CGObjectInstance * obj);
 	void onObjectInstantAdd(const CGObjectInstance * obj);
 	void onObjectInstantRemove(const CGObjectInstance * obj);
-	void onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
+	void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
 	void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
-	void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest);
 
 	/// Add object to receive notifications on any changes in visible map state
 	void addMapObserver(IMapObjectObserver * observer);

+ 3 - 10
client/windows/CPuzzleWindow.cpp

@@ -16,6 +16,7 @@
 #include "../adventureMap/CResDataBar.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/TextAlignment.h"
+#include "../mapRenderer/MapView.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
@@ -38,6 +39,8 @@ CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
 	quitb->assignedKeys.insert(SDLK_ESCAPE);
 	quitb->setBorderColor(Colors::METALLIC_GOLD);
 
+	mapView = std::make_shared<PuzzleMapView>(Point(8,9), Point(591, 544), grailPos);
+
 	logo = std::make_shared<CPicture>("PUZZLOGO", 607, 3);
 	title = std::make_shared<CLabel>(700, 95, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[463]);
 	resDataBar = std::make_shared<CResDataBar>("ARESBAR.bmp", 3, 575, 32, 2, 85, 85);
@@ -68,16 +71,6 @@ CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
 
 void CPuzzleWindow::showAll(SDL_Surface * to)
 {
-	assert(0);
-	//int3 moveInt = int3(8, 9, 0);
-	//Rect mapRect = Rect(Point(pos.x + 8, pos.y + 7), Point(544, 591));
-	//int3 topTile = grailPos - moveInt;
-
-	//MapDrawingInfo info(topTile, LOCPLINT->cb->getVisibilityMap(), mapRect);
-	//info.puzzleMode = true;
-	//info.grailPos = grailPos;
-	//CGI->mh->drawTerrainRectNew(to, &info);
-
 	CWindowObject::showAll(to);
 }
 

+ 2 - 0
client/windows/CPuzzleWindow.h

@@ -15,12 +15,14 @@
 class CLabel;
 class CButton;
 class CResDataBar;
+class PuzzleMapView;
 
 /// Puzzle screen which gets uncovered when you visit obilisks
 class CPuzzleWindow : public CWindowObject
 {
 private:
 	int3 grailPos;
+	std::shared_ptr<PuzzleMapView> mapView;
 	std::shared_ptr<CPicture> logo;
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CButton> quitb;

+ 1 - 1
config/terrains.json

@@ -138,7 +138,7 @@
 		"transitionRequired" : true,
 		"terrainViewPatterns" : "water",
 		"horseSound" : "horse08",
-		"horseSoundPenalty" : "horse28",
+		"horseSoundPenalty" : "horse08",
 		"sounds": {
 			"ambient": ["LOOPOCEA"]
 		}

+ 0 - 2
lib/NetPacks.h

@@ -593,8 +593,6 @@ struct DLL_LINKAGE TryMoveHero : public CPackForClient
 	std::unordered_set<int3, ShashInt3> fowRevealed; //revealed tiles
 	boost::optional<int3> attackedFrom; // Set when stepping into endangered tile.
 
-	bool humanKnows = false; //used locally during applying to client
-
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
 	bool stopMovement() const

+ 0 - 1
mapeditor/maphandler.cpp

@@ -21,7 +21,6 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CModHandler.h"
-#include "../lib/mapping/CMap.h"
 #include "../lib/GameConstants.h"
 #include "../lib/JsonDetail.h"