Browse Source

Merge remote-tracking branch 'upstream/develop' into features/cpp-map-editor

# Conflicts:
#	.github/workflows/github.yml
#	launcher/modManager/cmodlist.cpp
#	lib/CModHandler.cpp
#	lib/CModHandler.h
nordsoft 3 years ago
parent
commit
89d0de53da
84 changed files with 964 additions and 1180 deletions
  1. 56 6
      .github/workflows/github.yml
  2. 6 0
      AI/BattleAI/StackWithBonuses.cpp
  3. 4 0
      AI/BattleAI/StackWithBonuses.h
  4. 20 16
      AI/Nullkiller/AIUtility.h
  5. 19 18
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  6. 4 8
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  7. 0 49
      AI/VCAI/AIUtility.cpp
  8. 59 4
      AI/VCAI/AIUtility.h
  9. 0 4
      AI/VCAI/CMakeLists.txt
  10. 1 0
      AI/VCAI/Goals/AbstractGoal.h
  11. 18 13
      AI/VCAI/Goals/Explore.cpp
  12. 9 11
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  13. 7 1
      AI/VCAI/Pathfinding/AINodeStorage.h
  14. 0 431
      AI/VCAI/SectorMap.cpp
  15. 0 70
      AI/VCAI/SectorMap.h
  16. 3 1
      CCallback.cpp
  17. 2 0
      CCallback.h
  18. 12 3
      CMakeLists.txt
  19. 1 5
      CMakePresets.json
  20. 2 0
      client/CGameInfo.cpp
  21. 2 0
      client/CGameInfo.h
  22. 3 1
      client/CMT.cpp
  23. 8 1
      client/CMakeLists.txt
  24. 84 78
      client/CPlayerInterface.cpp
  25. 1 1
      client/CServerHandler.cpp
  26. 10 0
      client/Client.cpp
  27. 7 0
      client/Client.h
  28. 52 90
      client/mapHandler.cpp
  29. 5 4
      client/mapHandler.h
  30. 3 3
      client/windows/CAdvmapInterface.cpp
  31. 1 1
      client/windows/GUIClasses.cpp
  32. 109 0
      config/schemas/terrain.json
  33. 4 0
      include/vcmi/Services.h
  34. 2 0
      include/vcmi/scripting/Service.h
  35. 1 1
      launcher/mainwindow_moc.cpp
  36. 70 27
      launcher/modManager/cmodlist.cpp
  37. 9 9
      launcher/modManager/cmodlistview_moc.ui
  38. 13 12
      lib/CGameInfoCallback.cpp
  39. 2 2
      lib/CGameInfoCallback.h
  40. 2 0
      lib/CGameInterface.cpp
  41. 6 0
      lib/CGameInterface.h
  42. 17 21
      lib/CGameState.cpp
  43. 18 6
      lib/CModHandler.cpp
  44. 21 40
      lib/CModHandler.h
  45. 7 7
      lib/CPathfinder.cpp
  46. 2 2
      lib/CPathfinder.h
  47. 1 1
      lib/CPlayerState.h
  48. 2 0
      lib/CScriptingModule.cpp
  49. 2 0
      lib/CScriptingModule.h
  50. 4 5
      lib/IGameCallback.cpp
  51. 5 2
      lib/IGameCallback.h
  52. 5 3
      lib/NetPacksLib.cpp
  53. 3 3
      lib/PathfinderUtil.h
  54. 2 0
      lib/ScriptHandler.cpp
  55. 2 0
      lib/ScriptHandler.h
  56. 31 43
      lib/VCMIDirs.cpp
  57. 4 1
      lib/VCMIDirs.h
  58. 10 0
      lib/VCMI_Lib.cpp
  59. 11 0
      lib/VCMI_Lib.h
  60. 11 4
      lib/battle/BattleInfo.cpp
  61. 2 0
      lib/battle/BattleInfo.h
  62. 0 7
      lib/battle/CBattleInfoCallback.h
  63. 4 0
      lib/battle/IBattleInfoCallback.h
  64. 3 3
      lib/mapObjects/ObjectTemplate.cpp
  65. 7 3
      lib/mapping/CDrawRoadsOperation.cpp
  66. 41 35
      lib/mapping/CMap.cpp
  67. 22 19
      lib/mapping/CMap.h
  68. 1 1
      lib/mapping/CMapDefines.h
  69. 7 5
      lib/mapping/MapFormatH3M.cpp
  70. 2 2
      lib/rmg/CMapGenerator.cpp
  71. 11 12
      lib/rmg/CZonePlacer.cpp
  72. 0 42
      lib/rmg/Functions.cpp
  73. 9 8
      lib/rmg/ObjectManager.h
  74. 5 12
      lib/rmg/ObstaclePlacer.cpp
  75. 3 3
      lib/rmg/RmgMap.cpp
  76. 4 4
      lib/rmg/Zone.cpp
  77. 14 0
      lib/serializer/BinaryDeserializer.h
  78. 12 0
      lib/serializer/BinarySerializer.h
  79. 2 2
      lib/spells/AdventureSpellMechanics.cpp
  80. 2 0
      lib/spells/ISpellMechanics.cpp
  81. 6 0
      lib/spells/ISpellMechanics.h
  82. 23 13
      server/CGameHandler.cpp
  83. 8 0
      server/CGameHandler.h
  84. 1 1
      server/CVCMIServer.cpp

+ 56 - 6
.github/workflows/github.yml

@@ -1,18 +1,68 @@
 name: VCMI
 
 on:
-    push:
-      branches:
-        - features/*
-        - develop
-        - cpp-map-editor
-    pull_request:
+  push:
+    branches:
+      - features/*
+  pull_request:
+  schedule:
+    - cron: '0 2 * * *'
+  workflow_dispatch:
+
 env:
   # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
   BUILD_TYPE: Release
 
 jobs:
+  check_last_build:
+    if: github.event.schedule != ''
+    runs-on: ubuntu-latest
+    outputs:
+      skip_build: ${{ steps.check_if_built.outputs.skip_build }}
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - name: Get repo name
+        id: get_repo_name
+        run: echo "::set-output name=value::${GITHUB_REPOSITORY#*/}"
+      - name: Get last successful build for ${{ github.sha }}
+        uses: octokit/[email protected]
+        id: get_last_scheduled_run
+        with:
+          route: GET /repos/{owner}/{repo}/actions/runs
+          owner: ${{ github.repository_owner }}
+          repo: ${{ steps.get_repo_name.outputs.value }}
+          status: success
+          per_page: 1
+          head_sha: ${{ github.sha }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      - name: Check if successful build of the current commit exists
+        id: check_if_built
+        run: |
+          if [ ${{ fromJson(steps.get_last_scheduled_run.outputs.data).total_count }} -gt 0 ]; then
+            echo '::set-output name=skip_build::1'
+          else
+            echo '::set-output name=skip_build::0'
+          fi
+      - name: Cancel current run
+        if: steps.check_if_built.outputs.skip_build == 1
+        uses: octokit/[email protected]
+        with:
+          route: POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel
+          owner: ${{ github.repository_owner }}
+          repo: ${{ steps.get_repo_name.outputs.value }}
+          run_id: ${{ github.run_id }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      - name: Wait for the run to be cancelled
+        if: steps.check_if_built.outputs.skip_build == 1
+        run: sleep 60
+
   build:
+    needs: check_last_build
+    if: always() && needs.check_last_build.skip_build != 1
     strategy:
       matrix:
         include:

+ 6 - 0
AI/BattleAI/StackWithBonuses.cpp

@@ -16,7 +16,9 @@
 #include "../../lib/CStack.h"
 #include "../../lib/ScriptHandler.h"
 
+#if SCRIPTING_ENABLED
 using scripting::Pool;
+#endif
 
 void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 {
@@ -217,7 +219,9 @@ HypotheticBattle::HypotheticBattle(const Environment * ENV, Subject realBattle)
 	localEnvironment.reset(new HypotheticEnvironment(this, env));
 	serverCallback.reset(new HypotheticServerCallback(this));
 
+#if SCRIPTING_ENABLED
 	pool.reset(new scripting::PoolImpl(localEnvironment.get(), serverCallback.get()));
+#endif
 }
 
 bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const
@@ -420,10 +424,12 @@ int64_t HypotheticBattle::getTreeVersion() const
 	return getBattleNode()->getTreeVersion() + bonusTreeVersion;
 }
 
+#if SCRIPTING_ENABLED
 Pool * HypotheticBattle::getContextPool() const
 {
 	return pool.get();
 }
+#endif
 
 ServerCallback * HypotheticBattle::getServerCallback()
 {

+ 4 - 0
AI/BattleAI/StackWithBonuses.h

@@ -136,7 +136,9 @@ public:
 
 	int64_t getTreeVersion() const;
 
+#if SCRIPTING_ENABLED
 	scripting::Pool * getContextPool() const override;
+#endif
 
 	ServerCallback * getServerCallback();
 
@@ -189,6 +191,8 @@ private:
 	std::unique_ptr<HypotheticServerCallback> serverCallback;
 	std::unique_ptr<HypotheticEnvironment> localEnvironment;
 
+#if SCRIPTING_ENABLED
 	mutable std::shared_ptr<scripting::Pool> pool;
+#endif
 	mutable std::shared_ptr<events::EventBus> eventBus;
 };

+ 20 - 16
AI/Nullkiller/AIUtility.h

@@ -205,12 +205,14 @@ void foreach_tile_pos(const Func & foo)
 	// some micro-optimizations since this function gets called a LOT
 	// callback pointer is thread-specific and slow to retrieve -> read map size only once
 	int3 mapSize = cb->getMapSize();
-	for(int i = 0; i < mapSize.x; i++)
+	for(int z = 0; z < mapSize.z; z++)
 	{
-		for(int j = 0; j < mapSize.y; j++)
+		for(int x = 0; x < mapSize.x; x++)
 		{
-			for(int k = 0; k < mapSize.z; k++)
-				foo(int3(i, j, k));
+			for(int y = 0; y < mapSize.y; y++)
+			{
+				foo(int3(x, y, z));
+			}
 		}
 	}
 }
@@ -219,12 +221,14 @@ template<class Func>
 void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
 {
 	int3 mapSize = cbp->getMapSize();
-	for(int i = 0; i < mapSize.x; i++)
+	for(int z = 0; z < mapSize.z; z++)
 	{
-		for(int j = 0; j < mapSize.y; j++)
+		for(int x = 0; x < mapSize.x; x++)
 		{
-			for(int k = 0; k < mapSize.z; k++)
-				foo(cbp, int3(i, j, k));
+			for(int y = 0; y < mapSize.y; y++)
+			{
+				foo(cbp, int3(x, y, z));
+			}
 		}
 	}
 }
@@ -276,21 +280,21 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 template<typename TFunc>
 void pforeachTilePos(crint3 mapSize, TFunc fn)
 {
-	parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r)
+	for(int z = 0; z < mapSize.z; ++z)
 	{
-		int3 pos;
-
-		for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
+		parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r)
 		{
-			for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
+			int3 pos(0, 0, z);
+
+			for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
 			{
-				for(pos.z = 0; pos.z < mapSize.z; ++pos.z)
+				for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
 				{
 					fn(pos);
 				}
 			}
-		}
-	});
+		});
+	}
 }
 
 class CDistanceSorter

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

@@ -39,7 +39,7 @@ AISharedStorage::AISharedStorage(int3 sizes)
 {
 	if(!shared){
 		shared.reset(new boost::multi_array<AIPathNode, 5>(
-			boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]));
+			boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][NUM_CHAINS]));
 	}
 
 	nodes = shared;
@@ -69,40 +69,41 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 	const PlayerColor fowPlayer = ai->playerID;
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
+	const auto fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
 	const int3 sizes = gs->getMapSize();
 
+	//Each thread gets different x, but an array of y located next to each other in memory
+
 	parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
 	{
-		//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
-		const bool useFlying = options.useFlying;
-		const bool useWaterWalking = options.useWaterWalking;
-		const PlayerColor player = playerID;
-
 		int3 pos;
 
-		for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
+		for(pos.z = 0; pos.z < sizes.z; ++pos.z)
 		{
-			for(pos.y = 0; pos.y < sizes.y; ++pos.y)
+			const bool useFlying = options.useFlying;
+			const bool useWaterWalking = options.useWaterWalking;
+			const PlayerColor player = playerID;
+
+			for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
 			{
-				for(pos.z = 0; pos.z < sizes.z; ++pos.z)
+				for(pos.y = 0; pos.y < sizes.y; ++pos.y)
 				{
-					const TerrainTile * tile = &gs->map->getTile(pos);
-					if(!tile->terType.isPassable())
+					const TerrainTile* tile = &gs->map->getTile(pos);
+					if (!tile->terType.isPassable())
 						continue;
-					
-					if(tile->terType.isWater())
+
+					if (tile->terType.isWater())
 					{
 						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
-						if(useFlying)
+						if (useFlying)
 							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-						if(useWaterWalking)
+						if (useWaterWalking)
 							resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
 					}
 					else
 					{
 						resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
-						if(useFlying)
+						if (useFlying)
 							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
 					}
 				}
@@ -140,7 +141,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 {
 	int bucketIndex = ((uintptr_t)actor) % BUCKET_COUNT;
 	int bucketOffset = bucketIndex * BUCKET_SIZE;
-	auto chains = nodes.get(pos, layer);
+	auto chains = nodes.get(pos, layer); //FIXME: chain was the innermost layer
 
 	if(chains[0].blocked())
 	{

+ 4 - 8
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -120,23 +120,19 @@ enum EHeroChainPass
 
 class AISharedStorage
 {
-	/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
+	// 1 - layer (air, water, land)
+	// 2-4 - position on map[z][x][y]
+	// 5 - chain (normal, battle, spellcast and combinations)
 	static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
 	std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
 public:
 	AISharedStorage(int3 mapSize);
 	~AISharedStorage();
 
-	/*STRONG_INLINE
-	boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer)
-	{
-		return (*nodes)[tile.x][tile.y][tile.z][layer];
-	}*/
-
 	STRONG_INLINE
 	boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer) const
 	{
-		return (*nodes)[tile.x][tile.y][tile.z][layer];
+		return (*nodes)[layer][tile.z][tile.x][tile.y];
 	}
 };
 

+ 0 - 49
AI/VCAI/AIUtility.cpp

@@ -137,55 +137,6 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const
 	return h == rhs.get(true);
 }
 
-void foreach_tile_pos(std::function<void(const int3 & pos)> foo)
-{
-	// some micro-optimizations since this function gets called a LOT
-	// callback pointer is thread-specific and slow to retrieve -> read map size only once
-	int3 mapSize = cb->getMapSize();
-	for(int i = 0; i < mapSize.x; i++)
-	{
-		for(int j = 0; j < mapSize.y; j++)
-		{
-			for(int k = 0; k < mapSize.z; k++)
-				foo(int3(i, j, k));
-		}
-	}
-}
-
-void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3 & pos)> foo)
-{
-	int3 mapSize = cbp->getMapSize();
-	for(int i = 0; i < mapSize.x; i++)
-	{
-		for(int j = 0; j < mapSize.y; j++)
-		{
-			for(int k = 0; k < mapSize.z; k++)
-				foo(cbp, int3(i, j, k));
-		}
-	}
-}
-
-void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> foo)
-{
-	CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
-	for(const int3 & dir : int3::getDirs())
-	{
-		const int3 n = pos + dir;
-		if(cbp->isInTheMap(n))
-			foo(pos + dir);
-	}
-}
-
-void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo)
-{
-	for(const int3 & dir : int3::getDirs())
-	{
-		const int3 n = pos + dir;
-		if(cbp->isInTheMap(n))
-			foo(cbp, pos + dir);
-	}
-}
-
 bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const
 {
 	const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());

+ 59 - 4
AI/VCAI/AIUtility.h

@@ -18,6 +18,7 @@
 #include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CPathfinder.h"
+#include "../../CCallback.h"
 
 class CCallback;
 struct creInfo;
@@ -34,6 +35,8 @@ const int ALLOWED_ROAMING_HEROES = 8;
 extern const double SAFE_ATTACK_CONSTANT;
 extern const int GOLD_RESERVE;
 
+extern boost::thread_specific_ptr<CCallback> cb;
+
 //provisional class for AI to store a reference to an owned hero object
 //checks if it's valid on access, should be used in place of const CGHeroInstance*
 
@@ -154,10 +157,62 @@ struct creInfo
 };
 creInfo infoFromDC(const dwellingContent & dc);
 
-void foreach_tile_pos(std::function<void(const int3 & pos)> foo);
-void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
-void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> foo);
-void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
+template<class Func>
+void foreach_tile_pos(const Func & foo)
+{
+	// some micro-optimizations since this function gets called a LOT
+	// callback pointer is thread-specific and slow to retrieve -> read map size only once
+	int3 mapSize = cb->getMapSize();
+	for(int z = 0; z < mapSize.z; z++)
+	{
+		for(int x = 0; x < mapSize.x; x++)
+		{
+			for(int y = 0; y < mapSize.y; y++)
+			{
+				foo(int3(x, y, z));
+			}
+		}
+	}
+}
+
+template<class Func>
+void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
+{
+	int3 mapSize = cbp->getMapSize();
+	for(int z = 0; z < mapSize.z; z++)
+	{
+		for(int x = 0; x < mapSize.x; x++)
+		{
+			for(int y = 0; y < mapSize.y; y++)
+			{
+				foo(cbp, int3(x, y, z));
+			}
+		}
+	}
+}
+
+template<class Func>
+void foreach_neighbour(const int3 & pos, const Func & foo)
+{
+	CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
+	for(const int3 & dir : int3::getDirs())
+	{
+		const int3 n = pos + dir;
+		if(cbp->isInTheMap(n))
+			foo(pos + dir);
+	}
+}
+
+template<class Func>
+void foreach_neighbour(CCallback * cbp, const int3 & pos, const Func & foo) // avoid costly retrieval of thread-specific pointer
+{
+	for(const int3 & dir : int3::getDirs())
+	{
+		const int3 n = pos + dir;
+		if(cbp->isInTheMap(n))
+			foo(cbp, pos + dir);
+	}
+}
 
 bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
 bool isBlockedBorderGate(int3 tileToHit);

+ 0 - 4
AI/VCAI/CMakeLists.txt

