فهرست منبع

Merge pull request #2076 from IvanSavenko/configurable_adventure_map

Implement scalable adventure map window
Ivan Savenko 2 سال پیش
والد
کامیت
7927470d46
88فایلهای تغییر یافته به همراه4165 افزوده شده و 2859 حذف شده
  1. BIN
      Mods/vcmi/Data/settingsWindow/frameAudio.png
  2. BIN
      Mods/vcmi/Data/settingsWindow/frameMovement.png
  3. BIN
      Mods/vcmi/Data/settingsWindow/frameStackQueue.png
  4. BIN
      Mods/vcmi/Data/settingsWindow/gear.png
  5. 5 2
      Mods/vcmi/config/vcmi/english.json
  6. 27 499
      client/CMT.cpp
  7. 12 6
      client/CMakeLists.txt
  8. 2 3
      client/CPlayerInterface.cpp
  9. 1 1
      client/CPlayerInterface.h
  10. 2 1
      client/CServerHandler.cpp
  11. 1 1
      client/Client.cpp
  12. 0 9
      client/DPIaware.manifest
  13. 1 1
      client/PlayerLocalState.cpp
  14. 815 0
      client/adventureMap/AdventureMapInterface.cpp
  15. 37 84
      client/adventureMap/AdventureMapInterface.h
  16. 446 0
      client/adventureMap/AdventureMapShortcuts.cpp
  17. 87 0
      client/adventureMap/AdventureMapShortcuts.h
  18. 455 0
      client/adventureMap/AdventureMapWidget.cpp
  19. 109 0
      client/adventureMap/AdventureMapWidget.h
  20. 5 5
      client/adventureMap/AdventureOptions.cpp
  21. 2 2
      client/adventureMap/AdventureOptions.h
  22. 20 0
      client/adventureMap/AdventureState.h
  23. 0 94
      client/adventureMap/CAdvMapPanel.cpp
  24. 0 60
      client/adventureMap/CAdvMapPanel.h
  25. 0 1346
      client/adventureMap/CAdventureMapInterface.cpp
  26. 11 16
      client/adventureMap/CInGameConsole.cpp
  27. 1 1
      client/adventureMap/CInfoBar.cpp
  28. 45 18
      client/adventureMap/CList.cpp
  29. 23 31
      client/adventureMap/CList.h
  30. 1 1
      client/adventureMap/CMinimap.cpp
  31. 25 33
      client/adventureMap/CResDataBar.cpp
  32. 15 3
      client/adventureMap/CResDataBar.h
  33. 1 1
      client/battle/BattleInterface.cpp
  34. 6 2
      client/battle/BattleWindow.cpp
  35. 1 0
      client/battle/BattleWindow.h
  36. 31 7
      client/gui/CGuiHandler.cpp
  37. 8 1
      client/gui/CGuiHandler.h
  38. 17 11
      client/gui/CIntObject.cpp
  39. 12 7
      client/gui/CIntObject.h
  40. 69 39
      client/gui/InterfaceObjectConfigurable.cpp
  41. 6 0
      client/gui/InterfaceObjectConfigurable.h
  42. 7 3
      client/gui/Shortcut.h
  43. 13 86
      client/gui/ShortcutHandler.cpp
  44. 1 1
      client/lobby/CLobbyScreen.cpp
  45. 0 1
      client/lobby/CSelectionBase.cpp
  46. 1 0
      client/lobby/SelectionTab.cpp
  47. 20 0
      client/mainmenu/CMainMenu.cpp
  48. 2 0
      client/mainmenu/CMainMenu.h
  49. 1 1
      client/mapView/MapRendererContextState.cpp
  50. 1 1
      client/mapView/MapView.cpp
  51. 1 1
      client/mapView/MapViewActions.cpp
  52. 1 1
      client/mapView/MapViewController.cpp
  53. 36 0
      client/render/IScreenHandler.h
  54. 0 37
      client/renderSDL/SDL_Extensions.cpp
  55. 0 5
      client/renderSDL/SDL_Extensions.h
  56. 510 0
      client/renderSDL/ScreenHandler.cpp
  57. 89 0
      client/renderSDL/ScreenHandler.h
  58. 7 7
      client/widgets/Buttons.cpp
  59. 2 8
      client/widgets/Buttons.h
  60. 52 11
      client/widgets/Images.cpp
  61. 20 5
      client/widgets/Images.h
  62. 18 7
      client/widgets/TextControls.cpp
  63. 5 5
      client/widgets/TextControls.h
  64. 6 3
      client/windows/CCastleInterface.cpp
  65. 14 11
      client/windows/CKingdomInterface.cpp
  66. 1 1
      client/windows/CQuestLog.cpp
  67. 1 1
      client/windows/CSpellWindow.cpp
  68. 0 1
      client/windows/CTradeWindow.cpp
  69. 0 6
      client/windows/CWindowObject.cpp
  70. 0 1
      client/windows/CWindowObject.h
  71. 0 1
      client/windows/GUIClasses.cpp
  72. 0 1
      client/windows/GUIClasses.h
  73. 1 2
      client/windows/InfoWindows.cpp
  74. 77 54
      client/windows/settings/GeneralOptionsTab.cpp
  75. 5 5
      client/windows/settings/GeneralOptionsTab.h
  76. 0 33
      config/resolutions.json
  77. 6 5
      config/schemas/settings.json
  78. 869 0
      config/widgets/adventureMap.json
  79. 1 11
      config/widgets/settings/battleOptionsTab.json
  80. 33 6
      config/widgets/settings/generalOptionsTab.json
  81. 4 0
      launcher/CMakeLists.txt
  82. 0 1
      launcher/mainwindow_moc.cpp
  83. 55 43
      launcher/settingsView/csettingsview_moc.cpp
  84. 1 3
      launcher/settingsView/csettingsview_moc.h
  85. 5 5
      launcher/settingsView/csettingsview_moc.ui
  86. 0 120
      lib/CConfigHandler.cpp
  87. 0 77
      lib/CConfigHandler.h
  88. 0 3
      mapeditor/mainwindow.cpp

BIN
Mods/vcmi/Data/settingsWindow/frameAudio.png


BIN
Mods/vcmi/Data/settingsWindow/frameMovement.png


BIN
Mods/vcmi/Data/settingsWindow/frameStackQueue.png


BIN
Mods/vcmi/Data/settingsWindow/gear.png


+ 5 - 2
Mods/vcmi/config/vcmi/english.json

@@ -49,10 +49,13 @@
 	"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
 	"vcmi.systemOptions.fullscreenButton.help"  : "{Fullscreen}\n\nIf selected, VCMI will run in fullscreen mode, otherwise it will run in windowed mode",
 	"vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h",
-	"vcmi.systemOptions.resolutionButton.help"  : "{Select Resolution}\n\nChange in-game screen resolution. A game restart is required to apply the new resolution.",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Select Resolution}\n\nChange in-game screen resolution.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Select Resolution",
 	"vcmi.systemOptions.resolutionMenu.help"    : "Change in-game screen resolution.",
-	"vcmi.systemOptions.fullscreenFailed"       : "{Fullscreen}\n\nFailed to switch to fullscreen mode! The current resolution is not supported by the display!",
+	"vcmi.systemOptions.scalingButton.hover"   : "Interface Scaling: %p%",
+	"vcmi.systemOptions.scalingButton.help"    : "{Interface Scaling}\n\nChanges scaling of in-game interface",
+	"vcmi.systemOptions.scalingMenu.hover"     : "Select Interface Scaling",
+	"vcmi.systemOptions.scalingMenu.help"      : "Change in-game interface scaling.",
 	"vcmi.systemOptions.framerateButton.hover"  : "Show FPS",
 	"vcmi.systemOptions.framerateButton.help"   : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window",
 

+ 27 - 499
client/CMT.cpp

@@ -7,61 +7,48 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
+
 // CMT.cpp : Defines the entry point for the console application.
-//
 #include "StdInc.h"
+#include "CMT.h"
 
 #include "CGameInfo.h"
 #include "mainmenu/CMainMenu.h"
-#include "lobby/CSelectionBase.h"
-#include "windows/CCastleInterface.h"
+#include "mainmenu/CPrologEpilogVideo.h"
 #include "gui/CursorHandler.h"
 #include "CPlayerInterface.h"
 #include "CVideoHandler.h"
 #include "CMusicHandler.h"
-#include "Client.h"
 #include "gui/CGuiHandler.h"
 #include "CServerHandler.h"
 #include "gui/NotificationHandler.h"
 #include "ClientCommandManager.h"
 #include "windows/CMessage.h"
-#include "renderSDL/SDL_Extensions.h"
+#include "render/IScreenHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/FileStream.h"
 #include "../lib/CConsoleHandler.h"
-#include "../lib/CGameState.h"
-#include "../lib/CBuildingHandler.h"
-#include "../CCallback.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/serializer/BinaryDeserializer.h"
-#include "../lib/serializer/BinarySerializer.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/NetPacks.h"
-#include "../lib/CModHandler.h"
-#include "../lib/CTownHandler.h"
+#include "../lib/mapping/CCampaignHandler.h"
+
 #include "../lib/logging/CBasicLogConfigurator.h"
-#include "../lib/CPlayerState.h"
-#include "../lib/serializer/Connection.h"
 
-#include <boost/asio.hpp>
 #include <boost/program_options.hpp>
-
-#include "mainmenu/CPrologEpilogVideo.h"
 #include <vstd/StringUtils.h>
-#include <SDL.h>
+#include <SDL_events.h>
+#include <SDL_hints.h>
+#include <SDL_main.h>
 
 #ifdef VCMI_WINDOWS
 #include <SDL_syswm.h>
 #endif
+
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
+#include <SDL_system.h>
 #endif
 
-#include "CMT.h"
-
 #if __MINGW32__
 #undef main
 #endif
@@ -70,44 +57,21 @@ namespace po = boost::program_options;
 namespace po_style = boost::program_options::command_line_style;
 namespace bfs = boost::filesystem;
 
-std::string NAME_AFFIX = "client";
-std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
-CGuiHandler GH;
-
-int preferredDriverIndex = -1;
-SDL_Window * mainWindow = nullptr;
-SDL_Renderer * mainRenderer = nullptr;
-SDL_Texture * screenTexture = nullptr;
-
 extern boost::thread_specific_ptr<bool> inGuiThread;
 
-SDL_Surface *screen = nullptr, //main screen surface
-	*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
-	*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
-
 std::queue<SDL_Event> SDLEventsQueue;
 boost::mutex eventsM;
 
 static po::variables_map vm;
 
-//static bool setResolution = false; //set by event handling thread after resolution is adjusted
-
 #ifndef VCMI_IOS
 void processCommand(const std::string &message);
 #endif
-static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
 void playIntro();
 static void mainLoop();
 
 static CBasicLogConfigurator *logConfig;
 
-#ifndef VCMI_WINDOWS
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#include <getopt.h>
-#endif
-
 void init()
 {
 	CStopWatch tmh;
@@ -139,17 +103,6 @@ static void prog_help(const po::options_description &opts)
 	std::cout << opts;
 }
 
-static void SDLLogCallback(void*           userdata,
-						   int             category,
-						   SDL_LogPriority priority,
-						   const char*     message)
-{
-	//todo: convert SDL log priority to vcmi log priority
-	//todo: make separate log domain for SDL
-
-	logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
-}
-
 #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
 int wmain(int argc, wchar_t* argv[])
 #elif defined(VCMI_MOBILE)
@@ -255,7 +208,7 @@ int main(int argc, char * argv[])
 	const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
 	logConfig = new CBasicLogConfigurator(logPath, console);
 	logConfig->configureDefault();
-	logGlobal->info(NAME);
+	logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION);
 	logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
 	logGlobal->info("The log file will be saved to %s", logPath);
 
@@ -338,73 +291,11 @@ int main(int argc, char * argv[])
 	testFile("VIDEO/GOOD1A.SMK", "campaign movies");
 	testFile("SOUNDS/G1A.WAV", "campaign music"); //technically not a music but voiced intro sounds
 
-	conf.init();
-	logGlobal->info("Loading settings: %d ms", pomtime.getDiff());
-
 	srand ( (unsigned int)time(nullptr) );
 
-
-	const JsonNode& video = settings["video"];
-	const JsonNode& res = video["screenRes"];
-
-	//something is really wrong...
-	if (res["width"].Float() < 100 || res["height"].Float() < 100)
-	{
-		logGlobal->error("Fatal error: failed to load settings!");
-		logGlobal->error("Possible reasons:");
-		logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath());
-		logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath());
-		logGlobal->error("VCMI will now exit...");
-		exit(EXIT_FAILURE);
-	}
-
 	if(!settings["session"]["headless"].Bool())
-	{
-		if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
-		{
-			logGlobal->error("Something was wrong: %s", SDL_GetError());
-			exit(-1);
-		}
-
-		#ifdef VCMI_ANDROID
-		// manually setting egl pixel format, as a possible solution for sdl2<->android problem
-		// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
-		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
-		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
-		#endif // VCMI_ANDROID
-
-		//(!)init here AFTER SDL_Init() while using SDL for FPS management
 		GH.init();
 
-		SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
-
-		int driversCount = SDL_GetNumRenderDrivers();
-		std::string preferredDriverName = video["driver"].String();
-
-		logGlobal->info("Found %d render drivers", driversCount);
-
-		for(int it = 0; it < driversCount; it++)
-		{
-			SDL_RendererInfo info;
-			SDL_GetRenderDriverInfo(it,&info);
-
-			std::string driverName(info.name);
-
-			if(!preferredDriverName.empty() && driverName == preferredDriverName)
-			{
-				preferredDriverIndex = it;
-				logGlobal->info("\t%s (active)", driverName);
-			}
-			else
-				logGlobal->info("\t%s", driverName);
-		}
-
-		setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float());
-		logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff());
-	}
-
 	CCS = new CClientState();
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	CSH = new CServerHandler();
@@ -457,9 +348,7 @@ int main(int argc, char * argv[])
 	{
 		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
 			playIntro();
-		SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
-		SDL_RenderClear(mainRenderer);
-		SDL_RenderPresent(mainRenderer);
+		GH.screenHandler().clearScreen();
 	}
 
 
@@ -490,7 +379,6 @@ int main(int argc, char * argv[])
 		CCS->curh->show();
 	}
 
-
 	logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
 
 	session["autoSkip"].Bool()  = vm.count("autoSkip");
@@ -580,350 +468,6 @@ void playIntro()
 	}
 }
 
-#if !defined(VCMI_MOBILE)
-static bool checkVideoMode(int monitorIndex, int w, int h)
-{
-	//we only check that our desired window size fits on screen
-	SDL_DisplayMode mode;
-
-	if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode))
-	{
-		logGlobal->error("SDL_GetDesktopDisplayMode failed");
-		logGlobal->error(SDL_GetError());
-		return false;
-	}
-
-	logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h);
-
-	if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h))
-	{
-		return true;
-	}
-
-	return false;
-}
-#endif
-
-static void cleanupRenderer()
-{
-	screenBuf = nullptr; //it`s a link - just nullify
-
-	if(nullptr != screen2)
-	{
-		SDL_FreeSurface(screen2);
-		screen2 = nullptr;
-	}
-
-	if(nullptr != screen)
-	{
-		SDL_FreeSurface(screen);
-		screen = nullptr;
-	}
-
-	if(nullptr != screenTexture)
-	{
-		SDL_DestroyTexture(screenTexture);
-		screenTexture = nullptr;
-	}
-}
-
-static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex)
-{
-	// VCMI will only work with 2 or 4 bytes per pixel
-	vstd::amax(bpp, 16);
-	vstd::amin(bpp, 32);
-	if(bpp>16)
-		bpp = 32;
-
-	if(displayIndex < 0)
-	{
-		if (mainWindow != nullptr)
-			displayIndex = SDL_GetWindowDisplayIndex(mainWindow);
-		if (displayIndex < 0)
-			displayIndex = 0;
-	}
-
-#if defined(VCMI_MOBILE)
-	SDL_GetWindowSize(mainWindow, &w, &h);
-#else
-	if(!checkVideoMode(displayIndex, w, h))
-	{
-		logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
-	}
-#endif
-
-	bool bufOnScreen = (screenBuf == screen);
-	bool realFullscreen = settings["video"]["realFullscreen"].Bool();
-
-	/* match best rendering resolution */
-	int renderWidth = 0, renderHeight = 0;
-	auto aspectRatio = (float)w / (float)h;
-	auto minDiff = 10.f;
-	for (const auto& pair : conf.guiOptions)
-	{
-		int pWidth, pHeight;
-		std::tie(pWidth, pHeight) = pair.first;
-		/* filter out resolution which is larger than window */
-		if (pWidth > w || pHeight > h)
-		{
-			continue;
-		}
-		auto ratio = (float)pWidth / (float)pHeight;
-		auto diff = fabs(aspectRatio - ratio);
-		/* select closest aspect ratio */
-		if (diff < minDiff)
-		{
-			renderWidth = pWidth;
-			renderHeight = pHeight;
-			minDiff = diff;
-		}
-		/* select largest resolution meets prior conditions.
-		 * since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */
-		else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth)
-		{
-			renderWidth = pWidth;
-			renderHeight = pHeight;
-		}
-	}
-	if (renderWidth == 0)
-	{
-		// no matching resolution for upscaling - complain & fallback to default resolution.
-		logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h);
-		Settings newRes = settings.write["video"]["screenRes"];
-		std::tie(w, h) = conf.guiOptions.begin()->first;
-		newRes["width"].Float() = w;
-		newRes["height"].Float() = h;
-		conf.SetResolution(w, h);
-		logGlobal->error("Falling back to %dx%d", w, h);
-		renderWidth = w;
-		renderHeight = h;
-	}
-	else
-	{
-		logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight);
-	}
-
-	cleanupRenderer();
-
-	if(nullptr == mainWindow)
-	{
-#if defined(VCMI_MOBILE)
-		auto createWindow = [displayIndex](uint32_t extraFlags) -> bool {
-			mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags);
-			return mainWindow != nullptr;
-		};
-
-# ifdef VCMI_IOS
-		SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
-		SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
-		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-
-		uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
-		if(!createWindow(windowFlags | SDL_WINDOW_METAL))
-		{
-			logGlobal->warn("Metal unavailable, using OpenGLES");
-			createWindow(windowFlags);
-		}
-# else
-		createWindow(0);
-# endif // VCMI_IOS
-
-		// SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate
-		// That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate
-
-		SDL_Rect screenRect;
-
-		if(SDL_GetDisplayBounds(0, &screenRect) == 0)
-		{
-			const auto screenWidth = screenRect.w;
-			const auto screenHeight = screenRect.h;
-
-			const auto aspect = static_cast<double>(screenWidth) / screenHeight;
-
-			logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect);
-
-			if((double)w / aspect > (double)h)
-			{
-				h = (int)round((double)w / aspect);
-			}
-			else
-			{
-				w = (int)round((double)h * aspect);
-			}
-
-			logGlobal->info("Changing logical screen size to %dx%d", w, h);
-		}
-		else
-		{
-			logGlobal->error("Can't fix aspect ratio for screen");
-		}
-#else
-		if(fullscreen)
-		{
-			if(realFullscreen)
-				mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN);
-			else //in windowed full-screen mode use desktop resolution
-				mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
-			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-		}
-		else
-		{
-			mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
-		}
-#endif // defined(VCMI_MOBILE)
-
-		if(nullptr == mainWindow)
-		{
-			throw std::runtime_error("Unable to create window\n");
-		}
-
-		//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
-		mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
-
-		if(nullptr == mainRenderer)
-		{
-			throw std::runtime_error("Unable to create renderer\n");
-		}
-
-
-		SDL_RendererInfo info;
-		SDL_GetRendererInfo(mainRenderer, &info);
-		logGlobal->info("Created renderer %s", info.name);
-
-	}
-	else
-	{
-#if !defined(VCMI_MOBILE)
-
-		if(fullscreen)
-		{
-			if(realFullscreen)
-			{
-				SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
-
-				SDL_DisplayMode mode;
-				SDL_GetDesktopDisplayMode(displayIndex, &mode);
-				mode.w = renderWidth;
-				mode.h = renderHeight;
-
-				SDL_SetWindowDisplayMode(mainWindow, &mode);
-			}
-			else
-			{
-				SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
-			}
-
-			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
-
-			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-		}
-		else
-		{
-			SDL_SetWindowFullscreen(mainWindow, 0);
-			SDL_SetWindowSize(mainWindow, w, h);
-			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
-		}
-#endif
-	}
-
-	if(!(fullscreen && realFullscreen))
-	{
-		SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight);
-
-//following line is bugged not only on android, do not re-enable without checking
-//#ifndef VCMI_ANDROID
-//		// on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
-//		SDL_RenderSetViewport(mainRenderer, nullptr);
-//#endif
-
-	}
-
-
-	#ifdef VCMI_ENDIAN_BIG
-		int bmask = 0xff000000;
-		int gmask = 0x00ff0000;
-		int rmask = 0x0000ff00;
-		int amask = 0x000000ff;
-	#else
-		int bmask = 0x000000ff;
-		int gmask = 0x0000ff00;
-		int rmask = 0x00ff0000;
-		int amask = 0xFF000000;
-	#endif
-
-	screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask);
-	if(nullptr == screen)
-	{
-		logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError());
-		throw std::runtime_error("Unable to create surface");
-	}
-	//No blending for screen itself. Required for proper cursor rendering.
-	SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
-
-	screenTexture = SDL_CreateTexture(mainRenderer,
-											SDL_PIXELFORMAT_ARGB8888,
-											SDL_TEXTUREACCESS_STREAMING,
-											renderWidth, renderHeight);
-
-	if(nullptr == screenTexture)
-	{
-		logGlobal->error("Unable to create screen texture");
-		logGlobal->error(SDL_GetError());
-		throw std::runtime_error("Unable to create screen texture");
-	}
-
-	screen2 = CSDL_Ext::copySurface(screen);
-
-
-	if(nullptr == screen2)
-	{
-		throw std::runtime_error("Unable to copy surface\n");
-	}
-
-	screenBuf = bufOnScreen ? screen : screen2;
-
-	SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
-	SDL_RenderClear(mainRenderer);
-	SDL_RenderPresent(mainRenderer);
-
-	if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
-	{
-		NotificationHandler::init(mainWindow);
-	}
-
-	return true;
-}
-
-//used only once during initialization
-static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo)
-{
-	if(!recreateWindow(w, h, bpp, fullscreen, displayIndex))
-	{
-		throw std::runtime_error("Requested screen resolution is not available\n");
-	}
-}
-
-static void fullScreenChanged()
-{
-	boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-
-	Settings full = settings.write["video"]["fullscreen"];
-	const bool toFullscreen = full->Bool();
-
-	auto bitsPerPixel = screen->format->BitsPerPixel;
-
-	auto w = screen->w;
-	auto h = screen->h;
-
-	if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1))
-	{
-		//will return false and report error if video mode is not supported
-		return;
-	}
-
-	GH.totalRedraw();
-}
-
 static void handleEvent(SDL_Event & ev)
 {
 	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
@@ -1001,8 +545,11 @@ static void handleEvent(SDL_Event & ev)
 			CMM->menu->switchToTab("load");
 			break;
 		case EUserEvent::FULLSCREEN_TOGGLED:
-			fullScreenChanged();
-			break;
+			{
+				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+				GH.screenHandler().onScreenResize();
+				break;
+			}
 		default:
 			logGlobal->error("Unknown user event. Code %d", ev.user.code);
 			break;
@@ -1015,7 +562,10 @@ static void handleEvent(SDL_Event & ev)
 		switch (ev.window.event) {
 		case SDL_WINDOWEVENT_RESTORED:
 #ifndef VCMI_IOS
-			fullScreenChanged();
+			{
+				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+				GH.screenHandler().onScreenResize();
+			}
 #endif
 			break;
 		}
@@ -1039,14 +589,14 @@ static void handleEvent(SDL_Event & ev)
 		boost::unique_lock<boost::mutex> lock(eventsM);
 		SDLEventsQueue.push(ev);
 	}
-
 }
 
