Browse Source

Merge branch 'develop' into battle-queue-improvement

Dydzio 2 years ago
parent
commit
2ef33d54ab
100 changed files with 5666 additions and 3825 deletions
  1. 13 37
      .github/workflows/github.yml
  2. 8 0
      AI/CMakeLists.txt
  3. 3 2
      AI/Nullkiller/AIUtility.cpp
  4. 9 6
      AI/Nullkiller/Engine/AIMemory.cpp
  5. 0 4
      AI/VCAI/MapObjectsEvaluator.cpp
  6. 14 12
      AI/VCAI/VCAI.cpp
  7. 0 2
      CCallback.cpp
  8. 20 0
      CI/conan/base/cross-macro.j2
  9. 10 0
      CI/conan/base/cross-windows
  10. 15 0
      CI/conan/mingw32-linux.jinja
  11. 13 0
      CI/conan/mingw64-linux.jinja
  12. 0 3
      CI/ios/post_pack.sh
  13. 16 0
      CI/mingw-ubuntu/before_install.sh
  14. 0 53
      CI/mxe/before_install.sh
  15. 59 45
      CMakeLists.txt
  16. 24 0
      CMakePresets.json
  17. 324 319
      ChangeLog.md
  18. 243 0
      Mods/vcmi/config/vcmi/english.json
  19. 222 0
      Mods/vcmi/config/vcmi/german.json
  20. 70 0
      Mods/vcmi/config/vcmi/polish.json
  21. 225 0
      Mods/vcmi/config/vcmi/ukrainian.json
  22. 24 1
      Mods/vcmi/mod.json
  23. 2 2
      README.md
  24. 29 24
      client/CMT.cpp
  25. 218 171
      client/CMakeLists.txt
  26. 3 1
      client/CMusicHandler.cpp
  27. 10 10
      client/CPlayerInterface.cpp
  28. 2 0
      client/CServerHandler.cpp
  29. 32 23
      client/CVideoHandler.cpp
  30. 7 36
      client/CVideoHandler.h
  31. 6 6
      client/Client.cpp
  32. 6 2
      client/ClientCommandManager.cpp
  33. 12 12
      client/NetPacksClient.cpp
  34. 0 2
      client/StdInc.h
  35. 63 555
      client/adventureMap/CAdvMapInt.cpp
  36. 24 96
      client/adventureMap/CAdvMapInt.h
  37. 96 0
      client/adventureMap/CAdvMapPanel.cpp
  38. 60 0
      client/adventureMap/CAdvMapPanel.h
  39. 61 0
      client/adventureMap/CAdventureOptions.cpp
  40. 30 0
      client/adventureMap/CAdventureOptions.h
  41. 261 0
      client/adventureMap/CInGameConsole.cpp
  42. 39 0
      client/adventureMap/CInGameConsole.h
  43. 323 0
      client/adventureMap/CInfoBar.cpp
  44. 146 0
      client/adventureMap/CInfoBar.h
  45. 339 0
      client/adventureMap/CList.cpp
  46. 176 0
      client/adventureMap/CList.h
  47. 317 0
      client/adventureMap/CMinimap.cpp
  48. 77 0
      client/adventureMap/CMinimap.h
  49. 105 0
      client/adventureMap/CResDataBar.cpp
  50. 33 0
      client/adventureMap/CResDataBar.h
  51. 414 0
      client/adventureMap/CTerrainRect.cpp
  52. 64 0
      client/adventureMap/CTerrainRect.h
  53. 26 32
      client/adventureMap/mapHandler.cpp
  54. 3 3
      client/adventureMap/mapHandler.h
  55. 4 5
      client/battle/BattleAnimationClasses.cpp
  56. 5 2
      client/battle/BattleAnimationClasses.h
  57. 1 1
      client/battle/BattleConstants.h
  58. 2 2
      client/battle/BattleEffectsController.cpp
  59. 3 3
      client/battle/BattleFieldController.cpp
  60. 6 10
      client/battle/BattleInterface.cpp
  61. 18 13
      client/battle/BattleInterfaceClasses.cpp
  62. 1 2
      client/battle/BattleObstacleController.cpp
  63. 14 16
      client/battle/BattleProjectileController.cpp
  64. 1 1
      client/battle/BattleProjectileController.h
  65. 2 2
      client/battle/BattleSiegeController.cpp
  66. 8 9
      client/battle/BattleStacksController.cpp
  67. 1 1
      client/battle/BattleStacksController.h
  68. 7 4
      client/battle/BattleWindow.cpp
  69. 49 25
      client/battle/CreatureAnimation.cpp
  70. 16 6
      client/battle/CreatureAnimation.h
  71. 0 1282
      client/gui/CAnimation.cpp
  72. 198 63
      client/gui/CGuiHandler.cpp
  73. 27 16
      client/gui/CGuiHandler.h
  74. 4 2
      client/gui/CIntObject.cpp
  75. 1 1
      client/gui/CIntObject.h
  76. 15 136
      client/gui/CursorHandler.cpp
  77. 4 57
      client/gui/CursorHandler.h
  78. 0 395
      client/gui/Fonts.cpp
  79. 0 136
      client/gui/Fonts.h
  80. 0 1
      client/gui/InterfaceObjectConfigurable.cpp
  81. 0 25
      client/gui/SDL_Compat.h
  82. 3 1
      client/ios/GameChatKeyboardHandler.h
  83. 15 15
      client/ios/GameChatKeyboardHandler.m
  84. 1 1
      client/ios/startSDL.mm
  85. 7 9
      client/lobby/CBonusSelection.cpp
  86. 0 2
      client/lobby/CCampaignInfoScreen.cpp
  87. 4 3
      client/lobby/CSelectionBase.cpp
  88. 0 1
      client/lobby/OptionsTab.cpp
  89. 0 1
      client/lobby/RandomMapTab.cpp
  90. 6 7
      client/lobby/SelectionTab.cpp
  91. 0 2
      client/mainmenu/CCampaignScreen.cpp
  92. 1 13
      client/mainmenu/CMainMenu.cpp
  93. 377 0
      client/render/CAnimation.cpp
  94. 1 92
      client/render/CAnimation.h
  95. 5 3
      client/render/CBitmapHandler.cpp
  96. 0 0
      client/render/CBitmapHandler.h
  97. 358 0
      client/render/CDefFile.cpp
  98. 51 0
      client/render/CDefFile.h
  99. 101 0
      client/render/CFadeAnimation.cpp
  100. 53 0
      client/render/CFadeAnimation.h

+ 13 - 37
.github/workflows/github.yml

@@ -99,20 +99,21 @@ jobs:
             extension: ipa
             preset: ios-release-conan
             conan_profile: ios-arm64
-          - platform: mxe
-            os: ubuntu-20.04
-            mxe: i686-w64-mingw32.shared
-            test: 0
-            pack: 1
-            cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
-            extension: exe
-            cmake_args: -G Ninja
           - platform: msvc
             os: windows-latest
             test: 0
             pack: 1
             extension: exe
             preset: windows-msvc-release
+          - platform: mingw-ubuntu
+            os: ubuntu-22.04
+            test: 0
+            pack: 1
+            extension: exe
+            cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
+            cmake_args: -G Ninja
+            preset: windows-mingw-conan-linux
+            conan_profile: mingw64-linux.jinja
     runs-on: ${{ matrix.os }}
     defaults:
       run:
@@ -126,7 +127,6 @@ jobs:
     - name: Dependencies
       run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
       env:
-        MXE_TARGET: ${{ matrix.mxe }}
         VCMI_BUILD_PLATFORM: x64
 
     - uses: actions/setup-python@v4
@@ -164,40 +164,16 @@ jobs:
       env:
         PULL_REQUEST: ${{ github.event.pull_request.number }}
 
-    - name: Configure CMake
-      if: "${{ matrix.preset == '' }}"
-      run: |
-        mkdir -p '${{github.workspace}}/out/build/${{matrix.preset}}'
-        cd '${{github.workspace}}/out/build/${{matrix.preset}}'
-        cmake \
-            ../.. -GNinja \
-            ${{matrix.cmake_args}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-            -DENABLE_TEST=${{matrix.test}} \
-            -DENABLE_STRICT_COMPILATION=ON \
-            -DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX" \
-            -DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME" \
-            -DENABLE_GITVERSION="$VCMI_PACKAGE_GITVERSION"
-      env:
-        CC: ${{ matrix.cc }}
-        CXX: ${{ matrix.cxx }}
-
     - name: CMake Preset
-      if: "${{ matrix.preset != '' }}"
       run: |
         cmake --preset ${{ matrix.preset }}
 
-    - name: Build
-      if: "${{ matrix.preset == '' }}"
-      run: |
-        cmake --build '${{github.workspace}}/out/build/${{matrix.preset}}'
-
     - name: Build Preset
-      if: "${{ matrix.preset != '' }}"
       run: |
         cmake --build --preset ${{matrix.preset}}
 
     - name: Test
-      if: ${{ matrix.test == 1 &&  matrix.preset != ''}}
+      if: ${{ matrix.test == 1 }}
       run: |
         ctest --preset ${{matrix.preset}}
 
@@ -213,7 +189,7 @@ jobs:
         rm -rf _CPack_Packages
 
     - name: Additional logs
-      if: ${{ failure() && steps.cpack.outcome == 'failure' && matrix.platform == 'mxe' }}
+      if: ${{ failure() && steps.cpack.outcome == 'failure' && matrix.platform == 'msvc' }}
       run: |
         cat '${{github.workspace}}/out/build/${{matrix.preset}}/_CPack_Packages/win32/NSIS/project.nsi'
         cat '${{github.workspace}}/out/build/${{matrix.preset}}/_CPack_Packages/win32/NSIS/NSISOutput.log'
@@ -227,7 +203,7 @@ jobs:
           ${{github.workspace}}/**/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
 
     - name: Upload build
-      if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
+      if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform == 'msvc' }}
       run: |
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         source '${{github.workspace}}/CI/upload_package.sh'
@@ -245,7 +221,7 @@ jobs:
 
     - name: Trigger Android
       uses: peter-evans/repository-dispatch@v1
-      if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
+      if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') && matrix.platform == 'msvc' }}
       with:
         token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }}
         repository: vcmi/vcmi-android

+ 8 - 0
AI/CMakeLists.txt

@@ -12,6 +12,10 @@ if(TBB_FOUND AND MSVC)
 	   install_vcpkg_imported_tgt(TBB::tbb)
 endif()
 
+#FuzzyLite uses MSVC pragmas in headers, so, we need to disable -Wunknown-pragmas
+if(MINGW)
+    add_compile_options(-Wno-unknown-pragmas)
+endif()
 
 if(NOT FORCE_BUNDLED_FL)
 	find_package(fuzzylite)
@@ -27,6 +31,10 @@ if(NOT fuzzylite_FOUND)
     set(FL_BUILD_BINARY OFF CACHE BOOL "")
     set(FL_BUILD_SHARED OFF CACHE BOOL "")
 	set(FL_BUILD_TESTS OFF CACHE BOOL "")
+	#It is for compiling FuzzyLite, it will not compile without it on GCC
+	if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
+		add_compile_options(-Wno-error=deprecated-declarations)
+	endif()
 	add_subdirectory(FuzzyLite/fuzzylite EXCLUDE_FROM_ALL)
 	add_library(fuzzylite::fuzzylite ALIAS fl-static)
 	target_include_directories(fl-static PUBLIC ${CMAKE_HOME_DIRECTORY}/AI/FuzzyLite/fuzzylite)

+ 3 - 2
AI/Nullkiller/AIUtility.cpp

@@ -314,8 +314,9 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
 		return false;
 
 	//TODO: allow polling of remaining creatures in dwelling
-	if(dynamic_cast<const CGVisitableOPW *>(obj)) // ensures future compatibility, unlike IDs
-		return true;
+	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
+		return rewardable->getResetDuration() == 7;
+
 	if(dynamic_cast<const CGDwelling *>(obj))
 		return true;
 	if(dynamic_cast<const CBank *>(obj)) //banks tend to respawn often in mods

+ 9 - 6
AI/Nullkiller/Engine/AIMemory.cpp

@@ -70,12 +70,15 @@ void AIMemory::markObjectVisited(const CGObjectInstance * obj)
 		return;
 	
 	// TODO: maybe this logic belongs to CaptureObjects::shouldVisit
-	if(dynamic_cast<const CGVisitableOPH *>(obj)) //we may want to visit it with another hero
-		return;
-	
-	if(dynamic_cast<const CGBonusingObject *>(obj)) //or another time
-		return;
-	
+	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
+	{
+		if (rewardable->getVisitMode() == CRewardableObject::VISIT_HERO) //we may want to visit it with another hero
+			return;
+
+		if (rewardable->getVisitMode() == CRewardableObject::VISIT_BONUS) //or another time
+			return;
+	}
+
 	if(obj->ID == Obj::MONSTER)
 		return;
 

+ 0 - 4
AI/VCAI/MapObjectsEvaluator.cpp

@@ -32,10 +32,6 @@ MapObjectsEvaluator::MapObjectsEvaluator()
 				{
 					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get();
 				}
-				else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists
-				{
-					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get();
-				}
 				else //some default handling when aiValue not found, objects that require advanced properties (unavailable from handler) get their value calculated in getObjectValue
 				{
 					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0;

+ 14 - 12
AI/VCAI/VCAI.cpp

@@ -29,12 +29,6 @@
 
 extern FuzzyHelper * fh;
 
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CGVisitableOPW;
-
-VCMI_LIB_NAMESPACE_END
-
 const double SAFE_ATTACK_CONSTANT = 1.5;
 
 //one thread may be turn of AI and another will be handling a side effect for AI2
@@ -1606,12 +1600,19 @@ void VCAI::markObjectVisited(const CGObjectInstance * obj)
 {
 	if(!obj)
 		return;
-	if(dynamic_cast<const CGVisitableOPH *>(obj)) //we may want to visit it with another hero
-		return;
-	if(dynamic_cast<const CGBonusingObject *>(obj)) //or another time
-		return;
+
+	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj)) //we may want to visit it with another hero
+	{
+		if (rewardable->getVisitMode() == CRewardableObject::VISIT_HERO) //we may want to visit it with another hero
+			return;
+
+		if (rewardable->getVisitMode() == CRewardableObject::VISIT_BONUS) //or another time
+			return;
+	}
+
 	if(obj->ID == Obj::MONSTER)
 		return;
+
 	alreadyVisited.insert(obj);
 }
 
@@ -2742,8 +2743,9 @@ bool AIStatus::channelProbing()
 bool isWeeklyRevisitable(const CGObjectInstance * obj)
 {
 	//TODO: allow polling of remaining creatures in dwelling
-	if(dynamic_cast<const CGVisitableOPW *>(obj)) // ensures future compatibility, unlike IDs
-		return true;
+	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
+		return rewardable->getResetDuration() == 7;
+
 	if(dynamic_cast<const CGDwelling *>(obj))
 		return true;
 	if(dynamic_cast<const CBank *>(obj)) //banks tend to respawn often in mods

+ 0 - 2
CCallback.cpp

@@ -11,7 +11,6 @@
 #include "CCallback.h"
 
 #include "lib/CCreatureHandler.h"
-#include "client/CGameInfo.h"
 #include "lib/CGameState.h"
 #include "client/CPlayerInterface.h"
 #include "client/Client.h"
@@ -21,7 +20,6 @@
 #include "lib/CGeneralTextHandler.h"
 #include "lib/CHeroHandler.h"
 #include "lib/NetPacks.h"
-#include "client/mapHandler.h"
 #include "lib/CArtHandler.h"
 #include "lib/GameConstants.h"
 #include "lib/CPlayerState.h"

+ 20 - 0
CI/conan/base/cross-macro.j2

@@ -0,0 +1,20 @@
+{% macro generate_env(target_host) -%}
+CONAN_CROSS_COMPILE={{ target_host }}-
+CHOST={{ target_host }}
+AR={{ target_host }}-ar
+AS={{ target_host }}-as
+CC={{ target_host }}-gcc
+CXX={{ target_host }}-g++
+RANLIB={{ target_host }}-ranlib
+STRIP={{ target_host }}-strip
+{%- endmacro -%}
+
+{% macro generate_env_win32(target_host) -%}
+CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/10-posix/
+RC={{ target_host }}-windres
+{%- endmacro -%}
+
+{% macro generate_conf(target_host) -%}
+tools.build:compiler_executables = {"c": "{{ target_host }}-gcc", "cpp": "{{ target_host }}-g++"}
+tools.build:sysroot = /usr/{{ target_host }}
+{%- endmacro -%}

+ 10 - 0
CI/conan/base/cross-windows

@@ -0,0 +1,10 @@
+[settings]
+os=Windows
+compiler=gcc
+compiler.libcxx=libstdc++11
+compiler.version=10
+compiler.cppstd=11
+build_type=Release
+
+[conf]
+tools.cmake.cmaketoolchain:generator = Ninja

+ 15 - 0
CI/conan/mingw32-linux.jinja

@@ -0,0 +1,15 @@
+{% import 'base/cross-macro.j2' as cross -%}
+include(base/cross-windows)
+{% set target_host="i686-w64-mingw32" %}
+
+[settings]
+arch=x86
+
+[conf]
+{{ cross.generate_conf(target_host)}}
+tools.build:cflags = ["-msse2"]
+tools.build:cxxflags = ["-msse2"]
+
+[env]
+{{ cross.generate_env(target_host) }}
+{{ cross.generate_env_win32(target_host) }}

+ 13 - 0
CI/conan/mingw64-linux.jinja

@@ -0,0 +1,13 @@
+{% import 'base/cross-macro.j2' as cross -%}
+include(base/cross-windows)
+{% set target_host="x86_64-w64-mingw32" %}
+
+[settings]
+arch=x86_64
+
+[conf]
+{{ cross.generate_conf(target_host)}}
+
+[env]
+{{ cross.generate_env(target_host) }}
+{{ cross.generate_env_win32(target_host) }}

+ 0 - 3
CI/ios/post_pack.sh

@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-"$1/ios/zip2ipa.sh" "$2"

+ 16 - 0
CI/mingw-ubuntu/before_install.sh

@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+sudo apt-get update
+sudo apt-get install ninja-build mingw-w64 nsis
+sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
+
+# Workaround for getting new MinGW headers on Ubuntu 22.04.
+# Remove it once MinGW headers version in repository will be 10.0 at least
+curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-2_all.deb \
+  && sudo dpkg -i mingw-w64-common_10.0.0-2_all.deb;
+curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-x86-64-dev_10.0.0-2_all.deb \
+  && sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-2_all.deb;
+
+mkdir ~/.conan ; cd ~/.conan
+curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.0/vcmi-deps-windows-conan-w64.tgz" \
+	| tar -xzf -

+ 0 - 53
CI/mxe/before_install.sh

@@ -1,53 +0,0 @@
-#!/bin/sh
-
-# steps to upgrade MXE dependencies:
-# 1) Use Debian/Ubuntu system or install one (virtual machines will work too)
-# 2) update following script to include any new dependencies 
-# You can also run it to upgrade existing ones, but don't expect much
-# MXE repository only provides ancient versions for the sake of "stability"
-# https://github.com/vcmi/vcmi-deps-mxe/blob/master/mirror-mxe.sh
-# 3) make release in vcmi-deps-mxe repository using resulting tar archive
-# 4) update paths to tar archive in this script
-
-# Install nsis for installer creation
-sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security main'
-sudo apt-get install -qq nsis ninja-build libssl1.0.0
-
-# MXE repository was too slow for Travis far too often
-wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2021-02-20/mxe-i686-w64-mingw32.shared-2021-01-22.tar
-tar -xvf mxe-i686-w64-mingw32.shared-2021-01-22.tar
-sudo dpkg -i mxe-*.deb
-sudo apt-get install -f --yes
-
-if false; then
-	# Add MXE repository and key
-	echo "deb http://pkg.mxe.cc/repos/apt/debian wheezy main" \
-		| sudo tee /etc/apt/sources.list.d/mxeapt.list
-
-	sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D43A795B73B16ABE9643FE1AFD8FFF16DB45C6AB
-
-	# Install needed packages
-	sudo apt-get update -qq
-
-	sudo apt-get install -q --yes \
-	mxe-$MXE_TARGET-gcc \
-	mxe-$MXE_TARGET-boost \
-	mxe-$MXE_TARGET-zlib \
-	mxe-$MXE_TARGET-sdl2 \
-	mxe-$MXE_TARGET-sdl2-gfx \
-	mxe-$MXE_TARGET-sdl2-image \
-	mxe-$MXE_TARGET-sdl2-mixer \
-	mxe-$MXE_TARGET-sdl2-ttf \
-	mxe-$MXE_TARGET-ffmpeg \
-	mxe-$MXE_TARGET-qt \
-	mxe-$MXE_TARGET-qtbase \
-	mxe-$MXE_TARGET-intel-tbb \
-	mxe-i686-w64-mingw32.static-luajit
-
-fi # Disable
-
-# alias for CMake
-
-CMAKE_LOCATION=$(which cmake)
-sudo mv $CMAKE_LOCATION $CMAKE_LOCATION.orig
-sudo ln -s /usr/lib/mxe/usr/bin/$MXE_TARGET-cmake $CMAKE_LOCATION

+ 59 - 45
CMakeLists.txt

@@ -1,7 +1,7 @@
 # Minimum required version greatly affect CMake behavior
 # So cmake_minimum_required must be called before the project()
-# 3.10.0 is used since it's minimal in MXE dependencies for now
-cmake_minimum_required(VERSION 3.10.0)
+# 3.16.0 is used since it's used by our currently oldest suppored system: Ubuntu-20.04
+cmake_minimum_required(VERSION 3.16.0)
 
 project(VCMI)
 # TODO
@@ -10,9 +10,6 @@ project(VCMI)
 # Cmake put them after all install code of main CMakelists in cmake_install.cmake
 # Currently I just added extra add_subdirectory and CMakeLists.txt in osx directory to bypass that.
 #
-# MXE:
-# - Try to implement MXE support into BundleUtilities so we can deploy deps automatically
-#
 # Vckpg:
 # - Improve install code once there is better way to deploy DLLs and Qt plugins
 #
@@ -57,9 +54,8 @@ if(APPLE_IOS)
 else()
 	option(ENABLE_TEST "Enable compilation of unit tests" OFF)
 endif()
-if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
-	option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
-endif(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
+
+option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
 option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
 option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
 option(ENABLE_STRICT_COMPILATION "Treat all compiler warnings as errors" OFF)
@@ -90,11 +86,6 @@ if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
 	set(COPY_CONFIG_ON_BUILD OFF)
 endif()
 
-# No QT Linguist on MXE
-if((MINGW) AND (${CMAKE_CROSSCOMPILING}))
-	set(ENABLE_TRANSLATIONS OFF)
-endif()
-
 ############################################
 #        Miscellaneous options             #
 ############################################
@@ -138,10 +129,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_
 	set(ENABLE_PCH OFF) # broken
 endif()
 
-if( ${CMAKE_VERSION} VERSION_LESS "3.16.0")
-	set(ENABLE_PCH OFF) #not supported
-endif()
-
 if(ENABLE_PCH)
 	macro(enable_pch name)
 		target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:<StdInc.h$<ANGLE-R>>)
@@ -168,12 +155,12 @@ set(CMAKE_CXX_VISIBILITY_PRESET hidden)
 set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
 
 #Global fallback mapping
-# RelWithDebInfo falls back to Release, then MinSizeRel
-set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel "")
-# MinSizeRel falls back to Release, then RelWithDebInfo
-set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo "")
-# Release falls back to RelWithDebInfo, then MinSizeRel
-set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release RelWithDebInfo MinSizeRel "")
+# RelWithDebInfo falls back to Release, then MinSizeRel, and then to None (tbb in 22.04 requires it)
+set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel None "")
+# MinSizeRel falls back to Release, then RelWithDebInfo, and then to None (tbb in 22.04 requires it)
+set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo None "")
+# Release falls back to RelWithDebInfo, then MinSizeRel, and then to None (tbb in 22.04 requires it)
+set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release RelWithDebInfo MinSizeRel None "")
 
 set(CMAKE_XCODE_ATTRIBUTE_APP_DISPLAY_NAME ${APP_DISPLAY_NAME})
 set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
@@ -296,12 +283,6 @@ if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32)
 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare")       # low chance of any significant issues
 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs")            # emitted in fuzzylite headers, disabled
 
-	if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
-		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
-		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-variable") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
-		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-maybe-uninitialized") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
-	endif()
-
 	if(ENABLE_STRICT_COMPILATION)
 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=array-bounds") # false positives in boost::multiarray during release build, keep as warning-only
@@ -328,6 +309,27 @@ if(ENABLE_LUA)
 	add_definitions(-DSCRIPTING_ENABLED=1)
 endif()
 
+if(USING_CONAN AND (MINGW AND CMAKE_HOST_UNIX))
+	# Hack for workaround https://github.com/conan-io/conan-center-index/issues/15405
+	# Remove once it will be fixed
+	execute_process(COMMAND
+		bash -c "grep -rl Mf ${CONAN_INSTALL_FOLDER} | xargs sed -i 's/Mf/mf/g'"
+	)
+	# Hack for workaround ffmpeg broken linking (conan ffmpeg forgots to link to ws2_32)
+	# Remove once it will be fixed
+	execute_process(COMMAND
+		bash -c "grep -rl secur32 ${CONAN_INSTALL_FOLDER} | xargs sed -i 's/secur32)/secur32 ws2_32)/g'"
+	)
+	execute_process(COMMAND
+		bash -c "grep -rl secur32 ${CONAN_INSTALL_FOLDER} | xargs sed -i 's/secur32 mfplat/secur32 ws2_32 mfplat/g'"
+	)
+	# Fixup tbb for cross-compiling on Conan
+	# Remove once it will be fixed
+	execute_process(COMMAND
+		bash -c "grep -rl tbb12 ${CONAN_INSTALL_FOLDER} | xargs sed -i 's/tbb tbb12/tbb12/g'"
+	)
+endif()
+
 ############################################
 #        Finding packages                  #
 ############################################
@@ -385,14 +387,7 @@ endif()
 
 if(ENABLE_LUA)
 	find_package(luajit)
-	# MXE paths hardcoded for current dependencies pack - tried and could not make it work another way
-	if((MINGW) AND (${CMAKE_CROSSCOMPILING}) AND (DEFINED MSYS) AND (NOT TARGET luajit::luajit))
-		add_library(luajit::luajit STATIC IMPORTED)
-		set_target_properties(luajit::luajit PROPERTIES
-			INTERFACE_INCLUDE_DIRECTORIES "/usr/lib/mxe/usr/i686-w64-mingw32.static/include/luajit-2.0")
-		set_target_properties(luajit::luajit PROPERTIES
-			IMPORTED_LOCATION "/usr/lib/mxe/usr/i686-w64-mingw32.static/lib/libluajit-5.1.a")
-	endif()
+
 	if(TARGET luajit::luajit)
 		message(STATUS "Using LuaJIT provided by system")
 	else()
@@ -544,13 +539,21 @@ endif()
 
 
 if(WIN32)
-	file(GLOB dep_files
-		${dep_files}
-		"${CMAKE_FIND_ROOT_PATH}/bin/*.dll")
+	if(USING_CONAN)
+		#Conan imports enabled
+		vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
+		file(GLOB dep_files
+				${dep_files}
+				"${CMAKE_SYSROOT}/bin/*.dll" 
+				"${CMAKE_SYSROOT}/lib/*.dll" 
+				"${CONAN_SYSTEM_LIBRARY_LOCATION}/*.dll")
+	else()
+		file(GLOB dep_files
+				${dep_files}
+				"${CMAKE_FIND_ROOT_PATH}/bin/*.dll")
+	endif()
 
-	if((${CMAKE_CROSSCOMPILING}) AND (DEFINED MSYS))
-		message(STATUS "Detected MXE build")
-	elseif(CMAKE_BUILD_TYPE MATCHES Debug)
+	if(CMAKE_BUILD_TYPE MATCHES Debug)
 		# Copy debug versions of libraries if build type is debug
 		set(debug_postfix d)
 	endif()
@@ -617,7 +620,11 @@ if(WIN32)
 	else()
 		set(CPACK_NSIS_PACKAGE_NAME "VCMI ${CPACK_PACKAGE_VERSION} ${PACKAGE_NAME_SUFFIX} ")
 	endif()
-	set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
+	if(CMAKE_SYSTEM_PROCESSOR MATCHES ".*64")
+		set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
+	else()
+		set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
+	endif()
 	if(ENABLE_LAUNCHER)
 		set(CPACK_PACKAGE_EXECUTABLES "VCMI_launcher;VCMI")
 		set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " CreateShortCut \\\"$DESKTOP\\\\VCMI.lnk\\\" \\\"$INSTDIR\\\\VCMI_launcher.exe\\\"")
@@ -627,6 +634,12 @@ if(WIN32)
 	endif()
 	set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " Delete \\\"$DESKTOP\\\\VCMI.lnk\\\" ")
 
+	# Strip MinGW CPack target if build configuration without debug info
+	if(MINGW)
+		if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug") OR (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
+			set(CPACK_STRIP_FILES ON)
+		endif()
+	endif()
 	# set the install/unistall icon used for the installer itself
 	# There is a bug in NSI that does not handle full unix paths properly.
 	set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/client\\\\vcmi.ico")
@@ -643,7 +656,7 @@ if(WIN32)
 	set(CPACK_NSIS_URL_INFO_ABOUT "http://vcmi.eu/")
 	set(CPACK_NSIS_CONTACT @CPACK_PACKAGE_CONTACT@)
 	set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
-	# Use BundleUtilities to fix build when Vcpkg is used and disable it for MXE
+	# Use BundleUtilities to fix build when Vcpkg is used and disable it for mingw
 	if(NOT (${CMAKE_CROSSCOMPILING}))
 		add_subdirectory(win)
 	endif()
@@ -687,6 +700,7 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
 	add_subdirectory(osx)
 elseif(APPLE_IOS)
 	set(CPACK_GENERATOR ZIP)
+	set(CPACK_ARCHIVE_FILE_EXTENSION ipa)
 	set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
 	set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};${CMAKE_PROJECT_NAME};app;/")
 else()

+ 24 - 0
CMakePresets.json

@@ -81,6 +81,19 @@
                 "FORCE_BUNDLED_MINIZIP": "ON"
             }
         },
+        {
+            "name": "windows-mingw-conan-linux",
+            "displayName": "Ninja+Conan release",
+            "description": "VCMI Windows Ninja using Conan on Linux",
+            "inherits": [
+                "build-with-conan",
+                "default-release"
+            ],
+            "cacheVariables": {
+                "CMAKE_BUILD_TYPE": "Release",
+                "FORCE_BUNDLED_FL": "ON"
+            }
+        },
         {
             "name": "macos-ninja-release",
             "displayName": "Ninja release",
@@ -222,6 +235,12 @@
             "configurePreset": "windows-msvc-release",
             "inherits": "default-release"
         },
+        {
+            "name": "windows-mingw-conan-linux",
+            "configurePreset": "windows-mingw-conan-linux",
+            "inherits": "default-release",
+            "configuration": "Release"
+        },
         {
             "name": "ios-release-conan",
             "configurePreset": "ios-release-conan",
@@ -271,6 +290,11 @@
             "name": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",
             "inherits": "default-release"
+        },
+        {
+            "name": "windows-mingw-conan-linux",
+            "configurePreset": "windows-mingw-conan-linux",
+            "inherits": "default-release"
         }
     ]
 }

File diff suppressed because it is too large
+ 324 - 319
ChangeLog.md


+ 243 - 0
Mods/vcmi/config/vcmi/english.json

@@ -0,0 +1,243 @@
+{
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Threat: ",
+	"vcmi.adventureMap.monsterThreat.levels.0"  : "Effortless",
+	"vcmi.adventureMap.monsterThreat.levels.1"  : "Very Weak",
+	"vcmi.adventureMap.monsterThreat.levels.2"  : "Weak",
+	"vcmi.adventureMap.monsterThreat.levels.3"  : "A bit weaker",
+	"vcmi.adventureMap.monsterThreat.levels.4"  : "Equal",
+	"vcmi.adventureMap.monsterThreat.levels.5"  : "A bit stronger",
+	"vcmi.adventureMap.monsterThreat.levels.6"  : "Strong",
+	"vcmi.adventureMap.monsterThreat.levels.7"  : "Very Strong",
+	"vcmi.adventureMap.monsterThreat.levels.8"  : "Challenging",
+	"vcmi.adventureMap.monsterThreat.levels.9"  : "Overpowering",
+	"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
+	"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
+
+	"vcmi.adventureMap.confirmRestartGame"     : "Are you sure you want to restart game?",
+	"vcmi.adventureMap.noTownWithMarket"       : "No available marketplace!",
+	"vcmi.adventureMap.noTownWithTavern"       : "No available town with tavern!",
+	"vcmi.adventureMap.spellUnknownProblem"    : "Unknown problem with this spell, no more information available.",
+	"vcmi.adventureMap.playerAttacked"         : "Player has been attacked: %s",
+	"vcmi.adventureMap.moveCostDetails"        : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING",
+	"vcmi.adventureMap.moveCostDetailsNoTurns" : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING",
+
+	"vcmi.server.errors.existingProcess"     : "Another vcmiserver process is running, please terminate it first",
+	"vcmi.server.errors.modsIncompatibility" : "Required mods to load game:",
+	"vcmi.server.confirmReconnect"          : "Connect to the last session?",
+
+	"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
+	"vcmi.systemOptions.fullscreenButton.help"  : "{Fullscreen}\n\n If selected, VCMI will run in fullscreen mode, othervice VCMI will run in window",
+	"vcmi.systemOptions.resolutionButton.hover" : "Resolution",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Select resolution}\n\n Change in-game screen resolution. Game restart required to apply new resolution.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Select resolution",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Change in-game screen resolution.",
+
+	"vcmi.townHall.missingBase"             : "Base building %s must be built first",
+	"vcmi.townHall.noCreaturesToRecruit"    : "There are no creatures to recruit!",
+	"vcmi.townHall.greetingManaVortex"      : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
+	"vcmi.townHall.greetingKnowledge"       : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
+	"vcmi.townHall.greetingSpellPower"      : "The %s teaches you new ways to focus your magical powers (+1 Power).",
+	"vcmi.townHall.greetingExperience"      : "A visit to the %s teaches you many new skills (+1000 Experience).",
+	"vcmi.townHall.greetingAttack"          : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
+	"vcmi.townHall.greetingDefence"         : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
+	"vcmi.townHall.hasNotProduced"          : "The %s has not produced anything yet.",
+	"vcmi.townHall.hasProduced"             : "The %s produced %d %s this week.",
+	"vcmi.townHall.greetingCustomBonus"     : "%s gives you +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil"     : " until next battle.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.",
+
+	"vcmi.logicalExpressions.anyOf"  : "Any of the following:",
+	"vcmi.logicalExpressions.allOf"  : "All of the following:",
+	"vcmi.logicalExpressions.noneOf" : "None of the following:",
+
+	"vcmi.heroWindow.openCommander.hover" : "Open commander window",
+	"vcmi.heroWindow.openCommander.help"  : "Displays information about commander of this hero",
+
+	"vcmi.commanderWindow.artifactMessage" : "Do you want to give this artifact back to hero?",
+
+	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",
+	"vcmi.creatureWindow.showBonuses.help"     : "Displays all active bonuses of the commander",
+	"vcmi.creatureWindow.showSkills.hover"     : "Switch to skills view",
+	"vcmi.creatureWindow.showSkills.help"      : "Displays all learned skills of the commander",
+	"vcmi.creatureWindow.returnArtifact.hover" : "Give back artifact",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Use this button to return stack artifact back into hero backpack",
+
+	"vcmi.questLog.hideComplete.hover" : "Hide complete quests",
+	"vcmi.questLog.hideComplete.help"  : "Hide all quests that already completed",
+
+	"vcmi.randomMapTab.widgets.defaultTemplate"      : "default",
+	"vcmi.randomMapTab.widgets.templateLabel"        : "Template",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Setup...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Team alignments",
+	
+	// few strings from WoG used by vcmi
+	"vcmi.stackExperience.description" : "» S t a c k   E x p e r i e n c e   D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
+	"vcmi.stackExperience.rank.1" : "Basic",
+	"vcmi.stackExperience.rank.2" : "Novice",
+	"vcmi.stackExperience.rank.3" : "Trained",
+	"vcmi.stackExperience.rank.4" : "Skilled",
+	"vcmi.stackExperience.rank.5" : "Proven",
+	"vcmi.stackExperience.rank.6" : "Veteran",
+	"vcmi.stackExperience.rank.7" : "Adept",
+	"vcmi.stackExperience.rank.8" : "Expert",
+	"vcmi.stackExperience.rank.9" : "Elite",
+	"vcmi.stackExperience.rank.10" : "Master",
+	"vcmi.stackExperience.rank.11" : "Ace",
+	
+	"core.bonus.ADDITIONAL_ATTACK.name": "Double Strike",
+	"core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "May Retaliate ${val} extra times",
+	"core.bonus.AIR_IMMUNITY.name": "Air immunity",
+	"core.bonus.AIR_IMMUNITY.description": "Immune to all Air school spells",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Attack all around",
+	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Attacks all adjacent enemies",
+	"core.bonus.BLOCKS_RETALIATION.name": "No retaliation",
+	"core.bonus.BLOCKS_RETALIATION.description": "Enemy cannot Retaliate",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "No ranged retaliation",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot Retaliate by shooting",
+	"core.bonus.CATAPULT.name": "Catapult",
+	"core.bonus.CATAPULT.description": "Attacks siege walls",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.name": "Additional siege attacks",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.description": "Can hit siege walls ${val} extra times per attack",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduces spell cost for hero",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Magic Damper (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Increases Cost of enemy spells",
+	"core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge",
+	"core.bonus.CHARGE_IMMUNITY.description": "Immune to Champion charge",
+	"core.bonus.DARKNESS.name": "Darkness cover",
+	"core.bonus.DARKNESS.description": "Adds ${val} darkness radius",
+	"core.bonus.DEATH_STARE.name": "Death Stare (${val}%)",
+	"core.bonus.DEATH_STARE.description": "${val}% chance to kill single creature",
+	"core.bonus.DEFENSIVE_STANCE.name": "Defense Bonus",
+	"core.bonus.DEFENSIVE_STANCE.description": "+${val} Defense when defending",
+	"core.bonus.DESTRUCTION.name": "Destruction",
+	"core.bonus.DESTRUCTION.description": "Has ${val}% chance to kill extra units after attack",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Death Blow",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% chance for double damage",
+	"core.bonus.DRAGON_NATURE.name": "Dragon",
+	"core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Direct Damage Immunity",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Immune to direct damage spells",
+	"core.bonus.EARTH_IMMUNITY.name": "Earth immunity",
+	"core.bonus.EARTH_IMMUNITY.description": "Immune to all Earth school spells",
+	"core.bonus.ENCHANTER.name": "Enchanter",
+	"core.bonus.ENCHANTER.description": "Can cast mass ${subtype.spell} every turn",
+	"core.bonus.ENCHANTED.name": "Enchanted",
+	"core.bonus.ENCHANTED.description": "Affected by permanent ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignore Defense (${val}%)",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignores part of Defence for the attack",
+	"core.bonus.FIRE_IMMUNITY.name": "Fire immunity",
+	"core.bonus.FIRE_IMMUNITY.description": "Immune to all Fire school spells",
+	"core.bonus.FIRE_SHIELD.name": "Fire Shield (${val}%)",
+	"core.bonus.FIRE_SHIELD.description": "Reflects part of melee damage",
+	"core.bonus.FIRST_STRIKE.name": "First Strike",
+	"core.bonus.FIRST_STRIKE.description": "This creature attacks first instead of retaliating",
+	"core.bonus.FEAR.name": "Fear",
+	"core.bonus.FEAR.description": "Causes Fear on an enemy stack",
+	"core.bonus.FEARLESS.name": "Fearless",
+	"core.bonus.FEARLESS.description": "Immune to Fear ability",
+	"core.bonus.FLYING.name": "Fly",
+	"core.bonus.FLYING.description": "Can Fly (ignores obstacles)",
+	"core.bonus.FREE_SHOOTING.name": "Shoot Close",
+	"core.bonus.FREE_SHOOTING.description": "Can shoot in Close Combat",
+	"core.bonus.FULL_HP_REGENERATION.name": "Regeneration",
+	"core.bonus.FULL_HP_REGENERATION.description": "May Regenerate to full Health",
+	"core.bonus.GARGOYLE.name": "Gargoyle",
+	"core.bonus.GARGOYLE.description": "Cannot be rised or healed",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reduce Damage (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduces physical damage from ranged or melee",
+	"core.bonus.HATE.name": "Hates ${subtype.creature}",
+	"core.bonus.HATE.description": "Does ${val}% more damage",
+	"core.bonus.HEALER.name": "Healer",
+	"core.bonus.HEALER.description": "Heals allied units",
+	"core.bonus.HP_REGENERATION.name": "Regeneration",
+	"core.bonus.HP_REGENERATION.description": "Heals ${val} hit points every round",
+	"core.bonus.JOUSTING.name": "Champion Charge",
+	"core.bonus.JOUSTING.description": "+5% damage per hex travelled",
+	"core.bonus.KING1.name": "King 1",
+	"core.bonus.KING1.description": "Vulnerable to basic SLAYER",
+	"core.bonus.KING2.name": "King 2",
+	"core.bonus.KING2.description": "Vulnerable to advanced SLAYER",
+	"core.bonus.KING3.name": "King 3",
+	"core.bonus.KING3.description":"Vulnerable to expert SLAYER",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Spell immunity 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immune to spells of levels 1-${val}",
+	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Limited shooting range",
+	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "",
+	"core.bonus.LIFE_DRAIN.name": "Drain life (${val}%)",
+	"core.bonus.LIFE_DRAIN.description": "Drains ${val}% of damage dealt",
+	"core.bonus.MANA_CHANNELING.name": "Magic Channel ${val}%",
+	"core.bonus.MANA_CHANNELING.description": "Gives your hero mana spent by enemy",
+	"core.bonus.MANA_DRAIN.name": "Mana Drain",
+	"core.bonus.MANA_DRAIN.description": "Drains ${val} mana every turn",
+	"core.bonus.MAGIC_MIRROR.name": "Magic Mirror (${val}%)",
+	"core.bonus.MAGIC_MIRROR.description": "${val}% chance to redirects an offensive spell to enemy",
+	"core.bonus.MAGIC_RESISTANCE.name": "Magic Resistance(${MR}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "${MR}% chance to resist enemy spell",
+	"core.bonus.MIND_IMMUNITY.name": "Mind Spell Immunity",
+	"core.bonus.MIND_IMMUNITY.description": "Immune to Mind-type spells",
+	"core.bonus.NO_DISTANCE_PENALTY.name": "No distance penalty",
+	"core.bonus.NO_DISTANCE_PENALTY.description": "Full damage from any distance",
+	"core.bonus.NO_MELEE_PENALTY.name": "No melee penalty",
+	"core.bonus.NO_MELEE_PENALTY.description": "Creature has no Melee Penalty",
+	"core.bonus.NO_MORALE.name": "Neutral Morale",
+	"core.bonus.NO_MORALE.description": "Creature is immune to morale effects",
+	"core.bonus.NO_WALL_PENALTY.name": "No wall penalty",
+	"core.bonus.NO_WALL_PENALTY.description": "Full damage during siege",
+	"core.bonus.NON_LIVING.name": "Non living",
+	"core.bonus.NON_LIVING.description": "Immunity to many effects",
+	"core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster",
+	"core.bonus.RANDOM_SPELLCASTER.description": "Can cast random spell",
+	"core.bonus.RANGED_RETALIATION.name": "Ranged retaliation",
+	"core.bonus.RANGED_RETALIATION.description": "Can perform ranged counterattack",
+	"core.bonus.RECEPTIVE.name": "Receptive",
+	"core.bonus.RECEPTIVE.description": "No Immunity to Friendly Spells",
+	"core.bonus.REBIRTH.name": "Rebirth (${val}%)",
+	"core.bonus.REBIRTH.description": "${val}% of stack will rise after death",
+	"core.bonus.RETURN_AFTER_STRIKE.name": "Attack and Return",
+	"core.bonus.RETURN_AFTER_STRIKE.description": "Returns after melee attack",
+	"core.bonus.SELF_LUCK.name": "Positive luck",
+	"core.bonus.SELF_LUCK.description": "Always has Positive Luck",
+	"core.bonus.SELF_MORALE.name": "Positive morale",
+	"core.bonus.SELF_MORALE.description": "Always has Positive Morale",
+	"core.bonus.SHOOTER.name": "Ranged",
+	"core.bonus.SHOOTER.description": "Creature can shoot",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description": "This creature's ranged attacks strike all targets in a small area",
+	"core.bonus.SOUL_STEAL.name": "Soul Steal",
+	"core.bonus.SOUL_STEAL.description": "Gains ${val} new creatures for each enemy killed",
+	"core.bonus.SPELLCASTER.name": "Spellcaster",
+	"core.bonus.SPELLCASTER.description": "Can cast ${subtype.spell}",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "Cast After Attack",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% to cast ${subtype.spell} after attack",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "Cast Before Attack",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% to cast ${subtype.spell} before attack",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Spell Resistance",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Damage from spells reduced ${val}%.",
+	"core.bonus.SPELL_IMMUNITY.name": "Spell immunity",
+	"core.bonus.SPELL_IMMUNITY.description": "Immune to ${subtype.spell}",
+	"core.bonus.SPELL_LIKE_ATTACK.name": "Spell-like attack",
+	"core.bonus.SPELL_LIKE_ATTACK.description": "Attacks with ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura of Resistance",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Nearby stacks get ${val}% resistance",
+	"core.bonus.SUMMON_GUARDIANS.name": "Summon guardians",
+	"core.bonus.SUMMON_GUARDIANS.description": "At battle start summons ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name": "Synergizable",
+	"core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Breath Attack (2-hex range)",
+	"core.bonus.THREE_HEADED_ATTACK.name": "Three-headed attack",
+	"core.bonus.THREE_HEADED_ATTACK.description": "Attacks three adjacent units",
+	"core.bonus.TRANSMUTATION.name": "Transmutation",
+	"core.bonus.TRANSMUTATION.description": "${val}% chance to transform attacked unit to other type",
+	"core.bonus.UNDEAD.name": "Undead",
+	"core.bonus.UNDEAD.description": "Creature is Undead",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Unlimited retaliations",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Retaliates any number of attacks",
+	"core.bonus.WATER_IMMUNITY.name": "Water immunity",
+	"core.bonus.WATER_IMMUNITY.description": "Immune to all Water school spells",
+	"core.bonus.WIDE_BREATH.name": "Wide breath",
+	"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)"
+}

+ 222 - 0
Mods/vcmi/config/vcmi/german.json

@@ -0,0 +1,222 @@
+{
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Bedrohung: ",
+	"vcmi.adventureMap.monsterThreat.levels.0"  : "Mühelos",
+	"vcmi.adventureMap.monsterThreat.levels.1"  : "Sehr schwach",
+	"vcmi.adventureMap.monsterThreat.levels.2"  : "Schwach",
+	"vcmi.adventureMap.monsterThreat.levels.3"  : "Ein bisschen schwächer",
+	"vcmi.adventureMap.monsterThreat.levels.4"  : "Gleichauf",
+	"vcmi.adventureMap.monsterThreat.levels.5"  : "Ein bisschen stärker",
+	"vcmi.adventureMap.monsterThreat.levels.6"  : "Stark",
+	"vcmi.adventureMap.monsterThreat.levels.7"  : "Sehr Stark",
+	"vcmi.adventureMap.monsterThreat.levels.8"  : "Herausfordernd",
+	"vcmi.adventureMap.monsterThreat.levels.9"  : "Überwältigend",
+	"vcmi.adventureMap.monsterThreat.levels.10" : "Tötlich",
+	"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
+
+	"vcmi.adventureMap.confirmRestartGame"  : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?",
+	"vcmi.adventureMap.noTownWithMarket"    : "Kein Marktplatz verfügbar!",
+	"vcmi.adventureMap.noTownWithTavern"    : "Keine Stadt mit Taverne verfügbar!",
+	"vcmi.adventureMap.spellUnknownProblem" : "Unbekanntes Problem mit diesem Zauberspruch, keine weiteren Informationen verfügbar.",
+	"vcmi.adventureMap.playerAttacked"      : "Spieler wurde attackiert: %s",
+
+	"vcmi.server.errors.existingProcess"     : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
+	"vcmi.server.errors.modsIncompatibility" : "Erforderliche Mods um das Spiel zu laden:",
+	
+
+	"vcmi.systemOptions.fullscreenButton.hover" : "Vollbild",
+	"vcmi.systemOptions.fullscreenButton.help"  : "{Fullscreen}\n\n Wenn ausgewählt wird VCMI im Vollbildmodus laufen, ansonsten im Fenstermodus",
+	"vcmi.systemOptions.resolutionButton.hover" : "Auflösung",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Select resolution}\n\n Ändert die Spielauflösung. Spielneustart ist erforderlich um neue Auflösung zu übernehmen.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Wähle Auflösung",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Ändere die Spielauflösung.",
+
+	"vcmi.townHall.missingBase"             : "Basis Gebäude %s muss als erstes gebaut werden",
+	"vcmi.townHall.noCreaturesToRecruit"    : "Es gibt keine Kreaturen zu rekrutieren!",
+	"vcmi.townHall.greetingManaVortex"      : "Wenn Ihr Euch den %s nähert, wird Euer Körper mit neuer Energie gefüllt. Ihr habt Eure normalen Zauberpunkte verdoppelt.",
+	"vcmi.townHall.greetingKnowledge"       : "Ihr studiert die Glyphen auf dem %s und erhaltet Einblick in die Funktionsweise verschiedener Magie (+1 Wissen).",
+	"vcmi.townHall.greetingSpellPower"      : "Der %s lehrt Euch neue Wege, Eure magischen Kräfte zu bündeln (+1 Kraft).",
+	"vcmi.townHall.greetingExperience"      : "Ein Besuch bei den %s bringt Euch viele neue Fähigkeiten bei (+1000 Erfahrung).",
+	"vcmi.townHall.greetingAttack"          : "Nach einiger Zeit im %s könnt Ihr effizientere Kampffertigkeiten erlernen (+1 Angriffsfertigkeit).",
+	"vcmi.townHall.greetingDefence"         : "Wenn Ihr Zeit im %s verbringt, bringen Euch die erfahrenen Krieger dort zusätzliche Verteidigungsfähigkeiten bei (+1 Verteidigung).",
+	"vcmi.townHall.hasNotProduced"          : "Die %s hat noch nichts produziert.",
+	"vcmi.townHall.hasProduced"             : "Die %s hat diese Woche %d %s produziert.",
+	"vcmi.townHall.greetingCustomBonus"     : "%s gibt Ihnen +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil"     : " bis zur nächsten Schlacht.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s hat Eure Zauberpunkte wieder auf das Maximum erhöht.",
+
+	"vcmi.logicalExpressions.anyOf"  : "Eines der folgenden:",
+	"vcmi.logicalExpressions.allOf"  : "Alles der folgenden:",
+	"vcmi.logicalExpressions.noneOf" : "Keines der folgenden:",
+
+	"vcmi.heroWindow.openCommander.hover" : "Öffne Kommandanten-Fenster",
+	"vcmi.heroWindow.openCommander.help"  : "Zeige Informationen über Kommandanten dieses Helden",
+
+	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
+
+	"vcmi.creatureWindow.showBonuses.hover"    : "Wechsle zur Bonus-Ansicht",
+	"vcmi.creatureWindow.showBonuses.help"     : "Zeige alle aktiven Boni des Kommandanten",
+	"vcmi.creatureWindow.showSkills.hover"     : "Wechsle zur Fertigkeits-Ansicht",
+	"vcmi.creatureWindow.showSkills.help"      : "Zeige alle erlernten Fertigkeiten des Kommandanten",
+	"vcmi.creatureWindow.returnArtifact.hover" : "Artefekt zurückgeben",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Nutze diese Schaltfläche, um Stapel-Artefakt in den Rucksack des Helden zurückzugeben",
+
+	"vcmi.questLog.hideComplete.hover" : "Verstecke abgeschlossene Quests",
+	"vcmi.questLog.hideComplete.help"  : "Verstecke alle Quests die bereits abgeschlossen sind",
+	
+	"core.bonus.ADDITIONAL_ATTACK.name": "Doppelschlag",
+	"core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Zusätzliche Vergeltungsmaßnahmen",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "Kann ${val} zusätzliche Male vergelten",
+	"core.bonus.AIR_IMMUNITY.name": "Luftimmunität",
+	"core.bonus.AIR_IMMUNITY.description": "Immun gegen alle Luftschulzauber",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Rundum angreifen",
+	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Greift alle benachbarten Gegner an",
+	"core.bonus.BLOCKS_RETALIATION.name": "Keine Vergeltung",
+	"core.bonus.BLOCKS_RETALIATION.description": "Feind kann nicht vergelten",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Keine Reichweitenverschiebung",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Feind kann nicht durch Schießen vergelten",
+	"core.bonus.CATAPULT.name": "Katapult",
+	"core.bonus.CATAPULT.description": "Greift Belagerungsmauern an",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.name": "Zusätzliche Belagerungsangriffe",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.description": "Kann Belagerungsmauern ${val} zusätzliche Male pro Angriff treffen",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduziere Zauberkosten (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduziert die Zauberkosten für den Helden",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Zauberdämpfer (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Erhöht die Kosten von gegnerischen Zaubern",
+	"core.bonus.CHARGE_IMMUNITY.name": "Immun gegen Aufladung",
+	"core.bonus.CHARGE_IMMUNITY.description": "Immune to Champion charge",
+	"core.bonus.DAEMON_SUMMONING.name": "Beschwörer (${subtype.creature})",
+	"core.bonus.DAEMON_SUMMONING.description": "Kann Kreaturen aus Leichen auferstehen lassen",
+	"core.bonus.DARKNESS.name": "Darkness cover",
+	"core.bonus.DARKNESS.description": "Fügt ${val} Dunkelheitsradius hinzu",
+	"core.bonus.DEATH_STARE.name": "Todesstarren (${val}%)",
+	"core.bonus.DEATH_STARE.description": "${val}% Chance, eine einzelne Kreatur zu töten",
+	"core.bonus.DEFENSIVE_STANCE.name": "Verteidigungsbonus",
+	"core.bonus.DEFENSIVE_STANCE.description": "+${val} Verteidigung beim Verteidigen",
+	"core.bonus.DESTRUCTION.name": "Zerstörung",
+	"core.bonus.DESTRUCTION.description": "Hat ${val}% Chance, zusätzliche Einheiten nach dem Angriff zu töten",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Todesstoß",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% Chance auf doppelten Schaden",
+	"core.bonus.DRAGON_NATURE.name": "Drache",
+	"core.bonus.DRAGON_NATURE.description": "Kreatur hat eine Drachennatur",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Direkte Schadensimmunität",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Immun gegen Direktschadenszauber",
+	"core.bonus.EARTH_IMMUNITY.name": "Erdimmunität",
+	"core.bonus.EARTH_IMMUNITY.description": "Immun gegen alle Zauber der Erdschule",
+	"core.bonus.ENCHANTER.name": "Verzauberer",
+	"core.bonus.ENCHANTER.description": "Kann jede Runde eine Masse von ${subtype.spell} zaubern",
+	"core.bonus.ENCHANTED.name": "Verzaubert",
+	"core.bonus.ENCHANTED.description": "Beeinflusst von permanentem ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignoriere Verteidigung (${val}%)",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignoriert einen Teil der Verteidigung für den Angriff",
+	"core.bonus.FIRE_IMMUNITY.name": "Feuerimmunität",
+	"core.bonus.FIRE_IMMUNITY.description": "Immun gegen alle Zauber der Schule des Feuers",
+	"core.bonus.FIRE_SHIELD.name": "Feuerschild (${val}%)",
+	"core.bonus.FIRE_SHIELD.description": "Reflektiert einen Teil des Nahkampfschadens",
+	"core.bonus.FIRST_STRIKE.name": "Erstschlag",
+	"core.bonus.FIRST_STRIKE.description": "Diese Kreatur greift zuerst an, anstatt zu vergelten",
+	"core.bonus.FEAR.name": "Furcht",
+	"core.bonus.FEAR.description": "Verursacht Furcht bei einem gegnerischen Stapel",
+	"core.bonus.FEARLESS.name": "Furchtlos",
+	"core.bonus.FEARLESS.description": "immun gegen die Fähigkeit Furcht",
+	"core.bonus.FLYING.name": "Fliegen",
+	"core.bonus.FLYING.description": "Kann fliegen (ignoriert Hindernisse)",
+	"core.bonus.FREE_SHOOTING.name": "Nah schießen",
+	"core.bonus.FREE_SHOOTING.description": "Kann im Nahkampf schießen",
+	"core.bonus.FULL_HP_REGENERATION.name": "Regeneration",
+	"core.bonus.FULL_HP_REGENERATION.description": "Kann auf volle Lebenspunkte regenerieren",
+	"core.bonus.GARGOYLE.name": "Gargoyle",
+	"core.bonus.GARGOYLE.description": "Kann nicht aufgerichtet oder geheilt werden",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Schaden vermindern (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduziert physischen Schaden aus dem Fern- oder Nahkampf",
+	"core.bonus.HATE.name": "Hasst ${subtype.creature}",
+	"core.bonus.HATE.description": "Macht ${val}% mehr Schaden",
+	"core.bonus.HEALER.name": "Heiler",
+	"core.bonus.HEALER.description": "Heilt verbündete Einheiten",
+	"core.bonus.HP_REGENERATION.name": "Regeneration",
+	"core.bonus.HP_REGENERATION.description": "Heilt ${val} Trefferpunkte jede Runde",
+	"core.bonus.JOUSTING.name": "Champion Charge",
+	"core.bonus.JOUSTING.description": "+5% Schaden pro zurückgelegtem Feld",
+	"core.bonus.KING1.name": "König 1",
+	"core.bonus.KING1.description": "Anfällig für grundlegende SLAYER",
+	"core.bonus.KING2.name": "König 2",
+	"core.bonus.KING2.description": "Anfällig für erweiterte SLAYER",
+	"core.bonus.KING3.name": "König3",
+	"core.bonus.KING3.description": "Anfällig für Experten-SLAYER",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Zauberimmunität 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immun gegen Zaubersprüche der Stufen 1-${val}",
+	"core.bonus.LIFE_DRAIN.name": "Leben entziehen (${val}%)",
+	"core.bonus.LIFE_DRAIN.description": "Drainiert ${val}% des zugefügten Schadens",
+	"core.bonus.MANA_CHANNELING.name": "Magiekanal ${val}%",
+	"core.bonus.MANA_CHANNELING.description": "Gibt Ihrem Helden Mana, das vom Gegner ausgegeben wird",
+	"core.bonus.MANA_DRAIN.name": "Mana-Entzug",
+	"core.bonus.MANA_DRAIN.description": "Entzieht ${val} Mana jede Runde",
+	"core.bonus.MAGIC_MIRROR.name": "Zauberspiegel (${val}%)",
+	"core.bonus.MAGIC_MIRROR.description": "${val}% Chance, einen Angriffszauber auf den Gegner umzulenken",
+	"core.bonus.MAGIC_RESISTANCE.name": "Magie-Widerstand(${MR}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "${MR}% Chance, gegnerischem Zauber zu widerstehen",
+	"core.bonus.MIND_IMMUNITY.name": "Geist-Zauber-Immunität",
+	"core.bonus.MIND_IMMUNITY.description": "Immun gegen Zauber vom Typ Geist",
+	"core.bonus.NO_DISTANCE_PENALTY.name": "Keine Entfernungsstrafe",
+	"core.bonus.NO_DISTANCE_PENALTY.description": "Voller Schaden aus beliebiger Entfernung",
+	"core.bonus.NO_MELEE_PENALTY.name": "Keine Nahkampf-Strafe",
+	"core.bonus.NO_MELEE_PENALTY.description": "Kreatur hat keinen Nahkampf-Malus",
+	"core.bonus.NO_MORALE.name": "Neutrale Moral",
+	"core.bonus.NO_MORALE.description": "Kreatur ist immun gegen Moral-Effekte",
+	"core.bonus.NO_WALL_PENALTY.name": "Keine Wand-Strafe",
+	"core.bonus.NO_WALL_PENALTY.description": "Voller Schaden bei Belagerung",
+	"core.bonus.NON_LIVING.name": "Nicht lebend",
+	"core.bonus.NON_LIVING.description": "Immunität gegen viele Effekte",
+	"core.bonus.RANDOM_SPELLCASTER.name": "Zufälliger Zauberwirker",
+	"core.bonus.RANDOM_SPELLCASTER.description": "Kann einen zufälligen Zauberspruch wirken",
+	"core.bonus.RANGED_RETALIATION.name": "Fernkampf-Vergeltung",
+	"core.bonus.RANGED_RETALIATION.description": "Kann einen Fernkampf-Gegenangriff durchführen",
+	"core.bonus.RECEPTIVE.name": "Empfänglich",
+	"core.bonus.RECEPTIVE.description": "Keine Immunität gegen Freundschaftszauber",
+	"core.bonus.REBIRTH.name": "Wiedergeburt (${val}%)",
+	"core.bonus.REBIRTH.description": "${val}% des Stacks wird nach dem Tod auferstehen",
+	"core.bonus.RETURN_AFTER_STRIKE.name": "Angriff und Rückkehr",
+	"core.bonus.RETURN_AFTER_STRIKE.description": "Kehrt nach Nahkampfangriff zurück",
+	"core.bonus.SELF_LUCK.name": "Positives Glück",
+	"core.bonus.SELF_LUCK.description": "Hat immer positives Glück",
+	"core.bonus.SELF_MORALE.name": "Positive Moral",
+	"core.bonus.SELF_MORALE.description": "Hat immer positive Moral",
+	"core.bonus.SHOOTER.name": "Fernkämpfer",
+	"core.bonus.SHOOTER.description": "Kreatur kann schießen",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Schießt rundherum",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description": "Die Fernkampfangriffe dieser Kreatur treffen alle Ziele in einem kleinen Bereich",
+	"core.bonus.SOUL_STEAL.name": "Seelenraub",
+	"core.bonus.SOUL_STEAL.description": "Gewinnt ${val} neue Kreaturen für jeden getöteten Gegner",
+	"core.bonus.SPELLCASTER.name": "Zauberer",
+	"core.bonus.SPELLCASTER.description": "Kann ${subtype.spell} zaubern",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "Nach Angriff zaubern",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "${val}%, um ${subtype.spell} nach dem Angriff zu wirken",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "Zauber vor Angriff",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% um ${subtype.spell} vor dem Angriff zu wirken",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Zauberwiderstand",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Schaden von Zaubern reduziert ${val}%.",
+	"core.bonus.SPELL_IMMUNITY.name": "Zauberimmunität",
+	"core.bonus.SPELL_IMMUNITY.description": "Immun gegen ${subtype.spell}",
+	"core.bonus.SPELL_LIKE_ATTACK.name": "zauberähnlicher Angriff",
+	"core.bonus.SPELL_LIKE_ATTACK.description": "Angriffe mit ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura des Widerstands",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Stapel in der Nähe erhalten ${val}% Widerstand",
+	"core.bonus.SUMMON_GUARDIANS.name": "Wächter beschwören",
+	"core.bonus.SUMMON_GUARDIANS.description": "Beschwört bei Kampfbeginn ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name": "Synergierbar",
+	"core.bonus.SYNERGY_TARGET.description": "Diese Kreatur ist anfällig für Synergieeffekte",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Atem-Angriff (2-Hex-Bereich)",
+	"core.bonus.THREE_HEADED_ATTACK.name": "Dreiköpfiger Angriff",
+	"core.bonus.THREE_HEADED_ATTACK.description": "Greift drei benachbarte Einheiten an",
+	"core.bonus.TRANSMUTATION.name": "Transmutation",
+	"core.bonus.TRANSMUTATION.description": "${val}% Chance, angegriffene Einheit in einen anderen Typ zu verwandeln",
+	"core.bonus.UNDEAD.name": "Untot",
+	"core.bonus.UNDEAD.description": "Kreatur ist untot",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Unbegrenzte Vergeltungsmaßnahmen",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Vergeltungen für eine beliebige Anzahl von Angriffen",
+	"core.bonus.WATER_IMMUNITY.name": "Wasser-Immunität",
+	"core.bonus.WATER_IMMUNITY.description": "Immun gegen alle Zauber der Wasserschule",
+	"core.bonus.WIDE_BREATH.name": "Breiter Atem",
+	"core.bonus.WIDE_BREATH.description": "Breiter Atem-Angriff (mehrere Felder)"
+}

+ 70 - 0
Mods/vcmi/config/vcmi/polish.json

@@ -0,0 +1,70 @@
+{
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Poziom zagrożenia: ",
+	"vcmi.adventureMap.monsterThreat.levels.0"  : "Zerowy",
+	"vcmi.adventureMap.monsterThreat.levels.1"  : "Bardzo słaby",
+	"vcmi.adventureMap.monsterThreat.levels.2"  : "Słaby",
+	"vcmi.adventureMap.monsterThreat.levels.3"  : "Nieco słabszy",
+	"vcmi.adventureMap.monsterThreat.levels.4"  : "Równie silny",
+	"vcmi.adventureMap.monsterThreat.levels.5"  : "Nieco silniejszy",
+	"vcmi.adventureMap.monsterThreat.levels.6"  : "Silny",
+	"vcmi.adventureMap.monsterThreat.levels.7"  : "Bardzo silny",
+	"vcmi.adventureMap.monsterThreat.levels.8"  : "Wyzwanie",
+	"vcmi.adventureMap.monsterThreat.levels.9"  : "Przytłaczający",
+	"vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny",
+	"vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania",
+
+	"vcmi.adventureMap.confirmRestartGame"  : "Czy na pewno chcesz zrestartować grę?",
+	"vcmi.adventureMap.noTownWithMarket"    : "Brak dostępnego targowiska!",
+	"vcmi.adventureMap.noTownWithTavern"    : "Brak dostępnego miasta z karczmą!",
+	"vcmi.adventureMap.spellUnknownProblem" : "Nieznany problem z zaklęciem, brak dodatkowych informacji.",
+	"vcmi.adventureMap.playerAttacked"      : "Gracz został zaatakowany: %s",
+
+	"vcmi.server.errors.existingProcess"     : "Inny proces vcmiserver został już uruchomiony, zakończ go nim przejdziesz dalej",
+	"vcmi.server.errors.modsIncompatibility" : "Mody wymagane do wczytania gry:",
+	"vcmi.server.confirmReconnect"           : "Połączyć z ostatnią sesją?",
+
+	"vcmi.systemOptions.fullscreenButton.hover" : "Pełny ekran",
+	"vcmi.systemOptions.fullscreenButton.help"  : "{Fullscreen}\n\n Po wybraniu VCMI uruchomi się w trybie pełnoekranowym, w przeciwnym wypadku uruchomi się w oknie",
+	"vcmi.systemOptions.resolutionButton.hover" : "Rozdzielczość",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Select resolution}\n\n Zmień rozdzielczość ekranu w grze. Restart gry jest wymagany, by zmiany zostały uwzględnione.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Wybierz rozdzielczość",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Zmień rozdzielczość ekranu w grze.",
+
+	"vcmi.townHall.missingBase"             : "Podstawowy budynek %s musi zostać najpierw wybudowany",
+	"vcmi.townHall.noCreaturesToRecruit"    : "Brak stworzeń do rekrutacji!",
+	"vcmi.townHall.greetingManaVortex"      : "Zbliżając się do %s czujesz jak twoje ciało wypełnia energia. Ilość pkt. magii, które posiadasz, zwiększa się dwukrotnie.",
+	"vcmi.townHall.greetingKnowledge"       : "Studiując napisy na %s odkrywasz nowe aspekty stosowania magii (wiedza +1).",
+	"vcmi.townHall.greetingSpellPower"      : "Odwiedzając %s dowiadujesz się, jak zwiększyć potęgę swojej mocy magicznej (moc +1).",
+	"vcmi.townHall.greetingExperience"      : "Wizyta w %s zwiększa twoje doświadczenie (doświadczenie +1000).",
+	"vcmi.townHall.greetingAttack"          : "Krótka wizyka w %s umożliwia ci polepszenie technik walki (atak +1).",
+	"vcmi.townHall.greetingDefence"         : "Odwiedzasz %s. Doświadczeni żołnierze, którzy tam przebywają, uczą cię sztuki skutecznej obrony (obrona +1).",
+	"vcmi.townHall.hasNotProduced"          : "%s nic jeszcze nie wyprodukował.",
+	"vcmi.townHall.hasProduced"             : "%s wyprodukował w tym tygodniu: %d %s.",
+	"vcmi.townHall.greetingCustomBonus"     : "%s daje tobie +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil"     : " do następnej bitwy.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s przywraca ci wszystkie punkty magii.",
+
+	"vcmi.logicalExpressions.anyOf"  :  "Dowolne spośród:",
+	"vcmi.logicalExpressions.allOf"  :  "Wszystkie spośród:",
+	"vcmi.logicalExpressions.noneOf" : "Żadne spośród:",
+
+	"vcmi.heroWindow.openCommander.hover" : "Otwórz okno dowódcy",
+	"vcmi.heroWindow.openCommander.help"  : "Wyświetla informacje o dowódcy przynależącym do tego bohatera",
+
+	"vcmi.commanderWindow.artifactMessage" : "Czy chcesz zwrócić ten artefakt bohaterowi?",
+
+	"vcmi.creatureWindow.showBonuses.hover"    : "Przełącz do widoku bonusów",
+	"vcmi.creatureWindow.showBonuses.help"     : "Wyświetla wszystkie aktywne bonusy dowódcy",
+	"vcmi.creatureWindow.showSkills.hover"     : "Przełącz do widoku umiejętności",
+	"vcmi.creatureWindow.showSkills.help"      : "Wyświetla wszystkie nauczone umiejętności dowódcy",
+	"vcmi.creatureWindow.returnArtifact.hover" : "Zwróć artefakt",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Użyj tego przycisku by zwrócić artefakt do sakwy bohatera",
+
+	"vcmi.questLog.hideComplete.hover" : "Ukryj ukończone misje",
+	"vcmi.questLog.hideComplete.help"  : "Ukrywa wszystkie misje, które zostały zakończone",
+
+	"vcmi.randomMapTab.widgets.defaultTemplate"      : "domyślny",
+	"vcmi.randomMapTab.widgets.templateLabel"        : "Szablon",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Ustaw...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Sojusze"
+}

+ 225 - 0
Mods/vcmi/config/vcmi/ukrainian.json

@@ -0,0 +1,225 @@
+{
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Сила загону: ",
+	"vcmi.adventureMap.monsterThreat.levels.0"  : "Безсилий",
+	"vcmi.adventureMap.monsterThreat.levels.1"  : "Дуже слабкий",
+	"vcmi.adventureMap.monsterThreat.levels.2"  : "Слабкий",
+	"vcmi.adventureMap.monsterThreat.levels.3"  : "Трохи слабша",
+	"vcmi.adventureMap.monsterThreat.levels.4"  : "Відповідна",
+	"vcmi.adventureMap.monsterThreat.levels.5"  : "Трохи сильніша",
+	"vcmi.adventureMap.monsterThreat.levels.6"  : "Сильніша",
+	"vcmi.adventureMap.monsterThreat.levels.7"  : "Дуже сильна",
+	"vcmi.adventureMap.monsterThreat.levels.8"  : "Кидає виклик",
+	"vcmi.adventureMap.monsterThreat.levels.9"  : "Нездоланна",
+	"vcmi.adventureMap.monsterThreat.levels.10" : "Смертельна",
+	"vcmi.adventureMap.monsterThreat.levels.11" : "Неможлива",
+
+	"vcmi.adventureMap.confirmRestartGame"  : "Ви впевнені, що хочете перезапустити гру?",
+	"vcmi.adventureMap.noTownWithMarket"    : "Немає доступних ринків!",
+	"vcmi.adventureMap.noTownWithTavern"    : "Немає доступного міста з таверною!",
+	"vcmi.adventureMap.spellUnknownProblem" : "Невідома проблема з цим заклинанням, більше інформації немає.",
+	"vcmi.adventureMap.playerAttacked"      : "Гравця атаковано: %s",
+
+	"vcmi.server.errors.existingProcess"     : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
+	"vcmi.server.errors.modsIncompatibility" : "Потрібні модифікації для завантаження гри:",
+	"vcmi.server.confirmReconnect"           : "Підключитися до минулої сесії?",
+
+	"vcmi.systemOptions.fullscreenButton.hover" : "Режим на весь екран",
+	"vcmi.systemOptions.fullscreenButton.help"  : "{Режим на весь екран}\n\n Якщо обрано, VCMI буде запускатися в режимі на весь екран, інакше — віконний режим",
+	"vcmi.systemOptions.resolutionButton.hover" : "Розширення екрану",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Розширення екрану}\n\n Зміна розширення екрану в грі. Аби зміни набули чинності необхідно перезавантажити гру.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Обрати розширення екрану",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Змінити розширення екрану в грі.",
+	
+	"vcmi.townHall.missingBase"             : "Спочатку необхідно звести початкову будівлю: %s",
+	"vcmi.townHall.noCreaturesToRecruit"    : "Немає істот, яких можна завербувати!",
+	"vcmi.townHall.greetingManaVortex"      : "Неподалік %s ваше тіло наповнюється новою силою. Ваша звична магічна енергія сьогодні подвоєна.",
+	"vcmi.townHall.greetingKnowledge"       : "Ви вивчили знаки на %s, і на вас зійшло прозріння у справах магії. (+1 Knowledge).",
+	"vcmi.townHall.greetingSpellPower"      : "В %s вас навчили новим методам концентрації магічної сили. (+1 Power).",
+	"vcmi.townHall.greetingExperience"      : "Відвідавши %s, ви дізналися багато нового. (+1000 Experience).",
+	"vcmi.townHall.greetingAttack"          : "Перебування у %s дозволило вам краще використовувати бойові навички (+1 Attack Skill).",
+	"vcmi.townHall.greetingDefence"         : "У %s досвідчені воїни виклали вам свої захисні вміння. (+1 Defense).",
+	"vcmi.townHall.hasNotProduced"          : "Поки що %s нічого не створило.",
+	"vcmi.townHall.hasProduced"             : "Цього тижня %s створило %d одиниць, цього разу це %s.",
+	"vcmi.townHall.greetingCustomBonus"     : "%s дає вам +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil"     : " до наступної битви.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s повністю відновлює ваш запас очків магії.",
+	
+	"vcmi.logicalExpressions.anyOf"  : "Будь-що з перерахованого:",
+	"vcmi.logicalExpressions.allOf"  : "Все з перерахованого:",
+	"vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:",
+
+	"vcmi.heroWindow.openCommander.hover" : "Відкрити вікно командира",
+	"vcmi.heroWindow.openCommander.help"  : "Показує інформацію про командира героя",
+
+	"vcmi.commanderWindow.artifactMessage" : "Бажаєте передати цей артефакт герою?",
+
+	"vcmi.creatureWindow.showBonuses.hover"    : "Перейти до перегляду бонусів",
+	"vcmi.creatureWindow.showBonuses.help"     : "Відображає всі активні бонуси командира",
+	"vcmi.creatureWindow.showSkills.hover"     : "Перейдіть до перегляду вмінь",
+	"vcmi.creatureWindow.showSkills.help"      : "Відображає всі вивчені командиром вміння",
+	"vcmi.creatureWindow.returnArtifact.hover" : "Повернути артефакт",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Використовуйте цю кнопку, щоб повернути артефакт загону назад у рюкзак героя",
+
+	"vcmi.questLog.hideComplete.hover" : "Приховати завершені квести",
+	"vcmi.questLog.hideComplete.help"  : "Приховує всі квести, які вже мають стан виконаних",
+	
+	"vcmi.randomMapTab.widgets.defaultTemplate"      : "за замовчуванням",
+	"vcmi.randomMapTab.widgets.templateLabel"        : "Шаблон",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Налаштувати...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Розподіл команд",
+	
+	"core.bonus.ADDITIONAL_ATTACK.name" : "Подвійний удар",
+	"core.bonus.ADDITIONAL_ATTACK.description" : "Атакує двічі",
+	"core.bonus.ADDITIONAL_RETALIATION.name" : "Додаткові відплати",
+	"core.bonus.ADDITIONAL_RETALIATION.description" : "Може нанести ${val} додаткових ударів у відповідь",
+	"core.bonus.AIR_IMMUNITY.name" : "Імунітет до повітря",
+	"core.bonus.AIR_IMMUNITY.description" : "Імунітет до всіх заклять школи повітря",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name" : "Атакує всіх навколо",
+	"core.bonus.ATTACKS_ALL_ADJACENT.description" : "Атакує всіх сусідніх ворогів",
+	"core.bonus.BLOCKS_RETALIATION.name" : "Ворог не відповідає",
+	"core.bonus.BLOCKS_RETALIATION.description" : "Ворог не може атакувати у відповідь",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Немає дальнього удару у відповідь",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Ворог не може відповісти пострілом",
+	"core.bonus.CATAPULT.name" : "Катапульта",
+	"core.bonus.CATAPULT.description" : "Атакує стіни фортеці",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.name" : "Додаткові атаки стін",
+	"core.bonus.CATAPULT_EXTRA_SHOTS.description" : "Може вражати стіни фортеці ${val} додаткових разів за атаку",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Зменшує вартість закляття (${value})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description" : "Зменшує вартість закляття для героя",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name" : "Демпфер магії (${value})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description" : "Збільшує вартість ворожих заклять",
+	"core.bonus.CHARGE_IMMUNITY.name" : "Імунітет до атак з розгону",
+	"core.bonus.CHARGE_IMMUNITY.description" : "Імунітет до турнірної переваги",
+	"core.bonus.DARKNESS.name" : "Покриття темряви",
+	"core.bonus.DARKNESS.description" : "Додає ${val} радіусу темряви",
+	"core.bonus.DEATH_STARE.name" : "Погляд смерті (${val}%)",
+	"core.bonus.DEATH_STARE.description" : "${val}% шанс вбити одну істоту",
+	"core.bonus.DEFENSIVE_STANCE.name" : "Бонус до захисту",
+	"core.bonus.DEFENSIVE_STANCE.description" : "+${val} Захист при обороні",
+	"core.bonus.DESTRUCTION.name" : "Знищення",
+	"core.bonus.DESTRUCTION.description" : "Має ${val}% шанс вбити додаткових юнітів після атаки",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Смертельний удар",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "${val}% шанс нанести подвійний шкоди",
+	"core.bonus.DRAGON_NATURE.name" : "Дракон",
+	"core.bonus.DRAGON_NATURE.description" : "Істота має драконячу природу",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.name" : "Імунітет до прямої шкоди",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.description" : "Імунітет до заклять, що завдають прямої шкоди",
+	"core.bonus.EARTH_IMMUNITY.name" : "Імунітет Землі",
+	"core.bonus.EARTH_IMMUNITY.description" : "Імунітет до всіх заклять школи Землі",
+	"core.bonus.ENCHANTER.name" : "Чарівник",
+	"core.bonus.ENCHANTER.description" : "Може використовувати масове закляття ${subtype.spell} кожного ходу",
+	"core.bonus.ENCHANTED.name" : "Зачарований",
+	"core.bonus.ENCHANTED.description" : "Піддається впливу постійних закляття ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Ігнорує ${val}% захисту",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Ігнорує частину захисту для атаки",
+	"core.bonus.FIRE_IMMUNITY.name" : "Імунітет до вогню",
+	"core.bonus.FIRE_IMMUNITY.description" : "Імунітет до всіх заклять школи вогню",
+	"core.bonus.FIRE_SHIELD.name" : "Вогняний щит (${value}%)",
+	"core.bonus.FIRE_SHIELD.description" : "Повертає частину шкоди ближнього бою тому, хто їх завдав",
+	"core.bonus.FIRST_STRIKE.name" : "Перший удар",
+	"core.bonus.FIRST_STRIKE.description" : "Цей загін атакує першим замість того, щоб відповідати",
+	"core.bonus.FEAR.name" : "Страх",
+	"core.bonus.FEAR.description" : "Спричиняє страх у загоні ворога",
+	"core.bonus.FEARLESS.name" : "Безстрашний",
+	"core.bonus.FEARLESS.description" : "Імунітет до страху",
+	"core.bonus.FLYING.name" : "Літає",
+	"core.bonus.FLYING.description" : "Може літати (ігнорує перешкоди)",
+	"core.bonus.FREE_SHOOTING.name" : "Стріляє впритул",
+	"core.bonus.FREE_SHOOTING.description" : "Може стріляти в ближньому бою",
+	"core.bonus.FULL_HP_REGENERATION.name" : "Регенерація",
+	"core.bonus.FULL_HP_REGENERATION.description" : "Може регенерувати до повного здоров'я",
+	"core.bonus.GARGOYLE.name" : "Горгулья",
+	"core.bonus.GARGOYLE.description" : "Не може бути відроджена або зцілена",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Зменшує шкоду (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Зменшує фізичний урон від ударів з дальньої та ближньої дистанції",
+	"core.bonus.HATE.name" : "Ненавидить ${subtype.creature}",
+	"core.bonus.HATE.description" : "Завдає на ${val}% більше шкоди",
+	"core.bonus.HEALER.name" : "Цілитель",
+	"core.bonus.HEALER.description" : "Лікує союзників",
+	"core.bonus.HP_REGENERATION.name" : "Регенерація",
+	"core.bonus.HP_REGENERATION.description" : "Відновлює ${val} очок здоров'я кожного раунду",
+	"core.bonus.JOUSTING.name" : "Турнірна перевага",
+	"core.bonus.JOUSTING.description" : "+5% шкоди за кожен пройдений гекс",
+	"core.bonus.KING1.name" : "Король 1",
+	"core.bonus.KING1.description" : "Вразливий до 1-го рівня закляття Вбивця",
+	"core.bonus.KING2.name" : "Король 2",
+	"core.bonus.KING2.description" : "Вразливий до 2-го рівня закляття Вбивця",
+	"core.bonus.KING3.name" : "Король 3",
+	"core.bonus.KING3.description" : "Вразливий до 3-го рівня закляття Вбивця",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Імунітет до заклять 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description" : "Імунітет до заклять рівнів 1-${val}",
+	"core.bonus.LIFE_DRAIN.name" : "Висмоктує життя (${val}%)",
+	"core.bonus.LIFE_DRAIN.description" : "Висмоктує ${val}% від завданої шкоди",
+	"core.bonus.MANA_CHANNELING.name" : "Магічний канал ${val}%",
+	"core.bonus.MANA_CHANNELING.description" : "Повертає вашому герою ману, витрачену ворогом",
+	"core.bonus.MANA_DRAIN.name" : "Викрадання мани",
+	"core.bonus.MANA_DRAIN.description" : "Викрадає ${val} мани кожного ходу",
+	"core.bonus.MAGIC_MIRROR.name" : "Магічне дзеркало (${val}%)",
+	"core.bonus.MAGIC_MIRROR.description" : "Відбиває ворожі заклинання до випадкової істоти ворога з силою в ${val}%",
+	"core.bonus.MAGIC_RESISTANCE.name" : "Опір магії (${MR}%)",
+	"core.bonus.MAGIC_RESISTANCE.description" : "${MR}% шанс протистояти ворожим закляттям",
+	"core.bonus.MIND_IMMUNITY.name" : "Імунітет до заклять розуму",
+	"core.bonus.MIND_IMMUNITY.description" : "Імунітет до заклять типу ",
+	"core.bonus.NO_DISTANCE_PENALTY.name" : "Немає штрафу за відстань",
+	"core.bonus.NO_DISTANCE_PENALTY.description" : "Повна шкода з будь-якої відстані",
+	"core.bonus.NO_MELEE_PENALTY.name" : "Немає штрафу за ближній бій",
+	"core.bonus.NO_MELEE_PENALTY.description" : "Загін не має штрафу за ближній бій",
+	"core.bonus.NO_MORALE.name" : "Нейтральний бойовий дух",
+	"core.bonus.NO_MORALE.description" : "Загін має імунітет до ефектів моралі",
+	"core.bonus.NO_WALL_PENALTY.name" : "Немає штрафу за перешкоди",
+	"core.bonus.NO_WALL_PENALTY.description" : "Повна шкода при пострілах через стіни",
+	"core.bonus.NON_LIVING.name" : "Не жива",
+	"core.bonus.NON_LIVING.description" : "Імунітет до багатьох ефектів",
+	"core.bonus.RANDOM_SPELLCASTER.name" : "Випадковий заклинатель",
+	"core.bonus.RANDOM_SPELLCASTER.description" : "Може накласти випадкове закляття",
+	"core.bonus.RANGED_RETALIATION.name" : "Дистанційне відплата",
+	"core.bonus.RANGED_RETALIATION.description" : "Може наносити контратаку пострілом",
+	"core.bonus.RECEPTIVE.name" : "Сприйнятливий",
+	"core.bonus.RECEPTIVE.description" : "Не має імунітету до дружніх заклять",
+	"core.bonus.REBIRTH.name" : "Відродження (${val}%)",
+	"core.bonus.REBIRTH.description" : "${val}% загону відродиться після смерті",
+	"core.bonus.RETURN_AFTER_STRIKE.name" : "Атакує і повертається",
+	"core.bonus.RETURN_AFTER_STRIKE.description" : "Повертається після атаки ближнього бою",
+	"core.bonus.SELF_LUCK.name" : "Позитивна удача",
+	"core.bonus.SELF_LUCK.description" : "Завжди має позитивну удачу",
+	"core.bonus.SELF_MORALE.name" : "Позитивний бойовий дух",
+	"core.bonus.SELF_MORALE.description" : "Завжди має позитивний бойовий дух",
+	"core.bonus.SHOOTER.name" : "Стрілок",
+	"core.bonus.SHOOTER.description" : "Істота може стріляти",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name" : "Стріляйте по площі",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description" : "Дистанційні атаки цієї істоти вражають всі цілі на невеликій площі",
+	"core.bonus.SOUL_STEAL.name" : "Викрадення душ",
+	"core.bonus.SOUL_STEAL.description" : "Отримує ${val} нових істот за кожного вбитого ворога",
+	"core.bonus.SPELLCASTER.name" : "Заклинатель",
+	"core.bonus.SPELLCASTER.description" : "Може використовувати закляття ${subtype.spell}",
+	"core.bonus.SPELL_AFTER_ATTACK.name" : "Після атаки",
+	"core.bonus.SPELL_AFTER_ATTACK.description" : "${val}%, щоб застосувати ${subtype.spell} після атаки",
+	"core.bonus.SPELL_BEFORE_ATTACK.name" : "закляття перед атакою",
+	"core.bonus.SPELL_BEFORE_ATTACK.description" : "Застосовує ${subtype.spell} з вірогідністю ${value}% перед атакою",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Стійкість до заклять",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Шкода від заклять зменшується на ${val}%.",
+	"core.bonus.SPELL_IMMUNITY.name" : "Імунітет до заклять",
+	"core.bonus.SPELL_IMMUNITY.description" : "Імунітет до ${subtype.spell}",
+	"core.bonus.SPELL_LIKE_ATTACK.name" : "Атака, схожа на закляття",
+	"core.bonus.SPELL_LIKE_ATTACK.description" : "Атакує за допомогою ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name" : "Аура стійкості",
+	"core.bonus.SPELL_RESISTANCE_AURA.description" : "Поруч розташовані стеки отримують ${val}% опору",
+	"core.bonus.SUMMON_GUARDIANS.name" : "Закликати охоронців",
+	"core.bonus.SUMMON_GUARDIANS.description" : "На початку бою викликає ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name" : "Синергізм",
+	"core.bonus.SYNERGY_TARGET.description" : "Ця істота вразлива до ефекту синергії",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name" : "Подих",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Атакує додаткову ціль позаду",
+	"core.bonus.THREE_HEADED_ATTACK.name" : "Триголова атака",
+	"core.bonus.THREE_HEADED_ATTACK.description" : "Атакує до трьох сусідніх загонів",
+	"core.bonus.TRANSMUTATION.name" : "Трансмутація",
+	"core.bonus.TRANSMUTATION.description" : "${val}% шанс перетворити атакованого юніта в інший тип",
+	"core.bonus.UNDEAD.name" : "Нежить",
+	"core.bonus.UNDEAD.description" : "Істота є нежить",
+	"core.bonus.UNLIMITED_RETALIATIONS.name" : "Необмежена кількість ударів у відповідь",
+	"core.bonus.UNLIMITED_RETALIATIONS.description" : "Відбиває будь-яку кількість атак",
+	"core.bonus.WATER_IMMUNITY.name" : "Імунітет до води",
+	"core.bonus.WATER_IMMUNITY.description" : "Імунітет до всіх заклять школи Води",
+	"core.bonus.WIDE_BREATH.name" : "Широкий подих",
+	"core.bonus.WIDE_BREATH.description" : "Атака широким подихом",
+}

+ 24 - 1
Mods/vcmi/mod.json

@@ -7,6 +7,21 @@
 		"description" : "Grundlegende Dateien, die für die korrekte Ausführung von VCMI erforderlich sind",
 		"author" : "VCMI-Team",
 		"modType" : "Grafik",
+		
+		"translations" : [
+			"config/vcmi/german.json"
+		]
+	},
+	
+	"polish" : {
+		"name" : "VCMI essential files",
+		"description" : "Essential files required for VCMI to run correctly",
+		"author" : "VCMI Team",
+		"modType" : "Graphical",
+		
+		"translations" : [
+			"config/vcmi/polish.json"
+		]
 	},
 	
 	"ukrainian" : {
@@ -14,15 +29,23 @@
 		"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
 		"author" : "Команда VCMI",
 		"modType" : "Графіка",
+		
+		"translations" : [
+			"config/vcmi/ukrainian.json"
+		]
 	},
 
-	"version" : "1.1",
+	"version" : "1.1.1",
 	"author" : "VCMI Team",
 	"contact" : "http://forum.vcmi.eu/index.php",
 	"modType" : "Graphical",
 	
 	"factions" : [ "config/vcmi/towerFactions" ],
 	"creatures" : [ "config/vcmi/towerCreature" ],
+		
+	"translations" : [
+		"config/vcmi/english.json"
+	],
 
 	"filesystem":
 	{

+ 2 - 2
README.md

@@ -30,7 +30,7 @@ Platform support is constantly tested by continuous integration and CMake config
 
  * (optional) All platforms: [using Conan package manager to obtain prebuilt dependencies](docs/conan.md)
  * [On Linux](https://wiki.vcmi.eu/How_to_build_VCMI_(Linux))
- * [On Linux for Windows with MXE](https://wiki.vcmi.eu/How_to_build_VCMI_(Linux/Cmake/MXE))
+ * [On Linux for Windows with Conan and mingw](https://wiki.vcmi.eu/How_to_build_VCMI_(Linux/Cmake/Conan))
  * [On macOS](https://wiki.vcmi.eu/How_to_build_VCMI_(macOS))
  * [On Windows using MSVC and Vcpkg](https://wiki.vcmi.eu/How_to_build_VCMI_(Windows/Vcpkg))
  * [iOS on macOS](https://wiki.vcmi.eu/How_to_build_VCMI_(iOS))
@@ -40,4 +40,4 @@ Platform support is constantly tested by continuous integration and CMake config
 VCMI Project source code is licensed under GPL version 2 or later.
 VCMI Project assets are licensed under CC-BY-SA 4.0. Assets sources and information about contributors are available under following link: [https://github.com/vcmi/vcmi-assets]
 
-Copyright (C) 2007-2022  VCMI Team (check AUTHORS file for the contributors list)
+Copyright (C) 2007-2023  VCMI Team (check AUTHORS file for the contributors list)

+ 29 - 24
client/CMT.cpp

@@ -11,48 +11,43 @@
 //
 #include "StdInc.h"
 
-#include <boost/program_options.hpp>
-
-#include "gui/SDL_Extensions.h"
 #include "CGameInfo.h"
-#include "mapHandler.h"
-
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/FileStream.h"
 #include "mainmenu/CMainMenu.h"
 #include "lobby/CSelectionBase.h"
 #include "windows/CCastleInterface.h"
-#include "../lib/CConsoleHandler.h"
 #include "gui/CursorHandler.h"
-#include "../lib/CGameState.h"
-#include "../CCallback.h"
 #include "CPlayerInterface.h"
-#include "windows/CAdvmapInterface.h"
-#include "../lib/CBuildingHandler.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 "../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 "CMusicHandler.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "Graphics.h"
-#include "Client.h"
 #include "../lib/serializer/BinaryDeserializer.h"
 #include "../lib/serializer/BinarySerializer.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/NetPacks.h"
-#include "CMessage.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CTownHandler.h"
-#include "gui/CGuiHandler.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/CPlayerState.h"
-#include "gui/CAnimation.h"
 #include "../lib/serializer/Connection.h"
-#include "CServerHandler.h"
-#include "gui/NotificationHandler.h"
-#include "ClientCommandManager.h"
 
 #include <boost/asio.hpp>
+#include <boost/program_options.hpp>
 
 #include "mainmenu/CPrologEpilogVideo.h"
 #include <vstd/StringUtils.h>
@@ -376,7 +371,8 @@ int main(int argc, char * argv[])
 		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
 		#endif // VCMI_ANDROID
 
-		GH.mainFPSmng->init(); //(!)init here AFTER SDL_Init() while using SDL for FPS management
+		//(!)init here AFTER SDL_Init() while using SDL for FPS management
+		GH.init();
 
 		SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
 
@@ -432,11 +428,20 @@ int main(int argc, char * argv[])
 		CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
 		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
 	}
+
 #ifdef VCMI_MAC
 	// Ctrl+click should be treated as a right click on Mac OS X
 	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
 #endif
 
+#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
+	if(GH.isPointerRelativeMode)
+	{
+		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+	}
+#endif
+
 #ifndef VCMI_NO_THREADED_LOAD
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
 	boost::thread loading(init);
@@ -703,7 +708,7 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 	if(nullptr == mainWindow)
 	{
 #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-		auto createWindow = [displayIndex](Uint32 extraFlags) -> bool {
+		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;
 		};
@@ -713,7 +718,7 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 		SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
 		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
 
-		Uint32 windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
+		uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
 		if(!createWindow(windowFlags | SDL_WINDOW_METAL))
 		{
 			logGlobal->warn("Metal unavailable, using OpenGLES");

+ 218 - 171
client/CMakeLists.txt

@@ -1,174 +1,215 @@
 set(client_SRCS
-		StdInc.cpp
-		../CCallback.cpp
-
-		battle/BattleActionsController.cpp
-		battle/BattleAnimationClasses.cpp
-		battle/BattleEffectsController.cpp
-		battle/BattleFieldController.cpp
-		battle/BattleInterfaceClasses.cpp
-		battle/BattleInterface.cpp
-		battle/BattleObstacleController.cpp
-		battle/BattleProjectileController.cpp
-		battle/BattleRenderer.cpp
-		battle/BattleSiegeController.cpp
-		battle/BattleStacksController.cpp
-		battle/BattleWindow.cpp
-		battle/CreatureAnimation.cpp
-
-		gui/CAnimation.cpp
-		gui/Canvas.cpp
-		gui/CursorHandler.cpp
-		gui/CGuiHandler.cpp
-		gui/CIntObject.cpp
-		gui/ColorFilter.cpp
-		gui/Fonts.cpp
-		gui/SDL_Extensions.cpp
-		gui/NotificationHandler.cpp
-		gui/InterfaceObjectConfigurable.cpp
-
-		widgets/AdventureMapClasses.cpp
-		widgets/Buttons.cpp
-		widgets/CArtifactHolder.cpp
-		widgets/CComponent.cpp
-		widgets/CGarrisonInt.cpp
-		widgets/Images.cpp
-		widgets/MiscWidgets.cpp
-		widgets/ObjectLists.cpp
-		widgets/TextControls.cpp
-
-		windows/CAdvmapInterface.cpp
-		windows/CCastleInterface.cpp
-		windows/CCreatureWindow.cpp
-		windows/CreaturePurchaseCard.cpp
-		windows/CHeroWindow.cpp
-		windows/CKingdomInterface.cpp
-		windows/CQuestLog.cpp
-		windows/CSpellWindow.cpp
-		windows/CTradeWindow.cpp
-		windows/CWindowObject.cpp
-		windows/GUIClasses.cpp
-		windows/InfoWindows.cpp
-		windows/QuickRecruitmentWindow.cpp
-
-		mainmenu/CMainMenu.cpp
-		mainmenu/CCampaignScreen.cpp
-		mainmenu/CreditsScreen.cpp
-		mainmenu/CPrologEpilogVideo.cpp
-
-		lobby/CBonusSelection.cpp
-		lobby/CSelectionBase.cpp
-		lobby/CLobbyScreen.cpp
-		lobby/CSavingScreen.cpp
-		lobby/CScenarioInfoScreen.cpp
-		lobby/CCampaignInfoScreen.cpp
-		lobby/OptionsTab.cpp
-		lobby/RandomMapTab.cpp
-		lobby/SelectionTab.cpp
-
-		CBitmapHandler.cpp
-		CreatureCostBox.cpp
-		CGameInfo.cpp
-		Client.cpp
-		CMessage.cpp
-		CMT.cpp
-		CMusicHandler.cpp
-		CPlayerInterface.cpp
-		CVideoHandler.cpp
-		CServerHandler.cpp
-		Graphics.cpp
-		mapHandler.cpp
-		NetPacksClient.cpp
-		NetPacksLobbyClient.cpp
-		SDLRWwrapper.cpp
-		ClientCommandManager.cpp
+	StdInc.cpp
+	../CCallback.cpp
+
+	adventureMap/CAdvMapPanel.cpp
+	adventureMap/CAdvMapInt.cpp
+	adventureMap/CAdventureOptions.cpp
+	adventureMap/CInGameConsole.cpp
+	adventureMap/CInfoBar.cpp
+	adventureMap/CList.cpp
+	adventureMap/CMinimap.cpp
+	adventureMap/CResDataBar.cpp
+	adventureMap/CTerrainRect.cpp
+	adventureMap/mapHandler.cpp
+
+	battle/BattleActionsController.cpp
+	battle/BattleAnimationClasses.cpp
+	battle/BattleEffectsController.cpp
+	battle/BattleFieldController.cpp
+	battle/BattleInterface.cpp
+	battle/BattleInterfaceClasses.cpp
+	battle/BattleObstacleController.cpp
+	battle/BattleProjectileController.cpp
+	battle/BattleRenderer.cpp
+	battle/BattleSiegeController.cpp
+	battle/BattleStacksController.cpp
+	battle/BattleWindow.cpp
+	battle/CreatureAnimation.cpp
+
+	gui/CGuiHandler.cpp
+	gui/CIntObject.cpp
+	gui/CursorHandler.cpp
+	gui/InterfaceObjectConfigurable.cpp
+	gui/NotificationHandler.cpp
+
+	lobby/CBonusSelection.cpp
+	lobby/CCampaignInfoScreen.cpp
+	lobby/CLobbyScreen.cpp
+	lobby/CSavingScreen.cpp
+	lobby/CScenarioInfoScreen.cpp
+	lobby/CSelectionBase.cpp
+	lobby/OptionsTab.cpp
+	lobby/RandomMapTab.cpp
+	lobby/SelectionTab.cpp
+
+	mainmenu/CCampaignScreen.cpp
+	mainmenu/CMainMenu.cpp
+	mainmenu/CPrologEpilogVideo.cpp
+	mainmenu/CreditsScreen.cpp
+
+	render/CAnimation.cpp
+	render/CBitmapHandler.cpp
+	render/CDefFile.cpp
+	render/CFadeAnimation.cpp
+	render/Canvas.cpp
+	render/ColorFilter.cpp
+	render/Graphics.cpp
+	render/IFont.cpp
+
+	renderSDL/CBitmapFont.cpp
+	renderSDL/CBitmapHanFont.cpp
+	renderSDL/CTrueTypeFont.cpp
+	renderSDL/CursorHardware.cpp
+	renderSDL/CursorSoftware.cpp
+	renderSDL/SDLImage.cpp
+	renderSDL/SDLImageLoader.cpp
+	renderSDL/SDLRWwrapper.cpp
+	renderSDL/SDL_Extensions.cpp
+
+	widgets/Buttons.cpp
+	widgets/CArtifactHolder.cpp
+	widgets/CComponent.cpp
+	widgets/CGarrisonInt.cpp
+	widgets/CreatureCostBox.cpp
+	widgets/Images.cpp
+	widgets/MiscWidgets.cpp
+	widgets/ObjectLists.cpp
+	widgets/TextControls.cpp
+
+	windows/CCastleInterface.cpp
+	windows/CCreatureWindow.cpp
+	windows/CHeroWindow.cpp
+	windows/CKingdomInterface.cpp
+	windows/CMessage.cpp
+	windows/CQuestLog.cpp
+	windows/CSpellWindow.cpp
+	windows/CTradeWindow.cpp
+	windows/CWindowObject.cpp
+	windows/CreaturePurchaseCard.cpp
+	windows/GUIClasses.cpp
+	windows/InfoWindows.cpp
+	windows/QuickRecruitmentWindow.cpp
+
+	CGameInfo.cpp
+	CMT.cpp
+	CMusicHandler.cpp
+	CPlayerInterface.cpp
+	CServerHandler.cpp
+	CVideoHandler.cpp
+	Client.cpp
+	ClientCommandManager.cpp
+	NetPacksClient.cpp
+	NetPacksLobbyClient.cpp
 )
 
 set(client_HEADERS
-		StdInc.h
-
-		battle/BattleActionsController.h
-		battle/BattleAnimationClasses.h
-		battle/BattleEffectsController.h
-		battle/BattleFieldController.h
-		battle/BattleInterfaceClasses.h
-		battle/BattleInterface.h
-		battle/BattleObstacleController.h
-		battle/BattleProjectileController.h
-		battle/BattleRenderer.h
-		battle/BattleSiegeController.h
-		battle/BattleStacksController.h
-		battle/BattleWindow.h
-		battle/CreatureAnimation.h
-		battle/BattleConstants.h
-
-		gui/CAnimation.h
-		gui/Canvas.h
-		gui/CursorHandler.h
-		gui/CGuiHandler.h
-		gui/ColorFilter.h
-		gui/CIntObject.h
-		gui/Fonts.h
-		gui/TextAlignment.h
-		gui/SDL_Compat.h
-		gui/SDL_Extensions.h
-		gui/SDL_Pixels.h
-		gui/NotificationHandler.h
-		gui/InterfaceObjectConfigurable.h
-
-		widgets/AdventureMapClasses.h
-		widgets/Buttons.h
-		widgets/CArtifactHolder.h
-		widgets/CComponent.h
-		widgets/CGarrisonInt.h
-		widgets/Images.h
-		widgets/MiscWidgets.h
-		widgets/ObjectLists.h
-		widgets/TextControls.h
-		windows/CAdvmapInterface.h
-		windows/CCastleInterface.h
-		windows/CCreatureWindow.h
-		windows/CreaturePurchaseCard.h
-		windows/CHeroWindow.h
-		windows/CKingdomInterface.h
-		windows/CQuestLog.h
-		windows/CSpellWindow.h
-		windows/CTradeWindow.h
-		windows/CWindowObject.h
-		windows/GUIClasses.h
-		windows/InfoWindows.h
-		windows/QuickRecruitmentWindow.h
-
-		mainmenu/CMainMenu.h
-		mainmenu/CCampaignScreen.h
-		mainmenu/CreditsScreen.h
-		mainmenu/CPrologEpilogVideo.h
-
-		lobby/CBonusSelection.h
-		lobby/CSelectionBase.h
-		lobby/CLobbyScreen.h
-		lobby/CSavingScreen.h
-		lobby/CScenarioInfoScreen.h
-		lobby/CCampaignInfoScreen.h
-		lobby/OptionsTab.h
-		lobby/RandomMapTab.h
-		lobby/SelectionTab.h
-
-		CBitmapHandler.h
-		CreatureCostBox.h
-		CGameInfo.h
-		Client.h
-		CMessage.h
-		CMT.h
-		CMusicHandler.h
-		CPlayerInterface.h
-		CVideoHandler.h
-		CServerHandler.h
-		Graphics.h
-		mapHandler.h
-		resource.h
-		SDLRWwrapper.h
-		ClientCommandManager.h
+	StdInc.h
+
+	adventureMap/CAdvMapPanel.h
+	adventureMap/CAdvMapInt.h
+	adventureMap/CAdventureOptions.h
+	adventureMap/CInGameConsole.h
+	adventureMap/CInfoBar.h
+	adventureMap/CList.h
+	adventureMap/CMinimap.h
+	adventureMap/CResDataBar.h
+	adventureMap/CTerrainRect.h
+	adventureMap/mapHandler.h
+
+	battle/BattleActionsController.h
+	battle/BattleAnimationClasses.h
+	battle/BattleConstants.h
+	battle/BattleEffectsController.h
+	battle/BattleFieldController.h
+	battle/BattleInterface.h
+	battle/BattleInterfaceClasses.h
+	battle/BattleObstacleController.h
+	battle/BattleProjectileController.h
+	battle/BattleRenderer.h
+	battle/BattleSiegeController.h
+	battle/BattleStacksController.h
+	battle/BattleWindow.h
+	battle/CreatureAnimation.h
+
+	gui/CGuiHandler.h
+	gui/CIntObject.h
+	gui/CursorHandler.h
+	gui/InterfaceObjectConfigurable.h
+	gui/NotificationHandler.h
+	gui/TextAlignment.h
+
+	lobby/CBonusSelection.h
+	lobby/CCampaignInfoScreen.h
+	lobby/CLobbyScreen.h
+	lobby/CSavingScreen.h
+	lobby/CScenarioInfoScreen.h
+	lobby/CSelectionBase.h
+	lobby/OptionsTab.h
+	lobby/RandomMapTab.h
+	lobby/SelectionTab.h
+
+	mainmenu/CCampaignScreen.h
+	mainmenu/CMainMenu.h
+	mainmenu/CPrologEpilogVideo.h
+	mainmenu/CreditsScreen.h
+
+	render/CAnimation.h
+	render/CBitmapHandler.h
+	render/CDefFile.h
+	render/CFadeAnimation.h
+	render/Canvas.h
+	render/ColorFilter.h
+	render/Graphics.h
+	render/ICursor.h
+	render/IFont.h
+	render/IImage.h
+	render/IImageLoader.h
+
+	renderSDL/CBitmapFont.h
+	renderSDL/CBitmapHanFont.h
+	renderSDL/CTrueTypeFont.h
+	renderSDL/CursorHardware.h
+	renderSDL/CursorSoftware.h
+	renderSDL/SDLImage.h
+	renderSDL/SDLImageLoader.h
+	renderSDL/SDLRWwrapper.h
+	renderSDL/SDL_Extensions.h
+	renderSDL/SDL_PixelAccess.h
+
+	widgets/Buttons.h
+	widgets/CArtifactHolder.h
+	widgets/CComponent.h
+	widgets/CGarrisonInt.h
+	widgets/CreatureCostBox.h
+	widgets/Images.h
+	widgets/MiscWidgets.h
+	widgets/ObjectLists.h
+	widgets/TextControls.h
+
+	windows/CCastleInterface.h
+	windows/CCreatureWindow.h
+	windows/CHeroWindow.h
+	windows/CKingdomInterface.h
+	windows/CMessage.h
+	windows/CQuestLog.h
+	windows/CSpellWindow.h
+	windows/CTradeWindow.h
+	windows/CWindowObject.h
+	windows/CreaturePurchaseCard.h
+	windows/GUIClasses.h
+	windows/InfoWindows.h
+	windows/QuickRecruitmentWindow.h
+
+	CGameInfo.h
+	CMT.h
+	CMusicHandler.h
+	CPlayerInterface.h
+	CServerHandler.h
+	CVideoHandler.h
+	Client.h
+	ClientCommandManager.h
+	resource.h
 )
 
 if(APPLE_IOS)
@@ -263,8 +304,7 @@ elseif(APPLE_IOS)
 		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 
 		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
-		# add max version condition when https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7562 is merged
-		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0")
+		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
 			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
 		endif()
 	endforeach()
@@ -290,8 +330,15 @@ else()
 	target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO)
 endif()
 
-target_include_directories(vcmiclient
-	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories(vcmiclient PUBLIC
+	${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+if (ffmpeg_INCLUDE_DIRS)
+	target_include_directories(vcmiclient PRIVATE
+		${ffmpeg_INCLUDE_DIRS}
+	)
+endif()
 
 vcmi_set_output_dir(vcmiclient "")
 enable_pch(vcmiclient)

+ 3 - 1
client/CMusicHandler.cpp

@@ -13,7 +13,8 @@
 
 #include "CMusicHandler.h"
 #include "CGameInfo.h"
-#include "SDLRWwrapper.h"
+#include "renderSDL/SDLRWwrapper.h"
+
 #include "../lib/JsonNode.h"
 #include "../lib/GameConstants.h"
 #include "../lib/filesystem/Filesystem.h"
@@ -22,6 +23,7 @@
 #include "../lib/VCMIDirs.h"
 #include "../lib/TerrainHandler.h"
 
+
 #define VCMI_SOUND_NAME(x)
 #define VCMI_SOUND_FILE(y) #y,
 

+ 10 - 10
client/CPlayerInterface.cpp

@@ -11,7 +11,8 @@
 
 #include <vcmi/Artifact.h>
 
-#include "windows/CAdvmapInterface.h"
+#include "adventureMap/CAdvMapInt.h"
+#include "adventureMap/mapHandler.h"
 #include "battle/BattleInterface.h"
 #include "battle/BattleEffectsController.h"
 #include "battle/BattleFieldController.h"
@@ -25,15 +26,15 @@
 #include "windows/CHeroWindow.h"
 #include "windows/CCreatureWindow.h"
 #include "windows/CQuestLog.h"
-#include "CMessage.h"
 #include "CPlayerInterface.h"
-#include "gui/SDL_Extensions.h"
 #include "widgets/CComponent.h"
+#include "widgets/Buttons.h"
 #include "windows/CTradeWindow.h"
 #include "windows/CSpellWindow.h"
 #include "../lib/CConfigHandler.h"
-#include "Graphics.h"
 #include "windows/GUIClasses.h"
+#include "render/CAnimation.h"
+#include "render/IImage.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
@@ -51,24 +52,23 @@
 #include "../lib/NetPacks.h"//todo: remove
 #include "../lib/mapping/CMap.h"
 #include "../lib/VCMIDirs.h"
-#include "mapHandler.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/StartInfo.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
-#include "gui/CAnimation.h"
 #include "windows/InfoWindows.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/CPathfinder.h"
 #include "../lib/RoadHandler.h"
 #include "../lib/TerrainHandler.h"
-#include <SDL_timer.h>
 #include "CServerHandler.h"
 // FIXME: only needed for CGameState::mutex
 #include "../lib/CGameState.h"
 #include "gui/NotificationHandler.h"
+#include "adventureMap/CInGameConsole.h"
 
+#include <SDL_events.h>
 
 // The macro below is used to mark functions that are called by client when game state changes.
 // They all assume that CPlayerInterface::pim mutex is locked.
@@ -342,7 +342,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 
 		auto unlockPim = vstd::makeUnlockGuard(*pim);
 		while(frameNumber == GH.mainFPSmng->getFrameNumber())
-			SDL_Delay(5);
+			boost::this_thread::sleep(boost::posix_time::milliseconds(5));
 	};
 
 	//first initializing done
@@ -1514,7 +1514,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 		{
 			auto unlockPim = vstd::makeUnlockGuard(*pim);
 			IgnoreEvents ignore(*this);
-			SDL_Delay(focusTime);
+			boost::this_thread::sleep(boost::posix_time::milliseconds(focusTime));
 		}
 	}
 	CCS->curh->show();
@@ -2276,7 +2276,7 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 	while(!dialogs.empty())
 	{
 		auto unlock = vstd::makeUnlockGuardIf(*pim, unlockPim);
-		SDL_Delay(5);
+		boost::this_thread::sleep(boost::posix_time::milliseconds(5));
 	}
 	waitWhileDialog(unlockPim);
 }

+ 2 - 0
client/CServerHandler.cpp

@@ -60,6 +60,8 @@
 #include <windows.h>
 #endif
 
+#include <SDL_events.h>
+
 template<typename T> class CApplyOnLobby;
 
 const std::string CServerHandler::localhostAddress{"127.0.0.1"};

+ 32 - 23
client/CVideoHandler.cpp

@@ -11,13 +11,24 @@
 #include "CVideoHandler.h"
 
 #include "gui/CGuiHandler.h"
-#include "gui/SDL_Extensions.h"
+#include "renderSDL/SDL_Extensions.h"
 #include "CPlayerInterface.h"
 #include "../lib/filesystem/Filesystem.h"
 
+#include <SDL_render.h>
+#include <SDL_events.h>
+
 extern CGuiHandler GH; //global gui handler
 
 #ifndef DISABLE_VIDEO
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/imgutils.h>
+#include <libswscale/swscale.h>
+}
+
 //reads events and returns true on key down
 static bool keyDown()
 {
@@ -56,22 +67,20 @@ static si64 lodSeek(void * opaque, si64 pos, int whence)
 }
 
 CVideoPlayer::CVideoPlayer()
-{
-	stream = -1;
-	format = nullptr;
-	codecContext = nullptr;
-	codec = nullptr;
-	frame = nullptr;
-	sws = nullptr;
-	context = nullptr;
-	texture = nullptr;
-	dest = nullptr;
-	destRect = CSDL_Ext::genRect(0,0,0,0);
-	pos = CSDL_Ext::genRect(0,0,0,0);
-	refreshWait = 0;
-	refreshCount = 0;
-	doLoop = false;
-}
+	: stream(-1)
+	, format (nullptr)
+	, codecContext(nullptr)
+	, codec(nullptr)
+	, frame(nullptr)
+	, sws(nullptr)
+	, context(nullptr)
+	, texture(nullptr)
+	, dest(nullptr)
+	, destRect(0,0,0,0)
+	, pos(0,0,0,0)
+	, frameTime(0)
+	, doLoop(false)
+{}
 
 bool CVideoPlayer::open(std::string fname, bool scale)
 {
@@ -85,9 +94,8 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal
 	close();
 
 	this->fname = fname;
-	refreshWait = 3;
-	refreshCount = -1;
 	doLoop = loop;
+	frameTime = 0;
 
 	ResourceID resource(std::string("Video/") + fname, EResType::VIDEO);
 
@@ -254,6 +262,7 @@ bool CVideoPlayer::nextFrame()
 			if (doLoop && !gotError)
 			{
 				// Rewind
+				frameTime = 0;
 				if (av_seek_frame(format, stream, 0, AVSEEK_FLAG_BYTE) < 0)
 					break;
 				gotError = true;
@@ -354,9 +363,11 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 	if (sws == nullptr)
 		return;
 
-	if (refreshCount <= 0)
+	double frameEndTime = (frame->pts + frame->pkt_duration) * av_q2d(format->streams[stream]->time_base);
+	frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.0;
+
+	if (frameTime >= frameEndTime )
 	{
-		refreshCount = refreshWait;
 		if (nextFrame())
 			show(x,y,dst,update);
 		else
@@ -374,8 +385,6 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 	{
 		redraw(x, y, dst, update);
 	}
-
-	refreshCount --;
 }
 
 void CVideoPlayer::close()

+ 7 - 36
client/CVideoHandler.h

@@ -56,39 +56,11 @@ public:
 
 #include "../lib/filesystem/CInputStream.h"
 
-extern "C" {
-#include <libavformat/avformat.h>
-#include <libavcodec/avcodec.h>
-#include <libavutil/imgutils.h>
-#include <libswscale/swscale.h>
-}
-
-//compatibility for libav 9.18 in ubuntu 14.04, 52.66.100 is ffmpeg 2.2.3
-#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 66, 100))
-inline AVFrame * av_frame_alloc()
-{
-	return avcodec_alloc_frame();
-}
-
-inline void av_frame_free(AVFrame ** frame)
-{
-	av_free(*frame);
-	*frame = nullptr;
-}
-#endif // VCMI_USE_OLD_AVUTIL
-
-//fix for travis-ci
-#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 0, 0))
-	#define AVPixelFormat PixelFormat
-	#define AV_PIX_FMT_NONE PIX_FMT_NONE
-	#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
-	#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
-	#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
-	#define AV_PIX_FMT_BGR32 PIX_FMT_BGR32
-	#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
-	#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
-	#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
-#endif
+struct AVFormatContext;
+struct AVCodecContext;
+struct AVCodec;
+struct AVFrame;
+struct AVIOContext;
 
 class CVideoPlayer : public IMainVideoPlayer
 {
@@ -108,8 +80,8 @@ class CVideoPlayer : public IMainVideoPlayer
 	Rect destRect;			// valid when dest is used
 	Rect pos;				// destination on screen
 
-	int refreshWait; // Wait several refresh before updating the image
-	int refreshCount;
+	/// video playback currnet progress, in seconds
+	double frameTime;
 	bool doLoop;				// loop through video
 
 	bool playVideo(int x, int y, bool stopOnKey);
@@ -141,4 +113,3 @@ public:
 };
 
 #endif
-

+ 6 - 6
client/Client.cpp

@@ -13,6 +13,8 @@
 #include "CMusicHandler.h"
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../CCallback.h"
+#include "adventureMap/CAdvMapInt.h"
+#include "adventureMap/mapHandler.h"
 #include "../lib/CConsoleHandler.h"
 #include "CGameInfo.h"
 #include "../lib/CGameState.h"
@@ -35,7 +37,6 @@
 #include "../lib/mapping/CMap.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/JsonNode.h"
-#include "mapHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "mainmenu/CMainMenu.h"
 #include "mainmenu/CCampaignScreen.h"
@@ -46,7 +47,6 @@
 #include "gui/CGuiHandler.h"
 #include "CServerHandler.h"
 #include "../lib/ScriptHandler.h"
-#include "windows/CAdvmapInterface.h"
 #include <vcmi/events/EventBus.h>
 
 #ifdef VCMI_ANDROID
@@ -775,14 +775,14 @@ void CClient::removeGUI()
 }
 
 #ifdef VCMI_ANDROID
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received clientSetupJNI");
 
 	CAndroidVMHelper::cacheVM(env);
 }
 
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received server closed signal");
 	if (CSH) {
@@ -790,13 +790,13 @@ extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerCl
 	}
 }
 
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received server ready signal");
 	androidTestServerReadyFlag.store(true);
 }
 
-extern "C" JNIEXPORT bool JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls)
 {
 	logGlobal->info("Received emergency save game request");
 	if(!LOCPLINT || !LOCPLINT->cb)

+ 6 - 2
client/ClientCommandManager.cpp

@@ -12,6 +12,8 @@
 #include "ClientCommandManager.h"
 
 #include "Client.h"
+#include "adventureMap/CInGameConsole.h"
+#include "adventureMap/CAdvMapInt.h"
 #include "CPlayerInterface.h"
 #include "CServerHandler.h"
 #include "gui/CGuiHandler.h"
@@ -20,19 +22,21 @@
 #include "../lib/CGameState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/StringConstants.h"
-#include "gui/CAnimation.h"
-#include "windows/CAdvmapInterface.h"
 #include "windows/CCastleInterface.h"
+#include "render/CAnimation.h"
 #include "../CCallback.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CModHandler.h"
 #include "../lib/VCMIDirs.h"
+#include "CMT.h"
 
 #ifdef SCRIPTING_ENABLED
 #include "../lib/ScriptHandler.h"
 #endif
 
+#include <SDL_surface.h>
+
 void ClientCommandManager::handleGoSolo()
 {
 	Settings session = settings.write["session"];

+ 12 - 12
client/NetPacksClient.cpp

@@ -10,12 +10,21 @@
 #include "StdInc.h"
 #include "../lib/NetPacks.h"
 
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/FileInfo.h"
-#include "../CCallback.h"
 #include "Client.h"
 #include "CPlayerInterface.h"
 #include "CGameInfo.h"
+#include "windows/GUIClasses.h"
+#include "adventureMap/mapHandler.h"
+#include "adventureMap/CInGameConsole.h"
+#include "battle/BattleInterface.h"
+#include "gui/CGuiHandler.h"
+#include "widgets/MiscWidgets.h"
+#include "CMT.h"
+#include "CServerHandler.h"
+
+#include "../CCallback.h"
+#include "../lib/filesystem/Filesystem.h"
+#include "../lib/filesystem/FileInfo.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/BinarySerializer.h"
 #include "../lib/CGeneralTextHandler.h"
@@ -26,22 +35,13 @@
 #include "../lib/spells/CSpellHandler.h"
 #include "../lib/CSoundBase.h"
 #include "../lib/StartInfo.h"
-#include "mapHandler.h"
-#include "windows/GUIClasses.h"
 #include "../lib/CConfigHandler.h"
-#include "gui/SDL_Extensions.h"
-#include "battle/BattleInterface.h"
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/CStack.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/GameConstants.h"
 #include "../lib/CPlayerState.h"
-#include "gui/CGuiHandler.h"
-#include "widgets/MiscWidgets.h"
-#include "widgets/AdventureMapClasses.h"
-#include "CMT.h"
-#include "CServerHandler.h"
 
 // TODO: as Tow suggested these template should all be part of CClient
 // This will require rework spectator interface properly though

+ 0 - 2
client/StdInc.h

@@ -2,8 +2,6 @@
 
 #include "../Global.h"
 
-#include "gui/SDL_Compat.h"
-
 // This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
 
 // Here you can add specific libraries and macros which are specific to this project.

+ 63 - 555
client/windows/CAdvmapInterface.cpp → client/adventureMap/CAdvMapInt.cpp

@@ -1,5 +1,5 @@
 /*
- * CAdvmapInterface.cpp, part of VCMI engine
+ * CAdvMapInt.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,55 +8,44 @@
  *
  */
 #include "StdInc.h"
-#include "CAdvmapInterface.h"
-
-#include "CCastleInterface.h"
-#include "CHeroWindow.h"
-#include "CKingdomInterface.h"
-#include "CSpellWindow.h"
-#include "CTradeWindow.h"
-#include "GUIClasses.h"
-#include "InfoWindows.h"
-
-#include "../CBitmapHandler.h"
+#include "CAdvMapInt.h"
+
+#include "CAdvMapPanel.h"
+#include "CAdventureOptions.h"
+#include "CInGameConsole.h"
+#include "mapHandler.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 "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../mainmenu/CMainMenu.h"
-#include "../lobby/CSelectionBase.h"
-#include "../lobby/CCampaignInfoScreen.h"
 #include "../lobby/CSavingScreen.h"
-#include "../lobby/CScenarioInfoScreen.h"
-#include "../Graphics.h"
-#include "../mapHandler.h"
-
-#include "../gui/CAnimation.h"
+#include "../render/CAnimation.h"
 #include "../gui/CursorHandler.h"
+#include "../render/IImage.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/SDL_Extensions.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/TextControls.h"
+#include "../widgets/Buttons.h"
 
 #include "../../CCallback.h"
-
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/CGameState.h"
 #include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/CHeroHandler.h"
-#include "../../lib/CSoundBase.h"
 #include "../../lib/spells/CSpellHandler.h"
-#include "../../lib/CTownHandler.h"
-#include "../../lib/JsonNode.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/CPathfinder.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/UnlockGuard.h"
-#include "../../lib/VCMI_Lib.h"
-#include "../../lib/StartInfo.h"
-#include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/TerrainHandler.h"
 
+#include <SDL_surface.h>
+#include <SDL_events.h>
+
 #define ADVOPT (conf.go()->ac)
-using namespace CSDL_Ext;
 
 std::shared_ptr<CAdvMapInt> adventureInt;
 
@@ -86,470 +75,6 @@ static void setScrollingCursor(ui8 direction)
 		CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
 }
 
-CTerrainRect::CTerrainRect()
-	: fadeSurface(nullptr),
-	  lastRedrawStatus(EMapAnimRedrawStatus::OK),
-	  fadeAnim(std::make_shared<CFadeAnimation>()),
-	  curHoveredTile(-1,-1,-1),
-	  currentPath(nullptr)
-{
-	tilesw=(ADVOPT.advmapW+31)/32;
-	tilesh=(ADVOPT.advmapH+31)/32;
-	pos.x=ADVOPT.advmapX;
-	pos.y=ADVOPT.advmapY;
-	pos.w=ADVOPT.advmapW;
-	pos.h=ADVOPT.advmapH;
-	moveX = moveY = 0;
-	addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
-}
-
-CTerrainRect::~CTerrainRect()
-{
-	if(fadeSurface)
-		SDL_FreeSurface(fadeSurface);
-}
-
-void CTerrainRect::deactivate()
-{
-	CIntObject::deactivate();
-	curHoveredTile = int3(-1,-1,-1); //we lost info about hovered tile when disabling
-}
-
-void CTerrainRect::clickLeft(tribool down, bool previousState)
-{
-	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-	if(indeterminate(down))
-		return;
-
-#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	if(adventureInt->swipeEnabled)
-	{
-		if(handleSwipeStateChange((bool)down == true))
-		{
-			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
-		}
-	}
-	else
-	{
-#endif
-		if(down == false)
-			return;
-#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	}
-#endif
-	int3 mp = whichTileIsIt();
-	if(mp.x < 0 || mp.y < 0 || mp.x >= LOCPLINT->cb->getMapSize().x || mp.y >= LOCPLINT->cb->getMapSize().y)
-		return;
-
-	adventureInt->tileLClicked(mp);
-}
-
-void CTerrainRect::clickRight(tribool down, bool previousState)
-{
-#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	if(adventureInt->swipeEnabled && isSwiping)
-		return;
-#endif
-	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-	int3 mp = whichTileIsIt();
-
-	if(CGI->mh->map->isInTheMap(mp) && down)
-		adventureInt->tileRClicked(mp);
-}
-
-void CTerrainRect::clickMiddle(tribool down, bool previousState)
-{
-	handleSwipeStateChange((bool)down == true);
-}
-
-void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
-{
-	handleHover(sEvent);
-
-	if(!adventureInt->swipeEnabled)
-		return;
-
-	handleSwipeMove(sEvent);
-}
-
-void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
-{
-#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	if(sEvent.state == 0) // any "button" is enough on mobile
-#else
-	if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
-#endif
-	{
-		return;
-	}
-
-	if(!isSwiping)
-	{
-		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
-		if(abs(sEvent.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
-		   abs(sEvent.y - swipeInitialRealPos.y) > SwipeTouchSlop)
-		{
-			isSwiping = true;
-		}
-	}
-
-	if(isSwiping)
-	{
-		adventureInt->swipeTargetPosition.x =
-			swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - sEvent.x) / 32;
-		adventureInt->swipeTargetPosition.y =
-			swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - sEvent.y) / 32;
-		adventureInt->swipeMovementRequested = true;
-	}
-}
-
-bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
-{
-	if(btnPressed)
-	{
-		swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
-		swipeInitialMapPos = int3(adventureInt->position);
-		return true;
-	}
-	else if(isSwiping) // only accept this touch if it wasn't a swipe
-	{
-		isSwiping = false;
-		return true;
-	}
-	return false;
-}
-
-void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
-{
-	int3 tHovered = whichTileIsIt(sEvent.x, sEvent.y);
-	int3 pom = adventureInt->verifyPos(tHovered);
-
-	if(tHovered != pom) //tile outside the map
-	{
-		CCS->curh->set(Cursor::Map::POINTER);
-		return;
-	}
-
-	if (pom != curHoveredTile)
-	{
-		curHoveredTile = pom;
-		adventureInt->tileHovered(pom);
-	}
-}
-
-void CTerrainRect::hover(bool on)
-{
-	if (!on)
-	{
-		adventureInt->statusbar->clear();
-		CCS->curh->set(Cursor::Map::POINTER);
-	}
-	//Hoverable::hover(on);
-}
-void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
-{
-	const static int pns[9][9] = {
-				{16, 17, 18,  7, -1, 19,  6,  5, -1},
-				{ 8,  9, 18,  7, -1, 19,  6, -1, 20},
-				{ 8,  1, 10,  7, -1, 19, -1, 21, 20},
-				{24, 17, 18, 15, -1, -1,  6,  5,  4},
-				{-1, -1, -1, -1, -1, -1, -1, -1, -1},
-				{ 8,  1,  2, -1, -1, 11, 22, 21, 20},
-				{24, 17, -1, 23, -1,  3, 14,  5,  4},
-				{24, -1,  2, 23, -1,  3, 22, 13,  4},
-				{-1,  1,  2, 23, -1,  3, 22, 21, 12}
-			}; //table of magic values TODO meaning, change variable name
-
-	for (int i = 0; i < -1 + (int)currentPath->nodes.size(); ++i)
-	{
-		const int3 &curPos = currentPath->nodes[i].coord, &nextPos = currentPath->nodes[i+1].coord;
-		if(curPos.z != adventureInt->position.z)
-			continue;
-
-		int pn=-1;//number of picture
-		if (i==0) //last tile
-		{
-			int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
-				y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
-			if (x<0 || y<0 || x>pos.w || y>pos.h)
-				continue;
-			pn=0;
-		}
-		else
-		{
-			const int3 &prevPos = currentPath->nodes[i-1].coord;
-			std::vector<CGPathNode> & cv = currentPath->nodes;
-
-			/* Vector directions
-			 *  0   1   2
-			 *    \ | /
-			 *  3 - 4 - 5
-			 *    / | \
-			 *  6   7  8
-			 *For example:
-			 *  |
-			 *  |__\
-			 *     /
-			 * is id1=7, id2=5 (pns[7][5])
-			*/
-			bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
-			if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK)
-			{
-				int id1=(curPos.x-nextPos.x+1)+3*(curPos.y-nextPos.y+1);   //Direction of entering vector
-				int id2=(cv[i-1].coord.x-curPos.x+1)+3*(cv[i-1].coord.y-curPos.y+1); //Direction of exiting vector
-				pn=pns[id1][id2];
-			}
-			else //path discontinuity or sea/land transition (eg. when moving through Subterranean Gate or Boat)
-			{
-				pn = 0;
-			}
-		}
-		if (currentPath->nodes[i].turns)
-			pn+=25;
-		if (pn>=0)
-		{
-			const auto arrow = graphics->heroMoveArrows->getImage(pn);
-
-			int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
-				y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
-			if (x< -32 || y< -32 || x>pos.w || y>pos.h)
-				continue;
-			int hvx = (x + arrow->width())  - (pos.x + pos.w),
-				hvy = (y + arrow->height()) - (pos.y + pos.h);
-
-			Rect prevClip;
-			CSDL_Ext::getClipRect(to, prevClip);
-			CSDL_Ext::setClipRect(to, extRect); //preventing blitting outside of that rect
-
-			if(ADVOPT.smoothMove) //version for smooth hero move, with pos shifts
-			{
-				if (hvx<0 && hvy<0)
-				{
-					arrow->draw(to, x + moveX, y + moveY);
-				}
-				else if(hvx<0)
-				{
-					Rect srcRect = genRect(arrow->height() - hvy, arrow->width(), 0, 0);
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-				else if (hvy<0)
-				{
-					Rect srcRect = genRect(arrow->height(), arrow->width() - hvx, 0, 0);
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-				else
-				{
-					Rect srcRect = genRect(arrow->height() - hvy, arrow->width() - hvx, 0, 0);
-					arrow->draw(to, x + moveX, y + moveY, &srcRect);
-				}
-			}
-			else //standard version
-			{
-				if (hvx<0 && hvy<0)
-				{
-					arrow->draw(to, x, y);
-				}
-				else if(hvx<0)
-				{
-					Rect srcRect = genRect(arrow->height() - hvy, arrow->width(), 0, 0);
-					arrow->draw(to, x, y, &srcRect);
-				}
-				else if (hvy<0)
-				{
-					Rect srcRect = genRect(arrow->height(), arrow->width() - hvx, 0, 0);
-					arrow->draw(to, x, y, &srcRect);
-				}
-				else
-				{
-					Rect srcRect = genRect(arrow->height() - hvy, arrow->width() - hvx, 0, 0);
-					arrow->draw(to, x, y, &srcRect);
-				}
-			}
-			CSDL_Ext::setClipRect(to, prevClip);
-
-		}
-	} //for (int i=0;i<currentPath->nodes.size()-1;i++)
-}
-
-void CTerrainRect::show(SDL_Surface * to)
-{
-	if (adventureInt->mode == EAdvMapMode::NORMAL)
-	{
-		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos);
-		info.otherheroAnim = true;
-		info.anim = adventureInt->anim;
-		info.heroAnim = adventureInt->heroAnim;
-		if (ADVOPT.smoothMove)
-			info.movement = int3(moveX, moveY, 0);
-
-		lastRedrawStatus = CGI->mh->drawTerrainRectNew(to, &info);
-		if (fadeAnim->isFading())
-		{
-			Rect r(pos);
-			fadeAnim->update();
-			fadeAnim->draw(to, r.topLeft());
-		}
-
-		if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path
-		{
-			showPath(pos, to);
-		}
-	}
-}
-
-void CTerrainRect::showAll(SDL_Surface * to)
-{
-	// world view map is static and doesn't need redraw every frame
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-	{
-		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos, adventureInt->worldViewIcons);
-		info.scaled = true;
-		info.scale = adventureInt->worldViewScale;
-		adventureInt->worldViewOptions.adjustDrawingInfo(info);
-		CGI->mh->drawTerrainRectNew(to, &info);
-	}
-}
-
-void CTerrainRect::showAnim(SDL_Surface * to)
-{
-	if (fadeAnim->isFading())
-		show(to);
-	else if (lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED)
-		show(to); // currently the same; maybe we should pass some flag to map handler so it redraws ONLY tiles that need redraw instead of full
-}
-
-int3 CTerrainRect::whichTileIsIt(const int x, const int y)
-{
-	int3 ret;
-	ret.x = adventureInt->position.x + ((x-CGI->mh->offsetX-pos.x)/32);
-	ret.y = adventureInt->position.y + ((y-CGI->mh->offsetY-pos.y)/32);
-	ret.z = adventureInt->position.z;
-	return ret;
-}
-
-int3 CTerrainRect::whichTileIsIt()
-{
-	if(GH.current)
-		return whichTileIsIt(GH.current->motion.x,GH.current->motion.y);
-	else
-		return int3(-1);
-}
-
-int3 CTerrainRect::tileCountOnScreen()
-{
-	switch (adventureInt->mode)
-	{
-	default:
-		logGlobal->error("Unknown map mode %d", (int)adventureInt->mode);
-		return int3();
-	case EAdvMapMode::NORMAL:
-		return int3(tilesw, tilesh, 1);
-	case EAdvMapMode::WORLD_VIEW:
-		return int3((si32)(tilesw / adventureInt->worldViewScale), (si32)(tilesh / adventureInt->worldViewScale), 1);
-	}
-}
-
-void CTerrainRect::fadeFromCurrentView()
-{
-	if (!ADVOPT.screenFading)
-		return;
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
-		return;
-
-	if (!fadeSurface)
-		fadeSurface = CSDL_Ext::newSurface(pos.w, pos.h);
-	CSDL_Ext::blitSurface(screen, fadeSurface, Point(0,0));
-	fadeAnim->init(CFadeAnimation::EMode::OUT, fadeSurface);
-}
-
-bool CTerrainRect::needsAnimUpdate()
-{
-	return fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED;
-}
-
-void CResDataBar::clickRight(tribool down, bool previousState)
-{
-}
-
-CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist)
-{
-	pos.x += x;
-	pos.y += y;
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(defname, 0, 0);
-	background->colorize(LOCPLINT->playerID);
-
-	pos.w = background->bg->w;
-	pos.h = background->bg->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;
-	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
-	+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
-	addUsedEvents(RCLICK);
-}
-
-CResDataBar::CResDataBar()
-{
-	pos.x += ADVOPT.resdatabarX;
-	pos.y += ADVOPT.resdatabarY;
-
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(ADVOPT.resdatabarG, 0, 0);
-	background->colorize(LOCPLINT->playerID);
-
-	pos.w = background->bg->w;
-	pos.h = background->bg->h;
-
-	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;
-	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
-				+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
-}
-
-CResDataBar::~CResDataBar() = default;
-
-void CResDataBar::draw(SDL_Surface * to)
-{
-	//TODO: all this should be labels, but they require proper text update on change
-	for (auto i=Res::WOOD; i<=Res::GOLD; vstd::advance(i, 1))
-	{
-		std::string text = boost::lexical_cast<std::string>(LOCPLINT->cb->getResourceAmount(i));
-
-		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first,txtpos[i].second));
-	}
-	std::vector<std::string> temp;
-
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::MONTH)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::WEEK)));
-	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK)));
-
-	graphics->fonts[FONT_SMALL]->renderTextLeft(to, processStr(datetext,temp), Colors::WHITE, Point(txtpos[7].first,txtpos[7].second));
-}
-
-void CResDataBar::show(SDL_Surface * to)
-{
-
-}
-
-void CResDataBar::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-	draw(to);
-}
-
 CAdvMapInt::CAdvMapInt():
 	mode(EAdvMapMode::NORMAL),
 	worldViewScale(0.0f), //actual init later in changeMode
@@ -569,10 +94,10 @@ CAdvMapInt::CAdvMapInt():
 	pos.h = screen->h;
 	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
 	townList.onSelect = std::bind(&CAdvMapInt::selectionChanged,this);
-	bg = BitmapHandler::loadBitmap(ADVOPT.mainGraphic);
+	bg = IImage::createFromFile(ADVOPT.mainGraphic);
 	if(!ADVOPT.worldViewGraphic.empty())
 	{
-		bgWorldView = BitmapHandler::loadBitmap(ADVOPT.worldViewGraphic);
+		bgWorldView = IImage::createFromFile(ADVOPT.worldViewGraphic);
 	}
 	else
 	{
@@ -582,7 +107,7 @@ CAdvMapInt::CAdvMapInt():
 	if (!bgWorldView)
 	{
 		logGlobal->warn("bgWorldView not defined in resolution config; fallback to VWorld.bmp");
-		bgWorldView = BitmapHandler::loadBitmap("VWorld.bmp");
+		bgWorldView = IImage::createFromFile("VWorld.bmp");
 	}
 
 	worldViewIcons = std::make_shared<CAnimation>("VwSymbol");//todo: customize with ADVOPT
@@ -715,11 +240,6 @@ CAdvMapInt::CAdvMapInt():
 	addUsedEvents(MOVE);
 }
 
-CAdvMapInt::~CAdvMapInt()
-{
-	SDL_FreeSurface(bg);
-}
-
 void CAdvMapInt::fshowOverview()
 {
 	GH.pushIntT<CKingdomInterface>();
@@ -979,7 +499,7 @@ void CAdvMapInt::deactivate()
 
 void CAdvMapInt::showAll(SDL_Surface * to)
 {
-	blitAt(bg,0,0,to);
+	bg->draw(to, 0, 0);
 
 	if(state != INGAME)
 		return;
@@ -1096,7 +616,7 @@ void CAdvMapInt::handleMapScrollingUpdate()
 	int scrollSpeed = static_cast<int>(settings["adventure"]["scrollSpeed"].Float());
 	//if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
 	if((animValHitCount % (4 / scrollSpeed)) == 0
-	   && ((GH.topInt().get() == this) || isCtrlKeyDown()))
+	   && ((GH.topInt().get() == this) || CSDL_Ext::isCtrlKeyDown()))
 	{
 		if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW))
 			position.x--;
@@ -1460,7 +980,7 @@ void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 	// adventure map scrolling with mouse
 	// currently disabled in world view mode (as it is in OH3), but should work correctly if mode check is removed
 	// don't scroll if there is no window in focus - these events don't seem to correspond to the actual mouse movement
-	if(!isCtrlKeyDown() && isActive() && sEvent.windowID != 0 && mode == EAdvMapMode::NORMAL)
+	if(!CSDL_Ext::isCtrlKeyDown() && isActive() && sEvent.windowID != 0 && mode == EAdvMapMode::NORMAL)
 	{
 		if(sEvent.x<15)
 		{
@@ -1510,7 +1030,7 @@ void CAdvMapInt::startHotSeatWait(PlayerColor Player)
 void CAdvMapInt::setPlayer(PlayerColor Player)
 {
 	player = Player;
-	graphics->blueToPlayersAdv(bg,player);
+	bg->playerColored(player);
 
 	panelMain->setPlayerColor(player);
 	panelWorldView->setPlayerColor(player);
@@ -1587,6 +1107,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	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(selection->ID != Obj::HERO) //hero is not selected (presumably town)
 	{
 		assert(!terrain.currentPath); //path can be active only when hero is selected
@@ -1594,10 +1115,11 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
 		else if(canSelect)
 			select(static_cast<const CArmedInstance*>(topBlocking), false);
-		return;
 	}
 	else if(const CGHeroInstance * currentHero = curHero()) //hero is selected
 	{
+		isHero = true;
+
 		const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
 		if(currentHero == topBlocking) //clicked selected hero
 		{
@@ -1639,7 +1161,8 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 		throw std::runtime_error("Nothing is selected...");
 	}
 
-	if(const IShipyard *shipyard = ourInaccessibleShipyard(topBlocking))
+	const auto shipyard = ourInaccessibleShipyard(topBlocking);
+	if(isHero && shipyard != nullptr)
 	{
 		LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
 	}
@@ -1709,7 +1232,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		else
 			CCS->curh->set(Cursor::Map::POINTER);
 	}
-	else if(const CGHeroInstance * h = curHero())
+	else if(const CGHeroInstance * hero = curHero())
 	{
 		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,     };
@@ -1719,16 +1242,21 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		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 * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
-		assert(pnode);
+		const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
+		assert(pathNode);
+
+		if(LOCPLINT->altPressed() && pathNode->reachable()) //overwrite status bar text with movement info
+		{
+			ShowMoveDetailsInStatusbar(*hero, *pathNode);
+		}
 
-		int turns = pnode->turns;
+		int turns = pathNode->turns;
 		vstd::amin(turns, 3);
-		switch(pnode->action)
+		switch(pathNode->action)
 		{
 		case CGPathNode::NORMAL:
 		case CGPathNode::TELEPORT_NORMAL:
-			if(pnode->layer == EPathfindingLayer::LAND)
+			if(pathNode->layer == EPathfindingLayer::LAND)
 				CCS->curh->set(cursorMove[turns]);
 			else
 				CCS->curh->set(cursorSailVisit[turns]);
@@ -1744,7 +1272,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 				else
 					CCS->curh->set(cursorExchange[turns]);
 			}
-			else if(pnode->layer == EPathfindingLayer::LAND)
+			else if(pathNode->layer == EPathfindingLayer::LAND)
 				CCS->curh->set(cursorVisit[turns]);
 			else
 				CCS->curh->set(cursorSailVisit[turns]);
@@ -1785,6 +1313,21 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 	}
 }
 
+void CAdvMapInt::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 CAdvMapInt::tileRClicked(const int3 &mapPos)
 {
 	if(mode != EAdvMapMode::NORMAL)
@@ -1814,7 +1357,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos)
 		return;
 	}
 
-	CRClickPopup::createAndPush(obj, CSDL_Ext::fromSDL(GH.current->motion), ETextAlignment::CENTER);
+	CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
 }
 
 void CAdvMapInt::enterCastingMode(const CSpell * sp)
@@ -1947,42 +1490,6 @@ void CAdvMapInt::changeMode(EAdvMapMode newMode, float newScale)
 	}
 }
 
-CAdventureOptions::CAdventureOptions()
-	: CWindowObject(PLAYER_COLORED, "ADVOPTS")
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-
-	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_v);
-	viewWorld->addCallback(std::bind(&CPlayerInterface::viewWorldMap, LOCPLINT));
-
-	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), SDLK_RETURN);
-	exit->assignedKeys.insert(SDLK_ESCAPE);
-
-	scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_i);
-	scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
-
-	puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_p);
-	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
-
-	dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_d);
-	if(const CGHeroInstance *h = adventureInt->curHero())
-		dig->addCallback(std::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h));
-	else
-		dig->block(true);
-}
-
-void CAdventureOptions::showScenarioInfo()
-{
-	if(LOCPLINT->cb->getStartInfo()->campState)
-	{
-		GH.pushIntT<CCampaignInfoScreen>();
-	}
-	else
-	{
-		GH.pushIntT<CScenarioInfoScreen>();
-	}
-}
-
 CAdvMapInt::WorldViewOptions::WorldViewOptions()
 {
 	clear();
@@ -2001,3 +1508,4 @@ void CAdvMapInt::WorldViewOptions::adjustDrawingInfo(MapDrawingInfo& info)
 
 	info.additionalIcons = &iconPositions;
 }
+

+ 24 - 96
client/windows/CAdvmapInterface.h → client/adventureMap/CAdvMapInt.h

@@ -1,5 +1,5 @@
 /*
- * CAdvmapInterface.h, part of VCMI engine
+ * CAdvMapInt.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,118 +9,45 @@
  */
 #pragma once
 
-#include "../widgets/AdventureMapClasses.h"
-#include "CWindowObject.h"
+#include "../gui/CIntObject.h"
 
-#include "../widgets/TextControls.h"
-#include "../widgets/Buttons.h"
+#include "../../lib/int3.h"
+#include "../../lib/GameConstants.h"
 
-#include "../../lib/spells/ViewSpellInt.h"
+#include "CTerrainRect.h"
+#include "CResDataBar.h"
+#include "CList.h"
+#include "CInfoBar.h"
+#include "CMinimap.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-struct CGPath;
+class CGObjectInstance;
 class CGHeroInstance;
 class CGTownInstance;
-class CSpell;
+class CArmedInstance;
 class IShipyard;
+struct CGPathNode;
+struct ObjectPosInfo;
 
 VCMI_LIB_NAMESPACE_END
 
-class CCallback;
-class CAdvMapInt;
-class CHeroWindow;
-enum class EMapAnimRedrawStatus;
-class CFadeAnimation;
+class CButton;
+class IImage;
+class CAnimImage;
+class CGStatusBar;
+class CAdvMapPanel;
+class CAdvMapWorldViewPanel;
+class CAnimation;
 
 struct MapDrawingInfo;
 
-/*****************************/
-
 enum class EAdvMapMode
 {
 	NORMAL,
 	WORLD_VIEW
 };
 
-/// Adventure options dialog where you can view the world, dig, play the replay of the last turn,...
-class CAdventureOptions : public CWindowObject
-{
-public:
-	std::shared_ptr<CButton> exit;
-	std::shared_ptr<CButton> viewWorld;
-	std::shared_ptr<CButton> puzzle;
-	std::shared_ptr<CButton> dig;
-	std::shared_ptr<CButton> scenInfo;
-	/*std::shared_ptr<CButton> replay*/
-
-	CAdventureOptions();
-	static void showScenarioInfo();
-};
-
-/// Holds information about which tiles of the terrain are shown/not shown at the screen
-class CTerrainRect : public CIntObject
-{
-	SDL_Surface * fadeSurface;
-	EMapAnimRedrawStatus lastRedrawStatus;
-	std::shared_ptr<CFadeAnimation> fadeAnim;
-
-	int3 swipeInitialMapPos;
-	int3 swipeInitialRealPos;
-	bool isSwiping;
-	static constexpr float SwipeTouchSlop = 16.0f;
-
-	void handleHover(const SDL_MouseMotionEvent & sEvent);
-	void handleSwipeMove(const SDL_MouseMotionEvent & sEvent);
-	/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
-	bool handleSwipeStateChange(bool btnPressed);
-public:
-	int tilesw, tilesh; //width and height of terrain to blit in tiles
-	int3 curHoveredTile;
-	int moveX, moveY; //shift between actual position of screen and the one we wil blit; ranges from -31 to 31 (in pixels)
-	CGPath * currentPath;
-
-	CTerrainRect();
-	virtual ~CTerrainRect();
-	void deactivate() override;
-	void clickLeft(tribool down, bool previousState) override;
-	void clickRight(tribool down, bool previousState) override;
-	void clickMiddle(tribool down, bool previousState) override;
-	void hover(bool on) override;
-	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
-	void show(SDL_Surface * to) override;
-	void showAll(SDL_Surface * to) override;
-	void showAnim(SDL_Surface * to);
-	void showPath(const Rect &extRect, SDL_Surface * to);
-	int3 whichTileIsIt(const int x, const int y); //x,y are cursor position
-	int3 whichTileIsIt(); //uses current cursor pos
-	/// @returns number of visible tiles on screen respecting current map scaling
-	int3 tileCountOnScreen();
-	/// animates view by caching current surface and crossfading it with normal screen
-	void fadeFromCurrentView();
-	bool needsAnimUpdate();
-};
-
-/// Resources bar which shows information about how many gold, crystals,... you have
-/// Current date is displayed too
-class CResDataBar : public CIntObject
-{
-public:
-	std::shared_ptr<CPicture> background;
-
-	std::vector<std::pair<int,int> > txtpos;
-	std::string datetext;
-
-	void clickRight(tribool down, bool previousState) override;
-	CResDataBar();
-	CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
-	~CResDataBar();
-
-	void draw(SDL_Surface * to);
-	void show(SDL_Surface * to) override;
-	void showAll(SDL_Surface * to) override;
-};
-
 /// 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.
@@ -131,7 +58,6 @@ class CAdvMapInt : public CIntObject
 
 public:
 	CAdvMapInt();
-	~CAdvMapInt();
 
 	int3 position; //top left corner of visible map part
 	PlayerColor player;
@@ -169,8 +95,8 @@ public:
 
 	WorldViewOptions worldViewOptions;
 
-	SDL_Surface * bg;
-	SDL_Surface * bgWorldView;
+	std::shared_ptr<IImage> bg;
+	std::shared_ptr<IImage> bgWorldView;
 	std::vector<std::shared_ptr<CAnimImage>> gems;
 	CMinimap minimap;
 	std::shared_ptr<CGStatusBar> statusbar;
@@ -269,6 +195,8 @@ public:
 	void handleMapScrollingUpdate();
 	void handleSwipeUpdate();
 
+private:
+	void ShowMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode);
 };
 
 extern std::shared_ptr<CAdvMapInt> adventureInt;

+ 96 - 0
client/adventureMap/CAdvMapPanel.cpp

@@ -0,0 +1,96 @@
+/*
+ * 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));
+	}
+}
+
+CAdvMapWorldViewPanel::~CAdvMapWorldViewPanel() = default;
+
+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));
+}
+

+ 60 - 0
client/adventureMap/CAdvMapPanel.h

@@ -0,0 +1,60 @@
+/*
+ * 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);
+	virtual ~CAdvMapWorldViewPanel();
+
+	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);
+};
+

+ 61 - 0
client/adventureMap/CAdventureOptions.cpp

@@ -0,0 +1,61 @@
+/*
+ * CAdventureOptions.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 "CAdventureOptions.h"
+
+#include "CAdvMapInt.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../lobby/CCampaignInfoScreen.h"
+#include "../lobby/CScenarioInfoScreen.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/Buttons.h"
+
+#include "../../CCallback.h"
+#include "../../lib/StartInfo.h"
+
+CAdventureOptions::CAdventureOptions()
+	: CWindowObject(PLAYER_COLORED, "ADVOPTS")
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_v);
+	viewWorld->addCallback(std::bind(&CPlayerInterface::viewWorldMap, LOCPLINT));
+
+	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), SDLK_RETURN);
+	exit->assignedKeys.insert(SDLK_ESCAPE);
+
+	scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_i);
+	scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
+
+	puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_p);
+	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
+
+	dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_d);
+	if(const CGHeroInstance *h = adventureInt->curHero())
+		dig->addCallback(std::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h));
+	else
+		dig->block(true);
+}
+
+void CAdventureOptions::showScenarioInfo()
+{
+	if(LOCPLINT->cb->getStartInfo()->campState)
+	{
+		GH.pushIntT<CCampaignInfoScreen>();
+	}
+	else
+	{
+		GH.pushIntT<CScenarioInfoScreen>();
+	}
+}
+

+ 30 - 0
client/adventureMap/CAdventureOptions.h

@@ -0,0 +1,30 @@
+/*
+ * CAdventureOptions.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 "../windows/CWindowObject.h"
+
+class CButton;
+
+/// Adventure options dialog where you can view the world, dig, play the replay of the last turn,...
+class CAdventureOptions : public CWindowObject
+{
+public:
+	std::shared_ptr<CButton> exit;
+	std::shared_ptr<CButton> viewWorld;
+	std::shared_ptr<CButton> puzzle;
+	std::shared_ptr<CButton> dig;
+	std::shared_ptr<CButton> scenInfo;
+	/*std::shared_ptr<CButton> replay*/
+
+	CAdventureOptions();
+	static void showScenarioInfo();
+};
+

+ 261 - 0
client/adventureMap/CInGameConsole.cpp

@@ -0,0 +1,261 @@
+/*
+ * CInGameConsole.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 "CInGameConsole.h"
+
+#include "../renderSDL/SDL_Extensions.h"
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../ClientCommandManager.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CArmedInstance.h"
+
+#include <SDL_timer.h>
+#include <SDL_events.h>
+
+CInGameConsole::CInGameConsole()
+	: CIntObject(KEYBOARD | TEXTINPUT),
+	prevEntDisp(-1),
+	defaultTimeout(10000),
+	maxDisplayedTexts(10)
+{
+}
+
+void CInGameConsole::show(SDL_Surface * to)
+{
+	int number = 0;
+
+	std::vector<std::list< std::pair< std::string, uint32_t > >::iterator> toDel;
+
+	boost::unique_lock<boost::mutex> lock(texts_mx);
+	for(auto it = texts.begin(); it != texts.end(); ++it, ++number)
+	{
+		Point leftBottomCorner(0, pos.h);
+
+		graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN,
+			Point(leftBottomCorner.x + 50, leftBottomCorner.y - (int)texts.size() * 20 - 80 + number*20));
+
+		if((int)(SDL_GetTicks() - it->second) > defaultTimeout)
+		{
+			toDel.push_back(it);
+		}
+	}
+
+	for(auto & elem : toDel)
+	{
+		texts.erase(elem);
+	}
+}
+
+void CInGameConsole::print(const std::string &txt)
+{
+	boost::unique_lock<boost::mutex> lock(texts_mx);
+	int lineLen = conf.go()->ac.outputLineLength;
+
+	if(txt.size() < lineLen)
+	{
+		texts.push_back(std::make_pair(txt, SDL_GetTicks()));
+		if(texts.size() > maxDisplayedTexts)
+		{
+			texts.pop_front();
+		}
+	}
+	else
+	{
+		assert(lineLen);
+		for(int g=0; g<txt.size() / lineLen + 1; ++g)
+		{
+			std::string part = txt.substr(g * lineLen, lineLen);
+			if(part.size() == 0)
+				break;
+
+			texts.push_back(std::make_pair(part, SDL_GetTicks()));
+			if(texts.size() > maxDisplayedTexts)
+			{
+				texts.pop_front();
+			}
+		}
+	}
+}
+
+void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)
+{
+	if(key.type != SDL_KEYDOWN) return;
+
+	if(!captureAllKeys && key.keysym.sym != SDLK_TAB) return; //because user is not entering any text
+
+	switch(key.keysym.sym)
+	{
+	case SDLK_TAB:
+	case SDLK_ESCAPE:
+		{
+			if(captureAllKeys)
+			{
+				endEnteringText(false);
+			}
+			else if(SDLK_TAB == key.keysym.sym)
+			{
+				startEnteringText();
+			}
+			break;
+		}
+	case SDLK_RETURN: //enter key
+		{
+			if(!enteredText.empty() && captureAllKeys)
+			{
+				bool anyTextExceptCaret = enteredText.size() > 1;
+				endEnteringText(anyTextExceptCaret);
+
+				if(anyTextExceptCaret)
+				{
+					CCS->soundh->playSound("CHAT");
+				}
+			}
+			break;
+		}
+	case SDLK_BACKSPACE:
+		{
+			if(enteredText.size() > 1)
+			{
+				Unicode::trimRight(enteredText,2);
+				enteredText += '_';
+				refreshEnteredText();
+			}
+			break;
+		}
+	case SDLK_UP: //up arrow
+		{
+			if(previouslyEntered.size() == 0)
+				break;
+
+			if(prevEntDisp == -1)
+			{
+				prevEntDisp = static_cast<int>(previouslyEntered.size() - 1);
+				enteredText = previouslyEntered[prevEntDisp] + "_";
+				refreshEnteredText();
+			}
+			else if( prevEntDisp > 0)
+			{
+				--prevEntDisp;
+				enteredText = previouslyEntered[prevEntDisp] + "_";
+				refreshEnteredText();
+			}
+			break;
+		}
+	case SDLK_DOWN: //down arrow
+		{
+			if(prevEntDisp != -1 && prevEntDisp+1 < previouslyEntered.size())
+			{
+				++prevEntDisp;
+				enteredText = previouslyEntered[prevEntDisp] + "_";
+				refreshEnteredText();
+			}
+			else if(prevEntDisp+1 == previouslyEntered.size()) //useful feature
+			{
+				prevEntDisp = -1;
+				enteredText = "_";
+				refreshEnteredText();
+			}
+			break;
+		}
+	default:
+		{
+			break;
+		}
+	}
+}
+
+void CInGameConsole::textInputed(const SDL_TextInputEvent & event)
+{
+	if(!captureAllKeys || enteredText.size() == 0)
+		return;
+	enteredText.resize(enteredText.size()-1);
+
+	enteredText += event.text;
+	enteredText += "_";
+
+	refreshEnteredText();
+}
+
+void CInGameConsole::textEdited(const SDL_TextEditingEvent & event)
+{
+ //do nothing here
+}
+
+void CInGameConsole::startEnteringText()
+{
+	if (!active)
+		return;
+
+	if (captureAllKeys)
+		return;
+
+	assert(GH.statusbar);
+	assert(currentStatusBar.expired());//effectively, nullptr check
+
+	currentStatusBar = GH.statusbar;
+
+	captureAllKeys = true;
+	enteredText = "_";
+
+	GH.statusbar->setEnteringMode(true);
+	GH.statusbar->setEnteredText(enteredText);
+}
+
+void CInGameConsole::endEnteringText(bool processEnteredText)
+{
+	captureAllKeys = false;
+	prevEntDisp = -1;
+	if(processEnteredText)
+	{
+		std::string txt = enteredText.substr(0, enteredText.size()-1);
+		previouslyEntered.push_back(txt);
+
+		if(txt.at(0) == '/')
+		{
+			//some commands like gosolo don't work when executed from GUI thread
+			auto threadFunction = [=]()
+			{
+				ClientCommandManager commandController;
+				commandController.processCommand(txt.substr(1), true);
+			};
+
+			boost::thread clientCommandThread(threadFunction);
+			clientCommandThread.detach();
+		}
+		else
+			LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
+	}
+	enteredText.clear();
+
+	auto statusbar = currentStatusBar.lock();
+	assert(statusbar);
+
+	if (statusbar)
+		statusbar->setEnteringMode(false);
+
+	currentStatusBar.reset();
+}
+
+void CInGameConsole::refreshEnteredText()
+{
+	auto statusbar = currentStatusBar.lock();
+	assert(statusbar);
+
+	if (statusbar)
+		statusbar->setEnteredText(enteredText);
+}
+

+ 39 - 0
client/adventureMap/CInGameConsole.h

@@ -0,0 +1,39 @@
+/*
+ * CInGameConsole.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"
+
+class CInGameConsole : public CIntObject
+{
+private:
+	std::list< std::pair< std::string, uint32_t > > texts; //list<text to show, time of add>
+	boost::mutex texts_mx;		// protects texts
+	std::vector< std::string > previouslyEntered; //previously entered texts, for up/down arrows to work
+	int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1
+	int defaultTimeout; //timeout for new texts (in ms)
+	int maxDisplayedTexts; //hiw many texts can be displayed simultaneously
+
+	std::weak_ptr<IStatusBar> currentStatusBar;
+public:
+	std::string enteredText;
+	void show(SDL_Surface * to) override;
+	void print(const std::string &txt);
+	void keyPressed (const SDL_KeyboardEvent & key) override; //call-in
+
+	void textInputed(const SDL_TextInputEvent & event) override;
+	void textEdited(const SDL_TextEditingEvent & event) override;
+
+	void startEnteringText();
+	void endEnteringText(bool processEnteredText);
+	void refreshEnteredText();
+
+	CInGameConsole();
+};

+ 323 - 0
client/adventureMap/CInfoBar.cpp

@@ -0,0 +1,323 @@
+/*
+ * CInfoBar.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 "CInfoBar.h"
+
+#include "CAdvMapInt.h"
+
+#include "../widgets/CComponent.h"
+#include "../widgets/Images.h"
+#include "../widgets/TextControls.h"
+#include "../widgets/MiscWidgets.h"
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+
+CInfoBar::CVisibleInfo::CVisibleInfo()
+	: CIntObject(0, Point(8, 12))
+{
+}
+
+void CInfoBar::CVisibleInfo::show(SDL_Surface * to)
+{
+	CIntObject::show(to);
+	for(auto object : forceRefresh)
+		object->showAll(to);
+}
+
+CInfoBar::EmptyVisibleInfo::EmptyVisibleInfo()
+{
+}
+
+CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	background = std::make_shared<CPicture>("ADSTATHR");
+	heroTooltip = std::make_shared<CHeroTooltip>(Point(0,0), hero);
+}
+
+CInfoBar::VisibleTownInfo::VisibleTownInfo(const CGTownInstance * town)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	background = std::make_shared<CPicture>("ADSTATCS");
+	townTooltip = std::make_shared<CTownTooltip>(Point(0,0), town);
+}
+
+CInfoBar::VisibleDateInfo::VisibleDateInfo()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	animation = std::make_shared<CShowableAnim>(1, 0, getNewDayName(), CShowableAnim::PLAY_ONCE, 180);// H3 uses around 175-180 ms per frame
+
+	std::string labelText;
+	if(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK) == 1 && LOCPLINT->cb->getDate(Date::DAY) != 1) // monday of any week but first - show new week info
+		labelText = CGI->generaltexth->allTexts[63] + " " + boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::WEEK));
+	else
+		labelText = CGI->generaltexth->allTexts[64] + " " + boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK));
+
+	label = std::make_shared<CLabel>(95, 31, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, labelText);
+
+	forceRefresh.push_back(label);
+}
+
+std::string CInfoBar::VisibleDateInfo::getNewDayName()
+{
+	if(LOCPLINT->cb->getDate(Date::DAY) == 1)
+		return "NEWDAY";
+
+	if(LOCPLINT->cb->getDate(Date::DAY) != 1)
+		return "NEWDAY";
+
+	switch(LOCPLINT->cb->getDate(Date::WEEK))
+	{
+	case 1:
+		return "NEWWEEK1";
+	case 2:
+		return "NEWWEEK2";
+	case 3:
+		return "NEWWEEK3";
+	case 4:
+		return "NEWWEEK4";
+	default:
+		return "";
+	}
+}
+
+CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	background = std::make_shared<CPicture>("ADSTATNX");
+	banner = std::make_shared<CAnimImage>("CREST58", player.getNum(), 0, 20, 51);
+	sand = std::make_shared<CShowableAnim>(99, 51, "HOURSAND", 0, 100); // H3 uses around 100 ms per frame
+	glass = std::make_shared<CShowableAnim>(99, 51, "HOURGLAS", CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
+}
+
+CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	//get amount of halls of each level
+	std::vector<int> halls(4, 0);
+	for(auto town : LOCPLINT->towns)
+	{
+		int hallLevel = town->hallLevel();
+		//negative value means no village hall, unlikely but possible
+		if(hallLevel >= 0)
+			halls.at(hallLevel)++;
+	}
+
+	std::vector<PlayerColor> allies, enemies;
+
+	//generate list of allies and enemies
+	for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+	{
+		if(LOCPLINT->cb->getPlayerStatus(PlayerColor(i), false) == EPlayerStatus::INGAME)
+		{
+			if(LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, PlayerColor(i)) != PlayerRelations::ENEMIES)
+				allies.push_back(PlayerColor(i));
+			else
+				enemies.push_back(PlayerColor(i));
+		}
+	}
+
+	//generate widgets
+	background = std::make_shared<CPicture>("ADSTATIN");
+	allyLabel = std::make_shared<CLabel>(10, 106, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
+	enemyLabel = std::make_shared<CLabel>(10, 136, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
+
+	int posx = allyLabel->pos.w + allyLabel->pos.x - pos.x + 4;
+	for(PlayerColor & player : allies)
+	{
+		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 102);
+		posx += image->pos.w;
+		flags.push_back(image);
+	}
+
+	posx = enemyLabel->pos.w + enemyLabel->pos.x - pos.x + 4;
+	for(PlayerColor & player : enemies)
+	{
+		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 132);
+		posx += image->pos.w;
+		flags.push_back(image);
+	}
+
+	for(size_t i=0; i<halls.size(); i++)
+	{
+		hallIcons.push_back(std::make_shared<CAnimImage>("itmtl", i, 0, 6 + 42 * (int)i , 11));
+		if(halls[i])
+			hallLabels.push_back(std::make_shared<CLabel>( 26 + 42 * (int)i, 64, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast<std::string>(halls[i])));
+	}
+}
+
+CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const Component & compToDisplay, std::string message)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	background = std::make_shared<CPicture>("ADSTATOT", 1, 0);
+
+	comp = std::make_shared<CComponent>(compToDisplay);
+	comp->moveTo(Point(pos.x+47, pos.y+50));
+
+	text = std::make_shared<CTextBox>(message, Rect(10, 4, 160, 50), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+}
+
+void CInfoBar::playNewDaySound()
+{
+	if(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK) != 1) // not first day of the week
+		CCS->soundh->playSound(soundBase::newDay);
+	else if(LOCPLINT->cb->getDate(Date::WEEK) != 1) // not first week in month
+		CCS->soundh->playSound(soundBase::newWeek);
+	else if(LOCPLINT->cb->getDate(Date::MONTH) != 1) // not first month
+		CCS->soundh->playSound(soundBase::newMonth);
+	else
+		CCS->soundh->playSound(soundBase::newDay);
+}
+
+void CInfoBar::reset()
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	state = EMPTY;
+	visibleInfo = std::make_shared<EmptyVisibleInfo>();
+}
+
+void CInfoBar::showSelection()
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	if(adventureInt->selection)
+	{
+		if(auto hero = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
+		{
+			showHeroSelection(hero);
+			return;
+		}
+		else if(auto town = dynamic_cast<const CGTownInstance *>(adventureInt->selection))
+		{
+			showTownSelection(town);
+			return;
+		}
+	}
+	showGameStatus();//FIXME: may be incorrect but shouldn't happen in general
+}
+
+void CInfoBar::tick()
+{
+	removeUsedEvents(TIME);
+	if(GH.topInt() == adventureInt)
+		showSelection();
+}
+
+void CInfoBar::clickLeft(tribool down, bool previousState)
+{
+	if(down)
+	{
+		if(state == HERO || state == TOWN)
+			showGameStatus();
+		else if(state == GAME)
+			showDate();
+		else
+			showSelection();
+	}
+}
+
+void CInfoBar::clickRight(tribool down, bool previousState)
+{
+	adventureInt->handleRightClick(CGI->generaltexth->allTexts[109], down);
+}
+
+void CInfoBar::hover(bool on)
+{
+	if(on)
+		GH.statusbar->write(CGI->generaltexth->zelp[292].first);
+	else
+		GH.statusbar->clear();
+}
+
+CInfoBar::CInfoBar(const Rect & position)
+	: CIntObject(LCLICK | RCLICK | HOVER, position.topLeft()),
+	state(EMPTY)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	pos.w = position.w;
+	pos.h = position.h;
+	reset();
+}
+
+void CInfoBar::showDate()
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	playNewDaySound();
+	state = DATE;
+	visibleInfo = std::make_shared<VisibleDateInfo>();
+	setTimer(3000); // confirmed to match H3
+	redraw();
+}
+
+void CInfoBar::showComponent(const Component & comp, std::string message)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	state = COMPONENT;
+	visibleInfo = std::make_shared<VisibleComponentInfo>(comp, message);
+	setTimer(3000);
+	redraw();
+}
+
+void CInfoBar::startEnemyTurn(PlayerColor color)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	state = AITURN;
+	visibleInfo = std::make_shared<VisibleEnemyTurnInfo>(color);
+	redraw();
+}
+
+void CInfoBar::showHeroSelection(const CGHeroInstance * hero)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	if(!hero)
+	{
+		reset();
+	}
+	else
+	{
+		state = HERO;
+		visibleInfo = std::make_shared<VisibleHeroInfo>(hero);
+	}
+	redraw();
+}
+
+void CInfoBar::showTownSelection(const CGTownInstance * town)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	if(!town)
+	{
+		reset();
+	}
+	else
+	{
+		state = TOWN;
+		visibleInfo = std::make_shared<VisibleTownInfo>(town);
+	}
+	redraw();
+}
+
+void CInfoBar::showGameStatus()
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	state = GAME;
+	visibleInfo = std::make_shared<VisibleGameStatusInfo>();
+	setTimer(3000);
+	redraw();
+}
+

+ 146 - 0
client/adventureMap/CInfoBar.h

@@ -0,0 +1,146 @@
+/*
+ * CInfoBar.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 CGHeroInstance;
+class CGTownInstance;
+struct Component;
+class PlayerColor;
+
+VCMI_LIB_NAMESPACE_END
+
+class CAnimImage;
+class CShowableAnim;
+class CComponent;
+class CHeroTooltip;
+class CTownTooltip;
+class CLabel;
+class CTextBox;
+
+/// Info box which shows next week/day information, hold the current date
+class CInfoBar : public CIntObject
+{
+	//all visible information located in one object - for ease of replacing
+	class CVisibleInfo : public CIntObject
+	{
+	public:
+		void show(SDL_Surface * to) override;
+
+	protected:
+		std::shared_ptr<CPicture> background;
+		std::list<std::shared_ptr<CIntObject>> forceRefresh;
+
+		CVisibleInfo();
+	};
+
+	class EmptyVisibleInfo : public CVisibleInfo
+	{
+	public:
+		EmptyVisibleInfo();
+	};
+
+	class VisibleHeroInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CHeroTooltip> heroTooltip;
+	public:
+		VisibleHeroInfo(const CGHeroInstance * hero);
+	};
+
+	class VisibleTownInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CTownTooltip> townTooltip;
+	public:
+		VisibleTownInfo(const CGTownInstance * town);
+	};
+
+	class VisibleDateInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CShowableAnim> animation;
+		std::shared_ptr<CLabel> label;
+
+		std::string getNewDayName();
+	public:
+		VisibleDateInfo();
+	};
+
+	class VisibleEnemyTurnInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CAnimImage> banner;
+		std::shared_ptr<CShowableAnim> glass;
+		std::shared_ptr<CShowableAnim> sand;
+	public:
+		VisibleEnemyTurnInfo(PlayerColor player);
+	};
+
+	class VisibleGameStatusInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CLabel> allyLabel;
+		std::shared_ptr<CLabel> enemyLabel;
+
+		std::vector<std::shared_ptr<CAnimImage>> flags;
+		std::vector<std::shared_ptr<CAnimImage>> hallIcons;
+		std::vector<std::shared_ptr<CLabel>> hallLabels;
+	public:
+		VisibleGameStatusInfo();
+	};
+
+	class VisibleComponentInfo : public CVisibleInfo
+	{
+		std::shared_ptr<CComponent> comp;
+		std::shared_ptr<CTextBox> text;
+	public:
+		VisibleComponentInfo(const Component & compToDisplay, std::string message);
+	};
+
+	enum EState
+	{
+		EMPTY, HERO, TOWN, DATE, GAME, AITURN, COMPONENT
+	};
+
+	std::shared_ptr<CVisibleInfo> visibleInfo;
+	EState state;
+
+	//removes all information about current state, deactivates timer (if any)
+	void reset();
+
+	void tick() override;
+
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+	void hover(bool on) override;
+
+	void playNewDaySound();
+public:
+	CInfoBar(const Rect & pos);
+
+	/// show new day/week animation
+	void showDate();
+
+	/// show component for 3 seconds. Used to display picked up resources
+	void showComponent(const Component & comp, std::string message);
+
+	/// print enemy turn progress
+	void startEnemyTurn(PlayerColor color);
+
+	/// reset to default view - selected object
+	void showSelection();
+
+	/// show hero\town information
+	void showHeroSelection(const CGHeroInstance * hero);
+	void showTownSelection(const CGTownInstance * town);
+
+	/// for 3 seconds shows amount of town halls and players status
+	void showGameStatus();
+};
+

+ 339 - 0
client/adventureMap/CList.cpp

@@ -0,0 +1,339 @@
+/*
+ * CList.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 "CList.h"
+
+#include "CAdvMapInt.h"
+
+#include "../widgets/Images.h"
+#include "../widgets/Buttons.h"
+#include "../windows/InfoWindows.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/CModHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+
+CList::CListItem::CListItem(CList * Parent)
+	: CIntObject(LCLICK | RCLICK | HOVER),
+	parent(Parent),
+	selection()
+{
+	defActions = 255-DISPOSE;
+}
+
+CList::CListItem::~CListItem()
+{
+}
+
+void CList::CListItem::clickRight(tribool down, bool previousState)
+{
+	if (down == true)
+		showTooltip();
+}
+
+void CList::CListItem::clickLeft(tribool down, bool previousState)
+{
+	if(down == true)
+	{
+		//second click on already selected item
+		if(parent->selected == this->shared_from_this())
+		{
+			open();
+		}
+		else
+		{
+			//first click - switch selection
+			parent->select(this->shared_from_this());
+		}
+	}
+}
+
+void CList::CListItem::hover(bool on)
+{
+	if (on)
+		GH.statusbar->write(getHoverText());
+	else
+		GH.statusbar->clear();
+}
+
+void CList::CListItem::onSelect(bool on)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	selection.reset();
+	if(on)
+		selection = genSelection();
+	select(on);
+	GH.totalRedraw();
+}
+
+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),
+	size(Size),
+	selected(nullptr)
+{
+	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>(create, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount);
+
+	//assign callback only after list was created
+	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::update()
+{
+	bool onTop = listBox->getPos() == 0;
+	bool onBottom = listBox->getPos() + size >= listBox->size();
+
+	scrollUp->block(onTop);
+	scrollDown->block(onBottom);
+}
+
+void CList::select(std::shared_ptr<CListItem> which)
+{
+	if(selected == which)
+		return;
+
+	if(selected)
+		selected->onSelect(false);
+
+	selected = which;
+	if(which)
+	{
+		which->onSelect(true);
+		onSelect();
+	}
+}
+
+int CList::getSelectedIndex()
+{
+	return static_cast<int>(listBox->getIndexOf(selected));
+}
+
+void CList::selectIndex(int which)
+{
+	if(which < 0)
+	{
+		if(selected)
+			select(nullptr);
+	}
+	else
+	{
+		listBox->scrollTo(which);
+		update();
+		select(std::dynamic_pointer_cast<CListItem>(listBox->getItem(which)));
+	}
+}
+
+void CList::selectNext()
+{
+	int index = getSelectedIndex() + 1;
+	if(index >= listBox->size())
+		index = 0;
+	selectIndex(index);
+}
+
+void CList::selectPrev()
+{
+	int index = getSelectedIndex();
+	if(index <= 0)
+		selectIndex(0);
+	else
+		selectIndex(index-1);
+}
+
+CHeroList::CEmptyHeroItem::CEmptyHeroItem()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
+	portrait = std::make_shared<CPicture>("HPSXXX", movement->pos.w + 1, 0);
+	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
+
+	pos.w = mana->pos.w + mana->pos.x - pos.x;
+	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
+}
+
+CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
+	: CListItem(parent),
+	hero(Hero)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
+	portrait = std::make_shared<CAnimImage>("PortraitsSmall", hero->portrait, 0, movement->pos.w + 1);
+	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
+
+	pos.w = mana->pos.w + mana->pos.x - pos.x;
+	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
+
+	update();
+}
+
+void CHeroList::CHeroItem::update()
+{
+	movement->setFrame(std::min<size_t>(movement->size()-1, hero->movement / 100));
+	mana->setFrame(std::min<size_t>(mana->size()-1, hero->mana / 5));
+	redraw();
+}
+
+std::shared_ptr<CIntObject> CHeroList::CHeroItem::genSelection()
+{
+	return std::make_shared<CPicture>("HPSYYY", movement->pos.w + 1, 0);
+}
+
+void CHeroList::CHeroItem::select(bool on)
+{
+	if(on && adventureInt->selection != hero)
+		adventureInt->select(hero);
+}
+
+void CHeroList::CHeroItem::open()
+{
+	LOCPLINT->openHeroWindow(hero);
+}
+
+void CHeroList::CHeroItem::showTooltip()
+{
+	CRClickPopup::createAndPush(hero, GH.getCursorPosition());
+}
+
+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)
+{
+	if (LOCPLINT->wanderingHeroes.size() > index)
+		return std::make_shared<CHeroItem>(this, LOCPLINT->wanderingHeroes[index]);
+	return std::make_shared<CEmptyHeroItem>();
+}
+
+CHeroList::CHeroList(int size, Point position, std::string btnUp, std::string btnDown):
+	CList(size, position, btnUp, btnDown, LOCPLINT->wanderingHeroes.size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
+{
+}
+
+void CHeroList::select(const CGHeroInstance * hero)
+{
+	selectIndex(vstd::find_pos(LOCPLINT->wanderingHeroes, hero));
+}
+
+void CHeroList::update(const CGHeroInstance * hero)
+{
+	//this hero is already present, update its status
+	for(auto & elem : listBox->getItems())
+	{
+		auto item = std::dynamic_pointer_cast<CHeroItem>(elem);
+		if(item && item->hero == hero && vstd::contains(LOCPLINT->wanderingHeroes, hero))
+		{
+			item->update();
+			return;
+		}
+	}
+	//simplest solution for now: reset list and restore selection
+
+	listBox->resize(LOCPLINT->wanderingHeroes.size());
+	if (adventureInt->selection)
+	{
+		auto selectedHero = dynamic_cast<const CGHeroInstance *>(adventureInt->selection);
+		if (selectedHero)
+			select(selectedHero);
+	}
+	CList::update();
+}
+
+std::shared_ptr<CIntObject> CTownList::createTownItem(size_t index)
+{
+	if (LOCPLINT->towns.size() > index)
+		return std::make_shared<CTownItem>(this, LOCPLINT->towns[index]);
+	return std::make_shared<CAnimImage>("ITPA", 0);
+}
+
+CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
+	CListItem(parent),
+	town(Town)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	picture = std::make_shared<CAnimImage>("ITPA", 0);
+	pos = picture->pos;
+	update();
+}
+
+std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
+{
+	return std::make_shared<CAnimImage>("ITPA", 1);
+}
+
+void CTownList::CTownItem::update()
+{
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->modh->settings.MAX_BUILDING_PER_TURN];
+
+	picture->setFrame(iconIndex + 2);
+	redraw();
+}
+
+void CTownList::CTownItem::select(bool on)
+{
+	if (on && adventureInt->selection != town)
+			adventureInt->select(town);
+}
+
+void CTownList::CTownItem::open()
+{
+	LOCPLINT->openTownWindow(town);
+}
+
+void CTownList::CTownItem::showTooltip()
+{
+	CRClickPopup::createAndPush(town, GH.getCursorPosition());
+}
+
+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->towns.size(),  306, 307, std::bind(&CTownList::createTownItem, this, _1))
+{
+}
+
+void CTownList::select(const CGTownInstance * town)
+{
+	selectIndex(vstd::find_pos(LOCPLINT->towns, town));
+}
+
+void CTownList::update(const CGTownInstance *)
+{
+	//simplest solution for now: reset list and restore selection
+
+	listBox->resize(LOCPLINT->towns.size());
+	if (adventureInt->selection)
+	{
+		auto town = dynamic_cast<const CGTownInstance *>(adventureInt->selection);
+		if (town)
+			select(town);
+	}
+	CList::update();
+}
+

+ 176 - 0
client/adventureMap/CList.h

@@ -0,0 +1,176 @@
+/*
+ * CList.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../gui/CIntObject.h"
+
+#include "../widgets/ObjectLists.h"
+#include "../../lib/FunctionList.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGHeroInstance;
+class CGTownInstance;
+
+VCMI_LIB_NAMESPACE_END
+
+class CButton;
+
+/// Base UI Element for hero\town lists
+class CList : public CIntObject
+{
+protected:
+	class CListItem : public CIntObject, public std::enable_shared_from_this<CListItem>
+	{
+		CList * parent;
+		std::shared_ptr<CIntObject> selection;
+	public:
+		CListItem(CList * parent);
+		~CListItem();
+
+		void clickRight(tribool down, bool previousState) override;
+		void clickLeft(tribool down, bool previousState) override;
+		void hover(bool on) override;
+		void onSelect(bool on);
+
+		/// create object with selection rectangle
+		virtual std::shared_ptr<CIntObject> genSelection()=0;
+		/// reaction on item selection (e.g. enable selection border)
+		/// NOTE: item may be deleted in selected state
+		virtual void select(bool on)=0;
+		/// open item (town or hero screen)
+		virtual void open()=0;
+		/// show right-click tooltip
+		virtual void showTooltip()=0;
+		/// get hover text for status bar
+		virtual std::string getHoverText()=0;
+	};
+
+	std::shared_ptr<CListBox> listBox;
+	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);
+	friend class CListItem;
+
+	std::shared_ptr<CButton> scrollUp;
+	std::shared_ptr<CButton> scrollDown;
+
+	/// should be called when list is invalidated
+	void update();
+
+public:
+	/// functions that will be called when selection changes
+	CFunctionList<void()> onSelect;
+
+	/// return index of currently selected element
+	int getSelectedIndex();
+
+	/// set of methods to switch selection
+	void selectIndex(int which);
+	void selectNext();
+	void selectPrev();
+};
+
+/// List of heroes which is shown at the right of the adventure map screen
+class CHeroList	: public CList
+{
+	/// Empty hero item used as placeholder for unused entries in list
+	class CEmptyHeroItem : public CIntObject
+	{
+		std::shared_ptr<CAnimImage> movement;
+		std::shared_ptr<CAnimImage> mana;
+		std::shared_ptr<CPicture> portrait;
+	public:
+		CEmptyHeroItem();
+	};
+
+	class CHeroItem : public CListItem
+	{
+		std::shared_ptr<CAnimImage> movement;
+		std::shared_ptr<CAnimImage> mana;
+		std::shared_ptr<CAnimImage> portrait;
+	public:
+		const CGHeroInstance * const hero;
+
+		CHeroItem(CHeroList * parent, const CGHeroInstance * hero);
+
+		std::shared_ptr<CIntObject> genSelection() override;
+		void update();
+		void select(bool on) override;
+		void open() override;
+		void showTooltip() override;
+		std::string getHoverText() override;
+	};
+
+	std::shared_ptr<CIntObject> createHeroItem(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);
+
+	/// Select specific hero and scroll if needed
+	void select(const CGHeroInstance * hero = nullptr);
+
+	/// Update hero. Will add or remove it from the list if needed
+	void update(const CGHeroInstance * hero = nullptr);
+};
+
+/// List of towns which is shown at the right of the adventure map screen or in the town screen
+class CTownList	: public CList
+{
+	class CTownItem : public CListItem
+	{
+		std::shared_ptr<CAnimImage> picture;
+	public:
+		const CGTownInstance * const town;
+
+		CTownItem(CTownList *parent, const CGTownInstance * town);
+
+		std::shared_ptr<CIntObject> genSelection() override;
+		void update();
+		void select(bool on) override;
+		void open() override;
+		void showTooltip() override;
+		std::string getHoverText() override;
+	};
+
+	std::shared_ptr<CIntObject> createTownItem(size_t index);
+public:
+	/**
+	 * @brief CTownList
+	 * @param size, position, btnUp, btnDown @see CList::CList
+	 */
+	CTownList(int size, Point position, std::string btnUp, std::string btnDown);
+
+	/// Select specific town and scroll if needed
+	void select(const CGTownInstance * town = nullptr);
+
+	/// Update town. Will add or remove it from the list if needed
+	void update(const CGTownInstance * town = nullptr);
+};
+

+ 317 - 0
client/adventureMap/CMinimap.cpp

@@ -0,0 +1,317 @@
+/*
+ * CMinimap.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 "CMinimap.h"
+
+#include "CAdvMapInt.h"
+
+#include "../widgets/Images.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../renderSDL/SDL_PixelAccess.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/TerrainHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/CMapDefines.h"
+
+#include <SDL_surface.h>
+
+const SDL_Color & CMinimapInstance::getTileColor(const int3 & pos)
+{
+	const TerrainTile * tile = LOCPLINT->cb->getTile(pos, false);
+
+	// if tile is not visible it will be black on minimap
+	if(!tile)
+		return Colors::BLACK;
+
+	// if object at tile is owned - it will be colored as its owner
+	for (const CGObjectInstance *obj : tile->blockingObjects)
+	{
+		//heroes will be blitted later
+		switch (obj->ID)
+		{
+			case Obj::HERO:
+			case Obj::PRISON:
+				continue;
+		}
+
+		PlayerColor player = obj->getOwner();
+		if(player == PlayerColor::NEUTRAL)
+			return *graphics->neutralColor;
+		else
+		if (player < PlayerColor::PLAYER_LIMIT)
+			return graphics->playerColors[player.getNum()];
+	}
+
+	// else - use terrain color (blocked version or normal)
+	const auto & colorPair = parent->colors.find(tile->terType->getId())->second;
+	if (tile->blocked && (!tile->visitable))
+		return colorPair.second;
+	else
+		return colorPair.first;
+}
+void CMinimapInstance::tileToPixels (const int3 &tile, int &x, int &y, int toX, int toY)
+{
+	int3 mapSizes = LOCPLINT->cb->getMapSize();
+
+	double stepX = double(pos.w) / mapSizes.x;
+	double stepY = double(pos.h) / mapSizes.y;
+
+	x = static_cast<int>(toX + stepX * tile.x);
+	y = static_cast<int>(toY + stepY * tile.y);
+}
+
+void CMinimapInstance::blitTileWithColor(const SDL_Color &color, const int3 &tile, SDL_Surface *to, int toX, int toY)
+{
+	//coordinates of rectangle on minimap representing this tile
+	// begin - first to blit, end - first NOT to blit
+	int xBegin, yBegin, xEnd, yEnd;
+	tileToPixels (tile, xBegin, yBegin, toX, toY);
+	tileToPixels (int3 (tile.x + 1, tile.y + 1, tile.z), xEnd, yEnd, toX, toY);
+
+	for (int y=yBegin; y<yEnd; y++)
+	{
+		uint8_t *ptr = (uint8_t*)to->pixels + y * to->pitch + xBegin * minimap->format->BytesPerPixel;
+
+		for (int x=xBegin; x<xEnd; x++)
+			ColorPutter<4, 1>::PutColor(ptr, color);
+	}
+}
+
+void CMinimapInstance::refreshTile(const int3 &tile)
+{
+	blitTileWithColor(getTileColor(int3(tile.x, tile.y, level)), tile, minimap, 0, 0);
+}
+
+void CMinimapInstance::drawScaled(int level)
+{
+	int3 mapSizes = LOCPLINT->cb->getMapSize();
+
+	//size of one map tile on our minimap
+	double stepX = double(pos.w) / mapSizes.x;
+	double stepY = double(pos.h) / mapSizes.y;
+
+	double currY = 0;
+	for (int y=0; y<mapSizes.y; y++, currY += stepY)
+	{
+		double currX = 0;
+		for (int x=0; x<mapSizes.x; x++, currX += stepX)
+		{
+			const SDL_Color &color = getTileColor(int3(x,y,level));
+
+			//coordinates of rectangle on minimap representing this tile
+			// begin - first to blit, end - first NOT to blit
+			int xBegin = static_cast<int>(currX);
+			int yBegin = static_cast<int>(currY);
+			int xEnd = static_cast<int>(currX + stepX);
+			int yEnd = static_cast<int>(currY + stepY);
+
+			for (int y=yBegin; y<yEnd; y++)
+			{
+				uint8_t *ptr = (uint8_t*)minimap->pixels + y * minimap->pitch + xBegin * minimap->format->BytesPerPixel;
+
+				for (int x=xBegin; x<xEnd; x++)
+					ColorPutter<4, 1>::PutColor(ptr, color);
+			}
+		}
+	}
+}
+
+CMinimapInstance::CMinimapInstance(CMinimap *Parent, int Level):
+	parent(Parent),
+	minimap(CSDL_Ext::createSurfaceWithBpp<4>(parent->pos.w, parent->pos.h)),
+	level(Level)
+{
+	pos.w = parent->pos.w;
+	pos.h = parent->pos.h;
+	drawScaled(level);
+}
+
+CMinimapInstance::~CMinimapInstance()
+{
+	SDL_FreeSurface(minimap);
+}
+
+void CMinimapInstance::showAll(SDL_Surface * to)
+{
+	blitAtLoc(minimap, 0, 0, to);
+
+	//draw heroes
+	std::vector <const CGHeroInstance *> heroes = LOCPLINT->cb->getHeroesInfo(false); //TODO: do we really need separate function for drawing heroes?
+	for(auto & hero : heroes)
+	{
+		int3 position = hero->visitablePos();
+		if(position.z == level)
+		{
+			const SDL_Color & color = graphics->playerColors[hero->getOwner().getNum()];
+			blitTileWithColor(color, position, to, pos.x, pos.y);
+		}
+	}
+}
+
+std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors()
+{
+	std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > ret;
+
+	for(const auto & terrain : CGI->terrainTypeHandler->objects)
+	{
+		SDL_Color normal = CSDL_Ext::toSDL(terrain->minimapUnblocked);
+		SDL_Color blocked = CSDL_Ext::toSDL(terrain->minimapBlocked);
+
+		ret[terrain->getId()] = std::make_pair(normal, blocked);
+	}
+	return ret;
+}
+
+CMinimap::CMinimap(const Rect & position)
+	: CIntObject(LCLICK | RCLICK | HOVER | MOVE, position.topLeft()),
+	level(0),
+	colors(loadColors())
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	pos.w = position.w;
+	pos.h = position.h;
+
+	aiShield = std::make_shared<CPicture>("AIShield");
+	aiShield->disable();
+}
+
+int3 CMinimap::translateMousePosition()
+{
+	// 0 = top-left corner, 1 = bottom-right corner
+	double dx = double(GH.getCursorPosition().x - pos.x) / pos.w;
+	double dy = double(GH.getCursorPosition().y - pos.y) / pos.h;
+
+	int3 mapSizes = LOCPLINT->cb->getMapSize();
+
+	int3 tile ((si32)(mapSizes.x * dx), (si32)(mapSizes.y * dy), level);
+	return tile;
+}
+
+void CMinimap::moveAdvMapSelection()
+{
+	int3 newLocation = translateMousePosition();
+	adventureInt->centerOn(newLocation);
+
+	if (!(adventureInt->active & GENERAL))
+		GH.totalRedraw(); //redraw this as well as inactive adventure map
+	else
+		redraw();//redraw only this
+}
+
+void CMinimap::clickLeft(tribool down, bool previousState)
+{
+	if(down)
+		moveAdvMapSelection();
+}
+
+void CMinimap::clickRight(tribool down, bool previousState)
+{
+	adventureInt->handleRightClick(CGI->generaltexth->zelp[291].second, down);
+}
+
+void CMinimap::hover(bool on)
+{
+	if(on)
+		GH.statusbar->write(CGI->generaltexth->zelp[291].first);
+	else
+		GH.statusbar->clear();
+}
+
+void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent)
+{
+	if(mouseState(EIntObjMouseBtnType::LEFT))
+		moveAdvMapSelection();
+}
+
+void CMinimap::showAll(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+	if(minimap)
+	{
+		int3 mapSizes = LOCPLINT->cb->getMapSize();
+		int3 tileCountOnScreen = adventureInt->terrain.tileCountOnScreen();
+
+		//draw radar
+		Rect oldClip;
+		Rect radar =
+		{
+			si16(adventureInt->position.x * pos.w / mapSizes.x + pos.x),
+			si16(adventureInt->position.y * pos.h / mapSizes.y + pos.y),
+			ui16(tileCountOnScreen.x * pos.w / mapSizes.x),
+			ui16(tileCountOnScreen.y * pos.h / mapSizes.y)
+		};
+
+		if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
+		{
+			// adjusts radar so that it doesn't go out of map in world view mode (since there's no frame)
+			radar.x = std::min<int>(std::max(pos.x, radar.x), pos.x + pos.w - radar.w);
+			radar.y = std::min<int>(std::max(pos.y, radar.y), pos.y + pos.h - radar.h);
+
+			if(radar.x < pos.x && radar.y < pos.y)
+				return; // whole map is visible at once, no point in redrawing border
+		}
+
+		CSDL_Ext::getClipRect(to, oldClip);
+		CSDL_Ext::setClipRect(to, pos);
+		CSDL_Ext::drawDashedBorder(to, radar, Colors::PURPLE);
+		CSDL_Ext::setClipRect(to, oldClip);
+	}
+}
+
+void CMinimap::update()
+{
+	if(aiShield->recActions & UPDATE) //AI turn is going on. There is no need to update minimap
+		return;
+
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+	minimap = std::make_shared<CMinimapInstance>(this, level);
+	redraw();
+}
+
+void CMinimap::setLevel(int newLevel)
+{
+	level = newLevel;
+	update();
+}
+
+void CMinimap::setAIRadar(bool on)
+{
+	if(on)
+	{
+		aiShield->enable();
+		minimap.reset();
+	}
+	else
+	{
+		aiShield->disable();
+		update();
+	}
+	// this my happen during AI turn when this interface is inactive
+	// force redraw in order to properly update interface
+	GH.totalRedraw();
+}
+
+void CMinimap::hideTile(const int3 &pos)
+{
+	if(minimap)
+		minimap->refreshTile(pos);
+}
+
+void CMinimap::showTile(const int3 &pos)
+{
+	if(minimap)
+		minimap->refreshTile(pos);
+}
+

+ 77 - 0
client/adventureMap/CMinimap.h

@@ -0,0 +1,77 @@
+/*
+ * CMinimap.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../gui/CIntObject.h"
+#include "../../lib/GameConstants.h"
+
+
+struct SDL_Color;
+class CMinimap;
+
+class CMinimapInstance : public CIntObject
+{
+	CMinimap * parent;
+	SDL_Surface * minimap;
+	int level;
+
+	//get color of selected tile on minimap
+	const SDL_Color & getTileColor(const int3 & pos);
+
+	void blitTileWithColor(const SDL_Color & color, const int3 & pos, SDL_Surface * to, int x, int y);
+
+	//draw minimap already scaled.
+	//result is not antialiased. Will result in "missing" pixels on huge maps (>144)
+	void drawScaled(int level);
+public:
+	CMinimapInstance(CMinimap * parent, int level);
+	~CMinimapInstance();
+
+	void showAll(SDL_Surface * to) override;
+	void tileToPixels (const int3 & tile, int & x, int & y, int toX = 0, int toY = 0);
+	void refreshTile(const int3 & pos);
+};
+
+/// Minimap which is displayed at the right upper corner of adventure map
+class CMinimap : public CIntObject
+{
+protected:
+	std::shared_ptr<CPicture> aiShield; //the graphic displayed during AI turn
+	std::shared_ptr<CMinimapInstance> minimap;
+	int level;
+
+	//to initialize colors
+	std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > loadColors();
+
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+	void hover (bool on) override;
+	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+
+	void moveAdvMapSelection();
+
+public:
+	// terrainID -> (normal color, blocked color)
+	const std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > colors;
+
+	CMinimap(const Rect & position);
+
+	//should be called to invalidate whole map - different player or level
+	int3 translateMousePosition();
+	void update();
+	void setLevel(int level);
+	void setAIRadar(bool on);
+
+	void showAll(SDL_Surface * to) override;
+
+	void hideTile(const int3 &pos); //puts FoW
+	void showTile(const int3 &pos); //removes FoW
+};
+

+ 105 - 0
client/adventureMap/CResDataBar.cpp

@@ -0,0 +1,105 @@
+/*
+ * CResDataBar.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 "CResDataBar.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../renderSDL/SDL_Extensions.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/Images.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+
+#define ADVOPT (conf.go()->ac)
+
+void CResDataBar::clickRight(tribool down, bool previousState)
+{
+}
+
+CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist)
+{
+	pos.x += x;
+	pos.y += y;
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	background = std::make_shared<CPicture>(defname, 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;
+	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
+	+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
+	addUsedEvents(RCLICK);
+}
+
+CResDataBar::CResDataBar()
+{
+	pos.x += ADVOPT.resdatabarX;
+	pos.y += ADVOPT.resdatabarY;
+
+	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;
+
+	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;
+	datetext =  CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63]
+				+ ": %s, " + CGI->generaltexth->allTexts[64] + ": %s";
+}
+
+CResDataBar::~CResDataBar() = default;
+
+void CResDataBar::draw(SDL_Surface * to)
+{
+	//TODO: all this should be labels, but they require proper text update on change
+	for (auto i=Res::WOOD; i<=Res::GOLD; vstd::advance(i, 1))
+	{
+		std::string text = boost::lexical_cast<std::string>(LOCPLINT->cb->getResourceAmount(i));
+
+		graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first,txtpos[i].second));
+	}
+	std::vector<std::string> temp;
+
+	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::MONTH)));
+	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::WEEK)));
+	temp.push_back(boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK)));
+
+	graphics->fonts[FONT_SMALL]->renderTextLeft(to, CSDL_Ext::processStr(datetext,temp), Colors::WHITE, Point(txtpos[7].first,txtpos[7].second));
+}
+
+void CResDataBar::show(SDL_Surface * to)
+{
+
+}
+
+void CResDataBar::showAll(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+	draw(to);
+}
+

+ 33 - 0
client/adventureMap/CResDataBar.h

@@ -0,0 +1,33 @@
+/*
+ * CResDataBar.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"
+
+/// Resources bar which shows information about how many gold, crystals,... you have
+/// Current date is displayed too
+class CResDataBar : public CIntObject
+{
+public:
+	std::shared_ptr<CPicture> background;
+
+	std::vector<std::pair<int,int> > txtpos;
+	std::string datetext;
+
+	void clickRight(tribool down, bool previousState) override;
+	CResDataBar();
+	CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
+	~CResDataBar();
+
+	void draw(SDL_Surface * to);
+	void show(SDL_Surface * to) override;
+	void showAll(SDL_Surface * to) override;
+};
+

+ 414 - 0
client/adventureMap/CTerrainRect.cpp

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

+ 64 - 0
client/adventureMap/CTerrainRect.h

@@ -0,0 +1,64 @@
+/*
+ * CTerrainRect.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../gui/CIntObject.h"
+#include "../../lib/int3.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct CGPath;
+VCMI_LIB_NAMESPACE_END
+
+enum class EMapAnimRedrawStatus;
+class CFadeAnimation;
+
+/// Holds information about which tiles of the terrain are shown/not shown at the screen
+class CTerrainRect : public CIntObject
+{
+	SDL_Surface * fadeSurface;
+	EMapAnimRedrawStatus lastRedrawStatus;
+	std::shared_ptr<CFadeAnimation> fadeAnim;
+
+	int3 swipeInitialMapPos;
+	int3 swipeInitialRealPos;
+	bool isSwiping;
+	static constexpr float SwipeTouchSlop = 16.0f;
+
+	void handleHover(const SDL_MouseMotionEvent & sEvent);
+	void handleSwipeMove(const SDL_MouseMotionEvent & sEvent);
+	/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
+	bool handleSwipeStateChange(bool btnPressed);
+public:
+	int tilesw, tilesh; //width and height of terrain to blit in tiles
+	int3 curHoveredTile;
+	int moveX, moveY; //shift between actual position of screen and the one we wil blit; ranges from -31 to 31 (in pixels)
+	CGPath * currentPath;
+
+	CTerrainRect();
+	virtual ~CTerrainRect();
+	void deactivate() override;
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+	void clickMiddle(tribool down, bool previousState) override;
+	void hover(bool on) override;
+	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
+	void show(SDL_Surface * to) override;
+	void showAll(SDL_Surface * to) override;
+	void showAnim(SDL_Surface * to);
+	void showPath(const Rect &extRect, SDL_Surface * to);
+	int3 whichTileIsIt(const int x, const int y); //x,y are cursor position
+	int3 whichTileIsIt(); //uses current cursor pos
+	/// @returns number of visible tiles on screen respecting current map scaling
+	int3 tileCountOnScreen();
+	/// animates view by caching current surface and crossfading it with normal screen
+	void fadeFromCurrentView();
+	bool needsAnimUpdate();
+};
+

+ 26 - 32
client/mapHandler.cpp → client/adventureMap/mapHandler.cpp

@@ -11,30 +11,24 @@
 #include "StdInc.h"
 #include "mapHandler.h"
 
-#include "CBitmapHandler.h"
-#include "gui/CAnimation.h"
-#include "gui/SDL_Extensions.h"
-#include "CGameInfo.h"
-#include "../lib/mapObjects/CGHeroInstance.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
-#include "../lib/CGameState.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/CTownHandler.h"
-#include "../lib/CModHandler.h"
-#include "Graphics.h"
-#include "../lib/mapping/CMap.h"
-#include "../lib/CConfigHandler.h"
-#include "../lib/CGeneralTextHandler.h"
-#include "../lib/GameConstants.h"
-#include "../lib/CStopWatch.h"
-#include "CMT.h"
-#include "CMusicHandler.h"
-#include "../lib/CRandomGenerator.h"
-#include "../lib/RoadHandler.h"
-#include "../lib/RiverHandler.h"
-#include "../lib/TerrainHandler.h"
-#include "../lib/filesystem/ResourceID.h"
-#include "../lib/JsonDetail.h"
+#include "../render/CAnimation.h"
+#include "../render/CFadeAnimation.h"
+#include "../renderSDL/SDL_Extensions.h"
+#include "../CGameInfo.h"
+#include "../render/Graphics.h"
+#include "../render/IImage.h"
+#include "../CMusicHandler.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CObjectClassesHandler.h"
+#include "../../lib/mapping/CMap.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CStopWatch.h"
+#include "../../lib/CRandomGenerator.h"
+#include "../../lib/RoadHandler.h"
+#include "../../lib/RiverHandler.h"
+#include "../../lib/TerrainHandler.h"
 
 #define ADVOPT (conf.go()->ac)
 
@@ -899,22 +893,22 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 				{
 					if(parent->map->getTile(int3(pos.x, pos.y, pos.z)).blocked) //temporary hiding blocked positions
 					{
-						static SDL_Surface * block = nullptr;
+						static std::shared_ptr<IImage> block;
 						if (!block)
-							block = BitmapHandler::loadBitmap("blocked");
+							block = IImage::createFromFile("blocked");
 
-						CSDL_Ext::blitSurface(block, targetSurf, realTileRect.topLeft());
+						block->draw(targetSurf, realTileRect.x, realTileRect.y);
 					}
 				}
 				if (settings["session"]["showVisit"].Bool())
 				{
 					if(parent->map->getTile(int3(pos.x, pos.y, pos.z)).visitable) //temporary hiding visitable positions
 					{
-						static SDL_Surface * visit = nullptr;
+						static std::shared_ptr<IImage> visit;
 						if (!visit)
-							visit = BitmapHandler::loadBitmap("visitable");
+							visit = IImage::createFromFile("visitable");
 
-						CSDL_Ext::blitSurface(visit, targetSurf, realTileRect.topLeft());
+						visit->draw(targetSurf, realTileRect.x, realTileRect.y);
 					}
 				}
 			}
@@ -1371,7 +1365,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM
 
 	if(t.hasFavorableWinds())
 	{
-		out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS);
+		out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0);
 		return;
 	}
 	const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y];
@@ -1434,7 +1428,7 @@ std::shared_ptr<IImage> CMapHandler::CMapCache::requestWorldViewCacheOrCreate(CM
 	auto iter = cache.find(key);
 	if(iter == cache.end())
 	{
-		auto scaled = fullSurface->scaleFast(worldViewCachedScale);
+		auto scaled = fullSurface->scaleFast(fullSurface->dimensions() * worldViewCachedScale);
 		cache[key] = scaled;
 		return scaled;
 	}

+ 3 - 3
client/mapHandler.h → client/adventureMap/mapHandler.h

@@ -10,9 +10,9 @@
 #pragma once
 
 
-#include "../lib/int3.h"
-#include "../lib/spells/ViewSpellInt.h"
-#include "../lib/Rect.h"
+#include "../../lib/int3.h"
+#include "../../lib/spells/ViewSpellInt.h"
+#include "../../lib/Rect.h"
 
 #ifdef IN
 #undef IN

+ 4 - 5
client/battle/BattleAnimationClasses.cpp

@@ -364,7 +364,7 @@ bool MovementAnimation::init()
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
 	Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack);
 
-	timeToMove = AnimationControls::getMovementDuration(stack->getCreature());
+	progressPerSecond = AnimationControls::getMovementDistance(stack->getCreature());
 
 	begX = begPosition.x;
 	begY = begPosition.y;
@@ -375,8 +375,7 @@ bool MovementAnimation::init()
 	if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
 	{
 		float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
-
-		timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
+		progressPerSecond =  AnimationControls::getFlightDistance(stack->getCreature()) / distance;
 	}
 
 	return true;
@@ -384,7 +383,7 @@ bool MovementAnimation::init()
 
 void MovementAnimation::nextFrame()
 {
-	progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove;
+	progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * progressPerSecond;
 
 	//moving instructions
 	myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress );
@@ -432,7 +431,7 @@ MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stac
 	  curentMoveIndex(0),
 	  begX(0), begY(0),
 	  distanceX(0), distanceY(0),
-	  timeToMove(0.0),
+	  progressPerSecond(0.0),
 	  progress(0.0)
 {
 	logAnim->debug("Created MovementAnimation for %s", stack->getName());

+ 5 - 2
client/battle/BattleAnimationClasses.h

@@ -147,8 +147,11 @@ private:
 	double begX, begY; // starting position
 	double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
 
-	double timeToMove; // full length of movement animation
-	double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
+	/// progress gain per second
+	double progressPerSecond;
+
+	/// range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
+	double progress;
 
 public:
 	bool init() override;

+ 1 - 1
client/battle/BattleConstants.h

@@ -79,7 +79,7 @@ enum class ECreatureAnimType
 
 	DEAD            = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
 	DEAD_RANGED     = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
-	RESURRECTION    = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copiend here
+	RESURRECTION    = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copied here
 	FROZEN          = 25, // new group, used when stack animation is paused (e.g. petrified). If empty - consist of first frame from HOLDING animation
 
 	CAST_UP            = 30,

+ 2 - 2
client/battle/BattleEffectsController.cpp

@@ -21,8 +21,8 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
+#include "../render/Canvas.h"
+#include "../render/CAnimation.h"
 
 #include "../../CCallback.h"
 #include "../../lib/battle/BattleAction.h"

+ 3 - 3
client/battle/BattleFieldController.cpp

@@ -22,11 +22,11 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../widgets/AdventureMapClasses.h"
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
+#include "../render/Canvas.h"
+#include "../render/IImage.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
+#include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
 #include "../../lib/BattleFieldHandler.h"

+ 6 - 10
client/battle/BattleInterface.cpp

@@ -24,13 +24,12 @@
 #include "BattleRenderer.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../gui/Canvas.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
-#include "../windows/CAdvmapInterface.h"
+#include "../render/Canvas.h"
+#include "../adventureMap/CAdvMapInt.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -207,11 +206,8 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI
 
 	std::array<int, 2> killedBySide = {0, 0};
 
-	int targets = 0;
 	for(const StackAttackedInfo & attackedInfo : attackedInfos)
 	{
-		++targets;
-
 		ui8 side = attackedInfo.defender->side;
 		killedBySide.at(side) += attackedInfo.amountKilled;
 	}
@@ -523,16 +519,16 @@ void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinatio
 
 void BattleInterface::setAnimSpeed(int set)
 {
-	Settings speed = settings.write["battle"]["animationSpeed"];
-	speed->Float() = float(set) / 100;
+	Settings speed = settings.write["battle"]["speedFactor"];
+	speed->Float() = float(set);
 }
 
 int BattleInterface::getAnimSpeed() const
 {
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
-		return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
+		return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float()));
 
-	return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
+	return static_cast<int>(vstd::round(settings["battle"]["speedFactor"].Float()));
 }
 
 CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const

+ 18 - 13
client/battle/BattleInterfaceClasses.cpp

@@ -19,21 +19,21 @@
 #include "BattleWindow.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CVideoHandler.h"
-#include "../Graphics.h"
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
-#include "../widgets/AdventureMapClasses.h"
+#include "../render/Canvas.h"
+#include "../render/IImage.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
+#include "../windows/CMessage.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/CSpellWindow.h"
+#include "../render/CAnimation.h"
+#include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -48,6 +48,9 @@
 #include "../../lib/CondSh.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
+#include <SDL_surface.h>
+#include <SDL_events.h>
+
 void BattleConsole::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
@@ -218,8 +221,10 @@ void BattleHero::render(Canvas & canvas)
 	canvas.draw(flagFrame, flagPosition);
 	canvas.draw(heroFrame, heroPosition);
 
-	flagCurrentFrame += currentSpeed;
-	currentFrame += currentSpeed;
+	float timePassed = float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000.f;
+
+	flagCurrentFrame += currentSpeed * timePassed;
+	currentFrame += currentSpeed * timePassed;
 
 	if(flagCurrentFrame >= flagAnimation->size(0))
 		flagCurrentFrame -= flagAnimation->size(0);
@@ -238,8 +243,8 @@ void BattleHero::pause()
 
 void BattleHero::play()
 {
-	//FIXME: un-hardcode speed
-	currentSpeed = 0.25f;
+	//H3 speed: 10 fps ( 100 ms per frame)
+	currentSpeed = 10.f;
 }
 
 float BattleHero::getFrame() const
@@ -438,13 +443,13 @@ BattleOptionsWindow::BattleOptionsWindow(BattleInterface & owner):
 
 	std::shared_ptr<CToggleButton> toggle;
 	toggle = std::make_shared<CToggleButton>(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]);
-	animSpeeds->addToggle(40, toggle);
+	animSpeeds->addToggle(1, toggle);
 
 	toggle = std::make_shared<CToggleButton>(Point( 92, 225), "sysob10.def", CGI->generaltexth->zelp[423]);
-	animSpeeds->addToggle(63, toggle);
+	animSpeeds->addToggle(2, toggle);
 
 	toggle = std::make_shared<CToggleButton>(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]);
-	animSpeeds->addToggle(100, toggle);
+	animSpeeds->addToggle(3, toggle);
 
 	animSpeeds->setSelected(owner.getAnimSpeed());
 
@@ -872,7 +877,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 		if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS)
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 
-		amount->setText(CSDL_Ext::makeNumberShort(unit->getCount()));
+		amount->setText(CSDL_Ext::makeNumberShort(unit->getCount(), 4));
 
 		if(stateIcon)
 		{

+ 1 - 2
client/battle/BattleObstacleController.cpp

@@ -20,9 +20,8 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
 #include "../gui/CGuiHandler.h"
+#include "../render/Canvas.h"
 
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"

+ 14 - 16
client/battle/BattleProjectileController.cpp

@@ -15,8 +15,7 @@
 #include "BattleStacksController.h"
 #include "CreatureAnimation.h"
 
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
+#include "../render/Canvas.h"
 #include "../gui/CGuiHandler.h"
 #include "../CGameInfo.h"
 
@@ -77,7 +76,11 @@ void ProjectileAnimatedMissile::show(Canvas & canvas)
 
 void ProjectileCatapult::show(Canvas & canvas)
 {
-	auto image = animation->getImage(frameNum, 0, true);
+	frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
+	int frameCounter = std::floor(frameProgress);
+	int frameIndex = (frameCounter + 1) % animation->size(0);
+
+	auto image = animation->getImage(frameIndex, 0, true);
 
 	if(image)
 	{
@@ -86,8 +89,6 @@ void ProjectileCatapult::show(Canvas & canvas)
 		Point pos(posX, posY);
 
 		canvas.draw(image, pos);
-
-		frameNum = (frameNum + 1) % animation->size(0);
 	}
 
 	float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
@@ -116,10 +117,7 @@ void ProjectileRay::show(Canvas & canvas)
 		for (size_t i = 0; i < rayConfig.size(); ++i)
 		{
 			auto ray = rayConfig[i];
-			SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
-			SDL_Color endColor  { ray.r2, ray.g2, ray.b2, ray.a2};
-
-			canvas.drawLine(Point(x1, y1 + i), Point(x2, y2+i), beginColor, endColor);
+			canvas.drawLine(Point(x1, y1 + i), Point(x2, y2+i), ray.start, ray.end);
 		}
 	}
 	else // draw in vertical axis
@@ -133,10 +131,8 @@ void ProjectileRay::show(Canvas & canvas)
 		for (size_t i = 0; i < rayConfig.size(); ++i)
 		{
 			auto ray = rayConfig[i];
-			SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
-			SDL_Color endColor  { ray.r2, ray.g2, ray.b2, ray.a2};
 
-			canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), beginColor, endColor);
+			canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), ray.start, ray.end);
 		}
 	}
 
@@ -294,13 +290,13 @@ void BattleProjectileController::createCatapultProjectile(const CStack * shooter
 	auto catapultProjectile       = new ProjectileCatapult();
 
 	catapultProjectile->animation = getProjectileImage(shooter);
-	catapultProjectile->frameNum  = 0;
 	catapultProjectile->progress  = 0;
 	catapultProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
 	catapultProjectile->from      = from;
 	catapultProjectile->dest      = dest;
 	catapultProjectile->shooterID = shooter->ID;
 	catapultProjectile->playing   = false;
+	catapultProjectile->frameProgress = 0.f;
 
 	projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
 }
@@ -321,6 +317,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 		projectile.reset(rayProjectile);
 
 		rayProjectile->rayConfig = shooterInfo.animation.projectileRay;
+		rayProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getRayProjectileSpeed());
 	}
 	else if (stackUsesMissileProjectile(shooter))
 	{
@@ -328,11 +325,12 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 		projectile.reset(missileProjectile);
 
 		missileProjectile->animation = getProjectileImage(shooter);
-		missileProjectile->reverse  = !owner.stacksController->facingRight(shooter);
-		missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
+		missileProjectile->reverse   = !owner.stacksController->facingRight(shooter);
+		missileProjectile->frameNum  = computeProjectileFrameID(from, dest, shooter);
+		missileProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
 	}
 
-	projectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
+
 	projectile->from      = from;
 	projectile->dest      = dest;
 	projectile->shooterID = shooter->ID;

+ 1 - 1
client/battle/BattleProjectileController.h

@@ -61,7 +61,7 @@ struct ProjectileCatapult : ProjectileBase
 	void show(Canvas & canvas) override;
 
 	std::shared_ptr<CAnimation> animation;
-	int frameNum;  // frame to display from projectile animation
+	float frameProgress;
 };
 
 /// Projectile for mages/evil eye - render ray expanding from origin position to destination

+ 2 - 2
client/battle/BattleSiegeController.cpp

@@ -20,8 +20,8 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CAnimation.h"
-#include "../gui/Canvas.h"
+#include "../render/Canvas.h"
+#include "../render/IImage.h"
 
 #include "../../CCallback.h"
 #include "../../lib/NetPacks.h"

+ 8 - 9
client/battle/BattleStacksController.cpp

@@ -25,13 +25,12 @@
 #include "../CPlayerInterface.h"
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/Canvas.h"
-#include "../gui/SDL_Extensions.h"
-#include "../../lib/spells/ISpellMechanics.h"
+#include "../renderSDL/SDL_Extensions.h"
+#include "../render/Canvas.h"
 
 #include "../../CCallback.h"
+#include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/CStack.h"
@@ -89,10 +88,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f );
 	static const auto shifterNeutral  = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f );
 
-	amountNormal->adjustPalette(shifterNormal);
-	amountPositive->adjustPalette(shifterPositive);
-	amountNegative->adjustPalette(shifterNegative);
-	amountEffNeutral->adjustPalette(shifterNeutral);
+	amountNormal->adjustPalette(shifterNormal, 0);
+	amountPositive->adjustPalette(shifterPositive, 0);
+	amountNegative->adjustPalette(shifterNegative, 0);
+	amountEffNeutral->adjustPalette(shifterNeutral, 0);
 
 	//Restore border color {255, 231, 132, 255} to its original state
 	amountNormal->resetPalette(26);
@@ -318,7 +317,7 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
 	//blitting amount
 	Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd);
 
-	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, CSDL_Ext::makeNumberShort(stack->getCount()));
+	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, CSDL_Ext::makeNumberShort(stack->getCount(), 4));
 }
 
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)

+ 1 - 1
client/battle/BattleStacksController.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../gui/ColorFilter.h"
+#include "../render/ColorFilter.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 7 - 4
client/battle/BattleWindow.cpp

@@ -17,17 +17,17 @@
 #include "BattleActionsController.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CPlayerInterface.h"
 #include "../CMusicHandler.h"
-#include "../gui/Canvas.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/CAnimation.h"
 #include "../windows/CSpellWindow.h"
-#include "../widgets/AdventureMapClasses.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
+#include "../windows/CMessage.h"
+#include "../render/CAnimation.h"
+#include "../render/Canvas.h"
+#include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -36,6 +36,9 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/filesystem/ResourceID.h"
 
+#include <SDL_surface.h>
+#include <SDL_events.h>
+
 BattleWindow::BattleWindow(BattleInterface & owner):
 	owner(owner)
 {

+ 49 - 25
client/battle/CreatureAnimation.cpp

@@ -13,9 +13,9 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CCreatureHandler.h"
 
-#include "../gui/Canvas.h"
-#include "../gui/ColorFilter.h"
-#include "../gui/SDL_Extensions.h"
+#include "../render/Canvas.h"
+#include "../render/ColorFilter.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
 static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
@@ -47,6 +47,15 @@ std::shared_ptr<CreatureAnimation> AnimationControls::getAnimation(const CCreatu
 	return std::make_shared<CreatureAnimation>(creature->animDefName, func);
 }
 
+float AnimationControls::getAnimationSpeedFactor()
+{
+	// according to testing, H3 ratios between slow/medium/fast might actually be 36/60/100 (x1.666)
+	// exact value is hard to tell due to large rounding errors
+	// however we will assume them to be 33/66/100 since these values are better for standard 60 fps displays:
+	// with these numbers, base frame display duration will be 100/66/33 ms - exactly 6/4/2 frames
+	return settings["battle"]["speedFactor"].Float();
+}
+
 float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType type)
 {
 	assert(creature->animation.walkAnimationTime != 0);
@@ -56,20 +65,23 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	// possible new fields for creature format:
 	//split "Attack time" into "Shoot Time" and "Cast Time"
 
-	// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
-	const float baseSpeed = 0.1f;
-	const float speedMult = static_cast<float>(settings["battle"]["animationSpeed"].Float());
-	const float speed = baseSpeed / speedMult;
+	// base speed for all H3 animations on slow speed is 10 frames per second (or 100ms per frame)
+	const float baseSpeed = 10.f;
+	const float speed = baseSpeed * getAnimationSpeedFactor();
 
 	switch (type)
 	{
 	case ECreatureAnimType::MOVING:
-		return static_cast<float>(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type));
+		return speed / creature->animation.walkAnimationTime;
 
 	case ECreatureAnimType::MOUSEON:
 		return baseSpeed;
+
 	case ECreatureAnimType::HOLDING:
-		return static_cast<float>(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type));
+			if ( creature->animation.idleAnimationTime > 0.01)
+				return speed / creature->animation.idleAnimationTime;
+			else
+				return 0.f; // this animation is disabled for current creature
 
 	case ECreatureAnimType::SHOOT_UP:
 	case ECreatureAnimType::SHOOT_FRONT:
@@ -80,7 +92,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case ECreatureAnimType::CAST_DOWN:
 	case ECreatureAnimType::CAST_FRONT:
 	case ECreatureAnimType::CAST_UP:
-		return static_cast<float>(speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type));
+		return speed / creature->animation.attackAnimationTime;
 
 	// as strange as it looks like "attackAnimationTime" does not affects melee attacks
 	// necessary because length of these animations must be same for all creatures for synchronization
@@ -95,15 +107,15 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case ECreatureAnimType::GROUP_ATTACK_DOWN:
 	case ECreatureAnimType::GROUP_ATTACK_FRONT:
 	case ECreatureAnimType::GROUP_ATTACK_UP:
-		return speed * 3 / anim->framesInGroup(type);
+		return speed;
 
 	case ECreatureAnimType::TURN_L:
 	case ECreatureAnimType::TURN_R:
-		return speed / 3;
+		return speed;
 
 	case ECreatureAnimType::MOVE_START:
 	case ECreatureAnimType::MOVE_END:
-		return speed / 3;
+		return speed;
 
 	case ECreatureAnimType::DEAD:
 	case ECreatureAnimType::DEAD_RANGED:
@@ -116,37 +128,51 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 
 float AnimationControls::getProjectileSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4000);
+	// H3 speed: 1250/2500/3750 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 1250);
+}
+
+float AnimationControls::getRayProjectileSpeed()
+{
+	// H3 speed: 4000/8000/12000 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 4000);
 }
 
 float AnimationControls::getCatapultSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 1000);
+	// H3 speed: 200/400/600 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 200);
 }
 
 float AnimationControls::getSpellEffectSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
+	// H3 speed: 10/20/30 frames per second
+	return static_cast<float>(getAnimationSpeedFactor() * 10);
 }
 
-float AnimationControls::getMovementDuration(const CCreature * creature)
+float AnimationControls::getMovementDistance(const CCreature * creature)
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime);
+	// H3 speed: 2/4/6 tiles per second
+	return static_cast<float>( 2.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime);
 }
 
 float AnimationControls::getFlightDistance(const CCreature * creature)
 {
-	return static_cast<float>(creature->animation.flightAnimationDistance * 200);
+	// Note: for whatever reason, H3 uses "Walk Animation Time" here, even though "Flight Animation Distance" also exists
+	// H3 speed: 250/500/750 pixels per second
+	return static_cast<float>( 250.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime);
 }
 
 float AnimationControls::getFadeInDuration()
 {
-	return 1.0f / settings["battle"]["animationSpeed"].Float();
+	// H3 speed: 500/250/166 ms
+	return 0.5f / getAnimationSpeedFactor();
 }
 
 float AnimationControls::getObstaclesSpeed()
 {
-	return 10.0;// does not seems to be affected by animaiton speed settings
+	// H3 speed: 20 frames per second, irregardless of speed setting.
+	return 20.f;
 }
 
 ECreatureAnimType CreatureAnimation::getType() const
@@ -345,7 +371,7 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
 		genSpecialPalette(SpecialPalette);
 
 		image->setSpecialPallete(SpecialPalette);
-		image->adjustPalette(shifter);
+		image->adjustPalette(shifter, 8);
 
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
 
@@ -407,7 +433,5 @@ void CreatureAnimation::pause()
 void CreatureAnimation::play()
 {
 	//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
-    speed = 0;
-	if(speedController(this, type) != 0)
-		speed = 1 / speedController(this, type);
+	speed = speedController(this, type);
 }

+ 16 - 6
client/battle/CreatureAnimation.h

@@ -11,7 +11,10 @@
 
 #include "../../lib/FunctionList.h"
 #include "../widgets/Images.h"
-#include "../gui/CAnimation.h"
+#include "../render/CAnimation.h"
+#include "../render/IImage.h"
+
+#include <SDL_pixels.h>
 
 class CIntObject;
 class CreatureAnimation;
@@ -25,25 +28,32 @@ namespace AnimationControls
 	SDL_Color getGoldBorder();
 	SDL_Color getNoBorder();
 
+	/// returns animation speed factor according to game settings,
+	/// slow speed is considered to be "base speed" and will return 1.0
+	float getAnimationSpeedFactor();
+
 	/// creates animation object with preset speed control
 	std::shared_ptr<CreatureAnimation> getAnimation(const CCreature * creature);
 
 	/// returns animation speed of specific group, taking in mind game setting (in frames per second)
 	float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType groupID);
 
-	/// returns how far projectile should move per second
+	/// returns how far projectile should move per second, in pixels per second
 	float getProjectileSpeed();
 
-	/// returns speed of catapult projectile, in pixels per second (horizontal axis only)
+	/// returns how far projectile should move per second, in pixels per second
+	float getRayProjectileSpeed();
+
+	/// returns speed of catapult projectile, in pixels per second, on a straight line, without parabola correction
 	float getCatapultSpeed();
 
 	/// returns speed of any spell effects, including any special effects like morale (in frames per second)
 	float getSpellEffectSpeed();
 
-	/// returns duration of full movement animation, in seconds. Needed to move animation on screen
-	float getMovementDuration(const CCreature * creature);
+	/// returns speed of movement animation across the screen, in tiles per second
+	float getMovementDistance(const CCreature * creature);
 
-	/// Returns distance on which flying creatures should during one animation loop
+	/// returns speed of movement animation across the screen, in pixels per seconds
 	float getFlightDistance(const CCreature * creature);
 
 	/// Returns total time for full fade-in effect on newly summoned creatures, in seconds

+ 0 - 1282
client/gui/CAnimation.cpp

@@ -1,1282 +0,0 @@
-/*
- * CAnimation.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 "CAnimation.h"
-
-#include "SDL_Extensions.h"
-#include "SDL_Pixels.h"
-#include "ColorFilter.h"
-
-#include "../CBitmapHandler.h"
-#include "../Graphics.h"
-
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/ISimpleResourceLoader.h"
-#include "../lib/JsonNode.h"
-#include "../lib/CRandomGenerator.h"
-
-class SDLImageLoader;
-
-typedef std::map <size_t, std::vector <JsonNode> > source_map;
-typedef std::map<size_t, IImage* > image_map;
-typedef std::map<size_t, image_map > group_map;
-
-/// Class for def loading
-/// After loading will store general info (palette and frame offsets) and pointer to file itself
-class CDefFile
-{
-private:
-
-	PACKED_STRUCT_BEGIN
-	struct SSpriteDef
-	{
-		ui32 size;
-		ui32 format;    /// format in which pixel data is stored
-		ui32 fullWidth; /// full width and height of frame, including borders
-		ui32 fullHeight;
-		ui32 width;     /// width and height of pixel data, borders excluded
-		ui32 height;
-		si32 leftMargin;
-		si32 topMargin;
-	} PACKED_STRUCT_END;
-	//offset[group][frame] - offset of frame data in file
-	std::map<size_t, std::vector <size_t> > offset;
-
-	std::unique_ptr<ui8[]>       data;
-	std::unique_ptr<SDL_Color[]> palette;
-
-public:
-	CDefFile(std::string Name);
-	~CDefFile();
-
-	//load frame as SDL_Surface
-	template<class ImageLoader>
-	void loadFrame(size_t frame, size_t group, ImageLoader &loader) const;
-
-	const std::map<size_t, size_t> getEntries() const;
-};
-
-
-/*
- * Wrapper around SDL_Surface
- */
-class SDLImage : public IImage
-{
-public:
-	
-	const static int DEFAULT_PALETTE_COLORS = 256;
-	
-	//Surface without empty borders
-	SDL_Surface * surf;
-	//size of left and top borders
-	Point margins;
-	//total size including borders
-	Point fullSize;
-
-public:
-	//Load image from def file
-	SDLImage(CDefFile *data, size_t frame, size_t group=0);
-	//Load from bitmap file
-	SDLImage(std::string filename);
-
-	SDLImage(const JsonNode & conf);
-	//Create using existing surface, extraRef will increase refcount on SDL_Surface
-	SDLImage(SDL_Surface * from, bool extraRef);
-	~SDLImage();
-
-	// Keep the original palette, in order to do color switching operation
-	void savePalette();
-
-	void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
-	void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const override;
-	std::shared_ptr<IImage> scaleFast(float scale) const override;
-	void exportBitmap(const boost::filesystem::path & path) const override;
-	void playerColored(PlayerColor player) override;
-	void setFlagColor(PlayerColor player) override;
-	bool isTransparent(const Point & coords) const override;
-	Point dimensions() const override;
-
-	void horizontalFlip() override;
-	void verticalFlip() override;
-
-	void shiftPalette(int from, int howMany) override;
-	void adjustPalette(const ColorFilter & shifter) override;
-	void resetPalette(int colorID) override;
-	void resetPalette() override;
-
-	void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
-
-	friend class SDLImageLoader;
-
-private:
-	SDL_Palette * originalPalette;
-};
-
-class SDLImageLoader
-{
-	SDLImage * image;
-	ui8 * lineStart;
-	ui8 * position;
-public:
-	//load size raw pixels from data
-	inline void Load(size_t size, const ui8 * data);
-	//set size pixels to color
-	inline void Load(size_t size, ui8 color=0);
-	inline void EndLine();
-	//init image with these sizes and palette
-	inline void init(Point SpriteSize, Point Margins, Point FullSize, SDL_Color *pal);
-
-	SDLImageLoader(SDLImage * Img);
-	~SDLImageLoader();
-};
-
-std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
-{
-	return std::shared_ptr<IImage>(new SDLImage(path));
-}
-
-// Extremely simple file cache. TODO: smarter, more general solution
-class CFileCache
-{
-	static const int cacheSize = 50; //Max number of cached files
-	struct FileData
-	{
-		ResourceID             name;
-		size_t                 size;
-		std::unique_ptr<ui8[]> data;
-
-		std::unique_ptr<ui8[]> getCopy()
-		{
-			auto ret = std::unique_ptr<ui8[]>(new ui8[size]);
-			std::copy(data.get(), data.get() + size, ret.get());
-			return ret;
-		}
-		FileData(ResourceID name_, size_t size_, std::unique_ptr<ui8[]> data_):
-			name{std::move(name_)},
-			size{size_},
-			data{std::move(data_)}
-		{}
-	};
-
-	std::deque<FileData> cache;
-public:
-	std::unique_ptr<ui8[]> getCachedFile(ResourceID rid)
-	{
-		for(auto & file : cache)
-		{
-			if (file.name == rid)
-				return file.getCopy();
-		}
-		// Still here? Cache miss
-		if (cache.size() > cacheSize)
-			cache.pop_front();
-
-		auto data =  CResourceHandler::get()->load(rid)->readAll();
-
-		cache.emplace_back(std::move(rid), data.second, std::move(data.first));
-
-		return cache.back().getCopy();
-	}
-};
-
-enum class DefType : uint32_t
-{
-	SPELL = 0x40,
-	SPRITE = 0x41,
-	CREATURE = 0x42,
-	MAP = 0x43,
-	MAP_HERO = 0x44,
-	TERRAIN = 0x45,
-	CURSOR = 0x46,
-	INTERFACE = 0x47,
-	SPRITE_FRAME = 0x48,
-	BATTLE_HERO = 0x49
-};
-
-static CFileCache animationCache;
-
-/*************************************************************************
- *  DefFile, class used for def loading                                  *
- *************************************************************************/
-
-bool operator== (const SDL_Color & lhs, const SDL_Color & rhs)
-{
-	return (lhs.a == rhs.a) && (lhs.b == rhs.b) &&(lhs.g == rhs.g) &&(lhs.r == rhs.r);
-}
-
-CDefFile::CDefFile(std::string Name):
-	data(nullptr),
-	palette(nullptr)
-{
-	//First 8 colors in def palette used for transparency
-	static SDL_Color H3Palette[8] =
-	{
-		{   0,   0,   0,   0},// transparency                  ( used in most images )
-		{   0,   0,   0,  64},// shadow border                 ( used in battle, adventure map def's )
-		{   0,   0,   0,  64},// shadow border                 ( used in fog-of-war def's )
-		{   0,   0,   0, 128},// shadow body                   ( used in fog-of-war def's )
-		{   0,   0,   0, 128},// shadow body                   ( used in battle, adventure map def's )
-		{   0,   0,   0,   0},// selection                     ( used in battle def's )
-		{   0,   0,   0, 128},// shadow body   below selection ( used in battle def's )
-		{   0,   0,   0,  64} // shadow border below selection ( used in battle def's )
-	};
-	data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
-
-	palette = std::unique_ptr<SDL_Color[]>(new SDL_Color[256]);
-	int it = 0;
-
-	ui32 type = read_le_u32(data.get() + it);
-	it+=4;
-	//int width  = read_le_u32(data + it); it+=4;//not used
-	//int height = read_le_u32(data + it); it+=4;
-	it+=8;
-	ui32 totalBlocks = read_le_u32(data.get() + it);
-	it+=4;
-
-	for (ui32 i= 0; i<256; i++)
-	{
-		palette[i].r = data[it++];
-		palette[i].g = data[it++];
-		palette[i].b = data[it++];
-		palette[i].a = SDL_ALPHA_OPAQUE;
-	}
-
-	switch(static_cast<DefType>(type))
-	{
-	case DefType::SPELL:
-		palette[0] = H3Palette[0];
-		break;
-	case DefType::SPRITE:
-	case DefType::SPRITE_FRAME:
-		for(ui32 i= 0; i<8; i++)
-			palette[i] = H3Palette[i];
-		break;
-	case DefType::CREATURE:
-		palette[0] = H3Palette[0];
-		palette[1] = H3Palette[1];
-		palette[4] = H3Palette[4];
-		palette[5] = H3Palette[5];
-		palette[6] = H3Palette[6];
-		palette[7] = H3Palette[7];
-		break;
-	case DefType::MAP:
-	case DefType::MAP_HERO:
-		palette[0] = H3Palette[0];
-		palette[1] = H3Palette[1];
-		palette[4] = H3Palette[4];
-		//5 = owner flag, handled separately
-		break;
-	case DefType::TERRAIN:
-		palette[0] = H3Palette[0];
-		palette[1] = H3Palette[1];
-		palette[2] = H3Palette[2];
-		palette[3] = H3Palette[3];
-		palette[4] = H3Palette[4];
-		break;
-	case DefType::CURSOR:
-		palette[0] = H3Palette[0];
-		break;
-	case DefType::INTERFACE:
-		palette[0] = H3Palette[0];
-		palette[1] = H3Palette[1];
-		palette[4] = H3Palette[4];
-		//player colors handled separately
-		//TODO: disallow colorizing other def types
-		break;
-	case DefType::BATTLE_HERO:
-		palette[0] = H3Palette[0];
-		palette[1] = H3Palette[1];
-		palette[4] = H3Palette[4];
-		break;
-	default:
-		logAnim->error("Unknown def type %d in %s", type, Name);
-		break;
-	}
-
-
-	for (ui32 i=0; i<totalBlocks; i++)
-	{
-		size_t blockID = read_le_u32(data.get() + it);
-		it+=4;
-		size_t totalEntries = read_le_u32(data.get() + it);
-		it+=12;
-		//8 unknown bytes - skipping
-
-		//13 bytes for name of every frame in this block - not used, skipping
-		it+= 13 * (int)totalEntries;
-
-		for (ui32 j=0; j<totalEntries; j++)
-		{
-			size_t currOffset = read_le_u32(data.get() + it);
-			offset[blockID].push_back(currOffset);
-			it += 4;
-		}
-	}
-}
-
-template<class ImageLoader>
-void CDefFile::loadFrame(size_t frame, size_t group, ImageLoader &loader) const
-{
-	std::map<size_t, std::vector <size_t> >::const_iterator it;
-	it = offset.find(group);
-	assert (it != offset.end());
-
-	const ui8 * FDef = data.get()+it->second[frame];
-
-	const SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(FDef);
-	SSpriteDef sprite;
-
-	sprite.format = read_le_u32(&sd.format);
-	sprite.fullWidth = read_le_u32(&sd.fullWidth);
-	sprite.fullHeight = read_le_u32(&sd.fullHeight);
-	sprite.width = read_le_u32(&sd.width);
-	sprite.height = read_le_u32(&sd.height);
-	sprite.leftMargin = read_le_u32(&sd.leftMargin);
-	sprite.topMargin = read_le_u32(&sd.topMargin);
-
-	ui32 currentOffset = sizeof(SSpriteDef);
-
-	//special case for some "old" format defs (SGTWMTA.DEF and SGTWMTB.DEF)
-
-	if(sprite.format == 1 && sprite.width > sprite.fullWidth && sprite.height > sprite.fullHeight)
-	{
-		sprite.leftMargin = 0;
-		sprite.topMargin = 0;
-		sprite.width = sprite.fullWidth;
-		sprite.height = sprite.fullHeight;
-
-		currentOffset -= 16;
-	}
-
-	const ui32 BaseOffset = currentOffset;
-
-	loader.init(Point(sprite.width, sprite.height),
-				Point(sprite.leftMargin, sprite.topMargin),
-				Point(sprite.fullWidth, sprite.fullHeight), palette.get());
-
-	switch(sprite.format)
-	{
-	case 0:
-		{
-			//pixel data is not compressed, copy data to surface
-			for(ui32 i=0; i<sprite.height; i++)
-			{
-				loader.Load(sprite.width, FDef + currentOffset);
-				currentOffset += sprite.width;
-				loader.EndLine();
-			}
-			break;
-		}
-	case 1:
-		{
-			//for each line we have offset of pixel data
-			const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+currentOffset);
-			currentOffset += sizeof(ui32) * sprite.height;
-
-			for(ui32 i=0; i<sprite.height; i++)
-			{
-				//get position of the line
-				currentOffset=BaseOffset + read_le_u32(RWEntriesLoc + i);
-				ui32 TotalRowLength = 0;
-
-				while(TotalRowLength<sprite.width)
-				{
-					ui8 segmentType = FDef[currentOffset++];
-					ui32 length = FDef[currentOffset++] + 1;
-
-					if(segmentType==0xFF)//Raw data
-					{
-						loader.Load(length, FDef + currentOffset);
-						currentOffset+=length;
-					}
-					else// RLE
-					{
-						loader.Load(length, segmentType);
-					}
-					TotalRowLength += length;
-				}
-
-				loader.EndLine();
-			}
-			break;
-		}
-	case 2:
-		{
-			currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset);
-
-			for(ui32 i=0; i<sprite.height; i++)
-			{
-				ui32 TotalRowLength=0;
-
-				while(TotalRowLength<sprite.width)
-				{
-					ui8 segment=FDef[currentOffset++];
-					ui8 code = segment / 32;
-					ui8 length = (segment & 31) + 1;
-
-					if(code==7)//Raw data
-					{
-						loader.Load(length, FDef + currentOffset);
-						currentOffset += length;
-					}
-					else//RLE
-					{
-						loader.Load(length, code);
-					}
-					TotalRowLength+=length;
-				}
-				loader.EndLine();
-			}
-			break;
-		}
-	case 3:
-		{
-			for(ui32 i=0; i<sprite.height; i++)
-			{
-				currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset+i*2*(sprite.width/32));
-				ui32 TotalRowLength=0;
-
-				while(TotalRowLength<sprite.width)
-				{
-					ui8 segment = FDef[currentOffset++];
-					ui8 code = segment / 32;
-					ui8 length = (segment & 31) + 1;
-
-					if(code==7)//Raw data
-					{
-						loader.Load(length, FDef + currentOffset);
-						currentOffset += length;
-					}
-					else//RLE
-					{
-						loader.Load(length, code);
-					}
-					TotalRowLength += length;
-				}
-				loader.EndLine();
-			}
-			break;
-		}
-	default:
-	logGlobal->error("Error: unsupported format of def file: %d", sprite.format);
-		break;
-	}
-}
-
-CDefFile::~CDefFile() = default;
-
-const std::map<size_t, size_t > CDefFile::getEntries() const
-{
-	std::map<size_t, size_t > ret;
-
-	for (auto & elem : offset)
-		ret[elem.first] =  elem.second.size();
-	return ret;
-}
-
-/*************************************************************************
- *  Classes for image loaders - helpers for loading from def files       *
- *************************************************************************/
-
-SDLImageLoader::SDLImageLoader(SDLImage * Img):
-	image(Img),
-	lineStart(nullptr),
-	position(nullptr)
-{
-}
-
-void SDLImageLoader::init(Point SpriteSize, Point Margins, Point FullSize, SDL_Color *pal)
-{
-	//Init image
-	image->surf = SDL_CreateRGBSurface(0, SpriteSize.x, SpriteSize.y, 8, 0, 0, 0, 0);
-	image->margins  = Margins;
-	image->fullSize = FullSize;
-
-	//Prepare surface
-	SDL_Palette * p = SDL_AllocPalette(SDLImage::DEFAULT_PALETTE_COLORS);
-	SDL_SetPaletteColors(p, pal, 0, SDLImage::DEFAULT_PALETTE_COLORS);
-	SDL_SetSurfacePalette(image->surf, p);
-	SDL_FreePalette(p);
-
-	SDL_LockSurface(image->surf);
-	lineStart = position = (ui8*)image->surf->pixels;
-}
-
-inline void SDLImageLoader::Load(size_t size, const ui8 * data)
-{
-	if (size)
-	{
-		memcpy((void *)position, data, size);
-		position += size;
-	}
-}
-
-inline void SDLImageLoader::Load(size_t size, ui8 color)
-{
-	if (size)
-	{
-		memset((void *)position, color, size);
-		position += size;
-	}
-}
-
-inline void SDLImageLoader::EndLine()
-{
-	lineStart += image->surf->pitch;
-	position = lineStart;
-}
-
-SDLImageLoader::~SDLImageLoader()
-{
-	SDL_UnlockSurface(image->surf);
-	SDL_SetColorKey(image->surf, SDL_TRUE, 0);
-	//TODO: RLE if compressed and bpp>1
-}
-
-/*************************************************************************
- *  Classes for images, support loading from file and drawing on surface *
- *************************************************************************/
-
-IImage::IImage() = default;
-IImage::~IImage() = default;
-
-int IImage::width() const
-{
-	return dimensions().x;
-}
-
-int IImage::height() const
-{
-	return dimensions().y;
-}
-
-SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
-	: surf(nullptr),
-	margins(0, 0),
-	fullSize(0, 0),
-	originalPalette(nullptr)
-{
-	SDLImageLoader loader(this);
-	data->loadFrame(frame, group, loader);
-
-	savePalette();
-}
-
-SDLImage::SDLImage(SDL_Surface * from, bool extraRef)
-	: surf(nullptr),
-	margins(0, 0),
-	fullSize(0, 0),
-	originalPalette(nullptr)
-{
-	surf = from;
-	if (surf == nullptr)
-		return;
-
-	savePalette();
-
-	if (extraRef)
-		surf->refcount++;
-	fullSize.x = surf->w;
-	fullSize.y = surf->h;
-}
-
-SDLImage::SDLImage(const JsonNode & conf)
-	: surf(nullptr),
-	margins(0, 0),
-	fullSize(0, 0),
-	originalPalette(nullptr)
-{
-	std::string filename = conf["file"].String();
-
-	surf = BitmapHandler::loadBitmap(filename);
-
-	if(surf == nullptr)
-		return;
-
-	savePalette();
-
-	const JsonNode & jsonMargins = conf["margins"];
-
-	margins.x = static_cast<int>(jsonMargins["left"].Integer());
-	margins.y = static_cast<int>(jsonMargins["top"].Integer());
-
-	fullSize.x = static_cast<int>(conf["width"].Integer());
-	fullSize.y = static_cast<int>(conf["height"].Integer());
-
-	if(fullSize.x == 0)
-	{
-		fullSize.x = margins.x + surf->w + (int)jsonMargins["right"].Integer();
-	}
-
-	if(fullSize.y == 0)
-	{
-		fullSize.y = margins.y + surf->h + (int)jsonMargins["bottom"].Integer();
-	}
-}
-
-SDLImage::SDLImage(std::string filename)
-	: surf(nullptr),
-	margins(0, 0),
-	fullSize(0, 0),
-	originalPalette(nullptr)
-{
-	surf = BitmapHandler::loadBitmap(filename);
-
-	if(surf == nullptr)
-	{
-		logGlobal->error("Error: failed to load image %s", filename);
-		return;
-	}
-	else
-	{
-		savePalette();
-		fullSize.x = surf->w;
-		fullSize.y = surf->h;
-	}
-}
-
-void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
-{
-	if(!surf)
-		return;
-
-	Rect destRect(posX, posY, surf->w, surf->h);
-	draw(where, &destRect, src);
-}
-
-void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) const
-{
-	if (!surf)
-		return;
-
-	Rect sourceRect(0, 0, surf->w, surf->h);
-
-	Point destShift(0, 0);
-
-	if(src)
-	{
-		if(src->x < margins.x)
-			destShift.x += margins.x - src->x;
-
-		if(src->y < margins.y)
-			destShift.y += margins.y - src->y;
-
-		sourceRect = Rect(*src).intersect(Rect(margins.x, margins.y, surf->w, surf->h));
-
-		sourceRect -= margins;
-	}
-	else
-		destShift = margins;
-
-	if(dest)
-		destShift += dest->topLeft();
-
-	if(surf->format->BitsPerPixel == 8)
-	{
-		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift);
-	}
-	else
-	{
-		CSDL_Ext::blitSurface(surf, sourceRect, where, destShift);
-	}
-}
-
-std::shared_ptr<IImage> SDLImage::scaleFast(float scale) const
-{
-	auto scaled = CSDL_Ext::scaleSurfaceFast(surf, (int)(surf->w * scale), (int)(surf->h * scale));
-
-	if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point
-		CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]);
-	else if(scaled->format && scaled->format->Amask)
-		SDL_SetSurfaceBlendMode(scaled, SDL_BLENDMODE_BLEND);//just in case
-	else
-		CSDL_Ext::setDefaultColorKey(scaled);//just in case
-
-	SDLImage * ret = new SDLImage(scaled, false);
-
-	ret->fullSize.x = (int) round((float)fullSize.x * scale);
-	ret->fullSize.y = (int) round((float)fullSize.y * scale);
-
-	ret->margins.x = (int) round((float)margins.x * scale);
-	ret->margins.y = (int) round((float)margins.y * scale);
-
-	return std::shared_ptr<IImage>(ret);
-}
-
-void SDLImage::exportBitmap(const boost::filesystem::path& path) const
-{
-	SDL_SaveBMP(surf, path.string().c_str());
-}
-
-void SDLImage::playerColored(PlayerColor player)
-{
-	graphics->blueToPlayersAdv(surf, player);
-}
-
-void SDLImage::setFlagColor(PlayerColor player)
-{
-	if(player < PlayerColor::PLAYER_LIMIT || player==PlayerColor::NEUTRAL)
-		CSDL_Ext::setPlayerColor(surf, player);
-}
-
-bool SDLImage::isTransparent(const Point & coords) const
-{
-	return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
-}
-
-Point SDLImage::dimensions() const
-{
-	return fullSize;
-}
-
-void SDLImage::horizontalFlip()
-{
-	margins.y = fullSize.y - surf->h - margins.y;
-
-	//todo: modify in-place
-	SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
-	SDL_FreeSurface(surf);
-	surf = flipped;
-}
-
-void SDLImage::verticalFlip()
-{
-	margins.x = fullSize.x - surf->w - margins.x;
-
-	//todo: modify in-place
-	SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
-	SDL_FreeSurface(surf);
-	surf = flipped;
-}
-
-// Keep the original palette, in order to do color switching operation
-void SDLImage::savePalette()
-{
-	// For some images that don't have palette, skip this
-	if(surf->format->palette == nullptr)
-		return;
-
-	if(originalPalette == nullptr)
-		originalPalette = SDL_AllocPalette(DEFAULT_PALETTE_COLORS);
-
-	SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, DEFAULT_PALETTE_COLORS);
-}
-
-void SDLImage::shiftPalette(int from, int howMany)
-{
-	//works with at most 16 colors, if needed more -> increase values
-	assert(howMany < 16);
-
-	if(surf->format->palette)
-	{
-		SDL_Color palette[16];
-
-		for(int i=0; i<howMany; ++i)
-		{
-			palette[(i+1)%howMany] = surf->format->palette->colors[from + i];
-		}
-		CSDL_Ext::setColors(surf, palette, from, howMany);
-	}
-}
-
-void SDLImage::adjustPalette(const ColorFilter & shifter)
-{
-	if(originalPalette == nullptr)
-		return;
-
-	SDL_Palette* palette = surf->format->palette;
-
-	// Note: here we skip the first 8 colors in the palette that predefined in H3Palette
-	for(int i = 8; i < palette->ncolors; i++)
-	{
-		palette->colors[i] = shifter.shiftColor(originalPalette->colors[i]);
-	}
-}
-
-void SDLImage::resetPalette()
-{
-	if(originalPalette == nullptr)
-		return;
-	
-	// Always keept the original palette not changed, copy a new palette to assign to surface
-	SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
-}
-
-void SDLImage::resetPalette( int colorID )
-{
-	if(originalPalette == nullptr)
-		return;
-
-	// Always keept the original palette not changed, copy a new palette to assign to surface
-	SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
-}
-
-void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
-{
-	if(surf->format->palette)
-	{
-		CSDL_Ext::setColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
-	}
-}
-
-SDLImage::~SDLImage()
-{
-	SDL_FreeSurface(surf);
-
-	if(originalPalette != nullptr)
-	{
-		SDL_FreePalette(originalPalette);
-		originalPalette = nullptr;
-	}
-}
-
-std::shared_ptr<IImage> CAnimation::getFromExtraDef(std::string filename)
-{
-	size_t pos = filename.find(':');
-	if (pos == -1)
-		return nullptr;
-	CAnimation anim(filename.substr(0, pos));
-	pos++;
-	size_t frame = atoi(filename.c_str()+pos);
-	size_t group = 0;
-	pos = filename.find(':', pos);
-	if (pos != -1)
-	{
-		pos++;
-		group = frame;
-		frame = atoi(filename.c_str()+pos);
-	}
-	anim.load(frame ,group);
-	auto ret = anim.images[group][frame];
-	anim.images.clear();
-	return ret;
-}
-
-bool CAnimation::loadFrame(size_t frame, size_t group)
-{
-	if(size(group) <= frame)
-	{
-		printError(frame, group, "LoadFrame");
-		return false;
-	}
-
-	auto image = getImage(frame, group, false);
-	if(image)
-	{
-		return true;
-	}
-
-	//try to get image from def
-	if(source[group][frame].getType() == JsonNode::JsonType::DATA_NULL)
-	{
-		if(defFile)
-		{
-			auto frameList = defFile->getEntries();
-
-			if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present
-			{
-				images[group][frame] = std::make_shared<SDLImage>(defFile.get(), frame, group);
-				return true;
-			}
-		}
-		// still here? image is missing
-
-		printError(frame, group, "LoadFrame");
-		images[group][frame] = std::make_shared<SDLImage>("DEFAULT");
-	}
-	else //load from separate file
-	{
-		auto img = getFromExtraDef(source[group][frame]["file"].String());
-		if(!img)
-			img = std::make_shared<SDLImage>(source[group][frame]);
-
-		images[group][frame] = img;
-		return true;
-	}
-	return false;
-}
-
-bool CAnimation::unloadFrame(size_t frame, size_t group)
-{
-	auto image = getImage(frame, group, false);
-	if(image)
-	{
-		images[group].erase(frame);
-
-		if(images[group].empty())
-			images.erase(group);
-		return true;
-	}
-	return false;
-}
-
-void CAnimation::initFromJson(const JsonNode & config)
-{
-	std::string basepath;
-	basepath = config["basepath"].String();
-
-	JsonNode base(JsonNode::JsonType::DATA_STRUCT);
-	base["margins"] = config["margins"];
-	base["width"] = config["width"];
-	base["height"] = config["height"];
-
-	for(const JsonNode & group : config["sequences"].Vector())
-	{
-		size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
-		source[groupID].clear();
-
-		for(const JsonNode & frame : group["frames"].Vector())
-		{
-			JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
-			JsonUtils::inherit(toAdd, base);
-			toAdd["file"].String() = basepath + frame.String();
-			source[groupID].push_back(toAdd);
-		}
-	}
-
-	for(const JsonNode & node : config["images"].Vector())
-	{
-		size_t group = node["group"].Integer();
-		size_t frame = node["frame"].Integer();
-
-		if (source[group].size() <= frame)
-			source[group].resize(frame+1);
-
-		JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
-		JsonUtils::inherit(toAdd, base);
-		toAdd["file"].String() = basepath + node["file"].String();
-		source[group][frame] = toAdd;
-	}
-}
-
-void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
-{
-	if(images.empty())
-	{
-		logGlobal->error("Nothing to export, animation is empty");
-		return;
-	}
-
-	boost::filesystem::path actualPath = path / "SPRITES" / name;
-	boost::filesystem::create_directories(actualPath);
-
-	size_t counter = 0;
-
-	for(const auto & groupPair : images)
-	{
-		size_t group = groupPair.first;
-
-		for(const auto & imagePair : groupPair.second)
-		{
-			size_t frame = imagePair.first;
-			const auto img = imagePair.second;
-
-			boost::format fmt("%d_%d.bmp");
-			fmt % group % frame;
-
-			img->exportBitmap(actualPath / fmt.str());
-			counter++;
-		}
-	}
-
-	logGlobal->info("Exported %d frames to %s", counter, actualPath.string());
-}
-
-void CAnimation::init()
-{
-	if(defFile)
-	{
-		const std::map<size_t, size_t> defEntries = defFile->getEntries();
-
-		for (auto & defEntry : defEntries)
-			source[defEntry.first].resize(defEntry.second);
-	}
-
-	ResourceID resID(std::string("SPRITES/") + name, EResType::TEXT);
-
-	if (vstd::contains(graphics->imageLists, resID.getName()))
-		initFromJson(graphics->imageLists[resID.getName()]);
-
-	auto configList = CResourceHandler::get()->getResourcesWithName(resID);
-
-	for(auto & loader : configList)
-	{
-		auto stream = loader->load(resID);
-		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
-		stream->read(textData.get(), stream->getSize());
-
-		const JsonNode config((char*)textData.get(), stream->getSize());
-
-		initFromJson(config);
-	}
-}
-
-void CAnimation::printError(size_t frame, size_t group, std::string type) const
-{
-	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame);
-}
-
-CAnimation::CAnimation(std::string Name):
-	name(Name),
-	preloaded(false),
-	defFile()
-{
-	size_t dotPos = name.find_last_of('.');
-	if ( dotPos!=-1 )
-		name.erase(dotPos);
-	std::transform(name.begin(), name.end(), name.begin(), toupper);
-
-	ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION);
-
-	if(CResourceHandler::get()->existsResource(resource))
-		defFile = std::make_shared<CDefFile>(name);
-
-	init();
-
-	if(source.empty())
-		logAnim->error("Animation %s failed to load", Name);
-}
-
-CAnimation::CAnimation():
-	name(""),
-	preloaded(false),
-	defFile()
-{
-	init();
-}
-
-CAnimation::~CAnimation() = default;
-
-void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup)
-{
-	if(!source.count(sourceGroup))
-	{
-		logAnim->error("Group %d missing in %s", sourceGroup, name);
-		return;
-	}
-
-	if(source[sourceGroup].size() <= sourceFrame)
-	{
-		logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name);
-		return;
-	}
-
-	//todo: clone actual loaded Image object
-	JsonNode clone(source[sourceGroup][sourceFrame]);
-
-	if(clone.getType() == JsonNode::JsonType::DATA_NULL)
-	{
-		std::string temp =  name+":"+boost::lexical_cast<std::string>(sourceGroup)+":"+boost::lexical_cast<std::string>(sourceFrame);
-		clone["file"].String() = temp;
-	}
-
-	source[targetGroup].push_back(clone);
-
-	size_t index = source[targetGroup].size() - 1;
-
-	if(preloaded)
-		load(index, targetGroup);
-}
-
-void CAnimation::setCustom(std::string filename, size_t frame, size_t group)
-{
-	if (source[group].size() <= frame)
-		source[group].resize(frame+1);
-	source[group][frame]["file"].String() = filename;
-	//FIXME: update image if already loaded
-}
-
-std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose) const
-{
-	auto groupIter = images.find(group);
-	if (groupIter != images.end())
-	{
-		auto imageIter = groupIter->second.find(frame);
-		if (imageIter != groupIter->second.end())
-			return imageIter->second;
-	}
-	if (verbose)
-		printError(frame, group, "GetImage");
-	return nullptr;
-}
-
-void CAnimation::load()
-{
-	for (auto & elem : source)
-		for (size_t image=0; image < elem.second.size(); image++)
-			loadFrame(image, elem.first);
-}
-
-void CAnimation::unload()
-{
-	for (auto & elem : source)
-		for (size_t image=0; image < elem.second.size(); image++)
-			unloadFrame(image, elem.first);
-
-}
-
-void CAnimation::preload()
-{
-	if(!preloaded)
-	{
-		preloaded = true;
-		load();
-	}
-}
-
-void CAnimation::loadGroup(size_t group)
-{
-	if (vstd::contains(source, group))
-		for (size_t image=0; image < source[group].size(); image++)
-			loadFrame(image, group);
-}
-
-void CAnimation::unloadGroup(size_t group)
-{
-	if (vstd::contains(source, group))
-		for (size_t image=0; image < source[group].size(); image++)
-			unloadFrame(image, group);
-}
-
-void CAnimation::load(size_t frame, size_t group)
-{
-	loadFrame(frame, group);
-}
-
-void CAnimation::unload(size_t frame, size_t group)
-{
-	unloadFrame(frame, group);
-}
-
-size_t CAnimation::size(size_t group) const
-{
-	auto iter = source.find(group);
-	if (iter != source.end())
-		return iter->second.size();
-	return 0;
-}
-
-void CAnimation::horizontalFlip()
-{
-	for(auto & group : images)
-		for(auto & image : group.second)
-			image.second->horizontalFlip();
-}
-
-void CAnimation::verticalFlip()
-{
-	for(auto & group : images)
-		for(auto & image : group.second)
-			image.second->verticalFlip();
-}
-
-void CAnimation::playerColored(PlayerColor player)
-{
-	for(auto & group : images)
-		for(auto & image : group.second)
-			image.second->playerColored(player);
-}
-
-void CAnimation::createFlippedGroup(const size_t sourceGroup, const size_t targetGroup)
-{
-	for(size_t frame = 0; frame < size(sourceGroup); ++frame)
-	{
-		duplicateImage(sourceGroup, frame, targetGroup);
-
-		auto image = getImage(frame, targetGroup);
-		image->verticalFlip();
-	}
-}
-
-float CFadeAnimation::initialCounter() const
-{
-	if (fadingMode == EMode::OUT)
-		return 1.0f;
-	return 0.0f;
-}
-
-void CFadeAnimation::update()
-{
-	if (!fading)
-		return;
-
-	if (fadingMode == EMode::OUT)
-		fadingCounter -= delta;
-	else
-		fadingCounter += delta;
-
-	if (isFinished())
-	{
-		fading = false;
-		if (shouldFreeSurface)
-		{
-			SDL_FreeSurface(fadingSurface);
-			fadingSurface = nullptr;
-		}
-	}
-}
-
-bool CFadeAnimation::isFinished() const
-{
-	if (fadingMode == EMode::OUT)
-		return fadingCounter <= 0.0f;
-	return fadingCounter >= 1.0f;
-}
-
-CFadeAnimation::CFadeAnimation()
-	: delta(0),	fadingSurface(nullptr), fading(false), fadingCounter(0), shouldFreeSurface(false),
-	  fadingMode(EMode::NONE)
-{
-}
-
-CFadeAnimation::~CFadeAnimation()
-{
-	if (fadingSurface && shouldFreeSurface)
-		SDL_FreeSurface(fadingSurface);
-}
-
-void CFadeAnimation::init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd, float animDelta)
-{
-	if (fading)
-	{
-		// in that case, immediately finish the previous fade
-		// (alternatively, we could just return here to ignore the new fade request until this one finished (but we'd need to free the passed bitmap to avoid leaks))
-		logGlobal->warn("Tried to init fading animation that is already running.");
-		if (fadingSurface && shouldFreeSurface)
-			SDL_FreeSurface(fadingSurface);
-	}
-	if (animDelta <= 0.0f)
-	{
-		logGlobal->warn("Fade anim: delta should be positive; %f given.", animDelta);
-		animDelta = DEFAULT_DELTA;
-	}
-
-	if (sourceSurface)
-		fadingSurface = sourceSurface;
-
-	delta = animDelta;
-	fadingMode = mode;
-	fadingCounter = initialCounter();
-	fading = true;
-	shouldFreeSurface = freeSurfaceAtEnd;
-}
-
-void CFadeAnimation::draw(SDL_Surface * targetSurface, const Point &targetPoint)
-{
-	if (!fading || !fadingSurface || fadingMode == EMode::NONE)
-	{
-		fading = false;
-		return;
-	}
-
-	CSDL_Ext::setAlpha(fadingSurface, (int)(fadingCounter * 255));
-	CSDL_Ext::blitSurface(fadingSurface, targetSurface, targetPoint); //FIXME
-	CSDL_Ext::setAlpha(fadingSurface, 255);
-}

+ 198 - 63
client/gui/CGuiHandler.cpp

@@ -11,19 +11,22 @@
 #include "CGuiHandler.h"
 #include "../lib/CondSh.h"
 
-#include <SDL_timer.h>
-
 #include "CIntObject.h"
 #include "CursorHandler.h"
-#include "SDL_Extensions.h"
 
 #include "../CGameInfo.h"
-#include "../../lib/CThreadHelper.h"
-#include "../../lib/CConfigHandler.h"
+#include "../renderSDL/SDL_Extensions.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
 
+#include "../../lib/CThreadHelper.h"
+#include "../../lib/CConfigHandler.h"
+
+#include <SDL_render.h>
+#include <SDL_timer.h>
+#include <SDL_events.h>
+
 extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 
@@ -79,6 +82,13 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
 	processList(CIntObject::TEXTINPUT,activityFlag,&textInterested,cb);
 }
 
+void CGuiHandler::init()
+{
+	mainFPSmng->init();
+	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
+	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
+}
+
 void CGuiHandler::handleElementActivate(CIntObject * elem, ui16 activityFlag)
 {
 	processLists(activityFlag,[&](std::list<CIntObject*> * lst){
@@ -192,27 +202,99 @@ void CGuiHandler::handleEvents()
 	while(!SDLEventsQueue.empty())
 	{
 		continueEventHandling = true;
-		SDL_Event ev = SDLEventsQueue.front();
-		current = &ev;
+		SDL_Event currentEvent = SDLEventsQueue.front();
+		cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
 		SDLEventsQueue.pop();
 
 		// In a sequence of mouse motion events, skip all but the last one.
 		// This prevents freezes when every motion event takes longer to handle than interval at which
 		// the events arrive (like dragging on the minimap in world view, with redraw at every event)
 		// so that the events would start piling up faster than they can be processed.
-		if ((ev.type == SDL_MOUSEMOTION) && !SDLEventsQueue.empty() && (SDLEventsQueue.front().type == SDL_MOUSEMOTION))
+		if ((currentEvent.type == SDL_MOUSEMOTION) && !SDLEventsQueue.empty() && (SDLEventsQueue.front().type == SDL_MOUSEMOTION))
 			continue;
 
-		handleCurrentEvent();
+		handleCurrentEvent(currentEvent);
 	}
 }
 
-void CGuiHandler::handleCurrentEvent()
+void CGuiHandler::convertTouchToMouse(SDL_Event * current)
+{
+	int rLogicalWidth, rLogicalHeight;
+
+	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
+
+	int adjustedMouseY = (int)(current->tfinger.y * rLogicalHeight);
+	int adjustedMouseX = (int)(current->tfinger.x * rLogicalWidth);
+
+	current->button.x = adjustedMouseX;
+	current->motion.x = adjustedMouseX;
+	current->button.y = adjustedMouseY;
+	current->motion.y = adjustedMouseY;
+}
+
+void CGuiHandler::fakeMoveCursor(float dx, float dy)
 {
-	if(current->type == SDL_KEYDOWN || current->type == SDL_KEYUP)
+	int x, y, w, h;
+
+	SDL_Event event;
+	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	sme.state = SDL_GetMouseState(&x, &y);
+	SDL_GetWindowSize(mainWindow, &w, &h);
+
+	sme.x = CCS->curh->position().x + (int)(GH.pointerSpeedMultiplier * w * dx);
+	sme.y = CCS->curh->position().y + (int)(GH.pointerSpeedMultiplier * h * dy);
+
+	vstd::abetween(sme.x, 0, w);
+	vstd::abetween(sme.y, 0, h);
+
+	event.motion = sme;
+	SDL_PushEvent(&event);
+}
+
+void CGuiHandler::fakeMouseMove()
+{
+	fakeMoveCursor(0, 0);
+}
+
+void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
+{
+	SDL_Event event;
+	SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	if(!down)
 	{
-		SDL_KeyboardEvent key = current->key;
-		if(current->type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
+		sme.type = SDL_MOUSEBUTTONUP;
+	}
+
+	sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
+
+	sme.x = CCS->curh->position().x;
+	sme.y = CCS->curh->position().y;
+
+	float xScale, yScale;
+	int w, h, rLogicalWidth, rLogicalHeight;
+
+	SDL_GetWindowSize(mainWindow, &w, &h);
+	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
+	SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
+
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+	CSDL_Ext::warpMouse(
+		(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
+		(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2));
+	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
+
+	event.button = sme;
+	SDL_PushEvent(&event);
+}
+
+void CGuiHandler::handleCurrentEvent( SDL_Event & current )
+{
+	if(current.type == SDL_KEYDOWN || current.type == SDL_KEYUP)
+	{
+		SDL_KeyboardEvent key = current.key;
+		if(current.type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
 		{
 			//TODO: we need some central place for all interface-independent hotkeys
 			Settings s = settings.write["session"];
@@ -275,34 +357,39 @@ void CGuiHandler::handleCurrentEvent()
 			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisEvent(key)))
 				(**i).keyPressed(key);
 	}
-	else if(current->type == SDL_MOUSEMOTION)
+	else if(current.type == SDL_MOUSEMOTION)
 	{
-		handleMouseMotion();
+		handleMouseMotion(current);
 	}
-	else if(current->type == SDL_MOUSEBUTTONDOWN)
+	else if(current.type == SDL_MOUSEBUTTONDOWN)
 	{
-		switch(current->button.button)
+		switch(current.button.button)
 		{
 		case SDL_BUTTON_LEFT:
-			if(lastClick == current->motion && (SDL_GetTicks() - lastClickTime) < 300)
+		{
+			auto doubleClicked = false;
+			if(lastClick == getCursorPosition() && (SDL_GetTicks() - lastClickTime) < 300)
 			{
 				std::list<CIntObject*> hlp = doubleClickInterested;
 				for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
 				{
 					if(!vstd::contains(doubleClickInterested, *i)) continue;
-					if((*i)->pos.isInside(current->motion.x, current->motion.y))
+					if((*i)->pos.isInside(current.motion.x, current.motion.y))
 					{
 						(*i)->onDoubleClick();
+						doubleClicked = true;
 					}
 				}
 
 			}
 
-			lastClick = current->motion;
+			lastClick = current.motion;
 			lastClickTime = SDL_GetTicks();
 
-			handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true);
+			if(!doubleClicked)
+				handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true);
 			break;
+		}
 		case SDL_BUTTON_RIGHT:
 			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
 			break;
@@ -313,7 +400,7 @@ void CGuiHandler::handleCurrentEvent()
 			break;
 		}
 	}
-	else if(current->type == SDL_MOUSEWHEEL)
+	else if(current.type == SDL_MOUSEWHEEL)
 	{
 		std::list<CIntObject*> hlp = wheelInterested;
 		for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
@@ -322,40 +409,97 @@ void CGuiHandler::handleCurrentEvent()
 			// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
 			int x = 0, y = 0;
 			SDL_GetMouseState(&x, &y);
-			(*i)->wheelScrolled(current->wheel.y < 0, (*i)->pos.isInside(x, y));
+			(*i)->wheelScrolled(current.wheel.y < 0, (*i)->pos.isInside(x, y));
 		}
 	}
-	else if(current->type == SDL_TEXTINPUT)
+	else if(current.type == SDL_TEXTINPUT)
 	{
 		for(auto it : textInterested)
 		{
-			it->textInputed(current->text);
+			it->textInputed(current.text);
 		}
 	}
-	else if(current->type == SDL_TEXTEDITING)
+	else if(current.type == SDL_TEXTEDITING)
 	{
 		for(auto it : textInterested)
 		{
-			it->textEdited(current->edit);
+			it->textEdited(current.edit);
 		}
 	}
-	//todo: muiltitouch
-	else if(current->type == SDL_MOUSEBUTTONUP)
+	else if(current.type == SDL_MOUSEBUTTONUP)
 	{
-		switch(current->button.button)
+		if(!multifinger)
 		{
-		case SDL_BUTTON_LEFT:
-			handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
-			break;
-		case SDL_BUTTON_RIGHT:
+			switch(current.button.button)
+			{
+			case SDL_BUTTON_LEFT:
+				handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
+				break;
+			case SDL_BUTTON_RIGHT:
+				handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
+				break;
+			case SDL_BUTTON_MIDDLE:
+				handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
+				break;
+			}
+		}
+	}
+	else if(current.type == SDL_FINGERMOTION)
+	{
+		if(isPointerRelativeMode)
+		{
+			fakeMoveCursor(current.tfinger.dx, current.tfinger.dy);
+		}
+	}
+	else if(current.type == SDL_FINGERDOWN)
+	{
+		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
+
+		multifinger = fingerCount > 1;
+
+		if(isPointerRelativeMode)
+		{
+			if(current.tfinger.x > 0.5)
+			{
+				bool isRightClick = current.tfinger.y < 0.5;
+
+				fakeMouseButtonEventRelativeMode(true, isRightClick);
+			}
+		}
+#ifndef VCMI_IOS
+		else if(fingerCount == 2)
+		{
+			convertTouchToMouse(&current);
+			handleMouseMotion(current);
+			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
+		}
+#endif //VCMI_IOS
+	}
+	else if(current.type == SDL_FINGERUP)
+	{
+#ifndef VCMI_IOS
+		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
+#endif //VCMI_IOS
+
+		if(isPointerRelativeMode)
+		{
+			if(current.tfinger.x > 0.5)
+			{
+				bool isRightClick = current.tfinger.y < 0.5;
+
+				fakeMouseButtonEventRelativeMode(false, isRightClick);
+			}
+		}
+#ifndef VCMI_IOS
+		else if(multifinger)
+		{
+			convertTouchToMouse(&current);
+			handleMouseMotion(current);
 			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
-			break;
-		case SDL_BUTTON_MIDDLE:
-			handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
-			break;
+			multifinger = fingerCount != 0;
 		}
+#endif //VCMI_IOS
 	}
-	current = nullptr;
 } //event end
 
 void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed)
@@ -368,7 +512,7 @@ void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntOb
 		auto prev = (*i)->mouseState(btn);
 		if(!isPressed)
 			(*i)->updateMouseState(btn, isPressed);
-		if((*i)->pos.isInside(current->motion.x, current->motion.y))
+		if((*i)->pos.isInside(getCursorPosition()))
 		{
 			if(isPressed)
 				(*i)->updateMouseState(btn, isPressed);
@@ -379,13 +523,13 @@ void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntOb
 	}
 }
 
-void CGuiHandler::handleMouseMotion()
+void CGuiHandler::handleMouseMotion(const SDL_Event & current)
 {
 	//sending active, hovered hoverable objects hover() call
 	std::vector<CIntObject*> hlp;
 	for(auto & elem : hoverable)
 	{
-		if(elem->pos.isInside(current->motion.x, current->motion.y))
+		if(elem->pos.isInside(getCursorPosition()))
 		{
 			if (!(elem)->hovered)
 				hlp.push_back((elem));
@@ -402,7 +546,7 @@ void CGuiHandler::handleMouseMotion()
 		elem->hovered = true;
 	}
 
-	handleMoveInterested(current->motion);
+	handleMoveInterested(current.motion);
 }
 
 void CGuiHandler::simpleRedraw()
@@ -427,20 +571,6 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
 	}
 }
 
-void CGuiHandler::fakeMouseMove()
-{
-	SDL_Event event;
-	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
-	int x, y;
-
-	sme.state = SDL_GetMouseState(&x, &y);
-	sme.x = x;
-	sme.y = y;
-
-	event.motion = sme;
-	SDL_PushEvent(&event);
-}
-
 void CGuiHandler::renderFrame()
 {
 
@@ -482,11 +612,11 @@ void CGuiHandler::renderFrame()
 
 
 CGuiHandler::CGuiHandler()
-	: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false)
+	: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false),
+    multifinger(false)
 {
 	continueEventHandling = true;
 	curInt = nullptr;
-	current = nullptr;
 	statusbar = nullptr;
 
 	// Creates the FPS manager and sets the framerate to 48 which is doubled the value of the original Heroes 3 FPS rate
@@ -507,14 +637,18 @@ void CGuiHandler::breakEventHandling()
 	continueEventHandling = false;
 }
 
+const Point & CGuiHandler::getCursorPosition() const
+{
+	return cursorPosition;
+}
+
 void CGuiHandler::drawFPSCounter()
 {
-	const static SDL_Color yellow = {255, 255, 0, 0};
 	static SDL_Rect overlay = { 0, 0, 64, 32};
-	Uint32 black = SDL_MapRGB(screen->format, 10, 10, 10);
+	uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10);
 	SDL_FillRect(screen, &overlay, black);
 	std::string fps = boost::lexical_cast<std::string>(mainFPSmng->fps);
-	graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, yellow, Point(10, 10));
+	graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, Colors::YELLOW, Point(10, 10));
 }
 
 SDL_Keycode CGuiHandler::arrowToNum(SDL_Keycode key)
@@ -619,7 +753,8 @@ void CFramerateManager::framerateDelay()
 	// FPS is higher than it should be, then wait some time
 	if(timeElapsed < rateticks)
 	{
-		SDL_Delay((Uint32)ceil(this->rateticks) - timeElapsed);
+		int timeToSleep = (uint32_t)ceil(this->rateticks) - timeElapsed;
+		boost::this_thread::sleep(boost::posix_time::milliseconds(timeToSleep));
 	}
 
 	currentTicks = SDL_GetTicks();

+ 27 - 16
client/gui/CGuiHandler.h

@@ -10,8 +10,7 @@
 #pragma once
 
 #include "../../lib/Point.h"
-
-#include <SDL_events.h>
+#include "SDL_keycode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -20,6 +19,7 @@ template <typename T> struct CondSh;
 VCMI_LIB_NAMESPACE_END
 
 union SDL_Event;
+struct SDL_MouseMotionEvent;
 
 class CFramerateManager;
 class IStatusBar;
@@ -70,26 +70,35 @@ public:
 	std::shared_ptr<IStatusBar> statusbar;
 
 private:
+	Point cursorPosition;
+
 	std::vector<std::shared_ptr<IShowActivatable>> disposed;
 
 	std::atomic<bool> continueEventHandling;
 	typedef std::list<CIntObject*> CIntObjectList;
 
 	//active GUI elements (listening for events
-	CIntObjectList lclickable,
-				   rclickable,
-				   mclickable,
-				   hoverable,
-				   keyinterested,
-				   motioninterested,
-	               timeinterested,
-	               wheelInterested,
-	               doubleClickInterested,
-	               textInterested;
+	CIntObjectList lclickable;
+	CIntObjectList rclickable;
+	CIntObjectList mclickable;
+	CIntObjectList hoverable;
+	CIntObjectList keyinterested;
+	CIntObjectList motioninterested;
+	CIntObjectList timeinterested;
+	CIntObjectList wheelInterested;
+	CIntObjectList doubleClickInterested;
+	CIntObjectList textInterested;
 
 
 	void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
+	void handleCurrentEvent(SDL_Event &current);
+	void handleMouseMotion(const SDL_Event & current);
+	void handleMoveInterested( const SDL_MouseMotionEvent & motion );
+	void convertTouchToMouse(SDL_Event * current);
+	void fakeMoveCursor(float dx, float dy);
+	void fakeMouseButtonEventRelativeMode(bool down, bool right);
+
 public:
 	void handleElementActivate(CIntObject * elem, ui16 activityFlag);
 	void handleElementDeActivate(CIntObject * elem, ui16 activityFlag);
@@ -98,11 +107,15 @@ public:
 	//objs to blit
 	std::vector<std::shared_ptr<IShowActivatable>> objsToBlit;
 
-	SDL_Event * current; //current event - can be set to nullptr to stop handling event
+	const Point & getCursorPosition() const;
+
 	IUpdateable *curInt;
 
 	Point lastClick;
 	unsigned lastClickTime;
+	bool multifinger;
+	bool isPointerRelativeMode;
+	float pointerSpeedMultiplier;
 
 	ui8 defActionsDef; //default auto actions
 	bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list
@@ -111,6 +124,7 @@ public:
 	CGuiHandler();
 	~CGuiHandler();
 
+	void init();
 	void renderFrame();
 
 	void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering
@@ -132,9 +146,6 @@ public:
 
 	void updateTime(); //handles timeInterested
 	void handleEvents(); //takes events from queue and calls interested objects
-	void handleCurrentEvent();
-	void handleMouseMotion();
-	void handleMoveInterested( const SDL_MouseMotionEvent & motion );
 	void fakeMouseMove();
 	void breakEventHandling(); //current event won't be propagated anymore
 	void drawFPSCounter(); // draws the FPS to the upper left corner of the screen

+ 4 - 2
client/gui/CIntObject.cpp

@@ -11,10 +11,12 @@
 #include "CIntObject.h"
 
 #include "CGuiHandler.h"
-#include "SDL_Extensions.h"
-#include "../CMessage.h"
+#include "../renderSDL/SDL_Extensions.h"
+#include "../windows/CMessage.h"
 
 #include <SDL_pixels.h>
+#include <SDL_surface.h>
+#include <SDL_events.h>
 
 IShowActivatable::IShowActivatable()
 {

+ 1 - 1
client/gui/CIntObject.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../../lib/Rect.h"
-#include "../Graphics.h"
+#include "../render/Graphics.h"
 
 struct SDL_Surface;
 class CGuiHandler;

+ 15 - 136
client/gui/CursorHandler.cpp

@@ -11,11 +11,23 @@
 #include "StdInc.h"
 #include "CursorHandler.h"
 
-#include "SDL_Extensions.h"
 #include "CGuiHandler.h"
-#include "CAnimation.h"
+#include "../renderSDL/CursorSoftware.h"
+#include "../renderSDL/CursorHardware.h"
+#include "../render/CAnimation.h"
+#include "../render/IImage.h"
+#include "../renderSDL/SDL_Extensions.h"
+#include "../CMT.h"
+
 #include "../../lib/CConfigHandler.h"
 
+#include <SDL_render.h>
+#include <SDL_events.h>
+
+#ifdef VCMI_APPLE
+#include <dispatch/dispatch.h>
+#endif
+
 std::unique_ptr<ICursor> CursorHandler::createCursor()
 {
 	if (settings["video"]["cursor"].String() == "auto")
@@ -256,7 +268,7 @@ void CursorHandler::centerCursor()
 
 void CursorHandler::updateSpellcastCursor()
 {
-	static const float frameDisplayDuration = 0.1f;
+	static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
 
 	frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
 	size_t newFrame = frame;
@@ -304,136 +316,3 @@ void CursorHandler::show()
 	cursor->setVisible(true);
 }
 
-void CursorSoftware::render()
-{
-	//texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads
-	if (needUpdate)
-		updateTexture();
-
-	Point renderPos = pos - pivot;
-
-	SDL_Rect destRect;
-	destRect.x = renderPos.x;
-	destRect.y = renderPos.y;
-	destRect.w = 40;
-	destRect.h = 40;
-
-	SDL_RenderCopy(mainRenderer, cursorTexture, nullptr, &destRect);
-}
-
-void CursorSoftware::createTexture(const Point & dimensions)
-{
-	if(cursorTexture)
-		SDL_DestroyTexture(cursorTexture);
-
-	if (cursorSurface)
-		SDL_FreeSurface(cursorSurface);
-
-	cursorSurface = CSDL_Ext::newSurface(dimensions.x, dimensions.y);
-	cursorTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
-
-	SDL_SetSurfaceBlendMode(cursorSurface, SDL_BLENDMODE_NONE);
-	SDL_SetTextureBlendMode(cursorTexture, SDL_BLENDMODE_BLEND);
-}
-
-void CursorSoftware::updateTexture()
-{
-	Point dimensions(-1, -1);
-
-	if (!cursorSurface ||  Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions())
-		createTexture(cursorImage->dimensions());
-
-	CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
-
-	cursorImage->draw(cursorSurface);
-	SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch);
-	needUpdate = false;
-}
-
-void CursorSoftware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
-{
-	assert(image != nullptr);
-	cursorImage = image;
-	pivot = pivotOffset;
-	needUpdate = true;
-}
-
-void CursorSoftware::setCursorPosition( const Point & newPos )
-{
-	pos = newPos;
-}
-
-void CursorSoftware::setVisible(bool on)
-{
-	visible = on;
-}
-
-CursorSoftware::CursorSoftware():
-	cursorTexture(nullptr),
-	cursorSurface(nullptr),
-	needUpdate(false),
-	visible(false),
-	pivot(0,0)
-{
-	SDL_ShowCursor(SDL_DISABLE);
-}
-
-CursorSoftware::~CursorSoftware()
-{
-	if(cursorTexture)
-		SDL_DestroyTexture(cursorTexture);
-
-	if (cursorSurface)
-		SDL_FreeSurface(cursorSurface);
-}
-
-CursorHardware::CursorHardware():
-	cursor(nullptr)
-{
-	SDL_ShowCursor(SDL_DISABLE);
-}
-
-CursorHardware::~CursorHardware()
-{
-	if(cursor)
-		SDL_FreeCursor(cursor);
-}
-
-void CursorHardware::setVisible(bool on)
-{
-	if (on)
-		SDL_ShowCursor(SDL_ENABLE);
-	else
-		SDL_ShowCursor(SDL_DISABLE);
-}
-
-void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
-{
-	auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y);
-
-	CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
-
-	image->draw(cursorSurface);
-
-	auto oldCursor = cursor;
-	cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
-
-	if (!cursor)
-		logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
-
-	SDL_FreeSurface(cursorSurface);
-	SDL_SetCursor(cursor);
-
-	if (oldCursor)
-		SDL_FreeCursor(oldCursor);
-}
-
-void CursorHardware::setCursorPosition( const Point & newPos )
-{
-	//no-op
-}
-
-void CursorHardware::render()
-{
-	//no-op
-}

+ 4 - 57
client/gui/CursorHandler.h

@@ -9,14 +9,12 @@
  */
 #pragma once
 
-class CAnimation;
-class IImage;
-struct SDL_Surface;
-struct SDL_Texture;
-struct SDL_Cursor;
-
 #include "../../lib/Point.h"
 
+class ICursor;
+class IImage;
+class CAnimation;
+
 namespace Cursor
 {
 	enum class Type {
@@ -112,57 +110,6 @@ namespace Cursor
 	};
 }
 
-class ICursor
-{
-public:
-	virtual ~ICursor() = default;
-
-	virtual void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) = 0;
-	virtual void setCursorPosition( const Point & newPos ) = 0;
-	virtual void render() = 0;
-	virtual void setVisible( bool on) = 0;
-};
-
-class CursorHardware : public ICursor
-{
-	std::shared_ptr<IImage> cursorImage;
-
-	SDL_Cursor * cursor;
-
-public:
-	CursorHardware();
-	~CursorHardware();
-
-	void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
-	void setCursorPosition( const Point & newPos ) override;
-	void render() override;
-	void setVisible( bool on) override;
-};
-
-class CursorSoftware : public ICursor
-{
-	std::shared_ptr<IImage> cursorImage;
-
-	SDL_Texture * cursorTexture;
-	SDL_Surface * cursorSurface;
-
-	Point pos;
-	Point pivot;
-	bool needUpdate;
-	bool visible;
-
-	void createTexture(const Point & dimensions);
-	void updateTexture();
-public:
-	CursorSoftware();
-	~CursorSoftware();
-
-	void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
-	void setCursorPosition( const Point & newPos ) override;
-	void render() override;
-	void setVisible( bool on) override;
-};
-
 /// handles mouse cursor
 class CursorHandler final
 {

+ 0 - 395
client/gui/Fonts.cpp

@@ -1,395 +0,0 @@
-/*
- * Fonts.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 "Fonts.h"
-
-#include <SDL_ttf.h>
-
-#include "SDL_Pixels.h"
-#include "../../lib/JsonNode.h"
-#include "../../lib/vcmi_endian.h"
-#include "../../lib/filesystem/Filesystem.h"
-#include "../../lib/CGeneralTextHandler.h"
-
-size_t IFont::getStringWidth(const std::string & data) const
-{
-	size_t width = 0;
-
-	for(size_t i=0; i<data.size(); i += Unicode::getCharacterSize(data[i]))
-	{
-		width += getGlyphWidth(data.data() + i);
-	}
-	return width;
-}
-
-void IFont::renderTextLeft(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	renderText(surface, data, color, pos);
-}
-
-void IFont::renderTextRight(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	Point size((int)getStringWidth(data), (int)getLineHeight());
-	renderText(surface, data, color, pos - size);
-}
-
-void IFont::renderTextCenter(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	Point size((int)getStringWidth(data), (int)getLineHeight());
-	renderText(surface, data, color, pos - size / 2);
-}
-
-void IFont::renderTextLinesLeft(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const
-{
-	Point currPos = pos;
-
-	for(const std::string & line : data)
-	{
-		renderTextLeft(surface, line, color, currPos);
-		currPos.y += (int)getLineHeight();
-	}
-}
-
-void IFont::renderTextLinesRight(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const
-{
-	Point currPos = pos;
-	currPos.y -= (int)data.size() * (int)getLineHeight();
-
-	for(const std::string & line : data)
-	{
-		renderTextRight(surface, line, color, currPos);
-		currPos.y += (int)getLineHeight();
-	}
-}
-
-void IFont::renderTextLinesCenter(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const
-{
-	Point currPos = pos;
-	currPos.y -= (int)data.size() * (int)getLineHeight() / 2;
-
-	for(const std::string & line : data)
-	{
-		renderTextCenter(surface, line, color, currPos);
-		currPos.y += (int)getLineHeight();
-	}
-}
-
-std::array<CBitmapFont::BitmapChar, CBitmapFont::totalChars> CBitmapFont::loadChars() const
-{
-	std::array<BitmapChar, totalChars> ret;
-
-	size_t offset = 32;
-
-	for (auto & elem : ret)
-	{
-		elem.leftOffset =  read_le_u32(data.first.get() + offset); offset+=4;
-		elem.width =       read_le_u32(data.first.get() + offset); offset+=4;
-		elem.rightOffset = read_le_u32(data.first.get() + offset); offset+=4;
-	}
-
-	for (auto & elem : ret)
-	{
-		int pixelOffset =  read_le_u32(data.first.get() + offset); offset+=4;
-		elem.pixels = data.first.get() + 4128 + pixelOffset;
-
-		assert(pixelOffset + 4128 < data.second);
-	}
-	return ret;
-}
-
-CBitmapFont::CBitmapFont(const std::string & filename):
-	data(CResourceHandler::get()->load(ResourceID("data/" + filename, EResType::BMP_FONT))->readAll()),
-	chars(loadChars()),
-	height(data.first.get()[5])
-{}
-
-size_t CBitmapFont::getLineHeight() const
-{
-	return height;
-}
-
-size_t CBitmapFont::getGlyphWidth(const char * data) const
-{
-	std::string localChar = Unicode::fromUnicode(std::string(data, Unicode::getCharacterSize(data[0])));
-
-	if (localChar.size() == 1)
-	{
-		const BitmapChar & ch = chars[ui8(localChar[0])];
-		return ch.leftOffset + ch.width + ch.rightOffset;
-	}
-	return 0;
-}
-
-void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const
-{
-	Rect clipRect;
-	CSDL_Ext::getClipRect(surface, clipRect);
-
-	posX += character.leftOffset;
-
-	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
-
-	Uint8 bpp = surface->format->BytesPerPixel;
-
-	// start of line, may differ from 0 due to end of surface or clipped surface
-	int lineBegin = std::max<int>(0, clipRect.y - posY);
-	int lineEnd   = std::min<int>(height, clipRect.y + clipRect.h - posY - 1);
-
-	// start end end of each row, may differ from 0
-	int rowBegin = std::max<int>(0, clipRect.x - posX);
-	int rowEnd   = std::min<int>(character.width, clipRect.x + clipRect.w - posX - 1);
-
-	//for each line in symbol
-	for(int dy = lineBegin; dy <lineEnd; dy++)
-	{
-		Uint8 *dstLine = (Uint8*)surface->pixels;
-		Uint8 *srcLine = character.pixels;
-
-		// shift source\destination pixels to current position
-		dstLine += (posY+dy) * surface->pitch + posX * bpp;
-		srcLine += dy * character.width;
-
-		//for each column in line
-		for(int dx = rowBegin; dx < rowEnd; dx++)
-		{
-			Uint8* dstPixel = dstLine + dx*bpp;
-			switch(srcLine[dx])
-			{
-			case 1: //black "shadow"
-				colorPutter(dstPixel, 0, 0, 0);
-				break;
-			case 255: //text colour
-				colorPutter(dstPixel, color.r, color.g, color.b);
-				break;
-			default :
-				break; //transparency
-			}
-		}
-	}
-	posX += character.width;
-	posX += character.rightOffset;
-}
-
-void CBitmapFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	if (data.empty())
-		return;
-
-	assert(surface);
-
-	int posX = pos.x;
-	int posY = pos.y;
-
-	// Should be used to detect incorrect text parsing. Disabled right now due to some old UI code (mostly pregame and battles)
-	//assert(data[0] != '{');
-	//assert(data[data.size()-1] != '}');
-
-	SDL_LockSurface(surface);
-
-	for(size_t i=0; i<data.size(); i += Unicode::getCharacterSize(data[i]))
-	{
-		std::string localChar = Unicode::fromUnicode(data.substr(i, Unicode::getCharacterSize(data[i])));
-
-		if (localChar.size() == 1)
-			renderCharacter(surface, chars[ui8(localChar[0])], color, posX, posY);
-	}
-	SDL_UnlockSurface(surface);
-}
-
-std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode & config)
-{
-	std::string filename = "Data/" + config["file"].String();
-	return CResourceHandler::get()->load(ResourceID(filename, EResType::TTF_FONT))->readAll();
-}
-
-TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
-{
-	int pointSize = static_cast<int>(config["size"].Float());
-
-	if(!TTF_WasInit() && TTF_Init()==-1)
-		throw std::runtime_error(std::string("Failed to initialize true type support: ") + TTF_GetError() + "\n");
-
-	return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), (int)data.second), 1, pointSize);
-}
-
-int CTrueTypeFont::getFontStyle(const JsonNode &config)
-{
-	const JsonVector & names = config["style"].Vector();
-	int ret = 0;
-	for(const JsonNode & node : names)
-	{
-		if (node.String() == "bold")
-			ret |= TTF_STYLE_BOLD;
-		else if (node.String() == "italic")
-			ret |= TTF_STYLE_ITALIC;
-	}
-	return ret;
-}
-
-CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
-	data(loadData(fontConfig)),
-	font(loadFont(fontConfig), TTF_CloseFont),
-	blended(fontConfig["blend"].Bool())
-{
-	assert(font);
-
-	TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
-}
-
-size_t CTrueTypeFont::getLineHeight() const
-{
-	return TTF_FontHeight(font.get());
-}
-
-size_t CTrueTypeFont::getGlyphWidth(const char *data) const
-{
-	return getStringWidth(std::string(data, Unicode::getCharacterSize(*data)));
-	/*
-	int advance;
-	TTF_GlyphMetrics(font.get(), *data, nullptr, nullptr, nullptr, nullptr, &advance);
-	return advance;
-	*/
-}
-
-size_t CTrueTypeFont::getStringWidth(const std::string & data) const
-{
-	int width;
-	TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr);
-	return width;
-}
-
-void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
-	{
-		SDL_Color black = { 0, 0, 0, SDL_ALPHA_OPAQUE};
-		renderText(surface, data, black, pos + Point(1,1));
-	}
-
-	if (!data.empty())
-	{
-		SDL_Surface * rendered;
-		if (blended)
-			rendered = TTF_RenderUTF8_Blended(font.get(), data.c_str(), color);
-		else
-			rendered = TTF_RenderUTF8_Solid(font.get(), data.c_str(), color);
-
-		assert(rendered);
-
-		CSDL_Ext::blitSurface(rendered, surface, pos);
-		SDL_FreeSurface(rendered);
-	}
-}
-
-size_t CBitmapHanFont::getCharacterDataOffset(size_t index) const
-{
-	size_t rowSize  = (size + 7) / 8; // 1 bit per pixel, rounded up
-	size_t charSize = rowSize * size; // glyph contains "size" rows
-	return index * charSize;
-}
-
-size_t CBitmapHanFont::getCharacterIndex(ui8 first, ui8 second) const
-{
-	if (second > 0x7f )
-		second--;
-
-	return (first - 0x81) * (12*16 - 2) + (second - 0x40);
-}
-
-void CBitmapHanFont::renderCharacter(SDL_Surface * surface, int characterIndex, const SDL_Color & color, int &posX, int &posY) const
-{
-	//TODO: somewhat duplicated with CBitmapFont::renderCharacter();
-	Rect clipRect;
-	CSDL_Ext::getClipRect(surface, clipRect);
-
-	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
-	Uint8 bpp = surface->format->BytesPerPixel;
-
-	// start of line, may differ from 0 due to end of surface or clipped surface
-	int lineBegin = std::max<int>(0, clipRect.y - posY);
-	int lineEnd   = std::min((int)size, clipRect.y + clipRect.h - posY);
-
-	// start end end of each row, may differ from 0
-	int rowBegin = std::max<int>(0, clipRect.x - posX);
-	int rowEnd   = std::min<int>((int)size, clipRect.x + clipRect.w - posX);
-
-	//for each line in symbol
-	for(int dy = lineBegin; dy <lineEnd; dy++)
-	{
-		Uint8 *dstLine = (Uint8*)surface->pixels;
-		Uint8 *source = data.first.get() + getCharacterDataOffset(characterIndex);
-
-		// shift source\destination pixels to current position
-		dstLine += (posY+dy) * surface->pitch + posX * bpp;
-		source += ((size + 7) / 8) * dy;
-
-		//for each column in line
-		for(int dx = rowBegin; dx < rowEnd; dx++)
-		{
-			// select current bit in bitmap
-			int bit = (source[dx / 8] << (dx % 8)) & 0x80;
-
-			Uint8* dstPixel = dstLine + dx*bpp;
-			if (bit != 0)
-				colorPutter(dstPixel, color.r, color.g, color.b);
-		}
-	}
-	posX += (int)size + 1;
-}
-
-void CBitmapHanFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
-{
-	int posX = pos.x;
-	int posY = pos.y;
-
-	SDL_LockSurface(surface);
-
-	for(size_t i=0; i<data.size(); i += Unicode::getCharacterSize(data[i]))
-	{
-		std::string localChar = Unicode::fromUnicode(data.substr(i, Unicode::getCharacterSize(data[i])));
-
-		if (localChar.size() == 1)
-			fallback->renderCharacter(surface, fallback->chars[ui8(localChar[0])], color, posX, posY);
-
-		if (localChar.size() == 2)
-			renderCharacter(surface, (int)getCharacterIndex(localChar[0], localChar[1]), color, posX, posY);
-	}
-	SDL_UnlockSurface(surface);
-}
-
-CBitmapHanFont::CBitmapHanFont(const JsonNode &config):
-	fallback(new CBitmapFont(config["fallback"].String())),
-	data(CResourceHandler::get()->load(ResourceID("data/" + config["name"].String(), EResType::OTHER))->readAll()),
-	size((size_t)config["size"].Float())
-{
-	// basic tests to make sure that fonts are OK
-	// 1) fonts must contain 190 "sections", 126 symbols each.
-	assert(getCharacterIndex(0xfe, 0xff) == 190*126);
-	// 2) ensure that font size is correct - enough to fit all possible symbols
-	assert(getCharacterDataOffset(getCharacterIndex(0xfe, 0xff)) == data.second);
-}
-
-size_t CBitmapHanFont::getLineHeight() const
-{
-	return std::max(size + 1, fallback->getLineHeight());
-}
-
-size_t CBitmapHanFont::getGlyphWidth(const char * data) const
-{
-	std::string localChar = Unicode::fromUnicode(std::string(data, Unicode::getCharacterSize(data[0])));
-
-	if (localChar.size() == 1)
-		return fallback->getGlyphWidth(data);
-
-	if (localChar.size() == 2)
-		return size + 1;
-
-	return 0;
-}

+ 0 - 136
client/gui/Fonts.h

@@ -1,136 +0,0 @@
-/*
- * Fonts.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 JsonNode;
-class Point;
-
-VCMI_LIB_NAMESPACE_END
-
-struct SDL_Surface;
-struct SDL_Color;
-
-typedef struct _TTF_Font TTF_Font;
-
-class CBitmapFont;
-class CBitmapHanFont;
-
-class IFont
-{
-protected:
-	/// Internal function to render font, see renderTextLeft
-	virtual void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const = 0;
-
-public:
-	virtual ~IFont()
-	{}
-
-	/// Returns height of font
-	virtual size_t getLineHeight() const = 0;
-	/// Returns width, in pixels of a character glyph. Pointer must contain at least characterSize valid bytes
-	virtual size_t getGlyphWidth(const char * data) const = 0;
-	/// Return width of the string
-	virtual size_t getStringWidth(const std::string & data) const;
-
-	/**
-	 * @param surface - destination to print text on
-	 * @param data - string to print
-	 * @param color - font color
-	 * @param pos - position of rendered font
-	 */
-	/// pos = topleft corner of the text
-	void renderTextLeft(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const;
-	/// pos = center of the text
-	void renderTextRight(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const;
-	/// pos = bottomright corner of the text
-	void renderTextCenter(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const;
-
-	/// pos = topleft corner of the text
-	void renderTextLinesLeft(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const;
-	/// pos = center of the text
-	void renderTextLinesRight(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const;
-	/// pos = bottomright corner of the text
-	void renderTextLinesCenter(SDL_Surface * surface, const std::vector<std::string> & data, const SDL_Color & color, const Point & pos) const;
-};
-
-class CBitmapFont : public IFont
-{
-	static const size_t totalChars = 256;
-
-	struct BitmapChar
-	{
-		si32 leftOffset;
-		ui32 width;
-		si32 rightOffset;
-		ui8 *pixels; // pixels of this character, part of BitmapFont::data
-	};
-
-	const std::pair<std::unique_ptr<ui8[]>, ui64> data;
-
-	const std::array<BitmapChar, totalChars> chars;
-	const ui8 height;
-
-	std::array<BitmapChar, totalChars> loadChars() const;
-
-	void renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const;
-
-	void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override;
-public:
-	CBitmapFont(const std::string & filename);
-
-	size_t getLineHeight() const override;
-	size_t getGlyphWidth(const char * data) const override;
-
-	friend class CBitmapHanFont;
-};
-
-/// supports multi-byte characters for such languages like Chinese
-class CBitmapHanFont : public IFont
-{
-	std::unique_ptr<CBitmapFont> fallback;
-	// data, directly copied from file
-	const std::pair<std::unique_ptr<ui8[]>, ui64> data;
-
-	// size of the font. Not available in file but needed for proper rendering
-	const size_t size;
-
-	size_t getCharacterDataOffset(size_t index) const;
-	size_t getCharacterIndex(ui8 first, ui8 second) const;
-
-	void renderCharacter(SDL_Surface * surface, int characterIndex, const SDL_Color & color, int &posX, int &posY) const;
-	void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override;
-public:
-	CBitmapHanFont(const JsonNode & config);
-
-	size_t getLineHeight() const override;
-	size_t getGlyphWidth(const char * data) const override;
-};
-
-class CTrueTypeFont : public IFont
-{
-	const std::pair<std::unique_ptr<ui8[]>, ui64> data;
-
-	const std::unique_ptr<TTF_Font, void (*)(TTF_Font*)> font;
-	const bool blended;
-
-	std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
-	TTF_Font * loadFont(const JsonNode & config);
-	int getFontStyle(const JsonNode & config);
-
-	void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override;
-public:
-	CTrueTypeFont(const JsonNode & fontConfig);
-
-	size_t getLineHeight() const override;
-	size_t getGlyphWidth(const char * data) const override;
-	size_t getStringWidth(const std::string & data) const override;
-};

+ 0 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -14,7 +14,6 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"

+ 0 - 25
client/gui/SDL_Compat.h

@@ -1,25 +0,0 @@
-/*
- * SDL_Compat.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 <SDL_version.h>
-
-#if (SDL_MAJOR_VERSION == 2)
-
-#include <SDL_keycode.h>
-typedef int SDLX_Coord;
-typedef int SDLX_Size;
-
-
-#else
-#error "unknown or unsupported SDL version"
-#endif
- 
-

+ 3 - 1
client/ios/GameChatKeyboardHandler.h

@@ -10,11 +10,13 @@
 
 #import <UIKit/UIKit.h>
 
+#include <SDL_events.h>
+
 NS_ASSUME_NONNULL_BEGIN
 
 @interface GameChatKeyboardHandler : NSObject
 
-- (void)triggerInput;
++ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode;
 
 @end
 

+ 15 - 15
client/ios/GameChatKeyboardHandler.m

@@ -10,49 +10,49 @@
 
 #import "GameChatKeyboardHandler.h"
 
-#include <SDL_events.h>
-
 static int watchReturnKey(void * userdata, SDL_Event * event);
 
-static void sendKeyEvent(SDL_KeyCode keyCode)
+
+@interface GameChatKeyboardHandler ()
+@property (nonatomic) BOOL wasChatMessageSent;
+@end
+
+@implementation GameChatKeyboardHandler
+
++ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode
 {
 	SDL_Event keyEvent;
 	keyEvent.key = (SDL_KeyboardEvent){
 		.type = SDL_KEYDOWN,
+		.state = SDL_PRESSED,
 		.keysym.sym = keyCode,
 	};
 	SDL_PushEvent(&keyEvent);
 }
 
+- (instancetype)init {
+	self = [super init];
 
-@interface GameChatKeyboardHandler ()
-@property (nonatomic) BOOL wasChatMessageSent;
-@end
-
-@implementation GameChatKeyboardHandler
-
-- (void)triggerInput {
 	__auto_type notificationCenter = NSNotificationCenter.defaultCenter;
 	[notificationCenter addObserver:self selector:@selector(textDidBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
 	[notificationCenter addObserver:self selector:@selector(textDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
 
-	self.wasChatMessageSent = NO;
-	sendKeyEvent(SDLK_TAB);
+	return self;
 }
 
 #pragma mark - Notifications
 
 - (void)textDidBeginEditing:(NSNotification *)n {
+	self.wasChatMessageSent = NO;
+
 	// watch for pressing Return to ignore sending Escape key after keyboard is closed
 	SDL_AddEventWatch(watchReturnKey, (__bridge void *)self);
 }
 
 - (void)textDidEndEditing:(NSNotification *)n {
-	[NSNotificationCenter.defaultCenter removeObserver:self];
-
 	// discard chat message
 	if(!self.wasChatMessageSent)
-		sendKeyEvent(SDLK_ESCAPE);
+		[[self class] sendKeyEventWithKeyCode:SDLK_ESCAPE];
 }
 
 @end

+ 1 - 1
client/ios/startSDL.mm

@@ -95,7 +95,7 @@
 - (void)handlePinch:(UIGestureRecognizer *)gesture {
     if(gesture.state != UIGestureRecognizerStateBegan || CSH->state != EClientState::GAMEPLAY)
         return;
-	[self.gameChatHandler triggerInput];
+	[GameChatKeyboardHandler sendKeyEventWithKeyCode:SDLK_SPACE];
 }
 
 #pragma mark - UIGestureRecognizerDelegate

+ 7 - 9
client/lobby/CBonusSelection.cpp

@@ -17,13 +17,10 @@
 #include "CSelectionBase.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
-#include "../gui/CGuiHandler.h"
 #include "../mainmenu/CMainMenu.h"
 #include "../mainmenu/CPrologEpilogVideo.h"
 #include "../widgets/CComponent.h"
@@ -33,6 +30,9 @@
 #include "../widgets/TextControls.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
+#include "../render/IImage.h"
+#include "../render/CAnimation.h"
+#include "../gui/CGuiHandler.h"
 
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -119,7 +119,6 @@ CBonusSelection::CBonusSelection()
 void CBonusSelection::loadPositionsOfGraphics()
 {
 	const JsonNode config(ResourceID("config/campaign_regions.json"));
-	int idx = 0;
 
 	for(const JsonNode & campaign : config["campaign_regions"].Vector())
 	{
@@ -140,7 +139,6 @@ void CBonusSelection::loadPositionsOfGraphics()
 
 		campDescriptions.push_back(sc);
 
-		idx++;
 	}
 }
 
@@ -494,8 +492,8 @@ CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, cons
 	graphicsSelected->disable();
 	graphicsStriped = std::make_shared<CPicture>(prefix + "Co" + suffix + ".BMP");
 	graphicsStriped->disable();
-	pos.w = graphicsNotSelected->bg->w;
-	pos.h = graphicsNotSelected->bg->h;
+	pos.w = graphicsNotSelected->pos.w;
+	pos.h = graphicsNotSelected->pos.h;
 
 }
 
@@ -527,7 +525,7 @@ void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
 	if(indeterminate(down))
 		return;
 
-	if(!down && selectable && !CSDL_Ext::isTransparent(graphicsNotSelected->getSurface(), GH.current->motion.x - pos.x, GH.current->motion.y - pos.y))
+	if(!down && selectable && !graphicsNotSelected->getSurface()->isTransparent(GH.getCursorPosition() - pos.topLeft()))
 	{
 		CSH->setCampaignMap(idOfMapAndRegion);
 	}
@@ -537,7 +535,7 @@ void CBonusSelection::CRegion::clickRight(tribool down, bool previousState)
 {
 	// FIXME: For some reason "down" is only ever contain indeterminate_value
 	auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
-	if(!CSDL_Ext::isTransparent(graphicsNotSelected->getSurface(), GH.current->motion.x - pos.x, GH.current->motion.y - pos.y) && text.size())
+	if(!graphicsNotSelected->getSurface()->isTransparent(GH.getCursorPosition() - pos.topLeft()) && text.size())
 	{
 		CRClickPopup::createAndPush(text);
 	}

+ 0 - 2
client/lobby/CCampaignInfoScreen.cpp

@@ -15,10 +15,8 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/mapping/CMapInfo.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CPlayerInterface.h"
 
 CCampaignInfoScreen::CCampaignInfoScreen()

+ 4 - 3
client/lobby/CSelectionBase.cpp

@@ -20,12 +20,10 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../mainmenu/CMainMenu.h"
 #include "../widgets/CComponent.h"
@@ -35,6 +33,7 @@
 #include "../widgets/TextControls.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
+#include "../render/CAnimation.h"
 
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -44,6 +43,8 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/serializer/Connection.h"
 
+#include <SDL_events.h>
+
 ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
 	: screenType(ScreenType)
 {
@@ -312,7 +313,7 @@ CChatBox::CChatBox(const Rect & rect)
 	type |= REDRAW_PARENT;
 
 	const int height = static_cast<int>(graphics->fonts[FONT_SMALL]->getLineHeight());
-	inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height));
+	inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height), EFonts::FONT_SMALL, 0);
 	inputBox->removeUsedEvents(KEYBOARD);
 	chatHistory = std::make_shared<CTextBox>("", Rect(0, 0, rect.w, rect.h - height), 1);
 

+ 0 - 1
client/lobby/OptionsTab.cpp

@@ -14,7 +14,6 @@
 
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"

+ 0 - 1
client/lobby/RandomMapTab.cpp

@@ -14,7 +14,6 @@
 
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"

+ 6 - 7
client/lobby/SelectionTab.cpp

@@ -14,10 +14,8 @@
 #include "CLobbyScreen.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
@@ -26,6 +24,7 @@
 #include "../widgets/TextControls.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
+#include "../render/CAnimation.h"
 
 #include "../../CCallback.h"
 
@@ -36,6 +35,7 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/serializer/Connection.h"
 
+#include <SDL_events.h>
 
 bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
 {
@@ -273,9 +273,9 @@ void SelectionTab::clickLeft(tribool down, bool previousState)
 			select(line);
 		}
 #ifdef VCMI_IOS
-        // focus input field if clicked inside it
-		else if(inputName && inputName->active && inputNameRect.isInside(GH.current->button.x, GH.current->button.y))
-            inputName->giveFocus();
+		// focus input field if clicked inside it
+		else if(inputName && inputName->active && inputNameRect.isInside(GH.getCursorPosition()))
+			inputName->giveFocus();
 #endif
 	}
 }
@@ -454,8 +454,7 @@ void SelectionTab::updateListItems()
 int SelectionTab::getLine()
 {
 	int line = -1;
-	Point clickPos(GH.current->button.x, GH.current->button.y);
-	clickPos = clickPos - pos.topLeft();
+	Point clickPos = GH.getCursorPosition() - pos.topLeft();
 
 	// Ignore clicks on save name area
 	int maxPosY;

+ 0 - 2
client/mainmenu/CCampaignScreen.cpp

@@ -14,12 +14,10 @@
 #include "CMainMenu.h"
 
 #include "../CGameInfo.h"
-#include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
-#include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"

+ 1 - 13
client/mainmenu/CMainMenu.cpp

@@ -20,7 +20,6 @@
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CCompressedStream.h"
 
-#include "../gui/SDL_Extensions.h"
 #include "../gui/CursorHandler.h"
 
 #include "../CGameInfo.h"
@@ -28,7 +27,6 @@
 #include "../../lib/JsonNode.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
-#include "../Graphics.h"
 #include "../../lib/serializer/Connection.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/VCMIDirs.h"
@@ -36,10 +34,8 @@
 #include "../windows/GUIClasses.h"
 #include "../CPlayerInterface.h"
 #include "../../CCallback.h"
-#include "../CMessage.h"
 #include "../Client.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/CAnimation.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/MiscWidgets.h"
@@ -56,6 +52,7 @@
 #include "../../lib/CondSh.h"
 #include "../../lib/mapping/CCampaignHandler.h"
 
+#include <SDL_events.h>
 
 namespace fs = boost::filesystem;
 
@@ -76,11 +73,7 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
 
 	background = std::make_shared<CPicture>(config["background"].String());
 	if(config["scalable"].Bool())
-	{
-		if(background->bg->format->palette)
-			background->convertToScreenBPP();
 		background->scaleTo(Point(screen->w, screen->h));
-	}
 
 	pos = background->center();
 
@@ -439,11 +432,6 @@ CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen S
 
 void CMultiPlayers::onChange(std::string newText)
 {
-	size_t namesCount = 0;
-
-	for(auto & elem : inputNames)
-		if(!elem->getText().empty())
-			namesCount++;
 }
 
 void CMultiPlayers::enterSelectionScreen()

+ 377 - 0
client/render/CAnimation.cpp

@@ -0,0 +1,377 @@
+/*
+ * CAnimation.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 "CAnimation.h"
+
+#include "CDefFile.h"
+
+#include "Graphics.h"
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/JsonNode.h"
+#include "../renderSDL/SDLImage.h"
+
+std::shared_ptr<IImage> CAnimation::getFromExtraDef(std::string filename)
+{
+	size_t pos = filename.find(':');
+	if (pos == -1)
+		return nullptr;
+	CAnimation anim(filename.substr(0, pos));
+	pos++;
+	size_t frame = atoi(filename.c_str()+pos);
+	size_t group = 0;
+	pos = filename.find(':', pos);
+	if (pos != -1)
+	{
+		pos++;
+		group = frame;
+		frame = atoi(filename.c_str()+pos);
+	}
+	anim.load(frame ,group);
+	auto ret = anim.images[group][frame];
+	anim.images.clear();
+	return ret;
+}
+
+bool CAnimation::loadFrame(size_t frame, size_t group)
+{
+	if(size(group) <= frame)
+	{
+		printError(frame, group, "LoadFrame");
+		return false;
+	}
+
+	auto image = getImage(frame, group, false);
+	if(image)
+	{
+		return true;
+	}
+
+	//try to get image from def
+	if(source[group][frame].getType() == JsonNode::JsonType::DATA_NULL)
+	{
+		if(defFile)
+		{
+			auto frameList = defFile->getEntries();
+
+			if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present
+			{
+				images[group][frame] = std::make_shared<SDLImage>(defFile.get(), frame, group);
+				return true;
+			}
+		}
+		// still here? image is missing
+
+		printError(frame, group, "LoadFrame");
+		images[group][frame] = std::make_shared<SDLImage>("DEFAULT");
+	}
+	else //load from separate file
+	{
+		auto img = getFromExtraDef(source[group][frame]["file"].String());
+		if(!img)
+			img = std::make_shared<SDLImage>(source[group][frame]);
+
+		images[group][frame] = img;
+		return true;
+	}
+	return false;
+}
+
+bool CAnimation::unloadFrame(size_t frame, size_t group)
+{
+	auto image = getImage(frame, group, false);
+	if(image)
+	{
+		images[group].erase(frame);
+
+		if(images[group].empty())
+			images.erase(group);
+		return true;
+	}
+	return false;
+}
+
+void CAnimation::initFromJson(const JsonNode & config)
+{
+	std::string basepath;
+	basepath = config["basepath"].String();
+
+	JsonNode base(JsonNode::JsonType::DATA_STRUCT);
+	base["margins"] = config["margins"];
+	base["width"] = config["width"];
+	base["height"] = config["height"];
+
+	for(const JsonNode & group : config["sequences"].Vector())
+	{
+		size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
+		source[groupID].clear();
+
+		for(const JsonNode & frame : group["frames"].Vector())
+		{
+			JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+			JsonUtils::inherit(toAdd, base);
+			toAdd["file"].String() = basepath + frame.String();
+			source[groupID].push_back(toAdd);
+		}
+	}
+
+	for(const JsonNode & node : config["images"].Vector())
+	{
+		size_t group = node["group"].Integer();
+		size_t frame = node["frame"].Integer();
+
+		if (source[group].size() <= frame)
+			source[group].resize(frame+1);
+
+		JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+		JsonUtils::inherit(toAdd, base);
+		toAdd["file"].String() = basepath + node["file"].String();
+		source[group][frame] = toAdd;
+	}
+}
+
+void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
+{
+	if(images.empty())
+	{
+		logGlobal->error("Nothing to export, animation is empty");
+		return;
+	}
+
+	boost::filesystem::path actualPath = path / "SPRITES" / name;
+	boost::filesystem::create_directories(actualPath);
+
+	size_t counter = 0;
+
+	for(const auto & groupPair : images)
+	{
+		size_t group = groupPair.first;
+
+		for(const auto & imagePair : groupPair.second)
+		{
+			size_t frame = imagePair.first;
+			const auto img = imagePair.second;
+
+			boost::format fmt("%d_%d.bmp");
+			fmt % group % frame;
+
+			img->exportBitmap(actualPath / fmt.str());
+			counter++;
+		}
+	}
+
+	logGlobal->info("Exported %d frames to %s", counter, actualPath.string());
+}
+
+void CAnimation::init()
+{
+	if(defFile)
+	{
+		const std::map<size_t, size_t> defEntries = defFile->getEntries();
+
+		for (auto & defEntry : defEntries)
+			source[defEntry.first].resize(defEntry.second);
+	}
+
+	ResourceID resID(std::string("SPRITES/") + name, EResType::TEXT);
+
+	if (vstd::contains(graphics->imageLists, resID.getName()))
+		initFromJson(graphics->imageLists[resID.getName()]);
+
+	auto configList = CResourceHandler::get()->getResourcesWithName(resID);
+
+	for(auto & loader : configList)
+	{
+		auto stream = loader->load(resID);
+		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
+		stream->read(textData.get(), stream->getSize());
+
+		const JsonNode config((char*)textData.get(), stream->getSize());
+
+		initFromJson(config);
+	}
+}
+
+void CAnimation::printError(size_t frame, size_t group, std::string type) const
+{
+	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame);
+}
+
+CAnimation::CAnimation(std::string Name):
+	name(Name),
+	preloaded(false),
+	defFile()
+{
+	size_t dotPos = name.find_last_of('.');
+	if ( dotPos!=-1 )
+		name.erase(dotPos);
+	std::transform(name.begin(), name.end(), name.begin(), toupper);
+
+	ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION);
+
+	if(CResourceHandler::get()->existsResource(resource))
+		defFile = std::make_shared<CDefFile>(name);
+
+	init();
+
+	if(source.empty())
+		logAnim->error("Animation %s failed to load", Name);
+}
+
+CAnimation::CAnimation():
+	name(""),
+	preloaded(false),
+	defFile()
+{
+	init();
+}
+
+CAnimation::~CAnimation() = default;
+
+void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup)
+{
+	if(!source.count(sourceGroup))
+	{
+		logAnim->error("Group %d missing in %s", sourceGroup, name);
+		return;
+	}
+
+	if(source[sourceGroup].size() <= sourceFrame)
+	{
+		logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name);
+		return;
+	}
+
+	//todo: clone actual loaded Image object
+	JsonNode clone(source[sourceGroup][sourceFrame]);
+
+	if(clone.getType() == JsonNode::JsonType::DATA_NULL)
+	{
+		std::string temp =  name+":"+boost::lexical_cast<std::string>(sourceGroup)+":"+boost::lexical_cast<std::string>(sourceFrame);
+		clone["file"].String() = temp;
+	}
+
+	source[targetGroup].push_back(clone);
+
+	size_t index = source[targetGroup].size() - 1;
+
+	if(preloaded)
+		load(index, targetGroup);
+}
+
+void CAnimation::setCustom(std::string filename, size_t frame, size_t group)
+{
+	if (source[group].size() <= frame)
+		source[group].resize(frame+1);
+	source[group][frame]["file"].String() = filename;
+	//FIXME: update image if already loaded
+}
+
+std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose) const
+{
+	auto groupIter = images.find(group);
+	if (groupIter != images.end())
+	{
+		auto imageIter = groupIter->second.find(frame);
+		if (imageIter != groupIter->second.end())
+			return imageIter->second;
+	}
+	if (verbose)
+		printError(frame, group, "GetImage");
+	return nullptr;
+}
+
+void CAnimation::load()
+{
+	for (auto & elem : source)
+		for (size_t image=0; image < elem.second.size(); image++)
+			loadFrame(image, elem.first);
+}
+
+void CAnimation::unload()
+{
+	for (auto & elem : source)
+		for (size_t image=0; image < elem.second.size(); image++)
+			unloadFrame(image, elem.first);
+
+}
+
+void CAnimation::preload()
+{
+	if(!preloaded)
+	{
+		preloaded = true;
+		load();
+	}
+}
+
+void CAnimation::loadGroup(size_t group)
+{
+	if (vstd::contains(source, group))
+		for (size_t image=0; image < source[group].size(); image++)
+			loadFrame(image, group);
+}
+
+void CAnimation::unloadGroup(size_t group)
+{
+	if (vstd::contains(source, group))
+		for (size_t image=0; image < source[group].size(); image++)
+			unloadFrame(image, group);
+}
+
+void CAnimation::load(size_t frame, size_t group)
+{
+	loadFrame(frame, group);
+}
+
+void CAnimation::unload(size_t frame, size_t group)
+{
+	unloadFrame(frame, group);
+}
+
+size_t CAnimation::size(size_t group) const
+{
+	auto iter = source.find(group);
+	if (iter != source.end())
+		return iter->second.size();
+	return 0;
+}
+
+void CAnimation::horizontalFlip()
+{
+	for(auto & group : images)
+		for(auto & image : group.second)
+			image.second->horizontalFlip();
+}
+
+void CAnimation::verticalFlip()
+{
+	for(auto & group : images)
+		for(auto & image : group.second)
+			image.second->verticalFlip();
+}
+
+void CAnimation::playerColored(PlayerColor player)
+{
+	for(auto & group : images)
+		for(auto & image : group.second)
+			image.second->playerColored(player);
+}
+
+void CAnimation::createFlippedGroup(const size_t sourceGroup, const size_t targetGroup)
+{
+	for(size_t frame = 0; frame < size(sourceGroup); ++frame)
+	{
+		duplicateImage(sourceGroup, frame, targetGroup);
+
+		auto image = getImage(frame, targetGroup);
+		image->verticalFlip();
+	}
+}
+

+ 1 - 92
client/gui/CAnimation.h → client/render/CAnimation.h

@@ -9,77 +9,14 @@
  */
 #pragma once
 
-#include "../../lib/vcmi_endian.h"
 #include "../../lib/GameConstants.h"
 
-#ifdef IN
-#undef IN
-#endif
-
-#ifdef OUT
-#undef OUT
-#endif
-
 VCMI_LIB_NAMESPACE_BEGIN
-
 class JsonNode;
-class Rect;
-class Point;
-
 VCMI_LIB_NAMESPACE_END
 
-struct SDL_Surface;
-struct SDL_Color;
 class CDefFile;
-class ColorFilter;
-
-/*
- * Base class for images, can be used for non-animation pictures as well
- */
-class IImage
-{
-public:
-	using SpecialPalette = std::array<SDL_Color, 7>;
-
-	//draws image on surface "where" at position
-	virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
-	virtual void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const = 0;
-
-	virtual std::shared_ptr<IImage> scaleFast(float scale) const = 0;
-
-	virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
-
-	//Change palette to specific player
-	virtual void playerColored(PlayerColor player)=0;
-
-	//set special color for flag
-	virtual void setFlagColor(PlayerColor player)=0;
-
-	//test transparency of specific pixel
-	virtual bool isTransparent(const Point & coords) const = 0;
-
-	virtual Point dimensions() const = 0;
-	int width() const;
-	int height() const;
-
-	//only indexed bitmaps, 16 colors maximum
-	virtual void shiftPalette(int from, int howMany) = 0;
-	virtual void adjustPalette(const ColorFilter & shifter) = 0;
-	virtual void resetPalette(int colorID) = 0;
-	virtual void resetPalette() = 0;
-
-	//only indexed bitmaps with 7 special colors
-	virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
-
-	virtual void horizontalFlip() = 0;
-	virtual void verticalFlip() = 0;
-
-	IImage();
-	virtual ~IImage();
-
-	/// loads image from specified file. Returns 0-sized images on failure
-	static std::shared_ptr<IImage> createFromFile( const std::string & path );
-};
+class IImage;
 
 /// Class for handling animation
 class CAnimation
@@ -154,31 +91,3 @@ public:
 	void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup);
 };
 
-const float DEFAULT_DELTA = 0.05f;
-
-class CFadeAnimation
-{
-public:
-	enum class EMode
-	{
-		NONE, IN, OUT
-	};
-private:
-	float delta;
-	SDL_Surface * fadingSurface;
-	bool fading;
-	float fadingCounter;
-	bool shouldFreeSurface;
-
-	float initialCounter() const;
-	bool isFinished() const;
-public:
-	EMode fadingMode;
-
-	CFadeAnimation();
-	~CFadeAnimation();
-	void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA);
-	void update();
-	void draw(SDL_Surface * targetSurface, const Point & targetPoint);
-	bool isFading() const { return fading; }
-};

+ 5 - 3
client/CBitmapHandler.cpp → client/render/CBitmapHandler.cpp

@@ -8,13 +8,15 @@
  *
  */
 #include "StdInc.h"
+#include "CBitmapHandler.h"
+
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../lib/filesystem/Filesystem.h"
-#include <SDL_image.h>
-#include "CBitmapHandler.h"
-#include "gui/SDL_Extensions.h"
 #include "../lib/vcmi_endian.h"
 
+#include <SDL_image.h>
+
 namespace BitmapHandler
 {
 	SDL_Surface * loadH3PCX(ui8 * data, size_t size);

+ 0 - 0
client/CBitmapHandler.h → client/render/CBitmapHandler.h


+ 358 - 0
client/render/CDefFile.cpp

@@ -0,0 +1,358 @@
+/*
+ * CDefFile.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 "CDefFile.h"
+
+#include "IImageLoader.h"
+
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/Point.h"
+
+#include <SDL_pixels.h>
+
+// Extremely simple file cache. TODO: smarter, more general solution
+class CFileCache
+{
+	static const int cacheSize = 50; //Max number of cached files
+	struct FileData
+	{
+		ResourceID             name;
+		size_t                 size;
+		std::unique_ptr<ui8[]> data;
+
+		std::unique_ptr<ui8[]> getCopy()
+		{
+			auto ret = std::unique_ptr<ui8[]>(new ui8[size]);
+			std::copy(data.get(), data.get() + size, ret.get());
+			return ret;
+		}
+		FileData(ResourceID name_, size_t size_, std::unique_ptr<ui8[]> data_):
+			name{std::move(name_)},
+			size{size_},
+			data{std::move(data_)}
+		{}
+	};
+
+	std::deque<FileData> cache;
+public:
+	std::unique_ptr<ui8[]> getCachedFile(ResourceID rid)
+	{
+		for(auto & file : cache)
+		{
+			if (file.name == rid)
+				return file.getCopy();
+		}
+		// Still here? Cache miss
+		if (cache.size() > cacheSize)
+			cache.pop_front();
+
+		auto data =  CResourceHandler::get()->load(rid)->readAll();
+
+		cache.emplace_back(std::move(rid), data.second, std::move(data.first));
+
+		return cache.back().getCopy();
+	}
+};
+
+enum class DefType : uint32_t
+{
+	SPELL = 0x40,
+	SPRITE = 0x41,
+	CREATURE = 0x42,
+	MAP = 0x43,
+	MAP_HERO = 0x44,
+	TERRAIN = 0x45,
+	CURSOR = 0x46,
+	INTERFACE = 0x47,
+	SPRITE_FRAME = 0x48,
+	BATTLE_HERO = 0x49
+};
+
+static CFileCache animationCache;
+
+/*************************************************************************
+ *  DefFile, class used for def loading                                  *
+ *************************************************************************/
+
+bool operator== (const SDL_Color & lhs, const SDL_Color & rhs)
+{
+	return (lhs.a == rhs.a) && (lhs.b == rhs.b) &&(lhs.g == rhs.g) &&(lhs.r == rhs.r);
+}
+
+CDefFile::CDefFile(std::string Name):
+	data(nullptr),
+	palette(nullptr)
+{
+	//First 8 colors in def palette used for transparency
+	static SDL_Color H3Palette[8] =
+	{
+		{   0,   0,   0,   0},// transparency                  ( used in most images )
+		{   0,   0,   0,  64},// shadow border                 ( used in battle, adventure map def's )
+		{   0,   0,   0,  64},// shadow border                 ( used in fog-of-war def's )
+		{   0,   0,   0, 128},// shadow body                   ( used in fog-of-war def's )
+		{   0,   0,   0, 128},// shadow body                   ( used in battle, adventure map def's )
+		{   0,   0,   0,   0},// selection                     ( used in battle def's )
+		{   0,   0,   0, 128},// shadow body   below selection ( used in battle def's )
+		{   0,   0,   0,  64} // shadow border below selection ( used in battle def's )
+	};
+	data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
+
+	palette = std::unique_ptr<SDL_Color[]>(new SDL_Color[256]);
+	int it = 0;
+
+	ui32 type = read_le_u32(data.get() + it);
+	it+=4;
+	//int width  = read_le_u32(data + it); it+=4;//not used
+	//int height = read_le_u32(data + it); it+=4;
+	it+=8;
+	ui32 totalBlocks = read_le_u32(data.get() + it);
+	it+=4;
+
+	for (ui32 i= 0; i<256; i++)
+	{
+		palette[i].r = data[it++];
+		palette[i].g = data[it++];
+		palette[i].b = data[it++];
+		palette[i].a = SDL_ALPHA_OPAQUE;
+	}
+
+	switch(static_cast<DefType>(type))
+	{
+	case DefType::SPELL:
+		palette[0] = H3Palette[0];
+		break;
+	case DefType::SPRITE:
+	case DefType::SPRITE_FRAME:
+		for(ui32 i= 0; i<8; i++)
+			palette[i] = H3Palette[i];
+		break;
+	case DefType::CREATURE:
+		palette[0] = H3Palette[0];
+		palette[1] = H3Palette[1];
+		palette[4] = H3Palette[4];
+		palette[5] = H3Palette[5];
+		palette[6] = H3Palette[6];
+		palette[7] = H3Palette[7];
+		break;
+	case DefType::MAP:
+	case DefType::MAP_HERO:
+		palette[0] = H3Palette[0];
+		palette[1] = H3Palette[1];
+		palette[4] = H3Palette[4];
+		//5 = owner flag, handled separately
+		break;
+	case DefType::TERRAIN:
+		palette[0] = H3Palette[0];
+		palette[1] = H3Palette[1];
+		palette[2] = H3Palette[2];
+		palette[3] = H3Palette[3];
+		palette[4] = H3Palette[4];
+		break;
+	case DefType::CURSOR:
+		palette[0] = H3Palette[0];
+		break;
+	case DefType::INTERFACE:
+		palette[0] = H3Palette[0];
+		palette[1] = H3Palette[1];
+		palette[4] = H3Palette[4];
+		//player colors handled separately
+		//TODO: disallow colorizing other def types
+		break;
+	case DefType::BATTLE_HERO:
+		palette[0] = H3Palette[0];
+		palette[1] = H3Palette[1];
+		palette[4] = H3Palette[4];
+		break;
+	default:
+		logAnim->error("Unknown def type %d in %s", type, Name);
+		break;
+	}
+
+
+	for (ui32 i=0; i<totalBlocks; i++)
+	{
+		size_t blockID = read_le_u32(data.get() + it);
+		it+=4;
+		size_t totalEntries = read_le_u32(data.get() + it);
+		it+=12;
+		//8 unknown bytes - skipping
+
+		//13 bytes for name of every frame in this block - not used, skipping
+		it+= 13 * (int)totalEntries;
+
+		for (ui32 j=0; j<totalEntries; j++)
+		{
+			size_t currOffset = read_le_u32(data.get() + it);
+			offset[blockID].push_back(currOffset);
+			it += 4;
+		}
+	}
+}
+
+void CDefFile::loadFrame(size_t frame, size_t group, IImageLoader &loader) const
+{
+	std::map<size_t, std::vector <size_t> >::const_iterator it;
+	it = offset.find(group);
+	assert (it != offset.end());
+
+	const ui8 * FDef = data.get()+it->second[frame];
+
+	const SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(FDef);
+	SSpriteDef sprite;
+
+	sprite.format = read_le_u32(&sd.format);
+	sprite.fullWidth = read_le_u32(&sd.fullWidth);
+	sprite.fullHeight = read_le_u32(&sd.fullHeight);
+	sprite.width = read_le_u32(&sd.width);
+	sprite.height = read_le_u32(&sd.height);
+	sprite.leftMargin = read_le_u32(&sd.leftMargin);
+	sprite.topMargin = read_le_u32(&sd.topMargin);
+
+	ui32 currentOffset = sizeof(SSpriteDef);
+
+	//special case for some "old" format defs (SGTWMTA.DEF and SGTWMTB.DEF)
+
+	if(sprite.format == 1 && sprite.width > sprite.fullWidth && sprite.height > sprite.fullHeight)
+	{
+		sprite.leftMargin = 0;
+		sprite.topMargin = 0;
+		sprite.width = sprite.fullWidth;
+		sprite.height = sprite.fullHeight;
+
+		currentOffset -= 16;
+	}
+
+	const ui32 BaseOffset = currentOffset;
+
+	loader.init(Point(sprite.width, sprite.height),
+				Point(sprite.leftMargin, sprite.topMargin),
+				Point(sprite.fullWidth, sprite.fullHeight), palette.get());
+
+	switch(sprite.format)
+	{
+	case 0:
+		{
+			//pixel data is not compressed, copy data to surface
+			for(ui32 i=0; i<sprite.height; i++)
+			{
+				loader.load(sprite.width, FDef + currentOffset);
+				currentOffset += sprite.width;
+				loader.endLine();
+			}
+			break;
+		}
+	case 1:
+		{
+			//for each line we have offset of pixel data
+			const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+currentOffset);
+			currentOffset += sizeof(ui32) * sprite.height;
+
+			for(ui32 i=0; i<sprite.height; i++)
+			{
+				//get position of the line
+				currentOffset=BaseOffset + read_le_u32(RWEntriesLoc + i);
+				ui32 TotalRowLength = 0;
+
+				while(TotalRowLength<sprite.width)
+				{
+					ui8 segmentType = FDef[currentOffset++];
+					ui32 length = FDef[currentOffset++] + 1;
+
+					if(segmentType==0xFF)//Raw data
+					{
+						loader.load(length, FDef + currentOffset);
+						currentOffset+=length;
+					}
+					else// RLE
+					{
+						loader.load(length, segmentType);
+					}
+					TotalRowLength += length;
+				}
+
+				loader.endLine();
+			}
+			break;
+		}
+	case 2:
+		{
+			currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset);
+
+			for(ui32 i=0; i<sprite.height; i++)
+			{
+				ui32 TotalRowLength=0;
+
+				while(TotalRowLength<sprite.width)
+				{
+					ui8 segment=FDef[currentOffset++];
+					ui8 code = segment / 32;
+					ui8 length = (segment & 31) + 1;
+
+					if(code==7)//Raw data
+					{
+						loader.load(length, FDef + currentOffset);
+						currentOffset += length;
+					}
+					else//RLE
+					{
+						loader.load(length, code);
+					}
+					TotalRowLength+=length;
+				}
+				loader.endLine();
+			}
+			break;
+		}
+	case 3:
+		{
+			for(ui32 i=0; i<sprite.height; i++)
+			{
+				currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset+i*2*(sprite.width/32));
+				ui32 TotalRowLength=0;
+
+				while(TotalRowLength<sprite.width)
+				{
+					ui8 segment = FDef[currentOffset++];
+					ui8 code = segment / 32;
+					ui8 length = (segment & 31) + 1;
+
+					if(code==7)//Raw data
+					{
+						loader.load(length, FDef + currentOffset);
+						currentOffset += length;
+					}
+					else//RLE
+					{
+						loader.load(length, code);
+					}
+					TotalRowLength += length;
+				}
+				loader.endLine();
+			}
+			break;
+		}
+	default:
+	logGlobal->error("Error: unsupported format of def file: %d", sprite.format);
+		break;
+	}
+}
+
+CDefFile::~CDefFile() = default;
+
+const std::map<size_t, size_t > CDefFile::getEntries() const
+{
+	std::map<size_t, size_t > ret;
+
+	for (auto & elem : offset)
+		ret[elem.first] =  elem.second.size();
+	return ret;
+}
+

+ 51 - 0
client/render/CDefFile.h

@@ -0,0 +1,51 @@
+/*
+ * CDefFile.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/vcmi_endian.h"
+
+class IImageLoader;
+struct SDL_Color;
+
+/// Class for def loading
+/// After loading will store general info (palette and frame offsets) and pointer to file itself
+class CDefFile
+{
+private:
+
+	PACKED_STRUCT_BEGIN
+	struct SSpriteDef
+	{
+		ui32 size;
+		ui32 format;    /// format in which pixel data is stored
+		ui32 fullWidth; /// full width and height of frame, including borders
+		ui32 fullHeight;
+		ui32 width;     /// width and height of pixel data, borders excluded
+		ui32 height;
+		si32 leftMargin;
+		si32 topMargin;
+	} PACKED_STRUCT_END;
+	//offset[group][frame] - offset of frame data in file
+	std::map<size_t, std::vector <size_t> > offset;
+
+	std::unique_ptr<ui8[]>       data;
+	std::unique_ptr<SDL_Color[]> palette;
+
+public:
+	CDefFile(std::string Name);
+	~CDefFile();
+
+	//load frame as SDL_Surface
+	void loadFrame(size_t frame, size_t group, IImageLoader &loader) const;
+
+	const std::map<size_t, size_t> getEntries() const;
+};
+
+

+ 101 - 0
client/render/CFadeAnimation.cpp

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

+ 53 - 0
client/render/CFadeAnimation.h

@@ -0,0 +1,53 @@
+/*
+ * CFadeAnimation.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#ifdef IN
+#undef IN
+#endif
+
+#ifdef OUT
+#undef OUT
+#endif
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+
+struct SDL_Surface;
+
+const float DEFAULT_DELTA = 0.05f;
+
+class CFadeAnimation
+{
+public:
+	enum class EMode
+	{
+		NONE, IN, OUT
+	};
+private:
+	float delta;
+	SDL_Surface * fadingSurface;
+	bool fading;
+	float fadingCounter;
+	bool shouldFreeSurface;
+
+	float initialCounter() const;
+	bool isFinished() const;
+public:
+	EMode fadingMode;
+
+	CFadeAnimation();
+	~CFadeAnimation();
+	void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA);
+	void update();
+	void draw(SDL_Surface * targetSurface, const Point & targetPoint);
+	bool isFading() const { return fading; }
+};

Some files were not shown because too many files changed in this diff