@@ -17,8 +17,6 @@ set(VCAI_SRCS
 		ArmyManager.cpp
 		ResourceManager.cpp
 		BuildingManager.cpp
-		SectorMap.cpp
-		BuildingManager.cpp
 		MapObjectsEvaluator.cpp
 		FuzzyEngines.cpp
 		FuzzyHelper.cpp
@@ -68,8 +66,6 @@ set(VCAI_HEADERS
 		ArmyManager.h
 		ResourceManager.h
 		BuildingManager.h
-		SectorMap.h
-		BuildingManager.h
 		MapObjectsEvaluator.h
 		FuzzyEngines.h
 		FuzzyHelper.h

+ 1 - 0
AI/VCAI/Goals/AbstractGoal.h

@@ -18,6 +18,7 @@
 struct HeroPtr;
 class VCAI;
 class FuzzyHelper;
+class CCallback;
 
 namespace Goals
 {

+ 18 - 13
AI/VCAI/Goals/Explore.cpp

@@ -57,13 +57,16 @@ namespace Goals
 
 		void scanSector(int scanRadius)
 		{
-			for(int x = ourPos.x - scanRadius; x <= ourPos.x + scanRadius; x++)
+			int3 tile = int3(0, 0, ourPos.z);
+
+			const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
+
+			for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
 			{
-				for(int y = ourPos.y - scanRadius; y <= ourPos.y + scanRadius; y++)
+				for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
 				{
-					int3 tile = int3(x, y, ourPos.z);
 
-					if(cbp->isInTheMap(tile) && ts->fogOfWarMap[tile.x][tile.y][tile.z])
+					if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
 					{
 						scanTile(tile);
 					}
@@ -84,13 +87,13 @@ namespace Goals
 
 			foreach_tile_pos([&](const int3 & pos)
 			{
-				if(ts->fogOfWarMap[pos.x][pos.y][pos.z])
+				if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
 				{
 					bool hasInvisibleNeighbor = false;
 
 					foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
 					{
-						if(!ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z])
+						if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
 						{
 							hasInvisibleNeighbor = true;
 						}
@@ -174,7 +177,7 @@ namespace Goals
 			{
 				foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
 				{
-					if(ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z])
+					if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
 					{
 						out.push_back(neighbour);
 					}
@@ -182,18 +185,20 @@ namespace Goals
 			}
 		}
 
-		int howManyTilesWillBeDiscovered(
-			const int3 & pos) const
+		int howManyTilesWillBeDiscovered(const int3 & pos) const
 		{
 			int ret = 0;
-			for(int x = pos.x - sightRadius; x <= pos.x + sightRadius; x++)
+			int3 npos = int3(0, 0, pos.z);
+
+			const auto & slice = (*(ts->fogOfWarMap))[pos.z];
+
+			for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
 			{
-				for(int y = pos.y - sightRadius; y <= pos.y + sightRadius; y++)
+				for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
 				{
-					int3 npos = int3(x, y, pos.z);
 					if(cbp->isInTheMap(npos)
 						&& pos.dist2d(npos) - 0.5 < sightRadius
-						&& !ts->fogOfWarMap[npos.x][npos.y][npos.z])
+						&& !slice[npos.x][npos.y])
 					{
 						if(allowDeadEndCancellation
 							&& !hasReachableNeighbor(npos))

+ 9 - 11
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -20,7 +20,7 @@
 AINodeStorage::AINodeStorage(const int3 & Sizes)
 	: sizes(Sizes)
 {
-	nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
+	nodes.resize(boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][NUM_CHAINS]);
 	dangerEvaluator.reset(new FuzzyHelper());
 }
 
@@ -28,8 +28,6 @@ AINodeStorage::~AINodeStorage() = default;
 
 void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
 {
-	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
-
 	int3 pos;
 	const int3 sizes = gs->getMapSize();
 	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
@@ -39,11 +37,11 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 	const bool useFlying = options.useFlying;
 	const bool useWaterWalking = options.useWaterWalking;
 
-	for(pos.x=0; pos.x < sizes.x; ++pos.x)
+	for(pos.z=0; pos.z < sizes.z; ++pos.z)
 	{
-		for(pos.y=0; pos.y < sizes.y; ++pos.y)
+		for(pos.x=0; pos.x < sizes.x; ++pos.x)
 		{
-			for(pos.z=0; pos.z < sizes.z; ++pos.z)
+			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
 				const TerrainTile * tile = &gs->map->getTile(pos);
 				if(!tile->terType.isPassable())
@@ -87,7 +85,7 @@ bool AINodeStorage::isBattleNode(const CGPathNode * node) const
 
 boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, int chainNumber)
 {
-	auto chains = nodes[pos.x][pos.y][pos.z][layer];
+	auto chains = nodes[layer][pos.z][pos.x][pos.y];
 
 	for(AIPathNode & node : chains)
 	{
@@ -126,7 +124,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
 {
 	for(int i = 0; i < NUM_CHAINS; i++)
 	{
-		AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
+		AIPathNode & heroNode = nodes[layer][coord.z][coord.x][coord.y][i];
 
 		heroNode.chainMask = 0;
 		heroNode.danger = 0;
@@ -290,7 +288,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
 bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
 {
 	auto pos = destination.coord;
-	auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
+	auto chains = nodes[EPathfindingLayer::LAND][pos.z][pos.x][pos.y];
 	auto destinationNode = getAINode(destination.node);
 
 	for(const AIPathNode & node : chains)
@@ -323,7 +321,7 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 
 bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const
 {
-	const AIPathNode & node = nodes[pos.x][pos.y][pos.z][layer][0];
+	const AIPathNode & node = nodes[layer][pos.z][pos.x][pos.y][0];
 
 	return node.action != CGPathNode::ENodeAction::UNKNOWN;
 }
@@ -331,7 +329,7 @@ bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer l
 std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
 {
 	std::vector<AIPath> paths;
-	auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL];
+	auto chains = nodes[isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL][pos.z][pos.x][pos.y];
 	auto initialPos = hero->visitablePos();
 
 	for(const AIPathNode & node : chains)

+ 7 - 1
AI/VCAI/Pathfinding/AINodeStorage.h

@@ -17,6 +17,10 @@
 #include "../Goals/AbstractGoal.h"
 #include "Actions/ISpecialAction.h"
 
+class CCallback;
+
+extern boost::thread_specific_ptr<CCallback> cb; //for templates
+
 struct AIPathNode : public CGPathNode
 {
 	uint32_t chainMask;
@@ -57,7 +61,9 @@ class AINodeStorage : public INodeStorage
 private:
 	int3 sizes;
 
-	/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
+	// 1 - layer (air, water, land)
+	// 2-4 - position on map[z][x][y]
+	// 5 - chain (normal, battle, spellcast and combinations)
 	boost::multi_array<AIPathNode, 5> nodes;
 	const CPlayerSpecificInfoCallback * cb;
 	const VCAI * ai;

+ 0 - 431
AI/VCAI/SectorMap.cpp

@@ -1,431 +0,0 @@
-/*
-* SectorMap.cpp, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#include "StdInc.h"
-#include "SectorMap.h"
-#include "VCAI.h"
-
-#include "../../CCallback.h"
-#include "../../lib/mapping/CMap.h"
-#include "../../lib/mapObjects/MapObjects.h"
-#include "../../lib/CPathfinder.h"
-#include "../../lib/CGameState.h"
-
-extern boost::thread_specific_ptr<CCallback> cb;
-extern boost::thread_specific_ptr<VCAI> ai;
-
-SectorMap::SectorMap()
-{
-	update();
-}
-
-SectorMap::SectorMap(HeroPtr h)
-{
-	update();
-	makeParentBFS(h->visitablePos());
-}
-
-bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t)
-{
-	if (t->blocked && !t->visitable)
-	{
-		sec = NOT_AVAILABLE;
-		return true;
-	}
-
-	return false;
-}
-
-bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos)
-{
-	return markIfBlocked(sec, pos, getTile(pos));
-}
-
-void SectorMap::update()
-{
-	visibleTiles = cb->getAllVisibleTiles();
-	auto shape = visibleTiles->shape();
-	sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]);
-
-	clear();
-	int curSector = 3; //0 is invisible, 1 is not explored
-
-	CCallback * cbp = cb.get(); //optimization
-	foreach_tile_pos([&](crint3 pos)
-	{
-		if (retrieveTile(pos) == NOT_CHECKED)
-		{
-			if (!markIfBlocked(retrieveTile(pos), pos))
-				exploreNewSector(pos, curSector++, cbp);
-		}
-	});
-	valid = true;
-}
-
-SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos)
-{
-	return a[pos.x][pos.y][pos.z];
-}
-
-const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos)
-{
-	return a[pos.x][pos.y][pos.z];
-}
-
-void SectorMap::clear()
-{
-	//TODO: rotate to [z][x][y]
-	auto fow = cb->getVisibilityMap();
-	//TODO: any magic to automate this? will need array->array conversion
-	//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
-	//{
-	//	return f; //type conversion
-	//});
-	auto width = fow.size();
-	auto height = fow.front().size();
-	auto depth = fow.front().front().size();
-	for (size_t x = 0; x < width; x++)
-	{
-		for (size_t y = 0; y < height; y++)
-		{
-			for (size_t z = 0; z < depth; z++)
-				sector[x][y][z] = fow[x][y][z];
-		}
-	}
-	valid = false;
-}
-
-void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
-{
-	Sector & s = infoOnSectors[num];
-	s.id = num;
-	s.water = getTile(pos)->isWater();
-
-	std::queue<int3> toVisit;
-	toVisit.push(pos);
-	while (!toVisit.empty())
-	{
-		int3 curPos = toVisit.front();
-		toVisit.pop();
-		TSectorID & sec = retrieveTile(curPos);
-		if (sec == NOT_CHECKED)
-		{
-			const TerrainTile * t = getTile(curPos);
-			if (!markIfBlocked(sec, curPos, t))
-			{
-				if (t->isWater() == s.water) //sector is only-water or only-land
-				{
-					sec = num;
-					s.tiles.push_back(curPos);
-					foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
-					{
-						if (retrieveTile(neighPos) == NOT_CHECKED)
-						{
-							toVisit.push(neighPos);
-							//parent[neighPos] = curPos;
-						}
-						const TerrainTile * nt = getTile(neighPos);
-						if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water))
-						{
-							s.embarkmentPoints.push_back(neighPos);
-						}
-					});
-
-					if (t->visitable)
-					{
-						auto obj = t->visitableObjects.front();
-						if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
-							s.visitableObjs.push_back(obj);
-					}
-				}
-			}
-		}
-	}
-
-	vstd::removeDuplicates(s.embarkmentPoints);
-}
-
-void SectorMap::write(crstring fname)
-{
-	std::ofstream out(fname);
-	for (int k = 0; k < cb->getMapSize().z; k++)
-	{
-		for (int j = 0; j < cb->getMapSize().y; j++)
-		{
-			for (int i = 0; i < cb->getMapSize().x; i++)
-			{
-				out << (int)sector[i][j][k] << '\t';
-			}
-			out << std::endl;
-		}
-		out << std::endl;
-	}
-}
-
-/*
-this functions returns one target tile or invalid tile. We will use it to poll possible destinations
-For ship construction etc, another function (goal?) is needed
-*/
-int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
-{
-	int3 ret(-1, -1, -1);
-
-	int sourceSector = retrieveTile(h->visitablePos());
-	int destinationSector = retrieveTile(dst);
-
-	const Sector * src = &infoOnSectors[sourceSector];
-	const Sector * dest = &infoOnSectors[destinationSector];
-
-	if (sourceSector != destinationSector) //use ships, shipyards etc..
-	{
-		if (ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects
-			return dst;
-
-		std::map<const Sector *, const Sector *> preds;
-		std::queue<const Sector *> sectorQueue;
-		sectorQueue.push(src);
-		while (!sectorQueue.empty())
-		{
-			const Sector * s = sectorQueue.front();
-			sectorQueue.pop();
-
-			for (int3 ep : s->embarkmentPoints)
-			{
-				Sector * neigh = &infoOnSectors[retrieveTile(ep)];
-				//preds[s].push_back(neigh);
-				if (!preds[neigh])
-				{
-					preds[neigh] = s;
-					sectorQueue.push(neigh);
-				}
-			}
-		}
-
-		if (!preds[dest])
-		{
-			//write("test.txt");
-
-			return ret;
-			//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
-		}
-
-		std::vector<const Sector *> toTraverse;
-		toTraverse.push_back(dest);
-		while (toTraverse.back() != src)
-		{
-			toTraverse.push_back(preds[toTraverse.back()]);
-		}
-
-		if (preds[dest])
-		{
-			//TODO: would be nice to find sectors in loop
-			const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2);
-
-			if (!src->water && sectorToReach->water) //embark
-			{
-				//embark on ship -> look for an EP with a boat
-				auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool
-				{
-					const TerrainTile * t = getTile(pos);
-					if (t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
-					{
-						if (retrieveTile(pos) == sectorToReach->id)
-							return true;
-					}
-					return false;
-				});
-
-				if (firstEP != src->embarkmentPoints.end())
-				{
-					return *firstEP;
-				}
-				else
-				{
-					//we need to find a shipyard with an access to the desired sector's EP
-					//TODO what about Summon Boat spell?
-					std::vector<const IShipyard *> shipyards;
-					for (const CGTownInstance * t : cb->getTownsInfo())
-					{
-						if (t->hasBuilt(BuildingID::SHIPYARD))
-							shipyards.push_back(t);
-					}
-
-					for (const CGObjectInstance * obj : ai->getFlaggedObjects())
-					{
-						if (obj->ID != Obj::TOWN) //towns were handled in the previous loop
-						{
-							if (const IShipyard * shipyard = IShipyard::castFrom(obj))
-								shipyards.push_back(shipyard);
-						}
-					}
-
-					shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool
-					{
-						return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id;
-					}), shipyards.end());
-
-					if (!shipyards.size())
-					{
-						//TODO consider possibility of building shipyard in a town
-						return ret;
-
-						//throw cannotFulfillGoalException("There is no known shipyard!");
-					}
-
-					//we have only shipyards that possibly can build ships onto the appropriate EP
-					auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool
-					{
-						return s->o->tempOwner == ai->playerID;
-					});
-
-					if (ownedGoodShipyard != shipyards.end())
-					{
-						const IShipyard * s = *ownedGoodShipyard;
-						TResources shipCost;
-						s->getBoatCost(shipCost);
-						if (cb->getResourceAmount().canAfford(shipCost))
-						{
-							int3 ret = s->bestLocation();
-							cb->buildBoat(s); //TODO: move actions elsewhere
-							return ret;
-						}
-						else
-						{
-							//TODO gather res
-							return ret;
-
-							//throw cannotFulfillGoalException("Not enough resources to build a boat");
-						}
-					}
-					else
-					{
-						//TODO pick best shipyard to take over
-						return shipyards.front()->o->visitablePos();
-					}
-				}
-			}
-			else if (src->water && !sectorToReach->water)
-			{
-				//TODO
-				//disembark
-				return ret;
-			}
-			else //use subterranean gates - not needed since gates are now handled via Pathfinder
-			{
-				return ret;
-				//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
-			}
-		}
-		else
-		{
-			return ret;
-			//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
-		}
-	}
-	else //tiles are in same sector
-	{
-		return findFirstVisitableTile(h, dst);
-	}
-}
-
-int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst)
-{
-	int3 ret(-1, -1, -1);
-	int3 curtile = dst;
-
-	while (curtile != h->visitablePos())
-	{
-		auto topObj = cb->getTopObj(curtile);
-		if (topObj && topObj->ID == Obj::HERO && topObj != h.h)
-		{
-			if (cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
-			{
-				logAi->warn("Another allied hero stands in our way");
-				return ret;
-			}
-		}
-		if (ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
-		{
-			return curtile;
-		}
-		else
-		{
-			auto i = parent.find(curtile);
-			if (i != parent.end())
-			{
-				assert(curtile != i->second);
-				curtile = i->second;
-			}
-			else
-			{
-				return ret;
-				//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
-			}
-		}
-	}
-	return ret;
-}
-
-void SectorMap::makeParentBFS(crint3 source)
-{
-	parent.clear();
-
-	int mySector = retrieveTile(source);
-	std::queue<int3> toVisit;
-	toVisit.push(source);
-	while (!toVisit.empty())
-	{
-		int3 curPos = toVisit.front();
-		toVisit.pop();
-		TSectorID & sec = retrieveTile(curPos);
-		assert(sec == mySector); //consider only tiles from the same sector
-		UNUSED(sec);
-
-		foreach_neighbour(curPos, [&](crint3 neighPos)
-		{
-			if (retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos))
-			{
-				if (cb->canMoveBetween(curPos, neighPos))
-				{
-					toVisit.push(neighPos);
-					parent[neighPos] = curPos;
-				}
-			}
-		});
-	}
-}
-
-SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos)
-{
-	return retrieveTileN(sector, pos);
-}
-
-TerrainTile * SectorMap::getTile(crint3 pos) const
-{
-	//out of bounds access should be handled by boost::multi_array
-	//still we cached this array to avoid any checks
-	return visibleTiles->operator[](pos.x)[pos.y][pos.z];
-}
-
-std::vector<const CGObjectInstance *> SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround)
-{
-	const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())];
-	if (sectorsAround)
-	{
-		std::vector<const CGObjectInstance *> ret;
-		for (auto embarkPoint : heroSector->embarkmentPoints)
-		{
-			const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)];
-			range::copy(embarkSector->visitableObjs, std::back_inserter(ret));
-		}
-		return ret;
-	}
-	return heroSector->visitableObjs;
-}

+ 0 - 70
AI/VCAI/SectorMap.h

@@ -1,70 +0,0 @@
-/*
-* SectorMap.h, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-
-#pragma once
-
-#include "AIUtility.h"
-
-enum
-{
-	NOT_VISIBLE = 0,
-	NOT_CHECKED = 1,
-	NOT_AVAILABLE
-};
-
-struct SectorMap
-{
-	//a sector is set of tiles that would be mutually reachable if all visitable objs would be passable (incl monsters)
-	struct Sector
-	{
-		int id;
-		std::vector<int3> tiles;
-		std::vector<int3> embarkmentPoints; //tiles of other sectors onto which we can (dis)embark
-		std::vector<const CGObjectInstance *> visitableObjs;
-		bool water; //all tiles of sector are land or water
-		Sector()
-		{
-			id = -1;
-			water = false;
-		}
-	};
-
-	typedef unsigned short TSectorID; //smaller than int to allow -1 value. Max number of sectors 65K should be enough for any proper map.
-	typedef boost::multi_array<TSectorID, 3> TSectorArray;
-
-	bool valid; //some kind of lazy eval
-	std::map<int3, int3> parent;
-	TSectorArray sector;
-	//std::vector<std::vector<std::vector<unsigned char>>> pathfinderSector;
-
-	std::map<int, Sector> infoOnSectors;
-	std::shared_ptr<boost::multi_array<TerrainTile *, 3>> visibleTiles;
-
-	SectorMap();
-	SectorMap(HeroPtr h);
-	void update();
-	void clear();
-	void exploreNewSector(crint3 pos, int num, CCallback * cbp);
-	void write(crstring fname);
-
-	bool markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t);
-	bool markIfBlocked(TSectorID & sec, crint3 pos);
-	TSectorID & retrieveTile(crint3 pos);
-	TSectorID & retrieveTileN(TSectorArray & vectors, const int3 & pos);
-	const TSectorID & retrieveTileN(const TSectorArray & vectors, const int3 & pos);
-	TerrainTile * getTile(crint3 pos) const;
-	std::vector<const CGObjectInstance *> getNearbyObjs(HeroPtr h, bool sectorsAround);
-
-	void makeParentBFS(crint3 source);
-
-	int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
-	int3 findFirstVisitableTile(HeroPtr h, crint3 dst);
-};

+ 3 - 1
CCallback.cpp

@@ -324,7 +324,7 @@ int3 CCallback::getGuardingCreaturePosition(int3 tile)
 	if (!gs->map->isInTheMap(tile))
 		return int3(-1,-1,-1);
 
-	return gs->map->guardingCreaturePositions[tile.x][tile.y][tile.z];
+	return gs->map->guardingCreaturePositions[tile.z][tile.x][tile.y];
 }
 
 void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out)
@@ -366,10 +366,12 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
 	cl->additionalBattleInts[*player] -= battleEvents;
 }
 
+#if SCRIPTING_ENABLED
 scripting::Pool * CBattleCallback::getContextPool() const
 {
 	return cl->getGlobalContextPool();
 }
+#endif
 
 CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C )
 {

+ 2 - 0
CCallback.h

@@ -99,7 +99,9 @@ public:
 	int battleMakeAction(const BattleAction * action) override;//for casting spells by hero - DO NOT use it for moving active stack
 	bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
 
+#if SCRIPTING_ENABLED
 	scripting::Pool * getContextPool() const override;
+#endif
 
 	friend class CCallback;
 	friend class CClient;

+ 12 - 3
CMakeLists.txt

@@ -41,8 +41,8 @@ set(VCMI_VERSION_MAJOR 1)
 set(VCMI_VERSION_MINOR 0)
 set(VCMI_VERSION_PATCH 0)
 
-option(ENABLE_ERM "Enable compilation of ERM scripting module" ON)
-option(ENABLE_LUA "Enable compilation of LUA scripting module" ON)
+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_TEST "Enable compilation of unit tests" ON)
@@ -60,6 +60,11 @@ option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linu
 set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
 set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
 
+# ERM depends on LUA implicitly
+if(ENABLE_ERM AND NOT ENABLE_LUA)
+	set(ENABLE_LUA ON)
+endif()
+
 ############################################
 #        Miscellaneous options             #
 ############################################
@@ -215,6 +220,10 @@ if(NOT WIN32)
 	endif()
 endif()
 
+if(ENABLE_LUA)
+	add_compile_definitions(SCRIPTING_ENABLED=1)
+endif()
+
 ############################################
 #        Finding packages                  #
 ############################################
@@ -227,7 +236,7 @@ if(TARGET zlib::zlib)
 	add_library(ZLIB::ZLIB ALIAS zlib::zlib)
 endif()
 
-find_package(ffmpeg REQUIRED COMPONENTS avutil swscale avformat avcodec)
+find_package(ffmpeg COMPONENTS avutil swscale avformat avcodec)
 option(FORCE_BUNDLED_MINIZIP "Force bundled Minizip library" OFF)
 if(NOT FORCE_BUNDLED_MINIZIP)
 	find_package(minizip)

+ 1 - 5
CMakePresets.json

@@ -79,11 +79,7 @@
             "name": "macos-arm-conan-ninja-release",
             "displayName": "Ninja+Conan arm64 release",
             "description": "VCMI MacOS-arm64 Ninja using Conan",
-            "inherits": "macos-conan-ninja-release",
-            "cacheVariables": {
-                "ENABLE_ERM": "OFF",
-                "ENABLE_LUA": "OFF"
-            }
+            "inherits": "macos-conan-ninja-release"
         },
         {
             "name": "macos-xcode-release",

+ 2 - 0
client/CGameInfo.cpp

@@ -71,10 +71,12 @@ const HeroTypeService * CGameInfo::heroTypes() const
 	return globalServices->heroTypes();
 }
 
+#if SCRIPTING_ENABLED
 const scripting::Service * CGameInfo::scripts()  const
 {
 	return globalServices->scripts();
 }
+#endif
 
 const spells::Service * CGameInfo::spells()  const
 {

+ 2 - 0
client/CGameInfo.h

@@ -59,7 +59,9 @@ public:
 	const FactionService * factions() const override;
 	const HeroClassService * heroClasses() const override;
 	const HeroTypeService * heroTypes() const override;
+#if SCRIPTING_ENABLED
 	const scripting::Service * scripts() const override;
+#endif
 	const spells::Service * spells() const override;
 	const SkillService * skills() const override;
 	const BattleFieldService * battlefields() const override;

+ 3 - 1
client/CMT.cpp

@@ -232,7 +232,7 @@ int main(int argc, char * argv[])
 	*console->cb = processCommand;
 	console->start();
 
-	const bfs::path logPath = VCMIDirs::get().userCachePath() / "VCMI_Client_log.txt";
+	const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
 	logConfig = new CBasicLogConfigurator(logPath, console);
 	logConfig->configureDefault();
 	logGlobal->info(NAME);
@@ -686,6 +686,7 @@ void processCommand(const std::string &message)
 		std::cout << "\rExtracting done :)\n";
 		std::cout << " Extracted files can be found in " << outPath << " directory\n";
 	}
+#if SCRIPTING_ENABLED
 	else if(message=="get scripts")
 	{
 		std::cout << "Command accepted.\t";
@@ -708,6 +709,7 @@ void processCommand(const std::string &message)
 		std::cout << "\rExtracting done :)\n";
 		std::cout << " Extracted files can be found in " << outPath << " directory\n";
 	}
+#endif
 	else if(message=="get txt")
 	{
 		std::cout << "Command accepted.\t";

+ 8 - 1
client/CMakeLists.txt

@@ -177,9 +177,16 @@ endif()
 
 target_link_libraries(vcmiclient PRIVATE
 		vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF
-		ffmpeg::swscale ffmpeg::avutil ffmpeg::avcodec ffmpeg::avformat
 )
 
+if(ffmpeg_LIBRARIES)
+	target_link_libraries(vcmiclient PRIVATE
+		ffmpeg::swscale ffmpeg::avutil ffmpeg::avcodec ffmpeg::avformat
+	)
+else()
+	target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO)
+endif()
+
 target_include_directories(vcmiclient
 	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR})
 

+ 84 - 78
client/CPlayerInterface.cpp

@@ -218,7 +218,7 @@ void CPlayerInterface::yourTurn()
 
 STRONG_INLINE void subRect(const int & x, const int & y, const int & z, const SDL_Rect & r, const ObjectInstanceID & hid)
 {
-	TerrainTile2 & hlp = CGI->mh->ttiles[x][y][z];
+	TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y];
 	for (auto & elem : hlp.objects)
 		if (elem.obj && elem.obj->id == hid)
 		{
@@ -229,7 +229,7 @@ STRONG_INLINE void subRect(const int & x, const int & y, const int & z, const SD
 
 STRONG_INLINE void delObjRect(const int & x, const int & y, const int & z, const ObjectInstanceID & hid)
 {
-	TerrainTile2 & hlp = CGI->mh->ttiles[x][y][z];
+	TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y];
 	for (int h=0; h<hlp.objects.size(); ++h)
 		if (hlp.objects[h].obj && hlp.objects[h].obj->id == hid)
 		{
@@ -244,6 +244,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 	if(LOCPLINT != this)
 		return;
 
+	//FIXME: read once and store
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-ignore-hero"].Bool())
 		return;
 
@@ -255,7 +256,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 		//AI hero left the visible area (we can't obtain info)
 		//TODO very evil workaround -> retrieve pointer to hero so we could animate it
 		// TODO -> we should not need full CGHeroInstance structure to display animation or it should not be handled by playerint (but by the client itself)
-		const TerrainTile2 & tile = CGI->mh->ttiles[hp.x - 1][hp.y][hp.z];
+		const TerrainTile2 & tile = CGI->mh->ttiles[hp.z][hp.x - 1][hp.y];
 		for(auto & elem : tile.objects)
 			if(elem.obj && elem.obj->id == details.id)
 				hero = dynamic_cast<const CGHeroInstance *>(elem.obj);
@@ -1738,41 +1739,43 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
 
 void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp )
 {
+	auto subArr = (CGI->mh->ttiles)[hp.z];
+
 	if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl
 	{
 		//ho->moveDir = 1;
 		ho->isStanding = false;
-		CGI->mh->ttiles[hp.x-3][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -31)));
-		CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, -31)));
-		CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, -31)));
-		CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, -31)));
+		subArr[hp.x-3][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -31)));
+		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, -31)));
+		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, -31)));
+		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, -31)));
 
-		CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 1)));
+		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 1)));
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 1), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 1), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 1), ho->id);
 
-		CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 33)));
+		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 33)));
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 33), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 33), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 33), ho->id);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y-2].objects.begin(), subArr[hp.x-3][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t
 	{
 		//ho->moveDir = 2;
 		ho->isStanding = false;
-		CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, -31)));
-		CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, -31)));
-		CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, -31)));
+		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, -31)));
+		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, -31)));
+		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, -31)));
 
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, 1), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, 1), ho->id);
@@ -1782,37 +1785,37 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 33), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 33), ho->id);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr
 	{
 		//ho->moveDir = 3;
 		ho->isStanding = false;
-		CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, -31)));
-		CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, -31)));
-		CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, -31)));
-		CGI->mh->ttiles[hp.x+1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -31)));
+		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, -31)));
+		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, -31)));
+		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, -31)));
+		subArr[hp.x+1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -31)));
 
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 1), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 1), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 1), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 1)));
+		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 1)));
 
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 33), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 33), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 33), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 33)));
+		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 33)));
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y-2].objects.begin(), subArr[hp.x+1][hp.y-2].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r
 	{
@@ -1821,16 +1824,16 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 0), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 0), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 0), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 0)));
+		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 0)));
 
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 32), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 32), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 32), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 32)));
+		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 32)));
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br
 	{
@@ -1839,26 +1842,26 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, -1), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, -1), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, -1), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -1)));
+		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -1)));
 
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 31), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 31), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 31), ho->id);
-		CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 31)));
+		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 31)));
 
-		CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, 63)));
-		CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, 63)));
-		CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, 63)));
-		CGI->mh->ttiles[hp.x+1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 63)));
+		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, 63)));
+		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, 63)));
+		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, 63)));
+		subArr[hp.x+1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 63)));
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x+1][hp.y+1].objects.begin(), subArr[hp.x+1][hp.y+1].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b
 	{
@@ -1872,59 +1875,59 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 31), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 31), ho->id);
 
-		CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, 63)));
-		CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, 63)));
-		CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, 63)));
+		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, 63)));
+		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, 63)));
+		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, 63)));
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl
 	{
 		//ho->moveDir = 7;
 		ho->isStanding = false;
-		CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -1)));
+		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -1)));
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, -1), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, -1), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, -1), ho->id);
 
-		CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 31)));
+		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 31)));
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 31), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 31), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 31), ho->id);
 
-		CGI->mh->ttiles[hp.x-3][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 63)));
-		CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, 63)));
-		CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, 63)));
-		CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, 63)));
+		subArr[hp.x-3][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 63)));
+		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, 63)));
+		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, 63)));
+		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, 63)));
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y+1].objects.begin(), subArr[hp.x-3][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
 	}
 	else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l
 	{
 		//ho->moveDir = 8;
 		ho->isStanding = false;
-		CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 0)));
+		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 0)));
 		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 0), ho->id);
 		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 0), ho->id);
 		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 0), ho->id);
 
-		CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 32)));
+		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 32)));
 		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 32), ho->id);
 		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 32), ho->id);
 		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 32), ho->id);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
 
-		std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.end(), objectBlitOrderSorter);
+		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
 	}
 }
 
@@ -2154,13 +2157,16 @@ void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &h
 	subRect(details.end.x, details.end.y, details.end.z, genRect(32, 32, 64, 32), ho->id);
 
 	//restoring good order of objects
-	std::stable_sort(CGI->mh->ttiles[details.end.x-2][details.end.y-1][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x-2][details.end.y-1][details.end.z].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(CGI->mh->ttiles[details.end.x-1][details.end.y-1][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x-1][details.end.y-1][details.end.z].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(CGI->mh->ttiles[details.end.x][details.end.y-1][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x][details.end.y-1][details.end.z].objects.end(), objectBlitOrderSorter);
 
-	std::stable_sort(CGI->mh->ttiles[details.end.x-2][details.end.y][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x-2][details.end.y][details.end.z].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(CGI->mh->ttiles[details.end.x-1][details.end.y][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x-1][details.end.y][details.end.z].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(CGI->mh->ttiles[details.end.x][details.end.y][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x][details.end.y][details.end.z].objects.end(), objectBlitOrderSorter);
+	boost::detail::multi_array::sub_array<TerrainTile2, 2> subArr = (CGI->mh->ttiles)[details.end.z];
+
+	std::stable_sort(subArr[details.end.x-2][details.end.y-1].objects.begin(), subArr[details.end.x-2][details.end.y-1].objects.end(), objectBlitOrderSorter);
+	std::stable_sort(subArr[details.end.x-1][details.end.y-1].objects.begin(), subArr[details.end.x-1][details.end.y-1].objects.end(), objectBlitOrderSorter);
+	std::stable_sort(subArr[details.end.x][details.end.y-1].objects.begin(), subArr[details.end.x][details.end.y-1].objects.end(), objectBlitOrderSorter);
+
+	std::stable_sort(subArr[details.end.x-2][details.end.y].objects.begin(), subArr[details.end.x-2][details.end.y].objects.end(), objectBlitOrderSorter);
+	std::stable_sort(subArr[details.end.x-1][details.end.y].objects.begin(), subArr[details.end.x-1][details.end.y].objects.end(), objectBlitOrderSorter);
+	std::stable_sort(subArr[details.end.x][details.end.y].objects.begin(), subArr[details.end.x][details.end.y].objects.end(), objectBlitOrderSorter);
 }
 
 void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult )
@@ -2882,7 +2888,7 @@ void CPlayerInterface::updateAmbientSounds(bool resetAll)
 	{
 		int dist = pos.dist(tile, int3::DIST_CHEBYSHEV);
 		// We want sound for every special terrain on tile and not just one on top
-		for(auto & ttObj : CGI->mh->ttiles[tile.x][tile.y][tile.z].objects)
+		for(auto & ttObj : CGI->mh->ttiles[tile.z][tile.x][tile.y].objects)
 		{
 			if(ttObj.ambientSound)
 				updateSounds(ttObj.ambientSound.get(), dist);

+ 1 - 1
client/CServerHandler.cpp

@@ -685,7 +685,7 @@ void CServerHandler::threadRunServer()
 {
 #ifndef VCMI_ANDROID
 	setThreadName("CServerHandler::threadRunServer");
-	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
+	const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string();
 	std::string comm = VCMIDirs::get().serverPath().string()
 		+ " --port=" + getDefaultPortStr()
 		+ " --run-by-client"

+ 10 - 0
client/Client.cpp

@@ -263,12 +263,14 @@ void CClient::serialize(BinarySerializer & h, const int version)
 		i->second->saveGame(h, version);
 	}
 
+#if SCRIPTING_ENABLED
 	if(version >= 800)
 	{
 		JsonNode scriptsState;
 		clientScripts->serializeState(h.saving, scriptsState);
 		h & scriptsState;
 	}
+#endif
 }
 
 void CClient::serialize(BinaryDeserializer & h, const int version)
@@ -329,11 +331,13 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 		LOCPLINT = prevInt;
 	}
 
+#if SCRIPTING_ENABLED
 	{
 		JsonNode scriptsState;
 		h & scriptsState;
 		clientScripts->serializeState(h.saving, scriptsState);
 	}
+#endif
 
 	logNetwork->trace("Loaded client part of save %d ms", CSH->th->getDiff());
 }
@@ -352,7 +356,9 @@ void CClient::save(const std::string & fname)
 
 void CClient::endGame()
 {
+#if SCRIPTING_ENABLED
 	clientScripts.reset();
+#endif
 
 	//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
 	for(auto & i : playerint)
@@ -732,6 +738,7 @@ PlayerColor CClient::getLocalPlayer() const
 	return getCurrentPlayer();
 }
 
+#if SCRIPTING_ENABLED
 scripting::Pool * CClient::getGlobalContextPool() const
 {
 	return clientScripts.get();
@@ -741,11 +748,14 @@ scripting::Pool * CClient::getContextPool() const
 {
 	return clientScripts.get();
 }
+#endif
 
 void CClient::reinitScripting()
 {
 	clientEventBus = make_unique<events::EventBus>();
+#if SCRIPTING_ENABLED
 	clientScripts.reset(new scripting::PoolImpl(this));
+#endif
 }
 
 

+ 7 - 0
client/Client.h

@@ -39,10 +39,12 @@ namespace boost { class thread; }
 template<typename T> class CApplier;
 class CBaseForCLApply;
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class PoolImpl;
 }
+#endif
 
 namespace events
 {
@@ -233,13 +235,18 @@ public:
 	void showInfoDialog(InfoWindow * iw) override {};
 	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
 
+#if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;
 	scripting::Pool * getContextPool() const override;
+#endif
+
 private:
 	std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
 	std::map<PlayerColor, std::shared_ptr<CPlayerEnvironment>> playerEnvironments;
 
+#if SCRIPTING_ENABLED
 	std::shared_ptr<scripting::PoolImpl> clientScripts;
+#endif
 	std::unique_ptr<events::EventBus> clientEventBus;
 
 	std::shared_ptr<CApplier<CBaseForCLApply>> applier;

+ 52 - 90
client/mapHandler.cpp

@@ -52,20 +52,22 @@ struct NeighborTilesInfo
 		 d1,
 		 d2,
 		 d3;
-	NeighborTilesInfo(const int3 & pos, const int3 & sizes, const std::vector< std::vector< std::vector<ui8> > > & visibilityMap)
+	NeighborTilesInfo(const int3 & pos, const int3 & sizes, std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap)
 	{
 		auto getTile = [&](int dx, int dy)->bool
 		{
 			if ( dx + pos.x < 0 || dx + pos.x >= sizes.x
 			  || dy + pos.y < 0 || dy + pos.y >= sizes.y)
 				return false;
-			return settings["session"]["spectate"].Bool() ? true : visibilityMap[dx+pos.x][dy+pos.y][pos.z];
+
+			//FIXME: please do not read settings for every tile...
+			return settings["session"]["spectate"].Bool() ? true : (*visibilityMap)[pos.z][dx+pos.x][dy+pos.y];
 		};
 		d7 = getTile(-1, -1); //789
 		d8 = getTile( 0, -1); //456
 		d9 = getTile(+1, -1); //123
 		d4 = getTile(-1, 0);
-		d5 = visibilityMap[pos.x][pos.y][pos.z];
+		d5 = (*visibilityMap)[pos.z][pos.x][pos.y];
 		d6 = getTile(+1, 0);
 		d1 = getTile(-1, +1);
 		d2 = getTile( 0, +1);
@@ -113,22 +115,9 @@ void CMapHandler::prepareFOWDefs()
 		FoWfullHide[frame] = graphics->fogOfWarFullHide->getImage(frame);
 
 	//initialization of type of full-hide image
-	hideBitmap.resize(sizes.x);
-	for (auto & elem : hideBitmap)
-	{
-		elem.resize(sizes.y);
-	}
-	for (auto & elem : hideBitmap)
-	{
-		for (int j = 0; j < sizes.y; ++j)
-		{
-			elem[j].resize(sizes.z);
-			for(int k = 0; k < sizes.z; ++k)
-			{
-				elem[j][k] = CRandomGenerator::getDefault().nextInt((int)size - 1);
-			}
-		}
-	}
+	hideBitmap.resize(boost::extents[sizes.z][sizes.x][sizes.y]);
+	for (int i = 0; i < hideBitmap.num_elements(); i++)
+		hideBitmap.data()[i] = CRandomGenerator::getDefault().nextInt(size - 1);
 
 	size = graphics->fogOfWarPartialHide->size(0);
 	FoWpartialHide.resize(size);
@@ -211,16 +200,12 @@ void CMapHandler::initTerrainGraphics()
 
 	// Create enough room for the whole map and its frame
 
-	ttiles.resize(sizes.x, frameW, frameW);
-	for (int i=0-frameW;i<ttiles.size()-frameW;i++)
-	{
-		ttiles[i].resize(sizes.y, frameH, frameH);
-	}
-	for (int i=0-frameW;i<ttiles.size()-frameW;i++)
-	{
-		for (int j=0-frameH;j<(int)sizes.y+frameH;j++)
-			ttiles[i][j].resize(sizes.z, 0, 0);
-	}
+	//ttiles.resize(sizes.x, frameW, frameW);
+
+	//FIXME: why do we even handle array with z (surface and undeground) at the same time?
+
+	ttiles.resize(boost::extents[sizes.z][sizes.x + 2 * frameW][sizes.y + 2 * frameH]); 
+	ttiles.reindex(std::list<int>{ 0, -frameW, -frameH }); //need to move starting coordinates so that used index is always positive
 }
 
 void CMapHandler::initBorderGraphics()
@@ -321,19 +306,21 @@ void CMapHandler::initObjectRects()
 					obj->coveringAt(currTile.x, currTile.y) // object is visible here
 				  )
 				{
-					ttiles[currTile.x][currTile.y][currTile.z].objects.push_back(toAdd);
+					ttiles[currTile.z][currTile.x][currTile.y].objects.push_back(toAdd);
 				}
 			}
 		}
 	}
 
-	for(int ix=0; ix<ttiles.size()-frameW; ++ix)
+	auto shape = ttiles.shape();
+	for(size_t z = 0; z < shape[0]; z++)
 	{
-		for(int iy=0; iy<ttiles[0].size()-frameH; ++iy)
+		for(size_t x = 0; x < shape[1] - frameW; x++)
 		{
-			for(int iz=0; iz<ttiles[0][0].size(); ++iz)
+			for(size_t y = 0; y < shape[2] - frameH; y++)
 			{
-				stable_sort(ttiles[ix][iy][iz].objects.begin(), ttiles[ix][iy][iz].objects.end(), objectBlitOrderSorter);
+				auto & objects = ttiles[z][x][y].objects;
+				stable_sort(objects.begin(), objects.end(), objectBlitOrderSorter);
 			}
 		}
 	}
@@ -351,7 +338,7 @@ void CMapHandler::init()
 	//sizes of terrain
 	sizes.x = map->width;
 	sizes.y = map->height;
-	sizes.z = map->twoLevel ? 2 : 1;
+	sizes.z = map->levels();
 
 	// Total number of visible tiles. Subtract the center tile, then
 	// compute the number of tiles on each side, and reassemble.
@@ -557,7 +544,9 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf
 		const CGObjectInstance * obj = object.obj;
 
 		const bool sameLevel = obj->pos.z == pos.z;
-		const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.x][pos.y][pos.z];
+
+		//FIXME: Don't read options in  a loop :v
+		const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.z][pos.x][pos.y];
 		const bool isVisitable = obj->visitableAt(pos.x, pos.y);
 
 		if(sameLevel && isVisible && isVisitable)
@@ -826,11 +815,11 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain
 
 void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const
 {
-	const NeighborTilesInfo neighborInfo(pos, parent->sizes, *info->visibilityMap);
+	const NeighborTilesInfo neighborInfo(pos, parent->sizes, info->visibilityMap);
 
 	int retBitmapID = neighborInfo.getBitmapID();// >=0 -> partial hide, <0 - full hide
 	if (retBitmapID < 0)
-		retBitmapID = - parent->hideBitmap[pos.x][pos.y][pos.z] - 1; //fully hidden
+		retBitmapID = - parent->hideBitmap[pos.z][pos.x][pos.y] - 1; //fully hidden
 
 	std::shared_ptr<IImage> image;
 
@@ -865,7 +854,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			realTileRect.x = realPos.x;
 			realTileRect.y = realPos.y;
 
-			const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
+			const TerrainTile2 & tile = parent->ttiles[pos.z][pos.x][pos.y];
 			const TerrainTile & tinfo = parent->map->getTile(pos);
 			const TerrainTile * tinfoUpper = pos.y > 0 ? &parent->map->getTile(int3(pos.x, pos.y - 1, pos.z)) : nullptr;
 
@@ -896,9 +885,9 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			}
 			else
 			{
-				const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
+				const TerrainTile2 & tile = parent->ttiles[pos.z][pos.x][pos.y];
 
-				if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
+				if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[topTile.z][pos.x][pos.y] && !info->showAllTerrain)
 					drawFow(targetSurf);
 
 				// overlay needs to be drawn over fow, because of artifacts-aura-like spells
@@ -1105,7 +1094,7 @@ bool CMapHandler::CMapBlitter::canDrawCurrentTile() const
 	if(settings["session"]["spectate"].Bool())
 		return true;
 
-	const NeighborTilesInfo neighbors(pos, parent->sizes, *info->visibilityMap);
+	const NeighborTilesInfo neighbors(pos, parent->sizes, info->visibilityMap);
 	return !neighbors.areAllHidden();
 }
 
@@ -1136,7 +1125,7 @@ bool CMapHandler::updateObjectsFade()
 			++iter;
 		else // fade finished
 		{
-			auto &objs = ttiles[pos.x][pos.y][pos.z].objects;
+			auto &objs = ttiles[pos.z][pos.x][pos.y].objects;
 			for (auto objIter = objs.begin(); objIter != objs.end(); ++objIter)
 			{
 				if ((*objIter).fadeAnimKey == (*iter).first)
@@ -1210,6 +1199,9 @@ bool CMapHandler::printObject(const CGObjectInstance * obj, bool fadein)
 	const int tilesW = bitmap->width()/32;
 	const int tilesH = bitmap->height()/32;
 
+	auto ttilesWidth = ttiles.shape()[1];
+	auto ttilesHeight = ttiles.shape()[2];
+
 	for(int fx=0; fx<tilesW; ++fx)
 	{
 		for(int fy=0; fy<tilesH; ++fy)
@@ -1220,10 +1212,13 @@ bool CMapHandler::printObject(const CGObjectInstance * obj, bool fadein)
 			cr.x = fx*32;
 			cr.y = fy*32;
 
-			if((obj->pos.x + fx - tilesW+1)>=0 && (obj->pos.x + fx - tilesW+1)<ttiles.size()-frameW && (obj->pos.y + fy - tilesH+1)>=0 && (obj->pos.y + fy - tilesH+1)<ttiles[0].size()-frameH)
+			if((obj->pos.x + fx - tilesW + 1) >= 0 &&
+				(obj->pos.x + fx - tilesW + 1) < ttilesWidth - frameW &&
+				(obj->pos.y + fy - tilesH + 1) >= 0 &&
+				(obj->pos.y + fy - tilesH + 1) < ttilesHeight - frameH)
 			{
 				int3 pos(obj->pos.x + fx - tilesW + 1, obj->pos.y + fy - tilesH + 1, obj->pos.z);
-				TerrainTile2 & curt = ttiles[pos.x][pos.y][pos.z];
+				TerrainTile2 & curt = ttiles[pos.z][pos.x][pos.y];
 
 				TerrainTileObject toAdd(obj, cr, obj->visitableAt(pos.x, pos.y));
 				if (fadein && ADVOPT.objectFading)
@@ -1252,59 +1247,26 @@ bool CMapHandler::printObject(const CGObjectInstance * obj, bool fadein)
 
 bool CMapHandler::hideObject(const CGObjectInstance * obj, bool fadeout)
 {
-	//optimized version which reveals weird bugs with missing def name
-	//auto pos = obj->pos;
-
-	//for (size_t i = pos.x; i > pos.x - obj->getWidth(); i--)
-	//{
-	//	for (size_t j = pos.y; j > pos.y - obj->getHeight(); j--)
-	//	{
-	//		int3 t(i, j, pos.z);
-	//		if (!map->isInTheMap(t))
-	//			continue;
-
-	//		auto &objs = ttiles[i][j][pos.z].objects;
-	//		for (size_t x = 0; x < objs.size(); x++)
-	//		{
-	//			auto ourObj = objs[x].obj;
-	//			if (ourObj && ourObj->id == obj->id)
-	//			{
-	//				if (fadeout && ADVOPT.objectFading) // object should be faded == erase is delayed until the end of fadeout
-	//				{
-	//					if (startObjectFade(objs[x], false, t))
-	//						objs[x].obj = nullptr; //set original pointer to null
-	//					else
-	//						objs.erase(objs.begin() + x);
-	//				}
-	//				else
-	//					objs.erase(objs.begin() + x);
-	//				break;
-	//			}
-	//		}
-	//	}
-
-	//}
-
-	for (size_t i = 0; i<map->width; i++)
+	for(size_t z = 0; z < map->levels(); z++)
 	{
-		for (size_t j = 0; j<map->height; j++)
+		for(size_t x = 0; x < map->width; x++)
 		{
-			for (size_t k = 0; k<(map->twoLevel ? 2 : 1); k++)
+			for(size_t y = 0; y < map->height; y++)
 			{
-				auto &objs = ttiles[(int)i][(int)j][(int)k].objects;
-				for (size_t x = 0; x < objs.size(); x++)
+				auto &objs = ttiles[(int)z][(int)x][(int)y].objects;
+				for(size_t i = 0; i < objs.size(); i++)
 				{
-					if (objs[x].obj && objs[x].obj->id == obj->id)
+					if (objs[i].obj && objs[i].obj->id == obj->id)
 					{
 						if (fadeout && ADVOPT.objectFading) // object should be faded == erase is delayed until the end of fadeout
 						{
-							if (startObjectFade(objs[x], false, int3((si32)i, (si32)j, (si32)k)))
-								objs[x].obj = nullptr;
+							if (startObjectFade(objs[i], false, int3((si32)x, (si32)y, (si32)z)))
+								objs[i].obj = nullptr;
 							else
-								objs.erase(objs.begin() + x);
+								objs.erase(objs.begin() + i);
 						}
 						else
-							objs.erase(objs.begin() + x);
+							objs.erase(objs.begin() + i);
 						break;
 					}
 				}
@@ -1392,7 +1354,7 @@ CMapHandler::CMapHandler()
 
 bool CMapHandler::hasObjectHole(const int3 & pos) const
 {
-	const TerrainTile2 & tt = ttiles[pos.x][pos.y][pos.z];
+	const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y];
 
 	for(auto & elem : tt.objects)
 	{
@@ -1411,7 +1373,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM
 		out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS);
 		return;
 	}
-	const TerrainTile2 & tt = ttiles[pos.x][pos.y][pos.z];
+	const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y];
 	bool isTile2Terrain = false;
 	out.clear();
 

+ 5 - 4
client/mapHandler.h

@@ -92,7 +92,7 @@ struct MapDrawingInfo
 {
 	bool scaled;
 	int3 &topTile; // top-left tile in viewport [in tiles]
-	const std::vector< std::vector< std::vector<ui8> > > * visibilityMap;
+	std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap;
 	SDL_Rect * drawBounds; // map rect drawing bounds on screen
 	std::shared_ptr<CAnimation> icons; // holds overlay icons for world view mode
 	float scale; // map scale for world view mode (only if scaled == true)
@@ -110,9 +110,10 @@ struct MapDrawingInfo
 
 	bool showAllTerrain; //for expert viewEarth
 
-	MapDrawingInfo(int3 &topTile_, const std::vector< std::vector< std::vector<ui8> > > * visibilityMap_, SDL_Rect * drawBounds_, std::shared_ptr<CAnimation> icons_ = nullptr)
+	MapDrawingInfo(int3 &topTile_, std::shared_ptr<const boost::multi_array<ui8, 3>> visibilityMap_, SDL_Rect * drawBounds_, std::shared_ptr<CAnimation> icons_ = nullptr)
 		: scaled(false),
 		  topTile(topTile_),
+
 		  visibilityMap(visibilityMap_),
 		  drawBounds(drawBounds_),
 		  icons(icons_),
@@ -332,7 +333,7 @@ class CMapHandler
 	void initTerrainGraphics();
 	void prepareFOWDefs();
 public:
-	PseudoV< PseudoV< PseudoV<TerrainTile2> > > ttiles; //informations about map tiles
+	boost::multi_array<TerrainTile2, 3> ttiles; //informations about map tiles [z][x][y]
 	int3 sizes; //map size (x = width, y = height, z = number of levels)
 	const CMap * map;
 
@@ -368,7 +369,7 @@ public:
 
 	//Fog of War cache (not owned)
 	std::vector<std::shared_ptr<IImage>> FoWfullHide;
-	std::vector<std::vector<std::vector<ui8> > > hideBitmap; //frame indexes (in FoWfullHide) of graphic that should be used to fully hide a tile
+	boost::multi_array<ui8, 3> hideBitmap; //frame indexes (in FoWfullHide) of graphic that should be used to fully hide a tile
 
 	std::vector<std::shared_ptr<IImage>> FoWpartialHide;
 

+ 3 - 3
client/windows/CAdvmapInterface.cpp

@@ -380,7 +380,7 @@ void CTerrainRect::show(SDL_Surface * to)
 {
 	if (adventureInt->mode == EAdvMapMode::NORMAL)
 	{
-		MapDrawingInfo info(adventureInt->position, &LOCPLINT->cb->getVisibilityMap(), &pos);
+		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), &pos);
 		info.otherheroAnim = true;
 		info.anim = adventureInt->anim;
 		info.heroAnim = adventureInt->heroAnim;
@@ -407,7 +407,7 @@ void CTerrainRect::showAll(SDL_Surface * to)
 	// world view map is static and doesn't need redraw every frame
 	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
 	{
-		MapDrawingInfo info(adventureInt->position, &LOCPLINT->cb->getVisibilityMap(), &pos, adventureInt->worldViewIcons);
+		MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), &pos, adventureInt->worldViewIcons);
 		info.scaled = true;
 		info.scale = adventureInt->worldViewScale;
 		adventureInt->worldViewOptions.adjustDrawingInfo(info);
@@ -757,7 +757,7 @@ void CAdvMapInt::fworldViewScale4x()
 void CAdvMapInt::fswitchLevel()
 {
 	// with support for future multi-level maps :)
-	int maxLevels = CGI->mh->map->twoLevel ? 2 : 1;
+	int maxLevels = CGI->mh->map->levels();
 	if (maxLevels < 2)
 		return;
 

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -1514,7 +1514,7 @@ void CPuzzleWindow::showAll(SDL_Surface * to)
 	Rect mapRect = genRect(544, 591, pos.x + 8, pos.y + 7);
 	int3 topTile = grailPos - moveInt;
 
-	MapDrawingInfo info(topTile, &LOCPLINT->cb->getVisibilityMap(), &mapRect);
+	MapDrawingInfo info(topTile, LOCPLINT->cb->getVisibilityMap(), &mapRect);
 	info.puzzleMode = true;
 	info.grailPos = grailPos;
 	CGI->mh->drawTerrainRectNew(to, &info);

+ 109 - 0
config/schemas/terrain.json

@@ -0,0 +1,109 @@
+{
+	"type":"object",
+	"$schema": "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI terrain format",
+	"description" : "Format used to define new terrains in VCMI",
+	"required" : [ "tiles", "code", "moveCost" ],
+
+	"additionalProperties" : false,
+	"properties":{
+		"moveCost":
+		{
+			"type": "number",
+			"description": "How many movement points needed to move hero"
+		},
+		"minimapUnblocked":
+		{
+			"type": "array",
+			"description": "Color of terrain on minimap without unpassable objects",
+			"minItems": 3,
+			"maxItems": 3,
+			"items":
+			{
+				"type": "number"
+			}
+		},
+		"minimapBlocked":
+		{
+			"type": "array",
+			"description": "Color of terrain on minimap with unpassable objects",
+			"minItems": 3,
+			"maxItems": 3,
+			"items":
+			{
+				"type": "number"
+			}
+		},
+		"music":
+		{
+			"type": "string",
+			"description": "Music filename to play on this terrain on adventure map"
+		},
+		"tiles":
+		{
+			"type": "string",
+			"description": "Name of file with graphicks",
+			"format": "defFile"
+		},
+		"type":
+		{
+			"type": "string",
+			"description": "Type of this terrain. Can be land, water, subterranean or rock",
+			"enum": ["LAND", "WATER", "SUB", "ROCK"]
+		},
+		"rockTerrain":
+		{
+			"type": "string",
+			"description": "The name of tock type terrain which will be used as borders in the underground"
+		},
+		"river":
+		{
+			"type": "string",
+			"description": "River type which should be used for that terrain",
+			"enum": ["", "rw", "ri", "rm", "rl"]
+		},
+		"horseSoundId":
+		{
+			"type": "number",
+			"description": "Id of horse sound to be played when hero is moving across terrain"
+		},
+		"text":
+		{
+			"type": "string",
+			"description": "Text to be shown when mouse if over terrain"
+		},
+		"code":
+		{
+			"type": "string",
+			"description": "Two-letters unique indentifier for this terrain. Used for terrain serializaion"
+		},
+		"battleFields":
+		{
+			"type": "array",
+			"description": "array of battleFields for this terrain",
+			"items":
+			{
+				"type": "string"
+			}
+		},
+		"prohibitTransitions":
+		{
+			"type": "array",
+			"description": "array or terrain names, which is prohibited to make transition from/to",
+			"items":
+			{
+				"type": "string"
+			}
+		},
+		"transitionRequired":
+		{
+			"type": "boolean",
+			"description": "If sand/dirt transition required from/to other terrains"
+		},
+		"terrainViewPatterns":
+		{
+			"type": "string",
+			"description": "Can be normal, dirt, water, rock"
+		}
+	}
+}

+ 4 - 0
include/vcmi/Services.h

@@ -32,10 +32,12 @@ namespace spells
 	}
 }
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class Service;
 }
+#endif
 
 class DLL_LINKAGE Services
 {
@@ -47,7 +49,9 @@ public:
 	virtual const FactionService * factions() const = 0;
 	virtual const HeroClassService * heroClasses() const = 0;
 	virtual const HeroTypeService * heroTypes() const = 0;
+#if SCRIPTING_ENABLED
 	virtual const scripting::Service * scripts() const = 0;
+#endif
 	virtual const spells::Service * spells() const = 0;
 	virtual const SkillService * skills() const = 0;
 	virtual const BattleFieldService * battlefields() const = 0;

+ 2 - 0
include/vcmi/scripting/Service.h

@@ -10,6 +10,7 @@
 
 #pragma once
 
+#if SCRIPTING_ENABLED
 #include <vcmi/Environment.h>
 
 class Services;
@@ -78,3 +79,4 @@ public:
 
 
 }
+#endif

+ 1 - 1
launcher/mainwindow_moc.cpp

@@ -28,7 +28,7 @@ void MainWindow::load()
 	QDir::setCurrent(QApplication::applicationDirPath());
 
 	console = new CConsoleHandler();
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Launcher_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Launcher_log.txt", console);
 	logConfig.configureDefault();
 
 	CResourceHandler::initialize();

+ 70 - 27
launcher/modManager/cmodlist.cpp

@@ -12,29 +12,53 @@
 
 #include "../../lib/JsonNode.h"
 #include "../../lib/filesystem/CFileInputStream.h"
+#include "../../lib/GameConstants.h"
 
-bool CModEntry::compareVersions(QString lesser, QString greater)
+namespace
 {
-	static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
-
-	QStringList lesserList = lesser.split(".");
-	QStringList greaterList = greater.split(".");
-
-	assert(lesserList.size() <= maxSections);
-	assert(greaterList.size() <= maxSections);
+bool isCompatible(const QString & verMin, const QString & verMax)
+{
+	const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
+	QVersionNumber vcmiVersion(GameConstants::VCMI_VERSION_MAJOR,
+							   GameConstants::VCMI_VERSION_MINOR,
+							   GameConstants::VCMI_VERSION_PATCH);
+	
+	auto versionMin = QVersionNumber::fromString(verMin);
+	auto versionMax = QVersionNumber::fromString(verMax);
+	
+	auto buildVersion = [maxSections](QVersionNumber & ver)
+	{
+		if(ver.segmentCount() < maxSections)
+		{
+			auto segments = ver.segments();
+			for(int i = segments.size() - 1; i < maxSections; ++i)
+				segments.append(0);
+			ver = QVersionNumber(segments);
+		}
+	};
 
-	for(int i = 0; i < maxSections; i++)
+	if(!versionMin.isNull())
 	{
-		if(greaterList.size() <= i) // 1.1.1 > 1.1
+		buildVersion(versionMin);
+		if(vcmiVersion < versionMin)
 			return false;
-
-		if(lesserList.size() <= i) // 1.1 < 1.1.1
-			return true;
-
-		if(lesserList[i].toInt() != greaterList[i].toInt())
-			return lesserList[i].toInt() < greaterList[i].toInt(); // 1.1 < 1.2
 	}
-	return false;
+	
+	if(!versionMax.isNull())
+	{
+		buildVersion(versionMax);
+		if(vcmiVersion > versionMax)
+			return false;
+	}
+	return true;
+}
+}
+
+bool CModEntry::compareVersions(QString lesser, QString greater)
+{
+	auto versionLesser = QVersionNumber::fromString(lesser);
+	auto versionGreater = QVersionNumber::fromString(greater);
+	return versionLesser < versionGreater;
 }
 
 QString CModEntry::sizeToString(double size)
@@ -92,6 +116,15 @@ bool CModEntry::isUpdateable() const
 	return false;
 }
 
+bool CModEntry::isCompatible() const
+{
+	if(!isInstalled())
+		return false;
+
+	auto compatibility = localData["compatibility"].toMap();
+	return ::isCompatible(compatibility["min"].toString(), compatibility["max"].toString());
+}
+
 bool CModEntry::isEssential() const
 {
 	return getValue("storedLocaly").toBool();
@@ -102,6 +135,11 @@ bool CModEntry::isInstalled() const
 	return !localData.isEmpty();
 }
 
+bool CModEntry::isValid() const
+{
+	return !localData.isEmpty() || !repository.isEmpty();
+}
+
 int CModEntry::getModStatus() const
 {
 	int status = 0;
@@ -193,7 +231,11 @@ static QVariant getValue(QVariant input, QString path)
 		QString remainder = "/" + path.section('/', 2, -1);
 
 		entryName.remove(0, 1);
-		return getValue(input.toMap().value(entryName), remainder);
+		QMap<QString, QString> keyNormalize;
+		for(auto & key : input.toMap().keys())
+			keyNormalize[key.toLower()] = key;
+
+		return getValue(input.toMap().value(keyNormalize[entryName]), remainder);
 	}
 	else
 	{
@@ -203,6 +245,7 @@ static QVariant getValue(QVariant input, QString path)
 
 CModEntry CModList::getMod(QString modname) const
 {
+	modname = modname.toLower();
 	QVariantMap repo;
 	QVariantMap local = localModList[modname].toMap();
 	QVariantMap settings;
@@ -246,14 +289,14 @@ CModEntry CModList::getMod(QString modname) const
 		QVariant repoVal = getValue(entry, path);
 		if(repoVal.isValid())
 		{
-			if(repo.empty())
-			{
-				repo = repoVal.toMap();
-			}
-			else
+			auto repoValMap = repoVal.toMap();
+			auto compatibility = repoValMap["compatibility"].toMap();
+			if(isCompatible(compatibility["min"].toString(), compatibility["max"].toString()))
 			{
-				if(CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString()))
-					repo = repoVal.toMap();
+				if(repo.empty() || CModEntry::compareVersions(repo["version"].toString(), repoValMap["version"].toString()))
+				{
+					repo = repoValMap;
+				}
 			}
 		}
 	}
@@ -297,12 +340,12 @@ QVector<QString> CModList::getModList() const
 	{
 		for(auto it = repo.begin(); it != repo.end(); it++)
 		{
-			knownMods.insert(it.key());
+			knownMods.insert(it.key().toLower());
 		}
 	}
 	for(auto it = localModList.begin(); it != localModList.end(); it++)
 	{
-		knownMods.insert(it.key());
+		knownMods.insert(it.key().toLower());
 	}
 
 	for(auto entry : knownMods)

+ 9 - 9
launcher/modManager/cmodlistview_moc.ui

@@ -253,7 +253,7 @@
             </property>
             <property name="html">
              <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
 &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -386,7 +386,7 @@ p, li { white-space: pre-wrap; }
       </spacer>
      </item>
      <item>
-      <widget class="QPushButton" name="enableButton">
+      <widget class="QPushButton" name="uninstallButton">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
          <horstretch>0</horstretch>
@@ -406,12 +406,12 @@ p, li { white-space: pre-wrap; }
         </size>
        </property>
        <property name="text">
-        <string>Enable</string>
+        <string>Uninstall</string>
        </property>
       </widget>
      </item>
      <item>
-      <widget class="QPushButton" name="disableButton">
+      <widget class="QPushButton" name="enableButton">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
          <horstretch>0</horstretch>
@@ -431,12 +431,12 @@ p, li { white-space: pre-wrap; }
         </size>
        </property>
        <property name="text">
-        <string>Disable</string>
+        <string>Enable</string>
        </property>
       </widget>
      </item>
      <item>
-      <widget class="QPushButton" name="updateButton">
+      <widget class="QPushButton" name="disableButton">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
          <horstretch>0</horstretch>
@@ -456,12 +456,12 @@ p, li { white-space: pre-wrap; }
         </size>
        </property>
        <property name="text">
-        <string>Update</string>
+        <string>Disable</string>
        </property>
       </widget>
      </item>
      <item>
-      <widget class="QPushButton" name="uninstallButton">
+      <widget class="QPushButton" name="updateButton">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
          <horstretch>0</horstretch>
@@ -481,7 +481,7 @@ p, li { white-space: pre-wrap; }
         </size>
        </property>
        <property name="text">
-        <string>Uninstall</string>
+        <string>Update</string>
        </property>
       </widget>
      </item>

+ 13 - 12
lib/CGameInfoCallback.cpp

@@ -502,28 +502,29 @@ const TerrainTile * CGameInfoCallback::getTile( int3 tile, bool verbose) const
 }
 
 //TODO: typedef?
-std::shared_ptr<boost::multi_array<TerrainTile*, 3>> CGameInfoCallback::getAllVisibleTiles() const
+std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> CGameInfoCallback::getAllVisibleTiles() const
 {
 	assert(player.is_initialized());
 	auto team = getPlayerTeam(player.get());
 
 	size_t width = gs->map->width;
 	size_t height = gs->map->height;
-	size_t levels = (gs->map->twoLevel ? 2 : 1);
+	size_t levels = gs->map->levels();
 
+	auto ptr = new boost::multi_array<TerrainTile*, 3>(boost::extents[levels][width][height]);
 
-	boost::multi_array<TerrainTile*, 3> tileArray(boost::extents[width][height][levels]);
-
-	for (size_t x = 0; x < width; x++)
-		for (size_t y = 0; y < height; y++)
-			for (size_t z = 0; z < levels; z++)
+	int3 tile;
+	for(tile.z = 0; tile.z < levels; tile.z++)
+		for(tile.x = 0; tile.x < width; tile.x++)
+			for(tile.y = 0; tile.y < height; tile.y++)
 			{
-				if (team->fogOfWarMap[x][y][z])
-					tileArray[x][y][z] = &gs->map->getTile(int3((si32)x, (si32)y, (si32)z));
+				if ((*team->fogOfWarMap)[tile.z][tile.x][tile.y])
+					(*ptr)[tile.z][tile.x][tile.y] = &gs->map->getTile(tile);
 				else
-					tileArray[x][y][z] = nullptr;
+					(*ptr)[tile.z][tile.x][tile.y] = nullptr;
 			}
-	return std::make_shared<boost::multi_array<TerrainTile*, 3>>(tileArray);
+
+	return std::shared_ptr<const boost::multi_array<TerrainTile*, 3>>(ptr);
 }
 
 EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, BuildingID ID )
@@ -694,7 +695,7 @@ CGameInfoCallback::CGameInfoCallback(CGameState *GS, boost::optional<PlayerColor
 	player = Player;
 }
 
-const std::vector< std::vector< std::vector<ui8> > > & CPlayerSpecificInfoCallback::getVisibilityMap() const
+std::shared_ptr<const boost::multi_array<ui8, 3>> CPlayerSpecificInfoCallback::getVisibilityMap() const
 {
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 	return gs->getPlayerTeam(*player)->fogOfWarMap;

+ 2 - 2
lib/CGameInfoCallback.h

@@ -185,7 +185,7 @@ public:
 	virtual const CMapHeader * getMapHeader()const;
 	virtual int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
 	virtual const TerrainTile * getTile(int3 tile, bool verbose = true) const;
-	virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
+	virtual std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
 	virtual bool isInTheMap(const int3 &pos) const;
 	virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
 	virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config);
@@ -235,7 +235,7 @@ public:
 
 	virtual int getResourceAmount(Res::ERes type) const;
 	virtual TResources getResourceAmount() const;
-	virtual const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map
+	virtual std::shared_ptr<const boost::multi_array<ui8, 3>> getVisibilityMap() const; //returns visibility map
 	//virtual const PlayerSettings * getPlayerSettings(PlayerColor color) const;
 };
 

+ 2 - 0
lib/CGameInterface.cpp

@@ -126,10 +126,12 @@ std::shared_ptr<CBattleGameInterface> CDynLibHandler::getNewBattleAI(std::string
 	return createAnyAI<CBattleGameInterface>(dllname, "GetNewBattleAI");
 }
 
+#if SCRIPTING_ENABLED
 std::shared_ptr<scripting::Module> CDynLibHandler::getNewScriptingModule(const boost::filesystem::path & dllname)
 {
 	return createAny<scripting::Module>(dllname, "GetNewModule");
 }
+#endif
 
 BattleAction CGlobalAI::activeStack(const CStack * stack)
 {

+ 6 - 0
lib/CGameInterface.h

@@ -56,10 +56,14 @@ class CSaveFile;
 class BinaryDeserializer;
 class BinarySerializer;
 struct ArtifactLocation;
+
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class Module;
 }
+#endif
+
 
 class DLL_LINKAGE CBattleGameInterface : public IBattleEventsReceiver
 {
@@ -110,7 +114,9 @@ class DLL_LINKAGE CDynLibHandler
 public:
 	static std::shared_ptr<CGlobalAI> getNewAI(std::string dllname);
 	static std::shared_ptr<CBattleGameInterface> getNewBattleAI(std::string dllname);
+#if SCRIPTING_ENABLED
 	static std::shared_ptr<scripting::Module> getNewScriptingModule(const boost::filesystem::path & dllname);
+#endif
 };
 
 class DLL_LINKAGE CGlobalAI : public CGameInterface // AI class (to derivate)

+ 17 - 21
lib/CGameState.cpp

@@ -954,19 +954,20 @@ void CGameState::initGrailPosition()
 		static const int BORDER_WIDTH = 9; // grail must be at least 9 tiles away from border
 
 		// add all not blocked tiles in range
-		for (int i = BORDER_WIDTH; i < map->width - BORDER_WIDTH ; i++)
+
+		for (int z = 0; z < map->levels(); z++)
 		{
-			for (int j = BORDER_WIDTH; j < map->height - BORDER_WIDTH; j++)
+			for(int x = BORDER_WIDTH; x < map->width - BORDER_WIDTH ; x++)
 			{
-				for (int k = 0; k < (map->twoLevel ? 2 : 1); k++)
+				for(int y = BORDER_WIDTH; y < map->height - BORDER_WIDTH; y++)
 				{
-					const TerrainTile &t = map->getTile(int3(i, j, k));
+					const TerrainTile &t = map->getTile(int3(x, y, z));
 					if(!t.blocked
 						&& !t.visitable
 						&& t.terType.isLand()
 						&& t.terType.isPassable()
-						&& (int)map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadius * map->grailRadius))
-						allowedPos.push_back(int3(i,j,k));
+						&& (int)map->grailPos.dist2dSQ(int3(x, y, z)) <= (map->grailRadius * map->grailRadius))
+						allowedPos.push_back(int3(x,y,z));
 				}
 			}
 		}
@@ -1593,20 +1594,13 @@ void CGameState::giveCampaignBonusToHero(CGHeroInstance * hero)
 void CGameState::initFogOfWar()
 {
 	logGlobal->debug("\tFog of war"); //FIXME: should be initialized after all bonuses are set
+
+	int layers = map->levels();
 	for(auto & elem : teams)
 	{
-		elem.second.fogOfWarMap.resize(map->width);
-		for(int g=0; g<map->width; ++g)
-			elem.second.fogOfWarMap[g].resize(map->height);
-
-		for(int g=-0; g<map->width; ++g)
-			for(int h=0; h<map->height; ++h)
-				elem.second.fogOfWarMap[g][h].resize(map->twoLevel ? 2 : 1, 0);
-
-		for(int g=0; g<map->width; ++g)
-			for(int h=0; h<map->height; ++h)
-				for(int v = 0; v < (map->twoLevel ? 2 : 1); ++v)
-					elem.second.fogOfWarMap[g][h][v] = 0;
+		auto fow = elem.second.fogOfWarMap;
+		fow->resize(boost::extents[layers][map->width][map->height]);
+		std::fill(fow->data(), fow->data() + fow->num_elements(), 0);
 
 		for(CGObjectInstance *obj : map->objects)
 		{
@@ -1616,7 +1610,7 @@ void CGameState::initFogOfWar()
 			getTilesInRange(tiles, obj->getSightCenter(), obj->getSightRadius(), obj->tempOwner, 1);
 			for(int3 tile : tiles)
 			{
-				elem.second.fogOfWarMap[tile.x][tile.y][tile.z] = 1;
+				(*elem.second.fogOfWarMap)[tile.z][tile.x][tile.y] = 1;
 			}
 		}
 	}
@@ -2019,6 +2013,7 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
 
 void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config)
 {
+	//FIXME: creating pathfinder is costly, maybe reset / clear is enough?
 	CPathfinder pathfinder(this, config);
 	pathfinder.calculatePaths();
 }
@@ -2080,7 +2075,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 
 int3 CGameState::guardingCreaturePosition (int3 pos) const
 {
-	return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z];
+	return gs->map->guardingCreaturePositions[pos.z][pos.x][pos.y];
 }
 
 void CGameState::updateRumor()
@@ -2164,7 +2159,7 @@ bool CGameState::isVisible(int3 pos, PlayerColor player)
 	if(player.isSpectator())
 		return true;
 
-	return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z];
+	return (*getPlayerTeam(player)->fogOfWarMap)[pos.z][pos.x][pos.y];
 }
 
 bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional<PlayerColor> player )
@@ -3096,6 +3091,7 @@ int ArmyDescriptor::getStrength() const
 TeamState::TeamState()
 {
 	setNodeType(TEAM);
+	fogOfWarMap = std::make_shared<boost::multi_array<ui8, 3>>();
 }
 
 TeamState::TeamState(TeamState && other):

+ 18 - 6
lib/CModHandler.cpp

@@ -434,7 +434,9 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell")));
 	handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill")));
 	handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template")));
+#if SCRIPTING_ENABLED
 	handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script")));
+#endif
 	handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield")));
 	handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle")));
 	//TODO: any other types of moddables?
@@ -544,10 +546,14 @@ CModInfo::Version CModInfo::Version::fromString(std::string from)
 	{
 		auto pointPos = from.find('.');
 		major = std::stoi(from.substr(0, pointPos));
-		from = from.substr(pointPos);
-		pointPos = from.find('.');
-		minor = std::stoi(from.substr(0, pointPos));
-		patch = std::stoi(from.substr(pointPos));
+		if(pointPos != std::string::npos)
+		{
+			from = from.substr(pointPos + 1);
+			pointPos = from.find('.');
+			minor = std::stoi(from.substr(0, pointPos));
+			if(pointPos != std::string::npos)
+				patch = std::stoi(from.substr(pointPos + 1));
+		}
 	}
 	catch(const std::invalid_argument & e)
 	{
@@ -650,8 +656,12 @@ void CModInfo::loadLocalData(const JsonNode & data)
 	}
 	
 	//check compatibility
-	enabled &= vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin);
-	enabled &= vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion());
+	bool wasEnabled = enabled;
+	enabled = enabled && (vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin));
+	enabled = enabled && (vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion()));
+
+	if(wasEnabled && !enabled)
+		logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", name);
 
 	if (enabled)
 		validation = validated ? PASSED : PENDING;
@@ -1037,7 +1047,9 @@ void CModHandler::load()
 	for(const TModID & modName : activeMods)
 		content->load(allMods[modName]);
 
+#if SCRIPTING_ENABLED
 	VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load
+#endif
 
 	content->loadCustom();
 

+ 21 - 40
lib/CModHandler.h

@@ -212,7 +212,8 @@ public:
 	/// version of the mod
 	Version version;
 	
-	///The  vcmi versions compatible with the mod
+	/// vcmi versions compatible with the mod
+
 	Version vcmiCompatibleMin, vcmiCompatibleMax;
 
 	/// list of mods that should be loaded before this one
@@ -240,19 +241,6 @@ public:
 	static std::string getModDir(std::string name);
 	static std::string getModFile(std::string name);
 
-	//TODO: remove as soon as backward compatilibity for versions earlier 806 is not preserved.
-	template <typename Handler> void serialize(Handler &h, const int ver)
-	{
-		h & identifier;
-		h & description;
-		h & name;
-		h & dependencies;
-		h & conflicts;
-		h & config;
-		h & checksum;
-		h & validation;
-		h & enabled;
-	}
 private:
 	void loadLocalData(const JsonNode & data);
 };
@@ -374,41 +362,34 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		if(version < 806)
+		if(h.saving)
 		{
-			h & allMods; //don't serialize mods
 			h & activeMods;
+			for(const auto & m : activeMods)
+
+				h & allMods[m].version;
 		}
 		else
 		{
-			if(h.saving)
+			std::vector<TModID> newActiveMods;
+			h & newActiveMods;
+			for(auto & m : newActiveMods)
 			{
-				h & activeMods;
-				for(auto & m : activeMods)
-					h & allMods[m].version;
-			}
-			else
-			{
-				std::vector<TModID> newActiveMods;
-				h & newActiveMods;
-				for(auto & m : newActiveMods)
+				if(!allMods.count(m))
+					throw Incompatibility(m + " unkown mod");
+				
+				CModInfo::Version mver;
+				h & mver;
+				if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver))
 				{
-					if(!allMods.count(m))
-						throw Incompatibility(m + " unkown mod");
-					
-					CModInfo::Version mver;
-					h & mver;
-					if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver))
-					{
-						std::string err = allMods[m].name +
-						": version needed " + mver.toString() +
-						"but you have installed " + allMods[m].version.toString();
-						throw Incompatibility(err);
-					}
-					allMods[m].enabled = true;
+					std::string err = allMods[m].name +
+					": version needed " + mver.toString() +
+					"but you have installed " + allMods[m].version.toString();
+					throw Incompatibility(err);
 				}
-				std::swap(activeMods, newActiveMods);
+				allMods[m].enabled = true;
 			}
+			std::swap(activeMods, newActiveMods);
 		}
 				
 		h & settings;

+ 7 - 7
lib/CPathfinder.cpp

@@ -33,17 +33,17 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 	int3 pos;
 	const PlayerColor player = out.hero->tempOwner;
 	const int3 sizes = gs->getMapSize();
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
+	const auto fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
 
 	//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
 	const bool useFlying = options.useFlying;
 	const bool useWaterWalking = options.useWaterWalking;
 
-	for(pos.x=0; pos.x < sizes.x; ++pos.x)
+	for(pos.z=0; pos.z < sizes.z; ++pos.z)
 	{
-		for(pos.y=0; pos.y < sizes.y; ++pos.y)
+		for(pos.x=0; pos.x < sizes.x; ++pos.x)
 		{
-			for(pos.z=0; pos.z < sizes.z; ++pos.z)
+			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
 				const TerrainTile * tile = &gs->map->getTile(pos);
 				if(tile->terType.isWater())
@@ -1304,7 +1304,7 @@ void CGPath::convert(ui8 mode)
 CPathsInfo::CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_)
 	: sizes(Sizes), hero(hero_)
 {
-	nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][ELayer::NUM_LAYERS]);
+	nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]);
 }
 
 CPathsInfo::~CPathsInfo() = default;
@@ -1336,11 +1336,11 @@ bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const
 
 const CGPathNode * CPathsInfo::getNode(const int3 & coord) const
 {
-	auto landNode = &nodes[coord.x][coord.y][coord.z][ELayer::LAND];
+	auto landNode = &nodes[ELayer::LAND][coord.z][coord.x][coord.y];
 	if(landNode->reachable())
 		return landNode;
 	else
-		return &nodes[coord.x][coord.y][coord.z][ELayer::SAIL];
+		return &nodes[ELayer::SAIL][coord.z][coord.x][coord.y];
 }
 
 PathNodeInfo::PathNodeInfo()

+ 2 - 2
lib/CPathfinder.h

@@ -178,7 +178,7 @@ struct DLL_LINKAGE CPathsInfo
 	const CGHeroInstance * hero;
 	int3 hpos;
 	int3 sizes;
-	boost::multi_array<CGPathNode, 4> nodes; //[w][h][level][layer]
+	boost::multi_array<CGPathNode, 4> nodes; //[layer][level][w][h]
 
 	CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_);
 	~CPathsInfo();
@@ -189,7 +189,7 @@ struct DLL_LINKAGE CPathsInfo
 	STRONG_INLINE
 	CGPathNode * getNode(const int3 & coord, const ELayer layer)
 	{
-		return &nodes[coord.x][coord.y][coord.z][layer];
+		return &nodes[layer][coord.z][coord.x][coord.y];
 	}
 };
 

+ 1 - 1
lib/CPlayerState.h

@@ -81,7 +81,7 @@ public:
 	TeamID id; //position in gameState::teams
 	std::set<PlayerColor> players; // members of this team
 	//TODO: boost::array, bool if possible
-	std::vector<std::vector<std::vector<ui8> > >  fogOfWarMap; //true - visible, false - hidden
+	std::shared_ptr<boost::multi_array<ui8, 3>> fogOfWarMap; //[z][x][y] true - visible, false - hidden
 
 	TeamState();
 	TeamState(TeamState && other);

+ 2 - 0
lib/CScriptingModule.cpp

@@ -11,6 +11,7 @@
 
 #include "CScriptingModule.h"
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 
@@ -30,3 +31,4 @@ Module::Module()
 Module::~Module() = default;
 
 }
+#endif

+ 2 - 0
lib/CScriptingModule.h

@@ -9,6 +9,7 @@
  */
 #pragma once
 
+#if SCRIPTING_ENABLED
 #include <vcmi/scripting/Service.h>
 
 namespace spells
@@ -45,3 +46,4 @@ public:
 };
 
 }
+#endif

+ 4 - 5
lib/IGameCallback.cpp

@@ -38,14 +38,13 @@
 void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 {
 	std::vector<int> floors;
-	for (int b = 0; b < (gs->map->twoLevel ? 2 : 1); ++b)
+	for(int b = 0; b < gs->map->levels(); ++b)
 	{
 		floors.push_back(b);
 	}
 	const TerrainTile *tinfo;
 	for (auto zd : floors)
 	{
-
 		for (int xd = 0; xd < gs->map->width; xd++)
 		{
 			for (int yd = 0; yd < gs->map->height; yd++)
@@ -80,8 +79,8 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_set<int3, ShashInt3
 				if(distance <= radious)
 				{
 					if(!player
-						|| (mode == 1  && team->fogOfWarMap[xd][yd][pos.z]==0)
-						|| (mode == -1 && team->fogOfWarMap[xd][yd][pos.z]==1)
+						|| (mode == 1  && (*team->fogOfWarMap)[pos.z][xd][yd] == 0)
+						|| (mode == -1 && (*team->fogOfWarMap)[pos.z][xd][yd] == 1)
 					)
 						tiles.insert(int3(xd,yd,pos.z));
 				}
@@ -103,7 +102,7 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3, ShashInt3> &
 	std::vector<int> floors;
 	if(level == -1)
 	{
-		for(int b = 0; b < (gs->map->twoLevel ? 2 : 1); ++b)
+		for(int b = 0; b < gs->map->levels(); ++b)
 		{
 			floors.push_back(b);
 		}

+ 5 - 2
lib/IGameCallback.h

@@ -27,12 +27,13 @@ class CStackBasicDescriptor;
 class CGCreature;
 struct ShashInt3;
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
-	class Context;
 	class Pool;
-	class Script;
 }
+#endif
+
 
 class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback
 {
@@ -132,7 +133,9 @@ class DLL_LINKAGE IGameCallback : public CPrivilegedInfoCallback, public IGameEv
 public:
 	virtual ~IGameCallback(){};
 
+#if SCRIPTING_ENABLED
 	virtual scripting::Pool * getGlobalContextPool() const = 0;
+#endif
 
 	//get info
 	virtual bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero);

+ 5 - 3
lib/NetPacksLib.cpp

@@ -177,8 +177,9 @@ DLL_LINKAGE void SetMovePoints::applyGs(CGameState *gs)
 DLL_LINKAGE void FoWChange::applyGs(CGameState *gs)
 {
 	TeamState * team = gs->getPlayerTeam(player);
+	auto fogOfWarMap = team->fogOfWarMap;
 	for(int3 t : tiles)
-		team->fogOfWarMap[t.x][t.y][t.z] = mode;
+		(*fogOfWarMap)[t.z][t.x][t.y] = mode;
 	if (mode == 0) //do not hide too much
 	{
 		std::unordered_set<int3, ShashInt3> tilesRevealed;
@@ -200,7 +201,7 @@ DLL_LINKAGE void FoWChange::applyGs(CGameState *gs)
 			}
 		}
 		for(int3 t : tilesRevealed) //probably not the most optimal solution ever
-			team->fogOfWarMap[t.x][t.y][t.z] = 1;
+			(*fogOfWarMap)[t.z][t.x][t.y] = 1;
 	}
 }
 
@@ -561,8 +562,9 @@ void TryMoveHero::applyGs(CGameState *gs)
 		gs->map->addBlockVisTiles(h);
 	}
 
+	auto fogOfWarMap = gs->getPlayerTeam(h->getOwner())->fogOfWarMap;
 	for(int3 t : fowRevealed)
-		gs->getPlayerTeam(h->getOwner())->fogOfWarMap[t.x][t.y][t.z] = 1;
+		(*fogOfWarMap)[t.z][t.x][t.y] = 1;
 }
 
 DLL_LINKAGE void NewStructures::applyGs(CGameState *gs)

+ 3 - 3
lib/PathfinderUtil.h

@@ -14,13 +14,13 @@
 
 namespace PathfinderUtil
 {
-	using FoW = std::vector<std::vector<std::vector<ui8> > >;
+	using FoW = std::shared_ptr<const boost::multi_array<ui8, 3>>;
 	using ELayer = EPathfindingLayer;
 
 	template<EPathfindingLayer::EEPathfindingLayer layer>
-	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const FoW & fow, const PlayerColor player, const CGameState * gs)
+	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, FoW fow, const PlayerColor player, const CGameState * gs)
 	{
-		if(!fow[pos.x][pos.y][pos.z])
+		if(!(*fow)[pos.z][pos.x][pos.y])
 			return CGPathNode::BLOCKED;
 
 		switch(layer)

+ 2 - 0
lib/ScriptHandler.cpp

@@ -11,6 +11,7 @@
 
 #include "ScriptHandler.h"
 
+#if SCRIPTING_ENABLED
 #include <vcmi/Services.h>
 #include <vcmi/Environment.h>
 
@@ -311,3 +312,4 @@ void ScriptHandler::saveState(JsonNode & state)
 
 
 }
+#endif

+ 2 - 0
lib/ScriptHandler.h

@@ -10,6 +10,7 @@
 
 #pragma once
 
+#if SCRIPTING_ENABLED
 #include <vcmi/scripting/Service.h>
 #include "IHandlerBase.h"
 #include "JsonNode.h"
@@ -131,3 +132,4 @@ private:
 };
 
 }
+#endif

+ 31 - 43
lib/VCMIDirs.cpp

@@ -13,6 +13,8 @@
 
 namespace bfs = boost::filesystem;
 
+bfs::path IVCMIDirs::userLogsPath() const { return userCachePath(); }
+
 bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
 
 bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std::string &baseLibName) const
@@ -20,12 +22,32 @@ bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std
 	return libraryPath() / desiredFolder / libraryName(baseLibName);
 }
 
+std::string IVCMIDirs::genHelpString() const
+{
+	std::vector<std::string> tempVec;
+	for (const bfs::path & path : dataPaths())
+		tempVec.push_back(path.string());
+	const auto gdStringA = boost::algorithm::join(tempVec, ":");
+
+	return
+		"  game data:   " + gdStringA + "\n"
+		"  libraries:   " + libraryPath().string() + "\n"
+		"  server:      " + serverPath().string() + "\n"
+		"\n"
+		"  user data:   " + userDataPath().string() + "\n"
+		"  user cache:  " + userCachePath().string() + "\n"
+		"  user config: " + userConfigPath().string() + "\n"
+		"  user logs:   " + userLogsPath().string() + "\n"
+		"  user saves:  " + userSavePath().string() + "\n"; // Should end without new-line?
+}
+
 void IVCMIDirs::init()
 {
 	// TODO: Log errors
 	bfs::create_directories(userDataPath());
 	bfs::create_directories(userCachePath());
 	bfs::create_directories(userConfigPath());
+	bfs::create_directories(userLogsPath());
 	bfs::create_directories(userSavePath());
 }
 
@@ -134,8 +156,6 @@ class VCMIDirsWIN32 final : public IVCMIDirs
 
 		std::string libraryName(const std::string& basename) const override;
 
-		std::string genHelpString() const override;
-
 		void init() override;
 	protected:
 		boost::filesystem::path oldUserDataPath() const;
@@ -321,26 +341,6 @@ bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server
 bfs::path VCMIDirsWIN32::libraryPath() const { return "."; }
 bfs::path VCMIDirsWIN32::binaryPath() const { return ".";  }
 
-std::string VCMIDirsWIN32::genHelpString() const
-{
-
-	std::vector<std::string> tempVec;
-	for (const bfs::path& path : dataPaths())
-		tempVec.push_back(path.string());
-	std::string gdStringA = boost::algorithm::join(tempVec, ";");
-
-
-	return
-		"  game data:   " + gdStringA + "\n"
-		"  libraries:   " + libraryPath().string() + "\n"
-		"  server:      " + serverPath().string() + "\n"
-		"\n"
-		"  user data:   " + userDataPath().string() + "\n"
-		"  user cache:  " + userCachePath().string() + "\n"
-		"  user config: " + userConfigPath().string() + "\n"
-		"  user saves:  " + userSavePath().string() + "\n"; // Should end without new-line?
-}
-
 std::string VCMIDirsWIN32::libraryName(const std::string& basename) const { return basename + ".dll"; }
 #elif defined(VCMI_UNIX)
 class IVCMIDirsUNIX : public IVCMIDirs
@@ -349,8 +349,6 @@ class IVCMIDirsUNIX : public IVCMIDirs
 		boost::filesystem::path clientPath() const override;
 		boost::filesystem::path serverPath() const override;
 
-		std::string genHelpString() const override;
-
 		bool developmentMode() const;
 };
 
