Kaynağa Gözat

Merge pull request #1581 from IvanSavenko/map_render_rewrite

Adventure Map - rendering rewrite
Ivan Savenko 2 yıl önce
ebeveyn
işleme
7ef5163d9d
96 değiştirilmiş dosya ile 4907 ekleme ve 3935 silme
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIGateway.h
  3. 1 1
      AI/VCAI/VCAI.cpp
  4. 1 1
      AI/VCAI/VCAI.h
  5. 20 7
      Global.h
  6. BIN
      Mods/vcmi/Data/debug/blocked.png
  7. BIN
      Mods/vcmi/Data/debug/cached.png
  8. BIN
      Mods/vcmi/Data/debug/grid.png
  9. BIN
      Mods/vcmi/Data/debug/visitable.png
  10. 0 8
      client/CMT.cpp
  11. 26 6
      client/CMakeLists.txt
  12. 8 9
      client/CMusicHandler.cpp
  13. 0 2
      client/CMusicHandler.h
  14. 124 416
      client/CPlayerInterface.cpp
  15. 30 19
      client/CPlayerInterface.h
  16. 2 5
      client/Client.cpp
  17. 1 1
      client/Client.h
  18. 46 45
      client/NetPacksClient.cpp
  19. 147 288
      client/adventureMap/CAdvMapInt.cpp
  20. 29 39
      client/adventureMap/CAdvMapInt.h
  21. 1 1
      client/adventureMap/CAdventureOptions.cpp
  22. 4 3
      client/adventureMap/CInGameConsole.cpp
  23. 15 8
      client/adventureMap/CMinimap.cpp
  24. 2 2
      client/adventureMap/CMinimap.h
  25. 0 415
      client/adventureMap/CTerrainRect.cpp
  26. 0 71
      client/adventureMap/CTerrainRect.h
  27. 246 0
      client/adventureMap/MapAudioPlayer.cpp
  28. 71 0
      client/adventureMap/MapAudioPlayer.h
  29. 0 1486
      client/adventureMap/mapHandler.cpp
  30. 0 411
      client/adventureMap/mapHandler.h
  31. 28 18
      client/battle/BattleFieldController.cpp
  32. 0 3
      client/battle/BattleFieldController.h
  33. 3 7
      client/battle/BattleInterface.cpp
  34. 4 4
      client/battle/BattleStacksController.cpp
  35. 0 6
      client/gui/CGuiHandler.cpp
  36. 0 1
      client/gui/CGuiHandler.h
  37. 86 0
      client/mapView/IMapRendererContext.h
  38. 52 0
      client/mapView/IMapRendererObserver.h
  39. 792 0
      client/mapView/MapRenderer.cpp
  40. 171 0
      client/mapView/MapRenderer.h
  41. 496 0
      client/mapView/MapRendererContext.cpp
  42. 160 0
      client/mapView/MapRendererContext.h
  43. 93 0
      client/mapView/MapRendererContextState.cpp
  44. 62 0
      client/mapView/MapRendererContextState.h
  45. 161 0
      client/mapView/MapView.cpp
  46. 90 0
      client/mapView/MapView.h
  47. 172 0
      client/mapView/MapViewActions.cpp
  48. 49 0
      client/mapView/MapViewActions.h
  49. 191 0
      client/mapView/MapViewCache.cpp
  50. 77 0
      client/mapView/MapViewCache.h
  51. 478 0
      client/mapView/MapViewController.cpp
  52. 96 0
      client/mapView/MapViewController.h
  53. 117 0
      client/mapView/MapViewModel.cpp
  54. 57 0
      client/mapView/MapViewModel.h
  55. 227 0
      client/mapView/mapHandler.cpp
  56. 76 0
      client/mapView/mapHandler.h
  57. 2 2
      client/render/CAnimation.cpp
  58. 0 101
      client/render/CFadeAnimation.cpp
  59. 0 53
      client/render/CFadeAnimation.h
  60. 50 31
      client/render/Canvas.cpp
  61. 16 10
      client/render/Canvas.h
  62. 0 164
      client/render/Graphics.cpp
  63. 0 34
      client/render/Graphics.h
  64. 16 1
      client/render/IImage.h
  65. 37 18
      client/renderSDL/SDLImage.cpp
  66. 7 4
      client/renderSDL/SDLImage.h
  67. 18 61
      client/renderSDL/SDL_Extensions.cpp
  68. 3 3
      client/renderSDL/SDL_Extensions.h
  69. 9 7
      client/windows/CCastleInterface.cpp
  70. 1 1
      client/windows/CMessage.cpp
  71. 93 0
      client/windows/CPuzzleWindow.cpp
  72. 40 0
      client/windows/CPuzzleWindow.h
  73. 2 2
      client/windows/CQuestLog.cpp
  74. 1 76
      client/windows/GUIClasses.cpp
  75. 0 21
      client/windows/GUIClasses.h
  76. 0 1
      client/windows/InfoWindows.cpp
  77. 7 7
      client/windows/settings/AdventureOptionsTab.cpp
  78. 0 4
      config/ambientSounds.json
  79. 20 7
      config/schemas/settings.json
  80. 1 1
      config/terrains.json
  81. 12 12
      config/widgets/settings/adventureOptionsTab.json
  82. 13 0
      lib/CGameInfoCallback.cpp
  83. 1 0
      lib/CGameInfoCallback.h
  84. 1 1
      lib/CGameInterface.h
  85. 1 1
      lib/IGameCallback.h
  86. 2 5
      lib/NetPacks.h
  87. 11 4
      lib/Point.h
  88. 7 0
      lib/Rect.cpp
  89. 5 0
      lib/Rect.h
  90. 1 2
      lib/mapObjects/CGHeroInstance.cpp
  91. 0 1
      lib/mapObjects/CGHeroInstance.h
  92. 10 1
      lib/spells/AdventureSpellMechanics.cpp
  93. 3 0
      lib/spells/AdventureSpellMechanics.h
  94. 1 2
      mapeditor/maphandler.cpp
  95. 1 11
      server/CGameHandler.cpp
  96. 1 1
      server/CGameHandler.h

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -489,7 +489,7 @@ void AIGateway::showMarketWindow(const IMarket * market, const CGHeroInstance *
 	NET_EVENT_HANDLER;
 }
 
-void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
+void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
 {
 	//TODO: AI support for ViewXXX spell
 	LOG_TRACE(logAi);

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -165,7 +165,7 @@ public:
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
-	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -572,7 +572,7 @@ void VCAI::showMarketWindow(const IMarket * market, const CGHeroInstance * visit
 	NET_EVENT_HANDLER;
 }
 
-void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
+void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
 {
 	//TODO: AI support for ViewXXX spell
 	LOG_TRACE(logAi);

+ 1 - 1
AI/VCAI/VCAI.h

@@ -198,7 +198,7 @@ public:
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
-	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
 	void battleEnd(const BattleResult * br) override;

+ 20 - 7
Global.h

@@ -116,33 +116,32 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 
 #define _USE_MATH_DEFINES
 
-#include <cstdio>
-#include <stdio.h>
-
 #include <algorithm>
 #include <array>
+#include <atomic>
+#include <bitset>
 #include <cassert>
 #include <climits>
 #include <cmath>
 #include <cstdlib>
-#include <functional>
+#include <cstdio>
 #include <fstream>
+#include <functional>
 #include <iomanip>
 #include <iostream>
 #include <map>
 #include <memory>
+#include <mutex>
 #include <numeric>
 #include <queue>
 #include <random>
 #include <set>
 #include <sstream>
 #include <string>
-#include <unordered_set>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
-#include <atomic>
-#include <mutex>
 
 //The only available version is 3, as of Boost 1.50
 #include <boost/version.hpp>
@@ -442,6 +441,20 @@ namespace vstd
 		}
 	}
 
+	// c++17: makes a to fit the range <b, c>
+	template <typename t1, typename t2, typename t3>
+	t1 clamp(const t1 &value, const t2 &low, const t3 &high)
+	{
+		if ( value > high)
+			return high;
+
+		if ( value < low)
+			return low;
+
+		return value;
+	}
+
+
 	//makes a to fit the range <b, c>
 	template <typename t1, typename t2, typename t3>
 	t1 &abetween(t1 &a, const t2 &b, const t3 &c)

BIN
Mods/vcmi/Data/debug/blocked.png


BIN
Mods/vcmi/Data/debug/cached.png


BIN
Mods/vcmi/Data/debug/grid.png


BIN
Mods/vcmi/Data/debug/visitable.png


+ 0 - 8
client/CMT.cpp

@@ -480,10 +480,6 @@ int main(int argc, char * argv[])
 
 		CCS->curh = new CursorHandler();
 		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
-		pomtime.getDiff();
-
-		graphics->load();//must be after Content loading but should be in main thread
-		logGlobal->info("Main graphics: %d ms", pomtime.getDiff());
 
 		CMessage::init();
 		logGlobal->info("Message handler: %d ms", pomtime.getDiff());
@@ -1004,10 +1000,6 @@ static void handleEvent(SDL_Event & ev)
 		case EUserEvent::FULLSCREEN_TOGGLED:
 			fullScreenChanged();
 			break;
-		case EUserEvent::INTERFACE_CHANGED:
-			if(LOCPLINT)
-				LOCPLINT->updateAmbientSounds();
-			break;
 		default:
 			logGlobal->error("Unknown user event. Code %d", ev.user.code);
 			break;

+ 26 - 6
client/CMakeLists.txt

@@ -10,8 +10,7 @@ set(client_SRCS
 	adventureMap/CList.cpp
 	adventureMap/CMinimap.cpp
 	adventureMap/CResDataBar.cpp
-	adventureMap/CTerrainRect.cpp
-	adventureMap/mapHandler.cpp
+	adventureMap/MapAudioPlayer.cpp
 
 	battle/BattleActionsController.cpp
 	battle/BattleAnimationClasses.cpp
@@ -48,10 +47,19 @@ set(client_SRCS
 	mainmenu/CPrologEpilogVideo.cpp
 	mainmenu/CreditsScreen.cpp
 
+	mapView/MapRenderer.cpp
+	mapView/MapRendererContext.cpp
+	mapView/MapRendererContextState.cpp
+	mapView/MapView.cpp
+	mapView/MapViewActions.cpp
+	mapView/MapViewCache.cpp
+	mapView/MapViewController.cpp
+	mapView/MapViewModel.cpp
+	mapView/mapHandler.cpp
+
 	render/CAnimation.cpp
 	render/CBitmapHandler.cpp
 	render/CDefFile.cpp
-	render/CFadeAnimation.cpp
 	render/Canvas.cpp
 	render/ColorFilter.cpp
 	render/Colors.cpp
@@ -83,6 +91,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
@@ -120,8 +129,7 @@ set(client_HEADERS
 	adventureMap/CList.h
 	adventureMap/CMinimap.h
 	adventureMap/CResDataBar.h
-	adventureMap/CTerrainRect.h
-	adventureMap/mapHandler.h
+	adventureMap/MapAudioPlayer.h
 
 	battle/BattleActionsController.h
 	battle/BattleAnimationClasses.h
@@ -161,10 +169,21 @@ set(client_HEADERS
 	mainmenu/CPrologEpilogVideo.h
 	mainmenu/CreditsScreen.h
 
+	mapView/IMapRendererContext.h
+	mapView/IMapRendererObserver.h
+	mapView/MapRenderer.h
+	mapView/MapRendererContext.h
+	mapView/MapRendererContextState.h
+	mapView/MapView.h
+	mapView/MapViewActions.h
+	mapView/MapViewCache.h
+	mapView/MapViewController.h
+	mapView/MapViewModel.h
+	mapView/mapHandler.h
+
 	render/CAnimation.h
 	render/CBitmapHandler.h
 	render/CDefFile.h
-	render/CFadeAnimation.h
 	render/Canvas.h
 	render/ColorFilter.h
 	render/Colors.h
@@ -200,6 +219,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

+ 8 - 9
client/CMusicHandler.cpp

@@ -75,7 +75,6 @@ CSoundHandler::CSoundHandler():
 	listener(settings.listen["general"]["sound"]),
 	ambientConfig(JsonNode(ResourceID("config/ambientSounds.json")))
 {
-	allTilesSource = ambientConfig["allTilesSource"].Bool();
 	listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
 
 	// Vectors for helper(s)
@@ -259,11 +258,6 @@ int CSoundHandler::ambientGetRange() const
 	return static_cast<int>(ambientConfig["range"].Integer());
 }
 
-bool CSoundHandler::ambientCheckVisitable() const
-{
-	return !allTilesSource;
-}
-
 void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
 {
 	boost::mutex::scoped_lock guard(mutex);
@@ -278,7 +272,8 @@ void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
 		}
 		else
 		{
-			CCS->soundh->setChannelVolume(pair.second, ambientDistToVolume(soundsArg[pair.first]));
+			int volume = ambientDistToVolume(soundsArg[pair.first]);
+			CCS->soundh->setChannelVolume(pair.second, volume);
 		}
 	}
 	for(auto soundId : stoppedSounds)
@@ -289,7 +284,9 @@ void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
 		if(!vstd::contains(ambientChannels, pair.first))
 		{
 			int channel = CCS->soundh->playSound(pair.first, -1);
-			CCS->soundh->setChannelVolume(channel, ambientDistToVolume(pair.second));
+			int volume = ambientDistToVolume(pair.second);
+
+			CCS->soundh->setChannelVolume(channel, volume);
 			CCS->soundh->ambientChannels.insert(std::make_pair(pair.first, channel));
 		}
 	}
@@ -489,7 +486,9 @@ void CMusicHandler::musicFinishedCallback()
 			return;
 		}
 		else
+		{
 			current.reset();
+		}
 	}
 
 	if (current.get() == nullptr && next.get() != nullptr)
@@ -515,7 +514,7 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu
 }
 MusicEntry::~MusicEntry()
 {
-	if (playing)
+	if (playing && loop > 0)
 	{
 		assert(0);
 		logGlobal->error("Attempt to delete music while playing!");

+ 0 - 2
client/CMusicHandler.h

@@ -53,7 +53,6 @@ private:
 	void ambientStopSound(std::string soundId);
 
 	const JsonNode ambientConfig;
-	bool allTilesSource;
 	std::map<std::string, int> ambientChannels;
 
 public:
@@ -75,7 +74,6 @@ public:
 	void soundFinishedCallback(int channel);
 
 	int ambientGetRange() const;
-	bool ambientCheckVisitable() const;
 	void ambientUpdateChannels(std::map<std::string, int> currentSounds);
 	void ambientStopAllChannels();
 

+ 124 - 416
client/CPlayerInterface.cpp

@@ -12,9 +12,8 @@
 #include <vcmi/Artifact.h>
 
 #include "adventureMap/CAdvMapInt.h"
-#include "adventureMap/mapHandler.h"
+#include "mapView/mapHandler.h"
 #include "adventureMap/CList.h"
-#include "adventureMap/CTerrainRect.h"
 #include "adventureMap/CInfoBar.h"
 #include "adventureMap/CMinimap.h"
 #include "battle/BattleInterface.h"
@@ -31,6 +30,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"
@@ -103,11 +103,6 @@ std::shared_ptr<BattleInterface> CPlayerInterface::battleInt;
 enum  EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
 CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
 
-static bool objectBlitOrderSorter(const TerrainTileObject  & a, const TerrainTileObject & b)
-{
-	return CMapHandler::compareObjectBlitOrder(a.obj, b.obj);
-}
-
 struct HeroObjectRetriever : boost::static_visitor<const CGHeroInstance *>
 {
 	const CGHeroInstance * operator()(const ConstTransitivePtr<CGHeroInstance> &h) const
@@ -120,7 +115,97 @@ struct HeroObjectRetriever : boost::static_visitor<const CGHeroInstance *>
 	}
 };
 
-CPlayerInterface::CPlayerInterface(PlayerColor Player)
+HeroPathStorage::HeroPathStorage(CPlayerInterface & owner):
+	owner(owner)
+{
+}
+
+void HeroPathStorage::setPath(const CGHeroInstance *h, const CGPath & path)
+{
+	paths[h] = path;
+}
+
+const CGPath & HeroPathStorage::getPath(const CGHeroInstance *h) const
+{
+	assert(hasPath(h));
+	return paths.at(h);
+}
+
+bool HeroPathStorage::hasPath(const CGHeroInstance *h) const
+{
+	return paths.count(h) > 0;
+}
+
+bool HeroPathStorage::setPath(const CGHeroInstance *h, const int3 & destination)
+{
+	CGPath path;
+	if (!owner.cb->getPathsInfo(h)->getPath(path, destination))
+		return false;
+
+	setPath(h, path);
+	return true;
+}
+
+void HeroPathStorage::removeLastNode(const CGHeroInstance *h)
+{
+	assert(hasPath(h));
+	if (!hasPath(h))
+		return;
+
+	auto & path = paths[h];
+	path.nodes.pop_back();
+	if (path.nodes.size() < 2)  //if it was the last one, remove entire path and path with only one tile is not a real path
+		erasePath(h);
+}
+
+void HeroPathStorage::erasePath(const CGHeroInstance *h)
+{
+	paths.erase(h);
+	adventureInt->updateMoveHero(h, false);
+
+}
+
+void HeroPathStorage::verifyPath(const CGHeroInstance *h)
+{
+	if (!hasPath(h))
+		return;
+	setPath(h, getPath(h).endPos());
+}
+
+template<typename Handler>
+void HeroPathStorage::serialize(Handler & h, int version)
+{
+	std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
+	if (h.saving)
+	{
+		for (auto &p : paths)
+		{
+			if (p.second.nodes.size())
+				pathsMap[p.first] = p.second.endPos();
+			else
+				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
+		}
+		h & pathsMap;
+	}
+	else
+	{
+		h & pathsMap;
+
+		if (owner.cb)
+		{
+			for (auto &p : pathsMap)
+			{
+				CGPath path;
+				owner.cb->getPathsInfo(p.first)->getPath(path, p.second);
+				paths[p.first] = path;
+				logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
+			}
+		}
+	}
+}
+
+CPlayerInterface::CPlayerInterface(PlayerColor Player):
+	paths(*this)
 {
 	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
 	destinationTeleport = ObjectInstanceID();
@@ -130,7 +215,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 	curAction = nullptr;
 	playerID=Player;
 	human=true;
-	currentSelection = nullptr;
 	battleInt = nullptr;
 	castleInt = nullptr;
 	makingTurn = false;
@@ -148,9 +232,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 
 CPlayerInterface::~CPlayerInterface()
 {
-	if(CCS && CCS->soundh)
-		CCS->soundh->ambientStopAllChannels();
-
 	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
 	delete showingDialog;
 	delete cingconsole;
@@ -239,142 +320,70 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 		return;
 
 	const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero
-	int3 hp = details.start;
 
-	if(!hero)
-	{
-		//AI hero left the visible area (we can't obtain info)
-		//TODO very evil workaround -> retrieve pointer to hero so we could animate it
-		// TODO -> we should not need full CGHeroInstance structure to display animation or it should not be handled by playerint (but by the client itself)
-		const TerrainTile2 & tile = CGI->mh->ttiles[hp.z][hp.x - 1][hp.y];
-		for(auto & elem : tile.objects)
-			if(elem.obj && elem.obj->id == details.id)
-				hero = dynamic_cast<const CGHeroInstance *>(elem.obj);
+	if (!hero)
+		return;
 
-		if(!hero) //still nothing...
-			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));
 
-	bool directlyAttackingCreature =
-		details.attackedFrom
-		&& adventureInt->terrain->currentPath					//in case if movement has been canceled in the meantime and path was already erased
-		&& adventureInt->terrain->currentPath->nodes.size() == 3;//FIXME should be 2 but works nevertheless...
+	bool directlyAttackingCreature = details.attackedFrom && paths.hasPath(hero) && paths.getPath(hero).endPos() == *details.attackedFrom;
 
 	if(makingTurn && hero->tempOwner == playerID) //we are moving our hero - we may need to update assigned path
 	{
-		updateAmbientSounds();
-		//We may need to change music - select new track, music handler will change it if needed
-		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getJsonKey(), true, false);
-
 		if(details.result == TryMoveHero::TELEPORTATION)
 		{
-			if(adventureInt->terrain->currentPath)
+			if(paths.hasPath(hero))
 			{
-				assert(adventureInt->terrain->currentPath->nodes.size() >= 2);
-				std::vector<CGPathNode>::const_iterator nodesIt = adventureInt->terrain->currentPath->nodes.end() - 1;
+				assert(paths.getPath(hero).nodes.size() >= 2);
+				auto nodesIt = paths.getPath(hero).nodes.end() - 1;
 
 				if((nodesIt)->coord == hero->convertToVisitablePos(details.start)
 					&& (nodesIt - 1)->coord == hero->convertToVisitablePos(details.end))
 				{
 					//path was between entrance and exit of teleport -> OK, erase node as usual
-					removeLastNodeFromPath(hero);
+					paths.removeLastNode(hero);
 				}
 				else
 				{
 					//teleport was not along current path, it'll now be invalid (hero is somewhere else)
-					eraseCurrentPathOf(hero);
+					paths.erasePath(hero);
 
 				}
 			}
-			adventureInt->centerOn(hero, true); //actualizing screen pos
-			adventureInt->minimap->redraw();
-			adventureInt->heroList->update(hero);
-			return;	//teleport - no fancy moving animation
-					//TODO: smooth disappear / appear effect
 		}
 
 		if(hero->pos != details.end //hero didn't change tile but visit succeeded
 			|| directlyAttackingCreature) // or creature was attacked from endangering tile.
 		{
-			eraseCurrentPathOf(hero, false);
+			paths.erasePath(hero);
 		}
-		else if(adventureInt->terrain->currentPath && hero->pos == details.end) //&& hero is moving
+		else if(paths.hasPath(hero) && hero->pos == details.end) //&& hero is moving
 		{
 			if(details.start != details.end) //so we don't touch path when revisiting with spacebar
-				removeLastNodeFromPath(hero);
+				paths.removeLastNode(hero);
 		}
 	}
 
 	if(details.stopMovement()) //hero failed to move
 	{
-		hero->isStanding = true;
 		stillMoveHero.setn(STOP_MOVE);
 		GH.totalRedraw();
 		adventureInt->heroList->update(hero);
 		return;
 	}
 
-	ui32 speed = 0;
-	if(settings["session"]["spectate"].Bool())
-	{
-		if(!settings["session"]["spectate-hero-speed"].isNull())
-			speed = static_cast<ui32>(settings["session"]["spectate-hero-speed"].Integer());
-	}
-	else if(makingTurn) // our turn, our hero moves
-		speed = static_cast<ui32>(settings["adventure"]["heroSpeed"].Float());
-	else
-		speed = static_cast<ui32>(settings["adventure"]["enemySpeed"].Float());
-
-	if(speed == 0)
-	{
-		//FIXME: is this a proper solution?
-		CGI->mh->hideObject(hero);
-		CGI->mh->printObject(hero);
-		return; // no animation
-	}
-
-	adventureInt->centerOn(hero); //actualizing screen pos
-	adventureInt->minimap->redraw();
 	adventureInt->heroList->redraw();
 
-	initMovement(details, hero, hp);
-
-	auto waitFrame = [&]()
-	{
-		int frameNumber = GH.mainFPSmng->getFrameNumber();
-
-		auto unlockPim = vstd::makeUnlockGuard(*pim);
-		while(frameNumber == GH.mainFPSmng->getFrameNumber())
-			boost::this_thread::sleep(boost::posix_time::milliseconds(5));
-	};
-
-	//first initializing done
-
-	//main moving
-	for(int i = 1; i < 32; i += 2 * speed)
-	{
-		movementPxStep(details, i, hp, hero);
-#ifndef VCMI_ANDROID
-		// currently android doesn't seem to be able to handle all these full redraws here, so let's disable it so at least it looks less choppy;
-		// most likely this is connected with the way that this manual animation+framerate handling is solved
-		adventureInt->requestRedrawMapOnNextFrame();
-#endif
-
-		//evil returns here ...
-		//todo: get rid of it
-		waitFrame(); //for animation purposes
-	}
-	//main moving done
-
-	//finishing move
-	finishMovement(details, hp, hero);
-	hero->isStanding = true;
+	CGI->mh->waitForOngoingAnimations();
 
 	//move finished
-	adventureInt->minimap->redraw();
 	adventureInt->heroList->update(hero);
 
 	//check if user cancelled movement
@@ -413,6 +422,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 		const_cast<CGHeroInstance *>(hero)->moveDir = dirLookup[posOffset.y][posOffset.x];
 	}
 }
+
 void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -444,8 +454,7 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 	else if (adventureInt->selection == hero)
 		adventureInt->selection = nullptr;
 
-	if (vstd::contains(paths, hero))
-		paths.erase(hero);
+	paths.erasePath(hero);
 }
 
 void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
@@ -475,19 +484,6 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
 	GH.pushInt(newCastleInt);
 }
 
-int3 CPlayerInterface::repairScreenPos(int3 pos)
-{
-	if (pos.x<-CGI->mh->frameW)
-		pos.x = -CGI->mh->frameW;
-	if (pos.y<-CGI->mh->frameH)
-		pos.y = -CGI->mh->frameH;
-	if (pos.x>CGI->mh->sizes.x - adventureInt->terrain->tilesw + CGI->mh->frameW)
-		pos.x = CGI->mh->sizes.x - adventureInt->terrain->tilesw + CGI->mh->frameW;
-	if (pos.y>CGI->mh->sizes.y - adventureInt->terrain->tilesh + CGI->mh->frameH)
-		pos.y = CGI->mh->sizes.y - adventureInt->terrain->tilesh + CGI->mh->frameH;
-	return pos;
-}
-
 void CPlayerInterface::activateForSpectator()
 {
 	adventureInt->state = CAdvMapInt::INGAME;
@@ -568,14 +564,12 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
 
 	if (town->garrisonHero) //wandering hero moved to the garrison
 	{
-		CGI->mh->hideObject(town->garrisonHero);
 		if (town->garrisonHero->tempOwner == playerID && vstd::contains(wanderingHeroes,town->garrisonHero)) // our hero
 			wanderingHeroes -= town->garrisonHero;
 	}
 
 	if (town->visitingHero) //hero leaves garrison
 	{
-		CGI->mh->printObject(town->visitingHero);
 		if (town->visitingHero->tempOwner == playerID && !vstd::contains(wanderingHeroes,town->visitingHero)) // our hero
 			wanderingHeroes.push_back(town->visitingHero);
 	}
@@ -1260,7 +1254,7 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
 	if ((bonus.type == Bonus::FLYING_MOVEMENT || bonus.type == Bonus::WATER_WALKING) && !gain)
 	{
 		//recalculate paths because hero has lost bonus influencing pathfinding
-		eraseCurrentPathOf(hero, false);
+		paths.erasePath(hero);
 	}
 }
 
@@ -1269,33 +1263,7 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 	h & wanderingHeroes;
 	h & towns;
 	h & sleepingHeroes;
-
-	std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
-	if (h.saving)
-	{
-		for (auto &p : paths)
-		{
-			if (p.second.nodes.size())
-				pathsMap[p.first] = p.second.endPos();
-			else
-				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
-		}
-		h & pathsMap;
-	}
-	else
-	{
-		h & pathsMap;
-
-		if (cb)
-			for (auto &p : pathsMap)
-			{
-				CGPath path;
-				cb->getPathsInfo(p.first)->getPath(path, p.second);
-				paths[p.first] = path;
-				logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
-			}
-	}
-
+	h & paths;
 	h & spellbookSettings;
 }
 
@@ -1312,7 +1280,7 @@ void CPlayerInterface::loadGame( BinaryDeserializer & h, const int version )
 	firstCall = -1;
 }
 
-void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
+void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
 {
 	LOG_TRACE(logGlobal);
 	if (!LOCPLINT->makingTurn)
@@ -1343,7 +1311,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	auto onEnd = [=](){ cb->selectionMade(0, queryID); };
 
-	if (stillMoveHero.get() == DURING_MOVE  && adventureInt->terrain->currentPath && adventureInt->terrain->currentPath->nodes.size() > 1) //to ignore calls on passing through garrisons
+	if (stillMoveHero.get() == DURING_MOVE  && paths.hasPath(down) && paths.getPath(down).nodes.size() > 1) //to ignore calls on passing through garrisons
 	{
 		onEnd();
 		return;
@@ -1507,7 +1475,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	waitWhileDialog();
 	CCS->curh->hide();
-	adventureInt->centerOn (pos);
+	adventureInt->centerOnTile(pos);
 	if (focusTime)
 	{
 		GH.totalRedraw();
@@ -1528,6 +1496,8 @@ void CPlayerInterface::objectRemoved(const CGObjectInstance * obj)
 		waitWhileDialog();
 		CCS->soundh->playSound(obj->getRemovalSound().get());
 	}
+	CGI->mh->waitForOngoingAnimations();
+
 	if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
 	{
 		const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
@@ -1564,17 +1534,6 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 	}
 }
 
-const CArmedInstance * CPlayerInterface::getSelection()
-{
-	return currentSelection;
-}
-
-void CPlayerInterface::setSelection(const CArmedInstance * obj)
-{
-	currentSelection = obj;
-	updateAmbientSounds(true);
-}
-
 void CPlayerInterface::update()
 {
 	// Make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
@@ -1632,150 +1591,6 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
 	return 0;
 }
 
-void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp )
-{
-	auto subArr = (CGI->mh->ttiles)[hp.z];
-
-	ho->isStanding = false;
-
-	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 ( auto const & 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;
@@ -1858,7 +1673,7 @@ void CPlayerInterface::showPuzzleMap()
 
 void CPlayerInterface::viewWorldMap()
 {
-	adventureInt->changeMode(EAdvMapMode::WORLD_VIEW, 0.36F);
+	adventureInt->openWorldView();
 }
 
 void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellID)
@@ -1869,70 +1684,14 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 		GH.popInts(1);
 
 	if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
-		eraseCurrentPathOf(caster, false);
+		paths.erasePath(caster);
 
 	const spells::Spell * spell = CGI->spells()->getByIndex(spellID);
-
-	if(spellID == SpellID::VIEW_EARTH)
-	{
-		//TODO: implement on server side
-		const auto level = caster->getSpellSchoolLevel(spell);
-		adventureInt->worldViewOptions.showAllTerrain = (level > 2);
-	}
-
 	auto castSoundPath = spell->getCastSound();
 	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 }
 
-void CPlayerInterface::eraseCurrentPathOf(const CGHeroInstance * ho, bool checkForExistanceOfPath)
-{
-	if (checkForExistanceOfPath)
-	{
-		assert(vstd::contains(paths, ho));
-	}
-	else if (!vstd::contains(paths, ho))
-	{
-		return;
-	}
-	assert(ho == adventureInt->selection);
-
-	paths.erase(ho);
-	adventureInt->terrain->currentPath = nullptr;
-	adventureInt->updateMoveHero(ho, false);
-}
-
-void CPlayerInterface::removeLastNodeFromPath(const CGHeroInstance *ho)
-{
-	adventureInt->terrain->currentPath->nodes.erase(adventureInt->terrain->currentPath->nodes.end()-1);
-	if (adventureInt->terrain->currentPath->nodes.size() < 2)  //if it was the last one, remove entire path and path with only one tile is not a real path
-		eraseCurrentPathOf(ho);
-}
-
-CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
-{
-	if (vstd::contains(paths,h)) //hero has assigned path
-	{
-		CGPath &path = paths[h];
-		if (!path.nodes.size())
-		{
-			logGlobal->warn("Warning: empty path found...");
-			paths.erase(h);
-		}
-		else
-		{
-			assert(h->visitablePos() == path.startPos());
-			//update the hero path in case of something has changed on map
-			if (LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos()))
-				return &path;
-			else
-				paths.erase(h);
-		}
-	}
-
-	return nullptr;
-}
-
 void CPlayerInterface::acceptTurn()
 {
 	if (settings["session"]["autoSkip"].Bool())
@@ -1986,11 +1745,8 @@ void CPlayerInterface::acceptTurn()
 void CPlayerInterface::tryDiggging(const CGHeroInstance * h)
 {
 	int msgToShow = -1;
-	const bool isBlocked = CGI->mh->hasObjectHole(h->visitablePos()); // Don't dig in the pit.
 
-	const auto diggingStatus = isBlocked
-		? EDiggingStatus::TILE_OCCUPIED
-		: h->diggingStatus().num;
+	const auto diggingStatus = h->diggingStatus();
 
 	switch(diggingStatus)
 	{
@@ -2102,7 +1858,6 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
 
 void CPlayerInterface::requestReturningToMainMenu(bool won)
 {
-	CCS->soundh->ambientStopAllChannels();
 	if(won && cb->getStartInfo()->campState)
 		CSH->startCampaignScenario(cb->getStartInfo()->campState);
 	else
@@ -2441,61 +2196,14 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		// (i == 0) means hero went through all the path
 		adventureInt->updateMoveHero(h, (i != 0));
 		adventureInt->updateNextHero(h);
-
-		// ugly workaround to force instant update of adventure map
-		adventureInt->animValHitCount = 8;
 	}
 
+	CGI->mh->waitForOngoingAnimations();
 	setMovementStatus(false);
 }
 
-void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions)
+void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	//TODO: showWorldViewEx
-
-	std::copy(objectPositions.begin(), objectPositions.end(), std::back_inserter(adventureInt->worldViewOptions.iconPositions));
-
-	viewWorldMap();
-}
-
-void CPlayerInterface::updateAmbientSounds(bool resetAll)
-{
-	if(castleInt || battleInt || !makingTurn || !currentSelection)
-	{
-		CCS->soundh->ambientStopAllChannels();
-		return;
-	}
-	else if(!dynamic_cast<CAdvMapInt *>(GH.topInt().get()))
-	{
-		return;
-	}
-	if(resetAll)
-		CCS->soundh->ambientStopAllChannels();
-
-	std::map<std::string, int> currentSounds;
-	auto updateSounds = [&](std::string soundId, int distance) -> void
-	{
-		if(vstd::contains(currentSounds, soundId))
-			currentSounds[soundId] = std::max(currentSounds[soundId], distance);
-		else
-			currentSounds.insert(std::make_pair(soundId, distance));
-	};
-
-	int3 pos = currentSelection->getSightCenter();
-	std::unordered_set<int3, ShashInt3> tiles;
-	cb->getVisibleTilesInRange(tiles, pos, CCS->soundh->ambientGetRange(), int3::DIST_CHEBYSHEV);
-	for(int3 tile : tiles)
-	{
-		int dist = pos.dist(tile, int3::DIST_CHEBYSHEV);
-		// We want sound for every special terrain on tile and not just one on top
-		for(auto & ttObj : CGI->mh->ttiles[tile.z][tile.x][tile.y].objects)
-		{
-			if(ttObj.ambientSound)
-				updateSounds(ttObj.ambientSound.get(), dist);
-		}
-		if(CGI->mh->map->isCoastalTile(tile))
-			updateSounds("LOOPOCEA", dist);
-	}
-	CCS->soundh->ambientUpdateChannels(currentSounds);
+	adventureInt->openWorldView(objectPositions, showTerrain );
 }

+ 30 - 19
client/CPlayerInterface.h

@@ -64,12 +64,37 @@ namespace boost
 	class recursive_mutex;
 }
 
+class CPlayerInterface;
+
+class HeroPathStorage
+{
+	CPlayerInterface & owner;
+
+	std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
+
+public:
+	explicit HeroPathStorage(CPlayerInterface &owner);
+
+	void setPath(const CGHeroInstance *h, const CGPath & path);
+	bool setPath(const CGHeroInstance *h, const int3 & destination);
+
+	const CGPath & getPath(const CGHeroInstance *h) const;
+	bool hasPath(const CGHeroInstance *h) const;
+
+	void removeLastNode(const CGHeroInstance *h);
+	void erasePath(const CGHeroInstance *h);
+	void verifyPath(const CGHeroInstance *h);
+
+	template <typename Handler>
+	void serialize( Handler &h, int version );
+};
+
 /// Central class for managing user interface logic
 class CPlayerInterface : public CGameInterface, public IUpdateable
 {
-	const CArmedInstance * currentSelection;
-
 public:
+	HeroPathStorage paths;
+
 	std::shared_ptr<Environment> env;
 	ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
 	int3 destinationTeleportPos;
@@ -94,16 +119,12 @@ public:
 
 	std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
 	std::vector<const CGTownInstance *> towns; //our towns on the adventure map
-	std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
 	std::vector<const CGHeroInstance *> sleepingHeroes; //if hero is in here, he's sleeping
 
 	//During battle is quick combat mode is used
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
 
-	const CArmedInstance * getSelection();
-	void setSelection(const CArmedInstance * obj);
-
 	struct SpellbookLastSetting
 	{
 		int spellbookLastPageBattle, spellbokLastPageAdvmap; //on which page we left spellbook
@@ -184,7 +205,7 @@ public:
 	void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
 	void saveGame(BinarySerializer & h, const int version) override; //saving
 	void loadGame(BinaryDeserializer & h, const int version) override; //loading
-	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 	//for battles
 	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero
@@ -220,7 +241,6 @@ public:
 	void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
 	void updateInfo(const CGObjectInstance * specific);
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
-	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
 	void activateForSpectator(); // TODO: spectator probably need own player interface class
 
 	// show dialogs
@@ -230,23 +250,14 @@ public:
 	void showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, const std::vector<std::shared_ptr<CComponent>> & components = std::vector<std::shared_ptr<CComponent>>());
 
 	void stopMovement();
-	void moveHero(const CGHeroInstance *h, CGPath path);
-	void initMovement(const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp );//initializing objects and performing first step of move
-	void movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho );//performing step of movement
-	void finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ); //finish movement
-	void eraseCurrentPathOf( const CGHeroInstance * ho, bool checkForExistanceOfPath = true );
-
-	void removeLastNodeFromPath(const CGHeroInstance *ho);
-	CGPath *getAndVerifyPath( const CGHeroInstance * h );
+	void moveHero(const CGHeroInstance *h, const CGPath& path);
+
 	void acceptTurn(); //used during hot seat after your turn message is close
 	void tryDiggging(const CGHeroInstance *h);
 	void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
 	void requestReturningToMainMenu(bool won);
 	void proposeLoadingGame();
 
-	// Ambient sounds
-	void updateAmbientSounds(bool resetAll = false);
-
 	///returns true if all events are processed internally
 	bool capturedAllEvents();
 

+ 2 - 5
client/Client.cpp

@@ -14,7 +14,7 @@
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../CCallback.h"
 #include "adventureMap/CAdvMapInt.h"
-#include "adventureMap/mapHandler.h"
+#include "mapView/mapHandler.h"
 #include "../lib/CConsoleHandler.h"
 #include "CGameInfo.h"
 #include "../lib/CGameState.h"
