Browse Source

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

Xilmi 1 year ago
parent
commit
5ee7061ab7
100 changed files with 2747 additions and 2737 deletions
  1. 1 1
      AI/BattleAI/BattleAI.cpp
  2. 2 1
      AI/BattleAI/BattleEvaluator.cpp
  3. 1 0
      CMakeLists.txt
  4. 37 3
      Mods/vcmi/config/vcmi/chinese.json
  5. 2 2
      client/CMT.h
  6. 25 122
      client/CMakeLists.txt
  7. 0 10
      client/CPlayerInterface.cpp
  8. 0 1
      client/ClientCommandManager.cpp
  9. 1 1
      client/ClientNetPackVisitors.h
  10. 3 2
      client/NetPacksClient.cpp
  11. 1 1
      client/battle/BattleAnimationClasses.cpp
  12. 1 1
      client/battle/BattleInterface.cpp
  13. 1 1
      client/battle/BattleProjectileController.cpp
  14. 37 30
      client/battle/BattleSiegeController.cpp
  15. 1 1
      client/battle/BattleSiegeController.h
  16. 1 1
      client/battle/BattleStacksController.cpp
  17. 0 1
      client/eventsSDL/InputSourceText.cpp
  18. 0 1
      client/eventsSDL/InputSourceTouch.cpp
  19. 2 2
      client/windows/InfoWindows.cpp
  20. 1 1
      clientapp/CFocusableHelper.cpp
  21. 0 0
      clientapp/CFocusableHelper.h
  22. 137 0
      clientapp/CMakeLists.txt
  23. 30 28
      clientapp/EntryPoint.cpp
  24. 11 0
      clientapp/StdInc.cpp
  25. 14 0
      clientapp/StdInc.h
  26. 0 0
      clientapp/VCMI_client.rc
  27. 0 0
      clientapp/ios/GameChatKeyboardHandler.h
  28. 0 0
      clientapp/ios/GameChatKeyboardHandler.m
  29. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/Contents.json
  30. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  31. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  32. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  33. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  34. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  35. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  36. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  37. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  38. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  39. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  40. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  41. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  42. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  43. 0 0
      clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  44. 0 0
      clientapp/ios/Images.xcassets/Contents.json
  45. 0 0
      clientapp/ios/Info.plist
  46. 0 0
      clientapp/ios/LaunchScreen.storyboard
  47. 0 0
      clientapp/ios/Settings.bundle/Root.plist
  48. 0 0
      clientapp/ios/Settings.bundle/en.lproj/Root.strings
  49. 0 0
      clientapp/ios/Settings.bundle/ru.lproj/Root.strings
  50. 0 0
      clientapp/ios/main.m
  51. 0 0
      clientapp/ios/startSDL.h
  52. 0 0
      clientapp/ios/startSDL.mm
  53. 0 0
      clientapp/ios/vcmi_logo.png
  54. 25 3
      config/buildingsLibrary.json
  55. 16 0
      config/schemas/townBuilding.json
  56. 26 2
      docs/modders/Entities_Format/Town_Building_Format.md
  57. 190 216
      launcher/translation/chinese.ts
  58. 190 212
      launcher/translation/czech.ts
  59. 192 162
      launcher/translation/english.ts
  60. 189 215
      launcher/translation/french.ts
  61. 190 216
      launcher/translation/german.ts
  62. 190 216
      launcher/translation/polish.ts
  63. 190 216
      launcher/translation/portuguese.ts
  64. 189 203
      launcher/translation/russian.ts
  65. 190 212
      launcher/translation/spanish.ts
  66. 190 216
      launcher/translation/ukrainian.ts
  67. 190 208
      launcher/translation/vietnamese.ts
  68. 1 0
      lib/CMakeLists.txt
  69. 2 4
      lib/CPlayerState.h
  70. 1 1
      lib/IGameCallback.h
  71. 5 7
      lib/StartInfo.h
  72. 18 22
      lib/battle/BattleInfo.cpp
  73. 6 5
      lib/battle/CBattleInfoCallback.cpp
  74. 11 8
      lib/battle/CBattleInfoEssentials.cpp
  75. 2 1
      lib/battle/CBattleInfoEssentials.h
  76. 1 14
      lib/bonuses/Bonus.h
  77. 2 4
      lib/campaign/CampaignState.h
  78. 3 0
      lib/entities/building/CBuilding.h
  79. 49 0
      lib/entities/building/TownFortifications.h
  80. 1 1
      lib/entities/faction/CTown.cpp
  81. 5 2
      lib/entities/faction/CTown.h
  82. 30 3
      lib/entities/faction/CTownHandler.cpp
  83. 1 10
      lib/json/JsonNode.h
  84. 3 1
      lib/mapObjects/CGMarket.h
  85. 54 64
      lib/mapObjects/CGTownInstance.cpp
  86. 3 0
      lib/mapObjects/CGTownInstance.h
  87. 5 0
      lib/mapObjects/IMarket.h
  88. 0 8
      lib/mapping/CMap.h
  89. 10 6
      lib/mapping/CMapHeader.h
  90. 6 0
      lib/modding/IdentifierStorage.cpp
  91. 2 0
      lib/modding/IdentifierStorage.h
  92. 1 1
      lib/networkPacks/NetPackVisitor.h
  93. 40 29
      lib/networkPacks/NetPacksLib.cpp
  94. 7 3
      lib/networkPacks/PacksForClient.h
  95. 1 12
      lib/networkPacks/PacksForLobby.h
  96. 1 4
      lib/rmg/CMapGenOptions.h
  97. 4 15
      lib/serializer/ESerializationVersion.h
  98. 1 1
      lib/serializer/RegisterTypes.h
  99. 2 1
      lib/spells/effects/Catapult.cpp
  100. 3 2
      lib/spells/effects/Moat.cpp

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -229,7 +229,7 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
 		{
 			auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
 
-			if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
+			if(wallState != EWallState::NONE && wallState != EWallState::DESTROYED)
 			{
 				targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
 				break;

+ 2 - 1
AI/BattleAI/BattleEvaluator.cpp

@@ -17,6 +17,7 @@
 #include "../../lib/CStopWatch.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/entities/building/TownFortifications.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
@@ -265,7 +266,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 	if(score <= EvaluationResult::INEFFECTIVE_SCORE
 		&& !stack->hasBonusOfType(BonusType::FLYING)
 		&& stack->unitSide() == BattleSide::ATTACKER
-		&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+	   && cb->getBattle(battleID)->battleGetFortifications().hasMoat)
 	{
 		auto brokenWallMoat = getBrokenWallMoatHexes();
 

+ 1 - 0
CMakeLists.txt

@@ -680,6 +680,7 @@ endif()
 
 if (ENABLE_CLIENT)
 	add_subdirectory(client)
+	add_subdirectory(clientapp)
 endif()
 
 if(ENABLE_SERVER)

+ 37 - 3
Mods/vcmi/config/vcmi/chinese.json

@@ -54,9 +54,9 @@
 	"vcmi.radialWheel.moveUp" : "上移",
 	"vcmi.radialWheel.moveDown" : "下移",
 	"vcmi.radialWheel.moveBottom" : "移到底端",
-	
+
 	"vcmi.spellBook.search" : "搜索中...",
-	
+
 	"vcmi.mainMenu.serverConnecting" : "连接中...",
 	"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
 	"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
@@ -162,6 +162,38 @@
 	"vcmi.systemOptions.otherGroup" : "其他设置", // unused right now
 	"vcmi.systemOptions.townsGroup" : "城镇画面",
 
+	"vcmi.statisticWindow.statistics" : "统计",
+	"vcmi.statisticWindow.tsvCopy" : "复制到剪切板",
+	"vcmi.statisticWindow.selectView" : "选择视角",
+	"vcmi.statisticWindow.value" : "值",
+	"vcmi.statisticWindow.title.overview" : "概况",
+	"vcmi.statisticWindow.title.resources" : "资源",
+	"vcmi.statisticWindow.title.income" : "收入",
+	"vcmi.statisticWindow.title.numberOfHeroes" : "英雄数量",
+	"vcmi.statisticWindow.title.numberOfTowns" : "城镇数量",
+	"vcmi.statisticWindow.title.numberOfArtifacts" : "宝物数量",
+	"vcmi.statisticWindow.title.numberOfDwellings" : "野外巢穴数量",
+	"vcmi.statisticWindow.title.numberOfMines" : "矿井数量",
+	"vcmi.statisticWindow.title.armyStrength" : "部队强度",
+	"vcmi.statisticWindow.title.experience" : "经验",
+	"vcmi.statisticWindow.title.resourcesSpentArmy" : "部队花费",
+	"vcmi.statisticWindow.title.resourcesSpentBuildings" : "建造花费",
+	"vcmi.statisticWindow.title.mapExplored" : "地图探索比例",
+	"vcmi.statisticWindow.param.playerName" : "玩家名称",
+	"vcmi.statisticWindow.param.daysSurvived" : "存活天数",
+	"vcmi.statisticWindow.param.maxHeroLevel" : "最大英雄等级",
+	"vcmi.statisticWindow.param.battleWinRatioHero" : "胜率(对英雄)",
+	"vcmi.statisticWindow.param.battleWinRatioNeutral" : "胜率(对中立生物)",
+	"vcmi.statisticWindow.param.battlesHero" : "战斗(对英雄)",
+	"vcmi.statisticWindow.param.battlesNeutral" : "战斗(对中立生物)",
+	"vcmi.statisticWindow.param.maxArmyStrength" : "最大部队强度",
+	"vcmi.statisticWindow.param.tradeVolume" : "交易量",
+	"vcmi.statisticWindow.param.obeliskVisited" : "方尖塔访问",
+	"vcmi.statisticWindow.icon.townCaptured" : "占领城镇",
+	"vcmi.statisticWindow.icon.strongestHeroDefeated" : "击败对手最强英雄",
+	"vcmi.statisticWindow.icon.grailFound" : "找到神器",
+	"vcmi.statisticWindow.icon.defeated" : "被击败",
+
 	"vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)",
 	"vcmi.systemOptions.fullscreenBorderless.help"  : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。该模式下,游戏会始终和桌面分辨率保持一致,无视设置的分辨率。 ",
 	"vcmi.systemOptions.fullscreenExclusive.hover"  : "全屏 (独占)",
@@ -628,5 +660,7 @@
 	"core.bonus.WATER_IMMUNITY.name": "水系免疫",
 	"core.bonus.WATER_IMMUNITY.description": "免疫水系魔法",
 	"core.bonus.WIDE_BREATH.name": "弧形焰息",
-	"core.bonus.WIDE_BREATH.description": "大范围喷吐攻击(目标左右以及后方共6格)"
+	"core.bonus.WIDE_BREATH.description": "大范围喷吐攻击(目标左右以及后方共6格)",
+	"core.bonus.DISINTEGRATE.name": "解体",
+	"core.bonus.DISINTEGRATE.description": "死亡后不会留下尸体"
 }

+ 2 - 2
client/CMT.h

@@ -20,8 +20,8 @@ extern SDL_Surface *screen;      // main screen surface
 extern SDL_Surface *screen2;     // and hlp surface (used to store not-active interfaces layer)
 extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 
-void handleQuit(bool ask = true);
-
 /// Notify user about encountered fatal error and terminate the game
+/// Defined in clientapp EntryPoint
 /// TODO: decide on better location for this method
 [[noreturn]] void handleFatalError(const std::string & message, bool terminate);
+void handleQuit(bool ask = true);

+ 25 - 122
client/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(client_SRCS
+set(vcmiclientcommon_SRCS
 	StdInc.cpp
 	../CCallback.cpp
 
@@ -178,7 +178,6 @@ set(client_SRCS
 
 	ArtifactsUIController.cpp
 	CGameInfo.cpp
-	CMT.cpp
 	CPlayerInterface.cpp
 	PlayerLocalState.cpp
 	CServerHandler.cpp
@@ -191,7 +190,7 @@ set(client_SRCS
 	ServerRunner.cpp
 )
 
-set(client_HEADERS
+set(vcmiclientcommon_HEADERS
 	StdInc.h
 
 	adventureMap/AdventureMapInterface.h
@@ -407,76 +406,50 @@ set(client_HEADERS
 )
 
 if(APPLE_IOS)
-	set(client_SRCS ${client_SRCS}
-		CFocusableHelper.cpp
-		ios/GameChatKeyboardHandler.m
-		ios/main.m
-		ios/startSDL.mm
+	set(vcmiclientcommon_SRCS ${vcmiclientcommon_SRCS}
 		ios/utils.mm
 	)
-	set(client_HEADERS ${client_HEADERS}
-		CFocusableHelper.h
-		ios/GameChatKeyboardHandler.h
-		ios/startSDL.h
+	set(vcmiclientcommon_HEADERS ${vcmiclientcommon_HEADERS}
 		ios/utils.h
 	)
 endif()
 
-assign_source_group(${client_SRCS} ${client_HEADERS} VCMI_client.rc)
-
-if(ANDROID)
-	add_library(vcmiclient SHARED ${client_SRCS} ${client_HEADERS})
-	set_target_properties(vcmiclient PROPERTIES
-		OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt
-	)
-else()
-	add_executable(vcmiclient ${client_SRCS} ${client_HEADERS})
-endif()
+assign_source_group(${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
+add_library(vcmiclientcommon STATIC ${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
 
 if(NOT ENABLE_STATIC_LIBS)
-	add_dependencies(vcmiclient
+	add_dependencies(vcmiclientcommon
 		BattleAI
 		EmptyAI
 		StupidAI
 		VCAI
 	)
 	if(ENABLE_NULLKILLER_AI)
-		add_dependencies(vcmiclient Nullkiller)
+		add_dependencies(vcmiclientcommon Nullkiller)
 	endif()
 endif()
 if(APPLE_IOS)
 	if(ENABLE_ERM)
-		add_dependencies(vcmiclient vcmiERM)
+		add_dependencies(vcmiclientcommon vcmiERM)
 	endif()
 	if(ENABLE_LUA)
-		add_dependencies(vcmiclient vcmiLua)
+		add_dependencies(vcmiclientcommon vcmiLua)
 	endif()
 endif()
 
 if(WIN32)
-	target_sources(vcmiclient PRIVATE "VCMI_client.rc")
-	set_target_properties(vcmiclient
+	set_target_properties(vcmiclientcommon
 		PROPERTIES
-			OUTPUT_NAME "VCMI_client"
-			PROJECT_LABEL "VCMI_client"
+			OUTPUT_NAME "VCMI_vcmiclientcommon"
+			PROJECT_LABEL "VCMI_vcmiclientcommon"
 	)
-	set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclient)
+	set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclientcommon)
 	if(NOT ENABLE_DEBUG_CONSOLE)
-		set_target_properties(vcmiclient PROPERTIES WIN32_EXECUTABLE)
-		target_link_libraries(vcmiclient SDL2::SDL2main)
-	endif()
-	target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
-
-# TODO: very hacky, find proper solution to copy AI dlls into bin dir
-	if(MSVC)
-		add_custom_command(TARGET vcmiclient POST_BUILD
-			WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
-			COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
-			COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
-		)
+		target_link_libraries(vcmiclientcommon SDL2::SDL2main)
 	endif()
+	target_compile_definitions(vcmiclientcommon PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
 elseif(APPLE_IOS)
-	target_link_libraries(vcmiclient PRIVATE
+	target_link_libraries(vcmiclientcommon PRIVATE
 		iOS_utils
 
 		# FFmpeg
@@ -488,101 +461,31 @@ elseif(APPLE_IOS)
 		"-framework CoreMedia"
 		"-framework VideoToolbox"
 	)
-
-	set_target_properties(vcmiclient PROPERTIES
-		MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
-		XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
-		XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "$(CODE_SIGNING_ALLOWED_FOR_APPS)"
-		XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
-	)
-
-	foreach(XCODE_RESOURCE LaunchScreen.storyboard Images.xcassets Settings.bundle vcmi_logo.png)
-		set(XCODE_RESOURCE_PATH ios/${XCODE_RESOURCE})
-		target_sources(vcmiclient PRIVATE ${XCODE_RESOURCE_PATH})
-		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
-
-		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
-		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
-			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
-		endif()
-	endforeach()
-
-	set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
 endif()
 
-target_link_libraries(vcmiclient PRIVATE vcmiservercommon)
-if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER)
-	target_link_libraries(vcmiclient PRIVATE vcmilauncher)
-endif()
+target_link_libraries(vcmiclientcommon PRIVATE vcmiservercommon)
 
-target_link_libraries(vcmiclient PRIVATE
+target_link_libraries(vcmiclientcommon PUBLIC
 		vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF
 )
 
 if(ffmpeg_LIBRARIES)
-	target_link_libraries(vcmiclient PRIVATE
+	target_link_libraries(vcmiclientcommon PRIVATE
 		${ffmpeg_LIBRARIES}
 	)
 else()
-	target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO)
+	target_compile_definitions(vcmiclientcommon PRIVATE DISABLE_VIDEO)
 endif()
 
-target_include_directories(vcmiclient PUBLIC
+target_include_directories(vcmiclientcommon PUBLIC
 	${CMAKE_CURRENT_SOURCE_DIR}
 )
 
 if (ffmpeg_INCLUDE_DIRS)
-	target_include_directories(vcmiclient PRIVATE
+	target_include_directories(vcmiclientcommon PRIVATE
 		${ffmpeg_INCLUDE_DIRS}
 	)
 endif()
 
-vcmi_set_output_dir(vcmiclient "")
-enable_pch(vcmiclient)
-
-if(APPLE_IOS)
-	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
-	add_custom_command(TARGET vcmiclient POST_BUILD
-		COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
-		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
-		COMMAND ios/rpath_remove_symlinks.sh
-		COMMAND ios/codesign.sh
-		WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
-	)
-	install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack
-elseif(ANDROID)
-	find_program(androidDeployQt androiddeployqt
-		PATHS "${qtBinDir}"
-	)
-	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
-
-	add_custom_target(android_deploy ALL
-		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
-		COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
-		COMMAND_EXPAND_LISTS
-		VERBATIM
-		COMMENT "Create android package"
-	)
-	add_dependencies(android_deploy vcmiclient)
-else()
-	install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
-endif()
-
-#install icons and desktop file on Linux
-if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
-	#FIXME: move to client makefile?
-	foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048)
-		install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png"
-			DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps"
-			RENAME vcmiclient.png
-		)
-	endforeach()
-
-	install(FILES icons/vcmiclient.svg
-		DESTINATION share/icons/hicolor/scalable/apps
-		RENAME vcmiclient.svg
-	)
-	install(FILES icons/vcmiclient.desktop
-		DESTINATION share/applications
-	)
-endif()
+vcmi_set_output_dir(vcmiclientcommon "")
+enable_pch(vcmiclientcommon)

+ 0 - 10
client/CPlayerInterface.cpp

@@ -13,7 +13,6 @@
 #include <vcmi/Artifact.h>
 
 #include "CGameInfo.h"
-#include "CMT.h"
 #include "CServerHandler.h"
 #include "HeroMovementController.h"
 #include "PlayerLocalState.h"
@@ -1639,15 +1638,6 @@ void CPlayerInterface::showMarketWindow(const IMarket * market, const CGHeroInst
 		cb->selectionMade(0, queryID);
 	};
 
-	if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && market->getArtifactsStorage() == nullptr)
-	{
-		// compatibility check, safe to remove for 1.6
-		// 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class
-		static_assert(ESerializationVersion::RELEASE_143 < ESerializationVersion::CURRENT, "Please remove this compatibility check once it no longer needed");
-		onWindowClosed();
-		return;
-	}
-
 	if(market->allowsTrade(EMarketMode::ARTIFACT_EXP) && visitor->getAlignment() != EAlignment::EVIL)
 		GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::ARTIFACT_EXP);
 	else if(market->allowsTrade(EMarketMode::CREATURE_EXP) && visitor->getAlignment() != EAlignment::GOOD)

+ 0 - 1
client/ClientCommandManager.cpp

@@ -39,7 +39,6 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/logging/VisualLogger.h"
-#include "CMT.h"
 #include "../lib/serializer/Connection.h"
 
 #ifdef SCRIPTING_ENABLED

+ 1 - 1
client/ClientNetPackVisitors.h

@@ -47,7 +47,7 @@ public:
 	void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
 	void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) override;
 	void visitPutArtifact(PutArtifact & pack) override;
-	void visitEraseArtifact(EraseArtifact & pack) override;
+	void visitEraseArtifact(BulkEraseArtifacts & pack) override;
 	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
 	void visitAssembledArtifact(AssembledArtifact & pack) override;
 	void visitDisassembledArtifact(DisassembledArtifact & pack) override;

+ 3 - 2
client/NetPacksClient.cpp

@@ -290,9 +290,10 @@ void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 		callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
 }
 
-void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
+void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
 {
-	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
+	for(const auto & slotErase : pack.posPack)
+		callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));
 }
 
 void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)

+ 1 - 1
client/battle/BattleAnimationClasses.cpp

@@ -161,7 +161,7 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn
 const CCreature * AttackAnimation::getCreature() const
 {
 	if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS)
-		return owner.siegeController->getTurretCreature();
+		return owner.siegeController->getTurretCreature(attackingStack->initialPosition);
 	else
 		return attackingStack->unitType();
 }

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -85,7 +85,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
 	this->army2 = army2;
 
 	const CGTownInstance *town = getBattle()->battleGetDefendedTown();
-	if(town && town->hasFort())
+	if(town && town->fortificationsLevel().wallsHealth > 0)
 		siegeController.reset(new BattleSiegeController(*this, town));
 
 	windowObject = std::make_shared<BattleWindow>(*this);

+ 1 - 1
client/battle/BattleProjectileController.cpp

@@ -160,7 +160,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
 	const CCreature * creature = stack->unitType();
 
 	if(creature->getId() == CreatureID::ARROW_TOWERS)
-		creature = owner.siegeController->getTurretCreature();
+		creature = owner.siegeController->getTurretCreature(stack->initialPosition);
 
 	if(creature->animation.missileFrameAngles.empty())
 	{

+ 37 - 30
client/battle/BattleSiegeController.cpp

@@ -27,6 +27,7 @@
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
+#include "../../lib/entities/building/TownFortifications.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 
@@ -34,30 +35,27 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
 {
 	auto getImageIndex = [&]() -> int
 	{
-		bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER);
+		int health = static_cast<int>(state);
 
-		switch (state)
+		switch (what)
 		{
-		case EWallState::REINFORCED :
-			return 1;
-		case EWallState::INTACT :
-			if (town->hasBuilt(BuildingID::CASTLE))
-				return 2; // reinforced walls were damaged
-			else
-				return 1;
-		case EWallState::DAMAGED :
-			// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
-			if (isTower)
-				return 1;
-			else
-				return 2;
-		case EWallState::DESTROYED :
-			if (isTower)
-				return 2;
-			else
+			case EWallVisual::KEEP:
+			case EWallVisual::BOTTOM_TOWER:
+			case EWallVisual::UPPER_TOWER:
+				if (health > 0)
+					return 1;
+				else
+					return 2;
+			default:
+			{
+				int healthTotal = town->fortificationsLevel().wallsHealth;
+				if (healthTotal == health)
+					return 1;
+				if (health > 0)
+					return 2;
 				return 3;
-		}
-		return 1;
+			}
+		};
 	};
 
 	const std::string & prefix = town->town->clientInfo.siegePrefix;
@@ -128,16 +126,15 @@ ImagePath BattleSiegeController::getBattleBackgroundName() const
 
 bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) const
 {
-	//FIXME: use this instead of buildings test?
-	//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
+	const auto & fortifications = town->fortificationsLevel();
 
 	switch (what)
 	{
-	case EWallVisual::MOAT:              return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
-	case EWallVisual::MOAT_BANK:         return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
-	case EWallVisual::KEEP_BATTLEMENT:   return town->hasBuilt(BuildingID::CITADEL) && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
-	case EWallVisual::UPPER_BATTLEMENT:  return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
-	case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
+	case EWallVisual::MOAT:              return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
+	case EWallVisual::MOAT_BANK:         return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
+	case EWallVisual::KEEP_BATTLEMENT:   return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
+	case EWallVisual::UPPER_BATTLEMENT:  return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
+	case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
 	default:                             return true;
 	}
 }
@@ -186,9 +183,19 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 	}
 }
 
-const CCreature *BattleSiegeController::getTurretCreature() const
+const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) const
 {
-	return town->town->clientInfo.siegeShooter.toCreature();
+	switch (position)
+	{
+		case BattleHex::CASTLE_CENTRAL_TOWER:
+			return town->fortificationsLevel().citadelShooter.toCreature();
+		case BattleHex::CASTLE_UPPER_TOWER:
+			return town->fortificationsLevel().upperTowerShooter.toCreature();
+		case BattleHex::CASTLE_BOTTOM_TOWER:
+			return town->fortificationsLevel().lowerTowerShooter.toCreature();
+	}
+
+	throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex));
 }
 
 Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const

+ 1 - 1
client/battle/BattleSiegeController.h

@@ -104,7 +104,7 @@ public:
 	/// queries from other battle controllers
 	bool isAttackableByCatapult(BattleHex hex) const;
 	ImagePath getBattleBackgroundName() const;
-	const CCreature *getTurretCreature() const;
+	const CCreature *getTurretCreature(BattleHex turretPosition) const;
 	Point getTurretCreaturePosition( BattleHex position ) const;
 
 	const CGTownInstance *getSiegedTown() const;

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -191,7 +191,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
 	{
 		assert(owner.siegeController);
 
-		const CCreature *turretCreature = owner.siegeController->getTurretCreature();
+		const CCreature *turretCreature = owner.siegeController->getTurretCreature(stack->initialPosition);
 
 		stackAnimation[stack->unitId()] = AnimationControls::getAnimation(turretCreature);
 		stackAnimation[stack->unitId()]->pos.h = turretCreatureAnimationHeight;

+ 0 - 1
client/eventsSDL/InputSourceText.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "InputSourceText.h"
 
-#include "../CMT.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/EventDispatcher.h"
 #include "../render/IScreenHandler.h"

+ 0 - 1
client/eventsSDL/InputSourceTouch.cpp

@@ -14,7 +14,6 @@
 #include "InputHandler.h"
 
 #include "../../lib/CConfigHandler.h"
-#include "../CMT.h"
 #include "../CGameInfo.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"

+ 2 - 2
client/windows/InfoWindows.cpp

@@ -271,7 +271,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
 	: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position))
 {
 	InfoAboutTown iah;
-	LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentTown()); //todo: should this be nearest hero?
+	LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero?
 
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CTownTooltip>(Point(9, 10), iah);
@@ -281,7 +281,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
 	: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), toScreen(position))
 {
 	InfoAboutHero iah;
-	LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero()); //todo: should this be nearest hero?
+	LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero?
 
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CHeroTooltip>(Point(9, 10), iah);

