Przeglądaj źródła

Merge pull request #5044 from MichalZr6/battle_hex_array

New container for BattleHexes. Refactors aiming for quick-battle better performance.
Ivan Savenko 9 miesięcy temu
rodzic
commit
a44bbf4527
91 zmienionych plików z 1266 dodań i 831 usunięć
  1. 4 4
      AI/BattleAI/AttackPossibility.cpp
  2. 1 1
      AI/BattleAI/BattleAI.h
  3. 21 25
      AI/BattleAI/BattleEvaluator.cpp
  4. 1 1
      AI/BattleAI/BattleEvaluator.h
  5. 36 37
      AI/BattleAI/BattleExchangeVariant.cpp
  6. 2 2
      AI/BattleAI/BattleExchangeVariant.h
  7. 1 1
      AI/BattleAI/PotentialTargets.cpp
  8. 15 14
      AI/StupidAI/StupidAI.cpp
  9. 2 2
      AI/StupidAI/StupidAI.h
  10. 1 1
      client/CPlayerInterface.cpp
  11. 1 1
      client/CPlayerInterface.h
  12. 8 8
      client/battle/BattleActionsController.cpp
  13. 6 6
      client/battle/BattleAnimationClasses.cpp
  14. 9 9
      client/battle/BattleAnimationClasses.h
  15. 47 45
      client/battle/BattleFieldController.cpp
  16. 12 12
      client/battle/BattleFieldController.h
  17. 1 1
      client/battle/BattleInterface.cpp
  18. 2 2
      client/battle/BattleInterface.h
  19. 1 1
      client/battle/BattleObstacleController.h
  20. 3 3
      client/battle/BattleSiegeController.cpp
  21. 2 2
      client/battle/BattleStacksController.cpp
  22. 4 3
      client/battle/BattleStacksController.h
  23. 6 5
      include/vstd/RNG.h
  24. 1 1
      lib/BattleFieldHandler.cpp
  25. 2 2
      lib/BattleFieldHandler.h
  26. 1 1
      lib/CGameInterface.cpp
  27. 1 1
      lib/CGameInterface.h
  28. 2 0
      lib/CMakeLists.txt
  29. 8 8
      lib/CStack.cpp
  30. 2 2
      lib/CStack.h
  31. 2 2
      lib/IGameEventsReceiver.h
  32. 5 6
      lib/ObstacleHandler.cpp
  33. 2 2
      lib/ObstacleHandler.h
  34. 4 3
      lib/battle/AccessibilityInfo.cpp
  35. 3 2
      lib/battle/AccessibilityInfo.h
  36. 33 207
      lib/battle/BattleHex.cpp
  37. 204 42
      lib/battle/BattleHex.h
  38. 132 0
      lib/battle/BattleHexArray.cpp
  39. 317 0
      lib/battle/BattleHexArray.h
  40. 8 8
      lib/battle/BattleInfo.cpp
  41. 103 116
      lib/battle/CBattleInfoCallback.cpp
  42. 14 14
      lib/battle/CBattleInfoCallback.h
  43. 19 11
      lib/battle/CObstacleInstance.cpp
  44. 6 6
      lib/battle/CObstacleInstance.h
  45. 3 1
      lib/battle/CUnitState.cpp
  46. 1 1
      lib/battle/DamageCalculator.cpp
  47. 2 2
      lib/battle/IBattleInfoCallback.h
  48. 9 10
      lib/battle/ReachabilityInfo.cpp
  49. 10 5
      lib/battle/ReachabilityInfo.h
  50. 32 48
      lib/battle/Unit.cpp
  51. 7 7
      lib/battle/Unit.h
  52. 1 1
      lib/bonuses/BonusEnum.h
  53. 6 5
      lib/bonuses/Limiters.cpp
  54. 3 3
      lib/bonuses/Limiters.h
  55. 1 1
      lib/mapping/CMap.cpp
  56. 3 2
      lib/networkPacks/PacksForClientBattle.h
  57. 1 0
      lib/rmg/RmgPath.cpp
  58. 9 16
      lib/spells/BattleSpellMechanics.cpp
  59. 2 2
      lib/spells/BattleSpellMechanics.h
  60. 10 3
      lib/spells/CSpellHandler.cpp
  61. 0 2
      lib/spells/CSpellHandler.h
  62. 1 1
      lib/spells/ISpellMechanics.h
  63. 2 2
      lib/spells/effects/Catapult.cpp
  64. 1 1
      lib/spells/effects/Clone.cpp
  65. 1 1
      lib/spells/effects/DemonSummon.cpp
  66. 3 2
      lib/spells/effects/Effect.h
  67. 2 1
      lib/spells/effects/LocationEffect.cpp
  68. 1 1
      lib/spells/effects/LocationEffect.h
  69. 9 5
      lib/spells/effects/Moat.cpp
  70. 1 1
      lib/spells/effects/Moat.h
  71. 5 6
      lib/spells/effects/Obstacle.cpp
  72. 2 2
      lib/spells/effects/Obstacle.h
  73. 2 2
      lib/spells/effects/Summon.cpp
  74. 1 1
      lib/spells/effects/Summon.h
  75. 2 2
      lib/spells/effects/Teleport.cpp
  76. 4 4
      lib/spells/effects/UnitEffect.cpp
  77. 1 1
      lib/spells/effects/UnitEffect.h
  78. 3 3
      scripting/lua/LuaSpellEffect.cpp
  79. 1 1
      scripting/lua/LuaSpellEffect.h
  80. 4 2
      scripting/lua/api/BattleCb.cpp
  81. 1 1
      scripting/lua/api/netpacks/BattleStackMoved.cpp
  82. 7 7
      server/battles/BattleActionProcessor.cpp
  83. 1 1
      server/battles/BattleActionProcessor.h
  84. 24 24
      server/battles/BattleFlowProcessor.cpp
  85. 3 2
      server/battles/BattleFlowProcessor.h
  86. 9 9
      test/battle/BattleHexTest.cpp
  87. 9 9
      test/battle/battle_UnitTest.cpp
  88. 1 1
      test/mock/mock_IBattleInfoCallback.h
  89. 2 1
      test/mock/mock_spells_Mechanics.h
  90. 5 5
      test/scripting/LuaSpellEffectAPITest.cpp
  91. 3 3
      test/scripting/LuaSpellEffectTest.cpp

+ 4 - 4
AI/BattleAI/AttackPossibility.cpp

@@ -280,7 +280,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
 	std::set<uint32_t> checkedUnits;
 
 	auto attacker = attackInfo.attacker;
-	auto hexes = attacker->getSurroundingHexes(hex);
+	const auto & hexes = attacker->getSurroundingHexes(hex);
 	for(BattleHex tile : hexes)
 	{
 		auto st = state->battleGetUnitByPos(tile, true);
@@ -326,9 +326,9 @@ AttackPossibility AttackPossibility::evaluate(
 
 	AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
 
-	std::vector<BattleHex> defenderHex;
+	BattleHexArray defenderHex;
 	if(attackInfo.shooting)
-		defenderHex.push_back(defender->getPosition());
+		defenderHex.insert(defender->getPosition());
 	else
 		defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
 
@@ -384,7 +384,7 @@ AttackPossibility AttackPossibility::evaluate(
 		affectedUnits = defenderUnits;
 		vstd::concatenate(affectedUnits, retaliatedUnits);
 
-		logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex);
+		logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex);
 
 		std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates;
 

+ 1 - 1
AI/BattleAI/BattleAI.h

@@ -88,7 +88,7 @@ public:
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
-	//void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
+	//void battleStackMoved(const CStack * stack, BattleHexArray dest, int distance) override;
 	//void battleSpellCast(const BattleSpellCast *sc) override;
 	//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;

+ 21 - 25
AI/BattleAI/BattleEvaluator.cpp

@@ -214,8 +214,8 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 				bestAttack.attackerState->unitType()->getJsonKey(),
 				bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
 				bestAttack.affectedUnits[0]->getCount(),
-				(int)bestAttack.from,
-				(int)bestAttack.attack.attacker->getPosition().hex,
+				bestAttack.from.toInt(),
+				bestAttack.attack.attacker->getPosition().toInt(),
 				bestAttack.attack.chargeDistance,
 				bestAttack.attack.attacker->getMovementRange(0),
 				bestAttack.defenderDamageReduce,
@@ -252,7 +252,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 
 					if(siegeDefense)
 					{
-						logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex);
+						logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition());
 
 						BattleAttackInfo bai(stack, stack, 0, false);
 						AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai);
@@ -278,7 +278,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 		score = moveTarget.score;
 		cachedAttack.ap = moveTarget.cachedAttack;
 		cachedAttack.score = score;
-		cachedAttack.turn = moveTarget.turnsToRich;
+		cachedAttack.turn = moveTarget.turnsToReach;
 
 		if(stack->waited())
 		{
@@ -286,7 +286,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 				"Moving %s towards hex %s[%d], score: %2f",
 				stack->getDescription(),
 				moveTarget.cachedAttack->attack.defender->getDescription(),
-				moveTarget.cachedAttack->attack.defender->getPosition().hex,
+				moveTarget.cachedAttack->attack.defender->getPosition(),
 				moveTarget.score);
 
 			return goTowardsNearest(stack, moveTarget.positions, *targets);
@@ -320,9 +320,9 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 	return stack->waited() ?  BattleAction::makeDefend(stack) : BattleAction::makeWait(stack);
 }
 
-uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start)
+uint64_t timeElapsed(std::chrono::time_point<std::chrono::steady_clock> start)
 {
-	auto end = std::chrono::high_resolution_clock::now();
+	auto end = std::chrono::steady_clock::now();
 
 	return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
 }
@@ -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, const BattleHexArray & hexes, const PotentialTargets & targets)
 {
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
@@ -381,20 +381,20 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 		return BattleAction::makeDefend(stack);
 	}
 
-	std::vector<BattleHex> targetHexes = hexes;
+	BattleHexArray targetHexes = hexes;
 
 	vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
 
 	std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
 		{
-			return reachability.distances[h1] < reachability.distances[h2];
+			return reachability.distances[h1.toInt()] < reachability.distances[h2.toInt()];
 		});
 
-	BattleHex bestNeighbor = targetHexes.front();
+	BattleHex bestNeighbour = hexes.front();
 
-	if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
+	if(reachability.distances[bestNeighbour.toInt()] > GameConstants::BFIELD_SIZE)
 	{
-		logAi->trace("No richable hexes.");
+		logAi->trace("No reachable hexes.");
 		return BattleAction::makeDefend(stack);
 	}
 
@@ -419,24 +419,19 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 
 	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
-		std::set<BattleHex> obstacleHexes;
-
-		auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
-			auto affectedHexes = spellObst.getAffectedTiles();
-			obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
-		};
+		BattleHexArray obstacleHexes;
 
 		const auto & obstacles = hb->battleGetAllObstacles();
 
-		for (const auto & obst: obstacles) {
-
+		for (const auto & obst : obstacles) 
+		{
 			if(obst->triggersEffects())
 			{
 				auto triggerAbility =  VLC->spells()->getById(obst->getTrigger());
 				auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
 
 				if(triggerIsNegative)
-					insertAffected(*obst, obstacleHexes);
+					obstacleHexes.insert(obst->getAffectedTiles());
 			}
 		}
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
@@ -446,7 +441,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 			const int NEGATIVE_OBSTACLE_PENALTY = 100; // avoid landing on negative obstacle (moat, fire wall, etc)
 			const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
 
-			auto distance = BattleHex::getDistance(bestNeighbor, hex);
+			auto distance = BattleHex::getDistance(bestNeighbour, hex);
 
 			if(vstd::contains(obstacleHexes, hex))
 				distance += NEGATIVE_OBSTACLE_PENALTY;
@@ -458,7 +453,8 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 	}
 	else
 	{
-		BattleHex currentDest = bestNeighbor;
+		BattleHex currentDest = bestNeighbour;
+
 		while(true)
 		{
 			if(!currentDest.isValid())
@@ -472,7 +468,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 				return moveOrAttack(stack, currentDest, targets);
 			}
 
-			currentDest = reachability.predecessors[currentDest];
+			currentDest = reachability.predecessors[currentDest.toInt()];
 		}
 	}
 	

+ 1 - 1
AI/BattleAI/BattleEvaluator.h

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

+ 36 - 37
AI/BattleAI/BattleExchangeVariant.cpp

@@ -21,7 +21,7 @@ AttackerValue::AttackerValue()
 MoveTarget::MoveTarget()
 	: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
 {
-	turnsToRich = 1;
+	turnsToReach = 1;
 }
 
 float BattleExchangeVariant::trackAttack(
@@ -310,7 +310,7 @@ ReachabilityInfo getReachabilityWithEnemyBypass(
 
 			for(auto & hex : unit->getHexes())
 				if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
-					params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
+					params.destructibleEnemyTurns[hex.toInt()] = turnsToKill * unit->getMovementRange();
 		}
 
 		params.bypassEnemyStacks = true;
@@ -361,7 +361,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 		float penaltyMultiplier = 1.0f; // Default multiplier, no penalty
 		float closestAllyDistance = std::numeric_limits<float>::max();
 
-		for (const battle::Unit* ally : hb->battleAliveUnits()) {
+		for (const battle::Unit* ally : hb->battleAliveUnits()) 
+		{
 			if (ally == activeStack) 
 				continue;
 			if (ally->unitSide() != activeStack->unitSide()) 
@@ -375,12 +376,13 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 		}
 
 		// If an ally is closer to the enemy, compute the penaltyMultiplier
-		if (closestAllyDistance < distance) {
+		if (closestAllyDistance < distance) 
+		{
 			penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances
 		}
 
-		auto turnsToRich = (distance - 1) / speed + 1;
-		auto hexes = enemy->getSurroundingHexes();
+		auto turnsToReach = (distance - 1) / speed + 1;
+		const BattleHexArray & hexes = enemy->getSurroundingHexes();
 		auto enemySpeed = enemy->getMovementRange();
 		auto speedRatio = speed / static_cast<float>(enemySpeed);
 		auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
@@ -393,16 +395,16 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 
 			attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
 
-			auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
+			auto score = calculateExchange(attack, turnsToReach, targets, damageCache, hb);
 
 			score.enemyDamageReduce *= multiplier;
 
 #if BATTLE_TRACE_LEVEL >= 1
-			logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
+			logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToReach, result.score, scoreValue(score));
 #endif
 
 			if(result.score < scoreValue(score)
-				|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
+				|| (result.turnsToReach > turnsToReach && vstd::isAlmostEqual(result.score, scoreValue(score))))
 			{
 				result.score = scoreValue(score);
 				result.positions.clear();
@@ -411,15 +413,13 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 				logAi->trace("New high score");
 #endif
 
-				for(const BattleHex & initialEnemyHex : enemy->getAttackableHexes(activeStack))
+				for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
 				{
-					BattleHex enemyHex = initialEnemyHex;
-
-					while(!flying && dists.distances[enemyHex] > speed && dists.predecessors.at(enemyHex).isValid())
+					while(!flying && dists.distances[enemyHex.toInt()] > speed && dists.predecessors.at(enemyHex.toInt()).isValid())
 					{
-						enemyHex = dists.predecessors.at(enemyHex);
+						enemyHex = dists.predecessors.at(enemyHex.toInt());
 
-						if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
+						if(dists.accessibility[enemyHex.toInt()] == EAccessibility::ALIVE_STACK)
 						{
 							auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
 
@@ -429,7 +429,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 								logAi->trace("Found target to bypass at %d", enemyHex.hex);
 #endif
 
-								auto attackHex = dists.predecessors[enemyHex];
+								auto attackHex = dists.predecessors[enemyHex.toInt()];
 								auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
 								auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
 
@@ -440,7 +440,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 
 								auto bypassScore = calculateExchange(
 									attackBypass,
-									dists.distances[attackHex],
+									dists.distances[attackHex.toInt()],
 									targets,
 									damageCache,
 									hb,
@@ -458,11 +458,11 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 						}
 					}
 
-					result.positions.push_back(enemyHex);
+					result.positions.insert(enemyHex);
 				}
 
 				result.cachedAttack = attack;
-				result.turnsToRich = turnsToRich;
+				result.turnsToReach = turnsToReach;
 			}
 		}
 	}
@@ -484,15 +484,15 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(cons
 		queue.pop();
 		checkedStacks.push_back(stack);
 
-		auto hexes = stack->getSurroundingHexes();
+		auto const & hexes = stack->getSurroundingHexes();
 		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,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 
 	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;
 	
@@ -915,8 +916,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBa
 			}
 		}
 	}