@@ -363,25 +361,6 @@ bool IVCMIDirsUNIX::developmentMode() const
 bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; }
 bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; }
 
-std::string IVCMIDirsUNIX::genHelpString() const
-{
-	std::vector<std::string> tempVec;
-	for (const bfs::path& path : dataPaths())
-		tempVec.push_back(path.string());
-	std::string gdStringA = boost::algorithm::join(tempVec, ":");
-
-
-	return
-		"  game data:   " + gdStringA + "\n"
-		"  libraries:   " + libraryPath().string() + "\n"
-		"  server:      " + serverPath().string() + "\n"
-		"\n"
-		"  user data:   " + userDataPath().string() + "\n"
-		"  user cache:  " + userCachePath().string() + "\n"
-		"  user config: " + userConfigPath().string() + "\n"
-		"  user saves:  " + userSavePath().string() + "\n"; // Should end without new-line?
-}
-
 #ifdef VCMI_APPLE
 class VCMIDirsOSX final : public IVCMIDirsUNIX
 {
@@ -389,6 +368,7 @@ class VCMIDirsOSX final : public IVCMIDirsUNIX
 		boost::filesystem::path userDataPath() const override;
 		boost::filesystem::path userCachePath() const override;
 		boost::filesystem::path userConfigPath() const override;
+		boost::filesystem::path userLogsPath() const override;
 
 		std::vector<boost::filesystem::path> dataPaths() const override;
 
@@ -458,6 +438,14 @@ bfs::path VCMIDirsOSX::userDataPath() const
 bfs::path VCMIDirsOSX::userCachePath() const { return userDataPath(); }
 bfs::path VCMIDirsOSX::userConfigPath() const { return userDataPath() / "config"; }
 
+bfs::path VCMIDirsOSX::userLogsPath() const
+{
+	// TODO: use proper objc code from Foundation framework
+	if(const auto homeDir = std::getenv("HOME"))
+		return bfs::path{homeDir} / "Library" / "Logs" / "vcmi";
+	return IVCMIDirsUNIX::userLogsPath();
+}
+
 std::vector<bfs::path> VCMIDirsOSX::dataPaths() const
 {
 	std::vector<bfs::path> ret;

+ 4 - 1
lib/VCMIDirs.h

@@ -21,6 +21,9 @@ public:
 	// Path to writeable directory with user configs
 	virtual boost::filesystem::path userConfigPath() const = 0;
 
+	// Path to writeable directory to store log files
+	virtual boost::filesystem::path userLogsPath() const;
+
 	// Path to saved games
 	virtual boost::filesystem::path userSavePath() const;
 
@@ -49,7 +52,7 @@ public:
 	// virtual std::string libraryName(const char* basename) const = 0; ?
 	// virtual std::string libraryName(std::string&& basename) const = 0;?
 
-	virtual std::string genHelpString() const = 0;
+	virtual std::string genHelpString() const;
 
 	// Creates not existed, but required directories.
 	// Updates directories what change name/path between versions.

+ 10 - 0
lib/VCMI_Lib.cpp

@@ -82,10 +82,12 @@ const HeroTypeService * LibClasses::heroTypes() const
 	return heroh;
 }
 
+#if SCRIPTING_ENABLED
 const scripting::Service * LibClasses::scripts() const
 {
 	return scriptHandler;
 }
+#endif
 
 const spells::Service * LibClasses::spells() const
 {
@@ -215,7 +217,9 @@ void LibClasses::init(bool onlyEssential)
 
 	createHandler(tplh, "Template", pomtime); //templates need already resolved identifiers (refactor?)
 
+#if SCRIPTING_ENABLED
 	createHandler(scriptHandler, "Script", pomtime);
+#endif
 
 	createHandler(battlefieldsHandler, "Battlefields", pomtime);
 	
@@ -246,7 +250,9 @@ void LibClasses::clear()
 	delete bth;
 	delete tplh;
 	delete terviewh;
+#if SCRIPTING_ENABLED
 	delete scriptHandler;
+#endif
 	delete battlefieldsHandler;
 	makeNull();
 }
@@ -266,7 +272,9 @@ void LibClasses::makeNull()
 	bth = nullptr;
 	tplh = nullptr;
 	terviewh = nullptr;
+#if SCRIPTING_ENABLED
 	scriptHandler = nullptr;
+#endif
 	battlefieldsHandler = nullptr;
 }
 