+ 1 - 1
client/CFocusableHelper.cpp → clientapp/CFocusableHelper.cpp

@@ -9,7 +9,7 @@
  */
 #include "StdInc.h"
 #include "CFocusableHelper.h"
-#include "widgets/CTextInput.h"
+#include "../client/widgets/CTextInput.h"
 
 void removeFocusFromActiveInput()
 {

+ 0 - 0
client/CFocusableHelper.h → clientapp/CFocusableHelper.h


+ 137 - 0
clientapp/CMakeLists.txt

@@ -0,0 +1,137 @@
+set(clientapp_SRCS
+		StdInc.cpp
+		EntryPoint.cpp
+)
+
+set(clientapp_HEADERS
+		StdInc.h
+)
+
+if(APPLE_IOS)
+	set(clientapp_SRCS ${clientapp_SRCS}
+		CFocusableHelper.cpp
+		ios/GameChatKeyboardHandler.m
+		ios/main.m
+		ios/startSDL.mm
+	)
+	set(clientapp_HEADERS ${clientapp_HEADERS}
+		CFocusableHelper.h
+		ios/GameChatKeyboardHandler.h
+		ios/startSDL.h
+	)
+endif()
+
+assign_source_group(${clientapp_SRCS} ${clientapp_HEADERS})
+
+if(ANDROID)
+	add_library(vcmiclient SHARED ${clientapp_SRCS} ${clientapp_HEADERS})
+	set_target_properties(vcmiclient PROPERTIES
+		OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt
+	)
+else()
+	add_executable(vcmiclient ${clientapp_SRCS} ${clientapp_HEADERS})
+endif()
+
+target_link_libraries(vcmiclient PRIVATE vcmiclientcommon)
+
+if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER)
+	target_link_libraries(vcmiclient PRIVATE vcmilauncher)
+endif()
+
+target_include_directories(vcmiclient
+	PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+if(WIN32)
+	target_sources(vcmiclient PRIVATE "VCMI_client.rc")
+	set_target_properties(vcmiclient
+		PROPERTIES
+			OUTPUT_NAME "VCMI_client"
+			PROJECT_LABEL "VCMI_client"
+	)
+	set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclient)
+	if(NOT ENABLE_DEBUG_CONSOLE)
+		set_target_properties(vcmiclient PROPERTIES WIN32_EXECUTABLE)
+		target_link_libraries(vcmiclient SDL2::SDL2main)
+	endif()
+	target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
+
+	# TODO: very hacky, find proper solution to copy AI dlls into bin dir
+	if(MSVC)
+		add_custom_command(TARGET vcmiclient POST_BUILD
+			WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
+			COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
+			COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
+		)
+	endif()
+elseif(APPLE_IOS)
+	set_target_properties(vcmiclient PROPERTIES
+		MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
+		XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
+		XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "$(CODE_SIGNING_ALLOWED_FOR_APPS)"
+		XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
+	)
+
+	foreach(XCODE_RESOURCE LaunchScreen.storyboard Images.xcassets Settings.bundle vcmi_logo.png)
+		set(XCODE_RESOURCE_PATH ios/${XCODE_RESOURCE})
+		target_sources(vcmiclient PRIVATE ${XCODE_RESOURCE_PATH})
+		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+
+		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
+		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
+			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
+		endif()
+	endforeach()
+
+	set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
+endif()
+
+vcmi_set_output_dir(vcmiclient "")
+enable_pch(vcmiclient)
+
+if(APPLE_IOS)
+	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
+	add_custom_command(TARGET vcmiclient POST_BUILD
+		COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
+		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
+		COMMAND ios/rpath_remove_symlinks.sh
+		COMMAND ios/codesign.sh
+		WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+	)
+	install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack
+elseif(ANDROID)
+	find_program(androidDeployQt androiddeployqt
+		PATHS "${qtBinDir}"
+	)
+	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
+
+	add_custom_target(android_deploy ALL
+		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
+		COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
+		COMMAND_EXPAND_LISTS
+		VERBATIM
+		COMMENT "Create android package"
+	)
+	add_dependencies(android_deploy vcmiclient)
+else()
+	install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
+endif()
+
+#install icons and desktop file on Linux
+if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
+	#FIXME: move to client makefile?
+	foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048)
+		install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png"
+			DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps"
+			RENAME vcmiclient.png
+		)
+	endforeach()
+
+	install(FILES icons/vcmiclient.svg
+		DESTINATION share/icons/hicolor/scalable/apps
+		RENAME vcmiclient.svg
+	)
+	install(FILES icons/vcmiclient.desktop
+		DESTINATION share/applications
+	)
+endif()

