瀏覽代碼

Code reorganization, no changes to functionality

Ivan Savenko 2 年之前
父節點
當前提交
ec6f19ea18

+ 12 - 0
client/CMakeLists.txt

@@ -48,7 +48,11 @@ set(client_SRCS
 	mainmenu/CreditsScreen.cpp
 
 	mapRenderer/MapRenderer.cpp
+	mapRenderer/MapRendererContext.cpp
 	mapRenderer/MapView.cpp
+	mapRenderer/MapViewCache.cpp
+	mapRenderer/MapViewController.cpp
+	mapRenderer/MapViewModel.cpp
 	mapRenderer/mapHandler.cpp
 
 	render/CAnimation.cpp
@@ -85,6 +89,7 @@ set(client_SRCS
 	windows/CHeroWindow.cpp
 	windows/CKingdomInterface.cpp
 	windows/CMessage.cpp
+	windows/CPuzzleWindow.cpp
 	windows/CQuestLog.cpp
 	windows/CSpellWindow.cpp
 	windows/CTradeWindow.cpp
@@ -162,9 +167,15 @@ set(client_HEADERS
 	mainmenu/CPrologEpilogVideo.h
 	mainmenu/CreditsScreen.h
 
+	mapRenderer/IMapRendererContext.h
+	mapRenderer/IMapRendererObserver.h
 	mapRenderer/MapRenderer.h
 	mapRenderer/MapRendererContext.h
 	mapRenderer/MapView.h
+	mapRenderer/MapViewCache.cpp
+	mapRenderer/MapViewCache.h
+	mapRenderer/MapViewController.h
+	mapRenderer/MapViewModel.h
 	mapRenderer/mapHandler.h
 
 	render/CAnimation.h
@@ -205,6 +216,7 @@ set(client_HEADERS
 	windows/CHeroWindow.h
 	windows/CKingdomInterface.h
 	windows/CMessage.h
+	windows/CPuzzleWindow.h
 	windows/CQuestLog.h
 	windows/CSpellWindow.h
 	windows/CTradeWindow.h

+ 2 - 1
client/CPlayerInterface.cpp

@@ -12,7 +12,7 @@
 #include <vcmi/Artifact.h>
 
 #include "adventureMap/CAdvMapInt.h"
-#include "adventureMap/mapHandler.h"
+#include "mapRenderer/mapHandler.h"
 #include "adventureMap/CList.h"
 #include "adventureMap/CTerrainRect.h"
 #include "adventureMap/CInfoBar.h"
@@ -31,6 +31,7 @@
 #include "windows/CHeroWindow.h"
 #include "windows/CCreatureWindow.h"
 #include "windows/CQuestLog.h"
+#include "windows/CPuzzleWindow.h"
 #include "CPlayerInterface.h"
 #include "widgets/CComponent.h"
 #include "widgets/Buttons.h"

+ 1 - 1
client/Client.cpp

@@ -14,7 +14,7 @@
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../CCallback.h"
 #include "adventureMap/CAdvMapInt.h"
-#include "adventureMap/mapHandler.h"
+#include "mapRenderer/mapHandler.h"
 #include "../lib/CConsoleHandler.h"
 #include "CGameInfo.h"
 #include "../lib/CGameState.h"

+ 1 - 1
client/NetPacksClient.cpp

@@ -14,7 +14,7 @@
 #include "CPlayerInterface.h"
 #include "CGameInfo.h"
 #include "windows/GUIClasses.h"
-#include "adventureMap/mapHandler.h"
+#include "mapRenderer/mapHandler.h"
 #include "adventureMap/CInGameConsole.h"
 #include "battle/BattleInterface.h"
 #include "gui/CGuiHandler.h"

+ 1 - 1
client/adventureMap/CAdvMapInt.cpp

@@ -18,8 +18,8 @@
 #include "CTerrainRect.h"
 #include "CList.h"
 #include "CInfoBar.h"
-#include "mapHandler.h"
 
+#include "../mapRenderer/mapHandler.h"
 #include "../windows/CKingdomInterface.h"
 #include "../windows/CSpellWindow.h"
 #include "../windows/CTradeWindow.h"

+ 6 - 4
client/adventureMap/CTerrainRect.cpp

@@ -10,19 +10,21 @@
 #include "StdInc.h"
 #include "CTerrainRect.h"
 
-#include "mapHandler.h"
-#include "MapView.h"
 #include "CAdvMapInt.h"
 
 #include "../CGameInfo.h"
+#include "../CMT.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+#include "../mapRenderer/mapHandler.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../widgets/TextControls.h"
-#include "../CMT.h"
+#include "../mapRenderer/MapView.h"
+#include "../mapRenderer/MapViewController.h"
+#include "../mapRenderer/MapViewModel.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"

+ 74 - 0
client/mapRenderer/IMapRendererContext.h

@@ -0,0 +1,74 @@
+/*
+ * IMapRendererContext.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class int3;
+class Point;
+class CGObjectInstance;
+class ObjectInstanceID;
+struct TerrainTile;
+struct CGPath;
+
+VCMI_LIB_NAMESPACE_END
+
+class IMapRendererContext
+{
+public:
+	using MapObject = ObjectInstanceID;
+	using MapObjectsList = std::vector<MapObject>;
+
+	virtual ~IMapRendererContext() = default;
+
+	/// returns dimensions of current map
+	virtual int3 getMapSize() const = 0;
+
+	/// returns true if chosen coordinates exist on map
+	virtual bool isInMap(const int3 & coordinates) const = 0;
+
+	/// returns tile by selected coordinates. Coordinates MUST be valid
+	virtual const TerrainTile & getMapTile(const int3 & coordinates) const = 0;
+
+	/// returns all objects visible on specified tile
+	virtual const MapObjectsList & getObjects(const int3 & coordinates) const = 0;
+
+	/// returns specific object by ID, or nullptr if not found
+	virtual const CGObjectInstance * getObject(ObjectInstanceID objectID) const = 0;
+
+	/// returns path of currently active hero, or nullptr if none
+	virtual const CGPath * currentPath() const = 0;
+
+	/// returns true if specified tile is visible in current context
+	virtual bool isVisible(const int3 & coordinates) const = 0;
+
+	virtual size_t objectGroupIndex(ObjectInstanceID objectID) const = 0;
+	virtual Point objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const = 0;
+
+	/// returns object animation transparency. IF set to 0, object will not be visible
+	virtual double objectTransparency(ObjectInstanceID objectID) const = 0;
+
+	/// returns animation frame for selected object
+	virtual size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const = 0;
+
+	/// returns animation frame for terrain
+	virtual size_t terrainImageIndex(size_t groupSize) const = 0;
+
+//	/// returns size of ouput tile, in pixels. 32x32 for "standard" map, may be smaller for world view mode
+//	virtual Point getTileSize() const = 0;
+
+	/// if true, world view overlay will be shown
+	virtual bool showOverlay() const = 0;
+
+	/// if true, map grid should be visible on map
+	virtual bool showGrid() const = 0;
+	virtual bool showVisitable() const = 0;
+	virtual bool showBlockable() const = 0;
+};

+ 48 - 0
client/mapRenderer/IMapRendererObserver.h

@@ -0,0 +1,48 @@
+/*
+ * IMapRendererObserver.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class int3;
+class CGObjectInstance;
+class CGHeroInstance;
+
+VCMI_LIB_NAMESPACE_END
+
+class IMapObjectObserver
+{
+public:
+	IMapObjectObserver();
+	virtual ~IMapObjectObserver();
+
+	virtual bool hasOngoingAnimations() = 0;
+
+	/// Plays fade-in animation and adds object to map
+	virtual void onObjectFadeIn(const CGObjectInstance * obj) {}
+
+	/// Plays fade-out animation and removed object from map
+	virtual void onObjectFadeOut(const CGObjectInstance * obj) {}
+
+	/// Adds object to map instantly, with no animation
+	virtual void onObjectInstantAdd(const CGObjectInstance * obj) {}
+
+	/// 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) {}
+};

+ 3 - 19
client/mapRenderer/MapRenderer.cpp

@@ -21,12 +21,12 @@
 
 #include "../../CCallback.h"
 
+#include "../../lib/CPathfinder.h"
 #include "../../lib/RiverHandler.h"
 #include "../../lib/RoadHandler.h"
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"
-#include "../../lib/CPathfinder.h"
 
 struct NeighborTilesInfo
 {
@@ -87,22 +87,6 @@ struct NeighborTilesInfo
 	}
 };
 
-MapObjectsSorter::MapObjectsSorter(const IMapRendererContext & context)
-	: context(context)
-{
-}
-
-bool MapObjectsSorter::operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const
-{
-	return (*this)(context.getObject(left), context.getObject(right));
-}
-
-bool MapObjectsSorter::operator()(const CGObjectInstance * left, const CGObjectInstance * right) const
-{
-	//FIXME: remove mh access
-	return CGI->mh->compareObjectBlitOrder(left, right);
-}
-
 MapTileStorage::MapTileStorage(size_t capacity)
 	: animations(capacity)
 {
@@ -519,9 +503,9 @@ void MapRendererDebug::renderTile(const IMapRendererContext & context, Canvas &
 		bool blockable = false;
 		bool visitable = false;
 
-		for (auto const & objectID: context.getObjects(coordinates))
+		for(const auto & objectID : context.getObjects(coordinates))
 		{
-			auto const * object = context.getObject(objectID);
+			const auto * object = context.getObject(objectID);
 
 			if (context.objectTransparency(objectID) > 0)
 			{

+ 0 - 11
client/mapRenderer/MapRenderer.h

@@ -23,17 +23,6 @@ class CAnimation;
 class IImage;
 class Canvas;
 
-class MapObjectsSorter
-{
-	const IMapRendererContext & context;
-
-public:
-	explicit MapObjectsSorter(const IMapRendererContext & context);
-
-	bool operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const;
-	bool operator()(const CGObjectInstance * left, const CGObjectInstance * right) const;
-};
-
 class MapTileStorage
 {
 	using TerrainAnimation = std::array<std::unique_ptr<CAnimation>, 4>;

+ 278 - 0
client/mapRenderer/MapRendererContext.cpp

@@ -0,0 +1,278 @@
+/*
+ * 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 "MapRendererContext.h"
+
+#include "mapHandler.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CAdvMapInt.h"
+//#include "../gui/CGuiHandler.h"
+//#include "../render/CAnimation.h"
+//#include "../render/Canvas.h"
+//#include "../render/IImage.h"
+//#include "../renderSDL/SDL_Extensions.h"
+//
+#include "../../CCallback.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/CMap.h"
+
+MapObjectsSorter::MapObjectsSorter(const IMapRendererContext & context)
+	: context(context)
+{
+}
+
+bool MapObjectsSorter::operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const
+{
+	return (*this)(context.getObject(left), context.getObject(right));
+}
+
+bool MapObjectsSorter::operator()(const CGObjectInstance * left, const CGObjectInstance * right) const
+{
+	//FIXME: remove mh access
+	return CGI->mh->compareObjectBlitOrder(left, right);
+}
+
+int3 MapRendererContext::getMapSize() const
+{
+	return LOCPLINT->cb->getMapSize();
+}
+
+bool MapRendererContext::isInMap(const int3 & coordinates) const
+{
+	return LOCPLINT->cb->isInTheMap(coordinates);
+}
+
+const TerrainTile & MapRendererContext::getMapTile(const int3 & coordinates) const
+{
+	return CGI->mh->getMap()->getTile(coordinates);
+}
+
+const CGObjectInstance * MapRendererContext::getObject(ObjectInstanceID objectID) const
+{
+	return CGI->mh->getMap()->objects.at(objectID.getNum());
+}
+
+bool MapRendererContext::isVisible(const int3 & coordinates) const
+{
+	return LOCPLINT->cb->isVisible(coordinates) || settings["session"]["spectate"].Bool();
+}
+
+const CGPath * MapRendererContext::currentPath() const
+{
+	const auto * hero = adventureInt->curHero();
+
+	if(!hero)
+		return nullptr;
+
+	if(!LOCPLINT->paths.hasPath(hero))
+		return nullptr;
+
+	return &LOCPLINT->paths.getPath(hero);
+}
+
+size_t MapRendererContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	assert(groupSize > 0);
+	if(groupSize == 0)
+		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;
+}
+
+size_t MapRendererContext::terrainImageIndex(size_t groupSize) const
+{
+	size_t baseFrameTime = 180;
+	size_t frameCounter = animationTime / baseFrameTime;
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
+}
+
+//Point MapRendererContext::getTileSize() const
+//{
+//	return Point(32, 32);
+//}
+
+bool MapRendererContext::showOverlay() const
+{
+	return worldViewModeActive;
+}
+
+bool MapRendererContext::showGrid() const
+{
+	return settings["gameTweaks"]["showGrid"].Bool();
+}
+
+bool MapRendererContext::showVisitable() const
+{
+	return settings["session"]["showVisitable"].Bool();
+}
+
+bool MapRendererContext::showBlockable() const
+{
+	return settings["session"]["showBlockable"].Bool();
+}
+
+MapRendererContext::MapRendererContext()
+{
+	auto mapSize = getMapSize();
+
+	objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
+
+	for(const auto & obj : CGI->mh->getMap()->objects)
+		addObject(obj);
+}
+
+void MapRendererContext::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(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));
+			}
+		}
+	}
+}
+
+void MapRendererContext::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(isInMap(currTile))
+			{
+				auto & container = objects[currTile.z][currTile.x][currTile.y];
+
+				container.push_back(object->id);
+				boost::range::sort(container, MapObjectsSorter(*this));
+			}
+		}
+	}
+}
+
+void MapRendererContext::removeObject(const CGObjectInstance * object)
+{
+	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);
+}
+
+const MapRendererContext::MapObjectsList & MapRendererContext::getObjects(const int3 & coordinates) const
+{
+	assert(isInMap(coordinates));
+	return objects[coordinates.z][coordinates.x][coordinates.y];
+}
+
+size_t MapRendererContext::objectGroupIndex(ObjectInstanceID objectID) 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};
+
+	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];
+	}
+
+	if(obj->ID == Obj::BOAT)
+	{
+		const auto * boat = dynamic_cast<const CGBoat *>(obj);
+
+		uint8_t direction = boat->hero ? boat->hero->moveDir : boat->direction;
+
+		if(movementAnimation && movementAnimation->target == objectID)
+			return moveGroups[direction];
+		return idleGroups[direction];
+	}
+	return 0;
+}
+
+Point MapRendererContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	if(movementAnimation && movementAnimation->target == objectID)
+	{
+		int3 offsetTilesFrom = movementAnimation->tileFrom - coordinates;
+		int3 offsetTilesDest = movementAnimation->tileDest - coordinates;
+
+		Point offsetPixelsFrom = Point(offsetTilesFrom) * Point(32, 32);
+		Point offsetPixelsDest = Point(offsetTilesDest) * Point(32, 32);
+
+		Point result = vstd::lerp(offsetPixelsFrom, offsetPixelsDest, movementAnimation->progress);
+
+		return result;
+	}
+
+	const CGObjectInstance * object = getObject(objectID);
+	int3 offsetTiles(object->getPosition() - coordinates);
+	return Point(offsetTiles) * Point(32, 32);
+}
+
+double MapRendererContext::objectTransparency(ObjectInstanceID objectID) const
+{
+	const CGObjectInstance * object = getObject(objectID);
+
+	if(object && object->ID == Obj::HERO)
+	{
+		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
+
+		if(hero->inTownGarrison)
+			return 0;
+
+		if(hero->boat)
+			return 0;
+	}
+
+	if(fadeOutAnimation && objectID == fadeOutAnimation->target)
+		return 1.0 - fadeOutAnimation->progress;
+
+	if(fadeInAnimation && objectID == fadeInAnimation->target)
+		return fadeInAnimation->progress;
+
+	return 1.0;
+}

+ 59 - 80
client/mapRenderer/MapRendererContext.h

@@ -1,5 +1,5 @@
 /*
- * MapRenderer.h, part of VCMI engine
+ * MapRendererContext.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,99 +9,78 @@
  */
 #pragma once
 
-#include "../../lib/ConstTransitivePtr.h"
+#include "IMapRendererContext.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+#include "../lib/int3.h"
+#include "../lib/GameConstants.h"
 
-class int3;
-class Point;
-class CGObjectInstance;
-class CGHeroInstance;
-class ObjectInstanceID;
-struct TerrainTile;
-struct CGPath;
-
-VCMI_LIB_NAMESPACE_END
-
-class IMapRendererContext
+class MapObjectsSorter
 {
-public:
-	using MapObject = ObjectInstanceID;
-	using MapObjectsList = std::vector<MapObject>;
-
-	virtual ~IMapRendererContext() = default;
-
-	/// returns dimensions of current map
-	virtual int3 getMapSize() const = 0;
-
-	/// returns true if chosen coordinates exist on map
-	virtual bool isInMap(const int3 & coordinates) const = 0;
-
-	/// returns tile by selected coordinates. Coordinates MUST be valid
-	virtual const TerrainTile & getMapTile(const int3 & coordinates) const = 0;
-
-	/// returns all objects visible on specified tile
-	virtual const MapObjectsList & getObjects(const int3 & coordinates) const = 0;
-
-	/// returns specific object by ID, or nullptr if not found
-	virtual const CGObjectInstance * getObject(ObjectInstanceID objectID) const = 0;
-
-	/// returns path of currently active hero, or nullptr if none
-	virtual const CGPath * currentPath() const = 0;
-
-	/// returns true if specified tile is visible in current context
-	virtual bool isVisible(const int3 & coordinates) const = 0;
-
-	virtual size_t objectGroupIndex(ObjectInstanceID objectID) const = 0;
-	virtual Point objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const = 0;
+	const IMapRendererContext & context;
 
-	/// returns object animation transparency. IF set to 0, object will not be visible
-	virtual double objectTransparency(ObjectInstanceID objectID) const = 0;
-
-	/// returns animation frame for selected object
-	virtual size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const = 0;
-
-	/// returns animation frame for terrain
-	virtual size_t terrainImageIndex(size_t groupSize) const = 0;
-
-//	/// returns size of ouput tile, in pixels. 32x32 for "standard" map, may be smaller for world view mode
-//	virtual Point getTileSize() const = 0;
-
-	/// if true, world view overlay will be shown
-	virtual bool showOverlay() const = 0;
+public:
+	explicit MapObjectsSorter(const IMapRendererContext & context);
 
-	/// if true, map grid should be visible on map
-	virtual bool showGrid() const = 0;
-	virtual bool showVisitable() const = 0;
-	virtual bool showBlockable() const = 0;
+	bool operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const;
+	bool operator()(const CGObjectInstance * left, const CGObjectInstance * right) const;
 };
 
-class IMapObjectObserver
+struct HeroAnimationState
 {
-public:
-	IMapObjectObserver();
-	virtual ~IMapObjectObserver();
+	ObjectInstanceID target;
+	int3 tileFrom;
+	int3 tileDest;
+	double progress;
+};
 
-	virtual bool hasOngoingAnimations() = 0;
+struct FadingAnimationState
+{
+	ObjectInstanceID target;
+	double progress;
+};
 
-	/// Plays fade-in animation and adds object to map
-	virtual void onObjectFadeIn(const CGObjectInstance * obj) {}
+class MapRendererContext : public IMapRendererContext
+{
+	friend class MapViewController;
 
-	/// Plays fade-out animation and removed object from map
-	virtual void onObjectFadeOut(const CGObjectInstance * obj) {}
+	boost::multi_array<MapObjectsList, 3> objects;
 
-	/// Adds object to map instantly, with no animation
-	virtual void onObjectInstantAdd(const CGObjectInstance * obj) {}
+	//Point tileSize = Point(32, 32);
+	uint32_t animationTime = 0;
 
-	/// Removes object from map instantly, with no animation
-	virtual void onObjectInstantRemove(const CGObjectInstance * obj) {}
+	boost::optional<HeroAnimationState> movementAnimation;
+	boost::optional<HeroAnimationState> teleportAnimation;
 
-	/// Perform hero teleportation animation with terrain fade animation
-	virtual void onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+	boost::optional<FadingAnimationState> fadeOutAnimation;
+	boost::optional<FadingAnimationState> fadeInAnimation;
 
-	/// Perform hero movement animation, moving hero across terrain
-	virtual void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+	bool worldViewModeActive = false;
 
-	/// Instantly rotates hero to face destination tile
-	virtual void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
+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;
+
+	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 override;
+	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
+	size_t terrainImageIndex(size_t groupSize) const override;
+//	Point getTileSize() const override;
+
+	bool showOverlay() const override;
+	bool showGrid() const override;
+	bool showVisitable() const override;
+	bool showBlockable() const override;
 };

+ 6 - 615
client/mapRenderer/MapView.cpp

@@ -11,10 +11,13 @@
 #include "StdInc.h"
 #include "MapView.h"
 
-#include "MapRenderer.h"
+#include "MapViewModel.h"
+#include "MapViewCache.h"
+#include "MapViewController.h"
+#include "MapRendererContext.h"
 #include "mapHandler.h"
-#include "CAdvMapInt.h"
 
+#include "../adventureMap/CAdvMapInt.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
@@ -29,115 +32,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"
 
-MapViewCache::~MapViewCache() = default;
-
-MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
-	: model(model)
-	, mapRenderer(new MapRenderer())
-	, iconsStorage(new CAnimation("VwSymbol"))
-	, intermediate(new Canvas(Point(32,32)))
-	, terrain(new Canvas(model->getCacheDimensionsPixels()))
-{
-	iconsStorage->preload();
-	for (size_t i = 0; i < iconsStorage->size(); ++i)
-		iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY);
-
-}
-
-Canvas MapViewCache::getTile(const int3 & coordinates)
-{
-	return Canvas(*terrain, model->getCacheTileArea(coordinates));
-}
-
-std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
-{
-	if (!context->isVisible(coordinates))
-		return nullptr;
-
-	for(const auto & objectID : context->getObjects(coordinates))
-	{
-		const auto * object = context->getObject(objectID);
-
-		if (!object->visitableAt(coordinates.x, coordinates.y))
-			continue;
-
-		size_t ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
-		if (object->tempOwner.isValidPlayer())
-			ownerIndex = object->tempOwner.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
-
-		switch (object->ID)
-		{
-			case Obj::MONOLITH_ONE_WAY_ENTRANCE:
-			case Obj::MONOLITH_ONE_WAY_EXIT:
-			case Obj::MONOLITH_TWO_WAY:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::TELEPORT));
-			case Obj::SUBTERRANEAN_GATE:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::GATE));
-			case Obj::ARTIFACT:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::ARTIFACT));
-			case Obj::TOWN:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::TOWN));
-			case Obj::HERO:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::HERO));
-			case Obj::MINE:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::MINE_WOOD) + object->subID);
-			case Obj::RESOURCE:
-				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::RES_WOOD) + object->subID);
-		}
-	}
-	return nullptr;
-}
-
-void MapViewCache::updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
-{
-	Canvas target = getTile(coordinates);
-
-	if(model->getSingleTileSize() == Point(32, 32))
-	{
-		mapRenderer->renderTile(*context, target, coordinates);
-	}
-	else
-	{
-		mapRenderer->renderTile(*context, *intermediate, coordinates);
-		target.drawScaled(*intermediate, Point(0, 0), model->getSingleTileSize());
-	}
-}
-
-void MapViewCache::update(const std::shared_ptr<MapRendererContext> & context)
-{
-	Rect dimensions = model->getTilesTotalRect();
-
-	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
-		for(int x = dimensions.left(); x < dimensions.right(); ++x)
-			updateTile(context, {x, y, model->getLevel()});
-}
-
-void MapViewCache::render(const std::shared_ptr<MapRendererContext> & context, Canvas & target)
-{
-	Rect dimensions = model->getTilesTotalRect();
-
-	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
-	{
-		for(int x = dimensions.left(); x < dimensions.right(); ++x)
-		{
-			int3 tile(x, y, model->getLevel());
-			Canvas source = getTile(tile);
-			Rect targetRect = model->getTargetTileArea(tile);
-			target.draw(source, targetRect.topLeft());
-
-			if (context->showOverlay())
-			{
-				auto overlay = getOverlayImageForTile(context, tile);
-
-				if (overlay)
-				{
-					Point position = targetRect.center() - overlay->dimensions() / 2;
-					target.draw(overlay, position);
-				}
-			}
-		}
-	}
-}
+MapView::~MapView() = default;
 
 std::shared_ptr<MapViewModel> MapView::createModel(const Point & dimensions) const
 {
@@ -179,516 +74,12 @@ void MapView::showAll(SDL_Surface * to)
 	show(to);
 }
 