@@ -287,10 +295,12 @@ void LibClasses::callWhenDeserializing()
 	//modh->loadConfigFromFile ("defaultMods"); //TODO: remember last saved config
 }
 
+#if SCRIPTING_ENABLED
 void LibClasses::scriptsLoaded()
 {
 	scriptHandler->performRegistration(this);
 }
+#endif
 
 LibClasses::~LibClasses()
 {

+ 11 - 0
lib/VCMI_Lib.h

@@ -32,10 +32,13 @@ class CTerrainViewPatternConfig;
 class CRmgTemplateStorage;
 class IHandlerBase;
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class ScriptHandler;
 }
+#endif
+
 
 /// Loads and constructs several handlers
 class DLL_LINKAGE LibClasses : public Services
@@ -55,7 +58,9 @@ public:
 	const FactionService * factions() const override;
 	const HeroClassService * heroClasses() const override;
 	const HeroTypeService * heroTypes() const override;
+#if SCRIPTING_ENABLED
 	const scripting::Service * scripts() const override;
+#endif
 	const spells::Service * spells() const override;
 	const SkillService * skills() const override;
 	const BattleFieldService * battlefields() const override;
@@ -82,7 +87,9 @@ public:
 	CRmgTemplateStorage * tplh;
 	BattleFieldHandler * battlefieldsHandler;
 	ObstacleHandler * obstacleHandler;
