Explorar o código

Merge remote-tracking branch 'upstream/develop' into develop

Mike %!s(int64=2) %!d(string=hai) anos
pai
achega
90397a9604
Modificáronse 71 ficheiros con 1792 adicións e 815 borrados
  1. 4 0
      .github/workflows/github.yml
  2. 3 3
      AI/Nullkiller/AIGateway.cpp
  3. 2 2
      AI/Nullkiller/AIUtility.cpp
  4. 2 2
      AI/VCAI/AIUtility.cpp
  5. 2 2
      AI/VCAI/VCAI.cpp
  6. 22 1
      CMakePresets.json
  7. 0 13
      client/CMT.cpp
  8. 0 3
      client/CMT.h
  9. 2 0
      client/eventsSDL/InputHandler.cpp
  10. 6 24
      client/eventsSDL/InputSourceText.cpp
  11. 2 3
      client/lobby/RandomMapTab.cpp
  12. 4 0
      client/render/IScreenHandler.h
  13. 61 12
      client/renderSDL/ScreenHandler.cpp
  14. 6 2
      client/renderSDL/ScreenHandler.h
  15. 7 2
      client/widgets/Buttons.cpp
  16. 3 0
      client/widgets/Buttons.h
  17. 4 4
      client/widgets/CArtifactHolder.cpp
  18. 3 6
      client/widgets/CArtifactsOfHeroAltar.cpp
  19. 3 3
      client/widgets/CArtifactsOfHeroBase.cpp
  20. 5 8
      client/widgets/CComponent.cpp
  21. 1 1
      client/windows/CCastleInterface.cpp
  22. 1 1
      client/windows/CCreatureWindow.cpp
  23. 1 1
      client/windows/CTradeWindow.cpp
  24. 15 2
      client/windows/settings/GeneralOptionsTab.cpp
  25. 2 0
      cmake_modules/VCMI_lib.cmake
  26. 13 7
      config/schemas/settings.json
  27. 60 54
      launcher/translation/polish.ts
  28. 36 23
      lib/ArtifactUtils.cpp
  29. 1 0
      lib/ArtifactUtils.h
  30. 88 249
      lib/CArtHandler.cpp
  31. 68 116
      lib/CArtHandler.h
  32. 192 0
      lib/CArtifactInstance.cpp
  33. 102 0
      lib/CArtifactInstance.h
  34. 1 0
      lib/CCreatureSet.h
  35. 2 2
      lib/JsonRandom.cpp
  36. 22 12
      lib/NetPacks.h
  37. 42 38
      lib/NetPacksLib.cpp
  38. 1 1
      lib/battle/BattleInfo.cpp
  39. 2 2
      lib/mapObjects/CGHeroInstance.cpp
  40. 2 2
      lib/mapObjects/CQuest.cpp
  41. 1 1
      lib/mapObjects/MiscObjects.cpp
  42. 3 3
      lib/mapping/CMap.cpp
  43. 1 1
      lib/mapping/MapFormatH3M.cpp
  44. 0 2
      lib/registerTypes/RegisterTypes.h
  45. 18 6
      lib/rmg/CMapGenOptions.cpp
  46. 5 4
      lib/rmg/CMapGenOptions.h
  47. 1 6
      lib/rmg/CMapGenerator.cpp
  48. 18 4
      lib/rmg/CRmgTemplate.cpp
  49. 18 12
      lib/rmg/CRmgTemplate.h
  50. 5 5
      lib/rmg/CZonePlacer.cpp
  51. 32 18
      lib/rmg/modificators/ConnectionsPlacer.cpp
  52. 2 0
      lib/rmg/modificators/ConnectionsPlacer.h
  53. 13 6
      lib/rmg/modificators/MinePlacer.cpp
  54. 54 41
      lib/rmg/modificators/ObjectManager.cpp
  55. 20 8
      lib/rmg/modificators/ObjectManager.h
  56. 24 8
      lib/rmg/modificators/RoadPlacer.cpp
  57. 4 2
      lib/rmg/modificators/TownPlacer.cpp
  58. 24 8
      lib/rmg/modificators/WaterProxy.cpp
  59. 4 3
      lib/rmg/modificators/WaterProxy.h
  60. 1 1
      lib/serializer/CSerializer.cpp
  61. 96 1
      mapeditor/translation/english.ts
  62. 120 15
      mapeditor/translation/french.ts
  63. 96 1
      mapeditor/translation/german.ts
  64. 123 28
      mapeditor/translation/polish.ts
  65. 96 1
      mapeditor/translation/russian.ts
  66. 96 1
      mapeditor/translation/spanish.ts
  67. 96 1
      mapeditor/translation/ukrainian.ts
  68. 2 2
      mapeditor/validator.cpp
  69. 14 18
      server/CGameHandler.cpp
  70. 11 4
      server/CQuery.cpp
  71. 1 3
      test/entity/CArtifactTest.cpp

+ 4 - 0
.github/workflows/github.yml

@@ -76,6 +76,10 @@ jobs:
             os: ubuntu-20.04
             test: 0
             preset: linux-gcc-test
+          - platform: linux
+            os: ubuntu-20.04
+            test: 0
+            preset: linux-gcc-debug
           - platform: mac-intel
             os: macos-12
             test: 0

+ 3 - 3
AI/Nullkiller/AIGateway.cpp