@@ -404,11 +404,8 @@ void CClient::initMapHandler()
 	// During loading CPlayerInterface from serialized state it's depend on MH
 	if(!settings["session"]["headless"].Bool())
 	{
-		const_cast<CGameInfo *>(CGI)->mh = new CMapHandler();
-		CGI->mh->map = gs->map;
+		const_cast<CGameInfo *>(CGI)->mh = new CMapHandler(gs->map);
 		logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
-		CGI->mh->init();
-		logNetwork->trace("Initializing mapHandler (together): %d ms", CSH->th->getDiff());
 	}
 
 	pathCache.clear();

+ 1 - 1
client/Client.h

@@ -229,7 +229,7 @@ public:
 	void setMovePoints(SetMovePoints * smp) override {};
 	void setManaPoints(ObjectInstanceID hid, int val) override {};
 	void giveHero(ObjectInstanceID id, PlayerColor player) override {};
-	void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags) override {};
+	void changeObjPos(ObjectInstanceID objid, int3 newPos) override {};
 	void sendAndApply(CPackForClient * pack) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 

+ 46 - 45
client/NetPacksClient.cpp

@@ -14,7 +14,7 @@
 #include "CPlayerInterface.h"
 #include "CGameInfo.h"
 #include "windows/GUIClasses.h"
-#include "adventureMap/mapHandler.h"
+#include "mapView/mapHandler.h"
 #include "adventureMap/CInGameConsole.h"
 #include "battle/BattleInterface.h"
 #include "gui/CGuiHandler.h"
@@ -342,15 +342,18 @@ void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
 void ApplyFirstClientNetPackVisitor::visitChangeObjPos(ChangeObjPos & pack)
 {
 	CGObjectInstance *obj = gs.getObjInstance(pack.objid);
-	if(pack.flags & 1 && CGI->mh)
-		CGI->mh->hideObject(obj);
+	if(CGI->mh)
+		CGI->mh->onObjectFadeOut(obj);
+
+	CGI->mh->waitForOngoingAnimations();
 }
 void ApplyClientNetPackVisitor::visitChangeObjPos(ChangeObjPos & pack)
 {
 	CGObjectInstance *obj = gs.getObjInstance(pack.objid);
-	if(pack.flags & 1 && CGI->mh)
-		CGI->mh->printObject(obj);
+	if(CGI->mh)
+		CGI->mh->onObjectFadeIn(obj);
 
+	CGI->mh->waitForOngoingAnimations();
 	cl.invalidatePaths();
 }
 
@@ -416,7 +419,7 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
 	const CGObjectInstance *o = cl.getObj(pack.id);
 
 	if(CGI->mh)
-		CGI->mh->hideObject(o, true);
+		CGI->mh->onObjectFadeOut(o);
 
 	//notify interfaces about removal
 	for(auto i=cl.playerint.begin(); i!=cl.playerint.end(); i++)
@@ -426,6 +429,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
 		if(gs.isVisible(o, i->first) || (!cl.getPlayerState(i->first)->human && o->ID == Obj::HERO && o->tempOwner != i->first))
 			i->second->objectRemoved(o);
 	}
+
+	CGI->mh->waitForOngoingAnimations();
 }
 
 void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
@@ -439,25 +444,22 @@ 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(pack.start - int3(1, 0, 0), i->first) || gs.isVisible(pack.end - int3(1, 0, 0), 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;
 		}
+		CGI->mh->waitForOngoingAnimations();
 	}
-
-	if(!CGI->mh)
-		return;
-
-	if(pack.result == TryMoveHero::TELEPORTATION  || pack.result == TryMoveHero::EMBARK  || pack.result == TryMoveHero::DISEMBARK  ||  !pack.humanKnows)
-		CGI->mh->hideObject(h, pack.result == TryMoveHero::EMBARK && pack.humanKnows);
-
-	if(pack.result == TryMoveHero::DISEMBARK)
-		CGI->mh->printObject(h->boat);
 }
 
 void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
@@ -467,11 +469,21 @@ void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
 
 	if(CGI->mh)
 	{
-		if(pack.result == TryMoveHero::TELEPORTATION  || pack.result == TryMoveHero::EMBARK  || pack.result == TryMoveHero::DISEMBARK)
-			CGI->mh->printObject(h, pack.result == TryMoveHero::DISEMBARK);
-
-		if(pack.result == TryMoveHero::EMBARK)
-			CGI->mh->hideObject(h->boat);
+		switch(pack.result)
+		{
+			case TryMoveHero::SUCCESS:
+				CGI->mh->onHeroMoved(h, pack.start, pack.end);
+				break;
+			case TryMoveHero::EMBARK:
+				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->onAfterHeroDisembark(h, pack.start, pack.end);
+				break;
+		}
 	}
 
 	PlayerColor player = h->tempOwner;
@@ -485,19 +497,14 @@ void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
 		if(i->first != PlayerColor::SPECTATOR && gs.checkForStandardLoss(i->first)) // Do not notify vanquished pack.player's interface
 			continue;
 
-		if(gs.isVisible(pack.start - int3(1, 0, 0), i->first)
-			|| gs.isVisible(pack.end - int3(1, 0, 0), i->first))
+		if(gs.isVisible(h->convertToVisitablePos(pack.start), i->first)
+			|| gs.isVisible(h->convertToVisitablePos(pack.end), i->first))
 		{
 			// pack.src and pack.dst of enemy hero move may be not visible => 'verbose' should be false
 			const bool verbose = cl.getPlayerRelations(i->first, player) != PlayerRelations::ENEMIES;
 			i->second->heroMoved(pack, verbose);
 		}
 	}
-
-	//maphandler didn't get update from playerint, do it now
-	//TODO: restructure nicely
-	if(!pack.humanKnows && CGI->mh)
-		CGI->mh->printObject(h);
 }
 
 void ApplyClientNetPackVisitor::visitNewStructures(NewStructures & pack)
@@ -559,31 +566,25 @@ void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack)
 		logNetwork->error("Something wrong with hero recruited!");
 	}
 
-	bool needsPrinting = true;
 	if(callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroCreated, h))
 	{
 		if(const CGTownInstance *t = gs.getTown(pack.tid))
-		{
 			callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroInGarrisonChange, t);
-			needsPrinting = false;
-		}
 	}
-	if(needsPrinting && CGI->mh)
-		CGI->mh->printObject(h);
+	if(CGI->mh)
+		CGI->mh->onObjectInstantAdd(h);
 }
 
 void ApplyClientNetPackVisitor::visitGiveHero(GiveHero & pack)
 {
 	CGHeroInstance *h = gs.getHero(pack.id);
 	if(CGI->mh)
-		CGI->mh->printObject(h);
+		CGI->mh->onObjectInstantAdd(h);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroCreated, h);
 }
 
 void ApplyFirstClientNetPackVisitor::visitGiveHero(GiveHero & pack)
 {
-	if(CGI->mh)
-		CGI->mh->hideObject(gs.getHero(pack.id));
 }
 
 void ApplyClientNetPackVisitor::visitInfoWindow(InfoWindow & pack)
@@ -872,7 +873,7 @@ void ApplyClientNetPackVisitor::visitAdvmapSpellCast(AdvmapSpellCast & pack)
 
 void ApplyClientNetPackVisitor::visitShowWorldViewEx(ShowWorldViewEx & pack)
 {
-	callOnlyThatInterface(cl, pack.player, &CGameInterface::showWorldViewEx, pack.objectPositions);
+	callOnlyThatInterface(cl, pack.player, &CGameInterface::showWorldViewEx, pack.objectPositions, pack.showTerrain);
 }
 
 void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
@@ -936,7 +937,6 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
 		callInterfaceIfPresent(cl, obj1->tempOwner, &IGameEventsReceiver::showTavernWindow, obj2);
 		break;
 	}
-
 }
 
 void ApplyClientNetPackVisitor::visitCenterView(CenterView & pack)
@@ -950,13 +950,14 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
 
 	const CGObjectInstance *obj = cl.getObj(pack.id);
 	if(CGI->mh)
-		CGI->mh->printObject(obj, true);
+		CGI->mh->onObjectFadeIn(obj);
 
 	for(auto i=cl.playerint.begin(); i!=cl.playerint.end(); i++)
 	{
 		if(gs.isVisible(obj, i->first))
 			i->second->newObject(obj);
 	}
+	CGI->mh->waitForOngoingAnimations();
 }
 
 void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)

+ 147 - 288
client/adventureMap/CAdvMapInt.cpp

@@ -15,11 +15,12 @@
 #include "CInGameConsole.h"
 #include "CMinimap.h"
 #include "CResDataBar.h"
-#include "CTerrainRect.h"
 #include "CList.h"
 #include "CInfoBar.h"
-#include "mapHandler.h"
+#include "MapAudioPlayer.h"
 
+#include "../mapView/mapHandler.h"
+#include "../mapView/MapView.h"
 #include "../windows/CKingdomInterface.h"
 #include "../windows/CSpellWindow.h"
 #include "../windows/CTradeWindow.h"
@@ -81,20 +82,21 @@ void CAdvMapInt::setScrollingCursor(ui8 direction) const
 
 CAdvMapInt::CAdvMapInt():
 	mode(EAdvMapMode::NORMAL),
-	worldViewScale(0.0f), //actual init later in changeMode
 	minimap(new CMinimap(Rect(ADVOPT.minimapX, ADVOPT.minimapY, ADVOPT.minimapW, ADVOPT.minimapH))),
 	statusbar(CGStatusBar::create(ADVOPT.statusbarX,ADVOPT.statusbarY,ADVOPT.statusbarG)),
 	heroList(new CHeroList(ADVOPT.hlistSize, Point(ADVOPT.hlistX, ADVOPT.hlistY), ADVOPT.hlistAU, ADVOPT.hlistAD)),
 	townList(new CTownList(ADVOPT.tlistSize, Point(ADVOPT.tlistX, ADVOPT.tlistY), ADVOPT.tlistAU, ADVOPT.tlistAD)),
 	infoBar(new CInfoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192))),
 	resdatabar(new CResDataBar),
-	terrain(new CTerrainRect),
+	mapAudio(new MapAudioPlayer()),
+	terrain(new MapView(Point(ADVOPT.advmapX, ADVOPT.advmapY), Point(ADVOPT.advmapW, ADVOPT.advmapH))),
 	state(NA),
-	spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr),
-	redrawOnNextFrame(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
-	activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false),
-	swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
-	swipeTargetPosition(int3(-1, -1, -1))
+	spellBeingCasted(nullptr),
+	selection(nullptr),
+	activeMapPanel(nullptr),
+	duringAITurn(false),
+	scrollingDir(0),
+	scrollingState(false)
 {
 	pos.x = pos.y = 0;
 	pos.w = GH.screenDimensions().x;
@@ -238,11 +240,11 @@ CAdvMapInt::CAdvMapInt():
 
 	activeMapPanel = panelMain;
 
-	changeMode(EAdvMapMode::NORMAL, 0.36F);
+	exitWorldView();
 
-	underground->block(!CGI->mh->map->twoLevel);
-	questlog->block(!CGI->mh->map->quests.size());
-	worldViewUnderground->block(!CGI->mh->map->twoLevel);
+	underground->block(!CGI->mh->getMap()->twoLevel);
+	questlog->block(!CGI->mh->getMap()->quests.size());
+	worldViewUnderground->block(!CGI->mh->getMap()->twoLevel);
 
 	addUsedEvents(MOVE);
 }
@@ -254,55 +256,65 @@ void CAdvMapInt::fshowOverview()
 
 void CAdvMapInt::fworldViewBack()
 {
-	changeMode(EAdvMapMode::NORMAL, 0.36F);
-	CGI->mh->discardWorldViewCache();
+	exitWorldView();
 
 	auto hero = curHero();
 	if (hero)
-		centerOn(hero);
+		centerOnObject(hero);
 }
 
 void CAdvMapInt::fworldViewScale1x()
 {
 	// TODO set corresponding scale button to "selected" mode
-	changeMode(EAdvMapMode::WORLD_VIEW, 0.22f); // 7 pixels per tile
+	openWorldView(7);
 }
 
 void CAdvMapInt::fworldViewScale2x()
 {
-	changeMode(EAdvMapMode::WORLD_VIEW, 0.36f); // 11 pixels per tile
+	openWorldView(11);
 }
 
 void CAdvMapInt::fworldViewScale4x()
 {
-	changeMode(EAdvMapMode::WORLD_VIEW, 0.5f); // 16 pixels per tile
+	openWorldView(16);
 }
 
 void CAdvMapInt::fswitchLevel()
 {
 	// with support for future multi-level maps :)
-	int maxLevels = CGI->mh->map->levels();
+	int maxLevels = CGI->mh->getMap()->levels();
 	if (maxLevels < 2)
 		return;
 
-	position.z = (position.z + 1) % maxLevels;
+	terrain->onMapLevelSwitched();
+}
 
-	underground->setIndex(position.z, true);
+void CAdvMapInt::onMapViewMoved(const Rect & visibleArea, int mapLevel)
+{
+	underground->setIndex(mapLevel, true);
 	underground->redraw();
 
-	worldViewUnderground->setIndex(position.z, true);
+	worldViewUnderground->setIndex(mapLevel, true);
 	worldViewUnderground->redraw();
 
-	redrawOnNextFrame = true;
-	minimap->setLevel(position.z);
+	minimap->onMapViewMoved(visibleArea, mapLevel);
+}
+
+void CAdvMapInt::onAudioResumed()
+{
+	mapAudio->onAudioResumed();
+}
 
-	if (mode == EAdvMapMode::WORLD_VIEW)
-		terrain->redraw();
+void CAdvMapInt::onAudioPaused()
+{
+	mapAudio->onAudioPaused();
 }
+
 void CAdvMapInt::fshowQuestlog()
 {
 	LOCPLINT->showQuestLog();
 }
+
 void CAdvMapInt::fsleepWake()
 {
 	const CGHeroInstance *h = curHero();
@@ -324,10 +336,10 @@ void CAdvMapInt::fsleepWake()
 void CAdvMapInt::fmoveHero()
 {
 	const CGHeroInstance *h = curHero();
-	if (!h || !terrain->currentPath || !CGI->mh->canStartHeroMovement())
+	if (!h || !LOCPLINT->paths.hasPath(h) || CGI->mh->hasOngoingAnimations())
 		return;
 
-	LOCPLINT->moveHero(h, *terrain->currentPath);
+	LOCPLINT->moveHero(h, LOCPLINT->paths.getPath(h));
 }
 
 void CAdvMapInt::fshowSpellbok()
@@ -335,7 +347,7 @@ void CAdvMapInt::fshowSpellbok()
 	if (!curHero()) //checking necessary values
 		return;
 
-	centerOn(selection);
+	centerOnObject(selection);
 
 	GH.pushIntT<CSpellWindow>(curHero(), LOCPLINT, false);
 }
@@ -373,10 +385,18 @@ void CAdvMapInt::fendTurn()
 				// Only show hero reminder if conditions met:
 				// - There still movement points
 				// - Hero don't have a path or there not points for first step on path
-				auto path = LOCPLINT->getAndVerifyPath(hero);
-				if(!path || path->nodes.size() < 2 || !path->nodes[path->nodes.size()-2].turns)
+				LOCPLINT->paths.verifyPath(hero);
+
+				if(!LOCPLINT->paths.hasPath(hero))
 				{
-					LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr);
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr );
+					return;
+				}
+
+				auto path = LOCPLINT->paths.getPath(hero);
+				if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
+				{
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr );
 					return;
 				}
 			}
@@ -405,7 +425,7 @@ void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
 	}
 	//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
 	if(boost::logic::indeterminate(hasPath))
-		hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
+		hasPath = LOCPLINT->paths.hasPath(h);
 
 	moveHero->block(!(bool)hasPath || (h->movement == 0));
 }
@@ -520,21 +540,16 @@ void CAdvMapInt::showAll(SDL_Surface * to)
 		infoBar->showAll(to);
 		break;
 	case EAdvMapMode::WORLD_VIEW:
-
-		terrain->showAll(to);
 		break;
 	}
 	activeMapPanel->showAll(to);
 
-	redrawOnNextFrame = true;
 	minimap->showAll(to);
+	terrain->showAll(to);
 	show(to);
 
-
 	resdatabar->showAll(to);
-
 	statusbar->show(to);
-
 	LOCPLINT->cingconsole->show(to);
 }
 
@@ -560,30 +575,7 @@ void CAdvMapInt::show(SDL_Surface * to)
 	if(state != INGAME)
 		return;
 
-	++animValHitCount; //for animations
-
-	if(animValHitCount % 2 == 0)
-	{
-		++heroAnim;
-	}
-	if(animValHitCount >= 8)
-	{
-		CGI->mh->updateWater();
-		animValHitCount = 0;
-		++anim;
-		redrawOnNextFrame = true;
-	}
-
-	if(swipeEnabled)
-	{
-		handleSwipeUpdate();
-	}
-#if defined(VCMI_MOBILE) // on mobile, map-moving mode is exclusive (TODO technically it might work with both enabled; to be checked)
-	else
-#endif
-	{
-		handleMapScrollingUpdate();
-	}
+	handleMapScrollingUpdate();
 
 	for(int i = 0; i < 4; i++)
 	{
@@ -592,27 +584,14 @@ void CAdvMapInt::show(SDL_Surface * to)
 		else
 			gems[i]->setFrame(LOCPLINT->playerID.getNum());
 	}
-	if(redrawOnNextFrame)
-	{
-		int3 betterPos = LOCPLINT->repairScreenPos(position);
-		if (betterPos != position)
-		{
-			logGlobal->warn("Incorrect position for adventure map!");
-			position = betterPos;
-		}
 
-		terrain->show(to);
-		for(int i = 0; i < 4; i++)
-			gems[i]->showAll(to);
-		redrawOnNextFrame=false;
-		LOCPLINT->cingconsole->show(to);
-	}
-	else
-	{
-		terrain->showAnim(to);
-		for(int i = 0; i < 4; i++)
-			gems[i]->showAll(to);
-	}
+	minimap->show(to);
+	terrain->show(to);
+
+	for(int i = 0; i < 4; i++)
+		gems[i]->showAll(to);
+
+	LOCPLINT->cingconsole->show(to);
 
 	infoBar->show(to);
 	statusbar->showAll(to);
@@ -620,50 +599,32 @@ void CAdvMapInt::show(SDL_Surface * to)
 
 void CAdvMapInt::handleMapScrollingUpdate()
 {
-	int scrollSpeed = static_cast<int>(settings["adventure"]["scrollSpeed"].Float());
+	uint32_t timePassed = GH.mainFPSmng->getElapsedMilliseconds();
+	double scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
+	int32_t scrollDistance = static_cast<int32_t>(scrollSpeedPixels * timePassed / 1000);
 	//if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
-	if((animValHitCount % (4 / scrollSpeed)) == 0)
-	{
-		if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW))
-			position.x--;
 
-		if((scrollingDir & RIGHT) && (position.x < CGI->mh->map->width - CGI->mh->tilesW + CGI->mh->frameW))
-			position.x++;
+	if(scrollingDir & LEFT)
+		terrain->onMapScrolled(Point(-scrollDistance, 0));
 
-		if((scrollingDir & UP) && (position.y > -CGI->mh->frameH))
-			position.y--;
+	if(scrollingDir & RIGHT)
+		terrain->onMapScrolled(Point(+scrollDistance, 0));
 
-		if((scrollingDir & DOWN) && (position.y < CGI->mh->map->height - CGI->mh->tilesH + CGI->mh->frameH))
-			position.y++;
+	if(scrollingDir & UP)
+		terrain->onMapScrolled(Point(0, -scrollDistance));
 
-		if(scrollingDir)
-		{
-			setScrollingCursor(scrollingDir);
-			scrollingState = true;
-			redrawOnNextFrame = true;
-			minimap->redraw();
-			if(mode == EAdvMapMode::WORLD_VIEW)
-				terrain->redraw();
-		}
-		else if(scrollingState)
-		{
-			CCS->curh->set(Cursor::Map::POINTER);
-			scrollingState = false;
-		}
-	}
-}
+	if(scrollingDir & DOWN)
+		terrain->onMapScrolled(Point(0, +scrollDistance));
 
-void CAdvMapInt::handleSwipeUpdate()
-{
-	if(swipeMovementRequested)
+	if(scrollingDir)
+	{
+		setScrollingCursor(scrollingDir);
+		scrollingState = true;
+	}
+	else if(scrollingState)
 	{
-		auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
-		position.x = fixedPos.x;
-		position.y = fixedPos.y;
 		CCS->curh->set(Cursor::Map::POINTER);
-		redrawOnNextFrame = true;
-		minimap->redraw();
-		swipeMovementRequested = false;
+		scrollingState = false;
 	}
 }
 
@@ -674,53 +635,19 @@ void CAdvMapInt::selectionChanged()
 		select(to);
 }
 
-void CAdvMapInt::centerOn(int3 on, bool fade)
+void CAdvMapInt::centerOnTile(int3 on)
 {
-	bool switchedLevels = on.z != position.z;
-
-	if (fade)
-	{
-		terrain->fadeFromCurrentView();
-	}
-
-	switch (mode)
-	{
-	default:
-	case EAdvMapMode::NORMAL:
-		on.x -= CGI->mh->frameW; // is this intentional? frame size doesn't really have to correspond to camera size...
-		on.y -= CGI->mh->frameH;
-		break;
-	case EAdvMapMode::WORLD_VIEW:
-		on.x -= static_cast<si32>(CGI->mh->tilesW / 2 / worldViewScale);
-		on.y -= static_cast<si32>(CGI->mh->tilesH / 2 / worldViewScale);
-		break;
-	}
-
-
-	on = LOCPLINT->repairScreenPos(on);
-
-	position = on;
-	redrawOnNextFrame=true;
-	underground->setIndex(on.z,true); //change underground switch button image
-	underground->redraw();
-	worldViewUnderground->setIndex(on.z, true);
-	worldViewUnderground->redraw();
-	if (switchedLevels)
-		minimap->setLevel(position.z);
-	minimap->redraw();
-
-	if (mode == EAdvMapMode::WORLD_VIEW)
-		terrain->redraw();
+	terrain->onCenteredTile(on);
 }
 
-void CAdvMapInt::centerOn(const CGObjectInstance * obj, bool fade)
+void CAdvMapInt::centerOnObject(const CGObjectInstance * obj)
 {
-	centerOn(obj->getSightCenter(), fade);
+	terrain->onCenteredObject(obj);
 }
 
 void CAdvMapInt::keyReleased(const SDL_Keycode &key)
 {
-	if (mode == EAdvMapMode::WORLD_VIEW)
+	if (mode != EAdvMapMode::NORMAL)
 		return;
 
 	switch (key)
@@ -748,7 +675,7 @@ void CAdvMapInt::keyReleased(const SDL_Keycode &key)
 
 void CAdvMapInt::keyPressed(const SDL_Keycode & key)
 {
-	if (mode == EAdvMapMode::WORLD_VIEW)
+	if (mode != EAdvMapMode::NORMAL)
 		return;
 
 	const CGHeroInstance *h = curHero(); //selected hero
@@ -808,11 +735,6 @@ void CAdvMapInt::keyPressed(const SDL_Keycode & key)
 				return;
 			if(h)
 			{
-				auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
-				//TODO!!!!!!! possible freeze, when GS mutex is locked and network thread can't apply package
-				//this thread leaves scope and tries to lock pim while holding gs,
-				//network thread tries to lock gs (appluy cl) while holding pim
-				//this thread should first lock pim, however gs locking/unlocking is done inside cb
 				LOCPLINT->cb->moveHero(h,h->pos);
 			}
 		}
@@ -829,7 +751,8 @@ void CAdvMapInt::keyPressed(const SDL_Keycode & key)
 		}
 	case SDLK_ESCAPE:
 		{
-			if(isActive() || GH.topInt().get() != this || !spellBeingCasted)
+			//FIXME: this case is never executed since AdvMapInt is disabled while in spellcasting mode
+			if(!isActive() || GH.topInt().get() != this || !spellBeingCasted)
 				return;
 
 			leaveCastingMode();
@@ -889,23 +812,24 @@ void CAdvMapInt::keyPressed(const SDL_Keycode & key)
 			if(!h || !isActive())
 				return;
 
-			if (!CGI->mh->canStartHeroMovement())
+			if (CGI->mh->hasOngoingAnimations())
 				return;
 
 			if(*direction == Point(0,0))
 			{
-				centerOn(h);
+				centerOnObject(h);
 				return;
 			}
 
-			CGPath &path = LOCPLINT->paths[h];
-			terrain->currentPath = &path;
 			int3 dst = h->visitablePos() + int3(direction->x, direction->y, 0);
-			if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst))
-			{
-				terrain->currentPath = nullptr;
+
+			if (!CGI->mh->isInMap((dst)))
 				return;
-			}
+
+			if ( !LOCPLINT->paths.setPath(h, dst))
+				return;
+
+			const CGPath & path = LOCPLINT->paths.getPath(h);
 
 			if (path.nodes.size() > 2)
 				updateMoveHero(h);
@@ -939,39 +863,14 @@ boost::optional<Point> CAdvMapInt::keyToMoveDirection(const SDL_Keycode & key)
 	return boost::none;
 }
 
-int3 CAdvMapInt::verifyPos(int3 ver)
-{
-	if (ver.x<0)
-		ver.x=0;
-	if (ver.y<0)
-		ver.y=0;
-	if (ver.z<0)
-		ver.z=0;
-	if (ver.x>=CGI->mh->sizes.x)
-		ver.x=CGI->mh->sizes.x-1;
-	if (ver.y>=CGI->mh->sizes.y)
-		ver.y=CGI->mh->sizes.y-1;
-	if (ver.z>=CGI->mh->sizes.z)
-		ver.z=CGI->mh->sizes.z-1;
-	return ver;
-}
-
 void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
 {
 	assert(sel);
-	LOCPLINT->setSelection(sel);
 	selection = sel;
-	if (LOCPLINT->battleInt == nullptr && LOCPLINT->makingTurn)
-	{
-		auto pos = sel->visitablePos();
-		auto tile = LOCPLINT->cb->getTile(pos);
-		if(tile)
-			CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false);
-	}
+	mapAudio->onSelectionChanged(sel);
 	if(centerView)
-		centerOn(sel);
+		centerOnObject(sel);
 
-	terrain->currentPath = nullptr;
 	if(sel->ID==Obj::TOWN)
 	{
 		auto town = dynamic_cast<const CGTownInstance*>(sel);
@@ -992,7 +891,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
 		heroList->select(hero);
 		townList->select(nullptr);
 
-		terrain->currentPath = LOCPLINT->getAndVerifyPath(hero);
+		LOCPLINT->paths.verifyPath(hero);
 
 		updateSleepWake(hero);
 		updateMoveHero(hero);
@@ -1004,13 +903,8 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
 
 void CAdvMapInt::mouseMoved( const Point & cursorPosition )
 {
-#if defined(VCMI_MOBILE)
-	if(swipeEnabled)
-		return;
-#endif
 	// adventure map scrolling with mouse
 	// currently disabled in world view mode (as it is in OH3), but should work correctly if mode check is removed
-	// don't scroll if there is no window in focus - these events don't seem to correspond to the actual mouse movement
 	if(!GH.isKeyboardCtrlDown() && isActive() && mode == EAdvMapMode::NORMAL)
 	{
 		if(cursorPosition.x<15)
@@ -1087,6 +981,7 @@ void CAdvMapInt::initializeNewTurn()
 {
 	heroList->update();
 	townList->update();
+	mapAudio->onPlayerTurnStarted();
 
 	const CGHeroInstance * heroToSelect = nullptr;
 
@@ -1134,7 +1029,7 @@ void CAdvMapInt::endingTurn()
 
 	LOCPLINT->makingTurn = false;
 	LOCPLINT->cb->endTurn();
-	CCS->soundh->ambientStopAllChannels();
+	mapAudio->onPlayerTurnEnded();
 }
 
 const CGObjectInstance* CAdvMapInt::getActiveObject(const int3 &mapPos)
@@ -1152,11 +1047,15 @@ const CGObjectInstance* CAdvMapInt::getActiveObject(const int3 &mapPos)
 		return bobjs.front();*/
 }
 
-void CAdvMapInt::tileLClicked(const int3 &mapPos)
+void CAdvMapInt::onTileLeftClicked(const int3 &mapPos)
 {
 	if(mode != EAdvMapMode::NORMAL)
 		return;
-	if(!LOCPLINT->cb->isVisible(mapPos) || !LOCPLINT->makingTurn)
+
+	//FIXME: this line breaks H3 behavior for Dimension Door
+	if(!LOCPLINT->cb->isVisible(mapPos))
+		return;
+	if(!LOCPLINT->makingTurn)
 		return;
 
 	const TerrainTile *tile = LOCPLINT->cb->getTile(mapPos);
@@ -1188,7 +1087,6 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	bool isHero = false;
 	if(selection->ID != Obj::HERO) //hero is not selected (presumably town)
 	{
-		assert(!terrain->currentPath); //path can be active only when hero is selected
 		if(selection == topBlocking) //selected town clicked
 			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
 		else if(canSelect)
@@ -1211,25 +1109,16 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 		}
 		else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
 		{
-			if(terrain->currentPath && terrain->currentPath->endPos() == mapPos)//we'll be moving
+			if(LOCPLINT->paths.hasPath(currentHero) &&
+			   LOCPLINT->paths.getPath(currentHero).endPos() == mapPos)//we'll be moving
 			{
-				if(CGI->mh->canStartHeroMovement())
-					LOCPLINT->moveHero(currentHero, *terrain->currentPath);
+				if(!CGI->mh->hasOngoingAnimations())
+					LOCPLINT->moveHero(currentHero, LOCPLINT->paths.getPath(currentHero));
 				return;
 			}
 			else //remove old path and find a new one if we clicked on accessible tile
 			{
-				CGPath &path = LOCPLINT->paths[currentHero];
-				CGPath newpath;
-				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(newpath, mapPos); //try getting path, erase if failed
-				if(gotPath && newpath.nodes.size())
-					path = newpath;
-
-				if(path.nodes.size())
-					terrain->currentPath = &path;
-				else
-					LOCPLINT->eraseCurrentPathOf(currentHero);
-
+				LOCPLINT->paths.setPath(currentHero, mapPos);
 				updateMoveHero(currentHero);
 			}
 		}
@@ -1246,7 +1135,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	}
 }
 
-void CAdvMapInt::tileHovered(const int3 &mapPos)
+void CAdvMapInt::onTileHovered(const int3 &mapPos)
 {
 	if(mode != EAdvMapMode::NORMAL //disable in world view
 		|| !selection) //may occur just at the start of game (fake move before full intiialization)
@@ -1268,8 +1157,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 	}
 	else
 	{
-		std::string hlp;
-		CGI->mh->getTerrainDescr(mapPos, hlp, false);
+		std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
 		statusbar->write(hlp);
 	}
 
@@ -1406,7 +1294,7 @@ void CAdvMapInt::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const C
 	statusbar->write(result);
 }
 
-void CAdvMapInt::tileRClicked(const int3 &mapPos)
+void CAdvMapInt::onTileRightClicked(const int3 &mapPos)
 {
 	if(mode != EAdvMapMode::NORMAL)
 		return;
@@ -1426,10 +1314,9 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos)
 	{
 		// Bare or undiscovered terrain
 		const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
-		if (tile)
+		if(tile)
 		{
-			std::string hlp;
-			CGI->mh->getTerrainDescr(mapPos, hlp, true);
+			std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
 			CRClickPopup::createAndPush(hlp);
 		}
 		return;
@@ -1491,11 +1378,6 @@ Rect CAdvMapInt::terrainAreaPixels() const
 	return terrain->pos;
 }
 
-Rect CAdvMapInt::terrainAreaTiles() const
-{
-	return terrain->visibleTilesArea();
-}
-
 const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *obj) const
 {
 	const IShipyard *ret = IShipyard::castFrom(obj);
@@ -1514,7 +1396,7 @@ void CAdvMapInt::aiTurnStarted()
 		return;
 
 	adjustActiveness(true);
-	CCS->musich->playMusicFromSet("enemy-turn", true, false);
+	mapAudio->onEnemyTurnStarted();
 	adventureInt->minimap->setAIRadar(true);
 	adventureInt->infoBar->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
 	adventureInt->infoBar->showAll(screen);//force refresh on inactive object
@@ -1543,69 +1425,46 @@ void CAdvMapInt::quickCombatUnlock()
 		activate();
 }
 
-void CAdvMapInt::changeMode(EAdvMapMode newMode, float newScale)
+void CAdvMapInt::exitWorldView()
 {
-	if (mode != newMode)
-	{
-		mode = newMode;
-
-		switch (mode)
-		{
-		case EAdvMapMode::NORMAL:
-			panelMain->activate();
-			panelWorldView->deactivate();
-			activeMapPanel = panelMain;
-
-			townList->activate();
-			heroList->activate();
-			infoBar->activate();
-
-			worldViewOptions.clear();
-
-			break;
-		case EAdvMapMode::WORLD_VIEW:
-			panelMain->deactivate();
-			panelWorldView->activate();
+	mode = EAdvMapMode::NORMAL;
 
-			activeMapPanel = panelWorldView;
+	panelMain->activate();
+	panelWorldView->deactivate();
+	activeMapPanel = panelMain;
 
-			townList->deactivate();
-			heroList->deactivate();
-			infoBar->showSelection(); // to prevent new day animation interfering world view mode
-			infoBar->deactivate();
+	townList->activate();
+	heroList->activate();
+	infoBar->activate();
 
-			break;
-		}
-		worldViewScale = newScale;
-		redraw();
-	}
-	else if (worldViewScale != newScale) // still in world view mode, but the scale changed
-	{
-		worldViewScale = newScale;
-		redraw();
-	}
+	redraw();
+	terrain->onViewMapActivated();
 }
 
-CAdvMapInt::WorldViewOptions::WorldViewOptions()
+void CAdvMapInt::openWorldView(int tileSize)
 {
-	clear();
-}
+	mode = EAdvMapMode::WORLD_VIEW;
+	panelMain->deactivate();
+	panelWorldView->activate();
 
-void CAdvMapInt::WorldViewOptions::clear()
-{
-	showAllTerrain = false;
+	activeMapPanel = panelWorldView;
+
+	townList->deactivate();
+	heroList->deactivate();
+	infoBar->showSelection(); // to prevent new day animation interfering world view mode
+	infoBar->deactivate();
 
-	iconPositions.clear();
+	redraw();
+	terrain->onViewWorldActivated(tileSize);
 }
 
-void CAdvMapInt::WorldViewOptions::adjustDrawingInfo(MapDrawingInfo& info)
+void CAdvMapInt::openWorldView()
 {
-	info.showAllTerrain = showAllTerrain;
-
-	info.additionalIcons = &iconPositions;
+	openWorldView(11);
 }
 
-void CAdvMapInt::requestRedrawMapOnNextFrame()
+void CAdvMapInt::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
 {
-	redrawOnNextFrame = true;
+	openWorldView(11);
+	terrain->onViewSpellActivated(11, objectPositions, showTerrain);
 }

+ 29 - 39
client/adventureMap/CAdvMapInt.h

@@ -33,12 +33,13 @@ class CGStatusBar;
 class CAdvMapPanel;
 class CAdvMapWorldViewPanel;
 class CAnimation;
-class CTerrainRect;
+class MapView;
 class CResDataBar;
 class CHeroList;
 class CTownList;
 class CInfoBar;
 class CMinimap;
+class MapAudioPlayer;
 
 struct MapDrawingInfo;
 
@@ -55,37 +56,13 @@ class CAdvMapInt : public CIntObject
 {
 	//TODO: remove
 	friend class CPlayerInterface;
-	friend class CTerrainRect;
 
 private:
 	enum EDirections {LEFT=1, RIGHT=2, UP=4, DOWN=8};
 	enum EGameStates {NA, INGAME, WAITING};
 
-	struct WorldViewOptions
-	{
-		bool showAllTerrain; //for expert viewEarth
-		std::vector<ObjectPosInfo> iconPositions;
-		WorldViewOptions();
-		void clear();
-		void adjustDrawingInfo(MapDrawingInfo & info);
-	};
-
-	bool swipeEnabled;
-	bool swipeMovementRequested;
-	int3 swipeTargetPosition;
-
 	EGameStates state;
-
-	ui8 anim, animValHitCount; //animation frame
-	ui8 heroAnim, heroAnimValHitCount; //animation frame
-
-	/// top left corner of visible map part
-	int3 position;
-
 	EAdvMapMode mode;
-	float worldViewScale;
-
-	WorldViewOptions worldViewOptions;
 
 	/// Currently selected object, can be town, hero or null
 	const CArmedInstance *selection;
@@ -117,7 +94,7 @@ private:
 	std::shared_ptr<CButton> endTurn;
 	std::shared_ptr<CButton> worldViewUnderground;
 
-	std::shared_ptr<CTerrainRect> terrain;
+	std::shared_ptr<MapView> terrain;
 	std::shared_ptr<CMinimap> minimap;
 	std::shared_ptr<CHeroList> heroList;
 	std::shared_ptr<CTownList> townList;
@@ -131,6 +108,8 @@ private:
 
 	std::shared_ptr<CAnimation> worldViewIcons;// images for world view overlay
 
+	std::shared_ptr<MapAudioPlayer> mapAudio;
+
 private:
 	//functions bound to buttons
 	void fshowOverview();
@@ -159,7 +138,6 @@ private:
 	void updateSpellbook(const CGHeroInstance *h);
 
 	void handleMapScrollingUpdate();
-	void handleSwipeUpdate();
 
 	void showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode);
 
@@ -167,7 +145,6 @@ private:
 
 	boost::optional<Point> keyToMoveDirection(const SDL_Keycode & key);
 
-	bool redrawOnNextFrame;
 public:
 	CAdvMapInt();
 
@@ -185,12 +162,19 @@ public:
 
 	// public interface
 
-	void requestRedrawMapOnNextFrame();
+	/// called by MapView whenever currently visible area changes
+	/// visibleArea describen now visible map section measured in tiles
+	void onMapViewMoved(const Rect & visibleArea, int mapLevel);
+
+	/// Called when map audio should be paused, e.g. on combat or town scren access
+	void onAudioPaused();
+
+	/// Called when map audio should be resume, opposite to onPaused
+	void onAudioResumed();
 
 	void select(const CArmedInstance *sel, bool centerView = true);
-	void centerOn(int3 on, bool fade = false);
-	void centerOn(const CGObjectInstance *obj, bool fade = false);
-	int3 verifyPos(int3 ver);
+	void centerOnTile(int3 on);
+	void centerOnObject(const CGObjectInstance *obj);
 
 	bool isHeroSleeping(const CGHeroInstance *hero);
 	void setHeroSleeping(const CGHeroInstance *hero, bool sleep);