+#if SCRIPTING_ENABLED
 	scripting::ScriptHandler * scriptHandler;
+#endif
 
 	LibClasses(); //c-tor, loads .lods and NULLs handlers
 	~LibClasses();
@@ -92,15 +99,19 @@ public:
 
 	void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
 
+#if SCRIPTING_ENABLED
 	void scriptsLoaded();
+#endif
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+#if SCRIPTING_ENABLED
 		h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on
 		if(!h.saving)
 		{
 			scriptsLoaded();
 		}
+#endif
 
 		h & heroh;
 		h & arth;

+ 11 - 4
lib/battle/BattleInfo.cpp

@@ -522,10 +522,15 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 	return const_cast<CStack *>(battleGetStackByID(stackID, onlyAlive));
 }
 
-BattleInfo::BattleInfo()
-	: round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1),
-	battlefieldType(BattleField::NONE), terrainType(),
-	tacticsSide(0), tacticDistance(0)
+BattleInfo::BattleInfo():
+	round(-1),
+	activeStack(-1),
+	town(nullptr),
+	tile(-1,-1,-1),
+	battlefieldType(BattleField::NONE),
+	terrainType(),
+	tacticsSide(0),
+	tacticDistance(0)
 {
 	setBattle(this);
 	setNodeType(BATTLE);
@@ -960,12 +965,14 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
 	return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
 }
 
