Browse Source

Merge branch 'develop' into NewAbilities

Dydzio 8 years ago
parent
commit
14c07afd3c
86 changed files with 2256 additions and 1089 deletions
  1. 3 0
      AUTHORS
  2. 18 7
      CMakeLists.txt
  3. 3 0
      ChangeLog
  4. 12 0
      Global.h
  5. 0 112
      README.linux
  6. 21 8
      README.md
  7. 0 24
      client/CDefHandler.cpp
  8. 3 12
      client/CDefHandler.h
  9. 3 0
      client/CGameInfo.cpp
  10. 2 0
      client/CGameInfo.h
  11. 2 2
      client/CMT.cpp
  12. 2 1
      client/CPreGame.cpp
  13. 6 17
      client/Graphics.cpp
  14. 92 92
      client/battle/CBattleAnimations.cpp
  15. 7 9
      client/battle/CBattleAnimations.h
  16. 54 47
      client/battle/CBattleInterface.cpp
  17. 8 5
      client/battle/CBattleInterface.h
  18. 45 48
      client/battle/CBattleInterfaceClasses.cpp
  19. 8 4
      client/battle/CBattleInterfaceClasses.h
  20. 45 172
      client/battle/CCreatureAnimation.cpp
  21. 10 30
      client/battle/CCreatureAnimation.h
  22. 145 10
      client/gui/CAnimation.cpp
  23. 10 0
      client/gui/CAnimation.h
  24. 19 28
      client/gui/CCursorHandler.cpp
  25. 10 8
      client/gui/CCursorHandler.h
  26. 1 2
      client/gui/CGuiHandler.cpp
  27. 2 2
      client/widgets/CArtifactHolder.cpp
  28. 3 2
      client/widgets/CComponent.cpp
  29. 16 16
      client/windows/CHeroWindow.cpp
  30. 0 1
      client/windows/CHeroWindow.h
  31. 4 3
      client/windows/CKingdomInterface.cpp
  32. 1 1
      client/windows/CTradeWindow.cpp
  33. 10 9
      client/windows/GUIClasses.cpp
  34. 53 3
      cmake_modules/VCMIUtils.cmake
  35. 4 0
      config/gameConfig.json
  36. 5 0
      config/schemas/mod.json
  37. 57 0
      config/schemas/skill.json
  38. 805 0
      config/skills.json
  39. 1 0
      include/vstd/CLoggerBase.h
  40. 1 4
      launcher/CMakeLists.txt
  41. 1 3
      launcher/main.cpp
  42. 2 2
      launcher/mainwindow_moc.cpp
  43. 1 1
      launcher/mainwindow_moc.h
  44. 51 48
      launcher/mainwindow_moc.ui
  45. 0 42
      launcher/sdldisplayquery.cpp
  46. 0 14
      launcher/sdldisplayquery.h
  47. 22 11
      launcher/settingsView/csettingsview_moc.cpp
  48. 1 1
      launcher/settingsView/csettingsview_moc.h
  49. 7 7
      lib/CArtHandler.cpp
  50. 5 3
      lib/CGameState.cpp
  51. 0 1
      lib/CGameState.h
  52. 0 17
      lib/CGeneralTextHandler.cpp
  53. 0 2
      lib/CGeneralTextHandler.h
  54. 1 1
      lib/CHeroHandler.cpp
  55. 2 0
      lib/CMakeLists.txt
  56. 63 51
      lib/CModHandler.cpp
  57. 5 1
      lib/CModHandler.h
  58. 226 0
      lib/CSkillHandler.cpp
  59. 87 0
      lib/CSkillHandler.h
  60. 8 5
      lib/CTownHandler.cpp
  61. 6 0
      lib/GameConstants.cpp
  62. 3 0
      lib/GameConstants.h
  63. 9 1
      lib/HeroBonus.cpp
  64. 5 1
      lib/HeroBonus.h
  65. 1 0
      lib/IGameCallback.cpp
  66. 1 3
      lib/IHandlerBase.h
  67. 2 2
      lib/JsonDetail.cpp
  68. 12 12
      lib/JsonNode.cpp
  69. 49 24
      lib/VCMIDirs.cpp
  70. 5 0
      lib/VCMI_Lib.cpp
  71. 6 0
      lib/VCMI_Lib.h
  72. 28 26
      lib/VCMI_lib.cbp
  73. 2 2
      lib/battle/BattleInfo.cpp
  74. 3 2
      lib/filesystem/CFilesystemLoader.cpp
  75. 1 0
      lib/logging/CLogger.cpp
  76. 1 1
      lib/mapObjects/CBank.cpp
  77. 27 84
      lib/mapObjects/CGHeroInstance.cpp
  78. 3 0
      lib/mapObjects/CGHeroInstance.h
  79. 7 5
      lib/mapObjects/MiscObjects.cpp
  80. 2 0
      lib/mapObjects/ObjectTemplate.cpp
  81. 1 1
      lib/serializer/CSerializer.h
  82. 4 4
      lib/spells/CSpellHandler.cpp
  83. 75 0
      osx/DS_Store_Setup.scpt
  84. BIN
      osx/dmg_DS_Store
  85. 28 28
      server/CGameHandler.cpp
  86. 2 4
      server/CVCMIServer.cpp

+ 3 - 0
AUTHORS

@@ -66,3 +66,6 @@ Dydzio, <[email protected]>
 
 Piotr Wójcik aka Chocimier, <[email protected]>
    * Various bug fixes
+
+Henning Koehler, <[email protected]>
+   * skill modding

+ 18 - 7
CMakeLists.txt

@@ -54,7 +54,7 @@ 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)
 
-# Useful for debugging
+# Used for Snap packages and also useful for debugging
 option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF)
 
 # Allow to pass package name from Travis CI
@@ -68,6 +68,7 @@ set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
 set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules)
 # Contains custom functions and macros, but don't altering any options
 include(VCMIUtils)
+vcmi_print_important_variables()
 
 # Options to enable folders in CMake generated projects for Visual Studio, Xcode, etc
 # Very useful to put 3rd-party libraries such as Minizip, GoogleTest and FuzzyLite in their own folders
@@ -90,6 +91,7 @@ if(ENABLE_GITVERSION)
 	include(GetGitRevisionDescription)
 	get_git_head_revision(GIT_REFSPEC GIT_SHA1)
 	configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" "${CMAKE_BINARY_DIR}/Version.cpp" @ONLY)
+	vcmi_print_git_commit_hash()
 else()
 	add_definitions(-DVCMI_NO_EXTRA_VERSION)
 endif(ENABLE_GITVERSION)
@@ -248,6 +250,7 @@ else()
 	include(GNUInstallDirs)
 
 	if(ENABLE_MONOLITHIC_INSTALL)
+		set(CMAKE_INSTALL_RPATH "$ORIGIN/")
 		set(BIN_DIR "." CACHE STRING "Where to install binaries")
 		set(LIB_DIR "." CACHE STRING "Where to install main library")
 		set(DATA_DIR "." CACHE STRING "Where to install data files")
@@ -262,16 +265,16 @@ else()
 			set(DATA_DIR "share/vcmi" CACHE STRING "Where to install data files")
 		endif()
 	endif()
+
+	# following constants only used for platforms using XDG (Linux, BSD, etc)
+	add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
+	add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
+	add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
 endif()
 
 set(AI_LIB_DIR "${LIB_DIR}/AI")
 set(SCRIPTING_LIB_DIR "${LIB_DIR}/scripting")
 
-#define required constants
-add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
-add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
-add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
-
 #######################################
 #        Add subdirectories           #
 #######################################
@@ -414,7 +417,15 @@ elseif(APPLE AND NOT ENABLE_MONOLITHIC_INSTALL)
 	set(CPACK_MONOLITHIC_INSTALL 1)
 	set(CPACK_GENERATOR "DragNDrop")
 	set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/osx/dmg_background.png")
-	set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/osx/dmg_DS_Store")
+	# CMake code for CPACK_DMG_DS_STORE executed before DS_STORE_SETUP_SCRIPT
+	# So we can keep both enabled and this shouldn't hurt
+	# set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/osx/dmg_DS_Store")
+	set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/osx/DS_Store_Setup.scpt")
+
+	# Always use "VCMI" as volume name so full path will be /Volumes/VCMI/
+	# Otherwise DMG background image wouldn't work
+	# Pre-generated DS_Store use absolute path to background image
+	set(CPACK_DMG_VOLUME_NAME "${CMAKE_PROJECT_NAME}")
 
 	set(MACOSX_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")
 	set(MACOSX_BUNDLE_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")

+ 3 - 0
ChangeLog

@@ -11,12 +11,15 @@ GENERAL:
 - CATAPULT_EXTRA_SHOTS - defines number of extra wall attacks for units that can do so
 - RANGED_RETALIATION - allows ranged counterattack
 - BLOCKS_RANGED_RETALIATION - disallow enemy ranged counterattack
+- SECONDARY_SKILL_VAL2 - set additional parameter for certain secondary skills
+- MANUAL_CONTROL - grant manual control over war machine
 
 SPELLS:
 * Implemented cumulative effects for spells
 
 MODS:
 * Improve support for WoG commander artifacts and skill descriptions
+* Added basic support for secondary skill modding
 
 0.98 -> 0.99
 

+ 12 - 0
Global.h

@@ -676,6 +676,18 @@ namespace vstd
 		return v3;
 	}
 
+	template <typename Key, typename V>
+	bool containsMapping(const std::multimap<Key,V> & map, const std::pair<const Key,V> & mapping)
+	{
+		auto range = map.equal_range(mapping.first);
+		for(auto contained = range.first; contained != range.second; contained++)
+		{
+			if(mapping.second == contained->second)
+				return true;
+		}
+		return false;
+	}
+
 	using boost::math::round;
 }
 using vstd::operator-=;

+ 0 - 112
README.linux

@@ -1,112 +0,0 @@
-This readme covers VCMI compilation on Unix-like systems.
-
-To run the game you will need:
-1) Heroes 3 data files (SoD or Complete editions);
-2) VCMI data pack (http://download.vcmi.eu/core.zip)
-All of them can be installed manually or using vcmibuilder script
-
-For complete installation instructions see VCMI wiki:
-http://wiki.vcmi.eu/index.php?title=Installation_on_Linux#Preparing_data
-
-I. Prerequisites
-
-To compile, the following packages (and their development counterparts) are needed to build:
-	* libstdc++ devel
-	* CMake build system
-	* SDL and SDL-devel
-	* SDL_mixer and SDL_mixer-devel
-	* SDL_image and SDL_image-devel
-	* SDL_ttf and SDL_ttf-devel
-	* zlib and zlib-devel
-	* (optional) Qt 5, widget and network modules
-	* the ffmpeg libraries (libavformat and libswscale). Their name could be libavformat-devel and libswscale-devel, or ffmpeg-libs-devel or similar names.
-	* boost c++ libraries v1.50+ (www.boost.org):
-		- program-options
-		- filesystem
-		- system
-		- thread
-		- locale
-
-On Debian-based systems (e.g. Ubuntu) run:
-  sudo apt-get install cmake g++ libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev zlib1g-dev libavformat-dev libswscale-dev libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev qtbase5-dev
-
-On RPM-based distributions (e.g. Fedora) run:
-  sudo yum install cmake gcc-c++ SDL2-devel SDL2_image-devel SDL2_ttf-devel SDL2_mixer-devel boost boost-devel boost-filesystem boost-system boost-thread boost-program-options boost-locale zlib-devel ffmpeg-devel ffmpeg-libs qt5-qtbase-devel 
-
-On Arch-based distributions, there is a development package available for VCMI on the AUR. It can be found at:
-  https://aur.archlinux.org/packages/vcmi-git/
-
-  Information about building packages from the Arch User Repository (AUR) can be
-  found at the Arch wiki.
-
-II. Getting the sources
-
-VCMI is still in development. We recommend the following initial directory structure:
-.
-├── vcmi -> contains sources and is under git control
-└── build -> contains build output, makefiles, object files,...
-
-You can get latest sources with:
-  git clone https://github.com/vcmi/vcmi.git
-
-III. Compilation
-
-Run configure:
-  mkdir build && cd build
-  cmake ../vcmi <any other options, see below>
-
-Additional options that you may want to use:
-To enable debugging: -DCMAKE_BUILD_TYPE=Debug
-To change installation directory: -DCMAKE_INSTALL_PREFIX=$absolute_path_to_directory
-
-Notice:
-The ../vcmi/ is not a typo, it will place makefile scripts into the build dir 
-as the build dir is your working dir when calling CMake.
-
-Then build vcmi:
-  make -j2 (j2 = compile with 2 threads, you can specify any value)
-
-That will generate vcmiclient, vcmiserver, vcmilauncher as well as 3 .so libraries.
-
-III. Installing binaries
-
-To install VCMI you can use "make install" command however generation of distribution-specific packages is usually a better idea. In most cases this can be achieved using tool called "checkinstall"
-
-If you're compiling vcmi for development puposes, the easiest is to use cmake prefix and then make install:
-
-# mkdir .../trunk/install
-# cmake -DCMAKE_INSTALL_PREFIX=.../trunk/install ../vcmi
-# make && make install
-# .../trunk/install/bin/vcmiclient
-
-
-it's better to use links instead.
-Go to /BIN_PATH/, and type:
-
-  ln -s .../trunk/build/client/vcmiclient
-  ln -s .../trunk/build/server/vcmiserver
-  ln -s .../trunk/build/launcher/vcmilauncher
-  
-Go to /LIB_PATH/vcmi, and type:
-
-  ln -s .../trunk/build/lib/libvcmi.so libvcmi.so
-  
-Go to /LIB_PATH/vcmi/AI, and type: 
-  ln -s .../trunk/build/AI/VCAI/libVCAI.so
-  ln -s .../trunk/build/AI/StupidAI/libStupidAI.so
-  ln -s .../trunk/build/AI/BattleAI/libBattleAI.so
-
-Go to /DATA_PATH/vcmi, and type:
-  ln -s .../trunk/source/config
-  ln -s .../trunk/source/Mods
-
-IV. Compiling documentation
-
-To compile using Doxygen, the UseDoxygen CMake module must be installed. It can
-be fetched from: http://tobias.rautenkranz.ch/cmake/doxygen/
-
-Once UseDoxygen is installed, run:
-    cmake .
-    make doc
-
-The built documentation will be available from ./doc

+ 21 - 8
README.md

@@ -2,20 +2,33 @@
 [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi)
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi)
 # VCMI Project
-VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities. To use VCMI you need to own original data files.
+VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities.
 
 ## Links
 
- * Homepage:   http://vcmi.eu/
- * Wiki:       http://wiki.vcmi.eu/
- * Forums:     http://forum.vcmi.eu/
- * Bugtracker: http://bugs.vcmi.eu/
+ * Homepage:   https://vcmi.eu/
+ * Wiki:       https://wiki.vcmi.eu/
+ * Forums:     https://forum.vcmi.eu/
+ * Bugtracker: https://bugs.vcmi.eu/
  * Slack:      https://slack.vcmi.eu/
 
-## Installation
-For installation of latest release see release announcement on http://vcmi.eu/
+## Installation guides
 
-For building from source see project wiki at http://wiki.vcmi.eu/
+To use VCMI you need to own original data files.
+
+ * [Android](https://wiki.vcmi.eu/Installation_on_Android)
+ * [Linux](https://wiki.vcmi.eu/Installation_on_Linux)
+ * [macOS](https://wiki.vcmi.eu/Installation_on_macOS)
+ * [Windows](https://wiki.vcmi.eu/Installation_on_Windows)
+
+## Building from source
+
+Platform support is constantly tested by continuous integration and CMake configuration adjusted to generate nice looking projects for all major IDE. Following guides will help you to setup build environment with no effort:
+
+ * [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 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))
 
 ## Copyright and license
 

+ 0 - 24
client/CDefHandler.cpp

@@ -29,12 +29,9 @@ static long long pow(long long a, int b)
 
 CDefHandler::CDefHandler()
 {
-	notFreeImgs = false;
 }
 CDefHandler::~CDefHandler()
 {
-	if (notFreeImgs)
-		return;
 	for (auto & elem : ourImages)
 	{
 		if (elem.bitmap)
@@ -44,11 +41,6 @@ CDefHandler::~CDefHandler()
 		}
 	}
 }
-CDefEssential::~CDefEssential()
-{
-	for(auto & elem : ourImages)
-		SDL_FreeSurface(elem.bitmap);
-}
 
 void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
 {
@@ -350,14 +342,6 @@ SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Co
 	return ret;
 }
 
-CDefEssential * CDefHandler::essentialize()
-{
-	auto ret = new CDefEssential();
-	ret->ourImages = ourImages;
-	notFreeImgs = true;
-	return ret;
-}
-
 CDefHandler * CDefHandler::giveDef(const std::string & defName)
 {
 	ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION);
@@ -369,12 +353,4 @@ CDefHandler * CDefHandler::giveDef(const std::string & defName)
 	nh->openFromMemory(data.get(), defName);
 	return nh;
 }
-CDefEssential * CDefHandler::giveDefEss(const std::string & defName)
-{
-	CDefEssential * ret;
-	CDefHandler * temp = giveDef(defName);
-	ret = temp->essentialize();
-	delete temp;
-	return ret;
-}
 

+ 3 - 12
client/CDefHandler.h

@@ -66,12 +66,6 @@ struct SSpriteDef
 	ui32 TopMargin;
 } PACKED_STRUCT;
 
-class CDefEssential //DefHandler with images only
-{
-public:
-	std::vector<Cimage> ourImages;
-	~CDefEssential();
-};
 
 class CDefHandler
 {
@@ -84,20 +78,17 @@ private:
 		int group;
 	} ;
 	std::vector<SEntry> SEntries ;
-	
-	void openFromMemory(ui8 * table, const std::string & name);	
+
+	void openFromMemory(ui8 * table, const std::string & name);
 	SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const;
 public:
 	int width, height; //width and height
 	std::string defName;
 	std::vector<Cimage> ourImages;
-	bool notFreeImgs;
 
 	CDefHandler();
 	~CDefHandler();
-	
-	CDefEssential * essentialize();
+
 
 	static CDefHandler * giveDef(const std::string & defName);
-	static CDefEssential * giveDefEss(const std::string & defName);
 };

+ 3 - 0
client/CGameInfo.cpp

@@ -9,6 +9,8 @@
  */
 #include "StdInc.h"
 #include "CGameInfo.h"
+#include "../lib/CSkillHandler.h"
+#include "../lib/CGeneralTextHandler.h"
 
 #include "../lib/VCMI_Lib.h"
 
@@ -32,5 +34,6 @@ void CGameInfo::setFromLib()
 	heroh = VLC->heroh;
 	objh = VLC->objh;
 	spellh = VLC->spellh;
+	skillh = VLC->skillh;
 	objtypeh = VLC->objtypeh;
 }

+ 2 - 0
client/CGameInfo.h

@@ -18,6 +18,7 @@ class CArtHandler;
 class CHeroHandler;
 class CCreatureHandler;
 class CSpellHandler;
+class CSkillHandler;
 class CBuildingHandler;
 class CObjectHandler;
 class CSoundHandler;
@@ -55,6 +56,7 @@ public:
 	ConstTransitivePtr<CHeroHandler> heroh;
 	ConstTransitivePtr<CCreatureHandler> creh;
 	ConstTransitivePtr<CSpellHandler> spellh;
+	ConstTransitivePtr<CSkillHandler> skillh;
 	ConstTransitivePtr<CObjectHandler> objh;
 	ConstTransitivePtr<CObjectClassesHandler> objtypeh;
 	CGeneralTextHandler * generaltexth;

+ 2 - 2
client/CMT.cpp

@@ -938,9 +938,9 @@ void processCommand(const std::string &message)
 //plays intro, ends when intro is over or button has been pressed (handles events)
 void playIntro()
 {
-	if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 60, 40, screen, true, true))
+	if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 0, 1, screen, true, true))
 	{
-		CCS->videoh->openAndPlayVideo("AZVS.SMK", 60, 80, screen, true, true);
+		CCS->videoh->openAndPlayVideo("AZVS.SMK", 0, 1, screen, true, true);
 	}
 }
 

+ 2 - 1
client/CPreGame.cpp

@@ -18,6 +18,7 @@
 #include "CGameInfo.h"
 #include "gui/CCursorHandler.h"
 #include "../lib/CGeneralTextHandler.h"
+#include "../lib/CSkillHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/mapping/CCampaignHandler.h"
@@ -3624,7 +3625,7 @@ void CBonusSelection::updateBonusSelection()
 			desc = CGI->generaltexth->allTexts[718];
 
 			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
-			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->skillName[bonDescs[i].info2]); //skill name
+			boost::algorithm::replace_first(desc, "%s", CGI->skillh->skillName(bonDescs[i].info2)); //skill name
 			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
 
 			break;

+ 6 - 17
client/Graphics.cpp

@@ -232,16 +232,10 @@ std::shared_ptr<CAnimation> Graphics::loadHeroFlagAnimation(const std::string &
 
 	for(const auto & rotation : rotations)
 	{
-        const int sourceGroup = rotation.first;
-        const int targetGroup = rotation.second;
+		const int sourceGroup = rotation.first;
+		const int targetGroup = rotation.second;
 
-        for(size_t frame = 0; frame < anim->size(sourceGroup); ++frame)
-		{
-			anim->duplicateImage(sourceGroup, frame, targetGroup);
-
-			IImage * image = anim->getImage(frame, targetGroup);
-			image->verticalFlip();
-		}
+		anim->createFlippedGroup(sourceGroup, targetGroup);
 	}
 
 	return anim;
@@ -262,15 +256,10 @@ std::shared_ptr<CAnimation> Graphics::loadHeroAnimation(const std::string &name)
 
 	for(const auto & rotation : rotations)
 	{
-        const int sourceGroup = rotation.first;
-        const int targetGroup = rotation.second;
+		const int sourceGroup = rotation.first;
+		const int targetGroup = rotation.second;
 
-        for(size_t frame = 0; frame < anim->size(sourceGroup); ++frame)
-		{
-			anim->duplicateImage(sourceGroup, frame, targetGroup);
-			IImage * image = anim->getImage(frame, targetGroup);
-			image->verticalFlip();
-		}
+		anim->createFlippedGroup(sourceGroup, targetGroup);
 	}
 
 	return anim;

+ 92 - 92
client/battle/CBattleAnimations.cpp

@@ -16,11 +16,11 @@
 #include "CBattleInterface.h"
 #include "CCreatureAnimation.h"
 
-#include "../CDefHandler.h"
 #include "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../Graphics.h"
+#include "../gui/CAnimation.h"
 #include "../gui/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/SDL_Extensions.h"
@@ -58,13 +58,13 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
 {
 	int lowestMoveID = owner->animIDhelper + 5;
 	CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
-	CSpellEffectAnimation * thSen = dynamic_cast<CSpellEffectAnimation *>(this);
+	CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this);
 
 	for(auto & elem : owner->pendingAnims)
 	{
 
 		CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
-		CSpellEffectAnimation * sen = dynamic_cast<CSpellEffectAnimation *>(elem.first);
+		CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
 		if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
 			continue;
 
@@ -171,7 +171,7 @@ bool CDefenceAnimation::init()
 		if(attAnim && attAnim->stack->ID != stack->ID)
 			continue;
 
-		CSpellEffectAnimation * sen = dynamic_cast<CSpellEffectAnimation *>(elem.first);
+		CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
 		if (sen)
 			continue;
 
@@ -243,7 +243,7 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
 	if(killed)
 		return CCreatureAnim::DEATH;
 
-	if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
+	if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
 		return CCreatureAnim::DEFENCE;
 
 	return CCreatureAnim::HITTED;
@@ -270,10 +270,15 @@ void CDefenceAnimation::nextFrame()
 
 void CDefenceAnimation::endAnim()
 {
-	if (killed)
+	if(killed)
+	{
 		myAnim->setType(CCreatureAnim::DEAD);
+	}
 	else
+	{
 		myAnim->setType(CCreatureAnim::HOLDING);
+	}
+
 
 	CBattleAnimation::endAnim();
 
@@ -785,13 +790,13 @@ bool CShootingAnimation::init()
 		spi.dx = animSpeed;
 		spi.dy = 0;
 
-		SDL_Surface * img = owner->idToProjectile[spi.creID]->ourImages[0].bitmap;
+		IImage * img = owner->idToProjectile[spi.creID]->getImage(0);
 
 		// Add explosion anim
-		Point animPos(destPos.x - 126 + img->w / 2,
-		              destPos.y - 105 + img->h / 2);
+		Point animPos(destPos.x - 126 + img->width() / 2,
+		              destPos.y - 105 + img->height() / 2);
 
-		owner->addNewAnim( new CSpellEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y));
+		owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y));
 	}
 
 	auto & angles = shooterInfo->animation.missleFrameAngles;