-
 static void mainLoop()
 {
-	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
+	SettingsListener resChanged = settings.listen["video"]["resolution"];
+	SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
 	resChanged([](const JsonNode &newState){  CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
+	fsChanged([](const JsonNode &newState){  CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
 
 	inGuiThread.reset(new bool(true));
 	assert(GH.mainFPSmng);
@@ -1063,7 +613,6 @@ static void mainLoop()
 
 		CSH->applyPacksOnLobbyScreen();
 		GH.renderFrame();
-
 	}
 }
 
@@ -1100,29 +649,9 @@ static void quitApplication()
 	vstd::clear_pointer(console);// should be removed after everything else since used by logging
 
 	boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
-	if(!settings["session"]["headless"].Bool())
-	{
-		if(settings["general"]["notifications"].Bool())
-		{
-			NotificationHandler::destroy();
-		}
-
-		cleanupRenderer();
-
-		if(nullptr != mainRenderer)
-		{
-			SDL_DestroyRenderer(mainRenderer);
-			mainRenderer = nullptr;
-		}
 
-		if(nullptr != mainWindow)
-		{
-			SDL_DestroyWindow(mainWindow);
-			mainWindow = nullptr;
-		}
-
-		SDL_Quit();
-	}
+	if(!settings["session"]["headless"].Bool())
+		GH.screenHandler().close();
 
 	if(logConfig != nullptr)
 	{
@@ -1137,7 +666,6 @@ static void quitApplication()
 
 void handleQuit(bool ask)
 {
-
 	if(CSH->client && LOCPLINT && ask)
 	{
 		CCS->curh->set(Cursor::Map::POINTER);

+ 12 - 6
client/CMakeLists.txt

@@ -2,9 +2,10 @@ set(client_SRCS
 	StdInc.cpp
 	../CCallback.cpp
 
-	adventureMap/CAdvMapPanel.cpp
-	adventureMap/CAdventureMapInterface.cpp
-	adventureMap/CAdventureOptions.cpp
+	adventureMap/AdventureMapInterface.cpp
+	adventureMap/AdventureMapShortcuts.cpp
+	adventureMap/AdventureMapWidget.cpp
+	adventureMap/AdventureOptions.cpp
 	adventureMap/CInGameConsole.cpp
 	adventureMap/CInfoBar.cpp
 	adventureMap/CList.cpp
@@ -75,6 +76,7 @@ set(client_SRCS
 	renderSDL/SDLImage.cpp
 	renderSDL/SDLImageLoader.cpp
 	renderSDL/SDLRWwrapper.cpp
+	renderSDL/ScreenHandler.cpp
 	renderSDL/SDL_Extensions.cpp
 
 	widgets/Buttons.cpp
@@ -129,9 +131,11 @@ set(client_SRCS
 set(client_HEADERS
 	StdInc.h
 
-	adventureMap/CAdvMapPanel.h
-	adventureMap/CAdventureMapInterface.h
-	adventureMap/CAdventureOptions.h
+	adventureMap/AdventureMapInterface.h
+	adventureMap/AdventureMapShortcuts.h
+	adventureMap/AdventureMapWidget.h
+	adventureMap/AdventureState.h
+	adventureMap/AdventureOptions.h
 	adventureMap/CInGameConsole.h
 	adventureMap/CInfoBar.h
 	adventureMap/CList.h
@@ -202,6 +206,7 @@ set(client_HEADERS
 	render/IFont.h
 	render/IImage.h
 	render/IImageLoader.h
+	render/IScreenHandler.h
 
 	renderSDL/CBitmapFont.h
 	renderSDL/CBitmapHanFont.h
@@ -211,6 +216,7 @@ set(client_HEADERS
 	renderSDL/SDLImage.h
 	renderSDL/SDLImageLoader.h
 	renderSDL/SDLRWwrapper.h
+	renderSDL/ScreenHandler.h
 	renderSDL/SDL_Extensions.h
 	renderSDL/SDL_PixelAccess.h
 

+ 2 - 3
client/CPlayerInterface.cpp

@@ -12,10 +12,9 @@
 
 #include <vcmi/Artifact.h>
 
-#include "adventureMap/CAdventureMapInterface.h"
+#include "adventureMap/AdventureMapInterface.h"
 #include "mapView/mapHandler.h"
 #include "adventureMap/CList.h"
-#include "adventureMap/CInfoBar.h"
 #include "battle/BattleInterface.h"
 #include "battle/BattleEffectsController.h"
 #include "battle/BattleFieldController.h"
@@ -163,7 +162,7 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
 	initializeHeroTownList();
 
 	// always recreate advmap interface to avoid possible memory-corruption bugs
-	adventureInt.reset(new CAdventureMapInterface());
+	adventureInt.reset(new AdventureMapInterface());
 }
 
 void CPlayerInterface::playerStartsTurn(PlayerColor player)

+ 1 - 1
client/CPlayerInterface.h

@@ -31,7 +31,7 @@ struct CPathsInfo;
 VCMI_LIB_NAMESPACE_END
 
 class CButton;
-class CAdventureMapInterface;
+class AdventureMapInterface;
 class CCastleInterface;
 class BattleInterface;
 class CComponent;

+ 2 - 1
client/CServerHandler.cpp

@@ -123,7 +123,8 @@ public:
 	}
 };
 
-extern std::string NAME;
+static const std::string NAME_AFFIX = "client";
+static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 
 CServerHandler::CServerHandler()
 	: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)

+ 1 - 1
client/Client.cpp

@@ -15,7 +15,7 @@
 #include "CPlayerInterface.h"
 #include "CServerHandler.h"
 #include "ClientNetPackVisitors.h"
-#include "adventureMap/CAdventureMapInterface.h"
+#include "adventureMap/AdventureMapInterface.h"
 #include "battle/BattleInterface.h"
 #include "gui/CGuiHandler.h"
 #include "mapView/mapHandler.h"

+ 0 - 9
client/DPIaware.manifest

@@ -1,9 +0,0 @@
-<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
-  <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
-    <asmv3:windowsSettings
-         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
-      <dpiAware>true</dpiAware>
-    </asmv3:windowsSettings>
-  </asmv3:application>
-</assembly> 

+ 1 - 1
client/PlayerLocalState.cpp

@@ -15,7 +15,7 @@
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/mapObjects/CGTownInstance.h"
 #include "CPlayerInterface.h"
-#include "adventureMap/CAdventureMapInterface.h"
+#include "adventureMap/AdventureMapInterface.h"
 
 PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
 	: owner(owner)

+ 815 - 0
client/adventureMap/AdventureMapInterface.cpp

@@ -0,0 +1,815 @@
+/*
+ * AdventureMapInterface.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 "AdventureMapInterface.h"
+
+#include "AdventureOptions.h"
+#include "AdventureState.h"
+#include "CInGameConsole.h"
+#include "CMinimap.h"
+#include "CList.h"
+#include "CInfoBar.h"
+#include "MapAudioPlayer.h"
+#include "AdventureMapWidget.h"
+#include "AdventureMapShortcuts.h"
+
+#include "../mapView/mapHandler.h"
+#include "../mapView/MapView.h"
+#include "../windows/InfoWindows.h"
+#include "../CGameInfo.h"
+#include "../gui/CursorHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+#include "../CMT.h"
+#include "../PlayerLocalState.h"
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/CPathfinder.h"
+#include "../../lib/mapping/CMap.h"
+
+std::shared_ptr<AdventureMapInterface> adventureInt;
+
+AdventureMapInterface::AdventureMapInterface():
+	mapAudio(new MapAudioPlayer()),
+	spellBeingCasted(nullptr),
+	scrollingWasActive(false),
+	scrollingWasBlocked(false)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos.x = pos.y = 0;
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
+	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
+
+	shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
+
+	widget = std::make_shared<AdventureMapWidget>(shortcuts);
+	shortcuts->setState(EAdventureState::MAKING_TURN);
+	widget->getMapView()->onViewMapActivated();
+
+	addUsedEvents(KEYBOARD);
+}
+
+void AdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
+{
+	shortcuts->onMapViewMoved(visibleArea, mapLevel);
+	widget->getMinimap()->onMapViewMoved(visibleArea, mapLevel);
+	widget->onMapViewMoved(visibleArea, mapLevel);
+}
+
+void AdventureMapInterface::onAudioResumed()
+{
+	mapAudio->onAudioResumed();
+}
+
+void AdventureMapInterface::onAudioPaused()
+{
+	mapAudio->onAudioPaused();
+}
+
+void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
+{
+	widget->getInfoBar()->popAll();
+	widget->getInfoBar()->showSelection();
+}
+
+void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
+{
+	widget->getHeroList()->update(h);
+
+	if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
+		widget->getInfoBar()->showSelection();
+
+	widget->updateActiveState();
+}
+
+void AdventureMapInterface::onTownChanged(const CGTownInstance * town)
+{
+	widget->getTownList()->update(town);
+
+	if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents())
+		widget->getInfoBar()->showSelection();
+}
+
+void AdventureMapInterface::showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer)
+{
+	widget->getInfoBar()->pushComponents(components, message, timer);
+}
+
+void AdventureMapInterface::activate()
+{
+	CIntObject::activate();
+
+	adjustActiveness();
+
+	screenBuf = screen;
+	
+	if(LOCPLINT)
+	{
+		LOCPLINT->cingconsole->activate();
+		LOCPLINT->cingconsole->pos = this->pos;
+	}
+
+	GH.fakeMouseMove(); //to restore the cursor
+}
+
+void AdventureMapInterface::deactivate()
+{
+	CIntObject::deactivate();
+	CCS->curh->set(Cursor::Map::POINTER);
+}
+
+void AdventureMapInterface::showAll(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+	LOCPLINT->cingconsole->show(to);
+}
+
+void AdventureMapInterface::show(SDL_Surface * to)
+{
+	handleMapScrollingUpdate();
+
+	CIntObject::show(to);
+	LOCPLINT->cingconsole->show(to);
+}
+
+void AdventureMapInterface::handleMapScrollingUpdate()
+{
+	/// Width of window border, in pixels, that triggers map scrolling
+	static constexpr uint32_t borderScrollWidth = 15;
+
+	uint32_t timePassed = GH.mainFPSmng->getElapsedMilliseconds();
+	uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
+	uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000;
+
+	Point cursorPosition = GH.getCursorPosition();
+	Point scrollDirection;
+
+	if (cursorPosition.x < borderScrollWidth)
+		scrollDirection.x = -1;
+
+	if (cursorPosition.x > GH.screenDimensions().x - borderScrollWidth)
+		scrollDirection.x = +1;
+
+	if (cursorPosition.y < borderScrollWidth)
+		scrollDirection.y = -1;
+
+	if (cursorPosition.y > GH.screenDimensions().y - borderScrollWidth)
+		scrollDirection.y = +1;
+
+	Point scrollDelta = scrollDirection * scrollDistance;
+
+	bool cursorInScrollArea = scrollDelta != Point(0,0);
+	bool scrollingActive = cursorInScrollArea && active && shortcuts->optionSidePanelActive() && !scrollingWasBlocked;
+	bool scrollingBlocked = GH.isKeyboardCtrlDown();
+
+	if (!scrollingWasActive && scrollingBlocked)
+	{
+		scrollingWasBlocked = true;
+		return;
+	}
+
+	if (!cursorInScrollArea && scrollingWasBlocked)
+	{
+		scrollingWasBlocked = false;
+		return;
+	}
+
+	if (scrollingActive)
+		widget->getMapView()->onMapScrolled(scrollDelta);
+
+	if (!scrollingActive && !scrollingWasActive)
+		return;
+
+	if(scrollDelta.x > 0)
+	{
+		if(scrollDelta.y < 0)
+			CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
+		if(scrollDelta.y > 0)
+			CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
+		if(scrollDelta.y == 0)
+			CCS->curh->set(Cursor::Map::SCROLL_EAST);
+	}
+	if(scrollDelta.x < 0)
+	{
+		if(scrollDelta.y < 0)
+			CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
+		if(scrollDelta.y > 0)
+			CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
+		if(scrollDelta.y == 0)
+			CCS->curh->set(Cursor::Map::SCROLL_WEST);
+	}
+
+	if (scrollDelta.x == 0)
+	{
+		if(scrollDelta.y < 0)
+			CCS->curh->set(Cursor::Map::SCROLL_NORTH);
+		if(scrollDelta.y > 0)
+			CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
+		if(scrollDelta.y == 0)
+			CCS->curh->set(Cursor::Map::POINTER);
+	}
+
+	scrollingWasActive = scrollingActive;
+}
+
+void AdventureMapInterface::centerOnTile(int3 on)
+{
+	widget->getMapView()->onCenteredTile(on);
+}
+
+void AdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
+{
+	widget->getMapView()->onCenteredObject(obj);
+}
+
+void AdventureMapInterface::keyPressed(EShortcut key)
+{
+	if (key == EShortcut::GLOBAL_CANCEL && spellBeingCasted)
+		hotkeyAbortCastingMode();
+
+	//fake mouse use to trigger onTileHovered()
+	GH.fakeMouseMove();
+}
+
+void AdventureMapInterface::onSelectionChanged(const CArmedInstance *sel)
+{
+	assert(sel);
+
+	widget->getInfoBar()->popAll();
+	mapAudio->onSelectionChanged(sel);
+	bool centerView = !settings["session"]["autoSkip"].Bool();
+
+	if (centerView)
+		centerOnObject(sel);
+
+	if(sel->ID==Obj::TOWN)
+	{
+		auto town = dynamic_cast<const CGTownInstance*>(sel);
+
+		widget->getInfoBar()->showTownSelection(town);
+		widget->getTownList()->select(town);
+		widget->getHeroList()->select(nullptr);
+		onHeroChanged(nullptr);
+	}
+	else //hero selected
+	{
+		auto hero = dynamic_cast<const CGHeroInstance*>(sel);
+
+		widget->getInfoBar()->showHeroSelection(hero);
+		widget->getHeroList()->select(hero);
+		widget->getTownList()->select(nullptr);
+
+		LOCPLINT->localState->verifyPath(hero);
+		onHeroChanged(hero);
+	}
+
+	widget->updateActiveState();
+	widget->getHeroList()->redraw();
+	widget->getTownList()->redraw();
+}
+
+void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
+{
+	if (positions)
+		widget->getMinimap()->updateTiles(*positions);
+	else
+		widget->getMinimap()->update();
+}
+
+void AdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
+{
+	onCurrentPlayerChanged(playerID);
+	setState(EAdventureState::HOTSEAT_WAIT);
+}
+
+void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
+{
+	if(settings["session"]["spectate"].Bool())
+		return;
+
+	mapAudio->onEnemyTurnStarted();
+	widget->getMinimap()->setAIRadar(true);
+	widget->getInfoBar()->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
+	setState(EAdventureState::ENEMY_TURN);
+
+}
+
+void AdventureMapInterface::setState(EAdventureState state)
+{
+	shortcuts->setState(state);
+	adjustActiveness();
+	widget->updateActiveState();
+}
+
+void AdventureMapInterface::adjustActiveness()
+{
+	bool widgetMustBeActive = active && shortcuts->optionSidePanelActive();
+	bool mapViewMustBeActive = active && (shortcuts->optionMapViewActive());
+
+	if (widgetMustBeActive && !widget->active)
+		widget->activate();
+
+	if (!widgetMustBeActive && widget->active)
+		widget->deactivate();
+
+	if (mapViewMustBeActive && !widget->getMapView()->active)
+		widget->getMapView()->activate();
+
+	if (!mapViewMustBeActive && widget->getMapView()->active)
+		widget->getMapView()->deactivate();
+}
+
+void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
+{
+	LOCPLINT->localState->setSelection(nullptr);
+
+	if (playerID == currentPlayerID)
+		return;
+
+	currentPlayerID = playerID;
+	widget->setPlayer(playerID);
+}
+
+void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
+{
+	onCurrentPlayerChanged(playerID);
+
+	setState(EAdventureState::MAKING_TURN);
+	if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
+		|| settings["session"]["spectate"].Bool())
+	{
+		widget->getMinimap()->setAIRadar(false);
+		widget->getInfoBar()->showSelection();
+	}
+
+	widget->getHeroList()->update();
+	widget->getTownList()->update();
+
+	const CGHeroInstance * heroToSelect = nullptr;
+
+	// find first non-sleeping hero
+	for (auto hero : LOCPLINT->localState->getWanderingHeroes())
+	{
+		if (!LOCPLINT->localState->isHeroSleeping(hero))
+		{
+			heroToSelect = hero;
+			break;
+		}
+	}
+
+	//select first hero if available.
+	if (heroToSelect != nullptr)
+	{
+		LOCPLINT->localState->setSelection(heroToSelect);
+	}
+	else if (LOCPLINT->localState->getOwnedTowns().size())
+	{
+		LOCPLINT->localState->setSelection(LOCPLINT->localState->getOwnedTown(0));
+	}
+	else
+	{
+		LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
+	}
+
+	//show new day animation and sound on infobar
+	widget->getInfoBar()->showDate();
+
+	onHeroChanged(nullptr);
+	showAll(screen);
+	mapAudio->onPlayerTurnStarted();
+
+	if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
+	{
+		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
+			iw->close();
+
+		hotkeyEndingTurn();
+	}
+}
+
+void AdventureMapInterface::hotkeyEndingTurn()
+{
+	if(settings["session"]["spectate"].Bool())
+		return;
+
+	LOCPLINT->makingTurn = false;
+	LOCPLINT->cb->endTurn();
+	mapAudio->onPlayerTurnEnded();
+}
+
+const CGObjectInstance* AdventureMapInterface::getActiveObject(const int3 &mapPos)
+{
+	std::vector < const CGObjectInstance * > bobjs = LOCPLINT->cb->getBlockingObjs(mapPos);  //blocking objects at tile
+
+	if (bobjs.empty())
+		return nullptr;
+
+	return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
+}
+
+void AdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
+{
+	if(!shortcuts->optionMapViewActive())
+		return;
+
+	//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);
+
+	const CGObjectInstance *topBlocking = getActiveObject(mapPos);
+
+	int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
+	if(spellBeingCasted)
+	{
+		assert(shortcuts->optionSpellcasting());
+
+		if (!isInScreenRange(selPos, mapPos))
+			return;
+
+		const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
+
+		switch(spellBeingCasted->id)
+		{
+		case SpellID::SCUTTLE_BOAT: //Scuttle Boat
+			if(topBlocking && topBlocking->ID == Obj::BOAT)
+				performSpellcasting(mapPos);
+			break;
+		case SpellID::DIMENSION_DOOR:
+			if(!tile || tile->isClear(heroTile))
+				performSpellcasting(mapPos);
+			break;
+		}
+		return;
+	}
+	//check if we can select this object
+	bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
+	canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
+
+	bool isHero = false;
+	if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town)
+	{
+		if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked
+			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
+		else if(canSelect)
+			LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
+	}
+	else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected
+	{
+		isHero = true;
+
+		const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
+		if(currentHero == topBlocking) //clicked selected hero
+		{
+			LOCPLINT->openHeroWindow(currentHero);
+			return;
+		}
+		else if(canSelect && pn->turns == 255 ) //selectable object at inaccessible tile
+		{
+			LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
+			return;
+		}
+		else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
+		{
+			if(LOCPLINT->localState->hasPath(currentHero) &&
+			   LOCPLINT->localState->getPath(currentHero).endPos() == mapPos)//we'll be moving
+			{
+				if(!CGI->mh->hasOngoingAnimations())
+					LOCPLINT->moveHero(currentHero, LOCPLINT->localState->getPath(currentHero));
+				return;
+			}
+			else //remove old path and find a new one if we clicked on accessible tile
+			{
+				LOCPLINT->localState->setPath(currentHero, mapPos);
+				onHeroChanged(currentHero);
+			}
+		}
+	} //end of hero is selected "case"
+	else
+	{
+		throw std::runtime_error("Nothing is selected...");
+	}
+
+	const auto shipyard = ourInaccessibleShipyard(topBlocking);
+	if(isHero && shipyard != nullptr)
+	{
+		LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
+	}
+}
+
+void AdventureMapInterface::onTileHovered(const int3 &mapPos)
+{
+	if(!shortcuts->optionMapViewActive())
+		return;
+
+	//may occur just at the start of game (fake move before full intiialization)
+	if(!LOCPLINT->localState->getCurrentArmy())
+		return;
+
+	if(!LOCPLINT->cb->isVisible(mapPos))
+	{
+		CCS->curh->set(Cursor::Map::POINTER);
+		GH.statusbar->clear();
+		return;
+	}
+	auto objRelations = PlayerRelations::ALLIES;
+	const CGObjectInstance *objAtTile = getActiveObject(mapPos);
+	if(objAtTile)
+	{
+		objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
+		std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
+		boost::replace_all(text,"\n"," ");
+		GH.statusbar->write(text);
+	}
+	else
+	{
+		std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
+		GH.statusbar->write(hlp);
+	}
+
+	if(spellBeingCasted)
+	{
+		switch(spellBeingCasted->id)
+		{
+		case SpellID::SCUTTLE_BOAT:
+			{
+			int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
+
+			if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos))
+				CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
+			else
+				CCS->curh->set(Cursor::Map::POINTER);
+			return;
+			}
+		case SpellID::DIMENSION_DOOR:
+			{
+				const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
+				int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
+				if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
+					CCS->curh->set(Cursor::Map::TELEPORT);
+				else
+					CCS->curh->set(Cursor::Map::POINTER);
+				return;
+			}
+		}
+	}
+
+	if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN)
+	{
+		if(objAtTile)
+		{
+			if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
+				CCS->curh->set(Cursor::Map::TOWN);
+			else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
+				CCS->curh->set(Cursor::Map::HERO);
+			else
+				CCS->curh->set(Cursor::Map::POINTER);
+		}
+		else
+			CCS->curh->set(Cursor::Map::POINTER);
+	}
+	else if(const CGHeroInstance * hero = LOCPLINT->localState->getCurrentHero())
+	{
+		std::array<Cursor::Map, 4> cursorMove      = { Cursor::Map::T1_MOVE,       Cursor::Map::T2_MOVE,       Cursor::Map::T3_MOVE,       Cursor::Map::T4_MOVE,       };
+		std::array<Cursor::Map, 4> cursorAttack    = { Cursor::Map::T1_ATTACK,     Cursor::Map::T2_ATTACK,     Cursor::Map::T3_ATTACK,     Cursor::Map::T4_ATTACK,     };
+		std::array<Cursor::Map, 4> cursorSail      = { Cursor::Map::T1_SAIL,       Cursor::Map::T2_SAIL,       Cursor::Map::T3_SAIL,       Cursor::Map::T4_SAIL,       };
+		std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK,  Cursor::Map::T2_DISEMBARK,  Cursor::Map::T3_DISEMBARK,  Cursor::Map::T4_DISEMBARK,  };
+		std::array<Cursor::Map, 4> cursorExchange  = { Cursor::Map::T1_EXCHANGE,   Cursor::Map::T2_EXCHANGE,   Cursor::Map::T3_EXCHANGE,   Cursor::Map::T4_EXCHANGE,   };
+		std::array<Cursor::Map, 4> cursorVisit     = { Cursor::Map::T1_VISIT,      Cursor::Map::T2_VISIT,      Cursor::Map::T3_VISIT,      Cursor::Map::T4_VISIT,      };
+		std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
+
+		const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
+		assert(pathNode);
+
+		if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info
+		{
+			showMoveDetailsInStatusbar(*hero, *pathNode);
+		}
+
+		int turns = pathNode->turns;
+		vstd::amin(turns, 3);
+		switch(pathNode->action)
+		{
+		case CGPathNode::NORMAL:
+		case CGPathNode::TELEPORT_NORMAL:
+			if(pathNode->layer == EPathfindingLayer::LAND)
+				CCS->curh->set(cursorMove[turns]);
+			else
+				CCS->curh->set(cursorSailVisit[turns]);
+			break;
+
+		case CGPathNode::VISIT:
+		case CGPathNode::BLOCKING_VISIT:
+		case CGPathNode::TELEPORT_BLOCKING_VISIT:
+			if(objAtTile && objAtTile->ID == Obj::HERO)
+			{
+				if(LOCPLINT->localState->getCurrentArmy()  == objAtTile)
+					CCS->curh->set(Cursor::Map::HERO);
+				else
+					CCS->curh->set(cursorExchange[turns]);
+			}
+			else if(pathNode->layer == EPathfindingLayer::LAND)
+				CCS->curh->set(cursorVisit[turns]);
+			else
+				CCS->curh->set(cursorSailVisit[turns]);
+			break;
+
+		case CGPathNode::BATTLE:
+		case CGPathNode::TELEPORT_BATTLE:
+			CCS->curh->set(cursorAttack[turns]);
+			break;
+
+		case CGPathNode::EMBARK:
+			CCS->curh->set(cursorSail[turns]);
+			break;
+
+		case CGPathNode::DISEMBARK:
+			CCS->curh->set(cursorDisembark[turns]);
+			break;
+
+		default:
+			if(objAtTile && objRelations != PlayerRelations::ENEMIES)
+			{
+				if(objAtTile->ID == Obj::TOWN)
+					CCS->curh->set(Cursor::Map::TOWN);
+				else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
+					CCS->curh->set(Cursor::Map::HERO);
+				else
+					CCS->curh->set(Cursor::Map::POINTER);
+			}
+			else
+				CCS->curh->set(Cursor::Map::POINTER);
+			break;
+		}
+	}
+
+	if(ourInaccessibleShipyard(objAtTile))
+	{
+		CCS->curh->set(Cursor::Map::T1_SAIL);
+	}
+}
+
+void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
+{
+	const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
+	const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
+	const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
+
+	std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
+
+	boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
+	boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
+	boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
+
+	GH.statusbar->write(result);
+}
+
+void AdventureMapInterface::onTileRightClicked(const int3 &mapPos)
+{
+	if(!shortcuts->optionMapViewActive())
+		return;
+
+	if(spellBeingCasted)
+	{
+		hotkeyAbortCastingMode();
+		return;
+	}
+
+	if(!LOCPLINT->cb->isVisible(mapPos))
+	{
+		CRClickPopup::createAndPush(VLC->generaltexth->allTexts[61]); //Uncharted Territory
+		return;
+	}
+
+	const CGObjectInstance * obj = getActiveObject(mapPos);
+	if(!obj)
+	{
+		// Bare or undiscovered terrain
+		const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
+		if(tile)
+		{
+			std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
+			CRClickPopup::createAndPush(hlp);
+		}
+		return;
+	}
+
+	CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
+}
+
+void AdventureMapInterface::enterCastingMode(const CSpell * sp)
+{
+	assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR);
+	spellBeingCasted = sp;
+	Settings config = settings.write["session"]["showSpellRange"];
+	config->Bool() = true;
+
+	setState(EAdventureState::CASTING_SPELL);
+}
+
+void AdventureMapInterface::exitCastingMode()
+{
+	assert(spellBeingCasted);
+	spellBeingCasted = nullptr;
+	setState(EAdventureState::MAKING_TURN);
+
+	Settings config = settings.write["session"]["showSpellRange"];
+	config->Bool() = false;
+}
+
+void AdventureMapInterface::hotkeyAbortCastingMode()
+{
+	exitCastingMode();
+	LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
+}
+
+void AdventureMapInterface::performSpellcasting(const int3 & dest)
+{
+	SpellID id = spellBeingCasted->id;
+	exitCastingMode();
+	LOCPLINT->cb->castSpell(LOCPLINT->localState->getCurrentHero(), id, dest);
+}
+
+Rect AdventureMapInterface::terrainAreaPixels() const
+{
+	return widget->getMapView()->pos;
+}
+
+const IShipyard * AdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
+{
+	const IShipyard *ret = IShipyard::castFrom(obj);
+
+	if(!ret ||
+		obj->tempOwner != currentPlayerID ||
+		(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
+		return nullptr;
+
+	return ret;
+}
+
+void AdventureMapInterface::hotkeyExitWorldView()
+{
+	setState(EAdventureState::MAKING_TURN);
+	widget->getMapView()->onViewMapActivated();
+}
+
+void AdventureMapInterface::openWorldView(int tileSize)
+{
+	setState(EAdventureState::WORLD_VIEW);
+	widget->getMapView()->onViewWorldActivated(tileSize);
+}
+
+void AdventureMapInterface::openWorldView()
+{
+	openWorldView(11);
+}
+
+void AdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
+{
+	openWorldView(11);
+	widget->getMapView()->onViewSpellActivated(11, objectPositions, showTerrain);
+}
+
+void AdventureMapInterface::hotkeyNextTown()
+{
+	widget->getTownList()->selectNext();
+}
+
+void AdventureMapInterface::hotkeySwitchMapLevel()
+{
+	widget->getMapView()->onMapLevelSwitched();
+}
+
+void AdventureMapInterface::onScreenResize()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	widget.reset();
+	pos.x = pos.y = 0;
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
+
+	widget = std::make_shared<AdventureMapWidget>(shortcuts);
+	widget->getMapView()->onViewMapActivated();
+	widget->setPlayer(currentPlayerID);
+	widget->updateActiveState();
+	widget->getMinimap()->update();
+	widget->getInfoBar()->showSelection();
+
+	adjustActiveness();
+}

+ 37 - 84
client/adventureMap/CAdventureMapInterface.h → client/adventureMap/AdventureMapInterface.h

@@ -1,5 +1,5 @@
 /*
- * CAdvMapInt.h, part of VCMI engine
+ * AdventureMapInterface.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -29,8 +29,8 @@ class CButton;
 class IImage;
 class CAnimImage;
 class CGStatusBar;
-class CAdvMapPanel;
-class CAdvMapWorldViewPanel;
+class AdventureMapWidget;
+class AdventureMapShortcuts;
 class CAnimation;
 class MapView;
 class CResDataBar;
@@ -39,111 +39,56 @@ class CTownList;
 class CInfoBar;
 class CMinimap;
 class MapAudioPlayer;
+enum class EAdventureState;
 
 struct MapDrawingInfo;
 
 /// That's a huge class which handles general adventure map actions and
 /// shows the right menu(questlog, spellbook, end turn,..) from where you
 /// can get to the towns and heroes.
-class CAdventureMapInterface : public CIntObject
+class AdventureMapInterface : public CIntObject
 {
 private:
-	enum class EGameState
-	{
-		NOT_INITIALIZED,
-		HOTSEAT_WAIT,
-		MAKING_TURN,
-		ENEMY_TURN,
-		WORLD_VIEW
-	};
-
-	EGameState state;
-
 	/// currently acting player
 	PlayerColor currentPlayerID;
 
-	/// uses EDirections enum
-	bool scrollingCursorSet;
-
-	const CSpell *spellBeingCasted; //nullptr if none
-
-	std::vector<std::shared_ptr<CAnimImage>> gems;
-
-	std::shared_ptr<IImage> bg;
-	std::shared_ptr<IImage> bgWorldView;
-	std::shared_ptr<CButton> kingOverview;
-	std::shared_ptr<CButton> sleepWake;
-	std::shared_ptr<CButton> underground;
-	std::shared_ptr<CButton> questlog;
-	std::shared_ptr<CButton> moveHero;
-	std::shared_ptr<CButton> spellbook;
-	std::shared_ptr<CButton> advOptions;
-	std::shared_ptr<CButton> sysOptions;
-	std::shared_ptr<CButton> nextHero;
-	std::shared_ptr<CButton> endTurn;
-	std::shared_ptr<CButton> worldViewUnderground;
-
-	std::shared_ptr<MapView> terrain;
-	std::shared_ptr<CMinimap> minimap;
-	std::shared_ptr<CHeroList> heroList;
-	std::shared_ptr<CTownList> townList;
-	std::shared_ptr<CInfoBar> infoBar;
-	std::shared_ptr<CGStatusBar> statusbar;
-	std::shared_ptr<CResDataBar> resdatabar;
-
-	std::shared_ptr<CAdvMapPanel> panelMain; // panel that holds all right-side buttons in normal view
-	std::shared_ptr<CAdvMapWorldViewPanel> panelWorldView; // panel that holds all buttons and other ui in world view
-	std::shared_ptr<CAdvMapPanel> activeMapPanel; // currently active panel (either main or world view, depending on current mode)
-
-	std::shared_ptr<CAnimation> worldViewIcons;// images for world view overlay
+	/// if true, cursor was changed to scrolling and must be reset back once scroll is over
+	bool scrollingWasActive;
+
+	/// if true, then scrolling was blocked via ctrl and should not restart until player move cursor outside scrolling area
+	bool scrollingWasBlocked;
+
+	/// spell for which player is selecting target, or nullptr if none
+	const CSpell *spellBeingCasted;
 
 	std::shared_ptr<MapAudioPlayer> mapAudio;
+	std::shared_ptr<AdventureMapWidget> widget;
+	std::shared_ptr<AdventureMapShortcuts> shortcuts;
 
 private:
-	//functions bound to buttons
-	void fshowOverview();
-	void fworldViewBack();
-	void fworldViewScale1x();
-	void fworldViewScale2x();
-	void fworldViewScale4x();
-	void fswitchLevel();
-	void fshowQuestlog();
-	void fsleepWake();
-	void fmoveHero();
-	void fshowSpellbok();
-	void fadventureOPtions();
-	void fsystemOptions();
-	void fnextHero();
-	void fendTurn();
-
-	void hotkeyMoveHeroDirectional(Point direction);
-
-	bool isActive();
-	void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
-
-	const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const; //checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
-
-	// update locked state of buttons
-	void updateButtons();
+	void setState(EAdventureState state);
+
+	/// updates active state of game window whenever game state changes
+	void adjustActiveness();
+
+	/// checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
+	const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const;
 
+	/// check and if necessary reacts on scrolling by moving cursor to screen edge
 	void handleMapScrollingUpdate();
 
 	void showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode);
 
 	const CGObjectInstance *getActiveObject(const int3 &tile);
 
-	std::optional<Point> keyToMoveDirection(EShortcut key);
-
-	void endingTurn();
-
 	/// exits currently opened world view mode and returns to normal map
-	void exitWorldView();
 	void exitCastingMode();
-	void leaveCastingMode(const int3 & castTarget);
-	void abortCastingMode();
+
+	/// casts current spell at specified location
+	void performSpellcasting(const int3 & castTarget);
 
 protected:
-	// CIntObject interface implementation
+	/// CIntObject interface implementation
 
 	void activate() override;
 	void deactivate() override;
@@ -153,8 +98,16 @@ protected:
 
 	void keyPressed(EShortcut key) override;
 
+	void onScreenResize() override;
+
 public:
-	CAdventureMapInterface();
+	AdventureMapInterface();
+
+	void hotkeyAbortCastingMode();
+	void hotkeyExitWorldView();
+	void hotkeyEndingTurn();
+	void hotkeyNextTown();
+	void hotkeySwitchMapLevel();
 
 	/// Called by PlayerInterface when specified player is ready to start his turn
 	void onHotseatWaitStarted(PlayerColor playerID);
@@ -225,4 +178,4 @@ public:
 	void openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain);
 };
 
-extern std::shared_ptr<CAdventureMapInterface> adventureInt;
+extern std::shared_ptr<AdventureMapInterface> adventureInt;

+ 446 - 0
client/adventureMap/AdventureMapShortcuts.cpp

@@ -0,0 +1,446 @@
+/*
+ * AdventureMapShortcuts.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 "AdventureMapShortcuts.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../PlayerLocalState.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+#include "../lobby/CSavingScreen.h"
+#include "../mapView/mapHandler.h"
+#include "../windows/CKingdomInterface.h"
+#include "../windows/CSpellWindow.h"
+#include "../windows/CTradeWindow.h"
+#include "../windows/settings/SettingsMainWindow.h"
+#include "AdventureMapInterface.h"
+#include "AdventureOptions.h"
+#include "AdventureState.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CPathfinder.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/mapping/CMap.h"
+
+AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner)
+	: owner(owner)
+	, state(EAdventureState::NOT_INITIALIZED)
+	, mapLevel(0)
+{}
+
+void AdventureMapShortcuts::setState(EAdventureState newState)
+{
+	state = newState;
+}
+
+void AdventureMapShortcuts::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
+{
+	mapLevel = newMapLevel;
+}
+
+std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts()
+{
+	std::vector<AdventureMapShortcutState> result = {
+		{ EShortcut::ADVENTURE_KINGDOM_OVERVIEW, optionInMapView(),      [this]() { this->showOverview(); } },
+		{ EShortcut::ADVENTURE_EXIT_WORLD_VIEW,  optionInWorldView(),    [this]() { this->worldViewBack(); } },
+		{ EShortcut::ADVENTURE_VIEW_WORLD,       optionInMapView(),      [this]() { this->worldViewScale1x(); } },
+		{ EShortcut::ADVENTURE_VIEW_WORLD_X1,    optionInWorldView(),    [this]() { this->worldViewScale1x(); } },
+		{ EShortcut::ADVENTURE_VIEW_WORLD_X2,    optionInWorldView(),    [this]() { this->worldViewScale2x(); } },
+		{ EShortcut::ADVENTURE_VIEW_WORLD_X4,    optionInWorldView(),    [this]() { this->worldViewScale4x(); } },
+		{ EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, optionCanToggleLevel(), [this]() { this->switchMapLevel(); } },
+		{ EShortcut::ADVENTURE_QUEST_LOG,        optionCanViewQuests(),  [this]() { this->showQuestlog(); } },
+		{ EShortcut::ADVENTURE_TOGGLE_SLEEP,     optionHeroSelected(),   [this]() { this->toggleSleepWake(); } },
+		{ EShortcut::ADVENTURE_SET_HERO_ASLEEP,  optionHeroAwake(),      [this]() { this->setHeroSleeping(); } },
+		{ EShortcut::ADVENTURE_SET_HERO_AWAKE,   optionHeroSleeping(),   [this]() { this->setHeroAwake(); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO,        optionHeroCanMove(),    [this]() { this->moveHeroAlongPath(); } },
+		{ EShortcut::ADVENTURE_CAST_SPELL,       optionHeroSelected(),   [this]() { this->showSpellbook(); } },
+		{ EShortcut::ADVENTURE_GAME_OPTIONS,     optionInMapView(),      [this]() { this->adventureOptions(); } },
+		{ EShortcut::GLOBAL_OPTIONS,             optionInMapView(),      [this]() { this->systemOptions(); } },
+		{ EShortcut::ADVENTURE_NEXT_HERO,        optionHasNextHero(),    [this]() { this->nextHero(); } },
+		{ EShortcut::GAME_END_TURN,              optionInMapView(),      [this]() { this->endTurn(); } },
+		{ EShortcut::ADVENTURE_THIEVES_GUILD,    optionInMapView(),      [this]() { this->showThievesGuild(); } },
+		{ EShortcut::ADVENTURE_VIEW_SCENARIO,    optionInMapView(),      [this]() { this->showScenarioInfo(); } },
+		{ EShortcut::GAME_SAVE_GAME,             optionInMapView(),      [this]() { this->saveGame(); } },
+		{ EShortcut::GAME_LOAD_GAME,             optionInMapView(),      [this]() { this->loadGame(); } },
+		{ EShortcut::ADVENTURE_DIG_GRAIL,        optionHeroSelected(),   [this]() { this->digGrail(); } },
+		{ EShortcut::ADVENTURE_VIEW_PUZZLE,      optionSidePanelActive(),[this]() { this->viewPuzzleMap(); } },
+		{ EShortcut::GAME_RESTART_GAME,          optionInMapView(),      [this]() { this->restartGame(); } },
+		{ EShortcut::ADVENTURE_VISIT_OBJECT,     optionHeroSelected(),   [this]() { this->visitObject(); } },
+		{ EShortcut::ADVENTURE_VIEW_SELECTED,    optionInMapView(),      [this]() { this->openObject(); } },
+		{ EShortcut::GAME_OPEN_MARKETPLACE,      optionInMapView(),      [this]() { this->showMarketplace(); } },
+		{ EShortcut::ADVENTURE_NEXT_TOWN,        optionInMapView(),      [this]() { this->nextTown(); } },
+		{ EShortcut::ADVENTURE_NEXT_OBJECT,      optionInMapView(),      [this]() { this->nextObject(); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SW,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({-1, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SS,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({ 0, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SE,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({+1, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_WW,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({-1,  0}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_EE,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({+1,  0}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NW,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({-1, -1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NN,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({ 0, -1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NE,     optionHeroSelected(),   [this]() { this->moveHeroDirectional({+1, -1}); } }
+	};
+	return result;
+}
+
+void AdventureMapShortcuts::showOverview()
+{
+	GH.pushIntT<CKingdomInterface>();
+}
+
+void AdventureMapShortcuts::worldViewBack()
+{
+	owner.hotkeyExitWorldView();
+
+	auto hero = LOCPLINT->localState->getCurrentHero();
+	if (hero)
+		owner.centerOnObject(hero);
+}
+
+void AdventureMapShortcuts::worldViewScale1x()
+{
+	// TODO set corresponding scale button to "selected" mode
+	owner.openWorldView(7);
+}
+
+void AdventureMapShortcuts::worldViewScale2x()
+{
+	owner.openWorldView(11);
+}
+
+void AdventureMapShortcuts::worldViewScale4x()
+{
+	owner.openWorldView(16);
+}
+
+void AdventureMapShortcuts::switchMapLevel()
+{
+	int maxLevels = LOCPLINT->cb->getMapSize().z;
+	if (maxLevels < 2)
+		return;
+
+	owner.hotkeySwitchMapLevel();
+}
+
+void AdventureMapShortcuts::showQuestlog()
+{
+	LOCPLINT->showQuestLog();
+}
+
+void AdventureMapShortcuts::toggleSleepWake()
+{
+	if (!optionHeroSelected())
+		return;
+
+	if (optionHeroAwake())
+		setHeroSleeping();
+	else
+		setHeroAwake();
+}
+
+void AdventureMapShortcuts::setHeroSleeping()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (h)
+	{
+		LOCPLINT->localState->setHeroAsleep(h);
+		owner.onHeroChanged(h);
+		nextHero();
+	}
+}
+
+void AdventureMapShortcuts::setHeroAwake()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (h)
+	{
+		LOCPLINT->localState->setHeroAwaken(h);
+		owner.onHeroChanged(h);
+	}
+}
+
+void AdventureMapShortcuts::moveHeroAlongPath()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (!h || !LOCPLINT->localState->hasPath(h))
+		return;
+
+	LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
+}
+
+void AdventureMapShortcuts::showSpellbook()
+{
+	if (!LOCPLINT->localState->getCurrentHero())
+		return;
+
+	owner.centerOnObject(LOCPLINT->localState->getCurrentHero());
+
+	GH.pushIntT<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
+}
+
+void AdventureMapShortcuts::adventureOptions()
+{
+	GH.pushIntT<AdventureOptions>();
+}
+
+void AdventureMapShortcuts::systemOptions()
+{
+	GH.pushIntT<SettingsMainWindow>();
+}
+
+void AdventureMapShortcuts::nextHero()
+{
+	const auto * currHero = LOCPLINT->localState->getCurrentHero();
+	const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
+
+	if (nextHero)
+	{
+		LOCPLINT->localState->setSelection(nextHero);
+		owner.centerOnObject(nextHero);
+	}
+}
+
+void AdventureMapShortcuts::endTurn()
+{
+	if(!LOCPLINT->makingTurn)
+		return;
+
+	if(settings["adventure"]["heroReminder"].Bool())
+	{
+		for(auto hero : LOCPLINT->localState->getWanderingHeroes())
+		{
+			if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
+			{
+				// 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
+				LOCPLINT->localState->verifyPath(hero);
+
+				if(!LOCPLINT->localState->hasPath(hero))
+				{
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
+					return;
+				}
+
+				auto path = LOCPLINT->localState->getPath(hero);
+				if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
+				{
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
+					return;
+				}
+			}
+		}
+	}
+	owner.hotkeyEndingTurn();
+}
+
+void AdventureMapShortcuts::showThievesGuild()
+{
+	//find first town with tavern
+	auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
+	{
+		return town->hasBuilt(BuildingID::TAVERN);
+	});
+
+	if(itr != LOCPLINT->localState->getOwnedTowns().end())
+		LOCPLINT->showThievesGuildWindow(*itr);
+	else
+		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
+}
+
+void AdventureMapShortcuts::showScenarioInfo()
+{
+	AdventureOptions::showScenarioInfo();
+}
+
+void AdventureMapShortcuts::saveGame()
+{
+	GH.pushIntT<CSavingScreen>();
+}
+
+void AdventureMapShortcuts::loadGame()
+{
+	LOCPLINT->proposeLoadingGame();
+}
+
+void AdventureMapShortcuts::digGrail()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+
+	if(h && LOCPLINT->makingTurn)
+		LOCPLINT->tryDiggging(h);
+}
+
+void AdventureMapShortcuts::viewPuzzleMap()
+{
+	LOCPLINT->showPuzzleMap();
+}
+
+void AdventureMapShortcuts::restartGame()
+{
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
+		[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
+}
+
+void AdventureMapShortcuts::visitObject()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+
+	if(h)
+		LOCPLINT->cb->moveHero(h,h->pos);
+}
+
+void AdventureMapShortcuts::openObject()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
+	if(h)
+		LOCPLINT->openHeroWindow(h);
+
+	if(t)
+		LOCPLINT->openTownWindow(t);
+}
+
+void AdventureMapShortcuts::showMarketplace()
+{
+	//check if we have any marketplace
+	const CGTownInstance *townWithMarket = nullptr;
+	for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
+	{
+		if(t->hasBuilt(BuildingID::MARKETPLACE))
+		{
+			townWithMarket = t;
+			break;
+		}
+	}
+
+	if(townWithMarket) //if any town has marketplace, open window
+		GH.pushIntT<CMarketplaceWindow>(townWithMarket);
+	else //if not - complain
+		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
+}
+
+void AdventureMapShortcuts::nextTown()
+{
+	owner.hotkeyNextTown();
+}
+
+void AdventureMapShortcuts::nextObject()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
+	if(h)
+		nextHero();
+
+	if(t)
+		nextTown();
+}
+
+void AdventureMapShortcuts::moveHeroDirectional(const Point & direction)
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
+
+	if(!h)
+		return;
+
+	if (CGI->mh->hasOngoingAnimations())
+		return;
+
+	int3 dst = h->visitablePos() + int3(direction.x, direction.y, 0);
+
+	if (!CGI->mh->isInMap((dst)))
+		return;
+
+	if ( !LOCPLINT->localState->setPath(h, dst))
+		return;
+
+	const CGPath & path = LOCPLINT->localState->getPath(h);
+
+	if (path.nodes.size() > 2)
+		owner.onHeroChanged(h);
+	else
+		if(path.nodes[0].turns == 0)
+			LOCPLINT->moveHero(h, path);
+}
+
+bool AdventureMapShortcuts::optionCanViewQuests()
+{
+	return optionInMapView() && CGI->mh->getMap()->quests.empty();
+}
+
+bool AdventureMapShortcuts::optionCanToggleLevel()
+{
+	return optionInMapView() && LOCPLINT->cb->getMapSize().z > 0;
+}
+
+bool AdventureMapShortcuts::optionMapLevelSurface()
+{
+	return mapLevel == 0;
+}
+
+bool AdventureMapShortcuts::optionHeroSleeping()
+{
+	const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
+	return optionInMapView() && hero && LOCPLINT->localState->isHeroSleeping(hero);
+}
+
+bool AdventureMapShortcuts::optionHeroAwake()
+{
+	const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
+	return optionInMapView() && hero && !LOCPLINT->localState->isHeroSleeping(hero);
+}
+
+bool AdventureMapShortcuts::optionHeroSelected()
+{
+	return optionInMapView() && LOCPLINT->localState->getCurrentHero() != nullptr;
+}
+
+bool AdventureMapShortcuts::optionHeroCanMove()
+{
+	const auto * hero = LOCPLINT->localState->getCurrentHero();
+	return optionInMapView() && hero && hero->movement != 0 && LOCPLINT->localState->hasPath(hero);
+}
+
+bool AdventureMapShortcuts::optionHasNextHero()
+{
+	const auto * hero = LOCPLINT->localState->getCurrentHero();
+	const auto * nextSuitableHero = LOCPLINT->localState->getNextWanderingHero(hero);
+
+	return optionInMapView() && nextSuitableHero != nullptr;
+}
+
+bool AdventureMapShortcuts::optionSpellcasting()
+{
+	return state == EAdventureState::CASTING_SPELL;
+}
+
+bool AdventureMapShortcuts::optionInMapView()
+{
+	return state == EAdventureState::MAKING_TURN;
+}
+
+bool AdventureMapShortcuts::optionInWorldView()
+{
+	return state == EAdventureState::WORLD_VIEW;
+}
+
+bool AdventureMapShortcuts::optionSidePanelActive()
+{
+	return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
+}
+
+bool AdventureMapShortcuts::optionMapViewActive()
+{
+	return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL;
+}

+ 87 - 0
client/adventureMap/AdventureMapShortcuts.h

@@ -0,0 +1,87 @@
+/*
+ * AdventureMapShortcuts.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 Point;
+class Rect;
+VCMI_LIB_NAMESPACE_END
+
+enum class EShortcut;
+class AdventureMapInterface;
+enum class EAdventureState;
+
+struct AdventureMapShortcutState
+{
+	EShortcut shortcut;
+	bool isEnabled;
+	std::function<void()> callback;
+};
+
+/// Class that contains list of functions for shortcuts available from adventure map
+class AdventureMapShortcuts
+{
+	AdventureMapInterface & owner;
+	EAdventureState state;
+	int mapLevel;
+
+	void showOverview();
+	void worldViewBack();
+	void worldViewScale1x();
+	void worldViewScale2x();
+	void worldViewScale4x();
+	void switchMapLevel();
+	void showQuestlog();
+	void toggleSleepWake();
+	void setHeroSleeping();
+	void setHeroAwake();
+	void moveHeroAlongPath();
+	void showSpellbook();
+	void adventureOptions();
+	void systemOptions();
+	void nextHero();
+	void endTurn();
+	void showThievesGuild();
+	void showScenarioInfo();
+	void saveGame();
+	void loadGame();
+	void digGrail();
+	void viewPuzzleMap();
+	void restartGame();
+	void visitObject();
+	void openObject();
+	void showMarketplace();
+	void nextTown();
+	void nextObject();
+	void moveHeroDirectional(const Point & direction);
+
+public:
+	explicit AdventureMapShortcuts(AdventureMapInterface & owner);
+
+	std::vector<AdventureMapShortcutState> getShortcuts();
+
+	bool optionCanViewQuests();
+	bool optionCanToggleLevel();
+	bool optionMapLevelSurface();
+	bool optionHeroSleeping();
+	bool optionHeroAwake();
+	bool optionHeroSelected();
+	bool optionHeroCanMove();
+	bool optionHasNextHero();
+	bool optionSpellcasting();
+	bool optionInMapView();
+	bool optionInWorldView();
+	bool optionSidePanelActive();
+	bool optionMapViewActive();
+
+	void setState(EAdventureState newState);
+	void onMapViewMoved(const Rect & visibleArea, int mapLevel);
+};

+ 455 - 0
client/adventureMap/AdventureMapWidget.cpp

@@ -0,0 +1,455 @@
+/*
+ * CAdventureMapWidget.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 "AdventureMapWidget.h"
+
+#include "AdventureMapShortcuts.h"
+#include "CInfoBar.h"
+#include "CList.h"
+#include "CMinimap.h"
+#include "CResDataBar.h"
+#include "AdventureState.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+#include "../mapView/MapView.h"
+#include "../render/CAnimation.h"
+#include "../render/IImage.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/Images.h"
+#include "../widgets/TextControls.h"
+
+#include "../CPlayerInterface.h"
+#include "../PlayerLocalState.h"
+
+#include "../../lib/StringConstants.h"
+#include "../../lib/filesystem/ResourceID.h"
+
+AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
+	: shortcuts(shortcuts)
+	, mapLevel(0)
+{
+	pos.x = pos.y = 0;
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
+
+	REGISTER_BUILDER("adventureInfobar",         &AdventureMapWidget::buildInfobox         );
+	REGISTER_BUILDER("adventureMapImage",        &AdventureMapWidget::buildMapImage        );
+	REGISTER_BUILDER("adventureMapButton",       &AdventureMapWidget::buildMapButton       );
+	REGISTER_BUILDER("adventureMapContainer",    &AdventureMapWidget::buildMapContainer    );
+	REGISTER_BUILDER("adventureMapGameArea",     &AdventureMapWidget::buildMapGameArea     );
+	REGISTER_BUILDER("adventureMapHeroList",     &AdventureMapWidget::buildMapHeroList     );
+	REGISTER_BUILDER("adventureMapIcon",         &AdventureMapWidget::buildMapIcon         );
+	REGISTER_BUILDER("adventureMapTownList",     &AdventureMapWidget::buildMapTownList     );
+	REGISTER_BUILDER("adventureMinimap",         &AdventureMapWidget::buildMinimap         );
+	REGISTER_BUILDER("adventureResourceDateBar", &AdventureMapWidget::buildResourceDateBar );
+	REGISTER_BUILDER("adventureStatusBar",       &AdventureMapWidget::buildStatusBar       );
+	REGISTER_BUILDER("adventurePlayerTexture",   &AdventureMapWidget::buildTexturePlayerColored);
+
+	for (const auto & entry : shortcuts->getShortcuts())
+		addShortcut(entry.shortcut, entry.callback);
+
+	const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
+
+	for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
+	{
+		ResourceID resourceName(entry.String(), EResType::IMAGE);
+		playerColorerImages.push_back(resourceName.getName());
+	}
+
+	build(config);
+	addUsedEvents(KEYBOARD);
+}
+
+void AdventureMapWidget::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
+{
+	if(mapLevel == newMapLevel)
+		return;
+
+	mapLevel = newMapLevel;
+	updateActiveState();
+}
+
+Rect AdventureMapWidget::readSourceArea(const JsonNode & source, const JsonNode & sourceCommon)
+{
+	const auto & input = source.isNull() ? sourceCommon : source;
+
+	return readArea(input, Rect(Point(0, 0), Point(800, 600)));
+}
+
+Rect AdventureMapWidget::readTargetArea(const JsonNode & source)
+{
+	if(subwidgetSizes.empty())
+		return readArea(source, pos);
+	return readArea(source, subwidgetSizes.back());
+}
+
+Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & boundingBox)
+{
+	const auto & object = source.Struct();
+
+	if(object.count("left") + object.count("width") + object.count("right") != 2)
+		logGlobal->error("Invalid area definition in widget! Unable to load width!");
+
+	if(object.count("top") + object.count("height") + object.count("bottom") != 2)
+		logGlobal->error("Invalid area definition in widget! Unable to load height!");
+
+	int left = source["left"].Integer();
+	int width = source["width"].Integer();
+	int right = source["right"].Integer();
+
+	int top = source["top"].Integer();
+	int height = source["height"].Integer();
+	int bottom = source["bottom"].Integer();
+
+	Point topLeft(left, top);
+	Point dimensions(width, height);
+
+	if(source["left"].isNull())
+		topLeft.x = boundingBox.w - right - width;
+
+	if(source["width"].isNull())
+		dimensions.x = boundingBox.w - right - left;
+
+	if(source["top"].isNull())
+		topLeft.y = boundingBox.h - bottom - height;
+
+	if(source["height"].isNull())
+		dimensions.y = boundingBox.h - bottom - top;
+
+	return Rect(topLeft + boundingBox.topLeft(), dimensions);
+}
+
+std::shared_ptr<IImage> AdventureMapWidget::loadImage(const std::string & name)
+{
+	ResourceID resource(name, EResType::IMAGE);
+
+	if(images.count(resource.getName()) == 0)
+		images[resource.getName()] = IImage::createFromFile(resource.getName());
+
+	return images[resource.getName()];
+}
+
+std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const std::string & name)
+{
+	ResourceID resource(name, EResType::ANIMATION);
+
+	if(animations.count(resource.getName()) == 0)
+		animations[resource.getName()] = std::make_shared<CAnimation>(resource.getName());
+
+	return animations[resource.getName()];
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	infoBar = std::make_shared<CInfoBar>(area);
+	return infoBar;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & input)
+{
+	Rect targetArea = readTargetArea(input["area"]);
+	Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
+	std::string image = input["image"].String();
+
+	return std::make_shared<CFilledTexture>(loadImage(image), targetArea, sourceArea);
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
+{
+	auto position = readTargetArea(input["area"]);
+	auto image = input["image"].String();
+	auto help = readHintText(input["help"]);
+	bool playerColored = input["playerColored"].Bool();
+
+	auto button = std::make_shared<CButton>(position.topLeft(), image, help, 0, EShortcut::NONE, playerColored);
+
+	loadButtonBorderColor(button, input["borderColor"]);
+	loadButtonHotkey(button, input["hotkey"]);
+
+	return button;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapContainer(const JsonNode & input)
+{
+	auto position = readTargetArea(input["area"]);
+	std::shared_ptr<CAdventureMapContainerWidget> result;
+
+	if (!input["exists"].isNull())
+	{
+		if (!input["exists"]["heightMin"].isNull() && input["exists"]["heightMin"].Integer() >= pos.h)
+			return nullptr;
+		if (!input["exists"]["heightMax"].isNull() && input["exists"]["heightMax"].Integer() < pos.h)
+			return nullptr;
+		if (!input["exists"]["widthMin"].isNull() && input["exists"]["widthMin"].Integer() >= pos.w)
+			return nullptr;
+		if (!input["exists"]["widthMax"].isNull() && input["exists"]["widthMax"].Integer() < pos.w)
+			return nullptr;
+	}
+
+	if (input["overlay"].Bool())
+		result = std::make_shared<CAdventureMapOverlayWidget>();
+	else
+		result = std::make_shared<CAdventureMapContainerWidget>();
+
+	result->disableCondition = input["hideWhen"].String();
+
+	result->moveBy(position.topLeft());
+	subwidgetSizes.push_back(position);
+	for(const auto & entry : input["items"].Vector())
+	{
+		auto widget = buildWidget(entry);
+
+		addWidget(entry["name"].String(), widget);
+		result->ownedChildren.push_back(widget);
+
+		// FIXME: remove cast and replace it with better check
+		if (std::dynamic_pointer_cast<CLabel>(widget) || std::dynamic_pointer_cast<CLabelGroup>(widget))
+			result->addChild(widget.get(), true);
+		else
+			result->addChild(widget.get(), false);
+	}
+	subwidgetSizes.pop_back();
+
+	return result;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapGameArea(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	mapView = std::make_shared<MapView>(area.topLeft(), area.dimensions());
+	return mapView;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapHeroList(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	subwidgetSizes.push_back(area);
+
+	Rect item = readTargetArea(input["item"]);
+
+	Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
+	int itemsCount = input["itemsCount"].Integer();
+
+	auto result = std::make_shared<CHeroList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getWanderingHeroes().size());
+
+
+	if(!input["scrollUp"].isNull())
+		result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
+
+	if(!input["scrollDown"].isNull())
+		result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
+
+	subwidgetSizes.pop_back();
+
+	heroList = result;
+	return result;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	size_t index = input["index"].Integer();
+	size_t perPlayer = input["perPlayer"].Integer();
+	std::string image = input["image"].String();
+
+	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(image), index, perPlayer);
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	subwidgetSizes.push_back(area);
+
+	Rect item = readTargetArea(input["item"]);
+	Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
+	int itemsCount = input["itemsCount"].Integer();
+
+	auto result = std::make_shared<CTownList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getOwnedTowns().size());
+
+
+	if(!input["scrollUp"].isNull())
+		result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
+
+	if(!input["scrollDown"].isNull())
+		result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
+
+	subwidgetSizes.pop_back();
+
+	townList = result;
+	return result;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildMinimap(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	minimap = std::make_shared<CMinimap>(area);
+	return minimap;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	std::string image = input["image"].String();
+
+	auto result = std::make_shared<CResDataBar>(image, area.topLeft());
+
+	for(auto i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
+	{
+		const auto & node = input[GameConstants::RESOURCE_NAMES[i]];
+
+		if(node.isNull())
+			continue;
+
+		result->setResourcePosition(GameResID(i), Point(node["x"].Integer(), node["y"].Integer()));
+	}
+
+	result->setDatePosition(Point(input["date"]["x"].Integer(), input["date"]["y"].Integer()));
+
+	return result;
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode & input)
+{
+	Rect area = readTargetArea(input["area"]);
+	std::string image = input["image"].String();
+
+	auto background = std::make_shared<CFilledTexture>(image, area);
+
+	return CGStatusBar::create(background);
+}
+
+std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
+{
+	logGlobal->debug("Building widget CFilledTexture");
+	auto image = input["image"].String();
+	Rect area = readTargetArea(input["area"]);
+	return std::make_shared<FilledTexturePlayerColored>(image, area);
+}
+
+std::shared_ptr<CHeroList> AdventureMapWidget::getHeroList()
+{
+	return heroList;
+}
+
+std::shared_ptr<CTownList> AdventureMapWidget::getTownList()
+{
+	return townList;
+}
+
+std::shared_ptr<CMinimap> AdventureMapWidget::getMinimap()
+{
+	return minimap;
+}
+
+std::shared_ptr<MapView> AdventureMapWidget::getMapView()
+{
+	return mapView;
+}
+
+std::shared_ptr<CInfoBar> AdventureMapWidget::getInfoBar()
+{
+	return infoBar;
+}
+
+void AdventureMapWidget::setPlayer(const PlayerColor & player)
+{
+	setPlayerChildren(this, player);
+}
+
+void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColor & player)
+{
+	for(auto & entry : widget->children)
+	{
+		auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
+		auto icon = dynamic_cast<CAdventureMapIcon *>(entry);
+		auto button = dynamic_cast<CButton *>(entry);
+		auto texture = dynamic_cast<FilledTexturePlayerColored *>(entry);
+
+		if(button)
+			button->setPlayerColor(player);
+
+		if(icon)
+			icon->setPlayer(player);
+
+		if(container)
+			setPlayerChildren(container, player);
+
+		if (texture)
+			texture->playerColored(player);
+	}
+
+	for(const auto & entry : playerColorerImages)
+	{
+		if(images.count(entry))
+			images[entry]->playerColored(player);
+	}
+
+	redraw();
+}
+
+CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> animation, size_t index, size_t iconsPerPlayer)
+	: index(index)
+	, iconsPerPlayer(iconsPerPlayer)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos += position;
+	image = std::make_shared<CAnimImage>(animation, index);
+}
+
+void CAdventureMapIcon::setPlayer(const PlayerColor & player)
+{
+	image->setFrame(index + player.getNum() * iconsPerPlayer);
+}
+
+void CAdventureMapOverlayWidget::show(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+}
+
+void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget)
+{
+	for(auto & entry : widget->children)
+	{
+		auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
+
+		if (container)
+		{
+			if (container->disableCondition == "heroAwake")
+				container->setEnabled(!shortcuts->optionHeroSleeping());
+
+			if (container->disableCondition == "heroSleeping")
+				container->setEnabled(shortcuts->optionHeroSleeping());
+
+			if (container->disableCondition == "mapLayerSurface")
+				container->setEnabled(shortcuts->optionMapLevelSurface());
+
+			if (container->disableCondition == "mapLayerUnderground")
+				container->setEnabled(!shortcuts->optionMapLevelSurface());
+
+			if (container->disableCondition == "mapViewMode")
+				container->setEnabled(shortcuts->optionInWorldView());
+
+			if (container->disableCondition == "worldViewMode")
+				container->setEnabled(!shortcuts->optionInWorldView());
+
+			updateActiveStateChildden(container);
+		}
+	}
+}
+
+void AdventureMapWidget::updateActiveState()
+{
+	updateActiveStateChildden(this);
+
+	for (auto entry: shortcuts->getShortcuts())
+		setShortcutBlocked(entry.shortcut, !entry.isEnabled);
+}

+ 109 - 0
client/adventureMap/AdventureMapWidget.h

@@ -0,0 +1,109 @@
+/*
+ * CAdventureMapWidget.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/InterfaceObjectConfigurable.h"
+
+class CHeroList;
+class CTownList;
+class CMinimap;
+class MapView;
+class CInfoBar;
+class IImage;
+class AdventureMapShortcuts;
+enum class EAdventureState;
+
+/// Internal class of AdventureMapInterface that contains actual UI elements
+class AdventureMapWidget : public InterfaceObjectConfigurable
+{
+	int mapLevel;
+	/// temporary stack of sizes of currently building widgets
+	std::vector<Rect> subwidgetSizes;
+
+	/// list of images on which player-colored palette will be applied
+	std::vector<std::string> playerColorerImages;
+
+	/// list of named images shared between widgets
+	std::map<std::string, std::shared_ptr<IImage>> images;
+	std::map<std::string, std::shared_ptr<CAnimation>> animations;
+
+	/// Widgets that require access from adventure map
+	std::shared_ptr<CHeroList> heroList;
+	std::shared_ptr<CTownList> townList;
+	std::shared_ptr<CMinimap> minimap;
+	std::shared_ptr<MapView> mapView;
+	std::shared_ptr<CInfoBar> infoBar;
+
+	std::shared_ptr<AdventureMapShortcuts> shortcuts;
+
+	Rect readTargetArea(const JsonNode & source);
+	Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
+	Rect readArea(const JsonNode & source, const Rect & boundingBox);
+
+	std::shared_ptr<IImage> loadImage(const std::string & name);
+	std::shared_ptr<CAnimation> loadAnimation(const std::string & name);
+
+	std::shared_ptr<CIntObject> buildInfobox(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapImage(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapButton(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapContainer(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapGameArea(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapHeroList(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapIcon(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMapTownList(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildMinimap(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildResourceDateBar(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildStatusBar(const JsonNode & input);
+	std::shared_ptr<CIntObject> buildTexturePlayerColored(const JsonNode &);
+
+
+	void setPlayerChildren(CIntObject * widget, const PlayerColor & player);
+	void updateActiveStateChildden(CIntObject * widget);
+public:
+	explicit AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts );
+
+	std::shared_ptr<CHeroList> getHeroList();
+	std::shared_ptr<CTownList> getTownList();
+	std::shared_ptr<CMinimap> getMinimap();
+	std::shared_ptr<MapView> getMapView();
+	std::shared_ptr<CInfoBar> getInfoBar();
+
+	void setPlayer(const PlayerColor & player);
+
+	void onMapViewMoved(const Rect & visibleArea, int mapLevel);
+	void updateActiveState();
+};
+
+/// Small helper class that provides ownership for shared_ptr's of child elements
+class CAdventureMapContainerWidget : public CIntObject
+{
+	friend class AdventureMapWidget;
+	std::vector<std::shared_ptr<CIntObject>> ownedChildren;
+	std::string disableCondition;
+};
+
+class CAdventureMapOverlayWidget : public CAdventureMapContainerWidget
+{
+public:
+	void show(SDL_Surface * to) override;
+};
+
+/// Small helper class that provides player-colorable icon using animation file
+class CAdventureMapIcon : public CIntObject
+{
+	std::shared_ptr<CAnimImage> image;
+
+	size_t index;
+	size_t iconsPerPlayer;
+public:
+	CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> image, size_t index, size_t iconsPerPlayer);
+
+	void setPlayer(const PlayerColor & player);
+};

+ 5 - 5
client/adventureMap/CAdventureOptions.cpp → client/adventureMap/AdventureOptions.cpp

@@ -9,7 +9,7 @@
  */
 
 #include "StdInc.h"
-#include "CAdventureOptions.h"
+#include "AdventureOptions.h"
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
@@ -23,7 +23,7 @@
 #include "../../CCallback.h"
 #include "../../lib/StartInfo.h"
 
-CAdventureOptions::CAdventureOptions()
+AdventureOptions::AdventureOptions()
 	: CWindowObject(PLAYER_COLORED, "ADVOPTS")
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@@ -31,10 +31,10 @@ CAdventureOptions::CAdventureOptions()
 	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
 	viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
 
-	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
+	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
 
 	scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
-	scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
+	scenInfo->addCallback(AdventureOptions::showScenarioInfo);
 
 	puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
 	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
@@ -46,7 +46,7 @@ CAdventureOptions::CAdventureOptions()
 		dig->block(true);
 }
 
-void CAdventureOptions::showScenarioInfo()
+void AdventureOptions::showScenarioInfo()
 {
 	if(LOCPLINT->cb->getStartInfo()->campState)
 	{

+ 2 - 2
client/adventureMap/CAdventureOptions.h → client/adventureMap/AdventureOptions.h

@@ -14,7 +14,7 @@
 class CButton;
 
 /// Adventure options dialog where you can view the world, dig, play the replay of the last turn,...
-class CAdventureOptions : public CWindowObject
+class AdventureOptions : public CWindowObject
 {
 	std::shared_ptr<CButton> exit;
 	std::shared_ptr<CButton> viewWorld;
@@ -24,7 +24,7 @@ class CAdventureOptions : public CWindowObject
 	/*std::shared_ptr<CButton> replay*/
 
 public:
-	CAdventureOptions();
+	AdventureOptions();
 
 	static void showScenarioInfo();
 };

+ 20 - 0
client/adventureMap/AdventureState.h

@@ -0,0 +1,20 @@
+/*
+ * AdventureState.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
+
+enum class EAdventureState
+{
+	NOT_INITIALIZED,
+	HOTSEAT_WAIT,
+	MAKING_TURN,
+	ENEMY_TURN,
+	CASTING_SPELL,
+	WORLD_VIEW
+};

+ 0 - 94
client/adventureMap/CAdvMapPanel.cpp

@@ -1,94 +0,0 @@
-/*
- * CAdvMapPanel.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 "CAdvMapPanel.h"
-
-#include "../widgets/Buttons.h"
-#include "../widgets/Images.h"
-#include "../render/CAnimation.h"
-#include "../render/IImage.h"
-#include "../gui/CGuiHandler.h"
-
-CAdvMapPanel::CAdvMapPanel(std::shared_ptr<IImage> bg, Point position)
-	: CIntObject()
-	, background(bg)
-{
-	defActions = 255;
-	recActions = 255;
-	pos.x += position.x;
-	pos.y += position.y;
-	if (bg)
-	{
-		pos.w = bg->width();
-		pos.h = bg->height();
-	}
-}
-
-void CAdvMapPanel::addChildColorableButton(std::shared_ptr<CButton> button)
-{
-	colorableButtons.push_back(button);
-	addChildToPanel(button, ACTIVATE | DEACTIVATE);
-}
-
-void CAdvMapPanel::setPlayerColor(const PlayerColor & clr)
-{
-	for(auto & button : colorableButtons)
-	{
-		button->setPlayerColor(clr);
-	}
-}
-
-void CAdvMapPanel::showAll(SDL_Surface * to)
-{
-	if(background)
-		background->draw(to, pos.x, pos.y);
-
-	CIntObject::showAll(to);
-}
-
-void CAdvMapPanel::addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions)
-{
-	otherObjects.push_back(obj);
-	obj->recActions |= actions | SHOWALL;
-	obj->recActions &= ~DISPOSE;
-	addChild(obj.get(), false);
-}
-
-CAdvMapWorldViewPanel::CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color)
-	: CAdvMapPanel(bg, position), icons(_icons)
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	int fillerHeight = bg ? spaceBottom - pos.y - pos.h : 0;
-
-	if(fillerHeight > 0)
-	{
-		backgroundFiller = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, pos.h, pos.w, fillerHeight));
-	}
-}
-
-void CAdvMapWorldViewPanel::recolorIcons(const PlayerColor & color, int indexOffset)
-{
-	assert(iconsData.size() == currentIcons.size());
-
-	for(size_t idx = 0; idx < iconsData.size(); idx++)
-	{
-		const auto & data = iconsData.at(idx);
-		currentIcons[idx]->setFrame(data.first + indexOffset);
-	}
-}
-
-void CAdvMapWorldViewPanel::addChildIcon(std::pair<int, Point> data, int indexOffset)
-{
-	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-	iconsData.push_back(data);
-	currentIcons.push_back(std::make_shared<CAnimImage>(icons, data.first + indexOffset, 0, data.second.x, data.second.y));
-}
-

+ 0 - 60
client/adventureMap/CAdvMapPanel.h

@@ -1,60 +0,0 @@
-/*
- * CAdvMapPanel.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
-class PlayerColor;
-VCMI_LIB_NAMESPACE_END
-
-class CAnimation;
-class CAnimImage;
-class CFilledTexture;
-class CButton;
-class IImage;
-
-/// simple panel that contains other displayable elements; used to separate groups of controls
-class CAdvMapPanel : public CIntObject
-{
-	std::vector<std::shared_ptr<CButton>> colorableButtons;
-	std::vector<std::shared_ptr<CIntObject>> otherObjects;
-	/// the surface passed to this obj will be freed in dtor
-	std::shared_ptr<IImage> background;
-public:
-	CAdvMapPanel(std::shared_ptr<IImage> bg, Point position);
-
-	void addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions = 0);
-	void addChildColorableButton(std::shared_ptr<CButton> button);
-	/// recolors all buttons to given player color
-	void setPlayerColor(const PlayerColor & clr);
-
-	void showAll(SDL_Surface * to) override;
-};
-
-/// specialized version of CAdvMapPanel that handles recolorable def-based pictures for world view info panel
-class CAdvMapWorldViewPanel : public CAdvMapPanel
-{
-	/// data that allows reconstruction of panel info icons
-	std::vector<std::pair<int, Point>> iconsData;
-	/// ptrs to child-pictures constructed from iconsData
-	std::vector<std::shared_ptr<CAnimImage>> currentIcons;
-	/// surface drawn below world view panel on higher resolutions (won't be needed when world view panel is configured for extraResolutions mod)
-	std::shared_ptr<CFilledTexture> backgroundFiller;
-	std::shared_ptr<CAnimation> icons;
-public:
-	CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color);
-
-	void addChildIcon(std::pair<int, Point> data, int indexOffset);
-
-	/// recreates all pictures from given def to recolor them according to current player color
-	void recolorIcons(const PlayerColor & color, int indexOffset);
-};
-

+ 0 - 1346
client/adventureMap/CAdventureMapInterface.cpp

@@ -1,1346 +0,0 @@
-/*
- * CAdvMapInt.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 "CAdventureMapInterface.h"
-
-#include "CAdvMapPanel.h"
-#include "CAdventureOptions.h"
-#include "CInGameConsole.h"
-#include "CMinimap.h"
-#include "CResDataBar.h"
-#include "CList.h"
-#include "CInfoBar.h"
-#include "MapAudioPlayer.h"
-
-#include "../mapView/mapHandler.h"
-#include "../mapView/MapView.h"
-#include "../windows/CKingdomInterface.h"
-#include "../windows/CSpellWindow.h"
-#include "../windows/CTradeWindow.h"
-#include "../windows/GUIClasses.h"
-#include "../windows/InfoWindows.h"
-#include "../CGameInfo.h"
-#include "../CPlayerInterface.h"
-#include "../lobby/CSavingScreen.h"
-#include "../render/CAnimation.h"
-#include "../gui/CursorHandler.h"
-#include "../render/IImage.h"
-#include "../gui/CGuiHandler.h"
-#include "../gui/Shortcut.h"
-#include "../widgets/TextControls.h"
-#include "../widgets/Buttons.h"
-#include "../windows/settings/SettingsMainWindow.h"
-#include "../CMT.h"
-#include "../PlayerLocalState.h"
-
-#include "../../CCallback.h"
-#include "../../lib/CConfigHandler.h"
-#include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/spells/CSpellHandler.h"
-#include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../lib/mapObjects/CGTownInstance.h"
-#include "../../lib/CPathfinder.h"
-#include "../../lib/mapping/CMap.h"
-
-#define ADVOPT (conf.go()->ac)
-
-std::shared_ptr<CAdventureMapInterface> adventureInt;
-
-CAdventureMapInterface::CAdventureMapInterface():
-	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(Point(ADVOPT.infoboxX, ADVOPT.infoboxY))),
-	resdatabar(new CResDataBar),
-	mapAudio(new MapAudioPlayer()),
-	terrain(new MapView(Point(ADVOPT.advmapX, ADVOPT.advmapY), Point(ADVOPT.advmapW, ADVOPT.advmapH))),
-	state(EGameState::NOT_INITIALIZED),
-	spellBeingCasted(nullptr),
-	activeMapPanel(nullptr),
-	scrollingCursorSet(false)
-{
-	pos.x = pos.y = 0;
-	pos.w = GH.screenDimensions().x;
-	pos.h = GH.screenDimensions().y;
-	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
-
-	bg = IImage::createFromFile(ADVOPT.mainGraphic);
-	if(!ADVOPT.worldViewGraphic.empty())
-	{
-		bgWorldView = IImage::createFromFile(ADVOPT.worldViewGraphic);
-	}
-	else
-	{
-		bgWorldView = nullptr;
-		logGlobal->warn("ADVOPT.worldViewGraphic is empty => bitmap not loaded");
-	}
-	if (!bgWorldView)
-	{
-		logGlobal->warn("bgWorldView not defined in resolution config; fallback to VWorld.bmp");
-		bgWorldView = IImage::createFromFile("VWorld.bmp");
-	}
-
-	worldViewIcons = std::make_shared<CAnimation>("VwSymbol");//todo: customize with ADVOPT
-	worldViewIcons->preload();
-
-	for(int g = 0; g < ADVOPT.gemG.size(); ++g)
-	{
-		gems.push_back(std::make_shared<CAnimImage>(ADVOPT.gemG[g], 0, 0, ADVOPT.gemX[g], ADVOPT.gemY[g]));
-	}
-
-	auto makeButton = [&](int textID, std::function<void()> callback, config::ButtonInfo info, EShortcut key) -> std::shared_ptr<CButton>
-	{
-		auto button = std::make_shared<CButton>(Point(info.x, info.y), info.defName, CGI->generaltexth->zelp[textID], callback, key, info.playerColoured);
-		for(auto image : info.additionalDefs)
-			button->addImage(image);
-		return button;
-	};
-
-	kingOverview = makeButton(293, std::bind(&CAdventureMapInterface::fshowOverview,this),     ADVOPT.kingOverview, EShortcut::ADVENTURE_KINGDOM_OVERVIEW);
-	underground  = makeButton(294, std::bind(&CAdventureMapInterface::fswitchLevel,this),      ADVOPT.underground,  EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL);
-	questlog     = makeButton(295, std::bind(&CAdventureMapInterface::fshowQuestlog,this),     ADVOPT.questlog,     EShortcut::ADVENTURE_QUEST_LOG);
-	sleepWake    = makeButton(296, std::bind(&CAdventureMapInterface::fsleepWake,this),        ADVOPT.sleepWake,    EShortcut::ADVENTURE_TOGGLE_SLEEP);
-	moveHero     = makeButton(297, std::bind(&CAdventureMapInterface::fmoveHero,this),         ADVOPT.moveHero,     EShortcut::ADVENTURE_MOVE_HERO);
-	spellbook    = makeButton(298, std::bind(&CAdventureMapInterface::fshowSpellbok,this),     ADVOPT.spellbook,    EShortcut::ADVENTURE_CAST_SPELL);
-	advOptions   = makeButton(299, std::bind(&CAdventureMapInterface::fadventureOPtions,this), ADVOPT.advOptions,   EShortcut::ADVENTURE_GAME_OPTIONS);
-	sysOptions   = makeButton(300, std::bind(&CAdventureMapInterface::fsystemOptions,this),    ADVOPT.sysOptions,   EShortcut::GLOBAL_OPTIONS);
-	nextHero     = makeButton(301, std::bind(&CAdventureMapInterface::fnextHero,this),         ADVOPT.nextHero,     EShortcut::ADVENTURE_NEXT_HERO);
-	endTurn      = makeButton(302, std::bind(&CAdventureMapInterface::fendTurn,this),          ADVOPT.endTurn,      EShortcut::ADVENTURE_END_TURN);
-
-	int panelSpaceBottom = GH.screenDimensions().y - resdatabar->pos.h - 4;
-
-	panelMain = std::make_shared<CAdvMapPanel>(nullptr, Point(0, 0));
-	// TODO correct drawing position
-	panelWorldView = std::make_shared<CAdvMapWorldViewPanel>(worldViewIcons, bgWorldView, Point(heroList->pos.x - 2, 195), panelSpaceBottom, LOCPLINT->playerID);
-
-	panelMain->addChildColorableButton(kingOverview);
-	panelMain->addChildColorableButton(underground);
-	panelMain->addChildColorableButton(questlog);
-	panelMain->addChildColorableButton(sleepWake);
-	panelMain->addChildColorableButton(moveHero);
-	panelMain->addChildColorableButton(spellbook);
-	panelMain->addChildColorableButton(advOptions);
-	panelMain->addChildColorableButton(sysOptions);
-	panelMain->addChildColorableButton(nextHero);
-	panelMain->addChildColorableButton(endTurn);
-
-
-	// TODO move configs to resolutions.json, similarly to previous buttons
-	config::ButtonInfo worldViewBackConfig = config::ButtonInfo();
-	worldViewBackConfig.defName = "IOK6432.DEF";
-	worldViewBackConfig.x = GH.screenDimensions().x - 73;
-	worldViewBackConfig.y = 343 + 195;
-	worldViewBackConfig.playerColoured = false;
-	panelWorldView->addChildToPanel(
-		makeButton(288, std::bind(&CAdventureMapInterface::fworldViewBack,this), worldViewBackConfig, EShortcut::GLOBAL_CANCEL), ACTIVATE | DEACTIVATE);
-
-	config::ButtonInfo worldViewPuzzleConfig = config::ButtonInfo();
-	worldViewPuzzleConfig.defName = "VWPUZ.DEF";
-	worldViewPuzzleConfig.x = GH.screenDimensions().x - 188;
-	worldViewPuzzleConfig.y = 343 + 195;
-	worldViewPuzzleConfig.playerColoured = false;
-	panelWorldView->addChildToPanel( // no help text for this one
-		std::make_shared<CButton>(Point(worldViewPuzzleConfig.x, worldViewPuzzleConfig.y), worldViewPuzzleConfig.defName, std::pair<std::string, std::string>(),
-				std::bind(&CPlayerInterface::showPuzzleMap,LOCPLINT), EShortcut::ADVENTURE_VIEW_PUZZLE, worldViewPuzzleConfig.playerColoured), ACTIVATE | DEACTIVATE);
-
-	config::ButtonInfo worldViewScale1xConfig = config::ButtonInfo();
-	worldViewScale1xConfig.defName = "VWMAG1.DEF";
-	worldViewScale1xConfig.x = GH.screenDimensions().x - 191;
-	worldViewScale1xConfig.y = 23 + 195;
-	worldViewScale1xConfig.playerColoured = false;
-	panelWorldView->addChildToPanel( // help text is wrong for this button
-		makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale1x,this), worldViewScale1xConfig, EShortcut::SELECT_INDEX_1), ACTIVATE | DEACTIVATE);
-
-	config::ButtonInfo worldViewScale2xConfig = config::ButtonInfo();
-	worldViewScale2xConfig.defName = "VWMAG2.DEF";
-	worldViewScale2xConfig.x = GH.screenDimensions().x- 191 + 63;
-	worldViewScale2xConfig.y = 23 + 195;
-	worldViewScale2xConfig.playerColoured = false;
-	panelWorldView->addChildToPanel( // help text is wrong for this button
-		makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale2x,this), worldViewScale2xConfig, EShortcut::SELECT_INDEX_2), ACTIVATE | DEACTIVATE);
-
-	config::ButtonInfo worldViewScale4xConfig = config::ButtonInfo();
-	worldViewScale4xConfig.defName = "VWMAG4.DEF";
-	worldViewScale4xConfig.x = GH.screenDimensions().x- 191 + 126;
-	worldViewScale4xConfig.y = 23 + 195;
-	worldViewScale4xConfig.playerColoured = false;
-	panelWorldView->addChildToPanel( // help text is wrong for this button
-		makeButton(291, std::bind(&CAdventureMapInterface::fworldViewScale4x,this), worldViewScale4xConfig, EShortcut::SELECT_INDEX_4), ACTIVATE | DEACTIVATE);
-
-	config::ButtonInfo worldViewUndergroundConfig = config::ButtonInfo();
-	worldViewUndergroundConfig.defName = "IAM010.DEF";
-	worldViewUndergroundConfig.additionalDefs.push_back("IAM003.DEF");
-	worldViewUndergroundConfig.x = GH.screenDimensions().x - 115;
-	worldViewUndergroundConfig.y = 343 + 195;
-	worldViewUndergroundConfig.playerColoured = true;
-	worldViewUnderground = makeButton(294, std::bind(&CAdventureMapInterface::fswitchLevel,this), worldViewUndergroundConfig, EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL);
-	panelWorldView->addChildColorableButton(worldViewUnderground);
-
-	onCurrentPlayerChanged(LOCPLINT->playerID);
-
-	int iconColorMultiplier = currentPlayerID.getNum() * 19;
-	int wvLeft = heroList->pos.x - 2; // TODO correct drawing position
-	//int wvTop = 195;
-	for (int i = 0; i < 5; ++i)
-	{
-		panelWorldView->addChildIcon(std::pair<int, Point>(i, Point(5, 58 + i * 20)), iconColorMultiplier);
-		panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 263 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
-												Colors::WHITE, CGI->generaltexth->allTexts[612 + i]));
-	}
-	for (int i = 0; i < 7; ++i)
-	{
-		panelWorldView->addChildIcon(std::pair<int, Point>(i +  5, Point(5, 182 + i * 20)), iconColorMultiplier);
-		panelWorldView->addChildIcon(std::pair<int, Point>(i + 12, Point(160, 182 + i * 20)), iconColorMultiplier);
-		panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 387 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
-												Colors::WHITE, CGI->generaltexth->allTexts[619 + i]));
-	}
-	panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft +   5, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
-											Colors::WHITE, CGI->generaltexth->allTexts[617]));
-	panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
-											Colors::WHITE, CGI->generaltexth->allTexts[618]));
-
-	activeMapPanel = panelMain;
-
-	exitWorldView();
-
-	underground->block(!CGI->mh->getMap()->twoLevel);
-	questlog->block(!CGI->mh->getMap()->quests.size());
-	worldViewUnderground->block(!CGI->mh->getMap()->twoLevel);
-}
-
-void CAdventureMapInterface::fshowOverview()
-{
-	GH.pushIntT<CKingdomInterface>();
-}
-
-void CAdventureMapInterface::fworldViewBack()
-{
-	exitWorldView();
-
-	auto hero = LOCPLINT->localState->getCurrentHero();
-	if (hero)
-		centerOnObject(hero);
-}
-
-void CAdventureMapInterface::fworldViewScale1x()
-{
-	// TODO set corresponding scale button to "selected" mode
-	openWorldView(7);
-}
-
-void CAdventureMapInterface::fworldViewScale2x()
-{
-	openWorldView(11);
-}
-
-void CAdventureMapInterface::fworldViewScale4x()
-{
-	openWorldView(16);
-}
-
-void CAdventureMapInterface::fswitchLevel()
-{
-	// with support for future multi-level maps :)
-	int maxLevels = CGI->mh->getMap()->levels();
-	if (maxLevels < 2)
-		return;
-
-	terrain->onMapLevelSwitched();
-}
-
-void CAdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
-{
-	underground->setIndex(mapLevel, true);
-	underground->redraw();
-
-	worldViewUnderground->setIndex(mapLevel, true);
-	worldViewUnderground->redraw();
-
-	minimap->onMapViewMoved(visibleArea, mapLevel);
-}
-
-void CAdventureMapInterface::onAudioResumed()
-{
-	mapAudio->onAudioResumed();
-}
-
-void CAdventureMapInterface::onAudioPaused()
-{
-	mapAudio->onAudioPaused();
-}
-
-void CAdventureMapInterface::fshowQuestlog()
-{
-	LOCPLINT->showQuestLog();
-}
-
-void CAdventureMapInterface::fsleepWake()
-{
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
-	if (!h)
-		return;
-	bool newSleep = !LOCPLINT->localState->isHeroSleeping(h);
-
-	if (newSleep)
-		LOCPLINT->localState->setHeroAsleep(h);
-	else
-		LOCPLINT->localState->setHeroAwaken(h);
-
-	onHeroChanged(h);
-
-	if (newSleep)
-		fnextHero();
-
-	// redraw to update the image of sleep/wake button
-	panelMain->redraw();
-}
-
-void CAdventureMapInterface::fmoveHero()
-{
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
-	if (!h || !LOCPLINT->localState->hasPath(h) || CGI->mh->hasOngoingAnimations())
-		return;
-
-	LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
-}
-
-void CAdventureMapInterface::fshowSpellbok()
-{
-	if (!LOCPLINT->localState->getCurrentHero()) //checking necessary values
-		return;
-
-	centerOnObject(LOCPLINT->localState->getCurrentHero());
-
-	GH.pushIntT<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
-}
-
-void CAdventureMapInterface::fadventureOPtions()
-{
-	GH.pushIntT<CAdventureOptions>();
-}
-
-void CAdventureMapInterface::fsystemOptions()
-{
-	GH.pushIntT<SettingsMainWindow>();
-}
-
-void CAdventureMapInterface::fnextHero()
-{
-	const auto * currHero = LOCPLINT->localState->getCurrentHero();
-	const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
-
-	if (nextHero)
-	{
-		LOCPLINT->localState->setSelection(nextHero);
-		centerOnObject(nextHero);
-	}
-}
-
-void CAdventureMapInterface::fendTurn()
-{
-	if(!LOCPLINT->makingTurn)
-		return;
-
-	if(settings["adventure"]["heroReminder"].Bool())
-	{
-		for(auto hero : LOCPLINT->localState->getWanderingHeroes())
-		{
-			if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
-			{
-				// 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
-				LOCPLINT->localState->verifyPath(hero);
-
-				if(!LOCPLINT->localState->hasPath(hero))
-				{
-					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
-					return;
-				}
-
-				auto path = LOCPLINT->localState->getPath(hero);
-				if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
-				{
-					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
-					return;
-				}
-			}
-		}
-	}
-	endingTurn();
-}
-
-void CAdventureMapInterface::updateButtons()
-{
-	const auto * hero = LOCPLINT->localState->getCurrentHero();
-
-	sleepWake->block(!hero);
-	spellbook->block(!hero);
-	moveHero->block(!hero || !LOCPLINT->localState->hasPath(hero) || hero->movement == 0);
-
-	const auto * nextSuitableHero = LOCPLINT->localState->getNextWanderingHero(hero);
-	nextHero->block(nextSuitableHero == nullptr);
-
-	if(hero)
-	{
-		bool state = LOCPLINT->localState->isHeroSleeping(hero);
-		sleepWake->setIndex(state ? 1 : 0, true);
-		sleepWake->redraw();
-	}
-}
-
-void CAdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
-{
-	infoBar->popAll();
-	infoBar->showSelection();
-}
-
-void CAdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
-{
-	heroList->update(h);
-
-	if (h && h == LOCPLINT->localState->getCurrentHero() && !infoBar->showingComponents())
-		infoBar->showSelection();
-
-	updateButtons();
-}
-
-void CAdventureMapInterface::onTownChanged(const CGTownInstance * town)
-{
-	townList->update(town);
-
-	if (town && town == LOCPLINT->localState->getCurrentTown() && !infoBar->showingComponents())
-		infoBar->showSelection();
-}
-
-void CAdventureMapInterface::showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer)
-{
-	infoBar->pushComponents(components, message, timer);
-}
-
-void CAdventureMapInterface::activate()
-{
-	CIntObject::activate();
-	if (!(active & KEYBOARD))
-		CIntObject::activate(KEYBOARD);
-
-	screenBuf = screen;
-	GH.statusbar = statusbar;
-	
-	if(LOCPLINT)
-	{
-		LOCPLINT->cingconsole->activate();
-		LOCPLINT->cingconsole->pos = this->pos;
-	}
-
-	if(state != EGameState::ENEMY_TURN && state != EGameState::HOTSEAT_WAIT)
-	{
-		assert(state == EGameState::MAKING_TURN);
-
-		activeMapPanel->activate();
-		if (state == EGameState::MAKING_TURN)
-		{
-			heroList->activate();
-			townList->activate();
-			infoBar->activate();
-		}
-		minimap->activate();
-		terrain->activate();
-		statusbar->activate();
-
-		GH.fakeMouseMove(); //to restore the cursor
-	}
-}
-
-void CAdventureMapInterface::deactivate()
-{
-	CIntObject::deactivate();
-
-	if(state != EGameState::ENEMY_TURN && state != EGameState::HOTSEAT_WAIT)
-	{
-		assert(state == EGameState::MAKING_TURN);
-
-		CCS->curh->set(Cursor::Map::POINTER);
-		activeMapPanel->deactivate();
-		if (state == EGameState::MAKING_TURN)
-		{
-			heroList->deactivate();
-			townList->deactivate();
-			infoBar->deactivate();
-		}
-		minimap->deactivate();
-		terrain->deactivate();
-		statusbar->deactivate();
-	}
-}
-
-void CAdventureMapInterface::showAll(SDL_Surface * to)
-{
-	bg->draw(to, 0, 0);
-
-//	if(state != EGameState::MAKING_TURN)
-//		return;
-
-	heroList->showAll(to);
-	townList->showAll(to);
-	infoBar->showAll(to);
-
-	activeMapPanel->showAll(to);
-
-	minimap->showAll(to);
-	terrain->showAll(to);
-	show(to);
-
-	resdatabar->showAll(to);
-	statusbar->show(to);
-	LOCPLINT->cingconsole->show(to);
-}
-
-void CAdventureMapInterface::show(SDL_Surface * to)
-{
-//	if(state != EGameState::MAKING_TURN)
-//		return;
-
-	handleMapScrollingUpdate();
-
-	for(int i = 0; i < 4; i++)
-	{
-		if(settings["session"]["spectate"].Bool())
-			gems[i]->setFrame(PlayerColor(1).getNum());
-		else
-			gems[i]->setFrame(LOCPLINT->playerID.getNum());
-	}
-
-	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);
-}
-
-void CAdventureMapInterface::handleMapScrollingUpdate()
-{
-	/// Width of window border, in pixels, that triggers map scrolling
-	static constexpr uint32_t borderScrollWidth = 15;
-
-	uint32_t timePassed = GH.mainFPSmng->getElapsedMilliseconds();
-	uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
-	uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000;
-
-	bool scrollingActive = !GH.isKeyboardCtrlDown() && isActive() && state == EGameState::MAKING_TURN;
-
-	Point cursorPosition = GH.getCursorPosition();
-	Point scrollDirection;
-
-	if (cursorPosition.x < borderScrollWidth)
-		scrollDirection.x = -1;
-
-	if (cursorPosition.x > GH.screenDimensions().x - borderScrollWidth)
-		scrollDirection.x = +1;
-
-	if (cursorPosition.y < borderScrollWidth)
-		scrollDirection.y = -1;
-
-	if (cursorPosition.y > GH.screenDimensions().y - borderScrollWidth)
-		scrollDirection.y = +1;
-
-	Point scrollDelta = scrollDirection * scrollDistance;
-
-	if (scrollingActive && scrollDelta != Point(0,0))
-		terrain->onMapScrolled(scrollDelta);
-
-	if (scrollDelta == Point(0,0) && !scrollingCursorSet)
-		return;
-
-	if(scrollDelta.x > 0)
-	{
-		if(scrollDelta.y < 0)
-			CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
-		if(scrollDelta.y > 0)
-			CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
-		if(scrollDelta.y == 0)
-			CCS->curh->set(Cursor::Map::SCROLL_EAST);
-	}
-	if(scrollDelta.x < 0)
-	{
-		if(scrollDelta.y < 0)
-			CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
-		if(scrollDelta.y > 0)
-			CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
-		if(scrollDelta.y == 0)
-			CCS->curh->set(Cursor::Map::SCROLL_WEST);
-	}
-
-	if (scrollDelta.x == 0)
-	{
-		if(scrollDelta.y < 0)
-			CCS->curh->set(Cursor::Map::SCROLL_NORTH);
-		if(scrollDelta.y > 0)
-			CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
-		if(scrollDelta.y == 0)
-			CCS->curh->set(Cursor::Map::POINTER);
-	}
-
-	scrollingCursorSet = scrollDelta != Point(0,0);
-}
-
-void CAdventureMapInterface::centerOnTile(int3 on)
-{
-	terrain->onCenteredTile(on);
-}
-
-void CAdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
-{
-	terrain->onCenteredObject(obj);
-}
-
-void CAdventureMapInterface::keyPressed(EShortcut key)
-{
-	if (state != EGameState::MAKING_TURN)
-		return;
-
-	//fake mouse use to trigger onTileHovered()
-	GH.fakeMouseMove();
-
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
-	const CGTownInstance *t = LOCPLINT->localState->getCurrentTown(); //selected town
-
-	switch(key)
-	{
-	case EShortcut::ADVENTURE_THIEVES_GUILD:
-		if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
-			return;
-
-		{
-			//find first town with tavern
-			auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
-			{
-				return town->hasBuilt(BuildingID::TAVERN);
-			});
-
-			if(itr != LOCPLINT->localState->getOwnedTowns().end())
-				LOCPLINT->showThievesGuildWindow(*itr);
-			else
-				LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
-		}
-		return;
-	case EShortcut::ADVENTURE_VIEW_SCENARIO:
-		if(isActive())
-			CAdventureOptions::showScenarioInfo();
-		return;
-	case EShortcut::GAME_SAVE_GAME:
-		if(isActive())
-			GH.pushIntT<CSavingScreen>();
-		return;
-	case EShortcut::GAME_LOAD_GAME:
-		if(isActive())
-			LOCPLINT->proposeLoadingGame();
-		return;
-	case EShortcut::ADVENTURE_DIG_GRAIL:
-		{
-			if(h && isActive() && LOCPLINT->makingTurn)
-				LOCPLINT->tryDiggging(h);
-			return;
-		}
-	case EShortcut::ADVENTURE_VIEW_PUZZLE:
-		if(isActive())
-			LOCPLINT->showPuzzleMap();
-		return;
-	case EShortcut::ADVENTURE_VIEW_WORLD:
-		if(isActive())
-			LOCPLINT->viewWorldMap();
-		return;
-	case EShortcut::GAME_RESTART_GAME:
-		if(isActive() && GH.isKeyboardCtrlDown())
-		{
-			LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
-				[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
-		}
-		return;
-	case EShortcut::ADVENTURE_VISIT_OBJECT: //space - try to revisit current object with selected hero
-		{
-			if(!isActive())
-				return;
-			if(h)
-			{
-				LOCPLINT->cb->moveHero(h,h->pos);
-			}
-		}
-		return;
-	case EShortcut::ADVENTURE_VIEW_SELECTED:
-		{
-			if(!isActive() || !LOCPLINT->localState->getCurrentArmy())
-				return;
-			if(h)
-				LOCPLINT->openHeroWindow(h);
-			else if(t)
-				LOCPLINT->openTownWindow(t);
-			return;
-		}
-	case EShortcut::GLOBAL_CANCEL:
-		{
-			//FIXME: this case is never executed since AdvMapInt is disabled while in spellcasting mode
-			if(!isActive() || GH.topInt().get() != this || !spellBeingCasted)
-				return;
-
-			abortCastingMode();
-			return;
-		}
-	case EShortcut::GAME_OPEN_MARKETPLACE:
-		{
-			//act on key down if marketplace windows is not already opened
-			if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
-				return;
-
-			if(GH.isKeyboardCtrlDown()) //CTRL + T => open marketplace
-			{
-				//check if we have any marketplace
-				const CGTownInstance *townWithMarket = nullptr;
-				for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
-				{
-					if(t->hasBuilt(BuildingID::MARKETPLACE))
-					{
-						townWithMarket = t;
-						break;
-					}
-				}
-
-				if(townWithMarket) //if any town has marketplace, open window
-					GH.pushIntT<CMarketplaceWindow>(townWithMarket);
-				else //if not - complain
-					LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
-			}
-	case EShortcut::ADVENTURE_NEXT_TOWN:
-			if(isActive() && !GH.isKeyboardCtrlDown()) //no ctrl, advmapint is on the top => switch to town
-			{
-				townList->selectNext();
-			}
-			return;
-		}
-	case EShortcut::ADVENTURE_MOVE_HERO_SW: return hotkeyMoveHeroDirectional({-1, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_SS: return hotkeyMoveHeroDirectional({ 0, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_SE: return hotkeyMoveHeroDirectional({+1, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_WW: return hotkeyMoveHeroDirectional({-1,  0});
-	case EShortcut::ADVENTURE_MOVE_HERO_EE: return hotkeyMoveHeroDirectional({+1,  0});
-	case EShortcut::ADVENTURE_MOVE_HERO_NW: return hotkeyMoveHeroDirectional({-1, -1});
-	case EShortcut::ADVENTURE_MOVE_HERO_NN: return hotkeyMoveHeroDirectional({ 0, -1});
-	case EShortcut::ADVENTURE_MOVE_HERO_NE: return hotkeyMoveHeroDirectional({+1, -1});
-	}
-}
-
-void CAdventureMapInterface::hotkeyMoveHeroDirectional(Point direction)
-{
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
-
-	if(!h || !isActive())
-		return;
-
-	if (CGI->mh->hasOngoingAnimations())
-		return;
-
-	if(direction == Point(0,0))
-	{
-		centerOnObject(h);
-		return;
-	}
-
-	int3 dst = h->visitablePos() + int3(direction.x, direction.y, 0);
-
-	if (!CGI->mh->isInMap((dst)))
-		return;
-
-	if ( !LOCPLINT->localState->setPath(h, dst))
-		return;
-
-	const CGPath & path = LOCPLINT->localState->getPath(h);
-
-	if (path.nodes.size() > 2)
-		onHeroChanged(h);
-	else
-		if(!path.nodes[0].turns)
-			LOCPLINT->moveHero(h, path);
-}
-
-void CAdventureMapInterface::onSelectionChanged(const CArmedInstance *sel)
-{
-	assert(sel);
-
-	infoBar->popAll();
-	mapAudio->onSelectionChanged(sel);
-	bool centerView = !settings["session"]["autoSkip"].Bool();
-
-	if (centerView)
-		centerOnObject(sel);
-
-	if(sel->ID==Obj::TOWN)
-	{
-		auto town = dynamic_cast<const CGTownInstance*>(sel);
-
-		infoBar->showTownSelection(town);
-		townList->select(town);
-		heroList->select(nullptr);
-		onHeroChanged(nullptr);
-	}
-	else //hero selected
-	{
-		auto hero = dynamic_cast<const CGHeroInstance*>(sel);
-
-		infoBar->showHeroSelection(hero);
-		heroList->select(hero);
-		townList->select(nullptr);
-
-		LOCPLINT->localState->verifyPath(hero);
-		onHeroChanged(hero);
-	}
-	updateButtons();
-	townList->redraw();
-	heroList->redraw();
-}
-
-bool CAdventureMapInterface::isActive()
-{
-	return active & ~CIntObject::KEYBOARD;
-}
-
-void CAdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
-{
-	if (positions)
-		minimap->updateTiles(*positions);
-	else
-		minimap->update();
-}
-
-void CAdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
-{
-	onCurrentPlayerChanged(playerID);
-	state = EGameState::HOTSEAT_WAIT;
-}
-
-void CAdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
-{
-	if(settings["session"]["spectate"].Bool())
-		return;
-
-	adjustActiveness(true);
-	mapAudio->onEnemyTurnStarted();
-	minimap->setAIRadar(true);
-	infoBar->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
-	minimap->showAll(screen);//force refresh on inactive object
-	infoBar->showAll(screen);//force refresh on inactive object
-}
-
-void CAdventureMapInterface::adjustActiveness(bool aiTurnStart)
-{
-	bool wasActive = isActive();
-
-	if(wasActive)
-		deactivate();
-
-	if (aiTurnStart)
-		state = EGameState::ENEMY_TURN;
-	else
-		state = EGameState::MAKING_TURN;
-
-	if(wasActive)
-		activate();
-}
-
-void CAdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
-{
-	LOCPLINT->localState->setSelection(nullptr);
-
-	if (playerID == currentPlayerID)
-		return;
-
-	currentPlayerID = playerID;
-	bg->playerColored(currentPlayerID);
-
-	panelMain->setPlayerColor(currentPlayerID);
-	panelWorldView->setPlayerColor(currentPlayerID);
-	panelWorldView->recolorIcons(currentPlayerID, currentPlayerID.getNum() * 19);
-	resdatabar->colorize(currentPlayerID);
-}
-
-void CAdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
-{
-	onCurrentPlayerChanged(playerID);
-
-	state = EGameState::MAKING_TURN;
-	if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
-		|| settings["session"]["spectate"].Bool())
-	{
-		adjustActiveness(false);
-		minimap->setAIRadar(false);
-		infoBar->showSelection();
-	}
-
-	heroList->update();
-	townList->update();
-
-	const CGHeroInstance * heroToSelect = nullptr;
-
-	// find first non-sleeping hero
-	for (auto hero : LOCPLINT->localState->getWanderingHeroes())
-	{
-		if (!LOCPLINT->localState->isHeroSleeping(hero))
-		{
-			heroToSelect = hero;
-			break;
-		}
-	}
-
-	//select first hero if available.
-	if (heroToSelect != nullptr)
-	{
-		LOCPLINT->localState->setSelection(heroToSelect);
-	}
-	else if (LOCPLINT->localState->getOwnedTowns().size())
-	{
-		LOCPLINT->localState->setSelection(LOCPLINT->localState->getOwnedTown(0));
-	}
-	else
-	{
-		LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
-	}
-
-	//show new day animation and sound on infobar
-	infoBar->showDate();
-
-	onHeroChanged(nullptr);
-	showAll(screen);
-	mapAudio->onPlayerTurnStarted();
-
-	if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
-	{
-		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
-			iw->close();
-
-		endingTurn();
-	}
-}
-
-void CAdventureMapInterface::endingTurn()
-{
-	if(settings["session"]["spectate"].Bool())
-		return;
-
-	LOCPLINT->makingTurn = false;
-	LOCPLINT->cb->endTurn();
-	mapAudio->onPlayerTurnEnded();
-}
-
-const CGObjectInstance* CAdventureMapInterface::getActiveObject(const int3 &mapPos)
-{
-	std::vector < const CGObjectInstance * > bobjs = LOCPLINT->cb->getBlockingObjs(mapPos);  //blocking objects at tile
-
-	if (bobjs.empty())
-		return nullptr;
-
-	return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
-/*
-	if (bobjs.back()->ID == Obj::HERO)
-		return bobjs.back();
-	else
-		return bobjs.front();*/
-}
-
-void CAdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
-{
-	if(state != EGameState::MAKING_TURN)
-		return;
-
-	//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);
-
-	const CGObjectInstance *topBlocking = getActiveObject(mapPos);
-
-	int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
-	if(spellBeingCasted)
-	{
-		if (!isInScreenRange(selPos, mapPos))
-			return;
-
-		const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
-
-		switch(spellBeingCasted->id)
-		{
-		case SpellID::SCUTTLE_BOAT: //Scuttle Boat
-			if(topBlocking && topBlocking->ID == Obj::BOAT)
-				leaveCastingMode(mapPos);
-			break;
-		case SpellID::DIMENSION_DOOR:
-			if(!tile || tile->isClear(heroTile))
-				leaveCastingMode(mapPos);
-			break;
-		}
-		return;
-	}
-	//check if we can select this object
-	bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
-	canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
-
-	bool isHero = false;
-	if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town)
-	{
-		if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked
-			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
-		else if(canSelect)
-			LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
-	}
-	else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected
-	{
-		isHero = true;
-
-		const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
-		if(currentHero == topBlocking) //clicked selected hero
-		{
-			LOCPLINT->openHeroWindow(currentHero);
-			return;
-		}
-		else if(canSelect && pn->turns == 255 ) //selectable object at inaccessible tile
-		{
-			LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
-			return;
-		}
-		else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
-		{
-			if(LOCPLINT->localState->hasPath(currentHero) &&
-			   LOCPLINT->localState->getPath(currentHero).endPos() == mapPos)//we'll be moving
-			{
-				if(!CGI->mh->hasOngoingAnimations())
-					LOCPLINT->moveHero(currentHero, LOCPLINT->localState->getPath(currentHero));
-				return;
-			}
-			else //remove old path and find a new one if we clicked on accessible tile
-			{
-				LOCPLINT->localState->setPath(currentHero, mapPos);
-				onHeroChanged(currentHero);
-			}
-		}
-	} //end of hero is selected "case"
-	else
-	{
-		throw std::runtime_error("Nothing is selected...");
-	}
-
-	const auto shipyard = ourInaccessibleShipyard(topBlocking);
-	if(isHero && shipyard != nullptr)
-	{
-		LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
-	}
-}
-
-void CAdventureMapInterface::onTileHovered(const int3 &mapPos)
-{
-	if(state != EGameState::MAKING_TURN)
-		return;
-
-	//may occur just at the start of game (fake move before full intiialization)
-	if(!LOCPLINT->localState->getCurrentArmy())
-		return;
-
-	if(!LOCPLINT->cb->isVisible(mapPos))
-	{
-		CCS->curh->set(Cursor::Map::POINTER);
-		statusbar->clear();
-		return;
-	}
-	auto objRelations = PlayerRelations::ALLIES;
-	const CGObjectInstance *objAtTile = getActiveObject(mapPos);
-	if(objAtTile)
-	{
-		objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
-		std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
-		boost::replace_all(text,"\n"," ");
-		statusbar->write(text);
-	}
-	else
-	{
-		std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
-		statusbar->write(hlp);
-	}
-
-	if(spellBeingCasted)
-	{
-		switch(spellBeingCasted->id)
-		{
-		case SpellID::SCUTTLE_BOAT:
-			{
-			int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
-
-			if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos))
-				CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
-			else
-				CCS->curh->set(Cursor::Map::POINTER);
-			return;
-			}
-		case SpellID::DIMENSION_DOOR:
-			{
-				const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
-				int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
-				if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
-					CCS->curh->set(Cursor::Map::TELEPORT);
-				else
-					CCS->curh->set(Cursor::Map::POINTER);
-				return;
-			}
-		}
-	}
-
-	if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN)
-	{
-		if(objAtTile)
-		{
-			if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
-				CCS->curh->set(Cursor::Map::TOWN);
-			else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
-				CCS->curh->set(Cursor::Map::HERO);
-			else
-				CCS->curh->set(Cursor::Map::POINTER);
-		}
-		else
-			CCS->curh->set(Cursor::Map::POINTER);
-	}
-	else if(const CGHeroInstance * hero = LOCPLINT->localState->getCurrentHero())
-	{
-		std::array<Cursor::Map, 4> cursorMove      = { Cursor::Map::T1_MOVE,       Cursor::Map::T2_MOVE,       Cursor::Map::T3_MOVE,       Cursor::Map::T4_MOVE,       };
-		std::array<Cursor::Map, 4> cursorAttack    = { Cursor::Map::T1_ATTACK,     Cursor::Map::T2_ATTACK,     Cursor::Map::T3_ATTACK,     Cursor::Map::T4_ATTACK,     };
-		std::array<Cursor::Map, 4> cursorSail      = { Cursor::Map::T1_SAIL,       Cursor::Map::T2_SAIL,       Cursor::Map::T3_SAIL,       Cursor::Map::T4_SAIL,       };
-		std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK,  Cursor::Map::T2_DISEMBARK,  Cursor::Map::T3_DISEMBARK,  Cursor::Map::T4_DISEMBARK,  };
-		std::array<Cursor::Map, 4> cursorExchange  = { Cursor::Map::T1_EXCHANGE,   Cursor::Map::T2_EXCHANGE,   Cursor::Map::T3_EXCHANGE,   Cursor::Map::T4_EXCHANGE,   };
-		std::array<Cursor::Map, 4> cursorVisit     = { Cursor::Map::T1_VISIT,      Cursor::Map::T2_VISIT,      Cursor::Map::T3_VISIT,      Cursor::Map::T4_VISIT,      };
-		std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
-
-		const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
-		assert(pathNode);
-
-		if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info
-		{
-			showMoveDetailsInStatusbar(*hero, *pathNode);
-		}
-
-		int turns = pathNode->turns;
-		vstd::amin(turns, 3);
-		switch(pathNode->action)
-		{
-		case CGPathNode::NORMAL:
-		case CGPathNode::TELEPORT_NORMAL:
-			if(pathNode->layer == EPathfindingLayer::LAND)
-				CCS->curh->set(cursorMove[turns]);
-			else
-				CCS->curh->set(cursorSailVisit[turns]);
-			break;
-
-		case CGPathNode::VISIT:
-		case CGPathNode::BLOCKING_VISIT:
-		case CGPathNode::TELEPORT_BLOCKING_VISIT:
-			if(objAtTile && objAtTile->ID == Obj::HERO)
-			{
-				if(LOCPLINT->localState->getCurrentArmy()  == objAtTile)
-					CCS->curh->set(Cursor::Map::HERO);
-				else
-					CCS->curh->set(cursorExchange[turns]);
-			}
-			else if(pathNode->layer == EPathfindingLayer::LAND)
-				CCS->curh->set(cursorVisit[turns]);
-			else
-				CCS->curh->set(cursorSailVisit[turns]);
-			break;
-
-		case CGPathNode::BATTLE:
-		case CGPathNode::TELEPORT_BATTLE:
-			CCS->curh->set(cursorAttack[turns]);
-			break;
-
-		case CGPathNode::EMBARK:
-			CCS->curh->set(cursorSail[turns]);
-			break;
-
-		case CGPathNode::DISEMBARK:
-			CCS->curh->set(cursorDisembark[turns]);
-			break;
-
-		default:
-			if(objAtTile && objRelations != PlayerRelations::ENEMIES)
-			{
-				if(objAtTile->ID == Obj::TOWN)
-					CCS->curh->set(Cursor::Map::TOWN);
-				else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
-					CCS->curh->set(Cursor::Map::HERO);
-				else
-					CCS->curh->set(Cursor::Map::POINTER);
-			}
-			else
-				CCS->curh->set(Cursor::Map::POINTER);
-			break;
-		}
-	}
-
-	if(ourInaccessibleShipyard(objAtTile))
-	{
-		CCS->curh->set(Cursor::Map::T1_SAIL);
-	}
-}
-
-void CAdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
-{
-	const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
-	const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
-	const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
-
-	std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
-
-	boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
-	boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
-	boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
-
-	statusbar->write(result);
-}
-
-void CAdventureMapInterface::onTileRightClicked(const int3 &mapPos)
-{
-	if(state != EGameState::MAKING_TURN)
-		return;
-
-	if(spellBeingCasted)
-	{
-		abortCastingMode();
-		return;
-	}
-
-	if(!LOCPLINT->cb->isVisible(mapPos))
-	{
-		CRClickPopup::createAndPush(VLC->generaltexth->allTexts[61]); //Uncharted Territory
-		return;
-	}
-
-	const CGObjectInstance * obj = getActiveObject(mapPos);
-	if(!obj)
-	{
-		// Bare or undiscovered terrain
-		const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
-		if(tile)
-		{
-			std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
-			CRClickPopup::createAndPush(hlp);
-		}
-		return;
-	}
-
-	CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
-}
-
-void CAdventureMapInterface::enterCastingMode(const CSpell * sp)
-{
-	assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR);
-	spellBeingCasted = sp;
-	Settings config = settings.write["session"]["showSpellRange"];
-	config->Bool() = true;
-
-	deactivate();
-	terrain->activate();
-	GH.fakeMouseMove();
-}
-
-void CAdventureMapInterface::exitCastingMode()
-{
-	assert(spellBeingCasted);
-	spellBeingCasted = nullptr;
-	terrain->deactivate();
-	activate();
-
-	Settings config = settings.write["session"]["showSpellRange"];
-	config->Bool() = false;
-}
-
-void CAdventureMapInterface::abortCastingMode()
-{
-	exitCastingMode();
-	LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
-}
-
-void CAdventureMapInterface::leaveCastingMode(const int3 & dest)
-{
-	SpellID id = spellBeingCasted->id;
-	exitCastingMode();
-	LOCPLINT->cb->castSpell(LOCPLINT->localState->getCurrentHero(), id, dest);
-}
-
-Rect CAdventureMapInterface::terrainAreaPixels() const
-{
-	return terrain->pos;
-}
-
-const IShipyard * CAdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
-{
-	const IShipyard *ret = IShipyard::castFrom(obj);
-
-	if(!ret ||
-		obj->tempOwner != currentPlayerID ||
-		(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
-		return nullptr;
-
-	return ret;
-}
-
-void CAdventureMapInterface::exitWorldView()
-{
-	state = EGameState::MAKING_TURN;
-
-	panelMain->activate();
-	panelWorldView->deactivate();
-	activeMapPanel = panelMain;
-
-	townList->activate();
-	heroList->activate();
-	infoBar->activate();
-
-	redraw();
-	terrain->onViewMapActivated();
-}
-
-void CAdventureMapInterface::openWorldView(int tileSize)
-{
-	state = EGameState::WORLD_VIEW;
-	panelMain->deactivate();
-	panelWorldView->activate();
-
-	activeMapPanel = panelWorldView;
-
-	townList->deactivate();
-	heroList->deactivate();
-	infoBar->showSelection(); // to prevent new day animation interfering world view mode
-	infoBar->deactivate();
-
-	redraw();
-	terrain->onViewWorldActivated(tileSize);
-}
-
-void CAdventureMapInterface::openWorldView()
-{
-	openWorldView(11);
-}
-
-void CAdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
-{
-	openWorldView(11);
-	terrain->onViewSpellActivated(11, objectPositions, showTerrain);
-}

+ 11 - 16
client/adventureMap/CInGameConsole.cpp

@@ -19,6 +19,8 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../render/Colors.h"
+#include "../adventureMap/AdventureMapInterface.h"
+#include "../windows/CMessage.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
@@ -83,24 +85,17 @@ void CInGameConsole::print(const std::string & txt)
 	// boost::unique_lock scope
 	{
 		boost::unique_lock<boost::mutex> lock(texts_mx);
-		int lineLen = conf.go()->ac.outputLineLength;
 
-		if(txt.size() < lineLen)
-		{
-			texts.push_back({txt, 0});
-		}
-		else
-		{
-			assert(lineLen);
-			for(int g = 0; g < txt.size() / lineLen + 1; ++g)
-			{
-				std::string part = txt.substr(g * lineLen, lineLen);
-				if(part.empty())
-					break;
+		// Maximum width for a text line is limited by:
+		// 1) width of adventure map terrain area, for when in-game console is on top of advmap
+		// 2) width of castle/battle window (fixed to 800) when this window is open
+		// 3) arbitrary selected left and right margins
+		int maxWidth = std::min( 800, adventureInt->terrainAreaPixels().w) - 100;
 
-				texts.push_back({part, 0});
-			}
-		}
+		auto splitText = CMessage::breakText(txt, maxWidth, FONT_MEDIUM);
+
+		for (auto const & entry : splitText)
+			texts.push_back({entry, 0});
 
 		while(texts.size() > maxDisplayedTexts)
 			texts.erase(texts.begin());

+ 1 - 1
client/adventureMap/CInfoBar.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "CInfoBar.h"
 
-#include "CAdventureMapInterface.h"
+#include "AdventureMapInterface.h"
 
 #include "../widgets/CComponent.h"
 #include "../widgets/Images.h"

+ 45 - 18
client/adventureMap/CList.cpp

@@ -11,15 +11,17 @@
 #include "StdInc.h"
 #include "CList.h"
 
-#include "CAdventureMapInterface.h"
+#include "AdventureMapInterface.h"
 
 #include "../widgets/Images.h"
 #include "../widgets/Buttons.h"
+#include "../widgets/ObjectLists.h"
 #include "../windows/InfoWindows.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../PlayerLocalState.h"
 #include "../gui/CGuiHandler.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
@@ -81,24 +83,44 @@ void CList::CListItem::onSelect(bool on)
 	redraw();
 }
 
-CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create)
-	: CIntObject(0, position),
+CList::CList(int Size, Rect widgetDimensions)
+	: CIntObject(0, widgetDimensions.topLeft()),
 	size(Size),
 	selected(nullptr)
+{
+	pos.w = widgetDimensions.w;
+	pos.h = widgetDimensions.h;
+}
+
+void CList::showAll(SDL_Surface * to)
+{
+	CSDL_Ext::fillRect(to, pos, Colors::BLACK);
+	CIntObject::showAll(to);
+}
+
+void CList::createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	scrollUp = std::make_shared<CButton>(Point(0, 0), btnUp, CGI->generaltexth->zelp[helpUp]);
-	scrollDown = std::make_shared<CButton>(Point(0, scrollUp->pos.h + 32*(int)size), btnDown, CGI->generaltexth->zelp[helpDown]);
+	listBox = std::make_shared<CListBox>(std::bind(&CList::createItem, this, _1), firstItemPosition, itemPositionDelta, size, listAmount);
+}
 
-	listBox = std::make_shared<CListBox>(create, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount);
+void CList::setScrollUpButton(std::shared_ptr<CButton> button)
+{
+	addChild(button.get());
 
-	//assign callback only after list was created
+	scrollUp = button;
 	scrollUp->addCallback(std::bind(&CListBox::moveToPrev, listBox));
-	scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
-
 	scrollUp->addCallback(std::bind(&CList::update, this));
-	scrollDown->addCallback(std::bind(&CList::update, this));
+	update();
+}
 
+void CList::setScrollDownButton(std::shared_ptr<CButton> button)
+{
+	addChild(button.get());
+
+	scrollDown = button;
+	scrollDown->addCallback(std::bind(&CList::update, this));
+	scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
 	update();
 }
 
@@ -107,8 +129,11 @@ void CList::update()
 	bool onTop = listBox->getPos() == 0;
 	bool onBottom = listBox->getPos() + size >= listBox->size();
 
-	scrollUp->block(onTop);
-	scrollDown->block(onBottom);
+	if (scrollUp)
+		scrollUp->block(onTop);
+
+	if (scrollDown)
+		scrollDown->block(onBottom);
 }
 
 void CList::select(std::shared_ptr<CListItem> which)
@@ -223,16 +248,17 @@ std::string CHeroList::CHeroItem::getHoverText()
 	return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated());
 }
 