+#if SCRIPTING_ENABLED
 scripting::Pool * BattleInfo::getContextPool() const
 {
 	//this is real battle, use global scripting context pool
 	//TODO: make this line not ugly
 	return IObjectInterface::cb->getGlobalContextPool();
 }
+#endif
 
 bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b)
 {

+ 2 - 0
lib/battle/BattleInfo.h

@@ -144,7 +144,9 @@ public:
 	ui8 whatSide(PlayerColor player) const;
 
 protected:
+#if SCRIPTING_ENABLED
 	scripting::Pool * getContextPool() const override;
+#endif
 };
 
 

+ 0 - 7
lib/battle/CBattleInfoCallback.h

@@ -23,13 +23,6 @@ struct CObstacleInstance;
 class IBonusBearer;
 class CRandomGenerator;
 
-namespace scripting
-{
-	class Context;
-	class Pool;
-	class Script;
-}
-
 namespace spells
 {
 	class Caster;

+ 4 - 0
lib/battle/IBattleInfoCallback.h

@@ -24,15 +24,19 @@ namespace battle
 	using UnitFilter = std::function<bool(const Unit *)>;
 }
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class Pool;
 }
+#endif
 
 class DLL_LINKAGE IBattleInfoCallback
 {
 public:
+#if SCRIPTING_ENABLED
 	virtual scripting::Pool * getContextPool() const = 0;
+#endif
 
 	virtual Terrain battleTerrainType() const = 0;
 	virtual BattleField battleGetBattlefieldType() const = 0;

+ 3 - 3
lib/mapObjects/ObjectTemplate.cpp

@@ -322,15 +322,15 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 
 	size_t height = mask.size();
 	size_t width  = 0;
-	for (auto & line : mask)
+	for(auto & line : mask)
 		vstd::amax(width, line.String().size());
 
 	setSize((ui32)width, (ui32)height);
 
-	for (size_t i=0; i<mask.size(); i++)
+	for(size_t i = 0; i < mask.size(); i++)
 	{
 		const std::string & line = mask[i].String();
-		for (size_t j=0; j < line.size(); j++)
+		for(size_t j = 0; j < line.size(); j++)
 			usedTiles[mask.size() - 1 - i][line.size() - 1 - j] = charToTile(line[j]);
 	}
 

+ 7 - 3
lib/mapping/CDrawRoadsOperation.cpp

@@ -149,19 +149,23 @@ static bool ruleIsAny(const std::string & rule)
 
 ///CDrawLinesOperation
 CDrawLinesOperation::CDrawLinesOperation(CMap * map, const CTerrainSelection & terrainSel, CRandomGenerator * gen):
-	CMapOperation(map), terrainSel(terrainSel), gen(gen)
+	CMapOperation(map),
+	terrainSel(terrainSel),
+	gen(gen)
 {
 }
 
 ///CDrawRoadsOperation
 CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
-	CDrawLinesOperation(map, terrainSel, gen), roadType(roadType)
+	CDrawLinesOperation(map, terrainSel,gen),
+	roadType(roadType)
 {
 }
 
 ///CDrawRiversOperation
 CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & riverType, CRandomGenerator * gen):
