Просмотр исходного кода

Merge remote-tracking branch 'vcmi/develop' into battle_improvements

Ivan Savenko 2 лет назад
Родитель
Сommit
325c29da0d
100 измененных файлов с 725 добавлено и 285 удалено
  1. 7 3
      .github/workflows/github.yml
  2. 5 1
      AI/BattleAI/BattleExchangeVariant.cpp
  3. 3 1
      AI/CMakeLists.txt
  4. 3 1
      AI/EmptyAI/StdInc.h
  5. 14 3
      AI/Nullkiller/AIGateway.cpp
  6. 8 0
      AI/Nullkiller/Engine/Nullkiller.cpp
  7. 2 2
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  8. 1 19
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  9. 23 5
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  10. 4 0
      AI/Nullkiller/Pathfinding/AIPathfinder.cpp
  11. 1 1
      AUTHORS
  12. 13 2
      CI/get_package_name.sh
  13. 38 22
      CMakeLists.txt
  14. 2 1
      CMakePresets.json
  15. 70 22
      ChangeLog
  16. 4 21
      Global.h
  17. 10 0
      Mods/vcmi/config/vcmi/towerCreature.json
  18. 101 0
      Mods/vcmi/config/vcmi/towerFactions.json
  19. 8 1
      Mods/vcmi/mod.json
  20. 1 2
      client/CMT.cpp
  21. 5 2
      client/CMakeLists.txt
  22. 6 3
      client/CServerHandler.cpp
  23. 0 2
      client/CServerHandler.h
  24. 1 1
      client/Graphics.cpp
  25. 10 3
      client/battle/BattleInterfaceClasses.cpp
  26. 2 1
      client/battle/BattleInterfaceClasses.h
  27. 2 0
      client/gui/CCursorHandler.cpp
  28. 3 3
      client/lobby/CSavingScreen.cpp
  29. 3 3
      client/lobby/CSelectionBase.cpp
  30. 5 5
      client/mainmenu/CMainMenu.cpp
  31. 23 2
      client/widgets/AdventureMapClasses.cpp
  32. 2 0
      client/widgets/AdventureMapClasses.h
  33. 16 2
      client/widgets/CArtifactHolder.cpp
  34. 3 0
      client/widgets/CArtifactHolder.h
  35. 9 4
      client/widgets/TextControls.cpp
  36. 11 4
      client/widgets/TextControls.h
  37. 3 7
      client/windows/CAdvmapInterface.cpp
  38. 1 1
      client/windows/CCastleInterface.cpp
  39. 1 1
      client/windows/CKingdomInterface.cpp
  40. 3 3
      client/windows/CSpellWindow.cpp
  41. 4 0
      client/windows/CTradeWindow.cpp
  42. 2 0
      client/windows/CTradeWindow.h
  43. 1 1
      client/windows/GUIClasses.cpp
  44. 1 1
      cmake_modules/VCMI_lib.cmake
  45. 1 1
      cmake_modules/VersionDefinition.cmake
  46. 0 2
      config/creatures/special.json
  47. 0 2
      config/factions/castle.json
  48. 0 2
      config/factions/conflux.json
  49. 0 2
      config/factions/dungeon.json
  50. 0 2
      config/factions/fortress.json
  51. 0 2
      config/factions/inferno.json
  52. 0 2
      config/factions/necropolis.json
  53. 0 2
      config/factions/rampart.json
  54. 0 2
      config/factions/stronghold.json
  55. 0 2
      config/factions/tower.json
  56. 23 0
      config/schemas/mod.json
  57. 6 0
      debian/changelog
  58. 1 1
      launcher/CMakeLists.txt
  59. 1 0
      launcher/eu.vcmi.VCMI.metainfo.xml
  60. 1 1
      launcher/modManager/cmodlist.cpp
  61. 24 14
      lib/CArtHandler.cpp
  62. 4 0
      lib/CConsoleHandler.cpp
  63. 1 1
      lib/CCreatureHandler.cpp
  64. 1 1
      lib/CCreatureSet.cpp
  65. 36 10
      lib/CGameState.cpp
  66. 1 2
      lib/CGameState.h
  67. 3 3
      lib/CHeroHandler.cpp
  68. 3 0
      lib/CMakeLists.txt
  69. 61 20
      lib/CModHandler.cpp
  70. 19 1
      lib/CModHandler.h
  71. 1 1
      lib/CPathfinder.cpp
  72. 1 1
      lib/CSkillHandler.cpp
  73. 3 3
      lib/CTownHandler.cpp
  74. 7 7
      lib/GameConstants.cpp
  75. 4 0
      lib/IHandlerBase.cpp
  76. 4 2
      lib/IHandlerBase.h
  77. 2 2
      lib/JsonDetail.cpp
  78. 12 2
      lib/NetPacksLib.cpp
  79. 1 1
      lib/ScriptHandler.cpp
  80. 1 1
      lib/Terrain.cpp
  81. 2 1
      lib/filesystem/Filesystem.cpp
  82. 2 2
      lib/mapObjects/CGHeroInstance.cpp
  83. 1 1
      lib/mapObjects/CGTownInstance.cpp
  84. 9 7
      lib/mapObjects/CObjectClassesHandler.cpp
  85. 14 3
      lib/mapObjects/CObjectHandler.cpp
  86. 1 1
      lib/mapObjects/CQuest.cpp
  87. 2 2
      lib/mapObjects/CRewardableObject.cpp
  88. 3 3
      lib/mapObjects/MiscObjects.cpp
  89. 10 2
      lib/mapObjects/ObjectTemplate.cpp
  90. 2 0
      lib/mapping/CMap.cpp
  91. 3 3
      lib/mapping/MapFormatJson.cpp
  92. 2 1
      lib/rmg/CRmgTemplateStorage.cpp
  93. 5 0
      lib/rmg/CZonePlacer.cpp
  94. 1 1
      lib/spells/CSpellHandler.cpp
  95. 0 2
      lib_client/CMakeLists.txt
  96. 1 0
      lib_server/CMakeLists.txt
  97. 1 1
      mapeditor/CMakeLists.txt
  98. 9 4
      mapeditor/graphics.h
  99. BIN
      mapeditor/icons/edit-redo.png
  100. BIN
      mapeditor/icons/edit-undo.png

+ 7 - 3
.github/workflows/github.yml

@@ -4,6 +4,8 @@ on:
   push:
     branches:
       - features/*
+      - beta
+      - master
   pull_request:
   schedule:
     - cron: '0 2 * * *'
@@ -158,6 +160,7 @@ jobs:
         fi
         echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
         echo VCMI_PACKAGE_NAME_SUFFIX="$VCMI_PACKAGE_NAME_SUFFIX" >> $GITHUB_ENV
+        echo VCMI_PACKAGE_GITVERSION="$VCMI_PACKAGE_GITVERSION" >> $GITHUB_ENV
       env:
         PULL_REQUEST: ${{ github.event.pull_request.number }}
 
@@ -171,7 +174,8 @@ jobs:
             ${{matrix.cmake_args}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
             -DENABLE_TEST=${{matrix.test}} \
             -DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX" \
-            -DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME"
+            -DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME" \
+            -DENABLE_GITVERSION="$VCMI_PACKAGE_GITVERSION"
       env:
         CC: ${{ matrix.cc }}
         CXX: ${{ matrix.cxx }}
@@ -222,7 +226,7 @@ jobs:
           ${{github.workspace}}/**/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
 
     - name: Upload build
-      if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
+      if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
       run: |
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         source '${{github.workspace}}/CI/upload_package.sh'
@@ -240,7 +244,7 @@ jobs:
 
     - name: Trigger Android
       uses: peter-evans/repository-dispatch@v1
-      if: ${{ github.ref == 'refs/heads/develop' && matrix.platform == 'mxe' }}
+      if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
       with:
         token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }}
         repository: vcmi/vcmi-android

+ 5 - 1
AI/BattleAI/BattleExchangeVariant.cpp

@@ -204,10 +204,14 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
 	if(targets.unreachableEnemies.empty())
 		return result;
 
+	auto speed = activeStack->Speed();
+
+	if(speed == 0)
+		return result;
+
 	updateReachabilityMap(hb);
 
 	auto dists = cb->getReachability(activeStack);
-	auto speed = activeStack->Speed();
 
 	for(const battle::Unit * enemy : targets.unreachableEnemies)
 	{

+ 3 - 1
AI/CMakeLists.txt

@@ -40,4 +40,6 @@ add_subdirectory(BattleAI)
 add_subdirectory(StupidAI)
 add_subdirectory(EmptyAI)
 add_subdirectory(VCAI)
-add_subdirectory(Nullkiller)
+if(ENABLE_NULLKILLER_AI)
+	add_subdirectory(Nullkiller)
+endif()

+ 3 - 1
AI/EmptyAI/StdInc.h

@@ -4,4 +4,6 @@
 
 // This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
 
-// Here you can add specific libraries and macros which are specific to this project.
+// Here you can add specific libraries and macros which are specific to this project.
+
+VCMI_LIB_USING_NAMESPACE

+ 14 - 3
AI/Nullkiller/AIGateway.cpp

@@ -30,6 +30,7 @@ namespace NKAI
 // our to enemy strength ratio constants
 const float SAFE_ATTACK_CONSTANT = 1.2;
 const float RETREAT_THRESHOLD = 0.3;
+const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
 
 //one thread may be turn of AI and another will be handling a side effect for AI2
 boost::thread_specific_ptr<CCallback> cb;
@@ -500,10 +501,11 @@ boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 
-	double fightRatio = battleState.getOurStrength() / (double)battleState.getEnemyStrength();
+	double ourStrength = battleState.getOurStrength();
+	double fightRatio = ourStrength / (double)battleState.getEnemyStrength();
 
 	// if we have no towns - things are already bad, so retreat is not an option.
-	if(cb->getTownsInfo().size() && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
+	if(cb->getTownsInfo().size() && ourStrength < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
 	{
 		return BattleAction::makeRetreat(battleState.ourSide);
 	}
@@ -533,6 +535,7 @@ void AIGateway::yourTurn()
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 	status.startedTurn();
+
 	makingTurn = make_unique<boost::thread>(&AIGateway::makeTurn, this);
 }
 
@@ -1426,7 +1429,15 @@ void AIGateway::endTurn()
 	{
 		logAi->error("Not having turn at the end of turn???");
 	}
+
 	logAi->debug("Resources at the end of turn: %s", cb->getResourceAmount().toString());
+
+	if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
+	{
+		logAi->info("Ending turn is not needed because we already lost");
+		return;
+	}
+
 	do
 	{
 		cb->endTurn();
@@ -1599,7 +1610,7 @@ void AIStatus::waitTillFree()
 {
 	boost::unique_lock<boost::mutex> lock(mx);
 	while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
-		cv.timed_wait(lock, boost::posix_time::milliseconds(100));
+		cv.timed_wait(lock, boost::posix_time::milliseconds(10));
 }
 
 bool AIStatus::haveTurn()

+ 8 - 0
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -74,6 +74,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
 
 Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const
 {
+	boost::this_thread::interruption_point();
+
 	logAi->debug("Checking behavior %s", behavior->toString());
 
 	auto start = std::chrono::high_resolution_clock::now();
@@ -160,8 +162,12 @@ void Nullkiller::updateAiState(int pass, bool fast)
 			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
 		}
 
+		boost::this_thread::interruption_point();
+
 		pathfinder->updatePaths(activeHeroes, cfg);
 
+		boost::this_thread::interruption_point();
+
 		objectClusterizer->clusterize();
 	}
 
@@ -212,6 +218,8 @@ HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) co
 
 void Nullkiller::makeTurn()
 {
+	boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
+
 	const int MAX_DEPTH = 10;
 
 	resetAiState();

+ 2 - 2
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -127,7 +127,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 							continue;
 						}
 					}