@@ -705,7 +705,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	//you can't request action from action-response thread
 	requestActionASAP([=]()
 	{
-		if(removableUnits)
+		if(removableUnits && up->tempOwner == down->tempOwner)
 			pickBestCreatures(down, up);
 
 		answerQuery(queryID, 0);
@@ -1006,7 +1006,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				//FIXME: why are the above possible to be null?
 
 				bool emptySlotFound = false;
-				for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				{
 					ArtifactLocation destLocation(target, slot);
 					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
@@ -1019,7 +1019,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				}
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				{
-					for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+					for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 					{
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one

+ 2 - 2
AI/Nullkiller/AIUtility.cpp

@@ -306,10 +306,10 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	auto art1 = a1->artType;
 	auto art2 = a2->artType;
 
-	if(art1->price == art2->price)
+	if(art1->getPrice() == art2->getPrice())
 		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
 	else
-		return art1->price > art2->price;
+		return art1->getPrice() > art2->getPrice();
 }
 
 bool isWeeklyRevisitable(const CGObjectInstance * obj)

+ 2 - 2
AI/VCAI/AIUtility.cpp

@@ -256,8 +256,8 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	auto art1 = a1->artType;
 	auto art2 = a2->artType;
 
-	if(art1->price == art2->price)
+	if(art1->getPrice() == art2->getPrice())
 		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
 	else
-		return art1->price > art2->price;
+		return art1->getPrice() > art2->getPrice();
 }

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -1192,7 +1192,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				//FIXME: why are the above possible to be null?
 
 				bool emptySlotFound = false;
-				for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				{
 					ArtifactLocation destLocation(target, slot);
 					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
@@ -1205,7 +1205,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				}
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				{
-					for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+					for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 					{
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one

+ 22 - 1
CMakePresets.json

@@ -70,6 +70,18 @@
             "description": "VCMI Linux GCC",
             "inherits": "linux-release",
             "cacheVariables": {
+                "ENABLE_LUA" : "ON",
+                "CMAKE_C_COMPILER": "/usr/bin/gcc",
+                "CMAKE_CXX_COMPILER": "/usr/bin/g++"
+            }
+        },
+        {
+            "name": "linux-gcc-debug",
+            "displayName": "GCC x86_64-pc-linux-gnu (debug)",
+            "description": "VCMI Linux GCC (Debug)",
+            "inherits": "linux-release",
+            "cacheVariables": {
+                "CMAKE_BUILD_TYPE": "Debug",
                 "ENABLE_LUA" : "ON",
                 "ENABLE_PCH" : "OFF",
                 "CMAKE_C_COMPILER": "/usr/bin/gcc",
@@ -93,7 +105,6 @@
             "inherits": "linux-test",
             "cacheVariables": {
                 "ENABLE_LUA" : "OFF",
-                "ENABLE_PCH" : "OFF",
                 "CMAKE_C_COMPILER": "/usr/bin/gcc",
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
             }
@@ -239,6 +250,11 @@
             "hidden": true,
             "configuration": "RelWithDebInfo"
         },
+        {
+            "name": "default-debug",
+            "hidden": true,
+            "configuration": "Debug"
+        },
         {
             "name": "linux-clang-release",
             "configurePreset": "linux-clang-release",
@@ -259,6 +275,11 @@
             "configurePreset": "linux-gcc-release",
             "inherits": "default-release"
         },
+        {
+            "name": "linux-gcc-debug",
+            "configurePreset": "linux-gcc-debug",
+            "inherits": "default-debug"
+        },
         {
             "name": "macos-xcode-release",
             "configurePreset": "macos-xcode-release",

+ 0 - 13
client/CMT.cpp

@@ -449,19 +449,6 @@ void playIntro()
 
 static void mainLoop()
 {
-	SettingsListener resChanged = settings.listen["video"]["resolution"];
-	SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
-
-	auto functor = [](const JsonNode &newState){
-		GH.dispatchMainThread([](){
-			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-			GH.onScreenResize();
-		});
-	};
-
-	resChanged(functor);
-	fsChanged(functor);
-
 	inGuiThread.reset(new bool(true));
 
 	while(1) //main SDL events loop

+ 0 - 3
client/CMT.h

@@ -10,13 +10,10 @@
 #pragma once
 
 struct SDL_Texture;
-struct SDL_Window;
 struct SDL_Renderer;
 struct SDL_Surface;
 
 extern SDL_Texture * screenTexture;
-
-extern SDL_Window * mainWindow;
 extern SDL_Renderer * mainRenderer;
 
 extern SDL_Surface *screen;      // main screen surface

+ 2 - 0
client/eventsSDL/InputHandler.cpp

@@ -121,6 +121,8 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
 	{
 		Settings full = settings.write["video"]["fullscreen"];
 		full->Bool() = !full->Bool();
+
+		GH.onScreenResize();
 		return;
 	}
 	else if(ev.type == SDL_USEREVENT)

+ 6 - 24
client/eventsSDL/InputSourceText.cpp

@@ -14,20 +14,17 @@
 #include "../CMT.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/EventDispatcher.h"
+#include "../render/IScreenHandler.h"
+#include "../renderSDL/SDL_Extensions.h"
 
 #include "../../lib/Rect.h"
 
 #include <SDL_events.h>
-#include <SDL_render.h>
 
 #ifdef VCMI_APPLE
 #	include <dispatch/dispatch.h>
 #endif
 
-#ifdef VCMI_IOS
-#	include "ios/utils.h"
-#endif
-
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 {
 	GH.events().dispatchTextInput(text.text);
@@ -40,30 +37,15 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
 
 void InputSourceText::startTextInput(const Rect & whereInput)
 {
+
 #ifdef VCMI_APPLE
 	dispatch_async(dispatch_get_main_queue(), ^{
 #endif
 
-	// TODO ios: looks like SDL bug actually, try fixing there
-	auto renderer = SDL_GetRenderer(mainWindow);
-	float scaleX, scaleY;
-	SDL_Rect viewport;
-	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
-	SDL_RenderGetViewport(renderer, &viewport);
-
-#ifdef VCMI_IOS
-	const auto nativeScale = iOS_utils::screenScale();
-	scaleX /= nativeScale;
-	scaleY /= nativeScale;
-#endif
-
-	SDL_Rect rectInScreenCoordinates;
-	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
-	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
-	rectInScreenCoordinates.w = whereInput.w * scaleX;
-	rectInScreenCoordinates.h = whereInput.h * scaleY;
+	Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
+	SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
 
-	SDL_SetTextInputRect(&rectInScreenCoordinates);
+	SDL_SetTextInputRect(&textInputRect);
 
 	if (SDL_IsTextInputActive() == SDL_FALSE)
 	{

+ 2 - 3
client/lobby/RandomMapTab.cpp

@@ -119,12 +119,11 @@ RandomMapTab::RandomMapTab():
 		std::string cbRoadType = "selectRoad_" + road->getJsonKey();
 		addCallback(cbRoadType, [&, road](bool on)
 		{
-			mapGenOptions->setRoadEnabled(road->getJsonKey(), on);
+			mapGenOptions->setRoadEnabled(road->getId(), on);
 			updateMapInfoByHost();
 		});
 	}
 	
-	
 	build(config);
 	
 	updateMapInfoByHost();
@@ -313,7 +312,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	{
 		if(auto w = widget<CToggleButton>(r->getJsonKey()))
 		{
-			w->setSelected(opts->isRoadEnabled(r->getJsonKey()));
+			w->setSelected(opts->isRoadEnabled(r->getId()));
 		}
 	}
 }

+ 4 - 0
client/render/IScreenHandler.h

@@ -12,6 +12,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 class Point;
+class Rect;
 VCMI_LIB_NAMESPACE_END
 
 class IScreenHandler
@@ -33,4 +34,7 @@ public:
 
 	/// Returns <min, max> range of possible values for screen scaling percentage
 	virtual std::tuple<int, int> getSupportedScalingRange() const = 0;
+
+	/// Converts provided rect from logical coordinates into coordinates within window, accounting for scaling and viewport
+	virtual Rect convertLogicalPointsToWindow(const Rect & input) const = 0;
 };

+ 61 - 12
client/renderSDL/ScreenHandler.cpp

@@ -22,10 +22,14 @@
 #include "../lib/CAndroidVMHelper.h"
 #endif
 
+#ifdef VCMI_IOS
+#	include "ios/utils.h"
+#endif
+
 #include <SDL.h>
 
 // TODO: should be made into a private members of ScreenHandler
-SDL_Window * mainWindow = nullptr;
+static SDL_Window * mainWindow = nullptr;
 SDL_Renderer * mainRenderer = nullptr;
 SDL_Texture * screenTexture = nullptr;
 SDL_Surface * screen = nullptr; //main screen surface
@@ -42,28 +46,70 @@ std::tuple<int, int> ScreenHandler::getSupportedScalingRange() const
 	// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
 	static const double minimalScaling = 50;
 
-	Point renderResolution = getPreferredRenderingResolution();
-	double maximalScalingWidth = 100.0 * renderResolution.x / minResolution.x;
-	double maximalScalingHeight = 100.0 * renderResolution.y / minResolution.y;
+	Point renderResolution = getActualRenderResolution();
+	double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
+	Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
+
+	double maximalScalingWidth = 100.0 * availableResolution.x / minResolution.x;
+	double maximalScalingHeight = 100.0 * availableResolution.y / minResolution.y;
 	double maximalScaling = std::min(maximalScalingWidth, maximalScalingHeight);
 
 	return { minimalScaling, maximalScaling };
 }
 
+Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
+{
+	Rect result;
+
+	// FIXME: use SDL_RenderLogicalToWindow instead? Needs to be tested on ios
+
+	float scaleX, scaleY;
+	SDL_Rect viewport;
+	SDL_RenderGetScale(mainRenderer, &scaleX, &scaleY);
+	SDL_RenderGetViewport(mainRenderer, &viewport);
+
+#ifdef VCMI_IOS
+	// TODO ios: looks like SDL bug actually, try fixing there
+	const auto nativeScale = iOS_utils::screenScale();
+	scaleX /= nativeScale;
+	scaleY /= nativeScale;
+#endif
+
+	result.x = (viewport.x + input.x) * scaleX;
+	result.y = (viewport.y + input.y) * scaleY;
+	result.w = input.w * scaleX;
+	result.h = input.h * scaleY;
+
+	return result;
+}
+
 Point ScreenHandler::getPreferredLogicalResolution() const
 {
-	Point renderResolution = getPreferredRenderingResolution();
+	Point renderResolution = getActualRenderResolution();
+	double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
+	Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
+
 	auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
 
 	int userScaling = settings["video"]["resolution"]["scaling"].Integer();
 	int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
 
-	Point logicalResolution = renderResolution * 100.0 / scaling;
+	Point logicalResolution = availableResolution * 100.0 / scaling;
 
 	return logicalResolution;
 }
 
-Point ScreenHandler::getPreferredRenderingResolution() const
+Point ScreenHandler::getActualRenderResolution() const
+{
+	assert(mainRenderer != nullptr);
+
+	Point result;
+	SDL_GetRendererOutputSize(mainRenderer, &result.x, &result.y);
+
+	return result;
+}
+
+Point ScreenHandler::getPreferredWindowResolution() const
 {
 	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED)
 	{
@@ -178,11 +224,14 @@ void ScreenHandler::updateWindowState()
 	{
 		case EWindowMode::FULLSCREEN_EXCLUSIVE:
 		{
+			// for some reason, VCMI fails to switch from FULLSCREEN_BORDERLESS_WINDOWED to FULLSCREEN_EXCLUSIVE directly
+			// Switch to windowed mode first to avoid this bug
+			SDL_SetWindowFullscreen(mainWindow, 0);
 			SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
 
 			SDL_DisplayMode mode;
 			SDL_GetDesktopDisplayMode(displayIndex, &mode);
-			Point resolution = getPreferredRenderingResolution();
+			Point resolution = getPreferredWindowResolution();
 
 			mode.w = resolution.x;
 			mode.h = resolution.y;
@@ -200,7 +249,7 @@ void ScreenHandler::updateWindowState()
 		}
 		case EWindowMode::WINDOWED:
 		{
-			Point resolution = getPreferredRenderingResolution();
+			Point resolution = getPreferredWindowResolution();
 			SDL_SetWindowFullscreen(mainWindow, 0);
 			SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
 			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
@@ -290,7 +339,7 @@ SDL_Window * ScreenHandler::createWindowImpl(Point dimensions, int flags, bool c
 SDL_Window * ScreenHandler::createWindow()
 {
 #ifndef VCMI_MOBILE
-	Point dimensions = getPreferredRenderingResolution();
+	Point dimensions = getPreferredWindowResolution();
 
 	switch(getPreferredWindowMode())
 	{
@@ -350,7 +399,7 @@ void ScreenHandler::validateSettings()
 	{
 		//we only check that our desired window size fits on screen
 		int displayIndex = getPreferredDisplayIndex();
-		Point resolution = getPreferredRenderingResolution();
+		Point resolution = getPreferredWindowResolution();
 
 		SDL_DisplayMode mode;
 
@@ -368,7 +417,7 @@ void ScreenHandler::validateSettings()
 	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE)
 	{
 		auto legalOptions = getSupportedResolutions();
-		Point selectedResolution = getPreferredRenderingResolution();
+		Point selectedResolution = getPreferredWindowResolution();
 
 		if(!vstd::contains(legalOptions, selectedResolution))
 		{

+ 6 - 2
client/renderSDL/ScreenHandler.h

@@ -30,14 +30,17 @@ enum class EWindowMode
 };
 
 /// This class is responsible for management of game window and its main rendering surface
-class ScreenHandler : public IScreenHandler
+class ScreenHandler final : public IScreenHandler
 {
 	/// Dimensions of target surfaces/textures, this value is what game logic views as screen size
 	Point getPreferredLogicalResolution() const;
 
 	/// Dimensions of output window, if different from logicalResolution SDL will perform scaling
 	/// This value is what player views as window size
-	Point getPreferredRenderingResolution() const;
+	Point getPreferredWindowResolution() const;
+
+	/// Dimensions of render output, usually same as window size except for high-DPI screens on macOS / iOS
+	Point getActualRenderResolution() const;
 
 	EWindowMode getPreferredWindowMode() const;
 
@@ -86,4 +89,5 @@ public:
 	std::vector<Point> getSupportedResolutions() const final;
 	std::vector<Point> getSupportedResolutions(int displayIndex) const;
 	std::tuple<int, int> getSupportedScalingRange() const final;
+	Rect convertLogicalPointsToWindow(const Rect & input) const final;
 };

+ 7 - 2
client/widgets/Buttons.cpp

@@ -331,11 +331,16 @@ void CToggleBase::setEnabled(bool enabled)
 	// for overrides
 }
 
-void CToggleBase::setSelected(bool on)
+void CToggleBase::setSelectedSilent(bool on)
 {
-	bool changed = (on != selected);
 	selected = on;
 	doSelect(on);
+}
+
+void CToggleBase::setSelected(bool on)
+{
+	bool changed = (on != selected);
+	setSelectedSilent(on);
 	if (changed)
 		callback(on);
 }

+ 3 - 0
client/widgets/Buttons.h

@@ -136,6 +136,9 @@ public:
 	/// Changes selection to "on", and calls callback
 	void setSelected(bool on);
 
+	/// Changes selection to "on" without calling callback
+	void setSelectedSilent(bool on);
+
 	void addCallback(std::function<void(bool)> callback);
 
 	/// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet.

+ 4 - 4
client/widgets/CArtifactHolder.cpp

@@ -236,11 +236,11 @@ void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
 		text += "{" + combinedArt.first->getNameTranslated() + "}";
 		if(arts.size() == 1)
 		{
-			for(const auto part : *combinedArt.first->constituents)
+			for(const auto part : combinedArt.first->getConstituents())
 				artList += "\n" + part->getNameTranslated();
 		}
 		text += " (" + boost::str(boost::format("%d") % combinedArt.second) + " / " +
-			boost::str(boost::format("%d") % combinedArt.first->constituents->size()) + ")" + artList;
+			boost::str(boost::format("%d") % combinedArt.first->getConstituents().size()) + ")" + artList;
 	}
 }
 
@@ -290,9 +290,9 @@ bool ArtifactUtilsClient::askToDisassemble(const CGHeroInstance * hero, const Ar
 	const auto art = hero->getArt(slot);
 	assert(art);
 
-	if(art->canBeDisassembled())
+	if(art->isCombined())
 	{
-		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->constituents->size() - 1))
+		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
 			return false;
 
 		LOCPLINT->showArtifactAssemblyDialog(

+ 3 - 6
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -98,13 +98,10 @@ void CArtifactsOfHeroAltar::deleteFromVisible(const CArtifactInstance * artInst)
 	}
 	else
 	{
-		if(artInst->canBeDisassembled())
+		for(const auto & part : artInst->getPartsInfo())
 		{
-			for(const auto & part : dynamic_cast<const CCombinedArtifactInstance*>(artInst)->constituentsInfo)
-			{
-				if(part.slot != ArtifactPosition::PRE_FIRST)
-					getArtPlace(part.slot)->setArtifact(nullptr);
-			}
+			if(part.slot != ArtifactPosition::PRE_FIRST)
+				getArtPlace(part.slot)->setArtifact(nullptr);
 		}
 	}
 }

+ 3 - 3
client/widgets/CArtifactsOfHeroBase.cpp

@@ -257,14 +257,14 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 	{
 		artPlace->lockSlot(slotInfo->locked);
 		artPlace->setArtifact(slotInfo->artifact);
-		if(!slotInfo->artifact->canBeDisassembled())
+		if(!slotInfo->artifact->isCombined())
 		{
 			// If the artifact is part of at least one combined artifact, add additional information
 			std::map<const CArtifact*, int> arts;
-			for(const auto combinedArt : slotInfo->artifact->artType->constituentOf)
+			for(const auto combinedArt : slotInfo->artifact->artType->getPartOf())
 			{
 				arts.insert(std::pair(combinedArt, 0));
-				for(const auto part : *combinedArt->constituents)
+				for(const auto part : combinedArt->getConstituents())
 					if(artSet.hasArt(part->getId(), true))
 						arts.at(combinedArt)++;
 			}

+ 5 - 8
client/widgets/CComponent.cpp

@@ -34,6 +34,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/NetPacksBase.h"
 #include "../../lib/CArtHandler.h"
+#include "../../lib/CArtifactInstance.h"
 
 CComponent::CComponent(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font):
 	perDay(false)
@@ -169,16 +170,12 @@ std::string CComponent::getDescription()
 	case artifact:
 	{
 		auto artID = ArtifactID(subtype);
-		std::unique_ptr<CArtifactInstance> art;
-		if (artID != ArtifactID::SPELL_SCROLL)
+		auto description = VLC->arth->objects[artID]->getDescriptionTranslated();
+		if(artID == ArtifactID::SPELL_SCROLL)
 		{
-			art.reset(ArtifactUtils::createNewArtifactInstance(artID));
+			ArtifactUtils::insertScrrollSpellName(description, SpellID(val));
 		}
-		else
-		{
-			art.reset(ArtifactUtils::createScroll(SpellID(val)));
-		}
-		return art->getDescription();
+		return description;
 	}
 	case experience: return CGI->generaltexth->allTexts[241];
 	case spell:      return (*CGI->spellh)[subtype]->getDescriptionTranslated(val);

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -800,7 +800,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
 	bool possible = LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= price;
 	if(possible)
 	{
-		for(auto slot : art->possibleSlots.at(ArtBearer::HERO))
+		for(auto slot : art->getPossibleSlots().at(ArtBearer::HERO))
 		{
 			if(hero->getArt(slot) == nullptr)
 			{

+ 1 - 1
client/windows/CCreatureWindow.cpp

@@ -588,7 +588,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		auto art = parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT);
 		if(art)
 		{
-			parent->stackArtifactIcon = std::make_shared<CAnimImage>("ARTIFACT", art->artType->iconIndex, 0, pos.x, pos.y);
+			parent->stackArtifactIcon = std::make_shared<CAnimImage>("ARTIFACT", art->artType->getIconIndex(), 0, pos.x, pos.y);
 			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), CComponent::artifact);
 			parent->stackArtifactHelp->type = art->artType->getId();
 

+ 1 - 1
client/windows/CTradeWindow.cpp

@@ -803,7 +803,7 @@ void CMarketplaceWindow::makeDeal()
 			leftIdToSend = hLeft->serial;
 			break;
 		case EMarketMode::ARTIFACT_RESOURCE:
-			leftIdToSend = hLeft->getArtInstance()->id.getNum();
+			leftIdToSend = hLeft->getArtInstance()->getId().getNum();
 			break;
 		case EMarketMode::RESOURCE_ARTIFACT:
 			if(!ArtifactID(hRight->id).toArtifact()->canBePutAt(hero))

+ 15 - 2
client/windows/settings/GeneralOptionsTab.cpp

@@ -277,10 +277,18 @@ void GeneralOptionsTab::setGameResolution(int index)
 	gameRes["height"].Float() = resolution.y;
 
 	widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y));
+
+	GH.dispatchMainThread([](){
+		boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+		GH.onScreenResize();
+	});
 }
 
 void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
 {
+	if (on == settings["video"]["fullscreen"].Bool() && exclusive == settings["video"]["realFullscreen"].Bool())
+		return;
+
 	setBoolSetting("video", "realFullscreen", exclusive);
 	setBoolSetting("video", "fullscreen", on);
 
@@ -288,12 +296,17 @@ void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
 	std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox");
 
 	if (fullscreenBorderlessCheckbox)
-		fullscreenBorderlessCheckbox->setSelected(on && !exclusive);
+		fullscreenBorderlessCheckbox->setSelectedSilent(on && !exclusive);
 
 	if (fullscreenExclusiveCheckbox)
-		fullscreenExclusiveCheckbox->setSelected(on && exclusive);
+		fullscreenExclusiveCheckbox->setSelectedSilent(on && exclusive);
 
 	updateResolutionSelector();
+
+	GH.dispatchMainThread([](){
+		boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+		GH.onScreenResize();
+	});
 }
 
 void GeneralOptionsTab::selectGameScaling()

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -220,6 +220,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/BattleFieldHandler.cpp
 		${MAIN_LIB_DIR}/CAndroidVMHelper.cpp
 		${MAIN_LIB_DIR}/CArtHandler.cpp
+		${MAIN_LIB_DIR}/CArtifactInstance.cpp
 		${MAIN_LIB_DIR}/CBonusTypeHandler.cpp
 		${MAIN_LIB_DIR}/CBuildingHandler.cpp
 		${MAIN_LIB_DIR}/CConfigHandler.cpp
@@ -550,6 +551,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/BattleFieldHandler.h
 		${MAIN_LIB_DIR}/CAndroidVMHelper.h
 		${MAIN_LIB_DIR}/CArtHandler.h
+		${MAIN_LIB_DIR}/CArtifactInstance.h
 		${MAIN_LIB_DIR}/CBonusTypeHandler.h
 		${MAIN_LIB_DIR}/CBuildingHandler.h
 		${MAIN_LIB_DIR}/CConfigHandler.h

+ 13 - 7
config/schemas/settings.json

@@ -109,13 +109,14 @@
 			"additionalProperties" : false,
 			"default" : {},
 			"required" : [ 
-				"resolution", 
-				"fullscreen", 
-				"realFullscreen", 
-				"cursor", 
-				"showIntro", 
-				"spellbookAnimation", 
-				"driver", 
+				"resolution",
+				"reservedWidth",
+				"fullscreen",
+				"realFullscreen",
+				"cursor",
+				"showIntro",
+				"spellbookAnimation",
+				"driver",
 				"displayIndex",
 				"showfps",
 				"targetfps"
@@ -134,6 +135,11 @@
 					"defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 },
 					"default" : {"width" : 800, "height" : 600, "scaling" : 100 }
 				},
+				"reservedWidth" : {
+					"type" : "number",
+					"defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL
+					"default" : 0
+				},
 				"fullscreen" : {
 					"type" : "boolean",
 					"default" : false

+ 60 - 54
launcher/translation/polish.ts

@@ -6,84 +6,84 @@
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="22"/>
         <source>VCMI on Discord</source>
-        <translation type="unfinished">VCMI na Discordzie</translation>
+        <translation>VCMI na Discordzie</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="29"/>
         <source>Have a question? Found a bug? Want to help? Join us!</source>
-        <translation type="unfinished">Masz pytanie? Znalazłeś błąd? Chcesz pomóc? Dołącz do nas!</translation>
+        <translation>Masz pytanie? Znalazłeś błąd? Chcesz pomóc? Dołącz do nas!</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="36"/>
         <source>VCMI on Github</source>
-        <translation type="unfinished">VCMI na Github</translation>
+        <translation>VCMI na Github</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="55"/>
         <source>Our Community</source>
-        <translation type="unfinished"></translation>
+        <translation>Nasza społeczność</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="62"/>
         <source>VCMI on Slack</source>
-        <translation type="unfinished">VCMI na Slacku</translation>
+        <translation>VCMI na Slacku</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="98"/>
         <source>Build Information</source>
-        <translation type="unfinished"></translation>
+        <translation>Informacje o wersji</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="115"/>
         <source>User data directory</source>
-        <translation type="unfinished">Katalog danych użytkownika</translation>
+        <translation>Katalog danych użytkownika</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="122"/>
         <location filename="../aboutProject/aboutproject_moc.ui" line="129"/>
         <location filename="../aboutProject/aboutproject_moc.ui" line="193"/>
         <source>Open</source>
-        <translation type="unfinished">Otwórz</translation>
+        <translation>Otwórz</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="136"/>
         <source>Check for updates</source>
-        <translation type="unfinished"></translation>
+        <translation>Sprawdź aktualizacje</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="156"/>
         <source>Game version</source>
-        <translation type="unfinished"></translation>
+        <translation>Wersja gry</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="163"/>
         <source>Log files directory</source>
-        <translation type="unfinished">Katalog logów</translation>
+        <translation>Katalog logów</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="176"/>
         <source>Data Directories</source>
-        <translation type="unfinished">Katalogi z danymi</translation>
+        <translation>Katalogi z danymi</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="213"/>
         <source>Game data directory</source>
-        <translation type="unfinished"></translation>
+        <translation>Katalog danych gry</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="220"/>
         <source>Operating System</source>
-        <translation type="unfinished"></translation>
+        <translation>System operacyjny</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="273"/>
         <source>Project homepage</source>
-        <translation type="unfinished"></translation>
+        <translation>Witryna projektu</translation>
     </message>
     <message>
         <location filename="../aboutProject/aboutproject_moc.ui" line="286"/>
         <source>Report a bug</source>
-        <translation type="unfinished"></translation>
+        <translation>Zgłoś błąd</translation>
     </message>
 </context>
 <context>
@@ -365,27 +365,27 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="311"/>
         <source>This mod can not be installed or enabled because the following dependencies are not present</source>
-        <translation type="unfinished">Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione</translation>
+        <translation>Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
         <source>This mod can not be enabled because the following mods are incompatible with it</source>
-        <translation type="unfinished">Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne</translation>
+        <translation>Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="313"/>
         <source>This mod cannot be disabled because it is required by the following mods</source>
-        <translation type="unfinished">Ten mod nie może zostać wyłączony ponieważ jest wymagany by do uruchomienia następujących modów</translation>
+        <translation>Ten mod nie może zostać wyłączony ponieważ jest wymagany do uruchomienia następujących modów</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="314"/>
         <source>This mod cannot be uninstalled or updated because it is required by the following mods</source>
-        <translation type="unfinished">Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów</translation>
+        <translation>Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
         <source>This is a submod and it cannot be installed or uninstalled separately from its parent mod</source>
-        <translation type="unfinished">To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego</translation>
+        <translation>To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="330"/>
@@ -435,67 +435,67 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="448"/>
         <source>Interface Scaling</source>
-        <translation type="unfinished"></translation>
+        <translation>Skala interfejsu</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="398"/>
         <source>Neutral AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>AI bitewne jednostek neutralnych</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="203"/>
         <source>Enemy AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>AI bitewne wrogów</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="509"/>
         <source>Additional repository</source>
-        <translation type="unfinished"></translation>
+        <translation>Dodatkowe repozytorium</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="482"/>
         <source>Adventure Map Allies</source>
-        <translation type="unfinished"></translation>
+        <translation>AI sojuszników mapy przygody</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="441"/>
         <source>Adventure Map Enemies</source>
-        <translation type="unfinished"></translation>
+        <translation>AI wrogów mapy przygody</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="316"/>
         <source>Windowed</source>
-        <translation type="unfinished"></translation>
+        <translation>Okno</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="321"/>
         <source>Borderless fullscreen</source>
-        <translation type="unfinished"></translation>
+        <translation>Pełny ekran (tryb okna)</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="326"/>
         <source>Exclusive fullscreen</source>
-        <translation type="unfinished"></translation>
+        <translation>Pełny ekran klasyczny</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="530"/>
         <source>Friendly AI in battles</source>
-        <translation type="unfinished"></translation>
+        <translation>AI bitewne sojuszników</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="590"/>
         <source>Framerate Limit</source>
-        <translation type="unfinished"></translation>
+        <translation>Limit FPS</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="523"/>
         <source>Refresh now</source>
-        <translation type="unfinished"></translation>
+        <translation>Odśwież</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="234"/>
         <source>Default repository</source>
-        <translation type="unfinished"></translation>
+        <translation>Domyślne repozytorium</translation>
     </message>
     <message>
         <source>Update now</source>
@@ -527,7 +527,13 @@ Windowed - game will run inside a window that covers part of your screen
 Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen.
 
 Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution.</source>
-        <translation type="unfinished"></translation>
+        <translation>Wybierz tryb wyświetlania dla gry
+
+Okno - gra będzie funkcjonować w oknie przysłaniającym część ekranu
+
+Pełny ekran w trybie okna - gra uruchomi się w oknie przysłaniającym cały ekran, w obecnej rozdzielczości twojego ekranu.
+
+Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybranej przez ciebie rozdzielczości ekranu.</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="290"/>
@@ -654,12 +660,12 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/>
         <source>Select your language</source>
-        <translation type="unfinished">Wybierz język</translation>
+        <translation>Wybierz język</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="177"/>
         <source>Have a question? Found a bug? Want to help? Join us!</source>
-        <translation type="unfinished">Masz pytanie? Znalazłeś błąd? Chcesz pomóc? Dołącz do nas!</translation>
+        <translation>Masz pytanie? Znalazłeś błąd? Chcesz pomóc? Dołącz do nas!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="186"/>
@@ -670,7 +676,7 @@ Before you can start playing, there are a few more steps that need to be complet
 Please keep in mind that in order to use VCMI you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death.
 
 Heroes® of Might and Magic® III HD is currently not supported!</source>
-        <translation type="unfinished">Dziękujemy za zainstalowanie VCMI.
+        <translation>Dziękujemy za zainstalowanie VCMI.
 
 Jest jeszcze kilka kroków, które trzeba wykonać żeby móc zagrać.
 
@@ -681,22 +687,22 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="257"/>
         <source>Locate Heroes III data files</source>
-        <translation type="unfinished">Znajdź pliki Heroes III</translation>
+        <translation>Znajdź pliki Heroes III</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
         <source>If you don&apos;t have a copy of Heroes III installed, you can use our automatic installation tool &apos;vcmibuilder&apos;, which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source>
-        <translation type="unfinished">Jeśli nie masz zainstalowanej kopii Heroes III istnieje możliwość użycia naszego automatycznego narzędzia instalacyjnego &apos;vcmibuilder&apos; by wyodrębnić dane z instalatora GoG.com. Odwiedź nasze wiki po szczegółowe instrukcje.</translation>
+        <translation>Jeśli nie masz zainstalowanej kopii Heroes III istnieje możliwość użycia naszego automatycznego narzędzia instalacyjnego &apos;vcmibuilder&apos; by wyodrębnić dane z instalatora GoG.com. Odwiedź nasze wiki po szczegółowe instrukcje.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
         <source>To run VCMI, Heroes III data files need to be present in one of the specified locations. Please copy the Heroes III data to one of these directories.</source>
-        <translation type="unfinished">VCMI wymaga plików Heroes III w jednej z wymienionych wyżej lokalizacji. Proszę, skopiuj pliki Heroes III do jednego z tych katalogów.</translation>
+        <translation>VCMI wymaga plików Heroes III w jednej z wymienionych wyżej lokalizacji. Proszę, skopiuj pliki Heroes III do jednego z tych katalogów.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="397"/>
         <source>Alternatively, you can provide the directory where Heroes III data is installed and VCMI will copy the existing data automatically.</source>
-        <translation type="unfinished">Możesz też wybrać folder z zainstalowanym Heroes III i VCMI automatycznie skopiuje istniejące dane.</translation>
+        <translation>Możesz też wybrać folder z zainstalowanym Heroes III i VCMI automatycznie skopiuje istniejące dane.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="426"/>
@@ -706,32 +712,32 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="466"/>
         <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
-        <translation type="unfinished">Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation>
+        <translation>Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
         <source>Install a translation of Heroes III in your preferred language</source>
-        <translation type="unfinished">Zainstaluj tłumaczenie Heroes III dla twojego języka</translation>
+        <translation>Zainstaluj tłumaczenie Heroes III dla twojego preferowanego języka</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="756"/>
         <source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source>
-        <translation type="unfinished">Opcjonalnie możesz zainstalować dodatkowe modyfikacje teraz lub później</translation>
+        <translation>Opcjonalnie możesz zainstalować dodatkowe modyfikacje teraz lub później</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
         <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
-        <translation type="unfinished">Zapinstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600</translation>
+        <translation>Zainstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
         <source>Install compatible version of &quot;Horn of the Abyss&quot;, a fan-made Heroes III expansion ported by the VCMI team</source>
-        <translation type="unfinished">Zainstaluj kompatybilną wersję fanowskiego dodatku Horn of the Abyss odtworzoną przez zespół VCMI</translation>
+        <translation>Zainstaluj kompatybilną wersję fanowskiego dodatku Horn of the Abyss odtworzoną przez zespół VCMI</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="804"/>
         <source>Install compatible version of &quot;In The Wake of Gods&quot;, a fan-made Heroes III expansion</source>
-        <translation type="unfinished">Zainstaluj kompatybilną wersję fanowskiego dodatku &quot;In The Wake Of Gods&quot;</translation>
+        <translation>Zainstaluj kompatybilną wersję fanowskiego dodatku &quot;In The Wake Of Gods&quot;</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="851"/>
@@ -839,12 +845,12 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../languages.cpp" line="24"/>
         <source>Chinese</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="25"/>
         <source>English</source>
-        <translation type="unfinished">English (Angielski)</translation>
+        <translation type="unfinished"></translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="26"/>
@@ -988,7 +994,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="76"/>
         <source>Players in lobby</source>
-        <translation type="unfinished">Ludzie w lobby</translation>
+        <translation>Gracze w lobby</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="159"/>
@@ -1069,7 +1075,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../mainwindow_moc.ui" line="207"/>
         <source>Help</source>
-        <translation type="unfinished"></translation>
+        <translation>Pomoc</translation>
     </message>
     <message>
         <location filename="../mainwindow_moc.ui" line="276"/>
@@ -1097,7 +1103,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../updatedialog_moc.ui" line="71"/>
         <source>You have the latest version</source>
-        <translation type="unfinished">Posiadasz obecnie aktualną wersję</translation>
+        <translation>Masz obecnie najnowszą wersję</translation>
     </message>
     <message>
         <location filename="../updatedialog_moc.ui" line="94"/>
@@ -1107,7 +1113,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../updatedialog_moc.ui" line="101"/>
         <source>Check for updates on startup</source>
-        <translation type="unfinished">Sprawdź aktualizacje przy uruchomieniu</translation>
+        <translation>Sprawdź aktualizacje przy uruchomieniu</translation>
     </message>
 </context>
 </TS>

+ 36 - 23
lib/ArtifactUtils.cpp

@@ -12,6 +12,7 @@
 
 #include "CArtHandler.h"
 #include "GameSettings.h"
+#include "spells/CSpellHandler.h"
 
 #include "mapping/CMap.h"
 #include "mapObjects/CGHeroInstance.h"
@@ -21,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtAnyPosition(const CArtifactSet * target, const ArtifactID & aid)
 {
 	const auto * art = aid.toArtifact();
-	for(const auto & slot : art->possibleSlots.at(target->bearerType()))
+	for(const auto & slot : art->getPossibleSlots().at(target->bearerType()))
 	{
 		if(art->canBePutAt(target, slot))
 			return slot;
@@ -119,15 +120,15 @@ DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities(
 {
 	std::vector<const CArtifact*> arts;
 	const auto * art = aid.toArtifact();
-	if(art->canBeDisassembled())
+	if(art->isCombined())
 		return arts;
 
-	for(const auto artifact : art->constituentOf)
+	for(const auto artifact : art->getPartOf())
 	{
-		assert(artifact->constituents);
+		assert(artifact->isCombined());
 		bool possible = true;
 
-		for(const auto constituent : *artifact->constituents) //check if all constituents are available
+		for(const auto constituent : artifact->getConstituents()) //check if all constituents are available
 		{
 			if(equipped)
 			{
@@ -165,24 +166,23 @@ DLL_LINKAGE CArtifactInstance * ArtifactUtils::createScroll(const SpellID & sid)
 
 DLL_LINKAGE CArtifactInstance * ArtifactUtils::createNewArtifactInstance(CArtifact * art)
 {
-	if(art->canBeDisassembled())
+	assert(art);
+
+	auto * artInst = new CArtifactInstance(art);
+	if(art->isCombined())
 	{
-		auto * ret = new CCombinedArtifactInstance(art);
-		ret->createConstituents();
-		return ret;
+		assert(art->isCombined());
+		for(const auto & part : art->getConstituents())
+			artInst->addPart(ArtifactUtils::createNewArtifactInstance(part), ArtifactPosition::PRE_FIRST);
 	}
-	else
+	if(art->isGrowing())
 	{
-		auto * ret = new CArtifactInstance(art);
-		if(dynamic_cast<CGrowingArtifact*>(art))
-		{
-			auto bonus = std::make_shared<Bonus>();
-			bonus->type = BonusType::LEVEL_COUNTER;
-			bonus->val = 0;
-			ret->addNewBonus(bonus);
-		}
-		return ret;
+		auto bonus = std::make_shared<Bonus>();
+		bonus->type = BonusType::LEVEL_COUNTER;
+		bonus->val = 0;
+		artInst->addNewBonus(bonus);
 	}
+	return artInst;
 }
 
 DLL_LINKAGE CArtifactInstance * ArtifactUtils::createNewArtifactInstance(const ArtifactID & aid)
@@ -209,15 +209,28 @@ DLL_LINKAGE CArtifactInstance * ArtifactUtils::createArtifact(CMap * map, const
 		art = new CArtifactInstance(); // random, empty
 	}
 	map->addNewArtifactInstance(art);
-	if(art->artType && art->canBeDisassembled())
+	if(art->artType && art->isCombined())
 	{
-		auto * combined = dynamic_cast<CCombinedArtifactInstance*>(art);
-		for(CCombinedArtifactInstance::ConstituentInfo & ci : combined->constituentsInfo)
+		for(auto & part : art->getPartsInfo())
 		{
-			map->addNewArtifactInstance(ci.art);
+			map->addNewArtifactInstance(part.art);
 		}
 	}
 	return art;
 }
 
+DLL_LINKAGE void ArtifactUtils::insertScrrollSpellName(std::string & description, const SpellID & sid)
+{
+	// We expect scroll description to be like this: This scroll contains the [spell name] spell which is added
+	// into spell book for as long as hero carries the scroll. So we want to replace text in [...] with a spell name.
+	// However other language versions don't have name placeholder at all, so we have to be careful
+	auto nameStart = description.find_first_of('[');
+	auto nameEnd = description.find_first_of(']', nameStart);
+	if(sid.getNum() >= 0)
+	{
+		if(nameStart != std::string::npos && nameEnd != std::string::npos)
+			description = description.replace(nameStart, nameEnd - nameStart + 1, sid.toSpell(VLC->spells())->getNameTranslated());
+	}
+}
+
 VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/ArtifactUtils.h

@@ -41,6 +41,7 @@ namespace ArtifactUtils
 	DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(CArtifact * art);
 	DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(const ArtifactID & aid);
 	DLL_LINKAGE CArtifactInstance * createArtifact(CMap * map, const ArtifactID & aid, int spellID = -1);
+	DLL_LINKAGE void insertScrrollSpellName(std::string & description, const SpellID & sid);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 88 - 249
lib/CArtHandler.cpp

@@ -15,9 +15,7 @@
 #include "CGeneralTextHandler.h"
 #include "CModHandler.h"
 #include "GameSettings.h"
-#include "spells/CSpellHandler.h"
 #include "mapObjects/MapObjects.h"
-#include "NetPacksBase.h"
 #include "StringConstants.h"
 
 #include "mapObjectConstructors/AObjectTypeHandler.h"
@@ -48,6 +46,51 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+bool CCombinedArtifact::isCombined() const
+{
+	return !(constituents.empty());
+}
+
+const std::vector<CArtifact*> & CCombinedArtifact::getConstituents() const
+{
+	return constituents;
+}
+
+const std::vector<CArtifact*> & CCombinedArtifact::getPartOf() const
+{
+	return partOf;
+}
+
+bool CScrollArtifact::isScroll() const
+{
+	return static_cast<const CArtifact*>(this)->getId() == ArtifactID::SPELL_SCROLL;
+}
+
+bool CGrowingArtifact::isGrowing() const
+{
+	return !bonusesPerLevel.empty() || !thresholdBonuses.empty();
+}
+
+std::vector <std::pair<ui16, Bonus>> & CGrowingArtifact::getBonusesPerLevel()
+{
+	return bonusesPerLevel;
+}
+
+const std::vector <std::pair<ui16, Bonus>> & CGrowingArtifact::getBonusesPerLevel() const
+{
+	return bonusesPerLevel;
+}
+
+std::vector <std::pair<ui16, Bonus>> & CGrowingArtifact::getThresholdBonuses()
+{
+	return thresholdBonuses;
+}
+
+const std::vector <std::pair<ui16, Bonus>> & CGrowingArtifact::getThresholdBonuses() const
+{
+	return thresholdBonuses;
+}
+
 int32_t CArtifact::getIndex() const
 {
 	return id.toEnum();
@@ -136,11 +179,6 @@ bool CArtifact::isTradable() const
 	}
 }
 
-bool CArtifact::canBeDisassembled() const
-{
-	return !(constituents == nullptr);
-}
-
 bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) const
 {
 	auto simpleArtCanBePutAt = [this](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool
@@ -160,7 +198,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
 
 	auto artCanBePutAt = [this, simpleArtCanBePutAt](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool
 	{
-		if(canBeDisassembled())
+		if(isCombined())
 		{
 			if(!simpleArtCanBePutAt(artSet, slot, assumeDestRemoved))
 				return false;
@@ -171,8 +209,8 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
 			fittingSet.artifactsWorn = artSet->artifactsWorn;
 			if(assumeDestRemoved)
 				fittingSet.removeArtifact(slot);
-			assert(constituents);
-			for(const auto art : *constituents)
+
+			for(const auto art : constituents)
 			{
 				auto possibleSlot = ArtifactUtils::getArtAnyPosition(&fittingSet, art->getId());
 				if(ArtifactUtils::isSlotEquipment(possibleSlot))
@@ -215,6 +253,8 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
 }
 
 CArtifact::CArtifact()
+	: iconIndex(ArtifactID::NONE),
+	price(0)
 {
 	setNodeType(ARTIFACT);
 	possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty
@@ -259,38 +299,21 @@ void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
 	CBonusSystemNode::addNewBonus(b);
 }
 
-void CArtifact::updateFrom(const JsonNode& data)
+const std::map<ArtBearer::ArtBearer, std::vector<ArtifactPosition>> & CArtifact::getPossibleSlots() const
 {
-	//TODO:CArtifact::updateFrom
+	return possibleSlots;
 }
 
-void CArtifact::serializeJson(JsonSerializeFormat & handler)
+void CArtifact::updateFrom(const JsonNode& data)
 {
-
+	//TODO:CArtifact::updateFrom
 }
 
-void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
+void CArtifact::setImage(int32_t iconIndex, std::string image, std::string large)
 {
-	auto b = std::make_shared<Bonus>();
-	b->type = BonusType::LEVEL_COUNTER;
-	b->val = 1;
-	b->duration = BonusDuration::COMMANDER_KILLED;
-	art->accumulateBonus(b);
-
-	for(const auto & bonus : bonusesPerLevel)
-	{
-		if (art->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0) //every n levels
-		{
-			art->accumulateBonus(std::make_shared<Bonus>(bonus.second));
-		}
-	}
-	for(const auto & bonus : thresholdBonuses)
-	{
-		if (art->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first) //every n levels
-		{
-			art->addNewBonus(std::make_shared<Bonus>(bonus.second));
-		}
-	}
+	this->iconIndex = iconIndex;
+	this->image = image;
+	this->large = large;
 }
 
 CArtHandler::~CArtHandler() = default;
@@ -376,17 +399,19 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 	assert(identifier.find(':') == std::string::npos);
 	assert(!scope.empty());
 
-	CArtifact * art = nullptr;
-
-	if(!VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS) || node["growing"].isNull())
+	CArtifact * art = new CArtifact();
+	if(!node["growing"].isNull())
 	{
-		art = new CArtifact();
-	}
-	else
-	{
-		auto * growing = new CGrowingArtifact();
-		loadGrowingArt(growing, node);
-		art = growing;
+		for(auto bonus : node["growing"]["bonusesPerLevel"].Vector())
+		{
+			art->bonusesPerLevel.emplace_back(static_cast<ui16>(bonus["level"].Float()), Bonus());
+			JsonUtils::parseBonus(bonus["bonus"], &art->bonusesPerLevel.back().second);
+		}
+		for(auto bonus : node["growing"]["thresholdBonuses"].Vector())
+		{
+			art->thresholdBonuses.emplace_back(static_cast<ui16>(bonus["level"].Float()), Bonus());
+			JsonUtils::parseBonus(bonus["bonus"], &art->thresholdBonuses.back().second);
+		}
 	}
 	art->id = ArtifactID(index);
 	art->identifier = identifier;
@@ -571,34 +596,19 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node)
 {
 	if (!node["components"].isNull())
 	{
-		art->constituents = std::make_unique<std::vector<CArtifact *>>();
 		for(const auto & component : node["components"].Vector())
 		{
 			VLC->modh->identifiers.requestIdentifier("artifact", component, [=](si32 id)
 			{
 				// when this code is called both combinational art as well as component are loaded
 				// so it is safe to access any of them
-				art->constituents->push_back(objects[id]);
-				objects[id]->constituentOf.push_back(art);
+				art->constituents.push_back(objects[id]);
+				objects[id]->partOf.push_back(art);
 			});
 		}
 	}
 }
 
-void CArtHandler::loadGrowingArt(CGrowingArtifact * art, const JsonNode & node) const
-{
-	for (auto b : node["growing"]["bonusesPerLevel"].Vector())
-	{
-		art->bonusesPerLevel.emplace_back(static_cast<ui16>(b["level"].Float()), Bonus());
-		JsonUtils::parseBonus(b["bonus"], &art->bonusesPerLevel.back().second);
-	}
-	for (auto b : node["growing"]["thresholdBonuses"].Vector())
-	{
-		art->thresholdBonuses.emplace_back(static_cast<ui16>(b["level"].Float()), Bonus());
-		JsonUtils::parseBonus(b["bonus"], &art->thresholdBonuses.back().second);
-	}
-}
-
 ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
 {
 	auto getAllowedArts = [&](std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, CArtifact::EartClass flag)
@@ -683,7 +693,7 @@ bool CArtHandler::legalArtifact(const ArtifactID & id)
 	auto art = objects[id];
 	//assert ( (!art->constituents) || art->constituents->size() ); //artifacts is not combined or has some components
 
-	if(art->constituents)
+	if(art->isCombined())
 		return false; //no combo artifacts spawning
 
 	if(art->aClass < CArtifact::ART_TREASURE || art->aClass > CArtifact::ART_RELIC)
@@ -787,179 +797,11 @@ void CArtHandler::afterLoadFinalization()
 	CBonusSystemNode::treeHasChanged();
 }
 
-CArtifactInstance::CArtifactInstance()
-{
-	init();
-}
-
-CArtifactInstance::CArtifactInstance( CArtifact *Art)
-{
-	init();
-	setType(Art);
-}
-
-void CArtifactInstance::setType( CArtifact *Art )
-{
-	artType = Art;
-	attachTo(*Art);
-}
-
-std::string CArtifactInstance::nodeName() const
-{
-	return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type";
-}
-
-void CArtifactInstance::init()
-{
-	id = ArtifactInstanceID();
-	id = static_cast<ArtifactInstanceID>(ArtifactID::NONE); //to be randomized
-	setNodeType(ARTIFACT_INSTANCE);
-}
-
-std::string CArtifactInstance::getDescription() const
-{
-	std::string text = artType->getDescriptionTranslated();
-	if(artType->getId() == ArtifactID::SPELL_SCROLL)
-	{
-		// we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll.
-		// so we want to replace text in [...] with a spell name
-		// however other language versions don't have name placeholder at all, so we have to be careful
-		SpellID spellID = getScrollSpellID();
-		size_t nameStart = text.find_first_of('[');
-		size_t nameEnd = text.find_first_of(']', nameStart);
-		if(spellID.getNum() >= 0)
-		{
-			if(nameStart != std::string::npos  &&  nameEnd != std::string::npos)
-				text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getNameTranslated());
-		}
-	}
-	return text;
-}
-
-ArtifactID CArtifactInstance::getTypeId() const
-{
-	return artType->getId();
-}
-
-bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved) const
-{
-	return artType->canBePutAt(al.getHolderArtSet(), al.slot, assumeDestRemoved);
-}
-
-void CArtifactInstance::putAt(const ArtifactLocation & al)
-{
-	al.getHolderArtSet()->putArtifact(al.slot, this);
-}
-
-void CArtifactInstance::removeFrom(const ArtifactLocation & al)
-{
-	al.getHolderArtSet()->removeArtifact(al.slot);
-}
-
-bool CArtifactInstance::canBeDisassembled() const
-{
-	return artType->canBeDisassembled();
-}
-
-void CArtifactInstance::move(const ArtifactLocation & src, const ArtifactLocation & dst)
-{
-	removeFrom(src);
-	putAt(dst);
-}
-
-void CArtifactInstance::deserializationFix()
-{
-	setType(artType);
-}
-
-SpellID CArtifactInstance::getScrollSpellID() const
-{
-	const auto b = getBonusLocalFirst(Selector::type()(BonusType::SPELL));
-	if(!b)
-	{
-		logMod->warn("Warning: %s doesn't bear any spell!", nodeName());
-		return SpellID::NONE;
-	}
-	return SpellID(b->subtype);
-}
-
-bool CArtifactInstance::isPart(const CArtifactInstance *supposedPart) const
-{
-	return supposedPart == this;
-}
-
-CCombinedArtifactInstance::CCombinedArtifactInstance(CArtifact *Art)
-	: CArtifactInstance(Art) //TODO: seems unused, but need to be written
-{
-}
-
-void CCombinedArtifactInstance::createConstituents()
-{
-	assert(artType);
-	assert(artType->constituents);
-
-	for(const CArtifact * art : *artType->constituents)
-	{
-		addAsConstituent(ArtifactUtils::createNewArtifactInstance(art->getId()), ArtifactPosition::PRE_FIRST);
-	}
-}
-
-void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance * art, const ArtifactPosition & slot)
-{
-	assert(vstd::contains_if(*artType->constituents, [=](const CArtifact * constituent){
-		return constituent->getId() == art->artType->getId();
-	}));
-	assert(art->getParentNodes().size() == 1  &&  art->getParentNodes().front() == art->artType);
-	constituentsInfo.emplace_back(art, slot);
-	attachTo(*art);
-}
-
-void CCombinedArtifactInstance::removeFrom(const ArtifactLocation & al)
-{
-	CArtifactInstance::removeFrom(al);
-	for(auto & part : constituentsInfo)
-	{
-		if(part.slot != ArtifactPosition::PRE_FIRST)
-			part.slot = ArtifactPosition::PRE_FIRST;
-	}
-}
-
-void CCombinedArtifactInstance::deserializationFix()
-{
-	for(ConstituentInfo &ci : constituentsInfo)
-		attachTo(*ci.art);
-}
-
-bool CCombinedArtifactInstance::isPart(const CArtifactInstance *supposedPart) const
-{
-	bool me = CArtifactInstance::isPart(supposedPart);
-	if(me)
-		return true;
-
-	//check for constituents
-	for(const ConstituentInfo &constituent : constituentsInfo)
-		if(constituent.art == supposedPart)
-			return true;
-
-	return false;
-}
-
-CCombinedArtifactInstance::ConstituentInfo::ConstituentInfo(CArtifactInstance * Art, const ArtifactPosition & Slot):
-	art(Art),
-	slot(Slot)
-{
-}
-
-bool CCombinedArtifactInstance::ConstituentInfo::operator==(const ConstituentInfo &rhs) const
-{
-	return art == rhs.art && slot == rhs.slot;
-}
-
 CArtifactSet::~CArtifactSet() = default;
 
 const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const
 {
-	if(const ArtSlotInfo *si = getSlot(pos))
+	if(const ArtSlotInfo * si = getSlot(pos))
 	{
 		if(si->artifact && (!excludeLocked || !si->locked))
 			return si->artifact;
@@ -1033,11 +875,11 @@ ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance *art) const
 const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanceID & artInstId) const
 {
 	for(auto i : artifactsWorn)
-		if(i.second.artifact->id == artInstId)
+		if(i.second.artifact->getId() == artInstId)
 			return i.second.artifact;
 
 	for(auto i : artifactsInBackpack)
-		if(i.artifact->id == artInstId)
+		if(i.artifact->getId() == artInstId)
 			return i.artifact;
 
 	return nullptr;
@@ -1047,7 +889,7 @@ const ArtifactPosition CArtifactSet::getSlotByInstance(const CArtifactInstance *
 {
 	if(artInst)
 	{
-		for(auto & slot : artInst->artType->possibleSlots.at(bearerType()))
+		for(const auto & slot : artInst->artType->getPossibleSlots().at(bearerType()))
 			if(getArt(slot) == artInst)
 				return slot;
 
@@ -1087,19 +929,18 @@ unsigned CArtifactSet::getArtPosCount(const ArtifactID & aid, bool onlyWorn, boo
 void CArtifactSet::putArtifact(ArtifactPosition slot, CArtifactInstance * art)
 {
 	setNewArtSlot(slot, art, false);
-	if(art->artType->canBeDisassembled() && ArtifactUtils::isSlotEquipment(slot))
+	if(art->artType->isCombined() && ArtifactUtils::isSlotEquipment(slot))
 	{
 		const CArtifactInstance * mainPart = nullptr;
-		auto & parts = dynamic_cast<CCombinedArtifactInstance*>(art)->constituentsInfo;
-		for(const auto & part : parts)
-			if(vstd::contains(part.art->artType->possibleSlots.at(bearerType()), slot)
+		for(const auto & part : art->getPartsInfo())
+			if(vstd::contains(part.art->artType->getPossibleSlots().at(bearerType()), slot)
 				&& (part.slot == ArtifactPosition::PRE_FIRST))
 			{
 				mainPart = part.art;
 				break;
 			}
 
-		for(auto & part : parts)
+		for(auto & part : art->getPartsInfo())
 		{
 			if(part.art != mainPart)
 			{
@@ -1118,10 +959,9 @@ void CArtifactSet::removeArtifact(ArtifactPosition slot)
 	auto art = getArt(slot, false);
 	if(art)
 	{
-		if(art->canBeDisassembled())
+		if(art->isCombined())
 		{
-			auto combinedArt = dynamic_cast<CCombinedArtifactInstance*>(art);
-			for(auto & part : combinedArt->constituentsInfo)
+			for(auto & part : art->getPartsInfo())
 			{
 				if(getArt(part.slot, false))
 					eraseArtSlot(part.slot);
@@ -1131,19 +971,18 @@ void CArtifactSet::removeArtifact(ArtifactPosition slot)
 	}
 }
 
-std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> CArtifactSet::searchForConstituent(const ArtifactID & aid) const
+std::pair<const CArtifactInstance *, const CArtifactInstance *> CArtifactSet::searchForConstituent(const ArtifactID & aid) const
 {
 	for(const auto & slot : artifactsInBackpack)
 	{
 		auto art = slot.artifact;
-		if(art->canBeDisassembled())
+		if(art->isCombined())
 		{
-			auto * ass = dynamic_cast<CCombinedArtifactInstance *>(art.get());
-			for(auto& ci : ass->constituentsInfo)
+			for(auto & ci : art->getPartsInfo())
 			{
 				if(ci.art->getTypeId() == aid)
 				{
-					return {ass, ci.art};
+					return {art, ci.art};
 				}
 			}
 		}
@@ -1156,7 +995,7 @@ const CArtifactInstance * CArtifactSet::getHiddenArt(const ArtifactID & aid) con
 	return searchForConstituent(aid).second;
 }
 
-const CCombinedArtifactInstance * CArtifactSet::getAssemblyByConstituent(const ArtifactID & aid) const
+const CArtifactInstance * CArtifactSet::getAssemblyByConstituent(const ArtifactID & aid) const
 {
 	return searchForConstituent(aid).first;
 }

+ 68 - 116
lib/CArtHandler.h

@@ -20,9 +20,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CArtHandler;
-class CArtifact;
 class CGHeroInstance;
-struct ArtifactLocation;
 class CArtifactSet;
 class CArtifactInstance;
 class CRandomGenerator;
@@ -44,26 +42,75 @@ namespace ArtBearer
 	};
 }
 
-class DLL_LINKAGE CArtifact : public Artifact, public CBonusSystemNode //container for artifacts
+class DLL_LINKAGE CCombinedArtifact
 {
-	ArtifactID id;
+protected:
+	CCombinedArtifact() = default;
+
+	std::vector<CArtifact*> constituents; // Artifacts IDs a combined artifact consists of, or nullptr.
+	std::vector<CArtifact*> partOf; // Reverse map of constituents - combined arts that include this art
+public:
+	bool isCombined() const;
+	const std::vector<CArtifact*> & getConstituents() const;
+	const std::vector<CArtifact*> & getPartOf() const;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & constituents;
+		h & partOf;
+	}
+};
+
+class DLL_LINKAGE CScrollArtifact
+{
+protected:
+	CScrollArtifact() = default;
+public:
+	bool isScroll() const;
+};
+
+class DLL_LINKAGE CGrowingArtifact
+{
+protected:
+	CGrowingArtifact() = default;
+
+	std::vector <std::pair<ui16, Bonus>> bonusesPerLevel; // Bonus given each n levels
+	std::vector <std::pair<ui16, Bonus>> thresholdBonuses; // After certain level they will be added once
+public:
+	bool isGrowing() const;
+
+	std::vector <std::pair<ui16, Bonus>> & getBonusesPerLevel();
+	const std::vector <std::pair<ui16, Bonus>> & getBonusesPerLevel() const;
+	std::vector <std::pair<ui16, Bonus>> & getThresholdBonuses();
+	const std::vector <std::pair<ui16, Bonus>> & getThresholdBonuses() const;
 
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & bonusesPerLevel;
+		h & thresholdBonuses;
+	}
+};
+
+// Container for artifacts. Not for instances.
+class DLL_LINKAGE CArtifact
+	: public Artifact, public CBonusSystemNode, public CCombinedArtifact, public CScrollArtifact, public CGrowingArtifact
+{
+	ArtifactID id;
+	std::string image;
+	std::string large; // big image for custom artifacts, used in drag & drop
+	std::string advMapDef; // used for adventure map object
 	std::string modScope;
 	std::string identifier;
+	int32_t iconIndex;
+	uint32_t price;
+	CreatureID warMachine;
+	// Bearer Type => ids of slots where artifact can be placed
+	std::map<ArtBearer::ArtBearer, std::vector<ArtifactPosition>> possibleSlots;
 
 public:
 	enum EartClass {ART_SPECIAL=1, ART_TREASURE=2, ART_MINOR=4, ART_MAJOR=8, ART_RELIC=16}; //artifact classes
 
-	std::string image;
-	std::string large; // big image for custom artifacts, used in drag & drop
-	std::string advMapDef; //used for adventure map object
-	si32 iconIndex = ArtifactID::NONE;
-	ui32 price = 0;
-	std::map<ArtBearer::ArtBearer, std::vector<ArtifactPosition> > possibleSlots; //Bearer Type => ids of slots where artifact can be placed
-	std::unique_ptr<std::vector<CArtifact *> > constituents; // Artifacts IDs a combined artifact consists of, or nullptr.
-	std::vector<CArtifact *> constituentOf; // Reverse map of constituents - combined arts that include this art
 	EartClass aClass = ART_SPECIAL;
-	CreatureID warMachine;
 
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
@@ -88,26 +135,25 @@ public:
 	int getArtClassSerial() const; //0 - treasure, 1 - minor, 2 - major, 3 - relic, 4 - spell scroll, 5 - other
 	std::string nodeName() const override;
 	void addNewBonus(const std::shared_ptr<Bonus>& b) override;
+	const std::map<ArtBearer::ArtBearer, std::vector<ArtifactPosition>> & getPossibleSlots() const;
 
-	virtual void levelUpArtifact (CArtifactInstance * art){};
-
-	virtual bool canBeDisassembled() const;
 	virtual bool canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot = ArtifactPosition::FIRST_AVAILABLE,
 		bool assumeDestRemoved = false) const;
 	void updateFrom(const JsonNode & data);
-	void serializeJson(JsonSerializeFormat & handler);
+	// Is used for testing purposes only
+	void setImage(int32_t iconIndex, std::string image, std::string large);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & static_cast<CBonusSystemNode&>(*this);
+		h & static_cast<CCombinedArtifact&>(*this);
+		h & static_cast<CGrowingArtifact&>(*this);
 		h & image;
 		h & large;
 		h & advMapDef;
 		h & iconIndex;
 		h & price;
 		h & possibleSlots;
-		h & constituents;
-		h & constituentOf;
 		h & aClass;
 		h & id;
 		h & modScope;
@@ -121,99 +167,6 @@ public:
 	friend class CArtHandler;
 };
 
-class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle
-{
-public:
-	std::vector <std::pair <ui16, Bonus> > bonusesPerLevel; //bonus given each n levels
-	std::vector <std::pair <ui16, Bonus> > thresholdBonuses; //after certain level they will be added once
-
-	void levelUpArtifact(CArtifactInstance * art) override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArtifact&>(*this);
-		h & bonusesPerLevel;
-		h & thresholdBonuses;
-	}
-};
-
-class DLL_LINKAGE CArtifactInstance : public CBonusSystemNode
-{
-protected:
-	void init();
-public:
-	CArtifactInstance(CArtifact * Art);
-	CArtifactInstance();
-
-	ConstTransitivePtr<CArtifact> artType;
-	ArtifactInstanceID id;
-
-	std::string nodeName() const override;
-	void deserializationFix();
-	void setType(CArtifact *Art);
-
-	std::string getDescription() const;
-	SpellID getScrollSpellID() const; //to be used with scrolls (and similar arts), -1 if none
-
-	ArtifactID getTypeId() const;
-	bool canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved = false) const;  //forwards to the above one
-	virtual bool canBeDisassembled() const;
-	/// Checks if this a part of this artifact: artifact instance is a part
-	/// of itself, additionally truth is returned for constituents of combined arts
-	virtual bool isPart(const CArtifactInstance *supposedPart) const;
-
-	virtual void putAt(const ArtifactLocation & al);
-	virtual void removeFrom(const ArtifactLocation & al);
-	virtual void move(const ArtifactLocation & src, const ArtifactLocation & dst);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CBonusSystemNode&>(*this);
-		h & artType;
-		h & id;
-		BONUS_TREE_DESERIALIZATION_FIX
-	}
-};
-
-class DLL_LINKAGE CCombinedArtifactInstance : public CArtifactInstance
-{
-public:
-	CCombinedArtifactInstance(CArtifact * Art);
-	struct ConstituentInfo
-	{
-		ConstTransitivePtr<CArtifactInstance> art;
-		ArtifactPosition slot;
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & art;
-			h & slot;
-		}
-
-		bool operator==(const ConstituentInfo &rhs) const;
-		ConstituentInfo(CArtifactInstance * art = nullptr, const ArtifactPosition & slot = ArtifactPosition::PRE_FIRST);
-	};
-
-	std::vector<ConstituentInfo> constituentsInfo;
-
-	bool isPart(const CArtifactInstance *supposedPart) const override;
-	void createConstituents();
-	void addAsConstituent(CArtifactInstance * art, const ArtifactPosition & slot);
-	void removeFrom(const ArtifactLocation & al) override;
-
-	CCombinedArtifactInstance() = default;
-
-	void deserializationFix();
-
-	friend class CArtifactInstance;
-	friend struct AssembledArtifact;
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArtifactInstance&>(*this);
-		h & constituentsInfo;
-		BONUS_TREE_DESERIALIZATION_FIX
-	}
-};
-
 class DLL_LINKAGE CArtHandler : public CHandlerBase<ArtifactID, Artifact, CArtifact, ArtifactService>
 {
 public:
@@ -269,7 +222,6 @@ private:
 	void loadClass(CArtifact * art, const JsonNode & node) const;
 	void loadType(CArtifact * art, const JsonNode & node) const;
 	void loadComponents(CArtifact * art, const JsonNode & node);
-	void loadGrowingArt(CGrowingArtifact * art, const JsonNode & node) const;
 
 	void erasePickedArt(const ArtifactID & id);
 };
@@ -313,7 +265,7 @@ public:
 	const ArtifactPosition getSlotByInstance(const CArtifactInstance * artInst) const;
 	/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
 	const CArtifactInstance * getHiddenArt(const ArtifactID & aid) const;
-	const CCombinedArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const;
+	const CArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const;
 	/// Checks if hero possess artifact of given id (either in backack or worn)
 	bool hasArt(const ArtifactID & aid, bool onlyWorn = false, bool searchBackpackAssemblies = false, bool allowLocked = true) const;
 	bool hasArtBackpack(const ArtifactID & aid) const;
@@ -335,7 +287,7 @@ public:
 
 	void serializeJsonArtifacts(JsonSerializeFormat & handler, const std::string & fieldName, CMap * map);
 protected:
-	std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(const ArtifactID & aid) const;
+	std::pair<const CArtifactInstance *, const CArtifactInstance *> searchForConstituent(const ArtifactID & aid) const;
 
 private:
 	void serializeJsonHero(JsonSerializeFormat & handler, CMap * map);

+ 192 - 0
lib/CArtifactInstance.cpp

@@ -0,0 +1,192 @@
+/*
+ * CArtifactInstance.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 "CArtifactInstance.h"
+
+#include "ArtifactUtils.h"
+#include "CArtHandler.h"
+#include "NetPacksBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void CCombinedArtifactInstance::addPart(CArtifactInstance * art, const ArtifactPosition & slot)
+{
+	auto artInst = static_cast<CArtifactInstance*>(this);
+	assert(vstd::contains_if(artInst->artType->getConstituents(),
+		[=](const CArtifact * partType)
+		{
+			return partType->getId() == art->getTypeId();
+		}));
+	assert(art->getParentNodes().size() == 1  &&  art->getParentNodes().front() == art->artType);
+	partsInfo.emplace_back(art, slot);
+	artInst->attachTo(*art);
+}
+
+bool CCombinedArtifactInstance::isPart(const CArtifactInstance * supposedPart) const
+{
+	if(supposedPart == this)
+		return true;
+
+	for(const PartInfo & constituent : partsInfo)
+	{
+		if(constituent.art == supposedPart)
+			return true;
+	}
+
+	return false;
+}
+
+std::vector<CCombinedArtifactInstance::PartInfo> & CCombinedArtifactInstance::getPartsInfo()
+{
+	// TODO romove this func. encapsulation violation
+	return partsInfo;
+}
+
+const std::vector<CCombinedArtifactInstance::PartInfo> & CCombinedArtifactInstance::getPartsInfo() const
+{
+	return partsInfo;
+}
+
+SpellID CScrollArtifactInstance::getScrollSpellID() const
+{
+	auto artInst = static_cast<const CArtifactInstance*>(this);
+	const auto bonus = artInst->getBonusLocalFirst(Selector::type()(BonusType::SPELL));
+	if(!bonus)
+	{
+		logMod->warn("Warning: %s doesn't bear any spell!", artInst->nodeName());
+		return SpellID::NONE;
+	}
+	return SpellID(bonus->subtype);
+}
+
+void CGrowingArtifactInstance::growingUp()
+{
+	auto artInst = static_cast<CArtifactInstance*>(this);
+	
+	if(artInst->artType->isGrowing())
+	{
+
+		auto bonus = std::make_shared<Bonus>();
+		bonus->type = BonusType::LEVEL_COUNTER;
+		bonus->val = 1;
+		bonus->duration = BonusDuration::COMMANDER_KILLED;
+		artInst->accumulateBonus(bonus);
+
+		for(const auto & bonus : artInst->artType->getBonusesPerLevel())
+		{
+			// Every n levels
+			if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0)
+			{
+				artInst->accumulateBonus(std::make_shared<Bonus>(bonus.second));
+			}
+		}
+		for(const auto & bonus : artInst->artType->getThresholdBonuses())
+		{
+			// At n level
+			if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first)
+			{
+				artInst->addNewBonus(std::make_shared<Bonus>(bonus.second));
+			}
+		}
+	}
+}
+
+void CArtifactInstance::init()
+{
+	// Artifact to be randomized
+	id = static_cast<ArtifactInstanceID>(ArtifactID::NONE);
+	setNodeType(ARTIFACT_INSTANCE);
+}
+
+CArtifactInstance::CArtifactInstance(CArtifact * art)
+{
+	init();
+	setType(art);
+}
+
+CArtifactInstance::CArtifactInstance()
+{
+	init();
+}
+
+void CArtifactInstance::setType(CArtifact * art)
+{
+	artType = art;
+	attachTo(*art);
+}
+
+std::string CArtifactInstance::nodeName() const
+{
+	return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type";
+}
+
+std::string CArtifactInstance::getDescription() const
+{
+	std::string text = artType->getDescriptionTranslated();
+	if(artType->isScroll())
+		ArtifactUtils::insertScrrollSpellName(text, getScrollSpellID());
+	return text;
+}
+
+ArtifactID CArtifactInstance::getTypeId() const
+{
+	return artType->getId();
+}
+
+ArtifactInstanceID CArtifactInstance::getId() const
+{
+	return id;
+}
+
+void CArtifactInstance::setId(ArtifactInstanceID id)
+{
+	this->id = id;
+}
+
+bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved) const
+{
+	return artType->canBePutAt(al.getHolderArtSet(), al.slot, assumeDestRemoved);
+}
+
+bool CArtifactInstance::isCombined() const
+{
+	return artType->isCombined();
+}
+
+void CArtifactInstance::putAt(const ArtifactLocation & al)
+{
+	al.getHolderArtSet()->putArtifact(al.slot, this);
+}
+
+void CArtifactInstance::removeFrom(const ArtifactLocation & al)
+{
+	al.getHolderArtSet()->removeArtifact(al.slot);
+	for(auto & part : partsInfo)
+	{
+		if(part.slot != ArtifactPosition::PRE_FIRST)
+			part.slot = ArtifactPosition::PRE_FIRST;
+	}
+}
+
+void CArtifactInstance::move(const ArtifactLocation & src, const ArtifactLocation & dst)
+{
+	removeFrom(src);
+	putAt(dst);
+}
+
+void CArtifactInstance::deserializationFix()
+{
+	setType(artType);
+	for(PartInfo & part : partsInfo)
+		attachTo(*part.art);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 102 - 0
lib/CArtifactInstance.h

@@ -0,0 +1,102 @@
+/*
+ * CArtifactInstance.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 "bonuses/CBonusSystemNode.h"
+#include "GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct ArtifactLocation;
+
+class DLL_LINKAGE CCombinedArtifactInstance
+{
+protected:
+	CCombinedArtifactInstance() = default;
+public:
+	struct PartInfo
+	{
+		ConstTransitivePtr<CArtifactInstance> art;
+		ArtifactPosition slot;
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & art;
+			h & slot;
+		}
+		PartInfo(CArtifactInstance * art = nullptr, const ArtifactPosition & slot = ArtifactPosition::PRE_FIRST)
+			: art(art), slot(slot) {};
+	};
+	void addPart(CArtifactInstance * art, const ArtifactPosition & slot);
+	// Checks if supposed part inst is part of this combined art inst
+	bool isPart(const CArtifactInstance * supposedPart) const;
+	std::vector<PartInfo> & getPartsInfo();
+	const std::vector<PartInfo> & getPartsInfo() const;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & partsInfo;
+	}
+protected:
+	std::vector<PartInfo> partsInfo;
+};
+
+class DLL_LINKAGE CScrollArtifactInstance
+{
+protected:
+	CScrollArtifactInstance() = default;
+public:
+	SpellID getScrollSpellID() const;
+};
+
+class DLL_LINKAGE CGrowingArtifactInstance
+{
+protected:
+	CGrowingArtifactInstance() = default;
+public:
+	void growingUp();
+};
+
+class DLL_LINKAGE CArtifactInstance
+	: public CBonusSystemNode, public CCombinedArtifactInstance, public CScrollArtifactInstance, public CGrowingArtifactInstance
+{
+protected:
+	void init();
+
+	ArtifactInstanceID id;
+public:
+	ConstTransitivePtr<CArtifact> artType;
+
+	CArtifactInstance(CArtifact * art);
+	CArtifactInstance();
+	void setType(CArtifact * art);
+	std::string nodeName() const override;
+	std::string getDescription() const;
+	ArtifactID getTypeId() const;
+	ArtifactInstanceID getId() const;
+	void setId(ArtifactInstanceID id);
+
+	bool canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved = false) const;
+	bool isCombined() const;
+	void putAt(const ArtifactLocation & al);
+	void removeFrom(const ArtifactLocation & al);
+	void move(const ArtifactLocation & src, const ArtifactLocation & dst);
+	
+	void deserializationFix();
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<CBonusSystemNode&>(*this);
+		h & static_cast<CCombinedArtifactInstance&>(*this);
+		h & artType;
+		h & id;
+		BONUS_TREE_DESERIALIZATION_FIX
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/CCreatureSet.h

@@ -13,6 +13,7 @@
 #include "bonuses/CBonusSystemNode.h"
 #include "GameConstants.h"
 #include "CArtHandler.h"
+#include "CArtifactInstance.h"
 #include "CCreatureHandler.h"
 
 #include <vcmi/Entity.h>

+ 2 - 2
lib/JsonRandom.cpp

@@ -206,7 +206,7 @@ namespace JsonRandom
 		{
 			CArtifact * art = VLC->arth->objects[artID];
 
-			if(!vstd::iswithin(art->price, minValue, maxValue))
+			if(!vstd::iswithin(art->getPrice(), minValue, maxValue))
 				return false;
 
 			if(!allowedClasses.empty() && !allowedClasses.count(art->aClass))
@@ -217,7 +217,7 @@ namespace JsonRandom
 
 			if(!allowedPositions.empty())
 			{
-				for(const auto & pos : art->possibleSlots[ArtBearer::HERO])
+				for(const auto & pos : art->getPossibleSlots().at(ArtBearer::HERO))
 				{
 					if(allowedPositions.count(pos))
 						return true;

+ 22 - 12
lib/NetPacks.h

@@ -1491,20 +1491,30 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
 {
 	void applyGs(CGameState * gs) const;
-	
-	CGHeroInstance * hero1 = nullptr;
-	CGHeroInstance * hero2 = nullptr;
-	CArmedInstance * army1 = nullptr;
-	CArmedInstance * army2 = nullptr;
-	TExpType exp[2];
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	struct HeroBattleResults
 	{
-		h & hero1;
-		h & hero2;
-		h & army1;
-		h & army2;
-		h & exp;
+		HeroBattleResults()
+			: hero(nullptr), army(nullptr), exp(0) {}
+
+		CGHeroInstance * hero;
+		CArmedInstance * army;
+		TExpType exp;
+
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & hero;
+			h & army;
+			h & exp;
+		}
+	};
+	std::array<HeroBattleResults, 2> heroResult;
+	ui8 winnerSide;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & heroResult;
+		h & winnerSide;
 	}
 };
 

+ 42 - 38
lib/NetPacksLib.cpp

@@ -1524,12 +1524,16 @@ void NewObject::applyGs(CGameState *gs)
 void NewArtifact::applyGs(CGameState *gs)
 {
 	assert(!vstd::contains(gs->map->artInstances, art));
-	gs->map->addNewArtifactInstance(art);
-
 	assert(!art->getParentNodes().size());
+	assert(art->artType);
+
 	art->setType(art->artType);
-	if(auto * cart = dynamic_cast<CCombinedArtifactInstance *>(art.get()))
-		cart->createConstituents();
+	if(art->isCombined())
+	{
+		for(const auto & part : art->artType->getConstituents())
+			art->addPart(ArtifactUtils::createNewArtifactInstance(part), ArtifactPosition::PRE_FIRST);
+	}
+	gs->map->addNewArtifactInstance(art);
 }
 
 const CStackInstance * StackLocation::getStack()
@@ -1822,7 +1826,7 @@ void EraseArtifact::applyGs(CGameState *gs)
 		for(auto& p : aset->artifactsWorn)
 		{
 			auto art = p.second.artifact;
-			if(art->canBeDisassembled() && art->isPart(slot->artifact))
+			if(art->isCombined() && art->isPart(slot->artifact))
 			{
 				dis.al.slot = aset->getArtPos(art);
 				#ifndef NDEBUG
@@ -1930,10 +1934,10 @@ void AssembledArtifact::applyGs(CGameState *gs)
 			return art->getId() == builtArt->getId();
 		}));
 
-	auto * combinedArt = new CCombinedArtifactInstance(builtArt);
+	auto * combinedArt = new CArtifactInstance(builtArt);
 	gs->map->addNewArtifactInstance(combinedArt);
 	// Retrieve all constituents
-	for(const CArtifact * constituent : *builtArt->constituents)
+	for(const CArtifact * constituent : builtArt->getConstituents())
 	{
 		ArtifactPosition pos = combineEquipped ? artSet->getArtPos(constituent->getId(), true, false) :
 			artSet->getArtBackpackPos(constituent->getId());
@@ -1944,8 +1948,8 @@ void AssembledArtifact::applyGs(CGameState *gs)
 		constituentInstance->removeFrom(ArtifactLocation(al.artHolder, pos));
 		if(combineEquipped)
 		{
-			if(!vstd::contains(combinedArt->artType->possibleSlots[artSet->bearerType()], al.slot)
-				&& vstd::contains(combinedArt->artType->possibleSlots[artSet->bearerType()], pos))
+			if(!vstd::contains(combinedArt->artType->getPossibleSlots().at(artSet->bearerType()), al.slot)
+				&& vstd::contains(combinedArt->artType->getPossibleSlots().at(artSet->bearerType()), pos))
 				al.slot = pos;
 			if(al.slot == pos)
 				pos = ArtifactPosition::PRE_FIRST;
@@ -1955,7 +1959,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
 			al.slot = std::min(al.slot, pos);
 			pos = ArtifactPosition::PRE_FIRST;
 		}
-		combinedArt->addAsConstituent(constituentInstance, pos);
+		combinedArt->addPart(constituentInstance, pos);
 	}
 
 	//put new combined artifacts
@@ -1964,19 +1968,19 @@ void AssembledArtifact::applyGs(CGameState *gs)
 
 void DisassembledArtifact::applyGs(CGameState *gs)
 {
-	auto * disassembled = dynamic_cast<CCombinedArtifactInstance *>(al.getArt());
+	auto * disassembled = al.getArt();
 	assert(disassembled);
 
-	std::vector<CCombinedArtifactInstance::ConstituentInfo> constituents = disassembled->constituentsInfo;
+	auto parts = disassembled->getPartsInfo();
 	disassembled->removeFrom(al);
-	for(CCombinedArtifactInstance::ConstituentInfo &ci : constituents)
+	for(auto & part : parts)
 	{
-		ArtifactLocation constituentLoc = al;
-		constituentLoc.slot = (ci.slot >= 0 ? ci.slot : al.slot); //-1 is slot of main constituent -> it'll replace combined artifact in its pos
-		disassembled->detachFrom(*ci.art);
-		ci.art->putAt(constituentLoc);
+		ArtifactLocation partLoc = al;
+		// ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos
+		partLoc.slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : al.slot);
+		disassembled->detachFrom(*part.art);
+		part.art->putAt(partLoc);
 	}
-
 	gs->map->eraseArtifactInstance(disassembled);
 }
 
@@ -2209,32 +2213,32 @@ void BattleUpdateGateState::applyGs(CGameState * gs) const
 
 void BattleResultAccepted::applyGs(CGameState * gs) const
 {
-	for(auto * h : {hero1, hero2})
+	// Remove any "until next battle" bonuses
+	for(auto & res : heroResult)
 	{
-		if(h)
-		{
-			h->removeBonusesRecursive(Bonus::OneBattle); 	//remove any "until next battle" bonuses
-			if (h->commander && h->commander->alive)
-			{
-				for (auto art : h->commander->artifactsWorn) //increment bonuses for commander artifacts
-				{
-					art.second.artifact->artType->levelUpArtifact (art.second.artifact);
-				}
-			}
-		}
+		if(res.hero)
+			res.hero->removeBonusesRecursive(Bonus::OneBattle);
 	}
 
-	if(VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+	// Grow up growing artifacts
+	if(const auto hero = heroResult[winnerSide].hero)
 	{
-		for(int i = 0; i < 2; i++)
+		if(hero->commander && hero->commander->alive)
 		{
-			if(exp[i])
-			{
-				if(auto * army = (i == 0 ? army1 : army2))
-					army->giveStackExp(exp[i]);
-			}
+			for(auto & art : hero->commander->artifactsWorn)
+				art.second.artifact->growingUp();
 		}
-
+		for(auto & art : hero->artifactsWorn)
+		{
+			art.second.artifact->growingUp();
+		}
+	}
+	if(VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+	{
+		if(heroResult[0].army)
+			heroResult[0].army->giveStackExp(heroResult[0].exp);
+		if(heroResult[1].army)
+			heroResult[1].army->giveStackExp(heroResult[1].exp);
 		CBonusSystemNode::treeHasChanged();
 	}
 

+ 1 - 1
lib/battle/BattleInfo.cpp

@@ -378,7 +378,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 
 			if(nullptr != warMachineArt)
 			{
-				CreatureID cre = warMachineArt->artType->warMachine;
+				CreatureID cre = warMachineArt->artType->getWarMachine();
 
 				if(cre != CreatureID::NONE)
 					curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);

+ 2 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -406,10 +406,10 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst)
 			ArtifactID aid = creature->warMachine;
 			const CArtifact * art = aid.toArtifact();
 
-			if(art != nullptr && !art->possibleSlots.at(ArtBearer::HERO).empty())
+			if(art != nullptr && !art->getPossibleSlots().at(ArtBearer::HERO).empty())
 			{
 				//TODO: should we try another possible slots?
-				ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front();
+				ArtifactPosition slot = art->getPossibleSlots().at(ArtBearer::HERO).front();
 
 				if(!getArt(slot))
 					putArtifact(slot, ArtifactUtils::createNewArtifactInstance(aid));

+ 2 - 2
lib/mapObjects/CQuest.cpp

@@ -153,7 +153,7 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const
 				if(h->getArtPosCount(elem.first, false, true, true) < elem.second)
 					return false;
 				if(!h->hasArt(elem.first))
-					reqSlots += h->getAssemblyByConstituent(elem.first)->constituentsInfo.size() - 2;
+					reqSlots += h->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
 			}
 			if(ArtifactUtils::isBackpackFreeSlots(h, reqSlots))
 				return true;
@@ -804,7 +804,7 @@ void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
 					{
 						const auto * assembly = h->getAssemblyByConstituent(elem);
 						assert(assembly);
-						auto parts = assembly->constituentsInfo;
+						auto parts = assembly->getPartsInfo();
 
 						// Remove the assembly
 						cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -820,7 +820,7 @@ void CGArtifact::afterAddToMap(CMap * map)
 	//Artifacts from map objects are never removed
 	//FIXME: This should be revertible in map editor
 
-	if(ID == Obj::SPELL_SCROLL && storedArtifact && storedArtifact->id.getNum() < 0)
+	if(ID == Obj::SPELL_SCROLL && storedArtifact && storedArtifact->getId().getNum() < 0)
         map->addNewArtifactInstance(storedArtifact);
 }
 

+ 3 - 3
lib/mapping/CMap.cpp

@@ -466,15 +466,15 @@ void CMap::checkForObjectives()
 
 void CMap::addNewArtifactInstance(CArtifactInstance * art)
 {
-	art->id = ArtifactInstanceID(static_cast<si32>(artInstances.size()));
+	art->setId(static_cast<ArtifactInstanceID>(artInstances.size()));
 	artInstances.emplace_back(art);
 }
 
 void CMap::eraseArtifactInstance(CArtifactInstance * art)
 {
 	//TODO: handle for artifacts removed in map editor
-	assert(artInstances[art->id.getNum()] == art);
-	artInstances[art->id.getNum()].dellNull();
+	assert(artInstances[art->getId().getNum()] == art);
+	artInstances[art->getId().getNum()].dellNull();
 }
 
 void CMap::addNewQuestInstance(CQuest* quest)

+ 1 - 1
lib/mapping/MapFormatH3M.cpp

@@ -746,7 +746,7 @@ void CMapLoaderH3M::readAllowedArtifacts()
 	if(!features.levelSOD)
 	{
 		for(CArtifact * artifact : VLC->arth->objects)
-			if(artifact->constituents)
+			if(artifact->isCombined())
 				map->allowedArtifact[artifact->getId()] = false;
 	}
 

+ 0 - 2
lib/registerTypes/RegisterTypes.h

@@ -206,7 +206,6 @@ void registerTypesMapObjects2(Serializer &s)
 
 //	s.template registerType<CBonusSystemNode>();
 	s.template registerType<CBonusSystemNode, CArtifact>();
-	s.template registerType<CArtifact, CGrowingArtifact>();
 	s.template registerType<CBonusSystemNode, CCreature>();
 	s.template registerType<CBonusSystemNode, CStackInstance>();
 	s.template registerType<CStackInstance, CCommanderInstance>();
@@ -218,7 +217,6 @@ void registerTypesMapObjects2(Serializer &s)
 	s.template registerType<CBonusSystemNode, BattleInfo>();
 	//s.template registerType<QuestInfo>();
 	s.template registerType<CBonusSystemNode, CArtifactInstance>();
-	s.template registerType<CArtifactInstance, CCombinedArtifactInstance>();
 
 	//s.template registerType<CObstacleInstance>();
 		s.template registerType<CObstacleInstance, SpellCreatedObstacle>();

+ 18 - 6
lib/rmg/CMapGenOptions.cpp

@@ -26,6 +26,9 @@ CMapGenOptions::CMapGenOptions()
 	waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
 {
 	resetPlayersMap();
+	setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
+	setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
+	setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
 }
 
 si32 CMapGenOptions::getWidth() const
@@ -233,17 +236,26 @@ void CMapGenOptions::setMapTemplate(const std::string & name)
 		setMapTemplate(VLC->tplh->getTemplate(name));
 }
 
-void CMapGenOptions::setRoadEnabled(const std::string & roadName, bool enable)
+void CMapGenOptions::setRoadEnabled(const RoadId & roadType, bool enable)
 {
-	if(enable)
-		disabledRoads.erase(roadName);
+	if (enable)
+	{
+		enabledRoads.insert(roadType);
+	}
 	else
-		disabledRoads.insert(roadName);
+	{
+		enabledRoads.erase(roadType);
+	}	
+}
+
+bool CMapGenOptions::isRoadEnabled(const RoadId & roadType) const
+{
+	return enabledRoads.count(roadType);
 }
 
-bool CMapGenOptions::isRoadEnabled(const std::string & roadName) const
+bool CMapGenOptions::isRoadEnabled() const
 {
-	return !disabledRoads.count(roadName);
+	return !enabledRoads.empty();
 }
 
 void CMapGenOptions::setPlayerTeam(const PlayerColor & color, const TeamID & team)

+ 5 - 4
lib/rmg/CMapGenOptions.h

@@ -110,8 +110,9 @@ public:
 	EMonsterStrength::EMonsterStrength getMonsterStrength() const;
 	void setMonsterStrength(EMonsterStrength::EMonsterStrength value);
 	
-	bool isRoadEnabled(const std::string & roadName) const;
-	void setRoadEnabled(const std::string & roadName, bool enable);
+	bool isRoadEnabled(const RoadId & roadType) const;
+	bool isRoadEnabled() const;
+	void setRoadEnabled(const RoadId & roadType, bool enable);
 
 	/// The first player colors belong to standard players and the last player colors belong to comp only players.
 	/// All standard players are by default of type EPlayerType::AI.
@@ -156,7 +157,7 @@ private:
 	EWaterContent::EWaterContent waterContent;
 	EMonsterStrength::EMonsterStrength monsterStrength;
 	std::map<PlayerColor, CPlayerSettings> players;
-	std::set<std::string> disabledRoads;
+	std::set<RoadId> enabledRoads;
 	
 	const CRmgTemplate * mapTemplate;
 
@@ -187,7 +188,7 @@ public:
 				setMapTemplate(templateName);
 			}
 			
-			h & disabledRoads;
+			h & enabledRoads;
 		}
 	}
 };

+ 1 - 6
lib/rmg/CMapGenerator.cpp

@@ -80,11 +80,6 @@ void CMapGenerator::loadConfig()
 	config.pandoraMultiplierSpells = randomMapJson["pandoras"]["valueMultiplierSpells"].Integer();
 	config.pandoraSpellSchool = randomMapJson["pandoras"]["valueSpellSchool"].Integer();
 	config.pandoraSpell60 = randomMapJson["pandoras"]["valueSpell60"].Integer();
-	//override config with game options
-	if(!mapGenOptions.isRoadEnabled(config.secondaryRoadType))
-		config.secondaryRoadType = "";
-	if(!mapGenOptions.isRoadEnabled(config.defaultRoadType))
-		config.defaultRoadType = config.secondaryRoadType;
 	config.singleThread = randomMapJson["singleThread"].Bool();
 }
 
@@ -118,7 +113,7 @@ void CMapGenerator::initQuestArtsRemaining()
 	for (auto art : VLC->arth->objects)
 	{
 		//Don't use parts of combined artifacts
-		if (art->aClass == CArtifact::ART_TREASURE && VLC->arth->legalArtifact(art->getId()) && art->constituentOf.empty())
+		if (art->aClass == CArtifact::ART_TREASURE && VLC->arth->legalArtifact(art->getId()) && art->getPartOf().empty())
 			questArtifacts.push_back(art->getId());
 	}
 }

+ 18 - 4
lib/rmg/CRmgTemplate.cpp

@@ -435,11 +435,12 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 	}
 }
 
-ZoneConnection::ZoneConnection()
-	: zoneA(-1),
+ZoneConnection::ZoneConnection():
+	zoneA(-1),
 	zoneB(-1),
 	guardStrength(0),
-	connectionType(EConnectionType::EConnectionType::GUARDED)
+	connectionType(rmg::EConnectionType::GUARDED),
+	hasRoad(rmg::ERoadOption::ROAD_TRUE)
 {
 
 }
@@ -475,10 +476,15 @@ int ZoneConnection::getGuardStrength() const
 	return guardStrength;
 }
 
-EConnectionType::EConnectionType ZoneConnection::getConnectionType() const
+rmg::EConnectionType ZoneConnection::getConnectionType() const
 {
 	return connectionType;
 }
+
+rmg::ERoadOption ZoneConnection::getRoadOption() const
+{
+	return hasRoad;
+}
 	
 bool operator==(const ZoneConnection & l, const ZoneConnection & r)
 {
@@ -495,10 +501,18 @@ void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
 		"wide"
 	};
 
+	static const std::vector<std::string> roadOptions =
+	{
+		"true",
+		"false",
+		"random"
+	};
+
 	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("a", zoneA, -1);
 	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("b", zoneB, -1);
 	handler.serializeInt("guard", guardStrength, 0);
 	handler.serializeEnum("type", connectionType, connectionTypes);
+	handler.serializeEnum("road", hasRoad, roadOptions);
 }
 
 }

+ 18 - 12
lib/rmg/CRmgTemplate.h

@@ -67,19 +67,23 @@ public:
 	void serializeJson(JsonSerializeFormat & handler);
 };
 
-namespace EConnectionType
+namespace rmg
 {
-	enum class EConnectionType
-	{
-		GUARDED = 0, //default
-		FICTIVE,
-		REPULSIVE,
-		WIDE
-	};
-}
 
-namespace rmg
+enum class EConnectionType
 {
+	GUARDED = 0, //default
+	FICTIVE,
+	REPULSIVE,
+	WIDE
+};
+
+enum class ERoadOption
+{
+	ROAD_TRUE,
+	ROAD_FALSE,
+	ROAD_RANDOM
+};
 
 class DLL_LINKAGE ZoneConnection
 {
@@ -91,7 +95,8 @@ public:
 	TRmgTemplateZoneId getZoneB() const;
 	TRmgTemplateZoneId getOtherZoneId(TRmgTemplateZoneId id) const;
 	int getGuardStrength() const;
-	EConnectionType::EConnectionType getConnectionType() const;
+	rmg::EConnectionType getConnectionType() const;
+	rmg::ERoadOption getRoadOption() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
 	
@@ -100,7 +105,8 @@ private:
 	TRmgTemplateZoneId zoneA;
 	TRmgTemplateZoneId zoneB;
 	int guardStrength;
-	EConnectionType::EConnectionType connectionType;
+	rmg::EConnectionType connectionType;
+	rmg::ERoadOption hasRoad;
 };
 
 class DLL_LINKAGE ZoneOptions

+ 5 - 5
lib/rmg/CZonePlacer.cpp

@@ -75,7 +75,7 @@ void CZonePlacer::findPathsBetweenZones()
 
 			for (auto & connection : connectedZoneIds)
 			{
-				if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+				if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 				{
 					//Do not consider virtual connections for graph distance
 					continue;
@@ -536,7 +536,7 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces,
 
 		for (const auto & connection : zone.second->getConnections())
 		{
-			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				continue;
 			}
@@ -625,7 +625,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
 		//TODO: Consider z plane?
 		for (auto& connection : zone.second->getConnections())
 		{
-			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
 				float3 otherZoneCenter = otherZone->getCenter();
@@ -693,7 +693,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		for (const auto& connection : firstZone->getConnections())
 		{
 			//FIXME: Should we also exclude fictive connections?
-			if (connection.getConnectionType() != EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() != rmg::EConnectionType::REPULSIVE)
 			{
 				connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
 			}
@@ -740,7 +740,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		float maxDistance = 0;
 		for (auto con : misplacedZone->getConnections())
 		{
-			if (con.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				continue;
 			}

+ 32 - 18
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -98,6 +98,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 	bool success = false;
 	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
 	auto & otherZone = map.getZones().at(otherZoneId);
+	bool createRoad = shouldGenerateRoad(connection);
 	
 	//1. Try to make direct connection
 	//Do if it's not prohibited by terrain settings
@@ -110,7 +111,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 
 	if (directConnectionIterator != dNeighbourZones.end())
 	{
-		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		if (connection.getConnectionType() == rmg::EConnectionType::WIDE)
 		{
 			for (auto borderPos : directConnectionIterator->second)
 			{
@@ -159,8 +160,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 		}
 	}
 
-	if (connection.getConnectionType() == EConnectionType::EConnectionType::FICTIVE || 
-		connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+	if (connection.getConnectionType() == rmg::EConnectionType::FICTIVE || 
+		connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 	{
 		//Fictive or repulsive connections are not real, take no action
 		dCompleted.push_back(connection);
@@ -254,15 +255,18 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 					otherZone->getModificator<ObjectManager>()->updateDistances(guardPos);
 				}
 				
-				assert(zone.getModificator<RoadPlacer>());
-				zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
-				
-				assert(otherZone->getModificator<RoadPlacer>());
-				otherZone->getModificator<RoadPlacer>()->addRoadNode(roadNode);
-				
-				assert(otherZone->getModificator<ConnectionsPlacer>());
-				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
-				
+				if (createRoad)
+				{
+					assert(zone.getModificator<RoadPlacer>());
+					zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
+
+					assert(otherZone->getModificator<RoadPlacer>());
+					otherZone->getModificator<RoadPlacer>()->addRoadNode(roadNode);
+
+					assert(otherZone->getModificator<ConnectionsPlacer>());
+					otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+				}
+
 				success = true;
 			}
 		}
@@ -274,7 +278,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 	{
 		if(generator.getZoneWater() && generator.getZoneWater()->getModificator<WaterProxy>())
 		{
-			if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
+			if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection, createRoad))
 			{
 				assert(otherZone->getModificator<ConnectionsPlacer>());
 				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -292,6 +296,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 	bool success = false;
 	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
 	auto & otherZone = map.getZones().at(otherZoneId);
+
+	bool allowRoad = shouldGenerateRoad(connection);
 	
 	//3. place subterrain gates
 	if(zone.isUnderground() != otherZone->isUnderground())
@@ -341,8 +347,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 				zone.connectPath(path1);
 				otherZone->connectPath(path2);
 				
-				manager.placeObject(rmgGate1, guarded1, true);
-				managerOther.placeObject(rmgGate2, guarded2, true);
+				manager.placeObject(rmgGate1, guarded1, true, allowRoad);
+				managerOther.placeObject(rmgGate2, guarded2, true, allowRoad);
 				
 				assert(otherZone->getModificator<ConnectionsPlacer>());
 				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -359,8 +365,10 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 		auto * teleport1 = factory->create();
 		auto * teleport2 = factory->create();
 
-		zone.getModificator<ObjectManager>()->addRequiredObject(teleport1, connection.getGuardStrength());
-		otherZone->getModificator<ObjectManager>()->addRequiredObject(teleport2, connection.getGuardStrength());
+		RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad);
+		RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad);
+		zone.getModificator<ObjectManager>()->addRequiredObject(obj1);
+		otherZone->getModificator<ObjectManager>()->addRequiredObject(obj2);
 		
 		assert(otherZone->getModificator<ConnectionsPlacer>());
 		otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -386,6 +394,12 @@ void ConnectionsPlacer::collectNeighbourZones()
 	}
 }
 
+bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection) const
+{
+	return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE ||
+		(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM && zone.getRand().nextDouble() >= 0.5f);
+}
+
 void ConnectionsPlacer::createBorder()
 {
 	rmg::Area borderArea(zone.getArea().getBorder());
@@ -401,7 +415,7 @@ void ConnectionsPlacer::createBorder()
 	{
 		auto otherZone = connection.getOtherZoneId(zone.getId());
 
-		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		if (connection.getConnectionType() == rmg::EConnectionType::WIDE)
 		{
 			auto sharedBorder = borderArea.getSubarea([this, otherZone, &borderOutsideArea](const int3 & t)
 			{

+ 2 - 0
lib/rmg/modificators/ConnectionsPlacer.h

@@ -28,6 +28,8 @@ public:
 	void selfSideIndirectConnection(const rmg::ZoneConnection & connection);
 	void otherSideConnection(const rmg::ZoneConnection & connection);
 	void createBorder();
+
+	bool shouldGenerateRoad(const rmg::ZoneConnection& connection) const;
 	
 protected:
 	void collectNeighbourZones();

+ 13 - 6
lib/rmg/modificators/MinePlacer.cpp

@@ -63,8 +63,11 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 			createdMines.push_back(mine);
 
 
-			if(!i && (res == EGameResID::WOOD || res == EGameResID::ORE))
-				manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
+			if (!i && (res == EGameResID::WOOD || res == EGameResID::ORE))
+			{
+				//only first wood & ore mines are close
+				manager.addCloseObject(RequiredObjectInfo(mine, rmginfo.value));
+			}
 			else
 				requiredObjects.push_back(std::pair<CGObjectInstance*, ui32>(mine, rmginfo.value));
 		}
@@ -74,7 +77,7 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 	RandomGeneratorUtil::randomShuffle(requiredObjects, zone.getRand());
 	for (const auto& obj : requiredObjects)
 	{
-		manager.addRequiredObject(obj.first, obj.second);
+		manager.addRequiredObject(RequiredObjectInfo(obj.first, obj.second));
 	}
 
 	//create extra resources
@@ -84,9 +87,13 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 		{
 			for(int rc = zone.getRand().nextInt(1, extraRes); rc > 0; --rc)
 			{
-				auto * resourse = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
-				resourse->amount = CGResource::RANDOM_AMOUNT;
-				manager.addNearbyObject(resourse, mine);
+				auto * resource = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
+				resource->amount = CGResource::RANDOM_AMOUNT;
+
+				RequiredObjectInfo roi;
+				roi.obj = resource;
+				roi.nearbyTarget = mine;
+				manager.addNearbyObject(roi);
 			}
 		}
 	}

+ 54 - 41
lib/rmg/modificators/ObjectManager.cpp

@@ -57,22 +57,22 @@ void ObjectManager::createDistancesPriorityQueue()
 	}
 }
 
-void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength)
+void ObjectManager::addRequiredObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	requiredObjects.emplace_back(obj, strength);
+	requiredObjects.emplace_back(info);
 }
 
-void ObjectManager::addCloseObject(CGObjectInstance * obj, si32 strength)
+void ObjectManager::addCloseObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	closeObjects.emplace_back(obj, strength);
+	closeObjects.emplace_back(info);
 }
 
-void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
+void ObjectManager::addNearbyObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	nearbyObjects.emplace_back(obj, nearbyTarget);
+	nearbyObjects.emplace_back(info);
 }
 
 void ObjectManager::updateDistances(const rmg::Object & obj)
@@ -331,13 +331,11 @@ bool ObjectManager::createRequiredObjects()
 	logGlobal->trace("Creating required objects");
 	
 	//RecursiveLock lock(externalAccessMutex); //Why could requiredObjects be modified during the loop?
-	for(const auto & object : requiredObjects)
+	for(const auto & objInfo : requiredObjects)
 	{
-		auto * obj = object.first;
-		//FIXME: Invalid dObject inside object?
-		rmg::Object rmgObject(*obj);
+		rmg::Object rmgObject(*objInfo.obj);
 		rmgObject.setTemplate(zone.getTerrainType());
-		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
 
 		Zone::Lock lock(zone.areaMutex);
 		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
@@ -349,14 +347,14 @@ bool ObjectManager::createRequiredObjects()
 		}
 		
 		zone.connectPath(path);
-		placeObject(rmgObject, guarded, true);
+		placeObject(rmgObject, guarded, true, objInfo.createRoad);
 		
 		for(const auto & nearby : nearbyObjects)
 		{
-			if(nearby.second != obj)
+			if(nearby.nearbyTarget != nearby.obj)
 				continue;
 			
-			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Object rmgNearObject(*nearby.obj);
 			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
 			possibleArea.intersect(zone.areaPossible());
 			if(possibleArea.empty())
@@ -366,21 +364,18 @@ bool ObjectManager::createRequiredObjects()
 			}
 			
 			rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand()));
-			placeObject(rmgNearObject, false, false);
+			placeObject(rmgNearObject, false, false, nearby.createRoad);
 		}
 	}
 	
-	for(const auto & object : closeObjects)
+	for(const auto & objInfo : closeObjects)
 	{
-		auto * obj = object.first;
-
-		//TODO: Wrap into same area proxy?
 		Zone::Lock lock(zone.areaMutex);
 		auto possibleArea = zone.areaPossible();
 
-		rmg::Object rmgObject(*obj);
+		rmg::Object rmgObject(*objInfo.obj);
 		rmgObject.setTemplate(zone.getTerrainType());
-		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
 		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject,
 										  [this, &rmgObject](const int3 & tile)
 		{
@@ -401,10 +396,10 @@ bool ObjectManager::createRequiredObjects()
 		
 		for(const auto & nearby : nearbyObjects)
 		{
-			if(nearby.second != obj)
+			if(nearby.nearbyTarget != objInfo.obj)
 				continue;
 			
-			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Object rmgNearObject(*nearby.obj);
 			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
 			possibleArea.intersect(zone.areaPossible());
 			if(possibleArea.empty())
@@ -420,10 +415,10 @@ bool ObjectManager::createRequiredObjects()
 	
 	//create object on specific positions
 	//TODO: implement guards
-	for (const auto &obj : instantObjects)
+	for (const auto &objInfo : instantObjects)
 	{
-		rmg::Object rmgObject(*obj.first);
-		rmgObject.setPosition(obj.second);
+		rmg::Object rmgObject(*objInfo.obj);
+		rmgObject.setPosition(objInfo.pos);
 		placeObject(rmgObject, false, false);
 	}
 	
@@ -435,7 +430,7 @@ bool ObjectManager::createRequiredObjects()
 	return true;
 }
 
-void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance)
+void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance, bool createRoad/* = false*/)
 {	
 	object.finalize(map);
 
@@ -492,25 +487,27 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 				break;
 		}
 	}
-	
-	switch(object.instances().front()->object().ID)
+
+	if (createRoad)
 	{
-		case Obj::TOWN:
-		case Obj::RANDOM_TOWN:
-		case Obj::MONOLITH_TWO_WAY:
-		case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+		if (auto* m = zone.getModificator<RoadPlacer>())
+			m->addRoadNode(object.instances().front()->getVisitablePosition());
+	}
+
+	//TODO: Add road node to these objects:
+	/*
+	 	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+	 	case Obj::RANDOM_TOWN:
 		case Obj::MONOLITH_ONE_WAY_EXIT:
-		case Obj::SUBTERRANEAN_GATE:
-		case Obj::SHIPYARD:
-			if(auto * m = zone.getModificator<RoadPlacer>())
-				m->addRoadNode(object.instances().front()->getVisitablePosition());
-			break;
-			
+	*/
+
+	switch (object.instances().front()->object().ID)
+	{
 		case Obj::WATER_WHEEL:
-			if(auto * m = zone.getModificator<RiverPlacer>())
+			if (auto* m = zone.getModificator<RiverPlacer>())
 				m->addRiverNode(object.instances().front()->getVisitablePosition());
 			break;
-			
+
 		default:
 			break;
 	}
@@ -613,4 +610,20 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
 	return true;
 }
 
+RequiredObjectInfo::RequiredObjectInfo():
+	obj(nullptr),
+	nearbyTarget(nullptr),
+	guardStrength(0),
+	createRoad(true)
+{}
+
+RequiredObjectInfo::RequiredObjectInfo(CGObjectInstance* obj, ui32 guardStrength, bool createRoad, CGObjectInstance* nearbyTarget):
+	obj(obj),
+	nearbyTarget(nearbyTarget),
+	guardStrength(guardStrength),
+	createRoad(createRoad)
+{}
+
 VCMI_LIB_NAMESPACE_END
+
+

+ 20 - 8
lib/rmg/modificators/ObjectManager.h

@@ -29,6 +29,18 @@ struct DistanceMaximizeFunctor
 	}
 };
 
+struct RequiredObjectInfo
+{
+	RequiredObjectInfo();
+	RequiredObjectInfo(CGObjectInstance* obj, ui32 guardStrength = 0, bool createRoad = false, CGObjectInstance* nearbyTarget = nullptr);
+
+	CGObjectInstance* obj;
+	CGObjectInstance* nearbyTarget;
+	int3 pos;
+	ui32 guardStrength;
+	bool createRoad;
+};
+
 class ObjectManager: public Modificator
 {
 public:
@@ -45,9 +57,9 @@ public:
 	void process() override;
 	void init() override;
 
-	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
-	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
-	void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
+	void addRequiredObject(const RequiredObjectInfo & info);
+	void addCloseObject(const RequiredObjectInfo & info);
+	void addNearbyObject(const RequiredObjectInfo & info);
 
 	bool createRequiredObjects();
 
@@ -59,7 +71,7 @@ public:
 
 	CGCreature * chooseGuard(si32 strength, bool zoneGuard = false);
 	bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false);
-	void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
+	void placeObject(rmg::Object & object, bool guarded, bool updateDistance, bool createRoad = false);
 
 	void updateDistances(const rmg::Object & obj);
 	void updateDistances(const int3& pos);
@@ -72,10 +84,10 @@ public:
 	
 protected:
 	//content info
-	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
-	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
-	std::vector<std::pair<CGObjectInstance*, int3>> instantObjects;
-	std::vector<std::pair<CGObjectInstance*, CGObjectInstance*>> nearbyObjects;
+	std::vector<RequiredObjectInfo> requiredObjects;
+	std::vector<RequiredObjectInfo> closeObjects;
+	std::vector<RequiredObjectInfo> instantObjects;
+	std::vector<RequiredObjectInfo> nearbyObjects;
 	std::vector<CGObjectInstance*> objects;
 	rmg::Area objectsVisitableArea;
 	

+ 24 - 8
lib/rmg/modificators/RoadPlacer.cpp

@@ -79,25 +79,41 @@ bool RoadPlacer::createRoad(const int3 & dst)
 }
 
 void RoadPlacer::drawRoads(bool secondary)
-{
-	if((secondary && generator.getConfig().secondaryRoadType.empty())
-	   || (!secondary && generator.getConfig().defaultRoadType.empty()))
-		return;
-	
-	//RecursiveLock lock(externalAccessMutex);
+{	
 	{
-		//FIXME: double lock - unsafe
+		//Clean space under roads even if they won't be eventually generated
 		Zone::Lock lock(zone.areaMutex);
 
 		zone.areaPossible().subtract(roads);
 		zone.freePaths().unite(roads);
 	}
 
+	if (!generator.getMapGenOptions().isRoadEnabled())
+	{
+		return;
+	}
+
+	if((secondary && generator.getConfig().secondaryRoadType.empty())
+		|| (!secondary && generator.getConfig().defaultRoadType.empty()))
+		return;
+
+	//TODO: Allow custom road type for object
+	//TODO: Remove these default types
+
 	auto tiles = roads.getTilesVector();
 
 	std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
 	RoadId roadType(*VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "road", roadName));
-	mapProxy->drawRoads(zone.getRand(), tiles, roadType);
+
+	//If our road type is not enabled, choose highest below it
+	for (int8_t bestRoad = roadType.getNum(); bestRoad > RoadId(Road::NO_ROAD).getNum(); bestRoad--)
+	{
+		if (generator.getMapGenOptions().isRoadEnabled(RoadId(bestRoad)))
+		{
+			mapProxy->drawRoads(zone.getRand(), tiles, RoadId(bestRoad));
+			return;
+		}
+	}
 }
 
 void RoadPlacer::addRoadNode(const int3& node)

+ 4 - 2
lib/rmg/modificators/TownPlacer.cpp

@@ -152,7 +152,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
 			}, ObjectManager::OptimizeType::WEIGHT);
 	}
 	rmgObject.setPosition(position + int3(2, 2, 0)); //place visitable tile in the exact center of a zone
-	manager.placeObject(rmgObject, false, true);
+	manager.placeObject(rmgObject, false, true, true);
 	cleanupBoundaries(rmgObject);
 	zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
 	return position;
@@ -216,7 +216,9 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player
 			placeMainTown(manager, *town);
 		}
 		else
-			manager.addRequiredObject(town);
+		{
+			manager.addRequiredObject(RequiredObjectInfo(town, 0, true));
+		}
 		totalTowns++;
 	}
 }

+ 24 - 8
lib/rmg/modificators/WaterProxy.cpp

@@ -133,6 +133,8 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 	
 	if(adopter->getCoastTiles().empty())
 		return result;
+
+	bool createRoad = false;
 	
 	//block zones are not connected by template
 	for(auto& lake : lakes)
@@ -162,17 +164,23 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 			int zoneTowns = 0;
 			if(auto * m = dst.getModificator<TownPlacer>())
 				zoneTowns = m->getTotalTowns();
+
+			if (vstd::contains(lake.keepRoads, dst.getId()))
+			{
+				createRoad = true;
+			}
 			
+			//FIXME: Why are Shipyards not allowed in zones with no towns?
 			if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns)
 			{
-				if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, result))
+				if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, createRoad, result))
 				{
 					logGlobal->info("Shipyard successfully placed at zone %d", dst.getId());
 				}
 				else
 				{
 					logGlobal->warn("Shipyard placement failed, trying boat at zone %d", dst.getId());
-					if(placeBoat(dst, lake, result))
+					if(placeBoat(dst, lake, createRoad, result))
 					{
 						logGlobal->warn("Boat successfully placed at zone %d", dst.getId());
 					}
@@ -184,7 +192,7 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 			}
 			else
 			{
-				if(placeBoat(dst, lake, result))
+				if(placeBoat(dst, lake,  createRoad, result))
 				{
 					logGlobal->info("Boat successfully placed at zone %d", dst.getId());
 				}
@@ -199,21 +207,29 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 	return result;
 }
 
-bool WaterProxy::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB)
+bool WaterProxy::waterKeepConnection(const rmg::ZoneConnection & connection, bool createRoad)
 {
+	const auto & zoneA = connection.getZoneA();
+	const auto & zoneB = connection.getZoneB();
+
 	for(auto & lake : lakes)
 	{
 		if(lake.neighbourZones.count(zoneA) && lake.neighbourZones.count(zoneB))
 		{
 			lake.keepConnections.insert(zoneA);
 			lake.keepConnections.insert(zoneB);
+			if (createRoad)
+			{
+				lake.keepRoads.insert(zoneA);
+				lake.keepRoads.insert(zoneB);
+			}
 			return true;
 		}
 	}
 	return false;
 }
 
-bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
+bool WaterProxy::placeBoat(Zone & land, const Lake & lake, bool createRoad, RouteInfo & info)
 {
 	auto * manager = zone.getModificator<ObjectManager>();
 	if(!manager)
@@ -284,7 +300,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
 
 		zone.connectPath(path);
 		land.connectPath(landPath);
-		manager->placeObject(rmgObject, false, true);
+		manager->placeObject(rmgObject, false, true, createRoad);
 		land.getModificator<ObjectManager>()->updateDistances(rmgObject); //Keep land objects away from the boat
 		break;
 	}
@@ -292,7 +308,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
 	return !boardingPositions.empty();
 }
 
-bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info)
+bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool createRoad, RouteInfo & info)
 {
 	auto * manager = land.getModificator<ObjectManager>();
 	if(!manager)
@@ -372,7 +388,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, Route
 		info.boarding = boardingPosition;
 		info.water = shipPositions;
 		
-		manager->placeObject(rmgObject, guarded, true);
+		manager->placeObject(rmgObject, guarded, true, createRoad);
 		
 		zone.areaPossible().subtract(shipyardOutToBlock);
 		for(const auto & i : shipyardOutToBlock.getTilesVector())

+ 4 - 3
lib/rmg/modificators/WaterProxy.h

@@ -34,9 +34,10 @@ public:
 		std::map<int, rmg::Tileset> reverseDistanceMap;
 		std::map<TRmgTemplateZoneId, rmg::Area> neighbourZones; //zones boardered. Area - part of land
 		std::set<TRmgTemplateZoneId> keepConnections;
+		std::set<TRmgTemplateZoneId> keepRoads;
 	};
 		