-	CDrawLinesOperation(map, terrainSel, gen), riverType(riverType)
+	CDrawLinesOperation(map, terrainSel, gen),
+	riverType(riverType)
 {
 }
 

+ 41 - 35
lib/mapping/CMap.cpp

@@ -228,7 +228,11 @@ CMapHeader::CMapHeader() : version(EMapFormat::SOD), height(72), width(72),
 
 CMapHeader::~CMapHeader()
 {
+}
 
+ui8 CMapHeader::levels() const
+{
+	return (twoLevel ? 2 : 1);
 }
 
 CMap::CMap()
@@ -246,15 +250,15 @@ CMap::~CMap()
 {
 	if(terrain)
 	{
-		for (int i=0; i<width; i++)
+		for(int z = 0; z < levels(); z++)
 		{
-			for(int j=0; j<height; j++)
+			for(int x = 0; x < width; x++)
 			{
-				delete [] terrain[i][j];
-				delete [] guardingCreaturePositions[i][j];
+				delete[] terrain[z][x];
+				delete[] guardingCreaturePositions[z][x];
 			}
-			delete [] terrain[i];
-			delete [] guardingCreaturePositions[i];
+			delete[] terrain[z];
+			delete[] guardingCreaturePositions[z];
 		}
 		delete [] terrain;
 		delete [] guardingCreaturePositions;
@@ -271,16 +275,16 @@ CMap::~CMap()
 
 void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 {
-	for(int fx=0; fx<obj->getWidth(); ++fx)
+	const int zVal = obj->pos.z;
+	for(int fx = 0; fx < obj->getWidth(); ++fx)
 	{
-		for(int fy=0; fy<obj->getHeight(); ++fy)
+		int xVal = obj->pos.x - fx;
+		for(int fy = 0; fy < obj->getHeight(); ++fy)
 		{
-			int xVal = obj->pos.x - fx;
 			int yVal = obj->pos.y - fy;
-			int zVal = obj->pos.z;
-			if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
+			if(xVal>=0 && xVal < width && yVal>=0 && yVal < height)
 			{
-				TerrainTile & curt = terrain[xVal][yVal][zVal];
+				TerrainTile & curt = terrain[zVal][xVal][yVal];
 				if(total || obj->visitableAt(xVal, yVal))
 				{
 					curt.visitableObjects -= obj;
@@ -298,22 +302,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 
 void CMap::addBlockVisTiles(CGObjectInstance * obj)
 {
-	for(int fx=0; fx<obj->getWidth(); ++fx)
+	const int zVal = obj->pos.z;
+	for(int fx = 0; fx < obj->getWidth(); ++fx)
 	{
-		for(int fy=0; fy<obj->getHeight(); ++fy)
+		int xVal = obj->pos.x - fx;
+		for(int fy = 0; fy < obj->getHeight(); ++fy)
 		{
-			int xVal = obj->pos.x - fx;
 			int yVal = obj->pos.y - fy;
-			int zVal = obj->pos.z;
-			if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
+			if(xVal>=0 && xVal < width && yVal >= 0 && yVal < height)
 			{
-				TerrainTile & curt = terrain[xVal][yVal][zVal];
-				if( obj->visitableAt(xVal, yVal))
+				TerrainTile & curt = terrain[zVal][xVal][yVal];
+				if(obj->visitableAt(xVal, yVal))
 				{
 					curt.visitableObjects.push_back(obj);
 					curt.visitable = true;
 				}
-				if( obj->blockingAt(xVal, yVal))
+				if(obj->blockingAt(xVal, yVal))
 				{
 					curt.blockingObjects.push_back(obj);
 					curt.blocked = true;
@@ -326,12 +330,14 @@ void CMap::addBlockVisTiles(CGObjectInstance * obj)
 void CMap::calculateGuardingGreaturePositions()
 {
 	int levels = twoLevel ? 2 : 1;
-	for (int i=0; i<width; i++)
+	for(int z = 0; z < levels; z++)
 	{
-		for(int j=0; j<height; j++)
+		for(int x = 0; x < width; x++)
 		{
-			for (int k = 0; k < levels; k++)
-				guardingCreaturePositions[i][j][k] = guardingCreaturePosition(int3(i,j,k));
+			for(int y = 0; y < height; y++)
+			{
+				guardingCreaturePositions[z][x][y] = guardingCreaturePosition(int3(x, y, z));
+			}
 		}
 	}
 }
@@ -389,13 +395,13 @@ bool CMap::isInTheMap(const int3 & pos) const
 TerrainTile & CMap::getTile(const int3 & tile)
 {
 	assert(isInTheMap(tile));
-	return terrain[tile.x][tile.y][tile.z];
+	return terrain[tile.z][tile.x][tile.y];
 }
 
 const TerrainTile & CMap::getTile(const int3 & tile) const
 {
 	assert(isInTheMap(tile));
-	return terrain[tile.x][tile.y][tile.z];
+	return terrain[tile.z][tile.x][tile.y];
 }
 
 bool CMap::isWaterTile(const int3 &pos) const
@@ -679,17 +685,17 @@ void CMap::removeObject(CGObjectInstance * obj)
 
 void CMap::initTerrain()
 {
-	int level = twoLevel ? 2 : 1;
-	terrain = new TerrainTile**[width];
-	guardingCreaturePositions = new int3**[width];
-	for (int i = 0; i < width; ++i)
+	int level = levels();
+	terrain = new TerrainTile**[level];
+	guardingCreaturePositions = new int3**[level];
+	for(int z = 0; z < level; ++z)
 	{
-		terrain[i] = new TerrainTile*[height];
-		guardingCreaturePositions[i] = new int3*[height];
-		for (int j = 0; j < height; ++j)
+		terrain[z] = new TerrainTile*[width];
+		guardingCreaturePositions[z] = new int3*[width];
+		for(int x = 0; x < width; ++x)
 		{
-			terrain[i][j] = new TerrainTile[level];
-			guardingCreaturePositions[i][j] = new int3[level];
+			terrain[z][x] = new TerrainTile[height];
+			guardingCreaturePositions[z][x] = new int3[height];
 		}
 	}
 }

+ 22 - 19
lib/mapping/CMap.h

@@ -286,6 +286,8 @@ public:
 	CMapHeader();
 	virtual ~CMapHeader();
 
+	ui8 levels() const;
+
 	EMapFormat::EMapFormat version; /// The default value is EMapFormat::SOD.
 	si32 height; /// The default value is 72.
 	si32 width; /// The default value is 72.
@@ -432,18 +434,18 @@ public:
 		h & questIdentifierToId;
 
 		//TODO: viccondetails
-		int level = twoLevel ? 2 : 1;
+		const int level = levels();
 		if(h.saving)
 		{
 			// Save terrain
-			for(int i = 0; i < width ; ++i)
+			for(int z = 0; z < level; ++z)
 			{
-				for(int j = 0; j < height ; ++j)
+				for(int x = 0; x < width; ++x)
 				{
-					for(int k = 0; k < level; ++k)
+					for(int y = 0; y < height; ++y)
 					{
-						h & terrain[i][j][k];
-						h & guardingCreaturePositions[i][j][k];
+						h & terrain[z][x][y];
+						h & guardingCreaturePositions[z][x][y];
 					}
 				}
 			}
@@ -451,26 +453,27 @@ public:
 		else
 		{
 			// Load terrain
-			terrain = new TerrainTile**[width];
-			guardingCreaturePositions = new int3**[width];
-			for(int i = 0; i < width; ++i)
+			terrain = new TerrainTile**[level];
+			guardingCreaturePositions = new int3**[level];
+			for(int z = 0; z < level; ++z)
 			{
-				terrain[i] = new TerrainTile*[height];
-				guardingCreaturePositions[i] = new int3*[height];
-				for(int j = 0; j < height; ++j)
+				terrain[z] = new TerrainTile*[width];
+				guardingCreaturePositions[z] = new int3*[width];
+				for(int x = 0; x < width; ++x)
 				{
-					terrain[i][j] = new TerrainTile[level];
-					guardingCreaturePositions[i][j] = new int3[level];
+					terrain[z][x] = new TerrainTile[height];
+					guardingCreaturePositions[z][x] = new int3[height];
 				}
 			}
-			for(int i = 0; i < width ; ++i)
+			for(int z = 0; z < level; ++z)
 			{
-				for(int j = 0; j < height ; ++j)
+				for(int x = 0; x < width; ++x)
 				{
-					for(int k = 0; k < level; ++k)
+					for(int y = 0; y < height; ++y)
 					{
-						h & terrain[i][j][k];
-						h & guardingCreaturePositions[i][j][k];
+
+						h & terrain[z][x][y];
+						h & guardingCreaturePositions[z][x][y];
 					}
 				}
 			}

+ 1 - 1
lib/mapping/CMapDefines.h

@@ -84,7 +84,7 @@ struct DLL_LINKAGE TerrainTile
 	ui8 terView;
 	std::string riverType;
 	ui8 riverDir;
-	std::string roadType;
+	std::string roadType; //TODO: switch to ui8
 	ui8 roadDir;
 	/// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road);
 	///	7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect

+ 7 - 5
lib/mapping/MapFormatH3M.cpp

@@ -923,18 +923,20 @@ void CMapLoaderH3M::readTerrain()
 	map->initTerrain();
 
 	// Read terrain
-	for(int a = 0; a < 2; ++a)
+	int3 pos;
+	for(pos.z = 0; pos.z < 2; ++pos.z)
 	{
-		if(a == 1 && !map->twoLevel)
+		if(pos.z == 1 && !map->twoLevel)
 		{
 			break;
 		}
 
-		for(int c = 0; c < map->width; c++)
+		//OH3 format is [z][y][x]
+		for(pos.y = 0; pos.y < map->height; pos.y++)
 		{
-			for(int z = 0; z < map->height; z++)
+			for(pos.x = 0; pos.x < map->width; pos.x++)
 			{
-				auto & tile = map->getTile(int3(z, c, a));
+				auto & tile = map->getTile(pos);
 				tile.terType = Terrain::createTerrainTypeH3M(reader.readUInt8());
 				tile.terView = reader.readUInt8();
 				tile.riverType = RIVER_NAMES[reader.readUInt8()];

+ 2 - 2
lib/rmg/CMapGenerator.cpp

@@ -162,8 +162,8 @@ std::string CMapGenerator::getMapDescription() const
 
     std::stringstream ss;
     ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
-        ", levels %s, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() %
-		randomSeed % map->map().width % map->map().height % (map->map().twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
+        ", levels %d, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() %
+		randomSeed % map->map().width % map->map().height % map->map().levels() % static_cast<int>(mapGenOptions.getPlayerCount()) %
 		static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
 		monsterStrengthStr[monsterStrengthIndex]);
 

+ 11 - 12
lib/rmg/CZonePlacer.cpp

@@ -498,24 +498,24 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
 		zone->setPos(int3(total.x / size, total.y / size, total.z / size));
 	};
 
-	int levels = map.map().twoLevel ? 2 : 1;
+	int levels = map.map().levels();
 
 	/*
 	1. Create Voronoi diagram
 	2. find current center of mass for each zone. Move zone to that center to balance zones sizes
 	*/
 
-	for (int i = 0; i<width; i++)
+	int3 pos;
+	for(pos.z = 0; pos.z < levels; pos.z++)
 	{
-		for (int j = 0; j<height; j++)
+		for(pos.x = 0; pos.x < width; pos.x++)
 		{
-			for (int k = 0; k < levels; k++)
+			for(pos.y = 0; pos.y < height; pos.y++)
 			{
 				distances.clear();
-				int3 pos(i, j, k);
-				for (auto zone : zones)
+				for(auto zone : zones)
 				{
-					if (zone.second->getPos().z == k)
+					if (zone.second->getPos().z == pos.z)
 						distances.push_back(std::make_pair(zone.second, (float)pos.dist2dSQ(zone.second->getPos())));
 					else
 						distances.push_back(std::make_pair(zone.second, std::numeric_limits<float>::max()));
@@ -533,17 +533,16 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
 	for (auto zone : zones)
 		zone.second->clearTiles(); //now populate them again
 
-	for (int i=0; i<width; i++)
+	for (pos.z = 0; pos.z < levels; pos.z++)
 	{
-		for(int j=0; j<height; j++)
+		for (pos.x = 0; pos.x < width; pos.x++)
 		{
-			for (int k = 0; k < levels; k++)
+			for (pos.y = 0; pos.y < height; pos.y++)
 			{
 				distances.clear();
-				int3 pos(i, j, k);
 				for (auto zone : zones)
 				{
-					if (zone.second->getPos().z == k)
+					if (zone.second->getPos().z == pos.z)
 						distances.push_back (std::make_pair(zone.second, metric(pos, zone.second->getPos())));
 					else
 						distances.push_back (std::make_pair(zone.second, std::numeric_limits<float>::max()));

+ 0 - 42
lib/rmg/Functions.cpp

@@ -175,46 +175,4 @@ void createObstaclesCommon2(RmgMap & map, CRandomGenerator & generator)
 			}
 		}
 	}
-	
-	//tighten obstacles to improve visuals
-	
-	/*for (int i = 0; i < 3; ++i)
-	{
-		int blockedTiles = 0;
-		int freeTiles = 0;
-		
-		for (int z = 0; z < (map.map().twoLevel ? 2 : 1); z++)
-		{
-			for (int x = 0; x < map.map().width; x++)
-			{
-				for (int y = 0; y < map.map().height; y++)
-				{
-					int3 tile(x, y, z);
-					if (!map.isPossible(tile)) //only possible tiles can change
-						continue;
-					
-					int blockedNeighbours = 0;
-					int freeNeighbours = 0;
-					map.foreach_neighbour(tile, [&map, &blockedNeighbours, &freeNeighbours](int3 &pos)
-					{
-						if (map.isBlocked(pos))
-							blockedNeighbours++;
-						if (map.isFree(pos))
-							freeNeighbours++;
-					});
-					if (blockedNeighbours > 4)
-					{
-						map.setOccupied(tile, ETileType::BLOCKED);
-						blockedTiles++;
-					}
-					else if (freeNeighbours > 4)
-					{
-						map.setOccupied(tile, ETileType::FREE);
-						freeTiles++;
-					}
-				}
-			}
-		}
-		logGlobal->trace("Set %d tiles to BLOCKED and %d tiles to FREE", blockedTiles, freeTiles);
-	}*/
 }

+ 9 - 8
lib/rmg/ObjectManager.h

@@ -39,29 +39,30 @@ public:
 	
 public:
 	MODIFICATOR(ObjectManager);
-	
+
+
 	void process() override;
 	void init() override;
-	
+
 	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
 	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
 	void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
-		
+
 	bool createRequiredObjects();
-	
+
 	int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const;
 	int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, OptimizeType optimizer) const;
-	
+
 	rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const;
 	rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const;
-	
+
 	CGCreature * chooseGuard(si32 strength, bool zoneGuard = false);
 	bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false);
 	void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
-	
+
 	void updateDistances(const rmg::Object & obj);
 	void createDistancesPriorityQueue();
-	
+
 	const rmg::Area & getVisitableArea() const;
 
 	std::vector<CGObjectInstance*> getMines() const;

+ 5 - 12
lib/rmg/ObstaclePlacer.cpp

@@ -171,10 +171,7 @@ void ObstacleProxy::finalInsertion(CMapEditManager * manager, std::set<CGObjectI
 
 std::pair<bool, bool> ObstacleProxy::verifyCoverage(const int3 & t) const
 {
-	std::pair<bool, bool> result(false, false);
-	if(blockedArea.contains(t))
-		result.first = true;
-	return result;
+	return {blockedArea.contains(t), false};
 }
 
 void ObstacleProxy::placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances)
@@ -230,12 +227,7 @@ void ObstaclePlacer::init()
 
 std::pair<bool, bool> ObstaclePlacer::verifyCoverage(const int3 & t) const
 {
-	std::pair<bool, bool> result(false, false);
-	if(map.shouldBeBlocked(t))
-		result.first = true;
-	if(zone.areaPossible().contains(t))
-		result.second = true;
-	return result;
+	return {map.shouldBeBlocked(t), zone.areaPossible().contains(t)};
 }
 
 void ObstaclePlacer::placeObject(rmg::Object & object, std::set<CGObjectInstance*> &)
@@ -248,9 +240,10 @@ void ObstaclePlacer::postProcess(const rmg::Object & object)
 	//river processing
 	if(riverManager)
 	{
-		if(object.instances().front()->object().typeName == "mountain")
+		const auto objTypeName = object.instances().front()->object().typeName;
+		if(objTypeName == "mountain")
 			riverManager->riverSource().unite(object.getArea());
-		if(object.instances().front()->object().typeName == "lake")
+		else if(objTypeName == "lake")
 			riverManager->riverSink().unite(object.getArea());
 	}
 }

+ 3 - 3
lib/rmg/RmgMap.cpp

@@ -73,8 +73,8 @@ void RmgMap::initTiles(CMapGenerator & generator)
 {
 	mapInstance->initTerrain();
 	
-	tiles.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
-	zoneColouring.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
+	tiles.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->levels()]);
+	zoneColouring.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->levels()]);
 	
 	//init native town count with 0
 	for (auto faction : VLC->townh->getAllowedFactions())
@@ -302,7 +302,7 @@ void RmgMap::dump(bool zoneId) const
 {
 	static int id = 0;
 	std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++));
-	int levels = mapInstance->twoLevel ? 2 : 1;
+	int levels = mapInstance->levels();
 	int width =  mapInstance->width;
 	int height = mapInstance->height;
 	for (int k = 0; k < levels; k++)

+ 4 - 4
lib/rmg/Zone.cpp

@@ -369,16 +369,16 @@ void Modificator::dump()
 {
 	std::ofstream out(boost::to_string(boost::format("seed_%d_modzone_%d_%s.txt") % generator.getRandomSeed() % zone.getId() % getName()));
 	auto & mapInstance = map.map();
-	int levels = mapInstance.twoLevel ? 2 : 1;
+	int levels = mapInstance.levels();
 	int width =  mapInstance.width;
 	int height = mapInstance.height;
-	for (int k = 0; k < levels; k++)
+	for(int z = 0; z < levels; z++)
 	{
 		for(int j=0; j<height; j++)
 		{
-			for (int i=0; i<width; i++)
+			for(int i=0; i<width; i++)
 			{
-				out << dump(int3(i, j, k));
+				out << dump(int3(i, j, z));
 			}
 			out << std::endl;
 		}

+ 14 - 0
lib/serializer/BinaryDeserializer.h

@@ -560,6 +560,20 @@ public:
 			data = boost::optional<T>();
 		}
 	}
+
+	template <typename T>
+	void load(boost::multi_array<T, 3> & data)
+	{
+		ui32 length = readAndCheckLength();
+		ui32 x, y, z;
+		load(x);
+		load(y);
+		load(z);
+		data.resize(boost::extents[x][y][z]);
+		assert(length == data.num_elements()); //x*y*z should be equal to number of elements
+		for(ui32 i = 0; i < length; i++)
+			load(data.data()[i]);
+	}
 };
 
 class DLL_LINKAGE CLoadFile : public IBinaryReader

+ 12 - 0
lib/serializer/BinarySerializer.h

@@ -350,6 +350,18 @@ public:
 			save((ui8)0);
 		}
 	}
+
+	template <typename T>
+	void save(const boost::multi_array<T, 3> &data)
+	{
+		ui32 length = data.num_elements();
+		*this & length;
+		auto shape = data.shape();
+		ui32 x = shape[0], y = shape[1], z = shape[2];
+		*this & x & y & z;
+		for(ui32 i = 0; i < length; i++)
+			save(data.data()[i]);
+	}
 };
 
 class DLL_LINKAGE CSaveFile : public IBinaryWriter

+ 2 - 2
lib/spells/AdventureSpellMechanics.cpp

@@ -571,7 +571,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env
 
 	const auto spellLevel = parameters.caster->getSpellSchoolLevel(owner);
 
-	const auto & fowMap = env->getCb()->getPlayerTeam(parameters.caster->getOwner())->fogOfWarMap;
+	const auto fowMap = env->getCb()->getPlayerTeam(parameters.caster->getOwner())->fogOfWarMap;
 
 	for(const CGObjectInstance * obj : env->getMap()->objects)
 	{
@@ -580,7 +580,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env
 		{
 			ObjectPosInfo posInfo(obj);
 
-			if(fowMap[posInfo.pos.x][posInfo.pos.y][posInfo.pos.z] == 0)
+			if((*fowMap)[posInfo.pos.x][posInfo.pos.y][posInfo.pos.z] == 0)
 				pack.objectPositions.push_back(posInfo);
 		}
 	}

+ 2 - 0
lib/spells/ISpellMechanics.cpp

@@ -720,10 +720,12 @@ const CreatureService * BaseMechanics::creatures() const
 	return VLC->creatures(); //todo: redirect
 }
 
+#if SCRIPTING_ENABLED
 const scripting::Service * BaseMechanics::scripts() const
 {
 	return VLC->scripts(); //todo: redirect
 }
+#endif
 
 const Service * BaseMechanics::spells() const
 {

+ 6 - 0
lib/spells/ISpellMechanics.h

@@ -35,10 +35,12 @@ namespace vstd
 	class RNG;
 }
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class Service;
 }
+#endif
 
 
 ///callback to be provided by server
@@ -238,7 +240,9 @@ public:
 
 	//Global environment facade
 	virtual const CreatureService * creatures() const = 0;
+#if SCRIPTING_ENABLED
 	virtual const scripting::Service * scripts() const = 0;
+#endif
 	virtual const Service * spells() const = 0;
 
 	virtual const IGameInfoCallback * game() const = 0;
@@ -296,7 +300,9 @@ public:
 	std::vector<AimType> getTargetTypes() const override;
 
 	const CreatureService * creatures() const override;
+#if SCRIPTING_ENABLED
 	const scripting::Service * scripts() const override;
+#endif
 	const Service * spells() const override;
 
 	const IGameInfoCallback * game() const override;

+ 23 - 13
server/CGameHandler.cpp

@@ -1652,7 +1652,9 @@ CGameHandler::~CGameHandler()
 void CGameHandler::reinitScripting()
 {
 	serverEventBus = make_unique<events::EventBus>();
+#if SCRIPTING_ENABLED
 	serverScripts.reset(new scripting::PoolImpl(this, spellEnv));
+#endif
 }
 
 void CGameHandler::init(StartInfo *si)
@@ -1993,12 +1995,14 @@ void CGameHandler::newTurn()
 				fw.mode = 1;
 				fw.player = player;
 				// find all hidden tiles
-				const auto & fow = getPlayerTeam(player)->fogOfWarMap;
-				for (size_t i=0; i<fow.size(); i++)
-					for (size_t j=0; j<fow.at(i).size(); j++)
-						for (size_t k=0; k<fow.at(i).at(j).size(); k++)
-							if (!fow.at(i).at(j).at(k))
-								fw.tiles.insert(int3((si32)i,(si32)j,(si32)k));
+				const auto fow = getPlayerTeam(player)->fogOfWarMap;
+
+				auto shape = fow->shape();
+				for(size_t z = 0; z < shape[0]; z++)
+					for(size_t x = 0; x < shape[1]; x++)
+						for(size_t y = 0; y < shape[2]; y++)
+							if (!(*fow)[z][x][y])
+								fw.tiles.insert(int3(x, y, z));
 
 				sendAndApply (&fw);
 			}
@@ -2110,7 +2114,9 @@ void CGameHandler::run(bool resume)
 		logGlobal->info(sbuffer.str());
 	}
 
+#if SCRIPTING_ENABLED
 	services()->scripts()->run(serverScripts);
+#endif
 
 	if(resume)
 		events::GameResumed::defaultExecute(serverEventBus.get());
@@ -7012,13 +7018,15 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 		fc.mode = (cheat == "vcmieagles" ? 1 : 0);
 		fc.player = player;
 		const auto & fowMap = gs->getPlayerTeam(player)->fogOfWarMap;
-		auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->twoLevel ? 2 : 1)];
+		auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->levels())];
 		int lastUnc = 0;
-		for (int i = 0; i < gs->map->width; i++)
-			for (int j = 0; j < gs->map->height; j++)
-				for (int k = 0; k < (gs->map->twoLevel ? 2 : 1); k++)
-					if (!fowMap.at(i).at(j).at(k) || !fc.mode)
-						hlp_tab[lastUnc++] = int3(i, j, k);
+
+		for(int z = 0; z < gs->map->levels(); z++)
+			for(int x = 0; x < gs->map->width; x++)
+				for(int y = 0; y < gs->map->height; y++)
+					if(!(*fowMap)[z][x][y] || !fc.mode)
+						hlp_tab[lastUnc++] = int3(x, y, z);
+
 		fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
 		delete [] hlp_tab;
 		sendAndApply(&fc);
@@ -7315,15 +7323,17 @@ CRandomGenerator & CGameHandler::getRandomGenerator()
 	return CRandomGenerator::getDefault();
 }
 