@@ -206,9 +190,9 @@ public:
 	void quickCombatLock(); //should be called when quick battle started
 	void quickCombatUnlock();
 
-	void tileLClicked(const int3 &mapPos);
-	void tileHovered(const int3 &mapPos);
-	void tileRClicked(const int3 &mapPos);
+	void onTileLeftClicked(const int3 & mapPos);
+	void onTileHovered(const int3 & mapPos);
+	void onTileRightClicked(const int3 & mapPos);
 
 	void enterCastingMode(const CSpell * sp);
 	void leaveCastingMode(bool cast = false, int3 dest = int3(-1, -1, -1));
@@ -222,11 +206,17 @@ public:
 	/// returns area of screen covered by terrain (main game area)
 	Rect terrainAreaPixels() const;
 
-	/// returs visible section of game map, in tiles
-	Rect terrainAreaTiles() const;
+	/// exits currently opened world view mode and returns to normal map
+	void exitWorldView();
+
+	/// opens world view at default scale
+	void openWorldView();
+
+	/// opens world view at specific scale
+	void openWorldView(int tileSize);
 
-	/// changes current adventure map mode; used to switch between default view and world view; scale is ignored if EAdvMapMode == NORMAL
-	void changeMode(EAdvMapMode newMode, float newScale);
+	/// opens world view with specific info, e.g. after View Earth/Air is shown
+	void openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain);
 };
 
 extern std::shared_ptr<CAdvMapInt> adventureInt;

+ 1 - 1
client/adventureMap/CAdventureOptions.cpp

@@ -29,7 +29,7 @@ CAdventureOptions::CAdventureOptions()
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_v);
-	viewWorld->addCallback(std::bind(&CPlayerInterface::viewWorldMap, LOCPLINT));
+	viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
 
 	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);

+ 4 - 3
client/adventureMap/CInGameConsole.cpp

@@ -11,12 +11,13 @@
 #include "StdInc.h"
 #include "CInGameConsole.h"
 
-#include "../render/Colors.h"
 #include "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CGuiHandler.h"
 #include "../ClientCommandManager.h"
+#include "../adventureMap/CAdvMapInt.h"
+#include "../gui/CGuiHandler.h"
+#include "../render/Colors.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
@@ -234,7 +235,7 @@ void CInGameConsole::endEnteringText(bool processEnteredText)
 			clientCommandThread.detach();
 		}
 		else
-			LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
+			LOCPLINT->cb->sendMessage(txt, adventureInt->curArmy());
 	}
 	enteredText.clear();
 

+ 15 - 8
client/adventureMap/CMinimap.cpp

@@ -12,7 +12,6 @@
 #include "CMinimap.h"
 
 #include "CAdvMapInt.h"
-#include "CTerrainRect.h"
 
 #include "../widgets/Images.h"
 #include "../CGameInfo.h"
@@ -81,7 +80,7 @@ CMinimapInstance::CMinimapInstance(CMinimap *Parent, int Level):
 void CMinimapInstance::showAll(SDL_Surface * to)
 {
 	Canvas target(to);
-	target.draw(*minimap, pos.topLeft(), pos.dimensions());
+	target.drawScaled(*minimap, pos.topLeft(), pos.dimensions());
 }
 
 CMinimap::CMinimap(const Rect & position)
@@ -126,7 +125,7 @@ Point CMinimap::tileToPixels(const int3 &tile) const
 void CMinimap::moveAdvMapSelection()
 {
 	int3 newLocation = pixelToTile(GH.getCursorPosition() - pos.topLeft());
-	adventureInt->centerOn(newLocation);
+	adventureInt->centerOnTile(newLocation);
 
 	if (!(adventureInt->active & GENERAL))
 		GH.totalRedraw(); //redraw this as well as inactive adventure map
@@ -162,13 +161,14 @@ void CMinimap::mouseMoved(const Point & cursorPosition)
 
 void CMinimap::showAll(SDL_Surface * to)
 {
+	CSDL_Ext::CClipRectGuard guard(to, pos);
 	CIntObject::showAll(to);
+
 	if(minimap)
 	{
 		Canvas target(to);
 
 		int3 mapSizes = LOCPLINT->cb->getMapSize();
-		Rect screenArea = adventureInt->terrainAreaTiles();
 
 		//draw radar
 		Rect radar =
@@ -194,13 +194,20 @@ void CMinimap::update()
 	redraw();
 }
 
-void CMinimap::setLevel(int newLevel)
+void CMinimap::onMapViewMoved(const Rect & visibleArea, int mapLevel)
 {
-	if (level == newLevel)
+	if (screenArea == visibleArea && level == mapLevel)
 		return;
 
-	level = newLevel;
-	update();
+	screenArea = visibleArea;
+
+	if(level != mapLevel)
+	{
+		level = mapLevel;
+		update();
+	}
+	else
+		redraw();
 }
 
 void CMinimap::setAIRadar(bool on)

+ 2 - 2
client/adventureMap/CMinimap.h

@@ -41,6 +41,7 @@ class CMinimap : public CIntObject
 {
 	std::shared_ptr<CPicture> aiShield; //the graphic displayed during AI turn
 	std::shared_ptr<CMinimapInstance> minimap;
+	Rect screenArea;
 	int level;
 
 	void clickLeft(tribool down, bool previousState) override;
@@ -59,11 +60,10 @@ protected:
 	Point tileToPixels(const int3 & position) const;
 
 public:
-
 	explicit CMinimap(const Rect & position);
 
+	void onMapViewMoved(const Rect & visibleArea, int mapLevel);
 	void update();
-	void setLevel(int level);
 	void setAIRadar(bool on);
 
 	void showAll(SDL_Surface * to) override;

+ 0 - 415
client/adventureMap/CTerrainRect.cpp

@@ -1,415 +0,0 @@
-/*
- * CTerrainRect.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 "CTerrainRect.h"
-
-#include "mapHandler.h"
-#include "CAdvMapInt.h"
-
-#include "../CGameInfo.h"
-#include "../CPlayerInterface.h"
-#include "../gui/CursorHandler.h"
-#include "../gui/CGuiHandler.h"
-#include "../render/CAnimation.h"
-#include "../render/CFadeAnimation.h"
-#include "../render/IImage.h"
-#include "../renderSDL/SDL_Extensions.h"
-#include "../widgets/TextControls.h"
-#include "../CMT.h"
-
-#include "../../CCallback.h"
-#include "../../lib/CConfigHandler.h"
-#include "../../lib/mapping/CMap.h"
-#include "../../lib/CPathfinder.h"
-
-#include <SDL_surface.h>
-
-#define ADVOPT (conf.go()->ac)
-
-CTerrainRect::CTerrainRect()
-	: fadeSurface(nullptr),
-	  lastRedrawStatus(EMapAnimRedrawStatus::OK),
-	  fadeAnim(std::make_shared<CFadeAnimation>()),
-	  curHoveredTile(-1,-1,-1),
-	  currentPath(nullptr)
-{
-	tilesw=(ADVOPT.advmapW+31)/32;
-	tilesh=(ADVOPT.advmapH+31)/32;
-	pos.x=ADVOPT.advmapX;
-	pos.y=ADVOPT.advmapY;
-	pos.w=ADVOPT.advmapW;
-	pos.h=ADVOPT.advmapH;
-	moveX = moveY = 0;
-	addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
-}
-
-CTerrainRect::~CTerrainRect()
-{
-	if(fadeSurface)
-		SDL_FreeSurface(fadeSurface);
-}
-
-void CTerrainRect::deactivate()
-{
-	CIntObject::deactivate();
-	curHoveredTile = int3(-1,-1,-1); //we lost info about hovered tile when disabling
-}
-
-void CTerrainRect::clickLeft(tribool down, bool previousState)
-{
-	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-	if(indeterminate(down))
-		return;
-
-#if defined(VCMI_MOBILE)
-	if(adventureInt->swipeEnabled)
-	{
-		if(handleSwipeStateChange((bool)down == true))
-		{
-			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
-		}
-	}
-	else
-	{
-#endif
-		if(down == false)
-			return;
-#if defined(VCMI_MOBILE)
-	}
-#endif
-	int3 mp = whichTileIsIt();
-	if(mp.x < 0 || mp.y < 0 || mp.x >= LOCPLINT->cb->getMapSize().x || mp.y >= LOCPLINT->cb->getMapSize().y)
-		return;
-
-	adventureInt->tileLClicked(mp);
-}
-
-void CTerrainRect::clickRight(tribool down, bool previousState)
-{
-#if defined(VCMI_MOBILE)
-	if(adventureInt->swipeEnabled && isSwiping)
-		return;
-#endif
-	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-	int3 mp = whichTileIsIt();
-
-	if(CGI->mh->map->isInTheMap(mp) && down)
-		adventureInt->tileRClicked(mp);
-}
-
-void CTerrainRect::clickMiddle(tribool down, bool previousState)
-{
-	handleSwipeStateChange((bool)down == true);
-}
-
-void CTerrainRect::mouseMoved(const Point & cursorPosition)
-{
-	handleHover(cursorPosition);
-
-	if(!adventureInt->swipeEnabled)
-		return;
-
-	handleSwipeMove(cursorPosition);
-}
-
-void CTerrainRect::handleSwipeMove(const Point & cursorPosition)
-{
-#if defined(VCMI_MOBILE)
-	if(!GH.isMouseButtonPressed() || GH.multifinger) // any "button" is enough on mobile
-		return;
-#else
-	if(!GH.isMouseButtonPressed(MouseButton::MIDDLE)) // swipe only works with middle mouse on other platforms
-		return;
-#endif
-
-	if(!isSwiping)
-	{
-		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
-		if(abs(cursorPosition.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
-		   abs(cursorPosition.y - swipeInitialRealPos.y) > SwipeTouchSlop)
-		{
-			isSwiping = true;
-		}
-	}
-
-	if(isSwiping)
-	{
-		adventureInt->swipeTargetPosition.x =
-			swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - cursorPosition.x) / 32;
-		adventureInt->swipeTargetPosition.y =
-			swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - cursorPosition.y) / 32;
-		adventureInt->swipeMovementRequested = true;
-	}
-}
-
-bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
-{
-	if(btnPressed)
-	{
-		swipeInitialRealPos = Point(GH.getCursorPosition().x, GH.getCursorPosition().y);
-		swipeInitialMapPos = int3(adventureInt->position);
-		return true;
-	}
-	else if(isSwiping) // only accept this touch if it wasn't a swipe
-	{
-		isSwiping = false;
-		return true;
-	}
-	return false;
-}
-
-void CTerrainRect::handleHover(const Point & cursorPosition)
-{
-	int3 tHovered = whichTileIsIt(cursorPosition.x, cursorPosition.y);
-	int3 pom = adventureInt->verifyPos(tHovered);
-
-	if(tHovered != pom) //tile outside the map
-	{
-		CCS->curh->set(Cursor::Map::POINTER);
-		return;
-	}
-
-	if (pom != curHoveredTile)
-	{
-		curHoveredTile = pom;
-		adventureInt->tileHovered(pom);
-	}
-}
-
-void CTerrainRect::hover(bool on)
-{
-	if (!on)
-	{
-		GH.statusbar->clear();
-		CCS->curh->set(Cursor::Map::POINTER);
-	}
-	//Hoverable::hover(on);
-}
-void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
-{
-	const static int pns[9][9] = {
-				{16, 17, 18,  7, -1, 19,  6,  5, -1},
-				{ 8,  9, 18,  7, -1, 19,  6, -1, 20},
-				{ 8,  1, 10,  7, -1, 19, -1, 21, 20},
-				{24, 17, 18, 15, -1, -1,  6,  5,  4},
-				{-1, -1, -1, -1, -1, -1, -1, -1, -1},
-				{ 8,  1,  2, -1, -1, 11, 22, 21, 20},
-				{24, 17, -1, 23, -1,  3, 14,  5,  4},
-				{24, -1,  2, 23, -1,  3, 22, 13,  4},
-				{-1,  1,  2, 23, -1,  3, 22, 21, 12}
-			}; //table of magic values TODO meaning, change variable name
-
-	for (int i = 0; i < -1 + (int)currentPath->nodes.size(); ++i)
-	{
-		const int3 &curPos = currentPath->nodes[i].coord, &nextPos = currentPath->nodes[i+1].coord;
-		if(curPos.z != adventureInt->position.z)
-			continue;
-
-		int pn=-1;//number of picture
-		if (i==0) //last tile
-		{
-			int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
-				y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
-			if (x<0 || y<0 || x>pos.w || y>pos.h)
-				continue;
-			pn=0;
-		}
-		else
-		{
-			const int3 &prevPos = currentPath->nodes[i-1].coord;
-			std::vector<CGPathNode> & cv = currentPath->nodes;
-
-			/* Vector directions
-			 *  0   1   2
-			 *    \ | /
-			 *  3 - 4 - 5
-			 *    / | \
-			 *  6   7  8
-			 *For example:
-			 *  |
-			 *  |__\
-			 *     /
-			 * is id1=7, id2=5 (pns[7][5])
-			*/
-			bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
-			if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK)
-			{
-				int id1=(curPos.x-nextPos.x+1)+3*(curPos.y-nextPos.y+1);   //Direction of entering vector
-				int id2=(cv[i-1].coord.x-curPos.x+1)+3*(cv[i-1].coord.y-curPos.y+1); //Direction of exiting vector
-				pn=pns[id1][id2];
-			}
-			else //path discontinuity or sea/land transition (eg. when moving through Subterranean Gate or Boat)
-			{
-				pn = 0;
-			}
-		}
-		if (currentPath->nodes[i].turns)
-			pn+=25;
-		if (pn>=0)
-		{
-			const auto arrow = graphics->heroMoveArrows->getImage(pn);
-
-			int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
-				y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
-			if (x< -32 || y< -32 || x>pos.w || y>pos.h)
-				continue;
-			int hvx = (x + arrow->width())  - (pos.x + pos.w),
-				hvy = (y + arrow->height()) - (pos.y + pos.h);
-
-			Rect prevClip;
-			CSDL_Ext::getClipRect(to, prevClip);
-			CSDL_Ext::setClipRect(to, extRect); //preventing blitting outside of that rect
-
-			if(ADVOPT.smoothMove) //version for smooth hero move, with pos shifts
-			{
-				if (hvx<0 && hvy<0)
-				{
-					arrow->draw(to, x + moveX, y + moveY);
-				}
-				else if(hvx<0)
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width()));
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-				else if (hvy<0)
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height(), arrow->width() - hvx));
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-				else
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width() - hvx));
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-			}
-			else //standard version
-			{
-				if (hvx<0 && hvy<0)
-				{
-					arrow->draw(to, x, y);
-				}
-				else if(hvx<0)
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width()));
-					arrow->draw(to, x, y, &srcRect);
-				}
-				else if (hvy<0)
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height(), arrow->width() - hvx));
-					arrow->draw(to, x, y, &srcRect);
-				}
-				else
-				{
-					Rect srcRect (Point(0, 0), Point(arrow->height() - hvy, arrow->width() - hvx));
-					arrow->draw(to, x, y, &srcRect);
-				}
-			}
-			CSDL_Ext::setClipRect(to, prevClip);
-
-		}
-	} //for (int i=0;i<currentPath->nodes.size()-1;i++)
-}
-
-void CTerrainRect::show(SDL_Surface * to)
-{
-	if (adventureInt->mode == EAdvMapMode::NORMAL)
-	{
-		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos);
-		info.otherheroAnim = true;
-		info.anim = adventureInt->anim;
-		info.heroAnim = adventureInt->heroAnim;
-		if (ADVOPT.smoothMove)
-			info.movement = int3(moveX, moveY, 0);
-
-		lastRedrawStatus = CGI->mh->drawTerrainRectNew(to, &info);
-		if (fadeAnim->isFading())
-		{
-			Rect r(pos);
-			fadeAnim->update();
-			fadeAnim->draw(to, r.topLeft());
-		}
-
-		if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path
-		{
-			showPath(pos, to);
-		}
-	}
-}
-
-void CTerrainRect::showAll(SDL_Surface * to)
-{
-	// world view map is static and doesn't need redraw every frame
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-	{
-		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos, adventureInt->worldViewIcons);
-		info.scaled = true;
-		info.scale = adventureInt->worldViewScale;
-		adventureInt->worldViewOptions.adjustDrawingInfo(info);
-		CGI->mh->drawTerrainRectNew(to, &info);
-	}
-}
-
-void CTerrainRect::showAnim(SDL_Surface * to)
-{
-	if (!needsAnimUpdate())
-		return;
-
-	if (fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED)
-		show(to);
-}
-
-int3 CTerrainRect::whichTileIsIt(const int x, const int y)
-{
-	int3 ret;
-	ret.x = adventureInt->position.x + ((x-CGI->mh->offsetX-pos.x)/32);
-	ret.y = adventureInt->position.y + ((y-CGI->mh->offsetY-pos.y)/32);
-	ret.z = adventureInt->position.z;
-	return ret;
-}
-
-int3 CTerrainRect::whichTileIsIt()
-{
-	return whichTileIsIt(GH.getCursorPosition().x, GH.getCursorPosition().y);
-}
-
-Rect CTerrainRect::visibleTilesArea()
-{
-	switch (adventureInt->mode)
-	{
-	default:
-		logGlobal->error("Unknown map mode %d", (int)adventureInt->mode);
-		return Rect();
-	case EAdvMapMode::NORMAL:
-		return Rect(adventureInt->position.x, adventureInt->position.y, tilesw, tilesh);
-	case EAdvMapMode::WORLD_VIEW:
-		return Rect(adventureInt->position.x, adventureInt->position.y, tilesw / adventureInt->worldViewScale, tilesh / adventureInt->worldViewScale);
-	}
-}
-
-void CTerrainRect::fadeFromCurrentView()
-{
-	if (!ADVOPT.screenFading)
-		return;
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-
-	if (!fadeSurface)
-		fadeSurface = CSDL_Ext::newSurface(pos.w, pos.h);
-	CSDL_Ext::blitSurface(screen, fadeSurface, Point(0,0));
-	fadeAnim->init(CFadeAnimation::EMode::OUT, fadeSurface);
-}
-
-bool CTerrainRect::needsAnimUpdate()
-{
-	return fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED;
-}
-

+ 0 - 71
client/adventureMap/CTerrainRect.h