-	bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
+	bool waterKeepConnection(const rmg::ZoneConnection & connection, bool createRoad);
 	RouteInfo waterRoute(Zone & dst);
 	
 	void process() override;
@@ -47,8 +48,8 @@ public:
 protected:
 	void collectLakes();
 	
-	bool placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info);
-	bool placeBoat(Zone & land, const Lake & lake, RouteInfo & info);
+	bool placeShipyard(Zone & land, const Lake & lake, si32 guard, bool createRoad, RouteInfo & info);
+	bool placeBoat(Zone & land, const Lake & lake, bool createRoad, RouteInfo & info);
 
 protected:
 	std::vector<Lake> lakes; //disconnected parts of zone. Used to work with water zones

+ 1 - 1
lib/serializer/CSerializer.cpp

@@ -33,7 +33,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib)
 	registerVectoredType<CArtifact, ArtifactID>(&lib->arth->objects,
 		[](const CArtifact &art){ return art.getId(); });
 	registerVectoredType<CArtifactInstance, ArtifactInstanceID>(&gs->map->artInstances,
-		[](const CArtifactInstance &artInst){ return artInst.id; });
+		[](const CArtifactInstance &artInst){ return artInst.getId(); });
 	registerVectoredType<CQuest, si32>(&gs->map->quests,
 		[](const CQuest &q){ return q.qid; });
 