-int3 MapRendererContext::getMapSize() const
-{
-	return LOCPLINT->cb->getMapSize();
-}
-
-bool MapRendererContext::isInMap(const int3 & coordinates) const
-{
-	return LOCPLINT->cb->isInTheMap(coordinates);
-}
-
-const TerrainTile & MapRendererContext::getMapTile(const int3 & coordinates) const
-{
-	return CGI->mh->getMap()->getTile(coordinates);
-}
-
-const CGObjectInstance * MapRendererContext::getObject(ObjectInstanceID objectID) const
-{
-	return CGI->mh->getMap()->objects.at(objectID.getNum());
-}
-
-bool MapRendererContext::isVisible(const int3 & coordinates) const
-{
-	return LOCPLINT->cb->isVisible(coordinates) || settings["session"]["spectate"].Bool();
-}
-
-const CGPath * MapRendererContext::currentPath() const
-{
-	const auto * hero = adventureInt->curHero();
-
-	if(!hero)
-		return nullptr;
-
-	if(!LOCPLINT->paths.hasPath(hero))
-		return nullptr;
-
-	return &LOCPLINT->paths.getPath(hero);
-}
-
-size_t MapRendererContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
-{
-	assert(groupSize > 0);
-	if(groupSize == 0)
-		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;
-}
-
-size_t MapRendererContext::terrainImageIndex(size_t groupSize) const
-{
-	size_t baseFrameTime = 180;
-	size_t frameCounter = animationTime / baseFrameTime;
-	size_t frameIndex = frameCounter % groupSize;
-	return frameIndex;
-}
-
-//Point MapRendererContext::getTileSize() const
-//{
-//	return Point(32, 32);
-//}
-
-bool MapRendererContext::showOverlay() const
-{
-	return worldViewModeActive;
-}
-
-bool MapRendererContext::showGrid() const
-{
-	return settings["gameTweaks"]["showGrid"].Bool();
-}
-
-bool MapRendererContext::showVisitable() const
-{
-	return settings["session"]["showVisitable"].Bool();
-}
-
-bool MapRendererContext::showBlockable() const
-{
-	return settings["session"]["showBlockable"].Bool();
-}
-
-void MapViewController::setViewCenter(const int3 & position)
-{
-	assert(context->isInMap(position));
-	setViewCenter(Point(position) * model->getSingleTileSize(), position.z);
-}
-
-void MapViewController::setViewCenter(const Point & position, int level)
-{
-	Point betterPosition = {
-		vstd::clamp(position.x, 0, context->getMapSize().x * model->getSingleTileSize().x),
-		vstd::clamp(position.y, 0, context->getMapSize().y * model->getSingleTileSize().y)
-	};
-
-	model->setViewCenter(betterPosition);
-	model->setLevel(vstd::clamp(level, 0, context->getMapSize().z));
-}
-
-void MapViewController::setTileSize(const Point & tileSize)
-{
-	model->setTileSize(tileSize);
-}
-
 std::shared_ptr<const MapViewModel> MapView::getModel() const
 {
 	return model;
 }
 