-					catch(cannotFulfillGoalException)
+					catch(cannotFulfillGoalException &)
 					{
 						if(!heroPtr.validAndSet())
 						{
@@ -173,7 +173,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
 			blockedIndexes.insert(node.parentIndex);
 		}
-		catch(goalFulfilledException)
+		catch(goalFulfilledException &)
 		{
 			if(!heroPtr.validAndSet())
 			{

+ 1 - 19
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -23,6 +23,7 @@ namespace NKAI
 {
 
 std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
+boost::mutex AISharedStorage::locker;
 std::set<int3> commitedTiles;
 std::set<int3> commitedTilesInitial;
 
@@ -119,18 +120,6 @@ void AINodeStorage::clear()
 	turnDistanceLimit[HeroRole::SCOUT] = 255;
 }
 
-const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
-{
-	return static_cast<const AIPathNode *>(node);
-}
-
-void AINodeStorage::updateAINode(CGPathNode * node, std::function<void(AIPathNode *)> updater)
-{
-	auto aiNode = static_cast<AIPathNode *>(node);
-
-	updater(aiNode);
-}
-
 boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 	const int3 & pos, 
 	const EPathfindingLayer layer, 
@@ -823,13 +812,6 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
 	return candidate;
 }
 
-const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
-{
-	auto aiNode = getAINode(node);
-
-	return aiNode->actor->hero;
-}
-
 const std::set<const CGHeroInstance *> AINodeStorage::getAllHeroes() const
 {
 	std::set<const CGHeroInstance *> heroes;

+ 23 - 5
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -135,6 +135,8 @@ class AISharedStorage
 	static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
 	std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
 public:
+	static boost::mutex locker;
+
 	AISharedStorage(int3 mapSize);
 	~AISharedStorage();
 
@@ -196,8 +198,24 @@ public:
 		int movementLeft,
 		float cost) const;
 
-	const AIPathNode * getAINode(const CGPathNode * node) const;
-	void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
+	inline const AIPathNode * getAINode(const CGPathNode * node) const
+	{
+		return static_cast<const AIPathNode *>(node);
+	}
+
+	inline void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater)
+	{
+		auto aiNode = static_cast<AIPathNode *>(node);
+
+		updater(aiNode);
+	}
+
+	inline const CGHeroInstance * getHero(const CGPathNode * node) const
+	{
+		auto aiNode = getAINode(node);
+
+		return aiNode->actor->hero;
+	}
 
 	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
 
@@ -223,18 +241,17 @@ public:
 	void setTownsAndDwellings(
 		const std::vector<const CGTownInstance *> & towns,
 		const std::set<const CGObjectInstance *> & visitableObjs);
-	const CGHeroInstance * getHero(const CGPathNode * node) const;
 	const std::set<const CGHeroInstance *> getAllHeroes() const;
 	void clear();
 	bool calculateHeroChain();
 	bool calculateHeroChainFinal();
 
-	uint64_t evaluateDanger(const int3 &  tile, const CGHeroInstance * hero, bool checkGuards) const
+	inline uint64_t evaluateDanger(const int3 &  tile, const CGHeroInstance * hero, bool checkGuards) const
 	{
 		return dangerEvaluator->evaluateDanger(tile, hero, checkGuards);
 	}
 
-	uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
+	inline uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
 	{
 		double ratio = (double)danger / (armyValue * hero->getFightingStrength());
 
@@ -243,6 +260,7 @@ public:
 
 	STRONG_INLINE
 	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
+
 	STRONG_INLINE int getBucket(const ChainActor * actor) const
 	{
 		return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;

+ 4 - 0
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -80,6 +80,8 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
 
 		do
 		{
+			boost::this_thread::interruption_point();
+
 			while(storage->calculateHeroChain())
 			{
 				boost::this_thread::interruption_point();
@@ -91,6 +93,8 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
 			logAi->trace("Select next actor");
 		} while(storage->selectNextActor());
 
+		boost::this_thread::interruption_point();
+
 		if(storage->calculateHeroChainFinal())
 		{
 			boost::this_thread::interruption_point();

+ 1 - 1
AUTHORS

@@ -80,7 +80,7 @@ Dmitry Orlov, <[email protected]>
    * special buildings support in fan towns, new features and bug fixes
 
 Andrey Cherkas aka nordsoft, <[email protected]>
-   * new terrain support, random map generator features and various bug fixes
+   * new terrain support, rmg features, map editor, multiplayer improvements, bug fixes
 
 Andrey Filipenkov aka kambala-decapitator, <[email protected]>
    * iOS support, macOS improvements, various bug fixes

+ 13 - 2
CI/get_package_name.sh

@@ -27,16 +27,27 @@ fi
 
 VCMI_PACKAGE_FILE_NAME="${TMP_JOBID}-vcmi"
 VCMI_PACKAGE_NAME_SUFFIX=""
+VCMI_PACKAGE_GITVERSION="ON"
 if [ -z "$TMP_PRID" ] || [ "$TMP_PRID" == "false" ];
 then
 	branch_name=$(echo "$TMP_BRANCH" | sed 's/[^[:alnum:]]\+/_/g')
 	VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-branch-${branch_name}-${TMP_COMMIT}"
-	VCMI_PACKAGE_NAME_SUFFIX="branch ${branch_name}"
+	if [ "${branch_name}" != "master" ];
+	then
+		VCMI_PACKAGE_NAME_SUFFIX="branch ${branch_name}"
+	else
+		VCMI_PACKAGE_GITVERSION="OFF"
+	fi
 else
 	VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-PR-${TMP_PRID}-${TMP_COMMIT}"
 	VCMI_PACKAGE_NAME_SUFFIX="PR ${TMP_PRID}"
 fi
-VCMI_PACKAGE_NAME_SUFFIX="(${VCMI_PACKAGE_NAME_SUFFIX})"
+
+if [ "${VCMI_PACKAGE_NAME_SUFFIX}" != "" ];
+then
+	VCMI_PACKAGE_NAME_SUFFIX="(${VCMI_PACKAGE_NAME_SUFFIX})"
+fi
 
 export VCMI_PACKAGE_FILE_NAME
 export VCMI_PACKAGE_NAME_SUFFIX
+export VCMI_PACKAGE_GITVERSION

+ 38 - 22
CMakeLists.txt

@@ -33,10 +33,6 @@ if(APPLE)
 	endif()
 endif()
 
-if(APPLE_IOS)
-	set(BUILD_SINGLE_APP 1)
-endif()
-
 ############################################
 #        User-provided options             #
 ############################################
@@ -52,6 +48,7 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
 option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
 option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
 option(ENABLE_EDITOR "Enable compilation of map editor" ON)
+option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON)
 if(APPLE_IOS)
 	set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
 	set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
@@ -64,6 +61,8 @@ endif(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
 option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
 option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
 option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
+option(ENABLE_SINGLE_APP_BUILD "Builds client and server as single executable" OFF)
+option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
 
 # Used for Snap packages and also useful for debugging
 if(NOT APPLE_IOS)
@@ -74,11 +73,20 @@ endif()
 set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
 set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
 
+if(APPLE_IOS AND NOT ENABLE_SINGLE_APP_BUILD)
+	set(ENABLE_SINGLE_APP_BUILD ON)
+endif()
+
 # ERM depends on LUA implicitly
 if(ENABLE_ERM AND NOT ENABLE_LUA)
 	set(ENABLE_LUA ON)
 endif()
 
+# We don't want to deploy assets into build directory for iOS build
+if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
+	set(COPY_CONFIG_ON_BUILD OFF)
+endif()
+
 ############################################
 #        Miscellaneous options             #
 ############################################
@@ -162,8 +170,8 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
 
-if(BUILD_SINGLE_APP)
-	add_compile_definitions(SINGLE_PROCESS_APP=1)
+if(ENABLE_SINGLE_APP_BUILD)
+	add_definitions(-DSINGLE_PROCESS_APP=1)
 endif()
 
 if(APPLE_IOS)
@@ -274,7 +282,7 @@ if(NOT WIN32 AND NOT APPLE_IOS)
 endif()
 
 if(ENABLE_LUA)
-	add_compile_definitions(SCRIPTING_ENABLED=1)
+	add_definitions(-DSCRIPTING_ENABLED=1)
 endif()
 
 ############################################
@@ -316,7 +324,6 @@ find_package(SDL2_ttf REQUIRED)
 if(TARGET SDL2_ttf::SDL2_ttf)
 	add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
 endif()
-find_package(TBB REQUIRED)
 
 if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
 	# Widgets finds its own dependencies (QtGui and QtCore).
@@ -324,6 +331,10 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
 	find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
 endif()
 
+if(ENABLE_NULLKILLER_AI)
+	find_package(TBB REQUIRED)
+endif()
+
 if(ENABLE_LUA)
 	find_package(luajit)
 	# MXE paths hardcoded for current dependencies pack - tried and could not make it work another way
@@ -386,26 +397,34 @@ else()
 	include(GNUInstallDirs)
 
 	if(ENABLE_MONOLITHIC_INSTALL)
-		set(CMAKE_INSTALL_RPATH "$ORIGIN/")
 		set(BIN_DIR "." CACHE STRING "Where to install binaries")
 		set(LIB_DIR "." CACHE STRING "Where to install main library")
 		set(DATA_DIR "." CACHE STRING "Where to install data files")
+
+		# following constants only used for platforms using XDG (Linux, BSD, etc)
+		add_definitions(-DM_DATA_DIR="${DATA_DIR}")
+		add_definitions(-DM_BIN_DIR="${BIN_DIR}")
+		add_definitions(-DM_LIB_DIR="${LIB_DIR}")
+
+		set(CMAKE_INSTALL_RPATH "$ORIGIN/")
 	else()
 		if(NOT BIN_DIR)
-			set(BIN_DIR "bin" CACHE STRING "Where to install binaries")
+			set(BIN_DIR "${CMAKE_INSTALL_BINDIR}" CACHE STRING "Where to install binaries")
 		endif()
 		if(NOT LIB_DIR)
 			set(LIB_DIR "${CMAKE_INSTALL_LIBDIR}/vcmi" CACHE STRING "Where to install main library")
 		endif()
 		if(NOT DATA_DIR)
-			set(DATA_DIR "share/vcmi" CACHE STRING "Where to install data files")
+			set(DATA_DIR "${CMAKE_INSTALL_DATAROOTDIR}/vcmi" CACHE STRING "Where to install data files")
 		endif()
-	endif()
 
-	# following constants only used for platforms using XDG (Linux, BSD, etc)
-	add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
-	add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
-	add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
+		# following constants only used for platforms using XDG (Linux, BSD, etc)
+		add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
+		add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
+		add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
+
+		set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
+	endif()
 endif()
 
 # iOS has flat libs directory structure
@@ -426,13 +445,10 @@ if(APPLE_IOS)
 endif()
 
 include(VCMI_lib)
-if(BUILD_SINGLE_APP)
-	add_subdirectory(lib_client)
+add_subdirectory(lib)
+set(VCMI_LIB_TARGET vcmi)
+if(ENABLE_SINGLE_APP_BUILD)
 	add_subdirectory(lib_server)
-	set(VCMI_LIB_TARGET vcmi_lib_client)
-else()
-	add_subdirectory(lib)
-	set(VCMI_LIB_TARGET vcmi)
 endif()
 
 if(ENABLE_ERM)

+ 2 - 1
CMakePresets.json

@@ -23,7 +23,8 @@
                 "PACKAGE_FILE_NAME" : "$env{VCMI_PACKAGE_FILE_NAME}",
                 "PACKAGE_NAME_SUFFIX" : "$env{VCMI_PACKAGE_NAME_SUFFIX}",
                 "CMAKE_BUILD_TYPE": "RelWithDebInfo",
-                "ENABLE_TEST": "OFF"
+                "ENABLE_TEST": "OFF",
+                "ENABLE_GITVERSION": "$env{VCMI_PACKAGE_GITVERSION}"
             }
         },
         {

+ 70 - 22
ChangeLog

@@ -1,21 +1,69 @@
-1.0.0 -> 1.1.0
-
-GENERAL:
-* Mods and their versions and serialized into save files. Game checks mod compatibility before loading
-* Logs are stored in system default logs directory
-* LUA/ERM libs are not compiled by default
-* FFMpeg dependency is optional now
-
-MODS:
-* Supported rewardable objects customization
-* Battleground obstacles are extendable now with VLC mechanism
-* Introduced "compatibility" section into mods settings
-
-LAUNCHER:
-* Fixed problem with duplicated mods in the list
-* Launcher shows compatible mods only
-* Uninstall button was moved to the left of layout
-
+1.0.0 -> 1.1.0
+
+GENERAL:
+* iOS is supported
+* Mods and their versions and serialized into save files. Game checks mod compatibility before loading
+* Logs are stored in system default logs directory
+* LUA/ERM libs are not compiled by default
+* FFMpeg dependency is optional now
+* Conan package manager is supported for MacOS and iOS
+
+MULTIPLAYER:
+* Map is passed over network, so different platforms are compatible with each other
+* Server self-killing is more robust
+* Unlock in-game console while opponent's turn
+* Host can control game session by using console commands
+* Control over player is transferred to AI if client escaped the game
+* Reconnection mode for crashed client processes
+* Playing online is available using proxy server
+
+ADVENTURE MAP:
+* Fix for digging while opponent's turn
+* Supported right click for quick recruit window
+* Fixed problem with quests are requiring identical artefacts
+* Bulk move and swap artifacts
+* Pause & resume for towns and terrains music themes
+* Feature to assemble/disassemble artefacts in backpack
+* Clickable status bar to send messages
+* Heroes no longer have chance to receive forbidden skill on leveling up
+* Fixed visibility of newly recruited heroes near town 
+* Fixed missing artifact slot in Artifact Merchant window
+
+BATTLES:
+* Fix healing/regeneration behaviour and effect
+* Fix crashes related to auto battle
+* Implemented ray projectiles for shooters
+* Introduced default tower shooter icons
+* Towers destroyed during battle will no longer be listed as casualties
+
+AI:
+* BattleAI: Target prioritizing is now based on damage difference instead of health difference
+* Nullkiller AI can retreat and surrender
+* Nullkiller AI doesn't visit allied dwellings anymore
+* Fixed a few freezes in Nullkiller AI
+
+RANDOM MAP GENERATOR:
+* Speedup generation of random maps
+* Necromancy cannot be learned in Witch Hut on random maps
+
+MODS:
+* Supported rewardable objects customization
+* Battleground obstacles are extendable now with VLC mechanism
+* Introduced "compatibility" section into mods settings
+* Fixed bonus system for custom advmap spells
+* Supported customisable town entrance placement
+* Fixed validation of mods with new adventure map objects
+
+LAUNCHER:
+* Fixed problem with duplicated mods in the list
+* Launcher shows compatible mods only
+* Uninstall button was moved to the left of layout
+* Unsupported resolutions are not shown
+* Lobby for online gameplay is implemented
+
+MAP EDITOR:
+* Basic version of Qt-based map editor
+
 0.99 -> 1.0.0
 
 GENERAL:
@@ -98,10 +146,10 @@ BATTLES:
 
 ADVENTURE MAP:
 * Added buttons and keyboard shortcuts to quickly exchange army and artifacts between heroes
-* Fix: Captured town should not be duplicated on the UI
-
-LAUNCHER:
-* Implemented notifications about updates
+* Fix: Captured town should not be duplicated on the UI
+
+LAUNCHER:
+* Implemented notifications about updates
 * Supported redirection links for downloading mods
 
 0.98 -> 0.99

+ 4 - 21
Global.h

@@ -15,18 +15,6 @@
 // Fixed width bool data type is important for serialization
 static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 
-#ifdef __GNUC__
-#  define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__)
-#endif
-
-#if !defined(__clang__) && defined(__GNUC__) && (GCC_VERSION < 470)
-#  error VCMI requires at least gcc-4.7.2 for successful compilation or clang-3.1. Please update your compiler
-#endif
-
-#if defined(__GNUC__) && (GCC_VERSION == 470 || GCC_VERSION == 471)
-#  error This GCC version has buggy std::array::at version and should not be used. Please update to 4.7.2 or later
-#endif
-
 /* ---------------------------------------------------------------------------- */
 /* Suppress some compiler warnings */
 /* ---------------------------------------------------------------------------- */
@@ -122,10 +110,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #  define STRONG_INLINE inline
 #endif
 
-#define TO_STRING_HELPER(x) #x
-#define TO_STRING(x) TO_STRING_HELPER(x)
-#define LINE_IN_FILE __FILE__ ":" TO_STRING(__LINE__)
-
 #define _USE_MATH_DEFINES
 
 #include <cstdio>
@@ -233,7 +217,10 @@ typedef boost::lock_guard<boost::recursive_mutex> TLockGuardRec;
 /* ---------------------------------------------------------------------------- */
 // Import + Export macro declarations
 #ifdef VCMI_WINDOWS
-#  ifdef __GNUC__
+#ifdef VCMI_DLL_STATIC
+#    define DLL_IMPORT
+#    define DLL_EXPORT
+#elif defined(__GNUC__)
 #    define DLL_IMPORT __attribute__((dllimport))
 #    define DLL_EXPORT __attribute__((dllexport))
 #  else
@@ -503,9 +490,6 @@ namespace vstd
 		ptr = nullptr;
 	}
 
-#if _MSC_VER >= 1800
-	using std::make_unique;
-#else
 	template<typename T>
 	std::unique_ptr<T> make_unique()
 	{
@@ -531,7 +515,6 @@ namespace vstd
 	{
 		return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4)));
 	}
-#endif
 
 	template <typename Container>
 	typename Container::const_reference circularAt(const Container &r, size_t index)

+ 10 - 0
Mods/vcmi/config/vcmi/towerCreature.json

@@ -0,0 +1,10 @@
+{
+	"core:arrowTower" :
+	{
+		"graphics" :
+		{
+			"iconSmall" : "vcmi/creatureIcons/towerSmall",
+			"iconLarge" : "vcmi/creatureIcons/towerLarge",
+		}
+	}
+}

+ 101 - 0
Mods/vcmi/config/vcmi/towerFactions.json

@@ -0,0 +1,101 @@
+{
+	"core:castle" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:rampart" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:tower" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:inferno" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:necropolis" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:dungeon" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:stronghold" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:fortress" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	},
+	"core:conflux" :
+	{
+		"town" :
+		{
+			"siege" :
+			{
+				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
+				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
+			}
+		}
+	}
+}

+ 8 - 1
Mods/vcmi/mod.json

@@ -2,13 +2,20 @@
 	"name" : "VCMI essential files",
 	"description" : "Essential files required for VCMI to run correctly",
 
-	"version" : "1.0",
+	"version" : "1.1",
 	"author" : "VCMI Team",
 	"contact" : "http://forum.vcmi.eu/index.php",
 	"modType" : "Graphical",
+	
+	"factions" : [ "config/vcmi/towerFactions" ],
+	"creatures" : [ "config/vcmi/towerCreature" ],
 
 	"filesystem":
 	{
+		"CONFIG/" :
+		[
+			{"type" : "dir",  "path" : "/Config"}
+		],
 		"DATA/" :
 		[
 			{"type" : "dir",  "path" : "/Data"}

+ 1 - 2
client/CMT.cpp

@@ -721,7 +721,7 @@ void processCommand(const std::string &message)
 				{
 					const JsonNode & object = nameAndObject.second;
 
-					std::string name = CModHandler::normalizeIdentifier(object.meta, "core", nameAndObject.first);
+					std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first);
 
 					boost::algorithm::replace_all(name,":","_");
 
@@ -1085,7 +1085,6 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 	if(!checkVideoMode(displayIndex, w, h))
 	{
 		logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
-		return false;
 	}
 #endif
 

+ 5 - 2
client/CMakeLists.txt

@@ -200,7 +200,10 @@ else()
 	add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON})
 endif(ENABLE_DEBUG_CONSOLE)
 
-add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller)
+add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI)
+if(ENABLE_NULLKILLER_AI)
+	add_dependencies(vcmiclient Nullkiller)
+endif()
 if(APPLE_IOS)
 	if(ENABLE_ERM)
 		add_dependencies(vcmiclient vcmiERM)