-std::shared_ptr<CIntObject> CHeroList::createHeroItem(size_t index)
+std::shared_ptr<CIntObject> CHeroList::createItem(size_t index)
 {
 	if (LOCPLINT->localState->getWanderingHeroes().size() > index)
 		return std::make_shared<CHeroItem>(this, LOCPLINT->localState->getWanderingHero(index));
 	return std::make_shared<CEmptyHeroItem>();
 }
 
-CHeroList::CHeroList(int size, Point position, std::string btnUp, std::string btnDown):
-	CList(size, position, btnUp, btnDown, LOCPLINT->localState->getWanderingHeroes().size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
+CHeroList::CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
+	: CList(visibleItemsCount, widgetPosition)
 {
+	createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
 }
 
 void CHeroList::select(const CGHeroInstance * hero)
@@ -261,7 +287,7 @@ void CHeroList::update(const CGHeroInstance * hero)
 	CList::update();
 }
 
-std::shared_ptr<CIntObject> CTownList::createTownItem(size_t index)
+std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
 {
 	if (LOCPLINT->localState->getOwnedTowns().size() > index)
 		return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
@@ -312,9 +338,10 @@ std::string CTownList::CTownItem::getHoverText()
 	return town->getObjectName();
 }
 
-CTownList::CTownList(int size, Point position, std::string btnUp, std::string btnDown):
-	CList(size, position, btnUp, btnDown, LOCPLINT->localState->getOwnedTowns().size(),  306, 307, std::bind(&CTownList::createTownItem, this, _1))
+CTownList::CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
+	: CList(visibleItemsCount, widgetPosition)
 {
+	createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
 }
 
 void CTownList::select(const CGTownInstance * town)