-
-	for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
+	for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); ++hex)
 	{
 		reachabilityMap[hex] = getOneTurnReachableUnits(0, hex);
 	}
@@ -951,17 +951,17 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
 
 			ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
 
-			bool reachable = unitReachability.distances.at(hex) <= radius;
+			bool reachable = unitReachability.distances.at(hex.toInt()) <= radius;
 
-			if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
+			if(!reachable && unitReachability.accessibility[hex.toInt()] == EAccessibility::ALIVE_STACK)
 			{
 				const battle::Unit * hexStack = cb->battleGetUnitByPos(hex);
 
 				if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 				{
-					for(BattleHex neighbor : hex.neighbouringTiles())
+					for(BattleHex neighbour : hex.getNeighbouringTiles())
 					{
-						reachable = unitReachability.distances.at(neighbor) <= radius;
+						reachable = unitReachability.distances.at(neighbour.toInt()) <= radius;
 
 						if(reachable) break;
 					}
@@ -1008,22 +1008,21 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 			auto unitReachability = turnBattle.getReachability(unit);
 			auto unitSpeed = unit->getMovementRange(turn); // Cached value, to avoid performance hit
 
-			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
+			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex++)
 			{
 				bool enemyUnit = false;
-				bool reachable = unitReachability.distances.at(hex) <= unitSpeed;
+				bool reachable = unitReachability.distances.at(hex.toInt()) <= unitSpeed;
 
-				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
+				if(!reachable && unitReachability.accessibility[hex.toInt()] == EAccessibility::ALIVE_STACK)
 				{
 					const battle::Unit * hexStack = turnBattle.battleGetUnitByPos(hex);
 
 					if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
 					{
 						enemyUnit = true;
-
-						for(BattleHex neighbor : hex.neighbouringTiles())
+						for(BattleHex neighbour : hex.getNeighbouringTiles())
 						{
-							reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
+							reachable = unitReachability.distances.at(neighbour.toInt()) <= unitSpeed;
 
 							if(reachable) break;
 						}

+ 2 - 2
AI/BattleAI/BattleExchangeVariant.h

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

+ 1 - 1
AI/BattleAI/PotentialTargets.cpp

@@ -50,7 +50,7 @@ PotentialTargets::PotentialTargets(
 
 		auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
 		{
-			int distance = hex.isValid() ? reachability.distances[hex] : 0;
+			int distance = hex.isValid() ? reachability.distances[hex.toInt()] : 0;
 			auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting);
 
 			return AttackPossibility::evaluate(bai, hex, damageCache, state);

+ 15 - 14
AI/StupidAI/StupidAI.cpp

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

+ 2 - 2
AI/StupidAI/StupidAI.h

@@ -43,7 +43,7 @@ public:
 	//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 battleStackMoved(const BattleID & battleID, const CStack * stack, const 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;
@@ -51,6 +51,6 @@ public:
 	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;
+	BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const;
 };
 

+ 1 - 1
client/CPlayerInterface.cpp

@@ -846,7 +846,7 @@ void CPlayerInterface::battleLogMessage(const BattleID & battleID, const std::ve
 	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, const BattleHexArray & dest, int distance, bool teleport)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	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 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 battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, const 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 BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect

+ 8 - 8
client/battle/BattleActionsController.cpp

@@ -522,7 +522,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 			{
 				const auto * attacker = owner.stacksController->getActiveStack();
 				BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
-				int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0;
+				int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0;
 				DamageEstimation retaliation;
 				BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
 				DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
@@ -723,11 +723,11 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 		{
 			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);
-				if(vstd::contains(acc, targetHex))
+				if(acc.contains(targetHex))
 					owner.giveCommand(EActionType::WALK, targetHex);
-				else if(vstd::contains(acc, shiftedDest))
+				else if(acc.contains(shiftedDest))
 					owner.giveCommand(EActionType::WALK, shiftedDest);
 			}
 			else
@@ -1008,12 +1008,12 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 
 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);
 
-	if (vstd::contains(acc, myNumber))
+	if (acc.contains(myNumber))
 		return true;
-	else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
+	else if (stackToMove->doubleWide() && acc.contains(shiftedDest))
 		return true;
 	else
 		return false;
@@ -1126,4 +1126,4 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction
 void BattleActionsController::resetCurrentStackPossibleActions()
 {
 	possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
-}
+}

+ 6 - 6
client/battle/BattleAnimationClasses.cpp

@@ -174,7 +174,7 @@ AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker
 	  attackingStack(attacker)
 {
 	assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
-	attackingStackPosBeforeReturn = attackingStack->getPosition();
+	attackingStackPosBeforeReturn = attackingStack->getPosition().toInt();
 }
 
 HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
@@ -422,7 +422,7 @@ MovementAnimation::~MovementAnimation()
 		CCS->soundh->stopSound(moveSoundHandler);
 }
 
-MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance)
+MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, const BattleHexArray & _destTiles, int _distance)
 	: StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()),
 	  destTiles(_destTiles),
 	  currentMoveIndex(0),
@@ -892,17 +892,17 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 	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, const BattleHexArray & hexes, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
-	battlehexes = hex;
+	battlehexes = hexes;
 }
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, float transparencyFactor, bool reversed):
 	EffectAnimation(owner, animationName, effects, transparencyFactor, reversed)
 {
 	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):
@@ -921,7 +921,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 	assert(hex.isValid());
-	battlehexes.push_back(hex);
+	battlehexes.insert(hex);
 	positions.push_back(pos);
 }
 

+ 9 - 9
client/battle/BattleAnimationClasses.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../../lib/battle/BattleHex.h"
+#include "../../lib/battle/BattleHexArray.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 
@@ -143,7 +143,7 @@ class MovementAnimation : public StackMoveAnimation
 private:
 	int moveSoundHandler; // sound handler used when moving a unit
 
-	std::vector<BattleHex> destTiles; //full path, includes already passed hexes
+	const BattleHexArray & destTiles; //full path, includes already passed hexes
 	ui32 currentMoveIndex; // index of nextHex in destTiles
 
 	double begX, begY; // starting position
@@ -159,7 +159,7 @@ public:
 	bool init() 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, const BattleHexArray & _destTiles, int _distance);
 	~MovementAnimation();
 };
 
@@ -316,7 +316,7 @@ class EffectAnimation : public BattleAnimation
 
 	std::shared_ptr<CAnimation>	animation;
 	std::vector<Point> positions;
-	std::vector<BattleHex> battlehexes;
+	BattleHexArray battlehexes;
 
 	bool alignToBottom() const;
 	bool waitForSound() const;
@@ -339,14 +339,14 @@ public:
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, float transparencyFactor = 1.f, bool reversed = false);
 
 	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
-	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                 , int effects = 0, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                   , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos      , int effects = 0, bool reversed = false);
 
 	/// 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, std::vector<BattleHex> hex, int effects = 0, 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, const BattleHexArray & hexes, 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();
 
 	bool init() override;

+ 47 - 45
client/battle/BattleFieldController.cpp

@@ -267,7 +267,7 @@ void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
 void BattleFieldController::redrawBackgroundWithHexes()
 {
 	const CStack *activeStack = owner.stacksController->getActiveStack();
-	std::vector<BattleHex> attackableHexes;
+	BattleHexArray attackableHexes;
 	if(activeStack)
 		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
 	if(settings["battle"]["stackRange"].Bool())
 	{
-		std::vector<BattleHex> hexesToShade = occupiableHexes;
-		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
+		BattleHexArray hexesToShade = occupiableHexes;
+		hexesToShade.insert(attackableHexes);
 		for(BattleHex hex : hexesToShade)
 		{
 			showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
@@ -312,9 +312,9 @@ void BattleFieldController::showHighlightedHex(Canvas & canvas, std::shared_ptr<
 		canvas.draw(cellBorder, hexPos);
 }
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
+BattleHexArray BattleFieldController::getHighlightedHexesForActiveStack()
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 
 	if(!owner.stacksController->getActiveStack())
 		return result;
@@ -324,16 +324,16 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
 
 	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)
 		result.insert(hex);
 
 	return result;
 }
 
-std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
+BattleHexArray BattleFieldController::getMovementRangeForHoveredStack()
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 
 	if (!owner.stacksController->getActiveStack())
 		return result;
@@ -344,16 +344,16 @@ std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
 	auto hoveredStack = getHoveredStack();
 	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)
 			result.insert(hex);
 	}
 	return result;
 }
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
+BattleHexArray BattleFieldController::getHighlightedHexesForSpellRange()
 {
-	std::set<BattleHex> result;
+	BattleHexArray result;
 	auto hoveredHex = getHoveredHex();
 
 	const spells::Caster *caster = nullptr;
@@ -378,7 +378,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
 	return result;
 }
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget()
+BattleHexArray BattleFieldController::getHighlightedHexesForMovementTarget()
 {
 	const CStack * stack = owner.stacksController->getActiveStack();
 	auto hoveredHex = getHoveredHex();
@@ -386,7 +386,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 	if(!stack)
 		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);
 	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())
 			return {hoveredHex, stack->occupiedHex(hoveredHex)};
@@ -412,7 +412,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 
 	if(stack->doubleWide())
 	{
-		for(auto const & hex : availableHexes)
+		for(auto hex : availableHexes)
 		{
 			if(stack->occupiedHex(hex) == hoveredHex)
 				return {hoveredHex, hex};
@@ -424,9 +424,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 
 // 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())
 		return rangeHexes;
@@ -436,27 +436,27 @@ std::vector<BattleHex> BattleFieldController::getRangeHexes(BattleHex sourceHex,
 	{
 		BattleHex hex(i);
 		if(hex.isAvailable() && BattleHex::getDistance(sourceHex, hex) <= distance)
-			rangeHexes.push_back(hex);
+			rangeHexes.insert(hex);
 	}
 
 	return rangeHexes;
 }
 
-std::vector<BattleHex> BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, std::vector<BattleHex> rangeHexes, uint8_t distanceToLimit)
+BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, const BattleHexArray & rangeHexes, uint8_t distanceToLimit)
 {
-	std::vector<BattleHex> rangeLimitHexes;
+	BattleHexArray rangeLimitHexes;
 
 	// from range hexes get only the ones at the limit
 	for(auto & hex : rangeHexes)
 	{
 		if(BattleHex::getDistance(hoveredHex, hex) == distanceToLimit)
-			rangeLimitHexes.push_back(hex);
+			rangeLimitHexes.insert(hex);
 	}
 
 	return rangeLimitHexes;
 }
 
-bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit)
+bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, const BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit)
 {
 	bool  hexInRangeLimit = false;
 
@@ -470,18 +470,19 @@ bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector<BattleH
 	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(
+	const BattleHexArray & wholeRangeHexes, const BattleHexArray & rangeLimitHexes)
 {
 	std::vector<std::vector<BattleHex::EDir>> output;
 
 	if(wholeRangeHexes.empty())
 		return output;
 
-	for(auto & hex : rangeLimitHexes)
+	for(auto hex : rangeLimitHexes)
 	{
 		// get all neighbours and their directions
 		
-		auto neighbouringTiles = hex.allNeighbouringTiles();
+		const BattleHexArray & neighbouringTiles = hex.getAllNeighbouringTiles();
 
 		std::vector<BattleHex::EDir> outsideNeighbourDirections;
 
@@ -491,9 +492,7 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
 			if(!neighbouringTiles[direction].isAvailable())
 				continue;
 
-			auto it = std::find(wholeRangeHexes.begin(), wholeRangeHexes.end(), neighbouringTiles[direction]);
-
-			if(it == wholeRangeHexes.end())
+			if(!wholeRangeHexes.contains(neighbouringTiles[direction]))
 				outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction
 		}
 
@@ -525,9 +524,9 @@ std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangeLimitH
 	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);
 		std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
 		rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
@@ -535,18 +534,18 @@ void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distan
 
 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>> 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();
-	std::set<BattleHex> hoveredMouseHex = hoveredHex.isValid() ? std::set<BattleHex>({ hoveredHex }) : std::set<BattleHex>();
+	BattleHexArray hoveredMouseHex = hoveredHex.isValid() ? BattleHexArray({ hoveredHex }) : BattleHexArray();
 
 	const CStack * hoveredStack = getHoveredStack();
 	if(!hoveredStack && hoveredHex == BattleHex::INVALID)
@@ -573,8 +572,8 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 
 	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
 		int hexIndexInRangedFullDamageLimit = 0;
@@ -679,7 +678,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos)
 BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
 {
 	const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
-	auto neighbours = myNumber.allNeighbouringTiles();
+	const BattleHexArray & neighbours = myNumber.getAllNeighbouringTiles();
 	//   0 1
 	//  5 x 2
 	//   4 3
@@ -696,18 +695,18 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 
 		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})