@@ -801,7 +806,7 @@ bool CShootingAnimation::init()
 		owner->initStackProjectile(shooter);
 
 	// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used
-	size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->ourImages.size());
+	size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->size(0));
 
 	assert(maxFrame > 0);
 
@@ -872,35 +877,40 @@ void CShootingAnimation::endAnim()
 	delete this;
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
-	:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(-1), y(-1), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
+CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
+	: CBattleAnimation(_owner),
+	destTile(BattleHex::INVALID),
+	customAnim(_customAnim),
+	x(_x),
+	y(_y),
+	dx(_dx),
+	dy(_dy),
+	Vflip(_Vflip),
+	alignToBottom(_alignToBottom)
 {
-	logAnim->debug("Created spell anim for effect #%d", effect);
+	logAnim->debug("Created effect animation %s", customAnim);
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
-	:CBattleAnimation(_owner), effect(-1), destTile(BattleHex::INVALID), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
+CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
+	: CBattleAnimation(_owner),
+	destTile(_destTile),
+	customAnim(_customAnim),
+	x(-1),
+	y(-1),
+	dx(0),
+	dy(0),
+	Vflip(_Vflip),
+	alignToBottom(_alignToBottom)
 {
-	logAnim->debug("Created spell anim for %s", customAnim);
-}
-
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
-	:CBattleAnimation(_owner), effect(-1), destTile(_destTile), customAnim(_customAnim), x(-1), y(-1), dx(0), dy(0), Vflip(_Vflip), alignToBottom(_alignToBottom)
-{
-	logAnim->debug("Created spell anim for %s", customAnim);
+	logAnim->debug("Created effect animation %s", customAnim);
 }
 
 
-bool CSpellEffectAnimation::init()
+bool CEffectAnimation::init()
 {
 	if(!isEarliest(true))
 		return false;
 
-	if(customAnim.empty() && effect != ui32(-1) && !graphics->battleACToDef[effect].empty())
-	{
-		customAnim = graphics->battleACToDef[effect][0];
-	}
-
 	if(customAnim.empty())
 	{
 		endAnim();
@@ -909,97 +919,88 @@ bool CSpellEffectAnimation::init()
 
 	const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
 
-	if(areaEffect) //f.e. armageddon
+	std::shared_ptr<CAnimation> animation = std::make_shared<CAnimation>(customAnim);
+
+	animation->preload();
+	if(Vflip)
+		animation->verticalFlip();
+
+	IImage * first = animation->getImage(0, 0, true);
+	if(!first)
 	{
-		CDefHandler * anim = CDefHandler::giveDef(customAnim);
+		endAnim();
+		return false;
+	}
 
-		for(int i=0; i * anim->width < owner->pos.w ; ++i)
+	if(areaEffect) //f.e. armageddon
+	{
+		for(int i=0; i * first->width() < owner->pos.w ; ++i)
 		{
-			for(int j=0; j * anim->height < owner->pos.h ; ++j)
+			for(int j=0; j * first->height() < owner->pos.h ; ++j)
 			{
 				BattleEffect be;
 				be.effectID = ID;
-				be.anim = CDefHandler::giveDef(customAnim);
-				if (Vflip)
-				{
-					for (auto & elem : be.anim->ourImages)
-					{
-						CSDL_Ext::VflipSurf(elem.bitmap);
-					}
-				}
+				be.animation = animation;
 				be.currentFrame = 0;
-				be.maxFrame = be.anim->ourImages.size();
-				be.x = i * anim->width + owner->pos.x;
-				be.y = j * anim->height + owner->pos.y;
+
+				be.x = i * first->width() + owner->pos.x;
+				be.y = j * first->height() + owner->pos.y;
 				be.position = BattleHex::INVALID;
 
 				owner->battleEffects.push_back(be);
 			}
 		}
-
-		delete anim;
 	}
 	else // Effects targeted at a specific creature/hex.
 	{
+		const CStack * destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
+		Rect & tilePos = owner->bfield[destTile]->pos;
+		BattleEffect be;
+		be.effectID = ID;
+		be.animation = animation;
+		be.currentFrame = 0;
 
-			const CStack* destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
-			Rect & tilePos = owner->bfield[destTile]->pos;
-			BattleEffect be;
-			be.effectID = ID;
-			be.anim = CDefHandler::giveDef(customAnim);
 
-			if (Vflip)
-			{
-				for (auto & elem : be.anim->ourImages)
-				{
-					CSDL_Ext::VflipSurf(elem.bitmap);
-				}
-			}
-
-			be.currentFrame = 0;
-			be.maxFrame = be.anim->ourImages.size();
-
-			//todo: lightning anim frame count override
+		//todo: lightning anim frame count override
 
 //			if(effect == 1)
 //				be.maxFrame = 3;
 
-			if(x == -1)
-			{
-				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
-			}
-			else
-			{
-				be.x = x;
-			}
+		if(x == -1)
+		{
+			be.x = tilePos.x + tilePos.w/2 - first->width()/2;
+		}
+		else
+		{
+			be.x = x;
+		}
 
-			if(y == -1)
-			{
-				if(alignToBottom)
-					be.y = tilePos.y + tilePos.h - be.anim->height;
-				else
-					be.y = tilePos.y - be.anim->height/2;
-			}
+		if(y == -1)
+		{
+			if(alignToBottom)
+				be.y = tilePos.y + tilePos.h - first->height();
 			else
-			{
-				be.y = y;
-			}
-
-			// Correction for 2-hex creatures.
-			if (destStack != nullptr && destStack->doubleWide())
-				be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
+				be.y = tilePos.y - first->height()/2;
+		}
+		else
+		{
+			be.y = y;
+		}
 
-			//Indicate if effect should be drawn on top of everything or just on top of the hex
-			be.position = destTile;
+		// Correction for 2-hex creatures.
+		if(destStack != nullptr && destStack->doubleWide())
+			be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
 
-			owner->battleEffects.push_back(be);
+		//Indicate if effect should be drawn on top of everything or just on top of the hex
+		be.position = destTile;
 
+		owner->battleEffects.push_back(be);
 	}
 	//battleEffects
 	return true;
 }
 
-void CSpellEffectAnimation::nextFrame()
+void CEffectAnimation::nextFrame()
 {
 	//notice: there may be more than one effect in owner->battleEffects correcponding to this animation (ie. armageddon)
 	for(auto & elem : owner->battleEffects)
@@ -1008,7 +1009,7 @@ void CSpellEffectAnimation::nextFrame()
 		{
 			elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
 
-			if(elem.currentFrame >= elem.maxFrame)
+			if(elem.currentFrame >= elem.animation->size())
 			{
 				endAnim();
 				break;
@@ -1022,7 +1023,7 @@ void CSpellEffectAnimation::nextFrame()
 	}
 }
 
-void CSpellEffectAnimation::endAnim()
+void CEffectAnimation::endAnim()
 {
 	CBattleAnimation::endAnim();
 
@@ -1038,7 +1039,6 @@ void CSpellEffectAnimation::endAnim()
 
 	for(auto & elem : toDel)
 	{
-		delete elem->anim;
 		owner->battleEffects.erase(elem);
 	}
 

+ 7 - 9
client/battle/CBattleAnimations.h

@@ -208,16 +208,15 @@ public:
 	void endAnim() override;
 
 	//last two params only for catapult attacks
-	CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, 
-		const CStack * _attacked, bool _catapult = false, int _catapultDmg = 0); 
+	CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest,
+		const CStack * _attacked, bool _catapult = false, int _catapultDmg = 0);
 	virtual ~CShootingAnimation(){};
 };
 
-/// This class manages a spell effect animation
-class CSpellEffectAnimation : public CBattleAnimation
+/// This class manages effect animation
+class CEffectAnimation : public CBattleAnimation
 {
 private:
-	ui32 effect;
 	BattleHex destTile;
 	std::string	customAnim;
 	int	x, y, dx, dy;
@@ -228,8 +227,7 @@ public:
 	void nextFrame() override;
 	void endAnim() override;
 
-	CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
-	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
-	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
-	virtual ~CSpellEffectAnimation(){};
+	CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+	CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
+	virtual ~CEffectAnimation(){};
 };

+ 54 - 47
client/battle/CBattleInterface.cpp

@@ -23,6 +23,7 @@
 #include "../CPlayerInterface.h"
 #include "../CVideoHandler.h"
 #include "../Graphics.h"
+#include "../gui/CAnimation.h"
 #include "../gui/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/SDL_Extensions.h"
@@ -263,7 +264,10 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 			battleImage = hero1->type->heroClass->imageBattleMale;
 
 		attackingHero = new CBattleHero(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, this);
-		attackingHero->pos = genRect(attackingHero->dh->ourImages[0].bitmap->h, attackingHero->dh->ourImages[0].bitmap->w, pos.x - 43, pos.y - 19);
+
+		IImage * img = attackingHero->animation->getImage(0, 0, true);
+		if(img)
+			attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
 	}
 	else
 	{
@@ -278,7 +282,10 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 			battleImage = hero2->type->heroClass->imageBattleMale;
 
 		defendingHero = new CBattleHero(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, this);
-		defendingHero->pos = genRect(defendingHero->dh->ourImages[0].bitmap->h, defendingHero->dh->ourImages[0].bitmap->w, pos.x + 693, pos.y - 19);
+
+		IImage * img = defendingHero->animation->getImage(0, 0, true);
+		if(img)
+			defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
 	}
 	else
 	{
@@ -425,9 +432,6 @@ CBattleInterface::~CBattleInterface()
 	for (auto & elem : creAnims)
 		delete elem.second;
 
-	for (auto & elem : idToProjectile)
-		delete elem.second;
-
 	for (auto & elem : idToObstacle)
 		delete elem.second;
 
@@ -999,20 +1003,21 @@ void CBattleInterface::newStack(const CStack *stack)
 
 void CBattleInterface::initStackProjectile(const CStack * stack)
 {
-	CDefHandler *&projectile = idToProjectile[stack->getCreature()->idNumber];
-
-	const CCreature *creature;//creature whose shots should be loaded
-	if (stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
+	const CCreature * creature;//creature whose shots should be loaded
+	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
 		creature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter];
 	else
 		creature = stack->getCreature();
 
-	projectile = CDefHandler::giveDef(creature->animation.projectileImageName);
+	std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
+	projectile->preload();
 
-	for (auto & elem : projectile->ourImages) //alpha transforming
-	{
-		CSDL_Ext::alphaTransform(elem.bitmap);
-	}
+	if(projectile->size(1) != 0)
+		logAnim->error("Expected empty group 1 in stack projectile");
+	else
+		projectile->createFlippedGroup(0, 1);
+
+	idToProjectile[stack->getCreature()->idNumber] = projectile;
 }
 
 void CBattleInterface::stackRemoved(int stackID)
@@ -1216,7 +1221,7 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 		{
 			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, this) + Point(99, 120);
 
-			addNewAnim(new CSpellEffectAnimation(this, "SGEXPL.DEF", destPos.x, destPos.y));
+			addNewAnim(new CEffectAnimation(this, "SGEXPL.DEF", destPos.x, destPos.y));
 		}
 	}
 
@@ -1304,20 +1309,23 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
 
 		std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
 
-		if (!animToDisplay.empty())
+		if(!animToDisplay.empty())
 		{
+			//TODO: calculate inside CEffectAnimation
+			std::shared_ptr<CAnimation> tmp = std::make_shared<CAnimation>(animToDisplay);
+			tmp->load(0, 0);
+			IImage * first = tmp->getImage(0, 0);
+
 			//displaying animation
-			CDefEssential *animDef = CDefHandler::giveDefEss(animToDisplay);
 			double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
 			double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
 			double distance = sqrt(diffX + diffY);
 
 			int steps = distance / AnimationControls::getSpellEffectSpeed() + 1;
-			int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps;
-			int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
+			int dx = (destcoord.x - srccoord.x - first->width())/steps;
+			int dy = (destcoord.y - srccoord.y - first->height())/steps;
 
-			delete animDef;
-			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
+			addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
 		}
 	}
 	waitForAnims();
@@ -1349,8 +1357,8 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
 	{
 		Point leftHero = Point(15, 30) + pos;
 		Point rightHero = Point(755, 30) + pos;
-		addNewAnim(new CSpellEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
-		addNewAnim(new CSpellEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
+		addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
+		addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
 	}
 }
 
@@ -1413,9 +1421,11 @@ void CBattleInterface::castThisSpell(SpellID spellID)
 	}
 }
 
-void CBattleInterface::displayEffect(ui32 effect, int destTile)
+void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile)
 {
-	addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
+	std::string customAnim = graphics->battleACToDef[effect][0];
+
+	addNewAnim(new CEffectAnimation(this, customAnim, destTile));
 }
 
 void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile)
@@ -1426,7 +1436,7 @@ void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animatio
 	}
 	else
 	{
-		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+		addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
 	}
 }
 
@@ -2732,7 +2742,7 @@ void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
 	//we assume here that effect graphics have the same size as the usual obstacle image
 	// -> if we know how to blit obstacle, let's blit the effect in the same place
 	Point whereTo = getObstaclePosition(getObstacleImage(oi), oi);
-	addNewAnim(new CSpellEffectAnimation(this, defname, whereTo.x, whereTo.y));
+	addNewAnim(new CEffectAnimation(this, defname, whereTo.x, whereTo.y));
 
 	//TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad
 	//CCS->soundh->playSound(sound);
@@ -3166,23 +3176,18 @@ void CBattleInterface::showProjectiles(SDL_Surface *to)
 				continue; // wait...
 		}
 
-		SDL_Surface *image = idToProjectile[it->creID]->ourImages[it->frameNum].bitmap;
-
-		SDL_Rect dst;
-		dst.h = image->h;
-		dst.w = image->w;
-		dst.x = it->x - dst.w / 2;
-		dst.y = it->y - dst.h / 2;
+		size_t group = it->reverse ? 1 : 0;
+		IImage * image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
 
-		if (it->reverse)
+		if(image)
 		{
-			SDL_Surface *rev = CSDL_Ext::verticalFlip(image);
-			CSDL_Ext::blit8bppAlphaTo24bpp(rev, nullptr, to, &dst);
-			SDL_FreeSurface(rev);
-		}
-		else
-		{
-			CSDL_Ext::blit8bppAlphaTo24bpp(image, nullptr, to, &dst);
+			SDL_Rect dst;
+			dst.h = image->height();
+			dst.w = image->width();
+			dst.x = it->x - dst.w / 2;
+			dst.y = it->y - dst.h / 2;
+
+			image->draw(to, &dst, nullptr);
 		}
 
 		// Update projectile
@@ -3200,7 +3205,7 @@ void CBattleInterface::showProjectiles(SDL_Surface *to)
 				it->y = it->catapultInfo->calculateY(it->x);
 
 				++(it->frameNum);
-				it->frameNum %= idToProjectile[it->creID]->ourImages.size();
+				it->frameNum %= idToProjectile[it->creID]->size(0);
 			}
 			else
 			{
@@ -3368,11 +3373,13 @@ void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<cons
 	for (auto & elem : battleEffects)
 	{
 		int currentFrame = floor(elem->currentFrame);
-		currentFrame %= elem->anim->ourImages.size();
+		currentFrame %= elem->animation->size();
+
+		IImage * img = elem->animation->getImage(currentFrame);
+
+		SDL_Rect temp_rect = genRect(img->height(), img->width(), elem->x, elem->y);
 
-		SDL_Surface *bitmapToBlit = elem->anim->ourImages[currentFrame].bitmap;
-		SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, elem->x, elem->y);
-		SDL_BlitSurface(bitmapToBlit, nullptr, to, &temp_rect);
+		img->draw(to, &temp_rect, nullptr);
 	}
 }
 

+ 8 - 5
client/battle/CBattleInterface.h

@@ -48,6 +48,7 @@ struct BattleHex;
 struct InfoAboutHero;
 struct BattleAction;
 class CBattleGameInterface;
+class CAnimation;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -67,8 +68,7 @@ struct BattleEffect
 {
 	int x, y; //position on the screen
 	float currentFrame;
-	int maxFrame;
-	CDefHandler *anim; //animation to display
+	std::shared_ptr<CAnimation> animation;
 	int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
 	BattleHex position; //Indicates if effect which hex the effect is drawn on
 };
@@ -120,6 +120,7 @@ class CBattleInterface : public CIntObject
 	};
 private:
 	SDL_Surface *background, *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral, *cellBorders, *backgroundWithHexes;
+
 	CButton *bOptions, *bSurrender, *bFlee, *bAutofight, *bSpell,
 		* bWait, *bDefence, *bConsoleUp, *bConsoleDown, *btactNext, *btactEnd;
 	CBattleConsole *console;
@@ -128,7 +129,9 @@ private:
 	const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
 	const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
 	std::map<int, CCreatureAnimation *> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
-	std::map<int, CDefHandler *> idToProjectile; //projectiles of creatures (creatureID, defhandler)
+
+	std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
+
 	std::map<int, CDefHandler *> idToObstacle; //obstacles located on the battlefield
 	std::map<int, SDL_Surface *> idToAbsoluteObstacle; //obstacles located on the battlefield
 
@@ -340,7 +343,7 @@ public:
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
-	void displayEffect(ui32 effect, int destTile); //displays custom effect on the battlefield
+	void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
 
 	void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
 	void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
@@ -376,7 +379,7 @@ public:
 
 	friend class CBattleResultWindow;
 	friend class CBattleHero;
-	friend class CSpellEffectAnimation;
+	friend class CEffectAnimation;
 	friend class CBattleStackAnimation;
 	friend class CReverseAnimation;
 	friend class CDefenceAnimation;

+ 45 - 48
client/battle/CBattleInterfaceClasses.cpp

@@ -13,13 +13,13 @@
 #include "CBattleInterface.h"
 
 #include "../CBitmapHandler.h"