+ 96 - 1
mapeditor/translation/english.ts

@@ -599,10 +599,105 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 120 - 15
mapeditor/translation/french.ts

@@ -6,7 +6,7 @@
     <message>
         <location filename="../inspector/armywidget.ui" line="20"/>
         <source>Army settings</source>
-        <translation>Paramètres de l'armée</translation>
+        <translation>Paramètres de l&apos;armée</translation>
     </message>
     <message>
         <location filename="../inspector/armywidget.ui" line="162"/>
@@ -64,7 +64,7 @@
     <message>
         <location filename="../mainwindow.ui" line="117"/>
         <source>Toolbar</source>
-        <translation>Barre d'outils</translation>
+        <translation>Barre d&apos;outils</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="163"/>
@@ -139,7 +139,7 @@
     <message>
         <location filename="../mainwindow.ui" line="921"/>
         <source>Save as</source>
-        <translation>Enregistrer sous</translation>
+        <translation>Enregistrer sous...</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="924"/>
@@ -185,7 +185,7 @@
     <message>
         <location filename="../mainwindow.ui" line="1002"/>
         <source>Fills the selection with obstacles</source>
-        <translation>Remplir la sélection d'obstacles</translation>
+        <translation>Remplir la sélection d&apos;obstacles</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="1017"/>
@@ -236,7 +236,7 @@
     <message>
         <location filename="../mainwindow.ui" line="1126"/>
         <source>Update appearance</source>