@@ -266,7 +269,7 @@ elseif(APPLE_IOS)
 	set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
 endif()
 
-if(BUILD_SINGLE_APP)
+if(ENABLE_SINGLE_APP_BUILD)
 	target_link_libraries(vcmiclient PRIVATE vcmiserver)
 	if(ENABLE_LAUNCHER)
 		target_link_libraries(vcmiclient PRIVATE vcmilauncher)

+ 6 - 3
client/CServerHandler.cpp

@@ -25,12 +25,15 @@
 #include "../lib/CAndroidVMHelper.h"
 #elif defined(VCMI_IOS)
 #include "ios/utils.h"
-#include "../server/CVCMIServer.h"
-
 #include <dispatch/dispatch.h>
 #else
 #include "../lib/Interprocess.h"
 #endif
+
+#ifdef SINGLE_PROCESS_APP
+#include "../server/CVCMIServer.h"
+#endif
+
 #include "../lib/CConfigHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CThreadHelper.h"
@@ -142,7 +145,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
 	else
 		myNames.push_back(settings["general"]["playerName"].String());
 
-#if !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
+#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
 	shm.reset();
 
 	if(!settings["session"]["disable-shm"].Bool())

+ 0 - 2
client/CServerHandler.h

@@ -30,8 +30,6 @@ template<typename T> class CApplier;
 
 VCMI_LIB_NAMESPACE_END
 
-struct SharedMemory;
-
 class CClient;
 
 class CBaseForLobbyApply;

+ 1 - 1
client/Graphics.cpp

@@ -100,7 +100,7 @@ void Graphics::loadPaletteAndColors()
 void Graphics::initializeBattleGraphics()
 {
 	auto allConfigs = VLC->modh->getActiveMods();
-	allConfigs.insert(allConfigs.begin(), "core");
+	allConfigs.insert(allConfigs.begin(), CModHandler::scopeBuiltin());
 	for(auto & mod : allConfigs)
 	{
 		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/battles_graphics.json")))

+ 10 - 3
client/battle/BattleInterfaceClasses.cpp

@@ -28,6 +28,7 @@
 #include "../gui/Canvas.h"
 #include "../gui/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../widgets/AdventureMapClasses.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
@@ -123,10 +124,12 @@ BattleConsole::BattleConsole(std::shared_ptr<CPicture> backgroundSource, const P
 	background = std::make_shared<CPicture>(backgroundSource->getSurface(), Rect(imagePos, size), 0, 0 );
 }
 
-BattleConsole::~BattleConsole()
+void BattleConsole::deactivate()
 {
 	if (enteringText)
-		setEnteringMode(false);
+		LOCPLINT->cingconsole->endEnteringText(false);
+
+	CIntObject::deactivate();
 }
 
 void BattleConsole::setEnteringMode(bool on)
@@ -548,7 +551,11 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 			int yPos = 344 + step * 97;
 			for(auto & elem : br.casualties[step])
 			{
-				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", CGI->creatures()->getByIndex(elem.first)->getIconIndex(), 0, xPos, yPos));
+				auto creature = CGI->creatures()->getByIndex(elem.first);
+				if (creature->getId() == CreatureID::ARROW_TOWERS )
+					continue; // do not show destroyed towers in battle results
+
+				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, xPos, yPos));
 				std::ostringstream amount;
 				amount<<elem.second;
 				labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));

