Jelajahi Sumber

BattleHexArray - new container for BattleHexes

MichalZr6 1 tahun lalu
induk
melakukan
a99274d72e
85 mengubah file dengan 5357 tambahan dan 5036 penghapusan
  1. 2 2
      AI/BattleAI/AttackPossibility.cpp
  2. 6 6
      AI/BattleAI/BattleEvaluator.cpp
  3. 1 1
      AI/BattleAI/BattleEvaluator.h
  4. 12 11
      AI/BattleAI/BattleExchangeVariant.cpp
  5. 1 1
      AI/BattleAI/BattleExchangeVariant.h
  6. 49 49
      AI/Nullkiller/Helpers/ExplorationHelper.h
  7. 1 1
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  8. 14 14
      AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp
  9. 56 56
      AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h
  10. 12 12
      AI/StupidAI/StupidAI.cpp
  11. 56 56
      AI/StupidAI/StupidAI.h
  12. 448 448
      AI/VCAI/Goals/Explore.cpp
  13. 66 66
      AI/VCAI/Goals/Explore.h
  14. 1 1
      client/CPlayerInterface.cpp
  15. 1 1
      client/CPlayerInterface.h
  16. 7 7
      client/battle/BattleActionsController.cpp
  17. 4 4
      client/battle/BattleAnimationClasses.cpp
  18. 5 5
      client/battle/BattleAnimationClasses.h
  19. 41 41
      client/battle/BattleFieldController.cpp
  20. 141 141
      client/battle/BattleFieldController.h
  21. 1 1
      client/battle/BattleInterface.cpp
  22. 232 232
      client/battle/BattleInterface.h
  23. 2 2
      client/battle/BattleStacksController.cpp
  24. 149 148
      client/battle/BattleStacksController.h
  25. 10 10
      client/mapView/MapRenderer.cpp
  26. 1 1
      client/renderSDL/SDL_Extensions.cpp
  27. 2 2
      client/xBRZ/xbrz.cpp
  28. 1 1
      client/xBRZ/xbrz.h
  29. 4 4
      client/xBRZ/xbrz_tools.h
  30. 104 103
      include/vstd/RNG.h
  31. 101 101
      lib/BattleFieldHandler.cpp
  32. 80 80
      lib/BattleFieldHandler.h
  33. 1 1
      lib/CGameInterface.cpp
  34. 1 1
      lib/CGameInterface.h
  35. 2 0
      lib/CMakeLists.txt
  36. 6 6
      lib/CStack.cpp
  37. 2 2
      lib/CStack.h
  38. 2 2
      lib/IGameEventsReceiver.h
  39. 129 130
      lib/ObstacleHandler.cpp
  40. 79 79
      lib/ObstacleHandler.h
  41. 138 245
      lib/battle/BattleHex.cpp
  42. 127 117
      lib/battle/BattleHex.h
  43. 103 0
      lib/battle/BattleHexArray.cpp
  44. 300 0
      lib/battle/BattleHexArray.h
  45. 5 5
      lib/battle/BattleInfo.cpp
  46. 56 62
      lib/battle/CBattleInfoCallback.cpp
  47. 14 14
      lib/battle/CBattleInfoCallback.h
  48. 13 9
      lib/battle/CObstacleInstance.cpp
  49. 6 6
      lib/battle/CObstacleInstance.h
  50. 43 43
      lib/battle/Destination.h
  51. 89 89
      lib/battle/IBattleInfoCallback.h
  52. 89 91
      lib/battle/ReachabilityInfo.cpp
  53. 5 3
      lib/battle/ReachabilityInfo.h
  54. 240 242
      lib/battle/Unit.cpp
  55. 9 10
      lib/battle/Unit.h
  56. 1 1
      lib/bonuses/BonusEnum.h
  57. 1 1
      lib/bonuses/Limiters.cpp
  58. 2 2
      lib/bonuses/Limiters.h
  59. 1 1
      lib/mapping/CMap.cpp
  60. 3 2
      lib/networkPacks/PacksForClientBattle.h
  61. 243 243
      lib/pathfinder/CGPathNode.h
  62. 1027 1027
      lib/rmg/CZonePlacer.cpp
  63. 1 1
      lib/rmg/modificators/ObstaclePlacer.cpp
  64. 14 0
      lib/serializer/BinaryDeserializer.h
  65. 1 0
      lib/serializer/BinarySerializer.h
  66. 8 13
      lib/spells/BattleSpellMechanics.cpp
  67. 86 86
      lib/spells/BattleSpellMechanics.h
  68. 2 2
      lib/spells/CSpellHandler.cpp
  69. 1 1
      lib/spells/CSpellHandler.h
  70. 368 368
      lib/spells/ISpellMechanics.h
  71. 83 82
      lib/spells/effects/Effect.h
  72. 52 52
      lib/spells/effects/LocationEffect.cpp
  73. 41 41
      lib/spells/effects/LocationEffect.h
  74. 9 5
      lib/spells/effects/Moat.cpp
  75. 43 43
      lib/spells/effects/Moat.h
  76. 5 6
      lib/spells/effects/Obstacle.cpp
  77. 78 78
      lib/spells/effects/Obstacle.h
  78. 2 2
      lib/spells/effects/Summon.cpp
  79. 55 55
      lib/spells/effects/Summon.h
  80. 2 2
      lib/spells/effects/Teleport.cpp
  81. 4 4
      lib/spells/effects/UnitEffect.cpp
  82. 61 61
      lib/spells/effects/UnitEffect.h
  83. 7 7
      server/battles/BattleActionProcessor.cpp
  84. 24 24
      server/battles/BattleFlowProcessor.cpp
  85. 62 61
      server/battles/BattleFlowProcessor.h

+ 2 - 2
AI/BattleAI/AttackPossibility.cpp

@@ -326,9 +326,9 @@ AttackPossibility AttackPossibility::evaluate(
 
 
 	AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
 	AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
 
 
-	std::vector<BattleHex> defenderHex;
+	BattleHexArray defenderHex;
 	if(attackInfo.shooting)
 	if(attackInfo.shooting)
-		defenderHex.push_back(defender->getPosition());
+		defenderHex.insert(defender->getPosition());
 	else
 	else
 		defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
 		defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
 
 

+ 6 - 6
AI/BattleAI/BattleEvaluator.cpp

@@ -355,7 +355,7 @@ BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex,
 	}
 	}
 }
 }
 
 
-BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
+BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets)
 {
 {
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
@@ -381,7 +381,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 		return BattleAction::makeDefend(stack);
 		return BattleAction::makeDefend(stack);
 	}
 	}
 
 
-	std::vector<BattleHex> targetHexes = hexes;
+	BattleHexArray targetHexes = hexes;
 
 
 	vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
 	vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
 
 
@@ -419,16 +419,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 
 
 	if(stack->hasBonusOfType(BonusType::FLYING))
 	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
 	{
-		std::set<BattleHex> obstacleHexes;
+		BattleHexArray obstacleHexes;
 
 
-		auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
+		auto insertAffected = [](const CObstacleInstance & spellObst, BattleHexArray & obstacleHexes) {
 			auto affectedHexes = spellObst.getAffectedTiles();
 			auto affectedHexes = spellObst.getAffectedTiles();
-			obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
+			obstacleHexes.merge(affectedHexes);
 		};
 		};
 
 
 		const auto & obstacles = hb->battleGetAllObstacles();
 		const auto & obstacles = hb->battleGetAllObstacles();
 
 
-		for (const auto & obst: obstacles) {
+		for (const auto & obst : obstacles) {
 
 
 			if(obst->triggersEffects())
 			if(obst->triggersEffects())
 			{
 			{

+ 1 - 1
AI/BattleAI/BattleEvaluator.h

@@ -51,7 +51,7 @@ public:
 	bool attemptCastingSpell(const CStack * stack);
 	bool attemptCastingSpell(const CStack * stack);
 	bool canCastSpell();
 	bool canCastSpell();
 	std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
 	std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
-	BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
+	BattleAction goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets);
 	std::vector<BattleHex> getBrokenWallMoatHexes() const;
 	std::vector<BattleHex> getBrokenWallMoatHexes() const;
 	bool hasWorkingTowers() const;
 	bool hasWorkingTowers() const;
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only

+ 12 - 11
AI/BattleAI/BattleExchangeVariant.cpp

@@ -458,7 +458,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 						}
 						}
 					}
 					}
 
 
-					result.positions.push_back(enemyHex);
+					result.positions.insert(enemyHex);
 				}
 				}
 
 
 				result.cachedAttack = attack;
 				result.cachedAttack = attack;
@@ -487,12 +487,12 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(cons
 		auto hexes = stack->getSurroundingHexes();
 		auto hexes = stack->getSurroundingHexes();
 		for(auto hex : hexes)
 		for(auto hex : hexes)
 		{
 		{
-			auto neighbor = cb->battleGetUnitByPos(hex);
+			auto neighbour = cb->battleGetUnitByPos(hex);
 
 
-			if(neighbor && neighbor->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbor))
+			if(neighbour && neighbour->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbour))
 			{
 			{
-				queue.push(neighbor);
-				checkedStacks.push_back(neighbor);
+				queue.push(neighbour);
+				checkedStacks.push_back(neighbour);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -511,7 +511,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 
 
 	auto hexes = ap.attack.defender->getSurroundingHexes();
 	auto hexes = ap.attack.defender->getSurroundingHexes();
 
 
-	if(!ap.attack.shooting) hexes.push_back(ap.from);
+	if(!ap.attack.shooting) hexes.insert(ap.from);
 
 
 	std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
 	std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
 	
 	
@@ -959,9 +959,11 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
 
 
 				if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 				if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 				{
 				{
-					for(BattleHex neighbor : hex.neighbouringTiles())
+					BattleHexArray neighbours;
+					neighbours.generateNeighbouringTiles(hex);
+					for(BattleHex neighbour : neighbours)
 					{
 					{
-						reachable = unitReachability.distances.at(neighbor) <= radius;
+						reachable = unitReachability.distances.at(neighbour) <= radius;
 
 
 						if(reachable) break;
 						if(reachable) break;
 					}
 					}
@@ -1020,10 +1022,9 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 					if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 					if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 					{
 					{
 						enemyUnit = true;
 						enemyUnit = true;
-
-						for(BattleHex neighbor : hex.neighbouringTiles())
+						for(BattleHex neighbour : BattleHexArray::generateNeighbouringTiles(hex))
 						{
 						{
-							reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
+							reachable = unitReachability.distances.at(neighbour) <= unitSpeed;
 
 
 							if(reachable) break;
 							if(reachable) break;
 						}
 						}

+ 1 - 1
AI/BattleAI/BattleExchangeVariant.h

@@ -54,7 +54,7 @@ struct AttackerValue
 struct MoveTarget
 struct MoveTarget
 {
 {
 	float score;
 	float score;
-	std::vector<BattleHex> positions;
+	BattleHexArray positions;
 	std::optional<AttackPossibility> cachedAttack;
 	std::optional<AttackPossibility> cachedAttack;
 	uint8_t turnsToRich;
 	uint8_t turnsToRich;
 
 

+ 49 - 49
AI/Nullkiller/Helpers/ExplorationHelper.h

@@ -1,49 +1,49 @@
-/*
-* ExplorationHelper.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"
-
-#include "../../../lib/GameConstants.h"
-#include "../../../lib/VCMI_Lib.h"
-#include "../Goals/AbstractGoal.h"
-
-namespace NKAI
-{
-
-class ExplorationHelper
-{
-private:
-	const CGHeroInstance * hero;
-	int sightRadius;
-	float bestValue;
-	Goals::TSubgoal bestGoal;
-	int3 bestTile;
-	int bestTilesDiscovered;
-	const Nullkiller * ai;
-	CCallback * cbp;
-	const TeamState * ts;
-	int3 ourPos;
-	bool allowDeadEndCancellation;
-	bool useCPathfinderAccessibility;
-
-public:
-	ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false);
-	Goals::TSubgoal makeComposition() const;
-	bool scanSector(int scanRadius);
-	bool scanMap();
-	int howManyTilesWillBeDiscovered(const int3 & pos) const;
-
-private:
-	void scanTile(const int3 & tile);
-	bool hasReachableNeighbor(const int3 & pos) const;
-};
-
-}
+/*
+* ExplorationHelper.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"
+
+#include "../../../lib/GameConstants.h"
+#include "../../../lib/VCMI_Lib.h"
+#include "../Goals/AbstractGoal.h"
+
+namespace NKAI
+{
+
+class ExplorationHelper
+{
+private:
+	const CGHeroInstance * hero;
+	int sightRadius;
+	float bestValue;
+	Goals::TSubgoal bestGoal;
+	int3 bestTile;
+	int bestTilesDiscovered;
+	const Nullkiller * ai;
+	CCallback * cbp;
+	const TeamState * ts;
+	int3 ourPos;
+	bool allowDeadEndCancellation;
+	bool useCPathfinderAccessibility;
+
+public:
+	ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false);
+	Goals::TSubgoal makeComposition() const;
+	bool scanSector(int scanRadius);
+	bool scanMap();
+	int howManyTilesWillBeDiscovered(const int3 & pos) const;
+
+private:
+	void scanTile(const int3 & tile);
+	bool hasReachableneighbour(const int3 & pos) const;
+};
+
+}

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

@@ -399,7 +399,7 @@ void AINodeStorage::calculateNeighbours(
 
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 		logAi->trace(
 		logAi->trace(
-			"Node %s added to neighbors of %s, layer %d",
+			"Node %s added to neighbours of %s, layer %d",
 			neighbour.toString(),
 			neighbour.toString(),
 			source.coord.toString(),
 			source.coord.toString(),
 			static_cast<int32_t>(layer));
 			static_cast<int32_t>(layer));

+ 14 - 14
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp

@@ -56,9 +56,9 @@ void ObjectGraphCalculator::calculateConnections()
 	removeExtraConnections();
 	removeExtraConnections();
 }
 }
 
 
-float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache)
+float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache)
 {
 {
-	float neighborCost = std::numeric_limits<float>::max();
+	float neighbourCost = std::numeric_limits<float>::max();
 
 
 	if(NKAI_GRAPH_TRACE_LEVEL >= 2)
 	if(NKAI_GRAPH_TRACE_LEVEL >= 2)
 	{
 	{
@@ -68,24 +68,24 @@ float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::v
 	foreach_neighbour(
 	foreach_neighbour(
 		ai->cb.get(),
 		ai->cb.get(),
 		pos,
 		pos,
-		[this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
+		[this, &neighbourCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour)
 		{
 		{
-			ai->pathfinder->calculatePathInfo(pathCache, neighbor);
+			ai->pathfinder->calculatePathInfo(pathCache, neighbour);
 
 
 			auto costTotal = this->getConnectionsCost(pathCache);
 			auto costTotal = this->getConnectionsCost(pathCache);
 
 
-			if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
+			if(costTotal.connectionsCount > 2 && costTotal.avg < neighbourCost)
 			{
 			{
-				neighborCost = costTotal.avg;
+				neighbourCost = costTotal.avg;
 
 
 				if(NKAI_GRAPH_TRACE_LEVEL >= 2)
 				if(NKAI_GRAPH_TRACE_LEVEL >= 2)
 				{
 				{
-					logAi->trace("Better node found at %s", neighbor.toString());
+					logAi->trace("Better node found at %s", neighbour.toString());
 				}
 				}
 			}
 			}
 		});
 		});
 
 
-	return neighborCost;
+	return neighbourCost;
 }
 }
 
 
 void ObjectGraphCalculator::addMinimalDistanceJunctions()
 void ObjectGraphCalculator::addMinimalDistanceJunctions()
@@ -105,9 +105,9 @@ void ObjectGraphCalculator::addMinimalDistanceJunctions()
 			if(currentCost.connectionsCount <= 2)
 			if(currentCost.connectionsCount <= 2)
 				return;
 				return;
 
 
-			float neighborCost = getNeighborConnectionsCost(pos, paths);
+			float neighbourCost = getneighbourConnectionsCost(pos, paths);
 
 
-			if(currentCost.avg < neighborCost)
+			if(currentCost.avg < neighbourCost)
 			{
 			{
 				junctions.insert(pos);
 				junctions.insert(pos);
 			}
 			}
@@ -137,17 +137,17 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vector<A
 		foreach_neighbour(
 		foreach_neighbour(
 			ai->cb.get(),
 			ai->cb.get(),
 			pos,
 			pos,
-			[this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
+			[this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour)
 			{
 			{
-				if(target->hasNodeAt(neighbor))
+				if(target->hasNodeAt(neighbour))
 				{
 				{
-					ai->pathfinder->calculatePathInfo(pathCache, neighbor);
+					ai->pathfinder->calculatePathInfo(pathCache, neighbour);
 
 
 					for(auto & path : pathCache)
 					for(auto & path : pathCache)
 					{
 					{
 						if(pos == path.targetHero->visitablePos())
 						if(pos == path.targetHero->visitablePos())
 						{
 						{
-							target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
+							target->tryAddConnection(pos, neighbour, path.movementCost(), path.getTotalDanger());
 						}
 						}
 					}
 					}
 				}
 				}

+ 56 - 56
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h

@@ -1,56 +1,56 @@
-/*
-* ObjectGraphCalculator.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 "ObjectGraph.h"
-#include "../AIUtility.h"
-
-namespace NKAI
-{
-
-struct ConnectionCostInfo
-{
-	float totalCost = 0;
-	float avg = 0;
-	int connectionsCount = 0;
-};
-
-class ObjectGraphCalculator
-{
-private:
-	ObjectGraph * target;
-	const Nullkiller * ai;
-	std::mutex syncLock;
-
-	std::map<const CGHeroInstance *, HeroRole> actors;
-	std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
-
-	std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
-	std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
-
-public:
-	ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai);
-	void setGraphObjects();
-	void calculateConnections();
-	float getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache);
-	void addMinimalDistanceJunctions();
-
-private:
-	void updatePaths();
-	void calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache);
-	bool isExtraConnection(float direct, float side1, float side2) const;
-	void removeExtraConnections();
-	void addObjectActor(const CGObjectInstance * obj);
-	void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false);
-	ConnectionCostInfo getConnectionsCost(std::vector<AIPath> & paths) const;
-};
-
-}
+/*
+* ObjectGraphCalculator.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 "ObjectGraph.h"
+#include "../AIUtility.h"
+
+namespace NKAI
+{
+
+struct ConnectionCostInfo
+{
+	float totalCost = 0;
+	float avg = 0;
+	int connectionsCount = 0;
+};
+
+class ObjectGraphCalculator
+{
+private:
+	ObjectGraph * target;
+	const Nullkiller * ai;
+	std::mutex syncLock;
+
+	std::map<const CGHeroInstance *, HeroRole> actors;
+	std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
+
+	std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
+	std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
+
+public:
+	ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai);
+	void setGraphObjects();
+	void calculateConnections();
+	float getneighbourConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache);
+	void addMinimalDistanceJunctions();
+
+private:
+	void updatePaths();
+	void calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache);
+	bool isExtraConnection(float direct, float side1, float side2) const;
+	void removeExtraConnections();
+	void addObjectActor(const CGObjectInstance * obj);
+	void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false);
+	ConnectionCostInfo getConnectionsCost(std::vector<AIPath> & paths) const;
+};
+
+}

+ 12 - 12
AI/StupidAI/StupidAI.cpp

@@ -69,7 +69,7 @@ public:
 	const CStack * s;
 	const CStack * s;
 	int adi;
 	int adi;
 	int adr;
 	int adr;
-	std::vector<BattleHex> attackFrom; //for melee fight
+	BattleHexArray attackFrom; //for melee fight
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	{}
 	{}
 	void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
 	void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
@@ -107,7 +107,7 @@ static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback>
 
 
 	for(int i = 0; i < 2; i++)
 	for(int i = 0; i < 2; i++)
 	{
 	{
-		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
+		for (auto neighbour : BattleHexArray::generateNeighbouringTiles(i ? h2 : h1))
 			if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
 			if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 				if(s->isShooter())
 					shooters[i]++;
 					shooters[i]++;
@@ -157,7 +157,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 		}
 		}
 		else
 		else
 		{
 		{
-			std::vector<BattleHex> avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false);
+			BattleHexArray avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false);
 
 
 			for (BattleHex hex : avHexes)
 			for (BattleHex hex : avHexes)
 			{
 			{
@@ -170,7 +170,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 						i = enemiesReachable.begin() + (enemiesReachable.size() - 1);
 						i = enemiesReachable.begin() + (enemiesReachable.size() - 1);
 					}
 					}
 
 
-					i->attackFrom.push_back(hex);
+					i->attackFrom.insert(hex);
 				}
 				}
 			}
 			}
 
 
@@ -247,7 +247,7 @@ void CStupidAI::battleNewRound(const BattleID & battleID)
 	print("battleNewRound called");
 	print("battleNewRound called");
 }
 }
 
 
-void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
+void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport)
 {
 {
 	print("battleStackMoved called");
 	print("battleStackMoved called");
 }
 }
@@ -278,7 +278,7 @@ void CStupidAI::print(const std::string &text) const
 	logAi->trace("CStupidAI  [%p]: %s", this, text);
 	logAi->trace("CStupidAI  [%p]: %s", this, text);
 }
 }
 
 
-BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const
+BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const
 {
 {
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
@@ -295,7 +295,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 
 
 	for(auto hex : hexes)
 	for(auto hex : hexes)
 	{
 	{
-		if(vstd::contains(avHexes, hex))
+		if(avHexes.contains(hex))
 		{
 		{
 			if(stack->position == hex)
 			if(stack->position == hex)
 				return BattleAction::makeDefend(stack);
 				return BattleAction::makeDefend(stack);
@@ -310,9 +310,9 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 		}
 		}
 	}
 	}
 
 
-	BattleHex bestNeighbor = hexes.front();
+	BattleHex bestneighbour = hexes.front();
 
 
-	if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
+	if(reachability.distances[bestneighbour] > GameConstants::BFIELD_SIZE)
 	{
 	{
 		return BattleAction::makeDefend(stack);
 		return BattleAction::makeDefend(stack);
 	}
 	}
@@ -323,14 +323,14 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 		// We just check all available hexes and pick the one closest to the target.
 		// We just check all available hexes and pick the one closest to the target.
 		auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
 		auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
 		{
 		{
-			return BattleHex::getDistance(bestNeighbor, hex);
+			return BattleHex::getDistance(bestneighbour, hex);
 		});
 		});
 
 
 		return BattleAction::makeMove(stack, *nearestAvailableHex);
 		return BattleAction::makeMove(stack, *nearestAvailableHex);
 	}
 	}
 	else
 	else
 	{
 	{
-		BattleHex currentDest = bestNeighbor;
+		BattleHex currentDest = bestneighbour;
 		while(1)
 		while(1)
 		{
 		{
 			if(!currentDest.isValid())
 			if(!currentDest.isValid())
@@ -339,7 +339,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 				return BattleAction::makeDefend(stack);
 				return BattleAction::makeDefend(stack);
 			}
 			}
 
 
-			if(vstd::contains(avHexes, currentDest))
+			if(avHexes.contains(currentDest))
 			{
 			{
 				if(stack->position == currentDest)
 				if(stack->position == currentDest)
 					return BattleAction::makeDefend(stack);
 					return BattleAction::makeDefend(stack);

+ 56 - 56
AI/StupidAI/StupidAI.h

@@ -1,56 +1,56 @@
-/*
- * StupidAI.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../../lib/battle/BattleHex.h"
-#include "../../lib/battle/ReachabilityInfo.h"
-#include "../../lib/CGameInterface.h"
-
-class EnemyInfo;
-
-class CStupidAI : public CBattleGameInterface
-{
-	BattleSide side;
-	std::shared_ptr<CBattleCallback> cb;
-	std::shared_ptr<Environment> env;
-
-	bool wasWaitingForRealize;
-	bool wasUnlockingGs;
-
-	void print(const std::string &text) const;
-public:
-	CStupidAI();
-	~CStupidAI();
-
-	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
-	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
-
-	void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
-	void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
-	void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
-	void yourTacticPhase(const BattleID & battleID, int distance) override;
-
-	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack
-	void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
-	//void battleResultsApplied() override; //called when all effects of last battle are applied
-	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied;
-	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
-	void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
-	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
-	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks
-	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
-	void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
-
-private:
-	BattleAction goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const;
-};
-
+/*
+ * StupidAI.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/battle/BattleHex.h"
+#include "../../lib/battle/ReachabilityInfo.h"
+#include "../../lib/CGameInterface.h"
+
+class EnemyInfo;
+
+class CStupidAI : public CBattleGameInterface
+{
+	BattleSide side;
+	std::shared_ptr<CBattleCallback> cb;
+	std::shared_ptr<Environment> env;
+
+	bool wasWaitingForRealize;
+	bool wasUnlockingGs;
+
+	void print(const std::string &text) const;
+public:
+	CStupidAI();
+	~CStupidAI();
+
+	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
+	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
+
+	void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
+	void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
+	void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
+	void yourTacticPhase(const BattleID & battleID, int distance) override;
+
+	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack
+	void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
+	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
+	//void battleResultsApplied() override; //called when all effects of last battle are applied
+	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied;
+	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override;
+	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
+	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks
+	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
+	void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
+	void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
+
+private:
+	BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const;
+};
+

+ 448 - 448
AI/VCAI/Goals/Explore.cpp

@@ -1,448 +1,448 @@
-/*
-* Explore.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 "Goals.h"
-#include "../VCAI.h"
-#include "../AIUtility.h"
-#include "../AIhelper.h"
-#include "../FuzzyHelper.h"
-#include "../ResourceManager.h"
-#include "../BuildingManager.h"
-#include "../../../lib/constants/StringConstants.h"
-#include "../../../lib/CPlayerState.h"
-
-using namespace Goals;
-
-namespace Goals
-{
-	struct ExplorationHelper
-	{
-		HeroPtr hero;
-		int sightRadius;
-		float bestValue;
-		TSubgoal bestGoal;
-		VCAI * aip;
-		CCallback * cbp;
-		const TeamState * ts;
-		int3 ourPos;
-		bool allowDeadEndCancellation;
-		bool allowGatherArmy;
-
-		ExplorationHelper(HeroPtr h, bool gatherArmy)
-		{
-			cbp = cb;
-			aip = ai;
-			hero = h;
-			ts = cbp->getPlayerTeam(ai->playerID);
-			sightRadius = hero->getSightRadius();
-			bestGoal = sptr(Goals::Invalid());
-			bestValue = 0;
-			ourPos = h->visitablePos();
-			allowDeadEndCancellation = true;
-			allowGatherArmy = gatherArmy;
-		}
-
-		void scanSector(int scanRadius)
-		{
-			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(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
-				{
-
-					if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
-					{
-						scanTile(tile);
-					}
-				}
-			}
-		}
-
-		void scanMap()
-		{
-			int3 mapSize = cbp->getMapSize();
-			int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
-
-			std::vector<int3> from;
-			std::vector<int3> to;
-
-			from.reserve(perimeter);
-			to.reserve(perimeter);
-
-			foreach_tile_pos([&](const int3 & pos)
-			{
-				if(ts->fogOfWarMap[pos.z][pos.x][pos.y])
-				{
-					bool hasInvisibleNeighbor = false;
-
-					foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
-					{
-						if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
-						{
-							hasInvisibleNeighbor = true;
-						}
-					});
-
-					if(hasInvisibleNeighbor)
-						from.push_back(pos);
-				}
-			});
-
-			logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name);
-
-			for(const int3 & tile : from)
-			{
-				scanTile(tile);
-			}
-
-			if(!bestGoal->invalid())
-			{
-				return;
-			}
-
-			allowDeadEndCancellation = false;
-
-			for(int i = 0; i < sightRadius; i++)
-			{
-				getVisibleNeighbours(from, to);
-				vstd::concatenate(from, to);
-				vstd::removeDuplicates(from);
-			}
-
-			logAi->debug("Exploration scan all possible tiles for hero %s", hero.name);
-
-			for(const int3 & tile : from)
-			{
-				scanTile(tile);
-			}
-		}
-
-		void scanTile(const int3 & tile)
-		{
-			if(tile == ourPos
-				|| !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does
-				return;
-
-			int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
-			if(!tilesDiscovered)
-				return;
-
-			auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy);
-			for(auto goal : waysToVisit)
-			{
-				if(goal->evaluationContext.movementCost <= 0.0) // should not happen
-					continue;
-
-				float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost;
-
-				if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
-				{
-					auto obj = cb->getTopObj(tile);
-
-					// picking up resources does not yield any exploration at all.
-					// if it blocks the way to some explorable tile AIPathfinder will take care of it
-					if(obj && obj->isBlockedVisitable())
-					{
-						continue;
-					}
-
-					if(isSafeToVisit(hero, tile))
-					{
-						bestGoal = goal;
-						bestValue = ourValue;
-					}
-				}
-			}
-		}
-
-		void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
-		{
-			for(const int3 & tile : tiles)
-			{
-				foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
-				{
-					if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
-					{
-						out.push_back(neighbour);
-					}
-				});
-			}
-		}
-
-		int howManyTilesWillBeDiscovered(const int3 & pos) const
-		{
-			int ret = 0;
-			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(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
-				{
-					if(cbp->isInTheMap(npos)
-						&& pos.dist2d(npos) - 0.5 < sightRadius
-						&& !slice[npos.x][npos.y])
-					{
-						if(allowDeadEndCancellation
-							&& !hasReachableNeighbor(npos))
-						{
-							continue;
-						}
-
-						ret++;
-					}
-				}
-			}
-
-			return ret;
-		}
-
-		bool hasReachableNeighbor(const int3 &pos) const
-		{
-			for(crint3 dir : int3::getDirs())
-			{
-				int3 tile = pos + dir;
-				if(cbp->isInTheMap(tile))
-				{
-					auto isAccessible = aip->ah->isTileAccessible(hero, tile);
-
-					if(isAccessible)
-						return true;
-				}
-			}
-
-			return false;
-		}
-	};
-}
-
-bool Explore::operator==(const Explore & other) const
-{
-	return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy;
-}
-
-std::string Explore::completeMessage() const
-{
-	return "Hero " + hero.get()->getNameTranslated() + " completed exploration";
-}
-
-TSubgoal Explore::whatToDoToAchieve()
-{
-	return fh->chooseSolution(getAllPossibleSubgoals());
-}
-
-TGoalVec Explore::getAllPossibleSubgoals()
-{
-	TGoalVec ret;
-	std::vector<const CGHeroInstance *> heroes;
-
-	if(hero)
-	{
-		heroes.push_back(hero.h);
-	}
-	else
-	{
-		//heroes = ai->getUnblockedHeroes();
-		heroes = cb->getHeroesInfo();
-		vstd::erase_if(heroes, [](const HeroPtr h)
-		{
-			if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer
-				return true;
-
-			if(!ai->isAbleToExplore(h))
-				return true;
-
-			return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway
-		});
-	}
-
-	//try to use buildings that uncover map
-	std::vector<const CGObjectInstance *> objs;
-	for(auto obj : ai->visitableObjs)
-	{
-		if(!vstd::contains(ai->alreadyVisited, obj))
-		{
-			switch(obj->ID.num)
-			{
-			case Obj::REDWOOD_OBSERVATORY:
-			case Obj::PILLAR_OF_FIRE:
-			case Obj::CARTOGRAPHER:
-				objs.push_back(obj);
-				break;
-			case Obj::MONOLITH_ONE_WAY_ENTRANCE:
-			case Obj::MONOLITH_TWO_WAY:
-			case Obj::SUBTERRANEAN_GATE:
-				auto tObj = dynamic_cast<const CGTeleport *>(obj);
-				assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end());
-				if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability)
-					objs.push_back(obj);
-				break;
-			}
-		}
-		else
-		{
-			switch(obj->ID.num)
-			{
-			case Obj::MONOLITH_TWO_WAY:
-			case Obj::SUBTERRANEAN_GATE:
-				auto tObj = dynamic_cast<const CGTeleport *>(obj);
-				if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability)
-					break;
-				for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits)
-				{
-					if(!cb->getObj(exit))
-					{ // Always attempt to visit two-way teleports if one of channel exits is not visible
-						objs.push_back(obj);
-						break;
-					}
-				}
-				break;
-			}
-		}
-	}
-
-	for(auto h : heroes)
-	{
-		for(auto obj : objs) //double loop, performance risk?
-		{
-			auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy);
-
-			vstd::concatenate(ret, waysToVisitObj);
-		}
-
-		TSubgoal goal = exploreNearestNeighbour(h);
-
-		if(!goal->invalid())
-		{
-			ret.push_back(goal);
-		}
-	}
-
-	if(ret.empty())
-	{
-		for(auto h : heroes)
-		{
-			logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated());
-
-			TSubgoal goal = explorationNewPoint(h);
-
-			if(goal->invalid())
-			{
-				ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
-			}
-			else
-			{
-				ret.push_back(goal);
-			}
-		}
-	}
-
-	//we either don't have hero yet or none of heroes can explore
-	if((!hero || ret.empty()) && ai->canRecruitAnyHero())
-		ret.push_back(sptr(RecruitHero()));
-
-	if(ret.empty())
-	{
-		throw goalFulfilledException(sptr(Explore().sethero(hero)));
-	}
-
-	return ret;
-}
-
-bool Explore::fulfillsMe(TSubgoal goal)
-{
-	if(goal->goalType == EXPLORE)
-	{
-		if(goal->hero)
-			return hero == goal->hero;
-		else
-			return true; //cancel ALL exploration
-	}
-	return false;
-}
-
-TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const
-{
-	ExplorationHelper scanResult(h, allowGatherArmy);
-
-	for(crint3 dir : int3::getDirs())
-	{
-		int3 tile = hpos + dir;
-		if(cb->isInTheMap(tile))
-		{
-			scanResult.scanTile(tile);
-		}
-	}
-
-	return scanResult.bestGoal;
-}
-
-
-TSubgoal Explore::explorationNewPoint(HeroPtr h) const
-{
-	ExplorationHelper scanResult(h, allowGatherArmy);
-
-	scanResult.scanSector(10);
-
-	if(!scanResult.bestGoal->invalid())
-	{
-		return scanResult.bestGoal;
-	}
-
-	scanResult.scanMap();
-
-	return scanResult.bestGoal;
-}
-
-
-TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const
-{
-	TimeCheck tc("where to explore");
-	int3 hpos = h->visitablePos();
-
-	//look for nearby objs -> visit them if they're close enough
-	const int DIST_LIMIT = 3;
-	const float COST_LIMIT = .2f; //todo: fine tune
-
-	std::vector<const CGObjectInstance *> nearbyVisitableObjs;
-	for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
-	{
-		for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
-		{
-			for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
-			{
-				if(ai->isGoodForVisit(obj, h, COST_LIMIT))
-				{
-					nearbyVisitableObjs.push_back(obj);
-				}
-			}
-		}
-	}
-
-	if(nearbyVisitableObjs.size())
-	{
-		vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
-		boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
-
-		TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false));
-
-		if(!pickupNearestObj->invalid())
-		{
-			return pickupNearestObj;
-		}
-	}
-
-	//check if nearby tiles allow us to reveal anything - this is quick
-	return explorationBestNeighbour(hpos, h);
-}
+/*
+* Explore.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 "Goals.h"
+#include "../VCAI.h"
+#include "../AIUtility.h"
+#include "../AIhelper.h"
+#include "../FuzzyHelper.h"
+#include "../ResourceManager.h"
+#include "../BuildingManager.h"
+#include "../../../lib/constants/StringConstants.h"
+#include "../../../lib/CPlayerState.h"
+
+using namespace Goals;
+
+namespace Goals
+{
+	struct ExplorationHelper
+	{
+		HeroPtr hero;
+		int sightRadius;
+		float bestValue;
+		TSubgoal bestGoal;
+		VCAI * aip;
+		CCallback * cbp;
+		const TeamState * ts;
+		int3 ourPos;
+		bool allowDeadEndCancellation;
+		bool allowGatherArmy;
+
+		ExplorationHelper(HeroPtr h, bool gatherArmy)
+		{
+			cbp = cb;
+			aip = ai;
+			hero = h;
+			ts = cbp->getPlayerTeam(ai->playerID);
+			sightRadius = hero->getSightRadius();
+			bestGoal = sptr(Goals::Invalid());
+			bestValue = 0;
+			ourPos = h->visitablePos();
+			allowDeadEndCancellation = true;
+			allowGatherArmy = gatherArmy;
+		}
+
+		void scanSector(int scanRadius)
+		{
+			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(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
+				{
+
+					if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
+					{
+						scanTile(tile);
+					}
+				}
+			}
+		}
+
+		void scanMap()
+		{
+			int3 mapSize = cbp->getMapSize();
+			int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
+
+			std::vector<int3> from;
+			std::vector<int3> to;
+
+			from.reserve(perimeter);
+			to.reserve(perimeter);
+
+			foreach_tile_pos([&](const int3 & pos)
+			{
+				if(ts->fogOfWarMap[pos.z][pos.x][pos.y])
+				{
+					bool hasInvisibleneighbour = false;
+
+					foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
+					{
+						if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
+						{
+							hasInvisibleneighbour = true;
+						}
+					});
+
+					if(hasInvisibleneighbour)
+						from.push_back(pos);
+				}
+			});
+
+			logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name);
+
+			for(const int3 & tile : from)
+			{
+				scanTile(tile);
+			}
+
+			if(!bestGoal->invalid())
+			{
+				return;
+			}
+
+			allowDeadEndCancellation = false;
+
+			for(int i = 0; i < sightRadius; i++)
+			{
+				getVisibleNeighbours(from, to);
+				vstd::concatenate(from, to);
+				vstd::removeDuplicates(from);
+			}
+
+			logAi->debug("Exploration scan all possible tiles for hero %s", hero.name);
+
+			for(const int3 & tile : from)
+			{
+				scanTile(tile);
+			}
+		}
+
+		void scanTile(const int3 & tile)
+		{
+			if(tile == ourPos
+				|| !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does
+				return;
+
+			int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
+			if(!tilesDiscovered)
+				return;
+
+			auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy);
+			for(auto goal : waysToVisit)
+			{
+				if(goal->evaluationContext.movementCost <= 0.0) // should not happen
+					continue;
+
+				float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost;
+
+				if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
+				{
+					auto obj = cb->getTopObj(tile);
+
+					// picking up resources does not yield any exploration at all.
+					// if it blocks the way to some explorable tile AIPathfinder will take care of it
+					if(obj && obj->isBlockedVisitable())
+					{
+						continue;
+					}
+
+					if(isSafeToVisit(hero, tile))
+					{
+						bestGoal = goal;
+						bestValue = ourValue;
+					}
+				}
+			}
+		}
+
+		void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
+		{
+			for(const int3 & tile : tiles)
+			{
+				foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
+				{
+					if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
+					{
+						out.push_back(neighbour);
+					}
+				});
+			}
+		}
+
+		int howManyTilesWillBeDiscovered(const int3 & pos) const
+		{
+			int ret = 0;
+			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(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
+				{
+					if(cbp->isInTheMap(npos)
+						&& pos.dist2d(npos) - 0.5 < sightRadius
+						&& !slice[npos.x][npos.y])
+					{
+						if(allowDeadEndCancellation
+							&& !hasReachableneighbour(npos))
+						{
+							continue;
+						}
+
+						ret++;
+					}
+				}
+			}
+
+			return ret;
+		}
+
+		bool hasReachableneighbour(const int3 &pos) const
+		{
+			for(crint3 dir : int3::getDirs())
+			{
+				int3 tile = pos + dir;
+				if(cbp->isInTheMap(tile))
+				{
+					auto isAccessible = aip->ah->isTileAccessible(hero, tile);
+
+					if(isAccessible)
+						return true;
+				}
+			}
+
+			return false;
+		}
+	};
+}
+
+bool Explore::operator==(const Explore & other) const
+{
+	return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy;
+}
+
+std::string Explore::completeMessage() const
+{
+	return "Hero " + hero.get()->getNameTranslated() + " completed exploration";
+}
+
+TSubgoal Explore::whatToDoToAchieve()
+{
+	return fh->chooseSolution(getAllPossibleSubgoals());
+}
+
+TGoalVec Explore::getAllPossibleSubgoals()
+{
+	TGoalVec ret;
+	std::vector<const CGHeroInstance *> heroes;
+
+	if(hero)
+	{
+		heroes.push_back(hero.h);
+	}
+	else
+	{
+		//heroes = ai->getUnblockedHeroes();
+		heroes = cb->getHeroesInfo();
+		vstd::erase_if(heroes, [](const HeroPtr h)
+		{
+			if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer
+				return true;
+
+			if(!ai->isAbleToExplore(h))
+				return true;
+
+			return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway
+		});
+	}
+
+	//try to use buildings that uncover map
+	std::vector<const CGObjectInstance *> objs;
+	for(auto obj : ai->visitableObjs)
+	{
+		if(!vstd::contains(ai->alreadyVisited, obj))
+		{
+			switch(obj->ID.num)
+			{
+			case Obj::REDWOOD_OBSERVATORY:
+			case Obj::PILLAR_OF_FIRE:
+			case Obj::CARTOGRAPHER:
+				objs.push_back(obj);
+				break;
+			case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+			case Obj::MONOLITH_TWO_WAY:
+			case Obj::SUBTERRANEAN_GATE:
+				auto tObj = dynamic_cast<const CGTeleport *>(obj);
+				assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end());
+				if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability)
+					objs.push_back(obj);
+				break;
+			}
+		}
+		else
+		{
+			switch(obj->ID.num)
+			{
+			case Obj::MONOLITH_TWO_WAY:
+			case Obj::SUBTERRANEAN_GATE:
+				auto tObj = dynamic_cast<const CGTeleport *>(obj);
+				if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability)
+					break;
+				for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits)
+				{
+					if(!cb->getObj(exit))
+					{ // Always attempt to visit two-way teleports if one of channel exits is not visible
+						objs.push_back(obj);
+						break;
+					}
+				}
+				break;
+			}
+		}
+	}
+
+	for(auto h : heroes)
+	{
+		for(auto obj : objs) //double loop, performance risk?
+		{
+			auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy);
+
+			vstd::concatenate(ret, waysToVisitObj);
+		}
+
+		TSubgoal goal = exploreNearestNeighbour(h);
+
+		if(!goal->invalid())
+		{
+			ret.push_back(goal);
+		}
+	}
+
+	if(ret.empty())
+	{
+		for(auto h : heroes)
+		{
+			logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated());
+
+			TSubgoal goal = explorationNewPoint(h);
+
+			if(goal->invalid())
+			{
+				ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
+			}
+			else
+			{
+				ret.push_back(goal);
+			}
+		}
+	}
+
+	//we either don't have hero yet or none of heroes can explore
+	if((!hero || ret.empty()) && ai->canRecruitAnyHero())
+		ret.push_back(sptr(RecruitHero()));
+
+	if(ret.empty())
+	{
+		throw goalFulfilledException(sptr(Explore().sethero(hero)));
+	}
+
+	return ret;
+}
+
+bool Explore::fulfillsMe(TSubgoal goal)
+{
+	if(goal->goalType == EXPLORE)
+	{
+		if(goal->hero)
+			return hero == goal->hero;
+		else
+			return true; //cancel ALL exploration
+	}
+	return false;
+}
+
+TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const
+{
+	ExplorationHelper scanResult(h, allowGatherArmy);
+
+	for(crint3 dir : int3::getDirs())
+	{
+		int3 tile = hpos + dir;
+		if(cb->isInTheMap(tile))
+		{
+			scanResult.scanTile(tile);
+		}
+	}
+
+	return scanResult.bestGoal;
+}
+
+
+TSubgoal Explore::explorationNewPoint(HeroPtr h) const
+{
+	ExplorationHelper scanResult(h, allowGatherArmy);
+
+	scanResult.scanSector(10);
+
+	if(!scanResult.bestGoal->invalid())
+	{
+		return scanResult.bestGoal;
+	}
+
+	scanResult.scanMap();
+
+	return scanResult.bestGoal;
+}
+
+
+TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const
+{
+	TimeCheck tc("where to explore");
+	int3 hpos = h->visitablePos();
+
+	//look for nearby objs -> visit them if they're close enough
+	const int DIST_LIMIT = 3;
+	const float COST_LIMIT = .2f; //todo: fine tune
+
+	std::vector<const CGObjectInstance *> nearbyVisitableObjs;
+	for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
+	{
+		for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
+		{
+			for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
+			{
+				if(ai->isGoodForVisit(obj, h, COST_LIMIT))
+				{
+					nearbyVisitableObjs.push_back(obj);
+				}
+			}
+		}
+	}
+
+	if(nearbyVisitableObjs.size())
+	{
+		vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
+		boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
+
+		TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false));
+
+		if(!pickupNearestObj->invalid())
+		{
+			return pickupNearestObj;
+		}
+	}
+
+	//check if nearby tiles allow us to reveal anything - this is quick
+	return explorationBestNeighbour(hpos, h);
+}

+ 66 - 66
AI/VCAI/Goals/Explore.h

@@ -1,66 +1,66 @@
-/*
-* Explore.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 "CGoal.h"
-
-struct HeroPtr;
-class VCAI;
-class FuzzyHelper;
-
-namespace Goals
-{
-	struct ExplorationHelper;
-
-	class DLL_EXPORT Explore : public CGoal<Explore>
-	{
-	private:
-		bool allowGatherArmy;
-
-	public:
-		Explore(bool allowGatherArmy)
-			: CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy)
-		{
-			priority = 1;
-		}
-
-		Explore()
-			: Explore(true)
-		{
-		}
-
-		Explore(HeroPtr h)
-			: CGoal(Goals::EXPLORE)
-		{
-			hero = h;
-			priority = 1;
-		}
-		TGoalVec getAllPossibleSubgoals() override;
-		TSubgoal whatToDoToAchieve() override;
-		std::string completeMessage() const override;
-		bool fulfillsMe(TSubgoal goal) override;
-		bool operator==(const Explore & other) const override;
-
-	private:
-		TSubgoal exploreNearestNeighbour(HeroPtr h) const;
-		TSubgoal explorationNewPoint(HeroPtr h) const;
-		TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const;
-		void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const;
-		bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const;
-
-		void getVisibleNeighbours(
-			const std::vector<int3> & tiles,
-			std::vector<int3> & out,
-			CCallback * cbp,
-			const TeamState * ts) const;
-
-		int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const;
-	};
-}
+/*
+* Explore.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 "CGoal.h"
+
+struct HeroPtr;
+class VCAI;
+class FuzzyHelper;
+
+namespace Goals
+{
+	struct ExplorationHelper;
+
+	class DLL_EXPORT Explore : public CGoal<Explore>
+	{
+	private:
+		bool allowGatherArmy;
+
+	public:
+		Explore(bool allowGatherArmy)
+			: CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy)
+		{
+			priority = 1;
+		}
+
+		Explore()
+			: Explore(true)
+		{
+		}
+
+		Explore(HeroPtr h)
+			: CGoal(Goals::EXPLORE)
+		{
+			hero = h;
+			priority = 1;
+		}
+		TGoalVec getAllPossibleSubgoals() override;
+		TSubgoal whatToDoToAchieve() override;
+		std::string completeMessage() const override;
+		bool fulfillsMe(TSubgoal goal) override;
+		bool operator==(const Explore & other) const override;
+
+	private:
+		TSubgoal exploreNearestNeighbour(HeroPtr h) const;
+		TSubgoal explorationNewPoint(HeroPtr h) const;
+		TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const;
+		void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const;
+		bool hasReachableneighbour(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const;
+
+		void getVisibleNeighbours(
+			const std::vector<int3> & tiles,
+			std::vector<int3> & out,
+			CCallback * cbp,
+			const TeamState * ts) const;
+
+		int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const;
+	};
+}

+ 1 - 1
client/CPlayerInterface.cpp

@@ -846,7 +846,7 @@ void CPlayerInterface::battleLogMessage(const BattleID & battleID, const std::ve
 	battleInt->displayBattleLog(lines);
 	battleInt->displayBattleLog(lines);
 }
 }
 
 
-void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
+void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 	BATTLE_EVENT_POSSIBLE_RETURN;

+ 1 - 1
client/CPlayerInterface.h

@@ -154,7 +154,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines) override;
 	void battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines) override;
-	void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override;
 	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
 	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
 	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; //called when a specific effect is set to stacks
 	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; //called when a specific effect is set to stacks
 	void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect
 	void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect

+ 7 - 7
client/battle/BattleActionsController.cpp

@@ -723,11 +723,11 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 		{
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			{
 			{
-				std::vector<BattleHex> acc = owner.getBattle()->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false);
+				BattleHexArray acc = owner.getBattle()->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
-				if(vstd::contains(acc, targetHex))
+				if(acc.contains(targetHex))
 					owner.giveCommand(EActionType::WALK, targetHex);
 					owner.giveCommand(EActionType::WALK, targetHex);
-				else if(vstd::contains(acc, shiftedDest))
+				else if(acc.contains(shiftedDest))
 					owner.giveCommand(EActionType::WALK, shiftedDest);
 					owner.giveCommand(EActionType::WALK, shiftedDest);
 			}
 			}
 			else
 			else
@@ -1008,12 +1008,12 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 
 
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 {
 {
-	std::vector<BattleHex> acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false);
+	BattleHexArray acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 
 
-	if (vstd::contains(acc, myNumber))
+	if (acc.contains(myNumber))
 		return true;
 		return true;
-	else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
+	else if (stackToMove->doubleWide() && acc.contains(shiftedDest))
 		return true;
 		return true;
 	else
 	else
 		return false;
 		return false;
@@ -1126,4 +1126,4 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction
 void BattleActionsController::resetCurrentStackPossibleActions()
 void BattleActionsController::resetCurrentStackPossibleActions()
 {
 {
 	possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
 	possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
-}
+}

+ 4 - 4
client/battle/BattleAnimationClasses.cpp

@@ -422,7 +422,7 @@ MovementAnimation::~MovementAnimation()
 		CCS->soundh->stopSound(moveSoundHandler);
 		CCS->soundh->stopSound(moveSoundHandler);
 }
 }
 
 
-MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance)
+MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, BattleHexArray _destTiles, int _distance)
 	: StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()),
 	: StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()),
 	  destTiles(_destTiles),
 	  destTiles(_destTiles),
 	  currentMoveIndex(0),
 	  currentMoveIndex(0),
@@ -892,7 +892,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName());
 	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName());
 }
 }
 
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	battlehexes = hex;
 	battlehexes = hex;
@@ -902,7 +902,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 	EffectAnimation(owner, animationName, effects, transparencyFactor, reversed)
 	EffectAnimation(owner, animationName, effects, transparencyFactor, reversed)
 {
 {
 	assert(hex.isValid());
 	assert(hex.isValid());
-	battlehexes.push_back(hex);
+	battlehexes.insert(hex);
 }
 }
 
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
@@ -921,7 +921,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	assert(hex.isValid());
 	assert(hex.isValid());
-	battlehexes.push_back(hex);
+	battlehexes.insert(hex);
 	positions.push_back(pos);
 	positions.push_back(pos);
 }
 }
 
 

+ 5 - 5
client/battle/BattleAnimationClasses.h

@@ -9,7 +9,7 @@
  */
  */
 #pragma once
 #pragma once
 
 
-#include "../../lib/battle/BattleHex.h"
+#include "../../lib/battle/BattleHexArray.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 #include "BattleConstants.h"
 
 
@@ -143,7 +143,7 @@ class MovementAnimation : public StackMoveAnimation
 private:
 private:
 	int moveSoundHandler; // sound handler used when moving a unit
 	int moveSoundHandler; // sound handler used when moving a unit
 
 
-	std::vector<BattleHex> destTiles; //full path, includes already passed hexes
+	BattleHexArray destTiles; //full path, includes already passed hexes
 	ui32 currentMoveIndex; // index of nextHex in destTiles
 	ui32 currentMoveIndex; // index of nextHex in destTiles
 
 
 	double begX, begY; // starting position
 	double begX, begY; // starting position
@@ -159,7 +159,7 @@ public:
 	bool init() override;
 	bool init() override;
 	void tick(uint32_t msPassed) override;
 	void tick(uint32_t msPassed) override;
 
 
-	MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
+	MovementAnimation(BattleInterface & owner, const CStack *_stack, BattleHexArray _destTiles, int _distance);
 	~MovementAnimation();
 	~MovementAnimation();
 };
 };
 
 
@@ -316,7 +316,7 @@ class EffectAnimation : public BattleAnimation
 
 
 	std::shared_ptr<CAnimation>	animation;
 	std::shared_ptr<CAnimation>	animation;
 	std::vector<Point> positions;
 	std::vector<Point> positions;
-	std::vector<BattleHex> battlehexes;
+	BattleHexArray battlehexes;
 
 
 	bool alignToBottom() const;
 	bool alignToBottom() const;
 	bool waitForSound() const;
 	bool waitForSound() const;
@@ -344,7 +344,7 @@ public:
 
 
 	/// Create animation positioned at certain hex(es)
 	/// Create animation positioned at certain hex(es)
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects = 0, bool reversed = false);
 
 
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
 	 ~EffectAnimation();
 	 ~EffectAnimation();

+ 41 - 41
client/battle/BattleFieldController.cpp

@@ -267,7 +267,7 @@ void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
 void BattleFieldController::redrawBackgroundWithHexes()
 void BattleFieldController::redrawBackgroundWithHexes()
 {
 {
 	const CStack *activeStack = owner.stacksController->getActiveStack();
 	const CStack *activeStack = owner.stacksController->getActiveStack();
-	std::vector<BattleHex> attackableHexes;
+	BattleHexArray attackableHexes;
 	if(activeStack)
 	if(activeStack)
 		occupiableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
 		occupiableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
 
 
@@ -280,8 +280,8 @@ void BattleFieldController::redrawBackgroundWithHexes()
 	// show shaded hexes for active's stack valid movement and the hexes that it can attack
 	// show shaded hexes for active's stack valid movement and the hexes that it can attack
 	if(settings["battle"]["stackRange"].Bool())
 	if(settings["battle"]["stackRange"].Bool())
 	{
 	{
-		std::vector<BattleHex> hexesToShade = occupiableHexes;
-		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
+		BattleHexArray hexesToShade = occupiableHexes;
+		hexesToShade.merge(attackableHexes);
 		for(BattleHex hex : hexesToShade)
 		for(BattleHex hex : hexesToShade)
 		{
 		{
 			showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
 			showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
@@ -312,9 +312,9 @@ void BattleFieldController::showHighlightedHex(Canvas & canvas, std::shared_ptr<
 		canvas.draw(cellBorder, hexPos);
 		canvas.draw(cellBorder, hexPos);
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
+BattleHexArray BattleFieldController::getHighlightedHexesForActiveStack()
 {
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 
 
 	if(!owner.stacksController->getActiveStack())
 	if(!owner.stacksController->getActiveStack())
 		return result;
 		return result;
@@ -324,16 +324,16 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
 
 
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
 
 
-	std::set<BattleHex> set = owner.getBattle()->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex);
+	BattleHexArray set = owner.getBattle()->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex);
 	for(BattleHex hex : set)
 	for(BattleHex hex : set)
 		result.insert(hex);
 		result.insert(hex);
 
 
 	return result;
 	return result;
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
+BattleHexArray BattleFieldController::getMovementRangeForHoveredStack()
 {
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 
 
 	if (!owner.stacksController->getActiveStack())
 	if (!owner.stacksController->getActiveStack())
 		return result;
 		return result;
@@ -344,16 +344,16 @@ std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
 	auto hoveredStack = getHoveredStack();
 	auto hoveredStack = getHoveredStack();
 	if(hoveredStack)
 	if(hoveredStack)
 	{
 	{
-		std::vector<BattleHex> v = owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
+		BattleHexArray v = owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
 		for(BattleHex hex : v)
 		for(BattleHex hex : v)
 			result.insert(hex);
 			result.insert(hex);
 	}
 	}
 	return result;
 	return result;
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
+BattleHexArray BattleFieldController::getHighlightedHexesForSpellRange()
 {
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
 
 
 	const spells::Caster *caster = nullptr;
 	const spells::Caster *caster = nullptr;
@@ -378,7 +378,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
 	return result;
 	return result;
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget()
+BattleHexArray BattleFieldController::getHighlightedHexesForMovementTarget()
 {
 {
 	const CStack * stack = owner.stacksController->getActiveStack();
 	const CStack * stack = owner.stacksController->getActiveStack();
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
@@ -386,7 +386,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 	if(!stack)
 	if(!stack)
 		return {};
 		return {};
 
 
-	std::vector<BattleHex> availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr);
+	BattleHexArray availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr);
 
 
 	auto hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 	auto hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 	if(owner.getBattle()->battleCanAttack(stack, hoveredStack, hoveredHex))
 	if(owner.getBattle()->battleCanAttack(stack, hoveredStack, hoveredHex))
@@ -402,7 +402,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 		}
 		}
 	}
 	}
 
 
-	if(vstd::contains(availableHexes, hoveredHex))
+	if(availableHexes.contains(hoveredHex))
 	{
 	{
 		if(stack->doubleWide())
 		if(stack->doubleWide())
 			return {hoveredHex, stack->occupiedHex(hoveredHex)};
 			return {hoveredHex, stack->occupiedHex(hoveredHex)};
@@ -412,7 +412,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 
 
 	if(stack->doubleWide())
 	if(stack->doubleWide())
 	{
 	{
-		for(auto const & hex : availableHexes)
+		for(auto hex : availableHexes)
 		{
 		{
 			if(stack->occupiedHex(hex) == hoveredHex)
 			if(stack->occupiedHex(hex) == hoveredHex)
 				return {hoveredHex, hex};
 				return {hoveredHex, hex};
@@ -424,9 +424,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 
 
 // Range limit highlight helpers
 // Range limit highlight helpers
 
 
-std::vector<BattleHex> BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t distance)
+BattleHexArray BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t distance)
 {
 {
-	std::vector<BattleHex> rangeHexes;
+	BattleHexArray rangeHexes;
 
 
 	if (!settings["battle"]["rangeLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
 	if (!settings["battle"]["rangeLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
 		return rangeHexes;
 		return rangeHexes;
@@ -436,27 +436,27 @@ std::vector<BattleHex> BattleFieldController::getRangeHexes(BattleHex sourceHex,
 	{
 	{
 		BattleHex hex(i);
 		BattleHex hex(i);
 		if(hex.isAvailable() && BattleHex::getDistance(sourceHex, hex) <= distance)
 		if(hex.isAvailable() && BattleHex::getDistance(sourceHex, hex) <= distance)
-			rangeHexes.push_back(hex);
+			rangeHexes.insert(hex);
 	}
 	}
 
 
 	return rangeHexes;
 	return rangeHexes;
 }
 }
 
 
-std::vector<BattleHex> BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, std::vector<BattleHex> rangeHexes, uint8_t distanceToLimit)
+BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray rangeHexes, uint8_t distanceToLimit)
 {
 {
-	std::vector<BattleHex> rangeLimitHexes;
+	BattleHexArray rangeLimitHexes;
 
 
 	// from range hexes get only the ones at the limit
 	// from range hexes get only the ones at the limit
 	for(auto & hex : rangeHexes)
 	for(auto & hex : rangeHexes)
 	{
 	{
 		if(BattleHex::getDistance(hoveredHex, hex) == distanceToLimit)
 		if(BattleHex::getDistance(hoveredHex, hex) == distanceToLimit)
-			rangeLimitHexes.push_back(hex);
+			rangeLimitHexes.insert(hex);
 	}
 	}
 
 
 	return rangeLimitHexes;
 	return rangeLimitHexes;
 }
 }
 
 
-bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit)
+bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit)
 {
 {
 	bool  hexInRangeLimit = false;
 	bool  hexInRangeLimit = false;
 
 
@@ -470,18 +470,18 @@ bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector<BattleH
 	return hexInRangeLimit;
 	return hexInRangeLimit;
 }
 }
 
 
-std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> wholeRangeHexes, std::vector<BattleHex> rangeLimitHexes)
+std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray wholeRangeHexes, BattleHexArray rangeLimitHexes)
 {
 {
 	std::vector<std::vector<BattleHex::EDir>> output;
 	std::vector<std::vector<BattleHex::EDir>> output;
 
 
 	if(wholeRangeHexes.empty())
 	if(wholeRangeHexes.empty())
 		return output;
 		return output;
 
 
-	for(auto & hex : rangeLimitHexes)
+	for(auto hex : rangeLimitHexes)
 	{
 	{
 		// get all neighbours and their directions
 		// get all neighbours and their directions
 		
 		
-		auto neighbouringTiles = hex.allNeighbouringTiles();
+		auto neighbouringTiles = BattleHexArray::generateAllNeighbouringTiles(hex);
 
 
 		std::vector<BattleHex::EDir> outsideNeighbourDirections;
 		std::vector<BattleHex::EDir> outsideNeighbourDirections;
 
 
@@ -525,9 +525,9 @@ std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangeLimitH
 	return output;
 	return output;
 }
 }
 
 
-void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights)
+void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights)
 {
 {
-		std::vector<BattleHex> rangeHexes = getRangeHexes(hoveredHex, distance);
+		BattleHexArray rangeHexes = getRangeHexes(hoveredHex, distance);
 		rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance);
 		rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance);
 		std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
 		std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
 		rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
 		rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
@@ -535,18 +535,18 @@ void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distan
 
 
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 {
 {
-	std::vector<BattleHex> rangedFullDamageLimitHexes;
-	std::vector<BattleHex> shootingRangeLimitHexes;
+	BattleHexArray rangedFullDamageLimitHexes;
+	BattleHexArray shootingRangeLimitHexes;
 
 
 	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighlights;
 	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighlights;
 	std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighlights;
 	std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighlights;
 
 
-	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
-	std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
-	std::set<BattleHex> hoveredMoveHexes  = getHighlightedHexesForMovementTarget();
+	BattleHexArray hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
+	BattleHexArray hoveredSpellHexes = getHighlightedHexesForSpellRange();
+	BattleHexArray hoveredMoveHexes  = getHighlightedHexesForMovementTarget();
 
 
 	BattleHex hoveredHex = getHoveredHex();
 	BattleHex hoveredHex = getHoveredHex();
-	std::set<BattleHex> hoveredMouseHex = hoveredHex.isValid() ? std::set<BattleHex>({ hoveredHex }) : std::set<BattleHex>();
+	BattleHexArray hoveredMouseHex = hoveredHex.isValid() ? BattleHexArray({ hoveredHex }) : BattleHexArray();
 
 
 	const CStack * hoveredStack = getHoveredStack();
 	const CStack * hoveredStack = getHoveredStack();
 	if(!hoveredStack && hoveredHex == BattleHex::INVALID)
 	if(!hoveredStack && hoveredHex == BattleHex::INVALID)
@@ -573,8 +573,8 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 
 
 	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex)
 	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex)
 	{
 	{
-		bool stackMovement = hoveredStackMovementRangeHexes.count(hex);
-		bool mouse = hoveredMouseHexes.count(hex);
+		bool stackMovement = hoveredStackMovementRangeHexes.contains(hex);
+		bool mouse = hoveredMouseHexes.contains(hex);
 
 
 		// calculate if hex is Ranged Full Damage Limit and its position in highlight array
 		// calculate if hex is Ranged Full Damage Limit and its position in highlight array
 		int hexIndexInRangedFullDamageLimit = 0;
 		int hexIndexInRangedFullDamageLimit = 0;
@@ -679,7 +679,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos)
 BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
 BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
 {
 {
 	const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
 	const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
-	auto neighbours = myNumber.allNeighbouringTiles();
+	auto neighbours = BattleHexArray::generateAllNeighbouringTiles(myNumber);
 	//   0 1
 	//   0 1
 	//  5 x 2
 	//  5 x 2
 	//   4 3
 	//   4 3
@@ -696,18 +696,18 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 
 
 		for (size_t i : { 1, 2, 3})
 		for (size_t i : { 1, 2, 3})
-			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
+			attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
 
 
 		for (size_t i : { 4, 5, 0})
 		for (size_t i : { 4, 5, 0})
-			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
+			attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::LEFT, false));
 
 
-		attackAvailability[6] = vstd::contains(occupiableHexes, neighbours[0]) && vstd::contains(occupiableHexes, neighbours[1]);
-		attackAvailability[7] = vstd::contains(occupiableHexes, neighbours[3]) && vstd::contains(occupiableHexes, neighbours[4]);
+		attackAvailability[6] = occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]);
+		attackAvailability[7] = occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]);
 	}
 	}
 	else
 	else
 	{
 	{
 		for (size_t i = 0; i < 6; ++i)
 		for (size_t i = 0; i < 6; ++i)
-			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]);
+			attackAvailability[i] = occupiableHexes.contains(neighbours[i]);
 
 
 		attackAvailability[6] = false;
 		attackAvailability[6] = false;
 		attackAvailability[7] = false;
 		attackAvailability[7] = false;

+ 141 - 141
client/battle/BattleFieldController.h

@@ -1,141 +1,141 @@
-/*
- * BattleFieldController.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../../lib/battle/BattleHex.h"
-#include "../../lib/Point.h"
-#include "../gui/CIntObject.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-class CStack;
-class Rect;
-VCMI_LIB_NAMESPACE_END
-
-class BattleHero;
-class CAnimation;
-class Canvas;
-class IImage;
-class BattleInterface;
-
-/// Handles battlefield grid as well as rendering of background layer of battle interface
-class BattleFieldController : public CIntObject
-{
-	BattleInterface & owner;
-
-	std::shared_ptr<IImage> background;
-	std::shared_ptr<IImage> cellBorder;
-	std::shared_ptr<IImage> cellUnitMovementHighlight;
-	std::shared_ptr<IImage> cellUnitMaxMovementHighlight;
-	std::shared_ptr<IImage> cellShade;
-	std::shared_ptr<CAnimation> rangedFullDamageLimitImages;
-	std::shared_ptr<CAnimation> shootingRangeLimitImages;
-
-	std::shared_ptr<CAnimation> attackCursors;
-	std::shared_ptr<CAnimation> spellCursors;
-
-	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
-	std::unique_ptr<Canvas> backgroundWithHexes;
-
-	/// direction which will be used to perform attack with current cursor position
-	Point currentAttackOriginPoint;
-
-	/// hex currently under mouse hover
-	BattleHex hoveredHex;
-
-	/// hexes to which currently active stack can move
-	std::vector<BattleHex> occupiableHexes;
-
-	/// hexes that when in front of a unit cause it's amount box to move back
-	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
-
-	void showHighlightedHex(Canvas & to, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder);
-
-	std::set<BattleHex> getHighlightedHexesForActiveStack();
-	std::set<BattleHex> getMovementRangeForHoveredStack();
-	std::set<BattleHex> getHighlightedHexesForSpellRange();
-	std::set<BattleHex> getHighlightedHexesForMovementTarget();
-
-	// Range limit highlight helpers
-
-	/// get all hexes within a certain distance of given hex
-	std::vector<BattleHex> getRangeHexes(BattleHex sourceHex, uint8_t distance);
-
-	/// get only hexes at the limit of a range
-	std::vector<BattleHex> getRangeLimitHexes(BattleHex hoveredHex, std::vector<BattleHex> hexRange, uint8_t distanceToLimit);
-
-	/// calculate if a hex is in range limit and return its index in range
-	bool IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit);
-
-	/// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists
-	std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangeHexes, std::vector<BattleHex> rangeLimitHexes);
-
-	/// calculates what image to use as range limit, depending on the direction of neighbors
-	/// a mask is used internally to mark the directions of all neighbours
-	/// based on this mask the corresponding image is selected
-	std::vector<std::shared_ptr<IImage>> calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
-
-	/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
-	void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights);
-
-	void showBackground(Canvas & canvas);
-	void showBackgroundImage(Canvas & canvas);
-	void showBackgroundImageWithHexes(Canvas & canvas);
-	void showHighlightedHexes(Canvas & canvas);
-	void updateAccessibleHexes();
-
-	BattleHex getHexAtPosition(Point hoverPosition);
-
-	/// Checks whether selected pixel is transparent, uses local coordinates of a hex
-	bool isPixelInHex(Point const & position);
-	size_t selectBattleCursor(BattleHex myNumber);
-
-	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
-	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
-	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
-	void clickPressed(const Point & cursorPosition) override;
-	void showPopupWindow(const Point & cursorPosition) override;
-	void activate() override;
-
-	void showAll(Canvas & to) override;
-	void show(Canvas & to) override;
-	void tick(uint32_t msPassed) override;
-
-	bool receiveEvent(const Point & position, int eventType) const override;
-
-public:
-	BattleFieldController(BattleInterface & owner);
-
-	void createHeroes();
-
-	void redrawBackgroundWithHexes();
-	void renderBattlefield(Canvas & canvas);
-
-	/// Returns position of hex relative to owner (BattleInterface)
-	Rect hexPositionLocal(BattleHex hex) const;
-
-	/// Returns position of hex relative to game window
-	Rect hexPositionAbsolute(BattleHex hex) const;
-
-	/// Returns ID of currently hovered hex or BattleHex::INVALID if none
-	BattleHex getHoveredHex();
-
-	/// Returns the currently hovered stack
-	const CStack* getHoveredStack();
-
-	/// returns true if selected tile can be attacked in melee by current stack
-	bool isTileAttackable(const BattleHex & number) const;
-
-	/// returns true if stack should render its stack count image in default position - outside own hex
-	bool stackCountOutsideHex(const BattleHex & number) const;
-
-	BattleHex::EDir selectAttackDirection(BattleHex myNumber);
-
-	BattleHex fromWhichHexAttack(BattleHex myNumber);
-};
+/*
+ * BattleFieldController.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/battle/BattleHexArray.h"
+#include "../../lib/Point.h"
+#include "../gui/CIntObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class CStack;
+class Rect;
+VCMI_LIB_NAMESPACE_END
+
+class BattleHero;
+class CAnimation;
+class Canvas;
+class IImage;
+class BattleInterface;
+
+/// Handles battlefield grid as well as rendering of background layer of battle interface
+class BattleFieldController : public CIntObject
+{
+	BattleInterface & owner;
+
+	std::shared_ptr<IImage> background;
+	std::shared_ptr<IImage> cellBorder;
+	std::shared_ptr<IImage> cellUnitMovementHighlight;
+	std::shared_ptr<IImage> cellUnitMaxMovementHighlight;
+	std::shared_ptr<IImage> cellShade;
+	std::shared_ptr<CAnimation> rangedFullDamageLimitImages;
+	std::shared_ptr<CAnimation> shootingRangeLimitImages;
+
+	std::shared_ptr<CAnimation> attackCursors;
+	std::shared_ptr<CAnimation> spellCursors;
+
+	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
+	std::unique_ptr<Canvas> backgroundWithHexes;
+
+	/// direction which will be used to perform attack with current cursor position
+	Point currentAttackOriginPoint;
+
+	/// hex currently under mouse hover
+	BattleHex hoveredHex;
+
+	/// hexes to which currently active stack can move
+	BattleHexArray occupiableHexes;
+
+	/// hexes that when in front of a unit cause it's amount box to move back
+	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
+
+	void showHighlightedHex(Canvas & to, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder);
+
+	BattleHexArray getHighlightedHexesForActiveStack();
+	BattleHexArray getMovementRangeForHoveredStack();
+	BattleHexArray getHighlightedHexesForSpellRange();
+	BattleHexArray getHighlightedHexesForMovementTarget();
+
+	// Range limit highlight helpers
+
+	/// get all hexes within a certain distance of given hex
+	BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance);
+
+	/// get only hexes at the limit of a range
+	BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit);
+
+	/// calculate if a hex is in range limit and return its index in range
+	bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit);
+
+	/// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists
+	std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes);
+
+	/// calculates what image to use as range limit, depending on the direction of neighbours
+	/// a mask is used internally to mark the directions of all neighbours
+	/// based on this mask the corresponding image is selected
+	std::vector<std::shared_ptr<IImage>> calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
+
+	/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
+	void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights);
+
+	void showBackground(Canvas & canvas);
+	void showBackgroundImage(Canvas & canvas);
+	void showBackgroundImageWithHexes(Canvas & canvas);
+	void showHighlightedHexes(Canvas & canvas);
+	void updateAccessibleHexes();
+
+	BattleHex getHexAtPosition(Point hoverPosition);
+
+	/// Checks whether selected pixel is transparent, uses local coordinates of a hex
+	bool isPixelInHex(Point const & position);
+	size_t selectBattleCursor(BattleHex myNumber);
+
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
+	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
+	void clickPressed(const Point & cursorPosition) override;
+	void showPopupWindow(const Point & cursorPosition) override;
+	void activate() override;
+
+	void showAll(Canvas & to) override;
+	void show(Canvas & to) override;
+	void tick(uint32_t msPassed) override;
+
+	bool receiveEvent(const Point & position, int eventType) const override;
+
+public:
+	BattleFieldController(BattleInterface & owner);
+
+	void createHeroes();
+
+	void redrawBackgroundWithHexes();
+	void renderBattlefield(Canvas & canvas);
+
+	/// Returns position of hex relative to owner (BattleInterface)
+	Rect hexPositionLocal(BattleHex hex) const;
+
+	/// Returns position of hex relative to game window
+	Rect hexPositionAbsolute(BattleHex hex) const;
+
+	/// Returns ID of currently hovered hex or BattleHex::INVALID if none
+	BattleHex getHoveredHex();
+
+	/// Returns the currently hovered stack
+	const CStack* getHoveredStack();
+
+	/// returns true if selected tile can be attacked in melee by current stack
+	bool isTileAttackable(const BattleHex & number) const;
+
+	/// returns true if stack should render its stack count image in default position - outside own hex
+	bool stackCountOutsideHex(const BattleHex & number) const;
+
+	BattleHex::EDir selectAttackDirection(BattleHex myNumber);
+
+	BattleHex fromWhichHexAttack(BattleHex myNumber);
+};

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -216,7 +216,7 @@ void BattleInterface::stackActivated(const CStack *stack)
 	stacksController->stackActivated(stack);
 	stacksController->stackActivated(stack);
 }
 }
 
 
-void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport)
+void BattleInterface::stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport)
 {
 {
 	if (teleport)
 	if (teleport)
 		stacksController->stackTeleported(stack, destHex, distance);
 		stacksController->stackTeleported(stack, destHex, distance);

+ 232 - 232
client/battle/BattleInterface.h

@@ -1,232 +1,232 @@
-/*
- * BattleInterface.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 "BattleConstants.h"
-#include "../gui/CIntObject.h"
-#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
-#include "../ConditionalWait.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CCreatureSet;
-class CGHeroInstance;
-class CStack;
-struct BattleResult;
-struct BattleSpellCast;
-struct CObstacleInstance;
-struct SetStackEffect;
-class BattleAction;
-class CGTownInstance;
-struct CatapultAttack;
-struct BattleTriggerEffect;
-struct BattleHex;
-struct InfoAboutHero;
-class ObstacleChanges;
-class CPlayerBattleCallback;
-
-VCMI_LIB_NAMESPACE_END
-
-class BattleHero;
-class Canvas;
-class BattleResultWindow;
-class StackQueue;
-class CPlayerInterface;
-struct BattleEffect;
-class IImage;
-class StackQueue;
-
-class BattleProjectileController;
-class BattleSiegeController;
-class BattleObstacleController;
-class BattleFieldController;
-class BattleRenderer;
-class BattleWindow;
-class BattleStacksController;
-class BattleActionsController;
-class BattleEffectsController;
-class BattleConsole;
-
-/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
-struct StackAttackedInfo
-{
-	const CStack *defender;
-	const CStack *attacker;
-
-	int64_t  damageDealt;
-	uint32_t amountKilled;
-	SpellID spellEffect;
-
-	bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
-	bool killed; //if true, stack has been killed
-	bool rebirth; //if true, play rebirth animation after all
-	bool cloneKilled;
-	bool fireShield;
-};
-
-struct StackAttackInfo
-{
-	const CStack *attacker;
-	const CStack *defender;
-	std::vector< const CStack *> secondaryDefender;
-
-	SpellID spellEffect;
-	BattleHex tile;
-
-	bool indirectAttack;
-	bool lucky;
-	bool unlucky;
-	bool deathBlow;
-	bool lifeDrain;
-};
-
-/// Main class for battles, responsible for relaying information from server to various battle entities
-class BattleInterface
-{
-	using AwaitingAnimationAction = std::function<void()>;
-
-	struct AwaitingAnimationEvents {
-		AwaitingAnimationAction action;
-		EAnimationEvents event;
-	};
-
-	/// Conditional variables that are set depending on ongoing animations on the battlefield
-	ConditionalWait ongoingAnimationsState;
-
-	/// List of events that are waiting to be triggered
-	std::vector<AwaitingAnimationEvents> awaitingEvents;
-
-	/// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
-	std::shared_ptr<CPlayerInterface> tacticianInterface;
-
-	/// attacker interface, not null if attacker is human in our vcmiclient
-	std::shared_ptr<CPlayerInterface> attackerInt;
-
-	/// defender interface, not null if attacker is human in our vcmiclient
-	std::shared_ptr<CPlayerInterface> defenderInt;
-
-	/// if set to true, battle is still starting and waiting for intro sound to end / key press from player
-	bool battleOpeningDelayActive;
-
-	/// ID of ongoing battle
-	BattleID battleID;
-
-	void playIntroSoundAndUnlockInterface();
-	void onIntroSoundPlayed();
-public:
-	/// copy of initial armies (for result window)
-	const CCreatureSet *army1;
-	const CCreatureSet *army2;
-
-	std::shared_ptr<BattleWindow> windowObject;
-	std::shared_ptr<BattleConsole> console;
-
-	/// currently active player interface
-	std::shared_ptr<CPlayerInterface> curInt;
-
-	const CGHeroInstance *attackingHeroInstance;
-	const CGHeroInstance *defendingHeroInstance;
-
-	bool tacticsMode;
-	ui32 round;
-
-	std::unique_ptr<BattleProjectileController> projectilesController;
-	std::unique_ptr<BattleSiegeController> siegeController;
-	std::unique_ptr<BattleObstacleController> obstacleController;
-	std::unique_ptr<BattleFieldController> fieldController;
-	std::unique_ptr<BattleStacksController> stacksController;
-	std::unique_ptr<BattleActionsController> actionsController;
-	std::unique_ptr<BattleEffectsController> effectsController;
-
-	std::shared_ptr<BattleHero> attackingHero;
-	std::shared_ptr<BattleHero> defendingHero;
-
-	bool openingPlaying() const;
-	void openingEnd();
-
-	bool makingTurn() const;
-
-	BattleID getBattleID() const;
-	std::shared_ptr<CPlayerBattleCallback> getBattle() const;
-
-	BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
-	~BattleInterface();
-
-	void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
-	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
-	void requestAutofightingAIToTakeAction();
-
-	void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE);
-	void sendCommand(BattleAction command, const CStack * actor = nullptr);
-
-	const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
-
-	void showInterface(Canvas & to);
-
-	void setHeroAnimation(BattleSide side, EHeroAnimType phase);
-
-	void executeSpellCast(); //called when a hero casts a spell
-
-	void appendBattleLog(const std::string & newEntry);
-
-	void redrawBattlefield(); //refresh GUI after changing stack range / grid settings
-	CPlayerInterface *getCurrentPlayerInterface() const;
-
-	void tacticNextStack(const CStack *current);
-	void tacticPhaseEnd();
-
-	void setBattleQueueVisibility(bool visible);
-	void setStickyHeroWindowsVisibility(bool visible);
-	void setStickyQuickSpellWindowVisibility(bool visible);
-
-	void endNetwork();
-	void executeStagedAnimations();
-	void executeAnimationStage( EAnimationEvents event);
-	void onAnimationsStarted();
-	void onAnimationsFinished();
-	void waitForAnimations();
-	bool hasAnimations();
-	void checkForAnimations();
-	void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
-
-	//call-ins
-	void startAction(const BattleAction & action);
-	void stackReset(const CStack * stack);
-	void stackAdded(const CStack * stack); //new stack appeared on battlefield
-	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
-	void stackActivated(const CStack *stack); //active stack has been changed
-	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport); //stack with id number moved to destHex
-	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
-	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
-	void newRoundFirst();
-	void newRound(); //called when round is ended;
-	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
-	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
-	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
-	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
-	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
-
-	void displayBattleLog(const std::vector<MetaString> & battleLog);
-
-	void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
-	void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation
-	void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
-	void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
-
-	void endAction(const BattleAction & action);
-
-	void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
-	void obstacleRemoved(const std::vector<ObstacleChanges> & obstacles);
-
-	void gateStateChanged(const EGateState state);
-
-	const CGHeroInstance *currentHero() const;
-	InfoAboutHero enemyHero() const;
-};
+/*
+ * BattleInterface.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 "BattleConstants.h"
+#include "../gui/CIntObject.h"
+#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
+#include "../ConditionalWait.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CCreatureSet;
+class CGHeroInstance;
+class CStack;
+struct BattleResult;
+struct BattleSpellCast;
+struct CObstacleInstance;
+struct SetStackEffect;
+class BattleAction;
+class CGTownInstance;
+struct CatapultAttack;
+struct BattleTriggerEffect;
+struct BattleHex;
+struct InfoAboutHero;
+class ObstacleChanges;
+class CPlayerBattleCallback;
+
+VCMI_LIB_NAMESPACE_END
+
+class BattleHero;
+class Canvas;
+class BattleResultWindow;
+class StackQueue;
+class CPlayerInterface;
+struct BattleEffect;
+class IImage;
+class StackQueue;
+
+class BattleProjectileController;
+class BattleSiegeController;
+class BattleObstacleController;
+class BattleFieldController;
+class BattleRenderer;
+class BattleWindow;
+class BattleStacksController;
+class BattleActionsController;
+class BattleEffectsController;
+class BattleConsole;
+
+/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
+struct StackAttackedInfo
+{
+	const CStack *defender;
+	const CStack *attacker;
+
+	int64_t  damageDealt;
+	uint32_t amountKilled;
+	SpellID spellEffect;
+
+	bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
+	bool killed; //if true, stack has been killed
+	bool rebirth; //if true, play rebirth animation after all
+	bool cloneKilled;
+	bool fireShield;
+};
+
+struct StackAttackInfo
+{
+	const CStack *attacker;
+	const CStack *defender;
+	std::vector< const CStack *> secondaryDefender;
+
+	SpellID spellEffect;
+	BattleHex tile;
+
+	bool indirectAttack;
+	bool lucky;
+	bool unlucky;
+	bool deathBlow;
+	bool lifeDrain;
+};
+
+/// Main class for battles, responsible for relaying information from server to various battle entities
+class BattleInterface
+{
+	using AwaitingAnimationAction = std::function<void()>;
+
+	struct AwaitingAnimationEvents {
+		AwaitingAnimationAction action;
+		EAnimationEvents event;
+	};
+
+	/// Conditional variables that are set depending on ongoing animations on the battlefield
+	ConditionalWait ongoingAnimationsState;
+
+	/// List of events that are waiting to be triggered
+	std::vector<AwaitingAnimationEvents> awaitingEvents;
+
+	/// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
+	std::shared_ptr<CPlayerInterface> tacticianInterface;
+
+	/// attacker interface, not null if attacker is human in our vcmiclient
+	std::shared_ptr<CPlayerInterface> attackerInt;
+
+	/// defender interface, not null if attacker is human in our vcmiclient
+	std::shared_ptr<CPlayerInterface> defenderInt;
+
+	/// if set to true, battle is still starting and waiting for intro sound to end / key press from player
+	bool battleOpeningDelayActive;
+
+	/// ID of ongoing battle
+	BattleID battleID;
+
+	void playIntroSoundAndUnlockInterface();
+	void onIntroSoundPlayed();
+public:
+	/// copy of initial armies (for result window)
+	const CCreatureSet *army1;
+	const CCreatureSet *army2;
+
+	std::shared_ptr<BattleWindow> windowObject;
+	std::shared_ptr<BattleConsole> console;
+
+	/// currently active player interface
+	std::shared_ptr<CPlayerInterface> curInt;
+
+	const CGHeroInstance *attackingHeroInstance;
+	const CGHeroInstance *defendingHeroInstance;
+
+	bool tacticsMode;
+	ui32 round;
+
+	std::unique_ptr<BattleProjectileController> projectilesController;
+	std::unique_ptr<BattleSiegeController> siegeController;
+	std::unique_ptr<BattleObstacleController> obstacleController;
+	std::unique_ptr<BattleFieldController> fieldController;
+	std::unique_ptr<BattleStacksController> stacksController;
+	std::unique_ptr<BattleActionsController> actionsController;
+	std::unique_ptr<BattleEffectsController> effectsController;
+
+	std::shared_ptr<BattleHero> attackingHero;
+	std::shared_ptr<BattleHero> defendingHero;
+
+	bool openingPlaying() const;
+	void openingEnd();
+
+	bool makingTurn() const;
+
+	BattleID getBattleID() const;
+	std::shared_ptr<CPlayerBattleCallback> getBattle() const;
+
+	BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
+	~BattleInterface();
+
+	void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
+	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
+	void requestAutofightingAIToTakeAction();
+
+	void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE);
+	void sendCommand(BattleAction command, const CStack * actor = nullptr);
+
+	const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
+
+	void showInterface(Canvas & to);
+
+	void setHeroAnimation(BattleSide side, EHeroAnimType phase);
+
+	void executeSpellCast(); //called when a hero casts a spell
+
+	void appendBattleLog(const std::string & newEntry);
+
+	void redrawBattlefield(); //refresh GUI after changing stack range / grid settings
+	CPlayerInterface *getCurrentPlayerInterface() const;
+
+	void tacticNextStack(const CStack *current);
+	void tacticPhaseEnd();
+
+	void setBattleQueueVisibility(bool visible);
+	void setStickyHeroWindowsVisibility(bool visible);
+	void setStickyQuickSpellWindowVisibility(bool visible);
+
+	void endNetwork();
+	void executeStagedAnimations();
+	void executeAnimationStage( EAnimationEvents event);
+	void onAnimationsStarted();
+	void onAnimationsFinished();
+	void waitForAnimations();
+	bool hasAnimations();
+	void checkForAnimations();
+	void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
+
+	//call-ins
+	void startAction(const BattleAction & action);
+	void stackReset(const CStack * stack);
+	void stackAdded(const CStack * stack); //new stack appeared on battlefield
+	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
+	void stackActivated(const CStack *stack); //active stack has been changed
+	void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex
+	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
+	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
+	void newRoundFirst();
+	void newRound(); //called when round is ended;
+	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
+	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
+	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
+	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
+	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
+
+	void displayBattleLog(const std::vector<MetaString> & battleLog);
+
+	void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
+	void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation
+	void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
+	void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
+
+	void endAction(const BattleAction & action);
+
+	void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
+	void obstacleRemoved(const std::vector<ObstacleChanges> & obstacles);
+
+	void gateStateChanged(const EGateState state);
+
+	const CGHeroInstance *currentHero() const;
+	InfoAboutHero enemyHero() const;
+};

+ 2 - 2
client/battle/BattleStacksController.cpp

@@ -491,7 +491,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 	owner.waitForAnimations();
 	owner.waitForAnimations();
 }
 }
 
 
-void BattleStacksController::stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance)
+void BattleStacksController::stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance)
 {
 {
 	assert(destHex.size() > 0);
 	assert(destHex.size() > 0);
 	//owner.checkForAnimations(); // NOTE: at this point spellcast animations were added, but not executed
 	//owner.checkForAnimations(); // NOTE: at this point spellcast animations were added, but not executed
@@ -508,7 +508,7 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector<Ba
 	// animations will be executed by spell
 	// animations will be executed by spell
 }
 }
 
 
-void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
+void BattleStacksController::stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance)
 {
 {
 	assert(destHex.size() > 0);
 	assert(destHex.size() > 0);
 	owner.checkForAnimations();
 	owner.checkForAnimations();

+ 149 - 148
client/battle/BattleStacksController.h

@@ -1,148 +1,149 @@
-/*
- * BattleStacksController.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 "../render/ColorFilter.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct BattleHex;
-class BattleAction;
-class CStack;
-class CSpell;
-class SpellID;
-class Point;
-
-VCMI_LIB_NAMESPACE_END
-
-struct StackAttackedInfo;
-struct StackAttackInfo;
-
-class ColorFilter;
-class Canvas;
-class BattleInterface;
-class BattleAnimation;
-class CreatureAnimation;
-class BattleAnimation;
-class BattleRenderer;
-class IImage;
-
-struct BattleStackFilterEffect
-{
-	ColorFilter effect;
-	const CStack * target;
-	const CSpell * source;
-	bool persistent;
-};
-
-/// Class responsible for handling stacks in battle
-/// Handles ordering of stacks animation
-/// As well as rendering of stacks, their amount boxes
-/// And any other effect applied to stacks
-class BattleStacksController
-{
-	BattleInterface & owner;
-
-	std::shared_ptr<IImage> amountNormal;
-	std::shared_ptr<IImage> amountNegative;
-	std::shared_ptr<IImage> amountPositive;
-	std::shared_ptr<IImage> amountEffNeutral;
-
-	/// currently displayed animations <anim, initialized>
-	std::vector<BattleAnimation *> currentAnimations;
-
-	/// currently active color effects on stacks, in order of their addition (which corresponds to their apply order)
-	std::vector<BattleStackFilterEffect> stackFilterEffects;
-
-	/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
-	std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
-
-	/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
-	std::map<int, bool> stackFacingRight;
-
-	/// Stacks have amount box hidden due to ongoing animations
-	std::set<int> stackAmountBoxHidden;
-
-	/// currently active stack; nullptr - no one
-	const CStack *activeStack;
-
-	/// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation
-	std::vector<const CStack *> mouseHoveredStacks;
-
-	///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
-	const CStack *stackToActivate;
-
-	/// for giving IDs for animations
-	ui32 animIDhelper;
-
-	bool stackNeedsAmountBox(const CStack * stack) const;
-	void showStackAmountBox(Canvas & canvas, const CStack * stack);
-	BattleHex getStackCurrentPosition(const CStack * stack) const;
-
-	std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
-
-	void removeExpiredColorFilters();
-
-	void initializeBattleAnimations();
-	void tickFrameBattleAnimations(uint32_t msPassed);
-
-	void updateBattleAnimations(uint32_t msPassed);
-
-	std::vector<const CStack *> selectHoveredStacks();
-
-	bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender);
-
-public:
-	BattleStacksController(BattleInterface & owner);
-
-	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const;
-	bool facingRight(const CStack * stack) const;
-
-	void stackReset(const CStack * stack);
-	void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield
-	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
-	void stackActivated(const CStack *stack); //active stack has been changed
-	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
-	void stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
-	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
-	void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
-
-	void startAction(const BattleAction & action);
-	void endAction(const BattleAction & action);
-
-	void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
-
-	void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack
-
-	void setActiveStack(const CStack *stack);
-
-	void showAliveStack(Canvas & canvas, const CStack * stack);
-	void showStack(Canvas & canvas, const CStack * stack);
-
-	void updateHoveredStacks();
-
-	void collectRenderableObjects(BattleRenderer & renderer);
-
-	/// Adds new color filter effect targeting stack
-	/// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
-	/// If effect from same (target, source) already exists, it will be updated
-	void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent);
-	void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
-
-	const CStack* getActiveStack() const;
-	const std::vector<uint32_t> getHoveredStacksUnitIds() const;
-
-	void tick(uint32_t msPassed);
-
-	/// returns position of animation needed to place stack in specific hex
-	Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;
-
-	friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
-};
+/*
+ * BattleStacksController.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 "../render/ColorFilter.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct BattleHex;
+class BattleHexArray;
+class BattleAction;
+class CStack;
+class CSpell;
+class SpellID;
+class Point;
+
+VCMI_LIB_NAMESPACE_END
+
+struct StackAttackedInfo;
+struct StackAttackInfo;
+
+class ColorFilter;
+class Canvas;
+class BattleInterface;
+class BattleAnimation;
+class CreatureAnimation;
+class BattleAnimation;
+class BattleRenderer;
+class IImage;
+
+struct BattleStackFilterEffect
+{
+	ColorFilter effect;
+	const CStack * target;
+	const CSpell * source;
+	bool persistent;
+};
+
+/// Class responsible for handling stacks in battle
+/// Handles ordering of stacks animation
+/// As well as rendering of stacks, their amount boxes
+/// And any other effect applied to stacks
+class BattleStacksController
+{
+	BattleInterface & owner;
+
+	std::shared_ptr<IImage> amountNormal;
+	std::shared_ptr<IImage> amountNegative;
+	std::shared_ptr<IImage> amountPositive;
+	std::shared_ptr<IImage> amountEffNeutral;
+
+	/// currently displayed animations <anim, initialized>
+	std::vector<BattleAnimation *> currentAnimations;
+
+	/// currently active color effects on stacks, in order of their addition (which corresponds to their apply order)
+	std::vector<BattleStackFilterEffect> stackFilterEffects;
+
+	/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
+	std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
+
+	/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
+	std::map<int, bool> stackFacingRight;
+
+	/// Stacks have amount box hidden due to ongoing animations
+	std::set<int> stackAmountBoxHidden;
+
+	/// currently active stack; nullptr - no one
+	const CStack *activeStack;
+
+	/// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation
+	std::vector<const CStack *> mouseHoveredStacks;
+
+	///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
+	const CStack *stackToActivate;
+
+	/// for giving IDs for animations
+	ui32 animIDhelper;
+
+	bool stackNeedsAmountBox(const CStack * stack) const;
+	void showStackAmountBox(Canvas & canvas, const CStack * stack);
+	BattleHex getStackCurrentPosition(const CStack * stack) const;
+
+	std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
+
+	void removeExpiredColorFilters();
+
+	void initializeBattleAnimations();
+	void tickFrameBattleAnimations(uint32_t msPassed);
+
+	void updateBattleAnimations(uint32_t msPassed);
+
+	std::vector<const CStack *> selectHoveredStacks();
+
+	bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender);
+
+public:
+	BattleStacksController(BattleInterface & owner);
+
+	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const;
+	bool facingRight(const CStack * stack) const;
+
+	void stackReset(const CStack * stack);
+	void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield
+	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
+	void stackActivated(const CStack *stack); //active stack has been changed
+	void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex
+	void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex
+	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
+	void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
+
+	void startAction(const BattleAction & action);
+	void endAction(const BattleAction & action);
+
+	void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
+
+	void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack
+
+	void setActiveStack(const CStack *stack);
+
+	void showAliveStack(Canvas & canvas, const CStack * stack);
+	void showStack(Canvas & canvas, const CStack * stack);
+
+	void updateHoveredStacks();
+
+	void collectRenderableObjects(BattleRenderer & renderer);
+
+	/// Adds new color filter effect targeting stack
+	/// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
+	/// If effect from same (target, source) already exists, it will be updated
+	void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent);
+	void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
+
+	const CStack* getActiveStack() const;
+	const std::vector<uint32_t> getHoveredStacksUnitIds() const;
+
+	void tick(uint32_t msPassed);
+
+	/// returns position of animation needed to place stack in specific hex
+	Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;
+
+	friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
+};

+ 10 - 10
client/mapView/MapRenderer.cpp

@@ -34,14 +34,14 @@
 #include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/pathfinder/CGPathNode.h"
 #include "../../lib/pathfinder/CGPathNode.h"
 
 
-struct NeighborTilesInfo
+struct neighbourTilesInfo
 {
 {
 	//567
 	//567
 	//3 4
 	//3 4
 	//012
 	//012
 	std::bitset<8> d;
 	std::bitset<8> d;
 
 
-	NeighborTilesInfo(IMapRendererContext & context, const int3 & pos)
+	neighbourTilesInfo(IMapRendererContext & context, const int3 & pos)
 	{
 	{
 		auto checkTile = [&](int dx, int dy)
 		auto checkTile = [&](int dx, int dy)
 		{
 		{
@@ -340,9 +340,9 @@ void MapRendererFow::renderTile(IMapRendererContext & context, Canvas & target,
 {
 {
 	assert(!context.isVisible(coordinates));
 	assert(!context.isVisible(coordinates));
 
 
-	const NeighborTilesInfo neighborInfo(context, coordinates);
+	const neighbourTilesInfo neighbourInfo(context, coordinates);
 
 
-	int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide
+	int retBitmapID = neighbourInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide
 	if(retBitmapID < 0)
 	if(retBitmapID < 0)
 	{
 	{
 		// generate a number that is predefined for each tile,
 		// generate a number that is predefined for each tile,
@@ -367,8 +367,8 @@ uint8_t MapRendererFow::checksum(IMapRendererContext & context, const int3 & coo
 	if (context.showSpellRange(coordinates))
 	if (context.showSpellRange(coordinates))
 		return 0xff - 2;
 		return 0xff - 2;
 
 
-	const NeighborTilesInfo neighborInfo(context, coordinates);
-	int retBitmapID = neighborInfo.getBitmapID();
+	const neighbourTilesInfo neighbourInfo(context, coordinates);
+	int retBitmapID = neighbourInfo.getBitmapID();
 	if(retBitmapID < 0)
 	if(retBitmapID < 0)
 		return 0xff - 1;
 		return 0xff - 1;
 	return retBitmapID;
 	return retBitmapID;
@@ -738,9 +738,9 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con
 		return result;
 		return result;
 	}
 	}
 
 
-	const NeighborTilesInfo neighborInfo(context, coordinates);
+	const neighbourTilesInfo neighbourInfo(context, coordinates);
 
 
-	if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
+	if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden())
 	{
 	{
 		result[7] = rendererFow.checksum(context, coordinates);
 		result[7] = rendererFow.checksum(context, coordinates);
 	}
 	}
@@ -769,9 +769,9 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con
 		return;
 		return;
 	}
 	}
 
 
-	const NeighborTilesInfo neighborInfo(context, coordinates);
+	const neighbourTilesInfo neighbourInfo(context, coordinates);
 
 
-	if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
+	if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden())
 	{
 	{
 		rendererFow.renderTile(context, target, coordinates);
 		rendererFow.renderTile(context, target, coordinates);
 	}
 	}

+ 1 - 1
client/renderSDL/SDL_Extensions.cpp

@@ -674,7 +674,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
 	switch (algorithm)
 	switch (algorithm)
 	{
 	{
 		case EScalingAlgorithm::NEAREST:
 		case EScalingAlgorithm::NEAREST:
-			xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
+			xbrz::nearestneighbourScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
 			break;
 			break;
 		case EScalingAlgorithm::BILINEAR:
 		case EScalingAlgorithm::BILINEAR:
 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);

+ 2 - 2
client/xBRZ/xbrz.cpp

@@ -1273,10 +1273,10 @@ void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
 }
 }
 
 
 
 
-void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
+void xbrz::nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight,
                                 /**/  uint32_t* trg, int trgWidth, int trgHeight)
                                 /**/  uint32_t* trg, int trgWidth, int trgHeight)
 {
 {
-    nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
+    nearestneighbourScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t),
                          trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
                          trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t),
     0, trgHeight, [](uint32_t pix) { return pix; });
     0, trgHeight, [](uint32_t pix) { return pix; });
 }
 }

+ 1 - 1
client/xBRZ/xbrz.h

@@ -69,7 +69,7 @@ void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX
 void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
 void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
                    /**/  uint32_t* trg, int trgWidth, int trgHeight);
                    /**/  uint32_t* trg, int trgWidth, int trgHeight);
 
 
-void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
+void nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight,
                           /**/  uint32_t* trg, int trgWidth, int trgHeight);
                           /**/  uint32_t* trg, int trgWidth, int trgHeight);
 
 
 
 

+ 4 - 4
client/xBRZ/xbrz_tools.h

@@ -67,9 +67,9 @@ void fillBlock(Pix* trg, int pitch /*[bytes]*/, Pix col, int blockWidth, int blo
 }
 }
 
 
 
 
-//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!)
+//nearest-neighbour (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!)
 template <class PixSrc, class PixTrg, class PixConverter>
 template <class PixSrc, class PixTrg, class PixConverter>
-void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
+void nearestneighbourScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
                           /**/  PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
                           /**/  PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
                           int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
                           int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
 {
 {
@@ -104,9 +104,9 @@ void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int sr
 }
 }
 
 
 
 
-//nearest-neighbor (going over source image - fast for upscaling, since source is read only once
+//nearest-neighbour (going over source image - fast for upscaling, since source is read only once
 template <class PixSrc, class PixTrg, class PixConverter>
 template <class PixSrc, class PixTrg, class PixConverter>
-void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
+void nearestneighbourScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
                                     /**/  PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
                                     /**/  PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
                                     int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
                                     int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
 {
 {

+ 104 - 103
include/vstd/RNG.h

@@ -1,103 +1,104 @@
-/*
- * RNG.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
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace vstd
-{
-
-class DLL_LINKAGE RNG
-{
-public:
-	virtual ~RNG() = default;
-
-	/// Returns random number in range [lower, upper]
-	virtual int nextInt(int lower, int upper) = 0;
-
-	/// Returns random number in range [lower, upper]
-	virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0;
-
-	/// Returns random number in range [lower, upper]
-	virtual double nextDouble(double lower, double upper) = 0;
-
-	/// Returns random number in range [0, upper]
-	virtual int nextInt(int upper) = 0;
-
-	/// Returns random number in range [0, upper]
-	virtual int64_t nextInt64(int64_t upper) = 0;
-
-	/// Returns random number in range [0, upper]
-	virtual double nextDouble(double upper) = 0;
-
-	/// Generates an integer between 0 and the maximum value it can hold.
-	/// Should be only used for seeding other generators
-	virtual int nextInt() = 0;
-
-	/// Returns integer using binomial distribution
-	/// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts
-	virtual int nextBinomialInt(int coinsCount, double coinChance) = 0;
-};
-
-}
-
-namespace RandomGeneratorUtil
-{
-	template<typename Container>
-	auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
-	{
-		if(container.empty())
-			throw std::runtime_error("Unable to select random item from empty container!");
-
-		return std::next(container.begin(), rand.nextInt64(0, container.size() - 1));
-	}
-
-	template<typename Container>
-	auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
-	{
-		if(container.empty())
-			throw std::runtime_error("Unable to select random item from empty container!");
-
-		return std::next(container.begin(), rand.nextInt64(0, container.size() - 1));
-	}
-
-	template<typename Container>
-	size_t nextItemWeighted(Container & container, vstd::RNG & rand)
-	{
-		assert(!container.empty());
-
-		int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0);
-		assert(totalWeight > 0);
-
-		int64_t roll = rand.nextInt64(0, totalWeight - 1);
-
-		for (size_t i = 0; i < container.size(); ++i)
-		{
-			roll -= container[i];
-			if(roll < 0)
-				return i;
-		}
-		return container.size() - 1;
-	}
-
-	template<typename T>
-	void randomShuffle(std::vector<T> & container, vstd::RNG & rand)
-	{
-		int64_t n = (container.end() - container.begin());
-
-		for(int64_t i = n-1; i>0; --i)
-		{
-			std::swap(container.begin()[i],container.begin()[rand.nextInt64(0, i)]);
-		}
-	}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * RNG.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace vstd
+{
+
+class DLL_LINKAGE RNG
+{
+public:
+	virtual ~RNG() = default;
+
+	/// Returns random number in range [lower, upper]
+	virtual int nextInt(int lower, int upper) = 0;
+
+	/// Returns random number in range [lower, upper]
+	virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0;
+
+	/// Returns random number in range [lower, upper]
+	virtual double nextDouble(double lower, double upper) = 0;
+
+	/// Returns random number in range [0, upper]
+	virtual int nextInt(int upper) = 0;
+
+	/// Returns random number in range [0, upper]
+	virtual int64_t nextInt64(int64_t upper) = 0;
+
+	/// Returns random number in range [0, upper]
+	virtual double nextDouble(double upper) = 0;
+
+	/// Generates an integer between 0 and the maximum value it can hold.
+	/// Should be only used for seeding other generators
+	virtual int nextInt() = 0;
+
+	/// Returns integer using binomial distribution
+	/// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts
+	virtual int nextBinomialInt(int coinsCount, double coinChance) = 0;
+};
+
+}
+
+namespace RandomGeneratorUtil
+{
+	template<typename Container>
+	auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
+	{
+		if(container.empty())
+			throw std::runtime_error("Unable to select random item from empty container!");
+
+		return std::next(container.begin(), rand.nextInt64(0, container.size() - 1));
+	}
+
+	template<typename Container>
+	auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
+	{
+		if(container.empty())
+			throw std::runtime_error("Unable to select random item from empty container!");
+
+		return std::next(container.begin(), rand.nextInt64(0, container.size() - 1));
+	}
+
+	template<typename Container>
+	size_t nextItemWeighted(Container & container, vstd::RNG & rand)
+	{
+		assert(!container.empty());
+
+		int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0);
+		assert(totalWeight > 0);
+
+		int64_t roll = rand.nextInt64(0, totalWeight - 1);
+
+		for (size_t i = 0; i < container.size(); ++i)
+		{
+			roll -= container[i];
+			if(roll < 0)
+				return i;
+		}
+		return container.size() - 1;
+	}
+
+	template<typename Container>
+	void randomShuffle(Container & container, vstd::RNG & rand)
+	{
+		int64_t n = std::distance(container.begin(), container.end());
+
+		for(int64_t i = n - 1; i > 0; --i)
+		{
+			auto randIndex = rand.nextInt64(0, i);
+			std::swap(*(container.begin() + i), *(container.begin() + randIndex));
+		}
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 101 - 101
lib/BattleFieldHandler.cpp

@@ -1,101 +1,101 @@
-/*
- * BattleFieldHandler.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 <vcmi/Entity.h>
-#include "BattleFieldHandler.h"
-#include "json/JsonBonus.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-std::shared_ptr<BattleFieldInfo> BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)
-{
-	assert(identifier.find(':') == std::string::npos);
-
-	auto info = std::make_shared<BattleFieldInfo>(BattleField(index), identifier);
-
-	info->modScope = scope;
-	info->graphics = ImagePath::fromJson(json["graphics"]);
-	info->icon = json["icon"].String();
-	info->name = json["name"].String();
-	for(const auto & b : json["bonuses"].Vector())
-	{
-		auto bonus = JsonUtils::parseBonus(b);
-
-		bonus->source = BonusSource::TERRAIN_OVERLAY;
-		bonus->sid = BonusSourceID(info->getId());
-		bonus->duration = BonusDuration::ONE_BATTLE;
-
-		info->bonuses.push_back(bonus);
-	}
-
-	info->isSpecial = json["isSpecial"].Bool();
-	for(auto node : json["impassableHexes"].Vector())
-		info->impassableHexes.emplace_back(node.Integer());
-
-	info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]);
-	info->musicFilename = AudioPath::fromJson(json["music"]);
-
-	return info;
-}
-
-std::vector<JsonNode> BattleFieldHandler::loadLegacyData()
-{
-	return std::vector<JsonNode>();
-}
-
-const std::vector<std::string> & BattleFieldHandler::getTypeNames() const
-{
-	static const auto types = std::vector<std::string> { "battlefield" };
-
-	return types;
-}
-
-int32_t BattleFieldInfo::getIndex() const
-{
-	return battlefield.getNum();
-}
-
-int32_t BattleFieldInfo::getIconIndex() const
-{
-	return iconIndex;
-}
-
-std::string BattleFieldInfo::getJsonKey() const
-{
-	return modScope + ':' + identifier;
-}
-
-std::string BattleFieldInfo::getModScope() const
-{
-	return modScope;
-}
-
-std::string BattleFieldInfo::getNameTextID() const
-{
-	return name;
-}
-
-std::string BattleFieldInfo::getNameTranslated() const
-{
-	return name; // TODO?
-}
-
-void BattleFieldInfo::registerIcons(const IconRegistar & cb) const
-{
-	//cb(getIconIndex(), "BATTLEFIELD", icon);
-}
-
-BattleField BattleFieldInfo::getId() const
-{
-	return battlefield;
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * BattleFieldHandler.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 <vcmi/Entity.h>
+#include "BattleFieldHandler.h"
+#include "json/JsonBonus.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+std::shared_ptr<BattleFieldInfo> BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)
+{
+	assert(identifier.find(':') == std::string::npos);
+
+	auto info = std::make_shared<BattleFieldInfo>(BattleField(index), identifier);
+
+	info->modScope = scope;
+	info->graphics = ImagePath::fromJson(json["graphics"]);
+	info->icon = json["icon"].String();
+	info->name = json["name"].String();
+	for(const auto & b : json["bonuses"].Vector())
+	{
+		auto bonus = JsonUtils::parseBonus(b);
+
+		bonus->source = BonusSource::TERRAIN_OVERLAY;
+		bonus->sid = BonusSourceID(info->getId());
+		bonus->duration = BonusDuration::ONE_BATTLE;
+
+		info->bonuses.push_back(bonus);
+	}
+
+	info->isSpecial = json["isSpecial"].Bool();
+	for(auto node : json["impassableHexes"].Vector())
+		info->impassableHexes.insert(node.Integer());
+
+	info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]);
+	info->musicFilename = AudioPath::fromJson(json["music"]);
+
+	return info;
+}
+
+std::vector<JsonNode> BattleFieldHandler::loadLegacyData()
+{
+	return std::vector<JsonNode>();
+}
+
+const std::vector<std::string> & BattleFieldHandler::getTypeNames() const
+{
+	static const auto types = std::vector<std::string> { "battlefield" };
+
+	return types;
+}
+
+int32_t BattleFieldInfo::getIndex() const
+{
+	return battlefield.getNum();
+}
+
+int32_t BattleFieldInfo::getIconIndex() const
+{
+	return iconIndex;
+}
+
+std::string BattleFieldInfo::getJsonKey() const
+{
+	return modScope + ':' + identifier;
+}
+
+std::string BattleFieldInfo::getModScope() const
+{
+	return modScope;
+}
+
+std::string BattleFieldInfo::getNameTextID() const
+{
+	return name;
+}
+
+std::string BattleFieldInfo::getNameTranslated() const
+{
+	return name; // TODO?
+}
+
+void BattleFieldInfo::registerIcons(const IconRegistar & cb) const
+{
+	//cb(getIconIndex(), "BATTLEFIELD", icon);
+}
+
+BattleField BattleFieldInfo::getId() const
+{
+	return battlefield;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 80 - 80
lib/BattleFieldHandler.h

@@ -1,80 +1,80 @@
-/*
- * BattleFieldHandler.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 <vcmi/EntityService.h>
-#include <vcmi/Entity.h>
-#include "bonuses/Bonus.h"
-#include "GameConstants.h"
-#include "IHandlerBase.h"
-#include "battle/BattleHex.h"
-#include "filesystem/ResourcePath.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class BattleFieldInfo : public EntityT<BattleField>
-{
-public:
-	BattleField battlefield;
-	std::vector<std::shared_ptr<Bonus>> bonuses;
-	bool isSpecial;
-	ImagePath graphics;
-	std::string name;
-	std::string modScope;
-	std::string identifier;
-	std::string icon;
-	si32 iconIndex;
-	std::vector<BattleHex> impassableHexes;
-	AudioPath openingSoundFilename;
-	AudioPath musicFilename;
-
-	BattleFieldInfo() 
-		: BattleFieldInfo(BattleField::NONE, "")
-	{
-	}
-
-	BattleFieldInfo(BattleField battlefield, std::string identifier):
-		isSpecial(false),
-		battlefield(battlefield),
-		identifier(identifier),
-		iconIndex(battlefield.getNum()),
-		name(identifier)
-	{
-	}
-
-	int32_t getIndex() const override;
-	int32_t getIconIndex() const override;
-	std::string getJsonKey() const override;
-	std::string getModScope() const override;
-	std::string getNameTextID() const override;
-	std::string getNameTranslated() const override;
-	void registerIcons(const IconRegistar & cb) const override;
-	BattleField getId() const override;
-};
-
-class DLL_LINKAGE BattleFieldService : public EntityServiceT<BattleField, BattleFieldInfo>
-{
-public:
-};
-
-class BattleFieldHandler : public CHandlerBase<BattleField, BattleFieldInfo, BattleFieldInfo, BattleFieldService>
-{
-public:
-	std::shared_ptr<BattleFieldInfo> loadFromJson(
-		const std::string & scope,
-		const JsonNode & json,
-		const std::string & identifier,
-		size_t index) override;
-
-	const std::vector<std::string> & getTypeNames() const override;
-	std::vector<JsonNode> loadLegacyData() override;
-};
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * BattleFieldHandler.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 <vcmi/EntityService.h>
+#include <vcmi/Entity.h>
+#include "bonuses/Bonus.h"
+#include "GameConstants.h"
+#include "IHandlerBase.h"
+#include "battle/BattleHexArray.h"
+#include "filesystem/ResourcePath.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class BattleFieldInfo : public EntityT<BattleField>
+{
+public:
+	BattleField battlefield;
+	std::vector<std::shared_ptr<Bonus>> bonuses;
+	bool isSpecial;
+	ImagePath graphics;
+	std::string name;
+	std::string modScope;
+	std::string identifier;
+	std::string icon;
+	si32 iconIndex;
+	BattleHexArray impassableHexes;
+	AudioPath openingSoundFilename;
+	AudioPath musicFilename;
+
+	BattleFieldInfo() 
+		: BattleFieldInfo(BattleField::NONE, "")
+	{
+	}
+
+	BattleFieldInfo(BattleField battlefield, std::string identifier):
+		isSpecial(false),
+		battlefield(battlefield),
+		identifier(identifier),
+		iconIndex(battlefield.getNum()),
+		name(identifier)
+	{
+	}
+
+	int32_t getIndex() const override;
+	int32_t getIconIndex() const override;
+	std::string getJsonKey() const override;
+	std::string getModScope() const override;
+	std::string getNameTextID() const override;
+	std::string getNameTranslated() const override;
+	void registerIcons(const IconRegistar & cb) const override;
+	BattleField getId() const override;
+};
+
+class DLL_LINKAGE BattleFieldService : public EntityServiceT<BattleField, BattleFieldInfo>
+{
+public:
+};
+
+class BattleFieldHandler : public CHandlerBase<BattleField, BattleFieldInfo, BattleFieldInfo, BattleFieldService>
+{
+public:
+	std::shared_ptr<BattleFieldInfo> loadFromJson(
+		const std::string & scope,
+		const JsonNode & json,
+		const std::string & identifier,
+		size_t index) override;
+
+	const std::vector<std::string> & getTypeNames() const override;
+	std::vector<JsonNode> loadLegacyData() override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/CGameInterface.cpp

@@ -204,7 +204,7 @@ void CAdventureAI::battleObstaclesChanged(const BattleID & battleID, const std::
 	battleAI->battleObstaclesChanged(battleID, obstacles);
 	battleAI->battleObstaclesChanged(battleID, obstacles);
 }
 }
 
 
-void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
+void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport)
 {
 {
 	battleAI->battleStackMoved(battleID, stack, dest, distance, teleport);
 	battleAI->battleStackMoved(battleID, stack, dest, distance, teleport);
 }
 }

+ 1 - 1
lib/CGameInterface.h

@@ -151,7 +151,7 @@ public:
 	void actionFinished(const BattleID & battleID, const BattleAction &action) override;
 	void actionFinished(const BattleID & battleID, const BattleAction &action) override;
 	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;
 	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;
 	void battleObstaclesChanged(const BattleID & battleID, const std::vector<ObstacleChanges> & obstacles) override;
 	void battleObstaclesChanged(const BattleID & battleID, const std::vector<ObstacleChanges> & obstacles) override;
-	void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override;
 	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override;
 	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override;
 	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
 	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
 	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
 	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;

+ 2 - 0
lib/CMakeLists.txt

@@ -47,6 +47,7 @@ set(lib_MAIN_SRCS
 	battle/BattleAction.cpp
 	battle/BattleAction.cpp
 	battle/BattleAttackInfo.cpp
 	battle/BattleAttackInfo.cpp
 	battle/BattleHex.cpp
 	battle/BattleHex.cpp
+	battle/BattleHexArray.cpp
 	battle/BattleInfo.cpp
 	battle/BattleInfo.cpp
 	battle/BattleLayout.cpp
 	battle/BattleLayout.cpp
 	battle/BattleProxy.cpp
 	battle/BattleProxy.cpp
@@ -413,6 +414,7 @@ set(lib_MAIN_HEADERS
 	battle/BattleAction.h
 	battle/BattleAction.h
 	battle/BattleAttackInfo.h
 	battle/BattleAttackInfo.h
 	battle/BattleHex.h
 	battle/BattleHex.h
+	battle/BattleHexArray.h
 	battle/BattleInfo.h
 	battle/BattleInfo.h
 	battle/BattleLayout.h
 	battle/BattleLayout.h
 	battle/BattleSide.h
 	battle/BattleSide.h

+ 6 - 6
lib/CStack.cpp

@@ -242,10 +242,10 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 	bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
 	bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
 }
 }
 
 
-std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
+BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
 {
 {
 	int mask = 0;
 	int mask = 0;
-	std::vector<BattleHex> res;
+	BattleHexArray res;
 
 
 	if (!attackerPos.isValid())
 	if (!attackerPos.isValid())
 		attackerPos = attacker->getPosition();
 		attackerPos = attacker->getPosition();
@@ -260,7 +260,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 1) == 0)
 		if((mask & 1) == 0)
 		{
 		{
 			mask |= 1;
 			mask |= 1;
-			res.push_back(defenderPos);
+			res.insert(defenderPos);
 		}
 		}
 	}
 	}
 	if (attacker->doubleWide() //back <=> front
 	if (attacker->doubleWide() //back <=> front
@@ -269,7 +269,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 1) == 0)
 		if((mask & 1) == 0)
 		{
 		{
 			mask |= 1;
 			mask |= 1;
-			res.push_back(defenderPos);
+			res.insert(defenderPos);
 		}
 		}
 	}
 	}
 	if (defender->doubleWide()//front <=> back
 	if (defender->doubleWide()//front <=> back
@@ -278,7 +278,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 2) == 0)
 		if((mask & 2) == 0)
 		{
 		{
 			mask |= 2;
 			mask |= 2;
-			res.push_back(otherDefenderPos);
+			res.insert(otherDefenderPos);
 		}
 		}
 	}
 	}
 	if (defender->doubleWide() && attacker->doubleWide()//back <=> back
 	if (defender->doubleWide() && attacker->doubleWide()//back <=> back
@@ -287,7 +287,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 2) == 0)
 		if((mask & 2) == 0)
 		{
 		{
 			mask |= 2;
 			mask |= 2;
-			res.push_back(otherDefenderPos);
+			res.insert(otherDefenderPos);
 		}
 		}
 	}
 	}
 
 

+ 2 - 2
lib/CStack.h

@@ -60,7 +60,7 @@ public:
 	std::vector<SpellID> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
 	std::vector<SpellID> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
 	const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
 	const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
 
 
-	static std::vector<BattleHex> meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
+	static BattleHexArray meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
 	static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
 	static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
 
 
 	BattleHex::EDir destShiftDir() const;
 	BattleHex::EDir destShiftDir() const;
@@ -146,4 +146,4 @@ private:
 	const BattleInfo * battle; //do not serialize
 	const BattleInfo * battle; //do not serialize
 };
 };
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/IGameEventsReceiver.h

@@ -10,7 +10,7 @@
 #pragma once
 #pragma once
 
 
 #include "networkPacks/EInfoWindowMode.h"
 #include "networkPacks/EInfoWindowMode.h"
-#include "battle/BattleHex.h"
+#include "battle/BattleHexArray.h"
 #include "GameConstants.h"
 #include "GameConstants.h"
 #include "int3.h"
 #include "int3.h"
 
 
@@ -63,7 +63,7 @@ public:
 	virtual void battleNewRoundFirst(const BattleID & battleID){}; //called at the beginning of each turn before changes are applied;
 	virtual void battleNewRoundFirst(const BattleID & battleID){}; //called at the beginning of each turn before changes are applied;
 	virtual void battleNewRound(const BattleID & battleID){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	virtual void battleNewRound(const BattleID & battleID){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	virtual void battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines){};
 	virtual void battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines){};
-	virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport){};
+	virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport){};
 	virtual void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc){};
 	virtual void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc){};
 	virtual void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse){};//called when a specific effect is set to stacks
 	virtual void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse){};//called when a specific effect is set to stacks
 	virtual void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte){}; //called for various one-shot effects
 	virtual void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte){}; //called for various one-shot effects

+ 129 - 130
lib/ObstacleHandler.cpp

@@ -1,130 +1,129 @@
-/*
- * ObstacleHandler.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 "ObstacleHandler.h"
-#include "BattleFieldHandler.h"
-#include "json/JsonNode.h"
-#include "modding/IdentifierStorage.h"
-#include "VCMI_Lib.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-int32_t ObstacleInfo::getIndex() const
-{
-	return obstacle.getNum();
-}
-
-int32_t ObstacleInfo::getIconIndex() const
-{
-	return iconIndex;
-}
-
-std::string ObstacleInfo::getJsonKey() const
-{
-	return modScope + ':' + identifier;
-}
-
-std::string ObstacleInfo::getModScope() const
-{
-	return modScope;
-}
-
-std::string ObstacleInfo::getNameTranslated() const
-{
-	return identifier;
-}
-
-std::string ObstacleInfo::getNameTextID() const
-{
-	return identifier; // TODO?
-}
-
-void ObstacleInfo::registerIcons(const IconRegistar & cb) const
-{
-}
-
-Obstacle ObstacleInfo::getId() const
-{
-	return obstacle;
-}
-
-std::vector<BattleHex> ObstacleInfo::getBlocked(BattleHex hex) const
-{
-	std::vector<BattleHex> ret;
-	if(isAbsoluteObstacle)
-	{
-		assert(!hex.isValid());
-		range::copy(blockedTiles, std::back_inserter(ret));
-		return ret;
-	}
-	
-	for(int offset : blockedTiles)
-	{
-		BattleHex toBlock = hex + offset;
-		if((hex.getY() & 1) && !(toBlock.getY() & 1))
-			toBlock += BattleHex::LEFT;
-		
-		if(!toBlock.isValid())
-			logGlobal->error("Misplaced obstacle!");
-		else
-			ret.push_back(toBlock);
-	}
-	
-	return ret;
-}
-
-bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const
-{
-	const auto * bgInfo = battlefield.getInfo();
-
-	if(bgInfo->isSpecial)
-		return vstd::contains(allowedSpecialBfields, bgInfo->identifier);
-	
-	return vstd::contains(allowedTerrains, terrainType);
-}
-
-std::shared_ptr<ObstacleInfo> ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)
-{
-	assert(identifier.find(':') == std::string::npos);
-
-	auto info = std::make_shared<ObstacleInfo>(Obstacle(index), identifier);
-	
-	info->modScope = scope;
-	info->animation = AnimationPath::fromJson(json["animation"]);
-	info->width = json["width"].Integer();
-	info->height = json["height"].Integer();
-	for(const auto & t : json["allowedTerrains"].Vector())
-	{
-		VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){
-			info->allowedTerrains.emplace_back(identifier);
-		});
-	}
-	for(const auto & t : json["specialBattlefields"].Vector())
-
-		info->allowedSpecialBfields.emplace_back(t.String());
-	info->blockedTiles = json["blockedTiles"].convertTo<std::vector<si16>>();
-	info->isAbsoluteObstacle = json["absolute"].Bool();
-	info->isForegroundObstacle = json["foreground"].Bool();
-
-	return info;
-}
-
-std::vector<JsonNode> ObstacleHandler::loadLegacyData()
-{
-	return {};
-}
-
-const std::vector<std::string> & ObstacleHandler::getTypeNames() const
-{
-	static const std::vector<std::string> types = { "obstacle" };
-	return types;
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * ObstacleHandler.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 "ObstacleHandler.h"
+#include "BattleFieldHandler.h"
+#include "json/JsonNode.h"
+#include "modding/IdentifierStorage.h"
+#include "VCMI_Lib.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+int32_t ObstacleInfo::getIndex() const
+{
+	return obstacle.getNum();
+}
+
+int32_t ObstacleInfo::getIconIndex() const
+{
+	return iconIndex;
+}
+
+std::string ObstacleInfo::getJsonKey() const
+{
+	return modScope + ':' + identifier;
+}
+
+std::string ObstacleInfo::getModScope() const
+{
+	return modScope;
+}
+
+std::string ObstacleInfo::getNameTranslated() const
+{
+	return identifier;
+}
+
+std::string ObstacleInfo::getNameTextID() const
+{
+	return identifier; // TODO?
+}
+
+void ObstacleInfo::registerIcons(const IconRegistar & cb) const
+{
+}
+
+Obstacle ObstacleInfo::getId() const
+{
+	return obstacle;
+}
+
+BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const
+{
+	if(isAbsoluteObstacle)
+	{
+		assert(!hex.isValid());
+		return BattleHexArray(blockedTiles);
+	}
+	
+	BattleHexArray ret;
+	for(int offset : blockedTiles)
+	{
+		BattleHex toBlock = hex + offset;
+		if((hex.getY() & 1) && !(toBlock.getY() & 1))
+			toBlock += BattleHex::LEFT;
+		
+		if(!toBlock.isValid())
+			logGlobal->error("Misplaced obstacle!");
+		else
+			ret.insert(toBlock);
+	}
+	
+	return ret;
+}
+
+bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const
+{
+	const auto * bgInfo = battlefield.getInfo();
+
+	if(bgInfo->isSpecial)
+		return vstd::contains(allowedSpecialBfields, bgInfo->identifier);
+	
+	return vstd::contains(allowedTerrains, terrainType);
+}
+
+std::shared_ptr<ObstacleInfo> ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)
+{
+	assert(identifier.find(':') == std::string::npos);
+
+	auto info = std::make_shared<ObstacleInfo>(Obstacle(index), identifier);
+	
+	info->modScope = scope;
+	info->animation = AnimationPath::fromJson(json["animation"]);
+	info->width = json["width"].Integer();
+	info->height = json["height"].Integer();
+	for(const auto & t : json["allowedTerrains"].Vector())
+	{
+		VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){
+			info->allowedTerrains.emplace_back(identifier);
+		});
+	}
+	for(const auto & t : json["specialBattlefields"].Vector())
+
+		info->allowedSpecialBfields.emplace_back(t.String());
+	info->blockedTiles = json["blockedTiles"].convertTo<std::vector<si16>>();
+	info->isAbsoluteObstacle = json["absolute"].Bool();
+	info->isForegroundObstacle = json["foreground"].Bool();
+
+	return info;
+}
+
+std::vector<JsonNode> ObstacleHandler::loadLegacyData()
+{
+	return {};
+}
+
+const std::vector<std::string> & ObstacleHandler::getTypeNames() const
+{
+	static const std::vector<std::string> types = { "obstacle" };
+	return types;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 79 - 79
lib/ObstacleHandler.h

@@ -1,79 +1,79 @@
-/*
- * ObstacleHandler.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 <vcmi/EntityService.h>
-#include <vcmi/Entity.h>
-#include "GameConstants.h"
-#include "IHandlerBase.h"
-#include "battle/BattleHex.h"
-#include "filesystem/ResourcePath.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class DLL_LINKAGE ObstacleInfo : public EntityT<Obstacle>
-{
-public:
-	ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false)
-	{}
-	
-	ObstacleInfo(Obstacle obstacle, std::string identifier)
-	: obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false)
-	{
-	}
-	
-	Obstacle obstacle;
-	si32 iconIndex;
-	std::string modScope;
-	std::string identifier;
-	AudioPath appearSound;
-	AnimationPath appearAnimation;
-	AnimationPath animation;
-	std::vector<TerrainId> allowedTerrains;
-	std::vector<std::string> allowedSpecialBfields;
-	
-	bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same
-	bool isForegroundObstacle;
-	si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm)
-	si32 height;
-	std::vector<si16> blockedTiles; //offsets relative to obstacle position (that is its left bottom corner)
-	
-	int32_t getIndex() const override;
-	int32_t getIconIndex() const override;
-	std::string getJsonKey() const override;
-	std::string getModScope() const override;
-	std::string getNameTranslated() const override;
-	std::string getNameTextID() const override;
-	void registerIcons(const IconRegistar & cb) const override;
-	Obstacle getId() const override;
-	
-	std::vector<BattleHex> getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex'
-	
-	bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const;
-};
-
-class DLL_LINKAGE ObstacleService : public EntityServiceT<Obstacle, ObstacleInfo>
-{
-public:
-};
-
-class ObstacleHandler: public CHandlerBase<Obstacle, ObstacleInfo, ObstacleInfo, ObstacleService>
-{
-public:
-	std::shared_ptr<ObstacleInfo> loadFromJson(const std::string & scope,
-										const JsonNode & json,
-										const std::string & identifier,
-										size_t index) override;
-	
-	const std::vector<std::string> & getTypeNames() const override;
-	std::vector<JsonNode> loadLegacyData() override;
-};
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * ObstacleHandler.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 <vcmi/EntityService.h>
+#include <vcmi/Entity.h>
+#include "GameConstants.h"
+#include "IHandlerBase.h"
+#include "battle/BattleHexArray.h"
+#include "filesystem/ResourcePath.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE ObstacleInfo : public EntityT<Obstacle>
+{
+public:
+	ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false)
+	{}
+	
+	ObstacleInfo(Obstacle obstacle, std::string identifier)
+	: obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false)
+	{
+	}
+	
+	Obstacle obstacle;
+	si32 iconIndex;
+	std::string modScope;
+	std::string identifier;
+	AudioPath appearSound;
+	AnimationPath appearAnimation;
+	AnimationPath animation;
+	std::vector<TerrainId> allowedTerrains;
+	std::vector<std::string> allowedSpecialBfields;
+	
+	bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same
+	bool isForegroundObstacle;
+	si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm)
+	si32 height;
+	std::vector<si16> blockedTiles; //offsets relative to obstacle position (that is its left bottom corner)
+	
+	int32_t getIndex() const override;
+	int32_t getIconIndex() const override;
+	std::string getJsonKey() const override;
+	std::string getModScope() const override;
+	std::string getNameTranslated() const override;
+	std::string getNameTextID() const override;
+	void registerIcons(const IconRegistar & cb) const override;
+	Obstacle getId() const override;
+	
+	BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex'
+	
+	bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const;
+};
+
+class DLL_LINKAGE ObstacleService : public EntityServiceT<Obstacle, ObstacleInfo>
+{
+public:
+};
+
+class ObstacleHandler: public CHandlerBase<Obstacle, ObstacleInfo, ObstacleInfo, ObstacleService>
+{
+public:
+	std::shared_ptr<ObstacleInfo> loadFromJson(const std::string & scope,
+										const JsonNode & json,
+										const std::string & identifier,
+										size_t index) override;
+	
+	const std::vector<std::string> & getTypeNames() const override;
+	std::vector<JsonNode> loadLegacyData() override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 138 - 245
lib/battle/BattleHex.cpp

@@ -1,245 +1,138 @@
-/*
- * BattleHex.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 "BattleHex.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-BattleHex::BattleHex() : hex(INVALID) {}
-
-BattleHex::BattleHex(si16 _hex) : hex(_hex) {}
-
-BattleHex::BattleHex(si16 x, si16 y)
-{
-	setXY(x, y);
-}
-
-BattleHex::BattleHex(std::pair<si16, si16> xy)
-{
-	setXY(xy);
-}
-
-BattleHex::operator si16() const
-{
-	return hex;
-}
-
-bool BattleHex::isValid() const
-{
-	return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
-}
-
-bool BattleHex::isAvailable() const
-{
-	return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH-1;
-}
-
-void BattleHex::setX(si16 x)
-{
-	setXY(x, getY());
-}
-
-void BattleHex::setY(si16 y)
-{
-	setXY(getX(), y);
-}
-
-void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
-{
-	if(hasToBeValid)
-	{
-		if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT)
-			throw std::runtime_error("Valid hex required");
-	}
-
-	hex = x + y * GameConstants::BFIELD_WIDTH;
-}
-
-void BattleHex::setXY(std::pair<si16, si16> xy)
-{
-	setXY(xy.first, xy.second);
-}
-
-si16 BattleHex::getX() const
-{
-	return hex % GameConstants::BFIELD_WIDTH;
-}
-
-si16 BattleHex::getY() const
-{
-	return hex / GameConstants::BFIELD_WIDTH;
-}
-
-std::pair<si16, si16> BattleHex::getXY() const
-{
-	return std::make_pair(getX(), getY());
-}
-
-BattleHex& BattleHex::moveInDirection(EDir dir, bool hasToBeValid)
-{
-	si16 x = getX();
-	si16 y = getY();
-	switch(dir)
-	{
-	case TOP_LEFT:
-		setXY((y%2) ? x-1 : x, y-1, hasToBeValid);
-		break;
-	case TOP_RIGHT:
-		setXY((y%2) ? x : x+1, y-1, hasToBeValid);
-		break;
-	case RIGHT:
-		setXY(x+1, y, hasToBeValid);
-		break;
-	case BOTTOM_RIGHT:
-		setXY((y%2) ? x : x+1, y+1, hasToBeValid);
-		break;
-	case BOTTOM_LEFT:
-		setXY((y%2) ? x-1 : x, y+1, hasToBeValid);
-		break;
-	case LEFT:
-		setXY(x-1, y, hasToBeValid);
-		break;
-	case NONE:
-		break;
-	default:
-		throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n");
-		break;
-	}
-	return *this;
-}
-
-BattleHex &BattleHex::operator+=(BattleHex::EDir dir)
-{
-	return moveInDirection(dir);
-}
-
-BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const
-{
-	BattleHex result(hex);
-	result.moveInDirection(dir, hasToBeValid);
-	return result;
-}
-
-BattleHex BattleHex::operator+(BattleHex::EDir dir) const
-{
-	return cloneInDirection(dir);
-}
-
-std::vector<BattleHex> BattleHex::neighbouringTiles() const
-{
-	std::vector<BattleHex> ret;
-	ret.reserve(6);
-	for(auto dir : hexagonalDirections())
-		checkAndPush(cloneInDirection(dir, false), ret);
-	return ret;
-}
-
-std::vector<BattleHex> BattleHex::allNeighbouringTiles() const
-{
-	std::vector<BattleHex> ret;
-	ret.resize(6);
-
-	for(auto dir : hexagonalDirections())
-		ret[dir] = cloneInDirection(dir, false);
-
-	return ret;
-}
-
-BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2)
-{
-	for(auto dir : hexagonalDirections())
-		if(hex2 == hex1.cloneInDirection(dir, false))
-			return dir;
-	return NONE;
-}
-
-uint8_t BattleHex::getDistance(BattleHex hex1, BattleHex hex2)
-{
-	int y1 = hex1.getY();
-	int y2 = hex2.getY();
-
-	// FIXME: why there was * 0.5 instead of / 2?
-	int x1 = static_cast<int>(hex1.getX() + y1 / 2);
-	int x2 = static_cast<int>(hex2.getX() + y2 / 2);
-
-	int xDst = x2 - x1;
-	int yDst = y2 - y1;
-
-	if ((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0))
-		return std::max(std::abs(xDst), std::abs(yDst));
-
-	return std::abs(xDst) + std::abs(yDst);
-}
-
-void BattleHex::checkAndPush(BattleHex tile, std::vector<BattleHex> & ret)
-{
-	if(tile.isAvailable())
-		ret.push_back(tile);
-}
-
-BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, std::set<BattleHex> & possibilities)
-{
-	std::vector<BattleHex> sortedTiles (possibilities.begin(), possibilities.end()); //set can't be sorted properly :(
-	BattleHex initialHex = BattleHex(initialPos);
-	auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool
-	{
-		return initialHex.getDistance (initialHex, left) < initialHex.getDistance (initialHex, right);
-	};
-	boost::sort (sortedTiles, compareDistance); //closest tiles at front
-	int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away
-	auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool
-	{
-		return closestDistance < here.getDistance (initialPos, here);
-	};
-	vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting
-	auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool
-	{
-		if(left.getX() != right.getX())
-		{
-			if(side == BattleSide::ATTACKER)
-				return left.getX() > right.getX(); //find furthest right
-			else
-				return left.getX() < right.getX(); //find furthest left
-		}
-		else
-		{
-			//Prefer tiles in the same row.
-			return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
-		}
-	};
-	boost::sort (sortedTiles, compareHorizontal);
-	return sortedTiles.front();
-}
-
-std::ostream & operator<<(std::ostream & os, const BattleHex & hex)
-{
-	return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex);
-}
-
-static BattleHex::NeighbouringTilesCache calculateNeighbouringTiles()
-{
-	BattleHex::NeighbouringTilesCache ret;
-	ret.resize(GameConstants::BFIELD_SIZE);
-
-	for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
-	{
-		auto hexes = BattleHex(hex).neighbouringTiles();
-
-		size_t index = 0;
-		for(auto neighbour : hexes)
-			ret[hex].at(index++) = neighbour;
-	}
-
-	return ret;
-}
-
-const BattleHex::NeighbouringTilesCache BattleHex::neighbouringTilesCache = calculateNeighbouringTiles();
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * BattleHex.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 "BattleHex.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+BattleHex::BattleHex() : hex(INVALID) {}
+
+BattleHex::BattleHex(si16 _hex) : hex(_hex) {}
+
+BattleHex::BattleHex(si16 x, si16 y)
+{
+	setXY(x, y);
+}
+
+BattleHex::BattleHex(std::pair<si16, si16> xy)
+{
+	setXY(xy);
+}
+
+BattleHex::operator si16() const
+{
+	return hex;
+}
+
+void BattleHex::setX(si16 x)
+{
+	setXY(x, getY());
+}
+
+void BattleHex::setY(si16 y)
+{
+	setXY(getX(), y);
+}
+
+void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
+{
+	if(hasToBeValid)
+	{
+		if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT)
+			throw std::runtime_error("Valid hex required");
+	}
+
+	hex = x + y * GameConstants::BFIELD_WIDTH;
+}
+
+void BattleHex::setXY(std::pair<si16, si16> xy)
+{
+	setXY(xy.first, xy.second);
+}
+
+si16 BattleHex::getX() const
+{
+	return hex % GameConstants::BFIELD_WIDTH;
+}
+
+si16 BattleHex::getY() const
+{
+	return hex / GameConstants::BFIELD_WIDTH;
+}
+
+std::pair<si16, si16> BattleHex::getXY() const
+{
+	return std::make_pair(getX(), getY());
+}
+
+BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid)
+{
+	si16 x = getX();
+	si16 y = getY();
+	switch(dir)
+	{
+	case TOP_LEFT:
+		setXY((y%2) ? x-1 : x, y-1, hasToBeValid);
+		break;
+	case TOP_RIGHT:
+		setXY((y%2) ? x : x+1, y-1, hasToBeValid);
+		break;
+	case RIGHT:
+		setXY(x+1, y, hasToBeValid);
+		break;
+	case BOTTOM_RIGHT:
+		setXY((y%2) ? x : x+1, y+1, hasToBeValid);
+		break;
+	case BOTTOM_LEFT:
+		setXY((y%2) ? x-1 : x, y+1, hasToBeValid);
+		break;
+	case LEFT:
+		setXY(x-1, y, hasToBeValid);
+		break;
+	case NONE:
+		break;
+	default:
+		throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n");
+		break;
+	}
+	return *this;
+}
+
+BattleHex & BattleHex::operator+=(BattleHex::EDir dir)
+{
+	return moveInDirection(dir);
+}
+
+BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const
+{
+	BattleHex result(hex);
+	result.moveInDirection(dir, hasToBeValid);
+	return result;
+}
+
+BattleHex BattleHex::operator+(BattleHex::EDir dir) const
+{
+	return cloneInDirection(dir);
+}
+
+BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2)
+{
+	for(auto dir : hexagonalDirections())
+		if(hex2 == hex1.cloneInDirection(dir, false))
+			return dir;
+	return NONE;
+}
+
+std::ostream & operator<<(std::ostream & os, const BattleHex & hex)
+{
+	return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 127 - 117
lib/battle/BattleHex.h

@@ -1,117 +1,127 @@
-/*
- * BattleHex.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 "BattleSide.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-//TODO: change to enum class
-
-namespace GameConstants
-{
-	const int BFIELD_WIDTH = 17;
-	const int BFIELD_HEIGHT = 11;
-	const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
-}
-
-// for battle stacks' positions
-struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design
-{
-	// helpers for siege
-	static constexpr si16 CASTLE_CENTRAL_TOWER = -2;
-	static constexpr si16 CASTLE_BOTTOM_TOWER = -3;
-	static constexpr si16 CASTLE_UPPER_TOWER = -4;
-
-	// hexes for interaction with heroes
-	static constexpr si16 HERO_ATTACKER = 0;
-	static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1;
-
-	// helpers for rendering
-	static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits<si16>::min();
-	static constexpr si16 HEX_AFTER_ALL = std::numeric_limits<si16>::max();
-
-	static constexpr si16 DESTRUCTIBLE_WALL_1 = 29;
-	static constexpr si16 DESTRUCTIBLE_WALL_2 = 78;
-	static constexpr si16 DESTRUCTIBLE_WALL_3 = 130;
-	static constexpr si16 DESTRUCTIBLE_WALL_4 = 182;
-	static constexpr si16 GATE_BRIDGE = 94;
-	static constexpr si16 GATE_OUTER = 95;
-	static constexpr si16 GATE_INNER = 96;
-
-	si16 hex;
-	static constexpr si16 INVALID = -1;
-	enum EDir
-	{
-		NONE = -1,
-
-		TOP_LEFT,
-		TOP_RIGHT,
-		RIGHT,
-		BOTTOM_RIGHT,
-		BOTTOM_LEFT,
-		LEFT,
-
-		//Note: unused by BattleHex class, used by other code
-		TOP,
-		BOTTOM
-	};
-
-	BattleHex();
-	BattleHex(si16 _hex);
-	BattleHex(si16 x, si16 y);
-	BattleHex(std::pair<si16, si16> xy);
-	operator si16() const;
-	bool isValid() const;
-	bool isAvailable() const; //valid position not in first or last column
-	void setX(si16 x);
-	void setY(si16 y);
-	void setXY(si16 x, si16 y, bool hasToBeValid = true);
-	void setXY(std::pair<si16, si16> xy);
-	si16 getX() const;
-	si16 getY() const;
-	std::pair<si16, si16> getXY() const;
-	BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true);
-	BattleHex& operator+=(EDir dir);
-	BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const;
-	BattleHex operator+(EDir dir) const;
-
-	/// returns all valid neighbouring tiles
-	std::vector<BattleHex> neighbouringTiles() const;
-
-	/// returns all tiles, unavailable tiles will be set as invalid
-	/// order of returned tiles matches EDir enim
-	std::vector<BattleHex> allNeighbouringTiles() const;
-
-	static EDir mutualPosition(BattleHex hex1, BattleHex hex2);
-	static uint8_t getDistance(BattleHex hex1, BattleHex hex2);
-	static void checkAndPush(BattleHex tile, std::vector<BattleHex> & ret);
-	static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, std::set<BattleHex> & possibilities); //TODO: vector or set? copying one to another is bad
-
-	template <typename Handler>
-	void serialize(Handler &h)
-	{
-		h & hex;
-	}
-
-    using NeighbouringTiles = std::array<BattleHex, 6>;
-    using NeighbouringTilesCache = std::vector<NeighbouringTiles>;
-
-    static const NeighbouringTilesCache neighbouringTilesCache;
-private:
-	//Constexpr defined array with all directions used in battle
-	static constexpr auto hexagonalDirections() {
-		return std::array<EDir,6>{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT};
-	}
-};
-
-DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * BattleHex.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 "BattleSide.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+//TODO: change to enum class
+
+namespace GameConstants
+{
+	const int BFIELD_WIDTH = 17;
+	const int BFIELD_HEIGHT = 11;
+	const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
+}
+
+// for battle stacks' positions
+struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design
+{
+	// helpers for siege
+	static constexpr si16 CASTLE_CENTRAL_TOWER = -2;
+	static constexpr si16 CASTLE_BOTTOM_TOWER = -3;
+	static constexpr si16 CASTLE_UPPER_TOWER = -4;
+
+	// hexes for interaction with heroes
+	static constexpr si16 HERO_ATTACKER = 0;
+	static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1;
+
+	// helpers for rendering
+	static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits<si16>::min();
+	static constexpr si16 HEX_AFTER_ALL = std::numeric_limits<si16>::max();
+
+	static constexpr si16 DESTRUCTIBLE_WALL_1 = 29;
+	static constexpr si16 DESTRUCTIBLE_WALL_2 = 78;
+	static constexpr si16 DESTRUCTIBLE_WALL_3 = 130;
+	static constexpr si16 DESTRUCTIBLE_WALL_4 = 182;
+	static constexpr si16 GATE_BRIDGE = 94;
+	static constexpr si16 GATE_OUTER = 95;
+	static constexpr si16 GATE_INNER = 96;
+
+	si16 hex;
+	static constexpr si16 INVALID = -1;
+	enum EDir
+	{
+		NONE = -1,
+
+		TOP_LEFT,
+		TOP_RIGHT,
+		RIGHT,
+		BOTTOM_RIGHT,
+		BOTTOM_LEFT,
+		LEFT,
+
+		//Note: unused by BattleHex class, used by other code
+		TOP,
+		BOTTOM
+	};
+
+	BattleHex();
+	BattleHex(si16 _hex);
+	BattleHex(si16 x, si16 y);
+	BattleHex(std::pair<si16, si16> xy);
+	operator si16() const;
+	inline bool isValid() const
+	{
+		return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
+	}
+	
+	bool isAvailable() const //valid position not in first or last column
+	{
+		return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1;
+	}
+
+	void setX(si16 x);
+	void setY(si16 y);
+	void setXY(si16 x, si16 y, bool hasToBeValid = true);
+	void setXY(std::pair<si16, si16> xy);
+	si16 getX() const;
+	si16 getY() const;
+	std::pair<si16, si16> getXY() const;
+	BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true);
+	BattleHex& operator+=(EDir dir);
+	BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const;
+	BattleHex operator+(EDir dir) const;
+
+	static EDir mutualPosition(BattleHex hex1, BattleHex hex2);
+	static uint8_t getDistance(BattleHex hex1, BattleHex hex2)
+	{
+		int y1 = hex1.getY();
+		int y2 = hex2.getY();
+
+		// FIXME: why there was * 0.5 instead of / 2?
+		int x1 = static_cast<int>(hex1.getX() + y1 / 2);
+		int x2 = static_cast<int>(hex2.getX() + y2 / 2);
+
+		int xDst = x2 - x1;
+		int yDst = y2 - y1;
+
+		if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0))
+			return std::max(std::abs(xDst), std::abs(yDst));
+
+		return std::abs(xDst) + std::abs(yDst);
+	}
+	
+	template <typename Handler>
+	void serialize(Handler &h)
+	{
+		h & hex;
+	}
+
+	//Constexpr defined array with all directions used in battle
+	static constexpr auto hexagonalDirections() {
+		return std::array<EDir,6>{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT};
+	}
+};
+
+DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);
+
+VCMI_LIB_NAMESPACE_END

+ 103 - 0
lib/battle/BattleHexArray.cpp

@@ -0,0 +1,103 @@
+/*
+ * BattleHexArray.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 "BattleHexArray.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+BattleHexArray::BattleHexArray(std::initializer_list<BattleHex> initList) noexcept 
+	: BattleHexArray()
+{
+	for(auto hex : initList)
+	{
+		insert(hex);
+	}
+}
+
+BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos)
+{
+	BattleHex initialHex = BattleHex(initialPos);
+	auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool
+	{
+		return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right);
+	};
+	BattleHexArray sortedTiles(*this);
+	boost::sort(sortedTiles, compareDistance); //closest tiles at front
+	int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away
+	auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool
+	{
+		return closestDistance < here.getDistance(initialPos, here);
+	};
+	vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting
+	auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool
+	{
+		if(left.getX() != right.getX())
+		{
+			if(side == BattleSide::ATTACKER)
+				return left.getX() > right.getX(); //find furthest right
+			else
+				return left.getX() < right.getX(); //find furthest left
+		}
+		else
+		{
+			//Prefer tiles in the same row.
+			return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
+		}
+	};
+	boost::sort(sortedTiles, compareHorizontal);
+	return sortedTiles.front();
+}
+
+void BattleHexArray::merge(const BattleHexArray & other) noexcept
+{
+	for(auto hex : other)
+	{
+		insert(hex);
+	}
+}
+
+void BattleHexArray::erase(iterator first, iterator last) noexcept
+{
+	for(auto it = first; it != last && it != internalStorage.end(); ++it)
+	{
+		presenceFlags[*it] = 0;
+	}
+
+	internalStorage.erase(first, last);
+}
+
+void BattleHexArray::clear() noexcept
+{
+	for(auto hex : internalStorage)
+		presenceFlags[hex] = 0;
+
+	internalStorage.clear();
+}
+
+static BattleHexArray::NeighbouringTilesCache calculateNeighbouringTiles()
+{
+	BattleHexArray::NeighbouringTilesCache ret;
+
+	for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
+	{
+		auto hexes = BattleHexArray::generateNeighbouringTiles(hex);
+
+		size_t index = 0;
+		for(auto neighbour : hexes)
+			ret[hex].at(index++) = neighbour;
+	}
+
+	return ret;
+}
+
+const BattleHexArray::NeighbouringTilesCache BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles();
+
+VCMI_LIB_NAMESPACE_END

+ 300 - 0
lib/battle/BattleHexArray.h

@@ -0,0 +1,300 @@
+/*
+ * BattleHexArray.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 "BattleHex.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+/// Class representing an array of unique BattleHex objects
+class DLL_LINKAGE BattleHexArray
+{
+public:
+	static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE;
+	using StorageType = std::vector<BattleHex>;
+
+	using value_type = BattleHex;
+	using size_type = StorageType::size_type;
+	using reference = value_type &;
+	using const_reference = const value_type &;
+	using pointer = value_type *;
+	using const_pointer = const value_type *;
+	using difference_type = typename StorageType::difference_type;
+	using iterator = typename StorageType::iterator;
+	using const_iterator = typename StorageType::const_iterator;
+	using reverse_iterator = typename StorageType::reverse_iterator;
+	using const_reverse_iterator = typename StorageType::const_reverse_iterator;
+
+	using NeighbouringTiles = std::array<BattleHex, 6>;
+	using NeighbouringTilesCache = std::array<NeighbouringTiles, GameConstants::BFIELD_SIZE>;
+
+	static const NeighbouringTilesCache neighbouringTilesCache;
+
+	BattleHexArray() noexcept
+	{
+		internalStorage.reserve(totalSize);
+	}
+
+	template <typename Container, typename = std::enable_if_t<
+		std::is_convertible_v<typename Container::value_type, BattleHex>>>
+		BattleHexArray(const Container & container) noexcept
+			: BattleHexArray()
+	{
+		for(auto value : container)
+		{
+			insert(value); 
+		}
+	}
+
+	void resize(size_type size)
+	{
+		clear();
+		internalStorage.resize(size);
+	}
+	
+	BattleHexArray(std::initializer_list<BattleHex> initList) noexcept;
+
+	/// returns all valid neighbouring tiles
+	static BattleHexArray generateNeighbouringTiles(BattleHex hex)
+	{
+		BattleHexArray ret;
+		for(auto dir : BattleHex::hexagonalDirections())
+			ret.checkAndPush(hex.cloneInDirection(dir, false));
+
+		return ret;
+	}
+
+	/// returns all tiles, unavailable tiles will be set as invalid
+	/// order of returned tiles matches EDir enum
+	static BattleHexArray generateAllNeighbouringTiles(BattleHex hex)
+	{
+		BattleHexArray ret;
+
+		ret.resize(6);
+
+		for(auto dir : BattleHex::hexagonalDirections())
+			ret.set(dir, hex.cloneInDirection(dir, false));
+
+		return ret;
+	}
+
+	BattleHex getClosestTile(BattleSide side, BattleHex initialPos);
+
+	void checkAndPush(BattleHex tile)
+	{
+		if(tile.isAvailable() && !contains(tile))
+		{
+			presenceFlags[tile] = 1;
+			internalStorage.emplace_back(tile);
+		}
+	}
+
+	void insert(BattleHex hex) noexcept
+	{
+		/*if(isNotValidForInsertion(hex))
+			return;*/
+
+		if(contains(hex))
+			return;
+
+		presenceFlags[hex] = 1;
+		internalStorage.emplace_back(hex);
+	}
+
+	void set(size_type index, BattleHex hex)
+	{
+		/*if(isNotValidForInsertion(hex))
+			return;*/
+
+		if(contains(hex))
+			return;
+
+		presenceFlags[hex] = 1;
+		internalStorage[index] = hex;
+	}
+
+	iterator BattleHexArray::insert(iterator pos, BattleHex hex) noexcept
+	{
+		/*if(isNotValidForInsertion(hex))
+			return pos;*/
+
+		if(contains(hex))
+			return pos;
+
+		presenceFlags[hex] = 1;
+		return internalStorage.insert(pos, hex);
+	}
+
+	void merge(const BattleHexArray & other) noexcept;
+
+	void clear() noexcept;
+	inline void erase(size_type index) noexcept
+	{
+		assert(index < totalSize);
+		internalStorage[index] = BattleHex::INVALID;
+		presenceFlags[index] = 0;
+	}
+	void erase(iterator first, iterator last) noexcept;
+	inline void pop_back() noexcept
+	{
+		presenceFlags[internalStorage.back()] = 0;
+		internalStorage.pop_back();
+	}
+
+	inline std::vector<BattleHex> toVector() const noexcept
+	{
+		return internalStorage;
+	}
+
+	template <typename Predicate>
+	iterator findIf(Predicate predicate) noexcept
+	{
+		return std::find_if(begin(), end(), predicate);
+	}
+
+	template <typename Predicate>
+	const_iterator findIf(Predicate predicate) const noexcept
+	{
+		return std::find_if(begin(), end(), predicate);
+	}
+
+	template <typename Predicate>
+	BattleHexArray filterBy(Predicate predicate) const noexcept
+	{
+		BattleHexArray filtered;
+		for(auto hex : internalStorage)
+		{
+			if(predicate(hex))
+			{
+				filtered.insert(hex);
+			}
+		}
+		return filtered;
+	}
+
+	[[nodiscard]] inline bool BattleHexArray::contains(BattleHex hex) const noexcept
+	{
+		if(hex.isValid())
+			return presenceFlags[hex];
+		/*
+		if(!isTower(hex))
+			logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex);
+		*/
+		
+		// return true for invalid hexes
+		return true;
+	}
+
+	template <typename Serializer>
+	void serialize(Serializer & s)
+	{
+		s & internalStorage;
+		if(!internalStorage.empty() && presenceFlags[internalStorage.front()] == 0)
+		{
+			for(auto hex : internalStorage)
+				presenceFlags[hex] = 1;
+		}
+	}
+
+	[[nodiscard]] inline const BattleHex & BattleHexArray::back() const noexcept
+	{
+		return internalStorage.back();
+	}
+
+	[[nodiscard]] inline const BattleHex & BattleHexArray::front() const noexcept
+	{
+		return internalStorage.front();
+	}
+
+	[[nodiscard]] inline const BattleHex & BattleHexArray::operator[](size_type index) const noexcept
+	{
+		return internalStorage[index];
+	}
+
+	[[nodiscard]] inline const BattleHex & BattleHexArray::at(size_type index) const
+	{
+		return internalStorage.at(index);
+	}
+
+	[[nodiscard]] inline size_type size() const noexcept
+	{
+		return internalStorage.size();
+	}
+
+	[[nodiscard]] inline BattleHexArray::iterator BattleHexArray::begin() noexcept
+	{
+		return internalStorage.begin();
+	}
+
+	[[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::begin() const noexcept
+	{
+		return internalStorage.begin();
+	}
+
+	[[nodiscard]] inline bool BattleHexArray::empty() const noexcept
+	{
+		return internalStorage.empty();
+	}
+
+	[[nodiscard]] inline BattleHexArray::iterator BattleHexArray::end() noexcept
+	{
+		return internalStorage.end();
+	}
+
+	[[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::end() const noexcept
+	{
+		return internalStorage.end();
+	}
+
+	[[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rbegin() noexcept
+	{
+		return reverse_iterator(end());
+	}
+
+	[[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rbegin() const noexcept
+	{
+		return const_reverse_iterator(end());
+	}
+
+	[[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rend() noexcept
+	{
+		return reverse_iterator(begin());
+	}
+
+	[[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rend() const noexcept
+	{
+		return const_reverse_iterator(begin());
+	}
+
+private:
+	StorageType internalStorage;
+	std::array<uint8_t, totalSize> presenceFlags = {};
+
+	[[nodiscard]] inline bool BattleHexArray::isNotValidForInsertion(BattleHex hex) const
+	{
+		if(isTower(hex))
+			return true;
+		if(!hex.isValid())
+		{
+			//logGlobal->warn("BattleHexArray::insert( %d ) - invalid BattleHex!", hex);
+			return true;
+		}
+
+		return contains(hex) || internalStorage.size() >= totalSize;
+	}
+
+	[[nodiscard]] inline bool isTower(BattleHex hex) const
+	{
+		return hex == BattleHex::CASTLE_CENTRAL_TOWER || hex == BattleHex::CASTLE_UPPER_TOWER || hex == BattleHex::CASTLE_BOTTOM_TOWER;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 5 - 5
lib/battle/BattleInfo.cpp

@@ -207,7 +207,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 		r.rand(1,8); //battle sound ID to play... can't do anything with it here
 		r.rand(1,8); //battle sound ID to play... can't do anything with it here
 		int tilesToBlock = r.rand(5,12);
 		int tilesToBlock = r.rand(5,12);
 
 
-		std::vector<BattleHex> blockedTiles;
+		BattleHexArray blockedTiles;
 
 
 		auto appropriateAbsoluteObstacle = [&](int id)
 		auto appropriateAbsoluteObstacle = [&](int id)
 		{
 		{
@@ -232,7 +232,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 				currentBattle->obstacles.push_back(obstPtr);
 				currentBattle->obstacles.push_back(obstPtr);
 
 
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
-					blockedTiles.push_back(blocked);
+					blockedTiles.insert(blocked);
 				tilesToBlock -= Obstacle(obstPtr->ID).getInfo()->blockedTiles.size() / 2;
 				tilesToBlock -= Obstacle(obstPtr->ID).getInfo()->blockedTiles.size() / 2;
 			}
 			}
 			catch(RangeGenerator::ExhaustedPossibilities &)
 			catch(RangeGenerator::ExhaustedPossibilities &)
@@ -259,14 +259,14 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 						return false;
 						return false;
 					if(pos.getX() + obi.width > 15)
 					if(pos.getX() + obi.width > 15)
 						return false;
 						return false;
-					if(vstd::contains(blockedTiles, pos))
+					if(blockedTiles.contains(pos))
 						return false;
 						return false;
 
 
 					for(BattleHex blocked : obi.getBlocked(pos))
 					for(BattleHex blocked : obi.getBlocked(pos))
 					{
 					{
 						if(tileAccessibility[blocked] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles
 						if(tileAccessibility[blocked] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles
 							return false;
 							return false;
-						if(vstd::contains(blockedTiles, blocked))
+						if(blockedTiles.contains(blocked))
 							return false;
 							return false;
 						int x = blocked.getX();
 						int x = blocked.getX();
 						if(x <= 2 || x >= 14)
 						if(x <= 2 || x >= 14)
@@ -285,7 +285,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 				currentBattle->obstacles.push_back(obstPtr);
 				currentBattle->obstacles.push_back(obstPtr);
 
 
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
-					blockedTiles.push_back(blocked);
+					blockedTiles.insert(blocked);
 				tilesToBlock -= static_cast<int>(obi.blockedTiles.size());
 				tilesToBlock -= static_cast<int>(obi.blockedTiles.size());
 			}
 			}
 		}
 		}

+ 56 - 62
lib/battle/CBattleInfoCallback.cpp

@@ -147,21 +147,21 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster *
 	return ESpellCastProblem::OK;
 	return ESpellCastProblem::OK;
 }
 }
 
 
-std::pair< std::vector<BattleHex>, int > CBattleInfoCallback::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const
+std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const
 {
 {
 	auto reachability = getReachability(stack);
 	auto reachability = getReachability(stack);
 
 
 	if(reachability.predecessors[dest] == -1) //cannot reach destination
 	if(reachability.predecessors[dest] == -1) //cannot reach destination
 	{
 	{
-		return std::make_pair(std::vector<BattleHex>(), 0);
+		return std::make_pair(BattleHexArray(), 0);
 	}
 	}
 
 
 	//making the Path
 	//making the Path
-	std::vector<BattleHex> path;
+	BattleHexArray path;
 	BattleHex curElem = dest;
 	BattleHex curElem = dest;
 	while(curElem != start)
 	while(curElem != start)
 	{
 	{
-		path.push_back(curElem);
+		path.insert(curElem);
 		curElem = reachability.predecessors[curElem];
 		curElem = reachability.predecessors[curElem];
 	}
 	}
 
 
@@ -191,23 +191,21 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
 		return isWallPartAttackable(wallPart);
 		return isWallPartAttackable(wallPart);
 	};
 	};
 	// Count wall penalty requirement by shortest path, not by arbitrary line, to avoid various OH3 bugs
 	// Count wall penalty requirement by shortest path, not by arbitrary line, to avoid various OH3 bugs
-	auto getShortestPath = [](BattleHex from, BattleHex dest) -> std::vector<BattleHex>
+	auto getShortestPath = [](BattleHex from, BattleHex dest) -> BattleHexArray
 	{
 	{
 		//Out early
 		//Out early
 		if(from == dest)
 		if(from == dest)
 			return {};
 			return {};
 
 
-		std::vector<BattleHex> ret;
+		BattleHexArray ret;
 		auto next = from;
 		auto next = from;
 		//Not a real direction, only to indicate to which side we should search closest tile
 		//Not a real direction, only to indicate to which side we should search closest tile
 		auto direction = from.getX() > dest.getX() ? BattleSide::DEFENDER : BattleSide::ATTACKER;
 		auto direction = from.getX() > dest.getX() ? BattleSide::DEFENDER : BattleSide::ATTACKER;
 
 
 		while (next != dest)
 		while (next != dest)
 		{
 		{
-			auto tiles = next.neighbouringTiles();
-			std::set<BattleHex> possibilities = {tiles.begin(), tiles.end()};
-			next = BattleHex::getClosestTile(direction, dest, possibilities);
-			ret.push_back(next);
+			next = BattleHexArray::generateNeighbouringTiles(next).getClosestTile(direction, dest);
+			ret.insert(next);
 		}
 		}
 		assert(!ret.empty());
 		assert(!ret.empty());
 		ret.pop_back(); //Remove destination hex
 		ret.pop_back(); //Remove destination hex
@@ -318,9 +316,9 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s
 	return PossiblePlayerBattleAction(spellSelMode, spell->id);
 	return PossiblePlayerBattleAction(spellSelMode, spell->id);
 }
 }
 
 
-std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
+BattleHexArray CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
 {
 {
-	std::set<BattleHex> attackedHexes;
+	BattleHexArray attackedHexes;
 	RETURN_IF_NOT_BATTLE(attackedHexes);
 	RETURN_IF_NOT_BATTLE(attackedHexes);
 
 
 	AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
 	AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
@@ -347,7 +345,7 @@ const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyA
 {
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
 	RETURN_IF_NOT_BATTLE(nullptr);
 	for(const auto * s : battleGetAllStacks(true))
 	for(const auto * s : battleGetAllStacks(true))
-		if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive()))
+		if(s->getHexes().contains(pos) && (!onlyAlive || s->alive()))
 			return s;
 			return s;
 
 
 	return nullptr;
 	return nullptr;
@@ -569,21 +567,21 @@ void CBattleInfoCallback::battleGetTurnOrder(std::vector<battle::Units> & turns,
 		battleGetTurnOrder(turns, maxUnits, maxTurns, actualTurn + 1, sideThatLastMoved);
 		battleGetTurnOrder(turns, maxUnits, maxTurns, actualTurn + 1, sideThatLastMoved);
 }
 }
 
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const
+BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const
 {
 {
 
 
-	RETURN_IF_NOT_BATTLE(std::vector<BattleHex>());
+	RETURN_IF_NOT_BATTLE(BattleHexArray());
 	if(!unit->getPosition().isValid()) //turrets
 	if(!unit->getPosition().isValid()) //turrets
-		return std::vector<BattleHex>();
+		return BattleHexArray();
 
 
 	auto reachability = getReachability(unit);
 	auto reachability = getReachability(unit);
 
 
 	return battleGetAvailableHexes(reachability, unit, obtainMovementRange);
 	return battleGetAvailableHexes(reachability, unit, obtainMovementRange);
 }
 }
 
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const
+BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const
 {
 {
-	std::vector<BattleHex> ret;
+	BattleHexArray ret;
 
 
 	RETURN_IF_NOT_BATTLE(ret);
 	RETURN_IF_NOT_BATTLE(ret);
 	if(!unit->getPosition().isValid()) //turrets
 	if(!unit->getPosition().isValid()) //turrets
@@ -612,28 +610,27 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 				continue;
 				continue;
 		}
 		}
 
 
-		ret.emplace_back(i);
+		ret.insert(i);
 	}
 	}
 
 
 	return ret;
 	return ret;
 }
 }
 
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, std::vector<BattleHex> * attackable) const
+BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const
 {
 {
-	std::vector<BattleHex> ret = battleGetAvailableHexes(unit, obtainMovementRange);
+	BattleHexArray ret = battleGetAvailableHexes(unit, obtainMovementRange);
 
 
 	if(ret.empty())
 	if(ret.empty())
 		return ret;
 		return ret;
 
 
 	if(addOccupiable && unit->doubleWide())
 	if(addOccupiable && unit->doubleWide())
 	{
 	{
-		std::vector<BattleHex> occupiable;
+		BattleHexArray occupiable;
 
 
-		occupiable.reserve(ret.size());
 		for(auto hex : ret)
 		for(auto hex : ret)
-			occupiable.push_back(unit->occupiedHex(hex));
+			occupiable.insert(unit->occupiedHex(hex));
 
 
-		vstd::concatenate(ret, occupiable);
+		ret.merge(occupiable);
 	}
 	}
 
 
 
 
@@ -643,36 +640,33 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle
 		{
 		{
 			// Return true if given hex has at least one available neighbour.
 			// Return true if given hex has at least one available neighbour.
 			// Available hexes are already present in ret vector.
 			// Available hexes are already present in ret vector.
-			auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex)
+			auto availableneighbour = boost::find_if(ret, [=] (BattleHex availableHex)
 			{
 			{
 				return BattleHex::mutualPosition(hex, availableHex) >= 0;
 				return BattleHex::mutualPosition(hex, availableHex) >= 0;
 			});
 			});
-			return availableNeighbor != ret.end();
+			return availableneighbour != ret.end();
 		};
 		};
 		for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide())))
 		for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide())))
 		{
 		{
 			if(!otherSt->isValidTarget(false))
 			if(!otherSt->isValidTarget(false))
 				continue;
 				continue;
 
 
-			std::vector<BattleHex> occupied = otherSt->getHexes();
+			BattleHexArray occupied = otherSt->getHexes();
 
 
 			if(battleCanShoot(unit, otherSt->getPosition()))
 			if(battleCanShoot(unit, otherSt->getPosition()))
 			{
 			{
-				attackable->insert(attackable->end(), occupied.begin(), occupied.end());
+				attackable->merge(occupied);
 				continue;
 				continue;
 			}
 			}
 
 
 			for(BattleHex he : occupied)
 			for(BattleHex he : occupied)
 			{
 			{
 				if(meleeAttackable(he))
 				if(meleeAttackable(he))
-					attackable->push_back(he);
+					attackable->insert(he);
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	//adding occupiable likely adds duplicates to ret -> clean it up
-	boost::sort(ret);
-	ret.erase(boost::unique(ret).end(), ret.end());
 	return ret;
 	return ret;
 }
 }
 
 
@@ -857,8 +851,8 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battl
 	RETURN_IF_NOT_BATTLE(obstacles);
 	RETURN_IF_NOT_BATTLE(obstacles);
 	for(auto & obs : battleGetAllObstacles())
 	for(auto & obs : battleGetAllObstacles())
 	{
 	{
-		if(vstd::contains(obs->getBlockedTiles(), tile)
-				|| (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile)))
+		if(obs->getBlockedTiles().contains(tile)
+				|| (!onlyBlocking && obs->getAffectedTiles().contains(tile)))
 		{
 		{
 			obstacles.push_back(obs);
 			obstacles.push_back(obs);
 		}
 		}
@@ -866,18 +860,18 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battl
 	return obstacles;
 	return obstacles;
 }
 }
 
 
-std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set<BattleHex> & passed) const
+std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const
 {
 {
 	auto affectedObstacles = std::vector<std::shared_ptr<const CObstacleInstance>>();
 	auto affectedObstacles = std::vector<std::shared_ptr<const CObstacleInstance>>();
 	RETURN_IF_NOT_BATTLE(affectedObstacles);
 	RETURN_IF_NOT_BATTLE(affectedObstacles);
 	if(unit->alive())
 	if(unit->alive())
 	{
 	{
-		if(!passed.count(unit->getPosition()))
+		if(!passed.contains(unit->getPosition()))
 			affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false);
 			affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false);
 		if(unit->doubleWide())
 		if(unit->doubleWide())
 		{
 		{
 			BattleHex otherHex = unit->occupiedHex();
 			BattleHex otherHex = unit->occupiedHex();
-			if(otherHex.isValid() && !passed.count(otherHex))
+			if(otherHex.isValid() && !passed.contains(otherHex))
 				for(auto & i : battleGetAllObstaclesOnPos(otherHex, false))
 				for(auto & i : battleGetAllObstaclesOnPos(otherHex, false))
 					if(!vstd::contains(affectedObstacles, i))
 					if(!vstd::contains(affectedObstacles, i))
 						affectedObstacles.push_back(i);
 						affectedObstacles.push_back(i);
@@ -891,7 +885,7 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl
 	return affectedObstacles;
 	return affectedObstacles;
 }
 }
 
 
-bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed) const
+bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const BattleHexArray & passed) const
 {
 {
 	if(!unit.alive())
 	if(!unit.alive())
 		return false;
 		return false;
@@ -970,7 +964,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 
 
 	if(bFieldType != BattleField::NONE)
 	if(bFieldType != BattleField::NONE)
 	{
 	{
-		std::vector<BattleHex> impassableHexes = bFieldType.getInfo()->impassableHexes;
+		BattleHexArray impassableHexes = bFieldType.getInfo()->impassableHexes;
 
 
 		for(auto hex : impassableHexes)
 		for(auto hex : impassableHexes)
 			ret[hex] = EAccessibility::UNAVAILABLE;
 			ret[hex] = EAccessibility::UNAVAILABLE;
@@ -1040,7 +1034,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * sta
 	return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide()));
 	return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide()));
 }
 }
 
 
-AccessibilityInfo CBattleInfoCallback::getAccessibility(const std::vector<BattleHex> & accessibleHexes) const
+AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray & accessibleHexes) const
 {
 {
 	auto ret = getAccessibility();
 	auto ret = getAccessibility();
 	for(auto hex : accessibleHexes)
 	for(auto hex : accessibleHexes)
@@ -1062,7 +1056,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 	if(!params.startPosition.isValid()) //if got call for arrow turrets
 	if(!params.startPosition.isValid()) //if got call for arrow turrets
 		return ret;
 		return ret;
 
 
-	const std::set<BattleHex> obstacles = getStoppers(params.perspective);
+	const BattleHexArray obstacles = getStoppers(params.perspective);
 	auto checkParams = params;
 	auto checkParams = params;
 	checkParams.ignoreKnownAccessible = true; //Ignore starting hexes obstacles
 	checkParams.ignoreKnownAccessible = true; //Ignore starting hexes obstacles
 
 
@@ -1087,7 +1081,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 
 
 		const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
 		const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
 
 
-		for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
+		for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[curHex.hex])
 		{
 		{
 			if(neighbour.isValid())
 			if(neighbour.isValid())
 			{
 			{
@@ -1120,17 +1114,17 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 
 
 bool CBattleInfoCallback::isInObstacle(
 bool CBattleInfoCallback::isInObstacle(
 	BattleHex hex,
 	BattleHex hex,
-	const std::set<BattleHex> & obstacles,
+	const BattleHexArray & obstacleHexes,
 	const ReachabilityInfo::Parameters & params) const
 	const ReachabilityInfo::Parameters & params) const
 {
 {
 	auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side);
 	auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side);
 
 
 	for(auto occupiedHex : occupiedHexes)
 	for(auto occupiedHex : occupiedHexes)
 	{
 	{
-		if(params.ignoreKnownAccessible && vstd::contains(params.knownAccessible, occupiedHex))
+		if(params.ignoreKnownAccessible && params.knownAccessible.contains(occupiedHex))
 			continue;
 			continue;
 
 
-		if(vstd::contains(obstacles, occupiedHex))
+		if(obstacleHexes.contains(occupiedHex))
 		{
 		{
 			if(occupiedHex == BattleHex::GATE_BRIDGE)
 			if(occupiedHex == BattleHex::GATE_BRIDGE)
 			{
 			{
@@ -1145,9 +1139,9 @@ bool CBattleInfoCallback::isInObstacle(
 	return false;
 	return false;
 }
 }
 
 
-std::set<BattleHex> CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) const
+BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) const
 {
 {
-	std::set<BattleHex> ret;
+	BattleHexArray ret;
 	RETURN_IF_NOT_BATTLE(ret);
 	RETURN_IF_NOT_BATTLE(ret);
 
 
 	for(auto &oi : battleGetAllObstacles(whichSidePerspective))
 	for(auto &oi : battleGetAllObstacles(whichSidePerspective))
@@ -1225,7 +1219,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS
 
 
 	auto accessibility = getAccessibility();
 	auto accessibility = getAccessibility();
 
 
-	std::set<BattleHex> occupyable;
+	BattleHexArray occupyable;
 	for(int i = 0; i < accessibility.size(); i++)
 	for(int i = 0; i < accessibility.size(); i++)
 		if(accessibility.accessible(i, twoHex, side))
 		if(accessibility.accessible(i, twoHex, side))
 			occupyable.insert(i);
 			occupyable.insert(i);
@@ -1235,7 +1229,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS
 		return BattleHex::INVALID; //all tiles are covered
 		return BattleHex::INVALID; //all tiles are covered
 	}
 	}
 
 
-	return BattleHex::getClosestTile(side, pos, occupyable);
+	return occupyable.getClosestTile(side, pos);
 }
 }
 
 
 si8 CBattleInfoCallback::battleGetTacticDist() const
 si8 CBattleInfoCallback::battleGetTacticDist() const
@@ -1353,7 +1347,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
 	}
 	}
 	if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK))
 	if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK))
 	{
 	{
-		std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
+		BattleHexArray hexes = attacker->getSurroundingHexes(attackerPos);
 		for(BattleHex tile : hexes)
 		for(BattleHex tile : hexes)
 		{
 		{
 			if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile
 			if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile
@@ -1366,12 +1360,12 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
 	}
 	}
 	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH))
 	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH))
 	{
 	{
-		std::vector<BattleHex> hexes = destinationTile.neighbouringTiles();
-		for(int i = 0; i<hexes.size(); i++)
+		BattleHexArray hexes = BattleHexArray::generateNeighbouringTiles(destinationTile);
+		for(int i = 0; i < hexes.size(); i++)
 		{
 		{
 			if(hexes.at(i) == attackOriginHex)
 			if(hexes.at(i) == attackOriginHex)
 			{
 			{
-				hexes.erase(hexes.begin() + i);
+				hexes.erase(i);
 				i = 0;
 				i = 0;
 			}
 			}
 		}
 		}
@@ -1439,10 +1433,10 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::
 	AttackableTiles at;
 	AttackableTiles at;
 	RETURN_IF_NOT_BATTLE(at);
 	RETURN_IF_NOT_BATTLE(at);
 
 
-	if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !vstd::contains(attackerPos.neighbouringTiles(), destinationTile))
+	if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::generateNeighbouringTiles(attackerPos).contains(destinationTile))
 	{
 	{
-		std::vector<BattleHex> targetHexes = destinationTile.neighbouringTiles();
-		targetHexes.push_back(destinationTile);
+		auto targetHexes = BattleHexArray::generateNeighbouringTiles(destinationTile);
+		targetHexes.insert(destinationTile);
 		boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions));
 		boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions));
 	}
 	}
 
 
@@ -1480,9 +1474,9 @@ std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits(
 
 
 		for (BattleHex hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
 		for (BattleHex hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
 		{
 		{
-			if (vstd::contains(at.hostileCreaturePositions, hex))
+			if (at.hostileCreaturePositions.contains(hex))
 				return true;
 				return true;
-			if (vstd::contains(at.friendlyCreaturePositions, hex))
+			if (at.friendlyCreaturePositions.contains(hex))
 				return true;
 				return true;
 		}
 		}
 		return false;
 		return false;
@@ -1671,15 +1665,15 @@ bool CBattleInfoCallback::isWallPartAttackable(EWallPart wallPart) const
 	return false;
 	return false;
 }
 }
 
 
-std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
+BattleHexArray CBattleInfoCallback::getAttackableBattleHexes() const
 {
 {
-	std::vector<BattleHex> attackableBattleHexes;
+	BattleHexArray attackableBattleHexes;
 	RETURN_IF_NOT_BATTLE(attackableBattleHexes);
 	RETURN_IF_NOT_BATTLE(attackableBattleHexes);
 
 
 	for(const auto & wallPartPair : wallParts)
 	for(const auto & wallPartPair : wallParts)
 	{
 	{
 		if(isWallPartAttackable(wallPartPair.second))
 		if(isWallPartAttackable(wallPartPair.second))
-			attackableBattleHexes.emplace_back(wallPartPair.first);
+			attackableBattleHexes.insert(wallPartPair.first);
 	}
 	}
 
 
 	return attackableBattleHexes;
 	return attackableBattleHexes;

+ 14 - 14
lib/battle/CBattleInfoCallback.h

@@ -38,8 +38,8 @@ namespace spells
 
 
 struct DLL_LINKAGE AttackableTiles
 struct DLL_LINKAGE AttackableTiles
 {
 {
-	std::set<BattleHex> hostileCreaturePositions;
-	std::set<BattleHex> friendlyCreaturePositions; //for Dragon Breath
+	BattleHexArray hostileCreaturePositions;
+	BattleHexArray friendlyCreaturePositions; //for Dragon Breath
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
 	{
 	{
 		h & hostileCreaturePositions;
 		h & hostileCreaturePositions;
@@ -59,9 +59,9 @@ public:
 	std::optional<BattleSide> battleIsFinished() const override; //return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
 	std::optional<BattleSide> battleIsFinished() const override; //return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
 
 
 	std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override;
 	std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override;
-	std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set<BattleHex> & passed) const override;
+	std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const override;
 	//Handle obstacle damage here, requires SpellCastEnvironment
 	//Handle obstacle damage here, requires SpellCastEnvironment
-	bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed = {}) const;
+	bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const BattleHexArray & passed = {}) const;
 
 
 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const;
 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const;
 
 
@@ -75,20 +75,20 @@ public:
 	void battleGetTurnOrder(std::vector<battle::Units> & out, const size_t maxUnits, const int maxTurns, const int turn = 0, BattleSide lastMoved = BattleSide::NONE) const;
 	void battleGetTurnOrder(std::vector<battle::Units> & out, const size_t maxUnits, const int maxTurns, const int turn = 0, BattleSide lastMoved = BattleSide::NONE) const;
 
 
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position
-	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, std::vector<BattleHex> * attackable) const;
+	BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const;
 
 
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position (lite version)
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position (lite version)
-	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const;
+	BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const;
 
 
-	std::vector<BattleHex> battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const;
+	BattleHexArray battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const;
 
 
 	int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
 	int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
 	ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
 	ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
-	std::set<BattleHex> battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
+	BattleHexArray battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
 	bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
 	bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
 	bool isHexWithinSpecifiedRange(BattleHex attackerPosition, BattleHex targetPosition, unsigned int range) const;
 	bool isHexWithinSpecifiedRange(BattleHex attackerPosition, BattleHex targetPosition, unsigned int range) const;
 
 
-	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const;
+	std::pair< BattleHexArray, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const;
 
 
 	bool battleCanTargetEmptyHex(const battle::Unit * attacker) const; //determines of stack with given ID can target empty hex to attack - currently used only for SPELL_LIKE_ATTACK shooting
 	bool battleCanTargetEmptyHex(const battle::Unit * attacker) const; //determines of stack with given ID can target empty hex to attack - currently used only for SPELL_LIKE_ATTACK shooting
 	bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
 	bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
@@ -116,7 +116,7 @@ public:
 	EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
 	EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
 	bool isWallPartPotentiallyAttackable(EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not
 	bool isWallPartPotentiallyAttackable(EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not
 	bool isWallPartAttackable(EWallPart wallPart) const; // returns true if the wall part is actually attackable, false if not
 	bool isWallPartAttackable(EWallPart wallPart) const; // returns true if the wall part is actually attackable, false if not
-	std::vector<BattleHex> getAttackableBattleHexes() const;
+	BattleHexArray getAttackableBattleHexes() const;
 
 
 	si8 battleMinSpellLevel(BattleSide side) const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	si8 battleMinSpellLevel(BattleSide side) const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	si8 battleMaxSpellLevel(BattleSide side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	si8 battleMaxSpellLevel(BattleSide side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
@@ -162,15 +162,15 @@ public:
 	ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const;
 	ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const;
 	AccessibilityInfo getAccessibility() const;
 	AccessibilityInfo getAccessibility() const;
 	AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible.
 	AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible.
-	AccessibilityInfo getAccessibility(const std::vector<BattleHex> & accessibleHexes) const; //given hexes will be marked as accessible
+	AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible
 	std::pair<const battle::Unit *, BattleHex> getNearestStack(const battle::Unit * closest) const;
 	std::pair<const battle::Unit *, BattleHex> getNearestStack(const battle::Unit * closest) const;
 
 
 	BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack
 	BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack
 protected:
 protected:
 	ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters & params) const;
 	ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters & params) const;
 	ReachabilityInfo makeBFS(const AccessibilityInfo & accessibility, const ReachabilityInfo::Parameters & params) const;
 	ReachabilityInfo makeBFS(const AccessibilityInfo & accessibility, const ReachabilityInfo::Parameters & params) const;
-	bool isInObstacle(BattleHex hex, const std::set<BattleHex> & obstacles, const ReachabilityInfo::Parameters & params) const;
-	std::set<BattleHex> getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands)
+	bool isInObstacle(BattleHex hex, const BattleHexArray & obstacles, const ReachabilityInfo::Parameters & params) const;
+	BattleHexArray getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands)
 };
 };
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 13 - 9
lib/battle/CObstacleInstance.cpp

@@ -24,21 +24,21 @@ const ObstacleInfo & CObstacleInstance::getInfo() const
 	return *Obstacle(ID).getInfo();
 	return *Obstacle(ID).getInfo();
 }
 }
 
 
-std::vector<BattleHex> CObstacleInstance::getBlockedTiles() const
+BattleHexArray CObstacleInstance::getBlockedTiles() const
 {
 {
 	if(blocksTiles())
 	if(blocksTiles())
 		return getAffectedTiles();
 		return getAffectedTiles();
-	return std::vector<BattleHex>();
+	return BattleHexArray();
 }
 }
 
 
-std::vector<BattleHex> CObstacleInstance::getStoppingTile() const
+BattleHexArray CObstacleInstance::getStoppingTile() const
 {
 {
 	if(stopsMovement())
 	if(stopsMovement())
 		return getAffectedTiles();
 		return getAffectedTiles();
-	return std::vector<BattleHex>();
+	return BattleHexArray();
 }
 }
 
 
-std::vector<BattleHex> CObstacleInstance::getAffectedTiles() const
+BattleHexArray CObstacleInstance::getAffectedTiles() const
 {
 {
 	switch(obstacleType)
 	switch(obstacleType)
 	{
 	{
@@ -47,7 +47,7 @@ std::vector<BattleHex> CObstacleInstance::getAffectedTiles() const
 		return getInfo().getBlocked(pos);
 		return getInfo().getBlocked(pos);
 	default:
 	default:
 		assert(0);
 		assert(0);
-		return std::vector<BattleHex>();
+		return BattleHexArray();
 	}
 	}
 }
 }
 
 
@@ -215,12 +215,16 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
 		JsonArraySerializer customSizeJson = handler.enterArray("customSize");
 		JsonArraySerializer customSizeJson = handler.enterArray("customSize");
 		customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER);
 		customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER);
 
 
+		BattleHex hex;
 		for(size_t index = 0; index < customSizeJson.size(); index++)
 		for(size_t index = 0; index < customSizeJson.size(); index++)
-			customSizeJson.serializeInt(index, customSize.at(index));
+		{
+			customSizeJson.serializeInt(index, hex);
+			customSize.set(index, hex);
+		}
 	}
 	}
 }
 }
 
 
-std::vector<BattleHex> SpellCreatedObstacle::getAffectedTiles() const
+BattleHexArray SpellCreatedObstacle::getAffectedTiles() const
 {
 {
 	return customSize;
 	return customSize;
 }
 }
@@ -258,4 +262,4 @@ int SpellCreatedObstacle::getAnimationYOffset(int imageHeight) const
 	return offset;
 	return offset;
 }
 }
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 6 - 6
lib/battle/CObstacleInstance.h

@@ -8,7 +8,7 @@
  *
  *
  */
  */
 #pragma once
 #pragma once
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 
 
 #include "../constants/EntityIdentifiers.h"
 #include "../constants/EntityIdentifiers.h"
 #include "../filesystem/ResourcePath.h"
 #include "../filesystem/ResourcePath.h"
@@ -39,8 +39,8 @@ struct DLL_LINKAGE CObstacleInstance : public Serializeable
 
 
 	const ObstacleInfo &getInfo() const; //allowed only when not generated by spell (usual or absolute)
 	const ObstacleInfo &getInfo() const; //allowed only when not generated by spell (usual or absolute)
 
 
-	std::vector<BattleHex> getBlockedTiles() const;
-	std::vector<BattleHex> getStoppingTile() const; //hexes that will stop stack move
+	BattleHexArray getBlockedTiles() const;
+	BattleHexArray getStoppingTile() const; //hexes that will stop stack move
 
 
 	//The two functions below describe how the obstacle affects affected tiles
 	//The two functions below describe how the obstacle affects affected tiles
 	//additional effects (like hurting stack or disappearing) are hardcoded for appropriate obstacleTypes
 	//additional effects (like hurting stack or disappearing) are hardcoded for appropriate obstacleTypes
@@ -49,7 +49,7 @@ struct DLL_LINKAGE CObstacleInstance : public Serializeable
 	virtual bool triggersEffects() const;
 	virtual bool triggersEffects() const;
 	virtual SpellID getTrigger() const;
 	virtual SpellID getTrigger() const;
 
 
-	virtual std::vector<BattleHex> getAffectedTiles() const;
+	virtual BattleHexArray getAffectedTiles() const;
 	virtual bool visibleForSide(BattleSide side, bool hasNativeStack) const; //0 attacker
 	virtual bool visibleForSide(BattleSide side, bool hasNativeStack) const; //0 attacker
 
 
 	virtual void battleTurnPassed(){};
 	virtual void battleTurnPassed(){};
@@ -97,11 +97,11 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
 
 
 	int animationYOffset;
 	int animationYOffset;
 
 
-	std::vector<BattleHex> customSize;
+	BattleHexArray customSize;
 
 
 	SpellCreatedObstacle();
 	SpellCreatedObstacle();
 
 
-	std::vector<BattleHex> getAffectedTiles() const override;
+	BattleHexArray getAffectedTiles() const override;
 	bool visibleForSide(BattleSide side, bool hasNativeStack) const override;
 	bool visibleForSide(BattleSide side, bool hasNativeStack) const override;
 
 
 	bool blocksTiles() const override;
 	bool blocksTiles() const override;

+ 43 - 43
lib/battle/Destination.h

@@ -1,43 +1,43 @@
-/*
- * Destination.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 "BattleHex.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace battle
-{
-
-class Unit;
-
-class DLL_LINKAGE Destination
-{
-public:
-	Destination();
-	~Destination() = default;
-	explicit Destination(const Unit * destination);
-	explicit Destination(const BattleHex & destination);
-	explicit Destination(const Unit * destination, const BattleHex & exactHex);
-
-	Destination(const Destination & other) = default;
-
-	Destination & operator=(const Destination & other) = default;
-
-	const Unit * unitValue;
-	BattleHex hexValue;
-};
-
-using Target = std::vector<Destination>;
-
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Destination.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 "BattleHexArray.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace battle
+{
+
+class Unit;
+
+class DLL_LINKAGE Destination
+{
+public:
+	Destination();
+	~Destination() = default;
+	explicit Destination(const Unit * destination);
+	explicit Destination(const BattleHex & destination);
+	explicit Destination(const Unit * destination, const BattleHex & exactHex);
+
+	Destination(const Destination & other) = default;
+
+	Destination & operator=(const Destination & other) = default;
+
+	const Unit * unitValue;
+	BattleHex hexValue;
+};
+
+using Target = std::vector<Destination>;
+
+}
+
+VCMI_LIB_NAMESPACE_END

+ 89 - 89
lib/battle/IBattleInfoCallback.h

@@ -1,89 +1,89 @@
-/*
- * IBattleInfoCallback.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 "GameConstants.h"
-#include "BattleHex.h"
-
-#include <vcmi/Entity.h>
-
-#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false)
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct CObstacleInstance;
-class BattleField;
-class IBattleInfo;
-
-namespace battle
-{
-	class IUnitInfo;
-	class Unit;
-	using Units = std::vector<const Unit *>;
-	using UnitFilter = std::function<bool(const Unit *)>;
-}
-
-struct DamageRange
-{
-	int64_t min = 0;
-	int64_t max = 0;
-};
-
-struct DamageEstimation
-{
-	DamageRange damage;
-	DamageRange kills;
-};
-
-#if SCRIPTING_ENABLED
-namespace scripting
-{
-	class Pool;
-}
-#endif
-
-class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider
-{
-public:
-#if SCRIPTING_ENABLED
-	virtual scripting::Pool * getContextPool() const = 0;
-#endif
-	virtual ~IBattleInfoCallback() = default;
-
-	virtual const IBattleInfo * getBattle() const = 0;
-	virtual std::optional<PlayerColor> getPlayerID() const = 0;
-
-	virtual TerrainId battleTerrainType() const = 0;
-	virtual BattleField battleGetBattlefieldType() const = 0;
-
-	///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
-	virtual std::optional<BattleSide> battleIsFinished() const = 0;
-
-	virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase
-	virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?)
-
-	virtual uint32_t battleNextUnitId() const = 0;
-
-	virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0;
-
-	virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0;
-	virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0;
-
-	virtual const battle::Unit * battleActiveUnit() const = 0;
-
-	//blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands)
-	virtual std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0;
-	virtual std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set<BattleHex> & passed) const = 0;
-};
-
-
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * IBattleInfoCallback.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 "GameConstants.h"
+#include "BattleHexArray.h"
+
+#include <vcmi/Entity.h>
+
+#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false)
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct CObstacleInstance;
+class BattleField;
+class IBattleInfo;
+
+namespace battle
+{
+	class IUnitInfo;
+	class Unit;
+	using Units = std::vector<const Unit *>;
+	using UnitFilter = std::function<bool(const Unit *)>;
+}
+
+struct DamageRange
+{
+	int64_t min = 0;
+	int64_t max = 0;
+};
+
+struct DamageEstimation
+{
+	DamageRange damage;
+	DamageRange kills;
+};
+
+#if SCRIPTING_ENABLED
+namespace scripting
+{
+	class Pool;
+}
+#endif
+
+class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider
+{
+public:
+#if SCRIPTING_ENABLED
+	virtual scripting::Pool * getContextPool() const = 0;
+#endif
+	virtual ~IBattleInfoCallback() = default;
+
+	virtual const IBattleInfo * getBattle() const = 0;
+	virtual std::optional<PlayerColor> getPlayerID() const = 0;
+
+	virtual TerrainId battleTerrainType() const = 0;
+	virtual BattleField battleGetBattlefieldType() const = 0;
+
+	///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
+	virtual std::optional<BattleSide> battleIsFinished() const = 0;
+
+	virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase
+	virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?)
+
+	virtual uint32_t battleNextUnitId() const = 0;
+
+	virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0;
+
+	virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0;
+	virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0;
+
+	virtual const battle::Unit * battleActiveUnit() const = 0;
+
+	//blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands)
+	virtual std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0;
+	virtual std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0;
+};
+
+
+
+VCMI_LIB_NAMESPACE_END

+ 89 - 91
lib/battle/ReachabilityInfo.cpp

@@ -1,91 +1,89 @@
-/*
- * ReachabilityInfo.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 "ReachabilityInfo.h"
-#include "Unit.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition):
-	perspective(static_cast<BattleSide>(Stack->unitSide())),
-	startPosition(StartPosition),
-	doubleWide(Stack->doubleWide()),
-	side(Stack->unitSide()),
-	flying(Stack->hasBonusOfType(BonusType::FLYING))
-{
-	knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side);
-}
-
-ReachabilityInfo::ReachabilityInfo()
-{
-	distances.fill(INFINITE_DIST);
-	predecessors.fill(BattleHex::INVALID);
-}
-
-bool ReachabilityInfo::isReachable(BattleHex hex) const
-{
-	return distances[hex] < INFINITE_DIST;
-}
-
-uint32_t ReachabilityInfo::distToNearestNeighbour(
-	const std::vector<BattleHex> & targetHexes,
-	BattleHex * chosenHex) const
-{
-	uint32_t ret = 1000000;
-
-	for(auto targetHex : targetHexes)
-	{
-		for(auto & n : targetHex.neighbouringTiles())
-		{
-			if(distances[n] < ret)
-			{
-				ret = distances[n];
-				if(chosenHex)
-					*chosenHex = n;
-			}
-		}
-	}
-
-	return ret;
-}
-
-uint32_t ReachabilityInfo::distToNearestNeighbour(
-	const battle::Unit * attacker,
-	const battle::Unit * defender,
-	BattleHex * chosenHex) const
-{
-	auto attackableHexes = defender->getHexes();
-
-	if(attacker->doubleWide())
-	{
-		if(defender->doubleWide())
-		{
-			// It can be back to back attack  o==o  or head to head  =oo=.
-			// In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles
-			vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide()));
-		}
-		else
-		{
-			vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide()));
-		}
-	}
-
-	vstd::removeDuplicates(attackableHexes);
-
-	vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool
-		{
-			return h.getY() != defender->getPosition().getY() || !h.isAvailable();
-		});
-
-	return distToNearestNeighbour(attackableHexes, chosenHex);
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * ReachabilityInfo.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 "ReachabilityInfo.h"
+#include "Unit.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition):
+	perspective(static_cast<BattleSide>(Stack->unitSide())),
+	startPosition(StartPosition),
+	doubleWide(Stack->doubleWide()),
+	side(Stack->unitSide()),
+	flying(Stack->hasBonusOfType(BonusType::FLYING))
+{
+	knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side);
+}
+
+ReachabilityInfo::ReachabilityInfo()
+{
+	distances.fill(INFINITE_DIST);
+	predecessors.fill(BattleHex::INVALID);
+}
+
+bool ReachabilityInfo::isReachable(BattleHex hex) const
+{
+	return distances[hex] < INFINITE_DIST;
+}
+
+uint32_t ReachabilityInfo::distToNearestNeighbour(
+	const BattleHexArray & targetHexes,
+	BattleHex * chosenHex) const
+{
+	uint32_t ret = 1000000;
+
+	for(auto targetHex : targetHexes)
+	{
+		for(auto & n : BattleHexArray::generateNeighbouringTiles(targetHex))
+		{
+			if(distances[n] < ret)
+			{
+				ret = distances[n];
+				if(chosenHex)
+					*chosenHex = n;
+			}
+		}
+	}
+
+	return ret;
+}
+
+uint32_t ReachabilityInfo::distToNearestNeighbour(
+	const battle::Unit * attacker,
+	const battle::Unit * defender,
+	BattleHex * chosenHex) const
+{
+	auto attackableHexes = defender->getHexes();
+
+	if(attacker->doubleWide())
+	{
+		if(defender->doubleWide())
+		{
+			// It can be back to back attack  o==o  or head to head  =oo=.
+			// In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles
+			attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide()));
+		}
+		else
+		{
+			attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide()));
+		}
+	}
+
+	vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool
+		{
+			return h.getY() != defender->getPosition().getY() || !h.isAvailable();
+		});
+
+	return distToNearestNeighbour(attackableHexes, chosenHex);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 5 - 3
lib/battle/ReachabilityInfo.h

@@ -8,12 +8,14 @@
  *
  *
  */
  */
 #pragma once
 #pragma once
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 #include "CBattleInfoEssentials.h"
 #include "CBattleInfoEssentials.h"
 #include "AccessibilityInfo.h"
 #include "AccessibilityInfo.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+class BattleHexArray;
+
 // Reachability info is result of BFS calculation. It's dependent on stack (it's owner, whether it's flying),
 // Reachability info is result of BFS calculation. It's dependent on stack (it's owner, whether it's flying),
 // startPosition and perspective.
 // startPosition and perspective.
 struct DLL_LINKAGE ReachabilityInfo
 struct DLL_LINKAGE ReachabilityInfo
@@ -30,7 +32,7 @@ struct DLL_LINKAGE ReachabilityInfo
 		bool flying = false;
 		bool flying = false;
 		bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
 		bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
 		bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
 		bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
-		std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
+		BattleHexArray knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
 		std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
 		std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
 
 
 		BattleHex startPosition; //assumed position of stack
 		BattleHex startPosition; //assumed position of stack
@@ -50,7 +52,7 @@ struct DLL_LINKAGE ReachabilityInfo
 	bool isReachable(BattleHex hex) const;
 	bool isReachable(BattleHex hex) const;
 
 
 	uint32_t distToNearestNeighbour(
 	uint32_t distToNearestNeighbour(
-		const std::vector<BattleHex> & targetHexes,
+		const BattleHexArray & targetHexes,
 		BattleHex * chosenHex = nullptr) const;
 		BattleHex * chosenHex = nullptr) const;
 
 
 	uint32_t distToNearestNeighbour(
 	uint32_t distToNearestNeighbour(

+ 240 - 242
lib/battle/Unit.cpp

@@ -1,242 +1,240 @@
-/*
- * Unit.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 "Unit.h"
-
-#include "../VCMI_Lib.h"
-#include "../texts/CGeneralTextHandler.h"
-
-#include "../serializer/JsonDeserializer.h"
-#include "../serializer/JsonSerializer.h"
-
-#include <vcmi/Faction.h>
-#include <vcmi/FactionService.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace battle
-{
-
-///Unit
-Unit::~Unit() = default;
-
-bool Unit::isDead() const
-{
-	return !alive() && !isGhost();
-}
-
-bool Unit::isTurret() const
-{
-	return creatureIndex() == CreatureID::ARROW_TOWERS;
-}
-
-std::string Unit::getDescription() const
-{
-	boost::format fmt("Unit %d of side %d");
-	fmt % unitId() % static_cast<int>(unitSide());
-	return fmt.str();
-}
-
-//TODO: deduplicate these functions
-const IBonusBearer* Unit::getBonusBearer() const
-{
-	return this;
-}
-
-std::vector<BattleHex> Unit::getSurroundingHexes(BattleHex assumedPosition) const
-{
-	BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position
-
-	return getSurroundingHexes(hex, doubleWide(), unitSide());
-}
-
-std::vector<BattleHex> Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side)
-{
-	std::vector<BattleHex> hexes;
-	if(twoHex)
-	{
-		const BattleHex otherHex = occupiedHex(position, twoHex, side);
-
-		if(side == BattleSide::ATTACKER)
-		{
-			for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
-				BattleHex::checkAndPush(position.cloneInDirection(dir, false), hexes);
-
-			BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), hexes);
-			BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false), hexes);
-			BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), hexes);
-		}
-		else
-		{
-			BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), hexes);
-
-			for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
-				BattleHex::checkAndPush(otherHex.cloneInDirection(dir, false), hexes);
-
-			BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), hexes);
-			BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false), hexes);
-		}
-		return hexes;
-	}
-	else
-	{
-		return position.neighbouringTiles();
-	}
-}
-
-std::vector<BattleHex> Unit::getAttackableHexes(const Unit * attacker) const
-{
-	auto defenderHexes = battle::Unit::getHexes(
-		getPosition(),
-		doubleWide(),
-		unitSide());
-	
-	std::vector<BattleHex> targetableHexes;
-
-	for(auto defenderHex : defenderHexes)
-	{
-		auto hexes = battle::Unit::getHexes(
-			defenderHex,
-			attacker->doubleWide(),
-			unitSide());
-
-		if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1)
-			hexes.pop_back();
-
-		for(auto hex : hexes)
-			vstd::concatenate(targetableHexes, hex.neighbouringTiles());
-	}
-
-	vstd::removeDuplicates(targetableHexes);
-
-	return targetableHexes;
-}
-
-bool Unit::coversPos(BattleHex pos) const
-{
-	return getPosition() == pos || (doubleWide() && (occupiedHex() == pos));
-}
-
-std::vector<BattleHex> Unit::getHexes() const
-{
-	return getHexes(getPosition(), doubleWide(), unitSide());
-}
-
-std::vector<BattleHex> Unit::getHexes(BattleHex assumedPos) const
-{
-	return getHexes(assumedPos, doubleWide(), unitSide());
-}
-
-std::vector<BattleHex> Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side)
-{
-	std::vector<BattleHex> hexes;
-	hexes.push_back(assumedPos);
-
-	if(twoHex)
-		hexes.push_back(occupiedHex(assumedPos, twoHex, side));
-
-	return hexes;
-}
-
-BattleHex Unit::occupiedHex() const
-{
-	return occupiedHex(getPosition(), doubleWide(), unitSide());
-}
-
-BattleHex Unit::occupiedHex(BattleHex assumedPos) const
-{
-	return occupiedHex(assumedPos, doubleWide(), unitSide());
-}
-
-BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side)
-{
-	if(twoHex)
-	{
-		if(side == BattleSide::ATTACKER)
-			return assumedPos - 1;
-		else
-			return assumedPos + 1;
-	}
-	else
-	{
-		return BattleHex::INVALID;
-	}
-}
-
-void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const
-{
-	if(boost::logic::indeterminate(plural))
-		serial = VLC->generaltexth->pluralText(serial, getCount());
-	else if(plural)
-		serial = VLC->generaltexth->pluralText(serial, 2);
-	else
-		serial = VLC->generaltexth->pluralText(serial, 1);
-
-	text.appendLocalString(type, serial);
-}
-
-void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const
-{
-	if(boost::logic::indeterminate(plural))
-		text.replaceName(creatureId(), getCount());
-	else if(plural)
-		text.replaceNamePlural(creatureIndex());
-	else
-		text.replaceNameSingular(creatureIndex());
-}
-
-std::string Unit::formatGeneralMessage(const int32_t baseTextId) const
-{
-	const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount());
-
-	MetaString text;
-	text.appendLocalString(EMetaText::GENERAL_TXT, textId);
-	text.replaceName(creatureId(), getCount());
-
-	return text.toString();
-}
-
-int Unit::getRawSurrenderCost() const
-{
-	//we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
-	if(unitSlot().validSlot())
-		return creatureCost() * getCount();
-	else
-		return 0;
-}
-
-///UnitInfo
-void UnitInfo::serializeJson(JsonSerializeFormat & handler)
-{
-	handler.serializeInt("count", count);
-	handler.serializeId("type", type, CreatureID(CreatureID::NONE));
-	handler.serializeInt("side", side);
-	handler.serializeInt("position", position);
-	handler.serializeBool("summoned", summoned);
-}
-
-void UnitInfo::save(JsonNode & data)
-{
-	data.clear();
-	JsonSerializer ser(nullptr, data);
-	ser.serializeStruct("newUnitInfo", *this);
-}
-
-void UnitInfo::load(uint32_t id_, const JsonNode & data)
-{
-	id = id_;
-    JsonDeserializer deser(nullptr, data);
-    deser.serializeStruct("newUnitInfo", *this);
-}
-
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Unit.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 "Unit.h"
+
+#include "../VCMI_Lib.h"
+#include "../texts/CGeneralTextHandler.h"
+
+#include "../serializer/JsonDeserializer.h"
+#include "../serializer/JsonSerializer.h"
+
+#include <vcmi/Faction.h>
+#include <vcmi/FactionService.h>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace battle
+{
+
+///Unit
+Unit::~Unit() = default;
+
+bool Unit::isDead() const
+{
+	return !alive() && !isGhost();
+}
+
+bool Unit::isTurret() const
+{
+	return creatureIndex() == CreatureID::ARROW_TOWERS;
+}
+
+std::string Unit::getDescription() const
+{
+	boost::format fmt("Unit %d of side %d");
+	fmt % unitId() % static_cast<int>(unitSide());
+	return fmt.str();
+}
+
+//TODO: deduplicate these functions
+const IBonusBearer* Unit::getBonusBearer() const
+{
+	return this;
+}
+
+BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const
+{
+	BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position
+
+	return getSurroundingHexes(hex, doubleWide(), unitSide());
+}
+
+BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side)
+{
+	BattleHexArray hexes;
+	if(twoHex)
+	{
+		const BattleHex otherHex = occupiedHex(position, twoHex, side);
+
+		if(side == BattleSide::ATTACKER)
+		{
+			for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
+				hexes.checkAndPush(position.cloneInDirection(dir, false));
+
+			hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
+			hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false));
+			hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
+		}
+		else
+		{
+			hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
+
+			for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
+				hexes.checkAndPush(otherHex.cloneInDirection(dir, false));
+
+			hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
+			hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false));
+		}
+		return hexes;
+	}
+	else
+	{
+		return BattleHexArray::generateNeighbouringTiles(position);
+	}
+}
+
+BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
+{
+	auto defenderHexes = battle::Unit::getHexes(
+		getPosition(),
+		doubleWide(),
+		unitSide());
+	
+	BattleHexArray targetableHexes;
+
+	for(auto defenderHex : defenderHexes)
+	{
+		auto hexes = battle::Unit::getHexes(
+			defenderHex,
+			attacker->doubleWide(),
+			unitSide());
+
+		if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1)
+			hexes.pop_back();
+
+		for(auto hex : hexes)
+			targetableHexes.merge(BattleHexArray::generateNeighbouringTiles(hex));
+	}
+
+	return targetableHexes;
+}
+
+bool Unit::coversPos(BattleHex pos) const
+{
+	return getPosition() == pos || (doubleWide() && (occupiedHex() == pos));
+}
+
+BattleHexArray Unit::getHexes() const
+{
+	return getHexes(getPosition(), doubleWide(), unitSide());
+}
+
+BattleHexArray Unit::getHexes(BattleHex assumedPos) const
+{
+	return getHexes(assumedPos, doubleWide(), unitSide());
+}
+
+BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side)
+{
+	BattleHexArray hexes;
+	hexes.insert(assumedPos);
+
+	if(twoHex)
+		hexes.insert(occupiedHex(assumedPos, twoHex, side));
+
+	return hexes;
+}
+
+BattleHex Unit::occupiedHex() const
+{
+	return occupiedHex(getPosition(), doubleWide(), unitSide());
+}
+
+BattleHex Unit::occupiedHex(BattleHex assumedPos) const
+{
+	return occupiedHex(assumedPos, doubleWide(), unitSide());
+}
+
+BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side)
+{
+	if(twoHex)
+	{
+		if(side == BattleSide::ATTACKER)
+			return assumedPos - 1;
+		else
+			return assumedPos + 1;
+	}
+	else
+	{
+		return BattleHex::INVALID;
+	}
+}
+
+void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const
+{
+	if(boost::logic::indeterminate(plural))
+		serial = VLC->generaltexth->pluralText(serial, getCount());
+	else if(plural)
+		serial = VLC->generaltexth->pluralText(serial, 2);
+	else
+		serial = VLC->generaltexth->pluralText(serial, 1);
+
+	text.appendLocalString(type, serial);
+}
+
+void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const
+{
+	if(boost::logic::indeterminate(plural))
+		text.replaceName(creatureId(), getCount());
+	else if(plural)
+		text.replaceNamePlural(creatureIndex());
+	else
+		text.replaceNameSingular(creatureIndex());
+}
+
+std::string Unit::formatGeneralMessage(const int32_t baseTextId) const
+{
+	const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount());
+
+	MetaString text;
+	text.appendLocalString(EMetaText::GENERAL_TXT, textId);
+	text.replaceName(creatureId(), getCount());
+
+	return text.toString();
+}
+
+int Unit::getRawSurrenderCost() const
+{
+	//we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
+	if(unitSlot().validSlot())
+		return creatureCost() * getCount();
+	else
+		return 0;
+}
+
+///UnitInfo
+void UnitInfo::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("count", count);
+	handler.serializeId("type", type, CreatureID(CreatureID::NONE));
+	handler.serializeInt("side", side);
+	handler.serializeInt("position", position);
+	handler.serializeBool("summoned", summoned);
+}
+
+void UnitInfo::save(JsonNode & data)
+{
+	data.clear();
+	JsonSerializer ser(nullptr, data);
+	ser.serializeStruct("newUnitInfo", *this);
+}
+
+void UnitInfo::load(uint32_t id_, const JsonNode & data)
+{
+	id = id_;
+    JsonDeserializer deser(nullptr, data);
+    deser.serializeStruct("newUnitInfo", *this);
+}
+
+}
+
+VCMI_LIB_NAMESPACE_END

+ 9 - 10
lib/battle/Unit.h

@@ -17,7 +17,7 @@
 #include "../bonuses/IBonusBearer.h"
 #include "../bonuses/IBonusBearer.h"
 
 
 #include "IUnitInfo.h"
 #include "IUnitInfo.h"
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -84,14 +84,11 @@ public:
 	bool isTurret() const;
 	bool isTurret() const;
 	virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
 	virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
 
 
-	virtual bool isHypnotized() const = 0;
-
 	virtual bool isClone() const = 0;
 	virtual bool isClone() const = 0;
 	virtual bool hasClone() const = 0;
 	virtual bool hasClone() const = 0;
 
 
 	virtual bool canCast() const = 0;
 	virtual bool canCast() const = 0;
 	virtual bool isCaster() const = 0;
 	virtual bool isCaster() const = 0;
-	virtual bool canShootBlocked() const = 0;
 	virtual bool canShoot() const = 0;
 	virtual bool canShoot() const = 0;
 	virtual bool isShooter() const = 0;
 	virtual bool isShooter() const = 0;
 
 
@@ -115,6 +112,8 @@ public:
 	virtual BattleHex getPosition() const = 0;
 	virtual BattleHex getPosition() const = 0;
 	virtual void setPosition(BattleHex hex) = 0;
 	virtual void setPosition(BattleHex hex) = 0;
 
 
+	virtual int32_t getInitiative(int turn = 0) const = 0;
+
 	virtual bool canMove(int turn = 0) const = 0; //if stack can move
 	virtual bool canMove(int turn = 0) const = 0; //if stack can move
 	virtual bool defended(int turn = 0) const = 0;
 	virtual bool defended(int turn = 0) const = 0;
 	virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn
 	virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn
@@ -128,15 +127,15 @@ public:
 
 
 	virtual std::string getDescription() const;
 	virtual std::string getDescription() const;
 
 
-	std::vector<BattleHex> getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
-	std::vector<BattleHex> getAttackableHexes(const Unit * attacker) const;
-	static std::vector<BattleHex> getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side);
+	BattleHexArray getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
+	BattleHexArray getAttackableHexes(const Unit * attacker) const;
+	static BattleHexArray getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side);
 
 
 	bool coversPos(BattleHex position) const; //checks also if unit is double-wide
 	bool coversPos(BattleHex position) const; //checks also if unit is double-wide
 
 
-	std::vector<BattleHex> getHexes() const; //up to two occupied hexes, starting from front
-	std::vector<BattleHex> getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front
-	static std::vector<BattleHex> getHexes(BattleHex assumedPos, bool twoHex, BattleSide side);
+	BattleHexArray getHexes() const; //up to two occupied hexes, starting from front
+	BattleHexArray getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front
+	static BattleHexArray getHexes(BattleHex assumedPos, bool twoHex, BattleSide side);
 
 
 	BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1
 	BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1
 	BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1
 	BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1

+ 1 - 1
lib/bonuses/BonusEnum.h

@@ -142,7 +142,7 @@ class JsonNode;
 	BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\
 	BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\
 	BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\
 	BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\
 	BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\
 	BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\
-	BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighboring with target) without spell-like mechanics */\
+	BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighbouring with target) without spell-like mechanics */\
 	BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
 	BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
 	BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
 	BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
 	BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \
 	BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \

+ 1 - 1
lib/bonuses/Limiters.cpp

@@ -226,7 +226,7 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex
 	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 }
 }
 
 
-UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
+UnitOnHexLimiter::UnitOnHexLimiter(const BattleHexArray & applicableHexes):
 	applicableHexes(applicableHexes)
 	applicableHexes(applicableHexes)
 {
 {
 }
 }

+ 2 - 2
lib/bonuses/Limiters.h

@@ -263,9 +263,9 @@ public:
 class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
 class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
 {
 {
 public:
 public:
-	std::set<BattleHex> applicableHexes;
+	BattleHexArray applicableHexes;
 
 
-	UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes = {});
+	UnitOnHexLimiter(const BattleHexArray & applicableHexes = {});
 	EDecision limit(const BonusLimitationContext &context) const override;
 	EDecision limit(const BonusLimitationContext &context) const override;
 	JsonNode toJsonNode() const override;
 	JsonNode toJsonNode() const override;
 
 

+ 1 - 1
lib/mapping/CMap.cpp

@@ -278,7 +278,7 @@ CGHeroInstance * CMap::getHero(HeroTypeID heroID)
 
 
 bool CMap::isCoastalTile(const int3 & pos) const
 bool CMap::isCoastalTile(const int3 & pos) const
 {
 {
-	//todo: refactoring: extract neighbor tile iterator and use it in GameState
+	//todo: refactoring: extract neighbour tile iterator and use it in GameState
 	static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
 	static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
 					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
 					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
 
 

+ 3 - 2
lib/networkPacks/PacksForClientBattle.h

@@ -22,6 +22,7 @@ class CGHeroInstance;
 class CArmedInstance;
 class CArmedInstance;
 class IBattleState;
 class IBattleState;
 class BattleInfo;
 class BattleInfo;
+class BattleHexArray;
 
 
 struct DLL_LINKAGE BattleStart : public CPackForClient
 struct DLL_LINKAGE BattleStart : public CPackForClient
 {
 {
@@ -170,10 +171,10 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 {
 {
 	BattleID battleID = BattleID::NONE;
 	BattleID battleID = BattleID::NONE;
 	ui32 stack = 0;
 	ui32 stack = 0;
-	std::vector<BattleHex> tilesToMove;
+	BattleHexArray tilesToMove;
 	int distance = 0;
 	int distance = 0;
 	bool teleporting = false;
 	bool teleporting = false;
-
+	
 	void applyGs(CGameState * gs) override;
 	void applyGs(CGameState * gs) override;
 	void applyBattle(IBattleState * battleState);
 	void applyBattle(IBattleState * battleState);
 
 

+ 243 - 243
lib/pathfinder/CGPathNode.h

@@ -1,243 +1,243 @@
-/*
- * CGPathNode.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 "../GameConstants.h"
-#include "../int3.h"
-
-#include <boost/heap/fibonacci_heap.hpp>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CGHeroInstance;
-class CGObjectInstance;
-class CGameState;
-class CPathfinderHelper;
-struct TerrainTile;
-
-template<typename N>
-struct DLL_LINKAGE NodeComparer
-{
-	STRONG_INLINE
-	bool operator()(const N * lhs, const N * rhs) const
-	{
-		return lhs->getCost() > rhs->getCost();
-	}
-};
-
-enum class EPathAccessibility : ui8
-{
-	NOT_SET,
-	ACCESSIBLE, //tile can be entered and passed
-	VISITABLE, //tile can be entered as the last tile in path
-	GUARDED,  //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any)
-	BLOCKVIS,  //visitable from neighboring tile but not passable
-	FLYABLE, //can only be accessed in air layer
-	BLOCKED //tile can be neither entered nor visited
-};
-
-enum class EPathNodeAction : ui8
-{
-	UNKNOWN,
-	EMBARK,
-	DISEMBARK,
-	NORMAL,
-	BATTLE,
-	VISIT,
-	BLOCKING_VISIT,
-	TELEPORT_NORMAL,
-	TELEPORT_BLOCKING_VISIT,
-	TELEPORT_BATTLE
-};
-
-struct DLL_LINKAGE CGPathNode
-{
-	using TFibHeap = boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>>>;
-	using ELayer = EPathfindingLayer;
-
-	TFibHeap::handle_type pqHandle;
-	TFibHeap * pq;
-	CGPathNode * theNodeBefore;
-
-	int3 coord; //coordinates
-	ELayer layer;
-
-	float cost; //total cost of the path to this tile measured in turns with fractions
-	int moveRemains; //remaining movement points after hero reaches the tile
-	ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn
-	EPathAccessibility accessible;
-	EPathNodeAction action;
-	bool locked;
-
-	CGPathNode()
-		: coord(-1),
-		layer(ELayer::WRONG),
-		pqHandle(nullptr)
-	{
-		reset();
-	}
-
-	STRONG_INLINE
-	void reset()
-	{
-		locked = false;
-		accessible = EPathAccessibility::NOT_SET;
-		moveRemains = 0;
-		cost = std::numeric_limits<float>::max();
-		turns = 255;
-		theNodeBefore = nullptr;
-		pq = nullptr;
-		action = EPathNodeAction::UNKNOWN;
-	}
-
-	STRONG_INLINE
-	bool inPQ() const
-	{
-		return pq != nullptr;
-	}
-
-	STRONG_INLINE
-	float getCost() const
-	{
-		return cost;
-	}
-
-	STRONG_INLINE
-	void setCost(float value)
-	{
-		if(vstd::isAlmostEqual(value, cost))
-			return;
-
-		bool getUpNode = value < cost;
-		cost = value;
-		// If the node is in the heap, update the heap.
-		if(inPQ())
-		{
-			if(getUpNode)
-			{
-				pq->increase(this->pqHandle);
-			}
-			else
-			{
-				pq->decrease(this->pqHandle);
-			}
-		}
-	}
-
-	STRONG_INLINE
-	void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible)
-	{
-		if(layer == ELayer::WRONG)
-		{
-			coord = Coord;
-			layer = Layer;
-		}
-		else
-		{
-			reset();
-		}
-
-		accessible = Accessible;
-	}
-
-	STRONG_INLINE
-	bool reachable() const
-	{
-		return turns < 255;
-	}
-
-	bool isTeleportAction() const
-	{
-		if (action != EPathNodeAction::TELEPORT_NORMAL &&
-			action != EPathNodeAction::TELEPORT_BLOCKING_VISIT &&
-			action != EPathNodeAction::TELEPORT_BATTLE)
-		{
-			return false;
-		}
-
-		return true;
-	}
-};
-
-struct DLL_LINKAGE CGPath
-{
-	std::vector<CGPathNode> nodes; //just get node by node
-
-	/// Starting position of path, matches location of hero
-	const CGPathNode & currNode() const;
-	/// First node in path, this is where hero will move next
-	const CGPathNode & nextNode() const;
-	/// Last node in path, this is what hero wants to reach in the end
-	const CGPathNode & lastNode() const;
-
-	int3 startPos() const; // start point
-	int3 endPos() const; //destination point
-};
-
-struct DLL_LINKAGE CPathsInfo
-{
-	using ELayer = EPathfindingLayer;
-
-	const CGHeroInstance * hero;
-	int3 hpos;
-	int3 sizes;
-	boost::multi_array<CGPathNode, 4> nodes; //[layer][level][w][h]
-
-	CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_);
-	~CPathsInfo();
-	const CGPathNode * getPathInfo(const int3 & tile) const;
-	bool getPath(CGPath & out, const int3 & dst) const;
-	const CGPathNode * getNode(const int3 & coord) const;
-
-	STRONG_INLINE
-	CGPathNode * getNode(const int3 & coord, const ELayer layer)
-	{
-		return &nodes[layer.getNum()][coord.z][coord.x][coord.y];
-	}
-};
-
-struct DLL_LINKAGE PathNodeInfo
-{
-	CGPathNode * node;
-	const CGObjectInstance * nodeObject;
-	const CGHeroInstance * nodeHero;
-	const TerrainTile * tile;
-	int3 coord;
-	bool guarded;
-	PlayerRelations objectRelations;
-	PlayerRelations heroRelations;
-	bool isInitialPosition;
-
-	PathNodeInfo();
-
-	virtual void setNode(CGameState * gs, CGPathNode * n);
-
-	void updateInfo(CPathfinderHelper * hlp, CGameState * gs);
-
-	bool isNodeObjectVisitable() const;
-};
-
-struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo
-{
-	EPathNodeAction action;
-	int turn;
-	int movementLeft;
-	float cost; //same as CGPathNode::cost
-	bool blocked;
-	bool isGuardianTile;
-
-	CDestinationNodeInfo();
-
-	void setNode(CGameState * gs, CGPathNode * n) override;
-
-	virtual bool isBetterWay() const;
-};
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * CGPathNode.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 "../GameConstants.h"
+#include "../int3.h"
+
+#include <boost/heap/fibonacci_heap.hpp>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGHeroInstance;
+class CGObjectInstance;
+class CGameState;
+class CPathfinderHelper;
+struct TerrainTile;
+
+template<typename N>
+struct DLL_LINKAGE NodeComparer
+{
+	STRONG_INLINE
+	bool operator()(const N * lhs, const N * rhs) const
+	{
+		return lhs->getCost() > rhs->getCost();
+	}
+};
+
+enum class EPathAccessibility : ui8
+{
+	NOT_SET,
+	ACCESSIBLE, //tile can be entered and passed
+	VISITABLE, //tile can be entered as the last tile in path
+	GUARDED,  //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any)
+	BLOCKVIS,  //visitable from neighbouring tile but not passable
+	FLYABLE, //can only be accessed in air layer
+	BLOCKED //tile can be neither entered nor visited
+};
+
+enum class EPathNodeAction : ui8
+{
+	UNKNOWN,
+	EMBARK,
+	DISEMBARK,
+	NORMAL,
+	BATTLE,
+	VISIT,
+	BLOCKING_VISIT,
+	TELEPORT_NORMAL,
+	TELEPORT_BLOCKING_VISIT,
+	TELEPORT_BATTLE
+};
+
+struct DLL_LINKAGE CGPathNode
+{
+	using TFibHeap = boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>>>;
+	using ELayer = EPathfindingLayer;
+
+	TFibHeap::handle_type pqHandle;
+	TFibHeap * pq;
+	CGPathNode * theNodeBefore;
+
+	int3 coord; //coordinates
+	ELayer layer;
+
+	float cost; //total cost of the path to this tile measured in turns with fractions
+	int moveRemains; //remaining movement points after hero reaches the tile
+	ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn
+	EPathAccessibility accessible;
+	EPathNodeAction action;
+	bool locked;
+
+	CGPathNode()
+		: coord(-1),
+		layer(ELayer::WRONG),
+		pqHandle(nullptr)
+	{
+		reset();
+	}
+
+	STRONG_INLINE
+	void reset()
+	{
+		locked = false;
+		accessible = EPathAccessibility::NOT_SET;
+		moveRemains = 0;
+		cost = std::numeric_limits<float>::max();
+		turns = 255;
+		theNodeBefore = nullptr;
+		pq = nullptr;
+		action = EPathNodeAction::UNKNOWN;
+	}
+
+	STRONG_INLINE
+	bool inPQ() const
+	{
+		return pq != nullptr;
+	}
+
+	STRONG_INLINE
+	float getCost() const
+	{
+		return cost;
+	}
+
+	STRONG_INLINE
+	void setCost(float value)
+	{
+		if(vstd::isAlmostEqual(value, cost))
+			return;
+
+		bool getUpNode = value < cost;
+		cost = value;
+		// If the node is in the heap, update the heap.
+		if(inPQ())
+		{
+			if(getUpNode)
+			{
+				pq->increase(this->pqHandle);
+			}
+			else
+			{
+				pq->decrease(this->pqHandle);
+			}
+		}
+	}
+
+	STRONG_INLINE
+	void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible)
+	{
+		if(layer == ELayer::WRONG)
+		{
+			coord = Coord;
+			layer = Layer;
+		}
+		else
+		{
+			reset();
+		}
+
+		accessible = Accessible;
+	}
+
+	STRONG_INLINE
+	bool reachable() const
+	{
+		return turns < 255;
+	}
+
+	bool isTeleportAction() const
+	{
+		if (action != EPathNodeAction::TELEPORT_NORMAL &&
+			action != EPathNodeAction::TELEPORT_BLOCKING_VISIT &&
+			action != EPathNodeAction::TELEPORT_BATTLE)
+		{
+			return false;
+		}
+
+		return true;
+	}
+};
+
+struct DLL_LINKAGE CGPath
+{
+	std::vector<CGPathNode> nodes; //just get node by node
+
+	/// Starting position of path, matches location of hero
+	const CGPathNode & currNode() const;
+	/// First node in path, this is where hero will move next
+	const CGPathNode & nextNode() const;
+	/// Last node in path, this is what hero wants to reach in the end
+	const CGPathNode & lastNode() const;
+
+	int3 startPos() const; // start point
+	int3 endPos() const; //destination point
+};
+
+struct DLL_LINKAGE CPathsInfo
+{
+	using ELayer = EPathfindingLayer;
+
+	const CGHeroInstance * hero;
+	int3 hpos;
+	int3 sizes;
+	boost::multi_array<CGPathNode, 4> nodes; //[layer][level][w][h]
+
+	CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_);
+	~CPathsInfo();
+	const CGPathNode * getPathInfo(const int3 & tile) const;
+	bool getPath(CGPath & out, const int3 & dst) const;
+	const CGPathNode * getNode(const int3 & coord) const;
+
+	STRONG_INLINE
+	CGPathNode * getNode(const int3 & coord, const ELayer layer)
+	{
+		return &nodes[layer.getNum()][coord.z][coord.x][coord.y];
+	}
+};
+
+struct DLL_LINKAGE PathNodeInfo
+{
+	CGPathNode * node;
+	const CGObjectInstance * nodeObject;
+	const CGHeroInstance * nodeHero;
+	const TerrainTile * tile;
+	int3 coord;
+	bool guarded;
+	PlayerRelations objectRelations;
+	PlayerRelations heroRelations;
+	bool isInitialPosition;
+
+	PathNodeInfo();
+
+	virtual void setNode(CGameState * gs, CGPathNode * n);
+
+	void updateInfo(CPathfinderHelper * hlp, CGameState * gs);
+
+	bool isNodeObjectVisitable() const;
+};
+
+struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo
+{
+	EPathNodeAction action;
+	int turn;
+	int movementLeft;
+	float cost; //same as CGPathNode::cost
+	bool blocked;
+	bool isGuardianTile;
+
+	CDestinationNodeInfo();
+
+	void setNode(CGameState * gs, CGPathNode * n) override;
+
+	virtual bool isBetterWay() const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1027 - 1027
lib/rmg/CZonePlacer.cpp

@@ -1,1027 +1,1027 @@
-/*
- * CZonePlacer.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 "CZonePlacer.h"
-
-#include "../TerrainHandler.h"
-#include "../entities/faction/CFaction.h"
-#include "../entities/faction/CTownHandler.h"
-#include "../mapping/CMap.h"
-#include "../mapping/CMapEditManager.h"
-#include "../VCMI_Lib.h"
-#include "CMapGenOptions.h"
-#include "RmgMap.h"
-#include "Zone.h"
-#include "Functions.h"
-#include "PenroseTiling.h"
-
-#include <vstd/RNG.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-//#define ZONE_PLACEMENT_LOG true
-
-CZonePlacer::CZonePlacer(RmgMap & map)
-	: width(0), height(0), mapSize(0),
-	gravityConstant(1e-3f),
-	stiffnessConstant(3e-3f),
-	stifness(0),
-	stiffnessIncreaseFactor(1.03f),
-	bestTotalDistance(1e10),
-	bestTotalOverlap(1e10),
-	map(map)
-{
-}
-
-int3 CZonePlacer::cords(const float3 & f) const
-{
-	return int3(static_cast<si32>(std::max(0.f, (f.x * map.width()) - 1)), static_cast<si32>(std::max(0.f, (f.y * map.height() - 1))), f.z);
-}
-
-float CZonePlacer::getDistance (float distance) const
-{
-	return (distance ? distance * distance : 1e-6f);
-}
-
-void CZonePlacer::findPathsBetweenZones()
-{
-	auto zones = map.getZones();
-
-	std::set<std::shared_ptr<Zone>> zonesToCheck;
-
-	// Iterate through each pair of nodes in the graph
-
-	for (const auto& zone : zones)
-	{
-		int start = zone.first;
-		distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0
-
-		std::queue<int> q;
-		std::map<int, bool> visited;
-		visited[start] = true;
-		q.push(start);
-
-		// Perform Breadth-First Search from the starting node
-		while (!q.empty())
-		{
-			int current = q.front();
-			q.pop();
-
-			const auto& currentZone = zones.at(current);
-			const auto& connectedZoneIds = currentZone->getConnections();
-
-			for (auto & connection : connectedZoneIds)
-			{
-				switch (connection.getConnectionType())
-				{
-					//Do not consider virtual connections for graph distance
-					case rmg::EConnectionType::REPULSIVE:
-					case rmg::EConnectionType::FORCE_PORTAL:
-						continue;
-				}
-				auto neighbor = connection.getOtherZoneId(current);
-
-				if (current == neighbor)
-				{
-					//Do not consider self-connections
-					continue;
-				}
-
-				if (!visited[neighbor])
-				{
-					visited[neighbor] = true;
-					q.push(neighbor);
-					distancesBetweenZones[start][neighbor] = distancesBetweenZones[start][current] + 1;
-				}
-			}
-		}
-	}
-}
-
-void CZonePlacer::placeOnGrid(vstd::RNG* rand)
-{
-	auto zones = map.getZones();
-	assert(zones.size());
-
-	//Make sure there are at least as many grid fields as the number of zones
-	size_t gridSize = std::ceil(std::sqrt(zones.size()));
-
-	typedef boost::multi_array<std::shared_ptr<Zone>, 2> GridType;
-	GridType grid(boost::extents[gridSize][gridSize]);
-
-	TZoneVector zonesVector(zones.begin(), zones.end());
-
-	//Place first zone
-
-	auto firstZone = zonesVector[0].second;
-	size_t x = 0;
-	size_t y = 0;
-
-	auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y)
-	{
-		switch (rand->nextInt(0, 3) % 4)
-		{
-		case 0:
-			x = 0;
-			y = gridSize / 2;
-			break;
-		case 1:
-			x = gridSize - 1;
-			y = gridSize / 2;
-			break;
-		case 2:
-			x = gridSize / 2;
-			y = 0;
-			break;
-		case 3:
-			x = gridSize / 2;
-			y = gridSize - 1;
-			break;
-		}
-	};
-
-	switch (firstZone->getType())
-	{
-		case ETemplateZoneType::PLAYER_START:
-		case ETemplateZoneType::CPU_START:
-			if (firstZone->getConnectedZoneIds().size() > 2)
-			{
-				getRandomEdge(x, y);
-			}
-			else
-			{
-				//Random corner
-				if (rand->nextInt(0, 1) == 1)
-				{
-					x = 0;
-				}
-				else
-				{
-					x = gridSize - 1;
-				}
-				if (rand->nextInt(0, 1) == 1)
-				{
-					y = 0;
-				}
-				else
-				{
-					y = gridSize - 1;
-				}
-			}
-			break;
-		case ETemplateZoneType::TREASURE:
-			if (gridSize & 1) //odd
-			{
-				x = y = (gridSize / 2);
-			}
-			else
-			{
-				//One of 4 squares in the middle
-				x = (gridSize / 2) - 1 + rand->nextInt(0, 1);
-				y = (gridSize / 2) - 1 + rand->nextInt(0, 1);
-			}
-			break;
-		case ETemplateZoneType::JUNCTION:
-			getRandomEdge(x, y);
-			break;
-	}
-	grid[x][y] = firstZone;
-
-	//Ignore z placement for simplicity
-
-	for (size_t i = 1; i < zones.size(); i++)
-	{
-		auto zone = zonesVector[i].second;
-		auto connectedZoneIds = zone->getConnectedZoneIds();
-
-		float maxDistance = -1000.0;
-		int3 mostDistantPlace;
-
-		//Iterate over free positions
-		for (size_t freeX = 0; freeX < gridSize; ++freeX)
-		{
-			for (size_t freeY = 0; freeY < gridSize; ++freeY)
-			{
-				if (!grid[freeX][freeY])
-				{
-					//There is free space left here
-					int3 potentialPos(freeX, freeY, 0);
-					
-					//Compute distance to every existing zone
-
-					float distance = 0;
-					for (size_t existingX = 0; existingX < gridSize; ++existingX)
-					{
-						for (size_t existingY = 0; existingY < gridSize; ++existingY)
-						{
-							auto existingZone = grid[existingX][existingY];
-							if (existingZone)
-							{
-								//There is already zone here
-								float localDistance = 0.0f;
-
-								auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()];
-								if (graphDistance > 1)
-								{
-									//No direct connection
-									localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance;
-								}
-								else
-								{
-									//Has direct connection - place as close as possible
-									localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0));
-								}
-
-								localDistance *= scaleForceBetweenZones(zone, existingZone);
-
-								distance += localDistance;
-							}
-						}
-					}
-					if (distance > maxDistance)
-					{
-						maxDistance = distance;
-						mostDistantPlace = potentialPos;
-					}
-				}
-			}
-		}
-
-		//Place in a free slot
-		grid[mostDistantPlace.x][mostDistantPlace.y] = zone;
-	}
-
-	//TODO: toggle with a flag
-#ifdef ZONE_PLACEMENT_LOG
-	logGlobal->trace("Initial zone grid:");
-	for (size_t x = 0; x < gridSize; ++x)
-	{
-		std::string s;
-		for (size_t y = 0; y < gridSize; ++y)
-		{
-			if (grid[x][y])
-			{
-				s += (boost::format("%3d ") % grid[x][y]->getId()).str();
-			}
-			else
-			{
-				s += " -- ";
-			}
-		}
-		logGlobal->trace(s);
-	}
-#endif
-
-	//Set initial position for zones - random position in square centered around (x, y)
-	for (size_t x = 0; x < gridSize; ++x)
-	{
-		for (size_t y = 0; y < gridSize; ++y)
-		{
-			auto zone = grid[x][y];
-			if (zone)
-			{
-				//i.e. for grid size 5 we get range (0.25 - 4.75)
-				auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f);
-				vstd::abetween(targetX, 0.5, gridSize - 0.5);
-				auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f);
-				vstd::abetween(targetY, 0.5, gridSize - 0.5);
-
-				zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z));
-			}
-		}
-	}
-}
-
-float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr<Zone> zoneA, const std::shared_ptr<Zone> zoneB) const
-{
-	if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game
-	{
-		int firstPlayer = zoneA->getOwner().value();
-		int secondPlayer = zoneB->getOwner().value();
-
-		//Players with lower indexes (especially 1 and 2) will be placed further apart
-
-		return (1.0f + (2.0f / (firstPlayer * secondPlayer)));
-	}
-	else
-	{
-		return 1;
-	}
-}
-
-void CZonePlacer::placeZones(vstd::RNG * rand)
-{
-	logGlobal->info("Starting zone placement");
-
-	width = map.getMapGenOptions().getWidth();
-	height = map.getMapGenOptions().getHeight();
-
-	auto zones = map.getZones();
-	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
-	{
-		return pr.second->getType() == ETemplateZoneType::WATER;
-	});
-	bool underground = map.getMapGenOptions().getHasTwoLevels();
-
-	findPathsBetweenZones();
-	placeOnGrid(rand);
-
-	/*
-	Fruchterman-Reingold algorithm
-
-	Let's assume we try to fit N circular zones with radius = size on a map
-	Connected zones attract, intersecting zones and map boundaries push back
-	*/
-
-	TZoneVector zonesVector(zones.begin(), zones.end());
-	assert (zonesVector.size());
-
-	RandomGeneratorUtil::randomShuffle(zonesVector, *rand);
-
-	//0. set zone sizes and surface / underground level
-	prepareZones(zones, zonesVector, underground, rand);
-
-	std::map<std::shared_ptr<Zone>, float3> bestSolution;
-
-	TForceVector forces;
-	TForceVector totalForces; //  both attraction and pushback, overcomplicated?
-	TDistanceVector distances;
-	TDistanceVector overlaps;
-
-	auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool
-	{
-		bool improvement = false;
-
-		float totalDistance = 0;
-		float totalOverlap = 0;
-		for (const auto& zone : distances) //find most misplaced zone
-		{
-			totalDistance += zone.second;
-			float overlap = overlaps[zone.first];
-			totalOverlap += overlap;
-		}
-
-		//check fitness function
-		if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1))
-		{
-			//multiplication is better for auto-scaling, but stops working if one factor is 0
-			improvement = true;
-		}
-
-		//Save best solution
-		if (improvement)
-		{
-			bestTotalDistance = totalDistance;
-			bestTotalOverlap = totalOverlap;
-
-			for (const auto& zone : zones)
-				bestSolution[zone.second] = zone.second->getCenter();
-		}
-
-#ifdef ZONE_PLACEMENT_LOG
-		logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement);
-#endif
-
-		return improvement;
-	};
-
-	 //Start with low stiffness. Bigger graphs need more time and more flexibility
-	for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;)
-	{
-		//1. attract connected zones
-		attractConnectedZones(zones, forces, distances);
-		for(const auto & zone : forces)
-		{
-			zone.first->setCenter (zone.first->getCenter() + zone.second);
-			totalForces[zone.first] = zone.second; //override
-		}
-
-		//2. separate overlapping zones
-		separateOverlappingZones(zones, forces, overlaps);
-		for(const auto & zone : forces)
-		{
-			zone.first->setCenter (zone.first->getCenter() + zone.second);
-			totalForces[zone.first] += zone.second; //accumulate
-		}
-
-		bool improved = evaluateSolution();
-
-		if (!improved)
-		{
-			//3. now perform drastic movement of zone that is completely not linked
-			//TODO: Don't do this is fitness was improved
-			moveOneZone(zones, totalForces, distances, overlaps);
-
-			improved |= evaluateSolution();
-		}
-
-		if (!improved)
-		{
-			//Only cool down if we didn't see any improvement
-			stifness *= stiffnessIncreaseFactor;
-		}
-
-	}
-
-	logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap);
-	for(const auto & zone : zones) //finalize zone positions
-	{
-		zone.second->setPos (cords (bestSolution[zone.second]));
-#ifdef ZONE_PLACEMENT_LOG
-		logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString());
-#endif
-	}
-}
-
-void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand)
-{
-	std::vector<float> totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map
-
-	int zonesOnLevel[2] = { 0, 0 };
-
-	//even distribution for surface / underground zones. Surface zones always have priority.
-
-	TZoneVector zonesToPlace;
-	std::map<TRmgTemplateZoneId, int> levels;
-
-	//first pass - determine fixed surface for zones
-	for(const auto & zone : zonesVector)
-	{
-		if (!underground) //this step is ignored
-			zonesToPlace.push_back(zone);
-		else //place players depending on their factions
-		{
-			if(std::optional<int> owner = zone.second->getOwner())
-			{
-				auto player = PlayerColor(*owner - 1);
-				auto playerSettings = map.getMapGenOptions().getPlayersSettings();
-				FactionID faction = FactionID::RANDOM;
-				if (playerSettings.size() > player)
-				{
-					faction = std::next(playerSettings.begin(), player)->second.getStartingTown();
-				}
-				else
-				{
-					logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first);
-				}
-
-				if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized
-					zonesToPlace.push_back(zone);
-				else
-				{
-					auto & tt = (*VLC->townh)[faction]->nativeTerrain;
-					if(tt == ETerrainId::NONE)
-					{
-						//any / random
-						zonesToPlace.push_back(zone);
-					}
-					else
-					{
-						const auto & terrainType = VLC->terrainTypeHandler->getById(tt);
-						if(terrainType->isUnderground() && !terrainType->isSurface())
-						{
-							//underground only
-							zonesOnLevel[1]++;
-							levels[zone.first] = 1;
-						}
-						else
-						{
-							//surface
-							zonesOnLevel[0]++;
-							levels[zone.first] = 0;
-						}
-					}
-				}
-			}
-			else //no starting zone or no underground altogether
-			{
-				zonesToPlace.push_back(zone);
-			}
-		}
-	}
-	for(const auto & zone : zonesToPlace)
-	{
-		if (underground) //only then consider underground zones
-		{
-			int level = 0;
-			if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones
-				level = 1;
-			else
-				level = 0;
-
-			levels[zone.first] = level;
-			zonesOnLevel[level]++;
-		}
-		else
-			levels[zone.first] = 0;
-	}
-
-	for(const auto & zone : zonesVector)
-	{
-		int level = levels[zone.first];
-		totalSize[level] += (zone.second->getSize() * zone.second->getSize());
-		float3 center = zone.second->getCenter();
-		center.z = level;
-		zone.second->setCenter(center);
-	}
-
-	/*
-	prescale zones
-
-	formula: sum((prescaler*n)^2)*pi = WH
-
-	prescaler = sqrt((WH)/(sum(n^2)*pi))
-	*/
-
-	std::vector<float> prescaler = { 0, 0 };
-	for (int i = 0; i < 2; i++)
-		prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT));
-	mapSize = static_cast<float>(sqrt(width * height));
-	for(const auto & zone : zones)
-	{
-		zone.second->setSize(static_cast<int>(zone.second->getSize() * prescaler[zone.second->getCenter().z]));
-	}
-}
-
-void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const
-{
-	for(const auto & zone : zones)
-	{
-		float3 forceVector(0, 0, 0);
-		float3 pos = zone.second->getCenter();
-		float totalDistance = 0;
-
-		for (const auto & connection : zone.second->getConnections())
-		{
-			switch (connection.getConnectionType())
-			{
-				//Do not consider virtual connections for graph distance
-				case rmg::EConnectionType::REPULSIVE:
-				case rmg::EConnectionType::FORCE_PORTAL:
-					continue;
-			}
-			if (connection.getZoneA() == connection.getZoneB())
-			{
-				//Do not consider self-connections
-				continue;
-			}
-
-			auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
-			float3 otherZoneCenter = otherZone->getCenter();
-			auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
-			
-			forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value
-
-			//Attract zone centers always
-
-			float minDistance = 0;
-
-			if (pos.z != otherZoneCenter.z)
-				minDistance = 0; //zones on different levels can overlap completely
-			else
-				minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates
-
-			if (distance > minDistance)
-				totalDistance += (distance - minDistance);
-		}
-		distances[zone.second] = totalDistance;
-		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
-		forces[zone.second] = forceVector;
-	}
-}
-
-void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps)
-{
-	for(const auto & zone : zones)
-	{
-		float3 forceVector(0, 0, 0);
-		float3 pos = zone.second->getCenter();
-
-		float overlap = 0;
-		//separate overlapping zones
-		for(const auto & otherZone : zones)
-		{
-			float3 otherZoneCenter = otherZone.second->getCenter();
-			//zones on different levels don't push away
-			if (zone == otherZone || pos.z != otherZoneCenter.z)
-				continue;
-
-			auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
-			float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize;
-			if (distance < minDistance)
-			{
-				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
-				//negative value
-				localForce *= scaleForceBetweenZones(zone.second, otherZone.second);
-				forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f);
-				overlap += (minDistance - distance); //overlapping of small zones hurts us more
-			}
-		}
-
-		//move zones away from boundaries
-		//do not scale boundary distance - zones tend to get squashed
-		float size = zone.second->getSize() / mapSize;
-
-		auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y)
-		{
-			float3 boundary = float3(x, y, pos.z);
-			auto distance = static_cast<float>(pos.dist2d(boundary));
-			overlap += std::max<float>(0, distance - size); //check if we're closer to map boundary than value of zone size
-			forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value
-		};
-		if (pos.x < size)
-		{
-			pushAwayFromBoundary(0, pos.y);
-		}
-		if (pos.x > 1 - size)
-		{
-			pushAwayFromBoundary(1, pos.y);
-		}
-		if (pos.y < size)
-		{
-			pushAwayFromBoundary(pos.x, 0);
-		}
-		if (pos.y > 1 - size)
-		{
-			pushAwayFromBoundary(pos.x, 1);
-		}
-
-		//Always move repulsive zones away, no matter their distance
-		//TODO: Consider z plane?
-		for (auto& connection : zone.second->getConnections())
-		{
-			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
-			{
-				auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
-				float3 otherZoneCenter = otherZone->getCenter();
-
-				//TODO: Roll into lambda?
-				auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
-				float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize;
-				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
-				localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]);
-				forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone);
-			}
-		}
-
-		overlaps[zone.second] = overlap;
-		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
-		forces[zone.second] = forceVector;
-	}
-}
-
-void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps)
-{
-	//The more zones, the greater total distance expected
-	//Also, higher stiffness make expected movement lower
-	const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness);
-
-	typedef std::pair<float, std::shared_ptr<Zone>> Misplacement;
-	std::vector<Misplacement> misplacedZones;
-
-	float totalDistance = 0;
-	float totalOverlap = 0;
-	for (const auto& zone : distances) //find most misplaced zone
-	{
-		if (vstd::contains(lastSwappedZones, zone.first->getId()))
-		{
-			continue;
-		}
-		totalDistance += zone.second;
-		float overlap = overlaps[zone.first];
-		totalOverlap += overlap;
-		//if distance to actual movement is long, the zone is misplaced
-		float ratio = (zone.second + overlap) / static_cast<float>(totalForces[zone.first].mag());
-		if (ratio > maxDistanceMovementRatio)
-		{
-			misplacedZones.emplace_back(std::make_pair(ratio, zone.first));
-		}
-	}
-
-	if (misplacedZones.empty())
-		return;
-
-	boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs)
-	{
-		return lhs.first > rhs.first; //Largest displacement first
-	});
-
-#ifdef ZONE_PLACEMENT_LOG
-	logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first);
-#endif
-
-	if (misplacedZones.size() >= 2)
-	{
-		//Swap 2 misplaced zones
-
-		auto firstZone = misplacedZones.front().second;
-		std::shared_ptr<Zone> secondZone;
-		std::set<TRmgTemplateZoneId> connectedZones;
-		for (const auto& connection : firstZone->getConnections())
-		{
-			switch (connection.getConnectionType())
-			{
-				//Do not consider virtual connections for graph distance
-				case rmg::EConnectionType::REPULSIVE:
-				case rmg::EConnectionType::FORCE_PORTAL:
-					continue;
-			}
-			if (connection.getZoneA() == connection.getZoneB())
-			{
-				//Do not consider self-connections
-				continue;
-			}
-			connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
-		}
-
-		auto level = firstZone->getCenter().z;
-		for (size_t i = 1; i < misplacedZones.size(); i++)
-		{
-			//Only swap zones on the same level
-			//Don't swap zones that should be connected (Jebus)
-
-			if (misplacedZones[i].second->getCenter().z == level &&
-				!vstd::contains(connectedZones, misplacedZones[i].second->getId()))
-			{
-				secondZone = misplacedZones[i].second;
-				break;
-			}
-		}
-		if (secondZone)
-		{
-#ifdef ZONE_PLACEMENT_LOG
-			logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId());
-#endif
-
-			auto firstCenter = firstZone->getCenter();
-			auto secondCenter = secondZone->getCenter();
-			firstZone->setCenter(secondCenter);
-			secondZone->setCenter(firstCenter);
-
-			lastSwappedZones.insert(firstZone->getId());
-			lastSwappedZones.insert(secondZone->getId());
-			return;
-		}
-	}
-	lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next
-
-	//find most distant zone that should be attracted and move inside it
-	std::shared_ptr<Zone> targetZone;
-	auto misplacedZone = misplacedZones.front().second;
-	float3 ourCenter = misplacedZone->getCenter();
-		
-	if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1)))
-	{
-		//Move one zone towards most distant zone to reduce distance
-
-		float maxDistance = 0;
-		for (auto con : misplacedZone->getConnections())
-		{
-			if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE)
-			{
-				continue;
-			}
-
-			auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())];
-			float distance = static_cast<float>(otherZone->getCenter().dist2dSQ(ourCenter));
-			if (distance > maxDistance)
-			{
-				maxDistance = distance;
-				targetZone = otherZone;
-			}
-		}
-		if (targetZone)
-		{
-			float3 vec = targetZone->getCenter() - ourCenter;
-			float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize;
-#ifdef ZONE_PLACEMENT_LOG
-			logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString());
-#endif
-
-			misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
-		}
-	}
-	else
-	{
-		//Move misplaced zone away from overlapping zone
-
-		float maxOverlap = 0;
-		for(const auto & otherZone : zones)
-		{
-			float3 otherZoneCenter = otherZone.second->getCenter();
-
-			if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z)
-				continue;
-
-			auto distance = static_cast<float>(otherZoneCenter.dist2dSQ(ourCenter));
-			if (distance > maxOverlap)
-			{
-				maxOverlap = distance;
-				targetZone = otherZone.second;
-			}
-		}
-		if (targetZone)
-		{
-			float3 vec = ourCenter - targetZone->getCenter();
-			float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize;
-#ifdef ZONE_PLACEMENT_LOG
-			logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString());
-#endif
-
-			misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated
-		}
-	}
-	//Don't swap that zone in next iteration
-	lastSwappedZones.insert(misplacedZone->getId());
-}
-
-float CZonePlacer::metric (const int3 &A, const int3 &B) const
-{
-	return A.dist2dSQ(B);
-
-}
-
-void CZonePlacer::assignZones(vstd::RNG * rand)
-{
-	logGlobal->info("Starting zone colouring");
-
-	auto width = map.getMapGenOptions().getWidth();
-	auto height = map.getMapGenOptions().getHeight();
-
-
-	auto zones = map.getZones();
-	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
-	{
-		return pr.second->getType() == ETemplateZoneType::WATER;
-	});
-
-	using Dpair = std::pair<std::shared_ptr<Zone>, float>;
-	std::vector <Dpair> distances;
-	distances.reserve(zones.size());
-
-	//now place zones correctly and assign tiles to each zone
-
-	auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool
-	{
-		//bigger zones have smaller distance
-		return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
-	};
-
-	auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool
-	{
-		//bigger zones have smaller distance
-		return lhs.second < rhs.second;
-	};
-
-	auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr<Zone> & zone) -> void
-	{
-		int3 total(0, 0, 0);
-		auto tiles = zone->area()->getTiles();
-		for(const auto & tile : tiles)
-		{
-			total += tile;
-		}
-		int size = static_cast<int>(tiles.size());
-		assert(size);
-		auto newPos = int3(total.x / size, total.y / size, total.z / size);
-		zone->setPos(newPos);
-		zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z));
-	};
-
-	int levels = map.levels();
-
-	// Find current center of mass for each zone. Move zone to that center to balance zones sizes
-	std::vector<RmgMap::Zones> zonesOnLevel;
-	for(int level = 0; level < levels; level++)
-	{
-		zonesOnLevel.push_back(map.getZonesOnLevel(level));
-	}
-
-	int3 pos;
-
-	for(pos.z = 0; pos.z < levels; pos.z++)
-	{
-		for(pos.x = 0; pos.x < width; pos.x++)
-		{
-			for(pos.y = 0; pos.y < height; pos.y++)
-			{
-				distances.clear();
-				for(const auto & zone : zonesOnLevel[pos.z])
-				{
-					distances.emplace_back(zone.second, static_cast<float>(pos.dist2dSQ(zone.second->getPos())));
-				}
-				boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone
-			}
-		}
-	}
-
-	for(const auto & zone : zones)
-	{
-		if(zone.second->area()->empty())
-			throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
-		
-		moveZoneToCenterOfMass(zone.second);
-	}
-
-	for(const auto & zone : zones)
-		zone.second->clearTiles(); //now populate them again
-
-	PenroseTiling penrose;
-	for (int level = 0; level < levels; level++)
-	{
-		//Create different tiling for each level
-
-		auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand);
-
-		// Assign zones to closest Penrose vertex
-		std::map<std::shared_ptr<Zone>, std::set<int3>> vertexMapping;
-
-		for (const auto & vertex : vertices)
-		{
-			distances.clear();
-			for(const auto & zone : zonesOnLevel[level])
-			{
-				distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level)));
-			}
-			auto closestZone = boost::min_element(distances, compareByDistance)->first;
-
-			vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone
-		}
-
-		//Assign actual tiles to each zone
-		pos.z = level;
-		for (pos.x = 0; pos.x < width; pos.x++)
-		{
-			for (pos.y = 0; pos.y < height; pos.y++)
-			{
-				distances.clear();
-				for(const auto & zoneVertex : vertexMapping)
-				{
-					auto zone = zoneVertex.first;
-					for (const auto & vertex : zoneVertex.second)
-					{
-						distances.emplace_back(zone, metric(pos, vertex));
-					}
-				}
-
-				//Tile closest to vertex belongs to zone
-				auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first;
-				closestZone->area()->add(pos);
-				map.setZoneID(pos, closestZone->getId());
-			}
-		}
-
-		for(const auto & zone : zonesOnLevel[level])
-		{
-			if(zone.second->area()->empty())
-			{
-				// FIXME: Some vertices are duplicated, but it's not a source of problem
-				logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString());
-				for (const auto & vertex : vertices)
-				{
-					logGlobal->warn("Penrose Vertex: %s", vertex.toString());
-				}
-				throw rmgException("Empty zone after Penrose tiling");
-			}
-		}
-	}
-
-	//set position (town position) to center of mass of irregular zone
-	for(const auto & zone : zones)
-	{
-		moveZoneToCenterOfMass(zone.second);
-
-		//TODO: similar for islands
-		#define	CREATE_FULL_UNDERGROUND true //consider linking this with water amount
-		if (zone.second->isUnderground())
-		{
-			if (!CREATE_FULL_UNDERGROUND)
-			{
-				auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f);
-				for(const auto & t : discardTiles)
-					zone.second->area()->erase(t);
-			}
-
-			//make sure that terrain inside zone is not a rock
-
-			auto v = zone.second->area()->getTilesVector();
-			map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN);
-		}
-	}
-	logGlobal->info("Finished zone colouring");
-}
-
-const TDistanceMap& CZonePlacer::getDistanceMap()
-{
-	return distancesBetweenZones;
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * CZonePlacer.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 "CZonePlacer.h"
+
+#include "../TerrainHandler.h"
+#include "../entities/faction/CFaction.h"
+#include "../entities/faction/CTownHandler.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../VCMI_Lib.h"
+#include "CMapGenOptions.h"
+#include "RmgMap.h"
+#include "Zone.h"
+#include "Functions.h"
+#include "PenroseTiling.h"
+
+#include <vstd/RNG.h>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+//#define ZONE_PLACEMENT_LOG true
+
+CZonePlacer::CZonePlacer(RmgMap & map)
+	: width(0), height(0), mapSize(0),
+	gravityConstant(1e-3f),
+	stiffnessConstant(3e-3f),
+	stifness(0),
+	stiffnessIncreaseFactor(1.03f),
+	bestTotalDistance(1e10),
+	bestTotalOverlap(1e10),
+	map(map)
+{
+}
+
+int3 CZonePlacer::cords(const float3 & f) const
+{
+	return int3(static_cast<si32>(std::max(0.f, (f.x * map.width()) - 1)), static_cast<si32>(std::max(0.f, (f.y * map.height() - 1))), f.z);
+}
+
+float CZonePlacer::getDistance (float distance) const
+{
+	return (distance ? distance * distance : 1e-6f);
+}
+
+void CZonePlacer::findPathsBetweenZones()
+{
+	auto zones = map.getZones();
+
+	std::set<std::shared_ptr<Zone>> zonesToCheck;
+
+	// Iterate through each pair of nodes in the graph
+
+	for (const auto& zone : zones)
+	{
+		int start = zone.first;
+		distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0
+
+		std::queue<int> q;
+		std::map<int, bool> visited;
+		visited[start] = true;
+		q.push(start);
+
+		// Perform Breadth-First Search from the starting node
+		while (!q.empty())
+		{
+			int current = q.front();
+			q.pop();
+
+			const auto& currentZone = zones.at(current);
+			const auto& connectedZoneIds = currentZone->getConnections();
+
+			for (auto & connection : connectedZoneIds)
+			{
+				switch (connection.getConnectionType())
+				{
+					//Do not consider virtual connections for graph distance
+					case rmg::EConnectionType::REPULSIVE:
+					case rmg::EConnectionType::FORCE_PORTAL:
+						continue;
+				}
+				auto neighbour = connection.getOtherZoneId(current);
+
+				if (current == neighbour)
+				{
+					//Do not consider self-connections
+					continue;
+				}
+
+				if (!visited[neighbour])
+				{
+					visited[neighbour] = true;
+					q.push(neighbour);
+					distancesBetweenZones[start][neighbour] = distancesBetweenZones[start][current] + 1;
+				}
+			}
+		}
+	}
+}
+
+void CZonePlacer::placeOnGrid(vstd::RNG* rand)
+{
+	auto zones = map.getZones();
+	assert(zones.size());
+
+	//Make sure there are at least as many grid fields as the number of zones
+	size_t gridSize = std::ceil(std::sqrt(zones.size()));
+
+	typedef boost::multi_array<std::shared_ptr<Zone>, 2> GridType;
+	GridType grid(boost::extents[gridSize][gridSize]);
+
+	TZoneVector zonesVector(zones.begin(), zones.end());
+
+	//Place first zone
+
+	auto firstZone = zonesVector[0].second;
+	size_t x = 0;
+	size_t y = 0;
+
+	auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y)
+	{
+		switch (rand->nextInt(0, 3) % 4)
+		{
+		case 0:
+			x = 0;
+			y = gridSize / 2;
+			break;
+		case 1:
+			x = gridSize - 1;
+			y = gridSize / 2;
+			break;
+		case 2:
+			x = gridSize / 2;
+			y = 0;
+			break;
+		case 3:
+			x = gridSize / 2;
+			y = gridSize - 1;
+			break;
+		}
+	};
+
+	switch (firstZone->getType())
+	{
+		case ETemplateZoneType::PLAYER_START:
+		case ETemplateZoneType::CPU_START:
+			if (firstZone->getConnectedZoneIds().size() > 2)
+			{
+				getRandomEdge(x, y);
+			}
+			else
+			{
+				//Random corner
+				if (rand->nextInt(0, 1) == 1)
+				{
+					x = 0;
+				}
+				else
+				{
+					x = gridSize - 1;
+				}
+				if (rand->nextInt(0, 1) == 1)
+				{
+					y = 0;
+				}
+				else
+				{
+					y = gridSize - 1;
+				}
+			}
+			break;
+		case ETemplateZoneType::TREASURE:
+			if (gridSize & 1) //odd
+			{
+				x = y = (gridSize / 2);
+			}
+			else
+			{
+				//One of 4 squares in the middle
+				x = (gridSize / 2) - 1 + rand->nextInt(0, 1);
+				y = (gridSize / 2) - 1 + rand->nextInt(0, 1);
+			}
+			break;
+		case ETemplateZoneType::JUNCTION:
+			getRandomEdge(x, y);
+			break;
+	}
+	grid[x][y] = firstZone;
+
+	//Ignore z placement for simplicity
+
+	for (size_t i = 1; i < zones.size(); i++)
+	{
+		auto zone = zonesVector[i].second;
+		auto connectedZoneIds = zone->getConnectedZoneIds();
+
+		float maxDistance = -1000.0;
+		int3 mostDistantPlace;
+
+		//Iterate over free positions
+		for (size_t freeX = 0; freeX < gridSize; ++freeX)
+		{
+			for (size_t freeY = 0; freeY < gridSize; ++freeY)
+			{
+				if (!grid[freeX][freeY])
+				{
+					//There is free space left here
+					int3 potentialPos(freeX, freeY, 0);
+					
+					//Compute distance to every existing zone
+
+					float distance = 0;
+					for (size_t existingX = 0; existingX < gridSize; ++existingX)
+					{
+						for (size_t existingY = 0; existingY < gridSize; ++existingY)
+						{
+							auto existingZone = grid[existingX][existingY];
+							if (existingZone)
+							{
+								//There is already zone here
+								float localDistance = 0.0f;
+
+								auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()];
+								if (graphDistance > 1)
+								{
+									//No direct connection
+									localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance;
+								}
+								else
+								{
+									//Has direct connection - place as close as possible
+									localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0));
+								}
+
+								localDistance *= scaleForceBetweenZones(zone, existingZone);
+
+								distance += localDistance;
+							}
+						}
+					}
+					if (distance > maxDistance)
+					{
+						maxDistance = distance;
+						mostDistantPlace = potentialPos;
+					}
+				}
+			}
+		}
+
+		//Place in a free slot
+		grid[mostDistantPlace.x][mostDistantPlace.y] = zone;
+	}
+
+	//TODO: toggle with a flag
+#ifdef ZONE_PLACEMENT_LOG
+	logGlobal->trace("Initial zone grid:");
+	for (size_t x = 0; x < gridSize; ++x)
+	{
+		std::string s;
+		for (size_t y = 0; y < gridSize; ++y)
+		{
+			if (grid[x][y])
+			{
+				s += (boost::format("%3d ") % grid[x][y]->getId()).str();
+			}
+			else
+			{
+				s += " -- ";
+			}
+		}
+		logGlobal->trace(s);
+	}
+#endif
+
+	//Set initial position for zones - random position in square centered around (x, y)
+	for (size_t x = 0; x < gridSize; ++x)
+	{
+		for (size_t y = 0; y < gridSize; ++y)
+		{
+			auto zone = grid[x][y];
+			if (zone)
+			{
+				//i.e. for grid size 5 we get range (0.25 - 4.75)
+				auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f);
+				vstd::abetween(targetX, 0.5, gridSize - 0.5);
+				auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f);
+				vstd::abetween(targetY, 0.5, gridSize - 0.5);
+
+				zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z));
+			}
+		}
+	}
+}
+
+float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr<Zone> zoneA, const std::shared_ptr<Zone> zoneB) const
+{
+	if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game
+	{
+		int firstPlayer = zoneA->getOwner().value();
+		int secondPlayer = zoneB->getOwner().value();
+
+		//Players with lower indexes (especially 1 and 2) will be placed further apart
+
+		return (1.0f + (2.0f / (firstPlayer * secondPlayer)));
+	}
+	else
+	{
+		return 1;
+	}
+}
+
+void CZonePlacer::placeZones(vstd::RNG * rand)
+{
+	logGlobal->info("Starting zone placement");
+
+	width = map.getMapGenOptions().getWidth();
+	height = map.getMapGenOptions().getHeight();
+
+	auto zones = map.getZones();
+	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
+	{
+		return pr.second->getType() == ETemplateZoneType::WATER;
+	});
+	bool underground = map.getMapGenOptions().getHasTwoLevels();
+
+	findPathsBetweenZones();
+	placeOnGrid(rand);
+
+	/*
+	Fruchterman-Reingold algorithm
+
+	Let's assume we try to fit N circular zones with radius = size on a map
+	Connected zones attract, intersecting zones and map boundaries push back
+	*/
+
+	TZoneVector zonesVector(zones.begin(), zones.end());
+	assert (zonesVector.size());
+
+	RandomGeneratorUtil::randomShuffle(zonesVector, *rand);
+
+	//0. set zone sizes and surface / underground level
+	prepareZones(zones, zonesVector, underground, rand);
+
+	std::map<std::shared_ptr<Zone>, float3> bestSolution;
+
+	TForceVector forces;
+	TForceVector totalForces; //  both attraction and pushback, overcomplicated?
+	TDistanceVector distances;
+	TDistanceVector overlaps;
+
+	auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool
+	{
+		bool improvement = false;
+
+		float totalDistance = 0;
+		float totalOverlap = 0;
+		for (const auto& zone : distances) //find most misplaced zone
+		{
+			totalDistance += zone.second;
+			float overlap = overlaps[zone.first];
+			totalOverlap += overlap;
+		}
+
+		//check fitness function
+		if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1))
+		{
+			//multiplication is better for auto-scaling, but stops working if one factor is 0
+			improvement = true;
+		}
+
+		//Save best solution
+		if (improvement)
+		{
+			bestTotalDistance = totalDistance;
+			bestTotalOverlap = totalOverlap;
+
+			for (const auto& zone : zones)
+				bestSolution[zone.second] = zone.second->getCenter();
+		}
+
+#ifdef ZONE_PLACEMENT_LOG
+		logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement);
+#endif
+
+		return improvement;
+	};
+
+	 //Start with low stiffness. Bigger graphs need more time and more flexibility
+	for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;)
+	{
+		//1. attract connected zones
+		attractConnectedZones(zones, forces, distances);
+		for(const auto & zone : forces)
+		{
+			zone.first->setCenter (zone.first->getCenter() + zone.second);
+			totalForces[zone.first] = zone.second; //override
+		}
+
+		//2. separate overlapping zones
+		separateOverlappingZones(zones, forces, overlaps);
+		for(const auto & zone : forces)
+		{
+			zone.first->setCenter (zone.first->getCenter() + zone.second);
+			totalForces[zone.first] += zone.second; //accumulate
+		}
+
+		bool improved = evaluateSolution();
+
+		if (!improved)
+		{
+			//3. now perform drastic movement of zone that is completely not linked
+			//TODO: Don't do this is fitness was improved
+			moveOneZone(zones, totalForces, distances, overlaps);
+
+			improved |= evaluateSolution();
+		}
+
+		if (!improved)
+		{
+			//Only cool down if we didn't see any improvement
+			stifness *= stiffnessIncreaseFactor;
+		}
+
+	}
+
+	logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap);
+	for(const auto & zone : zones) //finalize zone positions
+	{
+		zone.second->setPos (cords (bestSolution[zone.second]));
+#ifdef ZONE_PLACEMENT_LOG
+		logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString());
+#endif
+	}
+}
+
+void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand)
+{
+	std::vector<float> totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map
+
+	int zonesOnLevel[2] = { 0, 0 };
+
+	//even distribution for surface / underground zones. Surface zones always have priority.
+
+	TZoneVector zonesToPlace;
+	std::map<TRmgTemplateZoneId, int> levels;
+
+	//first pass - determine fixed surface for zones
+	for(const auto & zone : zonesVector)
+	{
+		if (!underground) //this step is ignored
+			zonesToPlace.push_back(zone);
+		else //place players depending on their factions
+		{
+			if(std::optional<int> owner = zone.second->getOwner())
+			{
+				auto player = PlayerColor(*owner - 1);
+				auto playerSettings = map.getMapGenOptions().getPlayersSettings();
+				FactionID faction = FactionID::RANDOM;
+				if (playerSettings.size() > player)
+				{
+					faction = std::next(playerSettings.begin(), player)->second.getStartingTown();
+				}
+				else
+				{
+					logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first);
+				}
+
+				if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized
+					zonesToPlace.push_back(zone);
+				else
+				{
+					auto & tt = (*VLC->townh)[faction]->nativeTerrain;
+					if(tt == ETerrainId::NONE)
+					{
+						//any / random
+						zonesToPlace.push_back(zone);
+					}
+					else
+					{
+						const auto & terrainType = VLC->terrainTypeHandler->getById(tt);
+						if(terrainType->isUnderground() && !terrainType->isSurface())
+						{
+							//underground only
+							zonesOnLevel[1]++;
+							levels[zone.first] = 1;
+						}
+						else
+						{
+							//surface
+							zonesOnLevel[0]++;
+							levels[zone.first] = 0;
+						}
+					}
+				}
+			}
+			else //no starting zone or no underground altogether
+			{
+				zonesToPlace.push_back(zone);
+			}
+		}
+	}
+	for(const auto & zone : zonesToPlace)
+	{
+		if (underground) //only then consider underground zones
+		{
+			int level = 0;
+			if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones
+				level = 1;
+			else
+				level = 0;
+
+			levels[zone.first] = level;
+			zonesOnLevel[level]++;
+		}
+		else
+			levels[zone.first] = 0;
+	}
+
+	for(const auto & zone : zonesVector)
+	{
+		int level = levels[zone.first];
+		totalSize[level] += (zone.second->getSize() * zone.second->getSize());
+		float3 center = zone.second->getCenter();
+		center.z = level;
+		zone.second->setCenter(center);
+	}
+
+	/*
+	prescale zones
+
+	formula: sum((prescaler*n)^2)*pi = WH
+
+	prescaler = sqrt((WH)/(sum(n^2)*pi))
+	*/
+
+	std::vector<float> prescaler = { 0, 0 };
+	for (int i = 0; i < 2; i++)
+		prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT));
+	mapSize = static_cast<float>(sqrt(width * height));
+	for(const auto & zone : zones)
+	{
+		zone.second->setSize(static_cast<int>(zone.second->getSize() * prescaler[zone.second->getCenter().z]));
+	}
+}
+
+void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const
+{
+	for(const auto & zone : zones)
+	{
+		float3 forceVector(0, 0, 0);
+		float3 pos = zone.second->getCenter();
+		float totalDistance = 0;
+
+		for (const auto & connection : zone.second->getConnections())
+		{
+			switch (connection.getConnectionType())
+			{
+				//Do not consider virtual connections for graph distance
+				case rmg::EConnectionType::REPULSIVE:
+				case rmg::EConnectionType::FORCE_PORTAL:
+					continue;
+			}
+			if (connection.getZoneA() == connection.getZoneB())
+			{
+				//Do not consider self-connections
+				continue;
+			}
+
+			auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
+			float3 otherZoneCenter = otherZone->getCenter();
+			auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
+			
+			forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value
+
+			//Attract zone centers always
+
+			float minDistance = 0;
+
+			if (pos.z != otherZoneCenter.z)
+				minDistance = 0; //zones on different levels can overlap completely
+			else
+				minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates
+
+			if (distance > minDistance)
+				totalDistance += (distance - minDistance);
+		}
+		distances[zone.second] = totalDistance;
+		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
+		forces[zone.second] = forceVector;
+	}
+}
+
+void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps)
+{
+	for(const auto & zone : zones)
+	{
+		float3 forceVector(0, 0, 0);
+		float3 pos = zone.second->getCenter();
+
+		float overlap = 0;
+		//separate overlapping zones
+		for(const auto & otherZone : zones)
+		{
+			float3 otherZoneCenter = otherZone.second->getCenter();
+			//zones on different levels don't push away
+			if (zone == otherZone || pos.z != otherZoneCenter.z)
+				continue;
+
+			auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
+			float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize;
+			if (distance < minDistance)
+			{
+				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
+				//negative value
+				localForce *= scaleForceBetweenZones(zone.second, otherZone.second);
+				forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f);
+				overlap += (minDistance - distance); //overlapping of small zones hurts us more
+			}
+		}
+
+		//move zones away from boundaries
+		//do not scale boundary distance - zones tend to get squashed
+		float size = zone.second->getSize() / mapSize;
+
+		auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y)
+		{
+			float3 boundary = float3(x, y, pos.z);
+			auto distance = static_cast<float>(pos.dist2d(boundary));
+			overlap += std::max<float>(0, distance - size); //check if we're closer to map boundary than value of zone size
+			forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value
+		};
+		if (pos.x < size)
+		{
+			pushAwayFromBoundary(0, pos.y);
+		}
+		if (pos.x > 1 - size)
+		{
+			pushAwayFromBoundary(1, pos.y);
+		}
+		if (pos.y < size)
+		{
+			pushAwayFromBoundary(pos.x, 0);
+		}
+		if (pos.y > 1 - size)
+		{
+			pushAwayFromBoundary(pos.x, 1);
+		}
+
+		//Always move repulsive zones away, no matter their distance
+		//TODO: Consider z plane?
+		for (auto& connection : zone.second->getConnections())
+		{
+			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
+			{
+				auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
+				float3 otherZoneCenter = otherZone->getCenter();
+
+				//TODO: Roll into lambda?
+				auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
+				float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize;
+				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
+				localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]);
+				forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone);
+			}
+		}
+
+		overlaps[zone.second] = overlap;
+		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
+		forces[zone.second] = forceVector;
+	}
+}
+
+void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps)
+{
+	//The more zones, the greater total distance expected
+	//Also, higher stiffness make expected movement lower
+	const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness);
+
+	typedef std::pair<float, std::shared_ptr<Zone>> Misplacement;
+	std::vector<Misplacement> misplacedZones;
+
+	float totalDistance = 0;
+	float totalOverlap = 0;
+	for (const auto& zone : distances) //find most misplaced zone
+	{
+		if (vstd::contains(lastSwappedZones, zone.first->getId()))
+		{
+			continue;
+		}
+		totalDistance += zone.second;
+		float overlap = overlaps[zone.first];
+		totalOverlap += overlap;
+		//if distance to actual movement is long, the zone is misplaced
+		float ratio = (zone.second + overlap) / static_cast<float>(totalForces[zone.first].mag());
+		if (ratio > maxDistanceMovementRatio)
+		{
+			misplacedZones.emplace_back(std::make_pair(ratio, zone.first));
+		}
+	}
+
+	if (misplacedZones.empty())
+		return;
+
+	boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs)
+	{
+		return lhs.first > rhs.first; //Largest displacement first
+	});
+
+#ifdef ZONE_PLACEMENT_LOG
+	logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first);
+#endif
+
+	if (misplacedZones.size() >= 2)
+	{
+		//Swap 2 misplaced zones
+
+		auto firstZone = misplacedZones.front().second;
+		std::shared_ptr<Zone> secondZone;
+		std::set<TRmgTemplateZoneId> connectedZones;
+		for (const auto& connection : firstZone->getConnections())
+		{
+			switch (connection.getConnectionType())
+			{
+				//Do not consider virtual connections for graph distance
+				case rmg::EConnectionType::REPULSIVE:
+				case rmg::EConnectionType::FORCE_PORTAL:
+					continue;
+			}
+			if (connection.getZoneA() == connection.getZoneB())
+			{
+				//Do not consider self-connections
+				continue;
+			}
+			connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
+		}
+
+		auto level = firstZone->getCenter().z;
+		for (size_t i = 1; i < misplacedZones.size(); i++)
+		{
+			//Only swap zones on the same level
+			//Don't swap zones that should be connected (Jebus)
+
+			if (misplacedZones[i].second->getCenter().z == level &&
+				!vstd::contains(connectedZones, misplacedZones[i].second->getId()))
+			{
+				secondZone = misplacedZones[i].second;
+				break;
+			}
+		}
+		if (secondZone)
+		{
+#ifdef ZONE_PLACEMENT_LOG
+			logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId());
+#endif
+
+			auto firstCenter = firstZone->getCenter();
+			auto secondCenter = secondZone->getCenter();
+			firstZone->setCenter(secondCenter);
+			secondZone->setCenter(firstCenter);
+
+			lastSwappedZones.insert(firstZone->getId());
+			lastSwappedZones.insert(secondZone->getId());
+			return;
+		}
+	}
+	lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next
+
+	//find most distant zone that should be attracted and move inside it
+	std::shared_ptr<Zone> targetZone;
+	auto misplacedZone = misplacedZones.front().second;
+	float3 ourCenter = misplacedZone->getCenter();
+		
+	if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1)))
+	{
+		//Move one zone towards most distant zone to reduce distance
+
+		float maxDistance = 0;
+		for (auto con : misplacedZone->getConnections())
+		{
+			if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE)
+			{
+				continue;
+			}
+
+			auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())];
+			float distance = static_cast<float>(otherZone->getCenter().dist2dSQ(ourCenter));
+			if (distance > maxDistance)
+			{
+				maxDistance = distance;
+				targetZone = otherZone;
+			}
+		}
+		if (targetZone)
+		{
+			float3 vec = targetZone->getCenter() - ourCenter;
+			float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize;
+#ifdef ZONE_PLACEMENT_LOG
+			logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString());
+#endif
+
+			misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
+		}
+	}
+	else
+	{
+		//Move misplaced zone away from overlapping zone
+
+		float maxOverlap = 0;
+		for(const auto & otherZone : zones)
+		{
+			float3 otherZoneCenter = otherZone.second->getCenter();
+
+			if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z)
+				continue;
+
+			auto distance = static_cast<float>(otherZoneCenter.dist2dSQ(ourCenter));
+			if (distance > maxOverlap)
+			{
+				maxOverlap = distance;
+				targetZone = otherZone.second;
+			}
+		}
+		if (targetZone)
+		{
+			float3 vec = ourCenter - targetZone->getCenter();
+			float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize;
+#ifdef ZONE_PLACEMENT_LOG
+			logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString());
+#endif
+
+			misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated
+		}
+	}
+	//Don't swap that zone in next iteration
+	lastSwappedZones.insert(misplacedZone->getId());
+}
+
+float CZonePlacer::metric (const int3 &A, const int3 &B) const
+{
+	return A.dist2dSQ(B);
+
+}
+
+void CZonePlacer::assignZones(vstd::RNG * rand)
+{
+	logGlobal->info("Starting zone colouring");
+
+	auto width = map.getMapGenOptions().getWidth();
+	auto height = map.getMapGenOptions().getHeight();
+
+
+	auto zones = map.getZones();
+	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
+	{
+		return pr.second->getType() == ETemplateZoneType::WATER;
+	});
+
+	using Dpair = std::pair<std::shared_ptr<Zone>, float>;
+	std::vector <Dpair> distances;
+	distances.reserve(zones.size());
+
+	//now place zones correctly and assign tiles to each zone
+
+	auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool
+	{
+		//bigger zones have smaller distance
+		return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
+	};
+
+	auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool
+	{
+		//bigger zones have smaller distance
+		return lhs.second < rhs.second;
+	};
+
+	auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr<Zone> & zone) -> void
+	{
+		int3 total(0, 0, 0);
+		auto tiles = zone->area()->getTiles();
+		for(const auto & tile : tiles)
+		{
+			total += tile;
+		}
+		int size = static_cast<int>(tiles.size());
+		assert(size);
+		auto newPos = int3(total.x / size, total.y / size, total.z / size);
+		zone->setPos(newPos);
+		zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z));
+	};
+
+	int levels = map.levels();
+
+	// Find current center of mass for each zone. Move zone to that center to balance zones sizes
+	std::vector<RmgMap::Zones> zonesOnLevel;
+	for(int level = 0; level < levels; level++)
+	{
+		zonesOnLevel.push_back(map.getZonesOnLevel(level));
+	}
+
+	int3 pos;
+
+	for(pos.z = 0; pos.z < levels; pos.z++)
+	{
+		for(pos.x = 0; pos.x < width; pos.x++)
+		{
+			for(pos.y = 0; pos.y < height; pos.y++)
+			{
+				distances.clear();
+				for(const auto & zone : zonesOnLevel[pos.z])
+				{
+					distances.emplace_back(zone.second, static_cast<float>(pos.dist2dSQ(zone.second->getPos())));
+				}
+				boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone
+			}
+		}
+	}
+
+	for(const auto & zone : zones)
+	{
+		if(zone.second->area()->empty())
+			throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
+		
+		moveZoneToCenterOfMass(zone.second);
+	}
+
+	for(const auto & zone : zones)
+		zone.second->clearTiles(); //now populate them again
+
+	PenroseTiling penrose;
+	for (int level = 0; level < levels; level++)
+	{
+		//Create different tiling for each level
+
+		auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand);
+
+		// Assign zones to closest Penrose vertex
+		std::map<std::shared_ptr<Zone>, std::set<int3>> vertexMapping;
+
+		for (const auto & vertex : vertices)
+		{
+			distances.clear();
+			for(const auto & zone : zonesOnLevel[level])
+			{
+				distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level)));
+			}
+			auto closestZone = boost::min_element(distances, compareByDistance)->first;
+
+			vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone
+		}
+
+		//Assign actual tiles to each zone
+		pos.z = level;
+		for (pos.x = 0; pos.x < width; pos.x++)
+		{
+			for (pos.y = 0; pos.y < height; pos.y++)
+			{
+				distances.clear();
+				for(const auto & zoneVertex : vertexMapping)
+				{
+					auto zone = zoneVertex.first;
+					for (const auto & vertex : zoneVertex.second)
+					{
+						distances.emplace_back(zone, metric(pos, vertex));
+					}
+				}
+
+				//Tile closest to vertex belongs to zone
+				auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first;
+				closestZone->area()->add(pos);
+				map.setZoneID(pos, closestZone->getId());
+			}
+		}
+
+		for(const auto & zone : zonesOnLevel[level])
+		{
+			if(zone.second->area()->empty())
+			{
+				// FIXME: Some vertices are duplicated, but it's not a source of problem
+				logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString());
+				for (const auto & vertex : vertices)
+				{
+					logGlobal->warn("Penrose Vertex: %s", vertex.toString());
+				}
+				throw rmgException("Empty zone after Penrose tiling");
+			}
+		}
+	}
+
+	//set position (town position) to center of mass of irregular zone
+	for(const auto & zone : zones)
+	{
+		moveZoneToCenterOfMass(zone.second);
+
+		//TODO: similar for islands
+		#define	CREATE_FULL_UNDERGROUND true //consider linking this with water amount
+		if (zone.second->isUnderground())
+		{
+			if (!CREATE_FULL_UNDERGROUND)
+			{
+				auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f);
+				for(const auto & t : discardTiles)
+					zone.second->area()->erase(t);
+			}
+
+			//make sure that terrain inside zone is not a rock
+
+			auto v = zone.second->area()->getTilesVector();
+			map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN);
+		}
+	}
+	logGlobal->info("Finished zone colouring");
+}
+
+const TDistanceMap& CZonePlacer::getDistanceMap()
+{
+	return distancesBetweenZones;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -172,4 +172,4 @@ bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const
 	return false;
 	return false;
 }
 }
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 14 - 0
lib/serializer/BinaryDeserializer.h

@@ -13,6 +13,7 @@
 #include "SerializerReflection.h"
 #include "SerializerReflection.h"
 #include "ESerializationVersion.h"
 #include "ESerializationVersion.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGHeroInstance.h"
+#include "../battle/BattleHexArray.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -442,6 +443,19 @@ public:
 			load(data[key]);
 			load(data[key]);
 		}
 		}
 	}
 	}
+
+	//void load(BattleHexArray & data)
+	//{
+	//	uint32_t length = readAndCheckLength();
+	//	data.clear();
+	//	BattleHex hex;
+	//	for(uint32_t i = 0; i < length; i++)
+	//	{
+	//		load(hex);
+	//		data.insert(hex);
+	//	}
+	//}
+
 	void load(std::string &data)
 	void load(std::string &data)
 	{
 	{
 		if (hasFeature(Version::COMPACT_STRING_SERIALIZATION))
 		if (hasFeature(Version::COMPACT_STRING_SERIALIZATION))

+ 1 - 0
lib/serializer/BinarySerializer.h

@@ -15,6 +15,7 @@
 #include "ESerializationVersion.h"
 #include "ESerializationVersion.h"
 #include "Serializeable.h"
 #include "Serializeable.h"
 #include "../mapObjects/CArmedInstance.h"
 #include "../mapObjects/CArmedInstance.h"
+#include "../battle/BattleHexArray.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 

+ 8 - 13
lib/spells/BattleSpellMechanics.cpp

@@ -508,11 +508,11 @@ bool BattleSpellMechanics::counteringSelector(const Bonus * bonus) const
 	return false;
 	return false;
 }
 }
 
 
-std::set<BattleHex> BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const
+BattleHexArray BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const
 {
 {
 	using namespace SRSLPraserHelpers;
 	using namespace SRSLPraserHelpers;
 
 
-	std::set<BattleHex> ret;
+	BattleHexArray ret;
 	std::vector<int> rng = owner->getLevelInfo(getRangeLevel()).range;
 	std::vector<int> rng = owner->getLevelInfo(getRangeLevel()).range;
 
 
 	for(auto & elem : rng)
 	for(auto & elem : rng)
@@ -604,13 +604,13 @@ std::vector<Destination> BattleSpellMechanics::getPossibleDestinations(size_t in
 		if(fast)
 		if(fast)
 		{
 		{
 			auto stacks = battle()->battleGetAllStacks();
 			auto stacks = battle()->battleGetAllStacks();
-			std::set<BattleHex> hexesToCheck;
+			BattleHexArray hexesToCheck;
 
 
 			for(auto stack : stacks)
 			for(auto stack : stacks)
 			{
 			{
 				hexesToCheck.insert(stack->getPosition());
 				hexesToCheck.insert(stack->getPosition());
 
 
-				for(auto adjacent : stack->getPosition().neighbouringTiles())
+				for(auto adjacent : BattleHexArray::generateNeighbouringTiles(stack->getPosition()))
 					hexesToCheck.insert(adjacent);
 					hexesToCheck.insert(adjacent);
 			}
 			}
 
 
@@ -661,17 +661,17 @@ bool BattleSpellMechanics::isReceptive(const battle::Unit * target) const
 	return targetCondition->isReceptive(this, target);
 	return targetCondition->isReceptive(this, target);
 }
 }
 
 
-std::vector<BattleHex> BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const
+BattleHexArray BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const
 {
 {
 	if(isMassive() || !centralHex.isValid())
 	if(isMassive() || !centralHex.isValid())
-		return std::vector<BattleHex>(1, BattleHex::INVALID);
+		return BattleHexArray();
 
 
 	Target aimPoint;
 	Target aimPoint;
 	aimPoint.push_back(Destination(centralHex));
 	aimPoint.push_back(Destination(centralHex));
 
 
 	Target spellTarget = transformSpellTarget(aimPoint);
 	Target spellTarget = transformSpellTarget(aimPoint);
 
 
-	std::set<BattleHex> effectRange;
+	BattleHexArray effectRange;
 
 
 	effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * effect, bool & stop)
 	effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * effect, bool & stop)
 	{
 	{
@@ -681,12 +681,7 @@ std::vector<BattleHex> BattleSpellMechanics::rangeInHexes(BattleHex centralHex)
 		}
 		}
 	});
 	});
 
 
-	std::vector<BattleHex> ret;
-	ret.reserve(effectRange.size());
-
-	std::copy(effectRange.begin(), effectRange.end(), std::back_inserter(ret));
-
-	return ret;
+	return effectRange;
 }
 }
 
 
 const Spell * BattleSpellMechanics::getSpell() const
 const Spell * BattleSpellMechanics::getSpell() const

+ 86 - 86
lib/spells/BattleSpellMechanics.h

@@ -1,86 +1,86 @@
-/*
- * BattleSpellMechanics.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 "ISpellMechanics.h"
-
-#include "effects/Effects.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct BattleSpellCast;
-
-namespace spells
-{
-
-class BattleSpellMechanics : public BaseMechanics
-{
-public:
-	BattleSpellMechanics(const IBattleCast * event, std::shared_ptr<effects::Effects> effects_, std::shared_ptr<IReceptiveCheck> targetCondition_);
-	virtual ~BattleSpellMechanics();
-
-	// TODO: ??? (what's the difference compared to cast?)
-	void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override;
-
-	/// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield
-	bool canBeCast(Problem & problem) const override;
-
-	/// Returns false if spell can not be cast at specified target
-	bool canBeCastAt(const Target & target, Problem & problem) const override;
-
-	// TODO: ??? (what's the difference compared to applyEffects?)
-	void cast(ServerCallback * server, const Target & target) override final;
-	// TODO: ??? (what's the difference compared to cast?)
-	void castEval(ServerCallback * server, const Target & target) override final;
-
-	/// Returns list of affected stack using currently configured target
-	std::vector<const CStack *> getAffectedStacks(const Target & target) const override final;
-
-	/// Returns list of target types that can be targeted by spell
-	std::vector<AimType> getTargetTypes() const override final;
-
-	/// Returns vector of all possible destinations for specified aim type
-	/// index - ???
-	/// current - ???
-	std::vector<Destination> getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final;
-
-	/// Returns true if spell can be cast on unit
-	bool isReceptive(const battle::Unit * target) const override;
-
-	/// Returns list of hexes that are affected by spell assuming cast at centralHex
-	std::vector<BattleHex> rangeInHexes(BattleHex centralHex) const override;
-
-	const Spell * getSpell() const override;
-
-	bool counteringSelector(const Bonus * bonus) const;
-
-private:
-	std::shared_ptr<effects::Effects> effects;
-	std::shared_ptr<IReceptiveCheck> targetCondition;
-
-	std::vector<const battle::Unit *> affectedUnits;
-	effects::Effects::EffectsToApply effectsToApply;
-
-	void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target);
-
-	std::set<const battle::Unit *> collectTargets() const;
-
-	void doRemoveEffects(ServerCallback * server, const std::vector<const battle::Unit *> & targets, const CSelector & selector);
-
-	std::set<BattleHex> spellRangeInHexes(BattleHex centralHex) const;
-
-	Target transformSpellTarget(const Target & aimPoint) const;
-};
-
-}
-
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * BattleSpellMechanics.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 "ISpellMechanics.h"
+
+#include "effects/Effects.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct BattleSpellCast;
+
+namespace spells
+{
+
+class BattleSpellMechanics : public BaseMechanics
+{
+public:
+	BattleSpellMechanics(const IBattleCast * event, std::shared_ptr<effects::Effects> effects_, std::shared_ptr<IReceptiveCheck> targetCondition_);
+	virtual ~BattleSpellMechanics();
+
+	// TODO: ??? (what's the difference compared to cast?)
+	void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override;
+
+	/// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield
+	bool canBeCast(Problem & problem) const override;
+
+	/// Returns false if spell can not be cast at specified target
+	bool canBeCastAt(const Target & target, Problem & problem) const override;
+
+	// TODO: ??? (what's the difference compared to applyEffects?)
+	void cast(ServerCallback * server, const Target & target) override final;
+	// TODO: ??? (what's the difference compared to cast?)
+	void castEval(ServerCallback * server, const Target & target) override final;
+
+	/// Returns list of affected stack using currently configured target
+	std::vector<const CStack *> getAffectedStacks(const Target & target) const override final;
+
+	/// Returns list of target types that can be targeted by spell
+	std::vector<AimType> getTargetTypes() const override final;
+
+	/// Returns vector of all possible destinations for specified aim type
+	/// index - ???
+	/// current - ???
+	std::vector<Destination> getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final;
+
+	/// Returns true if spell can be cast on unit
+	bool isReceptive(const battle::Unit * target) const override;
+
+	/// Returns list of hexes that are affected by spell assuming cast at centralHex
+	BattleHexArray rangeInHexes(BattleHex centralHex) const override;
+
+	const Spell * getSpell() const override;
+
+	bool counteringSelector(const Bonus * bonus) const;
+
+private:
+	std::shared_ptr<effects::Effects> effects;
+	std::shared_ptr<IReceptiveCheck> targetCondition;
+
+	std::vector<const battle::Unit *> affectedUnits;
+	effects::Effects::EffectsToApply effectsToApply;
+
+	void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target);
+
+	std::set<const battle::Unit *> collectTargets() const;
+
+	void doRemoveEffects(ServerCallback * server, const std::vector<const battle::Unit *> & targets, const CSelector & selector);
+
+	BattleHexArray spellRangeInHexes(BattleHex centralHex) const;
+
+	Target transformSpellTarget(const Target & aimPoint) const;
+};
+
+}
+
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/spells/CSpellHandler.cpp

@@ -357,7 +357,7 @@ int32_t CSpell::getLevelPower(const int32_t skillLevel) const
 
 
 si32 CSpell::getProbability(const FactionID & factionId) const
 si32 CSpell::getProbability(const FactionID & factionId) const
 {
 {
-	if(!vstd::contains(probabilities,factionId))
+	if(!vstd::contains(probabilities, factionId))
 	{
 	{
 		return defaultProbability;
 		return defaultProbability;
 	}
 	}
@@ -701,7 +701,7 @@ const std::vector<std::string> & CSpellHandler::getTypeNames() const
 
 
 std::vector<int> CSpellHandler::spellRangeInHexes(std::string input) const
 std::vector<int> CSpellHandler::spellRangeInHexes(std::string input) const
 {
 {
-	std::set<BattleHex> ret;
+	BattleHexArray ret;
 	std::string rng = input + ','; //copy + artificial comma for easier handling
 	std::string rng = input + ','; //copy + artificial comma for easier handling
 
 
 	if(rng.size() >= 2 && std::tolower(rng[0]) != 'x') //there is at least one hex in range (+artificial comma)
 	if(rng.size() >= 2 && std::tolower(rng[0]) != 'x') //there is at least one hex in range (+artificial comma)

+ 1 - 1
lib/spells/CSpellHandler.h

@@ -17,7 +17,7 @@
 #include "../ConstTransitivePtr.h"
 #include "../ConstTransitivePtr.h"
 #include "../int3.h"
 #include "../int3.h"
 #include "../GameConstants.h"
 #include "../GameConstants.h"
-#include "../battle/BattleHex.h"
+#include "../battle/BattleHexArray.h"
 #include "../bonuses/Bonus.h"
 #include "../bonuses/Bonus.h"
 #include "../filesystem/ResourcePath.h"
 #include "../filesystem/ResourcePath.h"
 #include "../json/JsonNode.h"
 #include "../json/JsonNode.h"

+ 368 - 368
lib/spells/ISpellMechanics.h

@@ -1,368 +1,368 @@
-/*
- * ISpellMechanics.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 <vcmi/spells/Magic.h>
-#include <vcmi/ServerCallback.h>
-
-#include "../battle/Destination.h"
-#include "../int3.h"
-#include "../GameConstants.h"
-#include "../bonuses/Bonus.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct Query;
-class IBattleState;
-class CreatureService;
-class CMap;
-class CGameInfoCallback;
-class CBattleInfoCallback;
-class JsonNode;
-class CStack;
-class CGObjectInstance;
-class CGHeroInstance;
-
-namespace spells
-{
-class Service;
-}
-
-namespace vstd
-{
-	class RNG;
-}
-
-#if SCRIPTING_ENABLED
-namespace scripting
-{
-	class Service;
-}
-#endif
-
-
-///callback to be provided by server
-class DLL_LINKAGE SpellCastEnvironment : public ServerCallback
-{
-public:
-	virtual ~SpellCastEnvironment() = default;
-
-	virtual const CMap * getMap() const = 0;
-	virtual const CGameInfoCallback * getCb() const = 0;
-
-	virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0;
-	virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0;	//TODO: remove
-
-	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
-};
-
-namespace spells
-{
-
-class DLL_LINKAGE IBattleCast
-{
-public:
-	using Value = int32_t;
-	using Value64 = int64_t;
-
-	using OptionalValue = std::optional<Value>;
-	using OptionalValue64 = std::optional<Value64>;
-
-	virtual const CSpell * getSpell() const = 0;
-	virtual Mode getMode() const = 0;
-	virtual const Caster * getCaster() const = 0;
-	virtual const CBattleInfoCallback * getBattle() const = 0;
-
-	virtual OptionalValue getSpellLevel() const = 0;
-
-	virtual OptionalValue getEffectPower() const = 0;
-	virtual OptionalValue getEffectDuration() const = 0;
-
-	virtual OptionalValue64 getEffectValue() const = 0;
-
-	virtual boost::logic::tribool isSmart() const = 0;
-	virtual boost::logic::tribool isMassive() const = 0;
-};
-
-///all parameters of particular cast event
-class DLL_LINKAGE BattleCast : public IBattleCast
-{
-public:
-	boost::logic::tribool smart;
-	boost::logic::tribool massive;
-
-	//normal constructor
-	BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_);
-
-	//magic mirror constructor
-	BattleCast(const BattleCast & orig, const Caster * caster_);
-
-	virtual ~BattleCast();
-
-	///IBattleCast
-	const CSpell * getSpell() const override;
-	Mode getMode() const override;
-	const Caster * getCaster() const override;
-	const CBattleInfoCallback * getBattle() const override;
-
-	OptionalValue getSpellLevel() const override;
-
-	OptionalValue getEffectPower() const override;
-	OptionalValue getEffectDuration() const override;
-
-	OptionalValue64 getEffectValue() const override;
-
-	boost::logic::tribool isSmart() const override;
-	boost::logic::tribool isMassive() const override;
-
-	void setSpellLevel(Value value);
-
-	void setEffectPower(Value value);
-	void setEffectDuration(Value value);
-
-	void setEffectValue(Value64 value);
-
-	///only apply effects to specified targets
-	void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const;
-
-	///normal cast
-	void cast(ServerCallback * server, Target target);
-
-	///cast evaluation
-	void castEval(ServerCallback * server, Target target);
-
-	///cast with silent check for permitted cast
-	bool castIfPossible(ServerCallback * server, Target target);
-
-	std::vector<Target> findPotentialTargets(bool fast = false) const;
-
-private:
-	///spell school level
-	OptionalValue magicSkillLevel;
-
-	///actual spell-power affecting effect values
-	OptionalValue effectPower;
-	///actual spell-power affecting effect duration
-	OptionalValue effectDuration;
-
-	///for Archangel-like casting
-	OptionalValue64 effectValue;
-
-	Mode mode;
-	const CSpell * spell;
-	const CBattleInfoCallback * cb;
-	const Caster * caster;
-};
-
-class DLL_LINKAGE ISpellMechanicsFactory
-{
-public:
-	virtual ~ISpellMechanicsFactory();
-
-	virtual std::unique_ptr<Mechanics> create(const IBattleCast * event) const = 0;
-
-	static std::unique_ptr<ISpellMechanicsFactory> get(const CSpell * s);
-
-protected:
-	const CSpell * spell;
-
-	ISpellMechanicsFactory(const CSpell * s);
-};
-
-class DLL_LINKAGE Mechanics
-{
-public:
-	virtual ~Mechanics();
-
-	virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0;
-	virtual bool adaptGenericProblem(Problem & target) const = 0;
-
-	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex) const = 0;
-	virtual std::vector<const CStack *> getAffectedStacks(const Target & target) const = 0;
-
-	virtual bool canBeCast(Problem & problem) const = 0;
-	virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0;
-
-	virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0;
-
-	virtual void cast(ServerCallback * server, const Target & target) = 0;
-
-	virtual void castEval(ServerCallback * server, const Target & target) = 0;
-
-	virtual bool isReceptive(const battle::Unit * target) const = 0;
-
-	virtual std::vector<AimType> getTargetTypes() const = 0;
-
-	virtual std::vector<Destination> getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0;
-
-	virtual const Spell * getSpell() const = 0;
-
-	//Cast event facade
-
-	virtual IBattleCast::Value getEffectLevel() const = 0;
-	virtual IBattleCast::Value getRangeLevel() const = 0;
-
-	virtual IBattleCast::Value getEffectPower() const = 0;
-	virtual IBattleCast::Value getEffectDuration() const = 0;
-
-	virtual IBattleCast::Value64 getEffectValue() const = 0;
-
-	virtual PlayerColor getCasterColor() const = 0;
-
-	//Spell facade
-	virtual int32_t getSpellIndex() const = 0;
-	virtual SpellID getSpellId() const = 0;
-	virtual std::string getSpellName() const = 0;
-	virtual int32_t getSpellLevel() const = 0;
-
-	virtual bool isSmart() const = 0;
-	virtual bool isMassive() const = 0;
-	virtual bool alwaysHitFirstTarget() const = 0;
-	virtual bool requiresClearTiles() const = 0;
-
-	virtual bool isNegativeSpell() const = 0;
-	virtual bool isPositiveSpell() const = 0;
-	virtual bool isMagicalEffect() const = 0;
-
-	virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0;
-	virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0;
-	virtual int64_t applySpecificSpellBonus(int64_t value) const = 0;
-	virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0;
-
-	//Battle facade
-	virtual bool ownerMatches(const battle::Unit * unit) const = 0;
-	virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0;
-
-	//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 CBattleInfoCallback * battle() const = 0;
-
-	const Caster * caster;
-
-	BattleSide casterSide;
-
-protected:
-	Mechanics();
-};
-
-class DLL_LINKAGE BaseMechanics : public Mechanics
-{
-public:
-	virtual ~BaseMechanics();
-
-	bool adaptProblem(ESpellCastProblem source, Problem & target) const override;
-	bool adaptGenericProblem(Problem & target) const override;
-
-	int32_t getSpellIndex() const override;
-	SpellID getSpellId() const override;
-	std::string getSpellName() const override;
-	int32_t getSpellLevel() const override;
-
-	IBattleCast::Value getEffectLevel() const override;
-	IBattleCast::Value getRangeLevel() const override;
-
-	IBattleCast::Value getEffectPower() const override;
-	IBattleCast::Value getEffectDuration() const override;
-
-	IBattleCast::Value64 getEffectValue() const override;
-
-	PlayerColor getCasterColor() const override;
-
-	bool isSmart() const override;
-	bool isMassive() const override;
-	bool requiresClearTiles() const override;
-	bool alwaysHitFirstTarget() const override;
-
-	bool isNegativeSpell() const override;
-	bool isPositiveSpell() const override;
-	bool isMagicalEffect() const override;
-
-	int64_t adjustEffectValue(const battle::Unit * target) const override;
-	int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override;
-	int64_t applySpecificSpellBonus(int64_t value) const override;
-	int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override;
-
-	bool ownerMatches(const battle::Unit * unit) const override;
-	bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override;
-
-	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 CBattleInfoCallback * battle() const override;
-
-protected:
-	const CSpell * owner;
-	Mode mode;
-
-	BaseMechanics(const IBattleCast * event);
-
-private:
-    IBattleCast::Value rangeLevel;
-	IBattleCast::Value effectLevel;
-
-	///actual spell-power affecting effect values
-	IBattleCast::Value effectPower;
-	///actual spell-power affecting effect duration
-	IBattleCast::Value effectDuration;
-
-	///raw damage/heal amount
-	IBattleCast::Value64 effectValue;
-
-	boost::logic::tribool smart;
-	boost::logic::tribool massive;
-
-	const CBattleInfoCallback * cb;
-};
-
-class DLL_LINKAGE IReceptiveCheck
-{
-public:
-	virtual ~IReceptiveCheck() = default;
-
-	virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0;
-};
-
-}// namespace spells
-
-class DLL_LINKAGE AdventureSpellCastParameters
-{
-public:
-	const spells::Caster * caster;
-	int3 pos;
-};
-
-class DLL_LINKAGE IAdventureSpellMechanics
-{
-public:
-	IAdventureSpellMechanics(const CSpell * s);
-	virtual ~IAdventureSpellMechanics() = default;
-
-	virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0;
-	virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0;
-
-	virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0;
-
-	static std::unique_ptr<IAdventureSpellMechanics> createMechanics(const CSpell * s);
-protected:
-	const CSpell * owner;
-};
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * ISpellMechanics.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 <vcmi/spells/Magic.h>
+#include <vcmi/ServerCallback.h>
+
+#include "../battle/Destination.h"
+#include "../int3.h"
+#include "../GameConstants.h"
+#include "../bonuses/Bonus.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct Query;
+class IBattleState;
+class CreatureService;
+class CMap;
+class CGameInfoCallback;
+class CBattleInfoCallback;
+class JsonNode;
+class CStack;
+class CGObjectInstance;
+class CGHeroInstance;
+
+namespace spells
+{
+class Service;
+}
+
+namespace vstd
+{
+	class RNG;
+}
+
+#if SCRIPTING_ENABLED
+namespace scripting
+{
+	class Service;
+}
+#endif
+
+
+///callback to be provided by server
+class DLL_LINKAGE SpellCastEnvironment : public ServerCallback
+{
+public:
+	virtual ~SpellCastEnvironment() = default;
+
+	virtual const CMap * getMap() const = 0;
+	virtual const CGameInfoCallback * getCb() const = 0;
+
+	virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0;
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0;	//TODO: remove
+
+	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
+};
+
+namespace spells
+{
+
+class DLL_LINKAGE IBattleCast
+{
+public:
+	using Value = int32_t;
+	using Value64 = int64_t;
+
+	using OptionalValue = std::optional<Value>;
+	using OptionalValue64 = std::optional<Value64>;
+
+	virtual const CSpell * getSpell() const = 0;
+	virtual Mode getMode() const = 0;
+	virtual const Caster * getCaster() const = 0;
+	virtual const CBattleInfoCallback * getBattle() const = 0;
+
+	virtual OptionalValue getSpellLevel() const = 0;
+
+	virtual OptionalValue getEffectPower() const = 0;
+	virtual OptionalValue getEffectDuration() const = 0;
+
+	virtual OptionalValue64 getEffectValue() const = 0;
+
+	virtual boost::logic::tribool isSmart() const = 0;
+	virtual boost::logic::tribool isMassive() const = 0;
+};
+
+///all parameters of particular cast event
+class DLL_LINKAGE BattleCast : public IBattleCast
+{
+public:
+	boost::logic::tribool smart;
+	boost::logic::tribool massive;
+
+	//normal constructor
+	BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_);
+
+	//magic mirror constructor
+	BattleCast(const BattleCast & orig, const Caster * caster_);
+
+	virtual ~BattleCast();
+
+	///IBattleCast
+	const CSpell * getSpell() const override;
+	Mode getMode() const override;
+	const Caster * getCaster() const override;
+	const CBattleInfoCallback * getBattle() const override;
+
+	OptionalValue getSpellLevel() const override;
+
+	OptionalValue getEffectPower() const override;
+	OptionalValue getEffectDuration() const override;
+
+	OptionalValue64 getEffectValue() const override;
+
+	boost::logic::tribool isSmart() const override;
+	boost::logic::tribool isMassive() const override;
+
+	void setSpellLevel(Value value);
+
+	void setEffectPower(Value value);
+	void setEffectDuration(Value value);
+
+	void setEffectValue(Value64 value);
+
+	///only apply effects to specified targets
+	void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const;
+
+	///normal cast
+	void cast(ServerCallback * server, Target target);
+
+	///cast evaluation
+	void castEval(ServerCallback * server, Target target);
+
+	///cast with silent check for permitted cast
+	bool castIfPossible(ServerCallback * server, Target target);
+
+	std::vector<Target> findPotentialTargets(bool fast = false) const;
+
+private:
+	///spell school level
+	OptionalValue magicSkillLevel;
+
+	///actual spell-power affecting effect values
+	OptionalValue effectPower;
+	///actual spell-power affecting effect duration
+	OptionalValue effectDuration;
+
+	///for Archangel-like casting
+	OptionalValue64 effectValue;
+
+	Mode mode;
+	const CSpell * spell;
+	const CBattleInfoCallback * cb;
+	const Caster * caster;
+};
+
+class DLL_LINKAGE ISpellMechanicsFactory
+{
+public:
+	virtual ~ISpellMechanicsFactory();
+
+	virtual std::unique_ptr<Mechanics> create(const IBattleCast * event) const = 0;
+
+	static std::unique_ptr<ISpellMechanicsFactory> get(const CSpell * s);
+
+protected:
+	const CSpell * spell;
+
+	ISpellMechanicsFactory(const CSpell * s);
+};
+
+class DLL_LINKAGE Mechanics
+{
+public:
+	virtual ~Mechanics();
+
+	virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0;
+	virtual bool adaptGenericProblem(Problem & target) const = 0;
+
+	virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0;
+	virtual std::vector<const CStack *> getAffectedStacks(const Target & target) const = 0;
+
+	virtual bool canBeCast(Problem & problem) const = 0;
+	virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0;
+
+	virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0;
+
+	virtual void cast(ServerCallback * server, const Target & target) = 0;
+
+	virtual void castEval(ServerCallback * server, const Target & target) = 0;
+
+	virtual bool isReceptive(const battle::Unit * target) const = 0;
+
+	virtual std::vector<AimType> getTargetTypes() const = 0;
+
+	virtual std::vector<Destination> getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0;
+
+	virtual const Spell * getSpell() const = 0;
+
+	//Cast event facade
+
+	virtual IBattleCast::Value getEffectLevel() const = 0;
+	virtual IBattleCast::Value getRangeLevel() const = 0;
+
+	virtual IBattleCast::Value getEffectPower() const = 0;
+	virtual IBattleCast::Value getEffectDuration() const = 0;
+
+	virtual IBattleCast::Value64 getEffectValue() const = 0;
+
+	virtual PlayerColor getCasterColor() const = 0;
+
+	//Spell facade
+	virtual int32_t getSpellIndex() const = 0;
+	virtual SpellID getSpellId() const = 0;
+	virtual std::string getSpellName() const = 0;
+	virtual int32_t getSpellLevel() const = 0;
+
+	virtual bool isSmart() const = 0;
+	virtual bool isMassive() const = 0;
+	virtual bool alwaysHitFirstTarget() const = 0;
+	virtual bool requiresClearTiles() const = 0;
+
+	virtual bool isNegativeSpell() const = 0;
+	virtual bool isPositiveSpell() const = 0;
+	virtual bool isMagicalEffect() const = 0;
+
+	virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0;
+	virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0;
+	virtual int64_t applySpecificSpellBonus(int64_t value) const = 0;
+	virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0;
+
+	//Battle facade
+	virtual bool ownerMatches(const battle::Unit * unit) const = 0;
+	virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0;
+
+	//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 CBattleInfoCallback * battle() const = 0;
+
+	const Caster * caster;
+
+	BattleSide casterSide;
+
+protected:
+	Mechanics();
+};
+
+class DLL_LINKAGE BaseMechanics : public Mechanics
+{
+public:
+	virtual ~BaseMechanics();
+
+	bool adaptProblem(ESpellCastProblem source, Problem & target) const override;
+	bool adaptGenericProblem(Problem & target) const override;
+
+	int32_t getSpellIndex() const override;
+	SpellID getSpellId() const override;
+	std::string getSpellName() const override;
+	int32_t getSpellLevel() const override;
+
+	IBattleCast::Value getEffectLevel() const override;
+	IBattleCast::Value getRangeLevel() const override;
+
+	IBattleCast::Value getEffectPower() const override;
+	IBattleCast::Value getEffectDuration() const override;
+
+	IBattleCast::Value64 getEffectValue() const override;
+
+	PlayerColor getCasterColor() const override;
+
+	bool isSmart() const override;
+	bool isMassive() const override;
+	bool requiresClearTiles() const override;
+	bool alwaysHitFirstTarget() const override;
+
+	bool isNegativeSpell() const override;
+	bool isPositiveSpell() const override;
+	bool isMagicalEffect() const override;
+
+	int64_t adjustEffectValue(const battle::Unit * target) const override;
+	int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override;
+	int64_t applySpecificSpellBonus(int64_t value) const override;
+	int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override;
+
+	bool ownerMatches(const battle::Unit * unit) const override;
+	bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override;
+
+	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 CBattleInfoCallback * battle() const override;
+
+protected:
+	const CSpell * owner;
+	Mode mode;
+
+	BaseMechanics(const IBattleCast * event);
+
+private:
+    IBattleCast::Value rangeLevel;
+	IBattleCast::Value effectLevel;
+
+	///actual spell-power affecting effect values
+	IBattleCast::Value effectPower;
+	///actual spell-power affecting effect duration
+	IBattleCast::Value effectDuration;
+
+	///raw damage/heal amount
+	IBattleCast::Value64 effectValue;
+
+	boost::logic::tribool smart;
+	boost::logic::tribool massive;
+
+	const CBattleInfoCallback * cb;
+};
+
+class DLL_LINKAGE IReceptiveCheck
+{
+public:
+	virtual ~IReceptiveCheck() = default;
+
+	virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0;
+};
+
+}// namespace spells
+
+class DLL_LINKAGE AdventureSpellCastParameters
+{
+public:
+	const spells::Caster * caster;
+	int3 pos;
+};
+
+class DLL_LINKAGE IAdventureSpellMechanics
+{
+public:
+	IAdventureSpellMechanics(const CSpell * s);
+	virtual ~IAdventureSpellMechanics() = default;
+
+	virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0;
+	virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0;
+
+	virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0;
+
+	static std::unique_ptr<IAdventureSpellMechanics> createMechanics(const CSpell * s);
+protected:
+	const CSpell * owner;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 83 - 82
lib/spells/effects/Effect.h

@@ -1,82 +1,83 @@
-/*
- * Effect.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 <vcmi/spells/Magic.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct BattleHex;
-class CBattleInfoCallback;
-class JsonSerializeFormat;
-class ServerCallback;
-
-namespace vstd
-{
-	class RNG;
-}
-
-namespace spells
-{
-using EffectTarget = Target;
-
-namespace effects
-{
-using RNG = vstd::RNG;
-class Effects;
-class Effect;
-class Registry;
-
-using TargetType = spells::AimType;
-
-class DLL_LINKAGE Effect
-{
-public:
-	bool indirect = false;
-	bool optional = false;
-
-	std::string name;
-
-	virtual ~Effect() = default; //Required for child classes
-
-	// TODO: document me
-	virtual void adjustTargetTypes(std::vector<TargetType> & types) const = 0;
-
-	/// Generates list of hexes affected by spell, if spell were to cast at specified target
-	virtual void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const = 0;
-
-	/// Returns whether effect has any valid targets on the battlefield
-	virtual bool applicable(Problem & problem, const Mechanics * m) const;
-
-	/// Returns whether effect is valid and can be applied onto selected target
-	virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const;
-
-	virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0;
-
-	/// Processes input target and generates subset-result that contains only valid targets
-	virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0;
-
-	// TODO: document me
-	virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0;
-
-	/// Serializes (or deserializes) parameters of Effect
-	void serializeJson(JsonSerializeFormat & handler);
-
-	static std::shared_ptr<Effect> create(const Registry * registry, const std::string & type);
-
-protected:
-	virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0;
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Effect.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 <vcmi/spells/Magic.h>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct BattleHex;
+class BattleHexArray;
+class CBattleInfoCallback;
+class JsonSerializeFormat;
+class ServerCallback;
+
+namespace vstd
+{
+	class RNG;
+}
+
+namespace spells
+{
+using EffectTarget = Target;
+
+namespace effects
+{
+using RNG = vstd::RNG;
+class Effects;
+class Effect;
+class Registry;
+
+using TargetType = spells::AimType;
+
+class DLL_LINKAGE Effect
+{
+public:
+	bool indirect = false;
+	bool optional = false;
+
+	std::string name;
+
+	virtual ~Effect() = default; //Required for child classes
+
+	// TODO: document me
+	virtual void adjustTargetTypes(std::vector<TargetType> & types) const = 0;
+
+	/// Generates list of hexes affected by spell, if spell were to cast at specified target
+	virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0;
+
+	/// Returns whether effect has any valid targets on the battlefield
+	virtual bool applicable(Problem & problem, const Mechanics * m) const;
+
+	/// Returns whether effect is valid and can be applied onto selected target
+	virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const;
+
+	virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0;
+
+	/// Processes input target and generates subset-result that contains only valid targets
+	virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0;
+
+	// TODO: document me
+	virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0;
+
+	/// Serializes (or deserializes) parameters of Effect
+	void serializeJson(JsonSerializeFormat & handler);
+
+	static std::shared_ptr<Effect> create(const Registry * registry, const std::string & type);
+
+protected:
+	virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0;
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 52 - 52
lib/spells/effects/LocationEffect.cpp

@@ -1,52 +1,52 @@
-/*
- * LocationEffect.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 "LocationEffect.h"
-#include "../ISpellMechanics.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace spells
-{
-namespace effects
-{
-
-void LocationEffect::adjustTargetTypes(std::vector<TargetType> & types) const
-{
-
-}
-
-void LocationEffect::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
-{
-	for(const auto & destnation : spellTarget)
-		hexes.insert(destnation.hexValue);
-}
-
-EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const
-{
-	EffectTarget res;
-	vstd::copy_if(target, std::back_inserter(res), [](const Destination & d)
-	{
-		return !d.unitValue && (d.hexValue.isValid());
-	});
-	return res;
-}
-
-EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
-{
-	//by default effect covers exactly spell range
-	return EffectTarget(spellTarget);
-}
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * LocationEffect.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 "LocationEffect.h"
+#include "../ISpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace spells
+{
+namespace effects
+{
+
+void LocationEffect::adjustTargetTypes(std::vector<TargetType> & types) const
+{
+
+}
+
+void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
+{
+	for(const auto & destnation : spellTarget)
+		hexes.insert(destnation.hexValue);
+}
+
+EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const
+{
+	EffectTarget res;
+	vstd::copy_if(target, std::back_inserter(res), [](const Destination & d)
+	{
+		return !d.unitValue && (d.hexValue.isValid());
+	});
+	return res;
+}
+
+EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
+{
+	//by default effect covers exactly spell range
+	return EffectTarget(spellTarget);
+}
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 41 - 41
lib/spells/effects/LocationEffect.h

@@ -1,41 +1,41 @@
-/*
- * LocationEffect.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 "Effect.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-
-namespace spells
-{
-namespace effects
-{
-
-class LocationEffect : public Effect
-{
-public:
-	void adjustTargetTypes(std::vector<TargetType> & types) const override;
-
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const override;
-
-	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
-protected:
-
-private:
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * LocationEffect.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 "Effect.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+
+namespace spells
+{
+namespace effects
+{
+
+class LocationEffect : public Effect
+{
+public:
+	void adjustTargetTypes(std::vector<TargetType> & types) const override;
+
+	void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override;
+
+	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
+protected:
+
+private:
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 9 - 5
lib/spells/effects/Moat.cpp

@@ -32,7 +32,7 @@ namespace spells
 namespace effects
 namespace effects
 {
 {
 
 
-static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector<std::vector<BattleHex>> & moatHexes)
+static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector<BattleHexArray> & moatHexes)
 {
 {
 	{
 	{
 		JsonArraySerializer outer = handler.enterArray(fieldName);
 		JsonArraySerializer outer = handler.enterArray(fieldName);
@@ -43,8 +43,12 @@ static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string
 			JsonArraySerializer inner = outer.enterArray(outerIndex);
 			JsonArraySerializer inner = outer.enterArray(outerIndex);
 			inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER);
 			inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER);
 
 
+			BattleHex hex;
 			for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
 			for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
-				inner.serializeInt(innerIndex, moatHexes.at(outerIndex).at(innerIndex));
+			{
+				inner.serializeInt(innerIndex, hex);
+				moatHexes.at(outerIndex).set(innerIndex, hex);
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -96,10 +100,10 @@ void Moat::convertBonus(const Mechanics * m, std::vector<Bonus> & converted) con
 			nb.sid = BonusSourceID(m->getSpellId()); //for all
 			nb.sid = BonusSourceID(m->getSpellId()); //for all
 			nb.source = BonusSource::SPELL_EFFECT;//for all
 			nb.source = BonusSource::SPELL_EFFECT;//for all
 		}
 		}
-		std::set<BattleHex> flatMoatHexes;
+		BattleHexArray flatMoatHexes;
 
 
 		for(const auto & moatPatch : moatHexes)
 		for(const auto & moatPatch : moatHexes)
-			flatMoatHexes.insert(moatPatch.begin(), moatPatch.end());
+			flatMoatHexes.merge(moatPatch);
 
 
 		nb.limiter = std::make_shared<UnitOnHexLimiter>(std::move(flatMoatHexes));
 		nb.limiter = std::make_shared<UnitOnHexLimiter>(std::move(flatMoatHexes));
 		converted.push_back(nb);
 		converted.push_back(nb);
@@ -164,7 +168,7 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef
 		obstacle.appearSound = sideOptions.appearSound; //For dispellable moats
 		obstacle.appearSound = sideOptions.appearSound; //For dispellable moats
 		obstacle.appearAnimation = sideOptions.appearAnimation; //For dispellable moats
 		obstacle.appearAnimation = sideOptions.appearAnimation; //For dispellable moats
 		obstacle.animation = sideOptions.animation;
 		obstacle.animation = sideOptions.animation;
-		obstacle.customSize.insert(obstacle.customSize.end(),destination.cbegin(), destination.cend());
+		obstacle.customSize.merge(destination);
 		obstacle.animationYOffset = sideOptions.offsetY;
 		obstacle.animationYOffset = sideOptions.offsetY;
 		pack.changes.emplace_back();
 		pack.changes.emplace_back();
 		obstacle.toInfo(pack.changes.back());
 		obstacle.toInfo(pack.changes.back());

+ 43 - 43
lib/spells/effects/Moat.h

@@ -1,43 +1,43 @@
-/*
- * Moat.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 "Obstacle.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-struct Bonus;
-
-namespace spells
-{
-namespace effects
-{
-
-class Moat : public Obstacle
-{
-private:
-	ObstacleSideOptions sideOptions; //Defender only
-	std::vector<std::vector<BattleHex>> moatHexes; //Determine number of moat patches and hexes
-	std::vector<std::shared_ptr<Bonus>> bonus; //For battle-wide bonuses
-	bool dispellable; //For Tower landmines
-	int moatDamage; // Minimal moat damage
-public:
-	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
-protected:
-	void serializeJsonEffect(JsonSerializeFormat & handler) override;
-	void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
-	void convertBonus(const Mechanics * m, std::vector<Bonus> & converted) const;
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Moat.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 "Obstacle.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct Bonus;
+
+namespace spells
+{
+namespace effects
+{
+
+class Moat : public Obstacle
+{
+private:
+	ObstacleSideOptions sideOptions; //Defender only
+	std::vector<BattleHexArray> moatHexes; //Determine number of moat patches and hexes
+	std::vector<std::shared_ptr<Bonus>> bonus; //For battle-wide bonuses
+	bool dispellable; //For Tower landmines
+	int moatDamage; // Minimal moat damage
+public:
+	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+protected:
+	void serializeJsonEffect(JsonSerializeFormat & handler) override;
+	void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+	void convertBonus(const Mechanics * m, std::vector<Bonus> & converted) const;
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 5 - 6
lib/spells/effects/Obstacle.cpp

@@ -95,7 +95,7 @@ void ObstacleSideOptions::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("offsetY", offsetY);
 	handler.serializeInt("offsetY", offsetY);
 }
 }
 
 
-void Obstacle::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
+void Obstacle::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
 {
 {
 	EffectTarget effectTarget = transformTarget(m, spellTarget, spellTarget);
 	EffectTarget effectTarget = transformTarget(m, spellTarget, spellTarget);
 
 
@@ -180,11 +180,11 @@ void Obstacle::apply(ServerCallback * server, const Mechanics * m, const EffectT
 {
 {
 	if(patchCount > 0)
 	if(patchCount > 0)
 	{
 	{
-		std::vector<BattleHex> availableTiles;
-		auto insertAvailable = [&m](const BattleHex & hex, std::vector<BattleHex> & availableTiles)
+		BattleHexArray availableTiles;
+		auto insertAvailable = [&m](const BattleHex & hex, BattleHexArray & availableTiles)
 		{
 		{
 			if(isHexAvailable(m->battle(), hex, true))
 			if(isHexAvailable(m->battle(), hex, true))
-				availableTiles.push_back(hex);
+				availableTiles.insert(hex);
 		};
 		};
 
 
 		if(m->isMassive())
 		if(m->isMassive())
@@ -309,7 +309,6 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 		obstacle.animationYOffset = options.offsetY;
 		obstacle.animationYOffset = options.offsetY;
 
 
 		obstacle.customSize.clear();
 		obstacle.customSize.clear();
-		obstacle.customSize.reserve(options.shape.size());
 
 
 		for(const auto & shape : options.shape)
 		for(const auto & shape : options.shape)
 		{
 		{
@@ -318,7 +317,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 			for(auto direction : shape)
 			for(auto direction : shape)
 				hex.moveInDirection(direction, false);
 				hex.moveInDirection(direction, false);
 
 
-			obstacle.customSize.emplace_back(hex);
+			obstacle.customSize.insert(hex);
 		}
 		}
 
 
 		pack.changes.emplace_back();
 		pack.changes.emplace_back();

+ 78 - 78
lib/spells/effects/Obstacle.h

@@ -1,78 +1,78 @@
-/*
- * Obstacle.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 "LocationEffect.h"
-#include "../../GameConstants.h"
-#include "../../battle/BattleHex.h"
-#include "../../battle/CObstacleInstance.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace spells
-{
-namespace effects
-{
-
-class ObstacleSideOptions
-{
-public:
-	using RelativeShape = std::vector<std::vector<BattleHex::EDir>>;
-
-	RelativeShape shape; //shape of single obstacle relative to obstacle position
-	RelativeShape range; //position of obstacles relative to effect destination
-
-	AudioPath appearSound;
-	AnimationPath appearAnimation;
-	AnimationPath animation;
-
-	int offsetY = 0;
-
-	void serializeJson(JsonSerializeFormat & handler);
-};
-
-class Obstacle : public LocationEffect
-{
-public:
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const override;
-
-	bool applicable(Problem & problem, const Mechanics * m) const override;
-	bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
-
-	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
-
-protected:
-	void serializeJsonEffect(JsonSerializeFormat & handler) override;
-	virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const;
-
-	bool hidden = false;
-	bool trigger = false;
-	bool trap = false;
-	bool removeOnTrigger = false;
-	bool hideNative = false;
-	SpellID triggerAbility;
-private:
-	int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine)
-	bool passable = false;
-	int32_t turnsRemaining = -1;
-
-	BattleSideArray<ObstacleSideOptions> sideOptions;
-
-	static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
-	static bool noRoomToPlace(Problem & problem, const Mechanics * m);
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Obstacle.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 "LocationEffect.h"
+#include "../../GameConstants.h"
+#include "../../battle/BattleHexArray.h"
+#include "../../battle/CObstacleInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace spells
+{
+namespace effects
+{
+
+class ObstacleSideOptions
+{
+public:
+	using RelativeShape = std::vector<std::vector<BattleHex::EDir>>;
+
+	RelativeShape shape; //shape of single obstacle relative to obstacle position
+	RelativeShape range; //position of obstacles relative to effect destination
+
+	AudioPath appearSound;
+	AnimationPath appearAnimation;
+	AnimationPath animation;
+
+	int offsetY = 0;
+
+	void serializeJson(JsonSerializeFormat & handler);
+};
+
+class Obstacle : public LocationEffect
+{
+public:
+	void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override;
+
+	bool applicable(Problem & problem, const Mechanics * m) const override;
+	bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
+
+	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+
+protected:
+	void serializeJsonEffect(JsonSerializeFormat & handler) override;
+	virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const;
+
+	bool hidden = false;
+	bool trigger = false;
+	bool trap = false;
+	bool removeOnTrigger = false;
+	bool hideNative = false;
+	SpellID triggerAbility;
+private:
+	int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine)
+	bool passable = false;
+	int32_t turnsRemaining = -1;
+
+	BattleSideArray<ObstacleSideOptions> sideOptions;
+
+	static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
+	static bool noRoomToPlace(Problem & problem, const Mechanics * m);
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/spells/effects/Summon.cpp

@@ -28,7 +28,7 @@ namespace spells
 namespace effects
 namespace effects
 {
 {
 
 
-void Summon::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
+void Summon::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
 {
 {
 	//no hexes affected
 	//no hexes affected
 }
 }
@@ -207,4 +207,4 @@ EffectTarget Summon::transformTarget(const Mechanics * m, const Target & aimPoin
 }
 }
 }
 }
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 55 - 55
lib/spells/effects/Summon.h

@@ -1,55 +1,55 @@
-/*
- * Summon.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 "Effect.h"
-#include "../../GameConstants.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace spells
-{
-namespace effects
-{
-
-class Summon : public Effect
-{
-public:
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const override;
-	void adjustTargetTypes(std::vector<TargetType> & types) const override;
-
-	bool applicable(Problem & problem, const Mechanics * m) const override;
-
-	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
-
-protected:
-	void serializeJsonEffect(JsonSerializeFormat & handler) override final;
-
-private:
-	int32_t summonedCreatureAmount(const Mechanics * m) const;
-	int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const;
-
-	CreatureID creature;
-
-	bool permanent = false;
-	bool exclusive = true;
-	bool summonByHealth = false;
-	bool summonSameUnit = false;
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * Summon.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 "Effect.h"
+#include "../../GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace spells
+{
+namespace effects
+{
+
+class Summon : public Effect
+{
+public:
+	void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override;
+	void adjustTargetTypes(std::vector<TargetType> & types) const override;
+
+	bool applicable(Problem & problem, const Mechanics * m) const override;
+
+	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
+
+protected:
+	void serializeJsonEffect(JsonSerializeFormat & handler) override final;
+
+private:
+	int32_t summonedCreatureAmount(const Mechanics * m) const;
+	int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const;
+
+	CreatureID creature;
+
+	bool permanent = false;
+	bool exclusive = true;
+	bool summonByHealth = false;
+	bool summonSameUnit = false;
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/spells/effects/Teleport.cpp

@@ -81,8 +81,8 @@ void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectT
 	pack.battleID = m->battle()->getBattle()->getBattleID();
 	pack.battleID = m->battle()->getBattle()->getBattleID();
 	pack.distance = 0;
 	pack.distance = 0;
 	pack.stack = targetUnit->unitId();
 	pack.stack = targetUnit->unitId();
-	std::vector<BattleHex> tiles;
-	tiles.push_back(destination);
+	BattleHexArray tiles;
+	tiles.insert(destination);
 	pack.tilesToMove = tiles;
 	pack.tilesToMove = tiles;
 	pack.teleporting = true;
 	pack.teleporting = true;
 	server->apply(pack);
 	server->apply(pack);

+ 4 - 4
lib/spells/effects/UnitEffect.cpp

@@ -30,7 +30,7 @@ void UnitEffect::adjustTargetTypes(std::vector<TargetType> & types) const
 
 
 }
 }
 
 
-void UnitEffect::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
+void UnitEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
 {
 {
 	for(const auto & destnation : spellTarget)
 	for(const auto & destnation : spellTarget)
 		hexes.insert(destnation.hexValue);
 		hexes.insert(destnation.hexValue);
@@ -193,7 +193,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
 		return EffectTarget();
 		return EffectTarget();
 	}
 	}
 
 
-	std::set<BattleHex> possibleHexes;
+	BattleHexArray possibleHexes;
 
 
 	auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
 	auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
 	{
 	{
@@ -228,7 +228,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
 		if(possibleHexes.empty())
 		if(possibleHexes.empty())
 			break;
 			break;
 
 
-		destHex = BattleHex::getClosestTile(unit->unitSide(), destHex, possibleHexes);
+		destHex = possibleHexes.getClosestTile(unit->unitSide(), destHex);
 	}
 	}
 
 
 	return effectTarget;
 	return effectTarget;
@@ -278,4 +278,4 @@ void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler)
 }
 }
 }
 }
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 61 - 61
lib/spells/effects/UnitEffect.h

@@ -1,61 +1,61 @@
-/*
- * UnitEffect.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 "Effect.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-namespace spells
-{
-namespace effects
-{
-
-class UnitEffect : public Effect
-{
-public:
-	void adjustTargetTypes(std::vector<TargetType> & types) const override;
-
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const override;
-
-	bool applicable(Problem & problem, const Mechanics * m) const override;
-	bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
-
-	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
-
-    bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const;
-
-    virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const;
-
-protected:
-	int32_t chainLength = 0;
-	double chainFactor = 0.0;
-
-	virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const;
-	virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const;
-	virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const;
-
-	void serializeJsonEffect(JsonSerializeFormat & handler) override final;
-	virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0;
-
-private:
-	bool ignoreImmunity = false;
-
-	EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const;
-	EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const;
-};
-
-}
-}
-
-VCMI_LIB_NAMESPACE_END
+/*
+ * UnitEffect.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 "Effect.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace spells
+{
+namespace effects
+{
+
+class UnitEffect : public Effect
+{
+public:
+	void adjustTargetTypes(std::vector<TargetType> & types) const override;
+
+	void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override;
+
+	bool applicable(Problem & problem, const Mechanics * m) const override;
+	bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
+
+	EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override;
+
+    bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const;
+
+    virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const;
+
+protected:
+	int32_t chainLength = 0;
+	double chainFactor = 0.0;
+
+	virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const;
+	virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const;
+	virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const;
+
+	void serializeJsonEffect(JsonSerializeFormat & handler) override final;
+	virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0;
+
+private:
+	bool ignoreImmunity = false;
+
+	EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const;
+	EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const;
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 7 - 7
server/battles/BattleActionProcessor.cpp

@@ -635,7 +635,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 
 
 	//initing necessary tables
 	//initing necessary tables
 	auto accessibility = battle.getAccessibility(curStack);
 	auto accessibility = battle.getAccessibility(curStack);
-	std::set<BattleHex> passed;
+	BattleHexArray passed;
 	//Ignore obstacles on starting position
 	//Ignore obstacles on starting position
 	passed.insert(curStack->getPosition());
 	passed.insert(curStack->getPosition());
 	if(curStack->doubleWide())
 	if(curStack->doubleWide())
@@ -665,7 +665,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 		canUseGate = true;
 		canUseGate = true;
 	}
 	}
 
 
-	std::pair< std::vector<BattleHex>, int > path = battle.getPath(start, dest, curStack);
+	std::pair< BattleHexArray, int > path = battle.getPath(start, dest, curStack);
 
 
 	ret = path.second;
 	ret = path.second;
 
 
@@ -723,8 +723,8 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 			BattleStackMoved sm;
 			BattleStackMoved sm;
 			sm.battleID = battle.getBattle()->getBattleID();
 			sm.battleID = battle.getBattle()->getBattleID();
 			sm.stack = curStack->unitId();
 			sm.stack = curStack->unitId();
-			std::vector<BattleHex> tiles;
-			tiles.push_back(path.first[0]);
+			BattleHexArray tiles;
+			tiles.insert(path.first[0]);
 			sm.tilesToMove = tiles;
 			sm.tilesToMove = tiles;
 			sm.distance = path.second;
 			sm.distance = path.second;
 			sm.teleporting = false;
 			sm.teleporting = false;
@@ -733,10 +733,10 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 	}
 	}
 	else //for non-flying creatures
 	else //for non-flying creatures
 	{
 	{
-		std::vector<BattleHex> tiles;
+		BattleHexArray tiles;
 		const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		int v = (int)path.first.size()-1;
 		int v = (int)path.first.size()-1;
-		path.first.push_back(start);
+		path.first.insert(start);
 
 
 		// check if gate need to be open or closed at some point
 		// check if gate need to be open or closed at some point
 		BattleHex openGateAtHex, gateMayCloseAtHex;
 		BattleHex openGateAtHex, gateMayCloseAtHex;
@@ -822,7 +822,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 				for (bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v)
 				for (bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v)
 				{
 				{
 					BattleHex hex = path.first[v];
 					BattleHex hex = path.first[v];
-					tiles.push_back(hex);
+					tiles.insert(hex);
 
 
 					if ((openGateAtHex.isValid() && openGateAtHex == hex) ||
 					if ((openGateAtHex.isValid() && openGateAtHex == hex) ||
 						(gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex))
 						(gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex))

+ 24 - 24
server/battles/BattleFlowProcessor.cpp

@@ -36,7 +36,7 @@ BattleFlowProcessor::BattleFlowProcessor(BattleProcessor * owner, CGameHandler *
 {
 {
 }
 }
 
 
-void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & battle, std::vector<BattleHex> & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
+void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
 {
 {
 	int x = targetPosition.getX();
 	int x = targetPosition.getX();
 	int y = targetPosition.getY();
 	int y = targetPosition.getY();
@@ -44,31 +44,31 @@ void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & batt
 	const bool targetIsAttacker = side == BattleSide::ATTACKER;
 	const bool targetIsAttacker = side == BattleSide::ATTACKER;
 
 
 	if (targetIsAttacker) //handle front guardians, TODO: should we handle situation when units start battle near opposite side of the battlefield? Cannot happen in normal H3...
 	if (targetIsAttacker) //handle front guardians, TODO: should we handle situation when units start battle near opposite side of the battlefield? Cannot happen in normal H3...
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output);
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false));
 	else
 	else
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output);
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false));
 
 
 	//guardian spawn locations for four default position cases for attacker and defender, non-default starting location for att and def is handled in first two if's
 	//guardian spawn locations for four default position cases for attacker and defender, non-default starting location for att and def is handled in first two if's
 	if (targetIsAttacker && ((y % 2 == 0) || (x > 1)))
 	if (targetIsAttacker && ((y % 2 == 0) || (x > 1)))
 	{
 	{
 		if (targetIsTwoHex && (y % 2 == 1) && (x == 2)) //handle exceptional case
 		if (targetIsTwoHex && (y % 2 == 1) && (x == 2)) //handle exceptional case
 		{
 		{
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+			output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_RIGHT, false));
+			output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false));
 		}
 		}
 		else
 		else
 		{	//add back-side guardians for two-hex target, side guardians for one-hex
 		{	//add back-side guardians for two-hex target, side guardians for one-hex
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false), output);
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false), output);
+			output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false));
+			output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false));
 
 
 			if (!targetIsTwoHex && x > 2) //back guard for one-hex
 			if (!targetIsTwoHex && x > 2) //back guard for one-hex
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false), output);
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false));
 			else if (targetIsTwoHex)//front-side guardians for two-hex target
 			else if (targetIsTwoHex)//front-side guardians for two-hex target
 			{
 			{
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false));
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false));
 				if (x > 3) //back guard for two-hex
 				if (x > 3) //back guard for two-hex
-					BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output);
+					output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false));
 			}
 			}
 		}
 		}
 
 
@@ -78,36 +78,36 @@ void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & batt
 	{
 	{
 		if (targetIsTwoHex && (y % 2 == 0) && (x == GameConstants::BFIELD_WIDTH - 3)) //handle exceptional case... equivalent for above for defender side
 		if (targetIsTwoHex && (y % 2 == 0) && (x == GameConstants::BFIELD_WIDTH - 3)) //handle exceptional case... equivalent for above for defender side
 		{
 		{
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
+			output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
+			output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
 		}
 		}
 		else
 		else
 		{
 		{
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false), output);
-			BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false), output);
+			output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false));
+			output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false));
 
 
 			if (!targetIsTwoHex && x < GameConstants::BFIELD_WIDTH - 3)
 			if (!targetIsTwoHex && x < GameConstants::BFIELD_WIDTH - 3)
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false), output);
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false));
 			else if (targetIsTwoHex)
 			else if (targetIsTwoHex)
 			{
 			{
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
 				if (x < GameConstants::BFIELD_WIDTH - 4)
 				if (x < GameConstants::BFIELD_WIDTH - 4)
-					BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output);
+					output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false));
 			}
 			}
 		}
 		}
 	}
 	}
 
 
 	else if (!targetIsAttacker && y % 2 == 0)
 	else if (!targetIsAttacker && y % 2 == 0)
 	{
 	{
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
 	}
 	}
 
 
 	else if (targetIsAttacker && y % 2 == 1)
 	else if (targetIsAttacker && y % 2 == 1)
 	{
 	{
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
-		BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false));
+		output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false));
 	}
 	}
 }
 }
 
 
@@ -150,7 +150,7 @@ void BattleFlowProcessor::trySummonGuardians(const CBattleInfoCallback & battle,
 	std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS));
 	std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS));
 	auto accessibility = battle.getAccessibility();
 	auto accessibility = battle.getAccessibility();
 	CreatureID creatureData = summonInfo->subtype.as<CreatureID>();
 	CreatureID creatureData = summonInfo->subtype.as<CreatureID>();
-	std::vector<BattleHex> targetHexes;
+	BattleHexArray targetHexes;
 	const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard
 	const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard
 	const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
 	const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
 
 

+ 62 - 61
server/battles/BattleFlowProcessor.h

@@ -1,61 +1,62 @@
-/*
- * BattleFlowProcessor.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../lib/battle/BattleSide.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-class CStack;
-struct BattleHex;
-class BattleAction;
-class CBattleInfoCallback;
-struct CObstacleInstance;
-namespace battle
-{
-class Unit;
-}
-VCMI_LIB_NAMESPACE_END
-
-class CGameHandler;
-class BattleProcessor;
-
-/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions
-class BattleFlowProcessor : boost::noncopyable
-{
-	BattleProcessor * owner;
-	CGameHandler * gameHandler;
-
-	const CStack * getNextStack(const CBattleInfoCallback & battle);
-
-	bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack);
-	bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack);
-
-	void summonGuardiansHelper(const CBattleInfoCallback & battle, std::vector<BattleHex> & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex);
-	void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack);
-	void tryPlaceMoats(const CBattleInfoCallback & battle);
-	void castOpeningSpells(const CBattleInfoCallback & battle);
-	void activateNextStack(const CBattleInfoCallback & battle);
-	void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound);
-
-	void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack);
-	void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle);
-	void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack);
-	void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack);
-
-	void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next);
-	bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
-
-public:
-	explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler);
-
-	void onBattleStarted(const CBattleInfoCallback & battle);
-	void onTacticsEnded(const CBattleInfoCallback & battle);
-	void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba);
-};
+/*
+ * BattleFlowProcessor.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../lib/battle/BattleSide.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class CStack;
+struct BattleHex;
+class BattleHexArray;
+class BattleAction;
+class CBattleInfoCallback;
+struct CObstacleInstance;
+namespace battle
+{
+class Unit;
+}
+VCMI_LIB_NAMESPACE_END
+
+class CGameHandler;
+class BattleProcessor;
+
+/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions
+class BattleFlowProcessor : boost::noncopyable
+{
+	BattleProcessor * owner;
+	CGameHandler * gameHandler;
+
+	const CStack * getNextStack(const CBattleInfoCallback & battle);
+
+	bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack);
+	bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack);
+
+	void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex);
+	void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack);
+	void tryPlaceMoats(const CBattleInfoCallback & battle);
+	void castOpeningSpells(const CBattleInfoCallback & battle);
+	void activateNextStack(const CBattleInfoCallback & battle);
+	void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound);
+
+	void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack);
+	void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle);
+	void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack);
+	void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack);
+
+	void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next);
+	bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
+
+public:
+	explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler);
+
+	void onBattleStarted(const CBattleInfoCallback & battle);
+	void onTacticsEnded(const CBattleInfoCallback & battle);
+	void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba);
+};