-#include "../CDefHandler.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/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/SDL_Extensions.h"
@@ -129,13 +129,18 @@ CBattleConsole::CBattleConsole() : lastShown(-1), alterTxt(""), whoSetAlter(0)
 
 void CBattleHero::show(SDL_Surface * to)
 {
+	IImage * flagFrame = flagAnimation->getImage(flagAnim, 0, true);
+
+	if(!flagFrame)
+		return;
+
 	//animation of flag
 	SDL_Rect temp_rect;
 	if(flip)
 	{
 		temp_rect = genRect(
-			flag->ourImages[flagAnim].bitmap->h,
-			flag->ourImages[flagAnim].bitmap->w,
+			flagFrame->height(),
+			flagFrame->width(),
 			pos.x + 61,
 			pos.y + 39);
 
@@ -143,28 +148,30 @@ void CBattleHero::show(SDL_Surface * to)
 	else
 	{
 		temp_rect = genRect(
-			flag->ourImages[flagAnim].bitmap->h,
-			flag->ourImages[flagAnim].bitmap->w,
+			flagFrame->height(),
+			flagFrame->width(),
 			pos.x + 72,
 			pos.y + 39);
 	}
-	CSDL_Ext::blit8bppAlphaTo24bpp(
-		flag->ourImages[flagAnim].bitmap,
-		nullptr,
-		screen,
-		&temp_rect);
+
+	flagFrame->draw(screen, &temp_rect, nullptr); //FIXME: why screen?
 
 	//animation of hero
 	SDL_Rect rect = pos;
-	CSDL_Ext::blit8bppAlphaTo24bpp(dh->ourImages[currentFrame].bitmap, nullptr, to, &rect);
 
-	if ( ++animCount == 4 )
+	IImage * heroFrame = animation->getImage(currentFrame, phase, true);
+	if(!heroFrame)
+		return;
+
+	heroFrame->draw(to, &rect, nullptr);
+
+	if(++animCount >= 4)
 	{
 		animCount = 0;
-		if ( ++flagAnim >= flag->ourImages.size())
+		if(++flagAnim >= flagAnimation->size(0))
 			flagAnim = 0;
 
-		if ( ++currentFrame >= lastFrame)
+		if(++currentFrame >= lastFrame)
 			switchToNextPhase();
 	}
 }
@@ -190,7 +197,13 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 	if(myOwner->spellDestSelectMode) //we are casting a spell
 		return;
 
-	if(myHero != nullptr && !down &&  myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
+	if(boost::logic::indeterminate(down))
+		return;
+
+	if(!myHero || down || !myOwner->myTurn)
+		return;
+
+	if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
 	{
 		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
 		{
@@ -205,6 +218,9 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 
 void CBattleHero::clickRight(tribool down, bool previousState)
 {
+	if(boost::logic::indeterminate(down))
+		return;
+
 	Point windowPosition;
 	windowPosition.x = (!flip) ? myOwner->pos.topLeft().x + 1 : myOwner->pos.topRight().x - 79;
 	windowPosition.y = myOwner->pos.y + 135;
@@ -220,24 +236,19 @@ void CBattleHero::clickRight(tribool down, bool previousState)
 
 void CBattleHero::switchToNextPhase()
 {
-	if (phase != nextPhase)
+	if(phase != nextPhase)
 	{
 		phase = nextPhase;
 
-		//find first and last frames of our animation
-		for (firstFrame = 0;
-		     firstFrame < dh->ourImages.size() && dh->ourImages[firstFrame].groupNumber != phase;
-		     firstFrame++);
+		firstFrame = 0;
 
-		for (lastFrame = firstFrame;
-			 lastFrame < dh->ourImages.size() && dh->ourImages[lastFrame].groupNumber == phase;
-			 lastFrame++);
+		lastFrame = animation->size(phase);
 	}
 
 	currentFrame = firstFrame;
 }
 
-CBattleHero::CBattleHero(const std::string & defName, bool flipG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner):
+CBattleHero::CBattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner):
     flip(flipG),
     myHero(hero),
     myOwner(owner),
@@ -246,39 +257,25 @@ CBattleHero::CBattleHero(const std::string & defName, bool flipG, PlayerColor pl
     flagAnim(0),
     animCount(0)
 {
-	dh = CDefHandler::giveDef( defName );
-	for(auto & elem : dh->ourImages) //transforming images
-	{
-		if(flip)
-		{
-			SDL_Surface * hlp = CSDL_Ext::verticalFlip(elem.bitmap);
-			SDL_FreeSurface(elem.bitmap);
-			elem.bitmap = hlp;
-		}
-		CSDL_Ext::alphaTransform(elem.bitmap);
-	}
+	animation = std::make_shared<CAnimation>(animationPath);
+	animation->preload();
+	if(flipG)
+		animation->verticalFlip();
 
 	if(flip)
-		flag = CDefHandler::giveDef("CMFLAGR.DEF");
+		flagAnimation = std::make_shared<CAnimation>("CMFLAGR");
 	else
-		flag = CDefHandler::giveDef("CMFLAGL.DEF");
+		flagAnimation = std::make_shared<CAnimation>("CMFLAGL");
+
+	flagAnimation->preload();
+	flagAnimation->playerColored(player);
 
-	//coloring flag and adding transparency
-	for(auto & elem : flag->ourImages)
-	{
-		CSDL_Ext::alphaTransform(elem.bitmap);
-		graphics->blueToPlayersAdv(elem.bitmap, player);
-	}
 	addUsedEvents(LCLICK | RCLICK | HOVER);
 
 	switchToNextPhase();
 }
 
-CBattleHero::~CBattleHero()
-{
-	delete dh;
-	delete flag;
-}
+CBattleHero::~CBattleHero() = default;
 
 CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInterface *owner)
 {

+ 8 - 4
client/battle/CBattleInterfaceClasses.h

@@ -14,7 +14,6 @@
 #include "../windows/CWindowObject.h"
 
 struct SDL_Surface;
-class CDefHandler;
 class CGHeroInstance;
 class CBattleInterface;
 class CPicture;
@@ -53,19 +52,24 @@ class CBattleHero : public CIntObject
 	void switchToNextPhase();
 public:
 	bool flip; //false if it's attacking hero, true otherwise
-	CDefHandler *dh, *flag; //animation and flag
+
+	std::shared_ptr<CAnimation> animation;
+	std::shared_ptr<CAnimation> flagAnimation;
+
 	const CGHeroInstance * myHero; //this animation's hero instance
 	const CBattleInterface * myOwner; //battle interface to which this animation is assigned
 	int phase; //stage of animation
 	int nextPhase; //stage of animation to be set after current phase is fully displayed
 	int currentFrame, firstFrame, lastFrame; //frame of animation
-	ui8 flagAnim, animCount; //for flag animation
+
+	size_t flagAnim;
+	ui8 animCount; //for flag animation
 	void show(SDL_Surface * to) override; //prints next frame of animation to to
 	void setPhase(int newPhase); //sets phase of hero animation
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override; //call-in
 	void clickRight(tribool down, bool previousState) override; //call-in
-	CBattleHero(const std::string &defName, bool filpG, PlayerColor player, const CGHeroInstance *hero, const CBattleInterface *owner);
+	CBattleHero(const std::string & animationPath, bool filpG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner);
 	~CBattleHero();
 };
 

+ 45 - 172
client/battle/CCreatureAnimation.cpp

@@ -10,14 +10,10 @@
 #include "StdInc.h"
 #include "CCreatureAnimation.h"
 
-#include "../../lib/vcmi_endian.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CCreatureHandler.h"
-#include "../../lib/filesystem/Filesystem.h"
-#include "../../lib/filesystem/CBinaryReader.h"
-#include "../../lib/filesystem/CMemoryStream.h"
 
-#include "../gui/SDL_Pixels.h"
+#include "../gui/SDL_Extensions.h"
 
 static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
 static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
@@ -142,60 +138,43 @@ void CCreatureAnimation::setType(CCreatureAnim::EAnimType type)
 	play();
 }
 
-CCreatureAnimation::CCreatureAnimation(std::string name, TSpeedController controller)
-    : defName(name),
-      speed(0.1),
-      currentFrame(0),
-      elapsedTime(0),
+CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedController controller)
+	: name(name_),
+	  speed(0.1),
+	  currentFrame(0),
+	  elapsedTime(0),
 	  type(CCreatureAnim::HOLDING),
 	  border(CSDL_Ext::makeColor(0, 0, 0, 0)),
-      speedController(controller),
-      once(false)
+	  speedController(controller),
+	  once(false)
 {
-	// separate block to avoid accidental use of "data" after it was moved into "pixelData"
-	{
-		ResourceID resID(std::string("SPRITES/") + name, EResType::ANIMATION);
-
-		auto data = CResourceHandler::get()->load(resID)->readAll();
-
-		pixelData = std::move(data.first);
-		pixelDataSize = data.second;
-	}
-
-	CMemoryStream stm(pixelData.get(), pixelDataSize);
-
-	CBinaryReader reader(&stm);
+	forward = std::make_shared<CAnimation>(name_);
+	reverse = std::make_shared<CAnimation>(name_);
 
-	reader.readInt32(); // def type, unused
+	//todo: optimize
+	forward->preload();
+	reverse->preload();
 
-	fullWidth  = reader.readInt32();
-	fullHeight = reader.readInt32();
-
-	int totalBlocks = reader.readInt32();
-
-	for (auto & elem : palette)
+	// if necessary, add one frame into vcmi-only group DEAD
+	if(forward->size(CCreatureAnim::DEAD) == 0)
 	{
-		elem.r = reader.readUInt8();
-		elem.g = reader.readUInt8();
-		elem.b = reader.readUInt8();
-		elem.a = SDL_ALPHA_OPAQUE;
+		forward->duplicateImage(CCreatureAnim::DEATH, forward->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
+		reverse->duplicateImage(CCreatureAnim::DEATH, reverse->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
 	}
 
-	for (int i=0; i<totalBlocks; i++)
-	{
-		int groupID = reader.readInt32();
-
-		int totalInBlock = reader.readInt32();
-
-		reader.skip(4 + 4 + 13 * totalInBlock); // some unused data
+	//TODO: get dimensions form CAnimation
+	IImage * first = forward->getImage(0, type, true);
 
-		for (int j=0; j<totalInBlock; j++)
-			dataOffsets[groupID].push_back(reader.readUInt32());
+	if(!first)
+	{
+		fullWidth = 0;
+		fullHeight = 0;
+		return;
 	}
+	fullWidth = first->width();
+	fullHeight = first->height();
 
-	// if necessary, add one frame into vcmi-only group DEAD
-	if (dataOffsets.count(CCreatureAnim::DEAD) == 0)
-		dataOffsets[CCreatureAnim::DEAD].push_back(dataOffsets[CCreatureAnim::DEATH].back());
+	reverse->verticalFlip();
 
 	play();
 }
@@ -285,142 +264,35 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
 			);
 }
 
-std::array<SDL_Color, 8> CCreatureAnimation::genSpecialPalette()
+void CCreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
 {
-	std::array<SDL_Color, 8> ret;
-
-	ret[0] = genShadow(0);
-	ret[1] = genShadow(64);
-	ret[2] = genShadow(128);
-	ret[3] = genShadow(128);
-	ret[4] = genShadow(128);
-	ret[5] = genBorderColor(getBorderStrength(elapsedTime), border);
-	ret[6] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
-	ret[7] = addColors(genShadow(64),  genBorderColor(getBorderStrength(elapsedTime), border));
-
-	return ret;
+	target[0] = genBorderColor(getBorderStrength(elapsedTime), border);
+	target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
+	target[2] = addColors(genShadow(64),  genBorderColor(getBorderStrength(elapsedTime), border));
 }
 
-template<int bpp>
-void CCreatureAnimation::nextFrameT(SDL_Surface * dest, bool rotate)
+void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker)
 {
-	assert(dataOffsets.count(type) && dataOffsets.at(type).size() > size_t(currentFrame));
-
-	ui32 offset = dataOffsets.at(type).at(floor(currentFrame));
+	size_t frame = floor(currentFrame);
 
-	CMemoryStream stm(pixelData.get(), pixelDataSize);
+	IImage * image = nullptr;
 
-	CBinaryReader reader(&stm);
-
-	reader.getStream()->seek(offset);
-
-	reader.readUInt32(); // unused, size of pixel data for this frame
-	const ui32 defType2 = reader.readUInt32();
-	const ui32 fullWidth = reader.readUInt32();
-	/*const ui32 fullHeight =*/ reader.readUInt32();
-	const ui32 spriteWidth = reader.readUInt32();
-	const ui32 spriteHeight = reader.readUInt32();
-	const int leftMargin = reader.readInt32();
-	const int topMargin = reader.readInt32();
-
-	const int rightMargin = fullWidth - spriteWidth - leftMargin;
-	//const int bottomMargin = fullHeight - spriteHeight - topMargin;
-
-	const size_t baseOffset = reader.getStream()->tell();
+	if(attacker)
+		image = forward->getImage(frame, type);
+	else
+		image = reverse->getImage(frame, type);
 
-	assert(defType2 == 1);
-	UNUSED(defType2);
+	IImage::BorderPallete borderPallete;
+	genBorderPalette(borderPallete);
 
-	auto specialPalette = genSpecialPalette();
+	image->setBorderPallete(borderPallete);
 
-	for (ui32 i=0; i<spriteHeight; i++)
-	{
-		//NOTE: if this loop will be optimized to skip empty lines - recheck this read access
-		ui8 * lineData = pixelData.get() + baseOffset + reader.readUInt32();
-
-		size_t destX = pos.x;
-		if (rotate)
-			destX += rightMargin + spriteWidth - 1;
-		else
-			destX += leftMargin;
-
-		size_t destY = pos.y + topMargin + i;
-		size_t currentOffset = 0;
-		size_t totalRowLength = 0;
-
-		while (totalRowLength < spriteWidth)
-		{
-			ui8 type = lineData[currentOffset++];
-			ui32 length = lineData[currentOffset++] + 1;
-
-			if (type==0xFF)//Raw data
-			{
-				for (size_t j=0; j<length; j++)
-					putPixelAt<bpp>(dest, destX + (rotate?(-j):(j)), destY, lineData[currentOffset + j], specialPalette);
-
-				currentOffset += length;
-			}
-			else// RLE
-			{
-				if (type != 0) // transparency row, handle it here for speed
-				{
-					for (size_t j=0; j<length; j++)
-						putPixelAt<bpp>(dest, destX + (rotate?(-j):(j)), destY, type, specialPalette);
-				}
-			}
-
-			destX += rotate ? (-length) : (length);
-			totalRowLength += length;
-		}
-	}
-}
-
-void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker)
-{
-	// Note: please notice that attacker value is inversed when passed further.
-	// This is intended behavior because "attacker" actually does not needs rotation
-	switch(dest->format->BytesPerPixel)
-	{
-	case 2: return nextFrameT<2>(dest, !attacker);
-	case 3: return nextFrameT<3>(dest, !attacker);
-	case 4: return nextFrameT<4>(dest, !attacker);
-	default:
-		logGlobal->error("%d bpp is not supported!", (int)dest->format->BitsPerPixel);
-	}
+	image->draw(dest, pos.x, pos.y);
 }
 
 int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
 {
-	if(dataOffsets.count(group) == 0)
-		return 0;
-
-	return dataOffsets.at(group).size();
-}
-
-ui8 * CCreatureAnimation::getPixelAddr(SDL_Surface * dest, int X, int Y) const
-{
-	return (ui8*)dest->pixels + X * dest->format->BytesPerPixel + Y * dest->pitch;
-}
-
-template<int bpp>
-inline void CCreatureAnimation::putPixelAt(SDL_Surface * dest, int X, int Y, size_t index, const std::array<SDL_Color, 8> & special) const
-{
-	if ( X < pos.x + pos.w && Y < pos.y + pos.h && X >= 0 && Y >= 0)
-		putPixel<bpp>(getPixelAddr(dest, X, Y), palette[index], index, special);
-}
-
-template<int bpp>
-inline void CCreatureAnimation::putPixel(ui8 * dest, const SDL_Color & color, size_t index, const std::array<SDL_Color, 8> & special) const
-{
-	if (index < 8)
-	{
-		const SDL_Color & pal = special[index];
-		ColorPutter<bpp, 0>::PutColor(dest, pal.r, pal.g, pal.b, pal.a);
-	}
-	else
-	{
-		ColorPutter<bpp, 0>::PutColor(dest, color.r, color.g, color.b);
-	}
+	return forward->size(group);
 }
 
 bool CCreatureAnimation::isDead() const
@@ -456,7 +328,8 @@ void CCreatureAnimation::pause()
 
 void CCreatureAnimation::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)
+    if(speedController(this, type) != 0)
         speed = 1 / speedController(this, type);
 }

+ 10 - 30
client/battle/CCreatureAnimation.h

@@ -10,8 +10,8 @@
 #pragma once
 
 #include "../../lib/FunctionList.h"
-#include "../gui/SDL_Extensions.h"
 #include "../widgets/Images.h"
+#include "../gui/CAnimation.h"
 
 class CIntObject;
 class CCreatureAnimation;
@@ -52,21 +52,12 @@ public:
 	typedef std::function<float(CCreatureAnimation *, size_t)> TSpeedController;
 
 private:
-	std::string defName;
+	std::string name;
+	std::shared_ptr<CAnimation> forward;
+	std::shared_ptr<CAnimation> reverse;
 
-	int fullWidth, fullHeight;
-
-	// palette, as read from def file
-	std::array<SDL_Color, 256> palette;
-
-	//key = id of group (note that some groups may be missing)
-	//value = offset of pixel data for each frame, vector size = number of frames in group
-	std::map<int, std::vector<unsigned int>> dataOffsets;
-
-	//animation raw data
-	//TODO: use vector instead?
-	std::unique_ptr<ui8[]> pixelData;
-	size_t pixelDataSize;
+	int fullWidth;
+	int fullHeight;
 
 	// speed of animation, measure in frames per second
 	float speed;
@@ -85,21 +76,10 @@ private:
 
 	bool once; // animation will be played once and the reset to idling
 
-	ui8 * getPixelAddr(SDL_Surface * dest, int ftcpX, int ftcpY) const;
-
-	template<int bpp>
-	void putPixelAt(SDL_Surface * dest, int X, int Y, size_t index, const std::array<SDL_Color, 8> & special) const;
-
-	template<int bpp>
-	void putPixel( ui8 * dest, const SDL_Color & color, size_t index, const std::array<SDL_Color, 8> & special) const;
-
-	template<int bpp>
-	void nextFrameT(SDL_Surface * dest, bool rotate);
-
 	void endAnimation();
 
-	/// creates 8 special colors for current frame
-	std::array<SDL_Color, 8> genSpecialPalette();
+
+	void genBorderPalette(IImage::BorderPallete & target);
 public:
 
 	// function(s) that will be called when animation ends, after reset to 1st frame
@@ -113,12 +93,12 @@ public:
 	/// name - path to .def file, relative to SPRITES/ directory
 	/// controller - function that will return for how long *each* frame
 	/// in specified group of animation should be played, measured in seconds
-	CCreatureAnimation(std::string name, TSpeedController speedController);
+	CCreatureAnimation(const std::string & name_, TSpeedController speedController);
 
 	void setType(CCreatureAnim::EAnimType type); //sets type of animation and cleares framecount
 	CCreatureAnim::EAnimType getType() const; //returns type of animation
 
-	void nextFrame(SDL_Surface * dest, bool rotate);
+	void nextFrame(SDL_Surface * dest, bool attacker);
 
 	// should be called every frame, return true when animation was reset to beginning
 	bool incrementFrame(float timePassed);

+ 145 - 10
client/gui/CAnimation.cpp

@@ -29,7 +29,7 @@ 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, methods are based on CDefHandler
+/// Class for def loading
 /// After loading will store general info (palette and frame offsets) and pointer to file itself
 class CDefFile
 {
@@ -100,6 +100,8 @@ public:
 
 	void shiftPalette(int from, int howMany) override;
 
+	void setBorderPallete(const BorderPallete & borderPallete) override;
+
 	friend class SDLImageLoader;
 };
 
@@ -157,6 +159,7 @@ public:
 	void verticalFlip() override;
 
 	void shiftPalette(int from, int howMany) override;
+	void setBorderPallete(const BorderPallete & borderPallete) override;
 
 	friend class CompImageLoader;
 };
@@ -247,16 +250,50 @@ public:
 	}
 };
 
+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)
 {
+
+	#if 0
+	static SDL_Color H3_ORIG_PALETTE[8] =
+	{
+	   {  0, 255, 255, SDL_ALPHA_OPAQUE},
+	   {255, 150, 255, SDL_ALPHA_OPAQUE},
+	   {255, 100, 255, SDL_ALPHA_OPAQUE},
+	   {255,  50, 255, SDL_ALPHA_OPAQUE},
+	   {255,   0, 255, SDL_ALPHA_OPAQUE},
+	   {255, 255, 0,   SDL_ALPHA_OPAQUE},
+	   {180,   0, 255, SDL_ALPHA_OPAQUE},
+	   {  0, 255, 0,   SDL_ALPHA_OPAQUE}
+	};
+	#endif // 0
+
 	//First 8 colors in def palette used for transparency
 	static SDL_Color H3Palette[8] =
 	{
@@ -289,10 +326,59 @@ CDefFile::CDefFile(std::string Name):
 		palette[i].b = data[it++];
 		palette[i].a = SDL_ALPHA_OPAQUE;
 	}
-	if (type == 71 || type == 64)//Buttons/buildings don't have shadows\semi-transparency
-		memset(palette.get(), 0, sizeof(SDL_Color)*2);
-	else
-		memcpy(palette.get(), H3Palette, sizeof(SDL_Color)*8);//initialize shadow\selection colors
+
+	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++)
 	{
@@ -902,6 +988,15 @@ void SDLImage::shiftPalette(int from, int howMany)
 	}
 }
 
+void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
+{
+	if(surf->format->palette)
+	{
+		SDL_SetColors(surf, const_cast<SDL_Color *>(borderPallete.data()), 5, 3);
+	}
+}
+
+
 SDLImage::~SDLImage()
 {
 	SDL_FreeSurface(surf);
@@ -1155,22 +1250,27 @@ CompImage::~CompImage()
 
 void CompImage::horizontalFlip()
 {
-	logAnim->error("CompImage::horizontalFlip is not implemented");
+	logAnim->error("%s is not implemented", BOOST_CURRENT_FUNCTION);
 }
 
 void CompImage::verticalFlip()
 {
-	logAnim->error("CompImage::verticalFlip is not implemented");
+	logAnim->error("%s is not implemented", BOOST_CURRENT_FUNCTION);
 }
 
 void CompImage::shiftPalette(int from, int howMany)
 {
-	logAnim->error("CompImage::shiftPalette is not implemented");
+	logAnim->error("%s is not implemented", BOOST_CURRENT_FUNCTION);
 }
 
-void CompImage::exportBitmap(const boost::filesystem::path& path) const
+void CompImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
 {
-	logAnim->error("CompImage::exportBitmap is not implemented");
+	logAnim->error("%s is not implemented", BOOST_CURRENT_FUNCTION);
+}
+
+void CompImage::exportBitmap(const boost::filesystem::path & path) const
+{
+	logAnim->error("%s is not implemented", BOOST_CURRENT_FUNCTION);
 }
 
 
@@ -1388,6 +1488,9 @@ CAnimation::CAnimation(std::string Name, bool Compressed):
 	CDefFile * file = getFile();
 	init(file);
 	delete file;
+
+	if(source.empty())
+		logAnim->error("Animation %s failed to load", Name);
 }
 
 CAnimation::CAnimation():
@@ -1519,6 +1622,38 @@ size_t CAnimation::size(size_t group) const
 	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);
+
+		IImage * image = getImage(frame, targetGroup);
+		image->verticalFlip();
+	}
+}
+
 float CFadeAnimation::initialCounter() const
 {
 	if (fadingMode == EMode::OUT)

+ 10 - 0
client/gui/CAnimation.h

@@ -24,6 +24,7 @@ class IImage
 {
 	int refCount;
 public:
+	using BorderPallete = std::array<SDL_Color, 3>;
 
 	//draws image on surface "where" at position
 	virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, Rect * src = nullptr, ui8 alpha = 255) const=0;
@@ -49,6 +50,9 @@ public:
 	//only indexed bitmaps, 16 colors maximum
 	virtual void shiftPalette(int from, int howMany) = 0;
 
+	//only indexed bitmaps, colors 5,6,7 must be special
+	virtual void setBorderPallete(const BorderPallete & borderPallete) = 0;
+
 	virtual void horizontalFlip() = 0;
 	virtual void verticalFlip() = 0;
 
@@ -127,6 +131,12 @@ public:
 
 	//total count of frames in group (including not loaded)
 	size_t size(size_t group=0) const;
+
+	void horizontalFlip();
+	void verticalFlip();
+	void playerColored(PlayerColor player);
+
+	void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup);
 };
 
 const float DEFAULT_DELTA = 0.05f;

+ 19 - 28
client/gui/CCursorHandler.cpp

@@ -23,11 +23,20 @@ void CCursorHandler::initCursor()
 	xpos = ypos = 0;
 	type = ECursor::DEFAULT;
 	dndObject = nullptr;
-	currentCursor = nullptr;
+
+	cursors =
+	{
+		make_unique<CAnimImage>("CRADVNTR", 0),
+		make_unique<CAnimImage>("CRCOMBAT", 0),
+		make_unique<CAnimImage>("CRDEFLT",  0),
+		make_unique<CAnimImage>("CRSPELL",  0)
+	};
+
+	currentCursor = cursors.at(int(ECursor::DEFAULT)).get();
 
 	help = CSDL_Ext::newSurface(40,40);
 	//No blending. Ensure, that we are copying pixels during "screen restore draw"
-	SDL_SetSurfaceBlendMode(help,SDL_BLENDMODE_NONE);	
+	SDL_SetSurfaceBlendMode(help,SDL_BLENDMODE_NONE);
 	SDL_ShowCursor(SDL_DISABLE);
 
 	changeGraphic(ECursor::ADVENTURE, 0);
@@ -35,32 +44,23 @@ void CCursorHandler::initCursor()
 
 void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
 {
-	std::string cursorDefs[4] = { "CRADVNTR.DEF", "CRCOMBAT.DEF", "CRDEFLT.DEF", "CRSPELL.DEF" };
-
-	if (type != this->type)
+	if(type != this->type)
 	{
-		BLOCK_CAPTURING; // not used here
-
 		this->type = type;
 		this->frame = index;
-
-		delete currentCursor;
-		currentCursor = new CAnimImage(cursorDefs[int(type)], index);
+		currentCursor = cursors.at(int(type)).get();
+		currentCursor->setFrame(index);
 	}
-
-	if (frame != index)
+	else if(index != this->frame)
 	{
-		frame = index;
+		this->frame = index;
 		currentCursor->setFrame(index);
 	}
 }
 
-void CCursorHandler::dragAndDropCursor(CAnimImage * object)
+void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
 {
-	if (dndObject)
-		delete dndObject;
-
-	dndObject = object;
+	dndObject = std::move(object);
 }
 
 void CCursorHandler::cursorMove(const int & x, const int & y)
@@ -101,13 +101,6 @@ void CCursorHandler::drawRestored()
 
 	SDL_Rect temp_rect = genRect(40, 40, x, y);
 	SDL_BlitSurface(help, nullptr, screen, &temp_rect);