+ 2 - 1
client/battle/BattleInterfaceClasses.h

@@ -64,8 +64,9 @@ private:
 	bool enteringText;
 public:
 	BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
-	~BattleConsole();
+
 	void showAll(SDL_Surface * to) override;
+	void deactivate() override;
 
 	bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
 	void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions

+ 2 - 0
client/gui/CCursorHandler.cpp

@@ -77,6 +77,8 @@ Point CCursorHandler::position() const
 
 void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
 {
+	assert(dndObject == nullptr);
+
 	if(type != this->type)
 	{
 		this->type = type;

+ 3 - 3
client/lobby/CSavingScreen.cpp

@@ -66,10 +66,10 @@ void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to)
 
 void CSavingScreen::saveGame()
 {
-	if(!(tabSel && tabSel->inputName && tabSel->inputName->text.size()))
+	if(!(tabSel && tabSel->inputName && tabSel->inputName->getText().size()))
 		return;
 
-	std::string path = "Saves/" + tabSel->inputName->text;
+	std::string path = "Saves/" + tabSel->inputName->getText();
 
 	auto overWrite = [this, path]() -> void
 	{
@@ -82,7 +82,7 @@ void CSavingScreen::saveGame()
 	if(CResourceHandler::get("local")->existsResource(ResourceID(path, EResType::CLIENT_SAVEGAME)))
 	{
 		std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
-		boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->text);
+		boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->getText());
 		LOCPLINT->showYesNoDialog(hlp, overWrite, nullptr);
 	}
 	else

+ 3 - 3
client/lobby/CSelectionBase.cpp

@@ -321,9 +321,9 @@ CChatBox::CChatBox(const Rect & rect)
 
 void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
 {
-	if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->text.size())
+	if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->getText().size())
 	{
-		CSH->sendMessage(inputBox->text);
+		CSH->sendMessage(inputBox->getText());
 		inputBox->setText("");
 	}
 	else
@@ -333,7 +333,7 @@ void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
 void CChatBox::addNewMessage(const std::string & text)
 {
 	CCS->soundh->playSound("CHAT");
-	chatHistory->setText(chatHistory->label->text + text + "\n");
+	chatHistory->setText(chatHistory->label->getText() + text + "\n");
 	if(chatHistory->slider)
 		chatHistory->slider->moveToMax();
 }

+ 5 - 5
client/mainmenu/CMainMenu.cpp

@@ -438,7 +438,7 @@ void CMultiPlayers::onChange(std::string newText)
 	size_t namesCount = 0;
 
 	for(auto & elem : inputNames)
-		if(!elem->text.empty())
+		if(!elem->getText().empty())
 			namesCount++;
 }
 
@@ -447,8 +447,8 @@ void CMultiPlayers::enterSelectionScreen()
 	std::vector<std::string> names;
 	for(auto name : inputNames)
 	{
-		if(name->text.length())
-			names.push_back(name->text);
+		if(name->getText().length())
+			names.push_back(name->getText());
 	}
 
 	Settings name = settings.write["general"]["playerName"];
@@ -494,7 +494,7 @@ void CSimpleJoinScreen::connectToServer()
 	buttonOk->block(true);
 	CSDL_Ext::stopTextInput();
 
-	boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->text, boost::lexical_cast<ui16>(inputPort->text));
+	boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
 }
 
 void CSimpleJoinScreen::leaveScreen()
@@ -512,7 +512,7 @@ void CSimpleJoinScreen::leaveScreen()
 
 void CSimpleJoinScreen::onChange(const std::string & newText)
 {
-	buttonOk->block(inputAddress->text.empty() || inputPort->text.empty());
+	buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty());
 }
 
 void CSimpleJoinScreen::connectThread(const std::string addr, const ui16 port)

+ 23 - 2
client/widgets/AdventureMapClasses.cpp

@@ -1123,7 +1123,17 @@ void CInGameConsole::textEdited(const SDL_TextEditingEvent & event)
 
 void CInGameConsole::startEnteringText()
 {
+	if (!active)
+		return;
+
+	if (captureAllKeys)
+		return;
+
 	assert(GH.statusbar);
+	assert(currentStatusBar.expired());//effectively, nullptr check
+
+	currentStatusBar = GH.statusbar;
+
 	captureAllKeys = true;
 	enteredText = "_";
 
@@ -1142,12 +1152,23 @@ void CInGameConsole::endEnteringText(bool printEnteredText)
 		previouslyEntered.push_back(txt);
 	}
 	enteredText.clear();
-	GH.statusbar->setEnteringMode(false);
+
+	auto statusbar = currentStatusBar.lock();
+	assert(statusbar);
+
+	if (statusbar)
+		statusbar->setEnteringMode(false);
+
+	currentStatusBar.reset();
 }
 
 void CInGameConsole::refreshEnteredText()
 {
-	GH.statusbar->setEnteredText(enteredText);
+	auto statusbar = currentStatusBar.lock();
+	assert(statusbar);
+
+	if (statusbar)
+		statusbar->setEnteredText(enteredText);
 }
 
 CAdvMapPanel::CAdvMapPanel(SDL_Surface * bg, Point position)

+ 2 - 0
client/widgets/AdventureMapClasses.h

@@ -413,6 +413,8 @@ private:
 	int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1
 	int defaultTimeout; //timeout for new texts (in ms)
 	int maxDisplayedTexts; //hiw many texts can be displayed simultaneously
+
+	std::weak_ptr<IStatusBar> currentStatusBar;
 public:
 	std::string enteredText;
 	void show(SDL_Surface * to) override;

+ 16 - 2
client/widgets/CArtifactHolder.cpp

@@ -254,6 +254,22 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState)
 	}
 }
 
+void CArtifactsOfHero::activate()
+{
+	if (commonInfo->src.AOH == this && commonInfo->src.art)
+		CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", commonInfo->src.art->artType->getIconIndex()));
+
+	CIntObject::activate();
+}
+
+void CArtifactsOfHero::deactivate()
+{
+	if (commonInfo->src.AOH == this && commonInfo->src.art)
+		CCS->curh->dragAndDropCursor(nullptr);
+
+	CIntObject::deactivate();
+}
+
 /**
  * Selects artifact slot so that the containing artifact looks like it's picked up.
  */
@@ -675,10 +691,8 @@ void CArtifactsOfHero::updateParentWindow()
 
 		if(!updateState)
 		{
-			cew->deactivate();
 			cew->updateWidgets();
 			cew->redraw();
-			cew->activate();
 		}
 	}
 }

+ 3 - 0
client/widgets/CArtifactHolder.h

@@ -148,6 +148,9 @@ public:
 	void dispose(); //free resources not needed after closing windows and reset state
 	void scrollBackpack(int dir); //dir==-1 => to left; dir==1 => to right
 
+	void activate() override;
+	void deactivate() override;
+
 	void safeRedraw();
 	void markPossibleSlots(const CArtifactInstance* art);
 	void unmarkSlots(bool withRedraw = true); //unmarks slots in all visible AOHs

+ 9 - 4
client/widgets/TextControls.cpp

@@ -14,7 +14,9 @@
 #include "Images.h"
 
 #include "../CMessage.h"
+#include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
+#include "../widgets/AdventureMapClasses.h"
 
 #include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
 
@@ -428,14 +430,17 @@ void CGStatusBar::clickLeft(tribool down, bool previousState)
 {
 	if(!down && onClick)
 	{
-		onClick();
+		if(LOCPLINT && LOCPLINT->cingconsole->active)
+			LOCPLINT->cingconsole->startEnteringText();
 	}
 }
 
-void CGStatusBar::setOnClick(std::function<void()> handler)
+void CGStatusBar::deactivate()
 {
-	onClick = handler;
-	addUsedEvents(LCLICK);
+	if (enteringText)
+		LOCPLINT->cingconsole->endEnteringText(false);
+
+	CIntObject::deactivate();
 }
 
 Point CGStatusBar::getBorderSize()

+ 11 - 4
client/widgets/TextControls.h

@@ -41,11 +41,11 @@ protected:
 	virtual std::string visibleText();
 
 	std::shared_ptr<CPicture> background;
-public:
-
 	std::string text;
 	bool autoRedraw;  //whether control will redraw itself on setTxt
 
+public:
+
 	std::string getText();
 	virtual void setAutoRedraw(bool option);
 	virtual void setText(const std::string & Txt);
@@ -124,6 +124,14 @@ class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusB
 
 	CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
 	CGStatusBar(int x, int y, std::string name, int maxw = -1);
+
+	//make CLabel API private
+	using CLabel::getText;
+	using CLabel::setAutoRedraw;
+	using CLabel::setText;
+	using CLabel::setColor;
+	using CLabel::getWidth;
+
 protected:
 	Point getBorderSize() override;
 
@@ -141,9 +149,8 @@ public:
 		return ret;
 	}
 
-	void setOnClick(std::function<void()> handler);
-
 	void show(SDL_Surface * to) override;
+	void deactivate() override;
 
 	// IStatusBar interface
 	void write(const std::string & Text) override;

+ 3 - 7
client/windows/CAdvmapInterface.cpp

@@ -716,11 +716,6 @@ CAdvMapInt::CAdvMapInt():
 	worldViewUnderground->block(!CGI->mh->map->twoLevel);
 
 	addUsedEvents(MOVE);
-
-	statusbar->setOnClick([&]
-		{
-			if(LOCPLINT) LOCPLINT->cingconsole->startEnteringText();
-		});
 }
 
 CAdvMapInt::~CAdvMapInt()
@@ -981,6 +976,7 @@ void CAdvMapInt::deactivate()
 		}
 		minimap.deactivate();
 		terrain.deactivate();
+		statusbar->deactivate();
 	}
 }
 
@@ -1670,13 +1666,13 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
 		std::string text = curHero() ? objAtTile->getHoverText(curHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
 		boost::replace_all(text,"\n"," ");
-		statusbar->setText(text);
+		statusbar->write(text);
 	}
 	else
 	{
 		std::string hlp;
 		CGI->mh->getTerrainDescr(mapPos, hlp, false);
-		statusbar->setText(hlp);
+		statusbar->write(hlp);
 	}
 
 	if(spellBeingCasted)

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -1019,7 +1019,7 @@ void CCreaInfo::update()
 		else
 			value = boost::lexical_cast<std::string>(town->creatureGrowth(level));
 