-void MapViewModel::setTileSize(const Point & newValue)
-{
-	tileSize = newValue;
-}
-
-void MapViewModel::setViewCenter(const Point & newValue)
-{
-	viewCenter = newValue;
-}
-
-void MapViewModel::setViewDimensions(const Point & newValue)
-{
-	viewDimensions = newValue;
-}
-
-void MapViewModel::setLevel(int newLevel)
-{
-	mapLevel = newLevel;
-}
-
-Point MapViewModel::getSingleTileSize() const
-{
-	return tileSize;
-}
-
-Point MapViewModel::getMapViewCenter() const
-{
-	return viewCenter;
-}
-
-Point MapViewModel::getPixelsVisibleDimensions() const
-{
-	return viewDimensions;
-}
-
-int MapViewModel::getLevel() const
-{
-	return mapLevel;
-}
-
-Point MapViewModel::getTilesVisibleDimensions() const
-{
-	// total number of potentially visible tiles is:
-	// 1) number of completely visible tiles
-	// 2) additional tile that might be partially visible from left/top size
-	// 3) additional tile that might be partially visible from right/bottom size
-	return {
-		getPixelsVisibleDimensions().x / getSingleTileSize().x + 2,
-		getPixelsVisibleDimensions().y / getSingleTileSize().y + 2,
-	};
-}
-
-Rect MapViewModel::getTilesTotalRect() const
-{
-	return Rect(
-		Point(getTileAtPoint(Point(0,0))),
-		getTilesVisibleDimensions()
-	);
-}
-
-int3 MapViewModel::getTileAtPoint(const Point & position) const
-{
-	Point topLeftOffset = getMapViewCenter() - getPixelsVisibleDimensions() / 2;
-
-	Point absolutePosition = position + topLeftOffset;
-
-	// NOTE: using division via double in order to use std::floor
-	// which rounds to negative infinity and not towards zero (like integer division)
-	return {
-		static_cast<int>(std::floor(static_cast<double>(absolutePosition.x) / getSingleTileSize().x)),
-		static_cast<int>(std::floor(static_cast<double>(absolutePosition.y) / getSingleTileSize().y)),
-		getLevel()
-	};
-}
-
-Point MapViewModel::getCacheDimensionsPixels() const
-{
-	return getTilesVisibleDimensions() * getSingleTileSize();
-}
-
-Rect MapViewModel::getCacheTileArea(const int3 & coordinates) const
-{
-	assert(mapLevel == coordinates.z);
-	assert(getTilesVisibleDimensions().x + coordinates.x >= 0);
-	assert(getTilesVisibleDimensions().y + coordinates.y >= 0);
-
-	Point tileIndex{
-		(getTilesVisibleDimensions().x + coordinates.x) % getTilesVisibleDimensions().x,
-		(getTilesVisibleDimensions().y + coordinates.y) % getTilesVisibleDimensions().y
-	};
-
-	return Rect(tileIndex * tileSize, tileSize);
-}
-
-Rect MapViewModel::getTargetTileArea(const int3 & coordinates) const
-{
-	Point topLeftOffset = getMapViewCenter() - getPixelsVisibleDimensions() / 2;
-	Point tilePosAbsolute = Point(coordinates) * tileSize;
-	Point tilePosRelative = tilePosAbsolute - topLeftOffset;
-
-	return Rect(tilePosRelative, tileSize);
-}
-
-MapRendererContext::MapRendererContext()
-{
-	auto mapSize = getMapSize();
-
-	objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
-
-	for(const auto & obj : CGI->mh->getMap()->objects)
-		addObject(obj);
-}
-
-void MapRendererContext::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(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));
-			}
-		}
-	}
-}
-
-void MapRendererContext::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(isInMap(currTile))
-			{
-				auto & container = objects[currTile.z][currTile.x][currTile.y];
-
-				container.push_back(object->id);
-				boost::range::sort(container, MapObjectsSorter(*this));
-			}
-		}
-	}
-}
-
-void MapRendererContext::removeObject(const CGObjectInstance * object)
-{
-	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);
-}
-
-const MapRendererContext::MapObjectsList & MapRendererContext::getObjects(const int3 & coordinates) const
-{
-	assert(isInMap(coordinates));
-	return objects[coordinates.z][coordinates.x][coordinates.y];
-}
-
-size_t MapRendererContext::objectGroupIndex(ObjectInstanceID objectID) 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};
-
-	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];
-	}
-
-	if(obj->ID == Obj::BOAT)
-	{
-		const auto * boat = dynamic_cast<const CGBoat *>(obj);
-
-		uint8_t direction = boat->hero ? boat->hero->moveDir : boat->direction;
-
-		if(movementAnimation && movementAnimation->target == objectID)
-			return moveGroups[direction];
-		return idleGroups[direction];
-	}
-	return 0;
-}
-
-Point MapRendererContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const
-{
-	if(movementAnimation && movementAnimation->target == objectID)
-	{
-		int3 offsetTilesFrom = movementAnimation->tileFrom - coordinates;
-		int3 offsetTilesDest = movementAnimation->tileDest - coordinates;
-
-		Point offsetPixelsFrom = Point(offsetTilesFrom) * Point(32, 32);
-		Point offsetPixelsDest = Point(offsetTilesDest) * Point(32, 32);
-
-		Point result = vstd::lerp(offsetPixelsFrom, offsetPixelsDest, movementAnimation->progress);
-
-		return result;
-	}
-
-	const CGObjectInstance * object = getObject(objectID);
-	int3 offsetTiles(object->getPosition() - coordinates);
-	return Point(offsetTiles) * Point(32, 32);
-}
-
-double MapRendererContext::objectTransparency(ObjectInstanceID objectID) const
-{
-	const CGObjectInstance * object = getObject(objectID);
-
-	if(object && object->ID == Obj::HERO)
-	{
-		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
-
-		if(hero->inTownGarrison)
-			return 0;
-
-		if(hero->boat)
-			return 0;
-	}
-
-	if(fadeOutAnimation && objectID == fadeOutAnimation->target)
-		return 1.0 - fadeOutAnimation->progress;
-
-	if(fadeInAnimation && objectID == fadeInAnimation->target)
-		return fadeInAnimation->progress;
-
-	return 1.0;
-}
-
-MapViewController::MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model)
-	: context(std::move(context))
-	, model(std::move(model))
-{
-}
-
-void MapViewController::update(uint32_t timeDelta)
-{
-	// confirmed to match H3 for
-	// - hero embarking on boat (500 ms)
-	// - hero disembarking from boat (500 ms)
-	// - TODO: picking up resources
-	// - TODO: killing mosters
-	// - teleporting ( 250 ms)
-	static const double fadeOutDuration = 500;
-	static const double fadeInDuration = 500;
-	static const double heroTeleportDuration = 250;
-
-	//FIXME: remove code duplication?
-
-	if(context->movementAnimation)
-	{
-		// TODO: enemyMoveTime
-		double heroMoveTime = settings["adventure"]["heroMoveTime"].Float();
-
-		context->movementAnimation->progress += timeDelta / heroMoveTime;
-
-		Point positionFrom = Point(context->movementAnimation->tileFrom) * model->getSingleTileSize();
-		Point positionDest = Point(context->movementAnimation->tileDest) * model->getSingleTileSize();
-
-		Point positionCurr = vstd::lerp(positionFrom, positionDest, context->movementAnimation->progress);
-
-		setViewCenter(positionCurr, context->movementAnimation->tileDest.z);
-
-		if(context->movementAnimation->progress >= 1.0)
-		{
-			setViewCenter(context->movementAnimation->tileDest);
-
-			context->removeObject(context->getObject(context->movementAnimation->target));
-			context->addObject(context->getObject(context->movementAnimation->target));
-			context->movementAnimation.reset();
-		}
-	}
-
-	if(context->teleportAnimation)
-	{
-		context->teleportAnimation->progress += timeDelta / heroTeleportDuration;
-		if(context->teleportAnimation->progress >= 1.0)
-			context->teleportAnimation.reset();
-	}
-
-	if(context->fadeOutAnimation)
-	{
-		context->fadeOutAnimation->progress += timeDelta / fadeOutDuration;
-		if(context->fadeOutAnimation->progress >= 1.0)
-		{
-			context->removeObject(context->getObject(context->fadeOutAnimation->target));
-			context->fadeOutAnimation.reset();
-		}
-	}
-
-	if(context->fadeInAnimation)
-	{
-		context->fadeInAnimation->progress += timeDelta / fadeInDuration;
-		if(context->fadeInAnimation->progress >= 1.0)
-			context->fadeInAnimation.reset();
-	}
-
-	context->animationTime += timeDelta;
-	//context->tileSize = Point(32,32); //model->getSingleTileSize();
-	context->worldViewModeActive = model->getSingleTileSize() != Point(32,32);
-}
-
-void MapViewController::onObjectFadeIn(const CGObjectInstance * obj)
-{
-	assert(!context->fadeInAnimation);
-	context->fadeInAnimation = FadingAnimationState{obj->id, 0.0};
-	context->addObject(obj);
-}
-
-void MapViewController::onObjectFadeOut(const CGObjectInstance * obj)
-{
-	assert(!context->fadeOutAnimation);
-	context->fadeOutAnimation = FadingAnimationState{obj->id, 0.0};
-}
-
-void MapViewController::onObjectInstantAdd(const CGObjectInstance * obj)
-{
-	context->addObject(obj);
-};
-
-void MapViewController::onObjectInstantRemove(const CGObjectInstance * obj)
-{
-	context->removeObject(obj);
-};
-
-void MapViewController::onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
-{
-	assert(!context->teleportAnimation);
-	context->teleportAnimation = HeroAnimationState{obj->id, from, dest, 0.0};
-}
-
-void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
-{
-	assert(!context->movementAnimation);
-
-	const CGObjectInstance * movingObject = obj;
-	if(obj->boat)
-		movingObject = obj->boat;
-
-	context->removeObject(movingObject);
-
-	if(settings["adventure"]["heroMoveTime"].Float() > 1)
-	{
-		context->addMovingObject(movingObject, from, dest);
-		context->movementAnimation = HeroAnimationState{movingObject->id, from, dest, 0.0};
-	}
-	else
-	{
-		// instant movement
-		context->addObject(movingObject);
-	}
-}
-
-void MapViewController::onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
-{
-	//TODO
-}
-
 std::shared_ptr<MapViewController> MapView::getController()
 {
 	return controller;
 }