-	//blitAt(help,x,y);
-}
-
-void CCursorHandler::draw(SDL_Surface *to)
-{
-	currentCursor->moveTo(Point(xpos, ypos));
-	currentCursor->showAll(screen);
 }
 
 void CCursorHandler::shiftPos( int &x, int &y )
@@ -233,12 +226,10 @@ void CCursorHandler::render()
 	drawRestored();
 }
 
+CCursorHandler::CCursorHandler() = default;
 
 CCursorHandler::~CCursorHandler()
 {
 	if(help)
 		SDL_FreeSurface(help);
-
-	delete currentCursor;
-	delete dndObject;
 }

+ 10 - 8
client/gui/CCursorHandler.h

@@ -17,27 +17,28 @@ namespace ECursor
 	enum ECursorTypes { ADVENTURE, COMBAT, DEFAULT, SPELLBOOK };
 
 	enum EBattleCursors { COMBAT_BLOCKED, COMBAT_MOVE, COMBAT_FLY, COMBAT_SHOOT,
-						COMBAT_HERO, COMBAT_QUERY, COMBAT_POINTER, 
+						COMBAT_HERO, COMBAT_QUERY, COMBAT_POINTER,
 						//various attack frames
 						COMBAT_SHOOT_PENALTY = 15, COMBAT_SHOOT_CATAPULT, COMBAT_HEAL,
 						COMBAT_SACRIFICE, COMBAT_TELEPORT};
 }
 
 /// handles mouse cursor
-class CCursorHandler 
+class CCursorHandler final
 {
 	SDL_Surface * help;
 	CAnimImage * currentCursor;
-	CAnimImage * dndObject; //if set, overrides currentCursor
+
+	std::unique_ptr<CAnimImage> dndObject; //if set, overrides currentCursor
+
+	std::array<std::unique_ptr<CAnimImage>, 4> cursors;
+
 	bool showing;
 
 	/// Draw cursor preserving original image below cursor
 	void drawWithScreenRestore();
 	/// Restore original image below cursor
 	void drawRestored();
-	/// Simple draw cursor
-	void draw(SDL_Surface *to);
-	
 public:
 	/// position of cursor
 	int xpos, ypos;
@@ -58,8 +59,8 @@ public:
 	 * @param image Image to replace cursor with or nullptr to use the normal
 	 * cursor. CursorHandler takes ownership of object
 	 */
-	void dragAndDropCursor (CAnimImage * image);
-	
+	void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
+
 	void render();
 
 	void shiftPos( int &x, int &y );
@@ -71,5 +72,6 @@ public:
 	/// Move cursor to screen center
 	void centerCursor();
 
+	CCursorHandler();
 	~CCursorHandler();
 };

+ 1 - 2
client/gui/CGuiHandler.cpp

@@ -459,8 +459,7 @@ void CGuiHandler::renderFrame()
 		// draw the mouse cursor and update the screen
 		CCS->curh->render();
 
-		if(0 != SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr))
-			logGlobal->error("%s SDL_RenderCopy %s", __FUNCTION__, SDL_GetError());
+		SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr);
 
 		SDL_RenderPresent(mainRenderer);
 	}

+ 2 - 2
client/widgets/CArtifactHolder.cpp

@@ -290,7 +290,7 @@ void CHeroArtPlace::select ()
 		}
 	}
 
-	CCS->curh->dragAndDropCursor(new CAnimImage("artifact", ourArt->artType->iconIndex));
+	CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", ourArt->artType->iconIndex));
 	ourOwner->commonInfo->src.setTo(this, false);
 	ourOwner->markPossibleSlots(ourArt);
 
@@ -766,7 +766,7 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact
 			commonInfo->src.art = dst.getArt();
 			commonInfo->src.slotID = dst.slot;
 			assert(commonInfo->src.AOH);
-			CCS->curh->dragAndDropCursor(new CAnimImage("artifact", dst.getArt()->artType->iconIndex));
+			CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", dst.getArt()->artType->iconIndex));
 			markPossibleSlots(dst.getArt());
 		}
 	}

+ 3 - 2
client/widgets/CComponent.cpp

@@ -22,6 +22,7 @@
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CCreatureHandler.h"
+#include "../../lib/CSkillHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/NetPacksBase.h"
@@ -148,7 +149,7 @@ std::string CComponent::getDescription()
 	{
 	case primskill:  return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill
 										 : CGI->generaltexth->allTexts[149]; //mana
-	case secskill:   return CGI->generaltexth->skillInfoTexts[subtype][val-1];
+	case secskill:   return CGI->skillh->skillInfo(subtype, val);
 	case resource:   return CGI->generaltexth->allTexts[242];
 	case creature:   return "";
 	case artifact:
@@ -192,7 +193,7 @@ std::string CComponent::getSubtitleInternal()
 	switch(compType)
 	{
 	case primskill:  return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387]));
-	case secskill:   return CGI->generaltexth->levels[val-1] + "\n" + CGI->generaltexth->skillName[subtype];
+	case secskill:   return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->skillName(subtype);
 	case resource:   return boost::lexical_cast<std::string>(val);
 	case creature:   return (val? boost::lexical_cast<std::string>(val) + " " : "") + CGI->creh->creatures[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing);
 	case artifact:   return CGI->arth->artifacts[subtype]->Name();

+ 16 - 16
client/windows/CHeroWindow.cpp

@@ -24,6 +24,7 @@
 
 #include "../gui/SDL_Extensions.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/CAnimation.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/CComponent.h"
 
@@ -33,6 +34,7 @@
 #include "../lib/CConfigHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
+#include "../lib/CSkillHandler.h"
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/NetPacksBase.h"
 #include "../mapHandler.h"
@@ -106,7 +108,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
 	quitButton = new CButton(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [&](){ close(); }, SDLK_RETURN);
 	quitButton->assignedKeys.insert(SDLK_ESCAPE);
 	dismissButton = new CButton(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [&](){ dismissCurrent(); }, SDLK_d);
-	questlogButton = new CButton(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [&](){ questlog(); }, SDLK_q);
+	questlogButton = new CButton(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, SDLK_q);
 
 	formations = new CToggleGroup(0);
 	formations->addToggle(0, new CToggleButton(Point(481, 483), "hsbtns6.def", std::make_pair(heroscrn[23], heroscrn[29]), 0, SDLK_t));
@@ -143,11 +145,12 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
 	luck = new MoraleLuckBox(false, Rect(233,179,53,45));
 	spellPointsArea = new LRClickableAreaWText(Rect(162,228, 136, 42), CGI->generaltexth->heroscrn[22]);
 
+	auto secSkills = std::make_shared<CAnimation>("SECSKILL");
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	{
 		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
 		secSkillAreas.push_back(new LRClickableAreaWTextComp(r, CComponent::secskill));
-		secSkillImages.push_back(new CAnimImage("SECSKILL", 0, 0, r.x, r.y));
+		secSkillImages.push_back(new CAnimImage(secSkills, 0, 0, r.x, r.y));
 	}
 
 	//dismiss / quest log
@@ -157,12 +160,14 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
 	//////////////////////////////////////////////////////////////////////////???????????????
 
 	//primary skills & exp and mana
-	new CAnimImage("PSKIL42", 0, 0, 32, 111, false);
-	new CAnimImage("PSKIL42", 1, 0, 102, 111, false);
-	new CAnimImage("PSKIL42", 2, 0, 172, 111, false);
-	new CAnimImage("PSKIL42", 3, 0, 162, 230, false);
-	new CAnimImage("PSKIL42", 4, 0, 20, 230, false);
-	new CAnimImage("PSKIL42", 5, 0, 242, 111, false);
+	auto primSkills = std::make_shared<CAnimation>("PSKIL42");
+	primSkills->preload();
+	new CAnimImage(primSkills, 0, 0, 32, 111);
+	new CAnimImage(primSkills, 1, 0, 102, 111);
+	new CAnimImage(primSkills, 2, 0, 172, 111);
+	new CAnimImage(primSkills, 3, 0, 162, 230);
+	new CAnimImage(primSkills, 4, 0, 20, 230);
+	new CAnimImage(primSkills, 5, 0, 242, 111);
 
 	// various texts
 	new CLabel( 52, 99, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[1]);
@@ -240,8 +245,8 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 			level = curHero->getSecSkillLevel(SecondarySkill(curHero->secSkills[g].first));
 		secSkillAreas[g]->type = skill;
 		secSkillAreas[g]->bonusValue = level;
-		secSkillAreas[g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1];
-		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->generaltexth->skillName[skill]);
+		secSkillAreas[g]->text = CGI->skillh->skillInfo(skill, level);
+		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->skillh->skillName(skill));
 		secSkillImages[g]->setFrame(skill*3 + level + 2);
 	}
 
@@ -305,11 +310,6 @@ void CHeroWindow::dismissCurrent()
 	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, 0, false);
 }
 
-void CHeroWindow::questlog()
-{
-	LOCPLINT->showQuestLog();
-}
-
 void CHeroWindow::commanderWindow()
 {
 	//TODO: allow equipping commander artifacts by drag / drop
@@ -372,7 +372,7 @@ void CHeroWindow::showAll(SDL_Surface * to)
 	for(size_t v=0; v<std::min(secSkillAreas.size(), curHero->secSkills.size()); ++v)
 	{
 	 	printAtLoc(CGI->generaltexth->levels[curHero->secSkills[v].second-1], (v%2) ? 212 : 68, 280 + 48 * (v/2), FONT_SMALL, Colors::WHITE, to);
-	 	printAtLoc(CGI->generaltexth->skillName[curHero->secSkills[v].first], (v%2) ? 212 : 68, 300 + 48 * (v/2), FONT_SMALL, Colors::WHITE, to);
+		printAtLoc(CGI->skillh->skillName(curHero->secSkills[v].first), (v%2) ? 212 : 68, 300 + 48 * (v/2), FONT_SMALL, Colors::WHITE, to);
 	}
 
 	//printing special ability

+ 0 - 1
client/windows/CHeroWindow.h

@@ -86,7 +86,6 @@ public:
 	void showAll(SDL_Surface * to) override;
 
 	void dismissCurrent(); //dissmissed currently displayed hero (curHero)
-	void questlog(); //show quest log in hero window
 	void commanderWindow();
 	void switchHero(); //changes displayed hero
 	virtual void updateGarrisons() override;  //updates the morale widget and calls the parent

+ 4 - 3
client/windows/CKingdomInterface.cpp

@@ -28,6 +28,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CModHandler.h"
+#include "../../lib/CSkillHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
@@ -170,7 +171,7 @@ std::string InfoBoxAbstractHeroData::getNameText()
 		return CGI->heroh->heroes[getSubID()]->specName;
 	case HERO_SECONDARY_SKILL:
 		if (getValue())
-			return CGI->generaltexth->skillName[getSubID()];
+			return CGI->skillh->skillName(getSubID());
 		else
 			return "";
 	default:
@@ -281,7 +282,7 @@ bool InfoBoxAbstractHeroData::prepareMessage(std::string &text, CComponent **com
 			if (!value)
 				return false;
 
-			text = CGI->generaltexth->skillInfoTexts[subID][value-1];
+			text = CGI->skillh->skillInfo(subID, value);
 			*comp = new CComponent(CComponent::secskill, subID, value);
 			return true;
 		}
@@ -356,7 +357,7 @@ std::string InfoBoxHeroData::getHoverText()
 		if (hero->secSkills.size() > index)
 		{
 			std::string level = CGI->generaltexth->levels[hero->secSkills[index].second-1];
-			std::string skill = CGI->generaltexth->skillName[hero->secSkills[index].first];
+			std::string skill = CGI->skillh->skillName(hero->secSkills[index].first);
 			return boost::str(boost::format(CGI->generaltexth->heroscrn[21]) % level % skill);
 		}
 		else

+ 1 - 1
client/windows/CTradeWindow.cpp

@@ -187,7 +187,7 @@ void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState)
 				aw->arts->markPossibleSlots(art);
 
 				//aw->arts->commonInfo->dst.AOH = aw->arts;
-				CCS->curh->dragAndDropCursor(new CAnimImage("artifact", art->artType->iconIndex));
+				CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", art->artType->iconIndex));
 
 				aw->arts->artifactsOnAltar.erase(art);
 				setID(-1);

+ 10 - 9
client/windows/GUIClasses.cpp

@@ -49,6 +49,7 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CondSh.h"
+#include "../lib/CSkillHandler.h"
 #include "../lib/spells/CSpellHandler.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CTownHandler.h"
@@ -944,11 +945,11 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 			secSkillAreas[b][g]->type = skill;
 			secSkillAreas[b][g]->bonusValue = level;
-			secSkillAreas[b][g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1];
+			secSkillAreas[b][g]->text = CGI->skillh->skillInfo(skill, level);
 
 			secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
 			boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]);
-			boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->skillName[skill]);
+			boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->skillName(skill));
 		}
 
 		portrait[b] = new CHeroArea(257 + 228*b, 13, heroInst[b]);
@@ -1222,7 +1223,7 @@ void CUniversityWindow::CItem::clickRight(tribool down, bool previousState)
 {
 	if(down)
 	{
-		CRClickPopup::createAndPush(CGI->generaltexth->skillInfoTexts[ID][0],
+		CRClickPopup::createAndPush(CGI->skillh->skillInfo(ID, 1),
 				new CComponent(CComponent::secskill, ID, 1));
 	}
 }
@@ -1230,7 +1231,7 @@ void CUniversityWindow::CItem::clickRight(tribool down, bool previousState)
 void CUniversityWindow::CItem::hover(bool on)
 {
 	if (on)
-		GH.statusbar->setText(CGI->generaltexth->skillName[ID]);
+		GH.statusbar->setText(CGI->skillh->skillName(ID));
 	else
 		GH.statusbar->clear();
 }
@@ -1264,7 +1265,7 @@ void CUniversityWindow::CItem::showAll(SDL_Surface * to)
 
 	blitAtLoc(bar->bg, -28, -22, to);
 	blitAtLoc(bar->bg, -28,  48, to);
-	printAtMiddleLoc  (CGI->generaltexth->skillName[ID], 22, -13, FONT_SMALL, Colors::WHITE,to);//Name
+	printAtMiddleLoc  (CGI->skillh->skillName(ID), 22, -13, FONT_SMALL, Colors::WHITE,to);//Name
 	printAtMiddleLoc  (CGI->generaltexth->levels[0], 22, 57, FONT_SMALL, Colors::WHITE,to);//Level(always basic)
 
 	CAnimImage::showAll(to);
@@ -1328,12 +1329,12 @@ CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bo
 
 	std::string text = CGI->generaltexth->allTexts[608];
 	boost::replace_first(text, "%s", CGI->generaltexth->levels[0]);
-	boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]);
+	boost::replace_first(text, "%s", CGI->skillh->skillName(SKILL));
 	boost::replace_first(text, "%d", "2000");
 
 	new CTextBox(text, Rect(24, 129, 413, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);//Clerk speech
 
-	new CLabel(230, 37,  FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth-> skillName[SKILL]);//Skill name
+	new CLabel(230, 37,  FONT_SMALL, CENTER, Colors::WHITE, CGI->skillh->skillName(SKILL));//Skill name
 	new CAnimImage("SECSKILL", SKILL*3+3, 0, 211, 51);//skill
 	new CLabel(230, 107, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->levels[1]);//Skill level
 
@@ -1341,11 +1342,11 @@ CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bo
 	new CLabel(230, 267, FONT_SMALL, CENTER, Colors::WHITE, "2000");//Cost
 
 	std::string hoverText = CGI->generaltexth->allTexts[609];
-	boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->generaltexth->skillName[SKILL]);
+	boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->skillh->skillName(SKILL));
 
 	text = CGI->generaltexth->zelp[633].second;
 	boost::replace_first(text, "%s", CGI->generaltexth->levels[0]);
-	boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]);
+	boost::replace_first(text, "%s", CGI->skillh->skillName(SKILL));
 	boost::replace_first(text, "%d", "2000");
 
 	confirm= new CButton(Point(148, 299), "IBY6432.DEF", CButton::tooltip(hoverText, text), [=](){makeDeal(SKILL);}, SDLK_RETURN);

+ 53 - 3
cmake_modules/VCMIUtils.cmake

@@ -63,14 +63,64 @@ endif(${CMAKE_GENERATOR} MATCHES "Xcode")
 
 # Can be called to see check cmake variables and environment variables
 # For "install" debugging just copy it here. There no easy way to include modules from source.
-function(vcmi_get_cmake_debug_info)
+function(vcmi_print_all_variables)
 
-		message(STATUS "Debug - Internal variables:")
+		message(STATUS "-- -- Start of all internal variables")
 		get_cmake_property(_variableNames VARIABLES)
 		foreach(_variableName ${_variableNames})
 				message(STATUS "${_variableName}=${${_variableName}}")
 		endforeach()
-		message(STATUS "Debug - Environment variables:")
+		message(STATUS "-- -- End of all internal variables")
+		message(STATUS "--")
+		message(STATUS "-- -- Start of all environment variables")
 		execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")
+		message(STATUS "-- -- End of all environment variables")
+
+endfunction()
+
+# Print CMake variables most important for debugging
+function(vcmi_print_important_variables)
+
+	message(STATUS "-- -- Start of VCMI build debug information")
+
+	message(STATUS "CMAKE_VERSION: " ${CMAKE_VERSION})
+	message(STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
+	message(STATUS "CMAKE_BINARY_DIR: " ${CMAKE_BINARY_DIR})
+	message(STATUS "CMAKE_SOURCE_DIR: " ${CMAKE_SOURCE_DIR})
+#	message(STATUS "PROJECT_BINARY_DIR: " ${PROJECT_BINARY_DIR})
+#	message(STATUS "PROJECT_SOURCE_DIR: " ${PROJECT_SOURCE_DIR})
+	message(STATUS "CMAKE_MODULE_PATH: " ${CMAKE_MODULE_PATH})
+	message(STATUS "CMAKE_COMMAND: " ${CMAKE_COMMAND})
+	message(STATUS "CMAKE_ROOT: " ${CMAKE_ROOT})
+#	message(STATUS "CMAKE_CURRENT_LIST_FILE and LINE: ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}")
+#	message(STATUS "CMAKE_INCLUDE_PATH: " ${CMAKE_INCLUDE_PATH})
+#	message(STATUS "CMAKE_LIBRARY_PATH: " ${CMAKE_LIBRARY_PATH})
+
+	message(STATUS "UNIX: ${UNIX} - WIN32: ${WIN32} - APPLE: ${APPLE}")
+	message(STATUS "MINGW: ${MINGW} - CYGWIN: ${CYGWIN} - MSVC: ${MSVC}")
+	message(STATUS "CMAKE_CXX_COMPILER_ID: " ${CMAKE_CXX_COMPILER_ID})
+	message(STATUS "CMAKE_CXX_COMPILER_VERSION: " ${CMAKE_CXX_COMPILER_VERSION})
+	message(STATUS "CMAKE_C_COMPILER: " ${CMAKE_C_COMPILER})
+	message(STATUS "CMAKE_CXX_COMPILER: " ${CMAKE_CXX_COMPILER})
+#	message(STATUS "CMAKE_COMPILER_IS_GNUCC: " ${CMAKE_COMPILER_IS_GNUCC})
+#	message(STATUS "CMAKE_COMPILER_IS_GNUCXX: " ${CMAKE_COMPILER_IS_GNUCXX})
+#	message(STATUS "CMAKE_C_FLAGS: " ${CMAKE_C_FLAGS})
+#	message(STATUS "CMAKE_CXX_FLAGS: " ${CMAKE_CXX_FLAGS})
+
+	message(STATUS "CMAKE_SYSTEM: " ${CMAKE_SYSTEM})
+	message(STATUS "CMAKE_SYSTEM_NAME: " ${CMAKE_SYSTEM_NAME})
+	message(STATUS "CMAKE_SYSTEM_VERSION: " ${CMAKE_SYSTEM_VERSION})
+	message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR})
+
+	message(STATUS "-- -- End of VCMI build debug information")
+
+endfunction()
+
+# Print Git commit hash
+function(vcmi_print_git_commit_hash)
+
+	message(STATUS "-- -- Start of Git information")
+	message(STATUS "GIT_SHA1: " ${GIT_SHA1})
+	message(STATUS "-- -- End of Git information")
 
 endfunction()

+ 4 - 0
config/gameConfig.json

@@ -76,5 +76,9 @@
 		"config/spells/other.json",
 		"config/spells/timed.json",
 		"config/spells/ability.json"
+	],
+	"skills" :
+	[
+        "config/skills.json"
 	]
 }

+ 5 - 0
config/schemas/mod.json

@@ -97,6 +97,11 @@
 			"description": "List of configuration files for spells",
 			"items": { "type":"string", "format" : "textFile" }
 		},