-        <translation>Mettre à jour l'apparence</translation>
+        <translation>Mettre à jour l&apos;apparence</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="1137"/>
@@ -281,7 +281,7 @@
     <message>
         <location filename="../mainwindow.ui" line="1233"/>
         <source>Export as</source>
-        <translation>Exporter sous</translation>
+        <translation>Exporter sous...</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="1225"/>
@@ -427,7 +427,7 @@
     <message>
         <location filename="../mapsettings.cpp" line="164"/>
         <source>Capture artifact</source>
-        <translation>Récupérer l'artefact</translation>
+        <translation>Récupérer l&apos;artefact</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="165"/>
@@ -606,10 +606,105 @@
         <translation>Résultats de la validation de la carte</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation>Aucune carte n&apos;est chargée</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation>Aucun joueur autorisé à jouer sur cette carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation>La carte est autorisée pour un joueur et ne peut pas être démarrée</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation>Aucun joueur humain n&apos;est autorisé à jouer sur cette carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation>L&apos;instance blindée %1 est IMMARQUABLE mais doit avoir un propriétaire NEUTRE ou joueur</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation>L&apos;objet %1 est attribué au joueur non jouable %2</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>La ville %1 a le propriétaire indéfini %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation>La prison %1 doit être NEUTRE</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation>Le héros %1 doit avoir un propriétaire</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation>Le héros %1 est interdit par les paramètres de la carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation>Le héros %1 a un doublon sur la carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation>Le héros %1 a un type vide et doit être supprimé</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation>Le défilement des sorts %1 est interdit par les paramètres de la carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation>Le parchemin de sort %1 n&apos;a pas d&apos;instance assignée et doit être supprimé</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation>L&apos;artefact %1 est interdit par les paramètres de la carte</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation>Le joueur %1 n&apos;a pas de ville de départ</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation>Le nom de la carte n&apos;est pas spécifié</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation>La description de la carte n&apos;est pas spécifiée</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation>Une exception se produit lors de la validation : %1</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation>Une exception inconnue se produit lors de la validation</translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>