+ 30 - 28
client/CMT.cpp → clientapp/EntryPoint.cpp

@@ -1,5 +1,5 @@
 /*
- * CMT.cpp, part of VCMI engine
+ * EntryPoint.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,38 +8,38 @@
  *
  */
 
-// CMT.cpp : Defines the entry point for the console application.
+// EntryPoint.cpp : Defines the entry point for the console application.
+
 #include "StdInc.h"
-#include "CMT.h"
-
-#include "CGameInfo.h"
-#include "mainmenu/CMainMenu.h"
-#include "media/CEmptyVideoPlayer.h"
-#include "media/CMusicHandler.h"
-#include "media/CSoundHandler.h"
-#include "media/CVideoHandler.h"
-#include "gui/CursorHandler.h"
-#include "eventsSDL/InputHandler.h"
-#include "CPlayerInterface.h"
-#include "gui/CGuiHandler.h"
-#include "gui/WindowHandler.h"
-#include "CServerHandler.h"
-#include "ClientCommandManager.h"
-#include "windows/CMessage.h"
-#include "windows/InfoWindows.h"
-#include "render/IScreenHandler.h"
-#include "render/IRenderHandler.h"
-#include "render/Graphics.h"
-
-#include "../lib/CConfigHandler.h"
-#include "../lib/texts/CGeneralTextHandler.h"
+#include "../Global.h"
+
+#include "../client/CGameInfo.h"
+#include "../client/ClientCommandManager.h"
+#include "../client/CMT.h"
+#include "../client/CPlayerInterface.h"
+#include "../client/CServerHandler.h"
+#include "../client/eventsSDL/InputHandler.h"
+#include "../client/gui/CGuiHandler.h"
+#include "../client/gui/CursorHandler.h"
+#include "../client/gui/WindowHandler.h"
+#include "../client/mainmenu/CMainMenu.h"
+#include "../client/media/CEmptyVideoPlayer.h"
+#include "../client/media/CMusicHandler.h"
+#include "../client/media/CSoundHandler.h"
+#include "../client/media/CVideoHandler.h"
+#include "../client/render/Graphics.h"
+#include "../client/render/IRenderHandler.h"
+#include "../client/render/IScreenHandler.h"
+#include "../client/windows/CMessage.h"
+#include "../client/windows/InfoWindows.h"
+
 #include "../lib/CThreadHelper.h"
 #include "../lib/ExceptionsCommon.h"
-#include "../lib/VCMIDirs.h"
-#include "../lib/VCMI_Lib.h"
 #include "../lib/filesystem/Filesystem.h"
-
 #include "../lib/logging/CBasicLogConfigurator.h"