+		"skills": {
+			"type":"array",
+			"description": "List of configuration files for skills",
+			"items": { "type":"string", "format" : "textFile" }
+		},
 		"templates":{
 			"type":"array",
 			"description": "List of configuration files for RMG templates",

+ 57 - 0
config/schemas/skill.json

@@ -0,0 +1,57 @@
+{
+
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+
+	"title" : "VCMI skill format",
+	"description" : "Format used to replace bonuses provided by secondary skills in VCMI",
+
+	"definitions" : {
+
+		"skillBonus" : {
+			"type" : "object",
+			"description" : "Set of bonuses provided by skill at given level",
+			"required" : ["description", "effects"],
+			"properties" : {
+				"description" : {
+					"type" : "string",
+					"description" : "localizable description"
+				},
+				"effects" : {
+					"type" : "object",
+					"additionalProperties" : {
+						"$ref" : "vcmi:bonus"
+					}
+				}
+			}
+		}
+		
+	},
+
+	"required" : ["name", "basic", "advanced", "expert"],
+
+	"properties" : {
+		"index" : {
+			"type": "number",
+			"description": "numeric id of skill, required for existing skills"
+		},
+		"name" : {
+			"type": "string",
+			"description": "localizable skill name"
+		},
+		"base" : {
+			"type" : "object",
+			"description" : "will be merged with all levels",
+			"additionalProperties" : true
+		},
+		"basic" : {
+			"$ref" : "#/definitions/skillBonus"
+		},
+		"advanced" : {
+			"$ref" : "#/definitions/skillBonus"
+		},
+		"expert" : {
+			"$ref" : "#/definitions/skillBonus"
+		}
+	}
+}

+ 805 - 0
config/skills.json

@@ -0,0 +1,805 @@
+{
+	"pathfinding" : {
+		"index" : 0,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.pathfinding",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 25 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 50 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 75 }
+			}
+		}
+	},
+	"archery" : {
+		"index" : 1,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.archery",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 25 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 50 }
+			}
+		}
+	},
+	"logistics" : {
+		"index" : 2,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.logistics",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 20 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 30 }
+			}
+		}
+	},
+	"scouting" : {
+		"index" : 3,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"type" : "SIGHT_RADIOUS",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"diplomacy" : {
+		"index" : 4,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.diplomacy",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				},
+				"surr" : {
+					"type" : "SURRENDER_DISCOUNT",
+					"val" : 20,
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 },
+				"surr" : { "val" : 20 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val": 2 },
+				"surr" : { "val" : 40 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val": 3 },
+				"surr" : { "val" : 60 }
+			}
+		}
+	},
+	"navigation" : {
+		"index" : 5,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.navigation",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 50 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 100 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 150 }
+			}
+		}
+	},
+	"leadership" : {
+		"index" : 6,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"type" : "MORALE",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"wisdom" : {
+		"index" : 7,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.wisdom",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"mysticism" : {
+		"index" : 8,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"type" : "MANA_REGENERATION",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"luck" : {
+		"index" : 9,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"type" : "LUCK",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"ballistics" : {
+		"index" : 10,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.ballistics",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				},
+				"ctrl" : {
+					"subtype" : "creature.catapult",
+					"type" : "MANUAL_CONTROL",
+					"val" : 100,
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"eagleEye" : {
+		"index" : 11,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.eagleEye",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				},
+				"val2" : {
+					"subtype" : "skill.eagleEye",
+					"type" : "SECONDARY_SKILL_VAL2",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 40 },
+				"val2" : { "val" : 2 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 50 },
+				"val2" : { "val" : 3 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 60 },
+				"val2" : { "val" : 4 }
+			}
+		}
+	},
+	"necromancy" : {
+		"index" : 12,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.necromancy",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 20 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 30 }
+			}
+		}
+	},
+	"estates" : {
+		"index" : 13,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.estates",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"description" : "{Basic Estates}\n\nYour hero contributes 125 gold per day to your cause.",
+			"effects" : {
+				"main" : { "val" : 125 }
+			}
+		},
+		"advanced" : {
+			"description" : "{Advanced Estates}\n\nYour hero contributes 250 gold per day to your cause.",
+			"effects" : {
+				"main" : { "val" : 250 }
+			}
+		},
+		"expert" : {
+			"description" : "{Expert Estates}\n\nYour hero contributes 500 gold per day to your cause.",
+			"effects" : {
+				"main" : { "val" : 500 }
+			}
+		}
+	},
+	"fireMagic" : {
+		"index" : 14,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.fireMagic",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"airMagic" : {
+		"index" : 15,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.airMagic",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"waterMagic" : {
+		"index" : 16,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.waterMagic",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"earthMagic" : {
+		"index" : 17,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.earthMagic",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 1 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		}
+	},
+	"scholar" : {
+		"index" : 18,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.scholar",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 2 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 4 }
+			}
+		}
+	},
+	"tactics" : {
+		"index" : 19,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.tactics",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 3 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 5 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 7 }
+			}
+		}
+	},
+	"artillery" : {
+		"index" : 20,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.artillery",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				},
+				"val2" : {
+					"subtype" : "skill.artillery",
+					"type" : "SECONDARY_SKILL_VAL2",
+					"valueType" : "BASE_NUMBER"
+				},
+				"ctrl" : {
+					"subtype" : "creature.ballista",
+					"type" : "MANUAL_CONTROL",
+					"val" : 100,
+					"valueType" : "BASE_NUMBER"
+				},
+				"ctrl2" : {
+					"subtype" : "creature.arrowTower",
+					"type" : "MANUAL_CONTROL",
+					"val" : 100,
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 50 },
+				"val2" : { "val" : 0 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 75 },
+				"val2" : { "val" : 1 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 100 },
+				"val2" : { "val" : 1 }
+			}
+		}
+	},
+	"learning" : {
+		"index" : 21,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.learning",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 5 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 15 }
+			}
+		}
+	},
+	"offence" : {
+		"index" : 22,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.offence",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 20 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 30 }
+			}
+		}
+	},
+	"armorer" : {
+		"index" : 23,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.armorer",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 5 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 15 }
+			}
+		}
+	},
+	"intelligence" : {
+		"index" : 24,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.intelligence",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 25 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 50 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 100 }
+			}
+		}
+	},
+	"sorcery" : {
+		"index" : 25,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.sorcery",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 5 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 15 }
+			}
+		}
+	},
+	"resistance" : {
+		"index" : 26,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.resistance",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 5 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 10 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 20 }
+			}
+		}
+	},
+	"firstAid" : {
+		"index" : 27,
+		"base" : {
+			"effects" : {
+				"main" : {
+					"subtype" : "skill.firstAid",
+					"type" : "SECONDARY_SKILL_PREMY",
+					"valueType" : "BASE_NUMBER"
+				},
+				"ctrl" : {
+					"subtype" : "creature.firstAidTent",
+					"type" : "MANUAL_CONTROL",
+					"val" : 100,
+					"valueType" : "BASE_NUMBER"
+				}
+			}
+		},
+		"basic" : {
+			"effects" : {
+				"main" : { "val" : 50 }
+			}
+		},
+		"advanced" : {
+			"effects" : {
+				"main" : { "val" : 75 }
+			}
+		},
+		"expert" : {
+			"effects" : {
+				"main" : { "val" : 100 }
+			}
+		}
+	}
+}

+ 1 - 0
include/vstd/CLoggerBase.h

@@ -190,3 +190,4 @@ extern DLL_LINKAGE vstd::CLoggerBase * logBonus;
 extern DLL_LINKAGE vstd::CLoggerBase * logNetwork;
 extern DLL_LINKAGE vstd::CLoggerBase * logAi;
 extern DLL_LINKAGE vstd::CLoggerBase * logAnim;
+extern DLL_LINKAGE vstd::CLoggerBase * logMod;

+ 1 - 4
launcher/CMakeLists.txt

@@ -2,7 +2,6 @@
 # https://doc.qt.io/qt-5/cmake-manual.html
 include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_DIRECTORY}/include ${CMAKE_CURRENT_SOURCE_DIR})
 include_directories(${ZLIB_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS})