@@ -679,10 +774,10 @@
         <translation>Humain/Ordinateur</translation>
     </message>
     <message>
+        <location filename="../windownewmap.ui" line="220"/>
         <location filename="../windownewmap.ui" line="288"/>
-        <location filename="../windownewmap.ui" line="357"/>
-        <location filename="../windownewmap.ui" line="455"/>
-        <location filename="../windownewmap.ui" line="596"/>
+        <location filename="../windownewmap.ui" line="442"/>
+        <location filename="../windownewmap.ui" line="583"/>
         <source>Random</source>
         <translation>Aléatoire</translation>
     </message>
@@ -691,6 +786,16 @@
         <source>Computer only</source>
         <translation>Ordinateur uniquement</translation>
     </message>
+    <message>
+        <location filename="../windownewmap.ui" line="379"/>
+        <source>Human teams</source>
+        <translation>Équipes humaines</translation>
+    </message>
+    <message>
+        <location filename="../windownewmap.ui" line="398"/>
+        <source>Computer teams</source>
+        <translation>Équipes d&apos;ordinateur</translation>
+    </message>
     <message>
         <location filename="../windownewmap.ui" line="428"/>
         <source>Monster strength</source>
@@ -763,22 +868,22 @@
     <message>
         <location filename="../mainwindow.cpp" line="101"/>
         <source>Extract original H3 archives into a separate folder.</source>