+#include "../lib/texts/CGeneralTextHandler.h"
+#include "../lib/VCMI_Lib.h"
+#include "../lib/VCMIDirs.h"
 
 #include <boost/program_options.hpp>
 #include <vstd/StringUtils.h>
@@ -525,6 +525,8 @@ void handleQuit(bool ask)
 		CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
 }
 
+/// Notify user about encountered fatal error and terminate the game
+/// TODO: decide on better location for this method
 void handleFatalError(const std::string & message, bool terminate)
 {
 	logGlobal->error("FATAL ERROR ENCOUNTERED, VCMI WILL NOW TERMINATE");

+ 11 - 0
clientapp/StdInc.cpp

@@ -0,0 +1,11 @@
+/*
+ * StdInc.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
+ *
+ */
+// Creates the precompiled header
+#include "StdInc.h"

+ 14 - 0
clientapp/StdInc.h

@@ -0,0 +1,14 @@
+/*
+ * StdInc.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 "../Global.h"
+
+VCMI_LIB_USING_NAMESPACE

+ 0 - 0
client/VCMI_client.rc → clientapp/VCMI_client.rc


+ 0 - 0
client/ios/GameChatKeyboardHandler.h → clientapp/ios/GameChatKeyboardHandler.h


+ 0 - 0
client/ios/GameChatKeyboardHandler.m → clientapp/ios/GameChatKeyboardHandler.m


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/Contents.json → clientapp/ios/Images.xcassets/AppIcon.appiconset/Contents.json


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/AppIcon.appiconset/[email protected] → clientapp/ios/Images.xcassets/AppIcon.appiconset/[email protected]


+ 0 - 0
client/ios/Images.xcassets/Contents.json → clientapp/ios/Images.xcassets/Contents.json


+ 0 - 0
client/ios/Info.plist → clientapp/ios/Info.plist


+ 0 - 0
client/ios/LaunchScreen.storyboard → clientapp/ios/LaunchScreen.storyboard


+ 0 - 0
client/ios/Settings.bundle/Root.plist → clientapp/ios/Settings.bundle/Root.plist


+ 0 - 0
client/ios/Settings.bundle/en.lproj/Root.strings → clientapp/ios/Settings.bundle/en.lproj/Root.strings


+ 0 - 0
client/ios/Settings.bundle/ru.lproj/Root.strings → clientapp/ios/Settings.bundle/ru.lproj/Root.strings


+ 0 - 0
client/ios/main.m → clientapp/ios/main.m


+ 0 - 0
client/ios/startSDL.h → clientapp/ios/startSDL.h


+ 0 - 0
client/ios/startSDL.mm → clientapp/ios/startSDL.mm


+ 0 - 0
client/ios/vcmi_logo.png → clientapp/ios/vcmi_logo.png


+ 25 - 3
config/buildingsLibrary.json

@@ -19,9 +19,31 @@
 		]
 	},
 	"shipyard":       { "id" : 6 },
-	"fort":           { "id" : 7 },
-	"citadel":        { "id" : 8,  "upgrades" : "fort" },
-	"castle":         { "id" : 9,  "upgrades" : "citadel" },
+	"fort": {
+		"id" : 7,
+		"fortifications" : {
+			"wallsHealth" : 2
+		}
+	},
+	
+	"citadel": {
+		"id" : 8,
+		"upgrades" : "fort",
+		"fortifications" : {
+			"citadelHealth" : 2,
+			"hasMoat" : true
+		}
+	},
+	
+	"castle": {
+		"id" : 9,
+		"upgrades" : "citadel",
+		"fortifications" : {
+			"wallsHealth" : 3,
+			"upperTowerHealth" : 2,
+			"lowerTowerHealth" : 2
+		}
+	},
 	
 	"villageHall": {
 		"id" : 10,

+ 16 - 0
config/schemas/townBuilding.json

@@ -65,6 +65,22 @@
 			"description" : "Optional, configuration of building that can be activated by visiting hero",
 			"$ref" : "rewardable.json"
 		},
+		"firtufications" : {
+			"type" : "object",
+			"additionalProperties" : false,
+			"description" : "Fortifications provided by this buildings, if any",
+			"properties" : {
+				"citadelShooter" :    { "type" : "string", "description" : "Creature ID of shooter located in central keep (citadel). Used only if citadel is present." },
+				"upperTowerShooter" : { "type" : "string", "description" : "Creature ID of shooter located in upper tower. Used only if upper tower is present." },
+				"lowerTowerShooter" : { "type" : "string", "description" : "Creature ID of shooter located in lower tower. Used only if lower tower is present." },
+
+				"wallsHealth" :       { "type" : "number", "description" : "Maximum health of destructible walls. Walls are only present if their health is above zero" },
+				"citadelHealth" :     { "type" : "number", "description" : "Maximum health of central tower or 0 if not present. Requires walls presence." },
+				"upperTowerHealth" :  { "type" : "number", "description" : "Maximum health of upper tower or 0 if not present. Requires walls presence." },
+				"lowerTowerHealth" :  { "type" : "number", "description" : "Maximum health of lower tower or 0 if not present. Requires walls presence." },
+				"hasMoat" :           { "type" : "boolean","description" : "If set to true, moat will be placed in front of the walls. Requires walls presence." }
+			}
+		},
 		"cost" : {
 			"type" : "object",
 			"additionalProperties" : false,

+ 26 - 2
docs/modders/Entities_Format/Town_Building_Format.md

@@ -157,9 +157,33 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
 	"produce" : { 
 		"sulfur" : 1,
 		"gold" : 2000
-	}, 
+	},
+	
+	// Optional, allows this building to add fortifications during siege
+	"fortifications" : {
+		// Maximum health of destructible walls. Walls are only present if their health is above zero".
+		// Presence of walls is required for all other fortification types
+		"wallsHealth" : 3,
+
+		// If set to true, moat will be placed in front of the walls. Requires walls presence.
+		"hasMoat" : true
+
+		// Maximum health of central tower or 0 if not present. Requires walls presence.
+		"citadelHealth" : 2,
+		// Maximum health of upper tower or 0 if not present. Requires walls presence.
+		"upperTowerHealth" : 2,
+		// Maximum health of lower tower or 0 if not present. Requires walls presence.
+		"lowerTowerHealth" : 2,
+
+		// Creature ID of shooter located in central keep (citadel). Used only if citadel is present.
+		"citadelShooter" : "archer",
+		// Creature ID of shooter located in upper tower. Used only if upper tower is present.
+		"upperTowerShooter" : "archer",
+		// Creature ID of shooter located in lower tower. Used only if lower tower is present.
+		"lowerTowerShooter" : "archer",
+	},
 
-    //determine how this building can be built. Possible values are:
+	//determine how this building can be built. Possible values are:
 	// normal  - default value. Fulfill requirements, use resources, spend one day
 	// auto    - building appears when all requirements are built
 	// special - building can not be built manually

File diff suppressed because it is too large
+ 190 - 216
launcher/translation/chinese.ts


File diff suppressed because it is too large
+ 190 - 212
launcher/translation/czech.ts


+ 192 - 162
launcher/translation/english.ts

@@ -24,65 +24,65 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="227"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="220"/>
         <source>Build Information</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="189"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="182"/>
         <source>User data directory</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="88"/>
         <location filename="../aboutProject/aboutproject_moc.ui" line="95"/>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="102"/>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="168"/>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="254"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="161"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="247"/>
         <source>Open</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="128"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="121"/>
         <source>Check for updates</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="196"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="189"/>
         <source>Game version</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="121"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="114"/>
         <source>Log files directory</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="114"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="107"/>
         <source>Data Directories</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="175"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="168"/>
         <source>Game data directory</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="182"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="175"/>
         <source>Operating System</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="234"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="227"/>
         <source>Configuration files directory</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="297"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="290"/>
         <source>Project homepage</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../aboutProject/aboutproject_moc.ui" line="310"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="303"/>
         <source>Report a bug</source>
         <translation type="unfinished"></translation>
     </message>
@@ -233,7 +233,7 @@
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.ui" line="163"/>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="348"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="350"/>
         <source>Description</source>
         <translation type="unfinished"></translation>
     </message>
@@ -293,179 +293,179 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="283"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="285"/>
         <source>Mod name</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="284"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="286"/>
         <source>Installed version</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="285"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="287"/>
         <source>Latest version</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="288"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="290"/>
         <source>Size</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="290"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="292"/>
         <source>Download size</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="292"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="294"/>
         <source>Authors</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="295"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="297"/>
         <source>License</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="298"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="300"/>
         <source>Contact</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="307"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="309"/>
         <source>Compatibility</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="309"/>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="317"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="311"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="319"/>
         <source>Required VCMI version</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="317"/>
         <source>Supported VCMI version</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="317"/>
         <source>please upgrade mod</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="187"/>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="802"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="189"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="809"/>
         <source>mods repository index</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="317"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="319"/>
         <source>or newer</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="320"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="322"/>
         <source>Supported VCMI versions</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="344"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="346"/>
         <source>Languages</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="346"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="348"/>
         <source>Required mods</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="347"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="349"/>
         <source>Conflicting mods</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="352"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="354"/>
         <source>This mod can not be installed or enabled because the following dependencies are not present</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="353"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="355"/>
         <source>This mod can not be enabled because the following mods are incompatible with it</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="354"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="356"/>
         <source>This mod cannot be disabled because it is required by the following mods</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="355"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="357"/>
         <source>This mod cannot be uninstalled or updated because it is required by the following mods</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="356"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="358"/>
         <source>This is a submod and it cannot be installed or uninstalled separately from its parent mod</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="371"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="373"/>
         <source>Notes</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="633"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="639"/>
         <source>All supported files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="633"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="639"/>
         <source>Maps</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="633"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="639"/>
         <source>Campaigns</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="633"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="639"/>
         <source>Configs</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="633"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="639"/>
         <source>Mods</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="634"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="640"/>
         <source>Select files (configs, mods, maps, campaigns) to install...</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="658"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="665"/>
         <source>Replace config file?</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="658"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="665"/>
         <source>Do you want to replace %1?</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="701"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="708"/>
         <source>Downloading %1. %p% (%v MB out of %m MB) finished</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="726"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="733"/>
         <source>Download failed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="727"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="734"/>
         <source>Unable to download all files.
 
 Encountered errors:
@@ -474,40 +474,40 @@ Encountered errors:
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="728"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="735"/>
         <source>
 
 Install successfully downloaded?</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="874"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="881"/>
         <source>Installing mod %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="943"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="950"/>
         <source>Operation failed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="944"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="951"/>
         <source>Encountered errors:
 </source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="973"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="986"/>
         <source>screenshots</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="979"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="992"/>
         <source>Screenshot %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../modManager/cmodlistview_moc.cpp" line="278"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="280"/>
         <source>Mod is incompatible</source>
         <translation type="unfinished"></translation>
     </message>
@@ -611,242 +611,272 @@ Install successfully downloaded?</source>
 <context>
     <name>CSettingsView</name>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="85"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="90"/>
         <source>Off</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="309"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="307"/>
         <source>Artificial Intelligence</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1080"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1072"/>
         <source>Interface Scaling</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="930"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="924"/>
         <source>Neutral AI in battles</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="742"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="738"/>
         <source>Enemy AI in battles</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="826"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="821"/>
         <source>Additional repository</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="937"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="931"/>
         <source>Adventure Map Allies</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="492"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="490"/>
         <source>Online Lobby port</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="333"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="331"/>
         <source>Autocombat AI in battles</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="354"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="352"/>
         <source>Sticks Sensitivity</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="803"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="618"/>
+        <source>Automatic (Linear)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="798"/>
         <source>Haptic Feedback</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="840"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="835"/>
         <source>Software Cursor</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="139"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1166"/>
+        <source>Automatic</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1171"/>
+        <source>None</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1176"/>
+        <source>xBRZ x2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1181"/>
+        <source>xBRZ x3</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1186"/>
+        <source>xBRZ x4</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="138"/>
         <source>Online Lobby address</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="899"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1158"/>
         <source>Upscaling Filter</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="319"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="317"/>
         <source>Use Relative Pointer Mode</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="612"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="608"/>
         <source>Nearest</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="617"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="613"/>
         <source>Linear</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="622"/>
-        <source>Best (Linear)</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="755"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="750"/>
         <source>Input - Touchscreen</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="906"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="900"/>
         <source>Adventure Map Enemies</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1152"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1144"/>
         <source>Show Tutorial again</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1159"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1151"/>
         <source>Reset</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="860"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="854"/>
         <source>Network</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="540"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="536"/>
         <source>Audio</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="847"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="842"/>
         <source>Relative Pointer Speed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1145"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1137"/>
         <source>Music Volume</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="772"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="767"/>
         <source>Ignore SSL errors</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="950"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="943"/>
         <source>Input - Mouse</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="347"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="345"/>
         <source>Long Touch Duration</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="116"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="115"/>
         <source>%</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1045"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1037"/>
         <source>Controller Click Tolerance</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="361"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="359"/>
         <source>Touch Tap Tolerance</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1028"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1020"/>
         <source>Input - Controller</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1094"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1086"/>
         <source>Sound Volume</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="404"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="402"/>
         <source>Windowed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="409"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="407"/>
         <source>Borderless fullscreen</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="414"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="412"/>
         <source>Exclusive fullscreen</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="782"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="777"/>
         <source>Autosave limit (0 = off)</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1038"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="893"/>
+        <source>Downscaling Filter</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1030"/>
         <source>Framerate Limit</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="765"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="760"/>
         <source>Autosave prefix</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="833"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="828"/>
         <source>Mouse Click Tolerance</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="95"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="94"/>
         <source>Sticks Acceleration</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1015"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1008"/>
         <source>empty = map name prefix</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="102"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="101"/>
         <source>Refresh now</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="250"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="249"/>
         <source>Default repository</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="296"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="295"/>
         <source>Renderer</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="83"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="88"/>
         <source>On</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="391"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="389"/>
         <source>Select display mode for game
 
 Windowed - game will run inside a window that covers part of your screen
@@ -857,92 +887,92 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="132"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="131"/>
         <source>Reserved screen area</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="270"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="269"/>
         <source>Heroes III Translation</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="650"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="646"/>
         <source>Check on startup</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="326"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="324"/>
         <source>Fullscreen</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="65"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="64"/>
         <source>General</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="211"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="210"/>
         <source>VCMI Language</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="1087"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="1079"/>
         <source>Resolution</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="796"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="791"/>
         <source>Autosave</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="597"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="593"/>
         <source>VSync</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="340"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="338"/>
         <source>Display index</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="870"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="864"/>
         <source>Network port</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="524"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="521"/>
         <source>Video</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.ui" line="454"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="452"/>
         <source>Show intro</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="494"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="503"/>
         <source>Active</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="499"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="508"/>
         <source>Disabled</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="500"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="509"/>
         <source>Enable</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="505"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="514"/>
         <source>Not Installed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="506"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="515"/>
         <source>Install</source>
         <translation type="unfinished"></translation>
     </message>
@@ -998,12 +1028,12 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="176"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="169"/>
         <source>Have a question? Found a bug? Want to help? Join us!</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="185"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="178"/>
         <source>Thank you for installing VCMI!
 
 Before you can start playing, there are a few more steps that need to be completed.
@@ -1014,83 +1044,83 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="255"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="248"/>
         <source>Locate Heroes III data files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="304"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="297"/>
         <source>Use offline installer from gog.com</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="317"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="310"/>
         <source>You can manually copy directories Maps, Data and Mp3 from the original game directory to VCMI data directory that you can see on top of this page</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="336"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="329"/>
         <source>Install gog.com files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="495"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="488"/>
         <source>Your Heroes III data files have been successfully found.</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="749"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="742"/>
         <source>Interface Improvements</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="629"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="622"/>
         <source>Install a translation of Heroes III in your preferred language</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="395"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="388"/>
         <source>Installing... %p%</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="424"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="417"/>
         <source>If you already have Heroes III files on your device, you can select this directory and VCMI will copy the existing data automatically.</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="466"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="459"/>
         <source>Copy existing files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="511"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="504"/>
         <source>If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer. 
 Offline installer consists of two parts, .exe and .bin. Make sure you download both of them.</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="696"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="689"/>
         <source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="795"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
         <source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="680"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="673"/>
         <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"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="779"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
         <source>Install compatible version of &quot;In The Wake of Gods&quot;, a fan-made Heroes III expansion</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="874"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="867"/>
         <source>Finish</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1100,59 +1130,59 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="169"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="162"/>
         <source>VCMI on Discord</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="219"/>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="571"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="212"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="564"/>
         <source>Next</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="354"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="347"/>
         <source>Manual Installation</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="367"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="360"/>
         <source>Search again</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="448"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="441"/>
         <source>Heroes III data files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="286"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="279"/>
         <source>Copy existing data</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="564"/>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="867"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="557"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="860"/>
         <source>Back</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="601"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="594"/>
         <source>Install VCMI Mod Preset</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="717"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="710"/>
         <source>Horn of the Abyss</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="650"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="643"/>
         <source>Heroes III Translation</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../firstLaunch/firstlaunch_moc.ui" line="816"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="809"/>
         <source>In The Wake of Gods</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1416,12 +1446,12 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
 <context>
     <name>QObject</name>
     <message>
-        <location filename="../main.cpp" line="121"/>
+        <location filename="../main.cpp" line="122"/>
         <source>Error starting executable</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../main.cpp" line="122"/>
+        <location filename="../main.cpp" line="123"/>
         <source>Failed to start %1
 Reason: %2</source>
         <translation type="unfinished"></translation>

File diff suppressed because it is too large
+ 189 - 215
launcher/translation/french.ts


File diff suppressed because it is too large
+ 190 - 216
launcher/translation/german.ts


File diff suppressed because it is too large
+ 190 - 216
launcher/translation/polish.ts


File diff suppressed because it is too large
+ 190 - 216
launcher/translation/portuguese.ts


File diff suppressed because it is too large
+ 189 - 203
launcher/translation/russian.ts


File diff suppressed because it is too large
+ 190 - 212
launcher/translation/spanish.ts


File diff suppressed because it is too large
+ 190 - 216
launcher/translation/ukrainian.ts


File diff suppressed because it is too large
+ 190 - 208
launcher/translation/vietnamese.ts


+ 1 - 0
lib/CMakeLists.txt

@@ -450,6 +450,7 @@ set(lib_MAIN_HEADERS
 
 	entities/building/CBuilding.h
 	entities/building/CBuildingHandler.h
+	entities/building/TownFortifications.h
 	entities/faction/CFaction.h
 	entities/faction/CTown.h
 	entities/faction/CTownHandler.h

+ 2 - 4
lib/CPlayerState.h

@@ -137,13 +137,11 @@ public:
 		h & daysWithoutCastle;
 		h & cheated;
 		h & battleBonuses;
-		if (h.version >= Handler::Version::ARTIFACT_COSTUMES)
-			h & costumesArtifacts;
+		h & costumesArtifacts;
 		h & enteredLosingCheatCode;
 		h & enteredWinningCheatCode;
 		h & static_cast<CBonusSystemNode&>(*this);
-		if (h.version >= Handler::Version::DESTROYED_OBJECTS)
-			h & destroyedObjects;
+		h & destroyedObjects;
 	}
 };
 

+ 1 - 1
lib/IGameCallback.h

@@ -120,7 +120,7 @@ public:
 
 	virtual bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) = 0;
 	virtual bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble = std::nullopt) = 0;
-	virtual void removeArtifact(const ArtifactLocation &al) = 0;
+	virtual void removeArtifact(const ArtifactLocation& al) = 0;
 	virtual bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) = 0;
 
 	virtual void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0;

+ 5 - 7
lib/StartInfo.h

@@ -52,9 +52,10 @@ struct DLL_LINKAGE SimturnsInfo
 		h & optionalTurns;
 		h & allowHumanWithAI;
 
-		static_assert(Handler::Version::RELEASE_143 < Handler::Version::CURRENT, "Please add ignoreAlliedContacts to serialization for 1.6");
-		// disabled to allow multiplayer compatibility between 1.5.2 and 1.5.1
-		// h & ignoreAlliedContacts
+		if (h.version >= Handler::Version::SAVE_COMPATIBILITY_FIXES)
+			h & ignoreAlliedContacts;
+		else
+			ignoreAlliedContacts = true;
 	}
 };
 
@@ -183,10 +184,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 		h & fileURI;
 		h & simturnsInfo;
 		h & turnTimerInfo;
-		if(h.version >= Handler::Version::HAS_EXTRA_OPTIONS)
-			h & extraOptionsInfo;
-		else
-			extraOptionsInfo = ExtraOptionsInfo();
+		h & extraOptionsInfo;
 		h & mapname;
 		h & mapGenOptions;
 		h & campState;

+ 18 - 22
lib/battle/BattleInfo.cpp

@@ -14,6 +14,7 @@
 #include "bonuses/Updaters.h"
 #include "../CStack.h"
 #include "../CHeroHandler.h"
+#include "../entities/building/TownFortifications.h"
 #include "../filesystem/Filesystem.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../texts/CGeneralTextHandler.h"
@@ -202,28 +203,25 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 	}
 
 	//setting up siege obstacles
-	if (town && town->hasFort())
+	if (town && town->fortificationsLevel().wallsHealth != 0)
 	{
+		auto fortification = town->fortificationsLevel();
+
 		curB->si.gateState = EGateState::CLOSED;
 
 		curB->si.wallState[EWallPart::GATE] = EWallState::INTACT;
 
 		for(const auto wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL})
-		{
-			if (town->hasBuilt(BuildingID::CASTLE))
-				curB->si.wallState[wall] = EWallState::REINFORCED;
-			else
-				curB->si.wallState[wall] = EWallState::INTACT;
-		}
+			curB->si.wallState[wall] = static_cast<EWallState>(fortification.wallsHealth);
 
-		if (town->hasBuilt(BuildingID::CITADEL))
-			curB->si.wallState[EWallPart::KEEP] = EWallState::INTACT;
+		if (fortification.citadelHealth != 0)
+			curB->si.wallState[EWallPart::KEEP] = static_cast<EWallState>(fortification.citadelHealth);
 
-		if (town->hasBuilt(BuildingID::CASTLE))
-		{
-			curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::INTACT;
-			curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::INTACT;
-		}
+		if (fortification.upperTowerHealth != 0)
+			curB->si.wallState[EWallPart::UPPER_TOWER] = static_cast<EWallState>(fortification.upperTowerHealth);
+
+		if (fortification.lowerTowerHealth != 0)
+			curB->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast<EWallState>(fortification.lowerTowerHealth);
 	}
 
 	//randomize obstacles
@@ -369,7 +367,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 			handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, 52);
 			handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, 18);
 			handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, 154);
-			if(town && town->hasFort())
+			if(town && town->fortificationsLevel().wallsHealth > 0)
 				handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, 120);
 		}
 
@@ -419,18 +417,16 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 
 	}
 
-	if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
+	if (curB->town)
 	{
-		// keep tower
-		curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
+		if (curB->town->fortificationsLevel().citadelHealth != 0)
+			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
 
-		if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
-		{
-			// lower tower + upper tower
+		if (curB->town->fortificationsLevel().upperTowerHealth != 0)
 			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER);
 
+		if (curB->town->fortificationsLevel().lowerTowerHealth != 0)
 			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
-		}
 
 		//Moat generating is done on server
 	}

+ 6 - 5
lib/battle/CBattleInfoCallback.cpp

@@ -18,6 +18,7 @@
 #include "CObstacleInstance.h"
 #include "DamageCalculator.h"
 #include "PossiblePlayerBattleAction.h"
+#include "../entities/building/TownFortifications.h"
 #include "../spells/ObstacleCasterProxy.h"
 #include "../spells/ISpellMechanics.h"
 #include "../spells/Problem.h"
@@ -237,7 +238,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
 bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
 {
 	RETURN_IF_NOT_BATTLE(false);
-	if(!battleGetSiegeLevel())
+	if(battleGetFortifications().wallsHealth == 0)
 		return false;
 
 	const std::string cachingStrNoWallPenalty = "type_NO_WALL_PENALTY";
@@ -288,7 +289,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
 			allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
 
 		const auto * siegedTown = battleGetDefendedTown();
-		if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(BonusType::CATAPULT)) //TODO: check shots
+		if(siegedTown && siegedTown->fortificationsLevel().wallsHealth > 0 && stack->hasBonusOfType(BonusType::CATAPULT)) //TODO: check shots
 			allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT);
 		if(stack->hasBonusOfType(BonusType::HEALER))
 			allowedActionList.push_back(PossiblePlayerBattleAction::HEAL);
@@ -943,7 +944,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 	}
 
 	//gate -> should be before stacks
-	if(battleGetSiegeLevel() > 0)
+	if(battleGetFortifications().wallsHealth > 0)
 	{
 		EAccessibility accessibility = EAccessibility::ACCESSIBLE;
 		switch(battleGetGateState())
@@ -975,7 +976,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 	}
 
 	//walls
-	if(battleGetSiegeLevel() > 0)
+	if(battleGetFortifications().wallsHealth > 0)
 	{
 		static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
 		for(auto hex : permanentlyLocked)
@@ -1612,7 +1613,7 @@ bool CBattleInfoCallback::isWallPartAttackable(EWallPart wallPart) const
 	if(isWallPartPotentiallyAttackable(wallPart))
 	{
 		auto wallState = battleGetWallState(wallPart);
-		return (wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED);
+		return (wallState != EWallState::NONE && wallState != EWallState::DESTROYED);
 	}
 	return false;
 }

+ 11 - 8
lib/battle/CBattleInfoEssentials.cpp

@@ -9,12 +9,15 @@
  */
 #include "StdInc.h"
 #include "CBattleInfoEssentials.h"
+
 #include "../CStack.h"
 #include "BattleInfo.h"
 #include "CObstacleInstance.h"
-#include "../mapObjects/CGTownInstance.h"
-#include "../gameState/InfoAboutArmy.h"
+
 #include "../constants/EntityIdentifiers.h"
+#include "../entities/building/TownFortifications.h"
+#include "../gameState/InfoAboutArmy.h"
+#include "../mapObjects/CGTownInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -345,10 +348,10 @@ bool CBattleInfoEssentials::playerHasAccessToHeroInfo(const PlayerColor & player
 	return false;
 }
 