-include_directories(${SDL2_INCLUDE_DIR})
 
 set(launcher_modmanager_SRCS
 		modManager/cdownloadmanager_moc.cpp
@@ -38,7 +37,6 @@ set(launcher_SRCS
 		mainwindow_moc.cpp
 		launcherdirs.cpp
 		jsonutils.cpp
-		sdldisplayquery.cpp
 )
 
 set(launcher_HEADERS
@@ -48,7 +46,6 @@ set(launcher_HEADERS
 		mainwindow_moc.h
 		launcherdirs.h
 		jsonutils.h
-		sdldisplayquery.h
 )
 
 set(launcher_FORMS
@@ -102,7 +99,7 @@ if(APPLE)
 	set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmilauncher)
 endif()
 
-target_link_libraries(vcmilauncher vcmi Qt5::Widgets Qt5::Network ${SDL2_LIBRARY})
+target_link_libraries(vcmilauncher vcmi Qt5::Widgets Qt5::Network)
 
 vcmi_set_output_dir(vcmilauncher "")
 

+ 1 - 3
launcher/main.cpp

@@ -10,13 +10,11 @@
 #include <QApplication>
 #include "StdInc.h"
 #include "mainwindow_moc.h"
-#include "sdldisplayquery.h"
 
 int main(int argc, char * argv[])
 {
 	QApplication vcmilauncher(argc, argv);
-	auto displayList = getDisplays();
-	MainWindow mainWindow(displayList);
+	MainWindow mainWindow;
 	mainWindow.show();
 	return vcmilauncher.exec();
 }

+ 2 - 2
launcher/mainwindow_moc.cpp

@@ -39,7 +39,7 @@ void MainWindow::load()
 	settings.init();
 }
 
-MainWindow::MainWindow(const QStringList& displayList, QWidget *parent) :
+MainWindow::MainWindow(QWidget * parent) :
     QMainWindow(parent),
     ui(new Ui::MainWindow)
 {
@@ -62,7 +62,7 @@ MainWindow::MainWindow(const QStringList& displayList, QWidget *parent) :
 		ui->tabSelectList->setMaximumWidth(width + 4);
 	}
 	ui->tabListWidget->setCurrentIndex(0);
-	ui->settingsView->setDisplayList(displayList);
+	ui->settingsView->setDisplayList();
 
 	connect(ui->tabSelectList, SIGNAL(currentRowChanged(int)),
 	        ui->tabListWidget, SLOT(setCurrentIndex(int)));

+ 1 - 1
launcher/mainwindow_moc.h

@@ -25,7 +25,7 @@ private:
 	void load();
 	void startExecutable(QString name);
 public:
-	explicit MainWindow(const QStringList& displayList, QWidget *parent = 0);
+	explicit MainWindow(QWidget * parent = 0);
 	~MainWindow();
 
 private slots:

+ 51 - 48
launcher/mainwindow_moc.ui

@@ -34,15 +34,15 @@
     <item row="0" column="0">
      <widget class="QListWidget" name="tabSelectList">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
-        <horstretch>0</horstretch>
+       <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+        <horstretch>89</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
-      <property name="maximumSize">
+      <property name="minimumSize">
        <size>
-        <width>65</width>
-        <height>16777215</height>
+        <width>89</width>
+        <height>89</height>
        </size>
       </property>
       <property name="verticalScrollBarPolicy">
@@ -61,27 +61,24 @@
        <enum>QAbstractItemView::NoDragDrop</enum>
       </property>
       <property name="selectionBehavior">
-       <enum>QAbstractItemView::SelectRows</enum>
+       <enum>QAbstractItemView::SelectItems</enum>
       </property>
       <property name="iconSize">
        <size>
-        <width>48</width>
-        <height>64</height>
+        <width>89</width>
+        <height>89</height>
        </size>
       </property>
-      <property name="movement">
-       <enum>QListView::Static</enum>
+      <property name="flow">
+       <enum>QListView::TopToBottom</enum>
       </property>
       <property name="resizeMode">
-       <enum>QListView::Fixed</enum>
-      </property>
-      <property name="spacing">
-       <number>0</number>
+       <enum>QListView::Adjust</enum>
       </property>
       <property name="gridSize">
        <size>
-        <width>64</width>
-        <height>64</height>
+        <width>100</width>
+        <height>100</height>
        </size>
       </property>
       <property name="viewMode">
@@ -93,12 +90,6 @@
       <property name="wordWrap">
        <bool>true</bool>
       </property>
-      <property name="selectionRectVisible">
-       <bool>false</bool>
-      </property>
-      <property name="currentRow">
-       <number>0</number>
-      </property>
       <item>
        <property name="text">
         <string>Mods</string>
@@ -119,32 +110,6 @@
       </item>
      </widget>
     </item>
-    <item row="1" column="0">
-     <widget class="QToolButton" name="startGameButton">
-      <property name="text">
-       <string>Play</string>
-      </property>
-      <property name="icon">
-       <iconset>
-        <normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
-      </property>
-      <property name="iconSize">
-       <size>
-        <width>60</width>
-        <height>60</height>
-       </size>
-      </property>
-      <property name="checkable">
-       <bool>false</bool>
-      </property>
-      <property name="checked">
-       <bool>false</bool>
-      </property>
-      <property name="toolButtonStyle">
-       <enum>Qt::ToolButtonIconOnly</enum>
-      </property>
-     </widget>
-    </item>
     <item row="2" column="0">
      <widget class="QLabel" name="startGameTitle">
       <property name="font">
@@ -179,6 +144,44 @@
       <widget class="CSettingsView" name="settingsView"/>
      </widget>
     </item>
+    <item row="1" column="0">
+     <widget class="QToolButton" name="startGameButton">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Fixed" vsizetype="Maximum">
+        <horstretch>89</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="minimumSize">
+       <size>
+        <width>89</width>
+        <height>89</height>
+       </size>
+      </property>
+      <property name="text">
+       <string>Play</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
+      </property>
+      <property name="iconSize">
+       <size>
+        <width>60</width>
+        <height>60</height>
+       </size>
+      </property>
+      <property name="checkable">
+       <bool>false</bool>
+      </property>
+      <property name="checked">
+       <bool>false</bool>
+      </property>
+      <property name="toolButtonStyle">
+       <enum>Qt::ToolButtonIconOnly</enum>
+      </property>
+     </widget>
+    </item>
    </layout>
   </widget>
  </widget>

+ 0 - 42
launcher/sdldisplayquery.cpp

@@ -1,42 +0,0 @@
-/*
- * sdldisplayquery.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 "sdldisplayquery.h"
-
-#include <QString>
-#include <QTextStream>
-
-#include <SDL.h>
-#include <SDL_video.h>
-
-QStringList getDisplays()
-{
-  if(SDL_Init(SDL_INIT_VIDEO))
-    return QStringList("default display");
-
-  const int displays = SDL_GetNumVideoDisplays();
-  QStringList list;
-
-  for (int display = 0; display < displays; ++display)
-    {
-      SDL_Rect rect;
-
-      if (SDL_GetDisplayBounds (display, &rect))
-	continue;
-
-      QString string;
-      QTextStream(&string) << display << " - " << rect.w << "x" << rect.h << " (at " << rect.x << ", " << rect.y << ")";
-
-      list << string;
-    }
-
-  SDL_Quit();
-  return list;
-}

+ 0 - 14
launcher/sdldisplayquery.h

@@ -1,14 +0,0 @@
-/*
- * sdldisplayquery.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 <QStringList>
-
-QStringList getDisplays();

+ 22 - 11
launcher/settingsView/csettingsview_moc.cpp

@@ -29,19 +29,30 @@ static const std::string knownEncodingsList[] = //TODO: remove hardcode
     "GB2312"  // basic set for Simplified Chinese. Separate from GBK to allow proper detection of H3 fonts
 };
 
-void CSettingsView::setDisplayList(const QStringList& displayList)
+void CSettingsView::setDisplayList()
 {
-	if (displayList.count() < 2)
-	  {
-	    ui->comboBoxDisplayIndex->hide ();
-	    ui->labelDisplayIndex->hide ();
-	  }
+	QStringList list;
+	QDesktopWidget * widget = QApplication::desktop();
+	for(int display = 0; display < widget->screenCount(); display++)
+	{
+		QString string;
+		auto rect = widget->screenGeometry(display);
+		QTextStream(&string) << display << " - " << rect.width() << "x" << rect.height();
+		list << string;
+	}
+
+	if(list.count() < 2)
+	{
+		ui->comboBoxDisplayIndex->hide();
+		ui->labelDisplayIndex->hide();
+	}
 	else
-	  {
-	    ui->comboBoxDisplayIndex->clear();
-	    ui->comboBoxDisplayIndex->addItems(displayList);
-	    ui->comboBoxDisplayIndex->setCurrentIndex(settings["video"]["displayIndex"].Float());
-	  }
+	{
+		int displayIndex = settings["video"]["displayIndex"].Integer();
+		ui->comboBoxDisplayIndex->clear();
+		ui->comboBoxDisplayIndex->addItems(list);
+		ui->comboBoxDisplayIndex->setCurrentIndex(displayIndex);
+	}
 }
 
 void CSettingsView::loadSettings()

+ 1 - 1
launcher/settingsView/csettingsview_moc.h

@@ -22,7 +22,7 @@ public:
 	~CSettingsView();
 
 	void loadSettings();
-	void setDisplayList(const QStringList& displayList);
+	void setDisplayList();
 
 private slots:
 	void on_checkBoxFullScreen_stateChanged(int state);

+ 7 - 7
lib/CArtHandler.cpp

@@ -360,7 +360,7 @@ ArtifactPosition CArtHandler::stringToSlot(std::string slotName)
 	if (it != artifactPositionMap.end())
 		return it->second;
 
-	logGlobal->warn("Warning! Artifact slot %s not recognized!", slotName);
+	logMod->warn("Warning! Artifact slot %s not recognized!", slotName);
 	return ArtifactPosition::PRE_FIRST;
 }
 
@@ -421,7 +421,7 @@ CArtifact::EartClass CArtHandler::stringToClass(std::string className)
 	if (it != artifactClassMap.end())
 		return it->second;
 
-	logGlobal->warn("Warning! Artifact rarity %s not recognized!", className);
+	logMod->warn("Warning! Artifact rarity %s not recognized!", className);
 	return CArtifact::ART_SPECIAL;
 }
 
@@ -455,7 +455,7 @@ void CArtHandler::loadType(CArtifact * art, const JsonNode & node)
 			}
 		}
 		else
-			logGlobal->warn("Warning! Artifact type %s not recognized!", b.String());
+			logMod->warn("Warning! Artifact type %s not recognized!", b.String());
 	}
 }
 
@@ -679,11 +679,11 @@ void CArtHandler::erasePickedArt(ArtifactID id)
 			artifactList->erase(itr);
 		}
 		else
-			logGlobal->warn("Problem: cannot erase artifact %s from list, it was not present", art->Name());
+			logMod->warn("Problem: cannot erase artifact %s from list, it was not present", art->Name());
 
 	}
 	else
-		logGlobal->warn("Problem: cannot find list for artifact %s, strange class. (special?)", art->Name());
+		logMod->warn("Problem: cannot find list for artifact %s, strange class. (special?)", art->Name());
 }
 
 boost::optional<std::vector<CArtifact*>&> CArtHandler::listFromClass( CArtifact::EartClass artifactClass )
@@ -869,7 +869,7 @@ bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition
  	auto possibleSlots = artType->possibleSlots.find(artSet->bearerType());
  	if(possibleSlots == artType->possibleSlots.end())
  	{
-		logGlobal->warn("Warning: artifact %s doesn't have defined allowed slots for bearer of type %s", artType->Name(), artSet->bearerType());
+		logMod->warn("Warning: artifact %s doesn't have defined allowed slots for bearer of type %s", artType->Name(), artSet->bearerType());
 		return false;
 	}
 
@@ -1007,7 +1007,7 @@ SpellID CArtifactInstance::getGivenSpellID() const
 	const auto b = getBonusLocalFirst(Selector::type(Bonus::SPELL));
 	if(!b)
 	{
-		logGlobal->warn("Warning: %s doesn't bear any spell!", nodeName());
+		logMod->warn("Warning: %s doesn't bear any spell!", nodeName());
 		return SpellID::NONE;
 	}
 	return SpellID(b->subtype);

+ 5 - 3
lib/CGameState.cpp

@@ -20,6 +20,7 @@
 #include "CHeroHandler.h"
 #include "mapObjects/CObjectHandler.h"
 #include "CModHandler.h"
+#include "CSkillHandler.h"
 #include "mapping/CMap.h"
 #include "mapping/CMapService.h"
 #include "StartInfo.h"
@@ -113,6 +114,10 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 	{
 		dst = VLC->objtypeh->getObjectName(ser);
 	}
+	else if(type == SEC_SKILL_NAME)
+	{
+		dst = VLC->skillh->skillName(ser);
+	}
 	else
 	{
 		std::vector<std::string> *vec;
@@ -139,9 +144,6 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 		case ADVOB_TXT:
 			vec = &VLC->generaltexth->advobtxt;
 			break;
-		case SEC_SKILL_NAME:
-			vec = &VLC->generaltexth->skillName;
-			break;
 		case COLOR:
 			vec = &VLC->generaltexth->capColors;
 			break;

+ 0 - 1
lib/CGameState.h

@@ -37,7 +37,6 @@ class CGObjectInstance;
 class CCreature;
 class CMap;
 struct StartInfo;
-struct SDL_Surface;
 class CMapHandler;
 struct SetObjectProperty;
 struct MetaString;

+ 0 - 17
lib/CGeneralTextHandler.cpp

@@ -377,23 +377,6 @@ CGeneralTextHandler::CGeneralTextHandler()
 		}
 		while (parser.endLine());
 	}
-	{
-		CLegacyConfigParser parser("DATA/SSTRAITS.TXT");
-
-		//skip header
-		parser.endLine();
-		parser.endLine();
-
-		do
-		{
-			skillName.push_back(parser.readString());
-
-			skillInfoTexts.push_back(std::vector<std::string>());
-			for(int j = 0; j < 3; j++)
-				skillInfoTexts.back().push_back(parser.readString());
-		}
-		while (parser.endLine());
-	}
 	{
 		CLegacyConfigParser parser("DATA/SEERHUT.TXT");
 

+ 0 - 2
lib/CGeneralTextHandler.h

@@ -130,8 +130,6 @@ public:
 	std::vector<std::string> tentColors;
 
 	//sec skills
-	std::vector<std::string> skillName;
-	std::vector<std::vector<std::string>> skillInfoTexts; //[id][level] : level 0 - basic; 2 - advanced
 	std::vector<std::string> levels;
 	std::vector<std::string> zcrexp; //more or less useful content of that file
 	//commanders

+ 1 - 1
lib/CHeroHandler.cpp

@@ -362,7 +362,7 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node)
 		}
 		else
 		{
-			logGlobal->error("Unknown skill level: %s", set["level"].String());
+			logMod->error("Unknown skill level: %s", set["level"].String());
 		}
 	}
 

+ 2 - 0
lib/CMakeLists.txt

@@ -115,6 +115,7 @@ set(lib_SRCS
 		CModHandler.cpp
 		CPathfinder.cpp
 		CRandomGenerator.cpp
+		CSkillHandler.cpp
 		CStack.cpp
 		CThreadHelper.cpp
 		CTownHandler.cpp
@@ -256,6 +257,7 @@ set(lib_HEADERS
 		CPlayerState.h
 		CRandomGenerator.h
 		CScriptingModule.h
+		CSkillHandler.h
 		CSoundBase.h
 		CStack.h
 		CStopWatch.h

+ 63 - 51
lib/CModHandler.cpp

@@ -23,6 +23,7 @@
 #include "CStopWatch.h"
 #include "IHandlerBase.h"
 #include "spells/CSpellHandler.h"
+#include "CSkillHandler.h"
 
 CIdentifierStorage::CIdentifierStorage():
 	state(LOADING)
@@ -36,7 +37,7 @@ CIdentifierStorage::~CIdentifierStorage()
 void CIdentifierStorage::checkIdentifier(std::string & ID)
 {
 	if (boost::algorithm::ends_with(ID, "."))
-		logGlobal->warn("BIG WARNING: identifier %s seems to be broken!", ID);
+		logMod->warn("BIG WARNING: identifier %s seems to be broken!", ID);
 	else
 	{
 		size_t pos = 0;
@@ -44,7 +45,7 @@ void CIdentifierStorage::checkIdentifier(std::string & ID)
 		{
 			if (std::tolower(ID[pos]) != ID[pos] ) //Not in camelCase
 			{
-				logGlobal->warn("Warning: identifier %s is not in camelCase!", ID);
+				logMod->warn("Warning: identifier %s is not in camelCase!", ID);
 				ID[pos] = std::tolower(ID[pos]);// Try to fix the ID
 			}
 			pos = ID.find('.', pos);
@@ -148,7 +149,7 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(std::string scope, std::
 	if (idList.size() == 1)
 		return idList.front().id;
 	if (!silent)
-		logGlobal->error("Failed to resolve identifier %s of type %s from mod %s", name , type ,scope);
+		logMod->error("Failed to resolve identifier %s of type %s from mod %s", name , type ,scope);
 
 	return boost::optional<si32>();
 }
@@ -161,7 +162,7 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const
 	if (idList.size() == 1)
 		return idList.front().id;
 	if (!silent)
-		logGlobal->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), type, name.meta);
+		logMod->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), type, name.meta);
 
 	return boost::optional<si32>();
 }
@@ -175,7 +176,7 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, b
 	if (idList.size() == 1)
 		return idList.front().id;
 	if (!silent)
-		logGlobal->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), pair2.first, name.meta);
+		logMod->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), pair2.first, name.meta);
 
 	return boost::optional<si32>();
 }
@@ -189,7 +190,7 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(std::string scope, std::
 	if (idList.size() == 1)
 		return idList.front().id;
 	if (!silent)
-		logGlobal->error("Failed to resolve identifier %s of type %s from mod %s", fullName, pair2.first, scope);
+		logMod->error("Failed to resolve identifier %s of type %s from mod %s", fullName, pair2.first, scope);
 
 	return boost::optional<si32>();
 }
@@ -203,7 +204,12 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
 	std::string fullID = type + '.' + name;
 	checkIdentifier(fullID);
 
-	registeredObjects.insert(std::make_pair(fullID, data));
+	std::pair<const std::string, ObjectData> mapping = std::make_pair(fullID, data);
+	if(!vstd::containsMapping(registeredObjects, mapping))
+	{
+		logMod->trace("registered %s as %s:%s", fullID, scope, identifier);
+		registeredObjects.insert(mapping);
+	}
 }
 
 std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request)
@@ -274,15 +280,15 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
 
 	// error found. Try to generate some debug info
 	if (identifiers.size() == 0)
-		logGlobal->error("Unknown identifier!");
+		logMod->error("Unknown identifier!");
 	else
-		logGlobal->error("Ambiguous identifier request!");
+		logMod->error("Ambiguous identifier request!");
 
-	 logGlobal->error("Request for %s.%s from mod %s", request.type, request.name, request.localScope);
+	 logMod->error("Request for %s.%s from mod %s", request.type, request.name, request.localScope);
 
 	for (auto id : identifiers)
 	{
-		logGlobal->error("\tID is available in mod %s", id.scope);
+		logMod->error("\tID is available in mod %s", id.scope);
 	}
 	return false;
 }
@@ -302,9 +308,9 @@ void CIdentifierStorage::finalize()
 	{
 		for(auto object : registeredObjects)
 		{
-			logGlobal->trace("%s : %s -> %d", object.second.scope, object.first, object.second.id);
+			logMod->trace("%s : %s -> %d", object.second.scope, object.first, object.second.id);
 		}
-		logGlobal->error("All known identifiers were dumped into log file");
+		logMod->error("All known identifiers were dumped into log file");
 	}
 	assert(errorsFound == false);
 	state = FINISHED;
@@ -345,9 +351,9 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st
 
 			// patching this mod? Send warning and continue - this situation can be handled normally
 			if (remoteName == modName)
-				logGlobal->warn("Redundant namespace definition for %s", objectName);
+				logMod->warn("Redundant namespace definition for %s", objectName);
 
-			logGlobal->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
+			logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
 			JsonNode & remoteConf = modData[remoteName].patches[objectName];
 
 			JsonUtils::merge(remoteConf, entry.second);
@@ -383,17 +389,22 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali
 
 			if (originalData.size() > index)
 			{
+				logMod->trace("found original data in loadMod(%s) at index %d", name, index);
 				JsonUtils::merge(originalData[index], data);
-
 				performValidate(originalData[index],name);
 				handler->loadObject(modName, name, originalData[index], index);
-
 				originalData[index].clear(); // do not use same data twice (same ID)
-
-				continue;
 			}
+			else
+			{
+				logMod->debug("no original data in loadMod(%s) at index %d", name, index);
+				performValidate(data, name);
+				handler->loadObject(modName, name, data, index);
+			}
+			continue;
 		}
-		// normal new object or one with index bigger that data size
+		// normal new object
+		logMod->trace("no index in loadMod(%s)", name);
 		performValidate(data,name);
 		handler->loadObject(modName, name, data);
 	}
@@ -420,6 +431,7 @@ CContentHandler::CContentHandler()
 	handlers.insert(std::make_pair("objects", ContentTypeHandler(VLC->objtypeh, "object")));
 	handlers.insert(std::make_pair("heroes", ContentTypeHandler(VLC->heroh, "hero")));
 	handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell")));
+	handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill")));
 	handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template")));
 
 	//TODO: any other types of moddables?
@@ -466,7 +478,7 @@ void CContentHandler::preloadData(CModInfo & mod)
 	bool validate = (mod.validation != CModInfo::PASSED);
 
 	// print message in format [<8-symbols checksum>] <modname>
-	logGlobal->info("\t\t[%08x]%s", mod.checksum, mod.name);
+	logMod->info("\t\t[%08x]%s", mod.checksum, mod.name);
 
 	if (validate && mod.identifier != "core")
 	{
@@ -487,12 +499,12 @@ void CContentHandler::load(CModInfo & mod)
 	if (validate)
 	{
 		if (mod.validation != CModInfo::FAILED)
-			logGlobal->info("\t\t[DONE] %s", mod.name);
+			logMod->info("\t\t[DONE] %s", mod.name);
 		else
-			logGlobal->error("\t\t[FAIL] %s", mod.name);
+			logMod->error("\t\t[FAIL] %s", mod.name);
 	}
 	else
-		logGlobal->info("\t\t[SKIP] %s", mod.name);
+		logMod->info("\t\t[SKIP] %s", mod.name);
 }
 
 static JsonNode loadModSettings(std::string path)
@@ -618,39 +630,39 @@ void CModHandler::loadConfigFromFile (std::string name)
 		paths += p.string() + ", ";
 	}
 	paths = paths.substr(0, paths.size() - 2);
-	logGlobal->debug("Loading hardcoded features settings from [%s], result:", paths);
+	logMod->debug("Loading hardcoded features settings from [%s], result:", paths);
 	settings.data = JsonUtils::assembleFromFiles("config/" + name);
 	const JsonNode & hardcodedFeatures = settings.data["hardcodedFeatures"];
 	settings.MAX_HEROES_AVAILABLE_PER_PLAYER = hardcodedFeatures["MAX_HEROES_AVAILABLE_PER_PLAYER"].Integer();
-	logGlobal->debug("\tMAX_HEROES_AVAILABLE_PER_PLAYER\t%d", settings.MAX_HEROES_AVAILABLE_PER_PLAYER);
+	logMod->debug("\tMAX_HEROES_AVAILABLE_PER_PLAYER\t%d", settings.MAX_HEROES_AVAILABLE_PER_PLAYER);
 	settings.MAX_HEROES_ON_MAP_PER_PLAYER = hardcodedFeatures["MAX_HEROES_ON_MAP_PER_PLAYER"].Integer();
-	logGlobal->debug("\tMAX_HEROES_ON_MAP_PER_PLAYER\t%d", settings.MAX_HEROES_ON_MAP_PER_PLAYER);
+	logMod->debug("\tMAX_HEROES_ON_MAP_PER_PLAYER\t%d", settings.MAX_HEROES_ON_MAP_PER_PLAYER);
 	settings.CREEP_SIZE = hardcodedFeatures["CREEP_SIZE"].Integer();
-	logGlobal->debug("\tCREEP_SIZE\t%d", settings.CREEP_SIZE);
+	logMod->debug("\tCREEP_SIZE\t%d", settings.CREEP_SIZE);
 	settings.WEEKLY_GROWTH = hardcodedFeatures["WEEKLY_GROWTH_PERCENT"].Integer();
-	logGlobal->debug("\tWEEKLY_GROWTH\t%d", settings.WEEKLY_GROWTH);
+	logMod->debug("\tWEEKLY_GROWTH\t%d", settings.WEEKLY_GROWTH);
 	settings.NEUTRAL_STACK_EXP = hardcodedFeatures["NEUTRAL_STACK_EXP_DAILY"].Integer();
-	logGlobal->debug("\tNEUTRAL_STACK_EXP\t%d", settings.NEUTRAL_STACK_EXP);
+	logMod->debug("\tNEUTRAL_STACK_EXP\t%d", settings.NEUTRAL_STACK_EXP);
 	settings.MAX_BUILDING_PER_TURN = hardcodedFeatures["MAX_BUILDING_PER_TURN"].Integer();
-	logGlobal->debug("\tMAX_BUILDING_PER_TURN\t%d", settings.MAX_BUILDING_PER_TURN);
+	logMod->debug("\tMAX_BUILDING_PER_TURN\t%d", settings.MAX_BUILDING_PER_TURN);
 	settings.DWELLINGS_ACCUMULATE_CREATURES = hardcodedFeatures["DWELLINGS_ACCUMULATE_CREATURES"].Bool();
-	logGlobal->debug("\tDWELLINGS_ACCUMULATE_CREATURES\t%d", static_cast<int>(settings.DWELLINGS_ACCUMULATE_CREATURES));
+	logMod->debug("\tDWELLINGS_ACCUMULATE_CREATURES\t%d", static_cast<int>(settings.DWELLINGS_ACCUMULATE_CREATURES));
 	settings.ALL_CREATURES_GET_DOUBLE_MONTHS = hardcodedFeatures["ALL_CREATURES_GET_DOUBLE_MONTHS"].Bool();
-	logGlobal->debug("\tALL_CREATURES_GET_DOUBLE_MONTHS\t%d", static_cast<int>(settings.ALL_CREATURES_GET_DOUBLE_MONTHS));
+	logMod->debug("\tALL_CREATURES_GET_DOUBLE_MONTHS\t%d", static_cast<int>(settings.ALL_CREATURES_GET_DOUBLE_MONTHS));
 	settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS = hardcodedFeatures["WINNING_HERO_WITH_NO_TROOPS_RETREATS"].Bool();
-	logGlobal->debug("\tWINNING_HERO_WITH_NO_TROOPS_RETREATS\t%d", static_cast<int>(settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS));
+	logMod->debug("\tWINNING_HERO_WITH_NO_TROOPS_RETREATS\t%d", static_cast<int>(settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS));
 	settings.BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE = hardcodedFeatures["BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE"].Bool();
-	logGlobal->debug("\tBLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE\t%d", static_cast<int>(settings.BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE));
+	logMod->debug("\tBLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE\t%d", static_cast<int>(settings.BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE));
 
 	const JsonNode & gameModules = settings.data["modules"];
 	modules.STACK_EXP = gameModules["STACK_EXPERIENCE"].Bool();
-	logGlobal->debug("\tSTACK_EXP\t%d", static_cast<int>(modules.STACK_EXP));
+	logMod->debug("\tSTACK_EXP\t%d", static_cast<int>(modules.STACK_EXP));
 	modules.STACK_ARTIFACT = gameModules["STACK_ARTIFACTS"].Bool();
-	logGlobal->debug("\tSTACK_ARTIFACT\t%d", static_cast<int>(modules.STACK_ARTIFACT));
+	logMod->debug("\tSTACK_ARTIFACT\t%d", static_cast<int>(modules.STACK_ARTIFACT));
 	modules.COMMANDERS = gameModules["COMMANDERS"].Bool();
-	logGlobal->debug("\tCOMMANDERS\t%d", static_cast<int>(modules.COMMANDERS));
+	logMod->debug("\tCOMMANDERS\t%d", static_cast<int>(modules.COMMANDERS));
 	modules.MITHRIL = gameModules["MITHRIL"].Bool();
-	logGlobal->debug("\tMITHRIL\t%d", static_cast<int>(modules.MITHRIL));
+	logMod->debug("\tMITHRIL\t%d", static_cast<int>(modules.MITHRIL));
 }
 
 // currentList is passed by value to get current list of depending mods
@@ -661,8 +673,8 @@ bool CModHandler::hasCircularDependency(TModID modID, std::set <TModID> currentL
 	// Mod already present? We found a loop
 	if (vstd::contains(currentList, modID))
 	{
-		logGlobal->error("Error: Circular dependency detected! Printing dependency list:");
-		logGlobal->error("\t%s -> ", mod.name);
+		logMod->error("Error: Circular dependency detected! Printing dependency list:");
+		logMod->error("\t%s -> ", mod.name);
 		return true;
 	}
 
@@ -673,7 +685,7 @@ bool CModHandler::hasCircularDependency(TModID modID, std::set <TModID> currentL
 	{
 		if (hasCircularDependency(dependency, currentList))
 		{
-			logGlobal->error("\t%s ->\n", mod.name); // conflict detected, print dependency list
+			logMod->error("\t%s ->\n", mod.name); // conflict detected, print dependency list
 			return true;
 		}
 	}
@@ -690,7 +702,7 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
 		{
 			if (!vstd::contains(input, dep))
 			{
-				logGlobal->error("Error: Mod %s requires missing %s!", mod.name, dep);
+				logMod->error("Error: Mod %s requires missing %s!", mod.name, dep);
 				return false;
 			}
 		}
@@ -699,7 +711,7 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
 		{
 			if (vstd::contains(input, conflicting))
 			{
-				logGlobal->error("Error: Mod %s conflicts with %s!", mod.name, allMods.at(conflicting).name);
+				logMod->error("Error: Mod %s conflicts with %s!", mod.name, allMods.at(conflicting).name);
 				return false;
 			}
 		}
@@ -930,11 +942,11 @@ void CModHandler::load()
 	CStopWatch totalTime, timer;
 
 	CContentHandler content;
-	logGlobal->info("\tInitializing content handler: %d ms", timer.getDiff());
+	logMod->info("\tInitializing content handler: %d ms", timer.getDiff());
 
 	for(const TModID & modName : activeMods)
 	{
-		logGlobal->trace("Generating checksum for %s", modName);
+		logMod->trace("Generating checksum for %s", modName);
 		allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName)));
 	}
 
@@ -943,7 +955,7 @@ void CModHandler::load()
 	content.preloadData(coreMod);
 	for(const TModID & modName : activeMods)
 		content.preloadData(allMods[modName]);
-	logGlobal->info("\tParsing mod data: %d ms", timer.getDiff());
+	logMod->info("\tParsing mod data: %d ms", timer.getDiff());
 
 	content.load(coreMod);
 	for(const TModID & modName : activeMods)
@@ -951,17 +963,17 @@ void CModHandler::load()
 
 	content.loadCustom();
 
-	logGlobal->info("\tLoading mod data: %d ms", timer.getDiff());
+	logMod->info("\tLoading mod data: %d ms", timer.getDiff());
 
 	VLC->creh->loadCrExpBon();
 	VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
 
 	identifiers.finalize();
-	logGlobal->info("\tResolving identifiers: %d ms", timer.getDiff());
+	logMod->info("\tResolving identifiers: %d ms", timer.getDiff());
 
 	content.afterLoadFinalization();
-	logGlobal->info("\tHandlers post-load finalization: %d ms ", timer.getDiff());
-	logGlobal->info("\tAll game content loaded in %d ms", totalTime.getDiff());
+	logMod->info("\tHandlers post-load finalization: %d ms ", timer.getDiff());
+	logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff());
 }
 
 void CModHandler::afterLoad()

+ 5 - 1
lib/CModHandler.h

@@ -51,6 +51,10 @@ class CIdentifierStorage
 		si32 id;
 		std::string scope; /// scope in which this ID located
 
+		bool operator==(const ObjectData & other) const
+		{
+			return id == other.id && scope == other.scope;
+		}
 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
@@ -59,7 +63,7 @@ class CIdentifierStorage
 		}
 	};
 
-	std::multimap<std::string, ObjectData > registeredObjects;
+	std::multimap<std::string, ObjectData> registeredObjects;
 	std::vector<ObjectCallback> scheduledRequests;
 
 	ELoadingState state;

+ 226 - 0
lib/CSkillHandler.cpp

@@ -0,0 +1,226 @@
+/*
+ * CSkillHandler.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 <cctype>
+
+#include "CSkillHandler.h"
+
+#include "CGeneralTextHandler.h"
+#include "filesystem/Filesystem.h"
+
+#include "JsonNode.h"
+
+#include "CModHandler.h"
+#include "StringConstants.h"
+
+#include "CStack.h"
+#include "battle/BattleInfo.h"
+#include "battle/CBattleInfoCallback.h"
+
+///CSkill
+CSkill::LevelInfo::LevelInfo()
+{
+}
+
+CSkill::LevelInfo::~LevelInfo()
+{
+}
+
+CSkill::CSkill(SecondarySkill id) : id(id)
+{
+	if(id == SecondarySkill::DEFAULT)
+		identifier = "default";
+	else
+		identifier = NSecondarySkill::names[id];
+	// init levels
+	LevelInfo emptyLevel;
+	for(int level = 1; level < NSecondarySkill::levels.size(); level++)
+		levels.push_back(emptyLevel);
+}
+
+CSkill::~CSkill()
+{
+}
+
+void CSkill::addNewBonus(const std::shared_ptr<Bonus> & b, int level)
+{
+	b->source = Bonus::SECONDARY_SKILL;
+	b->sid = id;
+	b->duration = Bonus::PERMANENT;
+	b->description = identifier;
+	levels[level-1].effects.push_back(b);
+}
+
+void CSkill::setDescription(const std::string & desc, int level)
+{
+	levels[level-1].description = desc;
+}
+
+const std::vector<std::shared_ptr<Bonus>> & CSkill::getBonus(int level) const
+{
+	return levels[level-1].effects;
+}
+
+const std::string & CSkill::getDescription(int level) const
+{
+	return levels[level-1].description;
+}
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info)
+{
+	out << "(\"" << info.description << "\", [";
+	for(int i=0; i < info.effects.size(); i++)
+		out << (i ? "," : "") << info.effects[i]->Description();
+	return out << "])";
+}
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill & skill)
+{
+	out << "Skill(" << (int)skill.id << "," << skill.identifier << "): [";
+	for(int i=0; i < skill.levels.size(); i++)
+		out << (i ? "," : "") << skill.levels[i];
+	return out << "]";
+}
+
+std::string CSkill::toString() const
+{
+	std::ostringstream ss;
+	ss << *this;
+	return ss.str();
+}
+
+///CSkillHandler
+CSkillHandler::CSkillHandler()
+{
+}
+
+std::vector<JsonNode> CSkillHandler::loadLegacyData(size_t dataSize)
+{
+	CLegacyConfigParser parser("DATA/SSTRAITS.TXT");
+
+	//skip header
+	parser.endLine();
+	parser.endLine();
+
+	std::vector<std::string> skillNames;
+	std::vector<std::vector<std::string>> skillInfoTexts;
+	do
+	{
+		skillNames.push_back(parser.readString());
+		skillInfoTexts.push_back(std::vector<std::string>());
+		for(int i = 0; i < 3; i++)
+			skillInfoTexts.back().push_back(parser.readString());
+	}
+	while (parser.endLine());
+
+	assert(skillNames.size() == GameConstants::SKILL_QUANTITY);
+
+	//store & construct JSON
+	std::vector<JsonNode> legacyData;
+	for(int id = 0; id < GameConstants::SKILL_QUANTITY; id++)
+	{
+		JsonNode skillNode(JsonNode::DATA_STRUCT);
+		skillNode["name"].String() = skillNames[id];
+		for(int level = 1; level < NSecondarySkill::levels.size(); level++)
+		{
+			std::string & desc = skillInfoTexts[id][level-1];
+			auto & levelNode = skillNode[NSecondarySkill::levels[level]].Struct();
+			levelNode["description"].String() = desc;
+			levelNode["effects"].Struct(); // create empty effects objects
+		}
+		legacyData.push_back(skillNode);
+	}
+	objects.resize(legacyData.size());
+	return legacyData;
+}
+
+const std::string CSkillHandler::getTypeName() const
+{
+	return "skill";
+}
+
+const std::string & CSkillHandler::skillInfo(int skill, int level) const
+{
+	return objects[skill]->getDescription(level);
+}
+
+const std::string & CSkillHandler::skillName(int skill) const
+{
+	return objects[skill]->name;
+}
+
+CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier)
+{
+	CSkill * skill = nullptr;
+
+	for(int id = 0; id < GameConstants::SKILL_QUANTITY; id++)
+	{
+		if(NSecondarySkill::names[id].compare(identifier) == 0)
+		{
+			skill = new CSkill(SecondarySkill(id));
+			break;
+		}
+	}
+
+	if(!skill)
+	{
+		logMod->error("unknown secondary skill %s", identifier);
+		throw std::runtime_error("invalid skill");
+	}
+
+	skill->name = json["name"].String();
+	for(int level = 1; level < NSecondarySkill::levels.size(); level++)
+	{
+		const std::string & levelName = NSecondarySkill::levels[level]; // basic, advanced, expert
+		const JsonNode & levelNode = json[levelName];
+		// parse bonus effects
+		for(auto b : levelNode["effects"].Struct())
+		{
+			auto bonus = JsonUtils::parseBonus(b.second);
+			bonus->sid = skill->id;
+			skill->addNewBonus(bonus, level);
+		}
+		skill->setDescription(levelNode["description"].String(), level);
+	}
+	logMod->debug("loaded secondary skill %s(%d)", identifier, (int)skill->id);
+	logMod->trace("%s", skill->toString());
+
+	return skill;
+}
+
+void CSkillHandler::afterLoadFinalization()
+{
+}
+
+void CSkillHandler::beforeValidate(JsonNode & object)
+{
+	//handle "base" level info
+	JsonNode & base = object["base"];
+
+	auto inheritNode = [&](const std::string & name){
+		JsonUtils::inherit(object[name], base);
+	};
+
+	inheritNode("basic");
+	inheritNode("advanced");
+	inheritNode("expert");
+}
+
+CSkillHandler::~CSkillHandler()
+{
+}
+
+std::vector<bool> CSkillHandler::getDefaultAllowed() const
+{
+	std::vector<bool> allowedSkills(objects.size(), true);
+	return allowedSkills;
+}

+ 87 - 0
lib/CSkillHandler.h

@@ -0,0 +1,87 @@
+/*
+ * CSkillHandler.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/HeroBonus.h"
+#include "GameConstants.h"
+#include "IHandlerBase.h"
+
+class DLL_LINKAGE CSkill // secondary skill
+{
+protected:
+	struct LevelInfo
+	{
+		std::string description; //descriptions of spell for skill level
+		std::vector<std::shared_ptr<Bonus>> effects;
+
+		LevelInfo();
+		~LevelInfo();
+
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & description;
+			h & effects;
+		}
+	};
+
+	std::vector<LevelInfo> levels; // bonuses provided by basic, advanced and expert level
+
+public:
+	CSkill(SecondarySkill id = SecondarySkill::DEFAULT);
+	~CSkill();
+
+	void addNewBonus(const std::shared_ptr<Bonus> & b, int level);
+	void setDescription(const std::string & desc, int level);
+	const std::vector<std::shared_ptr<Bonus>> & getBonus(int level) const;
+	const std::string & getDescription(int level) const;
+	std::string toString() const;
+
+	SecondarySkill id;
+	std::string identifier;
+	std::string name; //as displayed in GUI
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & id;
+		h & identifier;
+		h & name;
+		h & levels;
+	}
+
+	friend class CSkillHandler;
+	friend DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill & skill);
+	friend DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info);
+};
+
+class DLL_LINKAGE CSkillHandler: public CHandlerBase<SecondarySkill, CSkill>
+{
+public:
+	CSkillHandler();
+	virtual ~CSkillHandler();
+
+	///IHandler base
+	std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
+	void afterLoadFinalization() override;
+	void beforeValidate(JsonNode & object) override;
+
+	std::vector<bool> getDefaultAllowed() const override;
+	const std::string getTypeName() const override;
+
+	const std::string & skillInfo(int skill, int level) const;
+	const std::string & skillName(int skill) const;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & objects;
+	}
+
+protected:
+	CSkill * loadFromJson(const JsonNode & json, const std::string & identifier) override;
+};

+ 8 - 5
lib/CTownHandler.cpp

@@ -748,7 +748,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 			auto & advMap = data["town"]["adventureMap"];
 			if (!advMap.isNull())
 			{
-				logGlobal->warn("Outdated town mod. Will try to generate valid templates out of fort");
+				logMod->warn("Outdated town mod. Will try to generate valid templates out of fort");
 				JsonNode config;
 				config["animation"] = advMap["castle"];
 				VLC->objtypeh->getHandlerFor(index, object->index)->addTemplate(config);
@@ -763,7 +763,10 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 {
 	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->index = index;
-	assert(factions[index] == nullptr); // ensure that this id was not loaded before
+	if (factions.size() > index)
+		assert(factions[index] == nullptr); // ensure that this id was not loaded before
+	else
+		factions.resize(index + 1);
 	factions[index] = object;
 
 	if (object->town)
@@ -815,9 +818,9 @@ void CTownHandler::initializeRequirements()
 		{
 			if (node.Vector().size() > 1)
 			{
-				logGlobal->warn("Unexpected length of town buildings requirements: %d", node.Vector().size());
-				logGlobal->warn("Entry contains: ");
-				logGlobal->warn(node.toJson());
+				logMod->warn("Unexpected length of town buildings requirements: %d", node.Vector().size());
+				logMod->warn("Entry contains: ");
+				logMod->warn(node.toJson());
 			}
 			return BuildingID(VLC->modh->identifiers.getIdentifier(requirement.town->getBuildingScope(), node.Vector()[0]).get());
 		});

+ 6 - 0
lib/GameConstants.cpp

@@ -21,6 +21,7 @@
 #include "CArtHandler.h"
 #include "CCreatureHandler.h"
 #include "spells/CSpellHandler.h"
+#include "CSkillHandler.h"
 #include "StringConstants.h"
 #include "CGeneralTextHandler.h"
 
@@ -65,6 +66,11 @@ const CSpell * SpellID::toSpell() const
 	return VLC->spellh->objects[*this];
 }
 
+const CSkill * SecondarySkill::toSkill() const
+{
+	return VLC->skillh->objects.at(*this);
+}
+
 //template std::ostream & operator << <ArtifactInstanceID>(std::ostream & os, BaseForID<ArtifactInstanceID> id);
 //template std::ostream & operator << <ObjectInstanceID>(std::ostream & os, BaseForID<ObjectInstanceID> id);
 

+ 3 - 0
lib/GameConstants.h

@@ -62,6 +62,7 @@ class CArtifactInstance;
 class CCreature;
 class CHero;
 class CSpell;
+class CSkill;
 class CGameInfoCallback;
 class CNonConstInfoCallback;
 
@@ -320,6 +321,8 @@ public:
 	SecondarySkill(ESecondarySkill _num = WRONG) : num(_num)
 	{}
 
+	DLL_LINKAGE const CSkill * toSkill() const;
+
 	ID_LIKE_CLASS_COMMON(SecondarySkill, ESecondarySkill)
 
 	ESecondarySkill num;

+ 9 - 1
lib/HeroBonus.cpp

@@ -17,6 +17,7 @@
 #include "CCreatureSet.h"
 #include "CHeroHandler.h"
 #include "CGeneralTextHandler.h"
+#include "CSkillHandler.h"
 #include "CStack.h"
 #include "CArtHandler.h"
 
@@ -1078,7 +1079,7 @@ std::string Bonus::Description() const
 			str << VLC->creh->creatures[sid]->namePl;
 			break;
 		case SECONDARY_SKILL:
-			str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/;
+			str << VLC->skillh->skillName(sid);
 			break;
 		default:
 			//todo: handle all possible sources
@@ -1166,6 +1167,11 @@ namespace Selector
 		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source);
 	}
 
+	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType)
+	{
+		return CSelectFieldEqual<Bonus::ValueType>(&Bonus::valType)(valType);
+	}
+
 	DLL_LINKAGE CSelector all([](const Bonus * b){return true;});
 	DLL_LINKAGE CSelector none([](const Bonus * b){return false;});
 
@@ -1215,6 +1221,8 @@ const CCreature * retrieveCreature(const CBonusSystemNode *node)
 	{
 	case CBonusSystemNode::CREATURE:
 		return (static_cast<const CCreature *>(node));
+	case CBonusSystemNode::STACK_BATTLE:
+		return (static_cast<const CStack*>(node))->type;
 	default:
 		const CStackInstance *csi = retreiveStackInstance(node);
 		if(csi)

+ 5 - 1
lib/HeroBonus.h

@@ -234,13 +234,16 @@ private:
 	BONUS_NAME(SUMMON_GUARDIANS) /*val - amount in % of stack count, subtype = creature ID*/\
 	BONUS_NAME(CATAPULT_EXTRA_SHOTS) /*val - number of additional shots, requires CATAPULT bonus to work*/\
 	BONUS_NAME(RANGED_RETALIATION) /*allows shooters to perform ranged retaliation*/\
-	BONUS_NAME(BLOCKS_RANGED_RETALIATION) /*disallows ranged retaliation for shooter unit, BLOCKS_RETALIATION bonus is for melee retaliation only*/\
+	BONUS_NAME(BLOCKS_RANGED_RETALIATION) /*disallows ranged retaliation for shooter unit, BLOCKS_RETALIATION bonus is for melee retaliation only*/
+  BONUS_NAME(SECONDARY_SKILL_VAL2) /*for secondary skills that have multiple effects, like eagle eye (max level and chance)*/  \
+	BONUS_NAME(MANUAL_CONTROL) /* manually control warmachine with id = subtype, chance = val */  \
 	BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\
 	BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\
 	BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\
 	BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighboring with target) without spell-like mechanics */\
 	BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
 	BONUS_NAME(TERMINATOR) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