+ 23 - 31
client/adventureMap/CList.h

@@ -10,8 +10,6 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
-
-#include "../widgets/ObjectLists.h"
 #include "../../lib/FunctionList.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -21,7 +19,9 @@ class CGTownInstance;
 
 VCMI_LIB_NAMESPACE_END
 
+class CListBox;
 class CButton;
+class CAnimImage;
 
 /// Base UI Element for hero\town lists
 class CList : public CIntObject
@@ -53,23 +53,9 @@ protected:
 		virtual std::string getHoverText()=0;
 	};
 
-	std::shared_ptr<CListBox> listBox;
+private:
 	const size_t size;
 
-	/**
-	 * @brief CList - protected constructor
-	 * @param size - maximal amount of visible at once items
-	 * @param position - cordinates
-	 * @param btnUp - path to image to use as top button
-	 * @param btnDown - path to image to use as bottom button
-	 * @param listAmount - amount of items in the list
-	 * @param helpUp - index in zelp.txt for button help tooltip
-	 * @param helpDown - index in zelp.txt for button help tooltip
-	 * @param create - function for creating items in listbox
-	 * @param destroy - function for deleting items in listbox
-	 */
-	CList(int size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create);
-
 	//for selection\deselection
 	std::shared_ptr<CListItem> selected;
 	void select(std::shared_ptr<CListItem> which);