+#if SCRIPTING_ENABLED
 scripting::Pool * CGameHandler::getGlobalContextPool() const
 {
 	return serverScripts.get();
 }
 
-scripting::Pool *  CGameHandler::getContextPool() const
+scripting::Pool * CGameHandler::getContextPool() const
 {
 	return serverScripts.get();
 }
+#endif
 
 const ObjectInstanceID CGameHandler::putNewObject(Obj ID, int subID, int3 pos)
 {

+ 8 - 0
server/CGameHandler.h

@@ -34,10 +34,12 @@ class IMarket;
 
 class SpellCastEnvironment;
 
+#if SCRIPTING_ENABLED
 namespace scripting
 {
 	class PoolImpl;
 }
+#endif
 
 
 template<typename T> class CApplier;
@@ -274,12 +276,14 @@ public:
 		h & finishingBattle;
 		h & getRandomGenerator();
 
+#if SCRIPTING_ENABLED
 		JsonNode scriptsState;
 		if(h.saving)
 			serverScripts->serializeState(h.saving, scriptsState);
 		h & scriptsState;
 		if(!h.saving)
 			serverScripts->serializeState(h.saving, scriptsState);
+#endif
 	}
 
 	void sendMessageToAll(const std::string &message);
@@ -326,13 +330,17 @@ public:
 
 	CRandomGenerator & getRandomGenerator();
 
+#if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;
 	scripting::Pool * getContextPool() const override;
+#endif
 
 	friend class CVCMIServer;
 private:
 	std::unique_ptr<events::EventBus> serverEventBus;
+#if SCRIPTING_ENABLED
 	std::shared_ptr<scripting::PoolImpl> serverScripts;
+#endif
 
 	void reinitScripting();
 

+ 1 - 1
server/CVCMIServer.cpp

@@ -915,7 +915,7 @@ int main(int argc, char * argv[])
 #endif
 
 	console = new CConsoleHandler();
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Server_log.txt", console);
 	logConfig.configureDefault();
 	logGlobal->info(NAME);