@@ -1,71 +0,0 @@
-/*
- * CTerrainRect.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 "../gui/CIntObject.h"
-#include "../../lib/int3.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-struct CGPath;
-VCMI_LIB_NAMESPACE_END
-
-enum class EMapAnimRedrawStatus;
-class CFadeAnimation;
-
-/// Holds information about which tiles of the terrain are shown/not shown at the screen
-class CTerrainRect : public CIntObject
-{
-	SDL_Surface * fadeSurface;
-	EMapAnimRedrawStatus lastRedrawStatus;
-	std::shared_ptr<CFadeAnimation> fadeAnim;
-
-	int3 swipeInitialMapPos;
-	Point swipeInitialRealPos;
-	bool isSwiping;
-	static constexpr float SwipeTouchSlop = 16.0f;
-
-	void handleHover(const Point & cursorPosition);
-	void handleSwipeMove(const Point & cursorPosition);
-	/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
-	bool handleSwipeStateChange(bool btnPressed);
-	int3 curHoveredTile;
-
-	int3 whichTileIsIt(const int x, const int y); //x,y are cursor position
-	int3 whichTileIsIt(); //uses current cursor pos
-	void showPath(const Rect &extRect, SDL_Surface * to);
-
-	bool needsAnimUpdate();
-public:
-	int tilesw, tilesh; //width and height of terrain to blit in tiles
-	int moveX, moveY; //shift between actual position of screen and the one we wil blit; ranges from -31 to 31 (in pixels)
-	CGPath * currentPath;
-
-	CTerrainRect();
-	~CTerrainRect();
-
-	// CIntObject interface implementation
-	void deactivate() override;
-	void clickLeft(tribool down, bool previousState) override;
-	void clickRight(tribool down, bool previousState) override;
-	void clickMiddle(tribool down, bool previousState) override;
-	void hover(bool on) override;
-	void mouseMoved (const Point & cursorPosition) override;
-	void show(SDL_Surface * to) override;
-	void showAll(SDL_Surface * to) override;
-
-	void showAnim(SDL_Surface * to);
-
-	/// @returns number of visible tiles on screen respecting current map scaling
-	Rect visibleTilesArea();
-
-	/// animates view by caching current surface and crossfading it with normal screen
-	void fadeFromCurrentView();
-};
-

+ 246 - 0
client/adventureMap/MapAudioPlayer.cpp

@@ -0,0 +1,246 @@
+/*
+ * MapAudioPlayer.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 "MapAudioPlayer.h"
+
+#include "../CCallback.h"
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CPlayerInterface.h"
+#include "../mapView/mapHandler.h"
+
+#include "../../lib/TerrainHandler.h"
+#include "../../lib/mapObjects/CArmedInstance.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/CMap.h"
+
+bool MapAudioPlayer::hasOngoingAnimations()
+{
+	return false;
+}
+
+void MapAudioPlayer::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if(obj == currentSelection)
+		update();
+}
+
+void MapAudioPlayer::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if(obj == currentSelection)
+		update();
+}
+
+void MapAudioPlayer::onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if(obj == currentSelection)
+		update();
+}
+
+void MapAudioPlayer::onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	if(obj == currentSelection)
+		update();
+}
+
+void MapAudioPlayer::onObjectFadeIn(const CGObjectInstance * obj)
+{
+	addObject(obj);
+}
+
+void MapAudioPlayer::onObjectFadeOut(const CGObjectInstance * obj)
+{
+	removeObject(obj);
+}
+
+void MapAudioPlayer::onObjectInstantAdd(const CGObjectInstance * obj)
+{
+	addObject(obj);
+}
+
+void MapAudioPlayer::onObjectInstantRemove(const CGObjectInstance * obj)
+{
+	removeObject(obj);
+}
+
+void MapAudioPlayer::addObject(const CGObjectInstance * obj)
+{
+	if(obj->isTile2Terrain())
+	{
+		// terrain overlay - all covering tiles act as sound source
+		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))
+					objects[currTile.z][currTile.x][currTile.y].push_back(obj->id);
+			}
+		}
+		return;
+	}
+
+	if(obj->isVisitable())
+	{
+		// visitable object - visitable tile acts as sound source
+		int3 currTile = obj->visitablePos();
+
+		if(LOCPLINT->cb->isInTheMap(currTile))
+			objects[currTile.z][currTile.x][currTile.y].push_back(obj->id);
+
+		return;
+	}
+
+	if(!obj->isVisitable())
+	{
+		// static object - blocking tiles act as sound source
+		auto tiles = obj->getBlockedOffsets();
+
+		for(const auto & tile : tiles)
+		{
+			int3 currTile = obj->pos + tile;
+
+			if(LOCPLINT->cb->isInTheMap(currTile))
+				objects[currTile.z][currTile.x][currTile.y].push_back(obj->id);
+		}
+		return;
+	}
+}
+
+void MapAudioPlayer::removeObject(const CGObjectInstance * obj)
+{
+	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], obj->id);
+}
+
+std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
+{
+	std::vector<std::string> result;
+
+	for(auto & objectID : objects[tile.z][tile.x][tile.y])
+	{
+		const auto & object = CGI->mh->getMap()->objects[objectID.getNum()];
+
+		if(object->getAmbientSound())
+			result.push_back(object->getAmbientSound().get());
+	}
+
+	if(CGI->mh->getMap()->isCoastalTile(tile))
+		result.emplace_back("LOOPOCEA");
+
+	return result;
+}
+
+void MapAudioPlayer::updateAmbientSounds()
+{
+	std::map<std::string, int> currentSounds;
+	auto updateSounds = [&](const std::string& soundId, int distance) -> void
+	{
+		if(vstd::contains(currentSounds, soundId))
+			currentSounds[soundId] = std::min(currentSounds[soundId], distance);
+		else
+			currentSounds.insert(std::make_pair(soundId, distance));
+	};
+
+	int3 pos = currentSelection->getSightCenter();
+	std::unordered_set<int3, ShashInt3> tiles;
+	LOCPLINT->cb->getVisibleTilesInRange(tiles, pos, CCS->soundh->ambientGetRange(), int3::DIST_CHEBYSHEV);
+	for(int3 tile : tiles)
+	{
+		int dist = pos.dist(tile, int3::DIST_CHEBYSHEV);
+
+		for(auto & soundName : getAmbientSounds(tile))
+			updateSounds(soundName, dist);
+	}
+	CCS->soundh->ambientUpdateChannels(currentSounds);
+}
+
+void MapAudioPlayer::updateMusic()
+{
+	if(audioPlaying && playerMakingTurn && currentSelection)
+	{
+		const auto * terrain = LOCPLINT->cb->getTile(currentSelection->visitablePos())->terType;
+		CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false);
+	}
+
+	if(audioPlaying && enemyMakingTurn)
+	{
+		CCS->musich->playMusicFromSet("enemy-turn", true, false);
+	}
+}
+
+void MapAudioPlayer::update()
+{
+	updateMusic();
+
+	if(audioPlaying && playerMakingTurn && currentSelection)
+		updateAmbientSounds();
+}
+
+MapAudioPlayer::MapAudioPlayer()
+{
+	auto mapSize = LOCPLINT->cb->getMapSize();
+
+	objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
+
+	for(const auto & obj : CGI->mh->getMap()->objects)
+		if (obj)
+			addObject(obj);
+}
+
+MapAudioPlayer::~MapAudioPlayer()
+{
+	CCS->soundh->ambientStopAllChannels();
+	CCS->musich->stopMusic(1000);
+}
+
+void MapAudioPlayer::onSelectionChanged(const CArmedInstance * newSelection)
+{
+	currentSelection = newSelection;
+	update();
+}
+
+void MapAudioPlayer::onAudioPaused()
+{
+	audioPlaying = false;
+	CCS->soundh->ambientStopAllChannels();
+	CCS->musich->stopMusic(1000);
+}
+
+void MapAudioPlayer::onAudioResumed()
+{
+	audioPlaying = true;
+	update();
+}
+
+void MapAudioPlayer::onPlayerTurnStarted()
+{
+	enemyMakingTurn = false;
+	playerMakingTurn = true;
+	update();
+}
+
+void MapAudioPlayer::onEnemyTurnStarted()
+{
+	playerMakingTurn = false;
+	enemyMakingTurn = true;
+	update();
+}
+
+void MapAudioPlayer::onPlayerTurnEnded()
+{
+	playerMakingTurn = false;
+	enemyMakingTurn = false;
+	CCS->soundh->ambientStopAllChannels();
+	CCS->musich->stopMusic(1000);
+}

+ 71 - 0
client/adventureMap/MapAudioPlayer.h

@@ -0,0 +1,71 @@
+/*
+ * MapAudioPlayer.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 "../mapView/IMapRendererObserver.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class ObjectInstanceID;
+class CArmedInstance;
+VCMI_LIB_NAMESPACE_END
+
+class MapAudioPlayer : public IMapObjectObserver
+{
+	using MapObjectsList = std::vector<ObjectInstanceID>;
+
+	boost::multi_array<MapObjectsList, 3> objects;
+	const CArmedInstance * currentSelection = nullptr;
+	bool playerMakingTurn = false;
+	bool enemyMakingTurn = false;
+	bool audioPlaying = true;
+
+	void addObject(const CGObjectInstance * obj);
+	void removeObject(const CGObjectInstance * obj);
+
+	std::vector<std::string> getAmbientSounds(const int3 & tile);
+	void updateAmbientSounds();
+	void updateMusic();
+	void update();
+
+protected:
+	// 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 onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+	void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
+
+public:
+	MapAudioPlayer();
+	~MapAudioPlayer() override;
+
+	/// Called whenever current adventure map selection changes
+	void onSelectionChanged(const CArmedInstance * newSelection);
+
+	/// Called when local player starts his turn
+	void onPlayerTurnStarted();
+
+	/// Called when AI or non-local player start his turn
+	void onEnemyTurnStarted();
+
+	/// Called when local player ends his turn
+	void onPlayerTurnEnded();
+
+	/// Called when map audio should be paused, e.g. on combat or town scren access
+	void onAudioPaused();
+
+	/// Called when map audio should be resume, opposite to onPaused
+	void onAudioResumed();
+};

+ 0 - 1486
client/adventureMap/mapHandler.cpp

@@ -1,1486 +0,0 @@
-/*
- * mapHandler.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 "mapHandler.h"
-
-#include "../render/CAnimation.h"
-#include "../render/CFadeAnimation.h"
-#include "../render/Colors.h"
-#include "../renderSDL/SDL_Extensions.h"
-#include "../CGameInfo.h"
-#include "../render/Graphics.h"
-#include "../render/IImage.h"
-#include "../CMusicHandler.h"
-
-#include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../lib/mapObjects/CObjectClassesHandler.h"
-#include "../../lib/mapping/CMap.h"
-#include "../../lib/Color.h"
-#include "../../lib/CConfigHandler.h"
-#include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/CStopWatch.h"
-#include "../../lib/CRandomGenerator.h"
-#include "../../lib/RoadHandler.h"
-#include "../../lib/RiverHandler.h"
-#include "../../lib/TerrainHandler.h"
-
-#define ADVOPT (conf.go()->ac)
-
-static bool objectBlitOrderSorter(const TerrainTileObject & a, const TerrainTileObject & b)
-{
-	return CMapHandler::compareObjectBlitOrder(a.obj, b.obj);
-}
-
-struct NeighborTilesInfo
-{
-	bool d7, //789
-		 d8, //456
-		 d9, //123
-		 d4,
-		 d5,
-		 d6,
-		 d1,
-		 d2,
-		 d3;
-	NeighborTilesInfo(const int3 & pos, const int3 & sizes, std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap)
-	{
-		auto getTile = [&](int dx, int dy)->bool
-		{
-			if ( dx + pos.x < 0 || dx + pos.x >= sizes.x
-			  || dy + pos.y < 0 || dy + pos.y >= sizes.y)
-				return false;
-
-			//FIXME: please do not read settings for every tile...
-			return settings["session"]["spectate"].Bool() ? true : (*visibilityMap)[pos.z][dx+pos.x][dy+pos.y];
-		};
-		d7 = getTile(-1, -1); //789
-		d8 = getTile( 0, -1); //456
-		d9 = getTile(+1, -1); //123
-		d4 = getTile(-1, 0);
-		d5 = (*visibilityMap)[pos.z][pos.x][pos.y];
-		d6 = getTile(+1, 0);
-		d1 = getTile(-1, +1);
-		d2 = getTile( 0, +1);
-		d3 = getTile(+1, +1);
-	}
-
-	bool areAllHidden() const
-	{
-		return !(d1 || d2 || d3 || d4 || d5 || d6 || d7 || d8 || d9);
-	}
-
-	int getBitmapID() const
-	{
-		//NOTE: some images have unused in VCMI pair (same blockmap but a bit different look)
-		// 0-1, 2-3, 4-5, 11-13, 12-14
-		static const int visBitmaps[256] = {
-			-1,  34,   4,   4,  22,  23,   4,   4,  36,  36,  38,  38,  47,  47,  38,  38, //16
-			 3,  25,  12,  12,   3,  25,  12,  12,   9,   9,   6,   6,   9,   9,   6,   6, //32
-			35,  39,  48,  48,  41,  43,  48,  48,  36,  36,  38,  38,  47,  47,  38,  38, //48
-			26,  49,  28,  28,  26,  49,  28,  28,   9,   9,   6,   6,   9,   9,   6,   6, //64
-			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //80
-			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //96
-			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //112
-			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //128
-			15,  17,  30,  30,  16,  19,  30,  30,  46,  46,  40,  40,  32,  32,  40,  40, //144
-			 2,  25,  12,  12,   2,  25,  12,  12,   9,   9,   6,   6,   9,   9,   6,   6, //160
-			18,  42,  31,  31,  20,  21,  31,  31,  46,  46,  40,  40,  32,  32,  40,  40, //176
-			26,  49,  28,  28,  26,  49,  28,  28,   9,   9,   6,   6,   9,   9,   6,   6, //192
-			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //208
-			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //224
-			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //240
-			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10  //256
-		};
-
-		return visBitmaps[d1 + d2 * 2 + d3 * 4 + d4 * 8 + d6 * 16 + d7 * 32 + d8 * 64 + d9 * 128]; // >=0 -> partial hide, <0 - full hide
-	}
-};
-
-void CMapHandler::prepareFOWDefs()
-{
-	//assume all frames in group 0
-	size_t size = graphics->fogOfWarFullHide->size(0);
-	FoWfullHide.resize(size);
-	for(size_t frame = 0; frame < size; frame++)
-		FoWfullHide[frame] = graphics->fogOfWarFullHide->getImage(frame);
-
-	//initialization of type of full-hide image
-	hideBitmap.resize(boost::extents[sizes.z][sizes.x][sizes.y]);
-	for (int i = 0; i < hideBitmap.num_elements(); i++)
-		hideBitmap.data()[i] = CRandomGenerator::getDefault().nextInt(size - 1);
-
-	size = graphics->fogOfWarPartialHide->size(0);
-	FoWpartialHide.resize(size);
-	for(size_t frame = 0; frame < size; frame++)
-		FoWpartialHide[frame] = graphics->fogOfWarPartialHide->getImage(frame);
-}
-
-EMapAnimRedrawStatus CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info, bool redrawOnlyAnim)
-{
-	assert(info);
-	bool hasActiveFade = updateObjectsFade();
-	resolveBlitter(info)->blit(targetSurface, info);
-	return hasActiveFade ? EMapAnimRedrawStatus::REDRAW_REQUESTED : EMapAnimRedrawStatus::OK;
-}
-
-void CMapHandler::initTerrainGraphics()
-{
-	auto loadFlipped = [](TFlippedAnimations & animation, TFlippedCache & cache, const std::map<std::string, std::string> & files)
-	{
-		//no rotation and basic setup
-		for(auto & type : files)
-		{
-			animation[type.first][0] = std::make_unique<CAnimation>(type.second);
-			animation[type.first][0]->preload();
-			const size_t views = animation[type.first][0]->size(0);
-			cache[type.first].resize(views);
-
-			for(int j = 0; j < views; j++)
-				cache[type.first][j][0] = animation[type.first][0]->getImage(j);
-		}
-
-		for(int rotation = 1; rotation < 4; rotation++)
-		{
-			for(auto & type : files)
-			{
-				animation[type.first][rotation] = std::make_unique<CAnimation>(type.second);
-				animation[type.first][rotation]->preload();
-				const size_t views = animation[type.first][rotation]->size(0);
-
-				for(int j = 0; j < views; j++)
-				{
-					auto image = animation[type.first][rotation]->getImage(j);
-
-					if(rotation == 2 || rotation == 3)
-						image->horizontalFlip();
-					if(rotation == 1 || rotation == 3)
-						image->verticalFlip();
-
-					cache[type.first][j][rotation] = image;
-				}
-			}
-		}
-	};
-	
-	std::map<std::string, std::string> terrainFiles;
-	std::map<std::string, std::string> riverFiles;
-	std::map<std::string, std::string> roadFiles;
-	for(const auto & terrain : VLC->terrainTypeHandler->objects)
-	{
-		terrainFiles[terrain->getJsonKey()] = terrain->tilesFilename;
-	}
-	for(const auto & river : VLC->riverTypeHandler->objects)
-	{
-		riverFiles[river->getJsonKey()] = river->tilesFilename;
-	}
-	for(const auto & road : VLC->roadTypeHandler->objects)
-	{
-		roadFiles[road->getJsonKey()] = road->tilesFilename;
-	}
-	
-	loadFlipped(terrainAnimations, terrainImages, terrainFiles);
-	loadFlipped(riverAnimations, riverImages, riverFiles);
-	loadFlipped(roadAnimations, roadImages, roadFiles);
-
-	// Create enough room for the whole map and its frame
-
-	//ttiles.resize(sizes.x, frameW, frameW);
-
-	//FIXME: why do we even handle array with z (surface and undeground) at the same time?
-
-	ttiles.resize(boost::extents[sizes.z][sizes.x + 2 * frameW][sizes.y + 2 * frameH]); 
-	ttiles.reindex(std::list<int>{ 0, -frameW, -frameH }); //need to move starting coordinates so that used index is always positive
-}
-
-void CMapHandler::initBorderGraphics()
-{
-	egdeImages.resize(egdeAnimation->size(0));
-	for(size_t i = 0; i < egdeImages.size(); i++)
-		egdeImages[i] = egdeAnimation->getImage(i);
-
-	edgeFrames.resize(sizes.x, frameW, frameW);
-	for (int i=0-frameW;i<edgeFrames.size()-frameW;i++)
-	{
-		edgeFrames[i].resize(sizes.y, frameH, frameH);
-	}
-	for (int i=0-frameW;i<edgeFrames.size()-frameW;i++)
-	{
-		for (int j=0-frameH;j<(int)sizes.y+frameH;j++)
-			edgeFrames[i][j].resize(sizes.z, 0, 0);
-	}
-
-	auto & rand = CRandomGenerator::getDefault();
-
-	for (int i=0-frameW; i<sizes.x+frameW; i++) //by width
-	{
-		for (int j=0-frameH; j<sizes.y+frameH;j++) //by height
-		{
-			for(int k=0; k<sizes.z; ++k) //by levels
-			{
-				ui8 terBitmapNum = 0;
-				if(i < 0 || i > (sizes.x-1) || j < 0  || j > (sizes.y-1))
-				{
-					if(i==-1 && j==-1)
-						terBitmapNum = 16;
-					else if(i==-1 && j==(sizes.y))
-						terBitmapNum = 19;
-					else if(i==(sizes.x) && j==-1)
-						terBitmapNum = 17;
-					else if(i==(sizes.x) && j==(sizes.y))
-						terBitmapNum = 18;
-					else if(j == -1 && i > -1 && i < sizes.x)
-						terBitmapNum = rand.nextInt(22, 23);
-					else if(i == -1 && j > -1 && j < sizes.y)
-						terBitmapNum = rand.nextInt(33, 34);
-					else if(j == sizes.y && i >-1 && i < sizes.x)
-						terBitmapNum = rand.nextInt(29, 30);
-					else if(i == sizes.x && j > -1 && j < sizes.y)
-						terBitmapNum = rand.nextInt(25, 26);
-					else
-						terBitmapNum = rand.nextInt(15);
-
-				}
-				edgeFrames[i][j][k] = terBitmapNum;
-			}
-		}
-	}
-}
-
-void CMapHandler::initObjectRects()
-{
-	//initializing objects / rects
-	for(auto & elem : map->objects)
-	{
-		const CGObjectInstance *obj = elem;
-		if(	!obj
-			|| (obj->ID==Obj::HERO && static_cast<const CGHeroInstance*>(obj)->inTownGarrison) //garrisoned hero
-			|| (obj->ID==Obj::BOAT && static_cast<const CGBoat*>(obj)->hero)) //boat with hero (hero graphics is used)
-		{
-			continue;
-		}
-
-		std::shared_ptr<CAnimation> animation = graphics->getAnimation(obj);
-
-		//no animation at all
-		if(!animation)
-			continue;
-
-		//empty animation
-		if(animation->size(0) == 0)
-			continue;
-
-		auto image = animation->getImage(0,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);
-				Rect cr;
-				cr.w = 32;
-				cr.h = 32;
-				cr.x = image->width() - fx * 32 - 32;
-				cr.y = image->height() - fy * 32 - 32;
-				TerrainTileObject toAdd(obj, cr, obj->visitableAt(currTile.x, currTile.y));
-
-
-				if( map->isInTheMap(currTile) && // within map
-					cr.x + cr.w > 0 &&           // image has data on this tile
-					cr.y + cr.h > 0 &&
-					obj->coveringAt(currTile.x, currTile.y) // object is visible here
-				  )
-				{
-					ttiles[currTile.z][currTile.x][currTile.y].objects.push_back(toAdd);
-				}
-			}
-		}
-	}
-
-	auto shape = ttiles.shape();
-	for(size_t z = 0; z < shape[0]; z++)
-	{
-		for(size_t x = 0; x < shape[1] - frameW; x++)
-		{
-			for(size_t y = 0; y < shape[2] - frameH; y++)
-			{
-				auto & objects = ttiles[z][x][y].objects;
-				stable_sort(objects.begin(), objects.end(), objectBlitOrderSorter);
-			}
-		}
-	}
-}
-
-void CMapHandler::init()
-{
-	CStopWatch th;
-	th.getDiff();
-
-	// Size of visible terrain.
-	int mapW = conf.go()->ac.advmapW;
-	int mapH = conf.go()->ac.advmapH;
-
-	//sizes of terrain
-	sizes.x = map->width;
-	sizes.y = map->height;
-	sizes.z = map->levels();
-
-	// Total number of visible tiles. Subtract the center tile, then
-	// compute the number of tiles on each side, and reassemble.
-	int t1, t2;
-	t1 = (mapW-32)/2;
-	t2 = mapW - 32 - t1;
-	tilesW = 1 + (t1+31)/32 + (t2+31)/32;
-
-	t1 = (mapH-32)/2;
-	t2 = mapH - 32 - t1;
-	tilesH = 1 + (t1+31)/32 + (t2+31)/32;
-
-	// Size of the frame around the map. In extremes positions, the
-	// frame must not be on the center of the map, but right on the
-	// edge of the center tile.
-	frameW = (mapW+31) /32 / 2;
-	frameH = (mapH+31) /32 / 2;
-
-	offsetX = (mapW - (2*frameW+1)*32)/2;
-	offsetY = (mapH - (2*frameH+1)*32)/2;
-
-	prepareFOWDefs();
-	initTerrainGraphics();
-	initBorderGraphics();
-	logGlobal->info("\tPreparing FoW, terrain, roads, rivers, borders: %d ms", th.getDiff());
-	initObjectRects();
-	logGlobal->info("\tMaking object rects: %d ms", th.getDiff());
-}
-
-CMapBlitter *CMapHandler::resolveBlitter(const MapDrawingInfo * info) const
-{
-	if (info->scaled)
-		return worldViewBlitter;
-	if (info->puzzleMode)
-		return puzzleViewBlitter;
-
-	return normalBlitter;
-}
-
-void CMapNormalBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr<IImage> source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const
-{
-	source->draw(targetSurf, destRect, sourceRect);
-}
-
-void CMapNormalBlitter::init(const MapDrawingInfo * drawingInfo)
-{
-	info = drawingInfo;
-	// Width and height of the portion of the map to process. Units in tiles.
-	tileCount.x = parent->tilesW;
-	tileCount.y = parent->tilesH;
-
-	topTile = info->topTile;
-	initPos.x = parent->offsetX + info->drawBounds.x;
-	initPos.y = parent->offsetY + info->drawBounds.y;
-
-	realTileRect = Rect(initPos.x, initPos.y, tileSize, tileSize);
-
-	// If moving, we need to add an extra column/line
-	if (info->movement.x != 0)
-	{
-		tileCount.x++;
-		initPos.x += info->movement.x;
-		if (info->movement.x > 0)
-		{
-			// Moving right. We still need to draw the old tile on the
-			// left, so adjust our referential
-			topTile.x--;
-			initPos.x -= tileSize;
-		}
-	}
-
-	if (info->movement.y != 0)
-	{
-		tileCount.y++;
-		initPos.y += info->movement.y;
-		if (info->movement.y > 0)
-		{
-			// Moving down. We still need to draw the tile on the top,
-			// so adjust our referential.
-			topTile.y--;
-			initPos.y -= tileSize;
-		}
-	}
-
-	// Reduce sizes if we go out of the full map.
-	if (topTile.x < -parent->frameW)
-		topTile.x = -parent->frameW;
-	if (topTile.y < -parent->frameH)
-		topTile.y = -parent->frameH;
-	if (topTile.x + tileCount.x > parent->sizes.x + parent->frameW)
-		tileCount.x = parent->sizes.x + parent->frameW - topTile.x;
-	if (topTile.y + tileCount.y > parent->sizes.y + parent->frameH)
-		tileCount.y = parent->sizes.y + parent->frameH - topTile.y;
-}
-
-Rect CMapNormalBlitter::clip(SDL_Surface * targetSurf) const
-{
-	Rect prevClip;
-	CSDL_Ext::getClipRect(targetSurf, prevClip);
-	CSDL_Ext::setClipRect(targetSurf, info->drawBounds);
-	return prevClip;
-}
-
-CMapNormalBlitter::CMapNormalBlitter(CMapHandler * parent)
-	: CMapBlitter(parent)
-{
-	tileSize = 32;
-	halfTileSizeCeil = 16;
-	defaultTileRect = Rect(0, 0, tileSize, tileSize);
-}
-
-std::shared_ptr<IImage> CMapWorldViewBlitter::objectToIcon(Obj id, si32 subId, PlayerColor owner) const
-{
-	int ownerIndex = 0;
-	if(owner < PlayerColor::PLAYER_LIMIT)
-	{
-		ownerIndex = owner.getNum() * 19;
-	}
-	else if (owner == PlayerColor::NEUTRAL)
-	{
-		ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * 19;
-	}
-
-	switch(id)
-	{
-	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
-	case Obj::MONOLITH_ONE_WAY_EXIT:
-	case Obj::MONOLITH_TWO_WAY:
-		return info->icons->getImage((int)EWorldViewIcon::TELEPORT);
-	case Obj::SUBTERRANEAN_GATE:
-		return info->icons->getImage((int)EWorldViewIcon::GATE);
-	case Obj::ARTIFACT:
-		return info->icons->getImage((int)EWorldViewIcon::ARTIFACT);
-	case Obj::TOWN:
-		return info->icons->getImage((int)EWorldViewIcon::TOWN + ownerIndex);
-	case Obj::HERO:
-		return info->icons->getImage((int)EWorldViewIcon::HERO + ownerIndex);
-	case Obj::MINE:
-		return info->icons->getImage((int)EWorldViewIcon::MINE_WOOD + subId + ownerIndex);
-	case Obj::RESOURCE:
-		return info->icons->getImage((int)EWorldViewIcon::RES_WOOD + subId + ownerIndex);
-	}
-	return std::shared_ptr<IImage>();
-}
-
-void CMapWorldViewBlitter::calculateWorldViewCameraPos()
-{
-	bool outsideLeft = topTile.x < 0;
-	bool outsideTop = topTile.y < 0;
-	bool outsideRight = std::max(0, topTile.x) + tileCount.x > parent->sizes.x;
-	bool outsideBottom = std::max(0, topTile.y) + tileCount.y > parent->sizes.y;
-
-	if (tileCount.x > parent->sizes.x)
-		topTile.x = parent->sizes.x / 2 - tileCount.x / 2; // center viewport if the whole map can fit into the screen at once
-	else if (outsideLeft)
-	{
-		if (outsideRight)
-			topTile.x = parent->sizes.x / 2 - tileCount.x / 2;
-		else
-			topTile.x = 0;
-	}
-	else if (outsideRight)
-		topTile.x = parent->sizes.x - tileCount.x;
-
-	if (tileCount.y > parent->sizes.y)
-		topTile.y = parent->sizes.y / 2 - tileCount.y / 2;
-	else if (outsideTop)
-	{
-		if (outsideBottom)
-			topTile.y = parent->sizes.y / 2 - tileCount.y / 2;
-		else
-			topTile.y = 0;
-	}
-	else if (outsideBottom)
-		topTile.y = parent->sizes.y - tileCount.y;
-}
-
-void CMapWorldViewBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr<IImage> source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const
-{
-	auto scaled = parent->cache.requestWorldViewCacheOrCreate(cacheType, source);
-
-	if(scaled)
-		scaled->draw(targetSurf, destRect, sourceRect);
-}
-
-void CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const
-{
-	auto drawIcon = [this,targetSurf](Obj id, si32 subId, PlayerColor owner)
-	{
-		auto wvIcon = this->objectToIcon(id, subId, owner);
-
-		if(nullptr != wvIcon)
-		{
-			// centering icon on the object
-			Point dest(realPos.x + tileSize / 2 - wvIcon->width() / 2, realPos.y + tileSize / 2 - wvIcon->height() / 2);
-			wvIcon->draw(targetSurf, dest.x, dest.y);
-		}
-	};
-
-	auto & objects = tile.objects;
-	for(auto & object : objects)
-	{
-		const CGObjectInstance * obj = object.obj;
-		if(!obj)
-			continue;
-
-		const bool sameLevel = obj->pos.z == pos.z;
-
-		//FIXME: Don't read options in  a loop :v
-		const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.z][pos.x][pos.y];
-		const bool isVisitable = obj->visitableAt(pos.x, pos.y);
-
-		if(sameLevel && isVisible && isVisitable)
-			drawIcon(obj->ID, obj->subID, obj->tempOwner);
-	}
-}
-
-void CMapWorldViewBlitter::drawOverlayEx(SDL_Surface * targetSurf)
-{
-	if(nullptr == info->additionalIcons)
-		return;
-
-	const int3 bottomRight = pos + tileCount;
-
-	for(const ObjectPosInfo & iconInfo : *(info->additionalIcons))
-	{
-		if( iconInfo.pos.z != pos.z)
-			continue;
-
-		if((iconInfo.pos.x < topTile.x) || (iconInfo.pos.y < topTile.y))
-			continue;
-
-		if((iconInfo.pos.x > bottomRight.x) || (iconInfo.pos.y > bottomRight.y))
-			continue;
-
-		realPos.x = initPos.x + (iconInfo.pos.x - topTile.x) * tileSize;
-		realPos.y = initPos.y + (iconInfo.pos.y - topTile.y) * tileSize;
-
-		auto wvIcon = this->objectToIcon(iconInfo.id, iconInfo.subId, iconInfo.owner);
-
-		if(nullptr != wvIcon)
-		{
-			// centering icon on the object
-			Point dest(realPos.x + tileSize / 2 - wvIcon->width() / 2, realPos.y + tileSize / 2 - wvIcon->height() / 2);
-			wvIcon->draw(targetSurf, dest.x, dest.y);
-		}
-	}
-}
-
-void CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, Rect * destRect, bool moving) const
-{
-	if (moving)
-		return;
-
-	CMapBlitter::drawHeroFlag(targetSurf, source, sourceRect, destRect, false);
-}
-
-void CMapWorldViewBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, bool moving) const
-{
-	if (moving)
-		return;
-
-	Rect scaledSourceRect((int)(sourceRect->x * info->scale), (int)(sourceRect->y * info->scale), sourceRect->w, sourceRect->h);
-	CMapBlitter::drawObject(targetSurf, source, &scaledSourceRect, false);
-}
-
-void CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile2 & tile) const
-{
-	Rect destRect(realTileRect);
-
-	ui8 rotation = tinfo.extTileFlags % 4;
-	
-	//TODO: use ui8 instead of string key
-	auto terrainName = tinfo.terType->getJsonKey();
-
-	if(parent->terrainImages[terrainName].size()<=tinfo.terView)
-		return;
-
-	drawElement(EMapCacheType::TERRAIN, parent->terrainImages[terrainName][tinfo.terView][rotation], nullptr, targetSurf, &destRect);
-}
-
-void CMapWorldViewBlitter::init(const MapDrawingInfo * drawingInfo)
-{
-	info = drawingInfo;
-	parent->cache.updateWorldViewScale(info->scale);
-
-	topTile = info->topTile;
-	tileSize = (int) floorf(32.0f * info->scale);
-	halfTileSizeCeil = (int)ceilf(tileSize / 2.0f);
-
-	tileCount.x = (int) ceilf((float)info->drawBounds.w / tileSize);
-	tileCount.y = (int) ceilf((float)info->drawBounds.h / tileSize);
-
-	initPos.x = info->drawBounds.x;
-	initPos.y = info->drawBounds.y;
-
-	realTileRect = Rect(initPos.x, initPos.y, tileSize, tileSize);
-	defaultTileRect = Rect(0, 0, tileSize, tileSize);
-
-	calculateWorldViewCameraPos();
-}
-
-Rect CMapWorldViewBlitter::clip(SDL_Surface * targetSurf) const
-{
-	Rect prevClip;
-
-	CSDL_Ext::fillRect(targetSurf, info->drawBounds, Colors::BLACK);
-	// makes the clip area smaller if the map is smaller than the screen frame
-	// (actually, it could be made 1 tile bigger so that overlay icons on edge tiles could be drawn partly outside)
-	Rect clipRect(std::max<int>(info->drawBounds.x, info->drawBounds.x - topTile.x * tileSize),
-				  std::max<int>(info->drawBounds.y, info->drawBounds.y - topTile.y * tileSize),
-				  std::min<int>(info->drawBounds.w, parent->sizes.x * tileSize),
-				  std::min<int>(info->drawBounds.h, parent->sizes.y * tileSize));
-	CSDL_Ext::getClipRect(targetSurf, prevClip);
-	CSDL_Ext::setClipRect(targetSurf, clipRect); //preventing blitting outside of that rect
-	return prevClip;
-}
-
-CMapWorldViewBlitter::CMapWorldViewBlitter(CMapHandler * parent)
-	: CMapBlitter(parent)
-{
-}
-
-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);
-}
-
-CMapBlitter::CMapBlitter(CMapHandler * p)
-	:parent(p), tileSize(0), halfTileSizeCeil(0), info(nullptr)
-{
-
-}
-
-CMapBlitter::~CMapBlitter() = default;
-
-void CMapBlitter::drawFrame(SDL_Surface * targetSurf) const
-{
-	Rect destRect(realTileRect);
-	drawElement(EMapCacheType::FRAME, parent->egdeImages[parent->edgeFrames[pos.x][pos.y][topTile.z]], nullptr, targetSurf, &destRect);
-}
-
-void CMapBlitter::drawOverlayEx(SDL_Surface * targetSurf)
-{
-//nothing to do here
-}
-
-void CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, Rect * destRect, bool moving) const
-{
-	drawElement(EMapCacheType::HERO_FLAGS, source, sourceRect, targetSurf, destRect);
-}
-
-void CMapBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, bool moving) const
-{
-	Rect dstRect(realTileRect);
-	drawElement(EMapCacheType::OBJECTS, source, sourceRect, targetSurf, &dstRect);
-}
-
-void CMapBlitter::drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const
-{
-	auto & objects = tile.objects;
-	for(auto & object : objects)
-	{
-		if (object.fadeAnimKey >= 0)
-		{
-			auto fadeIter = parent->fadeAnims.find(object.fadeAnimKey);
-			if (fadeIter != parent->fadeAnims.end())
-			{
-				// this object is currently fading, so skip normal drawing
-				Rect r2(realTileRect);
-				CFadeAnimation * fade = (*fadeIter).second.second;
-				fade->draw(targetSurf, r2.topLeft());
-				continue;
-			}
-			logGlobal->error("Fading map object with missing fade anim : %d", object.fadeAnimKey);
-			continue;
-		}
-
-		const CGObjectInstance * obj = object.obj;
-		if (!obj)
-		{
-			logGlobal->error("Stray map object that isn't fading");
-			continue;
-		}
-
-		if (!canDrawObject(obj))
-			continue;
-
-		uint8_t animationFrame;
-		if(obj->ID == Obj::HERO) //non-generic animation frame pick for hero and boat
-			animationFrame = info->heroAnim;
-		else
-			animationFrame = info->anim;
-
-		auto objData = findObjectBitmap(obj, animationFrame);
-		if (objData.objBitmap)
-		{
-			Rect srcRect(object.rect.x, object.rect.y, tileSize, tileSize);
-
-			drawObject(targetSurf, objData.objBitmap, &srcRect, objData.isMoving);
-			if (objData.flagBitmap)
-			{
-				if (objData.isMoving)
-				{
-					srcRect.y += FRAMES_PER_MOVE_ANIM_GROUP * 2 - tileSize;
-					Rect dstRect(realPos.x, realPos.y - tileSize / 2, tileSize, tileSize);
-					drawHeroFlag(targetSurf, objData.flagBitmap, &srcRect, &dstRect, true);
-				}
-				else if (obj->pos.x == pos.x && obj->pos.y == pos.y)
-				{
-					Rect dstRect(realPos.x - 2 * tileSize, realPos.y - tileSize, 3 * tileSize, 2 * tileSize);
-					drawHeroFlag(targetSurf, objData.flagBitmap, nullptr, &dstRect, false);
-				}
-			}
-		}
-	}
-}
-
-void CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const
-{
-	if (tinfoUpper && tinfoUpper->roadType->getId() != Road::NO_ROAD)
-	{
-		ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4;
-		Rect source(0, tileSize / 2, tileSize, tileSize / 2);
-		Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2);
-		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->getJsonKey()][tinfoUpper->roadDir][rotation],
-				&source, targetSurf, &dest);
-	}
-
-	if(tinfo.roadType->getId() != Road::NO_ROAD) //print road from this tile
-	{
-		ui8 rotation = (tinfo.extTileFlags >> 4) % 4;
-		Rect source(0, 0, tileSize, halfTileSizeCeil);
-		Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2);
-		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->getJsonKey()][tinfo.roadDir][rotation],
-				&source, targetSurf, &dest);
-	}
-}
-
-void CMapBlitter::drawRiver(SDL_Surface * targetSurf, const TerrainTile & tinfo) const
-{
-	Rect destRect(realTileRect);
-	ui8 rotation = (tinfo.extTileFlags >> 2) % 4;
-	drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->getJsonKey()][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
-}
-
-void CMapBlitter::drawFow(SDL_Surface * targetSurf) const
-{
-	const NeighborTilesInfo neighborInfo(pos, parent->sizes, info->visibilityMap);
-
-	int retBitmapID = neighborInfo.getBitmapID();// >=0 -> partial hide, <0 - full hide
-	if (retBitmapID < 0)
-		retBitmapID = - parent->hideBitmap[pos.z][pos.x][pos.y] - 1; //fully hidden
-
-	std::shared_ptr<IImage> image;
-
-	if (retBitmapID >= 0)
-		image = parent->FoWpartialHide.at(retBitmapID);
-	else
-		image = parent->FoWfullHide.at(-retBitmapID - 1);
-
-	Rect destRect(realTileRect);
-	drawElement(EMapCacheType::FOW, image, nullptr, targetSurf, &destRect);
-}
-
-void CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingInfo * info)
-{
-	init(info);
-	auto prevClip = clip(targetSurf);
-
-	pos = int3(0, 0, topTile.z);
-
-	for (realPos.x = initPos.x, pos.x = topTile.x; pos.x < topTile.x + tileCount.x; pos.x++, realPos.x += tileSize)
-	{
-		if (pos.x < 0 || pos.x >= parent->sizes.x)
-			continue;
-
-		for (realPos.y = initPos.y, pos.y = topTile.y; pos.y < topTile.y + tileCount.y; pos.y++, realPos.y += tileSize)
-		{
-			if (pos.y < 0 || pos.y >= parent->sizes.y)
-				continue;
-
-			const bool isVisible = canDrawCurrentTile();
-
-			realTileRect.x = realPos.x;
-			realTileRect.y = realPos.y;
-
-			const TerrainTile2 & tile = parent->ttiles[pos.z][pos.x][pos.y];
-			const TerrainTile & tinfo = parent->map->getTile(pos);
-			const TerrainTile * tinfoUpper = pos.y > 0 ? &parent->map->getTile(int3(pos.x, pos.y - 1, pos.z)) : nullptr;
-
-			if(isVisible || info->showAllTerrain)
-			{
-				drawTileTerrain(targetSurf, tinfo, tile);
-				if(tinfo.riverType->getId() != River::NO_RIVER)
-					drawRiver(targetSurf, tinfo);
-				drawRoad(targetSurf, tinfo, tinfoUpper);
-			}
-
-			if(isVisible)
-				drawObjects(targetSurf, tile);
-		}
-	}
-
-	for (realPos.x = initPos.x, pos.x = topTile.x; pos.x < topTile.x + tileCount.x; pos.x++, realPos.x += tileSize)
-	{
-		for (realPos.y = initPos.y, pos.y = topTile.y; pos.y < topTile.y + tileCount.y; pos.y++, realPos.y += tileSize)
-		{
-			realTileRect.x = realPos.x;
-			realTileRect.y = realPos.y;
-
-			if (pos.x < 0 || pos.x >= parent->sizes.x ||
-				pos.y < 0 || pos.y >= parent->sizes.y)
-			{
-				drawFrame(targetSurf);
-			}
-			else
-			{
-				const TerrainTile2 & tile = parent->ttiles[pos.z][pos.x][pos.y];
-
-				if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[topTile.z][pos.x][pos.y] && !info->showAllTerrain)
-					drawFow(targetSurf);
-
-				// overlay needs to be drawn over fow, because of artifacts-aura-like spells
-				drawTileOverlay(targetSurf, tile);
-
-				// drawDebugVisitables()
-				if (settings["session"]["showBlock"].Bool())
-				{
-					if(parent->map->getTile(int3(pos.x, pos.y, pos.z)).blocked) //temporary hiding blocked positions
-					{
-						static std::shared_ptr<IImage> block;
-						if (!block)
-							block = IImage::createFromFile("blocked");
-
-						block->draw(targetSurf, realTileRect.x, realTileRect.y);
-					}
-				}
-				if (settings["session"]["showVisit"].Bool())
-				{
-					if(parent->map->getTile(int3(pos.x, pos.y, pos.z)).visitable) //temporary hiding visitable positions
-					{
-						static std::shared_ptr<IImage> visit;
-						if (!visit)
-							visit = IImage::createFromFile("visitable");
-
-						visit->draw(targetSurf, realTileRect.x, realTileRect.y);
-					}
-				}
-			}
-		}
-	}
-
-	drawOverlayEx(targetSurf);
-
-	// drawDebugGrid()
-	if (settings["gameTweaks"]["showGrid"].Bool())
-	{
-		for (realPos.x = initPos.x, pos.x = topTile.x; pos.x < topTile.x + tileCount.x; pos.x++, realPos.x += tileSize)
-		{
-			for (realPos.y = initPos.y, pos.y = topTile.y; pos.y < topTile.y + tileCount.y; pos.y++, realPos.y += tileSize)
-			{
-				constexpr ColorRGBA color(0x55, 0x55, 0x55);
-
-				if (realPos.y >= info->drawBounds.y &&
-					realPos.y < info->drawBounds.y + info->drawBounds.h)
-					for(int i = 0; i < tileSize; i++)
-						if (realPos.x + i >= info->drawBounds.x &&
-							realPos.x + i < info->drawBounds.x + info->drawBounds.w)
-							CSDL_Ext::putPixelWithoutRefresh(targetSurf, realPos.x + i, realPos.y, color.r, color.g, color.b);
-
-				if (realPos.x >= info->drawBounds.x &&
-					realPos.x < info->drawBounds.x + info->drawBounds.w)
-					for(int i = 0; i < tileSize; i++)
-						if (realPos.y + i >= info->drawBounds.y &&
-							realPos.y + i < info->drawBounds.y + info->drawBounds.h)
-							CSDL_Ext::putPixelWithoutRefresh(targetSurf, realPos.x, realPos.y + i, color.r, color.g, color.b);
-			}
-		}
-	}
-
-	postProcessing(targetSurf);
-
-	CSDL_Ext::setClipRect(targetSurf, prevClip);
-}
-
-AnimBitmapHolder CMapBlitter::findHeroBitmap(const CGHeroInstance * hero, int anim) const
-{
-	if(hero && hero->moveDir && hero->type) //it's hero or boat
-	{
-		if(hero->tempOwner >= PlayerColor::PLAYER_LIMIT) //Neutral hero?
-		{
-			logGlobal->error("A neutral hero (%s) at %s. Should not happen!", hero->getNameTranslated(), hero->pos.toString());
-			return AnimBitmapHolder();
-		}
-
-		//pick graphics of hero (or boat if hero is sailing)
-		std::shared_ptr<CAnimation> animation;
-		if (hero->boat)
-			animation = graphics->boatAnimations[hero->boat->subID];
-		else
-			animation = graphics->heroAnimations[hero->appearance->animationFile];
-
-		bool moving = !hero->isStanding;
-		int group = getHeroFrameGroup(hero->moveDir, moving);
-
-		if(animation->size(group) > 0)
-		{
-			int frame = anim % animation->size(group);
-			auto heroImage = animation->getImage(frame, group);
-
-			//get flag overlay only if we have main image
-			auto flagImage = findFlagBitmap(hero, anim, &hero->tempOwner, group);
-
-			return AnimBitmapHolder(heroImage, flagImage, moving);
-		}
-	}
-	return AnimBitmapHolder();
-}
-
-AnimBitmapHolder CMapBlitter::findBoatBitmap(const CGBoat * boat, int anim) const
-{
-	auto animation = graphics->boatAnimations.at(boat->subID);
-	int group = getHeroFrameGroup(boat->direction, false);
-	if(animation->size(group) > 0)
-		return AnimBitmapHolder(animation->getImage(anim % animation->size(group), group));
-	else
-		return AnimBitmapHolder();
-}
-
-std::shared_ptr<IImage> CMapBlitter::findFlagBitmap(const CGHeroInstance * hero, int anim, const PlayerColor * color, int group) const
-{
-	if(!hero)
-		return std::shared_ptr<IImage>();
-
-	if(hero->boat)
-		return findBoatFlagBitmap(hero->boat, anim, color, group, hero->moveDir);
-	return findHeroFlagBitmap(hero, anim, color, group);
-}
-
-std::shared_ptr<IImage> CMapBlitter::findHeroFlagBitmap(const CGHeroInstance * hero, int anim, const PlayerColor * color, int group) const
-{
-	return findFlagBitmapInternal(graphics->heroFlagAnimations.at(color->getNum()), anim, group, hero->moveDir, !hero->isStanding);
-}
-
-std::shared_ptr<IImage> CMapBlitter::findBoatFlagBitmap(const CGBoat * boat, int anim, const PlayerColor * color, int group, ui8 dir) const
-{
-	int boatType = boat->subID;
-	if(boatType < 0 || boatType >= graphics->boatFlagAnimations.size())
-	{
-		logGlobal->error("Not supported boat subtype: %d", boat->subID);
-		return nullptr;
-	}
-
-	const auto & subtypeFlags = graphics->boatFlagAnimations.at(boatType);
-
-	int colorIndex = color->getNum();
-
-	if(colorIndex < 0 || colorIndex >= subtypeFlags.size())
-	{
-		logGlobal->error("Invalid player color %d", colorIndex);
-		return nullptr;
-	}
-
-	return findFlagBitmapInternal(subtypeFlags.at(colorIndex), anim, group, dir, false);
-}
-
-std::shared_ptr<IImage> CMapBlitter::findFlagBitmapInternal(std::shared_ptr<CAnimation> animation, int anim, int group, ui8 dir, bool moving) const
-{
-	size_t groupSize = animation->size(group);
-	if(groupSize == 0)
-		return nullptr;
-
-	if(moving)
-		return animation->getImage(anim % groupSize, group);
-	else
-		return animation->getImage((anim / 4) % groupSize, group);
-}
-
-AnimBitmapHolder CMapBlitter::findObjectBitmap(const CGObjectInstance * obj, int anim) const
-{
-	if (!obj)
-		return AnimBitmapHolder();
-	if (obj->ID == Obj::HERO)
-		return findHeroBitmap(static_cast<const CGHeroInstance*>(obj), anim);
-	if (obj->ID == Obj::BOAT)
-		return findBoatBitmap(static_cast<const CGBoat*>(obj), anim);
-
-	// normal object
-	std::shared_ptr<CAnimation> animation = graphics->getAnimation(obj);
-	size_t groupSize = animation->size();
-	if(groupSize == 0)
-		return AnimBitmapHolder();
-
-	auto bitmap = animation->getImage((anim + getPhaseShift(obj)) % groupSize);
-	if(!bitmap)
-		return AnimBitmapHolder();
-
-	bitmap->setFlagColor(obj->tempOwner);
-
-	return AnimBitmapHolder(bitmap);
-}
-
-ui8 CMapBlitter::getPhaseShift(const CGObjectInstance *object) const
-{
-	auto i = parent->animationPhase.find(object);
-	if(i == parent->animationPhase.end())
-	{
-		ui8 ret = CRandomGenerator::getDefault().nextInt(254);
-		parent->animationPhase[object] = ret;
-		return ret;
-	}
-
-	return i->second;
-}
-
-bool CMapBlitter::canDrawObject(const CGObjectInstance * obj) const
-{
-	//checking if object has non-empty graphic on this tile
-	return obj->ID == Obj::HERO || obj->coveringAt(pos.x, pos.y);
-}
-
-bool CMapBlitter::canDrawCurrentTile() const
-{
-	if(settings["session"]["spectate"].Bool())
-		return true;
-
-	const NeighborTilesInfo neighbors(pos, parent->sizes, info->visibilityMap);
-	return !neighbors.areAllHidden();
-}
-
-ui8 CMapBlitter::getHeroFrameGroup(ui8 dir, bool isMoving) const
-{
-	if(isMoving)
-	{
-		static const ui8 frame [] = {0xff, 10, 5, 6, 7, 8, 9, 12, 11};
-		return frame[dir];
-	}
-	else //if(isMoving)
-	{
-		static const ui8 frame [] = {0xff, 13, 0, 1, 2, 3, 4, 15, 14};
-		return frame[dir];
-	}
-}
-
-bool CMapHandler::updateObjectsFade()
-{
-	for (auto iter = fadeAnims.begin(); iter != fadeAnims.end(); )
-	{
-		int3 pos = (*iter).second.first;
-		CFadeAnimation * anim = (*iter).second.second;
-
-		anim->update();
-
-		if (anim->isFading())
-			++iter;
-		else // fade finished
-		{
-			auto &objs = ttiles[pos.z][pos.x][pos.y].objects;
-			for (auto objIter = objs.begin(); objIter != objs.end(); ++objIter)
-			{
-				if ((*objIter).fadeAnimKey == (*iter).first)
-				{
-					logAnim->trace("Fade anim finished for obj at %s; remaining: %d", pos.toString(), fadeAnims.size() - 1);
-					if (anim->fadingMode == CFadeAnimation::EMode::OUT)
-						objs.erase(objIter); // if this was fadeout, remove the object from the map
-					else
-						(*objIter).fadeAnimKey = -1; // for fadein, just remove its connection to the finished fade
-					break;
-				}
-			}
-			delete (*iter).second.second;
-			iter = fadeAnims.erase(iter);
-		}
-	}
-
-	return !fadeAnims.empty();
-}
-
-bool CMapHandler::startObjectFade(TerrainTileObject & obj, bool in, int3 pos)
-{
-	SDL_Surface * fadeBitmap;
-	assert(obj.obj);
-
-	auto objData = normalBlitter->findObjectBitmap(obj.obj, 0);
-	if (objData.objBitmap)
-	{
-		if (objData.isMoving) // ignore fading of moving objects (for now?)
-		{
-			logAnim->debug("Ignoring fade of moving object");
-			return false;
-		}
-
-		fadeBitmap = CSDL_Ext::newSurface(32, 32); // TODO cache these bitmaps instead of creating new ones?
-		Rect objSrcRect(obj.rect.x, obj.rect.y, 32, 32);
-		objData.objBitmap->draw(fadeBitmap,0,0,&objSrcRect);
-
-		if (objData.flagBitmap)
-		{
-			if (obj.obj->pos.x - 1 == pos.x && obj.obj->pos.y - 1 == pos.y) // -1 to draw flag in top-center instead of right-bottom; kind of a hack
-			{
-				Rect flagSrcRect(32, 0, 32, 32);
-				objData.flagBitmap->draw(fadeBitmap,0,0, &flagSrcRect);
-			}
-		}
-		auto anim = new CFadeAnimation();
-		anim->init(in ? CFadeAnimation::EMode::IN : CFadeAnimation::EMode::OUT, fadeBitmap, true);
-		fadeAnims[++fadeAnimCounter] = std::pair<int3, CFadeAnimation*>(pos, anim);
-		obj.fadeAnimKey = fadeAnimCounter;
-
-		logAnim->trace("Fade anim started for obj %d at %s; anim count: %d", obj.obj->ID, pos.toString(), fadeAnims.size());
-		return true;
-	}
-
-	return false;
-}
-
-bool CMapHandler::printObject(const CGObjectInstance * obj, bool fadein)
-{
-	auto animation = graphics->getAnimation(obj);
-
-	if(!animation)
-		return false;
-
-	auto bitmap = animation->getImage(0);
-
-	if(!bitmap)
-		return false;
-
-	const int tilesW = bitmap->width()/32;
-	const int tilesH = bitmap->height()/32;
-
-	auto ttilesWidth = ttiles.shape()[1];
-	auto ttilesHeight = ttiles.shape()[2];
-
-	for(int fx=0; fx<tilesW; ++fx)
-	{
-		for(int fy=0; fy<tilesH; ++fy)
-		{
-			Rect cr;
-			cr.w = 32;
-			cr.h = 32;
-			cr.x = fx*32;
-			cr.y = fy*32;
-
-			if((obj->pos.x + fx - tilesW + 1) >= 0 &&
-				(obj->pos.x + fx - tilesW + 1) < ttilesWidth - frameW &&
-				(obj->pos.y + fy - tilesH + 1) >= 0 &&
-				(obj->pos.y + fy - tilesH + 1) < ttilesHeight - frameH)
-			{
-				int3 pos(obj->pos.x + fx - tilesW + 1, obj->pos.y + fy - tilesH + 1, obj->pos.z);
-				TerrainTile2 & curt = ttiles[pos.z][pos.x][pos.y];
-
-				TerrainTileObject toAdd(obj, cr, obj->visitableAt(pos.x, pos.y));
-				if (fadein && ADVOPT.objectFading)
-				{
-					startObjectFade(toAdd, true, pos);
-				}
-
-				auto i = curt.objects.begin();
-				for(; i != curt.objects.end(); i++)
-				{
-					if(objectBlitOrderSorter(toAdd, *i))
-					{
-						curt.objects.insert(i, toAdd);
-						i = curt.objects.begin(); //to validate and avoid adding it second time
-						break;
-					}
-				}
-
-				if(i == curt.objects.end())
-					curt.objects.insert(i, toAdd);
-			}
-		}
-	}
-	return true;
-}
-
-bool CMapHandler::hideObject(const CGObjectInstance * obj, bool fadeout)
-{
-	for(size_t z = 0; z < map->levels(); z++)
-	{
-		for(size_t x = 0; x < map->width; x++)
-		{
-			for(size_t y = 0; y < map->height; y++)
-			{
-				auto &objs = ttiles[(int)z][(int)x][(int)y].objects;
-				for(size_t i = 0; i < objs.size(); i++)
-				{
-					if (objs[i].obj && objs[i].obj->id == obj->id)
-					{
-						if (fadeout && ADVOPT.objectFading) // object should be faded == erase is delayed until the end of fadeout
-						{
-							if (startObjectFade(objs[i], false, int3((si32)x, (si32)y, (si32)z)))
-								objs[i].obj = nullptr;
-							else
-								objs.erase(objs.begin() + i);
-						}
-						else
-							objs.erase(objs.begin() + i);
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	return true;
-}
-
-bool CMapHandler::canStartHeroMovement()
-{
-	return fadeAnims.empty(); // don't allow movement during fade animation
-}
-
-void CMapHandler::updateWater() //shift colors in palettes of water tiles
-{
-	for(auto & elem : terrainImages["lava"])
-	{
-		for(auto img : elem)
-			img->shiftPalette(246, 9);
-	}
-
-	for(auto & elem : terrainImages["water"])
-	{
-		for(auto img : elem)
-		{
-			img->shiftPalette(229, 12);
-			img->shiftPalette(242, 14);
-		}
-	}
-
-	for(auto & elem : riverImages["clrrvr"])
-	{
-		for(auto img : elem)
-		{
-			img->shiftPalette(183, 12);
-			img->shiftPalette(195, 6);
-		}
-	}
-
-	for(auto & elem : riverImages["mudrvr"])
-	{
-		for(auto img : elem)
-		{
-			img->shiftPalette(228, 12);
-			img->shiftPalette(183, 6);
-			img->shiftPalette(240, 6);
-		}
-	}
-
-	for(auto & elem : riverImages["lavrvr"])
-	{
-		for(auto img : elem)
-			img->shiftPalette(240, 9);
-	}
-}
-
-CMapHandler::~CMapHandler()
-{
-	delete normalBlitter;
-	delete worldViewBlitter;
-	delete puzzleViewBlitter;
-
-	for (auto & elem : fadeAnims)
-	{
-		delete elem.second.second;
-	}
-}
-
-CMapHandler::CMapHandler()
-{
-	frameW = frameH = 0;
-	normalBlitter = new CMapNormalBlitter(this);
-	worldViewBlitter = new CMapWorldViewBlitter(this);
-	puzzleViewBlitter = new CMapPuzzleViewBlitter(this);
-	fadeAnimCounter = 0;
-	map = nullptr;
-	tilesW = tilesH = 0;
-	offsetX = offsetY = 0;
-
-	egdeAnimation = std::make_unique<CAnimation>("EDG");
-	egdeAnimation->preload();
-}
-
-bool CMapHandler::hasObjectHole(const int3 & pos) const
-{
-	const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y];
-
-	for(auto & elem : tt.objects)
-	{
-		if(elem.obj && elem.obj->ID == Obj::HOLE)
-			return true;
-	}
-	return false;
-}
-
-void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRMB) const
-{
-	const TerrainTile & t = map->getTile(pos);
-
-	if(t.hasFavorableWinds())
-	{
-		out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0);
-		return;
-	}
-	const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y];
-	bool isTile2Terrain = false;
-	out.clear();
-
-	for(auto & elem : tt.objects)
-	{
-		if(elem.obj)
-		{
-			out = elem.obj->getObjectName();
-			if(elem.obj->ID == Obj::HOLE)
-				return;
-
-			isTile2Terrain = elem.obj->isTile2Terrain();
-			break;
-		}
-	}
-
-	if(!isTile2Terrain || out.empty())
-		out = t.terType->getNameTranslated();
-
-	if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG)
-	{
-		out = boost::str(boost::format(isRMB ? "%s\r\n%s" : "%s %s") // New line for the Message Box, space for the Status Bar
-			% out 
-			% CGI->generaltexth->allTexts[330]); // 'digging ok'
-	}
-}
-
-void CMapHandler::discardWorldViewCache()
-{
-	cache.discardWorldViewCache();
-}
-
-CMapCache::CMapCache()
-{
-	worldViewCachedScale = 0;
-}
-
-void CMapCache::discardWorldViewCache()
-{
-	for(auto & cache : data)
-		cache.clear();
-	logAnim->debug("Discarded world view cache");
-}
-
-void CMapCache::updateWorldViewScale(float scale)
-{
-	if (fabs(scale - worldViewCachedScale) > 0.001f)
-		discardWorldViewCache();
-	worldViewCachedScale = scale;
-}
-
-std::shared_ptr<IImage> CMapCache::requestWorldViewCacheOrCreate(EMapCacheType type, std::shared_ptr<IImage> fullSurface)
-{
-	intptr_t key = (intptr_t) (fullSurface.get());
-	auto & cache = data[(ui8)type];
-
-	auto iter = cache.find(key);
-	if(iter == cache.end())
-	{
-		auto scaled = fullSurface->scaleFast(fullSurface->dimensions() * worldViewCachedScale);
-		cache[key] = scaled;
-		return scaled;
-	}
-	else
-	{
-		return (*iter).second;
-	}
-}
-
-bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b)
-{
-	if (!a)
-		return true;
-	if (!b)
-		return false;
-	if (a->appearance->printPriority != b->appearance->printPriority)
-		return a->appearance->printPriority > b->appearance->printPriority;
-
-	if(a->pos.y != b->pos.y)
-		return a->pos.y < b->pos.y;
-
-	if(b->ID==Obj::HERO && a->ID!=Obj::HERO)
-		return true;
-	if(b->ID!=Obj::HERO && a->ID==Obj::HERO)
-		return false;
-
-	if(!a->isVisitable() && b->isVisitable())
-		return true;
-	if(!b->isVisitable() && a->isVisitable())
-		return false;
-	if(a->pos.x < b->pos.x)
-		return true;
-	return false;
-}
-
-TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, Rect rect_, bool visitablePos)
-	: obj(obj_),
-	  rect(rect_),
-	  fadeAnimKey(-1)
-{
-	// We store information about ambient sound is here because object might disappear while sound is updating
-	if(obj->getAmbientSound())
-	{
-		// All tiles of static objects are sound sources. E.g Volcanos and special terrains
-		// For visitable object only their visitable tile is sound source
-		if(!CCS->soundh->ambientCheckVisitable() || !obj->isVisitable() || visitablePos)
-			ambientSound = obj->getAmbientSound();
-	}
-}
-
-TerrainTileObject::~TerrainTileObject()
-{
-}

+ 0 - 411
client/adventureMap/mapHandler.h

@@ -1,411 +0,0 @@
-/*
- * mapHandler.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/spells/ViewSpellInt.h"
-#include "../../lib/Rect.h"
-
-#ifdef IN
-#undef IN
-#endif
-
-#ifdef OUT
-#undef OUT
-#endif
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CGObjectInstance;
-class CGHeroInstance;
-class CGBoat;
-class CMap;
-struct TerrainTile;
-class PlayerColor;
-
-VCMI_LIB_NAMESPACE_END
-
-struct SDL_Surface;
-class CAnimation;
-class IImage;
-class CFadeAnimation;
-class CMapHandler;
-
-enum class EWorldViewIcon
-{
-	TOWN = 0,
-	HERO,
-	ARTIFACT,
-	TELEPORT,
-	GATE,
-	MINE_WOOD,
-	MINE_MERCURY,
-	MINE_STONE,
-	MINE_SULFUR,
-	MINE_CRYSTAL,
-	MINE_GEM,
-	MINE_GOLD,
-	RES_WOOD,
-	RES_MERCURY,
-	RES_STONE,
-	RES_SULFUR,
-	RES_CRYSTAL,
-	RES_GEM,
-	RES_GOLD,
-
-};
-
-enum class EMapObjectFadingType
-{
-	NONE,
-	IN,
-	OUT
-};
-
-enum class EMapAnimRedrawStatus
-{
-	OK,
-	REDRAW_REQUESTED // map blitter requests quick redraw due to current animation
-};
-
-struct TerrainTileObject
-{
-	const CGObjectInstance *obj;
-	Rect rect;
-	int fadeAnimKey;
-	boost::optional<std::string> ambientSound;
-
-	TerrainTileObject(const CGObjectInstance *obj_, Rect rect_, bool visitablePos = false);
-	~TerrainTileObject();
-};
-
-struct TerrainTile2
-{
-	std::vector<TerrainTileObject> objects; //pointers to objects being on this tile with rects to be easier to blit this tile on screen
-};
-
-struct MapDrawingInfo
-{
-	bool scaled;
-	int3 &topTile; // top-left tile in viewport [in tiles]
-	std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap;
-	Rect drawBounds; // map rect drawing bounds on screen
-	std::shared_ptr<CAnimation> icons; // holds overlay icons for world view mode
-	float scale; // map scale for world view mode (only if scaled == true)
-
-	bool otherheroAnim;
-	ui8 anim;
-	ui8 heroAnim;
-
-	int3 movement; // used for smooth map movement
-
-	bool puzzleMode;
-	int3 grailPos; // location of grail for puzzle mode [in tiles]
-
-	const std::vector<ObjectPosInfo> * additionalIcons;
-
-	bool showAllTerrain; //for expert viewEarth
-
-	MapDrawingInfo(int3 &topTile_, std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap_, const Rect & drawBounds_, std::shared_ptr<CAnimation> icons_ = nullptr)
-		: scaled(false),
-		  topTile(topTile_),
-
-		  visibilityMap(visibilityMap_),
-		  drawBounds(drawBounds_),
-		  icons(icons_),
-		  scale(1.0f),
-		  otherheroAnim(false),
-		  anim(0u),
-		  heroAnim(0u),
-		  movement(int3()),
-		  puzzleMode(false),
-		  grailPos(int3()),
-		  additionalIcons(nullptr),
-		  showAllTerrain(false)
-	{}
-
-	ui8 getHeroAnim() const { return otherheroAnim ? anim : heroAnim; }
-};
-
-
-template <typename T> class PseudoV
-{
-public:
-	PseudoV() : offset(0) { }
-	inline T & operator[](const int & n)
-	{
-		return inver[n+offset];
-	}
-	inline const T & operator[](const int & n) const
-	{
-		return inver[n+offset];
-	}
-	void resize(int rest, int before, int after)
-	{
-		inver.resize(before + rest + after);
-		offset=before;
-	}
-	int size() const
-	{
-		return static_cast<int>(inver.size());
-	}
-
-private:
-	int offset;
-	std::vector<T> inver;
-};
-
-enum class EMapCacheType : ui8
-{
-	TERRAIN, OBJECTS, ROADS, RIVERS, FOW, HEROES, HERO_FLAGS, FRAME, AFTER_LAST
-};
-
-/// temporarily caches rescaled frames for map world view redrawing
-class CMapCache
-{
-	std::array< std::map<intptr_t, std::shared_ptr<IImage>>, (ui8)EMapCacheType::AFTER_LAST> data;
-	float worldViewCachedScale;
-public:
-	CMapCache();
-	/// destroys all cached data (frees surfaces)
-	void discardWorldViewCache();
-	/// updates scale and determines if currently cached data is still valid
-	void updateWorldViewScale(float scale);
-	/// asks for cached data; @returns cached data if found, new scaled surface otherwise, may return nullptr in case of scaling error
-	std::shared_ptr<IImage> requestWorldViewCacheOrCreate(EMapCacheType type, std::shared_ptr<IImage> fullSurface);
-};
-
-/// helper struct to pass around resolved bitmaps of an object; images can be nullptr if object doesn't have bitmap of that type
-struct AnimBitmapHolder
-{
-	std::shared_ptr<IImage> objBitmap; // main object bitmap
-	std::shared_ptr<IImage> flagBitmap; // flag bitmap for the object (probably only for heroes and boats with heroes)
-	bool isMoving; // indicates if the object is moving (again, heroes/boats only)
-
-	AnimBitmapHolder(std::shared_ptr<IImage> objBitmap_ = nullptr, std::shared_ptr<IImage> flagBitmap_ = nullptr, bool moving = false)
-		: objBitmap(objBitmap_),
-		  flagBitmap(flagBitmap_),
-		  isMoving(moving)
-	{}
-};
-
-class CMapBlitter
-{
-protected:
-	const int FRAMES_PER_MOVE_ANIM_GROUP = 8;
-	CMapHandler * parent; // ptr to enclosing map handler; generally for legacy reasons, probably could/should be refactored out of here
-	int tileSize; // size of a tile drawn on map [in pixels]
-	int halfTileSizeCeil; // half of the tile size, rounded up
-	int3 tileCount; // number of tiles in current viewport
-	int3 topTile; // top-left tile of the viewport
-	int3 initPos; // starting drawing position [in pixels]
-	int3 pos; // current position [in tiles]
-	int3 realPos; // current position [in pixels]
-	Rect realTileRect; // default rect based on current pos: [realPos.x, realPos.y, tileSize, tileSize]
-	Rect defaultTileRect; // default rect based on 0: [0, 0, tileSize, tileSize]
-	const MapDrawingInfo * info; // data for drawing passed from outside
-
-	/// general drawing method, called internally by more specialized ones
-	virtual void drawElement(EMapCacheType cacheType, std::shared_ptr<IImage> source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const = 0;
-
-	// first drawing pass
-
-	/// draws terrain bitmap (or custom bitmap if applicable) on current tile
-	virtual void drawTileTerrain(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile2 & tile) const;
-	/// draws a river segment on current tile
-	virtual void drawRiver(SDL_Surface * targetSurf, const TerrainTile & tinfo) const;
-	/// draws a road segment on current tile
-	virtual void drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const;
-	/// draws all objects on current tile (higher-level logic, unlike other draw*** methods)
-	virtual void drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const;
-	virtual void drawObject(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, bool moving) const;
-	virtual void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, Rect * destRect, bool moving) const;
-
-	// second drawing pass
-
-	/// current tile: draws overlay over the map, used to draw world view icons
-	virtual void drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const = 0;
-	/// draws fog of war on current tile
-	virtual void drawFow(SDL_Surface * targetSurf) const;
-	/// draws map border frame on current position
-	virtual void drawFrame(SDL_Surface * targetSurf) const;
-	/// draws additional icons (for VIEW_AIR, VIEW_EARTH spells atm)
-	virtual void drawOverlayEx(SDL_Surface * targetSurf);
-
-	// third drawing pass
-
-	/// custom post-processing, if needed (used by puzzle view)
-	virtual void postProcessing(SDL_Surface * targetSurf) const {}
-
-	// misc methods
-
-	/// initializes frame-drawing (called at the start of every redraw)
-	virtual void init(const MapDrawingInfo * drawingInfo) = 0;
-	/// calculates clip region for map viewport
-	virtual Rect clip(SDL_Surface * targetSurf) const = 0;
-
-	virtual ui8 getHeroFrameGroup(ui8 dir, bool isMoving) const;
-	virtual ui8 getPhaseShift(const CGObjectInstance *object) const;
-
-	virtual bool canDrawObject(const CGObjectInstance * obj) const;
-	virtual bool canDrawCurrentTile() const;
-
-	// internal helper methods to choose correct bitmap(s) for object; called internally by findObjectBitmap
-	AnimBitmapHolder findHeroBitmap(const CGHeroInstance * hero, int anim) const;
-	AnimBitmapHolder findBoatBitmap(const CGBoat * hero, int anim) const;
-	std::shared_ptr<IImage> findFlagBitmap(const CGHeroInstance * obj, int anim, const PlayerColor * color, int group) const;
-	std::shared_ptr<IImage> findHeroFlagBitmap(const CGHeroInstance * obj, int anim, const PlayerColor * color, int group) const;
-	std::shared_ptr<IImage> findBoatFlagBitmap(const CGBoat * obj, int anim, const PlayerColor * color, int group, ui8 dir) const;
-	std::shared_ptr<IImage> findFlagBitmapInternal(std::shared_ptr<CAnimation> animation, int anim, int group, ui8 dir, bool moving) const;
-
-public:
-	CMapBlitter(CMapHandler * p);
-	virtual ~CMapBlitter();
-	void blit(SDL_Surface * targetSurf, const MapDrawingInfo * info);
-	/// helper method that chooses correct bitmap(s) for given object
-	AnimBitmapHolder findObjectBitmap(const CGObjectInstance * obj, int anim) const;
-};
-
-class CMapNormalBlitter : public CMapBlitter
-{
-protected:
-	void drawElement(EMapCacheType cacheType, std::shared_ptr<IImage> source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const override;
-	void drawTileOverlay(SDL_Surface * targetSurf,const TerrainTile2 & tile) const override {}
-	void init(const MapDrawingInfo * info) override;
-	Rect clip(SDL_Surface * targetSurf) const override;
-public:
-	CMapNormalBlitter(CMapHandler * parent);
-	virtual ~CMapNormalBlitter(){}
-};
-
-class CMapWorldViewBlitter : public CMapBlitter
-{
-private:
-	std::shared_ptr<IImage> objectToIcon(Obj id, si32 subId, PlayerColor owner) const;
-protected:
-	void drawElement(EMapCacheType cacheType, std::shared_ptr<IImage> source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const override;
-	void drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const override;
-	void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, Rect * destRect, bool moving) const override;
-	void drawObject(SDL_Surface * targetSurf, std::shared_ptr<IImage> source, Rect * sourceRect, bool moving) const override;
-	void drawFrame(SDL_Surface * targetSurf) const override {}
-	void drawOverlayEx(SDL_Surface * targetSurf) override;
-	void init(const MapDrawingInfo * info) override;
-	Rect clip(SDL_Surface * targetSurf) const override;
-	ui8 getPhaseShift(const CGObjectInstance *object) const override { return 0u; }
-	void calculateWorldViewCameraPos();
-public:
-	CMapWorldViewBlitter(CMapHandler * parent);
-	virtual ~CMapWorldViewBlitter(){}
-};
-
-class CMapPuzzleViewBlitter : public CMapNormalBlitter
-{
-	std::vector<int> unblittableObjects;
-
-	void drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const override;
-	void drawFow(SDL_Surface * targetSurf) const override {} // skipping FoW in puzzle view
-	void postProcessing(SDL_Surface * targetSurf) const override;
-	bool canDrawObject(const CGObjectInstance * obj) const override;
-	bool canDrawCurrentTile() const override { return true; }
-public:
-	CMapPuzzleViewBlitter(CMapHandler * parent);
-};
-
-class CMapHandler
-{
-	friend class CMapBlitter;
-	friend class CMapNormalBlitter;
-	friend class CMapWorldViewBlitter;
-
-	CMapCache cache;
-	CMapBlitter * normalBlitter;
-	CMapBlitter * worldViewBlitter;
-	CMapBlitter * puzzleViewBlitter;
-
-	std::map<int, std::pair<int3, CFadeAnimation*>> fadeAnims;
-	int fadeAnimCounter;
-
-	CMapBlitter * resolveBlitter(const MapDrawingInfo * info) const;
-	bool updateObjectsFade();
-	bool startObjectFade(TerrainTileObject & obj, bool in, int3 pos);
-
-	void initObjectRects();
-	void initBorderGraphics();
-	void initTerrainGraphics();
-	void prepareFOWDefs();
-
-public: //TODO: make private
-	boost::multi_array<TerrainTile2, 3> ttiles; //informations about map tiles [z][x][y]
-	int3 sizes; //map size (x = width, y = height, z = number of levels)
-	const CMap * map;
-
-	// Max number of tiles that will fit in the map screen. Tiles
-	// can be partial on each edges.
-	int tilesW;
-	int tilesH;
-
-	// size of each side of the frame around the whole map, in tiles
-	int frameH;
-	int frameW;
-
-	// Coord in pixels of the top left corner of the top left tile to
-	// draw. Values range is [-31..0]. A negative value
-	// implies that part of the tile won't be displayed.
-	int offsetX;
-	int offsetY;
-
-	//terrain graphics
-private:
-	//FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013
-	typedef std::map<std::string, std::array<std::shared_ptr<CAnimation>, 4>> TFlippedAnimations; //[type, rotation]
-	typedef std::map<std::string, std::vector<std::array<std::shared_ptr<IImage>, 4>>> TFlippedCache;//[type, view type, rotation]
-
-	TFlippedAnimations terrainAnimations;//[terrain type, rotation]
-	TFlippedCache terrainImages;//[terrain type, view type, rotation]
-
-	TFlippedAnimations roadAnimations;//[road type, rotation]
-	TFlippedCache roadImages;//[road type, view type, rotation]
-
-	TFlippedAnimations riverAnimations;//[river type, rotation]
-	TFlippedCache riverImages;//[river type, view type, rotation]
-
-	//Fog of War cache (not owned)
-	std::vector<std::shared_ptr<IImage>> FoWfullHide;
-	boost::multi_array<ui8, 3> hideBitmap; //frame indexes (in FoWfullHide) of graphic that should be used to fully hide a tile
-
-	std::vector<std::shared_ptr<IImage>> FoWpartialHide;
-
-	//edge graphics
-	std::unique_ptr<CAnimation> egdeAnimation;
-	std::vector<std::shared_ptr<IImage>> egdeImages;//cache of links to egdeAnimation (for faster access)
-	PseudoV< PseudoV< PseudoV <ui8> > > edgeFrames; //frame indexes (in egdeImages) of tile outside of map
-
-	mutable std::map<const CGObjectInstance*, ui8> animationPhase;
-
-public:
-	CMapHandler();
-	~CMapHandler();
-
-	void getTerrainDescr(const int3 & pos, std::string & out, bool isRMB) const; // isRMB = whether Right Mouse Button is clicked
-	bool printObject(const CGObjectInstance * obj, bool fadein = false); //puts appropriate things to tiles, so obj will be visible on map
-	bool hideObject(const CGObjectInstance * obj, bool fadeout = false); //removes appropriate things from ttiles, so obj will be no longer visible on map (but still will exist)
-	bool hasObjectHole(const int3 & pos) const; // Checks if TerrainTile2 tile has a pit remained after digging.
-	void init();
-
-	EMapAnimRedrawStatus drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info, bool redrawOnlyAnim = false);
-	void updateWater();
-	/// determines if the map is ready to handle new hero movement (not available during fading animations)
-	bool canStartHeroMovement();
-
-	void discardWorldViewCache();
-
-	static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b);
-};

+ 28 - 18
client/battle/BattleFieldController.cpp

@@ -24,6 +24,7 @@
 #include "../CPlayerInterface.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../renderSDL/SDL_Extensions.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
 #include "../adventureMap/CInGameConsole.h"
@@ -43,7 +44,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	strongInterest = true;
 
 	//preparing cells and hexes
-	cellBorder = IImage::createFromFile("CCELLGRD.BMP");
+	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
 	cellShade = IImage::createFromFile("CCELLSHD.BMP");
 
 	if(!owner.siegeController)
@@ -53,29 +54,17 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 		if(bfieldType == BattleField::NONE)
 			logGlobal->error("Invalid battlefield returned for current battle");
 		else
-			background = IImage::createFromFile(bfieldType.getInfo()->graphics);
+			background = IImage::createFromFile(bfieldType.getInfo()->graphics, EImageBlitMode::OPAQUE);
 	}
 	else
 	{
 		std::string backgroundName = owner.siegeController->getBattleBackgroundName();
-		background = IImage::createFromFile(backgroundName);
+		background = IImage::createFromFile(backgroundName, EImageBlitMode::OPAQUE);
 	}
+
 	pos.w = background->width();
 	pos.h = background->height();
 
-	//preparing graphic with cell borders
-	cellBorders = std::make_unique<Canvas>(Point(background->width(), background->height()));
-
-	for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
-	{
-		if ( i % GameConstants::BFIELD_WIDTH == 0)
-			continue;
-		if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
-			continue;
-
-		cellBorders->draw(cellBorder, hexPositionLocal(i).topLeft());
-	}
-
 	backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
 
 	auto accessibility = owner.curInt->cb->getAccesibility();
@@ -166,7 +155,17 @@ void BattleFieldController::showBackgroundImage(Canvas & canvas)
 		owner.siegeController->showAbsoluteObstacles(canvas);
 
 	if (settings["battle"]["cellBorders"].Bool())
-		canvas.draw(*cellBorders, Point(0, 0));
+	{
+		for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+		{
+			if ( i % GameConstants::BFIELD_WIDTH == 0)
+				continue;
+			if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
+				continue;
+
+			canvas.draw(cellBorder, hexPositionLocal(i).topLeft());
+		}
+	}
 }
 
 void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
@@ -203,7 +202,17 @@ void BattleFieldController::redrawBackgroundWithHexes()
 	}
 
 	if(settings["battle"]["cellBorders"].Bool())
-		backgroundWithHexes->draw(*cellBorders, Point(0, 0));
+	{
+		for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+		{
+			if ( i % GameConstants::BFIELD_WIDTH == 0)
+				continue;
+			if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
+				continue;
+
+			backgroundWithHexes->draw(cellBorder, hexPositionLocal(i).topLeft());
+		}
+	}
 }
 
 void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
@@ -586,6 +595,7 @@ void BattleFieldController::show(SDL_Surface * to)
 	owner.obstacleController->update();
 
 	Canvas canvas(to);
+	CSDL_Ext::CClipRectGuard guard(to, pos);
 
 	renderBattlefield(canvas);
 }

+ 0 - 3
client/battle/BattleFieldController.h

@@ -37,9 +37,6 @@ class BattleFieldController : public CIntObject
 	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
 	std::unique_ptr<Canvas> backgroundWithHexes;
 
-	/// Canvas that contains cell borders of all tiles in the battlefield
-	std::unique_ptr<Canvas> cellBorders;
-
 	/// hex from which the stack would perform attack with current cursor
 	BattleHex attackingHex;
 

+ 3 - 7
client/battle/BattleInterface.cpp

@@ -95,7 +95,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	effectsController.reset(new BattleEffectsController(*this));
 	obstacleController.reset(new BattleObstacleController(*this));
 
-	CCS->musich->stopMusic();
+	adventureInt->onAudioPaused();
 	setAnimationCondition(EAnimationEvents::OPENING, true);
 
 	GH.pushInt(windowObject);
@@ -144,12 +144,8 @@ BattleInterface::~BattleInterface()
 	CPlayerInterface::battleInt = nullptr;
 	givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
 
-	if (adventureInt && adventureInt->curArmy())
-	{
-		//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
-		const auto * terrain = LOCPLINT->cb->getTile(adventureInt->curArmy()->visitablePos())->terType;
-		CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false);
-	}
+	if (adventureInt)
+		adventureInt->onAudioResumed();
 
 	// may happen if user decided to close game while in battle
 	if (getAnimationCondition(EAnimationEvents::ACTION) == true)

+ 4 - 4
client/battle/BattleStacksController.cpp

@@ -78,10 +78,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	animIDhelper(0)
 {
 	//preparing graphics for displaying amounts of creatures
-	amountNormal     = IImage::createFromFile("CMNUMWIN.BMP");
-	amountPositive   = IImage::createFromFile("CMNUMWIN.BMP");
-	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP");
-	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
+	amountNormal     = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
+	amountPositive   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
+	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
+	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
 
 	static const auto shifterNormal   = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f );
 	static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f );

+ 0 - 6
client/gui/CGuiHandler.cpp

@@ -126,8 +126,6 @@ void CGuiHandler::popInt(std::shared_ptr<IShowActivatable> top)
 	if(!listInt.empty())
 		listInt.front()->activate();
 	totalRedraw();
-
-	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
@@ -145,8 +143,6 @@ void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
 	newInt->activate();
 	objsToBlit.push_back(newInt);
 	totalRedraw();
-
-	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popInts(int howMany)
@@ -168,8 +164,6 @@ void CGuiHandler::popInts(int howMany)
 		totalRedraw();
 	}
 	fakeMouseMove();
-
-	pushUserEvent(EUserEvent::INTERFACE_CHANGED);
 }
 
 std::shared_ptr<IShowActivatable> CGuiHandler::topInt()

+ 0 - 1
client/gui/CGuiHandler.h

@@ -42,7 +42,6 @@ enum class EUserEvent
 	FULLSCREEN_TOGGLED,
 	CAMPAIGN_START_SCENARIO,
 	FORCE_QUIT, //quit client without question
-	INTERFACE_CHANGED
 };
 
 // A fps manager which holds game updates at a constant rate

+ 86 - 0
client/mapView/IMapRendererContext.h

@@ -0,0 +1,86 @@
+/*
+ * 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 true if selected tile has animation and should not be cached
+	virtual bool tileAnimated(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 int3 & coordinates) const = 0;
+
+	/// returns animation frame for selected object
+	virtual size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const = 0;
+
+	/// returns index of image for overlay on specific tile, or numeric_limits::max if none
+	virtual size_t overlayImageIndex(const int3 & coordinates) const = 0;
+
+	/// returns animation frame for terrain
+	virtual size_t terrainImageIndex(size_t groupSize) const = 0;
+
+	virtual double viewTransitionProgress() const = 0;
+
+	/// if true, rendered images will be converted to grayscale
+	virtual bool filterGrayscale() const = 0;
+
+	virtual bool showRoads() const = 0;
+	virtual bool showRivers() const = 0;
+	virtual bool showBorder() 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;
+};

+ 52 - 0
client/mapView/IMapRendererObserver.h

@@ -0,0 +1,52 @@
+/*
+ * 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 movement animation, moving hero across terrain
+	virtual void onHeroMoved(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){};
+};

+ 792 - 0
client/mapView/MapRenderer.cpp

@@ -0,0 +1,792 @@
+/*
+ * MapRenderer.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 "MapRenderer.h"
+
+#include "IMapRendererContext.h"
+#include "mapHandler.h"
+
+#include "../CGameInfo.h"
+#include "../render/CAnimation.h"
+#include "../render/Canvas.h"
+#include "../render/IImage.h"
+
+#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"
+
+struct NeighborTilesInfo
+{
+	//567
+	//3 4
+	//012
+	std::bitset<8> d;
+
+	NeighborTilesInfo(IMapRendererContext & context, const int3 & pos)
+	{
+		auto checkTile = [&](int dx, int dy)
+		{
+			return context.isVisible(pos + int3(dx, dy, 0));
+		};
+
+		// sides
+		d[1] = checkTile(0, +1);
+		d[3] = checkTile(-1, 0);
+		d[4] = checkTile(+1, 0);
+		d[6] = checkTile(0, -1);
+
+		// corners - select visible image if either corner or adjacent sides are visible
+		d[0] = d[1] || d[3] || checkTile(-1, +1);
+		d[2] = d[1] || d[4] || checkTile(+1, +1);
+		d[5] = d[3] || d[6] || checkTile(-1, -1);
+		d[7] = d[4] || d[6] || checkTile(+1, -1);
+	}
+
+	bool areAllHidden() const
+	{
+		return d.none();
+	}
+
+	int getBitmapID() const
+	{
+		//NOTE: some images have unused in VCMI pair (same blockmap but a bit different look)
+		// 0-1, 2-3, 4-5, 11-13, 12-14
+		static const int visBitmaps[256] = {
+			-1,  34,   4,   4,  22,  23,   4,   4,  36,  36,  38,  38,  47,  47,  38,  38, //16
+			 3,  25,  12,  12,   3,  25,  12,  12,   9,   9,   6,   6,   9,   9,   6,   6, //32
+			35,  39,  48,  48,  41,  43,  48,  48,  36,  36,  38,  38,  47,  47,  38,  38, //48
+			26,  49,  28,  28,  26,  49,  28,  28,   9,   9,   6,   6,   9,   9,   6,   6, //64
+			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //80
+			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //96
+			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //112
+			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //128
+			15,  17,  30,  30,  16,  19,  30,  30,  46,  46,  40,  40,  32,  32,  40,  40, //144
+			 2,  25,  12,  12,   2,  25,  12,  12,   9,   9,   6,   6,   9,   9,   6,   6, //160
+			18,  42,  31,  31,  20,  21,  31,  31,  46,  46,  40,  40,  32,  32,  40,  40, //176
+			26,  49,  28,  28,  26,  49,  28,  28,   9,   9,   6,   6,   9,   9,   6,   6, //192
+			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //208
+			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10, //224
+			 0,  45,  29,  29,  24,  33,  29,  29,  37,  37,   7,   7,  50,  50,   7,   7, //240
+			13,  27,  44,  44,  13,  27,  44,  44,   8,   8,  10,  10,   8,   8,  10,  10  //256
+		};
+
+		return visBitmaps[d.to_ulong()]; // >=0 -> partial hide, <0 - full hide
+	}
+};
+
+MapTileStorage::MapTileStorage(size_t capacity)
+	: animations(capacity)
+{
+}
+
+void MapTileStorage::load(size_t index, const std::string & filename, EImageBlitMode blitMode)
+{
+	auto & terrainAnimations = animations[index];
+
+	for(auto & entry : terrainAnimations)
+	{
+		entry = std::make_unique<CAnimation>(filename);
+		entry->preload();
+
+		for(size_t i = 0; i < entry->size(); ++i)
+			entry->getImage(i)->setBlitMode(blitMode);
+	}
+
+	for(size_t i = 0; i < terrainAnimations[0]->size(); ++i)
+	{
+		terrainAnimations[1]->getImage(i)->verticalFlip();
+		terrainAnimations[3]->getImage(i)->verticalFlip();
+
+		terrainAnimations[2]->getImage(i)->horizontalFlip();
+		terrainAnimations[3]->getImage(i)->horizontalFlip();
+	}
+}
+
+std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
+{
+	const auto & animation = animations[fileIndex][rotationIndex];
+	return animation->getImage(imageIndex);
+}
+
+MapRendererTerrain::MapRendererTerrain()
+	: storage(VLC->terrainTypeHandler->objects.size())
+{
+	for(const auto & terrain : VLC->terrainTypeHandler->objects)
+		storage.load(terrain->getIndex(), terrain->tilesFilename, EImageBlitMode::OPAQUE);
+}
+
+void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	int32_t terrainIndex = mapTile.terType->getIndex();
+	int32_t imageIndex = mapTile.terView;
+	int32_t rotationIndex = mapTile.extTileFlags % 4;
+
+	const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
+
+	if(mapTile.terType->getId() == ETerrainId::LAVA)
+	{
+		image->shiftPalette(246, 9, context.terrainImageIndex(9));
+	}
+
+	if(mapTile.terType->getId() == ETerrainId::WATER)
+	{
+		image->shiftPalette(229, 12, context.terrainImageIndex(12));
+		image->shiftPalette(242, 14, context.terrainImageIndex(14));
+	}
+
+	target.draw(image, Point(0, 0));
+}
+
+uint8_t MapRendererTerrain::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	if(mapTile.terType->getId() == ETerrainId::LAVA || mapTile.terType->getId() == ETerrainId::WATER)
+		return context.terrainImageIndex(250);
+	return 0xff - 1;
+}
+
+MapRendererRiver::MapRendererRiver()
+	: storage(VLC->riverTypeHandler->objects.size())
+{
+	for(const auto & river : VLC->riverTypeHandler->objects)
+		storage.load(river->getIndex(), river->tilesFilename, EImageBlitMode::COLORKEY);
+}
+
+void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	if(mapTile.riverType->getId() == River::NO_RIVER)
+		return;
+
+	int32_t terrainIndex = mapTile.riverType->getIndex();
+	int32_t imageIndex = mapTile.riverDir;
+	int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
+
+	const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
+
+	if(mapTile.riverType->getId() == River::WATER_RIVER)
+	{
+		image->shiftPalette(183, 12, context.terrainImageIndex(12));
+		image->shiftPalette(195, 6, context.terrainImageIndex(6));
+	}
+
+	if(mapTile.riverType->getId() == River::MUD_RIVER)
+	{
+		image->shiftPalette(228, 12, context.terrainImageIndex(12));
+		image->shiftPalette(183, 6, context.terrainImageIndex(6));
+		image->shiftPalette(240, 6, context.terrainImageIndex(6));
+	}
+
+	if(mapTile.riverType->getId() == River::LAVA_RIVER)
+	{
+		image->shiftPalette(240, 9, context.terrainImageIndex(9));
+	}
+
+	target.draw(image, Point(0, 0));
+}
+
+uint8_t MapRendererRiver::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	if(mapTile.riverType->getId() == River::WATER_RIVER ||
+	   mapTile.riverType->getId() == River::MUD_RIVER ||
+	   mapTile.riverType->getId() == River::LAVA_RIVER)
+		return context.terrainImageIndex(250);
+	return 0xff-1;
+}
+
+MapRendererRoad::MapRendererRoad()
+	: storage(VLC->roadTypeHandler->objects.size())
+{
+	for(const auto & road : VLC->roadTypeHandler->objects)
+		storage.load(road->getIndex(), road->tilesFilename, EImageBlitMode::COLORKEY);
+}
+
+void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	const int3 coordinatesAbove = coordinates - int3(0, 1, 0);
+
+	if(context.isInMap(coordinatesAbove))
+	{
+		const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove);
+		if(mapTileAbove.roadType->getId() != Road::NO_ROAD)
+		{
+			int32_t terrainIndex = mapTileAbove.roadType->getIndex();
+			int32_t imageIndex = mapTileAbove.roadDir;
+			int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4;
+
+			const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
+			target.draw(image, Point(0, 0), Rect(0, 16, 32, 16));
+		}
+	}
+
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+	if(mapTile.roadType->getId() != Road::NO_ROAD)
+	{
+		int32_t terrainIndex = mapTile.roadType->getIndex();
+		int32_t imageIndex = mapTile.roadDir;
+		int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4;
+
+		const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
+		target.draw(image, Point(0, 16), Rect(0, 0, 32, 16));
+	}
+}
+
+uint8_t MapRendererRoad::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
+MapRendererBorder::MapRendererBorder()
+{
+	emptyFill = std::make_unique<Canvas>(Point(32,32));
+	animation = std::make_unique<CAnimation>("EDG");
+	animation->preload();
+}
+
+size_t MapRendererBorder::getIndexForTile(IMapRendererContext & context, const int3 & tile)
+{
+	assert(!context.isInMap(tile));
+
+	int3 size = context.getMapSize();
+
+	if(tile.x < -1 || tile.x > size.x || tile.y < -1 || tile.y > size.y)
+		return std::abs(tile.x) % 4 + 4 * (std::abs(tile.y) % 4);
+
+	if(tile.x == -1 && tile.y == -1)
+		return 16;
+
+	if(tile.x == size.x && tile.y == -1)
+		return 17;
+
+	if(tile.x == size.x && tile.y == size.y)
+		return 18;
+
+	if(tile.x == -1 && tile.y == size.y)
+		return 19;
+
+	if(tile.y == -1)
+		return 20 + (tile.x % 4);
+
+	if(tile.x == size.x)
+		return 24 + (tile.y % 4);
+
+	if(tile.y == size.y)
+		return 28 + (tile.x % 4);
+
+	if(tile.x == -1)
+		return 32 + (tile.y % 4);
+
+	//else - visible area, no renderable border
+	assert(0);
+	return 0;
+}
+
+void MapRendererBorder::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	if (context.showBorder())
+	{
+		const auto & image = animation->getImage(getIndexForTile(context, coordinates));
+		target.draw(image, Point(0, 0));
+	}
+	else
+	{
+		target.draw(*emptyFill, Point(0,0));
+	}
+}
+
+uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
+MapRendererFow::MapRendererFow()
+{
+	fogOfWarFullHide = std::make_unique<CAnimation>("TSHRC");
+	fogOfWarFullHide->preload();
+	fogOfWarPartialHide = std::make_unique<CAnimation>("TSHRE");
+	fogOfWarPartialHide->preload();
+
+	for(size_t i = 0; i < fogOfWarFullHide->size(); ++i)
+		fogOfWarFullHide->getImage(i)->setBlitMode(EImageBlitMode::OPAQUE);
+
+	static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
+
+	size_t size = fogOfWarPartialHide->size(0); //group size after next rotation
+
+	for(const int rotation : rotations)
+	{
+		fogOfWarPartialHide->duplicateImage(0, rotation, 0);
+		auto image = fogOfWarPartialHide->getImage(size, 0);
+		image->verticalFlip();
+		size++;
+	}
+}
+
+void MapRendererFow::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	assert(!context.isVisible(coordinates));
+
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+
+	int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide
+	if(retBitmapID < 0)
+	{
+		// generate a number that is predefined for each tile,
+		// but appears random to break visible pattern in large areas of fow
+		// current approach (use primes as magic numbers for formula) looks to be suitable
+		size_t pseudorandomNumber = ((coordinates.x * 997) ^ (coordinates.y * 1009)) / 101;
+		size_t imageIndex = pseudorandomNumber % fogOfWarFullHide->size();
+
+		target.draw(fogOfWarFullHide->getImage(imageIndex), Point(0, 0));
+	}
+	else
+	{
+		target.draw(fogOfWarPartialHide->getImage(retBitmapID), Point(0, 0));
+	}
+}
+
+uint8_t MapRendererFow::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+	int retBitmapID = neighborInfo.getBitmapID();
+	if(retBitmapID < 0)
+		return 0xff - 1;
+	return retBitmapID;
+}
+
+std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectInstance* obj)
+{
+	const auto & info = obj->appearance;
+
+	//the only(?) invisible object
+	if(info->id == Obj::EVENT)
+		return std::shared_ptr<CAnimation>();
+
+	if(info->animationFile.empty())
+	{
+		logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
+		return std::shared_ptr<CAnimation>();
+	}
+
+	bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO);
+
+	//TODO: relocate to config file?
+	// Boat appearance files only contain single, unanimated image
+	// proper boat animations are actually in different file
+	static const std::vector<std::string> boatAnimations = {
+		"AB01_.def", "AB02_.def", "AB03_.def"
+	};
+
+	if (info->id == Obj::BOAT)
+		return getAnimation(boatAnimations[info->subid], generateMovementGroups);
+
+	return getAnimation(info->animationFile, generateMovementGroups);
+}
+
+std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const std::string & filename, bool generateMovementGroups)
+{
+	auto it = animations.find(filename);
+
+	if(it != animations.end())
+		return it->second;
+
+	auto ret = std::make_shared<CAnimation>(filename);
+	animations[filename] = ret;
+	ret->preload();
+
+	if(generateMovementGroups)
+	{
+		ret->createFlippedGroup(1, 13);
+		ret->createFlippedGroup(2, 14);
+		ret->createFlippedGroup(3, 15);
+
+		ret->createFlippedGroup(6, 10);
+		ret->createFlippedGroup(7, 11);
+		ret->createFlippedGroup(8, 12);
+	}
+	return ret;
+}
+
+std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectInstance* obj)
+{
+	//TODO: relocate to config file?
+	static const std::vector<std::string> heroFlags = {
+		"AF00", "AF01", "AF02", "AF03", "AF04", "AF05", "AF06", "AF07"
+	};
+
+	//TODO: relocate to config file?
+	static const std::vector<std::vector<std::string>> boatFlags = {
+		{"ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"},
+		{"ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"},
+		{"ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"}
+	};
+
+	if(obj->ID == Obj::HERO)
+	{
+		assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
+		assert(obj->tempOwner.isValidPlayer());
+		return getAnimation(heroFlags[obj->tempOwner.getNum()], true);
+	}
+
+	if(obj->ID == Obj::BOAT)
+	{
+		const auto * boat = dynamic_cast<const CGBoat *>(obj);
+		assert(boat->subID < boatFlags.size());
+		if (boat->hero)
+		{
+			assert(boat->hero->tempOwner.isValidPlayer());
+			return getAnimation(boatFlags[boat->subID][boat->hero->tempOwner.getNum()], true);
+		}
+	}
+
+	return nullptr;
+}
+
+std::shared_ptr<CAnimation> MapRendererObjects::getOverlayAnimation(const CGObjectInstance * obj)
+{
+	if(obj->ID == Obj::BOAT)
+	{
+		//TODO: relocate to config file?
+		// Boats have additional animation with waves around boat
+		static const std::vector<std::string> boatAnimations = {
+			"ABM01_.def", "ABM02_.def", "ABM03_.def"
+		};
+
+		const auto * boat = dynamic_cast<const CGBoat *>(obj);
+		if (boat->hero)
+			return getAnimation(boatAnimations[obj->subID], true);
+	}
+	return nullptr;
+}
+
+std::shared_ptr<IImage> MapRendererObjects::getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation>& animation) const
+{
+	if(!animation)
+		return nullptr;
+
+	size_t groupIndex = context.objectGroupIndex(obj->id);
+
+	if(animation->size(groupIndex) == 0)
+		return nullptr;
+
+	size_t frameIndex = context.objectImageIndex(obj->id, animation->size(groupIndex));
+
+	return animation->getImage(frameIndex, groupIndex);
+}
+
+void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage>& image)
+{
+	if(!image)
+		return;
+
+	auto transparency = static_cast<uint8_t>(std::round(255 * context.objectTransparency(object->id, coordinates)));
+
+	if (transparency == 0)
+		return;
+
+	image->setAlpha(transparency);
+	image->setFlagColor(object->tempOwner);
+
+	Point offsetPixels = context.objectImageOffset(object->id, coordinates);
+
+	if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
+	{
+		Point imagePos = image->dimensions() - offsetPixels - Point(32, 32);
+
+		//if (transparency == 255)
+		//{
+		//	Canvas intermediate(Point(32,32));
+		//	intermediate.enableTransparency(true);
+		//	image->setBlitMode(EImageBlitMode::OPAQUE);
+		//	intermediate.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
+		//	target.draw(intermediate, Point(0,0));
+		//	return;
+		//}
+		target.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
+	}
+}
+
+void MapRendererObjects::renderObject(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * instance)
+{
+	renderImage(context, target, coordinates, instance, getImage(context, instance, getBaseAnimation(instance)));
+	renderImage(context, target, coordinates, instance, getImage(context, instance, getFlagAnimation(instance)));
+	renderImage(context, target, coordinates, instance, getImage(context, instance, getOverlayAnimation(instance)));
+}
+
+void MapRendererObjects::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	for(const auto & objectID : context.getObjects(coordinates))
+	{
+		const auto * objectInstance = context.getObject(objectID);
+
+		assert(objectInstance);
+		if(!objectInstance)
+		{
+			logGlobal->error("Stray map object that isn't fading");
+			continue;
+		}
+
+		renderObject(context, target, coordinates, objectInstance);
+	}
+}
+
+uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	for(const auto & objectID : context.getObjects(coordinates))
+	{
+		const auto * objectInstance = context.getObject(objectID);
+		size_t groupIndex = context.objectGroupIndex(objectInstance->id);
+		Point offsetPixels = context.objectImageOffset(objectInstance->id, coordinates);
+
+		auto base = getBaseAnimation(objectInstance);
+		auto flag = getFlagAnimation(objectInstance);
+
+		if (base && base->size(groupIndex) > 1)
+		{
+			auto imageIndex = context.objectImageIndex(objectID, base->size(groupIndex));
+			auto image = base->getImage(imageIndex, groupIndex);
+			if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
+				return context.objectImageIndex(objectID, 250);
+		}
+
+		if (flag && flag->size(groupIndex) > 1)
+		{
+			auto imageIndex = context.objectImageIndex(objectID, flag->size(groupIndex));
+			auto image = flag->getImage(imageIndex, groupIndex);
+			if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
+				return context.objectImageIndex(objectID, 250);
+		}
+	}
+	return 0xff-1;
+}
+
+MapRendererDebug::MapRendererDebug()
+	: imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA))
+	, imageBlockable(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA))
+	, imageVisitable(IImage::createFromFile("debug/visitable", EImageBlitMode::ALPHA))
+{
+
+}
+
+void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	if(context.showGrid())
+		target.draw(imageGrid, Point(0,0));
+
+	if(context.showVisitable() || context.showBlockable())
+	{
+		bool blockable = false;
+		bool visitable = false;
+
+		for(const auto & objectID : context.getObjects(coordinates))
+		{
+			const auto * object = context.getObject(objectID);
+
+			if (context.objectTransparency(objectID, coordinates) > 0)
+			{
+				visitable |= object->visitableAt(coordinates.x, coordinates.y);
+				blockable |= object->blockingAt(coordinates.x, coordinates.y);
+			}
+		}
+
+		if (context.showBlockable() && blockable)
+			target.draw(imageBlockable, Point(0,0));
+		if (context.showVisitable() && visitable)
+			target.draw(imageVisitable, Point(0,0));
+	}
+}
+
+uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
+MapRendererPath::MapRendererPath()
+	: pathNodes(new CAnimation("ADAG"))
+{
+	pathNodes->preload();
+}
+
+size_t MapRendererPath::selectImageReachability(bool reachableToday, size_t imageIndex)
+{
+	const static size_t unreachableTodayOffset = 25;
+
+	if(!reachableToday)
+		return unreachableTodayOffset + imageIndex;
+
+	return imageIndex;
+}
+
+size_t MapRendererPath::selectImageCross(bool reachableToday, const int3 & curr)
+{
+	return selectImageReachability(reachableToday, 0);
+}
+
+size_t MapRendererPath::selectImageArrow(bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next)
+{
+	// Vector directions
+	//  0   1   2
+	//      |
+	//  3 - 4 - 5
+	//      |
+	//  6   7   8
+	//For example:
+	//  |
+	//  +->
+	// is (directionToArrowIndex[7][5])
+	//
+	const static size_t directionToArrowIndex[9][9] = {
+		{16, 17, 18, 7,  0, 19, 6,  5,  0 },
+		{8,  9,  18, 7,  0, 19, 6,  0,  20},
+		{8,  1,  10, 7,  0, 19, 0,  21, 20},
+		{24, 17, 18, 15, 0, 0,  6,  5,  4 },
+		{0,  0,  0,  0,  0, 0,  0,  0,  0 },
+		{8,  1,  2,  0,  0, 11, 22, 21, 20},
+		{24, 17, 0,  23, 0, 3,  14, 5,  4 },
+		{24, 0,  2,  23, 0, 3,  22, 13, 4 },
+		{0,  1,  2,  23, 0, 3,  22, 21, 12}
+	};
+
+	size_t enterDirection = (curr.x - next.x + 1) + 3 * (curr.y - next.y + 1);
+	size_t leaveDirection = (prev.x - curr.x + 1) + 3 * (prev.y - curr.y + 1);
+	size_t imageIndex = directionToArrowIndex[enterDirection][leaveDirection];
+
+	return selectImageReachability(reachableToday, imageIndex);
+}
+
+void MapRendererPath::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	size_t imageID = selectImage(context, coordinates);
+
+	if (imageID < pathNodes->size())
+		target.draw(pathNodes->getImage(imageID), Point(0,0));
+}
+
+size_t MapRendererPath::selectImage(IMapRendererContext & context, const int3 & coordinates)
+{
+	const auto & functor = [&](const CGPathNode & node)
+	{
+		return node.coord == coordinates;
+	};
+
+	const auto * path = context.currentPath();
+	if(!path)
+		return std::numeric_limits<size_t>::max();
+
+	const auto & iter = boost::range::find_if(path->nodes, functor);
+
+	if(iter == path->nodes.end())
+		return std::numeric_limits<size_t>::max();
+
+	bool reachableToday = iter->turns == 0;
+	if(iter == path->nodes.begin())
+		return selectImageCross(reachableToday, iter->coord);
+
+	auto next = iter + 1;
+	auto prev = iter - 1;
+
+	// start of path - current hero location
+	if(next == path->nodes.end())
+		return std::numeric_limits<size_t>::max();
+
+	bool pathContinuous = iter->coord.areNeighbours(next->coord) && iter->coord.areNeighbours(prev->coord);
+	bool embarking = iter->action == CGPathNode::EMBARK || iter->action == CGPathNode::DISEMBARK;
+
+	if(pathContinuous && !embarking)
+		return selectImageArrow(reachableToday, iter->coord, prev->coord, next->coord);
+
+	return selectImageCross(reachableToday, iter->coord);
+}
+
+uint8_t MapRendererPath::checksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	return selectImage(context, coordinates) & 0xff;
+}
+
+MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & context, const int3 & coordinates)
+{
+	// computes basic checksum to determine whether tile needs an update
+	// if any component gives different value, tile will be updated
+	TileChecksum result;
+	boost::range::fill(result, std::numeric_limits<uint8_t>::max());
+
+	if(!context.isInMap(coordinates))
+	{
+		result[0] = rendererBorder.checksum(context, coordinates);
+		return result;
+	}
+
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+
+	if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
+	{
+		result[7] = rendererFow.checksum(context, coordinates);
+	}
+	else
+	{
+		result[1] = rendererTerrain.checksum(context, coordinates);
+		if (context.showRivers())
+			result[2] = rendererRiver.checksum(context, coordinates);
+		if (context.showRoads())
+			result[3] = rendererRoad.checksum(context, coordinates);
+		result[4] = rendererObjects.checksum(context, coordinates);
+		result[5] = rendererPath.checksum(context, coordinates);
+		result[6] = rendererDebug.checksum(context, coordinates);
+
+		if(!context.isVisible(coordinates))
+			result[7] = rendererFow.checksum(context, coordinates);
+	}
+	return result;
+}
+
+void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	if(!context.isInMap(coordinates))
+	{
+		rendererBorder.renderTile(context, target, coordinates);
+		return;
+	}
+
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+
+	if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
+	{
+		rendererFow.renderTile(context, target, coordinates);
+	}
+	else
+	{
+		rendererTerrain.renderTile(context, target, coordinates);
+
+		if (context.showRivers())
+			rendererRiver.renderTile(context, target, coordinates);
+
+		if (context.showRoads())
+			rendererRoad.renderTile(context, target, coordinates);
+
+		rendererObjects.renderTile(context, target, coordinates);
+		rendererPath.renderTile(context, target, coordinates);
+		rendererDebug.renderTile(context, target, coordinates);
+
+		if(!context.isVisible(coordinates))
+			rendererFow.renderTile(context, target, coordinates);
+	}
+}

+ 171 - 0
client/mapView/MapRenderer.h

@@ -0,0 +1,171 @@
+/*
+ * MapRenderer.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 ObjectInstanceID;
+class CGObjectInstance;
+
+VCMI_LIB_NAMESPACE_END
+
+class CAnimation;
+class IImage;
+class Canvas;
+class IMapRendererContext;
+enum class EImageBlitMode : uint8_t;
+
+class MapTileStorage
+{
+	using TerrainAnimation = std::array<std::unique_ptr<CAnimation>, 4>;
+	std::vector<TerrainAnimation> animations;
+
+public:
+	explicit MapTileStorage(size_t capacity);
+	void load(size_t index, const std::string & filename, EImageBlitMode blitMode);
+	std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex);
+};
+
+class MapRendererTerrain
+{
+	MapTileStorage storage;
+
+public:
+	MapRendererTerrain();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererRiver
+{
+	MapTileStorage storage;
+
+public:
+	MapRendererRiver();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererRoad
+{
+	MapTileStorage storage;
+
+public:
+	MapRendererRoad();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererObjects
+{
+	std::unordered_map<std::string, std::shared_ptr<CAnimation>> animations;
+
+	std::shared_ptr<CAnimation> getBaseAnimation(const CGObjectInstance * obj);
+	std::shared_ptr<CAnimation> getFlagAnimation(const CGObjectInstance * obj);
+	std::shared_ptr<CAnimation> getOverlayAnimation(const CGObjectInstance * obj);
+
+	std::shared_ptr<CAnimation> getAnimation(const std::string & filename, bool generateMovementGroups);
+
+	std::shared_ptr<IImage> getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation> & animation) const;
+
+	void renderImage(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage> & image);
+	void renderObject(IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * obj);
+
+public:
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererBorder
+{
+	std::unique_ptr<CAnimation> animation;
+	std::unique_ptr<Canvas> emptyFill;
+
+	size_t getIndexForTile(IMapRendererContext & context, const int3 & coordinates);
+
+public:
+	MapRendererBorder();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererFow
+{
+	std::unique_ptr<CAnimation> fogOfWarFullHide;
+	std::unique_ptr<CAnimation> fogOfWarPartialHide;
+
+public:
+	MapRendererFow();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererPath
+{
+	std::unique_ptr<CAnimation> pathNodes;
+
+	size_t selectImageReachability(bool reachableToday, size_t imageIndex);
+	size_t selectImageCross(bool reachableToday, const int3 & curr);
+	size_t selectImageArrow(bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next);
+	size_t selectImage(IMapRendererContext & context, const int3 & coordinates);
+
+public:
+	MapRendererPath();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererDebug
+{
+	std::shared_ptr<IImage> imageGrid;
+	std::shared_ptr<IImage> imageVisitable;
+	std::shared_ptr<IImage> imageBlockable;
+public:
+	MapRendererDebug();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRendererOverlay
+{
+	std::unique_ptr<CAnimation> iconsStorage;
+public:
+	MapRendererOverlay();
+
+	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates);
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};
+
+class MapRenderer
+{
+	MapRendererTerrain rendererTerrain;
+	MapRendererRiver rendererRiver;
+	MapRendererRoad rendererRoad;
+	MapRendererBorder rendererBorder;
+	MapRendererFow rendererFow;
+	MapRendererObjects rendererObjects;
+	MapRendererPath rendererPath;
+	MapRendererDebug rendererDebug;
+
+public:
+	using TileChecksum = std::array<uint8_t, 8>;
+
+	TileChecksum getTileChecksum(IMapRendererContext & context, const int3 & coordinates);
+
+	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
+};

+ 496 - 0
client/mapView/MapRendererContext.cpp

@@ -0,0 +1,496 @@
+/*
+ * MapRendererContextState.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 "MapRendererContextState.h"
+#include "mapHandler.h"
+
+#include "../../CCallback.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CAdvMapInt.h"
+
+#include "../../lib/CPathfinder.h"
+#include "../../lib/Point.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/CMap.h"
+
+MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState)
+	: viewState(viewState)
+{
+}
+
+uint32_t MapRendererBaseContext::getObjectRotation(ObjectInstanceID objectID) const
+{
+	const CGObjectInstance * obj = getObject(objectID);
+
+	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 MapRendererBaseContext::getMapSize() const
+{
+	return LOCPLINT->cb->getMapSize();
+}
+
+bool MapRendererBaseContext::isInMap(const int3 & coordinates) const
+{
+	return LOCPLINT->cb->isInTheMap(coordinates);
+}
+
+bool MapRendererBaseContext::isVisible(const int3 & coordinates) const
+{
+	if(settingsSessionSpectate)
+		return LOCPLINT->cb->isInTheMap(coordinates);
+	else
+		return LOCPLINT->cb->isVisible(coordinates);
+}
+
+bool MapRendererBaseContext::tileAnimated(const int3 & coordinates) const
+{
+	return false;
+}
+
+const TerrainTile & MapRendererBaseContext::getMapTile(const int3 & coordinates) const
+{
+	return CGI->mh->getMap()->getTile(coordinates);
+}
+
+const MapRendererBaseContext::MapObjectsList & MapRendererBaseContext::getObjects(const int3 & coordinates) const
+{
+	assert(isInMap(coordinates));
+	return viewState.objects[coordinates.z][coordinates.x][coordinates.y];
+}
+
+const CGObjectInstance * MapRendererBaseContext::getObject(ObjectInstanceID objectID) const
+{
+	return CGI->mh->getMap()->objects.at(objectID.getNum());
+}
+
+const CGPath * MapRendererBaseContext::currentPath() const
+{
+	return nullptr;
+}
+
+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)];
+}
+
+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);
+}
+
+double MapRendererBaseContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	const CGObjectInstance * object = getObject(objectID);
+
+	if(object->ID == Obj::HERO)
+	{
+		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
+
+		if(hero->inTownGarrison)
+			return 0;
+
+		if(hero->boat)
+			return 0;
+	}
+	return 1;
+}
+
+size_t MapRendererBaseContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	return 0;
+}
+
+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();
+}
+
+double MapRendererBaseContext::viewTransitionProgress() const
+{
+	return 0;
+}
+
+bool MapRendererBaseContext::filterGrayscale() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showRoads() const
+{
+	return true;
+}
+
+bool MapRendererBaseContext::showRivers() const
+{
+	return true;
+}
+
+bool MapRendererBaseContext::showBorder() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showOverlay() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showGrid() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showVisitable() const
+{
+	return false;
+}
+
+bool MapRendererBaseContext::showBlockable() const
+{
+	return false;
+}
+
+MapRendererAdventureContext::MapRendererAdventureContext(const MapRendererContextState & viewState)
+	: MapRendererBaseContext(viewState)
+{
+}
+
+const CGPath * MapRendererAdventureContext::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 MapRendererAdventureContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	assert(groupSize > 0);
+
+	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;
+}
+
+size_t MapRendererAdventureContext::terrainImageIndex(size_t groupSize) const
+{
+	if(!settingsAdventureTerrainAnimation)
+		return 0;
+
+	size_t baseFrameTime = 180;
+	size_t frameCounter = animationTime / baseFrameTime;
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
+}
+
+bool MapRendererAdventureContext::showBorder() const
+{
+	return true;
+}
+
+bool MapRendererAdventureContext::showGrid() const
+{
+	return settingShowGrid;
+}
+
+bool MapRendererAdventureContext::showVisitable() const
+{
+	return settingShowVisitable;
+}
+
+bool MapRendererAdventureContext::showBlockable() const
+{
+	return settingShowBlockable;
+}
+
+MapRendererAdventureTransitionContext::MapRendererAdventureTransitionContext(const MapRendererContextState & viewState)
+	: MapRendererAdventureContext(viewState)
+{
+}
+
+double MapRendererAdventureTransitionContext::viewTransitionProgress() const
+{
+	return progress;
+}
+
+MapRendererAdventureFadingContext::MapRendererAdventureFadingContext(const MapRendererContextState & viewState)
+	: MapRendererAdventureContext(viewState)
+{
+}
+
+bool MapRendererAdventureFadingContext::tileAnimated(const int3 & coordinates) const
+{
+	if(!isInMap(coordinates))
+		return false;
+
+	auto objects = getObjects(coordinates);
+	if(vstd::contains(objects, target))
+		return true;
+
+	return false;
+}
+
+double MapRendererAdventureFadingContext::objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const
+{
+	if(objectID == target)
+		return progress;
+
+	return 1.0;
+}
+
+MapRendererAdventureMovingContext::MapRendererAdventureMovingContext(const MapRendererContextState & viewState)
+	: MapRendererAdventureContext(viewState)
+{
+}
+
+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)];
+	}
+	return MapRendererAdventureContext::objectGroupIndex(objectID);
+}
+
+bool MapRendererAdventureMovingContext::tileAnimated(const int3 & coordinates) const
+{
+	if(!isInMap(coordinates))
+		return false;
+
+	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)
+	{
+		int3 offsetTilesFrom = tileFrom - coordinates;
+		int3 offsetTilesDest = tileDest - coordinates;
+
+		Point offsetPixelsFrom = Point(offsetTilesFrom) * Point(32, 32);
+		Point offsetPixelsDest = Point(offsetTilesDest) * Point(32, 32);
+
+		Point result = vstd::lerp(offsetPixelsFrom, offsetPixelsDest, progress);
+
+		return result;
+	}
+
+	return MapRendererAdventureContext::objectImageOffset(objectID, coordinates);
+}
+
+size_t MapRendererAdventureMovingContext::objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const
+{
+	if(target != objectID)
+		return MapRendererAdventureContext::objectImageIndex(objectID, groupSize);
+
+	int32_t baseFrameTime = 50;
+	size_t frameCounter = animationTime / baseFrameTime;
+	size_t frameIndex = frameCounter % groupSize;
+	return frameIndex;
+}
+
+size_t MapRendererWorldViewContext::selectOverlayImageForObject(const ObjectPosInfo & object) const
+{
+	size_t ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * static_cast<size_t>(EWorldViewIcon::ICONS_PER_PLAYER);
+
+	if(object.owner.isValidPlayer())
+		ownerIndex = object.owner.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 ownerIndex + static_cast<size_t>(EWorldViewIcon::TELEPORT);
+		case Obj::SUBTERRANEAN_GATE:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::GATE);
+		case Obj::ARTIFACT:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::ARTIFACT);
+		case Obj::TOWN:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::TOWN);
+		case Obj::HERO:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::HERO);
+		case Obj::MINE:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::MINE_WOOD) + object.subId;
+		case Obj::RESOURCE:
+			return ownerIndex + static_cast<size_t>(EWorldViewIcon::RES_WOOD) + object.subId;
+	}
+	return std::numeric_limits<size_t>::max();
+}
+
+MapRendererWorldViewContext::MapRendererWorldViewContext(const MapRendererContextState & viewState)
+	: MapRendererBaseContext(viewState)
+{
+}
+
+bool MapRendererWorldViewContext::showOverlay() const
+{
+	return true;
+}
+
+size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates) const
+{
+	if(!isVisible(coordinates))
+		return std::numeric_limits<size_t>::max();
+
+	for(const auto & objectID : getObjects(coordinates))
+	{
+		const auto * object = getObject(objectID);
+
+		if(!object->visitableAt(coordinates.x, coordinates.y))
+			continue;
+
+		ObjectPosInfo info;
+		info.pos = coordinates;
+		info.id = object->ID;
+		info.subId = object->subID;
+		info.owner = object->tempOwner;
+
+		size_t iconIndex = selectOverlayImageForObject(info);
+
+		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;
+}

+ 160 - 0
client/mapView/MapRendererContext.h

@@ -0,0 +1,160 @@
+/*
+ * 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 "IMapRendererContext.h"
+
+#include "../lib/GameConstants.h"
+#include "../lib/int3.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct ObjectPosInfo;
+VCMI_LIB_NAMESPACE_END
+
+struct MapRendererContextState;
+
+class MapRendererBaseContext : public IMapRendererContext
+{
+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;
+
+	double viewTransitionProgress() 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:
+	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 showBorder() const override;
+	bool showGrid() const override;
+	bool showVisitable() const override;
+	bool showBlockable() const override;
+};
+
+class MapRendererAdventureTransitionContext : public MapRendererAdventureContext
+{
+public:
+	double progress = 0;
+
+	explicit MapRendererAdventureTransitionContext(const MapRendererContextState & viewState);
+
+	double viewTransitionProgress() const override;
+};
+
+class MapRendererAdventureFadingContext : public MapRendererAdventureContext
+{
+public:
+	ObjectInstanceID target;
+	double progress;
+
+	explicit MapRendererAdventureFadingContext(const MapRendererContextState & viewState);
+
+	bool tileAnimated(const int3 & coordinates) const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
+};
+
+class MapRendererAdventureMovingContext : public MapRendererAdventureContext
+{
+public:
+	ObjectInstanceID target;
+	int3 tileFrom;
+	int3 tileDest;
+	double progress;
+
+	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 MapRendererWorldViewContext : public MapRendererBaseContext
+{
+protected:
+	size_t selectOverlayImageForObject(const ObjectPosInfo & object) const;
+
+public:
+	explicit MapRendererWorldViewContext(const MapRendererContextState & viewState);
+
+	size_t overlayImageIndex(const int3 & coordinates) const override;
+	bool showOverlay() const override;
+};
+
+class MapRendererSpellViewContext : public MapRendererWorldViewContext
+{
+public:
+	std::vector<ObjectPosInfo> additionalOverlayIcons;
+	bool showAllTerrain = false;
+
+	explicit MapRendererSpellViewContext(const MapRendererContextState & viewState);
+
+	bool isVisible(const int3 & coordinates) const override;
+	double objectTransparency(ObjectInstanceID objectID, const int3 & coordinates) const override;
+	size_t overlayImageIndex(const int3 & coordinates) const override;
+};
+
+class MapRendererPuzzleMapContext : public MapRendererBaseContext
+{
+public:
+	std::unique_ptr<CGPath> grailPos;
+
+	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;
+};

+ 93 - 0
client/mapView/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 "../../CCallback.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CAdvMapInt.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/mapView/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/GameConstants.h"
+#include "../lib/int3.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);
+};

+ 161 - 0
client/mapView/MapView.cpp

@@ -0,0 +1,161 @@
+/*
+ * MapView.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 "MapView.h"
+
+#include "MapViewActions.h"
+#include "MapViewCache.h"
+#include "MapViewController.h"
+#include "MapViewModel.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"
+
+BasicMapView::~BasicMapView() = default;
+
+std::shared_ptr<MapViewModel> BasicMapView::createModel(const Point & dimensions) const
+{
+	auto result = std::make_shared<MapViewModel>();
+
+	result->setLevel(0);
+	result->setTileSize(Point(32, 32));
+	result->setViewCenter(Point(0, 0));
+	result->setViewDimensions(dimensions);
+
+	return result;
+}
+
+BasicMapView::BasicMapView(const Point & offset, const Point & dimensions)
+	: model(createModel(dimensions))
+	, tilesCache(new MapViewCache(model))
+	, controller(new MapViewController(model, tilesCache))
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos += offset;
+	pos.w = dimensions.x;
+	pos.h = dimensions.y;
+}
+
+void BasicMapView::render(Canvas & target, bool fullUpdate)
+{
+	Canvas targetClipped(target, pos);
+
+	controller->update(GH.mainFPSmng->getElapsedMilliseconds());
+	tilesCache->update(controller->getContext());
+	tilesCache->render(controller->getContext(), targetClipped, fullUpdate);
+}
+
+void BasicMapView::show(SDL_Surface * to)
+{
+	Canvas target(to);
+	CSDL_Ext::CClipRectGuard guard(to, pos);
+	render(target, false);
+}
+
+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)
+	{
+		if(model->getLevel() == 0)
+			controller->setViewCenter(model->getMapViewCenter(), 1);
+		else
+			controller->setViewCenter(model->getMapViewCenter(), 0);
+	}
+}
+
+void MapView::onMapScrolled(const Point & distance)
+{
+	if(!isSwiping)
+		controller->setViewCenter(model->getMapViewCenter() + distance, model->getLevel());
+}
+
+void MapView::onMapSwiped(const Point & viewPosition)
+{
+	isSwiping = true;
+	controller->setViewCenter(viewPosition, model->getLevel());
+}
+
+void MapView::onMapSwipeEnded()
+{
+	isSwiping = false;
+}
+
+void MapView::onCenteredTile(const int3 & tile)
+{
+	controller->setViewCenter(tile);
+}
+
+void MapView::onCenteredObject(const CGObjectInstance * target)
+{
+	controller->setViewCenter(target->getSightCenter());
+}
+
+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);
+}
+
+void MapView::onViewWorldActivated(uint32_t tileSize)
+{
+	controller->activateWorldViewContext();
+	controller->setTileSize(Point(tileSize, tileSize));
+}
+
+void MapView::onViewMapActivated()
+{
+	controller->activateAdventureContext();
+	controller->setTileSize(Point(32, 32));
+}
+
+PuzzleMapView::PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter)
+	: BasicMapView(offset, dimensions)
+{
+	controller->setViewCenter(tileToCenter);
+	controller->activatePuzzleMapContext(tileToCenter);
+}

+ 90 - 0
client/mapView/MapView.h

@@ -0,0 +1,90 @@
+/*
+ * MapView.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 "../gui/CIntObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct ObjectPosInfo;
+VCMI_LIB_NAMESPACE_END
+
+class Canvas;
+class MapViewActions;
+class MapViewController;
+class MapViewModel;
+class MapViewCache;
+
+/// 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<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);
+
+	/// Moves current view to another level, preserving position
+	void onMapLevelSwitched();
+
+	/// Moves current view by specified distance in pixels
+	void onMapScrolled(const Point & distance);
+
+	/// Moves current view to specified position, in pixels
+	void onMapSwiped(const Point & viewPosition);
+
+	/// Ends swiping mode and allows normal map scrolling once again
+	void onMapSwipeEnded();
+
+	/// Moves current view to specified tile
+	void onCenteredTile(const int3 & tile);
+
+	/// Moves current view to specified object
+	void onCenteredObject(const CGObjectInstance * target);
+
+	/// Switches view to "View Earth" / "View Air" mode, displaying downscaled map with overlay
+	void onViewSpellActivated(uint32_t tileSize, const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain);
+
+	/// Switches view to downscaled View World
+	void onViewWorldActivated(uint32_t tileSize);
+
+	/// Switches view from View World mode back to standard view
+	void onViewMapActivated();
+};
+
+/// Main class that represents map view for puzzle map
+class PuzzleMapView : public BasicMapView
+{
+public:
+	PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter);
+};

+ 172 - 0
client/mapView/MapViewActions.cpp

@@ -0,0 +1,172 @@
+/*
+ * MapViewActions.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 "MapViewActions.h"
+
+#include "IMapRendererContext.h"
+#include "MapView.h"
+#include "MapViewModel.h"
+
+#include "../CGameInfo.h"
+#include "../adventureMap/CAdvMapInt.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+
+#include "../../lib/CConfigHandler.h"
+
+MapViewActions::MapViewActions(MapView & owner, const std::shared_ptr<MapViewModel> & model)
+	: model(model)
+	, owner(owner)
+	, curHoveredTile(-1, -1, -1)
+	, isSwiping(false)
+#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
+	, swipeEnabled(settings["general"]["swipe"].Bool())
+#else
+	, swipeEnabled(settings["general"]["swipeDesktop"].Bool())
+#endif
+{
+	pos.w = model->getPixelsVisibleDimensions().x;
+	pos.h = model->getPixelsVisibleDimensions().y;
+
+	addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
+}
+
+void MapViewActions::setContext(const std::shared_ptr<IMapRendererContext> & context)
+{
+	this->context = context;
+}
+
+void MapViewActions::activate()
+{
+	CIntObject::activate();
+}
+
+void MapViewActions::deactivate()
+{
+	CIntObject::deactivate();
+	curHoveredTile = int3(-1, -1, -1); //we lost info about hovered tile when disabling
+}
+
+void MapViewActions::clickLeft(tribool down, bool previousState)
+{
+	if(indeterminate(down))
+		return;
+
+	if(swipeEnabled)
+	{
+		if(handleSwipeStateChange(static_cast<bool>(down)))
+		{
+			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
+		}
+	}
+	else
+	{
+		if(down == false)
+			return;
+	}
+
+	int3 tile = model->getTileAtPoint(GH.getCursorPosition() - pos.topLeft());
+
+	if(context->isInMap(tile))
+		adventureInt->onTileLeftClicked(tile);
+}
+
+void MapViewActions::clickRight(tribool down, bool previousState)
+{
+	if(isSwiping)
+		return;
+
+	int3 tile = model->getTileAtPoint(GH.getCursorPosition() - pos.topLeft());
+
+	if(down && context->isInMap(tile))
+		adventureInt->onTileRightClicked(tile);
+}
+
+void MapViewActions::clickMiddle(tribool down, bool previousState)
+{
+	handleSwipeStateChange(static_cast<bool>(down));
+}
+
+void MapViewActions::mouseMoved(const Point & cursorPosition)
+{
+	handleHover(cursorPosition);
+	handleSwipeMove(cursorPosition);
+}
+
+void MapViewActions::handleSwipeMove(const Point & cursorPosition)
+{
+	// unless swipe is enabled, swipe move only works with middle mouse button
+	if(!swipeEnabled && !GH.isMouseButtonPressed(MouseButton::MIDDLE))
+		return;
+
+	// on mobile platforms with enabled swipe any button is enough
+	if(swipeEnabled && (!GH.isMouseButtonPressed() || GH.multifinger))
+		return;
+
+	if(!isSwiping)
+	{
+		static constexpr int touchSwipeSlop = 16;
+		Point distance = (cursorPosition - swipeInitialRealPos);
+
+		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
+		if(std::abs(distance.x) + std::abs(distance.y) > touchSwipeSlop)
+			isSwiping = true;
+	}
+
+	if(isSwiping)
+	{
+		Point swipeTargetPosition = swipeInitialViewPos + swipeInitialRealPos - cursorPosition;
+		owner.onMapSwiped(swipeTargetPosition);
+	}
+}
+
+bool MapViewActions::handleSwipeStateChange(bool btnPressed)
+{
+	if(btnPressed)
+	{
+		swipeInitialRealPos = GH.getCursorPosition();
+		swipeInitialViewPos = model->getMapViewCenter();
+		return true;
+	}
+
+	if(isSwiping) // only accept this touch if it wasn't a swipe
+	{
+		owner.onMapSwipeEnded();
+		isSwiping = false;
+		return true;
+	}
+	return false;
+}
+
+void MapViewActions::handleHover(const Point & cursorPosition)
+{
+	int3 tile = model->getTileAtPoint(cursorPosition - pos.topLeft());
+
+	if(!context->isInMap(tile))
+	{
+		CCS->curh->set(Cursor::Map::POINTER);
+		return;
+	}
+
+	if(tile != curHoveredTile)
+	{
+		curHoveredTile = tile;
+		adventureInt->onTileHovered(tile);
+	}
+}
+
+void MapViewActions::hover(bool on)
+{
+	if(!on)
+	{
+		GH.statusbar->clear();
+		CCS->curh->set(Cursor::Map::POINTER);
+	}
+}

+ 49 - 0
client/mapView/MapViewActions.h

@@ -0,0 +1,49 @@
+/*
+ * MapViewActions.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 "../gui/CIntObject.h"
+
+class IMapRendererContext;
+class MapViewModel;
+class MapView;
+
+class MapViewActions : public CIntObject
+{
+	bool swipeEnabled;
+	bool isSwiping;
+
+	Point swipeInitialViewPos;
+	Point swipeInitialRealPos;
+
+	int3 curHoveredTile;
+
+	MapView & owner;
+	std::shared_ptr<MapViewModel> model;
+	std::shared_ptr<IMapRendererContext> context;
+
+	void handleHover(const Point & cursorPosition);
+	void handleSwipeMove(const Point & cursorPosition);
+	bool handleSwipeStateChange(bool btnPressed);
+
+public:
+	MapViewActions(MapView & owner, const std::shared_ptr<MapViewModel> & model);
+
+	void setContext(const std::shared_ptr<IMapRendererContext> & context);
+
+	void activate() override;
+	void deactivate() override;
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+	void clickMiddle(tribool down, bool previousState) override;
+	void hover(bool on) override;
+	void mouseMoved(const Point & cursorPosition) override;
+};

+ 191 - 0
client/mapView/MapViewCache.cpp

@@ -0,0 +1,191 @@
+/*
+ * 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 "IMapRendererContext.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)
+	, cachedLevel(0)
+	, mapRenderer(new MapRenderer())
+	, iconsStorage(new CAnimation("VwSymbol"))
+	, intermediate(new Canvas(Point(32, 32)))
+	, terrain(new Canvas(model->getCacheDimensionsPixels()))
+	, terrainTransition(new Canvas(model->getPixelsVisibleDimensions()))
+{
+	iconsStorage->preload();
+	for(size_t i = 0; i < iconsStorage->size(); ++i)
+		iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY);
+
+	Point visibleSize = model->getTilesVisibleDimensions();
+	terrainChecksum.resize(boost::extents[visibleSize.x][visibleSize.y]);
+	tilesUpToDate.resize(boost::extents[visibleSize.x][visibleSize.y]);
+}
+
+Canvas MapViewCache::getTile(const int3 & coordinates)
+{
+	return Canvas(*terrain, model->getCacheTileArea(coordinates));
+}
+
+std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_ptr<IMapRendererContext> & context, const int3 & coordinates)
+{
+	size_t imageIndex = context->overlayImageIndex(coordinates);
+
+	if(imageIndex < iconsStorage->size())
+		return iconsStorage->getImage(imageIndex);
+	return nullptr;
+}
+
+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];
+	int cacheY = (terrainChecksum.shape()[1] + coordinates.y) % terrainChecksum.shape()[1];
+
+	auto & oldCacheEntry = terrainChecksum[cacheX][cacheY];
+	TileChecksum newCacheEntry;
+
+	newCacheEntry.tileX = coordinates.x;
+	newCacheEntry.tileY = coordinates.y;
+	newCacheEntry.checksum = mapRenderer->getTileChecksum(*context, coordinates);
+
+	if(cachedLevel == coordinates.z && oldCacheEntry == newCacheEntry && !context->tileAnimated(coordinates))
+		return;
+
+	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());
+	}
+
+	if(context->filterGrayscale())
+		target.applyGrayscale();
+
+	oldCacheEntry = newCacheEntry;
+	tilesUpToDate[cacheX][cacheY] = false;
+}
+
+void MapViewCache::update(const std::shared_ptr<IMapRendererContext> & context)
+{
+	Rect dimensions = model->getTilesTotalRect();
+
+	if(dimensions.w != terrainChecksum.shape()[0] || dimensions.h != terrainChecksum.shape()[1])
+	{
+		boost::multi_array<TileChecksum, 2> newCache;
+		newCache.resize(boost::extents[dimensions.w][dimensions.h]);
+		terrainChecksum.resize(boost::extents[dimensions.w][dimensions.h]);
+		terrainChecksum = newCache;
+	}
+
+	if(dimensions.w != tilesUpToDate.shape()[0] || dimensions.h != tilesUpToDate.shape()[1])
+	{
+		boost::multi_array<bool, 2> newCache;
+		newCache.resize(boost::extents[dimensions.w][dimensions.h]);
+		tilesUpToDate.resize(boost::extents[dimensions.w][dimensions.h]);
+		tilesUpToDate = newCache;
+	}
+
+	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
+		for(int x = dimensions.left(); x < dimensions.right(); ++x)
+			updateTile(context, {x, y, model->getLevel()});
+
+	cachedLevel = model->getLevel();
+}
+
+void MapViewCache::render(const std::shared_ptr<IMapRendererContext> & context, Canvas & target, bool fullRedraw)
+{
+	bool mapMoved = (cachedPosition != model->getMapViewCenter());
+	bool lazyUpdate = !mapMoved && !fullRedraw && context->viewTransitionProgress() == 0;
+
+	Rect dimensions = model->getTilesTotalRect();
+
+	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
+	{
+		for(int x = dimensions.left(); x < dimensions.right(); ++x)
+		{
+			int cacheX = (terrainChecksum.shape()[0] + x) % terrainChecksum.shape()[0];
+			int cacheY = (terrainChecksum.shape()[1] + y) % terrainChecksum.shape()[1];
+			int3 tile(x, y, model->getLevel());
+
+			if(lazyUpdate && tilesUpToDate[cacheX][cacheY])
+				continue;
+
+			Canvas source = getTile(tile);
+			Rect targetRect = model->getTargetTileArea(tile);
+			target.draw(source, targetRect.topLeft());
+
+			if (!fullRedraw)
+				tilesUpToDate[cacheX][cacheY] = true;
+		}
+	}
+
+	if(context->showOverlay())
+	{
+		for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
+		{
+			for(int x = dimensions.left(); x < dimensions.right(); ++x)
+			{
+				int3 tile(x, y, model->getLevel());
+				Rect targetRect = model->getTargetTileArea(tile);
+				auto overlay = getOverlayImageForTile(context, tile);
+
+				if(overlay)
+				{
+					Point position = targetRect.center() - overlay->dimensions() / 2;
+					target.draw(overlay, position);
+				}
+			}
+		}
+	}
+
+	if(context->viewTransitionProgress() != 0)
+		target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress());
+
+	cachedPosition = model->getMapViewCenter();
+}
+
+void MapViewCache::createTransitionSnapshot(const std::shared_ptr<IMapRendererContext> & context)
+{
+	update(context);
+	render(context, *terrainTransition, true);
+}

+ 77 - 0
client/mapView/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
+
+#include "../../lib/Point.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class ObjectInstanceID;
+VCMI_LIB_NAMESPACE_END
+
+class IImage;
+class CAnimation;
+class Canvas;
+class MapRenderer;
+class IMapRendererContext;
+class MapViewModel;
+
+/// Class responsible for rendering of entire map view
+/// uses rendering parameters provided by owner class
+class MapViewCache
+{
+	struct TileChecksum
+	{
+		int tileX = std::numeric_limits<int>::min();
+		int tileY = std::numeric_limits<int>::min();
+		std::array<uint8_t, 8> checksum{};
+
+		bool operator==(const TileChecksum & other) const
+		{
+			return tileX == other.tileX && tileY == other.tileY && checksum == other.checksum;
+		}
+	};
+
+	boost::multi_array<TileChecksum, 2> terrainChecksum;
+	boost::multi_array<bool, 2> tilesUpToDate;
+
+	Point cachedPosition;
+	int cachedLevel;
+
+	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<IMapRendererContext> & context, const int3 & coordinates);
+
+	std::shared_ptr<IImage> getOverlayImageForTile(const std::shared_ptr<IMapRendererContext> & context, const int3 & coordinates);
+
+public:
+	explicit MapViewCache(const std::shared_ptr<MapViewModel> & model);
+	~MapViewCache();
+
+	/// invalidates cache of specified object
+	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);
+
+	/// renders updated terrain cache onto provided canvas
+	void render(const std::shared_ptr<IMapRendererContext> & context, Canvas & target, bool fullRedraw);
+
+	/// creates snapshot of current view and stores it into internal canvas
+	/// used for view transition, e.g. Dimension Door spell or teleporters (Subterra gates / Monolith)
+	void createTransitionSnapshot(const std::shared_ptr<IMapRendererContext> & context);
+};

+ 478 - 0
client/mapView/MapViewController.cpp

@@ -0,0 +1,478 @@
+/*
+ * 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 "MapRendererContextState.h"
+#include "MapViewCache.h"
+#include "MapViewModel.h"
+
+#include "../CPlayerInterface.h"
+#include "../adventureMap/CAdvMapInt.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CPathfinder.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/MiscObjects.h"
+#include "../../lib/spells/ViewSpellInt.h"
+
+void MapViewController::setViewCenter(const int3 & position)
+{
+	assert(context->isInMap(position));
+	setViewCenter(Point(position) * model->getSingleTileSize() + model->getSingleTileSize() / 2, position.z);
+}
+
+void MapViewController::setViewCenter(const Point & position, int level)
+{
+	Point upperLimit = Point(context->getMapSize()) * model->getSingleTileSize() + model->getSingleTileSize();
+	Point lowerLimit = Point(0, 0);
+
+	if(worldViewContext)
+	{
+		Point area = model->getPixelsVisibleDimensions();
+		Point mapCenter = upperLimit / 2;
+
+		Point desiredLowerLimit = lowerLimit + area / 2;
+		Point desiredUpperLimit = upperLimit - area / 2;
+
+		Point actualLowerLimit{
+			std::min(desiredLowerLimit.x, mapCenter.x),
+			std::min(desiredLowerLimit.y, mapCenter.y)
+		};
+
+		Point actualUpperLimit{
+			std::max(desiredUpperLimit.x, mapCenter.x),
+			std::max(desiredUpperLimit.y, mapCenter.y)
+		};
+
+		upperLimit = actualUpperLimit;
+		lowerLimit = actualLowerLimit;
+	}
+
+	Point betterPosition = {vstd::clamp(position.x, lowerLimit.x, upperLimit.x), vstd::clamp(position.y, lowerLimit.y, upperLimit.y)};
+
+	model->setViewCenter(betterPosition);
+	model->setLevel(vstd::clamp(level, 0, context->getMapSize().z));
+
+	if(adventureInt) // may be called before adventureInt is initialized
+		adventureInt->onMapViewMoved(model->getTilesTotalRect(), model->getLevel());
+}
+
+void MapViewController::setTileSize(const Point & tileSize)
+{
+	model->setTileSize(tileSize);
+
+	// force update of view center since changing tile size may invalidated it
+	setViewCenter(model->getMapViewCenter(), model->getLevel());
+}
+
+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
+{
+	return context;
+}
+
+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;
+
+	if(movementContext)
+	{
+		const auto * object = context->getObject(movementContext->target);
+		const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
+		const auto * boat = dynamic_cast<const CGBoat *>(object);
+
+		assert(boat || hero);
+
+		if(!hero)
+			hero = boat->hero;
+
+		double heroMoveTime = LOCPLINT->makingTurn ?
+			settings["adventure"]["heroMoveTime"].Float() :
+			settings["adventure"]["enemyMoveTime"].Float();
+
+		movementContext->progress += timeDelta / heroMoveTime;
+
+		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, movementContext->progress);
+
+		if(movementContext->progress >= 1.0)
+		{
+			setViewCenter(hero->getSightCenter());
+
+			removeObject(context->getObject(movementContext->target));
+			addObject(context->getObject(movementContext->target));
+
+			activateAdventureContext(movementContext->animationTime);
+		}
+		else
+		{
+			setViewCenter(positionCurr, movementContext->tileDest.z);
+		}
+	}
+
+	if(teleportContext)
+	{
+		teleportContext->progress += timeDelta / heroTeleportDuration;
+		if(teleportContext->progress >= 1.0)
+		{
+			activateAdventureContext(teleportContext->animationTime);
+		}
+	}
+
+	if(fadingOutContext)
+	{
+		fadingOutContext->progress -= timeDelta / fadeOutDuration;
+
+		if(fadingOutContext->progress <= 0.0)
+		{
+			removeObject(context->getObject(fadingOutContext->target));
+
+			activateAdventureContext(fadingOutContext->animationTime);
+		}
+	}
+
+	if(fadingInContext)
+	{
+		fadingInContext->progress += timeDelta / fadeInDuration;
+
+		if(fadingInContext->progress >= 1.0)
+		{
+			activateAdventureContext(fadingInContext->animationTime);
+		}
+	}
+
+	if(adventureContext)
+	{
+		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;
+
+	if(!LOCPLINT->makingTurn && settings["adventure"]["enemyMoveTime"].Float() < 0)
+		return false; // enemy move speed set to "hidden/none"
+
+	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)
+{
+	assert(!hasOngoingAnimations());
+
+	if(isEventVisible(obj))
+		fadeInObject(obj);
+
+	addObject(obj);
+}
+
+void MapViewController::onObjectFadeOut(const CGObjectInstance * obj)
+{
+	assert(!hasOngoingAnimations());
+
+	if(isEventVisible(obj))
+		fadeOutObject(obj);
+	else
+		removeObject(obj);
+}
+
+void MapViewController::onObjectInstantAdd(const CGObjectInstance * obj)
+{
+	addObject(obj);
+};
+
+void MapViewController::onObjectInstantRemove(const CGObjectInstance * obj)
+{
+	removeObject(obj);
+};
+
+void MapViewController::onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!hasOngoingAnimations());
+
+	if(isEventVisible(obj, from, dest))
+	{
+		setViewCenter(obj->getSightCenter());
+		view->createTransitionSnapshot(context);
+	}
+}
+
+void MapViewController::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!hasOngoingAnimations());
+
+	removeObject(obj);
+	addObject(obj);
+
+	if(isEventVisible(obj, from, dest))
+	{
+		teleportContext = std::make_shared<MapRendererAdventureTransitionContext>(*state);
+		teleportContext->animationTime = adventureContext->animationTime;
+		adventureContext = teleportContext;
+		context = teleportContext;
+		setViewCenter(obj->getSightCenter());
+	}
+}
+
+void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(!hasOngoingAnimations());
+
+	// revisiting via spacebar, no need to animate
+	if(from == dest)
+		return;
+
+	const CGObjectInstance * movingObject = obj;
+	if(obj->boat)
+		movingObject = obj->boat;
+
+	removeObject(movingObject);
+
+	if(!isEventVisible(obj, from, dest))
+	{
+		addObject(movingObject);
+		return;
+	}
+
+	double movementTime = LOCPLINT->playerID == obj->tempOwner ?
+		settings["adventure"]["heroMoveTime"].Float() :
+		settings["adventure"]["enemyMoveTime"].Float();
+
+	if(movementTime > 1)
+	{
+		movementContext = std::make_shared<MapRendererAdventureMovingContext>(*state);
+		movementContext->animationTime = adventureContext->animationTime;
+		adventureContext = movementContext;
+		context = movementContext;
+
+		state->addMovingObject(movingObject, from, dest);
+
+		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(movementContext)
+		return true;
+
+	if(fadingOutContext)
+		return true;
+
+	if(fadingInContext)
+		return true;
+
+	if(teleportContext)
+		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();
+	teleportContext.reset();
+	worldViewContext.reset();
+	spellViewContext.reset();
+	puzzleMapContext.reset();
+}
+
+void MapViewController::setTerrainVisibility(bool showAllTerrain)
+{
+	assert(spellViewContext);
+	spellViewContext->showAllTerrain = showAllTerrain;
+}
+
+void MapViewController::setOverlayVisibility(const std::vector<ObjectPosInfo> & objectPositions)
+{
+	assert(spellViewContext);
+	spellViewContext->additionalOverlayIcons = objectPositions;
+}

+ 96 - 0
client/mapView/MapViewController.h

@@ -0,0 +1,96 @@
+/*
+ * 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;
+struct ObjectPosInfo;
+VCMI_LIB_NAMESPACE_END
+
+struct MapRendererContextState;
+
+class MapViewCache;
+class MapViewModel;
+class IMapRendererContext;
+class MapRendererAdventureContext;
+class MapRendererAdventureFadingContext;
+class MapRendererAdventureMovingContext;
+class MapRendererAdventureTransitionContext;
+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<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<MapRendererAdventureTransitionContext> 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 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 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();
+
+public:
+	MapViewController(std::shared_ptr<MapViewModel> model, std::shared_ptr<MapViewCache> view);
+
+	std::shared_ptr<IMapRendererContext> getContext() const;
+
+	void setViewCenter(const int3 & position);
+	void setViewCenter(const Point & position, int level);
+	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);
+};

+ 117 - 0
client/mapView/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/mapView/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;
+};

+ 227 - 0
client/mapView/mapHandler.cpp

@@ -0,0 +1,227 @@
+/*
+ * mapHandler.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 "IMapRendererObserver.h"
+#include "mapHandler.h"
+
+#include "../CCallback.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/TerrainHandler.h"
+#include "../../lib/UnlockGuard.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CObjectClassesHandler.h"
+#include "../../lib/mapping/CMap.h"
+
+bool CMapHandler::hasOngoingAnimations()
+{
+	for(auto * observer : observers)
+		if(observer->hasOngoingAnimations())
+			return true;
+
+	return false;
+}
+
+void CMapHandler::waitForOngoingAnimations()
+{
+	while(CGI->mh->hasOngoingAnimations())
+	{
+		auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
+		boost::this_thread::sleep(boost::posix_time::milliseconds(1));
+	}
+}
+
+std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const
+{
+	const TerrainTile & t = map->getTile(pos);
+
+	if(t.hasFavorableWinds())
+		return CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0);
+
+	std::string result = t.terType->getNameTranslated();
+
+	for(const auto & object : map->objects)
+	{
+		if(object && object->coveringAt(pos.x, pos.y) && object->pos.z == pos.z && object->isTile2Terrain())
+		{
+			result = object->getObjectName();
+			break;
+		}
+	}
+
+	if(LOCPLINT->cb->getTileDigStatus(pos, false) == EDiggingStatus::CAN_DIG)
+	{
+		return boost::str(
+			boost::format(rightClick ? "%s\r\n%s" : "%s %s") // New line for the Message Box, space for the Status Bar
+			% result % CGI->generaltexth->allTexts[330]
+		); // 'digging ok'
+	}
+
+	return result;
+}
+
+bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b)
+{
+	if(!a)
+		return true;
+	if(!b)
+		return false;
+
+	// Background objects will always be placed below foreground objects
+	if(a->appearance->printPriority != 0 || b->appearance->printPriority != 0)
+	{
+		if(a->appearance->printPriority != b->appearance->printPriority)
+			return a->appearance->printPriority > b->appearance->printPriority;
+
+		//Two background objects will be placed based on their placement order on map
+		return a->id < b->id;
+	}
+
+	int aBlocksB = 0;
+	int bBlocksA = 0;
+
+	for(const auto & aOffset : a->getBlockedOffsets())
+	{
+		int3 testTarget = a->pos + aOffset + int3(0, 1, 0);
+		if(b->blockingAt(testTarget.x, testTarget.y))
+			bBlocksA += 1;
+	}
+
+	for(const auto & bOffset : b->getBlockedOffsets())
+	{
+		int3 testTarget = b->pos + bOffset + int3(0, 1, 0);
+		if(a->blockingAt(testTarget.x, testTarget.y))
+			aBlocksB += 1;
+	}
+
+	// Discovered by experimenting with H3 maps - object priority depends on how many tiles of object A are "blocked" by object B
+	// For example if blockmap of two objects looks like this:
+	//  ABB
+	//  AAB
+	// Here, in middle column object A has blocked tile that is immediately below tile blocked by object B
+	// Meaning, object A blocks 1 tile of object B and object B blocks 0 tiles of object A
+	// In this scenario in H3 object A will always appear above object B, irregardless of H3M order
+	if(aBlocksB != bBlocksA)
+		return aBlocksB < bBlocksA;
+
+	// object that don't have clear priority via tile blocking will appear based on their row
+	if(a->pos.y != b->pos.y)
+		return a->pos.y < b->pos.y;
+
+	// or, if all other tests fail to determine priority - simply based on H3M order
+	return a->id < b->id;
+}
+
+CMapHandler::CMapHandler(const CMap * map)
+	: map(map)
+{
+}
+
+const CMap * CMapHandler::getMap()
+{
+	return map;
+}
+
+bool CMapHandler::isInMap(const int3 & tile)
+{
+	return map->isInTheMap(tile);
+}
+
+void CMapHandler::onObjectFadeIn(const CGObjectInstance * obj)
+{
+	for(auto * observer : observers)
+		observer->onObjectFadeIn(obj);
+}
+
+void CMapHandler::onObjectFadeOut(const CGObjectInstance * obj)
+{
+	for(auto * observer : observers)
+		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)
+		observer->onObjectInstantAdd(obj);
+}
+
+void CMapHandler::onObjectInstantRemove(const CGObjectInstance * obj)
+{
+	for(auto * observer : observers)
+		observer->onObjectInstantRemove(obj);
+}
+
+void CMapHandler::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(obj->pos == dest);
+	for(auto * observer : observers)
+		observer->onAfterHeroTeleported(obj, from, dest);
+}
+
+void CMapHandler::onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(obj->pos == from);
+	for(auto * observer : observers)
+		observer->onBeforeHeroTeleported(obj, from, dest);
+}
+
+void CMapHandler::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
+{
+	assert(obj->pos == dest);
+	for(auto * observer : observers)
+		observer->onHeroMoved(obj, from, dest);
+}
+
+void CMapHandler::addMapObserver(IMapObjectObserver * object)
+{
+	observers.push_back(object);
+}
+
+void CMapHandler::removeMapObserver(IMapObjectObserver * object)
+{
+	vstd::erase(observers, object);
+}
+
+IMapObjectObserver::IMapObjectObserver()
+{
+	CGI->mh->addMapObserver(this);
+}
+
+IMapObjectObserver::~IMapObjectObserver()
+{
+	CGI->mh->removeMapObserver(this);
+}

+ 76 - 0
client/mapView/mapHandler.h

@@ -0,0 +1,76 @@
+/*
+ * mapHandler.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 "../gui/CIntObject.h"
+
+#include "../../lib/Rect.h"
+#include "../../lib/int3.h"
+#include "../../lib/spells/ViewSpellInt.h"
+
+#ifdef IN
+#	undef IN
+#endif
+
+#ifdef OUT
+#	undef OUT
+#endif
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGObjectInstance;
+class CGHeroInstance;
+class CMap;
+
+VCMI_LIB_NAMESPACE_END
+
+class IMapObjectObserver;
+
+class CMapHandler
+{
+	const CMap * map;
+	std::vector<IMapObjectObserver *> observers;
+
+public:
+	explicit CMapHandler(const CMap * map);
+
+	const CMap * getMap();
+
+	/// returns true if tile is within map bounds
+	bool isInMap(const int3 & tile);
+
+	/// see MapObjectObserver interface
+	void onObjectFadeIn(const CGObjectInstance * obj);
+	void onObjectFadeOut(const CGObjectInstance * obj);
+	void onObjectInstantAdd(const CGObjectInstance * obj);
+	void onObjectInstantRemove(const CGObjectInstance * obj);
+	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);
+
+	/// Add object to receive notifications on any changes in visible map state
+	void addMapObserver(IMapObjectObserver * observer);
+	void removeMapObserver(IMapObjectObserver * observer);
+
+	/// returns string description for terrain interaction
+	std::string getTerrainDescr(const int3 & pos, bool rightClick) const;
+
+	/// determines if the map is ready to handle new hero movement (not available during fading animations)
+	bool hasOngoingAnimations();
+
+	/// blocking wait until all ongoing animatins are over
+	void waitForOngoingAnimations();
+
+	static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b);
+};

+ 2 - 2
client/render/CAnimation.cpp

@@ -69,13 +69,13 @@ bool CAnimation::loadFrame(size_t frame, size_t group)
 		// still here? image is missing
 
 		printError(frame, group, "LoadFrame");
-		images[group][frame] = std::make_shared<SDLImage>("DEFAULT");
+		images[group][frame] = std::make_shared<SDLImage>("DEFAULT", EImageBlitMode::ALPHA);
 	}
 	else //load from separate file
 	{
 		auto img = getFromExtraDef(source[group][frame]["file"].String());
 		if(!img)
-			img = std::make_shared<SDLImage>(source[group][frame]);
+			img = std::make_shared<SDLImage>(source[group][frame], EImageBlitMode::ALPHA);
 
 		images[group][frame] = img;
 		return true;

+ 0 - 101
client/render/CFadeAnimation.cpp

@@ -1,101 +0,0 @@
-/*
- * CFadeAnimation.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 "CFadeAnimation.h"
-
-#include "../renderSDL/SDL_Extensions.h"
-
-#include <SDL_surface.h>
-
-float CFadeAnimation::initialCounter() const
-{
-	if (fadingMode == EMode::OUT)
-		return 1.0f;
-	return 0.0f;
-}
-
-void CFadeAnimation::update()
-{
-	if (!fading)
-		return;
-
-	if (fadingMode == EMode::OUT)
-		fadingCounter -= delta;
-	else
-		fadingCounter += delta;
-
-	if (isFinished())
-	{
-		fading = false;
-		if (shouldFreeSurface)
-		{
-			SDL_FreeSurface(fadingSurface);
-			fadingSurface = nullptr;
-		}
-	}
-}
-
-bool CFadeAnimation::isFinished() const
-{
-	if (fadingMode == EMode::OUT)
-		return fadingCounter <= 0.0f;
-	return fadingCounter >= 1.0f;
-}
-
-CFadeAnimation::CFadeAnimation()
-	: delta(0),	fadingSurface(nullptr), fading(false), fadingCounter(0), shouldFreeSurface(false),
-	  fadingMode(EMode::NONE)
-{
-}
-
-CFadeAnimation::~CFadeAnimation()
-{
-	if (fadingSurface && shouldFreeSurface)
-		SDL_FreeSurface(fadingSurface);
-}
-
-void CFadeAnimation::init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd, float animDelta)
-{
-	if (fading)
-	{
-		// in that case, immediately finish the previous fade
-		// (alternatively, we could just return here to ignore the new fade request until this one finished (but we'd need to free the passed bitmap to avoid leaks))
-		logGlobal->warn("Tried to init fading animation that is already running.");
-		if (fadingSurface && shouldFreeSurface)
-			SDL_FreeSurface(fadingSurface);
-	}
-	if (animDelta <= 0.0f)
-	{
-		logGlobal->warn("Fade anim: delta should be positive; %f given.", animDelta);
-		animDelta = DEFAULT_DELTA;
-	}
-
-	if (sourceSurface)
-		fadingSurface = sourceSurface;
-
-	delta = animDelta;
-	fadingMode = mode;
-	fadingCounter = initialCounter();
-	fading = true;
-	shouldFreeSurface = freeSurfaceAtEnd;
-}
-
-void CFadeAnimation::draw(SDL_Surface * targetSurface, const Point &targetPoint)
-{
-	if (!fading || !fadingSurface || fadingMode == EMode::NONE)
-	{
-		fading = false;
-		return;
-	}
-
-	CSDL_Ext::setAlpha(fadingSurface, (int)(fadingCounter * 255));
-	CSDL_Ext::blitSurface(fadingSurface, targetSurface, targetPoint); //FIXME
-	CSDL_Ext::setAlpha(fadingSurface, 255);
-}

+ 0 - 53
client/render/CFadeAnimation.h

@@ -1,53 +0,0 @@
-/*
- * CFadeAnimation.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
-
-#ifdef IN
-#undef IN
-#endif
-
-#ifdef OUT
-#undef OUT
-#endif
-
-VCMI_LIB_NAMESPACE_BEGIN
-class Point;
-VCMI_LIB_NAMESPACE_END
-
-struct SDL_Surface;
-
-const float DEFAULT_DELTA = 0.05f;
-
-class CFadeAnimation
-{
-public:
-	enum class EMode
-	{
-		NONE, IN, OUT
-	};
-private:
-	float delta;
-	SDL_Surface * fadingSurface;
-	bool fading;
-	float fadingCounter;
-	bool shouldFreeSurface;
-
-	float initialCounter() const;
-	bool isFinished() const;
-public:
-	EMode fadingMode;
-
-	CFadeAnimation();
-	~CFadeAnimation();
-	void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA);
-	void update();
-	void draw(SDL_Surface * targetSurface, const Point & targetPoint);
-	bool isFading() const { return fading; }
-};

+ 50 - 31
client/render/Canvas.cpp

@@ -11,6 +11,7 @@
 #include "Canvas.h"
 
 #include "../renderSDL/SDL_Extensions.h"
+#include "Colors.h"
 #include "IImage.h"
 #include "Graphics.h"
 
@@ -18,14 +19,14 @@
 
 Canvas::Canvas(SDL_Surface * surface):
 	surface(surface),
-	renderOffset(0,0)
+	renderArea(0,0, surface->w, surface->h)
 {
 	surface->refcount++;
 }
 
 Canvas::Canvas(const Canvas & other):
 	surface(other.surface),
-	renderOffset(other.renderOffset)
+	renderArea(other.renderArea)
 {
 	surface->refcount++;
 }
@@ -33,52 +34,70 @@ Canvas::Canvas(const Canvas & other):
 Canvas::Canvas(const Canvas & other, const Rect & newClipRect):
 	Canvas(other)
 {
-	clipRect.emplace();
-	CSDL_Ext::getClipRect(surface, clipRect.get());
-
-	Rect currClipRect = newClipRect + renderOffset;
-	CSDL_Ext::setClipRect(surface, currClipRect);
-
-	renderOffset += newClipRect.topLeft();
+	renderArea = other.renderArea.intersect(newClipRect + other.renderArea.topLeft());
 }
 
 Canvas::Canvas(const Point & size):
-	renderOffset(0,0),
+	renderArea(Point(0,0), size),
 	surface(CSDL_Ext::newSurface(size.x, size.y))
 {
+	CSDL_Ext::fillSurface(surface, Colors::TRANSPARENCY );
+	SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
 }
 
-Canvas::~Canvas()
+void Canvas::applyTransparency(bool on)
+{
+	if (on)
+		SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
+	else
+		SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
+}
+
+void Canvas::applyGrayscale()
 {
-	if (clipRect)
-		CSDL_Ext::setClipRect(surface, clipRect.get());
+	CSDL_Ext::convertToGrayscale(surface, renderArea);
+}
 
+Canvas::~Canvas()
+{
 	SDL_FreeSurface(surface);
 }
 
-void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
+void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos)
 {
 	assert(image);
 	if (image)
-		image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y);
+		image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y);
 }
 
-void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect)
+void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect)
 {
 	assert(image);
 	if (image)
-		image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
+		image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y, &sourceRect);
+}
+
+void Canvas::draw(const Canvas & image, const Point & pos)
+{
+	CSDL_Ext::blitSurface(image.surface, image.renderArea, surface, renderArea.topLeft() + pos);
 }
 
-void Canvas::draw(Canvas & image, const Point & pos)
+void Canvas::drawTransparent(const Canvas & image, const Point & pos, double transparency)
 {
-	CSDL_Ext::blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);
+	SDL_BlendMode oldMode;
+
+	SDL_GetSurfaceBlendMode(image.surface, &oldMode);
+	SDL_SetSurfaceBlendMode(image.surface, SDL_BLENDMODE_BLEND);
+	SDL_SetSurfaceAlphaMod(image.surface, 255 * transparency);
+	CSDL_Ext::blitSurface(image.surface, image.renderArea, surface, renderArea.topLeft() + pos);
+	SDL_SetSurfaceAlphaMod(image.surface, 255);
+	SDL_SetSurfaceBlendMode(image.surface, oldMode);
 }
 
-void Canvas::draw(Canvas & image, const Point & pos, const Point & targetSize)
+void Canvas::drawScaled(const Canvas & image, const Point & pos, const Point & targetSize)
 {
-	SDL_Rect targetRect = CSDL_Ext::toSDL(Rect(pos, targetSize));
-	SDL_BlitScaled(image.surface, nullptr, surface, &targetRect );
+	SDL_Rect targetRect = CSDL_Ext::toSDL(Rect(pos + renderArea.topLeft(), targetSize));
+	SDL_BlitScaled(image.surface, nullptr, surface, &targetRect);
 }
 
 void Canvas::drawPoint(const Point & dest, const ColorRGBA & color)
@@ -88,17 +107,17 @@ void Canvas::drawPoint(const Point & dest, const ColorRGBA & color)
 
 void Canvas::drawLine(const Point & from, const Point & dest, const ColorRGBA & colorFrom, const ColorRGBA & colorDest)
 {
-	CSDL_Ext::drawLine(surface, renderOffset + from, renderOffset + dest, CSDL_Ext::toSDL(colorFrom), CSDL_Ext::toSDL(colorDest));
+	CSDL_Ext::drawLine(surface, renderArea.topLeft() + from, renderArea.topLeft() + dest, CSDL_Ext::toSDL(colorFrom), CSDL_Ext::toSDL(colorDest));
 }
 
 void Canvas::drawLineDashed(const Point & from, const Point & dest, const ColorRGBA & color)
 {
-	CSDL_Ext::drawLineDashed(surface, renderOffset + from, renderOffset + dest, CSDL_Ext::toSDL(color));
+	CSDL_Ext::drawLineDashed(surface, renderArea.topLeft() + from, renderArea.topLeft() + dest, CSDL_Ext::toSDL(color));
 }
 
 void Canvas::drawBorderDashed(const Rect & target, const ColorRGBA & color)
 {
-	Rect realTarget = target + renderOffset;
+	Rect realTarget = target + renderArea.topLeft();
 
 	CSDL_Ext::drawLineDashed(surface, realTarget.topLeft(),    realTarget.topRight(),    CSDL_Ext::toSDL(color));
 	CSDL_Ext::drawLineDashed(surface, realTarget.bottomLeft(), realTarget.bottomRight(), CSDL_Ext::toSDL(color));
@@ -110,9 +129,9 @@ void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Col
 {
 	switch (alignment)
 	{
-	case ETextAlignment::TOPLEFT:      return graphics->fonts[font]->renderTextLeft  (surface, text, colorDest, renderOffset + position);
-	case ETextAlignment::CENTER:       return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderOffset + position);
-	case ETextAlignment::BOTTOMRIGHT:  return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderOffset + position);
+	case ETextAlignment::TOPLEFT:      return graphics->fonts[font]->renderTextLeft  (surface, text, colorDest, renderArea.topLeft() + position);
+	case ETextAlignment::CENTER:       return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderArea.topLeft() + position);
+	case ETextAlignment::BOTTOMRIGHT:  return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderArea.topLeft() + position);
 	}
 }
 
@@ -120,9 +139,9 @@ void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Col
 {
 	switch (alignment)
 	{
-	case ETextAlignment::TOPLEFT:      return graphics->fonts[font]->renderTextLinesLeft  (surface, text, colorDest, renderOffset + position);
-	case ETextAlignment::CENTER:       return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderOffset + position);
-	case ETextAlignment::BOTTOMRIGHT:  return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderOffset + position);
+	case ETextAlignment::TOPLEFT:      return graphics->fonts[font]->renderTextLinesLeft  (surface, text, colorDest, renderArea.topLeft() + position);
+	case ETextAlignment::CENTER:       return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderArea.topLeft() + position);
+	case ETextAlignment::BOTTOMRIGHT:  return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderArea.topLeft() + position);
 	}
 }
 

+ 16 - 10
client/render/Canvas.h

@@ -24,14 +24,11 @@ class Canvas
 	/// Target surface
 	SDL_Surface * surface;
 
-	/// Clip rect that was in use on surface originally and needs to be restored on destruction
-	boost::optional<Rect> clipRect;
+	/// Current rendering area, all rendering operations will be moved into selected area
+	Rect renderArea;
 
-	/// Current rendering area offset, all rendering operations will be moved into selected area
-	Point renderOffset;
-
-	Canvas & operator = (Canvas & other) = delete;
 public:
+	Canvas & operator = (const Canvas & other) = delete;
 
 	/// constructs canvas using existing surface. Caller maintains ownership on the surface
 	explicit Canvas(SDL_Surface * surface);
@@ -47,17 +44,26 @@ public:
 
 	~Canvas();
 
+	/// if set to true, drawing this canvas onto another canvas will use alpha channel information
+	void applyTransparency(bool on);
+
+	/// applies grayscale filter onto current image
+	void applyGrayscale();
+
 	/// renders image onto this canvas at specified position
-	void draw(std::shared_ptr<IImage> image, const Point & pos);
+	void draw(const std::shared_ptr<IImage>& image, const Point & pos);
 
 	/// renders section of image bounded by sourceRect at specified position
-	void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
+	void draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect);
 
 	/// renders another canvas onto this canvas
-	void draw(Canvas & image, const Point & pos);
+	void draw(const Canvas &image, const Point & pos);
+
+	/// renders another canvas onto this canvas with transparency
+	void drawTransparent(const Canvas & image, const Point & pos, double transparency);
 
 	/// renders another canvas onto this canvas with scaling
-	void draw(Canvas & image, const Point & pos, const Point & targetSize);
+	void drawScaled(const Canvas &image, const Point & pos, const Point & targetSize);
 
 	/// renders single pixels with specified color
 	void drawPoint(const Point & dest, const ColorRGBA & color);

+ 0 - 164
client/render/Graphics.cpp

@@ -159,115 +159,6 @@ Graphics::~Graphics()
 	delete[] neutralColorPalette;
 }
 
-void Graphics::load()
-{
-	heroMoveArrows = std::make_shared<CAnimation>("ADAG");
-	heroMoveArrows->preload();
-
-	loadHeroAnimations();
-	loadHeroFlagAnimations();
-	loadFogOfWar();
-}
-
-void Graphics::loadHeroAnimations()
-{
-	for(auto & elem : CGI->heroh->classes.objects)
-	{
-		for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->getIndex())->getTemplates())
-		{
-			if (!heroAnimations.count(templ->animationFile))
-				heroAnimations[templ->animationFile] = loadHeroAnimation(templ->animationFile);
-		}
-	}
-
-	boatAnimations[0] = loadHeroAnimation("AB01_.DEF");
-	boatAnimations[1] = loadHeroAnimation("AB02_.DEF");
-	boatAnimations[2] = loadHeroAnimation("AB03_.DEF");
-
-
-	mapObjectAnimations["AB01_.DEF"] = boatAnimations[0];
-	mapObjectAnimations["AB02_.DEF"] = boatAnimations[1];
-	mapObjectAnimations["AB03_.DEF"] = boatAnimations[2];
-}
-void Graphics::loadHeroFlagAnimations()
-{
-	static const std::vector<std::string> HERO_FLAG_ANIMATIONS =
-	{
-		"AF00", "AF01","AF02","AF03",
-		"AF04", "AF05","AF06","AF07"
-	};
-
-	static const std::vector< std::vector<std::string> > BOAT_FLAG_ANIMATIONS =
-	{
-		{
-			"ABF01L", "ABF01G", "ABF01R", "ABF01D",
-			"ABF01B", "ABF01P", "ABF01W", "ABF01K"
-		},
-		{
-			"ABF02L", "ABF02G", "ABF02R", "ABF02D",
-			"ABF02B", "ABF02P", "ABF02W", "ABF02K"
-		},
-		{
-			"ABF03L", "ABF03G", "ABF03R", "ABF03D",
-			"ABF03B", "ABF03P", "ABF03W", "ABF03K"
-		}
-	};
-
-	for(const auto & name : HERO_FLAG_ANIMATIONS)
-		heroFlagAnimations.push_back(loadHeroFlagAnimation(name));
-
-	for(int i = 0; i < BOAT_FLAG_ANIMATIONS.size(); i++)
-		for(const auto & name : BOAT_FLAG_ANIMATIONS[i])
-			boatFlagAnimations[i].push_back(loadHeroFlagAnimation(name));
-}
-
-std::shared_ptr<CAnimation> Graphics::loadHeroFlagAnimation(const std::string & name)
-{
-	//first - group number to be rotated, second - group number after rotation
-	static const std::vector<std::pair<int,int> > rotations =
-	{
-		{6,10}, {7,11}, {8,12}, {1,13},
-		{2,14}, {3,15}
-	};
-
-	std::shared_ptr<CAnimation> anim = std::make_shared<CAnimation>(name);
-	anim->preload();
-
-	for(const auto & rotation : rotations)
-	{
-		const int sourceGroup = rotation.first;
-		const int targetGroup = rotation.second;
-
-		anim->createFlippedGroup(sourceGroup, targetGroup);
-	}
-
-	return anim;
-}
-
-std::shared_ptr<CAnimation> Graphics::loadHeroAnimation(const std::string &name)
-{
-	//first - group number to be rotated, second - group number after rotation
-	static const std::vector<std::pair<int,int> > rotations =
-	{
-		{6,10}, {7,11}, {8,12}, {1,13},
-		{2,14}, {3,15}
-	};
-
-	std::shared_ptr<CAnimation> anim = std::make_shared<CAnimation>(name);
-	anim->preload();
-
-
-	for(const auto & rotation : rotations)
-	{
-		const int sourceGroup = rotation.first;
-		const int targetGroup = rotation.second;
-
-		anim->createFlippedGroup(sourceGroup, targetGroup);
-	}
-
-	return anim;
-}
-
 void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player)
 {
 	if(sur->format->palette)
@@ -335,26 +226,6 @@ void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player)
 	}
 }
 
-void Graphics::loadFogOfWar()
-{
-	fogOfWarFullHide = std::make_shared<CAnimation>("TSHRC");
-	fogOfWarFullHide->preload();
-	fogOfWarPartialHide = std::make_shared<CAnimation>("TSHRE");
-	fogOfWarPartialHide->preload();
-
-	static const int rotations [] = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
-
-	size_t size = fogOfWarPartialHide->size(0);//group size after next rotation
-
-	for(const int rotation : rotations)
-	{
-		fogOfWarPartialHide->duplicateImage(0, rotation, 0);
-		auto image = fogOfWarPartialHide->getImage(size, 0);
-		image->verticalFlip();
-		size++;
-	}
-}
-
 void Graphics::loadFonts()
 {
 	const JsonNode config(ResourceID("config/fonts.json"));
@@ -378,41 +249,6 @@ void Graphics::loadFonts()
 	}
 }
 
-std::shared_ptr<CAnimation> Graphics::getAnimation(const CGObjectInstance* obj)
-{
-	return getAnimation(obj->appearance);
-}
-
-std::shared_ptr<CAnimation> Graphics::getAnimation(std::shared_ptr<const ObjectTemplate> info)
-{
-	//the only(?) invisible object
-	if(info->id == Obj::EVENT)
-	{
-		return std::shared_ptr<CAnimation>();
-	}
-
-	if(info->animationFile.empty())
-	{
-		logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
-		return std::shared_ptr<CAnimation>();
-	}
-
-	std::shared_ptr<CAnimation> ret = mapObjectAnimations[info->animationFile];
-
-	//already loaded
-	if(ret)
-	{
-		ret->preload();
-		return ret;
-	}
-
-	ret = std::make_shared<CAnimation>(info->animationFile);
-	mapObjectAnimations[info->animationFile] = ret;
-
-	ret->preload();
-	return ret;
-}
-
 void Graphics::loadErmuToPicture()
 {
 	//loading ERMU to picture

+ 0 - 34
client/render/Graphics.h

@@ -39,23 +39,11 @@ enum EFonts : int
 class Graphics
 {
 	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
-
 	void addImageListEntries(const EntityService * service);
 
 	void initializeBattleGraphics();
 	void loadPaletteAndColors();
-
-	void loadHeroAnimations();
-	//loads animation and adds required rotated frames
-	std::shared_ptr<CAnimation> loadHeroAnimation(const std::string &name);
-
-	void loadHeroFlagAnimations();
-
-	//loads animation and adds required rotated frames
-	std::shared_ptr<CAnimation> loadHeroFlagAnimation(const std::string &name);
-
 	void loadErmuToPicture();
-	void loadFogOfWar();
 	void loadFonts();
 	void initializeImageLists();
 
@@ -70,23 +58,6 @@ public:
 	SDL_Color * playerColorPalette; //palette to make interface colors good - array of size [256]
 	SDL_Color * neutralColorPalette;
 
-	std::shared_ptr<CAnimation> heroMoveArrows;
-
-	// [hero class def name]  //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
-	std::map< std::string, std::shared_ptr<CAnimation> > heroAnimations;
-	std::vector< std::shared_ptr<CAnimation> > heroFlagAnimations;
-
-	// [boat type: 0 .. 2]  //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
-	std::array< std::shared_ptr<CAnimation>, 3> boatAnimations;
-
-	std::array< std::vector<std::shared_ptr<CAnimation> >, 3> boatFlagAnimations;
-
-	//all other objects (not hero or boat)
-	std::map< std::string, std::shared_ptr<CAnimation> > mapObjectAnimations;
-
-	std::shared_ptr<CAnimation> fogOfWarFullHide;
-	std::shared_ptr<CAnimation> fogOfWarPartialHide;
-
 	std::map<std::string, JsonNode> imageLists;
 
 	//towns
@@ -98,12 +69,7 @@ public:
 	Graphics();
 	~Graphics();
 
-	void load();
-
 	void blueToPlayersAdv(SDL_Surface * sur, PlayerColor player); //replaces blue interface colour with a color of player
-
-	std::shared_ptr<CAnimation> getAnimation(const CGObjectInstance * obj);
-	std::shared_ptr<CAnimation> getAnimation(std::shared_ptr<const ObjectTemplate> info);
 };
 
 extern Graphics * graphics;

+ 16 - 1
client/render/IImage.h

@@ -21,6 +21,19 @@ struct SDL_Surface;
 struct SDL_Color;
 class ColorFilter;
 
+/// Defines which blit method will be selected when image is used for rendering
+enum class EImageBlitMode : uint8_t
+{
+	/// Image can have no transparency and can be only used as background
+	OPAQUE,
+
+	/// Image can have only a single color as transparency and has no semi-transparent areas
+	COLORKEY,
+
+	/// Image might have full alpha transparency range, e.g. shadows
+	ALPHA
+};
+
 /*
  * Base class for images, can be used for non-animation pictures as well
  */