-		if(value != label->text)
+		if(value != label->getText())
 			label->setText(value);
 	}
 }

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -799,7 +799,7 @@ void CTownItem::updateGarrisons()
 void CTownItem::update()
 {
 	std::string incomeVal = boost::lexical_cast<std::string>(town->dailyIncome()[Res::GOLD]);
-	if (incomeVal != income->text)
+	if (incomeVal != income->getText())
 		income->setText(incomeVal);
 
 	heroes->update();

+ 3 - 3
client/windows/CSpellWindow.cpp

@@ -66,7 +66,7 @@ void CSpellWindow::InteractiveArea::clickRight(tribool down, bool previousState)
 void CSpellWindow::InteractiveArea::hover(bool on)
 {
 	if(on)
-		owner->statusBar->setText(hoverText);
+		owner->statusBar->write(hoverText);
 	else
 		owner->statusBar->clear();
 }
@@ -513,7 +513,7 @@ CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
 	cost = std::make_shared<CLabel>(39, 94, FONT_TINY, ETextAlignment::CENTER);
 
 	for(auto l : {name, level, cost})
-		l->autoRedraw = false;
+		l->setAutoRedraw(false);
 }
 
 CSpellWindow::SpellArea::~SpellArea() = default;
@@ -609,7 +609,7 @@ void CSpellWindow::SpellArea::hover(bool on)
 	if(mySpell)
 	{
 		if(on)
-			owner->statusBar->setText(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level]));
+			owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level]));
 		else
 			owner->statusBar->clear();
 	}

+ 4 - 0
client/windows/CTradeWindow.cpp

@@ -676,6 +676,10 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 			break;
 		case EMarketMode::ARTIFACT_RESOURCE:
 			title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
+
+			// create image that copies part of background containing slot MISC_1 into position of slot MISC_5
+			// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing
+			images.push_back(std::make_shared<CPicture>(background->bg, Rect(20, 187, 47, 47), 18, 339 ));
 			sliderNeeded = false;
 			break;
 		default:

+ 2 - 0
client/windows/CTradeWindow.h

@@ -21,6 +21,7 @@ VCMI_LIB_NAMESPACE_END
 
 class CSlider;
 class CTextBox;
+class CPicture;
 class CGStatusBar;
 
 class CTradeWindow : public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice
@@ -108,6 +109,7 @@ public:
 protected:
 	std::shared_ptr<CGStatusBar> statusBar;
 	std::vector<std::shared_ptr<CLabel>> labels;
+	std::vector<std::shared_ptr<CPicture>> images;
 	std::vector<std::shared_ptr<CButton>> buttons;
 	std::vector<std::shared_ptr<CTextBox>> texts;
 };

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -579,7 +579,7 @@ void CSystemOptionsWindow::selectGameRes()
 #endif
 
 		auto resolutionStr = resolutionToString(resolution.first, resolution.second);
-		if(gameResLabel->text == resolutionStr)
+		if(gameResLabel->getText() == resolutionStr)
 			currentResolutionIndex = i;
 		items.push_back(std::move(resolutionStr));
 		++i;

+ 1 - 1
cmake_modules/VCMI_lib.cmake

@@ -487,7 +487,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 	enable_pch(${TARGET_NAME})
 
 	# We want to deploy assets into build directory for easier debugging without install
-	if(NOT APPLE_IOS)
+	if(COPY_CONFIG_ON_BUILD)
 		add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods

+ 1 - 1
cmake_modules/VersionDefinition.cmake

@@ -1,5 +1,5 @@
 set(VCMI_VERSION_MAJOR 1)
-set(VCMI_VERSION_MINOR 1)
+set(VCMI_VERSION_MINOR 2)
 set(VCMI_VERSION_PATCH 0)
 add_definitions(
 	-DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR}

+ 0 - 2
config/creatures/special.json

@@ -123,8 +123,6 @@
 		},
 		"graphics" :
 		{
-			"iconSmall" : "vcmi/creatureIcons/towerSmall",
-			"iconLarge" : "vcmi/creatureIcons/towerLarge",
 			"animation": "CLCBOW.DEF" // needed to pass validation, never used
 		},
 		"sound": {}

+ 0 - 2
config/factions/castle.json