-ui8 CBattleInfoEssentials::battleGetSiegeLevel() const
+TownFortifications CBattleInfoEssentials::battleGetFortifications() const
 {
-	RETURN_IF_NOT_BATTLE(CGTownInstance::NONE);
-	return getBattle()->getDefendedTown() ? getBattle()->getDefendedTown()->fortLevel() : CGTownInstance::NONE;
+	RETURN_IF_NOT_BATTLE(TownFortifications());
+	return getBattle()->getDefendedTown() ? getBattle()->getDefendedTown()->fortificationsLevel() : TownFortifications();
 }
 
 bool CBattleInfoEssentials::battleCanSurrender(const PlayerColor & player) const
@@ -371,7 +374,7 @@ bool CBattleInfoEssentials::battleHasHero(BattleSide side) const
 EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const
 {
 	RETURN_IF_NOT_BATTLE(EWallState::NONE);
-	if(battleGetSiegeLevel() == CGTownInstance::NONE)
+	if(battleGetFortifications().wallsHealth == 0)
 		return EWallState::NONE;
 
 	return getBattle()->getWallState(partOfWall);
@@ -380,7 +383,7 @@ EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const
 EGateState CBattleInfoEssentials::battleGetGateState() const
 {
 	RETURN_IF_NOT_BATTLE(EGateState::NONE);
-	if(battleGetSiegeLevel() == CGTownInstance::NONE)
+	if(battleGetFortifications().wallsHealth == 0)
 		return EGateState::NONE;
 
 	return getBattle()->getGateState();
@@ -389,7 +392,7 @@ EGateState CBattleInfoEssentials::battleGetGateState() const
 bool CBattleInfoEssentials::battleIsGatePassable() const
 {
 	RETURN_IF_NOT_BATTLE(true);
-	if(battleGetSiegeLevel() == CGTownInstance::NONE)
+	if(battleGetFortifications().wallsHealth == 0)
 		return true;
 
 	return battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED; 

+ 2 - 1
lib/battle/CBattleInfoEssentials.h

@@ -18,6 +18,7 @@ class CGHeroInstance;
 class CStack;
 class IBonusBearer;
 struct InfoAboutHero;
+struct TownFortifications;
 class CArmedInstance;
 
 using TStacks = std::vector<const CStack *>;
@@ -75,7 +76,7 @@ public:
 	BattleSide playerToSide(const PlayerColor & player) const;
 	PlayerColor sideToPlayer(BattleSide side) const;
 	bool playerHasAccessToHeroInfo(const PlayerColor & player, const CGHeroInstance * h) const;
-	ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
+	TownFortifications battleGetFortifications() const;
 	bool battleHasHero(BattleSide side) const;
 	uint32_t battleCastSpells(BattleSide side) const; //how many spells has given side cast
 	const CGHeroInstance * battleGetFightingHero(BattleSide side) const; //deprecated for players callback, easy to get wrong

+ 1 - 14
lib/bonuses/Bonus.h

@@ -95,15 +95,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
 		h & source;
 		h & val;
 		h & sid;
-		if (h.version < Handler::Version::BONUS_META_STRING)
-		{
-			std::string oldDescription;
-			h & oldDescription;
-			description = MetaString::createFromRawString(oldDescription);
-		}
-		else
-			h & description;
-
+		h & description;
 		h & additionalInfo;
 		h & turnsRemain;
 		h & valType;
@@ -114,11 +106,6 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
 		h & updater;
 		h & propagationUpdater;
 		h & targetSourceType;
-		if (h.version < Handler::Version::MANA_LIMIT && type == BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)
-		{
-			if (valType == BonusValueType::ADDITIVE_VALUE || valType == BonusValueType::BASE_NUMBER)
-				val *= 100;
-		}
 	}
 
 	template <typename Ptr>

+ 2 - 4
lib/campaign/CampaignState.h

@@ -141,8 +141,7 @@ public:
 		h & modName;
 		h & music;
 		h & encoding;
-		if (h.version >= Handler::Version::RELEASE_143)
-			h & textContainer;
+		h & textContainer;
 	}
 };
 