@@ -51,12 +64,13 @@ public:
 	int height() const;
 
 	//only indexed bitmaps, 16 colors maximum
-	virtual void shiftPalette(int from, int howMany) = 0;
+	virtual void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) = 0;
 	virtual void adjustPalette(const ColorFilter & shifter, size_t colorsToSkip) = 0;
 	virtual void resetPalette(int colorID) = 0;
 	virtual void resetPalette() = 0;
 
 	virtual void setAlpha(uint8_t value) = 0;
+	virtual void setBlitMode(EImageBlitMode mode) = 0;
 
 	//only indexed bitmaps with 7 special colors
 	virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
@@ -69,6 +83,7 @@ public:
 
 	/// loads image from specified file. Returns 0-sized images on failure
 	static std::shared_ptr<IImage> createFromFile( const std::string & path );
+	static std::shared_ptr<IImage> createFromFile( const std::string & path, EImageBlitMode mode );
 
 	/// temporary compatibility method. Creates IImage from existing SDL_Surface
 	/// Surface will be shared, called must still free it with SDL_FreeSurface

+ 37 - 18
client/renderSDL/SDLImage.cpp

@@ -26,12 +26,17 @@ class SDLImageLoader;
 
 std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
 {
-	return std::shared_ptr<IImage>(new SDLImage(path));
+	return createFromFile(path, EImageBlitMode::ALPHA);
+}
+
+std::shared_ptr<IImage> IImage::createFromFile( const std::string & path, EImageBlitMode mode )
+{
+	return std::shared_ptr<IImage>(new SDLImage(path, mode));
 }
 
 std::shared_ptr<IImage> IImage::createFromSurface( SDL_Surface * source )
 {
-	return std::shared_ptr<IImage>(new SDLImage(source, true));
+	return std::shared_ptr<IImage>(new SDLImage(source, EImageBlitMode::ALPHA));
 }
 
 IImage::IImage() = default;