@@ -78,8 +64,14 @@ protected:
 	std::shared_ptr<CButton> scrollUp;
 	std::shared_ptr<CButton> scrollDown;
 
-	/// should be called when list is invalidated
-	void update();
+protected:
+	std::shared_ptr<CListBox> listBox;
+
+	CList(int size, Rect widgetDimensions);
+
+	void createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount);
+
+	virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0;
 
 public:
 	/// functions that will be called when selection changes
@@ -88,10 +80,18 @@ public:
 	/// return index of currently selected element
 	int getSelectedIndex();
 
+	void setScrollUpButton(std::shared_ptr<CButton> button);
+	void setScrollDownButton(std::shared_ptr<CButton> button);
+
+	/// should be called when list is invalidated
+	void update();
+
 	/// set of methods to switch selection
 	void selectIndex(int which);
 	void selectNext();
 	void selectPrev();
+
+	void showAll(SDL_Surface * to) override;
 };
 
 /// List of heroes which is shown at the right of the adventure map screen
@@ -125,13 +125,9 @@ class CHeroList	: public CList
 		std::string getHoverText() override;
 	};
 
-	std::shared_ptr<CIntObject> createHeroItem(size_t index);
+	std::shared_ptr<CIntObject> createItem(size_t index);
 public:
-	/**
-	 * @brief CHeroList
-	 * @param size, position, btnUp, btnDown @see CList::CList
-	 */
-	CHeroList(int size, Point position, std::string btnUp, std::string btnDown);
+	CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
 
 	/// Select specific hero and scroll if needed
 	void select(const CGHeroInstance * hero = nullptr);
@@ -159,13 +155,9 @@ class CTownList	: public CList
 		std::string getHoverText() override;
 	};
 
-	std::shared_ptr<CIntObject> createTownItem(size_t index);
+	std::shared_ptr<CIntObject> createItem(size_t index) override;
 public:
-	/**
-	 * @brief CTownList
-	 * @param size, position, btnUp, btnDown @see CList::CList
-	 */
-	CTownList(int size, Point position, std::string btnUp, std::string btnDown);
+	CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
 
 	/// Select specific town and scroll if needed
 	void select(const CGTownInstance * town = nullptr);

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "CMinimap.h"
 
-#include "CAdventureMapInterface.h"
+#include "AdventureMapInterface.h"
 
 #include "../widgets/Images.h"
 #include "../CGameInfo.h"

+ 25 - 33
client/adventureMap/CResDataBar.cpp

@@ -19,50 +19,40 @@
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/ResourceSet.h"
 
-#define ADVOPT (conf.go()->ac)
-
-CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist)
+CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
 {
-	pos.x += x;
-	pos.y += y;
+	pos.x += position.x;
+	pos.y += position.y;
+
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(defname, 0, 0);
+	background = std::make_shared<CPicture>(imageName, 0, 0);
 	background->colorize(LOCPLINT->playerID);
 
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;
 
-	txtpos.resize(8);
-	for (int i = 0; i < 8 ; i++)
-	{
-		txtpos[i].first = pos.x + offx + resdist*i;
-		txtpos[i].second = pos.y + offy;
-	}
-	txtpos[7].first = txtpos[6].first + datedist;
 	addUsedEvents(RCLICK);
 }
 
-CResDataBar::CResDataBar()
+CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist):
+	CResDataBar(defname, Point(x,y))
 {
-	pos.x += ADVOPT.resdatabarX;
-	pos.y += ADVOPT.resdatabarY;
+	for (int i = 0; i < 7 ; i++)
+		resourcePositions[GameResID(i)] = Point( offx + resdist*i, offy );
 
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(ADVOPT.resdatabarG, 0, 0);
-	background->colorize(LOCPLINT->playerID);
-
-	pos.w = background->pos.w;
-	pos.h = background->pos.h;
+	datePosition = resourcePositions[EGameResID::GOLD] + Point(datedist, 0);
+}
 
-	txtpos.resize(8);
-	for (int i = 0; i < 8 ; i++)
-	{
-		txtpos[i].first = pos.x + ADVOPT.resOffsetX + ADVOPT.resDist*i;
-		txtpos[i].second = pos.y + ADVOPT.resOffsetY;
-	}
-	txtpos[7].first = txtpos[6].first + ADVOPT.resDateDist;
+void CResDataBar::setDatePosition(const Point & position)
+{
+	datePosition = position;
+}
 
+void CResDataBar::setResourcePosition(const GameResID & resource, const Point & position)
+{
+	resourcePositions[resource] = position;
 }
 
 std::string CResDataBar::buildDateString()
@@ -80,13 +70,15 @@ std::string CResDataBar::buildDateString()
 void CResDataBar::draw(SDL_Surface * to)
 {
 	//TODO: all this should be labels, but they require proper text update on change
-	for (GameResID i=EGameResID::WOOD; i <= GameResID(EGameResID::GOLD); ++i)
+	for (auto & entry : resourcePositions)
 	{
-		std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));
+		std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(entry.first));
 
-		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first, txtpos[i].second));
+		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, pos.topLeft() + entry.second);
 	}
-	graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, Point(txtpos[7].first, txtpos[7].second));
+
+	if (datePosition)
+		graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, pos.topLeft() + *datePosition);
 }
 
 void CResDataBar::showAll(SDL_Surface * to)

+ 15 - 3
client/adventureMap/CResDataBar.h

@@ -11,6 +11,11 @@
 
 #include "../gui/CIntObject.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
+enum class EGameResID : int8_t;
+using GameResID = Identifier<EGameResID>;
+VCMI_LIB_NAMESPACE_END
+
 /// Resources bar which shows information about how many gold, crystals,... you have
 /// Current date is displayed too
 class CResDataBar : public CIntObject
@@ -19,14 +24,21 @@ class CResDataBar : public CIntObject
 
 	std::shared_ptr<CPicture> background;
 
-	std::vector<std::pair<int,int> > txtpos;
-
+	std::map<GameResID, Point> resourcePositions;
+	std::optional<Point> datePosition;
 
 	void draw(SDL_Surface * to);
 public:
-	CResDataBar();
+
+	/// For dynamically-sized UI windows, e.g. adventure map interface
+	CResDataBar(const std::string & imageName, const Point & position);
+
+	/// For fixed-size UI windows, e.g. CastleInterface
 	CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
 
+	void setDatePosition(const Point & position);
+	void setResourcePosition(const GameResID & resource, const Point & position);
+
 	void colorize(PlayerColor player);
 	void showAll(SDL_Surface * to) override;
 };

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -29,7 +29,7 @@
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"

+ 6 - 2
client/battle/BattleWindow.cpp

@@ -72,7 +72,6 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	
 	console = widget<BattleConsole>("console");
 
-	GH.statusbar = console;
 	owner.console = console;
 
 	owner.fieldController.reset( new BattleFieldController(owner));
@@ -187,6 +186,11 @@ void BattleWindow::deactivate()
 	LOCPLINT->cingconsole->deactivate();
 }
 
+bool BattleWindow::captureThisKey(EShortcut key)
+{
+	return owner.openingPlaying();
+}
+
 void BattleWindow::keyPressed(EShortcut key)
 {
 	if (owner.openingPlaying())
@@ -359,7 +363,7 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 	}
 		
 	auto anim = std::make_shared<CAnimation>(iconName);
-	w->setImage(anim, false);
+	w->setImage(anim);
 	w->redraw();
 }
 

+ 1 - 0
client/battle/BattleWindow.h

@@ -85,6 +85,7 @@ public:
 	void activate() override;
 	void deactivate() override;
 	void keyPressed(EShortcut key) override;
+	bool captureThisKey(EShortcut key) override;
 	void clickRight(tribool down, bool previousState) override;
 	void show(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;

+ 31 - 7
client/gui/CGuiHandler.cpp

@@ -18,6 +18,7 @@
 #include "../CGameInfo.h"
 #include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
+#include "../renderSDL/ScreenHandler.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
@@ -38,6 +39,8 @@
 #include "ios/utils.h"
 #endif
 
+CGuiHandler GH;
+
 extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 
@@ -95,9 +98,10 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
 
 void CGuiHandler::init()
 {
+	screenHandlerInstance = std::make_unique<ScreenHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
-	mainFPSmng = new CFramerateManager();
-	mainFPSmng->init(settings["video"]["targetfps"].Integer());
+	mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer());
+
 	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
 	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
 }
@@ -180,9 +184,8 @@ std::shared_ptr<IShowActivatable> CGuiHandler::topInt()
 
 void CGuiHandler::totalRedraw()
 {
-#ifdef VCMI_ANDROID
-	SDL_FillRect(screen2, NULL, SDL_MapRGB(screen2->format, 0, 0, 0));
-#endif
+	CSDL_Ext::fillSurface( screen2, Colors::BLACK);
+
 	for(auto & elem : objsToBlit)
 		elem->showAll(screen2);
 	CSDL_Ext::blitAt(screen2,0,0,screen);
@@ -803,7 +806,26 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
 	SDL_PushEvent(&event);
 }
 
-CFramerateManager::CFramerateManager()
+IScreenHandler & CGuiHandler::screenHandler()
+{
+	return *screenHandlerInstance;
+}
+
+void CGuiHandler::onScreenResize()
+{
+	for (auto const & entry : listInt)
+	{
+		auto intObject = std::dynamic_pointer_cast<CIntObject>(entry);
+
+		if (intObject)
+			intObject->onScreenResize();
+	}
+
+	totalRedraw();
+}
+
+
+CFramerateManager::CFramerateManager(int newRate)
 	: rate(0)
 	, rateticks(0)
 	, fps(0)
@@ -811,7 +833,9 @@ CFramerateManager::CFramerateManager()
 	, accumulatedTime(0)
 	, lastticks(0)
 	, timeElapsed(0)
-{}
+{
+	init(newRate);
+}
 
 void CFramerateManager::init(int newRate)
 {

+ 8 - 1
client/gui/CGuiHandler.h

@@ -29,6 +29,7 @@ class CIntObject;
 class IUpdateable;
 class IShowActivatable;
 class IShowable;
+class IScreenHandler;
 
 // TODO: event handling need refactoring
 enum class EUserEvent
@@ -56,7 +57,7 @@ private:
 	ui32 accumulatedFrames;
 
 public:
-	CFramerateManager(); // initializes the manager with a given fps rate
+	CFramerateManager(int newRate); // initializes the manager with a given fps rate
 	void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
 	void framerateDelay(); // needs to be called every game update cycle
 	ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
@@ -95,6 +96,7 @@ private:
 	CIntObjectList doubleClickInterested;
 	CIntObjectList textInterested;
 
+	std::unique_ptr<IScreenHandler> screenHandlerInstance;
 
 	void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
@@ -135,6 +137,8 @@ public:
 	/// moves mouse pointer into specified position inside vcmi window
 	void moveCursorToPosition(const Point & position);
 
+	IScreenHandler & screenHandler();
+
 	IUpdateable *curInt;
 
 	Point lastClick;
@@ -156,6 +160,9 @@ public:
 	void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering
 	void simpleRedraw(); //update only top interface and draw background from buffer, sets a flag, method gets called at the end of the rendering
 
+	/// called whenever user selects different resolution, requiring to center/resize all windows
+	void onScreenResize();
+
 	void pushInt(std::shared_ptr<IShowActivatable> newInt); //deactivate old top interface, activates this one and pushes to the top
 	template <typename T, typename ... Args>
 	void pushIntT(Args && ... args)

+ 17 - 11
client/gui/CIntObject.cpp

@@ -159,16 +159,6 @@ void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFon
 	graphics->fonts[font]->renderTextCenter(dst, text, kolor, pos.topLeft() + p);
 }
 
-void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst )
-{
-	CSDL_Ext::blitAt(src, pos.x + x, pos.y + y, dst);
-}
-
-void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst)
-{
-	blitAtLoc(src, p.x, p.y, dst);
-}
-
 void CIntObject::printAtMiddleWBLoc( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst)
 {
 	graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, Point(pos.x + x, pos.y + y));
@@ -199,11 +189,22 @@ void CIntObject::disable()
 void CIntObject::enable()
 {
 	if(!active_m && (!parent_m || parent_m->active))
+	{
 		activate();
+		redraw();
+	}
 
 	recActions = 255;
 }
 
+void CIntObject::setEnabled(bool on)
+{
+	if (on)
+		enable();
+	else
+		disable();
+}
+
 void CIntObject::fitToScreen(int borderWidth, bool propagate)
 {
 	Point newPos = pos.topLeft();
@@ -242,7 +243,7 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
 	children.push_back(child);
 	child->parent_m = this;
 	if(adjustPosition)
-		child->pos += pos.topLeft();
+		child->moveBy(pos.topLeft(), adjustPosition);
 
 	if (!active && child->active)
 		child->deactivate();
@@ -286,6 +287,11 @@ void CIntObject::redraw()
 	}
 }
 
+void CIntObject::onScreenResize()
+{
+	center(pos, true);
+}
+
 const Rect & CIntObject::center( const Rect &r, bool propagate )
 {
 	pos.w = r.w;

+ 12 - 7
client/gui/CIntObject.h

@@ -53,7 +53,7 @@ class IShowActivatable : public IShowable, public IActivatable
 {
 public:
 	//redraw parent flag - this int may be semi-transparent and require redraw of parent window
-	enum {BLOCK_ADV_HOTKEYS = 2, REDRAW_PARENT=8};
+	enum {REDRAW_PARENT=8};
 	int type; //bin flags using etype
 	IShowActivatable();
 	virtual ~IShowActivatable(){};
@@ -140,8 +140,13 @@ public:
 	ui8 defActions; //which calls will be tried to be redirected to children
 	ui8 recActions; //which calls we allow to receive from parent
 
-	void disable(); //deactivates if needed, blocks all automatic activity, allows only disposal
-	void enable(); //activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
+	/// deactivates if needed, blocks all automatic activity, allows only disposal
+	void disable();
+	/// activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
+	void enable();
+	/// deactivates or activates UI element based on flag
+	void setEnabled(bool on);
+
 
 	// activate or deactivate object. Inactive object won't receive any input events (keyboard\mouse)
 	// usually used automatically by parent
@@ -155,6 +160,10 @@ public:
 	//request complete redraw of this object
 	void redraw() override;
 
+	/// called only for windows whenever screen size changes
+	/// default behavior is to re-center, can be overriden
+	virtual void onScreenResize();
+
 	const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
 	const Rect & center(const Point &p, bool propagate = true);  //moves object so that point p will be in its center
 	const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position
@@ -177,10 +186,6 @@ public:
 	void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color color, SDL_Surface * dst);
 	void printAtMiddleWBLoc(const std::string & text, int x, int y, EFonts font, int charsPerLine, SDL_Color color, SDL_Surface * dst);
 
-	//image blitting. If possible use CPicture or CAnimImage instead
-	void blitAtLoc(SDL_Surface * src, int x, int y, SDL_Surface * dst);
-	void blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst);
-
 	friend class CGuiHandler;
 };
 

+ 69 - 39
client/gui/InterfaceObjectConfigurable.cpp

@@ -82,15 +82,26 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 		items = &config["items"];
 	}
 	
-	const std::string unnamedObjectPrefix = "__widget_";
 	for(const auto & item : items->Vector())
-	{
-		std::string name = item["name"].isNull()
-						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
-						: item["name"].String();
-		logGlobal->debug("Building widget with name %s", name);
-		widgets[name] = buildWidget(item);
-	}
+		addWidget(item["name"].String(), buildWidget(item));
+}
+
+void InterfaceObjectConfigurable::addWidget(const std::string & namePreferred, std::shared_ptr<CIntObject> widget)
+{
+	static const std::string unnamedObjectPrefix = "__widget_";
+
+	std::string nameActual;
+
+	if (widgets.count(namePreferred) == 0)
+		nameActual = namePreferred;
+	else
+		logGlobal->error("Duplicated widget name: '%s'", namePreferred);
+
+	if (nameActual.empty())
+		nameActual = unnamedObjectPrefix + std::to_string(unnamedObjectId++);
+
+	logGlobal->debug("Building widget with name %s", nameActual);
+	widgets[nameActual] = widget;
 }
 
 std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
@@ -174,6 +185,8 @@ EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
 			return EFonts::FONT_SMALL;
 		if(config.String() == "tiny")
 			return EFonts::FONT_TINY;
+		if(config.String() == "calisto")
+			return EFonts::FONT_CALLI;
 	}
 	logGlobal->debug("Uknown font attribute");
 	return EFonts::FONT_TIMES;
@@ -287,15 +300,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 		assert(imgOrder.size() >= 4);
 		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
 	}
-	if(!config["callback"].isNull())
-	{
-		std::string callbackName = config["callback"].String();
-
-		if (callbacks.count(callbackName))
-			button->addCallback(callbacks.at(callbackName));
-		else
-			logGlobal->error("Invalid callback '%s' in widget", callbackName );
-	}
+	loadButtonCallback(button, config["callback"]);
 	return button;
 }
 
@@ -319,30 +324,54 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 		assert(imgOrder.size() >= 4);
 		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
 	}
-	if(!config["callback"].isNull())
-	{
-		std::string callbackName = config["callback"].String();
 
-		if (callbacks.count(callbackName) > 0)
-			button->addCallback(std::bind(callbacks.at(callbackName), 0));
-		else
-			logGlobal->error("Invalid callback '%s' in widget", callbackName );
-	}
-	if(!config["hotkey"].isNull())
+	loadButtonBorderColor(button, config["borderColor"]);
+	loadButtonCallback(button, config["callback"]);
+	loadButtonHotkey(button, config["hotkey"]);
+	return button;
+}
+
+void InterfaceObjectConfigurable::loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const
+{
+	if (config.isNull())
+		return;
+
+	auto color = readColor(config);
+	button->setBorderColor(color);
+}
+
+void InterfaceObjectConfigurable::loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const
+{
+	if(config.isNull())
+		return;
+
+	std::string callbackName = config.String();
+
+	if (callbacks.count(callbackName) > 0)
+		button->addCallback(std::bind(callbacks.at(callbackName), 0));
+	else
+		logGlobal->error("Invalid callback '%s' in widget", callbackName );
+}
+
+void InterfaceObjectConfigurable::loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const
+{
+	if(config.isNull())
+		return;
+
+	if(config.getType() != JsonNode::JsonType::DATA_STRING)
 	{
-		if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING)
-		{
-			button->assignedKey = readHotkey(config["hotkey"]);
-
-			auto target = shortcuts.find(button->assignedKey);
-			if (target != shortcuts.end())
-			{
-				button->addCallback(target->second.callback);
-				target->second.assignedToButton = true;
-			}
-		}
+		logGlobal->error("Invalid shortcut format - string expected!");
+		return;
 	}
-	return button;
+
+	button->assignedKey = readHotkey(config);
+
+	auto target = shortcuts.find(button->assignedKey);
+	if (target == shortcuts.end())
+		return;
+
+	button->addCallback(target->second.callback);
+	target->second.assignedToButton = true;
 }
 
 std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
@@ -374,7 +403,8 @@ std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode
 	auto itemsTotal = config["itemsTotal"].Integer();
 	auto value = config["selected"].Integer();
 	bool horizontal = config["orientation"].String() == "horizontal";
-	auto const & result = std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
+	const auto & result =
+		std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
 
 	if (!config["scrollBounds"].isNull())
 	{

+ 6 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -48,6 +48,8 @@ protected:
 	
 	//must be called after adding callbacks
 	void build(const JsonNode & config);
+
+	void addWidget(const std::string & name, std::shared_ptr<CIntObject> widget);
 	
 	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
 	JsonNode variables;
@@ -73,6 +75,10 @@ protected:
 	std::pair<std::string, std::string> readHintText(const JsonNode &) const;
 	EShortcut readHotkey(const JsonNode &) const;
 	
+	void loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const;
+	void loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const;
+	void loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const;
+
 	//basic widgets
 	std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
 	std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;

+ 7 - 3
client/gui/Shortcut.h

@@ -81,7 +81,9 @@ enum class EShortcut
 	// Adventure map screen
 	ADVENTURE_GAME_OPTIONS, // 'o', Open CAdventureOptions window
 	ADVENTURE_TOGGLE_GRID,  // F6, Toggles map grid
-	ADVENTURE_TOGGLE_SLEEP, // z,w, Toggles hero sleep status
+	ADVENTURE_TOGGLE_SLEEP, // Toggles hero sleep status
+	ADVENTURE_SET_HERO_ASLEEP, // Moves hero to sleep state
+	ADVENTURE_SET_HERO_AWAKE, // Move hero to awake state
 	ADVENTURE_MOVE_HERO,    // Moves hero alongside set path
 	ADVENTURE_VISIT_OBJECT, // Revisits object hero is standing on
 	ADVENTURE_VIEW_SELECTED,// Open window with currently selected hero/town
@@ -94,12 +96,15 @@ enum class EShortcut
 	ADVENTURE_DIG_GRAIL,
 	ADVENTURE_VIEW_PUZZLE,
 	ADVENTURE_VIEW_WORLD,
+	ADVENTURE_VIEW_WORLD_X1,
+	ADVENTURE_VIEW_WORLD_X2,
+	ADVENTURE_VIEW_WORLD_X4,
 	ADVENTURE_TOGGLE_MAP_LEVEL,
 	ADVENTURE_KINGDOM_OVERVIEW,
 	ADVENTURE_QUEST_LOG,
 	ADVENTURE_CAST_SPELL,
-	ADVENTURE_END_TURN,
 	ADVENTURE_THIEVES_GUILD,
+	ADVENTURE_EXIT_WORLD_VIEW,
 
 	// Move hero one tile in specified direction. Bound to cursors & numpad buttons
 	ADVENTURE_MOVE_HERO_SW,
@@ -153,4 +158,3 @@ enum class EShortcut
 
 	AFTER_LAST
 };
-

+ 13 - 86
client/gui/ShortcutHandler.cpp

@@ -64,6 +64,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_RETURN,    EShortcut::LOBBY_LOAD_GAME           },
 		{SDLK_KP_ENTER,  EShortcut::LOBBY_LOAD_GAME           },
 		{SDLK_s,         EShortcut::LOBBY_SAVE_GAME           },
+		{SDLK_RETURN,    EShortcut::LOBBY_SAVE_GAME           },
+		{SDLK_KP_ENTER,  EShortcut::LOBBY_SAVE_GAME           },
 		{SDLK_r,         EShortcut::LOBBY_RANDOM_MAP          },
 		{SDLK_h,         EShortcut::LOBBY_HIDE_CHAT           },
 		{SDLK_a,         EShortcut::LOBBY_ADDITIONAL_OPTIONS  },
@@ -79,8 +81,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_TAB,       EShortcut::GAME_ACTIVATE_CONSOLE     },
 		{SDLK_o,         EShortcut::ADVENTURE_GAME_OPTIONS    },
 		{SDLK_F6,        EShortcut::ADVENTURE_TOGGLE_GRID     },
-		{SDLK_z,         EShortcut::ADVENTURE_TOGGLE_SLEEP    },
-		{SDLK_w,         EShortcut::ADVENTURE_TOGGLE_SLEEP    },
+		{SDLK_z,         EShortcut::ADVENTURE_SET_HERO_ASLEEP },
+		{SDLK_w,         EShortcut::ADVENTURE_SET_HERO_AWAKE  },
 		{SDLK_m,         EShortcut::ADVENTURE_MOVE_HERO       },
 		{SDLK_SPACE,     EShortcut::ADVENTURE_VISIT_OBJECT    },
 		{SDLK_KP_1,      EShortcut::ADVENTURE_MOVE_HERO_SW    },
@@ -106,11 +108,13 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_d,         EShortcut::ADVENTURE_DIG_GRAIL       },
 		{SDLK_p,         EShortcut::ADVENTURE_VIEW_PUZZLE     },
 		{SDLK_v,         EShortcut::ADVENTURE_VIEW_WORLD      },
+		{SDLK_1,         EShortcut::ADVENTURE_VIEW_WORLD_X1   },
+		{SDLK_2,         EShortcut::ADVENTURE_VIEW_WORLD_X2   },
+		{SDLK_4,         EShortcut::ADVENTURE_VIEW_WORLD_X4   },
 		{SDLK_u,         EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
 		{SDLK_k,         EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
 		{SDLK_q,         EShortcut::ADVENTURE_QUEST_LOG       },
 		{SDLK_c,         EShortcut::ADVENTURE_CAST_SPELL      },
-		{SDLK_e,         EShortcut::ADVENTURE_END_TURN        },
 		{SDLK_g,         EShortcut::ADVENTURE_THIEVES_GUILD   },
 		{SDLK_q,         EShortcut::BATTLE_TOGGLE_QUEUE       },
 		{SDLK_c,         EShortcut::BATTLE_USE_CREATURE_SPELL },
@@ -218,6 +222,8 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"adventureGameOptions",     EShortcut::ADVENTURE_GAME_OPTIONS    },
 		{"adventureToggleGrid",      EShortcut::ADVENTURE_TOGGLE_GRID     },
 		{"adventureToggleSleep",     EShortcut::ADVENTURE_TOGGLE_SLEEP    },
+		{"adventureSetHeroAsleep",   EShortcut::ADVENTURE_SET_HERO_ASLEEP },
+		{"adventureSetHeroAwake",    EShortcut::ADVENTURE_SET_HERO_AWAKE  },
 		{"adventureMoveHero",        EShortcut::ADVENTURE_MOVE_HERO       },
 		{"adventureVisitObject",     EShortcut::ADVENTURE_VISIT_OBJECT    },
 		{"adventureMoveHeroSW",      EShortcut::ADVENTURE_MOVE_HERO_SW    },
@@ -238,12 +244,15 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"adventureDigGrail",        EShortcut::ADVENTURE_DIG_GRAIL       },
 		{"adventureViewPuzzle",      EShortcut::ADVENTURE_VIEW_PUZZLE     },
 		{"adventureViewWorld",       EShortcut::ADVENTURE_VIEW_WORLD      },