@@ -203,8 +203,6 @@
 			"siege" :
 			{
 				"shooter" : "archer",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGCS",
 				"gate" :
 				{

+ 0 - 2
config/factions/conflux.json

@@ -210,8 +210,6 @@
 			"siege" :
 			{
 				"shooter" : "stormElemental",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGEL",
 				"gate" :
 				{

+ 0 - 2
config/factions/dungeon.json

@@ -204,8 +204,6 @@
 			"siege" :
 			{
 				"shooter" : "medusa",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGDN",
 				"gate" :
 				{

+ 0 - 2
config/factions/fortress.json

@@ -209,8 +209,6 @@
 			"siege" :
 			{
 				"shooter" : "lizardman",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGFR",
 				"gate" :
 				{

+ 0 - 2
config/factions/inferno.json

@@ -204,8 +204,6 @@
 			"siege" :
 			{
 				"shooter" : "gog",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGIN",
 				"gate" :
 				{

+ 0 - 2
config/factions/necropolis.json

@@ -214,8 +214,6 @@
 			"siege" :
 			{
 				"shooter" : "lich",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGNC",
 				"gate" :
 				{

+ 0 - 2
config/factions/rampart.json

@@ -211,8 +211,6 @@
 			"siege" :
 			{
 				"shooter" : "woodElf",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGRM",
 				"gate" :
 				{

+ 0 - 2
config/factions/stronghold.json

@@ -203,8 +203,6 @@
 			{
 				"shooter" : "orc",
 				"imagePrefix" : "SGST",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"gate" :
 				{
 					"arch" : { "x" : 478, "y" : 235 },

+ 0 - 2
config/factions/tower.json

@@ -202,8 +202,6 @@
 			"siege" :
 			{
 				"shooter" : "mage",
-				"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
-				"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
 				"imagePrefix" : "SGTW",
 				"gate" :
 				{

+ 23 - 0
config/schemas/mod.json

@@ -56,6 +56,23 @@
 			"description": "List of mods that can't be enabled in the same time as this one",
 			"items": { "type":"string" }
 		},
+		"compatibility" : {
+			"type":"object",
+			"description": "Supported versions of vcmi engine",
+			"additionalProperties" : false,
+			"properties" : {
+				"min" : {
+					"type" : "string",
+					"description" : "minimal compatible vcmi engine version in a format major.minor.patch. When specified, earlier versions won't be supported"
+					//"pattern" : "^\\d+\\.\\d+\\.\\d+$" // Not implemented in schema support
+				},
+				"max" : {
+					"type" : "string",
+					"description" : "maximum compatible vcmi engine version in a format major.minor.patch. When specified, later versions won't be supported"
+					//"pattern" : "^\\d+\\.\\d+\\.\\d+$" // Not implemented in schema support
+				}
+			}
+		},
 
 		"keepDisabled" : {
 			"type":"boolean",
@@ -118,6 +135,12 @@
 			"description": "List of configuration files for battlefields",
 			"items": { "type":"string", "format" : "textFile" }
 
+		},
+		"obstacles":{
+			"type":"array",
+			"description": "List of configuration files for obstacles",
+			"items": { "type":"string", "format" : "textFile" }
+
 		},
 
 		"changelog" : {

+ 6 - 0
debian/changelog

@@ -1,3 +1,9 @@
+vcmi (1.1.0) jammy; urgency=medium
+
+  * New upstream release
+
+ -- Ivan Savenko <[email protected]>  Fri, 23 Dec 2022 12:00:00 +0200
+ 
 vcmi (1.0.0) jammy; urgency=medium
 
   * New upstream release

+ 1 - 1
launcher/CMakeLists.txt

@@ -91,7 +91,7 @@ if(WIN32)
 	set(launcher_ICON VCMI_launcher.rc)
 endif()
 
-if(BUILD_SINGLE_APP)
+if(ENABLE_SINGLE_APP_BUILD)
 	add_library(vcmilauncher STATIC ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS})
 else()
 	add_executable(vcmilauncher WIN32 ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON})

+ 1 - 0
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -38,6 +38,7 @@
 	<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
 	<url type="faq">https://vcmi.eu/faq/</url>
 	<releases>
+		<release version="1.1.0" date="2022-12-23" />
 		<release version="1.0.0" date="2022-09-11" />
 		<release version="0.99" date="2016-11-01" />
 	</releases>

+ 1 - 1
launcher/modManager/cmodlist.cpp

@@ -31,7 +31,7 @@ bool isCompatible(const QString & verMin, const QString & verMax)
 		if(ver.segmentCount() < maxSections)
 		{
 			auto segments = ver.segments();
-			for(int i = segments.size() - 1; i < maxSections; ++i)
+			for(int i = segments.size(); i < maxSections; ++i)
 				segments.append(0);
 			ver = QVersionNumber(segments);
 		}

+ 24 - 14
lib/CArtHandler.cpp

@@ -280,7 +280,7 @@ std::vector<JsonNode> CArtHandler::loadLegacyData(size_t dataSize)
 
 void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size());
 
 	object->iconIndex = object->getIndex() + 5;
 
@@ -291,7 +291,7 @@ void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode
 
 void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
 
 	object->iconIndex = object->getIndex();
 
@@ -861,8 +861,6 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
 	al.getHolderArtSet()->eraseArtSlot(al.slot);
 	if(!ArtifactUtils::isSlotBackpack(al.slot))
 		al.getHolderNode()->detachFrom(*this);
-
-	//TODO delete me?
 }
 
 bool CArtifactInstance::canBeDisassembled() const
@@ -1062,7 +1060,9 @@ void CCombinedArtifactInstance::createConstituents()
 
 void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, ArtifactPosition slot)
 {
-	assert(vstd::contains(*artType->constituents, art->artType.get()));
+	assert(vstd::contains_if(*artType->constituents, [=](const CArtifact * constituent){
+		return constituent->id == art->artType->id;
+	}));
 	assert(art->getParentNodes().size() == 1  &&  art->getParentNodes().front() == art->artType);
 	constituentsInfo.push_back(ConstituentInfo(art, slot));
 	attachTo(*art);
@@ -1354,11 +1354,16 @@ bool CArtifactSet::isPositionFree(ArtifactPosition pos, bool onlyLockCheck) cons
 ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot)
 {
 	assert(!vstd::contains(artifactsWorn, slot));
-	ArtSlotInfo &ret = !ArtifactUtils::isSlotBackpack(slot)
-		? artifactsWorn[slot]
-		: *artifactsInBackpack.insert(artifactsInBackpack.begin() + (slot - GameConstants::BACKPACK_START), ArtSlotInfo());
 
-	return ret;
+	if (!ArtifactUtils::isSlotBackpack(slot))
+		return artifactsWorn[slot];
+
+	ArtSlotInfo newSlot;
+	size_t index  = slot - GameConstants::BACKPACK_START;
+	auto position = artifactsInBackpack.begin() + index;
+	auto inserted = artifactsInBackpack.insert(position, newSlot);
+
+	return *inserted;
 }
 
 void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked)
@@ -1372,9 +1377,10 @@ void CArtifactSet::eraseArtSlot(ArtifactPosition slot)
 {
 	if(ArtifactUtils::isSlotBackpack(slot))
 	{
-		assert(artifactsInBackpack.begin() + slot < artifactsInBackpack.end());
-		slot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
-		artifactsInBackpack.erase(artifactsInBackpack.begin() + slot);
+		auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
+
+		assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end());
+		artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot);
 	}
 	else
 	{
@@ -1547,16 +1553,18 @@ DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtifactDstPosition(	const CArtif
 
 DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::unmovableSlots()
 {
-	return
+	static const std::vector<ArtifactPosition::EArtifactPosition> positions =
 	{
 		ArtifactPosition::SPELLBOOK,
 		ArtifactPosition::MACH4
 	};
+
+	return positions;
 }
 
 DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::constituentWornSlots()
 {
-	return
+	static const std::vector<ArtifactPosition::EArtifactPosition> positions =
 	{
 		ArtifactPosition::HEAD,
 		ArtifactPosition::SHOULDERS,
@@ -1573,6 +1581,8 @@ DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUti
 		ArtifactPosition::MISC4,
 		ArtifactPosition::MISC5,
 	};
+
+	return positions;
 }
 
 DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot)

+ 4 - 0
lib/CConsoleHandler.cpp

@@ -19,6 +19,8 @@ boost::mutex CConsoleHandler::smx;
 
 DLL_LINKAGE CConsoleHandler * console = nullptr;
 
+VCMI_LIB_NAMESPACE_END
+
 #ifndef VCMI_WINDOWS
 	typedef std::string TColor;
 	#define CONSOLE_GREEN "\x1b[1;32m"
@@ -51,6 +53,8 @@ DLL_LINKAGE CConsoleHandler * console = nullptr;
 
 static TColor defColor;
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 #ifdef VCMI_WINDOWS
 
 void printWinError()

+ 1 - 1
lib/CCreatureHandler.cpp

@@ -425,7 +425,7 @@ const CCreature * CCreatureHandler::getCreature(const std::string & scope, const
 void CCreatureHandler::loadCommanders()
 {
 	JsonNode data(ResourceID("config/commanders.json"));
-	data.setMeta("core"); // assume that commanders are in core mod (for proper bonuses resolution)
+	data.setMeta(CModHandler::scopeBuiltin()); // assume that commanders are in core mod (for proper bonuses resolution)
 
 	const JsonNode & config = data; // switch to const data accessors
 

+ 1 - 1
lib/CCreatureSet.cpp

@@ -1053,7 +1053,7 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
 		std::string typeName("");
 		handler.serializeString("type", typeName);
 		if(!typeName.empty())
-			setType(VLC->creh->getCreature("core", typeName));
+			setType(VLC->creh->getCreature(CModHandler::scopeMap(), typeName));
 	}
 }
 

+ 36 - 10
lib/CGameState.cpp

@@ -760,6 +760,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
 	initHeroes();
 	initStartingBonus();
 	initTowns();
+	placeHeroesInTowns();
 	initMapObjects();
 	buildBonusSystemTree();
 	initVisitingAndGarrisonedHeroes();
@@ -1363,7 +1364,8 @@ void CGameState::placeStartingHeroes()
 			{
 				if(auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap())
 				{
-					if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1)) continue;
+					if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1))
+						continue;
 				}
 			}
 
@@ -1861,6 +1863,37 @@ void CGameState::initMapObjects()
 	map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized
 }
 
+void CGameState::placeHeroesInTowns()
+{
+	for(auto k=players.begin(); k!=players.end(); ++k)
+	{
+		if(k->first==PlayerColor::NEUTRAL)
+			continue;
+
+		for(CGHeroInstance *h : k->second.heroes)
+		{
+			for(CGTownInstance *t : k->second.towns)
+			{
+				bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y);
+
+				// current hero position is at one of blocking tiles of current town
+				// assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position
+				if (heroOnTownBlockableTile)
+				{
+					int3 townVisitablePos = t->visitablePos();
+					int3 correctedPos = townVisitablePos + h->getVisitableOffset();
+
+					map->removeBlockVisTiles(h);
+					h->pos = correctedPos;
+					map->addBlockVisTiles(h);
+
+					assert(t->visitableAt(h->visitablePos().x, h->visitablePos().y));
+				}
+			}
+		}
+	}
+}
+
 void CGameState::initVisitingAndGarrisonedHeroes()
 {
 	for(auto k=players.begin(); k!=players.end(); ++k)
@@ -1873,17 +1906,10 @@ void CGameState::initVisitingAndGarrisonedHeroes()
 		{
 			for(CGTownInstance *t : k->second.towns)
 			{
-				int3 vistile = t->visitablePos(); vistile.x++; //tile next to the entrance
-				if(vistile == h->pos || h->pos==t->visitablePos())
+				if (t->visitableAt(h->visitablePos().x, h->visitablePos().y))
 				{
+					assert(t->visitingHero == nullptr);
 					t->setVisitingHero(h);
-					if(h->pos == t->pos) //visiting hero placed in the editor has same pos as the town - we need to correct it
-					{
-						map->removeBlockVisTiles(h);
-						h->pos.x -= 1;
-						map->addBlockVisTiles(h);
-					}
-					break;
 				}
 			}
 		}

+ 1 - 2
lib/CGameState.h

@@ -269,6 +269,7 @@ private:
 	void placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeId, int3 townPos);
 	void initStartingResources();
 	void initHeroes();
+	void placeHeroesInTowns();
 	void giveCampaignBonusToHero(CGHeroInstance * hero);
 	void initFogOfWar();
 	void initStartingBonus();
@@ -297,8 +298,6 @@ private:
 	CRandomGenerator rand;
 	Services * services;
 
-	friend class CCallback;
-	friend class CClient;
 	friend class IGameCallback;
 	friend class CMapHandler;
 	friend class CGameHandler;

+ 3 - 3
lib/CHeroHandler.cpp

@@ -348,7 +348,7 @@ CHeroHandler::CHeroHandler()
 	loadTerrains();
 	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 	{
-		VLC->modh->identifiers.registerObject("core", "terrain", terrain.name, terrain.id);
+		VLC->modh->identifiers.registerObject(CModHandler::scopeBuiltin(), "terrain", terrain.name, terrain.id);
 	}
 	loadBallistics();
 	loadExperience();
@@ -868,7 +868,7 @@ std::vector<JsonNode> CHeroHandler::loadLegacyData(size_t dataSize)
 void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
 	size_t index = objects.size();
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
 	object->imageIndex = (si32)index + GameConstants::HERO_PORTRAIT_SHIFT; // 2 special frames + some extra portraits
 
 	objects.push_back(object);
@@ -878,7 +878,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
 	object->imageIndex = static_cast<si32>(index);
 
 	assert(objects[index] == nullptr); // ensure that this id was not loaded before

+ 3 - 0
lib/CMakeLists.txt

@@ -1 +1,4 @@
 add_main_lib(vcmi SHARED)
+if(ENABLE_SINGLE_APP_BUILD)
+	target_compile_definitions(vcmi PUBLIC VCMI_LIB_NAMESPACE=LIB_CLIENT)
+endif()

+ 61 - 20
lib/CModHandler.cpp

@@ -209,16 +209,15 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
 	// called have not specified destination mod explicitly
 	if (request.remoteScope.empty())
 	{
-		// FIXME: temporary, for queries from map loader allow access to any identifer
-		// should be changed to list of mods that are marked as required by current map
-		if (request.localScope == "map")
+		// special scope that should have access to all in-game objects
+		if (request.localScope == CModHandler::scopeGame())
 		{
 			for (auto const & modName : VLC->modh->getActiveMods())
 				allowedScopes.insert(modName);
 		}
 
-		// normally ID's from all required mods, own mod and virtual "core" mod are allowed
-		else if(request.localScope != "core" && !request.localScope.empty())
+		// normally ID's from all required mods, own mod and virtual built-in mod are allowed
+		else if(request.localScope != CModHandler::scopeBuiltin() && !request.localScope.empty())
 		{
 			allowedScopes = VLC->modh->getModDependencies(request.localScope, isValidScope);
 
@@ -229,15 +228,19 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
 		}
 
 		// all mods can access built-in mod
-		allowedScopes.insert("core");
+		allowedScopes.insert(CModHandler::scopeBuiltin());
 	}
 	else
 	{
 		//if destination mod was specified explicitly, restrict lookup to this mod
-
-		if(request.remoteScope == "core" )
+		if(request.remoteScope == CModHandler::scopeBuiltin() )
+		{
+			//built-in mod is an implicit dependency for all mods, allow access into it
+			allowedScopes.insert(request.remoteScope);
+		}
+		else if ( request.localScope == CModHandler::scopeGame() )
 		{
-			//"core" mod is an implicit dependency for all mods, allow access into it
+			// allow access, this is special scope that should have access to all in-game objects
 			allowedScopes.insert(request.remoteScope);
 		}
 		else if(request.remoteScope == request.localScope )
@@ -340,7 +343,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objec
 {
 	for(auto & node : originalData)
 	{
-		node.setMeta("core");
+		node.setMeta(CModHandler::scopeBuiltin());
 	}
 }
 
@@ -401,6 +404,9 @@ bool ContentTypeHandler::loadMod(std::string modName, bool validate)
 
 		if (vstd::contains(data.Struct(), "index") && !data["index"].isNull())
 		{
+			if (modName != "core")
+				logMod->warn("Mod %s is attempting to load original data! This should be reserved for built-in mod.", modName);
+
 			// try to add H3 object data
 			size_t index = static_cast<size_t>(data["index"].Float());
 
@@ -413,7 +419,7 @@ bool ContentTypeHandler::loadMod(std::string modName, bool validate)
 			}
 			else
 			{
-				logMod->warn("no original data in loadMod(%s) at index %d", name, index);
+				logMod->trace("no original data in loadMod(%s) at index %d", name, index);
 			}
 			performValidate(data, name);
 			handler->loadObject(modName, name, data, index);
@@ -506,7 +512,7 @@ void CContentHandler::preloadData(CModInfo & mod)
 	// print message in format [<8-symbols checksum>] <modname>
 	logMod->info("\t\t[%08x]%s", mod.checksum, mod.name);
 
-	if (validate && mod.identifier != "core")
+	if (validate && mod.identifier != CModHandler::scopeBuiltin())
 	{
 		if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier))
 			mod.validation = CModInfo::FAILED;
@@ -698,13 +704,13 @@ CModHandler::CModHandler() : content(std::make_shared<CContentHandler>())
     modules.MITHRIL = false;
 	for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i)
 	{
-		identifiers.registerObject("core", "resource", GameConstants::RESOURCE_NAMES[i], i);
+		identifiers.registerObject(CModHandler::scopeBuiltin(), "resource", GameConstants::RESOURCE_NAMES[i], i);
 	}
 
 	for(int i=0; i<GameConstants::PRIMARY_SKILLS; ++i)
 	{
-		identifiers.registerObject("core", "primSkill", PrimarySkill::names[i], i);
-		identifiers.registerObject("core", "primarySkill", PrimarySkill::names[i], i);
+		identifiers.registerObject(CModHandler::scopeBuiltin(), "primSkill", PrimarySkill::names[i], i);
+		identifiers.registerObject(CModHandler::scopeBuiltin(), "primarySkill", PrimarySkill::names[i], i);
 	}
 }
 
@@ -905,6 +911,35 @@ std::vector<std::string> CModHandler::getModList(std::string path)
 	return foundMods;
 }
 
+bool CModHandler::isScopeReserved(const TModID & scope)
+{
+	static const std::array<TModID, 3> reservedScopes = {
+		"core", "map", "game"
+	};
+
+	return std::find(reservedScopes.begin(), reservedScopes.end(), scope) != reservedScopes.end();
+}
+
+const TModID & CModHandler::scopeBuiltin()
+{
+	static const TModID scope = "core";
+	return scope;
+}
+
+const TModID & CModHandler::scopeGame()
+{
+	static const TModID scope = "game";
+	return scope;
+}
+
+const TModID & CModHandler::scopeMap()
+{
+	//TODO: implement accessing map dependencies for both H3 and VCMI maps
+	// for now, allow access to any identifiers
+	static const TModID scope = "game";
+	return scope;
+}
+
 void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
 {
 	for(std::string modName : getModList(path))
@@ -916,6 +951,12 @@ void CModHandler::loadOneMod(std::string modName, std::string parent, const Json
 	boost::to_lower(modName);
 	std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
 
+	if ( isScopeReserved(modFullName))
+	{
+		logMod->error("Can not load mod %s - this name is reserved for internal use!", modFullName);
+		return;
+	}
+
 	if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
 	{
 		CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
@@ -944,7 +985,7 @@ void CModHandler::loadMods(bool onlyEssential)
 		loadMods("", "", modConfig["activeMods"], true);
 	}
 
-	coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
+	coreMod = CModInfo(CModHandler::scopeBuiltin(), modConfig[CModHandler::scopeBuiltin()], JsonNode(ResourceID("config/gameConfig.json")));
 	coreMod.name = "Original game files";
 }
 
@@ -991,7 +1032,7 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade
 
 	// second - add mod.json into checksum because filesystem does not contains this file
 	// FIXME: remove workaround for core mod
-	if (modName != "core")
+	if (modName != CModHandler::scopeBuiltin())
 	{
 		ResourceID modConfFile(CModInfo::getModFile(modName), EResType::TEXT);
 		ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32();
@@ -1017,7 +1058,7 @@ void CModHandler::loadModFilesystems()
 {
 	activeMods = validateAndSortDependencies(activeMods);
 
-	coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::get("core")));
+	coreMod.updateChecksum(calculateModChecksum(CModHandler::scopeBuiltin(), CResourceHandler::get(CModHandler::scopeBuiltin())));
 
 	for(std::string & modName : activeMods)
 	{
@@ -1057,7 +1098,7 @@ void CModHandler::load()
 		allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName)));
 	}
 
-	// first - load virtual "core" mod that contains all data
+	// first - load virtual builtin mod that contains all data
 	// TODO? move all data into real mods? RoE, AB, SoD, WoG
 	content->preloadData(coreMod);
 	for(const TModID & modName : activeMods)
@@ -1096,7 +1137,7 @@ void CModHandler::afterLoad(bool onlyEssential)
 
 		modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData();
 	}