@@ -57,9 +62,10 @@ SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
 	data->loadFrame(frame, group, loader);
 
 	savePalette();
+	setBlitMode(EImageBlitMode::ALPHA);
 }
 
-SDLImage::SDLImage(SDL_Surface * from, bool extraRef)
+SDLImage::SDLImage(SDL_Surface * from, EImageBlitMode mode)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -70,14 +76,14 @@ SDLImage::SDLImage(SDL_Surface * from, bool extraRef)
 		return;
 
 	savePalette();
+	setBlitMode(mode);
 
-	if (extraRef)
-		surf->refcount++;
+	surf->refcount++;
 	fullSize.x = surf->w;
 	fullSize.y = surf->h;
 }
 
-SDLImage::SDLImage(const JsonNode & conf)
+SDLImage::SDLImage(const JsonNode & conf, EImageBlitMode mode)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -91,6 +97,7 @@ SDLImage::SDLImage(const JsonNode & conf)
 		return;
 
 	savePalette();
+	setBlitMode(mode);
 
 	const JsonNode & jsonMargins = conf["margins"];
 
@@ -111,7 +118,7 @@ SDLImage::SDLImage(const JsonNode & conf)
 	}
 }
 
-SDLImage::SDLImage(std::string filename)
+SDLImage::SDLImage(std::string filename, EImageBlitMode mode)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -127,6 +134,7 @@ SDLImage::SDLImage(std::string filename)
 	else
 	{
 		savePalette();
+		setBlitMode(mode);
 		fullSize.x = surf->w;
 		fullSize.y = surf->h;
 	}