@@ -342,8 +341,7 @@ public:
 		h & currentMap;
 		h & chosenCampaignBonuses;
 		h & campaignSet;
-		if (h.version >= Handler::Version::CAMPAIGN_MAP_TRANSLATIONS)
-			h & mapTranslations;
+		h & mapTranslations;
 		if (h.version >= Handler::Version::HIGHSCORE_PARAMETERS)
 			h & highscoreParameters;
 	}

+ 3 - 0
lib/entities/building/CBuilding.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "TownFortifications.h"
+
 #include "../../constants/EntityIdentifiers.h"
 #include "../../LogicalExpression.h"
 #include "../../ResourceSet.h"
@@ -35,6 +37,7 @@ public:
 	TResources produce;
 	TRequired requirements;
 	ArtifactID warMachine;
+	TownFortifications fortifications;
 	std::set<EMarketMode> marketModes;
 
 	BuildingID bid; //structure ID

+ 49 - 0
lib/entities/building/TownFortifications.h

@@ -0,0 +1,49 @@
+/*
+ * TownFortifications.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 "../../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct TownFortifications
+{
+	CreatureID citadelShooter;
+	CreatureID upperTowerShooter;
+	CreatureID lowerTowerShooter;
+	SpellID moatSpell;
+
+	int8_t wallsHealth = 0;
+	int8_t citadelHealth = 0;
+	int8_t upperTowerHealth = 0;
+	int8_t lowerTowerHealth = 0;
+	bool hasMoat = false;
+
+	const TownFortifications & operator +=(const TownFortifications & other)
+	{
+		if (other.citadelShooter.hasValue())
+			citadelShooter = other.citadelShooter;
+		if (other.upperTowerShooter.hasValue())
+			upperTowerShooter = other.upperTowerShooter;
+		if (other.lowerTowerShooter.hasValue())
+			lowerTowerShooter = other.lowerTowerShooter;
+		if (other.moatSpell.hasValue())
+			moatSpell = other.moatSpell;
+
+		wallsHealth = std::max(wallsHealth, other.wallsHealth);
+		citadelHealth = std::max(citadelHealth, other.citadelHealth);
+		upperTowerHealth = std::max(upperTowerHealth, other.upperTowerHealth);
+		lowerTowerHealth = std::max(lowerTowerHealth, other.lowerTowerHealth);
+		hasMoat = hasMoat || other.hasMoat;
+		return *this;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/entities/faction/CTown.cpp

@@ -18,7 +18,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 CTown::CTown()
-	: faction(nullptr), mageLevel(0), primaryRes(0), moatAbility(SpellID::NONE), defaultTavernChance(0)
+	: faction(nullptr), mageLevel(0), primaryRes(0), defaultTavernChance(0)
 {
 }
 

+ 5 - 2
lib/entities/faction/CTown.h

@@ -9,6 +9,7 @@
  */
 #pragma once
 
+#include "../building/TownFortifications.h"
 #include "../../ConstTransitivePtr.h"
 #include "../../Point.h"
 #include "../../constants/EntityIdentifiers.h"
@@ -70,7 +71,10 @@ public:
 	ui32 mageLevel; //max available mage guild level
 	GameResID primaryRes;
 	CreatureID warMachineDeprecated;
-	SpellID moatAbility;
+
+	/// Base state of fortifications for empty town.
+	/// Used to define shooter units and moat spell ID
+	TownFortifications fortifications;
 
 	// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
 	// resulting chance = sqrt(town.chance * heroClass.chance)
@@ -99,7 +103,6 @@ public:
 
 		std::string siegePrefix;
 		std::vector<Point> siegePositions;
-		CreatureID siegeShooter; // shooter creature ID
 		std::string towerIconSmall;
 		std::string towerIconLarge;
 

+ 30 - 3
lib/entities/faction/CTownHandler.cpp

@@ -299,6 +299,31 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	ret->resources = TResources(source["cost"]);
 	ret->produce =   TResources(source["produce"]);
 