-			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
 	{
 		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[7] = false;
@@ -819,6 +818,9 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 {
+	if(!number.isValid())
+		return false;
+
 	for (auto & elem : occupiableHexes)
 	{
 		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
@@ -837,7 +839,7 @@ void BattleFieldController::updateAccessibleHexes()
 
 bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const
 {
-	return stackCountOutsideHexes[number];
+	return stackCountOutsideHexes[number.toInt()];
 }
 
 void BattleFieldController::showAll(Canvas & to)

+ 12 - 12
client/battle/BattleFieldController.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../../lib/battle/BattleHex.h"
+#include "../../lib/battle/BattleHexArray.h"
 #include "../../lib/Point.h"
 #include "../gui/CIntObject.h"
 
@@ -50,39 +50,39 @@ class BattleFieldController : public CIntObject
 	BattleHex hoveredHex;
 
 	/// hexes to which currently active stack can move
-	std::vector<BattleHex> occupiableHexes;
+	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);
 
-	std::set<BattleHex> getHighlightedHexesForActiveStack();
-	std::set<BattleHex> getMovementRangeForHoveredStack();
-	std::set<BattleHex> getHighlightedHexesForSpellRange();
-	std::set<BattleHex> getHighlightedHexesForMovementTarget();
+	BattleHexArray getHighlightedHexesForActiveStack();
+	BattleHexArray getMovementRangeForHoveredStack();
+	BattleHexArray getHighlightedHexesForSpellRange();
+	BattleHexArray getHighlightedHexesForMovementTarget();
 
 	// Range limit highlight helpers
 
 	/// get all hexes within a certain distance of given hex
-	std::vector<BattleHex> getRangeHexes(BattleHex sourceHex, uint8_t distance);
+	BattleHexArray 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);
+	BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, const BattleHexArray & 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);
+	bool IsHexInRangeLimit(BattleHex hex, const 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(std::vector<BattleHex> rangeHexes, std::vector<BattleHex> rangeLimitHexes);
+	std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(const BattleHexArray & rangeHexes, const BattleHexArray & rangeLimitHexes);
 
-	/// calculates what image to use as range limit, depending on the direction of neighbors
+	/// 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, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights);
+	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);

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -216,7 +216,7 @@ void BattleInterface::stackActivated(const CStack *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)
 		stacksController->stackTeleported(stack, destHex, distance);

+ 2 - 2
client/battle/BattleInterface.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "BattleConstants.h"
+#include "../lib/battle/BattleHex.h"
 #include "../gui/CIntObject.h"
 #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
 #include "../ConditionalWait.h"
@@ -27,7 +28,6 @@ class BattleAction;
 class CGTownInstance;
 struct CatapultAttack;
 struct BattleTriggerEffect;
-struct BattleHex;
 struct InfoAboutHero;
 class ObstacleChanges;
 class CPlayerBattleCallback;
@@ -202,7 +202,7 @@ public:
 	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 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();

+ 1 - 1
client/battle/BattleObstacleController.h

@@ -13,7 +13,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-struct BattleHex;
+class BattleHex;
 struct CObstacleInstance;
 class JsonNode;
 class ObstacleChanges;

+ 3 - 3
client/battle/BattleSiegeController.cpp

@@ -185,7 +185,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 
 const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) const
 {
-	switch (position)
+	switch (position.toInt())
 	{
 		case BattleHex::CASTLE_CENTRAL_TOWER:
 			return town->fortificationsLevel().citadelShooter.toCreature();
@@ -195,14 +195,14 @@ const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) co
 			return town->fortificationsLevel().lowerTowerShooter.toCreature();
 	}
 
-	throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex));
+	throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.toInt()));
 }
 
 Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const
 {
 	// Turret positions are read out of the config/wall_pos.txt
 	int posID = 0;
-	switch (position)
+	switch (position.toInt())
 	{
 	case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature
 		posID = EWallVisual::CREATURE_KEEP;

+ 2 - 2
client/battle/BattleStacksController.cpp

@@ -491,7 +491,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 	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);
 	//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
 }
 
-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);
 	owner.checkForAnimations();

+ 4 - 3
client/battle/BattleStacksController.h

@@ -13,7 +13,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-struct BattleHex;
+class BattleHex;
+class BattleHexArray;
 class BattleAction;
 class CStack;
 class CSpell;
@@ -109,8 +110,8 @@ public:
 	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 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
 

+ 6 - 5
include/vstd/RNG.h

@@ -88,14 +88,15 @@ namespace RandomGeneratorUtil
 		return container.size() - 1;
 	}
 
-	template<typename T>
-	void randomShuffle(std::vector<T> & container, vstd::RNG & rand)
+	template<typename Container>
+	void randomShuffle(Container & container, vstd::RNG & rand)
 	{
-		int64_t n = (container.end() - container.begin());
+		int64_t n = std::distance(container.begin(), container.end());
 
-		for(int64_t i = n-1; i>0; --i)
+		for(int64_t i = n - 1; i > 0; --i)
 		{
-			std::swap(container.begin()[i],container.begin()[rand.nextInt64(0, i)]);
+			auto randIndex = rand.nextInt64(0, i);
+			std::swap(*(container.begin() + i), *(container.begin() + randIndex));
 		}
 	}
 }

+ 1 - 1
lib/BattleFieldHandler.cpp

@@ -38,7 +38,7 @@ std::shared_ptr<BattleFieldInfo> BattleFieldHandler::loadFromJson(const std::str
 
 	info->isSpecial = json["isSpecial"].Bool();
 	for(auto node : json["impassableHexes"].Vector())
-		info->impassableHexes.emplace_back(node.Integer());
+		info->impassableHexes.insert(node.Integer());
 
 	info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]);
 	info->musicFilename = AudioPath::fromJson(json["music"]);

+ 2 - 2
lib/BattleFieldHandler.h

@@ -14,7 +14,7 @@
 #include "bonuses/Bonus.h"
 #include "GameConstants.h"
 #include "IHandlerBase.h"
-#include "battle/BattleHex.h"
+#include "battle/BattleHexArray.h"
 #include "filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -31,7 +31,7 @@ public:
 	std::string identifier;
 	std::string icon;
 	si32 iconIndex;
-	std::vector<BattleHex> impassableHexes;
+	BattleHexArray impassableHexes;
 	AudioPath openingSoundFilename;
 	AudioPath musicFilename;
 

+ 1 - 1
lib/CGameInterface.cpp

@@ -204,7 +204,7 @@ void CAdventureAI::battleObstaclesChanged(const BattleID & battleID, const std::
 	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, const BattleHexArray & dest, int distance, bool 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 battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) 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, const BattleHexArray & dest, int distance, bool teleport) override;
 	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override;
 	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) 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/BattleAttackInfo.cpp
 	battle/BattleHex.cpp
+	battle/BattleHexArray.cpp
 	battle/BattleInfo.cpp
 	battle/BattleLayout.cpp
 	battle/BattleProxy.cpp
@@ -413,6 +414,7 @@ set(lib_MAIN_HEADERS
 	battle/BattleAction.h
 	battle/BattleAttackInfo.h
 	battle/BattleHex.h
+	battle/BattleHexArray.h
 	battle/BattleInfo.h
 	battle/BattleLayout.h
 	battle/BattleSide.h

+ 8 - 8
lib/CStack.cpp

@@ -242,25 +242,25 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 	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;
-	std::vector<BattleHex> res;
+	BattleHexArray res;
 
 	if (!attackerPos.isValid())
 		attackerPos = attacker->getPosition();
 	if (!defenderPos.isValid())
 		defenderPos = defender->getPosition();
 
-	BattleHex otherAttackerPos = attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1);
-	BattleHex otherDefenderPos = defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1);
+	BattleHex otherAttackerPos = attackerPos.toInt() + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1);
+	BattleHex otherDefenderPos = defenderPos.toInt() + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1);
 
 	if(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front
 	{
 		if((mask & 1) == 0)
 		{
 			mask |= 1;
-			res.push_back(defenderPos);
+			res.insert(defenderPos);
 		}
 	}
 	if (attacker->doubleWide() //back <=> front
@@ -269,7 +269,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 1) == 0)
 		{
 			mask |= 1;
-			res.push_back(defenderPos);
+			res.insert(defenderPos);
 		}
 	}
 	if (defender->doubleWide()//front <=> back
@@ -278,7 +278,7 @@ std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, c
 		if((mask & 2) == 0)
 		{
 			mask |= 2;
-			res.push_back(otherDefenderPos);
+			res.insert(otherDefenderPos);
 		}
 	}
 	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)
 		{
 			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
 	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);
 
 	BattleHex::EDir destShiftDir() const;
@@ -146,4 +146,4 @@ private:
 	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
 
 #include "networkPacks/EInfoWindowMode.h"
-#include "battle/BattleHex.h"
+#include "battle/BattleHexArray.h"
 #include "GameConstants.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 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 battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport){};
+	virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport){};
 	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 battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte){}; //called for various one-shot effects

+ 5 - 6
lib/ObstacleHandler.cpp

@@ -55,26 +55,25 @@ Obstacle ObstacleInfo::getId() const
 	return obstacle;
 }
 
-std::vector<BattleHex> ObstacleInfo::getBlocked(BattleHex hex) const
+BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const
 {
-	std::vector<BattleHex> ret;
 	if(isAbsoluteObstacle)
 	{
 		assert(!hex.isValid());
-		range::copy(blockedTiles, std::back_inserter(ret));
-		return ret;
+		return BattleHexArray(blockedTiles);
 	}
 	
+	BattleHexArray ret;
 	for(int offset : blockedTiles)
 	{
-		BattleHex toBlock = hex + offset;
+		BattleHex toBlock = hex.toInt() + offset;
 		if((hex.getY() & 1) && !(toBlock.getY() & 1))
 			toBlock += BattleHex::LEFT;
 		
 		if(!toBlock.isValid())
 			logGlobal->error("Misplaced obstacle!");
 		else
-			ret.push_back(toBlock);
+			ret.insert(toBlock);
 	}
 	
 	return ret;

+ 2 - 2
lib/ObstacleHandler.h

@@ -13,7 +13,7 @@
 #include <vcmi/Entity.h>
 #include "GameConstants.h"
 #include "IHandlerBase.h"
-#include "battle/BattleHex.h"
+#include "battle/BattleHexArray.h"
 #include "filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -54,7 +54,7 @@ public:
 	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'
+	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;
 };

+ 4 - 3
lib/battle/AccessibilityInfo.cpp

@@ -18,13 +18,14 @@ VCMI_LIB_NAMESPACE_BEGIN
 bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const
 {
 	//at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER)
-	auto accessibility = at(tile);
+	const auto & accessibility = at(tile.toInt());
 
 	if(accessibility == EAccessibility::ALIVE_STACK)
 	{
-		auto destructible = destructibleEnemyTurns.find(tile);
+		if(!destructibleEnemyTurns)
+			return false;
 
-		return destructible != destructibleEnemyTurns.end();
+		return destructibleEnemyTurns->at(tile.toInt()) >= 0;
 	}
 
 	if(accessibility != EAccessibility::ACCESSIBLE)

+ 3 - 2
lib/battle/AccessibilityInfo.h

@@ -27,15 +27,16 @@ enum class EAccessibility
 	DESTRUCTIBLE_WALL,
 	GATE, //sieges -> gate opens only for defender stacks
 	UNAVAILABLE, //indestructible wall parts, special battlefields (like boat-to-boat)
-	SIDE_COLUMN //used for first and last columns of hexes that are unavailable but wat machines can stand there
+	SIDE_COLUMN //used for first and last columns of hexes that are unavailable but war machines can stand there
 };
 
 
 using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZE>;
+using TBattlefieldTurnsArray = std::array<int8_t, GameConstants::BFIELD_SIZE>;
 
 struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
 {
-	std::map<BattleHex, ui8> destructibleEnemyTurns;
+	std::shared_ptr<const TBattlefieldTurnsArray> destructibleEnemyTurns; //used only as a view for destructibleEnemyTurns from ReachabilityInfo::Parameters
 
 	public:
 		bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide

+ 33 - 207
lib/battle/BattleHex.cpp

@@ -9,237 +9,63 @@
  */
 #include "StdInc.h"
 #include "BattleHex.h"
+#include "BattleHexArray.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
+BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes)
 {
-	return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
-}
-
-bool BattleHex::isAvailable() const
-{
-	return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH-1;
-}
+	if(hexes.empty())
+		return BattleHex();
 
-void BattleHex::setX(si16 x)
-{
-	setXY(x, getY());
-}
-
-void BattleHex::setY(si16 y)
-{
-	setXY(getX(), y);
-}
+	BattleHex initialHex = BattleHex(initialPos);
+	int closestDistance = std::numeric_limits<int>::max();
+	BattleHexArray closestTiles;
 
-void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
-{
-	if(hasToBeValid)
+	for(auto hex : hexes)
 	{
-		if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT)
-			throw std::runtime_error("Valid hex required");
+		int distance = initialHex.getDistance(initialHex, hex);
+		if(distance < closestDistance)
+		{
+			closestDistance = distance;
+			closestTiles.clear();
+			closestTiles.insert(hex);
+		}
+		else if(distance == closestDistance)
+			closestTiles.insert(hex);
 	}
 
-	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)
+	auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right)
 	{
-	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;
-}
+		if(left.getX() != right.getX())
+		{
+			return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX());
+		}
+		return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
+	};
 
-BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2)
-{
-	for(auto dir : hexagonalDirections())
-		if(hex2 == hex1.cloneInDirection(dir, false))
-			return dir;
-	return NONE;
+	auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal);
+	return (bestTile != closestTiles.end()) ? *bestTile : BattleHex();
 }
 
-uint8_t BattleHex::getDistance(BattleHex hex1, BattleHex hex2)
+const BattleHexArray & BattleHex::getAllNeighbouringTiles() const noexcept
 {
-	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);
+	return BattleHexArray::getAllNeighbouringTiles(*this);
 }
 
-void BattleHex::checkAndPush(BattleHex tile, std::vector<BattleHex> & ret)
+const BattleHexArray & BattleHex::getNeighbouringTiles() const noexcept
 {
-	if(tile.isAvailable())
-		ret.push_back(tile);
+	return BattleHexArray::getNeighbouringTiles(*this);
 }
 
-BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, std::set<BattleHex> & possibilities)
+const BattleHexArray & BattleHex::getNeighbouringTilesDoubleWide(BattleSide side) const noexcept
 {
-	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();
+	return BattleHexArray::getNeighbouringTilesDoubleWide(*this, side);
 }
 
 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);
+	return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.toInt());
 }
 
-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

+ 204 - 42
lib/battle/BattleHex.h

@@ -22,9 +22,14 @@ namespace GameConstants
 	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