@@ -172,7 +180,7 @@ void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) cons
 	if (SDL_GetSurfaceAlphaMod(surf, &perSurfaceAlpha) != 0)
 		logGlobal->error("SDL_GetSurfaceAlphaMod faied! %s", SDL_GetError());
 
-	if(surf->format->BitsPerPixel == 8 && perSurfaceAlpha == SDL_ALPHA_OPAQUE)
+	if(surf->format->BitsPerPixel == 8 && perSurfaceAlpha == SDL_ALPHA_OPAQUE && blitMode == EImageBlitMode::ALPHA)
 	{
 		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift);
 	}
@@ -196,7 +204,7 @@ std::shared_ptr<IImage> SDLImage::scaleFast(const Point & size) const
 	else
 		CSDL_Ext::setDefaultColorKey(scaled);//just in case
 
-	SDLImage * ret = new SDLImage(scaled, false);
+	SDLImage * ret = new SDLImage(scaled, EImageBlitMode::ALPHA);
 
 	ret->fullSize.x = (int) round((float)fullSize.x * scaleX);
 	ret->fullSize.y = (int) round((float)fullSize.y * scaleY);
@@ -204,6 +212,9 @@ std::shared_ptr<IImage> SDLImage::scaleFast(const Point & size) const
 	ret->margins.x = (int) round((float)margins.x * scaleX);
 	ret->margins.y = (int) round((float)margins.y * scaleY);
 