+	const JsonNode & fortifications = source["fortifications"];
+	if (!fortifications.isNull())
+	{
+		VLC->identifiers()->requestIdentifierOptional("creature", fortifications["citadelShooter"], [=](si32 identifier)
+		{
+			ret->fortifications.citadelShooter = CreatureID(identifier);
+		});
+
+		VLC->identifiers()->requestIdentifierOptional("creature", fortifications["upperTowerShooter"], [=](si32 identifier)
+		{
+			ret->fortifications.upperTowerShooter = CreatureID(identifier);
+		});
+
+		VLC->identifiers()->requestIdentifierOptional("creature", fortifications["lowerTowerShooter"], [=](si32 identifier)
+		{
+			ret->fortifications.lowerTowerShooter = CreatureID(identifier);
+		});
+
+		ret->fortifications.wallsHealth = fortifications["wallsHealth"].Integer();
+		ret->fortifications.citadelHealth = fortifications["citadelHealth"].Integer();
+		ret->fortifications.upperTowerHealth = fortifications["upperTowerHealth"].Integer();
+		ret->fortifications.lowerTowerHealth = fortifications["lowerTowerHealth"].Integer();
+		ret->fortifications.hasMoat = fortifications["hasMoat"].Bool();
+	}
+
 	loadBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret);
 
 	if(!source["configuration"].isNull())
@@ -477,7 +502,9 @@ void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source) const
 				, town.faction->getNameTranslated()
 				, (*VLC->creh)[crId]->getNameSingularTranslated());
 
-		town.clientInfo.siegeShooter = crId;
+		town.fortifications.citadelShooter = crId;
+		town.fortifications.upperTowerShooter = crId;
+		town.fortifications.lowerTowerShooter = crId;
 	});
 
 	auto & pos = town.clientInfo.siegePositions;
@@ -581,14 +608,14 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		VLC->identifiers()->requestIdentifier( "spell", source["moatAbility"], [=](si32 ability)
 		{
-			town->moatAbility = SpellID(ability);
+			town->fortifications.moatSpell = SpellID(ability);
 		});
 	}
 	else
 	{
 		VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability)
 		{
-			town->moatAbility = SpellID(ability);
+			town->fortifications.moatSpell = SpellID(ability);
 		});
 	}
 

+ 1 - 10
lib/json/JsonNode.h

@@ -152,16 +152,7 @@ public:
 	void serialize(Handler & h)
 	{
 		h & modScope;
-
-		if(h.version >= Handler::Version::JSON_FLAGS)
-		{
-			h & overrideFlag;
-		}
-		else
-		{
-			std::vector<std::string> oldFlags;
-			h & oldFlags;
-		}
+		h & overrideFlag;
 		h & data;
 	}
 };

+ 3 - 1
lib/mapObjects/CGMarket.h

@@ -31,7 +31,8 @@ public:
 	int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
 	std::set<EMarketMode> availableModes() const override;
 
-	template <typename Handler> void serialize(Handler &h)
+	template <typename Handler>
+	void serialize(Handler &h)
 	{
 		h & static_cast<CGObjectInstance&>(*this);
 		if (h.version < Handler::Version::NEW_MARKETS)
@@ -53,6 +54,7 @@ public:
 	template <typename Handler> void serializeArtifactsAltar(Handler &h)
 	{
 		serialize(h);
+		IMarket::serializeArtifactsAltar(h);
 	}
 };
 

+ 54 - 64
lib/mapObjects/CGTownInstance.cpp

@@ -225,6 +225,9 @@ TResources CGTownInstance::dailyIncome() const
 		}
 	}
 
+	if (!getOwner().isValidPlayer())
+		return ret;
+
 	const auto & playerSettings = cb->getPlayerSettings(getOwner());
 	ret.applyHandicap(playerSettings->handicap.percentIncome);
 	return ret;
@@ -245,6 +248,19 @@ bool CGTownInstance::hasCapitol() const
 	return hasBuilt(BuildingID::CAPITOL);
 }
 
+TownFortifications CGTownInstance::fortificationsLevel() const
+{
+	auto result = town->fortifications;
+
+	for (auto const	& buildingID : builtBuildings)
+		result += town->buildings.at(buildingID)->fortifications;
+
+	if (result.wallsHealth == 0)
+		return TownFortifications();
+
+	return result;
+}
+
 CGTownInstance::CGTownInstance(IGameCallback *cb):
 	CGDwelling(cb),
 	town(nullptr),
@@ -384,8 +400,6 @@ void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
 
 DamageRange CGTownInstance::getTowerDamageRange() const
 {
-	assert(hasBuilt(BuildingID::CASTLE));
-
 	// http://heroes.thelazy.net/wiki/Arrow_tower
 	// base damage, irregardless of town level
 	static constexpr int baseDamage = 6;
@@ -402,8 +416,6 @@ DamageRange CGTownInstance::getTowerDamageRange() const
 
 DamageRange CGTownInstance::getKeepDamageRange() const
 {
-	assert(hasBuilt(BuildingID::CITADEL));
-
 	// http://heroes.thelazy.net/wiki/Arrow_tower
 	// base damage, irregardless of town level
 	static constexpr int baseDamage = 10;
@@ -473,67 +485,50 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
 		}
 	}
 	initializeConfigurableBuildings(rand);
+	initializeNeutralTownGarrison(rand);
 	recreateBuildingsBonuses();
 	updateAppearance();
 }
 
-void CGTownInstance::newTurn(vstd::RNG & rand) const
+void CGTownInstance::initializeNeutralTownGarrison(vstd::RNG & rand)
 {
-	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
+	struct RandomGuardsInfo{
+		int tier;
+		int chance;
+		int min;
+		int max;
+	};
+
+	constexpr std::array<RandomGuardsInfo, 4> randomGuards = {
+		RandomGuardsInfo{ 0, 33, 8, 15 },
+		RandomGuardsInfo{ 1, 33, 5,  7 },
+		RandomGuardsInfo{ 2, 20, 3,  5 },
+		RandomGuardsInfo{ 3, 14, 1,  3 },
+	};
+
+	// Only neutral towns may get initial garrison
+	if (getOwner().isValidPlayer())
+		return;
+
+	// Only towns with garrison not set in map editor may get initial garrison
+	// FIXME: H3 editor allow explicitly empty garrison, but vcmi loses this flag on load
+	if (stacksCount() > 0)
+		return;
+
+	for (auto const & guard : randomGuards)
 	{
-		if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
-		{
-			std::vector<SlotID> nativeCrits; //slots
-			for(const auto & elem : Slots())
-			{
-				if (elem.second->type->getFaction() == getFaction()) //native
-				{
-					nativeCrits.push_back(elem.first); //collect matching slots
-				}
-			}
-			if(!nativeCrits.empty())
-			{
-				SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand);
-				StackLocation sl(this, pos);
-				
-				const CCreature *c = getCreature(pos);
-				if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available
-				{
-					cb->changeStackCount(sl, c->getGrowth());
-				}
-				else //upgrade
-				{
-					cb->changeStackType(sl, c->upgrades.begin()->toCreature());
-				}
-			}
-			if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack
-			{
-				int i = rand.nextInt(std::min((int)town->creatures.size(), cb->getDate(Date::MONTH) << 1) - 1);
-				if (!town->creatures[i].empty())
-				{
-					CreatureID c = town->creatures[i][0];
-					SlotID n;
-					
-					TQuantity count = creatureGrowth(i);
-					if (!count) // no dwelling
-						count = VLC->creatures()->getById(c)->getGrowth();
-					
-					{//no lower tiers or above current month
-						
-						if ((n = getSlotFor(c)).validSlot())
-						{
-							StackLocation sl(this, n);
-							if (slotEmpty(n))
-								cb->insertNewStack(sl, c.toCreature(), count);
-							else //add to existing
-								cb->changeStackCount(sl, count);
-						}
-					}
-				}
-			}
-		}
+		if (rand.nextInt(99) >= guard.chance)
+			continue;
+
+		CreatureID guardID = getTown()->creatures[guard.tier].at(0);
+		int guardSize = rand.nextInt(guard.min, guard.max);
+
+		putStack(getFreeSlot(), new CStackInstance(guardID, guardSize));
 	}
-	
+}
+
+void CGTownInstance::newTurn(vstd::RNG & rand) const
+{
 	for(const auto & building : rewardableBuildings)
 		building.second->newTurn(rand);
 		
@@ -545,12 +540,7 @@ void CGTownInstance::newTurn(vstd::RNG & rand) const
 		cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_SECOND, bonusValue.second - 500);
 	}
 }
-/*
-int3 CGTownInstance::getSightCenter() const
-{
-	return pos - int3(2,0,0);
-}
-*/
+
 bool CGTownInstance::passableFor(PlayerColor color) const
 {
 	if (!armedGarrison())//empty castle - anyone can visit

+ 3 - 0
lib/mapObjects/CGTownInstance.h

@@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CCastleEvent;
 class CTown;
 class TownBuildingInstance;
+struct TownFortifications;
 class TownRewardableBuildingInstance;
 struct DamageRange;
 
@@ -162,6 +163,7 @@ public:
 
 	bool needsLastStack() const override;
 	CGTownInstance::EFortLevel fortLevel() const;
+	TownFortifications fortificationsLevel() const;
 	int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
 	int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
 	int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present
@@ -246,6 +248,7 @@ private:
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<const CGObjectInstance* >& dwellings) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
 	void initializeConfigurableBuildings(vstd::RNG & rand);
+	void initializeNeutralTownGarrison(vstd::RNG & rand);
 };
 
 VCMI_LIB_NAMESPACE_END

+ 5 - 0
lib/mapObjects/IMarket.h

@@ -36,6 +36,11 @@ public:
 	CArtifactSet * getArtifactsStorage() const;
 	bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
 
+	template <typename Handler> void serializeArtifactsAltar(Handler &h)
+	{
+		h & *altarArtifactsStorage;
+	}
+
 private:
 	std::unique_ptr<CArtifactSetAltar> altarArtifactsStorage;
 };

+ 0 - 8
lib/mapping/CMap.h

@@ -196,14 +196,6 @@ public:
 		h & quests;
 		h & allHeroes;
 
-		if (h.version < Handler::Version::DESTROYED_OBJECTS)
-		{
-			// old save compatibility
-			//FIXME: remove this field after save-breaking change
-			h & questIdentifierToId;
-			resolveQuestIdentifiers();
-		}
-
 		//TODO: viccondetails
 		h & terrain;
 		h & guardingCreaturePositions;

+ 10 - 6
lib/mapping/CMapHeader.h

@@ -277,12 +277,16 @@ public:
 		h & width;
 		h & height;
 		h & twoLevel;
-		// FIXME: we should serialize enum's according to their underlying type
-		// should be fixed when we are making breaking change to save compatibility
-		static_assert(Handler::Version::MINIMAL < Handler::Version::RELEASE_143);
-		uint8_t difficultyInteger = static_cast<uint8_t>(difficulty);
-		h & difficultyInteger;
-		difficulty = static_cast<EMapDifficulty>(difficultyInteger);
+
+		if (h.version >= Handler::Version::SAVE_COMPATIBILITY_FIXES)
+			h & difficulty;
+		else
+		{
+			uint8_t difficultyInteger = static_cast<uint8_t>(difficulty);
+			h & difficultyInteger;
+			difficulty = static_cast<EMapDifficulty>(difficultyInteger);
+		}
+
 		h & levelLimit;
 		h & areAnyPlayers;
 		h & players;

+ 6 - 0
lib/modding/IdentifierStorage.cpp