+
 	/* end of list */
 
 
@@ -956,6 +959,7 @@ namespace Selector
 	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, si32 info);
 	CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID);
 	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source);
+	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType);
 
 	/**
 	 * Selects all bonuses

+ 1 - 0
lib/IGameCallback.cpp

@@ -12,6 +12,7 @@
 
 #include "CHeroHandler.h" // for CHeroHandler
 #include "spells/CSpellHandler.h"// for CSpell
+#include "CSkillHandler.h"// for CSkill
 #include "NetPacks.h"
 #include "CBonusTypeHandler.h"
 #include "CModHandler.h"

+ 1 - 3
lib/IHandlerBase.h

@@ -82,12 +82,10 @@ public:
 		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 		object->id = _ObjectID(index);
 
-
 		assert(objects[index] == nullptr); // ensure that this id was not loaded before
 		objects[index] = object;
 
 		registerObject(scope,type_name, name, object->id);
-
 	}
 
 	ConstTransitivePtr<_Object> operator[] (const _ObjectID id) const
@@ -96,7 +94,7 @@ public:
 
 		if (raw_id < 0 || raw_id >= objects.size())
 		{
-			logGlobal->error("%s id %d is invalid", getTypeName(), static_cast<si64>(raw_id));
+			logMod->error("%s id %d is invalid", getTypeName(), static_cast<si64>(raw_id));
 			throw std::runtime_error("internal error");
 		}
 

+ 2 - 2
lib/JsonDetail.cpp

@@ -163,8 +163,8 @@ JsonNode JsonParser::parse(std::string fileName)
 
 	if (!errors.empty())
 	{
-		logGlobal->warn("File %s is not a valid JSON file!", fileName);
-		logGlobal->warn(errors);
+		logMod->warn("File %s is not a valid JSON file!", fileName);
+		logMod->warn(errors);
 	}
 	return root;
 }

+ 12 - 12
lib/JsonNode.cpp

@@ -395,7 +395,7 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus (const JsonVector &ability_vec) //T
 	auto it = bonusNameMap.find(type);
 	if (it == bonusNameMap.end())
 	{
-		logGlobal->error("Error: invalid ability type %s", type);
+		logMod->error("Error: invalid ability type %s", type);
 		return b;
 	}
 	b->type = it->second;
@@ -413,7 +413,7 @@ const T & parseByMap(const std::map<std::string, T> & map, const JsonNode * val,
 		auto it = map.find(type);
 		if (it == map.end())
 		{
-			logGlobal->error("Error: invalid %s%s", err, type);
+			logMod->error("Error: invalid %s%s", err, type);
 			return defaultValue;
 		}
 		else
@@ -445,7 +445,7 @@ void JsonUtils::resolveIdentifier(si32 &var, const JsonNode &node, std::string n
 				});
 				break;
 			default:
-				logGlobal->error("Error! Wrong identifier used for value of %s", name);
+				logMod->error("Error! Wrong identifier used for value of %s", name);
 		}
 	}
 }
@@ -467,7 +467,7 @@ void JsonUtils::resolveIdentifier(const JsonNode &node, si32 &var)
 			});
 			break;
 		default:
-			logGlobal->error("Error! Wrong identifier used for identifier!");
+			logMod->error("Error! Wrong identifier used for identifier!");
 	}
 }
 
@@ -489,7 +489,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 	auto it = bonusNameMap.find(type);
 	if (it == bonusNameMap.end())
 	{
-		logGlobal->error("Error: invalid ability type %s", type);
+		logMod->error("Error: invalid ability type %s", type);
 		return false;
 	}
 	b->type = it->second;
@@ -533,7 +533,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 			}
 			break;
 		default:
-			logGlobal->error("Error! Wrong bonus duration format.");
+			logMod->error("Error! Wrong bonus duration format.");
 		}
 	}
 
@@ -580,7 +580,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 							auto it = bonusNameMap.find(anotherBonusType);
 							if (it == bonusNameMap.end())
 							{
-								logGlobal->error("Error: invalid ability type %s", anotherBonusType);
+								logMod->error("Error: invalid ability type %s", anotherBonusType);
 								continue;
 							}
 							l2->type = it->second;
@@ -723,8 +723,8 @@ bool JsonUtils::validate(const JsonNode &node, std::string schemaName, std::stri
 	std::string log = Validation::check(schemaName, node);
 	if (!log.empty())
 	{
-		logGlobal->warn("Data in %s is invalid!", dataName);
-		logGlobal->warn(log);
+		logMod->warn("Data in %s is invalid!", dataName);
+		logMod->warn(log);
 	}
 	return log.empty();
 }
@@ -745,7 +745,7 @@ const JsonNode & getSchemaByName(std::string name)
 		return loadedSchemas[name];
 	}
 
-	logGlobal->error("Error: missing schema with name %s!", name);
+	logMod->error("Error: missing schema with name %s!", name);
 	assert(0);
 	return nullNode;
 }
@@ -756,7 +756,7 @@ const JsonNode & JsonUtils::getSchema(std::string URI)
 	size_t posHash  = URI.find('#');
 	if(posColon == std::string::npos)
 	{
-		logGlobal->error("Invalid schema URI:%s", URI);
+		logMod->error("Invalid schema URI:%s", URI);
 		return nullNode;
 	}
 
@@ -765,7 +765,7 @@ const JsonNode & JsonUtils::getSchema(std::string URI)
 
 	if(protocolName != "vcmi")
 	{
-		logGlobal->error("Error: unsupported URI protocol for schema: %s", URI);
+		logMod->error("Error: unsupported URI protocol for schema: %s", URI);
 		return nullNode;
 	}
 

+ 49 - 24
lib/VCMIDirs.cpp

@@ -271,7 +271,7 @@ bfs::path VCMIDirsWIN32::userDataPath() const
 	wchar_t profileDir[MAX_PATH];
 
 	if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_MYDOCUMENTS, FALSE) != FALSE)
-		return bfs::path(profileDir) / "My Games\\vcmi";
+		return bfs::path(profileDir) / "My Games" / "vcmi";
 
 	return ".";
 }
@@ -350,8 +350,16 @@ class IVCMIDirsUNIX : public IVCMIDirs
 		boost::filesystem::path serverPath() const override;
 
 		std::string genHelpString() const override;
+
+		bool developmentMode() const;
 };
 
+bool IVCMIDirsUNIX::developmentMode() const
+{
+	// We want to be able to run VCMI from single directory. E.g to run from build output directory
+	return bfs::exists("AI") && bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiserver") && bfs::exists("vcmiclient");
+}
+
 bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; }
 bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; }
 
@@ -454,7 +462,7 @@ std::vector<bfs::path> VCMIDirsOSX::dataPaths() const
 {
 	std::vector<bfs::path> ret;
 	//FIXME: need some proper codepath for detecting running from build output directory
-	if(bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiserver"))
+	if(developmentMode())
 	{
 		ret.push_back(".");
 	}
@@ -489,9 +497,9 @@ bfs::path VCMIDirsXDG::userDataPath() const
 {
 	// $XDG_DATA_HOME, default: $HOME/.local/share
 	const char* homeDir;
-	if ((homeDir = getenv("XDG_DATA_HOME")))
-		return homeDir;
-	else if ((homeDir = getenv("HOME")))
+	if((homeDir = getenv("XDG_DATA_HOME")))
+		return bfs::path(homeDir) / "vcmi";
+	else if((homeDir = getenv("HOME")))
 		return bfs::path(homeDir) / ".local" / "share" / "vcmi";
 	else
 		return ".";
@@ -499,7 +507,7 @@ bfs::path VCMIDirsXDG::userDataPath() const
 bfs::path VCMIDirsXDG::userCachePath() const
 {
 	// $XDG_CACHE_HOME, default: $HOME/.cache
-	const char* tempResult;
+	const char * tempResult;
 	if ((tempResult = getenv("XDG_CACHE_HOME")))
 		return bfs::path(tempResult) / "vcmi";
 	else if ((tempResult = getenv("HOME")))
@@ -510,7 +518,7 @@ bfs::path VCMIDirsXDG::userCachePath() const
 bfs::path VCMIDirsXDG::userConfigPath() const
 {
 	// $XDG_CONFIG_HOME, default: $HOME/.config
-	const char* tempResult;
+	const char * tempResult;
 	if ((tempResult = getenv("XDG_CONFIG_HOME")))
 		return bfs::path(tempResult) / "vcmi";
 	else if ((tempResult = getenv("HOME")))
@@ -528,34 +536,51 @@ std::vector<bfs::path> VCMIDirsXDG::dataPaths() const
 	// in vcmi fs last directory has highest priority
 	std::vector<bfs::path> ret;
 
-	const char* tempResult;
-	ret.push_back(M_DATA_DIR);
-
-	//FIXME: need some proper codepath for detecting running from build output directory
-	if(bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiserver"))
+	if(developmentMode())
 	{
 		//For now we'll disable usage of system directories when VCMI running from bin directory
 		ret.push_back(".");
 	}
-	else if((tempResult = getenv("XDG_DATA_DIRS")) != nullptr)
-	{
-		std::string dataDirsEnv = tempResult;
-		std::vector<std::string> dataDirs;
-		boost::split(dataDirs, dataDirsEnv, boost::is_any_of(":"));
-		for (auto & entry : boost::adaptors::reverse(dataDirs))
-			ret.push_back(entry + "/vcmi");
-	}
 	else
 	{
-		ret.push_back("/usr/share/");
-		ret.push_back("/usr/local/share/");
+		ret.push_back(M_DATA_DIR);
+		const char * tempResult;
+		if((tempResult = getenv("XDG_DATA_DIRS")) != nullptr)
+		{
+			std::string dataDirsEnv = tempResult;
+			std::vector<std::string> dataDirs;
+			boost::split(dataDirs, dataDirsEnv, boost::is_any_of(":"));
+			for (auto & entry : boost::adaptors::reverse(dataDirs))
+				ret.push_back(bfs::path(entry) / "vcmi");
+		}
+		else
+		{
+			ret.push_back(bfs::path("/usr/share") / "vcmi");
+			ret.push_back(bfs::path("/usr/local/share") / "vcmi");
+		}
+
+		// Debian and other distributions might want to use it while it's not part of XDG
+		ret.push_back(bfs::path("/usr/share/games") / "vcmi");
 	}
 
 	return ret;
 }
 
-bfs::path VCMIDirsXDG::libraryPath() const { return M_LIB_DIR; }
-bfs::path VCMIDirsXDG::binaryPath() const { return M_BIN_DIR; }
+bfs::path VCMIDirsXDG::libraryPath() const
+{
+	if(developmentMode())
+		return ".";
+	else
+		return M_LIB_DIR;
+}
+
+bfs::path VCMIDirsXDG::binaryPath() const
+{
+	if(developmentMode())
+		return ".";
+	else
+		return M_BIN_DIR;
+}
 
 std::string VCMIDirsXDG::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; }
 

+ 5 - 0
lib/VCMI_Lib.cpp

@@ -20,6 +20,7 @@
 #include "CTownHandler.h"
 #include "CBuildingHandler.h"
 #include "spells/CSpellHandler.h"
+#include "CSkillHandler.h"
 #include "CGeneralTextHandler.h"
 #include "CModHandler.h"
 #include "IGameEventsReceiver.h"
@@ -113,6 +114,8 @@ void LibClasses::init()
 
 	createHandler(spellh, "Spell", pomtime);
 
+	createHandler(skillh, "Skill", pomtime);
+
 	createHandler(terviewh, "Terrain view pattern", pomtime);
 
 	createHandler(tplh, "Template", pomtime); //templates need already resolved identifiers (refactor?)
@@ -137,6 +140,7 @@ void LibClasses::clear()
 	delete objh;
 	delete objtypeh;
 	delete spellh;
+	delete skillh;
 	delete modh;
 	delete bth;
 	delete tplh;
@@ -154,6 +158,7 @@ void LibClasses::makeNull()
 	objh = nullptr;
 	objtypeh = nullptr;
 	spellh = nullptr;
+	skillh = nullptr;
 	modh = nullptr;
 	bth = nullptr;
 	tplh = nullptr;

+ 6 - 0
lib/VCMI_Lib.h

@@ -14,6 +14,7 @@ class CArtHandler;
 class CHeroHandler;
 class CCreatureHandler;
 class CSpellHandler;
+class CSkillHandler;
 class CBuildingHandler;
 class CObjectHandler;
 class CObjectClassesHandler;
@@ -41,6 +42,7 @@ public:
 	CHeroHandler * heroh;
 	CCreatureHandler * creh;
 	CSpellHandler * spellh;
+	CSkillHandler * skillh;
 	CObjectHandler * objh;
 	CObjectClassesHandler * objtypeh;
 	CTownHandler * townh;
@@ -67,6 +69,10 @@ public:
 		h & objh;
 		h & objtypeh;
 		h & spellh;
+		if(version >= 777)
+		{
+			h & skillh;
+		}
 		h & modh;
 		h & IS_AI_ENABLED;
 		h & bth;

+ 28 - 26
lib/VCMI_lib.cbp

@@ -125,30 +125,6 @@
 		<Unit filename="../Version.h" />
 		<Unit filename="../include/vstd/CLoggerBase.h" />
 		<Unit filename="AI_Base.h" />
-		<Unit filename="battle/BattleAction.cpp" />
-		<Unit filename="battle/BattleAction.h" />
-		<Unit filename="battle/BattleHex.cpp" />
-		<Unit filename="battle/BattleHex.h" />
-		<Unit filename="battle/BattleInfo.cpp" />
-		<Unit filename="battle/BattleInfo.h" />
-		<Unit filename="battle/AccessibilityInfo.cpp" />
-		<Unit filename="battle/AccessibilityInfo.h" />
-		<Unit filename="battle/BattleAttackInfo.cpp" />
-		<Unit filename="battle/BattleAttackInfo.h" />
-		<Unit filename="battle/CBattleInfoCallback.cpp" />
-		<Unit filename="battle/CBattleInfoCallback.h" />
-		<Unit filename="battle/CBattleInfoEssentials.cpp" />
-		<Unit filename="battle/CBattleInfoEssentials.h" />
-		<Unit filename="battle/CCallbackBase.cpp" />
-		<Unit filename="battle/CCallbackBase.h" />
-		<Unit filename="battle/CPlayerBattleCallback.cpp" />
-		<Unit filename="battle/CPlayerBattleCallback.h" />
-		<Unit filename="battle/ReachabilityInfo.cpp" />
-		<Unit filename="battle/ReachabilityInfo.h" />
-		<Unit filename="battle/SideInBattle.cpp" />
-		<Unit filename="battle/SideInBattle.h" />
-		<Unit filename="battle/SiegeInfo.cpp" />
-		<Unit filename="battle/SiegeInfo.h" />
 		<Unit filename="CArtHandler.cpp" />
 		<Unit filename="CArtHandler.h" />
 		<Unit filename="CBonusTypeHandler.cpp" />
@@ -177,14 +153,14 @@
 		<Unit filename="CMakeLists.txt" />
 		<Unit filename="CModHandler.cpp" />
 		<Unit filename="CModHandler.h" />
-		<Unit filename="battle/CObstacleInstance.cpp" />
-		<Unit filename="battle/CObstacleInstance.h" />
 		<Unit filename="CPathfinder.cpp" />
 		<Unit filename="CPathfinder.h" />
 		<Unit filename="CPlayerState.h" />
 		<Unit filename="CRandomGenerator.cpp" />
 		<Unit filename="CRandomGenerator.h" />
 		<Unit filename="CScriptingModule.h" />
+		<Unit filename="CSkillHandler.cpp" />
+		<Unit filename="CSkillHandler.h" />
 		<Unit filename="CSoundBase.h" />
 		<Unit filename="CStack.cpp" />
 		<Unit filename="CStack.h" />
@@ -230,6 +206,32 @@
 		<Unit filename="VCMIDirs.h" />
 		<Unit filename="VCMI_Lib.cpp" />
 		<Unit filename="VCMI_Lib.h" />
+		<Unit filename="battle/AccessibilityInfo.cpp" />
+		<Unit filename="battle/AccessibilityInfo.h" />
+		<Unit filename="battle/BattleAction.cpp" />
+		<Unit filename="battle/BattleAction.h" />
+		<Unit filename="battle/BattleAttackInfo.cpp" />
+		<Unit filename="battle/BattleAttackInfo.h" />
+		<Unit filename="battle/BattleHex.cpp" />
+		<Unit filename="battle/BattleHex.h" />
+		<Unit filename="battle/BattleInfo.cpp" />
+		<Unit filename="battle/BattleInfo.h" />
+		<Unit filename="battle/CBattleInfoCallback.cpp" />
+		<Unit filename="battle/CBattleInfoCallback.h" />
+		<Unit filename="battle/CBattleInfoEssentials.cpp" />
+		<Unit filename="battle/CBattleInfoEssentials.h" />
+		<Unit filename="battle/CCallbackBase.cpp" />
+		<Unit filename="battle/CCallbackBase.h" />
+		<Unit filename="battle/CObstacleInstance.cpp" />
+		<Unit filename="battle/CObstacleInstance.h" />
+		<Unit filename="battle/CPlayerBattleCallback.cpp" />
+		<Unit filename="battle/CPlayerBattleCallback.h" />
+		<Unit filename="battle/ReachabilityInfo.cpp" />
+		<Unit filename="battle/ReachabilityInfo.h" />
+		<Unit filename="battle/SideInBattle.cpp" />
+		<Unit filename="battle/SideInBattle.h" />
+		<Unit filename="battle/SiegeInfo.cpp" />
+		<Unit filename="battle/SiegeInfo.h" />
 		<Unit filename="filesystem/AdapterLoaders.cpp" />
 		<Unit filename="filesystem/AdapterLoaders.h" />
 		<Unit filename="filesystem/CArchiveLoader.cpp" />

+ 2 - 2
lib/battle/BattleInfo.cpp

@@ -601,14 +601,14 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 	for(int i = 0; i < ARRAY_COUNT(tacticLvls); i++)
 	{
 		if(heroes[i])
-			tacticLvls[i] += heroes[i]->getSecSkillLevel(SecondarySkill::TACTICS);
+			tacticLvls[i] += heroes[i]->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::TACTICS));
 	}
 	int tacticsSkillDiff = tacticLvls[0] - tacticLvls[1];
 
 	if(tacticsSkillDiff && isTacticsAllowed)
 	{
 		curB->tacticsSide = tacticsSkillDiff < 0;
-		curB->tacticDistance = std::abs(tacticsSkillDiff)*2 + 1;
+		curB->tacticDistance = std::abs(tacticsSkillDiff);
 	}
 	else
 		curB->tacticDistance = 0;

+ 3 - 2
lib/filesystem/CFilesystemLoader.cpp

@@ -26,8 +26,9 @@ CFilesystemLoader::CFilesystemLoader(std::string _mountPoint, bfs::path baseDire
 std::unique_ptr<CInputStream> CFilesystemLoader::load(const ResourceID & resourceName) const
 {
 	assert(fileList.count(resourceName));
-
-	return make_unique<CFileInputStream>(baseDirectory / fileList.at(resourceName));
+	bfs::path file = baseDirectory / fileList.at(resourceName);
+	logGlobal->trace("loading %s", file.string());
+	return make_unique<CFileInputStream>(file);
 }
 
 bool CFilesystemLoader::existsResource(const ResourceID & resourceName) const

+ 1 - 0
lib/logging/CLogger.cpp

@@ -83,6 +83,7 @@ DLL_LINKAGE vstd::CLoggerBase * logBonus = CLogger::getLogger(CLoggerDomain("bon
 DLL_LINKAGE vstd::CLoggerBase * logNetwork = CLogger::getLogger(CLoggerDomain("network"));
 DLL_LINKAGE vstd::CLoggerBase * logAi = CLogger::getLogger(CLoggerDomain("ai"));
 DLL_LINKAGE vstd::CLoggerBase * logAnim = CLogger::getLogger(CLoggerDomain("animation"));
+DLL_LINKAGE vstd::CLoggerBase * logMod = CLogger::getLogger(CLoggerDomain("mod"));
 
 CLogger * CLogger::getLogger(const CLoggerDomain & domain)
 {

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -266,7 +266,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			{
 				const CSpell * spell = spellId.toSpell();
 				iw.text.addTxt (MetaString::SPELL_NAME, spellId);
-				if(spell->level <= hero->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
+				if(spell->level <= hero->maxSpellLevel())
 				{
 					if(hero->canLearnSpell(spell))
 					{

+ 27 - 84
lib/mapObjects/CGHeroInstance.cpp

@@ -17,6 +17,7 @@
 #include "../CModHandler.h"
 #include "../CSoundBase.h"
 #include "../spells/CSpellHandler.h"
+#include "../CSkillHandler.h"
 #include "CObjectClassesHandler.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
@@ -86,7 +87,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro
 	else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
 	{
 		ret = VLC->heroh->terrCosts[from.terType];
-		ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25;
+		ret -= valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING));
 		if(ret < GameConstants::BASE_MOVEMENT_COST)
 			ret = GameConstants::BASE_MOVEMENT_COST;
 	}
@@ -760,91 +761,26 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
 		removeBonus(bonus);
 
 	for(auto skill_info : secSkills)
-		updateSkill(SecondarySkill(skill_info.first), skill_info.second);
+		for(int level = 1; level <= skill_info.second; level++)
+			updateSkill(SecondarySkill(skill_info.first), level);
 }
 
 void CGHeroInstance::updateSkill(SecondarySkill which, int val)
 {
-	if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK)
-	{ //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill]
-		bool luck = which == SecondarySkill::LUCK;
-		Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
-
-		auto b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL)));
-		if(!b)
-		{
-			b = std::make_shared<Bonus>(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER);
-			addNewBonus(b);
-		}
-		else
-			b->val = +val;
-	}
-	else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level
-	{
-
-		if(auto b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL))))
-			b->val = +val;
+	auto skillBonus = (*VLC->skillh)[which]->getBonus(val);
+	for (auto b : skillBonus)
+	{
+		// bonuses provided by different levels of a secondary skill are aggregated via max (not + as usual)
+		// different secondary skills providing the same bonus (e.g. ballistics might improve archery as well) are kept separate
+		std::shared_ptr<Bonus> existing = getBonusLocalFirst(
+			Selector::typeSubtype(b->type, b->subtype).And(
+			Selector::source(Bonus::SECONDARY_SKILL, b->sid).And(
+			Selector::valueType(b->valType))));
+		if(existing)
+			vstd::amax(existing->val, b->val);
 		else
-			addNewBonus(std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which));
+			addNewBonus(std::make_shared<Bonus>(*b));
 	}
-
-	int skillVal = 0;
-	switch (which)
-	{
-	case SecondarySkill::ARCHERY:
-		switch (val)
-		{
-		case 1:
-			skillVal = 10; break;
-		case 2:
-			skillVal = 25; break;
-		case 3:
-			skillVal = 50; break;
-		}
-		break;
-	case SecondarySkill::LOGISTICS:
-		skillVal = 10 * val; break;
-	case SecondarySkill::NAVIGATION:
-		skillVal = 50 * val; break;
-	case SecondarySkill::MYSTICISM:
-		skillVal = val; break;
-	case SecondarySkill::EAGLE_EYE:
-		skillVal = 30 + 10 * val; break;
-	case SecondarySkill::NECROMANCY:
-		skillVal = 10 * val; break;
-	case SecondarySkill::LEARNING:
-		skillVal = 5 * val; break;
-	case SecondarySkill::OFFENCE:
-		skillVal = 10 * val; break;
-	case SecondarySkill::ARMORER:
-		skillVal = 5 * val; break;
-	case SecondarySkill::INTELLIGENCE:
-		skillVal = 25 << (val-1); break;
-	case SecondarySkill::SORCERY:
-		skillVal = 5 * val; break;
-	case SecondarySkill::RESISTANCE:
-		skillVal = 5 << (val-1); break;
-	case SecondarySkill::FIRST_AID:
-		skillVal = 25 + 25*val; break;
-	case SecondarySkill::ESTATES:
-		skillVal = 125 << (val-1); break;
-	}
-
-
-	Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN;
-	if(auto b = getExportedBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which)
-			.And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus
-	{
-		b->val = skillVal;
-		b->valType = skillValType;
-	}
-	else
-	{
-		auto bonus = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType);
-		bonus->source = Bonus::SECONDARY_SKILL;
-		addNewBonus(bonus);
-	}
-
 	CBonusSystemNode::treeHasChanged();
 }
 void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
@@ -890,7 +826,9 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
 
 	spell->forEachSchool([&, this](const SpellSchoolInfo & cnf, bool & stop)
 	{
-		int thisSchool = std::max<int>(getSecSkillLevel(cnf.skill),	valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << ((ui8)cnf.id))); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
+		int thisSchool = std::max<int>(
+			valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, cnf.skill),
+			valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << ((ui8)cnf.id))); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
 		if(thisSchool > skill)
 		{
 			skill = thisSchool;
@@ -1021,7 +959,7 @@ bool CGHeroInstance::canLearnSpell(const CSpell * spell) const
     if(!hasSpellbook())
 		return false;
 
-    if(spell->level > getSecSkillLevel(SecondarySkill::WISDOM) + 2) //not enough wisdom
+	if(spell->level > maxSpellLevel()) //not enough wisdom
 		return false;
 
 	if(vstd::contains(spells, spell->id))//already known
@@ -1135,7 +1073,7 @@ int3 CGHeroInstance::getSightCenter() const
 
 int CGHeroInstance::getSightRadius() const
 {
-	return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting
+	return 5 + valOfBonuses(Bonus::SIGHT_RADIOUS); // scouting gives SIGHT_RADIUS bonus
 }
 
 si32 CGHeroInstance::manaRegain() const
@@ -1143,7 +1081,7 @@ si32 CGHeroInstance::manaRegain() const
 	if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION))
 		return manaLimit();
 
-	return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level
+	return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::MYSTICISM) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level
 }
 
 si32 CGHeroInstance::getManaNewTurn() const
@@ -1239,6 +1177,11 @@ bool CGHeroInstance::hasSpellbook() const
 	return getArt(ArtifactPosition::SPELLBOOK);
 }
 
+int CGHeroInstance::maxSpellLevel() const
+{
+	return std::min(GameConstants::SPELL_LEVELS, 2 + valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::WISDOM)));
+}
+
 void CGHeroInstance::deserializationFix()
 {
 	artDeserializationFix(this);

+ 3 - 0
lib/mapObjects/CGHeroInstance.h

@@ -146,6 +146,7 @@ public:
 	//////////////////////////////////////////////////////////////////////////
 
 	bool hasSpellbook() const;
+	int maxSpellLevel() const;
 	EAlignment::EAlignment getAlignment() const;
 	const std::string &getBiography() const;
 	bool needsLastStack()const override;
@@ -302,5 +303,7 @@ public:
 		h & visitedObjects;
 		BONUS_TREE_DESERIALIZATION_FIX
 		//visitied town pointer will be restored by map serialization method
+		if(version < 777 && !h.saving)
+			recreateSecondarySkillsBonuses();
 	}
 };

+ 7 - 5
lib/mapObjects/MiscObjects.cpp

@@ -17,6 +17,7 @@
 #include "../CSoundBase.h"
 #include "../CModHandler.h"
 #include "../CHeroHandler.h"
+#include "../CSkillHandler.h"
 #include "CObjectClassesHandler.h"
 #include "../spells/CSpellHandler.h"
 #include "../IGameCallback.h"
@@ -323,17 +324,18 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 	if(count*2 > totalCount)
 		sympathy++; // 2 - hero have similar creatures more that 50%
 
-	int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy;
+	int diplomacy = h->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::DIPLOMACY);
+	int charisma = powerFactor + diplomacy + sympathy;
 
 	if(charisma < character)
 		return FIGHT;
 
 	if (allowJoin)
 	{
-		if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character)
+		if(diplomacy + sympathy + 1 >= character)
 			return JOIN_FOR_FREE;
 
-		else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2  +  sympathy  +  1 >= character)
+		else if(diplomacy * 2  +  sympathy  +  1 >= character)
 			return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold
 	}
 
@@ -1475,7 +1477,7 @@ std::string CGWitchHut::getHoverText(PlayerColor player) const
 	if(wasVisited(player))
 	{
 		hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s)
-		boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]);
+		boost::algorithm::replace_first(hoverName, "%s", VLC->skillh->skillName(ability));
 	}
 	return hoverName;
 }
@@ -1601,7 +1603,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,174);
 	}
-	else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT  && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom
+	else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT  && h->maxSpellLevel() < 3) //it's third level spell and hero doesn't have wisdom
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,130);
 	}

+ 2 - 0
lib/mapObjects/ObjectTemplate.cpp

@@ -101,6 +101,8 @@ void ObjectTemplate::afterLoadFixup()
 		usedTiles[0][0] = VISITABLE;
 		visitDir = 0xFF;
 	}
+	boost::algorithm::replace_all(animationFile, "\\", "/");
+	boost::algorithm::replace_all(editorAnimationFile, "\\", "/");
 }
 
 void ObjectTemplate::readTxt(CLegacyConfigParser & parser)

+ 1 - 1
lib/serializer/CSerializer.h

@@ -12,7 +12,7 @@
 #include "../ConstTransitivePtr.h"
 #include "../GameConstants.h"
 
-const ui32 SERIALIZATION_VERSION = 776;
+const ui32 SERIALIZATION_VERSION = 777;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 

+ 4 - 4
lib/spells/CSpellHandler.cpp

@@ -819,7 +819,7 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
 
 	spell->name = json["name"].String();
 
-	logGlobal->trace("%s: loading spell %s", __FUNCTION__, spell->name);
+	logMod->trace("%s: loading spell %s", __FUNCTION__, spell->name);
 
 	const auto schoolNames = json["school"];
 
@@ -854,7 +854,7 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
 	else if(targetType == "LOCATION")
 		spell->targetType = CSpell::LOCATION;
 	else
-		logGlobal->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->name, (targetType.empty() ? "empty" : "unknown ("+targetType+")"));
+		logMod->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->name, (targetType.empty() ? "empty" : "unknown ("+targetType+")"));
 
 	for(const auto & counteredSpell: json["counters"].Struct())
 		if (counteredSpell.second.Bool())
@@ -899,7 +899,7 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
 	else if(!implicitPositiveness)
 	{
 		spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case
-		logGlobal->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->name);
+		logMod->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->name);
 	}
 
 	spell->isSpecial = flags["special"].Bool();
@@ -909,7 +909,7 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
 		auto it = bonusNameMap.find(name);
 		if(it == bonusNameMap.end())
 		{
-			logGlobal->error("Spell %s: invalid bonus name %s", spell->name, name);
+			logMod->error("Spell %s: invalid bonus name %s", spell->name, name);
 		}
 		else
 		{

+ 75 - 0
osx/DS_Store_Setup.scpt

@@ -0,0 +1,75 @@
+-- Shamelessly taken from CMake source: Packaging/CMakeDMGSetup.scpt
+-- For licensing check cmake_modules/kitware license.txt
+--
+-- You can as well use this script to manually generate DS_Store
+-- First make DMG writable:
+--  hdiutil convert VCMI.dmg -format UDRW -o VCMI_writable.dmg
+-- Check current size of the image
+--  hdiutil resize -limits VCMI_writable.dmg
+-- Increase it size slightly so you can update .DS_Store
+-- Using 999999 will result in 512mb file though
+--  hdiutil resize -sectors 999999 VCMI_writable.dmg
+-- Attach it to /Volumes/VCMI/
+--  hdiutil attach VCMI_writable.dmg
+-- After run this script directly and it's will do the job
+--  osascript /path/to/vcmi/source/osx/DS_Store_Setup.scpt VCMI
+-- You should see icons moving and background appear
+-- Now /Volumes/VCMI/.DS_Store can be copied over to /path/to/vcmi/source/osx/dmg_DS_Stor
+
+on run argv
+  set image_name to item 1 of argv
+
+  tell application "Finder"
+  tell disk image_name
+
+    -- wait for the image to finish mounting
+    set open_attempts to 0
+    repeat while open_attempts < 4
+      try
+        open
+          delay 1
+          set open_attempts to 5
+        close
+      on error errStr number errorNumber
+        set open_attempts to open_attempts + 1
+        delay 10
+      end try
+    end repeat
+    delay 5
+
+    -- open the image the first time and save a DS_Store with just
+    -- background and icon setup
+    open
+      set current view of container window to icon view
+      set theViewOptions to the icon view options of container window
+      set background picture of theViewOptions to file ".background:background.png"
+      set arrangement of theViewOptions to not arranged
+      set icon size of theViewOptions to 128
+      delay 5
+    close
+
+    -- next setup the position of the app and Applications symlink
+    -- plus hide all the window decoration
+    open
+      update without registering applications
+      tell container window
+        set sidebar width to 0
+        set statusbar visible to false
+        set toolbar visible to false
+        set the bounds to { 400, 100, 900, 423 }
+        set position of item "VCMI.app" to { 133, 140 }
+        set position of item "Applications" to { 378, 140 }
+      end tell
+      update without registering applications
+      delay 5
+    close
+
+    -- one last open and close so you can see everything looks correct
+    open
+      delay 5
+    close
+
+  end tell
+  delay 1
+end tell
+end run

BIN
osx/dmg_DS_Store


+ 28 - 28
server/CGameHandler.cpp

@@ -592,12 +592,11 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 
 	if (finishingBattle->winnerHero)
 	{
-		if (int eagleEyeLevel = finishingBattle->winnerHero->getSecSkillLevel(SecondarySkill::EAGLE_EYE))
+		if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_VAL2, SecondarySkill::EAGLE_EYE))
 		{
-			int maxLevel = eagleEyeLevel + 1;
 			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE);
 			for (const CSpell *sp : gs->curB->sides.at(!battleResult.data->winner).usedSpellsHistory)
-				if (sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && getRandomGenerator().nextInt(99) < eagleEyeChance)
+				if (sp->level <= eagleEyeLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && getRandomGenerator().nextInt(99) < eagleEyeChance)
 					cs.spells.insert(sp->id);
 		}
 	}
@@ -881,9 +880,8 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 
 	if (att->getCreature()->idNumber == CreatureID::BALLISTA)
 	{
-		static const int artilleryLvlToChance[] = {0, 50, 75, 100};
 		const CGHeroInstance * owner = gs->curB->getHero(att->owner);
-		int chance = artilleryLvlToChance[owner->getSecSkillLevel(SecondarySkill::ARTILLERY)];
+		int chance = owner->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARTILLERY);
 		if (chance > getRandomGenerator().nextInt(99))
 		{
 			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
@@ -1972,7 +1970,7 @@ void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
 	if (t->hasBuilt(BuildingID::GRAIL, ETownType::CONFLUX) && t->hasBuilt(BuildingID::MAGES_GUILD_1))
 	{
 		// Aurora Borealis give spells of all levels even if only level 1 mages guild built
-		for (int i = 0; i < h->getSecSkillLevel(SecondarySkill::WISDOM)+2; i++)
+		for (int i = 0; i < h->maxSpellLevel(); i++)
 		{
 			std::vector<SpellID> spells;
 			getAllowedSpells(spells, i+1);
@@ -1982,7 +1980,7 @@ void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
 	}
 	else
 	{
-		for (int i = 0; i < std::min(t->mageGuildLevel(), h->getSecSkillLevel(SecondarySkill::WISDOM)+2); i++)
+		for (int i = 0; i < std::min(t->mageGuildLevel(), h->maxSpellLevel()); i++)
 		{
 			for (int j = 0; j < t->spellsAtLevel(i+1, true) && j < t->spells.at(i).size(); j++)
 			{
@@ -2488,19 +2486,21 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 {
 	const CGHeroInstance * h1 = getHero(fromHero);
 	const CGHeroInstance * h2 = getHero(toHero);
+	int h1_scholarLevel = h1->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SCHOLAR);
+	int h2_scholarLevel = h2->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SCHOLAR);
 
-	if (h1->getSecSkillLevel(SecondarySkill::SCHOLAR) < h2->getSecSkillLevel(SecondarySkill::SCHOLAR))
+	if (h1_scholarLevel < h2_scholarLevel)
 	{
 		std::swap (h1,h2);//1st hero need to have higher scholar level for correct message
 		std::swap(fromHero, toHero);
 	}
 
-	int ScholarLevel = h1->getSecSkillLevel(SecondarySkill::SCHOLAR);//heroes can trade up to this level
+	int ScholarLevel = std::max(h1_scholarLevel, h2_scholarLevel);//heroes can trade up to this level
 	if (!ScholarLevel || !h1->hasSpellbook() || !h2->hasSpellbook())
 		return;//no scholar skill or no spellbook
 
-	int h1Lvl = std::min(ScholarLevel+1, h1->getSecSkillLevel(SecondarySkill::WISDOM)+2),
-	    h2Lvl = std::min(ScholarLevel+1, h2->getSecSkillLevel(SecondarySkill::WISDOM)+2);//heroes can receive this levels
+	int h1Lvl = std::min(ScholarLevel, h1->maxSpellLevel()),
+		h2Lvl = std::min(ScholarLevel, h2->maxSpellLevel());//heroes can receive this levels
 
 	ChangeSpells cs1;
 	cs1.learn = true;
@@ -4019,19 +4019,18 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 				handleAfterAttackCasting(bat);
 			}
 
-			//second shot for ballista, only if hero has advanced artillery
-
-			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
-
-			if(destinationStack->alive()
-				&& (stack->getCreature()->idNumber == CreatureID::BALLISTA)
-				&& (attackingHero->getSecSkillLevel(SecondarySkill::ARTILLERY) >= SecSkillLevel::ADVANCED)
-			)
+			//extra shot(s) for ballista, based on artillery skill
+			if(stack->getCreature()->idNumber == CreatureID::BALLISTA)
 			{
-				BattleAttack bat2;
-				bat2.flags |= BattleAttack::SHOT;
-				prepareAttack(bat2, stack, destinationStack, 0, ba.destinationTile);
-				sendAndApply(&bat2);
+				const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
+				int ballistaBonusAttacks = attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_VAL2, SecondarySkill::ARTILLERY);
+				while(destinationStack->alive() && ballistaBonusAttacks-- > 0)
+				{
+					BattleAttack bat2;
+					bat2.flags |= BattleAttack::SHOT;
+					prepareAttack(bat2, stack, destinationStack, 0, ba.destinationTile);
+					sendAndApply(&bat2);
+				}
 			}
 			//allow more than one additional attack
 
@@ -4083,7 +4082,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 
 			CHeroHandler::SBallisticsLevelInfo sbi;
 			if(stack->getCreature()->idNumber == CreatureID::CATAPULT)
-				sbi = VLC->heroh->ballistics.at(attackingHero->getSecSkillLevel(SecondarySkill::BALLISTICS));
+				sbi = VLC->heroh->ballistics.at(attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::BALLISTICS));
 			else //may need to use higher ballistics level for creatures in future for some cases to match original H3 (upgraded cyclops etc)
 			{
 				sbi = VLC->heroh->ballistics.at(1);
@@ -5843,9 +5842,10 @@ void CGameHandler::runBattle()
 			}
 
 			const CGHeroInstance * curOwner = battleGetOwnerHero(next);
+			const int stackCreatureId = next->getCreature()->idNumber;
 
-			if ((next->position < 0 || next->getCreature()->idNumber == CreatureID::BALLISTA)	//arrow turret or ballista
-				&& (!curOwner || curOwner->getSecSkillLevel(SecondarySkill::ARTILLERY) == 0)) //hero has no artillery
+			if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
+				&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, stackCreatureId)))
 			{
 				BattleAction attack;
 				attack.actionType = Battle::SHOOT;
@@ -5875,7 +5875,7 @@ void CGameHandler::runBattle()
 					continue;
 				}
 
-				if (!curOwner || curOwner->getSecSkillLevel(SecondarySkill::BALLISTICS) == 0)
+				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::CATAPULT))
 				{
 					BattleAction attack;
 					attack.destinationTile = *RandomGeneratorUtil::nextItem(attackableBattleHexes,
@@ -5903,7 +5903,7 @@ void CGameHandler::runBattle()
 					continue;
 				}
 
-				if (!curOwner || curOwner->getSecSkillLevel(SecondarySkill::FIRST_AID) == 0) //no hero or hero has no first aid
+				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::FIRST_AID_TENT))
 				{
 					RandomGeneratorUtil::randomShuffle(possibleStacks, getRandomGenerator());
 					const CStack * toBeHealed = possibleStacks.front();

+ 2 - 4
server/CVCMIServer.cpp

@@ -609,11 +609,9 @@ void handleLinuxSignal(int sig)
 
 int main(int argc, char * argv[])
 {
-#ifndef VCMI_WINDOWS
+#ifndef VCMI_ANDROID
 	// Correct working dir executable folder (not bundle folder) so we can use executable relative paths
-	std::string executablePath = argv[0];
-	std::string workDir = executablePath.substr(0, executablePath.rfind('/'));
-	chdir(workDir.c_str());
+	boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path());
 #endif
 	// Installs a sig sev segmentation violation handler
 	// to log stacktrace