+	// erase our own reference
+	SDL_FreeSurface(scaled);
+
 	return std::shared_ptr<IImage>(ret);
 }
 
@@ -220,7 +231,18 @@ void SDLImage::playerColored(PlayerColor player)
 void SDLImage::setAlpha(uint8_t value)
 {
 	CSDL_Ext::setAlpha (surf, value);
-	SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
+	if (value != 255)
+		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
+}
+
+void SDLImage::setBlitMode(EImageBlitMode mode)
+{
+	blitMode = mode;
+
+	if (blitMode != EImageBlitMode::OPAQUE && surf->format->Amask != 0)
+		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
+	else
+		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
 }
 
 void SDLImage::setFlagColor(PlayerColor player)
@@ -272,20 +294,17 @@ void SDLImage::savePalette()
 	SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, DEFAULT_PALETTE_COLORS);
 }
 
-void SDLImage::shiftPalette(int from, int howMany)
+void SDLImage::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
 {
-	//works with at most 16 colors, if needed more -> increase values
-	assert(howMany < 16);
-
 	if(surf->format->palette)
 	{
-		SDL_Color palette[16];
+		std::vector<SDL_Color> shifterColors(colorsToMove);
 
-		for(int i=0; i<howMany; ++i)
+		for(uint32_t i=0; i<colorsToMove; ++i)
 		{
-			palette[(i+1)%howMany] = surf->format->palette->colors[from + i];
+			shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
 		}
-		CSDL_Ext::setColors(surf, palette, from, howMany);
+		CSDL_Ext::setColors(surf, shifterColors.data(), firstColorID, colorsToMove);
 	}
 }
 

+ 7 - 4
client/renderSDL/SDLImage.h

@@ -37,15 +37,17 @@ public:
 	//total size including borders
 	Point fullSize;
 
+	EImageBlitMode blitMode;
+
 public:
 	//Load image from def file
 	SDLImage(CDefFile *data, size_t frame, size_t group=0);
 	//Load from bitmap file
-	SDLImage(std::string filename);
+	SDLImage(std::string filename, EImageBlitMode blitMode);
 
-	SDLImage(const JsonNode & conf);
+	SDLImage(const JsonNode & conf, EImageBlitMode blitMode);
 	//Create using existing surface, extraRef will increase refcount on SDL_Surface
-	SDLImage(SDL_Surface * from, bool extraRef);
+	SDLImage(SDL_Surface * from, EImageBlitMode blitMode);
 	~SDLImage();
 
 	// Keep the original palette, in order to do color switching operation
@@ -63,12 +65,13 @@ public:
 	void horizontalFlip() override;
 	void verticalFlip() override;
 
-	void shiftPalette(int from, int howMany) override;
+	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void adjustPalette(const ColorFilter & shifter, size_t colorsToSkip) override;
 	void resetPalette(int colorID) override;
 	void resetPalette() override;
 
 	void setAlpha(uint8_t value) override;
+	void setBlitMode(EImageBlitMode mode) override;
 
 	void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
 

+ 18 - 61
client/renderSDL/SDL_Extensions.cpp

@@ -625,80 +625,37 @@ void CSDL_Ext::putPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x,
 }
 
 template<int bpp>
-void CSDL_Ext::applyEffectBpp(SDL_Surface * surf, const Rect & rect, int mode )
+void CSDL_Ext::convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect )
 {
-	switch(mode)
-	{
-	case 0: //sepia
-		{
-			const int sepiaDepth = 20;
-			const int sepiaIntensity = 30;
-
-			for(int xp = rect.x; xp < rect.x + rect.w; ++xp)
-			{
-				for(int yp = rect.y; yp < rect.y + rect.h; ++yp)
-				{
-					uint8_t * pixel = (ui8*)surf->pixels + yp * surf->pitch + xp * surf->format->BytesPerPixel;
-					int r = Channels::px<bpp>::r.get(pixel);
-					int g = Channels::px<bpp>::g.get(pixel);
-					int b = Channels::px<bpp>::b.get(pixel);
-					int gray = static_cast<int>(0.299 * r + 0.587 * g + 0.114 * b);
+	uint8_t * pixels = static_cast<uint8_t*>(surf->pixels);
 
-					r = g = b = gray;
-					r = r + (sepiaDepth * 2);
-					g = g + sepiaDepth;
-
-					if (r>255) r=255;
-					if (g>255) g=255;
-					if (b>255) b=255;
-
-					// Darken blue color to increase sepia effect
-					b -= sepiaIntensity;
-
-					// normalize if out of bounds
-					if (b<0) b=0;
+	for(int yp = rect.top(); yp < rect.bottom(); ++yp)
+	{
+		uint8_t * pixel_from = pixels + yp * surf->pitch + rect.left() * surf->format->BytesPerPixel;
+		uint8_t * pixel_dest = pixels + yp * surf->pitch + rect.right() * surf->format->BytesPerPixel;
 
-					Channels::px<bpp>::r.set(pixel, r);
-					Channels::px<bpp>::g.set(pixel, g);
-					Channels::px<bpp>::b.set(pixel, b);
-				}
-			}
-		}
-		break;
-	case 1: //grayscale
+		for (uint8_t * pixel = pixel_from; pixel < pixel_dest; pixel += surf->format->BytesPerPixel)
 		{
-			for(int xp = rect.x; xp < rect.x + rect.w; ++xp)
-			{
-				for(int yp = rect.y; yp < rect.y + rect.h; ++yp)
-				{
-					uint8_t * pixel = (ui8*)surf->pixels + yp * surf->pitch + xp * surf->format->BytesPerPixel;
+			int r = Channels::px<bpp>::r.get(pixel);
+			int g = Channels::px<bpp>::g.get(pixel);
+			int b = Channels::px<bpp>::b.get(pixel);
 
-					int r = Channels::px<bpp>::r.get(pixel);
-					int g = Channels::px<bpp>::g.get(pixel);
-					int b = Channels::px<bpp>::b.get(pixel);
+			int gray = static_cast<int>(0.299 * r + 0.587 * g + 0.114 *b);
 
-					int gray = static_cast<int>(0.299 * r + 0.587 * g + 0.114 *b);
-					vstd::amax(gray, 255);
-
-					Channels::px<bpp>::r.set(pixel, gray);
-					Channels::px<bpp>::g.set(pixel, gray);
-					Channels::px<bpp>::b.set(pixel, gray);
-				}
-			}
+			Channels::px<bpp>::r.set(pixel, gray);
+			Channels::px<bpp>::g.set(pixel, gray);
+			Channels::px<bpp>::b.set(pixel, gray);
 		}
-		break;
-	default:
-		throw std::runtime_error("Unsupported effect!");
 	}
 }
 
-void CSDL_Ext::applyEffect( SDL_Surface * surf, const Rect & rect, int mode )
+void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect )
 {
 	switch(surf->format->BytesPerPixel)
 	{
-		case 2: applyEffectBpp<2>(surf, rect, mode); break;
-		case 3: applyEffectBpp<3>(surf, rect, mode); break;
-		case 4: applyEffectBpp<4>(surf, rect, mode); break;
+		case 2: convertToGrayscaleBpp<2>(surf, rect); break;
+		case 3: convertToGrayscaleBpp<3>(surf, rect); break;
+		case 4: convertToGrayscaleBpp<4>(surf, rect); break;
 	}
 }
 

+ 3 - 3
client/renderSDL/SDL_Extensions.h

@@ -102,8 +102,8 @@ typedef void (*TColorPutterAlpha)(uint8_t *&ptr, const uint8_t & R, const uint8_
 	SDL_Surface * scaleSurface(SDL_Surface *surf, int width, int height);
 
 	template<int bpp>
-	void applyEffectBpp( SDL_Surface * surf, const Rect & rect, int mode );
-	void applyEffect(SDL_Surface * surf, const Rect & rect, int mode); //mode: 0 - sepia, 1 - grayscale
+	void convertToGrayscaleBpp( SDL_Surface * surf, const Rect & rect );
+	void convertToGrayscale(SDL_Surface * surf, const Rect & rect);
 
 	bool isResolutionSupported(const std::vector<Point> & resolutions, const Point toTest );
 
@@ -118,7 +118,7 @@ typedef void (*TColorPutterAlpha)(uint8_t *&ptr, const uint8_t & R, const uint8_
 	void setDefaultColorKeyPresize(SDL_Surface * surface);
 
 	/// helper that will safely set and un-set ClipRect for SDL_Surface
-	class CClipRectGuard
+	class CClipRectGuard : boost::noncopyable
 	{
 		SDL_Surface * surf;
 		Rect oldRect;

+ 9 - 7
client/windows/CCastleInterface.cpp

@@ -61,6 +61,8 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
 	  parent(Par),
 	  town(Town),
 	  str(Str),
+	  border(nullptr),
+	  area(nullptr),
 	  stateTimeCounter(BUILD_ANIMATION_FINISHED_TIMEPOINT)
 {
 	addUsedEvents(LCLICK | RCLICK | HOVER);
@@ -82,14 +84,10 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
 	}
 
 	if(!str->borderName.empty())
-		border = IImage::createFromFile(str->borderName);
-	else
-		border = nullptr;
+		border = IImage::createFromFile(str->borderName, EImageBlitMode::ALPHA);
 
 	if(!str->areaName.empty())
-		area = IImage::createFromFile(str->areaName);
-	else
-		area = nullptr;
+		area = IImage::createFromFile(str->areaName, EImageBlitMode::ALPHA);
 }
 
 const CBuilding * CBuildingRect::getBuilding()
@@ -565,6 +563,8 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town):
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	background = std::make_shared<CPicture>(town->town->clientInfo.townBackground);
+	background->needRefresh = true;
+	background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;
 
@@ -1188,11 +1188,13 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	townlist->onSelect = std::bind(&CCastleInterface::townChange, this);
 
 	recreateIcons();
+	adventureInt->onAudioPaused();
 	CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false);
 }
 
 CCastleInterface::~CCastleInterface()
 {
+	adventureInt->onAudioResumed();
 	if(LOCPLINT->castleInt == this)
 		LOCPLINT->castleInt = nullptr;
 }
@@ -1219,7 +1221,7 @@ void CCastleInterface::castleTeleport(int where)
 	const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(where));
 	adventureInt->select(town->visitingHero);//according to assert(ho == adventureInt->selection) in the eraseCurrentPathOf
 	LOCPLINT->cb->teleportHero(town->visitingHero, dest);
-	LOCPLINT->eraseCurrentPathOf(town->visitingHero, false);
+	LOCPLINT->paths.erasePath(town->visitingHero);
 }
 
 void CCastleInterface::townChange()

+ 1 - 1
client/windows/CMessage.cpp

@@ -88,7 +88,7 @@ void CMessage::init()
 		}
 	}
 
-	background = IImage::createFromFile("DIBOXBCK.BMP");
+	background = IImage::createFromFile("DIBOXBCK.BMP", EImageBlitMode::OPAQUE);
 }
 
 void CMessage::dispose()

+ 93 - 0
client/windows/CPuzzleWindow.cpp

@@ -0,0 +1,93 @@
+/*
+ * 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 "../mapView/MapView.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);
+
+	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);
+
+	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)
+{
+	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);
+}

+ 40 - 0
client/windows/CPuzzleWindow.h

@@ -0,0 +1,40 @@
+/*
+ * 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;
+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;
+	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);
+};

+ 2 - 2
client/windows/CQuestLog.cpp

@@ -84,7 +84,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q)
 
 	Point offset = tileToPixels(tile);
 
-	setLevel(tile.z);
+	onMapViewMoved(Rect(), tile.z);
 
 	auto pic = std::make_shared<CQuestIcon>("VwSymbol.def", 3, offset.x, offset.y);
 
@@ -104,7 +104,7 @@ void CQuestMinimap::update()
 void CQuestMinimap::iconClicked()
 {
 	if(currentQuest->obj)
-		adventureInt->centerOn (currentQuest->obj->pos);
+		adventureInt->centerOnTile(currentQuest->obj->pos);
 	//moveAdvMapSelection();
 }
 

+ 1 - 76
client/windows/GUIClasses.cpp

@@ -21,8 +21,7 @@
 #include "../CVideoHandler.h"
 #include "../CServerHandler.h"
 
-#include "../adventureMap/mapHandler.h"
-#include "../adventureMap/CResDataBar.h"
+//#include "../adventureMap/CResDataBar.h"
 #include "../battle/BattleInterfaceClasses.h"
 #include "../battle/BattleInterface.h"
 
@@ -430,7 +429,6 @@ CLevelWindow::~CLevelWindow()
 	LOCPLINT->showingDialog->setn(false);
 }
 
-
 CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj)
 	: CStatusbarWindow(PLAYER_COLORED, "TPTAVERN"),
 	tavernObj(TavernObj)
@@ -1121,79 +1119,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)
-{
-	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
 {

+ 0 - 1
client/windows/InfoWindows.cpp

@@ -22,7 +22,6 @@
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
 #include "../adventureMap/CAdvMapInt.h"
-#include "../adventureMap/CTerrainRect.h"
 #include "../windows/CMessage.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../gui/CursorHandler.h"

+ 7 - 7
client/windows/settings/AdventureOptionsTab.cpp

@@ -37,15 +37,15 @@ AdventureOptionsTab::AdventureOptionsTab()
 	const JsonNode config(ResourceID("config/widgets/settings/adventureOptionsTab.json"));
 	addCallback("playerHeroSpeedChanged", [](int value)
 	{
-		return setIntSetting("adventure", "heroSpeed", value);
+		return setIntSetting("adventure", "heroMoveTime", value);
 	});
 	addCallback("enemyHeroSpeedChanged", [](int value)
 	{
-		return setIntSetting("adventure", "enemySpeed", value);
+		return setIntSetting("adventure", "enemyMoveTime", value);
 	});
 	addCallback("mapScrollSpeedChanged", [](int value)
 	{
-		return setIntSetting("adventure", "scrollSpeed", value);
+		return setIntSetting("adventure", "scrollSpeedPixels", value);
 	});
 	addCallback("heroReminderChanged", [](bool value)
 	{
@@ -71,13 +71,13 @@ AdventureOptionsTab::AdventureOptionsTab()
 	build(config);
 
 	std::shared_ptr<CToggleGroup> playerHeroSpeedToggle = widget<CToggleGroup>("heroMovementSpeedPicker");
-	playerHeroSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["heroSpeed"].Float()));
+	playerHeroSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["heroMoveTime"].Float()));
 
 	std::shared_ptr<CToggleGroup> enemyHeroSpeedToggle = widget<CToggleGroup>("enemyMovementSpeedPicker");
-	enemyHeroSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["enemySpeed"].Float()));
+	enemyHeroSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["enemyMoveTime"].Float()));
 
 	std::shared_ptr<CToggleGroup> mapScrollSpeedToggle = widget<CToggleGroup>("mapScrollSpeedPicker");
-	mapScrollSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["scrollSpeed"].Float()));
+	mapScrollSpeedToggle->setSelected(static_cast<int>(settings["adventure"]["scrollSpeedPixels"].Float()));
 
 	std::shared_ptr<CToggleButton> heroReminderCheckbox = widget<CToggleButton>("heroReminderCheckbox");
 	heroReminderCheckbox->setSelected(settings["adventure"]["heroReminder"].Bool());
@@ -93,4 +93,4 @@ AdventureOptionsTab::AdventureOptionsTab()
 
 	std::shared_ptr<CToggleButton> showGridCheckbox = widget<CToggleButton>("showGridCheckbox");
 	showGridCheckbox->setSelected(settings["gameTweaks"]["showGrid"].Bool());
-}
+}

+ 0 - 4
config/ambientSounds.json

@@ -1,8 +1,4 @@
 {
-  // By default visitable objects only have ambient sound source on visitable tile
-  // Static objects and special terrains that have ambient sound have source on all tiles
-  // If set to true then all tiles will become source for visitable objects
-  "allTilesSource": false,
   // By default SDL2_Mixer allocate 8 channels, but more sounds require more channels
   "allocateChannels" : 16,
   // Maximal ambient sounds volume must be about 20% of global effects volume

+ 20 - 7
config/schemas/settings.json

@@ -25,6 +25,7 @@
 				"encoding",
 				"language",
 				"swipe",
+				"swipeDesktop",
 				"saveRandomMaps",
 				"saveFrequency",
 				"notifications",
@@ -58,6 +59,10 @@
 					"type" : "boolean",
 					"default" : true
 				},
+				"swipeDesktop" : {
+					"type" : "boolean",
+					"default" : false
+				},
 				"saveRandomMaps" : {
 					"type" : "boolean",
 					"default" : false
@@ -161,19 +166,19 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "heroSpeed", "enemySpeed", "scrollSpeed", "heroReminder", "quickCombat" ],
+			"required" : [ "heroMoveTime", "enemyMoveTime", "scrollSpeedPixels", "heroReminder", "quickCombat", "objectAnimation", "terrainAnimation" ],
 			"properties" : {
-				"heroSpeed" : {
+				"heroMoveTime" : {
 					"type" : "number",
-					"default" : 2
+					"default" : 150
 				},
-				"enemySpeed" : {
+				"enemyMoveTime" : {
 					"type" : "number",
-					"default" : 2
+					"default" : 150
 				},
-				"scrollSpeed" : {
+				"scrollSpeedPixels" : {
 					"type" : "number",
-					"default" : 1
+					"default" : 800
 				},
 				"heroReminder" : {
 					"type" : "boolean",
@@ -182,6 +187,14 @@
 				"quickCombat" : {
 					"type" : "boolean",
 					"default" : false
+				},
+				"objectAnimation" : {
+					"type" : "boolean",
+					"default" : true
+				},
+				"terrainAnimation" : {
+					"type" : "boolean",
+					"default" : true
 				}
 			}
 		},

+ 1 - 1
config/terrains.json

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

+ 12 - 12
config/widgets/settings/adventureOptionsTab.json

@@ -31,7 +31,7 @@
 			"items":
 			[
 				{
-					"index": 1,
+					"index": 200,
 					"type": "toggleButton",
 					"image": "sysopb1",
 					"help": "core.help.349",
@@ -39,7 +39,7 @@
 				},
 
 				{
-					"index": 2,
+					"index": 150,
 					"type": "toggleButton",
 					"image": "sysopb2",
 					"help": "core.help.350",
@@ -47,7 +47,7 @@
 				},
 
 				{
-					"index": 4,
+					"index": 100,
 					"type": "toggleButton",
 					"image": "sysopb3",
 					"help": "core.help.351",
@@ -55,7 +55,7 @@
 				},
 
 				{
-					"index": 16,
+					"index": 0,
 					"type": "toggleButton",
 					"image": "sysopb4",
 					"help": "core.help.352",
@@ -80,7 +80,7 @@
 			"items":
 			[
 				{
-					"index": 2,
+					"index": 150,
 					"type": "toggleButton",
 					"image": "sysopb5",
 					"help": "core.help.353",
@@ -88,7 +88,7 @@
 				},
 
 				{
-					"index": 4,
+					"index": 100,
 					"type": "toggleButton",
 					"image": "sysopb6",
 					"help": "core.help.354",
@@ -96,7 +96,7 @@
 				},
 
 				{
-					"index": 8,
+					"index": 0,
 					"type": "toggleButton",
 					"image": "sysopb7",
 					"help": "core.help.355",
@@ -104,7 +104,7 @@
 				},
 
 				{
-					"index": 0,
+					"index": -1,
 					"type": "toggleButton",
 					"image": "sysopb8",
 					"help": "core.help.356",
@@ -121,7 +121,7 @@
 			"items":
 			[
 				{
-					"index": 1,
+					"index": 400,
 					"type": "toggleButton",
 					"image": "sysopb9",
 					"help": "core.help.357",
@@ -129,7 +129,7 @@
 				},
 
 				{
-					"index": 2,
+					"index": 800,
 					"type": "toggleButton",
 					"image": "sysob10",
 					"help": "core.help.358",
@@ -137,7 +137,7 @@
 				},
 
 				{
-					"index": 4,
+					"index": 1200,
 					"type": "toggleButton",
 					"image": "sysob11",
 					"help": "core.help.359",
@@ -264,4 +264,4 @@
 			"callback": "showGridChanged"
 		}
 	]
-}
+}

+ 13 - 0
lib/CGameInfoCallback.cpp

@@ -504,6 +504,19 @@ const TerrainTile * CGameInfoCallback::getTile( int3 tile, bool verbose) const
 	return nullptr;
 }
 
+EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) const
+{
+	if(!isVisible(tile))
+		return EDiggingStatus::UNKNOWN;
+
+	for(const auto & object : gs->map->objects)
+	{
+		if(object && object->ID == Obj::HOLE && object->pos == tile)
+			return EDiggingStatus::TILE_OCCUPIED;
+	}
+	return getTile(tile)->getDiggingStatus();
+}
+
 //TODO: typedef?
 std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> CGameInfoCallback::getAllVisibleTiles() const
 {

+ 1 - 0
lib/CGameInfoCallback.h

@@ -197,6 +197,7 @@ public:
 	virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
 	virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config);
 	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
+	virtual EDiggingStatus getTileDigStatus(int3 tile, bool verbose = true) const;
 
 	//town
 	virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;

+ 1 - 1
lib/CGameInterface.h

@@ -107,7 +107,7 @@ public:
 	virtual void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) = 0;
 	virtual void finish(){}; //if for some reason we want to end
 
-	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};
+	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain){};
 
 	virtual boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
 	{

+ 1 - 1
lib/IGameCallback.h

@@ -126,7 +126,7 @@ public:
 	virtual void setMovePoints(SetMovePoints * smp)=0;
 	virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
 	virtual void giveHero(ObjectInstanceID id, PlayerColor player)=0;
-	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags)=0;
+	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos)=0;
 	virtual void sendAndApply(CPackForClient * pack) = 0;
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) = 0;

+ 2 - 5
lib/NetPacks.h

@@ -380,7 +380,6 @@ struct DLL_LINKAGE ChangeObjPos : public CPackForClient
 
 	ObjectInstanceID objid;
 	int3 nPos;
-	ui8 flags = 0; //bit flags: 1 - redraw
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
@@ -388,7 +387,6 @@ struct DLL_LINKAGE ChangeObjPos : public CPackForClient
 	{
 		h & objid;
 		h & nPos;
-		h & flags;
 	}
 };
 
@@ -583,7 +581,6 @@ struct DLL_LINKAGE TryMoveHero : public CPackForClient
 		FAILED,
 		SUCCESS,
 		TELEPORTATION,
-		RESERVED_,
 		BLOCKING_VISIT,
 		EMBARK,
 		DISEMBARK
@@ -596,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
@@ -1911,12 +1906,14 @@ protected:
 struct DLL_LINKAGE ShowWorldViewEx : public CPackForClient
 {
 	PlayerColor player;
+	bool showTerrain; // TODO: send terrain state
 
 	std::vector<ObjectPosInfo> objectPositions;
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & player;
+		h & showTerrain;
 		h & objectPositions;
 	}
 

+ 11 - 4
lib/Point.h

@@ -22,13 +22,15 @@ public:
 	Point()
 	{
 		x = y = 0;
-	};
+	}
 
 	Point(int X, int Y)
-		:x(X),y(Y)
-	{};
+		: x(X)
+		, y(Y)
+	{
+	}
 
-	Point(const int3 &a);
+	explicit DLL_LINKAGE Point(const int3 &a);
 
 	template<typename T>
 	Point operator+(const T &b) const
@@ -48,6 +50,11 @@ public:
 		return Point(x*mul, y*mul);
 	}
 
+	Point operator*(const Point &b) const
+	{
+		return Point(x*b.x,y*b.y);
+	}
+
 	template<typename T>
 	Point& operator+=(const T &b)
 	{

+ 7 - 0
lib/Rect.cpp

@@ -10,9 +10,16 @@
 
 #include "StdInc.h"
 #include "Rect.h"
+#include "int3.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+Point::Point(const int3 & a)
+	: x(a.x)
+	, y(a.y)
+{
+}
+
 /// Returns rect union - rect that covers both this rect and provided rect
 Rect Rect::include(const Rect & other) const
 {

+ 5 - 0
lib/Rect.h

@@ -132,6 +132,11 @@ public:
 		return *this;
 	}
 
+	bool operator == (const Rect & other)
+	{
+		return x == other.x && y == other.y && w == other.w && h == other.h;
+	}
+
 	/// returns true if this rect intersects with another rect
 	DLL_LINKAGE bool intersectionTest(const Rect & other) const;
 

+ 1 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -237,7 +237,6 @@ CGHeroInstance::CGHeroInstance():
 	IBoatGenerator(this),
 	tacticFormationEnabled(false),
 	inTownGarrison(false),
-	isStanding(true),
 	moveDir(4),
 	mana(UNINITIALIZED_MANA),
 	movement(UNINITIALIZED_MOVEMENT),
@@ -1141,7 +1140,7 @@ EDiggingStatus CGHeroInstance::diggingStatus() const
 	if(static_cast<int>(movement) < maxMovePoints(true))
 		return EDiggingStatus::LACK_OF_MOVEMENT;
 
-	return cb->getTile(visitablePos())->getDiggingStatus();
+	return cb->getTileDigStatus(visitablePos());
 }
 
 ArtBearer::ArtBearer CGHeroInstance::bearerType() const

+ 0 - 1
lib/mapObjects/CGHeroInstance.h

@@ -57,7 +57,6 @@ public:
 	//          8 4
 	//          765
 	ui8 moveDir;
-	mutable ui8 isStanding;
 	mutable ui8 tacticFormationEnabled;
 
 	//////////////////////////////////////////////////////////////////////////

+ 10 - 1
lib/spells/AdventureSpellMechanics.cpp

@@ -200,7 +200,6 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		ChangeObjPos cop;
 		cop.objid = nearest->id;
 		cop.nPos = summonPos + int3(1,0,0);
-		cop.flags = 1;
 		env->apply(&cop);
 	}
 	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
@@ -586,6 +585,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env
 				pack.objectPositions.push_back(posInfo);
 		}
 	}
+	pack.showTerrain = showTerrain(spellLevel);
 
 	env->apply(&pack);
 
@@ -603,6 +603,11 @@ bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int32_t
 	return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
 }
 
+bool ViewAirMechanics::showTerrain(const int32_t spellLevel) const
+{
+	return false;
+}
+
 ///ViewEarthMechanics
 ViewEarthMechanics::ViewEarthMechanics(const CSpell * s):
 	ViewMechanics(s)
@@ -614,5 +619,9 @@ bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int32_
 	return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
 }
 
+bool ViewEarthMechanics::showTerrain(const int32_t spellLevel) const
+{
+	return spellLevel > 2;
+}
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 0
lib/spells/AdventureSpellMechanics.h

@@ -82,6 +82,7 @@ public:
 protected:
 	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 	virtual bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const = 0;
+	virtual bool showTerrain(const int32_t spellLevel) const = 0;
 };
 
 class DLL_LINKAGE ViewAirMechanics : public ViewMechanics
@@ -90,6 +91,7 @@ public:
 	ViewAirMechanics(const CSpell * s);
 protected:
 	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
+	bool showTerrain(const int32_t spellLevel) const override;
 };
 
 class DLL_LINKAGE ViewEarthMechanics : public ViewMechanics
@@ -98,6 +100,7 @@ public:
 	ViewEarthMechanics(const CSpell * s);
 protected:
 	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
+	bool showTerrain(const int32_t spellLevel) const override;
 };
 
 

+ 1 - 2
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"
 
@@ -282,7 +281,7 @@ std::shared_ptr<QImage> MapHandler::findFlagBitmap(const CGHeroInstance * hero,
 	if(!hero || hero->boat)
 		return std::shared_ptr<QImage>();
 	
-	return findFlagBitmapInternal(graphics->heroFlagAnimations.at(color.getNum()), anim, group, hero->moveDir, !hero->isStanding);
+	return findFlagBitmapInternal(graphics->heroFlagAnimations.at(color.getNum()), anim, group, hero->moveDir, true);
 }
 
 std::shared_ptr<QImage> MapHandler::findFlagBitmapInternal(std::shared_ptr<Animation> animation, int anim, int group, ui8 dir, bool moving) const

+ 1 - 11
server/CGameHandler.cpp

@@ -2773,12 +2773,11 @@ void CGameHandler::giveHero(ObjectInstanceID id, PlayerColor player)
 	sendAndApply(&gh);
 }
 
-void CGameHandler::changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags)
+void CGameHandler::changeObjPos(ObjectInstanceID objid, int3 newPos)
 {
 	ChangeObjPos cop;
 	cop.objid = objid;
 	cop.nPos = newPos;
-	cop.flags = flags;
 	sendAndApply(&cop);
 }
 
@@ -6034,15 +6033,6 @@ void CGameHandler::getVictoryLossMessage(PlayerColor player, const EVictoryLossC
 
 bool CGameHandler::dig(const CGHeroInstance *h)
 {
-	for (auto i = gs->map->objects.cbegin(); i != gs->map->objects.cend(); i++) //unflag objs
-	{
-		if (*i && (*i)->ID == Obj::HOLE  &&  (*i)->pos == h->visitablePos())
-		{
-			complain("Cannot dig - there is already a hole under the hero!");
-			return false;
-		}
-	}
-
 	if (h->diggingStatus() != EDiggingStatus::CAN_DIG) //checks for terrain and movement
 		COMPLAIN_RETF("Hero cannot dig (error code %d)!", h->diggingStatus());
 

+ 1 - 1
server/CGameHandler.h

@@ -195,7 +195,7 @@ public:
 	void setMovePoints(SetMovePoints * smp) override;
 	void setManaPoints(ObjectInstanceID hid, int val) override;
 	void giveHero(ObjectInstanceID id, PlayerColor player) override;
-	void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags) override;
+	void changeObjPos(ObjectInstanceID objid, int3 newPos) override;
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override;
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override;