+		{"adventureViewWorld1",      EShortcut::ADVENTURE_VIEW_WORLD_X1   },
+		{"adventureViewWorld2",      EShortcut::ADVENTURE_VIEW_WORLD_X2   },
+		{"adventureViewWorld4",      EShortcut::ADVENTURE_VIEW_WORLD_X4   },
 		{"adventureToggleMapLevel",  EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
 		{"adventureKingdomOverview", EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
 		{"adventureQuestLog",        EShortcut::ADVENTURE_QUEST_LOG       },
 		{"adventureCastSpell",       EShortcut::ADVENTURE_CAST_SPELL      },
-		{"adventureEndTurn",         EShortcut::ADVENTURE_END_TURN        },
 		{"adventureThievesGuild",    EShortcut::ADVENTURE_THIEVES_GUILD   },
+		{"adventureExitWorldView",   EShortcut::ADVENTURE_EXIT_WORLD_VIEW },
 		{"battleToggleQueue",        EShortcut::BATTLE_TOGGLE_QUEUE       },
 		{"battleUseCreatureSpell",   EShortcut::BATTLE_USE_CREATURE_SPELL },
 		{"battleSurrender",          EShortcut::BATTLE_SURRENDER          },
@@ -278,85 +287,3 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		return shortcutNames.at(identifier);
 	return EShortcut::NONE;
 }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

+ 1 - 1
client/lobby/CLobbyScreen.cpp

@@ -51,7 +51,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 		buttonOptions = std::make_shared<CButton>(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
 	};
 
-	buttonChat = std::make_shared<CButton>(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
+	buttonChat = std::make_shared<CButton>(Point(619, 80), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
 	buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
 
 	switch(screenType)

+ 0 - 1
client/lobby/CSelectionBase.cpp

@@ -71,7 +71,6 @@ CSelectionBase::CSelectionBase(ESelectionScreen type)
 	: CWindowObject(BORDERED | SHADOW_DISABLED), ISelectionScreenInfo(type)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	IShowActivatable::type = BLOCK_ADV_HOTKEYS;
 	pos.w = 762;
 	pos.h = 584;
 	if(screenType == ESelectionScreen::campaignList)

+ 1 - 0
client/lobby/SelectionTab.cpp

@@ -31,6 +31,7 @@
 
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CConfigHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/filesystem/Filesystem.h"

+ 20 - 0
client/mainmenu/CMainMenu.cpp

@@ -299,6 +299,26 @@ CMainMenu::~CMainMenu()
 		GH.curInt = nullptr;
 }
 
+void CMainMenu::activate()
+{
+	// check if screen was resized while main menu was inactive - e.g. in gameplay mode
+	if (pos.dimensions() != GH.screenDimensions())
+		onScreenResize();
+
+	CIntObject::activate();
+}
+
+void CMainMenu::onScreenResize()
+{
+	pos.w = GH.screenDimensions().x;
+	pos.h = GH.screenDimensions().y;
+
+	menu = nullptr;
+	menu = std::make_shared<CMenuScreen>(CMainMenuConfig::get().getConfig()["window"]);
+
+	backgroundAroundMenu->pos = pos;
+}
+
 void CMainMenu::update()
 {
 	if(CMM != this->shared_from_this()) //don't update if you are not a main interface

+ 2 - 0
client/mainmenu/CMainMenu.h

@@ -141,6 +141,8 @@ public:
 	std::shared_ptr<CMenuScreen> menu;
 
 	~CMainMenu();
+	void activate() override;
+	void onScreenResize() override;
 	void update() override;
 	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
 	static void openCampaignLobby(const std::string & campaignFileName);

+ 1 - 1
client/mapView/MapRendererContextState.cpp

@@ -17,7 +17,7 @@
 #include "../../CCallback.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"

+ 1 - 1
client/mapView/MapView.cpp

@@ -19,7 +19,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"

+ 1 - 1
client/mapView/MapViewActions.cpp

@@ -15,7 +15,7 @@
 #include "MapViewModel.h"
 
 #include "../CGameInfo.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
 

+ 1 - 1
client/mapView/MapViewController.cpp

@@ -17,7 +17,7 @@
 #include "MapViewModel.h"
 
 #include "../CPlayerInterface.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../gui/CGuiHandler.h"
 
 #include "../../lib/CConfigHandler.h"

+ 36 - 0
client/render/IScreenHandler.h

@@ -0,0 +1,36 @@
+/*
+ * IScreenHandler.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 Point;
+VCMI_LIB_NAMESPACE_END
+
+class IScreenHandler
+{
+public:
+	virtual ~IScreenHandler() = default;
+
+	/// Updates window state after fullscreen state has been changed in settings
+	virtual void onScreenResize() = 0;
+
+	/// De-initializes window state
+	virtual void close() = 0;
+
+	/// Fills screen with black color, erasing any existing content
+	virtual void clearScreen() = 0;
+
+	/// Returns list of resolutions supported by current screen
+	virtual std::vector<Point> getSupportedResolutions() const = 0;
+
+	/// Returns <min, max> range of possible values for screen scaling percentage
+	virtual std::tuple<int, int> getSupportedScalingRange() const = 0;
+};

+ 0 - 37
client/renderSDL/SDL_Extensions.cpp

@@ -861,43 +861,6 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other)
 	other = CSDL_Ext::fromSDL(rect);
 }
 
-bool CSDL_Ext::isResolutionSupported(const std::vector<Point> & resolutions, const Point toTest )
-{
-#if defined(VCMI_MOBILE)
-	// ios can use any resolution
-	// presumably, same goes for Android
-	return true;
-#else
-	// in fullscreen only resolutions supported by monitor can be used
-	return vstd::contains(resolutions, toTest);
-#endif
-}
-
-std::vector<Point> CSDL_Ext::getSupportedResolutions()
-{
-	int displayID = SDL_GetWindowDisplayIndex(mainWindow);
-	return getSupportedResolutions(displayID);
-}
-
-std::vector<Point> CSDL_Ext::getSupportedResolutions( int displayIndex)
-{
-	std::vector<Point> result;
-
-	int modesCount = SDL_GetNumDisplayModes(displayIndex);
-
-	for (int i =0; i < modesCount; ++i)
-	{
-		SDL_DisplayMode mode;
-		if (SDL_GetDisplayMode(displayIndex, i, &mode) != 0)
-			continue;
-
-		Point resolution(mode.w, mode.h);
-
-		result.push_back(resolution);
-	}
-	return result;
-}
-
 template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int);
 template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
 template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);

+ 0 - 5
client/renderSDL/SDL_Extensions.h

@@ -105,11 +105,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 	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);
-
-	std::vector<Point> getSupportedResolutions();
-	std::vector<Point> getSupportedResolutions(int displayIndex);
-
 	void setColorKey(SDL_Surface * surface, SDL_Color color);
 
 	///set key-color to 0,255,255

+ 510 - 0
client/renderSDL/ScreenHandler.cpp

@@ -0,0 +1,510 @@
+/*
+ * ScreenHandler.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 "ScreenHandler.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/NotificationHandler.h"
+#include "CMT.h"
+#include "SDL_Extensions.h"
+
+#ifdef VCMI_ANDROID
+#include "../lib/CAndroidVMHelper.h"
+#endif
+
+#include <SDL.h>
+
+// TODO: should be made into a private members of ScreenHandler
+SDL_Window * mainWindow = nullptr;
+SDL_Renderer * mainRenderer = nullptr;
+SDL_Texture * screenTexture = nullptr;
+SDL_Surface * screen = nullptr; //main screen surface
+SDL_Surface * screen2 = nullptr; //and hlp surface (used to store not-active interfaces layer)
+SDL_Surface * screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
+
+static const std::string NAME_AFFIX = "client";
+static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
+
+std::tuple<int, int> ScreenHandler::getSupportedScalingRange() const
+{
+	// H3 resolution, any resolution smaller than that is not correctly supported
+	static const Point minResolution = {800, 600};
+	// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
+	static const double minimalScaling = 50;
+
+	Point renderResolution = getPreferredRenderingResolution();
+	double maximalScalingWidth = 100.0 * renderResolution.x / minResolution.x;
+	double maximalScalingHeight = 100.0 * renderResolution.y / minResolution.y;
+	double maximalScaling = std::min(maximalScalingWidth, maximalScalingHeight);
+
+	return { minimalScaling, maximalScaling };
+}
+
+Point ScreenHandler::getPreferredLogicalResolution() const
+{
+	Point renderResolution = getPreferredRenderingResolution();
+	auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
+
+	int userScaling = settings["video"]["resolution"]["scaling"].Integer();
+	int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
+
+	Point logicalResolution = renderResolution * 100.0 / scaling;
+
+	return logicalResolution;
+}
+
+Point ScreenHandler::getPreferredRenderingResolution() const
+{
+	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED)
+	{
+		SDL_Rect bounds;
+		if (SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds) == 0)
+			return Point(bounds.w, bounds.h);
+	}
+
+	const JsonNode & video = settings["video"];
+	int width = video["resolution"]["width"].Integer();
+	int height = video["resolution"]["height"].Integer();
+
+	return Point(width, height);
+}
+
+int ScreenHandler::getPreferredDisplayIndex() const
+{
+#ifdef VCMI_MOBILE
+	// Assuming no multiple screens on Android / ios?
+	return 0;
+#else
+	if (mainWindow != nullptr)
+	{
+		int result = SDL_GetWindowDisplayIndex(mainWindow);
+		if (result >= 0)
+			return result;
+	}
+
+	return settings["video"]["displayIndex"].Integer();
+#endif
+}
+
+EWindowMode ScreenHandler::getPreferredWindowMode() const
+{
+#ifdef VCMI_MOBILE
+	// On Android / ios game will always render to screen size
+	return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED;
+#else
+	const JsonNode & video = settings["video"];
+	bool fullscreen = video["fullscreen"].Bool();
+	bool realFullscreen = settings["video"]["realFullscreen"].Bool();
+
+	if (!fullscreen)
+		return EWindowMode::WINDOWED;
+
+	if (realFullscreen)
+		return EWindowMode::FULLSCREEN_EXCLUSIVE;
+	else
+		return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED;
+#endif
+}
+
+ScreenHandler::ScreenHandler()
+{
+#ifdef VCMI_WINDOWS
+	// set VCMI as "per-monitor DPI awareness". This completely disables any DPI-scaling by system.
+	// Might not be the best solution since VCMI can't automatically adjust to DPI changes (including moving to monitors with different DPI scaling)
+	// However this fixed unintuitive bug where player selects specific resolution for windowed mode, but ends up with completely different one due to scaling
+	// NOTE: requires SDL 2.24.
+	SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
+#endif
+
+	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO))
+	{
+		logGlobal->error("Something was wrong: %s", SDL_GetError());
+		exit(-1);
+	}
+
+	const auto & logCallback = [](void * userdata, int category, SDL_LogPriority priority, const char * message)
+	{
+		logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
+	};
+
+	SDL_LogSetOutputFunction(logCallback, nullptr);
+
+#ifdef VCMI_ANDROID
+	// manually setting egl pixel format, as a possible solution for sdl2<->android problem
+	// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
+	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
+	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+#endif // VCMI_ANDROID
+
+	validateSettings();
+	recreateWindowAndScreenBuffers();
+}
+
+void ScreenHandler::recreateWindowAndScreenBuffers()
+{
+	destroyScreenBuffers();
+
+	if(mainWindow == nullptr)
+		initializeWindow();
+	else
+		updateWindowState();
+
+	initializeScreenBuffers();
+
+	if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
+	{
+		NotificationHandler::init(mainWindow);
+	}
+}
+
+void ScreenHandler::updateWindowState()
+{
+#ifndef VCMI_MOBILE
+	int displayIndex = getPreferredDisplayIndex();
+
+	switch(getPreferredWindowMode())
+	{
+		case EWindowMode::FULLSCREEN_EXCLUSIVE:
+		{
+			SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
+
+			SDL_DisplayMode mode;
+			SDL_GetDesktopDisplayMode(displayIndex, &mode);
+			Point resolution = getPreferredRenderingResolution();
+
+			mode.w = resolution.x;
+			mode.h = resolution.y;
+
+			SDL_SetWindowDisplayMode(mainWindow, &mode);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
+
+			return;
+		}
+		case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED:
+		{
+			SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
+			return;
+		}
+		case EWindowMode::WINDOWED:
+		{
+			Point resolution = getPreferredRenderingResolution();
+			SDL_SetWindowFullscreen(mainWindow, 0);
+			SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
+			return;
+		}
+	}
+#endif
+}
+
+void ScreenHandler::initializeWindow()
+{
+	mainWindow = createWindow();
+
+	if(mainWindow == nullptr)
+		throw std::runtime_error("Unable to create window\n");
+
+	//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
+	mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0);
+
+	if(mainRenderer == nullptr)
+		throw std::runtime_error("Unable to create renderer\n");
+
+	SDL_RendererInfo info;
+	SDL_GetRendererInfo(mainRenderer, &info);
+	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
+	logGlobal->info("Created renderer %s", info.name);
+}
+
+void ScreenHandler::initializeScreenBuffers()
+{
+#ifdef VCMI_ENDIAN_BIG
+	int bmask = 0xff000000;
+	int gmask = 0x00ff0000;
+	int rmask = 0x0000ff00;
+	int amask = 0x000000ff;
+#else
+	int bmask = 0x000000ff;
+	int gmask = 0x0000ff00;
+	int rmask = 0x00ff0000;
+	int amask = 0xFF000000;
+#endif
+
+	auto logicalSize = getPreferredLogicalResolution();
+	SDL_RenderSetLogicalSize(mainRenderer, logicalSize.x, logicalSize.y);
+
+	screen = SDL_CreateRGBSurface(0, logicalSize.x, logicalSize.y, 32, rmask, gmask, bmask, amask);
+	if(nullptr == screen)
+	{
+		logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", logicalSize.x, logicalSize.y, 32, SDL_GetError());
+		throw std::runtime_error("Unable to create surface");
+	}
+	//No blending for screen itself. Required for proper cursor rendering.
+	SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
+
+	screenTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, logicalSize.x, logicalSize.y);
+
+	if(nullptr == screenTexture)
+	{
+		logGlobal->error("Unable to create screen texture");
+		logGlobal->error(SDL_GetError());
+		throw std::runtime_error("Unable to create screen texture");
+	}
+
+	screen2 = CSDL_Ext::copySurface(screen);
+
+	if(nullptr == screen2)
+	{
+		throw std::runtime_error("Unable to copy surface\n");
+	}
+
+	if (GH.listInt.size() > 1)
+		screenBuf = screen2;
+	else
+		screenBuf = screen;
+
+	clearScreen();
+}
+
+SDL_Window * ScreenHandler::createWindowImpl(Point dimensions, int flags, bool center)
+{
+	int displayIndex = getPreferredDisplayIndex();
+	int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
+
+	return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags);
+}
+
+SDL_Window * ScreenHandler::createWindow()
+{
+#ifndef VCMI_MOBILE
+	Point dimensions = getPreferredRenderingResolution();
+
+	switch(getPreferredWindowMode())
+	{
+		case EWindowMode::FULLSCREEN_EXCLUSIVE:
+			return createWindowImpl(dimensions, SDL_WINDOW_FULLSCREEN, false);
+
+		case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED:
+			return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN_DESKTOP, false);
+
+		case EWindowMode::WINDOWED:
+			return createWindowImpl(dimensions, SDL_WINDOW_RESIZABLE, true);
+
+		default:
+			return nullptr;
+	};
+#endif
+
+#ifdef VCMI_IOS
+	SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
+	SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
+
+	uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
+	SDL_Window * result = createWindowImpl(Point(), windowFlags | SDL_WINDOW_METAL, false);
+
+	if(result != nullptr)
+		return result;
+
+	logGlobal->warn("Metal unavailable, using OpenGLES");
+	return createWindowImpl(Point(), windowFlags, false);
+#endif
+
+#ifdef VCMI_ANDROID
+	return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN, false);
+#endif
+}
+
+void ScreenHandler::onScreenResize()
+{
+	recreateWindowAndScreenBuffers();
+	GH.onScreenResize();
+}
+
+void ScreenHandler::validateSettings()
+{
+#ifndef VCMI_MOBILE
+	{
+		int displayIndex = settings["video"]["displayIndex"].Integer();
+		int displaysCount = SDL_GetNumVideoDisplays();
+
+		if (displayIndex >= displaysCount)
+		{
+			Settings writer = settings.write["video"]["displayIndex"];
+			writer->Float() = 0;
+		}
+	}
+
+	if (getPreferredWindowMode() == EWindowMode::WINDOWED)
+	{
+		//we only check that our desired window size fits on screen
+		int displayIndex = getPreferredDisplayIndex();
+		Point resolution = getPreferredRenderingResolution();
+
+		SDL_DisplayMode mode;
+
+		if (SDL_GetDesktopDisplayMode(displayIndex, &mode) == 0)
+		{
+			if(resolution.x > mode.w || resolution.y > mode.h)
+			{
+				Settings writer = settings.write["video"]["resolution"];
+				writer["width"].Float() = mode.w;
+				writer["height"].Float() = mode.h;
+			}
+		}
+	}
+
+	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE)
+	{
+		auto legalOptions = getSupportedResolutions();
+		Point selectedResolution = getPreferredRenderingResolution();
+
+		if(!vstd::contains(legalOptions, selectedResolution))
+		{
+			// resolution selected for fullscreen mode is not supported by display
+			// try to find current display resolution and use it instead as "reasonable default"
+			SDL_DisplayMode mode;
+
+			if (SDL_GetDesktopDisplayMode(getPreferredDisplayIndex(), &mode) == 0)
+			{
+				Settings writer = settings.write["video"]["resolution"];
+				writer["width"].Float() = mode.w;
+				writer["height"].Float() = mode.h;
+			}
+		}
+	}
+#endif
+}
+
+int ScreenHandler::getPreferredRenderingDriver() const
+{
+	int result = -1;
+	const JsonNode & video = settings["video"];
+
+	int driversCount = SDL_GetNumRenderDrivers();
+	std::string preferredDriverName = video["driver"].String();
+
+	logGlobal->info("Found %d render drivers", driversCount);
+
+	for(int it = 0; it < driversCount; it++)
+	{
+		SDL_RendererInfo info;
+		if (SDL_GetRenderDriverInfo(it, &info) == 0)
+		{
+			std::string driverName(info.name);
+
+			if(!preferredDriverName.empty() && driverName == preferredDriverName)
+			{
+				result = it;
+				logGlobal->info("\t%s (active)", driverName);
+			}
+			else
+				logGlobal->info("\t%s", driverName);
+		}
+		else
+			logGlobal->info("\t(error)");
+	}
+	return result;
+}
+
+void ScreenHandler::destroyScreenBuffers()
+{
+	// screenBuf is not a separate surface, but points to either screen or screen2 - just set to null
+	screenBuf = nullptr;
+
+	if(nullptr != screen2)
+	{
+		SDL_FreeSurface(screen2);
+		screen2 = nullptr;
+	}
+
+	if(nullptr != screen)
+	{
+		SDL_FreeSurface(screen);
+		screen = nullptr;
+	}
+
+	if(nullptr != screenTexture)
+	{
+		SDL_DestroyTexture(screenTexture);
+		screenTexture = nullptr;
+	}
+}
+
+void ScreenHandler::destroyWindow()
+{
+	if(nullptr != mainRenderer)
+	{
+		SDL_DestroyRenderer(mainRenderer);
+		mainRenderer = nullptr;
+	}
+
+	if(nullptr != mainWindow)
+	{
+		SDL_DestroyWindow(mainWindow);
+		mainWindow = nullptr;
+	}
+}
+
+void ScreenHandler::close()
+{
+	if(settings["general"]["notifications"].Bool())
+		NotificationHandler::destroy();
+
+	destroyScreenBuffers();
+	destroyWindow();
+	SDL_Quit();
+}
+
+void ScreenHandler::clearScreen()
+{
+	SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
+	SDL_RenderClear(mainRenderer);
+	SDL_RenderPresent(mainRenderer);
+}
+
+std::vector<Point> ScreenHandler::getSupportedResolutions() const
+{
+	int displayID = SDL_GetWindowDisplayIndex(mainWindow);
+	return getSupportedResolutions(displayID);
+}
+
+std::vector<Point> ScreenHandler::getSupportedResolutions( int displayIndex) const
+{
+	//NOTE: this method is never called on Android/iOS, only on desktop systems
+
+	std::vector<Point> result;
+
+	int modesCount = SDL_GetNumDisplayModes(displayIndex);
+
+	for (int i =0; i < modesCount; ++i)
+	{
+		SDL_DisplayMode mode;
+		if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0)
+		{
+			Point resolution(mode.w, mode.h);
+			result.push_back(resolution);
+		}
+	}
+
+	boost::range::sort(result, [](const auto & left, const auto & right)
+	{
+		return left.x * left.y < right.x * right.y;
+	});
+
+	// erase potential duplicates, e.g. resolutions with different framerate / bits per pixel
+	result.erase(boost::unique(result).end(), result.end());
+
+	return result;
+}

+ 89 - 0
client/renderSDL/ScreenHandler.h

@@ -0,0 +1,89 @@
+/*
+ * ScreenHandler.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
+
+struct SDL_Texture;
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Surface;
+
+#include "../../lib/Point.h"
+#include "../render/IScreenHandler.h"
+
+enum class EWindowMode
+{
+	// game runs in a window that covers part of the screen
+	WINDOWED,
+	// game runs in a 'window' that always covers entire screen and uses unmodified desktop resolution
+	// The only mode that is available on mobile devices
+	FULLSCREEN_BORDERLESS_WINDOWED,
+	// game runs in a fullscreen mode with resolution selected by player
+	FULLSCREEN_EXCLUSIVE
+};
+
+/// This class is responsible for management of game window and its main rendering surface
+class ScreenHandler : public IScreenHandler
+{
+	/// Dimensions of target surfaces/textures, this value is what game logic views as screen size
+	Point getPreferredLogicalResolution() const;
+
+	/// Dimensions of output window, if different from logicalResolution SDL will perform scaling
+	/// This value is what player views as window size
+	Point getPreferredRenderingResolution() const;
+
+	EWindowMode getPreferredWindowMode() const;
+
+	/// Returns index of display on which window should be created
+	int getPreferredDisplayIndex() const;
+
+	/// Returns index of rendering driver preferred by player or -1 if no preference
+	int getPreferredRenderingDriver() const;
+
+	/// Creates SDL window with specified parameters
+	SDL_Window * createWindowImpl(Point dimensions, int flags, bool center);
+
+	/// Creates SDL window using OS-specific settings & user-specific config
+	SDL_Window * createWindow();
+
+	/// Manages window and SDL renderer
+	void initializeWindow();
+	void destroyWindow();
+
+	/// Manages surfaces & textures used for
+	void initializeScreenBuffers();
+	void destroyScreenBuffers();
+
+	/// Updates state (e.g. position) of game window after resolution/fullscreen change
+	void updateWindowState();
+
+	/// Initializes or reiniitalizes all screen state
+	void recreateWindowAndScreenBuffers();
+
+	/// Performs validation of settings and updates them to valid values if necessary
+	void validateSettings();
+public:
+
+	/// Creates and initializes screen, window and SDL state
+	ScreenHandler();
+
+	/// Updates and potentially recreates target screen to match selected fullscreen status
+	void onScreenResize() final;
+
+	/// De-initializes and destroys screen, window and SDL state
+	void close() final;
+
+	/// Fills screen with black color, erasing any existing content
+	void clearScreen() final;
+
+	std::vector<Point> getSupportedResolutions() const final;
+	std::vector<Point> getSupportedResolutions(int displayIndex) const;
+	std::tuple<int, int> getSupportedScalingRange() const final;
+};

+ 7 - 7
client/widgets/Buttons.cpp

@@ -249,32 +249,32 @@ CButton::CButton(Point position, const std::string &defName, const std::pair<std
 	if (!defName.empty())
 	{
 		imageNames.push_back(defName);
-		setIndex(0, playerColoredButton);
+		setIndex(0);
+		if (playerColoredButton)
+			image->playerColored(LOCPLINT->playerID);
 	}
 }
 
-void CButton::setIndex(size_t index, bool playerColoredButton)
+void CButton::setIndex(size_t index)
 {
 	if (index == currentImage || index>=imageNames.size())
 		return;
 	currentImage = index;
 	auto anim = std::make_shared<CAnimation>(imageNames[index]);
-	setImage(anim, playerColoredButton);
+	setImage(anim);
 }
 
-void CButton::setImage(std::shared_ptr<CAnimation> anim, bool playerColoredButton, int animFlags)
+void CButton::setImage(std::shared_ptr<CAnimation> anim, int animFlags)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 
 	image = std::make_shared<CAnimImage>(anim, getState(), 0, 0, 0, animFlags);
-	if (playerColoredButton)
-		image->playerColored(LOCPLINT->playerID);
 	pos = image->pos;
 }
 
 void CButton::setPlayerColor(PlayerColor player)
 {
-	if (image)
+	if (image && image->isPlayerColored())
 		image->playerColored(player);
 }
 

+ 2 - 8
client/widgets/Buttons.h

@@ -16,13 +16,7 @@
 #include <SDL_pixels.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
-
-namespace config
-{
-struct ButtonInfo;
-}
 class Rect;
-
 VCMI_LIB_NAMESPACE_END
 
 struct SDL_Surface;
@@ -104,8 +98,8 @@ public:
 			CFunctionList<void()> Callback = 0, EShortcut key = {}, bool playerColoredButton = false );
 
 	/// Appearance modifiers
-	void setIndex(size_t index, bool playerColoredButton=false);
-	void setImage(std::shared_ptr<CAnimation> anim, bool playerColoredButton=false, int animFlags=0);
+	void setIndex(size_t index);
+	void setImage(std::shared_ptr<CAnimation> anim, int animFlags=0);
 	void setPlayerColor(PlayerColor player);
 
 	/// CIntObject overrides

+ 52 - 11
client/widgets/Images.cpp

@@ -16,6 +16,7 @@
 #include "../renderSDL/SDL_Extensions.h"
 #include "../render/IImage.h"
 #include "../render/CAnimation.h"
+#include "../render/ColorFilter.h"
 
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
@@ -109,6 +110,16 @@ void CPicture::colorize(PlayerColor player)
 CFilledTexture::CFilledTexture(std::string imageName, Rect position):
     CIntObject(0, position.topLeft()),
 	texture(IImage::createFromFile(imageName))
+{
+	pos.w = position.w;
+	pos.h = position.h;
+	imageArea = Rect(Point(), texture->dimensions());
+}
+
+CFilledTexture::CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea)
+	: CIntObject(0, position.topLeft())
+	, texture(image)
+	, imageArea(imageArea)
 {
 	pos.w = position.w;
 	pos.h = position.h;
@@ -118,17 +129,45 @@ void CFilledTexture::showAll(SDL_Surface *to)
 {
 	CSDL_Ext::CClipRectGuard guard(to, pos);
 
-	for (int y=pos.top(); y < pos.bottom(); y+= texture->height())
+	for (int y=pos.top(); y < pos.bottom(); y+= imageArea.h)
+	{
+		for (int x=pos.left(); x < pos.right(); x+= imageArea.w)
+			texture->draw(to, x, y, &imageArea);
+	}
+}
+
+FilledTexturePlayerColored::FilledTexturePlayerColored(std::string imageName, Rect position)
+	: CFilledTexture(imageName, position)
+{
+}
+
+void FilledTexturePlayerColored::playerColored(PlayerColor player)
+{
+	// Color transform to make color of brown DIBOX.PCX texture match color of specified player
+	std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
+		ColorFilter::genRangeShifter(  0.25,  0,     0,     1.25, 0.00, 0.00 ), // red
+		ColorFilter::genRangeShifter(  0,     0,     0,     0.45, 1.20, 4.50 ), // blue
+		ColorFilter::genRangeShifter(  0.40,  0.27,  0.23,  1.10, 1.20, 1.15 ), // tan
+		ColorFilter::genRangeShifter( -0.27,  0.10, -0.27,  0.70, 1.70, 0.70 ), // green
+		ColorFilter::genRangeShifter(  0.47,  0.17, -0.27,  1.60, 1.20, 0.70 ), // orange
+		ColorFilter::genRangeShifter(  0.12, -0.1,   0.25,  1.15, 1.20, 2.20 ), // purple
+		ColorFilter::genRangeShifter( -0.13,  0.23,  0.23,  0.90, 1.20, 2.20 ), // teal
+		ColorFilter::genRangeShifter(  0.44,  0.15,  0.25,  1.00, 1.00, 1.75 )  // pink
+	};
+
+	assert(player.isValidPlayer());
+	if (!player.isValidPlayer())
 	{
-		for (int x=pos.left(); x < pos.right(); x+=texture->width())
-			texture->draw(to, x, y);
+		logGlobal->error("Unable to colorize to invalid player color %d!", static_cast<int>(player.getNum()));
+		return;
 	}
+
+	texture->adjustPalette(filters[player.getNum()], 0);
 }
 
 CAnimImage::CAnimImage(const std::string & name, size_t Frame, size_t Group, int x, int y, ui8 Flags):
 	frame(Frame),
 	group(Group),
-	player(-1),
 	flags(Flags)
 {
 	pos.x += x;
@@ -141,7 +180,6 @@ CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Gr
 	anim(Anim),
 	frame(Frame),
 	group(Group),
-	player(-1),
 	flags(Flags)
 {
 	pos.x += x;
@@ -153,7 +191,6 @@ CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targ
 	anim(Anim),
 	frame(Frame),
 	group(Group),
-	player(-1),
 	flags(Flags),
 	scaledSize(targetPos.w, targetPos.h)
 {
@@ -242,8 +279,8 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
 		group = Group;
 		if(auto img = anim->getImage(frame, group))
 		{
-			if (flags & CShowableAnim::PLAYER_COLORED)
-				img->playerColored(player);
+			if (player.has_value())
+				img->playerColored(*player);
 			setSizeFromImage(*img);
 		}
 	}
@@ -254,10 +291,14 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
 void CAnimImage::playerColored(PlayerColor currPlayer)
 {
 	player = currPlayer;
-	flags |= CShowableAnim::PLAYER_COLORED;
-	anim->getImage(frame, group)->playerColored(player);
+	anim->getImage(frame, group)->playerColored(*player);
 	if (flags & CShowableAnim::BASE)
-			anim->getImage(0, group)->playerColored(player);
+			anim->getImage(0, group)->playerColored(*player);
+}
+
+bool CAnimImage::isPlayerColored() const
+{
+	return player.has_value();
 }
 
 CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):

+ 20 - 5
client/widgets/Images.h

@@ -68,14 +68,25 @@ public:
 /// area filled with specific texture
 class CFilledTexture : public CIntObject
 {
+protected:
 	std::shared_ptr<IImage> texture;
+	Rect imageArea;
 
 public:
 	CFilledTexture(std::string imageName, Rect position);
+	CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea);
 
 	void showAll(SDL_Surface *to) override;
 };
 
+class FilledTexturePlayerColored : public CFilledTexture
+{
+public:
+	FilledTexturePlayerColored(std::string imageName, Rect position);
+
+	void playerColored(PlayerColor player);
+};
+
 /// Class for displaying one image from animation
 class CAnimImage: public CIntObject
 {
@@ -84,10 +95,12 @@ private:
 	//displayed frame/group
 	size_t frame;
 	size_t group;
-	PlayerColor player;
 	ui8 flags;
 	const Point scaledSize;
 
+	/// If set, then image is colored using player-specific palette
+	std::optional<PlayerColor> player;
+
 	bool isScaled() const;
 	void setSizeFromImage(const IImage &img);
 	void init();
@@ -99,15 +112,18 @@ public:
 	CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0);
 	~CAnimImage();
 
-	//size of animation
+	/// size of animation
 	size_t size();
 
-	//change displayed frame on this one
+	/// change displayed frame on this one
 	void setFrame(size_t Frame, size_t Group=0);
 
-	//makes image player-colored
+	/// makes image player-colored to specific player
 	void playerColored(PlayerColor player);
 
+	/// returns true if image has player-colored effect applied
+	bool isPlayerColored() const;
+
 	void showAll(SDL_Surface * to) override;
 };
 
@@ -120,7 +136,6 @@ public:
 		BASE=1,            //base frame will be blitted before current one
 		HORIZONTAL_FLIP=2, //TODO: will be displayed rotated
 		VERTICAL_FLIP=4,   //TODO: will be displayed rotated
-		PLAYER_COLORED=16, //TODO: all loaded images will be player-colored
 		PLAY_ONCE=32       //play animation only once and stop at last frame
 	};
 protected:

+ 18 - 7
client/widgets/TextControls.cpp

@@ -399,7 +399,7 @@ void CGStatusBar::clear()
 	write({});
 }
 
-CGStatusBar::CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font, ETextAlignment Align, const SDL_Color & Color)
+CGStatusBar::CGStatusBar(std::shared_ptr<CIntObject> background_, EFonts Font, ETextAlignment Align, const SDL_Color & Color)
 	: CLabel(background_->pos.x, background_->pos.y, Font, Align, Color, "")
 	, enteringText(false)
 {
@@ -419,26 +419,29 @@ CGStatusBar::CGStatusBar(int x, int y, std::string name, int maxw)
 	addUsedEvents(LCLICK);
 
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
-	background = std::make_shared<CPicture>(name);
+	auto backgroundImage = std::make_shared<CPicture>(name);
+	background = backgroundImage;
 	pos = background->pos;
 
 	if((unsigned)maxw < (unsigned)pos.w) //(insigned)-1 > than any correct value of pos.w
 	{
 		//execution of this block when maxw is incorrect breaks text centralization (issue #3151)
 		vstd::amin(pos.w, maxw);
-		background->srcRect = Rect(0, 0, maxw, pos.h);
+		backgroundImage->srcRect = Rect(0, 0, maxw, pos.h);
 	}
 	autoRedraw = false;
 }
 
-void CGStatusBar::show(SDL_Surface * to)
+CGStatusBar::~CGStatusBar()
 {
-	showAll(to);
+	assert(GH.statusbar.get() != this || GH.statusbar == nullptr);
+	if (GH.statusbar.get() == this)
+		GH.statusbar = nullptr;
 }
 
-void CGStatusBar::init()
+void CGStatusBar::show(SDL_Surface * to)
 {
-	GH.statusbar = shared_from_this();
+	showAll(to);
 }
 
 void CGStatusBar::clickLeft(tribool down, bool previousState)
@@ -450,8 +453,16 @@ void CGStatusBar::clickLeft(tribool down, bool previousState)
 	}
 }
 
+void CGStatusBar::activate()
+{
+	GH.statusbar = shared_from_this();
+	CIntObject::deactivate();
+}
+
 void CGStatusBar::deactivate()
 {
+	assert(GH.statusbar.get() == this);
+
 	if (enteringText)
 		LOCPLINT->cingconsole->endEnteringText(false);
 

+ 5 - 5
client/widgets/TextControls.h

@@ -45,7 +45,7 @@ protected:
 	Point getBorderSize() override;
 	virtual std::string visibleText();
 
-	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CIntObject> background;
 	std::string text;
 	bool autoRedraw;  //whether control will redraw itself on setTxt
 
@@ -125,9 +125,7 @@ class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusB
 	std::string consoleText;
 	bool enteringText;
 
-	void init();
-
-	CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
+	CGStatusBar(std::shared_ptr<CIntObject> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
 	CGStatusBar(int x, int y, std::string name, int maxw = -1);
 
 	//make CLabel API private
@@ -143,15 +141,17 @@ protected:
 	void clickLeft(tribool down, bool previousState) override;
 
 public:
+	~CGStatusBar();
+
 	template<typename ...Args>
 	static std::shared_ptr<CGStatusBar> create(Args... args)
 	{
 		std::shared_ptr<CGStatusBar> ret{new CGStatusBar{args...}};
-		ret->init();
 		return ret;
 	}
 
 	void show(SDL_Surface * to) override;
+	void activate() override;
 	void deactivate() override;
 
 	// IStatusBar interface

+ 6 - 3
client/windows/CCastleInterface.cpp

@@ -29,7 +29,7 @@
 #include "../renderSDL/SDL_Extensions.h"
 #include "../render/IImage.h"
 #include "../render/ColorFilter.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../adventureMap/CList.h"
 #include "../adventureMap/CResDataBar.h"
 
@@ -1198,9 +1198,12 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	Rect barRect(9, 182, 732, 18);
 	auto statusbarBackground = std::make_shared<CPicture>(panel->getSurface(), barRect, 9, 555);
 	statusbar = CGStatusBar::create(statusbarBackground);
-	resdatabar = std::make_shared<CResDataBar>("ARESBAR", 3, 575, 32, 2, 85, 85);
+	resdatabar = std::make_shared<CResDataBar>("ARESBAR", 3, 575, 37, 3, 84, 78);
+
+	townlist = std::make_shared<CTownList>(3, Rect(Point(743, 414), Point(48, 128)), Point(1,16), Point(0, 32), LOCPLINT->localState->getOwnedTowns().size() );
+	townlist->setScrollUpButton( std::make_shared<CButton>( Point(744, 414), "IAM014", CButton::tooltipLocalized("core.help.306")));
+	townlist->setScrollDownButton( std::make_shared<CButton>( Point(744, 526), "IAM015", CButton::tooltipLocalized("core.help.307")));
 
-	townlist = std::make_shared<CTownList>(3, Point(744, 414), "IAM014", "IAM015");
 	if(from)
 		townlist->select(from);
 

+ 14 - 11
client/windows/CKingdomInterface.cpp

@@ -39,6 +39,9 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
 
+static const std::string OVERVIEW_BACKGROUND = "OvCast.pcx";
+static const size_t OVERVIEW_SIZE = 4;
+
 InfoBox::InfoBox(Point position, InfoPos Pos, InfoSize Size, std::shared_ptr<IInfoBoxData> Data):
 	size(Size),
 	infoPos(Pos),
@@ -468,10 +471,10 @@ void InfoBoxCustom::prepareMessage(std::string & text, std::shared_ptr<CComponen
 }
 
 CKingdomInterface::CKingdomInterface()
-	: CWindowObject(PLAYER_COLORED | BORDERED, conf.go()->ac.overviewBg)
+	: CWindowObject(PLAYER_COLORED | BORDERED, OVERVIEW_BACKGROUND)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	ui32 footerPos = conf.go()->ac.overviewSize * 116;
+	ui32 footerPos = OVERVIEW_SIZE * 116;
 
 	tabArea = std::make_shared<CTabbedInt>(std::bind(&CKingdomInterface::createMainTab, this, _1), Point(4,4));
 
@@ -481,12 +484,12 @@ CKingdomInterface::CKingdomInterface()
 	generateButtons();
 
 	statusbar = CGStatusBar::create(std::make_shared<CPicture>("KSTATBAR", 10,pos.h - 45));
-	resdatabar = std::make_shared<CResDataBar>("KRESBAR", 3, 111+footerPos, 32, 2, 76, 76);
+	resdatabar = std::make_shared<CResDataBar>("KRESBAR", 7, 111+footerPos, 29, 5, 76, 81);
 }
 
 void CKingdomInterface::generateObjectsList(const std::vector<const CGObjectInstance * > &ownedObjects)
 {
-	ui32 footerPos = conf.go()->ac.overviewSize * 116;
+	ui32 footerPos = OVERVIEW_SIZE * 116;
 	size_t dwellSize = (footerPos - 64)/57;
 
 	//Map used to determine image number for several objects
@@ -550,7 +553,7 @@ std::shared_ptr<CIntObject> CKingdomInterface::createOwnedObject(size_t index)
 
 std::shared_ptr<CIntObject> CKingdomInterface::createMainTab(size_t index)
 {
-	size_t size = conf.go()->ac.overviewSize;
+	size_t size = OVERVIEW_SIZE;
 	switch(index)
 	{
 	case 0:
@@ -564,7 +567,7 @@ std::shared_ptr<CIntObject> CKingdomInterface::createMainTab(size_t index)
 
 void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstance *> & ownedObjects)
 {
-	ui32 footerPos = conf.go()->ac.overviewSize * 116;
+	ui32 footerPos = OVERVIEW_SIZE * 116;
 	TResources minesCount(GameConstants::RESOURCE_QUANTITY, 0);
 	int totalIncome=0;
 
@@ -610,7 +613,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
 
 void CKingdomInterface::generateButtons()
 {
-	ui32 footerPos = conf.go()->ac.overviewSize * 116;
+	ui32 footerPos = OVERVIEW_SIZE * 116;
 
 	//Main control buttons
 	btnHeroes = std::make_shared<CButton>(Point(748, 28+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->overview[11], CGI->generaltexth->overview[6]),
@@ -689,7 +692,7 @@ CKingdHeroList::CKingdHeroList(size_t maxSize)
 	skillsLabel = std::make_shared<CLabel>(500, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[1]);
 
 	ui32 townCount = LOCPLINT->cb->howManyHeroes(false);
-	ui32 size = conf.go()->ac.overviewSize*116 + 19;
+	ui32 size = OVERVIEW_SIZE*116 + 19;
 	heroes = std::make_shared<CListBox>(std::bind(&CKingdHeroList::createHeroItem, this, _1),
 		Point(19,21), Point(0,116), maxSize, townCount, 0, 1, Rect(-19, -21, size, size));
 }
@@ -705,7 +708,7 @@ void CKingdHeroList::updateGarrisons()
 
 std::shared_ptr<CIntObject> CKingdHeroList::createHeroItem(size_t index)
 {
-	ui32 picCount = conf.go()->ac.overviewPics;
+	ui32 picCount = 4; // OVSLOT contains 4 images
 	size_t heroesCount = LOCPLINT->cb->howManyHeroes(false);
 
 	if(index < heroesCount)
@@ -730,7 +733,7 @@ CKingdTownList::CKingdTownList(size_t maxSize)
 	visitHeroLabel = std::make_shared<CLabel>(608, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[5]);
 
 	ui32 townCount = LOCPLINT->cb->howManyTowns();
-	ui32 size = conf.go()->ac.overviewSize*116 + 19;
+	ui32 size = OVERVIEW_SIZE*116 + 19;
 	towns = std::make_shared<CListBox>(std::bind(&CKingdTownList::createTownItem, this, _1),
 		Point(19,21), Point(0,116), maxSize, townCount, 0, 1, Rect(-19, -21, size, size));
 }
@@ -756,7 +759,7 @@ void CKingdTownList::updateGarrisons()
 
 std::shared_ptr<CIntObject> CKingdTownList::createTownItem(size_t index)
 {
-	ui32 picCount = conf.go()->ac.overviewPics;
+	ui32 picCount = 4; // OVSLOT contains 4 images
 	size_t townsCount = LOCPLINT->cb->howManyTowns();
 
 	if(index < townsCount)

+ 1 - 1
client/windows/CQuestLog.cpp

@@ -16,7 +16,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../widgets/CComponent.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../widgets/Buttons.h"
 #include "../adventureMap/CMinimap.h"
 #include "../renderSDL/SDL_Extensions.h"

+ 1 - 1
client/windows/CSpellWindow.cpp

@@ -27,7 +27,7 @@
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/TextControls.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../render/CAnimation.h"
 #include "../renderSDL/SDL_Extensions.h"
 

+ 0 - 1
client/windows/CTradeWindow.cpp

@@ -332,7 +332,6 @@ CTradeWindow::CTradeWindow(std::string bgName, const IMarket *Market, const CGHe
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	type |= BLOCK_ADV_HOTKEYS;
 	mode = Mode;
 	initTypes();
 }

+ 0 - 6
client/windows/CWindowObject.cpp

@@ -248,9 +248,3 @@ CStatusbarWindow::CStatusbarWindow(int options, std::string imageName, Point cen
 CStatusbarWindow::CStatusbarWindow(int options, std::string imageName) : CWindowObject(options, imageName)
 {
 }
-
-void CStatusbarWindow::activate()
-{
-	CIntObject::activate();
-	GH.statusbar = statusbar;
-}

+ 0 - 1
client/windows/CWindowObject.h

@@ -58,7 +58,6 @@ class CStatusbarWindow : public CWindowObject
 public:
 	CStatusbarWindow(int options, std::string imageName, Point centerAt);
 	CStatusbarWindow(int options, std::string imageName = "");
-	void activate() override;
 protected:
 	std::shared_ptr<CGStatusBar> statusbar;
 };

+ 0 - 1
client/windows/GUIClasses.cpp

@@ -1619,7 +1619,6 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner):
 	owner(_owner)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	type |= BLOCK_ADV_HOTKEYS;
 
 	SThievesGuildInfo tgi; //info to be displayed
 	LOCPLINT->cb->getThievesGuildInfo(tgi, owner);

+ 0 - 1
client/windows/GUIClasses.h

@@ -12,7 +12,6 @@
 #include "CWindowObject.h"
 #include "../lib/GameConstants.h"
 #include "../lib/ResourceSet.h"
-#include "../lib/CConfigHandler.h"
 #include "../lib/int3.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"

+ 1 - 2
client/windows/InfoWindows.cpp

@@ -22,7 +22,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
-#include "../adventureMap/CAdventureMapInterface.h"
+#include "../adventureMap/AdventureMapInterface.h"
 #include "../windows/CMessage.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../gui/CursorHandler.h"
@@ -119,7 +119,6 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	type |= BLOCK_ADV_HOTKEYS;
 	ID = QueryID(-1);
 	for(auto & Button : Buttons)
 	{

+ 77 - 54
client/windows/settings/GeneralOptionsTab.cpp

@@ -22,7 +22,7 @@
 #include "CPlayerInterface.h"
 #include "windows/GUIClasses.h"
 #include "CServerHandler.h"
-#include "renderSDL/SDL_Extensions.h"
+#include "render/IScreenHandler.h"
 
 
 static void setIntSetting(std::string group, std::string field, int value)
@@ -37,6 +37,19 @@ static void setBoolSetting(std::string group, std::string field, bool value)
 	entry->Bool() = value;
 }
 
+static std::string scalingToEntryString( int scaling)
+{
+	return std::to_string(scaling) + '%';
+}
+
+static std::string scalingToLabelString( int scaling)
+{
+	std::string string = CGI->generaltexth->translate("vcmi.systemOptions.scalingButton.hover");
+	boost::replace_all(string, "%p", std::to_string(scaling));
+
+	return string;
+}
+
 static std::string resolutionToEntryString( int w, int h)
 {
 	std::string string = "%wx%h";
@@ -96,6 +109,10 @@ GeneralOptionsTab::GeneralOptionsTab()
 	{
 		selectGameResolution();
 	});
+	addCallback("setGameScaling", [this](int dummyValue)
+	{
+		selectGameScaling();
+	});
 	addCallback("framerateChanged", [](bool value)
 	{
 		setBoolSetting("video", "showfps", value);
@@ -114,10 +131,14 @@ GeneralOptionsTab::GeneralOptionsTab()
 
 	build(config);
 
+	const auto & currentResolution = settings["video"]["resolution"];
+
 	std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel");
-	const auto & currentResolution = settings["video"]["screenRes"];
 	resolutionLabel->setText(resolutionToLabelString(currentResolution["width"].Integer(), currentResolution["height"].Integer()));
 
+	std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
+	scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer()));
+
 	std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
 	spellbookAnimationCheckbox->setSelected(settings["video"]["spellbookAnimation"].Bool());
 
@@ -149,32 +170,25 @@ GeneralOptionsTab::GeneralOptionsTab()
 	std::shared_ptr<CLabel> soundVolumeLabel = widget<CLabel>("soundValueLabel");
 	soundVolumeLabel->setText(std::to_string(CCS->soundh->getVolume()) + "%");
 
-}
-
-
-bool GeneralOptionsTab::isResolutionSupported(const Point & resolution)
-{
-	return isResolutionSupported( resolution, settings["video"]["fullscreen"].Bool());
-}
-
-bool GeneralOptionsTab::isResolutionSupported(const Point & resolution, bool fullscreen)
-{
-	if (!fullscreen)
-		return true;
-
-	auto supportedList = CSDL_Ext::getSupportedResolutions();
+#ifdef VCMI_MOBILE
+	// On mobile platforms, VCMI always uses OS screen resolutions
+	// Players can control UI size via "Interface Scaling" option instead
+	std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton");
 
-	return CSDL_Ext::isResolutionSupported(supportedList, resolution);
+	resolutionButton->disable();
+	resolutionLabel->disable();
+	fullscreenCheckbox->block(true);
+#endif
 }
 
 void GeneralOptionsTab::selectGameResolution()
 {
-	fillSelectableResolutions();
+	supportedResolutions = GH.screenHandler().getSupportedResolutions();
 
 	std::vector<std::string> items;
 	size_t currentResolutionIndex = 0;
 	size_t i = 0;
-	for(const auto & it : selectableResolutions)
+	for(const auto & it : supportedResolutions)
 	{
 		auto resolutionStr = resolutionToEntryString(it.x, it.y);
 		if(widget<CLabel>("resolutionLabel")->getText() == resolutionToLabelString(it.x, it.y))
@@ -195,14 +209,14 @@ void GeneralOptionsTab::selectGameResolution()
 
 void GeneralOptionsTab::setGameResolution(int index)
 {
-	assert(index >= 0 && index < selectableResolutions.size());
+	assert(index >= 0 && index < supportedResolutions.size());
 
-	if ( index < 0 || index >= selectableResolutions.size() )
+	if ( index < 0 || index >= supportedResolutions.size() )
 		return;
 
-	Point resolution = selectableResolutions[index];
+	Point resolution = supportedResolutions[index];
 
-	Settings gameRes = settings.write["video"]["screenRes"];
+	Settings gameRes = settings.write["video"]["resolution"];
 	gameRes["width"].Float() = resolution.x;
 	gameRes["height"].Float() = resolution.y;
 
@@ -211,48 +225,57 @@ void GeneralOptionsTab::setGameResolution(int index)
 
 void GeneralOptionsTab::setFullscreenMode(bool on)
 {
-	fillSelectableResolutions();
+	setBoolSetting("video", "fullscreen", on);
+}
 
-	const auto & screenRes = settings["video"]["screenRes"];
-	const Point desiredResolution(screenRes["width"].Integer(), screenRes["height"].Integer());
-	const Point currentResolution = GH.screenDimensions();
+void GeneralOptionsTab::selectGameScaling()
+{
+	supportedScaling.clear();
 
-	if (!isResolutionSupported(currentResolution, on))
+	auto [minimalScaling, maximalScaling] = GH.screenHandler().getSupportedScalingRange();
+	for (int i = 0; i <= maximalScaling; i += 10)
 	{
-		widget<CToggleButton>("fullscreenCheckbox")->setSelected(!on);
-		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.systemOptions.fullscreenFailed"));
-		return;
+		if (i >= minimalScaling)
+			supportedScaling.push_back(i);
 	}
 
-	setBoolSetting("video", "fullscreen", on);
-
-	if (!isResolutionSupported(desiredResolution, on))
+	std::vector<std::string> items;
+	size_t currentIndex = 0;
+	size_t i = 0;
+	for(const auto & it : supportedScaling)
 	{
-		// user changed his desired resolution and switched to fullscreen
-		// however resolution he selected before is not available in fullscreen
-		// so reset it back to currect resolution which is confirmed to be supported earlier
-		Settings gameRes = settings.write["video"]["screenRes"];
-		gameRes["width"].Float() = currentResolution.x;
-		gameRes["height"].Float() = currentResolution.y;
-
-		widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(currentResolution.x, currentResolution.y));
+		auto resolutionStr = scalingToEntryString(it);
+		if(widget<CLabel>("scalingLabel")->getText() == scalingToLabelString(it))
+			currentIndex = i;
+
+		items.push_back(std::move(resolutionStr));
+		++i;
 	}
+
+	GH.pushIntT<CObjectListWindow>(
+		items,
+		nullptr,
+		CGI->generaltexth->translate("vcmi.systemOptions.scalingMenu.hover"),
+		CGI->generaltexth->translate("vcmi.systemOptions.scalingMenu.help"),
+		[this](int index)
+		{
+			setGameScaling(index);
+		},
+		currentIndex
+	);
 }
 
-void GeneralOptionsTab::fillSelectableResolutions()
+void GeneralOptionsTab::setGameScaling(int index)
 {
-	selectableResolutions.clear();
+	assert(index >= 0 && index < supportedScaling.size());
 
-	for(const auto & it : conf.guiOptions)
-	{
-		const Point dimensions(it.first.first, it.first.second);
+	if ( index < 0 || index >= supportedScaling.size() )
+		return;
 
-		if(isResolutionSupported(dimensions))
-			selectableResolutions.push_back(dimensions);
-	}
+	int scaling = supportedScaling[index];
 
-	boost::range::sort(selectableResolutions, [](const auto & left, const auto & right)
-	{
-		return left.x * left.y < right.x * right.y;
-	});
+	Settings gameRes = settings.write["video"]["resolution"];
+	gameRes["scaling"].Float() = scaling;
+
+	widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
 }

+ 5 - 5
client/windows/settings/GeneralOptionsTab.h

@@ -19,16 +19,16 @@ private:
 	SettingsListener onFullscreenChanged;
 
 	std::vector<Point> supportedResolutions;
-	std::vector<Point> selectableResolutions;
+	std::vector<int> supportedScaling;
 
 	void setFullscreenMode( bool on);
-	void fillSelectableResolutions();
-	bool isResolutionSupported(const Point & resolution);
-	bool isResolutionSupported(const Point & resolution, bool fullscreen);
 
 	void selectGameResolution();
 	void setGameResolution(int index);
 
+	void selectGameScaling();
+	void setGameScaling(int index);
+
 public:
 	GeneralOptionsTab();
-};
+};

+ 0 - 33
config/resolutions.json

@@ -1,33 +0,0 @@
-{
-	"GUISettings":
-	[
-		{
-			"resolution": { "x": 800, "y": 600 },
-			"InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 60 },
-			"AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1, "objectFading" : 1, "screenFading" : 1 },
-			"InfoBox": { "x": 605, "y": 389 },
-			"gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" },
-			"gem1": { "x": 556, "y": 508, "graphic": "agemLR.def" },
-			"gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" },
-			"gem3": { "x": 556, "y": 6, "graphic": "agemUR.def" },
-			"background": "AdvMap.bmp",
-			"backgroundWorldView": "VWorld.bmp",
-			"HeroList": { "size": 5, "x": 609, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" },
-			"TownList": { "size": 5, "x": 747, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" },
-			"Minimap": { "width": 144, "height": 144, "x": 630, "y": 26 },
-			"Overview": { "pics": 4, "size": 4, "graphic": "OvCast.pcx" },
-			"Statusbar": { "x": 7, "y": 556, "graphic": "AdRollvr.bmp" },
-			"ResDataBar": { "x": 3, "y": 575, "graphic": "ARESBAR.bmp", "offsetX": 32, "offsetY": 2, "resSpace": 85, "resDateSpace": 85 },
-			"ButtonKingdomOv": { "x": 679, "y": 196, "graphic": "IAM002.DEF", "playerColoured": 1 },
-			"ButtonUnderground": { "x": 711, "y": 196, "graphic": "IAM010.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003.DEF" ] },
-			"ButtonQuestLog": { "x": 679, "y": 228, "graphic": "IAM004.DEF", "playerColoured": 1 },
-			"ButtonSleepWake": { "x": 711, "y": 228, "graphic": "IAM005.DEF", "playerColoured": 1, "additionalDefs":["IAM011.DEF"] },
-			"ButtonMoveHero": { "x": 679, "y": 260, "graphic": "IAM006.DEF", "playerColoured": 1 },
-			"ButtonSpellbook": { "x": 711, "y": 260, "graphic": "IAM007.DEF", "playerColoured": 1 },
-			"ButtonAdvOptions": { "x": 679, "y": 292, "graphic": "IAM008.DEF", "playerColoured": 1 },
-			"ButtonSysOptions": { "x": 711, "y": 292, "graphic": "IAM009.DEF", "playerColoured": 1 },
-			"ButtonNextHero": { "x": 679, "y": 324, "graphic": "IAM000.DEF", "playerColoured": 1 },
-			"ButtonEndTurn": { "x": 679, "y": 356, "graphic": "IAM001.DEF", "playerColoured": 1 }
-		}
-	]
-}

+ 6 - 5
config/schemas/settings.json

@@ -111,7 +111,7 @@
 			"additionalProperties" : false,
 			"default": {},
 			"required" : [ 
-				"screenRes", 
+				"resolution", 
 				"bitsPerPixel", 
 				"fullscreen", 
 				"realFullscreen", 
@@ -124,15 +124,16 @@
 				"targetfps"
 			],
 			"properties" : {
-				"screenRes" : {
+				"resolution" : {
 					"type" : "object",
 					"additionalProperties" : false,
-					"required" : [ "width", "height" ],
+					"required" : [ "width", "height", "scaling" ],
 					"properties" : {
 						"width"  : { "type" : "number" },
-						"height" : { "type" : "number" }
+						"height" : { "type" : "number" },
+						"scaling" : { "type" : "number" }
 					},
-					"default": {"width" : 800, "height": 600 }
+					"default": {"width" : 800, "height": 600, "scaling" : 100 }
 				},
 				"bitsPerPixel" : {
 					"type" : "number",

+ 869 - 0
config/widgets/adventureMap.json

@@ -0,0 +1,869 @@
+{
+	"options" : {
+		// player-colored images used for background
+		"imagesPlayerColored" : [ "AdvMap.pcx" ],
+	},
+
+	"items":
+	[
+		// Background sections - left side
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundLeftTop",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 0, "top" : 0, "width" : 7, "height" : 52 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundLeftCenter",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 0, "top" : 52, "width" : 7, "bottom" : 91 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundLeftBottom",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 0, "bottom" : 0, "width" : 7, "height" : 91 }
+		},
+		// Background sections - top side
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundTopLeft",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 7, "top" : 0, "width" : 193, "height" : 7 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundTopCenter",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 200, "top" : 0, "right" : 244, "height" : 7 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundTopRight",
+			"image" : "AdvMap.pcx",
+			"area" : { "width": 45, "top" : 0, "right" : 199, "height" : 7 }
+		},
+		// Background sections - bottom side
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundBottomLeft",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 7, "bottom" : 0, "width" : 44, "height" : 47 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundBottomCenter",
+			"image" : "AdvMap.pcx",
+			"area" : { "left": 51, "bottom" : 0, "right" : 244, "height" : 47 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundBottomRight",
+			"image" : "AdvMap.pcx",
+			"area" : { "width": 45, "bottom" : 0, "right" : 199, "height" : 47 }
+		},
+		// Background sections - side panel
+		{
+			"type": "adventureMapImage",
+			"name" : "backgroundRightMinimap",
+			"image" : "AdvMap.pcx",
+			"area" : { "right": 0, "top" : 0, "width" : 199, "height" : 196 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name": "backgroundHeroListBorderLeft",
+			"image" : "AdvMap.pcx",
+			"area": { "top": 196, "bottom" : 211, "right" : 191, "width" : 8 }
+		},
+		{
+			"type": "adventureMapImage",
+			"name": "backgroundTownListBorderRight",
+			"image" : "AdvMap.pcx",
+			"area": { "top": 196, "bottom" : 211, "right" : 0, "width" : 5 }
+		},
+		// Game area
+		{
+			"type": "adventureMapGameArea",
+			"name": "mapView",
+			"area": { "top": 7, "bottom" : 47, "left" : 7, "right" : 199 }
+		},
+		
+		// Minimap
+		{
+			"type": "adventureMinimap",
+			"name": "minimap",
+			"area": { "top": 26, "right" : 26, "width" : 144, "height" : 144 }
+		},
+
+		// Adventure map buttons
+		{
+			"type": "adventureMapContainer",
+			"name" : "buttonsContainer",
+			"hideWhen" : "worldViewMode",
+			"area": { "top": 196, "right" : 57, "width" : 64, "height" : 192 },
+			"items" : [
+				{
+					"type" : "adventureMapButton",
+					"name" : "buttonKingdomOverview",
+					"image" : "IAM002.DEF",
+					"help" : "core.help.293",
+					"hotkey": "adventureKingdomOverview",
+					"playerColored" : true,
+					"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapContainer",
+					"hideWhen" : "mapLayerSurface",
+					"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 },
+					"items" : [
+						{
+							"type": "adventureMapButton",
+							"name": "buttonUnderground",
+							"image" : "IAM010.DEF",
+							"help" : "core.help.294",
+							"hotkey": "adventureToggleMapLevel",
+							"playerColored" : true,
+							"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
+						}
+					],
+				},
+				{
+					"type": "adventureMapContainer",
+					"hideWhen" : "mapLayerUnderground",
+					"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 },
+					"items" : [
+						{
+							"type": "adventureMapButton",
+							"name": "buttonSurface",
+							"image" : "IAM003.DEF",
+							"help" : "core.help.294",
+							"hotkey": "adventureToggleMapLevel",
+							"playerColored" : true,
+							"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
+						}
+					],
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonQuestLog",
+					"image" : "IAM004.DEF",
+					"help" : "core.help.295",
+					"hotkey": "adventureQuestLog",
+					"playerColored" : true,
+					"area": { "top" : 32, "left": 0, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapContainer",
+					"hideWhen" : "heroAwake",
+					"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 },
+					"items" : [
+						{
+							"type": "adventureMapButton",
+							"name": "buttonSleep",
+							"image" : "IAM005.DEF",
+							"help" : "core.help.296",
+							"hotkey": "adventureSetHeroAsleep",
+							"playerColored" : true,
+							"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
+						}
+					]
+				},
+				{
+					"type": "adventureMapContainer",
+					"hideWhen" : "heroSleeping",
+					"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 },
+					"items" : [
+						{
+							"type": "adventureMapButton",
+							"name": "buttonWake",
+							"image" : "IAM011.DEF",
+							"help" : "core.help.296",
+							"hotkey": "adventureSetHeroAwake",
+							"playerColored" : true,
+							"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
+						}
+					]
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonMove",
+					"image" : "IAM006.DEF",
+					"help" : "core.help.297",
+					"hotkey": "adventureMoveHero",
+					"playerColored" : true,
+					"area": { "top" : 64, "left": 0, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonCast",
+					"image" : "IAM007.DEF",
+					"help" : "core.help.298",
+					"hotkey": "adventureCastSpell",
+					"playerColored" : true,
+					"area": { "top" : 64, "left": 32, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonAdventureOptions",
+					"image" : "IAM008.DEF",
+					"help" : "core.help.299",
+					"hotkey": "adventureGameOptions",
+					"playerColored" : true,
+					"area": { "top" : 96, "left": 0, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonSystemOptions",
+					"image" : "IAM009.DEF",
+					"help" : "core.help.300",
+					"hotkey": "globalOptions",
+					"playerColored" : true,
+					"area": { "top" : 96, "left": 32, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonNextHero",
+					"image" : "IAM000.DEF",
+					"help" : "core.help.301",
+					"hotkey": "adventureNextHero",
+					"playerColored" : true,
+					"area": { "top" : 128, "left": 0, "width" : 64, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "buttonEndTurn",
+					"image" : "IAM001.DEF",
+					"hotkey": "gameEndTurn",
+					"help" : "core.help.302",
+					"playerColored" : true,
+					"area": { "top" : 160, "left": 0, "width" : 64, "height" : 32 }
+				}
+			]
+		},
+		// Town / Hero lists for small (600-664) vertical resolution
+		{
+			"type": "adventureMapContainer",
+			"name" : "listContainerSmall",
+			"hideWhen" : "worldViewMode",
+			"area": { "top": 196, "right" : 0, "width" : 193, "height" : 196 },
+			"exists" : { "heightMax" : 664 },
+			"items" : [
+				{
+					"type": "adventureMapImage",
+					"name": "backgroundHeroListBorderRight",
+					"image" : "AdvMap.pcx",
+					"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 },
+					"sourceArea": { "top": 196, "bottom" : 211, "right" : 121, "width" : 6 }
+				},
+				{
+					"type": "adventureMapImage",
+					"name": "backgroundTownListBorderLeft",
+					"image" : "AdvMap.pcx",
+					"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 },
+					"sourceArea": { "top": 196, "bottom" : 211, "right" : 53, "width" : 4 }
+				},
+				{
+					"type": "adventureMapImage",
+					"name" : "backgroundBelowHeroTownList",
+					"image" : "AdvMap.pcx",
+					"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 4 },
+					"sourceArea": { "bottom" : 208, "height" : 4, "right" : 0, "width" : 193 }
+				},
+				// Hero List
+				{
+					"type": "adventureMapHeroList",
+					"name" : "heroList",
+					"area": { "top": 0, "right" : 125, "width" : 68, "height" : 193 },
+					"scrollUp" : {
+						"type": "adventureMapButton",
+						"name": "heroListScrollUp",
+						"image" : "IAM012.DEF",
+						"help" : "core.help.303",
+						"area": { "top" : 0, "left": 2, "width" : 64, "height" : 16 }
+					},
+					"scrollDown" : {
+						"type": "adventureMapButton",
+						"name": "heroListScrollDown",
+						"image" : "IAM013.DEF",
+						"help" : "core.help.304",
+						"area": { "bottom" : 0, "left": 2, "width" : 64, "height" : 16 }
+					},
+					"item" : { "top" :  16, "left": 3, "width" : 62, "height" : 32 },
+					"itemsOffset" : { "x" : 0, "y" : 32 },
+					"itemsCount" : 5
+				},
+				// Town List
+				{
+					"type": "adventureMapTownList",
+					"name" : "townList",
+					"area": { "top": 0, "right" : 3, "width" : 51, "height" : 193 },
+					"scrollUp" : {
+						"type": "adventureMapButton",
+						"name": "townListScrollUp",
+						"image" : "IAM014.DEF",
+						"help" : "core.help.306",
+						"area": { "top" : 0, "left": 1, "width" : 48, "height" : 16 }
+					},
+					"scrollDown" : {
+						"type": "adventureMapButton",
+						"name": "townListScrollDown",
+						"image" : "IAM015.DEF",
+						"help" : "core.help.307",
+						"area": { "bottom" : 0, "left": 1, "width" : 48, "height" : 16 }
+					},
+					"item" : { "top" :  16, "left": 2, "width" : 48, "height" : 32 },
+					"itemsOffset" : { "x" : 0, "y" : 32 },
+					"itemsCount" : 5
+				},
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "emptyAreaFillSmall",
+			"hideWhen" : "worldViewMode",
+			"area": { "top": 392, "right" : 3, "width" : 190, "bottom" : 211 },
+			"exists" : { "heightMax" : 664 },
+			"items" : [
+				{
+					"type": "adventureMapImage",
+					"name": "emptyAreaFillSmallImage",
+					"image" : "DiBoxBck.pcx",
+					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
+					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
+				},
+			]
+		},
+		// Town / Hero lists for large (664+) vertical resolution
+		{
+			"type": "adventureMapContainer",
+			"name" : "listContainerLarge",
+			"hideWhen" : "worldViewMode",
+			"area": { "top": 196, "right" : 0, "width" : 193, "height" : 260 },
+			"exists" : { "heightMin" : 664 },
+			"items" : [
+				{
+					"type": "adventureMapImage",
+					"name": "backgroundHeroListBorderRight",
+					"image" : "AdvMap.pcx",
+					"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 },
+					"sourceArea": { "top": 196, "bottom" : 211, "right" : 121, "width" : 6 }
+				},
+				{
+					"type": "adventureMapImage",
+					"name": "backgroundTownListBorderLeft",
+					"image" : "AdvMap.pcx",
+					"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 },
+					"sourceArea": { "top": 196, "bottom" : 211, "right" : 53, "width" : 4 }
+				},
+				{
+					"type": "adventureMapImage",
+					"name" : "backgroundBelowHeroTownList",
+					"image" : "AdvMap.pcx",
+					"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 4 },
+					"sourceArea": { "bottom" : 208, "height" : 4, "right" : 0, "width" : 193 }
+				},
+				// Hero List
+				{
+					"type": "adventureMapHeroList",
+					"name" : "heroList",
+					"area": { "top": 0, "right" : 125, "width" : 68, "height" : 257 },
+					"item" : { "top" :  1, "left": 3, "width" : 62, "height" : 32 },
+					"itemsOffset" : { "x" : 0, "y" : 32 },
+					"itemsCount" : 8
+				},
+				// Town List
+				{
+					"type": "adventureMapTownList",
+					"name" : "townList",
+					"area": { "top": 0, "right" : 3, "width" : 51, "height" : 257 },
+					"scrollUp" : {
+						"type": "adventureMapButton",
+						"name": "townListScrollUp",
+						"image" : "IAM014.DEF",
+						"help" : "core.help.306",
+						"area": { "top" : 0, "left": 1, "width" : 48, "height" : 16 }
+					},
+					"scrollDown" : {
+						"type": "adventureMapButton",
+						"name": "townListScrollDown",
+						"image" : "IAM015.DEF",
+						"help" : "core.help.307",
+						"area": { "bottom" : 0, "left": 1, "width" : 48, "height" : 16 }
+					},
+					"item" : { "top" :  16, "left": 1, "width" : 48, "height" : 32 },
+					"itemsOffset" : { "x" : 0, "y" : 32 },
+					"itemsCount" : 7
+				},
+				// Fill empty area below buttons
+				{
+					"type": "adventureMapImage",
+					"name" : "backgroundBelowButtons",
+					"image" : "DiBoxBck.pcx",
+					"area": { "top": 192, "bottom" : 3, "right" : 57, "width" : 64 },
+					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
+				},
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "emptyAreaFillLarge",
+			"hideWhen" : "worldViewMode",
+			"area": { "top": 456, "right" : 3, "width" : 190, "bottom" : 211 },
+			"exists" : { "heightMin" : 664 },
+			"items" : [
+				{
+					"type": "adventureMapImage",
+					"name": "emptyAreaFillLargeImage",
+					"image" : "DiBoxBck.pcx",
+					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
+					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
+				},
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "adventureInfobarContainer",
+			"hideWhen" : "worldViewMode",
+			"area" : { "bottom": 0, "right" : 0, "width" : 199, "height" : 211 },
+			"items" : [
+				// Infobar
+				{
+					"type": "adventureMapImage",
+					"name" : "backgroundRightInfobar",
+					"image" : "AdvMap.pcx",
+					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
+					"sourceArea" : { "bottom": 0, "right" : 0, "width" : 199, "height" : 211 }
+				},
+				{
+					"type": "adventureInfobar",
+					"name": "infoBar",
+					"area": { "bottom": 44, "right" : 19, "width" : 175, "height" : 168 }
+				}
+			]
+		},
+		// Status bar
+		{
+			"type": "adventureStatusBar",
+			"name": "statusBar",
+			"image" : "DiBoxBck.pcx",
+			"area": { "left": 8, "bottom" : 26, "right" : 199, "height" : 18 }
+		},
+		// Resource & Data bar
+		{
+			"type": "adventurePlayerTexture",
+			"name" : "backgroundLeftOfResourceDateBar",
+			"image" : "DiBoxBck.pcx",
+			"area" : { "left": 3, "bottom" : 4, "right" : 797, "height" : 21 }
+		},
+		{
+			"type": "adventureResourceDateBar",
+			"name": "resourceDataBar",
+			"image" : "AResBar.pcx",
+			"area": { "bottom" : 3, "right" : 3, "height" : 22, "width" : 794 },
+			
+			"wood"    : { "x" :  37, "y" : 3 },
+			"mercury" : { "x" : 121, "y" : 3 },
+			"ore"     : { "x" : 205, "y" : 3 },
+			"sulfur"  : { "x" : 289, "y" : 3 },
+			"crystal" : { "x" : 373, "y" : 3 },
+			"gems"    : { "x" : 457, "y" : 3 },
+			"gold"    : { "x" : 541, "y" : 3 },
+			"date"    : { "x" : 619, "y" : 3 }
+		},
+		// World view mode widgets
+		{
+			"type": "adventureMapContainer",
+			"name" : "worldViewContainer",
+			"hideWhen" : "mapViewMode",
+			"area": { "top": 195, "right" : 3, "width" : 190, "bottom" : 26 },
+			"items" : [
+				{
+					"type": "adventureMapImage",
+					"name": "worldViewBackground",
+					"image" : "VWorld.pcx",
+					"area": { "left" : 0, "right" : 0, "top" : 0, "height" : 381 },
+					"sourceArea": { "left" : 0, "right" : 0, "top" : 0, "bottom" : 0 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewZoom1",
+					"image" : "VWMAG1.DEF",
+					"hotkey": "adventureViewWorld1",
+					"area": { "top" : 23, "left": 1, "width" : 60, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewZoom2",
+					"image" : "VWMAG2.DEF",
+					"hotkey": "adventureViewWorld2",
+					"area": { "top" : 23, "left": 64, "width" : 60, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewZoom4",
+					"image" : "VWMAG4.DEF",
+					"hotkey": "adventureViewWorld4",
+					"area": { "top" : 23, "left": 128, "width" : 60, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewSurface",
+					"image" : "IAM003.DEF",
+					"hotkey": "adventureToggleMapLevel",
+					"playerColored" : true,
+					"area": { "top" : 79, "left": 343, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewPuzzle",
+					"borderColor" : "gold",
+					"image" : "VWPUZ.DEF",
+					"hotkey": "adventureViewPuzzle",
+					"area": { "top" : 343, "left": 5, "width" : 66, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewUnderground",
+					"image" : "IAM010.DEF",
+					"playerColored" : true,
+					"hotkey": "adventureToggleMapLevel",
+					"area": { "top" : 343, "left": 79, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapButton",
+					"name": "worldViewExit",
+					"borderColor" : "gold",
+					"image" : "IOKAY32.DEF",
+					"hotkey": "adventureExitWorldView",
+					"area": { "top" : 343, "left": 119, "width" : 66, "height" : 32 }
+				},
+				// World view - objects icons
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconTown",
+					"image" : "VwSymbol.def",
+					"index" : 0,
+					"perPlayer" : 19,
+					"area": { "top" : 59, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconHero",
+					"image" : "VwSymbol.def",
+					"index" : 1,
+					"perPlayer" : 19,
+					"area": { "top" : 79, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconArtifact",
+					"image" : "VwSymbol.def",
+					"index" : 2,
+					"perPlayer" : 19,
+					"area": { "top" : 99, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconTeleporter",
+					"image" : "VwSymbol.def",
+					"index" : 3,
+					"perPlayer" : 19,
+					"area": { "top" : 119, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconGate",
+					"image" : "VwSymbol.def",
+					"index" : 4,
+					"perPlayer" : 19,
+					"area": { "top" : 139, "left": 5, "width" : 32, "height" : 32 }
+				},
+				
+				// World view - mines icons
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineWood",
+					"image" : "VwSymbol.def",
+					"index" : 5,
+					"perPlayer" : 19,
+					"area": { "top" : 183, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineMercury",
+					"image" : "VwSymbol.def",
+					"index" : 6,
+					"perPlayer" : 19,
+					"area": { "top" : 203, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineOre",
+					"image" : "VwSymbol.def",
+					"index" : 7,
+					"perPlayer" : 19,
+					"area": { "top" : 223, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineSulfur",
+					"image" : "VwSymbol.def",
+					"index" : 8,
+					"perPlayer" : 19,
+					"area": { "top" : 243, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineCrystal",
+					"image" : "VwSymbol.def",
+					"index" : 9,
+					"perPlayer" : 19,
+					"area": { "top" : 263, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineGems",
+					"image" : "VwSymbol.def",
+					"index" : 10,
+					"perPlayer" : 19,
+					"area": { "top" : 283, "left": 5, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconMineGold",
+					"image" : "VwSymbol.def",
+					"index" : 11,
+					"perPlayer" : 19,
+					"area": { "top" : 303, "left": 5, "width" : 32, "height" : 32 }
+				},
+				
+				// World view - resources icons
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceWood",
+					"image" : "VwSymbol.def",
+					"index" : 12,
+					"perPlayer" : 19,
+					"area": { "top" : 183, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceMercury",
+					"image" : "VwSymbol.def",
+					"index" : 13,
+					"perPlayer" : 19,
+					"area": { "top" : 203, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceOre",
+					"image" : "VwSymbol.def",
+					"index" : 14,
+					"perPlayer" : 19,
+					"area": { "top" : 223, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceSulfur",
+					"image" : "VwSymbol.def",
+					"index" : 15,
+					"perPlayer" : 19,
+					"area": { "top" : 243, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceCrystal",
+					"image" : "VwSymbol.def",
+					"index" : 16,
+					"perPlayer" : 19,
+					"area": { "top" : 263, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceGems",
+					"image" : "VwSymbol.def",
+					"index" : 17,
+					"perPlayer" : 19,
+					"area": { "top" : 283, "left": 154, "width" : 32, "height" : 32 }
+				},
+				{
+					"type": "adventureMapIcon",
+					"name": "worldViewIconResourceGold",
+					"image" : "VwSymbol.def",
+					"index" : 18,
+					"perPlayer" : 19,
+					"area": { "top" : 303, "left": 154, "width" : 32, "height" : 32 }
+				},
+
+				{
+					"name": "worldViewLabelTitle",
+					"type": "label",
+					"font": "big",
+					"alignment": "center",
+					"color": "yellow",
+					"position": {"x": 94, "y": 11},
+					"text": "core.genrltxt.611"
+				},
+				{
+					"name": "worldViewLabelMine",
+					"type": "label",
+					"font": "calisto",
+					"alignment": "left",
+					"color": "white",
+					"position": {"x": 7, "y": 173},
+					"text": "core.genrltxt.617"
+				},
+				{
+					"name": "worldViewLabelResource",
+					"type": "label",
+					"font": "calisto",
+					"alignment": "right",
+					"color": "white",
+					"position": {"x": 185, "y": 190},
+					"text": "core.genrltxt.618"
+				},
+				{
+					"name": "worldViewLabelsObjects",
+					"type": "labelGroup",
+					"font": "calisto",
+					"alignment": "left",
+					"color": "white",
+					"items":
+					[
+						{
+							"position": {"x": 43, "y": 66},
+							"text": "core.genrltxt.612"
+						},
+						{
+							"position": {"x": 43, "y": 86},
+							"text": "core.genrltxt.613"
+						},
+						{
+							"position": {"x": 43, "y": 106},
+							"text": "core.genrltxt.614"
+						},
+						{
+							"position": {"x": 43, "y": 126},
+							"text": "core.genrltxt.615"
+						},
+						{
+							"position": {"x": 43, "y": 146},
+							"text": "core.genrltxt.616"
+						}
+					]
+				},
+				{
+					"name": "worldViewLabelsResources",
+					"type": "labelGroup",
+					"font": "calisto",
+					"alignment": "center",
+					"color": "white",
+					"items":
+					[
+						{
+							"position": {"x": 101, "y": 198},
+							"text": "core.genrltxt.619"
+						},
+						{
+							"position": {"x": 101, "y": 218},
+							"text": "core.genrltxt.620"
+						},
+						{
+							"position": {"x": 101, "y": 238},
+							"text": "core.genrltxt.621"
+						},
+						{
+							"position": {"x": 101, "y": 258},
+							"text": "core.genrltxt.622"
+						},
+						{
+							"position": {"x": 101, "y": 278},
+							"text": "core.genrltxt.623"
+						},
+						{
+							"position": {"x": 101, "y": 298},
+							"text": "core.genrltxt.624"
+						},
+						{
+							"position": {"x": 101, "y": 318},
+							"text": "core.genrltxt.625"
+						}
+					]
+				},
+				{
+					"type": "adventureMapImage",
+					"name" : "backgroundBelowWorldView",
+					"image" : "DiBoxBck.pcx",
+					"area": { "top": 381, "bottom" : 0, "left" : 0, "right" : 0 },
+					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
+				}
+			]
+		},
+		// GEMS - set of images with different image for each player
+		{
+			"type": "adventureMapContainer",
+			"name" : "overlayGemTopLeft",
+			"overlay" : true,
+			"area": { "left": 6, "top" : 6, "width" : 46, "height" : 46 },
+			"items" : [
+				{
+					"type": "adventureMapIcon",
+					"name" : "gemTopLeft",
+					"image" : "agemUL.def",
+					"index" : 0,
+					"perPlayer" : 1,
+					"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
+				}
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "overlayGemTopRight",
+			"overlay" : true,
+			"area": { "right": 198, "top" : 6, "width" : 46, "height" : 46 },
+			"items" : [
+				{
+					"type": "adventureMapIcon",
+					"name" : "gemTopRight",
+					"image" : "agemUR.def",
+					"index" : 0,
+					"perPlayer" : 1,
+					"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
+				}
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "overlayGemBottomLeft",
+			"overlay" : true,
+			"area": { "left": 6, "bottom" : 46, "width" : 46, "height" : 46 },
+			"items" : [
+				{
+					"type": "adventureMapIcon",
+					"name" : "gemBottomLeft",
+					"image" : "agemLL.def",
+					"index" : 0,
+					"perPlayer" : 1,
+					"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
+				}
+			]
+		},
+		{
+			"type": "adventureMapContainer",
+			"name" : "overlayGemBottomRight",
+			"overlay" : true,
+			"area": { "right": 198, "bottom" : 46, "width" : 46, "height" : 46 },
+			"items" : [
+				{
+					"type": "adventureMapIcon",
+					"name" : "gemBottomRight",
+					"image" : "agemLR.def",
+					"index" : 0,
+					"perPlayer" : 1,
+					"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
+				}
+			]
+		}
+	]
+}

+ 1 - 11
config/widgets/settings/battleOptionsTab.json

@@ -149,10 +149,6 @@
 					"text": "core.genrltxt.406",
 					"position": {"x": 45, "y": 145}
 				},
-				{
-					"text": "core.genrltxt.407",
-					"position": {"x": 45, "y": 175}
-				},
 				{
 					"text": "vcmi.battleOptions.skipBattleIntroMusic.hover",
 					"position": {"x": 45, "y": 175}
@@ -196,18 +192,12 @@
 			"position": {"x": 10, "y": 143},
 			"callback": "mouseShadowChanged"
 		},
-		{
-			"name": "battleFieldCasualtiesPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 10, "y": 173},
-		},
 		{
 			"name": "skipBattleIntroMusicCheckbox",
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "vcmi.battleOptions.skipBattleIntroMusic",
-			"position": {"x": 10, "y": 203},
+			"position": {"x": 10, "y": 173},
 			"callback": "skipBattleIntroMusicChanged"
 		},
 		{

+ 33 - 6
config/widgets/settings/generalOptionsTab.json

@@ -58,6 +58,33 @@
 			]
 		},
 		
+		{
+			"name": "scalingLabel",
+			"type": "label",
+			"font": "medium",
+			"alignment": "left",
+			"color": "white",
+			"position": {"x": 45, "y": 115},
+			"text": "vcmi.systemOptions.scalingButton.hover"
+		},
+		{
+			"name": "resolutionButton",
+			"type": "button",
+			"position": {"x": 10, "y": 113},
+			"image": "settingsWindow/button32",
+			"help": "vcmi.systemOptions.scalingButton",
+			"callback": "setGameScaling",
+			"items":
+			[
+				{
+					"name": "gearIcon",
+					"type": "picture",
+					"image": "settingsWindow/gear",
+					"position": {"x": 0, "y": 0 }
+				}
+			]
+		},
+		
 		{
 			"name": "topCheckboxesLabels",
 			"type": "labelGroup",
@@ -67,15 +94,15 @@
 			"items":
 			[
 				{
-					"position": {"x": 45, "y": 115},
+					"position": {"x": 45, "y": 145},
 					"text": "vcmi.systemOptions.fullscreenButton.hover"
 				},
 				{
-					"position": {"x": 45, "y": 145},
+					"position": {"x": 45, "y": 175},
 					"text": "vcmi.systemOptions.framerateButton.hover"
 				},
 				{
-					"position": {"x": 45, "y": 175},
+					"position": {"x": 45, "y": 205},
 					"text": "core.genrltxt.577"
 				},
 
@@ -86,7 +113,7 @@
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "vcmi.systemOptions.fullscreenButton",
-			"position": {"x": 10, "y": 113},
+			"position": {"x": 10, "y": 143},
 			"callback": "fullscreenChanged"
 		},
 		{
@@ -94,7 +121,7 @@
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "vcmi.systemOptions.framerateButton",
-			"position": {"x": 10, "y": 143},
+			"position": {"x": 10, "y": 173},
 			"callback": "framerateChanged"
 		},
 		
@@ -103,7 +130,7 @@
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "core.help.364",
-			"position": {"x": 10, "y": 173},
+			"position": {"x": 10, "y": 203},
 			"callback": "spellbookAnimationChanged"
 		},
 

+ 4 - 0
launcher/CMakeLists.txt

@@ -133,6 +133,10 @@ if(APPLE)
 	set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmilauncher)
 endif()
 
+if (NOT APPLE_IOS AND NOT ANDROID)
+	target_link_libraries(vcmilauncher SDL2::SDL2)
+endif()
+
 target_link_libraries(vcmilauncher ${VCMI_LIB_TARGET} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
 target_include_directories(vcmilauncher
 	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR}

+ 0 - 1
launcher/mainwindow_moc.cpp

@@ -110,7 +110,6 @@ MainWindow::MainWindow(QWidget * parent)
 	else
 		enterSetup();
 
-	ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled();
 	ui->settingsView->setDisplayList();
 	connect(ui->modlistView, &CModListView::extraResolutionsEnabledChanged,
 		ui->settingsView, &CSettingsView::fillValidResolutions);

+ 55 - 43
launcher/settingsView/csettingsview_moc.cpp

@@ -25,6 +25,10 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/VCMIDirs.h"
 
+#ifndef VCMI_MOBILE
+#include <SDL2/SDL.h>
+#endif
+
 namespace
 {
 QString resolutionToString(const QSize & resolution)
@@ -67,9 +71,9 @@ void CSettingsView::loadSettings()
 {
 	ui->comboBoxShowIntro->setCurrentIndex(settings["video"]["showIntro"].Bool());
 
-#ifdef Q_OS_IOS
-	ui->comboBoxFullScreen->setCurrentIndex(1);
-	ui->comboBoxFullScreen->setDisabled(true);
+#ifdef VCMI_MOBILE
+	ui->comboBoxFullScreen->hide();
+	ui->labelFullScreen->hide();
 #else
 	if (settings["video"]["realFullscreen"].Bool())
 		ui->comboBoxFullScreen->setCurrentIndex(2);
@@ -106,66 +110,74 @@ void CSettingsView::loadSettings()
 	ui->comboBoxCursorType->setCurrentIndex((int)cursorTypeIndex);
 }
 
-void CSettingsView::fillValidResolutions(bool isExtraResolutionsModEnabled)
+void CSettingsView::fillValidResolutions()
 {
-	this->isExtraResolutionsModEnabled = isExtraResolutionsModEnabled;
 	fillValidResolutionsForScreen(ui->comboBoxDisplayIndex->isVisible() ? ui->comboBoxDisplayIndex->currentIndex() : 0);
 }
 
-void CSettingsView::fillValidResolutionsForScreen(int screenIndex)
+#ifndef VCMI_MOBILE
+
+static QVector<QSize> findAvailableResolutions(int displayIndex)
 {
-	ui->comboBoxResolution->blockSignals(true); // avoid saving wrong resolution after adding first item from the list
-	ui->comboBoxResolution->clear();
+	// Ugly workaround since we don't actually need SDL in Launcher
+	// However Qt at the moment provides no way to query list of available resolutions
+	QVector<QSize> result;
+	SDL_Init(SDL_INIT_VIDEO);
+
+	int modesCount = SDL_GetNumDisplayModes(displayIndex);
 
-	// TODO: read available resolutions from all mods
-	QVariantList resolutions;
-	if(isExtraResolutionsModEnabled)
+	for (int i =0; i < modesCount; ++i)
 	{
-		const auto extrasResolutionsPath = settings["launcher"]["extraResolutionsModPath"].String().c_str();
-		const auto extrasResolutionsJson = JsonUtils::JsonFromFile(CLauncherDirs::get().modsPath() + extrasResolutionsPath);
-		resolutions = extrasResolutionsJson.toMap().value(QLatin1String{"GUISettings"}).toList();
+		SDL_DisplayMode mode;
+		if (SDL_GetDisplayMode(displayIndex, i, &mode) != 0)
+			continue;
+
+		QSize resolution(mode.w, mode.h);
+
+		result.push_back(resolution);
 	}
-	if(resolutions.isEmpty())
+
+	boost::range::sort(result, [](const auto & left, const auto & right)
 	{
-		ui->comboBoxResolution->blockSignals(false);
-		ui->comboBoxResolution->addItem(resolutionToString({800, 600}));
-		return;
-	}
+		return left.height() * left.width() < right.height() * right.width();
+	});
 
-	const auto screens = qGuiApp->screens();
-	const auto currentScreen = screenIndex < screens.size() ? screens[screenIndex] : qGuiApp->primaryScreen();
-	[[maybe_unused]] const auto screenSize = currentScreen->size();
+	result.erase(boost::unique(result).end(), result.end());
 
-	for(const auto & entry : resolutions)
-	{
-		const auto resolutionMap = entry.toMap().value(QLatin1String{"resolution"}).toMap();
-		if(resolutionMap.isEmpty())
-			continue;
+	SDL_Quit();
 
-		const auto widthValue = resolutionMap[QLatin1String{"x"}];
-		const auto heightValue = resolutionMap[QLatin1String{"y"}];
-		if(!widthValue.isValid() || !heightValue.isValid())
-			continue;
+	return result;
+}
 
-		const QSize resolution{widthValue.toInt(), heightValue.toInt()};
-#ifndef VCMI_IOS
-		if(screenSize.width() < resolution.width() || screenSize.height() < resolution.height())
-			continue;
-#endif
-		ui->comboBoxResolution->addItem(resolutionToString(resolution));
-	}
+void CSettingsView::fillValidResolutionsForScreen(int screenIndex)
+{
+	ui->comboBoxResolution->blockSignals(true); // avoid saving wrong resolution after adding first item from the list
+	ui->comboBoxResolution->clear();
 
-	int resX = settings["video"]["screenRes"]["width"].Integer();
-	int resY = settings["video"]["screenRes"]["height"].Integer();
+	QVector<QSize> resolutions = findAvailableResolutions(screenIndex);
+
+	for(const auto & entry : resolutions)
+		ui->comboBoxResolution->addItem(resolutionToString(entry));
+
+	int resX = settings["video"]["resolution"]["width"].Integer();
+	int resY = settings["video"]["resolution"]["height"].Integer();
 	int resIndex = ui->comboBoxResolution->findText(resolutionToString({resX, resY}));
 	ui->comboBoxResolution->setCurrentIndex(resIndex);
 
 	ui->comboBoxResolution->blockSignals(false);
 
-	// if selected resolution no longer exists, force update value to the first resolution
+	// if selected resolution no longer exists, force update value to the largest (last) resolution
 	if(resIndex == -1)
-		ui->comboBoxResolution->setCurrentIndex(0);
+		ui->comboBoxResolution->setCurrentIndex(ui->comboBoxResolution->count() - 1);
+}
+#else
+void CSettingsView::fillValidResolutionsForScreen(int screenIndex)
+{
+	// resolutions are not selectable on mobile platforms
+	ui->comboBoxResolution->hide();
+	ui->labelResolution->hide();
 }
+#endif
 
 CSettingsView::CSettingsView(QWidget * parent)
 	: QWidget(parent), ui(new Ui::CSettingsView)
@@ -186,7 +198,7 @@ void CSettingsView::on_comboBoxResolution_currentTextChanged(const QString & arg
 {
 	QStringList list = arg1.split("x");
 
-	Settings node = settings.write["video"]["screenRes"];
+	Settings node = settings.write["video"]["resolution"];
 	node["width"].Float() = list[0].toInt();
 	node["height"].Float() = list[1].toInt();
 }

+ 1 - 3
launcher/settingsView/csettingsview_moc.h

@@ -29,10 +29,8 @@ public:
 	void changeEvent(QEvent *event) override;
 	void showEvent(QShowEvent * event) override;
 
-	bool isExtraResolutionsModEnabled{};
-
 public slots:
-	void fillValidResolutions(bool isExtraResolutionsModEnabled);
+	void fillValidResolutions();
 
 private slots:
 	void on_comboBoxResolution_currentTextChanged(const QString & arg1);

+ 5 - 5
launcher/settingsView/csettingsview_moc.ui

@@ -112,9 +112,9 @@
       <property name="geometry">
        <rect>
         <x>0</x>
-        <y>0</y>
+        <y>-107</y>
         <width>620</width>
-        <height>793</height>
+        <height>745</height>
        </rect>
       </property>
       <layout class="QGridLayout" name="gridLayout" columnstretch="3,0,0,0">
@@ -253,17 +253,17 @@
          </property>
          <item>
           <property name="text">
-           <string>Off</string>
+           <string>Windowed</string>
           </property>
          </item>
          <item>
           <property name="text">
-           <string>On</string>
+           <string>Borderless fullscreen</string>
           </property>
          </item>
          <item>
           <property name="text">
-           <string>Real</string>
+           <string>Exclusive fullscreen</string>
           </property>
          </item>
         </widget>

+ 0 - 120
lib/CConfigHandler.cpp

@@ -17,10 +17,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-using namespace config;
-
 SettingsStorage settings;
-CConfigHandler conf;
 
 template<typename Accessor>
 SettingsStorage::NodeAccessor<Accessor>::NodeAccessor(SettingsStorage & _parent, std::vector<std::string> _path):
@@ -178,123 +175,6 @@ JsonNode & Settings::operator[](const std::string & value)
 {
 	return node[value];
 }
-//
-// template DLL_LINKAGE struct SettingsStorage::NodeAccessor<SettingsListener>;
-// template DLL_LINKAGE struct SettingsStorage::NodeAccessor<Settings>;
-
-static void setButton(ButtonInfo &button, const JsonNode &g)
-{
-	button.x = static_cast<int>(g["x"].Float());
-	button.y = static_cast<int>(g["y"].Float());
-	button.playerColoured = g["playerColoured"].Float();
-	button.defName = g["graphic"].String();
-
-	if (!g["additionalDefs"].isNull()) {
-		const JsonVector &defs_vec = g["additionalDefs"].Vector();
-
-		for(const JsonNode &def : defs_vec) {
-			button.additionalDefs.push_back(def.String());
-		}
-	}
-}
-
-static void setGem(AdventureMapConfig &ac, const int gem, const JsonNode &g)
-{
-	ac.gemX[gem] = static_cast<int>(g["x"].Float());
-	ac.gemY[gem] = static_cast<int>(g["y"].Float());
-	ac.gemG.push_back(g["graphic"].String());
-}
-
-CConfigHandler::CConfigHandler()
-	: current(nullptr)
-{
-}
-
-void config::CConfigHandler::init()
-{
-	/* Read resolutions. */
-	const JsonNode config(ResourceID("config/resolutions.json"));
-	const JsonVector &guisettings_vec = config["GUISettings"].Vector();
-
-	for(const JsonNode &g : guisettings_vec)
-	{
-		std::pair<int, int> curRes(static_cast<int>(g["resolution"]["x"].Float()), static_cast<int>(g["resolution"]["y"].Float()));
-		GUIOptions *current = &conf.guiOptions[curRes];
-
-		current->ac.inputLineLength =  static_cast<int>(g["InGameConsole"]["maxInputPerLine"].Float());
-		current->ac.outputLineLength = static_cast<int>(g["InGameConsole"]["maxOutputPerLine"].Float());
-
-		current->ac.advmapX = static_cast<int>(g["AdvMap"]["x"].Float());
-		current->ac.advmapY = static_cast<int>(g["AdvMap"]["y"].Float());
-		current->ac.advmapW = static_cast<int>(g["AdvMap"]["width"].Float());
-		current->ac.advmapH = static_cast<int>(g["AdvMap"]["height"].Float());
-		current->ac.smoothMove = g["AdvMap"]["smoothMove"].Float();
-		current->ac.puzzleSepia = g["AdvMap"]["puzzleSepia"].Float();
-		current->ac.screenFading = g["AdvMap"]["screenFading"].isNull() ? true : g["AdvMap"]["screenFading"].Float(); // enabled by default
-		current->ac.objectFading = g["AdvMap"]["objectFading"].isNull() ? true : g["AdvMap"]["objectFading"].Float();
-
-		current->ac.infoboxX = static_cast<int>(g["InfoBox"]["x"].Float());
-		current->ac.infoboxY = static_cast<int>(g["InfoBox"]["y"].Float());
-
-		setGem(current->ac, 0, g["gem0"]);
-		setGem(current->ac, 1, g["gem1"]);
-		setGem(current->ac, 2, g["gem2"]);
-		setGem(current->ac, 3, g["gem3"]);
-
-		current->ac.mainGraphic = g["background"].String();
-		current->ac.worldViewGraphic = g["backgroundWorldView"].String();
-
-		current->ac.hlistX =    static_cast<int>(g["HeroList"]["x"].Float());
-		current->ac.hlistY =    static_cast<int>(g["HeroList"]["y"].Float());
-		current->ac.hlistSize = static_cast<int>(g["HeroList"]["size"].Float());
-		current->ac.hlistMB = g["HeroList"]["movePoints"].String();
-		current->ac.hlistMN = g["HeroList"]["manaPoints"].String();
-		current->ac.hlistAU = g["HeroList"]["arrowUp"].String();
-		current->ac.hlistAD = g["HeroList"]["arrowDown"].String();
-
-		current->ac.tlistX =    static_cast<int>(g["TownList"]["x"].Float());
-		current->ac.tlistY =    static_cast<int>(g["TownList"]["y"].Float());
-		current->ac.tlistSize = static_cast<int>(g["TownList"]["size"].Float());
-		current->ac.tlistAU = g["TownList"]["arrowUp"].String();
-		current->ac.tlistAD = g["TownList"]["arrowDown"].String();
-
-		current->ac.minimapW = static_cast<int>(g["Minimap"]["width"].Float());
-		current->ac.minimapH = static_cast<int>(g["Minimap"]["height"].Float());
-		current->ac.minimapX = static_cast<int>(g["Minimap"]["x"].Float());
-		current->ac.minimapY = static_cast<int>(g["Minimap"]["y"].Float());
-
-		current->ac.overviewPics = static_cast<int>(g["Overview"]["pics"].Float());
-		current->ac.overviewSize = static_cast<int>(g["Overview"]["size"].Float());
-		current->ac.overviewBg = g["Overview"]["graphic"].String();
-
-		current->ac.statusbarX = static_cast<int>(g["Statusbar"]["x"].Float());
-		current->ac.statusbarY = static_cast<int>(g["Statusbar"]["y"].Float());
-		current->ac.statusbarG = g["Statusbar"]["graphic"].String();
-
-		current->ac.resdatabarX = static_cast<int>(g["ResDataBar"]["x"].Float());
-		current->ac.resdatabarY = static_cast<int>(g["ResDataBar"]["y"].Float());
-		current->ac.resOffsetX =  static_cast<int>(g["ResDataBar"]["offsetX"].Float());
-		current->ac.resOffsetY =  static_cast<int>(g["ResDataBar"]["offsetY"].Float());
-		current->ac.resDist =     static_cast<int>(g["ResDataBar"]["resSpace"].Float());
-		current->ac.resDateDist = static_cast<int>(g["ResDataBar"]["resDateSpace"].Float());
-		current->ac.resdatabarG = g["ResDataBar"]["graphic"].String();
-
-		setButton(current->ac.kingOverview, g["ButtonKingdomOv"]);
-		setButton(current->ac.underground, g["ButtonUnderground"]);
-		setButton(current->ac.questlog, g["ButtonQuestLog"]);
-		setButton(current->ac.sleepWake, g["ButtonSleepWake"]);
-		setButton(current->ac.moveHero, g["ButtonMoveHero"]);
-		setButton(current->ac.spellbook, g["ButtonSpellbook"]);
-		setButton(current->ac.advOptions, g["ButtonAdvOptions"]);
-		setButton(current->ac.sysOptions, g["ButtonSysOptions"]);
-		setButton(current->ac.nextHero, g["ButtonNextHero"]);
-		setButton(current->ac.endTurn, g["ButtonEndTurn"]);
-	}
-
-	const JsonNode& screenRes = settings["video"]["screenRes"];
-
-	SetResolution(static_cast<int>(screenRes["width"].Float()), static_cast<int>(screenRes["height"].Float()));
-}
 
 // Force instantiation of the SettingsStorage::NodeAccessor class template.
 // That way method definitions can sit in the cpp file

+ 0 - 77
lib/CConfigHandler.h

@@ -112,83 +112,6 @@ public:
 	friend class SettingsStorage;
 };
 
-namespace config
-{
-	struct DLL_LINKAGE ButtonInfo
-	{
-		std::string defName;
-		std::vector<std::string> additionalDefs;
-		int x, y; //position on the screen
-		bool playerColoured; //if true button will be colored to main player's color (works properly only for appropriate 8bpp graphics)
-	};
-	/// Struct which holds data about position of several GUI elements at the adventure map screen
-	struct DLL_LINKAGE AdventureMapConfig
-	{
-		//minimap properties
-		int minimapX, minimapY, minimapW, minimapH;
-		//statusbar
-		int statusbarX, statusbarY; //pos
-		std::string statusbarG; //graphic name
-		//resdatabar
-		int resdatabarX, resdatabarY, resDist, resDateDist, resOffsetX, resOffsetY; //pos
-		std::string resdatabarG; //graphic name
-		//infobox
-		int infoboxX, infoboxY;
-		//advmap
-		int advmapX, advmapY, advmapW, advmapH;
-		bool smoothMove;
-		bool puzzleSepia;
-		bool screenFading;
-		bool objectFading;
-		//general properties
-		std::string mainGraphic;
-		std::string worldViewGraphic;
-		//buttons
-		ButtonInfo kingOverview, underground, questlog,	sleepWake, moveHero, spellbook,	advOptions,
-			sysOptions,	nextHero, endTurn;
-		//hero list
-		int hlistX, hlistY, hlistSize;
-		std::string hlistMB, hlistMN, hlistAU, hlistAD;
-		//town list
-		int tlistX, tlistY, tlistSize;
-		std::string tlistAU, tlistAD;
-		//gems
-		int gemX[4], gemY[4];
-		std::vector<std::string> gemG;
-		//in-game console
-		int inputLineLength, outputLineLength;
-		//kingdom overview
-		int overviewPics, overviewSize; //pic count in def and count of visible slots
-		std::string overviewBg; //background name
-	};
-	struct DLL_LINKAGE GUIOptions
-	{
-		AdventureMapConfig ac;
-	};
-	/// Handles adventure map screen settings
-	class DLL_LINKAGE CConfigHandler
-	{
-		GUIOptions *current; // pointer to current gui options
-
-	public:
-		using GuiOptionsMap = std::map<std::pair<int, int>, GUIOptions>;
-		GuiOptionsMap guiOptions;
-		void init();
-		CConfigHandler();
-
-		GUIOptions *go() { return current; };
-		void SetResolution(int x, int y)
-		{
-			std::pair<int,int> index(x, y);
-			if (guiOptions.count(index) == 0)
-				current = nullptr;
-			else
-				current = &guiOptions.at(index);
-		}
-	};
-}
-
 extern DLL_LINKAGE SettingsStorage settings;
-extern DLL_LINKAGE config::CConfigHandler conf;
 
 VCMI_LIB_NAMESPACE_END

+ 0 - 3
mapeditor/mainwindow.cpp

@@ -191,9 +191,6 @@ MainWindow::MainWindow(QWidget* parent) :
 		QApplication::quit();
 	}
 
-	conf.init();
-	logGlobal->info("Loading settings");
-
 	loadTranslation();
 
 	ui->setupUi(this);