@@ -190,6 +190,12 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
 	requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false));
 }
 
+void CIdentifierStorage::requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
+{
+	if (!name.isNull())
+		requestIdentifier(type, name, callback);
+}
+
 void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
 {
 	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true));

+ 2 - 0
lib/modding/IdentifierStorage.h

@@ -84,6 +84,8 @@ public:
 	void requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
 	void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const;
 
+	void requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
+
 	/// try to request ID. If ID with such name won't be loaded, callback function will not be called
 	void tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const;
 	void tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;

+ 1 - 1
lib/networkPacks/NetPackVisitor.h

@@ -78,7 +78,7 @@ public:
 	virtual void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) {}
 	virtual void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) {}
 	virtual void visitPutArtifact(PutArtifact & pack) {}
-	virtual void visitEraseArtifact(EraseArtifact & pack) {}
+	virtual void visitEraseArtifact(BulkEraseArtifacts & pack) {}
 	virtual void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) {}
 	virtual void visitAssembledArtifact(AssembledArtifact & pack) {}
 	virtual void visitDisassembledArtifact(DisassembledArtifact & pack) {}

+ 40 - 29
lib/networkPacks/NetPacksLib.cpp

@@ -33,6 +33,7 @@
 #include "CPlayerState.h"
 #include "TerrainHandler.h"
 #include "entities/building/CBuilding.h"
+#include "entities/building/TownFortifications.h"
 #include "mapObjects/CBank.h"
 #include "mapObjects/CGCreature.h"
 #include "mapObjects/CGMarket.h"
@@ -336,7 +337,7 @@ void PutArtifact::visitTyped(ICPackVisitor & visitor)
 	visitor.visitPutArtifact(*this);
 }
 
-void EraseArtifact::visitTyped(ICPackVisitor & visitor)
+void BulkEraseArtifacts::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitEraseArtifact(*this);
 }
@@ -1611,9 +1612,10 @@ void RebalanceStacks::applyGs(CGameState *gs)
 					//else - artifact can be lost :/
 					else
 					{
-						EraseArtifact ea;
-						ea.al = ArtifactLocation(dstHero->id, ArtifactPosition::CREATURE_SLOT);
-						ea.al.creature = dst.slot;
+						BulkEraseArtifacts ea;
+						ea.artHolder = dstHero->id;
+						ea.posPack.emplace_back(ArtifactPosition::CREATURE_SLOT);
+						ea.creature = dst.slot;
 						ea.applyGs(gs);
 						logNetwork->warn("Cannot move artifact! No free slots");
 					}
@@ -1701,37 +1703,46 @@ void PutArtifact::applyGs(CGameState *gs)
 	art->putAt(*hero, al.slot);
 }
 
-void EraseArtifact::applyGs(CGameState *gs)
+void BulkEraseArtifacts::applyGs(CGameState *gs)
 {
-	const auto artSet = gs->getArtSet(al.artHolder);
+	const auto artSet = gs->getArtSet(artHolder);
 	assert(artSet);
-	const auto slot = artSet->getSlot(al.slot);
-	if(slot->locked)
-	{
-		logGlobal->debug("Erasing locked artifact: %s", slot->artifact->artType->getNameTranslated());
-		DisassembledArtifact dis;
-		dis.al.artHolder = al.artHolder;
-		
-		for(auto & slotInfo : artSet->artifactsWorn)
+
+	std::sort(posPack.begin(), posPack.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
+		{
+			return slot0.num > slot1.num;
+		});
+
+	for(const auto & slot : posPack)
+	{
+		const auto slotInfo = artSet->getSlot(slot);
+		if(slotInfo->locked)
 		{
-			auto art = slotInfo.second.artifact;
-			if(art->isCombined() && art->isPart(slot->artifact))
+			logGlobal->debug("Erasing locked artifact: %s", slotInfo->artifact->artType->getNameTranslated());
+			DisassembledArtifact dis;
+			dis.al.artHolder = artHolder;
+
+			for(auto & slotInfoWorn : artSet->artifactsWorn)
 			{
-				dis.al.slot = artSet->getArtPos(art);
-				break;
+				auto art = slotInfoWorn.second.artifact;
+				if(art->isCombined() && art->isPart(slotInfo->getArt()))
+				{
+					dis.al.slot = artSet->getArtPos(art);
+					break;
+				}
 			}
+			assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to");
+			logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->artType->getNameTranslated());
+			dis.applyGs(gs);
 		}
-		assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to");
-		logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->artType->getNameTranslated());
-		dis.applyGs(gs);
-	}
-	else
-	{
-		logGlobal->debug("Erasing artifact %s", slot->artifact->artType->getNameTranslated());
+		else
+		{
+			logGlobal->debug("Erasing artifact %s", slotInfo->artifact->artType->getNameTranslated());
+		}
+		auto art = artSet->getArt(slot);
+		assert(art);
+		art->removeFrom(*artSet, slot);
 	}
-	auto art = artSet->getArt(al.slot);
-	assert(art);
-	art->removeFrom(*artSet, al.slot);
 }
 
 void BulkMoveArtifacts::applyGs(CGameState *gs)
@@ -2328,7 +2339,7 @@ void CatapultAttack::applyBattle(IBattleState * battleState)
 	if(!town)
 		return;
 
-	if(town->fortLevel() == CGTownInstance::NONE)
+	if(town->fortificationsLevel().wallsHealth == 0)
 		return;
 
 	for(const auto & part : attackedParts)

+ 7 - 3
lib/networkPacks/PacksForClient.h

@@ -998,16 +998,20 @@ struct DLL_LINKAGE NewArtifact : public CArtifactOperationPack
 	}
 };
 
-struct DLL_LINKAGE EraseArtifact : CArtifactOperationPack
+struct DLL_LINKAGE BulkEraseArtifacts : CArtifactOperationPack
 {
-	ArtifactLocation al;
+	ObjectInstanceID artHolder;
+	std::vector<ArtifactPosition> posPack;
+	std::optional<SlotID> creature;
 
 	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
 	{
-		h & al;
+		h & artHolder;
+		h & posPack;
+		h & creature;
 	}
 };
 

+ 1 - 12
lib/networkPacks/PacksForLobby.h

@@ -55,18 +55,7 @@ struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate
 
 		h & clientId;
 		h & hostClientId;
-
-		try
-		{
-			if (h.version >= Handler::Version::RELEASE_152)
-				h & version;
-			else
-				version = ESerializationVersion::RELEASE_150;
-		}
-		 catch (const std::runtime_error &)
-		{
-			version = ESerializationVersion::RELEASE_150;
-		}
+		h & version;
 	}
 };
 

+ 1 - 4
lib/rmg/CMapGenOptions.h

@@ -77,10 +77,7 @@ public:
 			h & startingTown;
 			h & playerType;
 			h & team;
-			if (h.version >= Handler::Version::RELEASE_143)
-				h & startingHero;
-			else
-				startingHero = HeroTypeID::RANDOM;
+			h & startingHero;
 		}
 	};
 

+ 4 - 15
lib/serializer/ESerializationVersion.h

@@ -31,25 +31,13 @@ enum class ESerializationVersion : int32_t
 {
 	NONE = 0,
 
-	MINIMAL = 831,
-
-	RELEASE_143, // 832 +text container in campaigns, +starting hero in RMG options
-	HAS_EXTRA_OPTIONS, // 833 +extra options struct as part of startinfo
-	DESTROYED_OBJECTS, // 834 +list of objects destroyed by player
-	CAMPAIGN_MAP_TRANSLATIONS, // 835 +campaigns include translations for its maps
-	JSON_FLAGS, // 836 json uses new format for flags
-	MANA_LIMIT,	// 837 change MANA_PER_KNOWLEDGE to percentage
-	BONUS_META_STRING,	// 838 bonuses use MetaString instead of std::string for descriptions
-	TURN_TIMERS_STATE, // 839 current state of turn timers is serialized
-	ARTIFACT_COSTUMES, // 840 swappable artifacts set added
-
-	RELEASE_150 = ARTIFACT_COSTUMES, // for convenience
+	RELEASE_150 = 840,
+	MINIMAL = RELEASE_150,
 
 	VOTING_SIMTURNS, // 841 - allow modification of simturns duration via vote
 	REMOVE_TEXT_CONTAINER_SIZE_T, // 842 Fixed serialization of size_t from text containers
 	BANK_UNIT_PLACEMENT, // 843 Banks have unit placement flag
 
-	RELEASE_152 = BANK_UNIT_PLACEMENT,
 	RELEASE_156 = BANK_UNIT_PLACEMENT,
 
 	COMPACT_STRING_SERIALIZATION, // 844 - optimized serialization of previously encountered strings
@@ -67,6 +55,7 @@ enum class ESerializationVersion : int32_t
 	STATISTICS_SCREEN, // 856 - extent statistic functions
 	NEW_MARKETS, // 857 - reworked market classes
 	PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list
+	SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization
 
-	CURRENT = PLAYER_STATE_OWNED_OBJECTS
+	CURRENT = SAVE_COMPATIBILITY_FIXES
 };

+ 1 - 1
lib/serializer/RegisterTypes.h

@@ -223,7 +223,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<InsertNewStack>(166);
 	s.template registerType<RebalanceStacks>(167);
 	s.template registerType<PutArtifact>(169);
-	s.template registerType<EraseArtifact>(170);
+	s.template registerType<BulkEraseArtifacts>(170);
 	s.template registerType<AssembledArtifact>(171);
 	s.template registerType<DisassembledArtifact>(172);
 	s.template registerType<BulkMoveArtifacts>(173);

+ 2 - 1
lib/spells/effects/Catapult.cpp

@@ -18,6 +18,7 @@
 #include "../../battle/CBattleInfoCallback.h"
 #include "../../battle/Unit.h"
 #include "../../mapObjects/CGTownInstance.h"
+#include "../../entities/building/TownFortifications.h"
 #include "../../networkPacks/PacksForClientBattle.h"
 #include "../../serializer/JsonSerializeFormat.h"
 
@@ -39,7 +40,7 @@ bool Catapult::applicable(Problem & problem, const Mechanics * m) const
 		return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
 	}
 
-	if(CGTownInstance::NONE == town->fortLevel())
+	if(town->fortificationsLevel().wallsHealth == 0)
 	{
 		return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
 	}

+ 3 - 2
lib/spells/effects/Moat.cpp

@@ -19,6 +19,7 @@
 #include "../../bonuses/Limiters.h"
 #include "../../battle/IBattleState.h"
 #include "../../battle/CBattleInfoCallback.h"
+#include "../../entities/building/TownFortifications.h"
 #include "../../json/JsonBonus.h"
 #include "../../serializer/JsonSerializeFormat.h"
 #include "../../networkPacks/PacksForClient.h"
@@ -85,7 +86,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector<Bonus> & converted) con
 		//Moat battlefield effect is always permanent
 		nb.duration = BonusDuration::ONE_BATTLE;
 
-		if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+		if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetFortifications().hasMoat)
 		{
 			nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID());
 			nb.source = BonusSource::TOWN_STRUCTURE;
@@ -109,7 +110,7 @@ void Moat::apply(ServerCallback * server, const Mechanics * m, const EffectTarge
 {
 	assert(m->isMassive());
 	assert(m->battle()->battleGetDefendedTown());
-	if(m->isMassive() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+	if(m->isMassive() && m->battle()->battleGetFortifications().hasMoat)
 	{
 		EffectTarget moat;
 		placeObstacles(server, m, moat);

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