-	modSettings["core"] = coreMod.saveLocalData();
+	modSettings[CModHandler::scopeBuiltin()] = coreMod.saveLocalData();
 
 	if(!onlyEssential)
 	{

+ 19 - 1
lib/CModHandler.h

@@ -14,6 +14,12 @@
 #include "VCMI_Lib.h"
 #include "JsonNode.h"
 
+#ifdef __UCLIBC__
+#undef major
+#undef minor
+#undef patch
+#endif
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CModHandler;
@@ -277,7 +283,19 @@ class DLL_LINKAGE CModHandler
 	void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);
 	void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods);
 public:
-	
+
+	/// returns true if scope is reserved for internal use and can not be used by mods
+	static bool isScopeReserved(const TModID & scope);
+
+	/// reserved scope name for referencing built-in (e.g. H3) objects
+	static const TModID & scopeBuiltin();
+
+	/// reserved scope name for accessing objects from any loaded mod
+	static const TModID & scopeGame();
+
+	/// reserved scope name for accessing object for map loading
+	static const TModID & scopeMap();
+
 	class DLL_LINKAGE Incompatibility: public std::exception
 	{
 	public:

+ 1 - 1
lib/CPathfinder.cpp

@@ -625,7 +625,7 @@ void LayerTransitionRule::process(
 		else if(destination.node->accessible != CGPathNode::ACCESSIBLE)
 		{
 			/// Hero that fly can only land on accessible tiles
-			if(!destination.isGuardianTile && destination.nodeObject)
+			if(destination.nodeObject)
 				destination.blocked = true;
 		}
 

+ 1 - 1
lib/CSkillHandler.cpp

@@ -267,7 +267,7 @@ std::vector<bool> CSkillHandler::getDefaultAllowed() const
 
 si32 CSkillHandler::decodeSkill(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "skill", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "skill", identifier);
 	if(rawId)
 		return rawId.get();
 	else

+ 3 - 3
lib/CTownHandler.cpp

@@ -1010,7 +1010,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size());
 
 	objects.push_back(object);
 
@@ -1049,7 +1049,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
 
 	if (objects.size() > index)
 		assert(objects[index] == nullptr); // ensure that this id was not loaded before
@@ -1083,7 +1083,7 @@ void CTownHandler::loadRandomFaction()
 	static const ResourceID randomFactionPath("config/factions/random.json");
 
 	JsonNode randomFactionJson(randomFactionPath);
-	randomFactionJson.setMeta("core", true);
+	randomFactionJson.setMeta(CModHandler::scopeBuiltin(), true);
 	loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]);
 }
 

+ 7 - 7
lib/GameConstants.cpp

@@ -62,7 +62,7 @@ namespace GameConstants
 
 si32 HeroTypeID::decode(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", identifier);
 	if(rawId)
 		return rawId.get();
 	else
@@ -86,7 +86,7 @@ const Artifact * ArtifactID::toArtifact(const ArtifactService * service) const
 
 si32 ArtifactID::decode(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "artifact", identifier);
 	if(rawId)
 		return rawId.get();
 	else
@@ -110,7 +110,7 @@ const Creature * CreatureID::toCreature(const CreatureService * creatures) const
 
 si32 CreatureID::decode(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", identifier);
 	if(rawId)
 		return rawId.get();
 	else
@@ -139,7 +139,7 @@ const spells::Spell * SpellID::toSpell(const spells::Service * service) const
 
 si32 SpellID::decode(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "spell", identifier);
 	if(rawId)
 		return rawId.get();
 	else
@@ -201,7 +201,7 @@ const FactionID FactionID::NEUTRAL = FactionID(9);
 
 si32 FactionID::decode(const std::string & identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "faction", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "faction", identifier);
 	if(rawId)
 		return rawId.get();
 	else
@@ -288,7 +288,7 @@ const BattleFieldInfo * BattleField::getInfo() const
 
 BattleField BattleField::fromString(std::string identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "battlefield", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "battlefield", identifier);
 
 	if(rawId)
 		return BattleField(rawId.get());
@@ -308,7 +308,7 @@ Obstacle::operator std::string() const
 
 Obstacle Obstacle::fromString(std::string identifier)
 {
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "obstacle", identifier);
+	auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "obstacle", identifier);
 
 	if(rawId)
 		return Obstacle(rawId.get());

+ 4 - 0
lib/IHandlerBase.cpp

@@ -14,6 +14,10 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+std::string IHandlerBase::getScopeBuiltin() const
+{
+	return CModHandler::scopeBuiltin();
+}
 
 void IHandlerBase::registerObject(std::string scope, std::string type_name, std::string name, si32 index)
 {

+ 4 - 2
lib/IHandlerBase.h

@@ -21,6 +21,8 @@ class Entity;
 class DLL_LINKAGE IHandlerBase
 {
 protected:
+	std::string getScopeBuiltin() const;
+
 	/// Calls modhandler. Mostly needed to avoid large number of includes in headers
 	void registerObject(std::string scope, std::string type_name, std::string name, si32 index);
 	std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const;
@@ -92,7 +94,7 @@ public:
 
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override
 	{
-		auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
+		auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), objects.size());
 
 		objects.push_back(object);
 
@@ -102,7 +104,7 @@ public:
 
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
 	{
-		auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
+		auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), index);
 
 		assert(objects[index] == nullptr); // ensure that this id was not loaded before
 		objects[index] = object;

+ 2 - 2
lib/JsonDetail.cpp

@@ -999,7 +999,7 @@ namespace
 		bool testFilePresence(std::string scope, ResourceID resource)
 		{
 			std::set<std::string> allowedScopes;
-			if(scope != "core" && !scope.empty()) // all real mods may have dependencies
+			if(scope != CModHandler::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
 			{
 				//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
 				bool found = true;
@@ -1008,7 +1008,7 @@ namespace
 				if(!found)
 					return false;
 
-				allowedScopes.insert("core"); // all mods can use H3 files
+				allowedScopes.insert(CModHandler::scopeBuiltin()); // all mods can use H3 files
 			}
 			allowedScopes.insert(scope); // mods can use their own files
 

+ 12 - 2
lib/NetPacksLib.cpp

@@ -703,13 +703,18 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs)
 	//bonus system
 	h->detachFrom(gs->globalEffects);
 	h->attachTo(*gs->getPlayerState(player));
-	h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
 
+	auto oldOffset = h->getVisitableOffset();
 	gs->map->removeBlockVisTiles(h,true);
+	h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
+	auto newOffset = h->getVisitableOffset();
+
 	h->setOwner(player);
 	h->movement =  h->maxMovePoints(true);
+	h->pos = h->pos - oldOffset + newOffset;
 	gs->map->heroesOnMap.push_back(h);
 	gs->getPlayerState(h->getOwner())->heroes.push_back(h);
+
 	gs->map->addBlockVisTiles(h);
 	h->inTownGarrison = false;
 }
@@ -1171,7 +1176,12 @@ DLL_LINKAGE void AssembledArtifact::applyGs(CGameState *gs)
 	const CArtifactInstance *transformedArt = al.getArt();
 	assert(transformedArt);
 	bool combineEquipped = !ArtifactUtils::isSlotBackpack(al.slot);
-	assert(vstd::contains(transformedArt->assemblyPossibilities(artSet, combineEquipped), builtArt));
+
+	assert(vstd::contains_if(transformedArt->assemblyPossibilities(artSet, combineEquipped), [=](const CArtifact * art)->bool
+		{
+			return art->id == builtArt->id;
+		}));
+
 	UNUSED(transformedArt);
 
 	auto combinedArt = new CCombinedArtifactInstance(builtArt);

+ 1 - 1
lib/ScriptHandler.cpp

@@ -242,7 +242,7 @@ ScriptPtr ScriptHandler::loadFromJson(vstd::CLoggerBase * logger, const std::str
 
 void ScriptHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(logMod, scope, data, normalizeIdentifier(scope, "core", name));
+	auto object = loadFromJson(logMod, scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
 	objects[object->identifier] = object;
 }
 

+ 1 - 1
lib/Terrain.cpp

@@ -22,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 TerrainTypeHandler::TerrainTypeHandler()
 {
 	auto allConfigs = VLC->modh->getActiveMods();
-	allConfigs.insert(allConfigs.begin(), "core");
+	allConfigs.insert(allConfigs.begin(), CModHandler::scopeBuiltin());
 
 	initRivers(allConfigs);
 	recreateRiverMaps();

+ 2 - 1
lib/filesystem/Filesystem.cpp

@@ -20,6 +20,7 @@
 #include "../GameConstants.h"
 #include "../VCMIDirs.h"
 #include "../CStopWatch.h"
+#include "../CModHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -207,7 +208,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives
 
 	const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
 
-	addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"], extractArchives));
+	addFilesystem("data", CModHandler::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives));
 }
 
 void CResourceHandler::addFilesystem(const std::string & parent, const std::string & identifier, ISimpleResourceLoader * loader)

+ 2 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -1413,7 +1413,7 @@ void CGHeroInstance::setHeroTypeName(const std::string & identifier)
 {
 	if(ID == Obj::HERO || ID == Obj::PRISON)
 	{
-		auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier);
+		auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", identifier);
 
 		if(rawId)
 			subID = rawId.get();
@@ -1434,7 +1434,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 	handler.serializeString("biography", biography);
 	handler.serializeInt("experience", exp, 0);
 
-	if (!handler.saving)
+	if(!handler.saving && exp != 0xffffffff) //do not gain levels if experience is not initialized
 	{
 		while (gainsLevel())
 		{

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -1466,7 +1466,7 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 	{
 		auto decodeBuilding = [this](const std::string & identifier) -> si32
 		{
-			auto rawId = VLC->modh->identifiers.getIdentifier("core", getTown()->getBuildingScope(), identifier);
+			auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), getTown()->getBuildingScope(), identifier);
 
 			if(rawId)
 				return rawId.get();

+ 9 - 7
lib/mapObjects/CObjectClassesHandler.cpp

@@ -170,7 +170,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons
 		logGlobal->error("Handler with name %s was not found!", obj->handlerName);
 		return;
 	}
-	const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier);
+	const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, CModHandler::scopeBuiltin(), identifier);
 	const auto & entryIndex = entry["index"];
 	bool useSelectNextID = !isSubobject || entryIndex.isNull();
 
@@ -259,14 +259,14 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name));
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
 	objects[object->id] = object;
 	VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
 }
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name));
+	auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
 	assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before
 	objects[(si32)index] = object;
 	VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