-
-bool MapViewController::hasOngoingAnimations()
-{
-	if(context->movementAnimation)
-		return true;
-
-	if(context->teleportAnimation)
-		return true;
-
-	if(context->fadeOutAnimation)
-		return true;
-
-	if(context->fadeInAnimation)
-		return true;
-
-	return false;
-}

+ 4 - 165
client/mapRenderer/MapView.h

@@ -10,173 +10,11 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
-#include "../lib/int3.h"
-#include "MapRendererContext.h"
 
-class IImage;
-class Canvas;
-class MapRenderer;
+class MapRendererContext;
 class MapViewController;
-
-struct HeroAnimationState
-{
-	ObjectInstanceID target;
-	int3 tileFrom;
-	int3 tileDest;
-	double progress;
-};
-
-struct FadingAnimationState
-{
-	ObjectInstanceID target;
-	double progress;
-};
-
-class MapRendererContext : public IMapRendererContext
-{
-	friend class MapViewController;
-
-	boost::multi_array<MapObjectsList, 3> objects;
-
-	//Point tileSize = Point(32, 32);
-	uint32_t animationTime = 0;
-
-	boost::optional<HeroAnimationState> movementAnimation;
-	boost::optional<HeroAnimationState> teleportAnimation;
-
-	boost::optional<FadingAnimationState> fadeOutAnimation;
-	boost::optional<FadingAnimationState> fadeInAnimation;
-
-	bool worldViewModeActive = false;
-
-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;
-
-	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 override;
-	size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override;
-	size_t terrainImageIndex(size_t groupSize) const override;
-//	Point getTileSize() const override;
-
-	bool showOverlay() const override;
-	bool showGrid() const override;
-	bool showVisitable() const override;
-	bool showBlockable() const override;
-};
-
-class MapViewModel
-{
-	Point tileSize;
-	Point viewCenter;
-	Point viewDimensions;
-
-	int mapLevel = 0;
-
-public:
-	void setTileSize(const Point & newValue);
-	void setViewCenter(const Point & newValue);
-	void setViewDimensions(const Point & newValue);
-	void setLevel(int newLevel);
-
-	/// returns current size of map tile in pixels
-	Point getSingleTileSize() const;
-
-	/// returns center point of map view, in Map coordinates
-	Point getMapViewCenter() const;
-
-	/// returns total number of visible tiles
-	Point getTilesVisibleDimensions() const;
-
-	/// returns rect encompassing all visible tiles
-	Rect getTilesTotalRect() const;
-
-	/// returns required area in pixels of cache canvas
-	Point getCacheDimensionsPixels() const;
-
-	/// returns actual player-visible area
-	Point getPixelsVisibleDimensions() const;
-
-	/// returns area covered by specified tile in map cache
-	Rect getCacheTileArea(const int3 & coordinates) const;
-
-	/// returns area covered by specified tile in target view
-	Rect getTargetTileArea(const int3 & coordinates) const;
-
-	/// returns tile under specified position in target view
-	int3 getTileAtPoint(const Point & position) const;
-
-	/// returns currently visible map level
-	int getLevel() const;
-};
-
-/// Class responsible for rendering of entire map view
-/// uses rendering parameters provided by owner class
-class MapViewCache
-{
-	std::shared_ptr<MapViewModel> model;
-
-	std::unique_ptr<Canvas> terrain;
-	std::unique_ptr<Canvas> terrainTransition;
-	std::unique_ptr<Canvas> intermediate;
-	std::unique_ptr<MapRenderer> mapRenderer;
-
-	std::unique_ptr<CAnimation> iconsStorage;
-
-	Canvas getTile(const int3 & coordinates);
-	void updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
-
-	std::shared_ptr<IImage> getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
-public:
-	explicit MapViewCache(const std::shared_ptr<MapViewModel> & model);
-	~MapViewCache();
-
-	/// updates internal terrain cache according to provided time delta
-	void update(const std::shared_ptr<MapRendererContext> & context);
-
-	/// renders updated terrain cache onto provided canvas
-	void render(const std::shared_ptr<MapRendererContext> &context, Canvas & target);
-};
-
-/// 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<MapViewModel> model;
-
-private:
-	// 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 onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
-	void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
-
-public:
-	MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model);
-
-	void setViewCenter(const int3 & position);
-	void setViewCenter(const Point & position, int level);
-	void setTileSize(const Point & tileSize);
-	void update(uint32_t timeDelta);
-};
+class MapViewModel;
+class MapViewCache;
 
 /// Main map rendering class that mostly acts as container for component classes
 class MapView : public CIntObject