+class BattleHexArray;
+
+// for battle stacks' positions; valid hexes are from 0 to 186; available are only those not in first and last column
+// castle towers are -2, -3 and -4
+class DLL_LINKAGE BattleHex
 {
+public:
+
 	// helpers for siege
 	static constexpr si16 CASTLE_CENTRAL_TOWER = -2;
 	static constexpr si16 CASTLE_BOTTOM_TOWER = -3;
@@ -46,8 +51,8 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 	static constexpr si16 GATE_OUTER = 95;
 	static constexpr si16 GATE_INNER = 96;
 
-	si16 hex;
 	static constexpr si16 INVALID = -1;
+
 	enum EDir
 	{
 		NONE = -1,
@@ -64,52 +69,209 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 		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
+	BattleHex() noexcept
+		: hex(INVALID) 
+	{}
+	BattleHex(si16 _hex) noexcept
+		: hex(_hex) 
+	{}
+	BattleHex(si16 x, si16 y)
+	{
+		setXY(x, y);
+	}
+	BattleHex(std::pair<si16, si16> xy)
+	{
+		setXY(xy);
+	}
 
-	template <typename Handler>
-	void serialize(Handler &h)
+	[[nodiscard]] bool isValid() const noexcept
 	{
-		h & hex;
+		return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
+	}
+	
+	[[nodiscard]] bool isAvailable() const noexcept //valid position not in first or last column
+	{
+		return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1;
 	}
 
-    using NeighbouringTiles = std::array<BattleHex, 6>;
-    using NeighbouringTilesCache = std::vector<NeighbouringTiles>;
+	void setX(si16 x)
+	{
+		setXY(x, getY());
+	}
+
+	void setY(si16 y)
+	{
+		setXY(getX(), y);
+	}
+
+	void setXY(si16 x, si16 y, bool hasToBeValid = true)
+	{
+		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 setXY(std::pair<si16, si16> xy)
+	{
+		setXY(xy.first, xy.second);
+	}
+
+	[[nodiscard]] si16 getX() const noexcept
+	{
+		return hex % GameConstants::BFIELD_WIDTH;
+	}
+
+	[[nodiscard]] si16 getY() const noexcept
+	{
+		return hex / GameConstants::BFIELD_WIDTH;
+	}
+
+	[[nodiscard]] std::pair<si16, si16> getXY() const noexcept
+	{
+		return std::make_pair(getX(), getY());
+	}
+
+	BattleHex & moveInDirection(EDir dir, bool hasToBeValid = true)
+	{
+		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;
+	}
+
+	[[nodiscard]] BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const
+	{
+		BattleHex result(hex);
+		result.moveInDirection(dir, hasToBeValid);
+		return result;
+	}
+
+	[[nodiscard]] static uint8_t getDistance(BattleHex hex1, BattleHex hex2) noexcept
+	{
+		int y1 = hex1.getY();
+		int y2 = hex2.getY();
+
+		int x1 = hex1.getX() + y1 / 2;
+		int x2 = 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);
+	}
+
+	[[nodiscard]] static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes);
 
-    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};
+	[[nodiscard]] static constexpr auto hexagonalDirections() noexcept
+	{
+		return std::array<EDir,6>{TOP_LEFT, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT};
+	}
+
+	[[nodiscard]] static EDir mutualPosition(BattleHex hex1, BattleHex hex2)
+	{
+		for(auto dir : hexagonalDirections())
+			if(hex2 == hex1.cloneInDirection(dir, false))
+				return dir;
+		return NONE;
+	}
+
+	/// get (precomputed) all possible surrounding tiles
+	[[nodiscard]] const BattleHexArray & getAllNeighbouringTiles() const noexcept;
+
+	/// get (precomputed) only valid and available surrounding tiles
+	[[nodiscard]] const BattleHexArray & getNeighbouringTiles() const noexcept;
+
+	/// get (precomputed) only valid and available surrounding tiles for double wide creatures
+	[[nodiscard]] const BattleHexArray & getNeighbouringTilesDoubleWide(BattleSide side) const noexcept;
+
+	/// get integer hex value
+	[[nodiscard]] si16 toInt() const noexcept
+	{
+		return hex;
+	}
+
+	BattleHex & operator+=(EDir dir)
+	{
+		return moveInDirection(dir);
+	}
+
+	[[nodiscard]] BattleHex operator+(EDir dir) const
+	{
+		return cloneInDirection(dir);
+	}
+
+	// Prefix increment
+	BattleHex & operator++() noexcept
+	{
+		++hex;
+		return *this;
+	}
+
+	// Postfix increment
+	BattleHex operator++(int) noexcept
+	{
+		BattleHex temp = *this;
+		++hex;
+		return temp;
+	}
+
+	[[nodiscard]] bool operator ==(BattleHex other) const noexcept
+	{
+		return hex == other.hex;
+	}
+
+	[[nodiscard]] bool operator !=(BattleHex other) const noexcept
+	{
+		return hex != other.hex;
+	}
+
+	[[nodiscard]] bool operator <(BattleHex other) const noexcept
+	{
+		return hex < other.hex;
 	}
+
+	template <typename Handler>
+	void serialize(Handler & h)
+	{
+		h & hex;
+	}
+
+private:
+
+	si16 hex;
 };
 
 DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);

+ 132 - 0
lib/battle/BattleHexArray.cpp

@@ -0,0 +1,132 @@
+/*
+ * 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);
+	}
+}
+
+void BattleHexArray::insert(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->toInt()] = 0;
+	}
+
+	internalStorage.erase(first, last);
+}
+
+void BattleHexArray::clear() noexcept
+{
+	for(auto hex : internalStorage)
+		presenceFlags[hex.toInt()] = 0;
+
+	internalStorage.clear();
+}
+
+BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTiles()
+{
+	BattleHexArray::ArrayOfBattleHexArrays ret;
+
+	for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
+	{
+		BattleHexArray hexes;
+
+		for(auto dir : BattleHex::hexagonalDirections())
+			hexes.checkAndPush(BattleHex(hex).cloneInDirection(dir, false));
+
+		size_t index = 0;
+		ret[hex].resize(hexes.size());
+		for(auto neighbour : hexes)
+			ret[hex].set(index++, neighbour);
+	}
+
+	return ret;
+}
+
+BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateAllNeighbouringTiles()
+{
+	ArrayOfBattleHexArrays ret;
+
+	for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
+	{
+		ret[hex].resize(6);
+
+		for(auto dir : BattleHex::hexagonalDirections())
+			ret[hex].set(dir, BattleHex(hex).cloneInDirection(dir, false));
+	}
+
+	return ret;
+}
+
+BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDoubleWide(BattleSide side)
+{
+	ArrayOfBattleHexArrays ret;
+
+	for(si16 h = 0; h < GameConstants::BFIELD_SIZE; h++)
+	{
+		BattleHexArray hexes;
+		BattleHex hex(h);
+
+		if(side == BattleSide::ATTACKER)
+		{
+			const BattleHex otherHex = h - 1;
+
+			for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
+				hexes.checkAndPush(hex.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 if(side == BattleSide::DEFENDER)
+		{
+			const BattleHex otherHex = h + 1;
+
+			hexes.checkAndPush(hex.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(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
+			hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false));
+		}
+		ret[h] = std::move(hexes);
+	}
+
+	return ret;
+}
+
+const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTiles = precalculateNeighbouringTiles();
+const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::allNeighbouringTiles = precalculateAllNeighbouringTiles();
+const std::map<BattleSide, BattleHexArray::ArrayOfBattleHexArrays> BattleHexArray::neighbouringTilesDoubleWide =
+	{
+	   { BattleSide::ATTACKER, precalculateNeighbouringTilesDoubleWide(BattleSide::ATTACKER) },
+	   { BattleSide::DEFENDER, precalculateNeighbouringTilesDoubleWide(BattleSide::DEFENDER) }
+	};
+
+VCMI_LIB_NAMESPACE_END

+ 317 - 0
lib/battle/BattleHexArray.h

@@ -0,0 +1,317 @@
+/*
+ * 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"
+#include <boost/container/small_vector.hpp>
+
+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 = boost::container::small_vector<BattleHex, 8>;
+	using ArrayOfBattleHexArrays = std::array<BattleHexArray, totalSize>;
+
+	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;
+
+	BattleHexArray() = default;
+
+	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;
+
+	void checkAndPush(BattleHex tile)
+	{
+		if(tile.isAvailable() && !contains(tile))
+		{
+			presenceFlags[tile.toInt()] = true;
+			internalStorage.emplace_back(tile);
+		}
+	}
+
+	void insert(BattleHex hex) noexcept
+	{
+		if(contains(hex))
+			return;
+
+		presenceFlags[hex.toInt()] = true;
+		internalStorage.emplace_back(hex);
+	}
+
+	void set(size_type index, BattleHex hex)
+	{
+		if(index >= internalStorage.size())
+		{
+			logGlobal->error("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index)
+				+ " and current size is " + std::to_string(internalStorage.size()));
+			throw std::out_of_range("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index)
+				+ " and current size is " + std::to_string(internalStorage.size()));
+		}
+
+		if(contains(hex))
+			return;
+
+		presenceFlags[hex.toInt()] = true;
+		internalStorage[index] = hex;
+	}
+
+	iterator insert(iterator pos, BattleHex hex) noexcept
+	{
+		if(contains(hex))
+			return pos;
+
+		presenceFlags[hex.toInt()] = true;
+		return internalStorage.insert(pos, hex);
+	}
+
+	void insert(const BattleHexArray & other) noexcept;
+
+	template <typename Container, typename = std::enable_if_t<
+		std::is_convertible_v<typename Container::value_type, BattleHex>>>
+		void insert(const Container & container) noexcept
+	{
+		for(auto value : container)
+		{
+			insert(value);
+		}
+	}
+
+	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().toInt()] = false;
+		internalStorage.pop_back();
+	}
+
+	inline std::vector<BattleHex> toVector() const noexcept
+	{
+		return std::vector<BattleHex>(internalStorage.begin(), internalStorage.end());
+	}
+
+	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;
+	}
+
+	/// get (precomputed) all possible surrounding tiles
+	static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept
+	{
+		assert(hex.isValid());
+
+		return allNeighbouringTiles[hex.toInt()];
+	}
+
+	/// get (precomputed) only valid and available surrounding tiles
+	static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept
+	{
+		assert(hex.isValid());
+
+		return neighbouringTiles[hex.toInt()];
+	}
+
+	/// get (precomputed) only valid and available surrounding tiles for double wide creatures
+	static const BattleHexArray & getNeighbouringTilesDoubleWide(BattleHex hex, BattleSide side) noexcept
+	{
+		assert(hex.isValid() && (side == BattleSide::ATTACKER || side == BattleSide::DEFENDER));
+
+		return neighbouringTilesDoubleWide.at(side)[hex.toInt()];
+	}
+
+	[[nodiscard]] inline bool contains(BattleHex hex) const noexcept
+	{
+		if(hex.isValid())
+			return presenceFlags[hex.toInt()];
+		/*
+		if(!isTower(hex))
+			logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex);
+		*/
+		
+		// returns true also for invalid hexes
+		return true;
+	}
+
+	template <typename Serializer>
+	void serialize(Serializer & s)
+	{
+		s & internalStorage;
+		if(!s.saving)
+		{
+			for(auto hex : internalStorage)
+				presenceFlags[hex.toInt()] = true;
+		}
+	}
+
+	[[nodiscard]] inline const BattleHex & back() const noexcept
+	{
+		return internalStorage.back();
+	}
+
+	[[nodiscard]] inline const BattleHex & front() const noexcept
+	{
+		return internalStorage.front();
+	}
+
+	[[nodiscard]] inline const BattleHex & operator[](size_type index) const noexcept
+	{
+		return internalStorage[index];
+	}
+
+	[[nodiscard]] inline const BattleHex & at(size_type index) const
+	{
+		return internalStorage.at(index);
+	}
+
+	[[nodiscard]] inline size_type size() const noexcept
+	{
+		return internalStorage.size();
+	}
+
+	[[nodiscard]] inline iterator begin() noexcept
+	{
+		return internalStorage.begin();
+	}
+
+	[[nodiscard]] inline const_iterator begin() const noexcept
+	{
+		return internalStorage.begin();
+	}
+
+	[[nodiscard]] inline bool empty() const noexcept
+	{
+		return internalStorage.empty();
+	}
+
+	[[nodiscard]] inline iterator end() noexcept
+	{
+		return internalStorage.end();
+	}
+
+	[[nodiscard]] inline const_iterator end() const noexcept
+	{
+		return internalStorage.end();
+	}
+
+	[[nodiscard]] inline reverse_iterator rbegin() noexcept
+	{
+		return reverse_iterator(end());
+	}
+
+	[[nodiscard]] inline const_reverse_iterator rbegin() const noexcept
+	{
+		return const_reverse_iterator(end());
+	}
+
+	[[nodiscard]] inline reverse_iterator rend() noexcept
+	{
+		return reverse_iterator(begin());
+	}
+
+	[[nodiscard]] inline const_reverse_iterator rend() const noexcept
+	{
+		return const_reverse_iterator(begin());
+	}
+
+	bool operator ==(const BattleHexArray & other) const noexcept
+	{
+		if(internalStorage != other.internalStorage || presenceFlags != other.presenceFlags)
+			return false;
+
+		return true;
+	}
+
+private:
+	StorageType internalStorage;
+	std::bitset<totalSize> presenceFlags;
+
+	[[nodiscard]] inline bool 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;
+	}
+
+	static const ArrayOfBattleHexArrays neighbouringTiles;
+	static const ArrayOfBattleHexArrays allNeighbouringTiles;
+	static const std::map<BattleSide, ArrayOfBattleHexArrays> neighbouringTilesDoubleWide;
+
+	static ArrayOfBattleHexArrays precalculateNeighbouringTiles();
+	static ArrayOfBattleHexArrays precalculateAllNeighbouringTiles();
+	static ArrayOfBattleHexArrays precalculateNeighbouringTilesDoubleWide(BattleSide side);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 8 - 8
lib/battle/BattleInfo.cpp

@@ -46,7 +46,7 @@ CStack * BattleInfo::generateNewStack(uint32_t id, const CStackInstance & base,
 	assert(!owner.isValidPlayer() || (base.armyObj && base.armyObj->tempOwner == owner));
 
 	auto * ret = new CStack(&base, owner, id, side, slot);
-	ret->initialPosition = getAvailableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
+	ret->initialPosition = getAvailableHex(base.getCreatureID(), side, position.toInt()); //TODO: what if no free tile on battlefield was found?
 	stacks.push_back(ret);
 	return ret;
 }
@@ -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
 		int tilesToBlock = r.rand(5,12);
 
-		std::vector<BattleHex> blockedTiles;
+		BattleHexArray blockedTiles;
 
 		auto appropriateAbsoluteObstacle = [&](int id)
 		{
@@ -232,7 +232,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 				currentBattle->obstacles.push_back(obstPtr);
 
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
-					blockedTiles.push_back(blocked);
+					blockedTiles.insert(blocked);
 				tilesToBlock -= Obstacle(obstPtr->ID).getInfo()->blockedTiles.size() / 2;
 			}
 			catch(RangeGenerator::ExhaustedPossibilities &)