-        <translation>Extraire les archives H3 d'origine dans un dossier séparé.</translation>
+        <translation>Extraire les archives H3 d&apos;origine dans un dossier séparé.</translation>
     </message>
     <message>
         <location filename="../mainwindow.cpp" line="102"/>
         <source>From an extracted archive, it Splits TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44 into individual PNG&apos;s.</source>
-        <translation>À partir d'une archive extraite, il divise TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 et Un44 en fichiers PNG individuels.</translation>
+        <translation>À partir d&apos;une archive extraite, il divise TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 et Un44 en fichiers PNG individuels.</translation>
     </message>
     <message>
         <location filename="../mainwindow.cpp" line="103"/>
         <source>From an extracted archive, Converts single Images (found in Images folder) from .pcx to png.</source>
-        <translation>À partir d'une archive extraite, convertit des images uniques (trouvées dans le dossier Images) de .pcx en png.</translation>
+        <translation>À partir d&apos;une archive extraite, convertit des images uniques (trouvées dans le dossier Images) de .pcx en png.</translation>
     </message>
     <message>
         <location filename="../mainwindow.cpp" line="104"/>
         <source>Delete original files, for the ones splitted / converted.</source>
-        <translation>Supprimer les fichiers d'origine, pour ceux fractionnés/convertis.</translation>
+        <translation>Supprimer les fichiers d&apos;origine, pour ceux fractionnés/convertis.</translation>
     </message>
 </context>
 </TS>

+ 96 - 1
mapeditor/translation/german.ts

@@ -599,10 +599,105 @@
         <translation>Ergebnisse der Kartenvalidierung</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>Stadt %1 hat undefinierten Besitzer %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 123 - 28
mapeditor/translation/polish.ts

@@ -62,17 +62,17 @@
     <message>
         <location filename="../mainwindow.ui" line="117"/>
         <source>Toolbar</source>
-        <translation type="unfinished"></translation>
+        <translation>Przybornik</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="163"/>
         <source>Minimap</source>
-        <translation type="unfinished"></translation>
+        <translation>Minimapa</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="239"/>
         <source>Map Objects View</source>
-        <translation type="unfinished"></translation>
+        <translation>Widok obiektów</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="283"/>
@@ -97,7 +97,7 @@
     <message>
         <location filename="../mainwindow.ui" line="432"/>
         <source>Terrains View</source>
-        <translation type="unfinished"></translation>
+        <translation>Widok terenów</translation>
     </message>
     <message>
         <location filename="../mainwindow.ui" line="484"/>
@@ -339,53 +339,53 @@
     <message>
         <location filename="../mapsettings.ui" line="66"/>
         <source>Difficulty</source>
-        <translation type="unfinished"></translation>
+        <translation>Poziom trudności</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="111"/>
         <source>Events</source>
-        <translation type="unfinished"></translation>
+        <translation>Zdarzenia</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="121"/>
         <source>Victory</source>
-        <translation type="unfinished"></translation>
+        <translation>Zwycięstwo</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="135"/>
         <source>Victory message</source>
-        <translation type="unfinished"></translation>
+        <translation>Komunikat zwycięstwa</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="150"/>
         <source>Only for human players</source>
-        <translation type="unfinished"></translation>
+        <translation>Dotyczy tylko graczy ludzkich</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="157"/>
         <source>Allow standard victory</source>
-        <translation type="unfinished"></translation>
+        <translation>Także standardowy warunek zwycięstwa</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="170"/>
         <location filename="../mapsettings.ui" line="218"/>
         <source>Parameters</source>
-        <translation type="unfinished"></translation>
+        <translation>Parametry</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="186"/>
         <source>Loss</source>
-        <translation type="unfinished"></translation>
+        <translation>Porażka</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="195"/>
         <source>7 days without town</source>
-        <translation type="unfinished"></translation>
+        <translation>7 dni bez miasta</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="202"/>
         <source>Defeat message</source>
-        <translation type="unfinished"></translation>
+        <translation>Komunikat o porażce</translation>
     </message>
     <message>
         <location filename="../mapsettings.ui" line="235"/>
@@ -415,67 +415,67 @@
     <message>
         <location filename="../mapsettings.cpp" line="163"/>
         <source>No special victory</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Bez specjalnych warunków zwycięstwa</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="164"/>
         <source>Capture artifact</source>
-        <translation type="unfinished"></translation>
+        <translation>Zdobądź artefakt</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="165"/>
         <source>Hire creatures</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Zdobądź stworzenia</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="166"/>
         <source>Accumulate resources</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Zbierz zasoby</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="167"/>
         <source>Construct building</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Zbuduj budynek</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="168"/>
         <source>Capture town</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Zdobądź miasto</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="169"/>
         <source>Defeat hero</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Pokonaj bohatera</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="170"/>
         <source>Transport artifact</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Przenieś artefakt</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="173"/>
         <source>No special loss</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Bez specjalnych warunków porażki</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="174"/>
         <source>Lose castle</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Utrata miasta</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="175"/>
         <source>Lose hero</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Utrata bohatera</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="176"/>
         <source>Time expired</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Upłynięcie czasu</translation>
     </message>
     <message>
         <location filename="../mapsettings.cpp" line="177"/>
         <source>Days without town</source>
-        <translation type="unfinished"></translation>
+        <translation type="unfinished">Dni bez miasta</translation>
     </message>
 </context>
 <context>
@@ -599,10 +599,105 @@
         <translation>Wynik sprawdzenia mapy</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>Miasto %1 ma niezdefiniowanego właściciela %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 96 - 1
mapeditor/translation/russian.ts

@@ -599,10 +599,105 @@
         <translation>Результаты проверки карты</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>У города %1 неопределенный владелец %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 96 - 1
mapeditor/translation/spanish.ts

@@ -601,10 +601,105 @@
         <translation>Resultados de la validación del mapa</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation>No se ha cargado ningún mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation>No hay jugadores autorizados a jugar en este mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation>El mapa está autorizado para un jugador y no se puede iniciar</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation>Ningún jugador humano puede jugar en este mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation>La instancia protegida %1 NOSEPUEDEMARCAR, pero debe tener un propietario NEUTRAL o jugador</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation>El artículo %1 está asignado al jugador no jugable %2</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>La ciudad %1 no tiene un propietario definido %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation>%1 prisión debe ser NEUTRA</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation>El héroe %1 debe tener un propietario</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation>El héroe %1 está prohibido por la configuración del mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation>El héroe %1 tiene un duplicado en el mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation>El héroe %1 tiene un tipo vacío y debe eliminarse</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation>%1 desplazamiento de hechizos está prohibido por la configuración del mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation>Pergamino ortográfico %1 no tiene una instancia asignada y debe eliminarse</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation>El artefacto %1 está prohibido por la configuración del mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation>El jugador %1 no tiene ciudad inicial</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation>No se especifica el nombre del mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation>No se especifica la descripción del mapa</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation>Se produce una excepción durante la validación: %1</translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation>Se produce una excepción desconocida durante la validación</translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 96 - 1
mapeditor/translation/ukrainian.ts

@@ -599,10 +599,105 @@
         <translation>Результати валідації карти</translation>
     </message>
     <message>
-        <location filename="../validator.cpp" line="101"/>
+        <location filename="../validator.cpp" line="50"/>
+        <source>Map is not loaded</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="73"/>
+        <source>No players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="75"/>
+        <source>Map is allowed for one player and cannot be started</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="77"/>
+        <source>No human players allowed to play this map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="93"/>
+        <source>Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="99"/>
+        <source>Object %1 is assigned to non-playable player %2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="106"/>
         <source>Town %1 has undefined owner %2</source>
         <translation>Місто %1 має невизначеного володаря %2</translation>
     </message>
+    <message>
+        <location filename="../validator.cpp" line="116"/>
+        <source>Prison %1 must be a NEUTRAL</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="122"/>
+        <source>Hero %1 must have an owner</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="127"/>
+        <source>Hero %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="130"/>
+        <source>Hero %1 has duplicate on map</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="133"/>
+        <source>Hero %1 has an empty type and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="144"/>
+        <source>Spell scroll %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="147"/>
+        <source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="153"/>
+        <source>Artifact %1 is prohibited by map settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="162"/>
+        <source>Player %1 doesn&apos;t have any starting town</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="166"/>
+        <source>Map name is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="168"/>
+        <source>Map description is not specified</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="181"/>
+        <source>Exception occurs during validation: %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../validator.cpp" line="185"/>
+        <source>Unknown exception occurs during validation</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WindowNewMap</name>

+ 2 - 2
mapeditor/validator.cpp

@@ -96,7 +96,7 @@ std::list<Validator::Issue> Validator::validate(const CMap * map)
 			if(o->getOwner() != PlayerColor::NEUTRAL && o->getOwner().getNum() < map->players.size())
 			{
 				if(!map->players[o->getOwner().getNum()].canAnyonePlay())
-					issues.emplace_back(QString("Object %1 is assinged to non-playable player %2").arg(o->instanceName.c_str(), o->getOwner().getStr().c_str()), true);
+					issues.emplace_back(QString("Object %1 is assigned to non-playable player %2").arg(o->instanceName.c_str(), o->getOwner().getStr().c_str()), true);
 			}
 			//checking towns
 			if(auto * ins = dynamic_cast<CGTownInstance*>(o.get()))
@@ -140,7 +140,7 @@ std::list<Validator::Issue> Validator::validate(const CMap * map)
 				{
 					if(ins->storedArtifact)
 					{
-						if(!map->allowedSpell[ins->storedArtifact->id.getNum()])
+						if(!map->allowedSpell[ins->storedArtifact->getId().getNum()])
 							issues.emplace_back(QString("Spell scroll %1 is prohibited by map settings").arg(ins->getObjectName().c_str()), false);
 					}
 					else

+ 14 - 18
server/CGameHandler.cpp

@@ -807,12 +807,13 @@ void CGameHandler::endBattleConfirm(const BattleInfo * battleInfo)
 		changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult.data->exp[finishingBattle->winnerSide]);
 	
 	BattleResultAccepted raccepted;
-	raccepted.army1 = const_cast<CArmedInstance*>(bEndArmy1);
-	raccepted.army2 = const_cast<CArmedInstance*>(bEndArmy2);
-	raccepted.hero1 = const_cast<CGHeroInstance*>(battleInfo->sides.at(0).hero);
-	raccepted.hero2 = const_cast<CGHeroInstance*>(battleInfo->sides.at(1).hero);
-	raccepted.exp[0] = battleResult.data->exp[0];
-	raccepted.exp[1] = battleResult.data->exp[1];
+	raccepted.heroResult[0].army = const_cast<CArmedInstance*>(bEndArmy1);
+	raccepted.heroResult[1].army = const_cast<CArmedInstance*>(bEndArmy2);
+	raccepted.heroResult[0].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(0).hero);
+	raccepted.heroResult[1].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(1).hero);
+	raccepted.heroResult[0].exp = battleResult.data->exp[0];
+	raccepted.heroResult[1].exp = battleResult.data->exp[1];
+	raccepted.winnerSide = finishingBattle->winnerSide;
 	sendAndApply(&raccepted);
 
 	queries.popIfTop(battleQuery);
@@ -4083,7 +4084,7 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
 	if(assemble)
 	{
 		CArtifact * combinedArt = VLC->arth->objects[assembleTo];
-		if(!combinedArt->constituents)
+		if(!combinedArt->isCombined())
 			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!");
 		if (!vstd::contains(ArtifactUtils::assemblyPossibilities(hero, destArtifact->getTypeId(),
 			ArtifactUtils::isSlotEquipment(artifactSlot)), combinedArt))
@@ -4102,11 +4103,11 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
 	}
 	else
 	{
-		if(!destArtifact->canBeDisassembled())
+		if(!destArtifact->isCombined())
 			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is not a combined artifact!");
 
 		if(ArtifactUtils::isSlotBackpack(artifactSlot)
-			&& !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->artType->constituents->size() - 1))
+			&& !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->artType->getConstituents().size() - 1))
 			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble but backpack is full!");
 
 		DisassembledArtifact da;
@@ -4159,9 +4160,9 @@ bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
 	{
 		const CArtifact * art = aid.toArtifact();
 		COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid artifact index to buy");
-		COMPLAIN_RET_FALSE_IF(art->warMachine == CreatureID::NONE, "War machine artifact required");
+		COMPLAIN_RET_FALSE_IF(art->getWarMachine() == CreatureID::NONE, "War machine artifact required");
 		COMPLAIN_RET_FALSE_IF(hero->hasArt(aid),"Hero already has this machine!");
-		const int price = art->price;
+		const int price = art->getPrice();
 		COMPLAIN_RET_FALSE_IF(getPlayerState(hero->getOwner())->resources[EGameResID::GOLD] < price, "Not enough gold!");
 
 		if ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid)
@@ -6808,17 +6809,12 @@ bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact
 		COMPLAIN_RET_FALSE_IF(!artType->canBePutAt(h, pos, false), "Cannot put artifact in that slot!");
 	}
 
-	CArtifactInstance * newArtInst = nullptr;
-	if(artType->canBeDisassembled())
-		newArtInst = new CCombinedArtifactInstance();
-	else
-		newArtInst = new CArtifactInstance();
-
+	auto * newArtInst = new CArtifactInstance();
 	newArtInst->artType = artType; // *NOT* via settype -> all bonus-related stuff must be done by NewArtifact apply
 
 	NewArtifact na;
 	na.art = newArtInst;
-	sendAndApply(&na); // -> updates a!!!, will create a on other machines
+	sendAndApply(&na); // -> updates newArtInst!!!
 
 	if(giveHeroArtifact(h, newArtInst, pos))
 		return true;

+ 11 - 4
server/CQuery.cpp

@@ -181,7 +181,7 @@ void CObjectVisitQuery::onExposure(QueryPtr topQuery)
 
 void Queries::popQuery(PlayerColor player, QueryPtr query)
 {
-	//LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
+	LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
 	if(topQuery(player) != query)
 	{
 		logGlobal->trace("Cannot remove, not a top!");
@@ -200,7 +200,7 @@ void Queries::popQuery(PlayerColor player, QueryPtr query)
 
 void Queries::popQuery(const CQuery &query)
 {
-	//LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
+	LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
 
 	assert(query.players.size());
 	for(auto player : query.players)
@@ -209,7 +209,14 @@ void Queries::popQuery(const CQuery &query)
 		if(top.get() == &query)
 			popQuery(top);
 		else
+		{
 			logGlobal->trace("Cannot remove query %s", query.toString());
+			logGlobal->trace("Queries found:");
+			for(auto q : queries[player])
+			{
+				logGlobal->trace(q->toString());
+			}
+		}
 	}
 }
 
@@ -230,7 +237,7 @@ void Queries::addQuery(QueryPtr query)
 
 void Queries::addQuery(PlayerColor player, QueryPtr query)
 {
-	//LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
+	LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
 	query->onAdding(player);
 	queries[player].push_back(query);
 }
@@ -242,7 +249,7 @@ QueryPtr Queries::topQuery(PlayerColor player)
 
 void Queries::popIfTop(QueryPtr query)
 {
-	//LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
+	LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
 	if(!query)
 		logGlobal->error("The query is nullptr! Ignoring.");
 

+ 1 - 3
test/entity/CArtifactTest.cpp

@@ -32,9 +32,7 @@ protected:
 
 TEST_F(CArtifactTest, RegistersIcons)
 {
-	subject->iconIndex = 4242;
-	subject->image = "Test1";
-	subject->large = "Test2";
+        subject-> setImage(4242, "Test1", "Test2");
 
 	auto cb = [this](auto && PH1, auto && PH2, auto && PH3, auto && PH4) 
 	{