@@ -308,8 +308,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype)
 		if (objects.at(type)->subObjects.count(subtype))
 			return objects.at(type)->subObjects.at(subtype);
 	}
-	logGlobal->error("Failed to find object of type %d:%d", type, subtype);
-	throw std::runtime_error("Object type handler not found");
+	std::string errorString = "Failed to find object of type " + std::to_string(type) + "::" + std::to_string(subtype);
+	logGlobal->error(errorString);
+	throw std::runtime_error(errorString);
 }
 
 TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std::string type, std::string subtype) const
@@ -325,8 +326,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std::
 			return object->subObjects.at(subId);
 		}
 	}
-	logGlobal->error("Failed to find object of type %s::%s", type, subtype);
-	throw std::runtime_error("Object type handler not found");
+	std::string errorString = "Failed to find object of type " + type + "::" + subtype;
+	logGlobal->error(errorString);
+	throw std::runtime_error(errorString);
 }
 
 TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const

+ 14 - 3
lib/mapObjects/CObjectHandler.cpp

@@ -195,7 +195,9 @@ std::set<int3> CGObjectInstance::getBlockedOffsets() const
 
 void CGObjectInstance::setType(si32 ID, si32 subID)
 {
-	const TerrainTile &tile = cb->gameState()->map->getTile(visitablePos());
+	auto position = visitablePos();
+	auto oldOffset = getVisitableOffset();
+	auto &tile = cb->gameState()->map->getTile(position);
 
 	//recalculate blockvis tiles - new appearance might have different blockmap than before
 	cb->gameState()->map->removeBlockVisTiles(this, true);
@@ -206,14 +208,23 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 		return;
 	}
 	if(!handler->getTemplates(tile.terType->id).empty())
+	{
 		appearance = handler->getTemplates(tile.terType->id)[0];
+	}
 	else
+	{
+		logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->name);
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
+	}
 
 	if(this->ID == Obj::PRISON && ID == Obj::HERO)
 	{
-		//adjust for the prison offset
-		pos = visitablePos();
+		auto newOffset = getVisitableOffset();
+		// FIXME: potentially unused code - setType is NOT called when releasing hero from prison
+		// instead, appearance update & pos adjustment occurs in GiveHero::applyGs
+
+		// adjust position since hero and prison may have different visitable offset
+		pos = pos - oldOffset + newOffset;
 	}
 
 	this->ID = Obj(ID);

+ 1 - 1
lib/mapObjects/CQuest.cpp

@@ -1028,7 +1028,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 
 		if(doRequest)
 		{
-			auto rawId = VLC->modh->identifiers.getIdentifier("core", fullIdentifier, false);
+			auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), fullIdentifier, false);
 
 			if(rawId)
 			{

+ 2 - 2
lib/mapObjects/CRewardableObject.cpp

@@ -970,22 +970,22 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 			break;
 		case Obj::LIBRARY_OF_ENLIGHTENMENT:
 		{
+			selectMode = SELECT_FIRST;
 			onVisited.addTxt(MetaString::ADVOB_TXT, 67);
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 68);
 
-			// Don't like this one but don't see any easier approach
 			CVisitInfo visit;
 			visit.reward.primary[PrimarySkill::ATTACK] = 2;
 			visit.reward.primary[PrimarySkill::DEFENSE] = 2;
 			visit.reward.primary[PrimarySkill::KNOWLEDGE] = 2;
 			visit.reward.primary[PrimarySkill::SPELL_POWER] = 2;
+			visit.message.addTxt(MetaString::ADVOB_TXT, 66);
 
 			static_assert(SecSkillLevel::LEVELS_SIZE == 4, "Behavior of Library of Enlignment may not be correct");
 			for (int i=0; i<SecSkillLevel::LEVELS_SIZE; i++)
 			{
 				visit.limiter.minLevel = 10 - i * 2;
 				visit.limiter.secondary[SecondarySkill::DIPLOMACY] = i;
-				visit.message.addTxt(MetaString::ADVOB_TXT, 66);
 				info.push_back(visit);
 			}
 			break;

+ 3 - 3
lib/mapObjects/MiscObjects.cpp

@@ -1804,7 +1804,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
 		bonusType = RANDOM;
 		if(!json["rewardPrimSkill"].String().empty())
 		{
-			auto raw = VLC->modh->identifiers.getIdentifier("core", "primSkill", json["rewardPrimSkill"].String());
+			auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "primSkill", json["rewardPrimSkill"].String());
 			if(raw)
 			{
 				bonusType = PRIM_SKILL;
@@ -1813,7 +1813,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
 		}
 		else if(!json["rewardSkill"].String().empty())
 		{
-			auto raw = VLC->modh->identifiers.getIdentifier("core", "skill", json["rewardSkill"].String());
+			auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "skill", json["rewardSkill"].String());
 			if(raw)
 			{
 				bonusType = SECONDARY_SKILL;
@@ -1822,7 +1822,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
 		}
 		else if(!json["rewardSpell"].String().empty())
 		{
-			auto raw = VLC->modh->identifiers.getIdentifier("core", "spell", json["rewardSpell"].String());
+			auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "spell", json["rewardSpell"].String());
 			if(raw)
 			{
 				bonusType = SPELL;

+ 10 - 2
lib/mapObjects/ObjectTemplate.cpp

@@ -287,7 +287,15 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 	if(withTerrain && !node["allowedTerrains"].isNull())
 	{
 		for(auto& entry : node["allowedTerrains"].Vector())
-			allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id);
+		{
+			try {
+				allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id);
+			}
+			catch (const std::out_of_range & )
+			{
+				logGlobal->warn("Failed to find terrain '%s' for object '%s'", entry.String(), animationFile);
+			}
+		}
 	}
 	else
 	{
@@ -300,7 +308,7 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 	}
 
 	if(withTerrain && allowedTerrains.empty())
-		logGlobal->warn("Loaded template without allowed terrains!");
+		logGlobal->warn("Loaded template %s without allowed terrains!", animationFile);
 
 	auto charToTile = [&](const char & ch) -> ui8
 	{

+ 2 - 0
lib/mapping/CMap.cpp

@@ -256,6 +256,8 @@ CMap::CMap()
 
 CMap::~CMap()
 {
+	getEditManager()->getUndoManager().clearAll();
+	
 	if(terrain)
 	{
 		for(int z = 0; z < levels(); z++)

+ 3 - 3
lib/mapping/MapFormatJson.cpp

@@ -215,7 +215,7 @@ namespace TriggeredEventsDetail
 
 					event.metaType = decodeMetaclass(metaTypeName);
 
-					auto type = VLC->modh->identifiers.getIdentifier("core", fullIdentifier, false);
+					auto type = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), fullIdentifier, false);
 
 					if(type)
 						event.objectType = type.get();
@@ -1121,7 +1121,7 @@ void CMapLoaderJson::MapObjectLoader::construct()
 		return;
 	}
 
-	auto handler = VLC->objtypeh->getHandlerFor( "map", typeName, subtypeName);
+	auto handler = VLC->objtypeh->getHandlerFor( CModHandler::scopeMap(), typeName, subtypeName);
 
 	auto appearance = new ObjectTemplate;
 
@@ -1158,7 +1158,7 @@ void CMapLoaderJson::MapObjectLoader::configure()
 		if(art->ID == Obj::SPELL_SCROLL)
 		{
 			auto spellIdentifier = configuration["options"]["spell"].String();
-			auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", spellIdentifier);
+			auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "spell", spellIdentifier);
 			if(rawId)
 				spellID = rawId.get();
 			else

+ 2 - 1
lib/rmg/CRmgTemplateStorage.cpp

@@ -14,6 +14,7 @@
 #include "CRmgTemplate.h"
 
 #include "../serializer/JsonDeserializer.h"
+#include "../CModHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -30,7 +31,7 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 	try
 	{
 		JsonDeserializer handler(nullptr, data);
-		auto fullKey = normalizeIdentifier(scope, "core", name); //actually it's not used
+		auto fullKey = normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name); //actually it's not used
 		templates[fullKey].setId(name);
 		templates[fullKey].serializeJson(handler);
 		templates[fullKey].validate();

+ 5 - 0
lib/rmg/CZonePlacer.cpp

@@ -529,7 +529,12 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
 	}
 
 	for (auto zone : zones)
+	{
+		if(zone.second->area().empty())
+			throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
+		
 		moveZoneToCenterOfMass(zone.second);
+	}
 
 	//assign actual tiles to each zone using nonlinear norm for fine edges
 

+ 1 - 1
lib/spells/CSpellHandler.cpp

@@ -764,7 +764,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	{
 		if(counteredSpell.second.Bool())
 		{
-			VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
+			VLC->modh->identifiers.requestIdentifier(counteredSpell.second.meta, counteredSpell.first, [=](si32 id)
 			{
 				spell->counteredSpells.push_back(SpellID(id));
 			});

+ 0 - 2
lib_client/CMakeLists.txt

@@ -1,2 +0,0 @@
-add_main_lib(vcmi_lib_client SHARED)
-target_compile_definitions(vcmi_lib_client PUBLIC VCMI_LIB_NAMESPACE=LIB_CLIENT)

+ 1 - 0
lib_server/CMakeLists.txt

@@ -1,2 +1,3 @@
 add_main_lib(vcmi_lib_server STATIC)
 target_compile_definitions(vcmi_lib_server PUBLIC VCMI_LIB_NAMESPACE=LIB_SERVER)
+target_compile_definitions(vcmi_lib_server PUBLIC VCMI_DLL_STATIC=1)

+ 1 - 1
mapeditor/CMakeLists.txt

@@ -116,7 +116,7 @@ if(APPLE)
 	set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor)
 endif()
 
-target_link_libraries(vcmieditor vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
+target_link_libraries(vcmieditor ${VCMI_LIB_TARGET} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
 target_include_directories(vcmieditor
 	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR}
 )

+ 9 - 4
mapeditor/graphics.h

@@ -13,16 +13,21 @@
 #include "../lib/GameConstants.h"
 #include <QImage>
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 class CGHeroInstance;
 class CGTownInstance;
+class CGObjectInstance;
+class EntityService;
+class JsonNode;
+class ObjectTemplate;
+
+VCMI_LIB_NAMESPACE_END
+
 class CHeroClass;
 struct InfoAboutHero;
 struct InfoAboutTown;
-class CGObjectInstance;
-class ObjectTemplate;
 class Animation;
-class EntityService;
-class JsonNode;
 
 /// Handles fonts, hero images, town images, various graphics
 class Graphics

BIN
mapeditor/icons/edit-redo.png


BIN
mapeditor/icons/edit-undo.png


Некоторые файлы не были показаны из-за большого количества измененных файлов