@@ -259,14 +259,14 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 						return false;
 					if(pos.getX() + obi.width > 15)
 						return false;
-					if(vstd::contains(blockedTiles, pos))
+					if(blockedTiles.contains(pos))
 						return false;
 
 					for(BattleHex blocked : obi.getBlocked(pos))
 					{
-						if(tileAccessibility[blocked] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles
+						if(tileAccessibility[blocked.toInt()] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles
 							return false;
-						if(vstd::contains(blockedTiles, blocked))
+						if(blockedTiles.contains(blocked))
 							return false;
 						int x = blocked.getX();
 						if(x <= 2 || x >= 14)
@@ -285,7 +285,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 				currentBattle->obstacles.push_back(obstPtr);
 
 				for(BattleHex blocked : obstPtr->getBlockedTiles())
-					blockedTiles.push_back(blocked);
+					blockedTiles.insert(blocked);
 				tilesToBlock -= static_cast<int>(obi.blockedTiles.size());
 			}
 		}
@@ -709,7 +709,7 @@ void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t health
 
 		if(!accessibility.accessible(changedStack->getPosition(), changedStack))
 		{
-			logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition().hex);
+			logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition());
 			return; //position is already occupied
 		}
 	}

+ 103 - 116
lib/battle/CBattleInfoCallback.cpp

@@ -43,19 +43,15 @@ static BattleHex lineToWallHex(int line) //returns hex with wall in given line (
 
 static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
 {
-	const int wallInStackLine = lineToWallHex(pos1.getY());
-	const int wallInDestLine = lineToWallHex(pos2.getY());
-
-	const bool stackLeft = pos1 < wallInStackLine;
-	const bool destLeft = pos2 < wallInDestLine;
+	const bool stackLeft = pos1 < lineToWallHex(pos1.getY());
+	const bool destLeft = pos2 < lineToWallHex(pos2.getY());
 
 	return stackLeft == destLeft;
 }
 
 static bool isInsideWalls(BattleHex pos)
 {
-	const int wallInStackLine = lineToWallHex(pos.getY());
-	return wallInStackLine < pos;
+	return lineToWallHex(pos.getY()) < pos;
 }
 
 // parts of wall
@@ -79,9 +75,10 @@ static const std::pair<int, EWallPart> wallParts[] =
 
 static EWallPart hexToWallPart(BattleHex hex)
 {
+	si16 hexValue = hex.toInt();
 	for(const auto & elem : wallParts)
 	{
-		if(elem.first == hex)
+		if(elem.first == hexValue)
 			return elem.second;
 	}
 
@@ -147,25 +144,25 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster *
 	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);
 
-	if(reachability.predecessors[dest] == -1) //cannot reach destination
+	if(reachability.predecessors[dest.toInt()] == -1) //cannot reach destination
 	{
-		return std::make_pair(std::vector<BattleHex>(), 0);
+		return std::make_pair(BattleHexArray(), 0);
 	}
 
 	//making the Path
-	std::vector<BattleHex> path;
+	BattleHexArray path;
 	BattleHex curElem = dest;
 	while(curElem != start)
 	{
-		path.push_back(curElem);
-		curElem = reachability.predecessors[curElem];
+		path.insert(curElem);
+		curElem = reachability.predecessors[curElem.toInt()];
 	}
 
-	return std::make_pair(path, reachability.distances[dest]);
+	return std::make_pair(path, reachability.distances[dest.toInt()]);
 }
 
 bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const
@@ -176,7 +173,7 @@ bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const
 bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const
 {
 	if (!from.isAvailable() || !dest.isAvailable())
-		throw std::runtime_error("Invalid hex (" + std::to_string(from.hex) + " and " + std::to_string(dest.hex) + ") received in battleHasPenaltyOnLine!" );
+		throw std::runtime_error("Invalid hex (" + std::to_string(from.toInt()) + " and " + std::to_string(dest.toInt()) + ") received in battleHasPenaltyOnLine!" );
 
 	auto isTileBlocked = [&](BattleHex tile)
 	{
@@ -191,23 +188,21 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
 		return isWallPartAttackable(wallPart);
 	};
 	// 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
 		if(from == dest)
 			return {};
 
-		std::vector<BattleHex> ret;
+		BattleHexArray ret;
 		auto next = from;
 		//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;
 
 		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 = BattleHex::getClosestTile(direction, dest, next.getNeighbouringTiles());
+			ret.insert(next);
 		}
 		assert(!ret.empty());
 		ret.pop_back(); //Remove destination hex
@@ -227,7 +222,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
 
 		auto obstacles = battleGetAllObstaclesOnPos(hex, false);
 
-		if(hex != BattleHex::GATE_BRIDGE || (battleIsGatePassable()))
+		if(hex.toInt() != BattleHex::GATE_BRIDGE || (battleIsGatePassable()))
 			for(const auto & obst : obstacles)
 				if(obst->obstacleType ==  CObstacleInstance::MOAT)
 					pathHasMoat |= true;
@@ -318,9 +313,9 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s
 	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);
 
 	AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
@@ -347,7 +342,7 @@ const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyA
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
 	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 nullptr;
@@ -569,21 +564,21 @@ void CBattleInfoCallback::battleGetTurnOrder(std::vector<battle::Units> & turns,
 		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
-		return std::vector<BattleHex>();
+		return BattleHexArray();
 
 	auto reachability = getReachability(unit);
 
 	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);
 	if(!unit->getPosition().isValid()) //turrets
@@ -612,28 +607,27 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 				continue;
 		}
 
-		ret.emplace_back(i);
+		ret.insert(i);
 	}
 
 	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())
 		return ret;
 
 	if(addOccupiable && unit->doubleWide())
 	{
-		std::vector<BattleHex> occupiable;
+		BattleHexArray occupiable;
 
-		occupiable.reserve(ret.size());
 		for(auto hex : ret)
-			occupiable.push_back(unit->occupiedHex(hex));
+			occupiable.insert(unit->occupiedHex(hex));
 
-		vstd::concatenate(ret, occupiable);
+		ret.insert(occupiable);
 	}
 
 
@@ -643,36 +637,33 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle
 		{
 			// Return true if given hex has at least one available neighbour.
 			// 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 availableNeighbor != ret.end();
+			return availableNeighbour != ret.end();
 		};
 		for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide())))
 		{
 			if(!otherSt->isValidTarget(false))
 				continue;
 
-			std::vector<BattleHex> occupied = otherSt->getHexes();
+			const BattleHexArray & occupied = otherSt->getHexes();
 
 			if(battleCanShoot(unit, otherSt->getPosition()))
 			{
-				attackable->insert(attackable->end(), occupied.begin(), occupied.end());
+				attackable->insert(occupied);
 				continue;
 			}
 
 			for(BattleHex he : occupied)
 			{
 				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;
 }
 
@@ -792,7 +783,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit *
 {
 	RETURN_IF_NOT_BATTLE({});
 	auto reachability = battleGetDistances(attacker, attacker->getPosition());
-	int movementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0;
+	int movementRange = attackerPosition.isValid() ? reachability[attackerPosition.toInt()] : 0;
 	return battleEstimateDamage(attacker, defender, movementRange, retaliationDmg);
 }
 
@@ -857,8 +848,8 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battl
 	RETURN_IF_NOT_BATTLE(obstacles);
 	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);
 		}
@@ -866,18 +857,18 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battl
 	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>>();
 	RETURN_IF_NOT_BATTLE(affectedObstacles);
 	if(unit->alive())
 	{
-		if(!passed.count(unit->getPosition()))
+		if(!passed.contains(unit->getPosition()))
 			affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false);
 		if(unit->doubleWide())
 		{
 			BattleHex otherHex = unit->occupiedHex();
-			if(otherHex.isValid() && !passed.count(otherHex))
+			if(otherHex.isValid() && !passed.contains(otherHex))
 				for(auto & i : battleGetAllObstaclesOnPos(otherHex, false))
 					if(!vstd::contains(affectedObstacles, i))
 						affectedObstacles.push_back(i);
@@ -891,7 +882,7 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl
 	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())
 		return false;
@@ -961,8 +952,8 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 	//removing accessibility for side columns of hexes
 	for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++)
 	{
-		ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN;
-		ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN;
+		ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y).toInt()] = EAccessibility::SIDE_COLUMN;
+		ret[BattleHex(0, y).toInt()] = EAccessibility::SIDE_COLUMN;
 	}
 
 	//special battlefields with logically unavailable tiles
@@ -970,10 +961,8 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 
 	if(bFieldType != BattleField::NONE)
 	{
-		std::vector<BattleHex> impassableHexes = bFieldType.getInfo()->impassableHexes;
-
-		for(auto hex : impassableHexes)
-			ret[hex] = EAccessibility::UNAVAILABLE;
+		for(auto hex : bFieldType.getInfo()->impassableHexes)
+			ret[hex.toInt()] = EAccessibility::UNAVAILABLE;
 	}
 
 	//gate -> should be before stacks
@@ -998,14 +987,14 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 	{
 		for(auto hex : unit->getHexes())
 			if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
-				ret[hex] = EAccessibility::ALIVE_STACK;
+				ret[hex.toInt()] = EAccessibility::ALIVE_STACK;
 	}
 
 	//obstacles
 	for(const auto &obst : battleGetAllObstacles())
 	{
 		for(auto hex : obst->getBlockedTiles())
-			ret[hex] = EAccessibility::OBSTACLE;
+			ret[hex.toInt()] = EAccessibility::OBSTACLE;
 	}
 
 	//walls
@@ -1028,7 +1017,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 		for(const auto & elem : lockedIfNotDestroyed)
 		{
 			if(battleGetWallState(elem.first) != EWallState::DESTROYED)
-				ret[elem.second] = EAccessibility::DESTRUCTIBLE_WALL;
+				ret[elem.second.toInt()] = EAccessibility::DESTRUCTIBLE_WALL;
 		}
 	}
 
@@ -1040,17 +1029,17 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * sta
 	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();
 	for(auto hex : accessibleHexes)
 		if(hex.isValid())
-			ret[hex] = EAccessibility::ACCESSIBLE;
+			ret[hex.toInt()] = EAccessibility::ACCESSIBLE;
 
 	return ret;
 }
 
-ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters & params) const
+ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessibility, const ReachabilityInfo::Parameters & params) const
 {
 	ReachabilityInfo ret;
 	ret.accessibility = accessibility;
@@ -1062,7 +1051,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 	if(!params.startPosition.isValid()) //if got call for arrow turrets
 		return ret;
 
-	const std::set<BattleHex> obstacles = getStoppers(params.perspective);
+	const BattleHexArray obstacles = getStoppers(params.perspective);
 	auto checkParams = params;
 	checkParams.ignoreKnownAccessible = true; //Ignore starting hexes obstacles
 
@@ -1070,7 +1059,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 
 	//first element
 	hexq.push(params.startPosition);
-	ret.distances[params.startPosition] = 0;
+	ret.distances[params.startPosition.toInt()] = 0;
 
 	std::array<bool, GameConstants::BFIELD_SIZE> accessibleCache{};
 	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
@@ -1085,32 +1074,29 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 		if(isInObstacle(curHex, obstacles, checkParams))
 			continue;
 
-		const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
+		const int costToNeighbour = ret.distances.at(curHex.toInt()) + 1;
 
-		for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
+		for(BattleHex neighbour : curHex.getNeighbouringTiles())
 		{
-			if(neighbour.isValid())
+			auto additionalCost = 0;
+
+			if(params.bypassEnemyStacks)
 			{
-				auto additionalCost = 0;
+				auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour.toInt());
 
-				if(params.bypassEnemyStacks)
+				if(enemyToBypass >= 0)
 				{
-					auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour);
-
-					if(enemyToBypass != params.destructibleEnemyTurns.end())
-					{
-						additionalCost = enemyToBypass->second;
-					}
+					additionalCost = enemyToBypass;
 				}
+			}
 
-				const int costFoundSoFar = ret.distances[neighbour.hex];
+			const int costFoundSoFar = ret.distances[neighbour.toInt()];
 
-				if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar)
-				{
-					hexq.push(neighbour);
-					ret.distances[neighbour.hex] = costToNeighbour + additionalCost;
-					ret.predecessors[neighbour.hex] = curHex;
-				}
+			if(accessibleCache[neighbour.toInt()] && costToNeighbour + additionalCost < costFoundSoFar)
+			{
+				hexq.push(neighbour);
+				ret.distances[neighbour.toInt()] = costToNeighbour + additionalCost;
+				ret.predecessors[neighbour.toInt()] = curHex;
 			}
 		}
 	}
@@ -1120,17 +1106,16 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 
 bool CBattleInfoCallback::isInObstacle(
 	BattleHex hex,
-	const std::set<BattleHex> & obstacles,
+	const BattleHexArray & obstacleHexes,
 	const ReachabilityInfo::Parameters & params) const
 {
-	auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side);
 