@@ -193,6 +31,7 @@ public:
 	std::shared_ptr<MapViewController> getController();
 
 	MapView(const Point & offset, const Point & dimensions);
+	~MapView() override;
 
 	void show(SDL_Surface * to) override;
 	void showAll(SDL_Surface * to) override;

+ 131 - 0
client/mapRenderer/MapViewCache.cpp

@@ -0,0 +1,131 @@
+/*
+ * MapViewCache.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 "MapViewCache.h"
+
+#include "MapRenderer.h"
+#include "MapViewModel.h"
+
+#include "../render/CAnimation.h"
+#include "../render/Canvas.h"
+#include "../render/IImage.h"
+
+#include "../../lib/mapObjects/CObjectHandler.h"
+
+MapViewCache::~MapViewCache() = default;
+
+MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
+	: model(model)
+	, mapRenderer(new MapRenderer())
+	, iconsStorage(new CAnimation("VwSymbol"))
+	, intermediate(new Canvas(Point(32,32)))
+	, terrain(new Canvas(model->getCacheDimensionsPixels()))
+{
+	iconsStorage->preload();
+	for (size_t i = 0; i < iconsStorage->size(); ++i)
+		iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY);
+
+}
+
+Canvas MapViewCache::getTile(const int3 & coordinates)
+{
+	return Canvas(*terrain, model->getCacheTileArea(coordinates));
+}
+
+std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
+{
+	if (!context->isVisible(coordinates))
+		return nullptr;
+
+	for(const auto & objectID : context->getObjects(coordinates))
+	{
+		const auto * object = context->getObject(objectID);
+
+		if (!object->visitableAt(coordinates.x, coordinates.y))
+			continue;
+
+		size_t ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
+		if (object->tempOwner.isValidPlayer())
+			ownerIndex = object->tempOwner.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
+
+		switch (object->ID)
+		{
+			case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+			case Obj::MONOLITH_ONE_WAY_EXIT:
+			case Obj::MONOLITH_TWO_WAY:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::TELEPORT));
+			case Obj::SUBTERRANEAN_GATE:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::GATE));
+			case Obj::ARTIFACT:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::ARTIFACT));
+			case Obj::TOWN:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::TOWN));
+			case Obj::HERO:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::HERO));
+			case Obj::MINE:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::MINE_WOOD) + object->subID);
+			case Obj::RESOURCE:
+				return iconsStorage->getImage(ownerIndex + static_cast<size_t>(EWorldViewIcon::RES_WOOD) + object->subID);
+		}
+	}
+	return nullptr;
+}
+
+void MapViewCache::updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
+{
+	Canvas target = getTile(coordinates);
+
+	if(model->getSingleTileSize() == Point(32, 32))
+	{
+		mapRenderer->renderTile(*context, target, coordinates);
+	}
+	else
+	{
+		mapRenderer->renderTile(*context, *intermediate, coordinates);
+		target.drawScaled(*intermediate, Point(0, 0), model->getSingleTileSize());
+	}
+}
+
+void MapViewCache::update(const std::shared_ptr<MapRendererContext> & context)
+{
+	Rect dimensions = model->getTilesTotalRect();
+
+	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
+		for(int x = dimensions.left(); x < dimensions.right(); ++x)
+			updateTile(context, {x, y, model->getLevel()});
+}
+
+void MapViewCache::render(const std::shared_ptr<MapRendererContext> & context, Canvas & target)
+{
+	Rect dimensions = model->getTilesTotalRect();
+
+	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
+	{
+		for(int x = dimensions.left(); x < dimensions.right(); ++x)
+		{
+			int3 tile(x, y, model->getLevel());
+			Canvas source = getTile(tile);
+			Rect targetRect = model->getTargetTileArea(tile);
+			target.draw(source, targetRect.topLeft());
+
+			if (context->showOverlay())
+			{
+				auto overlay = getOverlayImageForTile(context, tile);
+
+				if (overlay)
+				{
+					Point position = targetRect.center() - overlay->dimensions() / 2;
+					target.draw(overlay, position);
+				}
+			}
+		}
+	}
+}

+ 77 - 0
client/mapRenderer/MapViewCache.h

@@ -0,0 +1,77 @@
+/*
+ * MapViewCache.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+class int3;
+VCMI_LIB_NAMESPACE_END
+
+class IImage;
+class CAnimation;
+class Canvas;
+class MapRenderer;
+class MapRendererContext;
+//class MapViewController;
+class MapViewModel;
+
+// 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
+};
+
+/// Class responsible for rendering of entire map view
+/// uses rendering parameters provided by owner class
+class MapViewCache
+{
+	std::shared_ptr<MapViewModel> model;
+
+	std::unique_ptr<Canvas> terrain;
+	std::unique_ptr<Canvas> terrainTransition;
+	std::unique_ptr<Canvas> intermediate;
+	std::unique_ptr<MapRenderer> mapRenderer;
+
+	std::unique_ptr<CAnimation> iconsStorage;
+
+	Canvas getTile(const int3 & coordinates);
+	void updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
+
+	std::shared_ptr<IImage> getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
+public:
+	explicit MapViewCache(const std::shared_ptr<MapViewModel> & model);
+	~MapViewCache();
+
+	/// updates internal terrain cache according to provided time delta
+	void update(const std::shared_ptr<MapRendererContext> & context);
+
+	/// renders updated terrain cache onto provided canvas
+	void render(const std::shared_ptr<MapRendererContext> &context, Canvas & target);
+};

+ 187 - 0
client/mapRenderer/MapViewController.cpp

@@ -0,0 +1,187 @@
+/*
+ * MapViewController.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 "MapViewController.h"
+
+#include "MapRendererContext.h"
+#include "MapViewModel.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/MiscObjects.h"
+
+void MapViewController::setViewCenter(const int3 & position)
+{
+	assert(context->isInMap(position));
+	setViewCenter(Point(position) * model->getSingleTileSize(), position.z);
+}
+
+void MapViewController::setViewCenter(const Point & position, int level)
+{
+	Point betterPosition = {
+		vstd::clamp(position.x, 0, context->getMapSize().x * model->getSingleTileSize().x),
+		vstd::clamp(position.y, 0, context->getMapSize().y * model->getSingleTileSize().y)
+	};
+
+	model->setViewCenter(betterPosition);
+	model->setLevel(vstd::clamp(level, 0, context->getMapSize().z));
+}
+
+void MapViewController::setTileSize(const Point & tileSize)
+{
+	model->setTileSize(tileSize);
+}
+
+MapViewController::MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model)
+	: context(std::move(context))
+	, model(std::move(model))
+{
+}
+
+void MapViewController::update(uint32_t timeDelta)
+{
+	// confirmed to match H3 for
+	// - hero embarking on boat (500 ms)
+	// - hero disembarking from boat (500 ms)
+	// - TODO: picking up resources
+	// - TODO: killing mosters
+	// - teleporting ( 250 ms)
+	static const double fadeOutDuration = 500;
+	static const double fadeInDuration = 500;
+	static const double heroTeleportDuration = 250;
+
+	//FIXME: remove code duplication?
+
+	if(context->movementAnimation)
+	{
+		// TODO: enemyMoveTime
+		double heroMoveTime = settings["adventure"]["heroMoveTime"].Float();
+
+		context->movementAnimation->progress += timeDelta / heroMoveTime;
+
+		Point positionFrom = Point(context->movementAnimation->tileFrom) * model->getSingleTileSize();
+		Point positionDest = Point(context->movementAnimation->tileDest) * model->getSingleTileSize();
+
+		Point positionCurr = vstd::lerp(positionFrom, positionDest, context->movementAnimation->progress);
+
+		setViewCenter(positionCurr, context->movementAnimation->tileDest.z);
+
+		if(context->movementAnimation->progress >= 1.0)
+		{
+			setViewCenter(context->movementAnimation->tileDest);
+
+			context->removeObject(context->getObject(context->movementAnimation->target));
+			context->addObject(context->getObject(context->movementAnimation->target));
+			context->movementAnimation.reset();
+		}
+	}
+
+	if(context->teleportAnimation)
+	{
+		context->teleportAnimation->progress += timeDelta / heroTeleportDuration;
+		if(context->teleportAnimation->progress >= 1.0)
+			context->teleportAnimation.reset();
+	}
+
+	if(context->fadeOutAnimation)
+	{
+		context->fadeOutAnimation->progress += timeDelta / fadeOutDuration;
+		if(context->fadeOutAnimation->progress >= 1.0)
+		{
+			context->removeObject(context->getObject(context->fadeOutAnimation->target));
+			context->fadeOutAnimation.reset();
+		}
+	}
+
+	if(context->fadeInAnimation)
+	{
+		context->fadeInAnimation->progress += timeDelta / fadeInDuration;
+		if(context->fadeInAnimation->progress >= 1.0)
+			context->fadeInAnimation.reset();
+	}
+
+	context->animationTime += timeDelta;
+	//context->tileSize = Point(32,32); //model->getSingleTileSize();
+	context->worldViewModeActive = model->getSingleTileSize() != Point(32,32);
+}
+
+void MapViewController::onObjectFadeIn(const CGObjectInstance * obj)
+{
+	assert(!context->fadeInAnimation);
+	context->fadeInAnimation = FadingAnimationState{obj->id, 0.0};
+	context->addObject(obj);
+}
+
+void MapViewController::onObjectFadeOut(const CGObjectInstance * obj)
+{
+	assert(!context->fadeOutAnimation);
+	context->fadeOutAnimation = FadingAnimationState{obj->id, 0.0};
+}
+
+void MapViewController::onObjectInstantAdd(const CGObjectInstance * obj)
+{
+	context->addObject(obj);
+};
+
+void MapViewController::onObjectInstantRemove(const CGObjectInstance * obj)
+{
+	context->removeObject(obj);
+};
+
+void MapViewController::onHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!context->teleportAnimation);
+	context->teleportAnimation = HeroAnimationState{obj->id, from, dest, 0.0};
+}
+
+void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!context->movementAnimation);
+
+	const CGObjectInstance * movingObject = obj;
+	if(obj->boat)
+		movingObject = obj->boat;
+
+	context->removeObject(movingObject);
+
+	if(settings["adventure"]["heroMoveTime"].Float() > 1)
+	{
+		context->addMovingObject(movingObject, from, dest);
+		context->movementAnimation = HeroAnimationState{movingObject->id, from, dest, 0.0};
+	}
+	else
+	{
+		// instant movement
+		context->addObject(movingObject);
+	}
+}
+
+void MapViewController::onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	//TODO
+}
+
+bool MapViewController::hasOngoingAnimations()
+{
+	if(context->movementAnimation)
+		return true;
+
+	if(context->teleportAnimation)
+		return true;
+
+	if(context->fadeOutAnimation)
+		return true;
+
+	if(context->fadeInAnimation)
+		return true;
+
+	return false;
+}

+ 45 - 0
client/mapRenderer/MapViewController.h

@@ -0,0 +1,45 @@
+/*
+ * MapViewController.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 "IMapRendererObserver.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+class MapViewModel;
+class MapRendererContext;
+
+/// 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<MapViewModel> model;
+
+private:
+	// 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 onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+
+public:
+	MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model);
+
+	void setViewCenter(const int3 & position);
+	void setViewCenter(const Point & position, int level);
+	void setTileSize(const Point & tileSize);
+	void update(uint32_t timeDelta);
+};

+ 117 - 0
client/mapRenderer/MapViewModel.cpp

@@ -0,0 +1,117 @@
+/*
+ * MapViewModel.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 "MapViewModel.h"
+
+#include "../../lib/int3.h"
+
+void MapViewModel::setTileSize(const Point & newValue)
+{
+	tileSize = newValue;
+}
+
+void MapViewModel::setViewCenter(const Point & newValue)
+{
+	viewCenter = newValue;
+}
+
+void MapViewModel::setViewDimensions(const Point & newValue)
+{
+	viewDimensions = newValue;
+}
+
+void MapViewModel::setLevel(int newLevel)
+{
+	mapLevel = newLevel;
+}
+
+Point MapViewModel::getSingleTileSize() const
+{
+	return tileSize;
+}
+
+Point MapViewModel::getMapViewCenter() const
+{
+	return viewCenter;
+}
+
+Point MapViewModel::getPixelsVisibleDimensions() const
+{
+	return viewDimensions;
+}
+
+int MapViewModel::getLevel() const
+{
+	return mapLevel;
+}
+
+Point MapViewModel::getTilesVisibleDimensions() const
+{
+	// total number of potentially visible tiles is:
+	// 1) number of completely visible tiles
+	// 2) additional tile that might be partially visible from left/top size
+	// 3) additional tile that might be partially visible from right/bottom size
+	return {
+		getPixelsVisibleDimensions().x / getSingleTileSize().x + 2,
+		getPixelsVisibleDimensions().y / getSingleTileSize().y + 2,
+	};
+}
+
+Rect MapViewModel::getTilesTotalRect() const
+{
+	return Rect(
+		Point(getTileAtPoint(Point(0,0))),
+		getTilesVisibleDimensions()
+	);
+}
+
+int3 MapViewModel::getTileAtPoint(const Point & position) const
+{
+	Point topLeftOffset = getMapViewCenter() - getPixelsVisibleDimensions() / 2;
+
+	Point absolutePosition = position + topLeftOffset;
+
+	// NOTE: using division via double in order to use std::floor
+	// which rounds to negative infinity and not towards zero (like integer division)
+	return {
+		static_cast<int>(std::floor(static_cast<double>(absolutePosition.x) / getSingleTileSize().x)),
+		static_cast<int>(std::floor(static_cast<double>(absolutePosition.y) / getSingleTileSize().y)),
+		getLevel()
+	};
+}
+
+Point MapViewModel::getCacheDimensionsPixels() const
+{
+	return getTilesVisibleDimensions() * getSingleTileSize();
+}
+
+Rect MapViewModel::getCacheTileArea(const int3 & coordinates) const
+{
+	assert(mapLevel == coordinates.z);
+	assert(getTilesVisibleDimensions().x + coordinates.x >= 0);
+	assert(getTilesVisibleDimensions().y + coordinates.y >= 0);
+
+	Point tileIndex{
+		(getTilesVisibleDimensions().x + coordinates.x) % getTilesVisibleDimensions().x,
+		(getTilesVisibleDimensions().y + coordinates.y) % getTilesVisibleDimensions().y
+	};
+
+	return Rect(tileIndex * tileSize, tileSize);
+}
+
+Rect MapViewModel::getTargetTileArea(const int3 & coordinates) const
+{
+	Point topLeftOffset = getMapViewCenter() - getPixelsVisibleDimensions() / 2;
+	Point tilePosAbsolute = Point(coordinates) * tileSize;
+	Point tilePosRelative = tilePosAbsolute - topLeftOffset;
+
+	return Rect(tilePosRelative, tileSize);
+}

+ 57 - 0
client/mapRenderer/MapViewModel.h

@@ -0,0 +1,57 @@
+/*
+ * MapViewModel.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/Rect.h"
+
+class MapViewModel
+{
+	Point tileSize;
+	Point viewCenter;
+	Point viewDimensions;
+
+	int mapLevel = 0;
+
+public:
+	void setTileSize(const Point & newValue);
+	void setViewCenter(const Point & newValue);
+	void setViewDimensions(const Point & newValue);
+	void setLevel(int newLevel);
+
+	/// returns current size of map tile in pixels
+	Point getSingleTileSize() const;
+
+	/// returns center point of map view, in Map coordinates
+	Point getMapViewCenter() const;
+
+	/// returns total number of visible tiles
+	Point getTilesVisibleDimensions() const;
+
+	/// returns rect encompassing all visible tiles
+	Rect getTilesTotalRect() const;
+
+	/// returns required area in pixels of cache canvas
+	Point getCacheDimensionsPixels() const;
+
+	/// returns actual player-visible area
+	Point getPixelsVisibleDimensions() const;
+
+	/// returns area covered by specified tile in map cache
+	Rect getCacheTileArea(const int3 & coordinates) const;
+
+	/// returns area covered by specified tile in target view
+	Rect getTargetTileArea(const int3 & coordinates) const;
+
+	/// returns tile under specified position in target view
+	int3 getTileAtPoint(const Point & position) const;
+
+	/// returns currently visible map level
+	int getLevel() const;
+};

+ 1 - 1
client/mapRenderer/mapHandler.cpp

@@ -10,7 +10,7 @@
 
 #include "StdInc.h"
 #include "mapHandler.h"
-#include "MapRendererContext.h"
+#include "IMapRendererObserver.h"
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"

+ 0 - 27
client/mapRenderer/mapHandler.h

@@ -41,33 +41,6 @@ class IImage;
 class CMapHandler;
 class IMapObjectObserver;
 
-// 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
-};
-
 class CMapHandler
 {
 	const CMap * map;

+ 100 - 0
client/windows/CPuzzleWindow.cpp

@@ -0,0 +1,100 @@
+/*
+ * CPuzzleWindow.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 "CPuzzleWindow.h"
+
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CResDataBar.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/TextAlignment.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/Images.h"
+#include "../widgets/TextControls.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CTownHandler.h"
+#include "../../lib/StartInfo.h"
+
+CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
+	: CWindowObject(PLAYER_COLORED | BORDERED, "PUZZLE"),
+	grailPos(GrailPos),
+	currentAlpha(SDL_ALPHA_OPAQUE)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	CCS->soundh->playSound(soundBase::OBELISK);
+
+	quitb = std::make_shared<CButton>(Point(670, 538), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->allTexts[599]), std::bind(&CPuzzleWindow::close, this), SDLK_RETURN);
+	quitb->assignedKeys.insert(SDLK_ESCAPE);
+	quitb->setBorderColor(Colors::METALLIC_GOLD);
+
+	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);
+
+	int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle;
+
+	auto & puzzleMap = (*CGI->townh)[faction]->puzzleMap;
+
+	for(auto & elem : puzzleMap)
+	{
+		const SPuzzleInfo & info = elem;
+
+		auto piece = std::make_shared<CPicture>(info.filename, info.x, info.y);
+
+		//piece that will slowly disappear
+		if(info.whenUncovered <= GameConstants::PUZZLE_MAP_PIECES * discoveredRatio)
+		{
+			piecesToRemove.push_back(piece);
+			piece->needRefresh = true;
+			piece->recActions = piece->recActions & ~SHOWALL;
+		}
+		else
+		{
+			visiblePieces.push_back(piece);
+		}
+	}
+}
+
+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);
+}
+
+void CPuzzleWindow::show(SDL_Surface * to)
+{
+	static int animSpeed = 2;
+
+	if(currentAlpha < animSpeed)
+	{
+		piecesToRemove.clear();
+	}
+	else
+	{
+		//update disappearing puzzles
+		for(auto & piece : piecesToRemove)
+			piece->setAlpha(currentAlpha);
+		currentAlpha -= animSpeed;
+	}
+	CWindowObject::show(to);
+}

+ 38 - 0
client/windows/CPuzzleWindow.h

@@ -0,0 +1,38 @@
+/*
+ * CPuzzleWindow.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 "CWindowObject.h"
+#include "../../lib/int3.h"
+
+class CLabel;
+class CButton;
+class CResDataBar;
+
+/// Puzzle screen which gets uncovered when you visit obilisks
+class CPuzzleWindow : public CWindowObject
+{
+private:
+	int3 grailPos;
+	std::shared_ptr<CPicture> logo;
+	std::shared_ptr<CLabel> title;
+	std::shared_ptr<CButton> quitb;
+	std::shared_ptr<CResDataBar> resDataBar;
+
+	std::vector<std::shared_ptr<CPicture>> piecesToRemove;
+	std::vector<std::shared_ptr<CPicture>> visiblePieces;
+	ui8 currentAlpha;
+
+public:
+	void showAll(SDL_Surface * to) override;
+	void show(SDL_Surface * to) override;
+
+	CPuzzleWindow(const int3 & grailPos, double discoveredRatio);
+};

+ 1 - 75
client/windows/GUIClasses.cpp

@@ -21,7 +21,7 @@
 #include "../CVideoHandler.h"
 #include "../CServerHandler.h"
 
-#include "../adventureMap/CResDataBar.h"
+//#include "../adventureMap/CResDataBar.h"
 #include "../battle/BattleInterfaceClasses.h"
 #include "../battle/BattleInterface.h"
 
@@ -1114,80 +1114,6 @@ CShipyardWindow::CShipyardWindow(const std::vector<si32> & cost, int state, int
 	costLabel = std::make_shared<CLabel>(164, 220, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->jktexts[14]);
 }
 
-CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
-	: CWindowObject(PLAYER_COLORED | BORDERED, "PUZZLE"),
-	grailPos(GrailPos),
-	currentAlpha(SDL_ALPHA_OPAQUE)
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-
-	CCS->soundh->playSound(soundBase::OBELISK);
-
-	quitb = std::make_shared<CButton>(Point(670, 538), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->allTexts[599]), std::bind(&CPuzzleWindow::close, this), SDLK_RETURN);
-	quitb->assignedKeys.insert(SDLK_ESCAPE);
-	quitb->setBorderColor(Colors::METALLIC_GOLD);
-
-	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);
-
-	int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle;
-
-	auto & puzzleMap = (*CGI->townh)[faction]->puzzleMap;
-
-	for(auto & elem : puzzleMap)
-	{
-		const SPuzzleInfo & info = elem;
-
-		auto piece = std::make_shared<CPicture>(info.filename, info.x, info.y);
-
-		//piece that will slowly disappear
-		if(info.whenUncovered <= GameConstants::PUZZLE_MAP_PIECES * discoveredRatio)
-		{
-			piecesToRemove.push_back(piece);
-			piece->needRefresh = true;
-			piece->recActions = piece->recActions & ~SHOWALL;
-		}
-		else
-		{
-			visiblePieces.push_back(piece);
-		}
-	}
-}
-
-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);
-}
-
-void CPuzzleWindow::show(SDL_Surface * to)
-{
-	static int animSpeed = 2;
-
-	if(currentAlpha < animSpeed)
-	{
-		piecesToRemove.clear();
-	}
-	else
-	{
-		//update disappearing puzzles
-		for(auto & piece : piecesToRemove)
-			piece->setAlpha(currentAlpha);
-		currentAlpha -= animSpeed;
-	}
-	CWindowObject::show(to);
-}
-
 void CTransformerWindow::CItem::move()
 {
 	if(left)

+ 0 - 21
client/windows/GUIClasses.h

@@ -362,27 +362,6 @@ public:
 	CShipyardWindow(const std::vector<si32> & cost, int state, int boatType, const std::function<void()> & onBuy);
 };
 
-/// Puzzle screen which gets uncovered when you visit obilisks
-class CPuzzleWindow : public CWindowObject
-{
-private:
-	int3 grailPos;
-	std::shared_ptr<CPicture> logo;
-	std::shared_ptr<CLabel> title;
-	std::shared_ptr<CButton> quitb;
-	std::shared_ptr<CResDataBar> resDataBar;
-
-	std::vector<std::shared_ptr<CPicture>> piecesToRemove;
-	std::vector<std::shared_ptr<CPicture>> visiblePieces;
-	ui8 currentAlpha;
-
-public:
-	void showAll(SDL_Surface * to) override;
-	void show(SDL_Surface * to) override;
-
-	CPuzzleWindow(const int3 & grailPos, double discoveredRatio);
-};
-
 /// Creature transformer window
 class CTransformerWindow : public CStatusbarWindow, public CGarrisonHolder
 {