-	for(auto occupiedHex : occupiedHexes)
+	for(auto occupiedHex : battle::Unit::getHexes(hex, params.doubleWide, params.side))
 	{
-		if(params.ignoreKnownAccessible && vstd::contains(params.knownAccessible, occupiedHex))
+		if(params.ignoreKnownAccessible && params.knownAccessible->contains(occupiedHex))
 			continue;
 
-		if(vstd::contains(obstacles, occupiedHex))
+		if(obstacleHexes.contains(occupiedHex))
 		{
 			if(occupiedHex == BattleHex::GATE_BRIDGE)
 			{
@@ -1145,9 +1130,9 @@ bool CBattleInfoCallback::isInObstacle(
 	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);
 
 	for(auto &oi : battleGetAllObstacles(whichSidePerspective))
@@ -1155,7 +1140,7 @@ std::set<BattleHex> CBattleInfoCallback::getStoppers(BattleSide whichSidePerspec
 		if(!battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
 			continue;
 
-		for(const auto & hex : oi->getStoppingTile())
+		for(auto hex : oi->getStoppingTile())
 		{
 			if(hex == BattleHex::GATE_BRIDGE && oi->obstacleType == CObstacleInstance::MOAT)
 			{
@@ -1193,7 +1178,7 @@ std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack(
 		for(BattleHex hex : avHexes)
 			if(CStack::isMeleeAttackPossible(closest, st, hex))
 			{
-				DistStack hlp = {reachability.distances[hex], hex, st};
+				DistStack hlp = {reachability.distances[hex.toInt()], hex, st};
 				stackPairs.push_back(hlp);
 			}
 	}
@@ -1225,7 +1210,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS
 
 	auto accessibility = getAccessibility();
 
-	std::set<BattleHex> occupyable;
+	BattleHexArray occupyable;
 	for(int i = 0; i < accessibility.size(); i++)
 		if(accessibility.accessible(i, twoHex, side))
 			occupyable.insert(i);
@@ -1278,24 +1263,27 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const battle::Unit * unit)
 	return getReachability(params);
 }
 
-ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters &params) const
+ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters & params) const
 {
 	if(params.flying)
 		return getFlyingReachability(params);
 	else
 	{
-		auto accessibility = getAccessibility(params.knownAccessible);
+		auto accessibility = getAccessibility(* params.knownAccessible);
 
-		accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns;
+		accessibility.destructibleEnemyTurns = std::shared_ptr<const TBattlefieldTurnsArray>(
+			& params.destructibleEnemyTurns,
+			[](const TBattlefieldTurnsArray *) { }
+		);
 
 		return makeBFS(accessibility, params);
 	}
 }
 
-ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters &params) const
+ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters & params) const
 {
 	ReachabilityInfo ret;
-	ret.accessibility = getAccessibility(params.knownAccessible);
+	ret.accessibility = getAccessibility(* params.knownAccessible);
 
 	for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
 	{
@@ -1338,9 +1326,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
 	AttackableTiles at;
 	RETURN_IF_NOT_BATTLE(at);
 
-	BattleHex attackOriginHex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position
+	BattleHex attackOriginHex = (attackerPos.toInt() != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position
 	
-	defenderPos = (defenderPos != BattleHex::INVALID) ? defenderPos : defender->getPosition(); //real or hypothetical (cursor) position
+	defenderPos = (defenderPos.toInt() != BattleHex::INVALID) ? defenderPos : defender->getPosition(); //real or hypothetical (cursor) position
 	
 	bool reverse = isToReverse(attacker, defender, attackerPos, defenderPos);
 	if(reverse && attacker->doubleWide())
@@ -1349,11 +1337,11 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
 	}
 	if(attacker->hasBonusOfType(BonusType::ATTACKS_ALL_ADJACENT))
 	{
-		boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions));
+		at.hostileCreaturePositions.insert(attacker->getSurroundingHexes(attackerPos));
 	}
 	if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK))
 	{
-		std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
+		const BattleHexArray & hexes = attacker->getSurroundingHexes(attackerPos);
 		for(BattleHex tile : hexes)
 		{
 			if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile
@@ -1366,12 +1354,12 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
 	}
 	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH))
 	{
-		std::vector<BattleHex> hexes = destinationTile.neighbouringTiles();
-		for(int i = 0; i<hexes.size(); i++)
+		BattleHexArray hexes = destinationTile.getNeighbouringTiles();
+		for(int i = 0; i < hexes.size(); i++)
 		{
 			if(hexes.at(i) == attackOriginHex)
 			{
-				hexes.erase(hexes.begin() + i);
+				hexes.erase(i);
 				i = 0;
 			}
 		}
@@ -1439,11 +1427,10 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::
 	AttackableTiles 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) && !attackerPos.getNeighbouringTiles().contains(destinationTile))
 	{
-		std::vector<BattleHex> targetHexes = destinationTile.neighbouringTiles();
-		targetHexes.push_back(destinationTile);
-		boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions));
+		at.hostileCreaturePositions.insert(destinationTile.getNeighbouringTiles());
+		at.hostileCreaturePositions.insert(destinationTile);
 	}
 
 	return at;
@@ -1480,9 +1467,9 @@ std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits(
 
 		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;
-			if (vstd::contains(at.friendlyCreaturePositions, hex))
+			if (at.friendlyCreaturePositions.contains(hex))
 				return true;
 		}
 		return false;
@@ -1671,15 +1658,15 @@ bool CBattleInfoCallback::isWallPartAttackable(EWallPart wallPart) const
 	return false;
 }
 
-std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
+BattleHexArray CBattleInfoCallback::getAttackableBattleHexes() const
 {
-	std::vector<BattleHex> attackableBattleHexes;
+	BattleHexArray attackableBattleHexes;
 	RETURN_IF_NOT_BATTLE(attackableBattleHexes);
 
 	for(const auto & wallPartPair : wallParts)
 	{
 		if(isWallPartAttackable(wallPartPair.second))
-			attackableBattleHexes.emplace_back(wallPartPair.first);
+			attackableBattleHexes.insert(wallPartPair.first);
 	}
 
 	return attackableBattleHexes;

+ 14 - 14
lib/battle/CBattleInfoCallback.h

@@ -38,8 +38,8 @@ namespace spells
 
 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)
 	{
 		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::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
-	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;
 
@@ -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;
 
 	///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)
-	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
 	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 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 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
 	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
-	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 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;
 	AccessibilityInfo getAccessibility() const;
 	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;
 
 	BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack
 protected:
 	ReachabilityInfo getFlyingReachability(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

+ 19 - 11
lib/battle/CObstacleInstance.cpp

@@ -24,21 +24,21 @@ const ObstacleInfo & CObstacleInstance::getInfo() const
 	return *Obstacle(ID).getInfo();
 }
 
-std::vector<BattleHex> CObstacleInstance::getBlockedTiles() const
+BattleHexArray CObstacleInstance::getBlockedTiles() const
 {
 	if(blocksTiles())
 		return getAffectedTiles();
-	return std::vector<BattleHex>();
+	return BattleHexArray();
 }
 
-std::vector<BattleHex> CObstacleInstance::getStoppingTile() const
+BattleHexArray CObstacleInstance::getStoppingTile() const
 {
 	if(stopsMovement())
 		return getAffectedTiles();
-	return std::vector<BattleHex>();
+	return BattleHexArray();
 }
 
-std::vector<BattleHex> CObstacleInstance::getAffectedTiles() const
+BattleHexArray CObstacleInstance::getAffectedTiles() const
 {
 	switch(obstacleType)
 	{
@@ -47,7 +47,7 @@ std::vector<BattleHex> CObstacleInstance::getAffectedTiles() const
 		return getInfo().getBlocked(pos);
 	default:
 		assert(0);
-		return std::vector<BattleHex>();
+		return BattleHexArray();
 	}
 }
 
@@ -113,7 +113,9 @@ void CObstacleInstance::serializeJson(JsonSerializeFormat & handler)
 		animationYOffset -= 42;
 
 	//We need only a subset of obstacle info for correct render
-	handler.serializeInt("position", pos);
+	si16 posValue = pos.toInt();
+	handler.serializeInt("position", posValue);
+	pos = posValue;
 	handler.serializeInt("animationYOffset", animationYOffset);
 	handler.serializeBool("hidden", hidden);
 	handler.serializeBool("needAnimationOffsetFix", needAnimationOffsetFix);
@@ -188,7 +190,9 @@ void SpellCreatedObstacle::fromInfo(const ObstacleChanges & info)
 void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
 {
 	handler.serializeInt("spell", ID);
-	handler.serializeInt("position", pos);
+	si16 posValue = pos.toInt();
+	handler.serializeInt("position", posValue);
+	pos = posValue;
 
 	handler.serializeInt("turnsRemaining", turnsRemaining);
 	handler.serializeInt("casterSpellPower", casterSpellPower);
@@ -216,11 +220,15 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
 		customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER);
 
 		for(size_t index = 0; index < customSizeJson.size(); index++)
-			customSizeJson.serializeInt(index, customSize.at(index));
+		{
+			si16 hex = customSize.at(index).toInt();
+			customSizeJson.serializeInt(index, hex);
+			customSize.set(index, hex);
+		}
 	}
 }
 
-std::vector<BattleHex> SpellCreatedObstacle::getAffectedTiles() const
+BattleHexArray SpellCreatedObstacle::getAffectedTiles() const
 {
 	return customSize;
 }
@@ -258,4 +266,4 @@ int SpellCreatedObstacle::getAnimationYOffset(int imageHeight) const
 	return offset;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 6 - 6
lib/battle/CObstacleInstance.h

@@ -8,7 +8,7 @@
  *
  */
 #pragma once
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 
 #include "../constants/EntityIdentifiers.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)
 
-	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
 	//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 SpellID getTrigger() const;
 
-	virtual std::vector<BattleHex> getAffectedTiles() const;
+	virtual BattleHexArray getAffectedTiles() const;
 	virtual bool visibleForSide(BattleSide side, bool hasNativeStack) const; //0 attacker
 
 	virtual void battleTurnPassed(){};
@@ -97,11 +97,11 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
 
 	int animationYOffset;
 
-	std::vector<BattleHex> customSize;
+	BattleHexArray customSize;
 
 	SpellCreatedObstacle();
 
-	std::vector<BattleHex> getAffectedTiles() const override;
+	BattleHexArray getAffectedTiles() const override;
 	bool visibleForSide(BattleSide side, bool hasNativeStack) const override;
 
 	bool blocksTiles() const override;

+ 3 - 1
lib/battle/CUnitState.cpp

@@ -800,7 +800,9 @@ void CUnitState::serializeJson(JsonSerializeFormat & handler)
 
 	handler.serializeInt("cloneID", cloneID);
 
-	handler.serializeInt("position", position);
+	si16 posValue = position.toInt();
+	handler.serializeInt("position", posValue);
+	position = posValue;
 }
 
 void CUnitState::localInit(const IUnitEnvironment * env_)

+ 1 - 1
lib/battle/DamageCalculator.cpp

@@ -45,7 +45,7 @@ DamageRange DamageCalculator::getBaseDamageSingle() const
 		const auto * town = callback.battleGetDefendedTown();
 		assert(town);
 
-		switch(info.attacker->getPosition())
+		switch(info.attacker->getPosition().toInt())
 		{
 		case BattleHex::CASTLE_CENTRAL_TOWER:
 			return town->getKeepDamageRange();

+ 2 - 2
lib/battle/IBattleInfoCallback.h

@@ -11,7 +11,7 @@
 #pragma once
 
 #include "GameConstants.h"
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 
 #include <vcmi/Entity.h>
 
@@ -81,7 +81,7 @@ public:
 
 	//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;
+	virtual std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0;
 };
 
 

+ 9 - 10
lib/battle/ReachabilityInfo.cpp

@@ -21,7 +21,8 @@ ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex S
 	side(Stack->unitSide()),
 	flying(Stack->hasBonusOfType(BonusType::FLYING))
 {
-	knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side);
+	knownAccessible = & battle::Unit::getHexes(startPosition, doubleWide, side);
+	destructibleEnemyTurns.fill(-1);
 }
 
 ReachabilityInfo::ReachabilityInfo()
@@ -32,22 +33,22 @@ ReachabilityInfo::ReachabilityInfo()
 
 bool ReachabilityInfo::isReachable(BattleHex hex) const
 {
-	return distances[hex] < INFINITE_DIST;
+	return distances[hex.toInt()] < INFINITE_DIST;
 }
 
 uint32_t ReachabilityInfo::distToNearestNeighbour(
-	const std::vector<BattleHex> & targetHexes,
+	const BattleHexArray & targetHexes,
 	BattleHex * chosenHex) const
 {
 	uint32_t ret = 1000000;
 
 	for(auto targetHex : targetHexes)
 	{
-		for(auto & n : targetHex.neighbouringTiles())
+		for(auto & n : targetHex.getNeighbouringTiles())
 		{
-			if(distances[n] < ret)
+			if(distances[n.toInt()] < ret)
 			{
-				ret = distances[n];
+				ret = distances[n.toInt()];
 				if(chosenHex)
 					*chosenHex = n;
 			}
@@ -70,16 +71,14 @@ uint32_t ReachabilityInfo::distToNearestNeighbour(
 		{
 			// 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()));
+			attackableHexes.insert(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide()));
 		}
 		else
 		{
-			vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide()));
+			attackableHexes.insert(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();

+ 10 - 5
lib/battle/ReachabilityInfo.h

@@ -8,12 +8,14 @@
  *
  */
 #pragma once
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 #include "CBattleInfoEssentials.h"
 #include "AccessibilityInfo.h"
 
 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),
 // startPosition and perspective.
 struct DLL_LINKAGE ReachabilityInfo
@@ -30,13 +32,16 @@ struct DLL_LINKAGE ReachabilityInfo
 		bool flying = false;
 		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
-		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)
-		std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
+		const 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)
+		TBattlefieldTurnsArray destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex)
 
 		BattleHex startPosition; //assumed position of stack
 		BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side
 
-		Parameters() = default;
+		Parameters()
+		{
+			destructibleEnemyTurns.fill(-1);
+		}
 		Parameters(const battle::Unit * Stack, BattleHex StartPosition);
 	};
 
@@ -50,7 +55,7 @@ struct DLL_LINKAGE ReachabilityInfo
 	bool isReachable(BattleHex hex) const;
 
 	uint32_t distToNearestNeighbour(
-		const std::vector<BattleHex> & targetHexes,
+		const BattleHexArray & targetHexes,
 		BattleHex * chosenHex = nullptr) const;
 
 	uint32_t distToNearestNeighbour(

+ 32 - 48
lib/battle/Unit.cpp

@@ -51,55 +51,29 @@ const IBonusBearer* Unit::getBonusBearer() const
 	return this;
 }
 
-std::vector<BattleHex> Unit::getSurroundingHexes(BattleHex assumedPosition) const
+const BattleHexArray & Unit::getSurroundingHexes(BattleHex assumedPosition) const
 {
-	BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position
+	BattleHex hex = (assumedPosition.toInt() != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position
 
 	return getSurroundingHexes(hex, doubleWide(), unitSide());
 }
 
-std::vector<BattleHex> Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side)
+const BattleHexArray & 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);
+	if(!twoHex)
+		return position.getNeighbouringTiles();
 
-			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();
-	}
+	return position.getNeighbouringTilesDoubleWide(side);
 }
 
-std::vector<BattleHex> Unit::getAttackableHexes(const Unit * attacker) const
+BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
 {
-	auto defenderHexes = battle::Unit::getHexes(
+	const BattleHexArray & defenderHexes = battle::Unit::getHexes(
 		getPosition(),
 		doubleWide(),
 		unitSide());
 	
-	std::vector<BattleHex> targetableHexes;
+	BattleHexArray targetableHexes;
 
 	for(auto defenderHex : defenderHexes)
 	{
@@ -112,11 +86,9 @@ std::vector<BattleHex> Unit::getAttackableHexes(const Unit * attacker) const
 			hexes.pop_back();
 
 		for(auto hex : hexes)
-			vstd::concatenate(targetableHexes, hex.neighbouringTiles());
+			targetableHexes.insert(hex.getNeighbouringTiles());
 	}
 
-	vstd::removeDuplicates(targetableHexes);
-
 	return targetableHexes;
 }
 
@@ -125,25 +97,35 @@ bool Unit::coversPos(BattleHex pos) const
 	return getPosition() == pos || (doubleWide() && (occupiedHex() == pos));
 }
 
-std::vector<BattleHex> Unit::getHexes() const
+const BattleHexArray & Unit::getHexes() const
 {
 	return getHexes(getPosition(), doubleWide(), unitSide());
 }
 
-std::vector<BattleHex> Unit::getHexes(BattleHex assumedPos) const
+const BattleHexArray & Unit::getHexes(BattleHex assumedPos) const
 {
 	return getHexes(assumedPos, doubleWide(), unitSide());
 }
 
-std::vector<BattleHex> Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side)
+const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side)
 {
-	std::vector<BattleHex> hexes;
-	hexes.push_back(assumedPos);
+	static BattleHexArray::ArrayOfBattleHexArrays precomputed[4];
+	int index = side == BattleSide::ATTACKER ? 0 : 2;
+
+	if(!precomputed[index + twoHex][assumedPos.toInt()].empty())
+		return precomputed[index + twoHex][assumedPos.toInt()];
+
+	// first run, compute
+
+	BattleHexArray hexes;
+	hexes.insert(assumedPos);
 
 	if(twoHex)
-		hexes.push_back(occupiedHex(assumedPos, twoHex, side));
+		hexes.insert(occupiedHex(assumedPos, twoHex, side));
+
+	precomputed[index + twoHex][assumedPos.toInt()] = std::move(hexes);
 
-	return hexes;
+	return precomputed[index + twoHex][assumedPos.toInt()];
 }
 
 BattleHex Unit::occupiedHex() const
@@ -161,9 +143,9 @@ BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side)
 	if(twoHex)
 	{
 		if(side == BattleSide::ATTACKER)
-			return assumedPos - 1;
+			return assumedPos.toInt() - 1;
 		else
-			return assumedPos + 1;
+			return assumedPos.toInt() + 1;
 	}
 	else
 	{
@@ -219,7 +201,9 @@ void UnitInfo::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("count", count);
 	handler.serializeId("type", type, CreatureID(CreatureID::NONE));
 	handler.serializeInt("side", side);
-	handler.serializeInt("position", position);
+	si16 positionValue = position.toInt();
+	handler.serializeInt("position", positionValue);
+	position = positionValue;
 	handler.serializeBool("summoned", summoned);
 }
 

+ 7 - 7
lib/battle/Unit.h

@@ -17,7 +17,7 @@
 #include "../bonuses/IBonusBearer.h"
 
 #include "IUnitInfo.h"
-#include "BattleHex.h"
+#include "BattleHexArray.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -128,15 +128,15 @@ public:
 
 	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);
+	const BattleHexArray & getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
+	BattleHexArray getAttackableHexes(const Unit * attacker) const;
+	static const BattleHexArray & getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side);
 
 	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);
+	const BattleHexArray & getHexes() const; //up to two occupied hexes, starting from front
+	const BattleHexArray & getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front
+	static const 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(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(FIRST_STRIKE) /* first counterattack, then attack if possible */\
 	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(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*/ \

+ 6 - 5
lib/bonuses/Limiters.cpp

@@ -220,13 +220,14 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex
 		return ILimiter::EDecision::DISCARD;
 
 	auto accept = false;
-	for (const auto & hex : stack->getHexes())
-		accept |= !!applicableHexes.count(hex);
+
+	for (auto hex : stack->getHexes())
+		accept |= applicableHexes.contains(hex);
 
 	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 }
 
-UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
+UnitOnHexLimiter::UnitOnHexLimiter(const BattleHexArray & applicableHexes):
 	applicableHexes(applicableHexes)
 {
 }
@@ -236,8 +237,8 @@ JsonNode UnitOnHexLimiter::toJsonNode() const
 	JsonNode root;
 
 	root["type"].String() = "UNIT_ON_HEXES";
-	for(const auto & hex : applicableHexes)
-		root["parameters"].Vector().emplace_back(hex);
+	for(auto hex : applicableHexes)
+		root["parameters"].Vector().emplace_back(hex.toInt());
 
 	return root;
 }

+ 3 - 3
lib/bonuses/Limiters.h

@@ -10,7 +10,7 @@
 
 #include "Bonus.h"
 
-#include "../battle/BattleHex.h"
+#include "../battle/BattleHexArray.h"
 #include "../serializer/Serializeable.h"
 #include "../constants/Enumerations.h"
 
@@ -263,9 +263,9 @@ public:
 class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
 {
 public:
-	std::set<BattleHex> applicableHexes;
+	BattleHexArray applicableHexes;
 
-	UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes = {});
+	UnitOnHexLimiter(const BattleHexArray & applicableHexes = {});
 	EDecision limit(const BonusLimitationContext &context) 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
 {
-	//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),
 					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
 

+ 3 - 2
lib/networkPacks/PacksForClientBattle.h

@@ -11,6 +11,7 @@
 
 #include "NetPacksBase.h"
 #include "BattleChanges.h"
+#include "../battle/BattleHexArray.h"
 #include "../battle/BattleAction.h"
 #include "../texts/MetaString.h"
 
@@ -170,10 +171,10 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 {
 	BattleID battleID = BattleID::NONE;
 	ui32 stack = 0;
-	std::vector<BattleHex> tilesToMove;
+	BattleHexArray tilesToMove;
 	int distance = 0;
 	bool teleporting = false;
-
+	
 	void applyGs(CGameState * gs) override;
 	void applyBattle(IBattleState * battleState);
 

+ 1 - 0
lib/rmg/RmgPath.cpp

@@ -117,6 +117,7 @@ Path Path::search(const Tileset & dst, bool straight, std::function<float(const
 					return;
 				
 				float movementCost = moveCostFunction(currentNode, pos);
+
 				float distance = distances[currentNode] + movementCost; //we prefer to use already free paths
 				int bestDistanceSoFar = std::numeric_limits<int>::max();
 				auto it = distances.find(pos);

+ 9 - 16
lib/spells/BattleSpellMechanics.cpp

@@ -508,16 +508,16 @@ bool BattleSpellMechanics::counteringSelector(const Bonus * bonus) const
 	return false;
 }
 
-std::set<BattleHex> BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const
+BattleHexArray BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const
 {
 	using namespace SRSLPraserHelpers;
 
-	std::set<BattleHex> ret;
+	BattleHexArray ret;
 	std::vector<int> rng = owner->getLevelInfo(getRangeLevel()).range;
 
 	for(auto & elem : rng)
 	{
-		std::set<ui16> curLayer = getInRange(centralHex, elem, elem);
+		std::set<ui16> curLayer = getInRange(centralHex.toInt(), elem, elem);
 		//adding obtained hexes
 		for(const auto & curLayer_it : curLayer)
 			ret.insert(curLayer_it);
@@ -604,14 +604,12 @@ std::vector<Destination> BattleSpellMechanics::getPossibleDestinations(size_t in
 		if(fast)
 		{
 			auto stacks = battle()->battleGetAllStacks();
-			std::set<BattleHex> hexesToCheck;
+			BattleHexArray hexesToCheck;
 
 			for(auto stack : stacks)
 			{
 				hexesToCheck.insert(stack->getPosition());
-
-				for(auto adjacent : stack->getPosition().neighbouringTiles())
-					hexesToCheck.insert(adjacent);
+				hexesToCheck.insert(stack->getPosition().getNeighbouringTiles());
 			}
 
 			for(auto hex : hexesToCheck)
@@ -661,17 +659,17 @@ bool BattleSpellMechanics::isReceptive(const battle::Unit * target) const
 	return targetCondition->isReceptive(this, target);
 }
 
-std::vector<BattleHex> BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const
+BattleHexArray BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const
 {
 	if(isMassive() || !centralHex.isValid())
-		return std::vector<BattleHex>(1, BattleHex::INVALID);
+		return BattleHexArray();
 
 	Target aimPoint;
 	aimPoint.push_back(Destination(centralHex));
 
 	Target spellTarget = transformSpellTarget(aimPoint);
 
-	std::set<BattleHex> effectRange;
+	BattleHexArray effectRange;
 
 	effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * effect, bool & stop)
 	{
@@ -681,12 +679,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

+ 2 - 2
lib/spells/BattleSpellMechanics.h

@@ -56,7 +56,7 @@ public:
 	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;
+	BattleHexArray rangeInHexes(BattleHex centralHex) const override;
 
 	const Spell * getSpell() const override;
 
@@ -75,7 +75,7 @@ private:
 
 	void doRemoveEffects(ServerCallback * server, const std::vector<const battle::Unit *> & targets, const CSelector & selector);
 
-	std::set<BattleHex> spellRangeInHexes(BattleHex centralHex) const;
+	BattleHexArray spellRangeInHexes(BattleHex centralHex) const;
 
 	Target transformSpellTarget(const Target & aimPoint) const;
 };

+ 10 - 3
lib/spells/CSpellHandler.cpp

@@ -357,7 +357,7 @@ int32_t CSpell::getLevelPower(const int32_t skillLevel) const
 
 si32 CSpell::getProbability(const FactionID & factionId) const
 {
-	if(!vstd::contains(probabilities,factionId))
+	if(!vstd::contains(probabilities, factionId))
 	{
 		return defaultProbability;
 	}
@@ -701,7 +701,7 @@ const std::vector<std::string> & CSpellHandler::getTypeNames() 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
 
 	if(rng.size() >= 2 && std::tolower(rng[0]) != 'x') //there is at least one hex in range (+artificial comma)
@@ -754,7 +754,14 @@ std::vector<int> CSpellHandler::spellRangeInHexes(std::string input) const
 		}
 	}
 
-	return std::vector<int>(ret.begin(), ret.end());
+	std::vector<int> result;
+	result.reserve(ret.size());
+
+	std::transform(ret.begin(), ret.end(), std::back_inserter(result),
+		[](BattleHex hex) { return hex.toInt(); }
+	);
+
+	return result;
 }
 
 std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)

+ 0 - 2
lib/spells/CSpellHandler.h

@@ -16,8 +16,6 @@
 #include "../IHandlerBase.h"
 #include "../ConstTransitivePtr.h"
 #include "../int3.h"
-#include "../GameConstants.h"
-#include "../battle/BattleHex.h"
 #include "../bonuses/Bonus.h"
 #include "../filesystem/ResourcePath.h"
 #include "../json/JsonNode.h"

+ 1 - 1
lib/spells/ISpellMechanics.h

@@ -185,7 +185,7 @@ public:
 	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 BattleHexArray rangeInHexes(BattleHex centralHex) const = 0;
 	virtual std::vector<const CStack *> getAffectedStacks(const Target & target) const = 0;
 
 	virtual bool canBeCast(Problem & problem) const = 0;

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

@@ -95,7 +95,7 @@ void Catapult::applyMassive(ServerCallback * server, const Mechanics * m) const
 			CatapultAttack::AttackInfo newInfo;
 			newInfo.damageDealt = getRandomDamage(server);
 			newInfo.attackedPart = target;
-			newInfo.destinationTile = m->battle()->wallPartToBattleHex(target);
+			newInfo.destinationTile = m->battle()->wallPartToBattleHex(target).toInt();
 			ca.attackedParts.push_back(newInfo);
 			attackInfo = ca.attackedParts.end() - 1;
 		}
@@ -137,7 +137,7 @@ void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const
 
 		CatapultAttack::AttackInfo attack;
 		attack.attackedPart = actualTarget;
-		attack.destinationTile = m->battle()->wallPartToBattleHex(actualTarget);
+		attack.destinationTile = m->battle()->wallPartToBattleHex(actualTarget).toInt();
 		attack.damageDealt = getRandomDamage(server);
 
 		CatapultAttack ca; //package for clients

+ 1 - 1
lib/spells/effects/Clone.cpp

@@ -43,7 +43,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg
 		if(clonedStack->getCount() < 1)
 			continue;
 
-		auto hex = m->battle()->getAvailableHex(clonedStack->creatureId(), m->casterSide, clonedStack->getPosition());
+		auto hex = m->battle()->getAvailableHex(clonedStack->creatureId(), m->casterSide, clonedStack->getPosition().toInt());
 
 		if(!hex.isValid())
 		{

+ 1 - 1
lib/spells/effects/DemonSummon.cpp

@@ -65,7 +65,7 @@ void DemonSummon::apply(ServerCallback * server, const Mechanics * m, const Effe
 			continue;
 		}
 
-		auto hex = m->battle()->getAvailableHex(targetStack->creatureId(), m->casterSide, targetStack->getPosition());
+		auto hex = m->battle()->getAvailableHex(targetStack->creatureId(), m->casterSide, targetStack->getPosition().toInt());
 
 		if(!hex.isValid())
 		{

+ 3 - 2
lib/spells/effects/Effect.h

@@ -14,7 +14,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-struct BattleHex;
+class BattleHex;
+class BattleHexArray;
 class CBattleInfoCallback;
 class JsonSerializeFormat;
 class ServerCallback;
@@ -51,7 +52,7 @@ public:
 	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;
+	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;

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

@@ -11,6 +11,7 @@
 
 #include "LocationEffect.h"
 #include "../ISpellMechanics.h"
+#include "battle/BattleHexArray.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -24,7 +25,7 @@ void LocationEffect::adjustTargetTypes(std::vector<TargetType> & types) const
 
 }
 
-void LocationEffect::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
+void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
 {
 	for(const auto & destnation : spellTarget)
 		hexes.insert(destnation.hexValue);

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

@@ -25,7 +25,7 @@ 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;
+	void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override;
 
 	EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override;
 

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

@@ -32,7 +32,7 @@ namespace spells
 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);
@@ -44,7 +44,11 @@ static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string
 			inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER);
 
 			for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
-				inner.serializeInt(innerIndex, moatHexes.at(outerIndex).at(innerIndex));
+			{
+				si16 hex = moatHexes.at(outerIndex).at(innerIndex).toInt();
+				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.source = BonusSource::SPELL_EFFECT;//for all
 		}
-		std::set<BattleHex> flatMoatHexes;
+		BattleHexArray flatMoatHexes;
 
 		for(const auto & moatPatch : moatHexes)
-			flatMoatHexes.insert(moatPatch.begin(), moatPatch.end());
+			flatMoatHexes.insert(moatPatch);
 
 		nb.limiter = std::make_shared<UnitOnHexLimiter>(std::move(flatMoatHexes));
 		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.appearAnimation = sideOptions.appearAnimation; //For dispellable moats
 		obstacle.animation = sideOptions.animation;
-		obstacle.customSize.insert(obstacle.customSize.end(),destination.cbegin(), destination.cend());
+		obstacle.customSize.insert(destination);
 		obstacle.animationYOffset = sideOptions.offsetY;
 		pack.changes.emplace_back();
 		obstacle.toInfo(pack.changes.back());

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

@@ -25,7 +25,7 @@ class Moat : public Obstacle
 {
 private:
 	ObstacleSideOptions sideOptions; //Defender only
-	std::vector<std::vector<BattleHex>> moatHexes; //Determine number of moat patches and hexes
+	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

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

@@ -95,7 +95,7 @@ void ObstacleSideOptions::serializeJson(JsonSerializeFormat & handler)
 	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);
 
@@ -180,11 +180,11 @@ void Obstacle::apply(ServerCallback * server, const Mechanics * m, const EffectT
 {
 	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))
-				availableTiles.push_back(hex);
+				availableTiles.insert(hex);
 		};
 
 		if(m->isMassive())
@@ -309,7 +309,6 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 		obstacle.animationYOffset = options.offsetY;
 
 		obstacle.customSize.clear();
-		obstacle.customSize.reserve(options.shape.size());
 
 		for(const auto & shape : options.shape)
 		{
@@ -318,7 +317,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 			for(auto direction : shape)
 				hex.moveInDirection(direction, false);
 
-			obstacle.customSize.emplace_back(hex);
+			obstacle.customSize.insert(hex);
 		}
 
 		pack.changes.emplace_back();

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

@@ -12,7 +12,7 @@
 
 #include "LocationEffect.h"
 #include "../../GameConstants.h"
-#include "../../battle/BattleHex.h"
+#include "../../battle/BattleHexArray.h"
 #include "../../battle/CObstacleInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -42,7 +42,7 @@ public:
 class Obstacle : public LocationEffect
 {
 public:
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) 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;

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

@@ -28,7 +28,7 @@ namespace spells
 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
 }
@@ -207,4 +207,4 @@ EffectTarget Summon::transformTarget(const Mechanics * m, const Target & aimPoin
 }
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

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

@@ -23,7 +23,7 @@ namespace effects
 class Summon : public Effect
 {
 public:
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const override;
+	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;

+ 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.distance = 0;
 	pack.stack = targetUnit->unitId();
-	std::vector<BattleHex> tiles;
-	tiles.push_back(destination);
+	BattleHexArray tiles;
+	tiles.insert(destination);
 	pack.tilesToMove = tiles;
 	pack.teleporting = true;
 	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)
 		hexes.insert(destnation.hexValue);
@@ -193,7 +193,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
 		return EffectTarget();
 	}
 
-	std::set<BattleHex> possibleHexes;
+	BattleHexArray possibleHexes;
 
 	auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
 	{
@@ -223,7 +223,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
 			effectTarget.emplace_back();
 
 		for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
-			possibleHexes.erase(hex);
+			possibleHexes.erase(hex.toInt());
 
 		if(possibleHexes.empty())
 			break;
@@ -278,4 +278,4 @@ void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler)
 }
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

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

@@ -24,7 +24,7 @@ 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;
+	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;

+ 3 - 3
scripting/lua/LuaSpellEffect.cpp

@@ -58,7 +58,7 @@ void LuaSpellEffect::adjustTargetTypes(std::vector<TargetType> & types) const
 
 }
 
-void LuaSpellEffect::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
+void LuaSpellEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const
 {
 
 }
@@ -98,7 +98,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef
 	for(const auto & dest : target)
 	{
 		JsonNode targetData;
-		targetData.Vector().emplace_back(dest.hexValue.hex);
+		targetData.Vector().emplace_back(dest.hexValue.toInt());
 
 		if(dest.unitValue)
 			targetData.Vector().emplace_back(dest.unitValue->unitId());
@@ -141,7 +141,7 @@ void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const E
 	for(const auto & dest : target)
 	{
 		JsonNode targetData;
-		targetData.Vector().emplace_back(dest.hexValue.hex);
+		targetData.Vector().emplace_back(dest.hexValue.toInt());
 
 		if(dest.unitValue)
 			targetData.Vector().emplace_back(dest.unitValue->unitId());

+ 1 - 1
scripting/lua/LuaSpellEffect.h

@@ -49,7 +49,7 @@ public:
 
 	void adjustTargetTypes(std::vector<TargetType> & types) const override;
 
-	void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) 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;

+ 4 - 2
scripting/lua/api/BattleCb.cpp

@@ -99,11 +99,13 @@ int BattleCbProxy::getUnitByPos(lua_State * L)
 	if(!S.tryGet(1, object))
 		return S.retVoid();
 
-	BattleHex hex;
+	si16 hexVal;
 
-	if(!S.tryGet(2, hex.hex))
+	if(!S.tryGet(2, hexVal))
 		return S.retNil();
 
+	BattleHex hex(hexVal);
+
 	bool onlyAlive;
 
 	if(!S.tryGet(3, onlyAlive))

+ 1 - 1
scripting/lua/api/netpacks/BattleStackMoved.cpp

@@ -47,7 +47,7 @@ int BattleStackMovedProxy::addTileToMove(lua_State * L)
 	lua_Integer hex = 0;
 	if(!S.tryGetInteger(2, hex))
 		return S.retVoid();
-	object->tilesToMove.emplace_back(hex);
+	object->tilesToMove.insert(hex);
 	return S.retVoid();
 }
 

+ 7 - 7
server/battles/BattleActionProcessor.cpp

@@ -635,7 +635,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 
 	//initing necessary tables
 	auto accessibility = battle.getAccessibility(curStack);
-	std::set<BattleHex> passed;
+	BattleHexArray passed;
 	//Ignore obstacles on starting position
 	passed.insert(curStack->getPosition());
 	if(curStack->doubleWide())
@@ -665,7 +665,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 		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;
 
@@ -723,8 +723,8 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 			BattleStackMoved sm;
 			sm.battleID = battle.getBattle()->getBattleID();
 			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.distance = path.second;
 			sm.teleporting = false;
@@ -733,10 +733,10 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 	}
 	else //for non-flying creatures
 	{
-		std::vector<BattleHex> tiles;
+		BattleHexArray tiles;
 		const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		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
 		BattleHex openGateAtHex, gateMayCloseAtHex;
@@ -822,7 +822,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 				for (bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v)
 				{
 					BattleHex hex = path.first[v];
-					tiles.push_back(hex);
+					tiles.insert(hex);
 
 					if ((openGateAtHex.isValid() && openGateAtHex == hex) ||
 						(gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex))

+ 1 - 1
server/battles/BattleActionProcessor.h

@@ -16,7 +16,7 @@ struct BattleLogMessage;
 struct BattleAttack;
 class BattleAction;
 class CBattleInfoCallback;
-struct BattleHex;
+class BattleHex;
 class CStack;
 class PlayerColor;
 enum class BonusType : uint8_t;

+ 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 y = targetPosition.getY();
@@ -44,31 +44,31 @@ void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & batt
 	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...
-		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
-		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
 	if (targetIsAttacker && ((y % 2 == 0) || (x > 1)))
 	{
 		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
 		{	//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
-				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
 			{
-				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
-					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
 		{
-			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
 		{
-			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)
-				BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false), output);
+				output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false));
 			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)
-					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)
 	{
-		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)
 	{
-		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));
 	auto accessibility = battle.getAccessibility();
 	CreatureID creatureData = summonInfo->subtype.as<CreatureID>();
-	std::vector<BattleHex> targetHexes;
+	BattleHexArray targetHexes;
 	const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard
 	const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
 

+ 3 - 2
server/battles/BattleFlowProcessor.h

@@ -13,7 +13,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
-struct BattleHex;
+class BattleHex;
+class BattleHexArray;
 class BattleAction;
 class CBattleInfoCallback;
 struct CObstacleInstance;
@@ -37,7 +38,7 @@ class BattleFlowProcessor : boost::noncopyable
 	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 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);

+ 9 - 9
test/battle/BattleHexTest.cpp

@@ -9,29 +9,29 @@
  */
 
 #include "StdInc.h"
-#include "../lib/battle/BattleHex.h"
+#include "../lib/battle/BattleHexArray.h"
 
 TEST(BattleHexTest, getNeighbouringTiles)
 {
 	BattleHex mainHex;
-	std::vector<BattleHex> neighbouringTiles;
+	BattleHexArray neighbouringTiles;
 	mainHex.setXY(16,0);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 1);
 	mainHex.setXY(0,0);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 2);
 	mainHex.setXY(15,2);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 3);
 	mainHex.setXY(2,0);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 4);
 	mainHex.setXY(1,2);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 5);
 	mainHex.setXY(8,5);
-	neighbouringTiles = mainHex.neighbouringTiles();
+	neighbouringTiles = mainHex.getNeighbouringTiles();
 	EXPECT_EQ(neighbouringTiles.size(), 6);
 
 	ASSERT_TRUE(neighbouringTiles.size()==6 && mainHex==93);
@@ -85,7 +85,7 @@ TEST(BattleHexTest, mutualPositions)
 TEST(BattleHexTest, getClosestTile)
 {
 	BattleHex mainHex(0);
-	std::set<BattleHex> possibilities;
+	BattleHexArray possibilities;
 	possibilities.insert(3);
 	possibilities.insert(170);
 	possibilities.insert(100);

+ 9 - 9
test/battle/battle_UnitTest.cpp

@@ -17,7 +17,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWide)
 
 	auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER);
 
-	EXPECT_EQ(actual, position.neighbouringTiles());
+	EXPECT_EQ(actual, position.getNeighbouringTiles());
 }
 
 TEST(battle_Unit_getSurroundingHexes, oneWideLeftCorner)
@@ -26,7 +26,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWideLeftCorner)
 
 	auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER);
 
-	EXPECT_EQ(actual, position.neighbouringTiles());
+	EXPECT_EQ(actual, position.getNeighbouringTiles());
 }
 
 TEST(battle_Unit_getSurroundingHexes, oneWideRightCorner)
@@ -35,7 +35,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWideRightCorner)
 
 	auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER);
 
-	EXPECT_EQ(actual, position.neighbouringTiles());
+	EXPECT_EQ(actual, position.getNeighbouringTiles());
 }
 
 TEST(battle_Unit_getSurroundingHexes, doubleWideAttacker)
@@ -44,7 +44,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideAttacker)
 
 	auto actual = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER);
 
-	static const std::vector<BattleHex> expected =
+	static const BattleHexArray expected =
 	{
 		60,
 		61,
@@ -65,7 +65,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideLeftCorner)
 
 	auto actualAtt = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER);
 
-	static const std::vector<BattleHex> expectedAtt =
+	static const BattleHexArray expectedAtt =
 	{
 		35,
 		53,
@@ -76,7 +76,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideLeftCorner)
 
 	auto actualDef = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER);
 
-	static const std::vector<BattleHex> expectedDef =
+	static const BattleHexArray expectedDef =
 	{
 		35,
 		36,
@@ -94,7 +94,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideRightCorner)
 
 	auto actualAtt = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER);
 
-	static const std::vector<BattleHex> expectedAtt =
+	static const BattleHexArray expectedAtt =
 	{
 		116,
 		117,
@@ -109,7 +109,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideRightCorner)
 
 	auto actualDef = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER);
 
-	static const std::vector<BattleHex> expectedDef =
+	static const BattleHexArray expectedDef =
 	{
 		116,
 		117,
@@ -128,7 +128,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideDefender)
 
 	auto actual = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER);
 
-	static const std::vector<BattleHex> expected =
+	static const BattleHexArray expected =
 	{
 		60,
 		61,

+ 1 - 1
test/mock/mock_IBattleInfoCallback.h

@@ -40,7 +40,7 @@ public:
 	MOCK_CONST_METHOD0(getPlayerID, std::optional<PlayerColor>());
 
 	MOCK_CONST_METHOD2(battleGetAllObstaclesOnPos, std::vector<std::shared_ptr<const CObstacleInstance>>(BattleHex, bool));
-	MOCK_CONST_METHOD2(getAllAffectedObstaclesByStack, std::vector<std::shared_ptr<const CObstacleInstance>>(const battle::Unit *, const std::set<BattleHex> &));
+	MOCK_CONST_METHOD2(getAllAffectedObstaclesByStack, std::vector<std::shared_ptr<const CObstacleInstance>>(const battle::Unit *, const BattleHexArray &));
 
 };
 

+ 2 - 1
test/mock/mock_spells_Mechanics.h

@@ -12,6 +12,7 @@
 
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/CGameInfoCallback.h"
+#include "../../lib/battle/BattleHexArray.h"
 
 namespace spells
 {
@@ -22,7 +23,7 @@ public:
 	MOCK_CONST_METHOD2(adaptProblem, bool(ESpellCastProblem, Problem &));
 	MOCK_CONST_METHOD1(adaptGenericProblem, bool(Problem &));
 
-	MOCK_CONST_METHOD1(rangeInHexes, std::vector<BattleHex>(BattleHex));
+	MOCK_CONST_METHOD1(rangeInHexes, BattleHexArray(BattleHex));
 	MOCK_CONST_METHOD1(getAffectedStacks, std::vector<const CStack *>(const Target &));
 
 	MOCK_CONST_METHOD1(canBeCast, bool(Problem &));

+ 5 - 5
test/scripting/LuaSpellEffectAPITest.cpp

@@ -84,7 +84,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField)
 	BattleHex hex(2,2);
 
 	JsonNode first;
-	first.Vector().emplace_back(hex.hex);
+	first.Vector().emplace_back(hex.toInt());
 	first.Vector().emplace_back();
 
 	JsonNode targets;
@@ -113,7 +113,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField)
 	BattleHex hex(11,2);
 
 	JsonNode first;
-	first.Vector().emplace_back(hex.hex);
+	first.Vector().emplace_back(hex.toInt());
 	first.Vector().emplace_back(-1);
 
 	JsonNode targets;
@@ -138,13 +138,13 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit)
 	BattleHex hex1(11,2);
 
 	JsonNode unit;
-	unit.Vector().emplace_back(hex1.hex);
+	unit.Vector().emplace_back(hex1.toInt());
 	unit.Vector().emplace_back(42);
 
 	BattleHex hex2(5,4);
 
 	JsonNode destination;
-	destination.Vector().emplace_back(hex2.hex);
+	destination.Vector().emplace_back(hex2.toInt());
 	destination.Vector().emplace_back(-1);
 
 	JsonNode targets;
@@ -163,7 +163,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit)
 		EXPECT_EQ(pack.teleporting, true);
 		EXPECT_EQ(pack.distance, 0);
 
-		std::vector<BattleHex> toMove(1, hex2);
+		BattleHexArray toMove = { hex2 };
 
 		EXPECT_EQ(pack.tilesToMove, toMove);
 	};

+ 3 - 3
test/scripting/LuaSpellEffectTest.cpp

@@ -154,11 +154,11 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected)
 
 
 	JsonNode first;
-	first.Vector().emplace_back(hex1.hex);
+	first.Vector().emplace_back(hex1.toInt());
 	first.Vector().emplace_back(id1);
 
 	JsonNode second;
-	second.Vector().emplace_back(hex2.hex);
+	second.Vector().emplace_back(hex2.toInt());
 	second.Vector().emplace_back(-1);
 
 	JsonNode targets;
@@ -193,7 +193,7 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected)
 	subject->apply(&serverMock, &mechanicsMock, target);
 
 	JsonNode first;
-	first.Vector().emplace_back(hex1.hex);
+	first.Vector().emplace_back(hex1.toInt());
 	first.Vector().emplace_back(id1);
 
 	JsonNode targets;