Bläddra i källkod

Changes following review:
- shared_ptr for destructibleEnemyTurns instead of raw pointer
- drop implicit int conversion for BattleHex class
and implement toInt() instead
- implement necessary operators in BattleHex
- adjust code to work properly with JSON serializer

MichalZr6 9 månader sedan
förälder
incheckning
dbe82b94f6
39 ändrade filer med 223 tillägg och 174 borttagningar
  1. 1 1
      AI/BattleAI/AttackPossibility.cpp
  2. 6 6
      AI/BattleAI/BattleEvaluator.cpp
  3. 1 1
      AI/BattleAI/BattleEvaluator.h
  4. 14 15
      AI/BattleAI/BattleExchangeVariant.cpp
  5. 1 1
      AI/BattleAI/PotentialTargets.cpp
  6. 3 3
      AI/StupidAI/StupidAI.cpp
  7. 1 1
      client/battle/BattleActionsController.cpp
  8. 1 1
      client/battle/BattleAnimationClasses.cpp
  9. 1 1
      client/battle/BattleFieldController.cpp
  10. 3 3
      client/battle/BattleSiegeController.cpp
  11. 2 2
      lib/CStack.cpp
  12. 1 1
      lib/ObstacleHandler.cpp
  13. 2 2
      lib/battle/AccessibilityInfo.cpp
  14. 2 1
      lib/battle/AccessibilityInfo.h
  15. 5 5
      lib/battle/BattleHex.cpp
  16. 63 30
      lib/battle/BattleHex.h
  17. 6 6
      lib/battle/BattleHexArray.cpp
  18. 16 16
      lib/battle/BattleHexArray.h
  19. 2 2
      lib/battle/BattleInfo.cpp
  20. 37 37
      lib/battle/CBattleInfoCallback.cpp
  21. 1 1
      lib/battle/CBattleInfoCallback.h
  22. 7 3
      lib/battle/CObstacleInstance.cpp
  23. 3 1
      lib/battle/CUnitState.cpp
  24. 1 1
      lib/battle/DamageCalculator.cpp
  25. 3 3
      lib/battle/ReachabilityInfo.cpp
  26. 1 1
      lib/battle/ReachabilityInfo.h
  27. 11 9
      lib/battle/Unit.cpp
  28. 3 2
      lib/battle/Unit.h
  29. 1 1
      lib/bonuses/Limiters.cpp
  30. 1 1
      lib/spells/BattleSpellMechanics.cpp
  31. 8 1
      lib/spells/CSpellHandler.cpp
  32. 2 2
      lib/spells/effects/Catapult.cpp
  33. 1 1
      lib/spells/effects/Clone.cpp
  34. 1 1
      lib/spells/effects/DemonSummon.cpp
  35. 1 1
      lib/spells/effects/Moat.cpp
  36. 1 1
      lib/spells/effects/UnitEffect.cpp
  37. 2 2
      scripting/lua/LuaSpellEffect.cpp
  38. 4 4
      test/scripting/LuaSpellEffectAPITest.cpp
  39. 3 3
      test/scripting/LuaSpellEffectTest.cpp

+ 1 - 1
AI/BattleAI/AttackPossibility.cpp

@@ -280,7 +280,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
 	std::set<uint32_t> checkedUnits;
 
 	auto attacker = attackInfo.attacker;
-	const BattleHexArray & hexes = attacker->getSurroundingHexes(hex);
+	const auto & hexes = attacker->getSurroundingHexes(hex);
 	for(BattleHex tile : hexes)
 	{
 		auto st = state->battleGetUnitByPos(tile, true);

+ 6 - 6
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(),
+				bestAttack.from.toInt(),
+				bestAttack.attack.attacker->getPosition().toInt(),
 				bestAttack.attack.chargeDistance,
 				bestAttack.attack.attacker->getMovementRange(0),
 				bestAttack.defenderDamageReduce,
@@ -355,7 +355,7 @@ BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex,
 	}
 }
 
-BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexArray 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);
@@ -387,12 +387,12 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr
 
 	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 bestNeighbour = hexes.front();
 
-	if(reachability.distances[bestNeighbour] > GameConstants::BFIELD_SIZE)
+	if(reachability.distances[bestNeighbour.toInt()] > GameConstants::BFIELD_SIZE)
 	{
 		logAi->trace("No reachable hexes.");
 		return BattleAction::makeDefend(stack);
@@ -468,7 +468,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr
 				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, BattleHexArray 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

+ 14 - 15
AI/BattleAI/BattleExchangeVariant.cpp

@@ -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;
@@ -415,11 +415,11 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 
 				for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
 				{
-					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,
@@ -916,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);
 	}
@@ -952,9 +951,9 @@ 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);
 
@@ -962,7 +961,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
 				{
 					for(BattleHex neighbour : hex.getNeighbouringTiles())
 					{
-						reachable = unitReachability.distances.at(neighbour) <= radius;
+						reachable = unitReachability.distances.at(neighbour.toInt()) <= radius;
 
 						if(reachable) break;
 					}
@@ -1009,12 +1008,12 @@ 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);
 
@@ -1023,7 +1022,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 						enemyUnit = true;
 						for(BattleHex neighbour : hex.getNeighbouringTiles())
 						{
-							reachable = unitReachability.distances.at(neighbour) <= unitSpeed;
+							reachable = unitReachability.distances.at(neighbour.toInt()) <= unitSpeed;
 
 							if(reachable) break;
 						}

+ 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);

+ 3 - 3
AI/StupidAI/StupidAI.cpp

@@ -291,7 +291,7 @@ 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)
@@ -313,7 +313,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 
 	BattleHex bestneighbour = hexes.front();
 
-	if(reachability.distances[bestneighbour] > GameConstants::BFIELD_SIZE)
+	if(reachability.distances[bestneighbour.toInt()] > GameConstants::BFIELD_SIZE)
 	{
 		return BattleAction::makeDefend(stack);
 	}
@@ -347,7 +347,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
 				return BattleAction::makeMove(stack, currentDest);
 			}
 
-			currentDest = reachability.predecessors[currentDest];
+			currentDest = reachability.predecessors[currentDest.toInt()];
 		}
 	}
 }

+ 1 - 1
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);

+ 1 - 1
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)

+ 1 - 1
client/battle/BattleFieldController.cpp

@@ -839,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)

+ 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));
+	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
lib/CStack.cpp

@@ -252,8 +252,8 @@ BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const bat
 	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
 	{

+ 1 - 1
lib/ObstacleHandler.cpp

@@ -66,7 +66,7 @@ BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const
 	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;
 		

+ 2 - 2
lib/battle/AccessibilityInfo.cpp

@@ -18,14 +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)
-	const auto & accessibility = at(tile);
+	const auto & accessibility = at(tile.toInt());
 
 	if(accessibility == EAccessibility::ALIVE_STACK)
 	{
 		if(!destructibleEnemyTurns)
 			return false;
 
-		return destructibleEnemyTurns->at(tile) >= 0;
+		return destructibleEnemyTurns->at(tile.toInt()) >= 0;
 	}
 
 	if(accessibility != EAccessibility::ACCESSIBLE)

+ 2 - 1
lib/battle/AccessibilityInfo.h

@@ -32,10 +32,11 @@ enum class EAccessibility
 
 
 using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZE>;
+using TBattlefieldTurnsArray = std::array<int8_t, GameConstants::BFIELD_SIZE>;
 
 struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
 {
-	const std::array<int8_t, GameConstants::BFIELD_SIZE> * destructibleEnemyTurns = nullptr;
+	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

+ 5 - 5
lib/battle/BattleHex.cpp

@@ -48,24 +48,24 @@ BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, const
 	return (bestTile != closestTiles.end()) ? *bestTile : BattleHex();
 }
 
-const BattleHexArray & BattleHex::getAllNeighbouringTiles() const
+const BattleHexArray & BattleHex::getAllNeighbouringTiles() const noexcept
 {
 	return BattleHexArray::getAllNeighbouringTiles(*this);
 }
 
-const BattleHexArray & BattleHex::getNeighbouringTiles() const
+const BattleHexArray & BattleHex::getNeighbouringTiles() const noexcept
 {
 	return BattleHexArray::getNeighbouringTiles(*this);
 }
 
-const BattleHexArray & BattleHex::getNeighbouringTilesDblWide(BattleSide side) const
+const BattleHexArray & BattleHex::getNeighbouringTilesDoubleWide(BattleSide side) const noexcept
 {
-	return BattleHexArray::getNeighbouringTilesDblWide(*this, side);
+	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() % static_cast<si16>(hex));
+	return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.toInt());
 }
 
 VCMI_LIB_NAMESPACE_END

+ 63 - 30
lib/battle/BattleHex.h

@@ -24,7 +24,8 @@ namespace GameConstants
 
 class BattleHexArray;
 
-// for battle stacks' positions
+// 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:
@@ -68,10 +69,10 @@ public:
 		BOTTOM
 	};
 
-	BattleHex() 
+	BattleHex() noexcept
 		: hex(INVALID) 
 	{}
-	BattleHex(si16 _hex) 
+	BattleHex(si16 _hex) noexcept
 		: hex(_hex) 
 	{}
 	BattleHex(si16 x, si16 y)
@@ -82,17 +83,13 @@ public:
 	{
 		setXY(xy);
 	}
-	operator si16() const
-	{
-		return hex;
-	}
 
-	inline bool isValid() const
+	[[nodiscard]] bool isValid() const noexcept
 	{
 		return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
 	}
 	
-	bool isAvailable() const //valid position not in first or last column
+	[[nodiscard]] bool isAvailable() const noexcept //valid position not in first or last column
 	{
 		return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1;
 	}
@@ -123,17 +120,17 @@ public:
 		setXY(xy.first, xy.second);
 	}
 
-	si16 getX() const
+	[[nodiscard]] si16 getX() const noexcept
 	{
 		return hex % GameConstants::BFIELD_WIDTH;
 	}
 
-	si16 getY() const
+	[[nodiscard]] si16 getY() const noexcept
 	{
 		return hex / GameConstants::BFIELD_WIDTH;
 	}
 
-	std::pair<si16, si16> getXY() const
+	[[nodiscard]] std::pair<si16, si16> getXY() const noexcept
 	{
 		return std::make_pair(getX(), getY());
 	}
@@ -171,24 +168,14 @@ public:
 		return *this;
 	}
 
-	BattleHex & operator+=(EDir dir)
-	{
-		return moveInDirection(dir);
-	}
-
-	BattleHex operator+(EDir dir) const
-	{
-		return cloneInDirection(dir);
-	}
-
-	BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const
+	[[nodiscard]] BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const
 	{
 		BattleHex result(hex);
 		result.moveInDirection(dir, hasToBeValid);
 		return result;
 	}
 
-	static uint8_t getDistance(BattleHex hex1, BattleHex hex2)
+	[[nodiscard]] static uint8_t getDistance(BattleHex hex1, BattleHex hex2) noexcept
 	{
 		int y1 = hex1.getY();
 		int y2 = hex2.getY();
@@ -205,15 +192,15 @@ public:
 		return std::abs(xDst) + std::abs(yDst);
 	}
 
-	static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes);
+	[[nodiscard]] static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes);
 
 	//Constexpr defined array with all directions used in battle
-	static constexpr auto hexagonalDirections() 
+	[[nodiscard]] static constexpr auto hexagonalDirections() noexcept
 	{
 		return std::array<EDir,6>{TOP_LEFT, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT};
 	}
 
-	static EDir mutualPosition(BattleHex hex1, BattleHex hex2)
+	[[nodiscard]] static EDir mutualPosition(BattleHex hex1, BattleHex hex2)
 	{
 		for(auto dir : hexagonalDirections())
 			if(hex2 == hex1.cloneInDirection(dir, false))
@@ -222,13 +209,59 @@ public:
 	}
 
 	/// get (precomputed) all possible surrounding tiles
-	const BattleHexArray & getAllNeighbouringTiles() const;
+	[[nodiscard]] const BattleHexArray & getAllNeighbouringTiles() const noexcept;
 
 	/// get (precomputed) only valid and available surrounding tiles
-	const BattleHexArray & getNeighbouringTiles() const;
+	[[nodiscard]] const BattleHexArray & getNeighbouringTiles() const noexcept;
 
 	/// get (precomputed) only valid and available surrounding tiles for double wide creatures
-	const BattleHexArray & getNeighbouringTilesDblWide(BattleSide side) const;
+	[[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)

+ 6 - 6
lib/battle/BattleHexArray.cpp

@@ -34,7 +34,7 @@ void BattleHexArray::erase(iterator first, iterator last) noexcept
 {
 	for(auto it = first; it != last && it != internalStorage.end(); ++it)
 	{
-		presenceFlags[*it] = 0;
+		presenceFlags[it->toInt()] = 0;
 	}
 
 	internalStorage.erase(first, last);
@@ -43,7 +43,7 @@ void BattleHexArray::erase(iterator first, iterator last) noexcept
 void BattleHexArray::clear() noexcept
 {
 	for(auto hex : internalStorage)
-		presenceFlags[hex] = 0;
+		presenceFlags[hex.toInt()] = 0;
 
 	internalStorage.clear();
 }
@@ -83,7 +83,7 @@ BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateAllNeighbouri
 	return ret;
 }
 
-BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDblWide(BattleSide side)
+BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDoubleWide(BattleSide side)
 {
 	ArrayOfBattleHexArrays ret;
 
@@ -123,10 +123,10 @@ BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringT
 
 const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTiles = precalculateNeighbouringTiles();
 const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::allNeighbouringTiles = precalculateAllNeighbouringTiles();
-const std::map<BattleSide, BattleHexArray::ArrayOfBattleHexArrays> BattleHexArray::neighbouringTilesDblWide =
+const std::map<BattleSide, BattleHexArray::ArrayOfBattleHexArrays> BattleHexArray::neighbouringTilesDoubleWide =
 	{
-	   { BattleSide::ATTACKER, precalculateNeighbouringTilesDblWide(BattleSide::ATTACKER) },
-	   { BattleSide::DEFENDER, precalculateNeighbouringTilesDblWide(BattleSide::DEFENDER) }
+	   { BattleSide::ATTACKER, precalculateNeighbouringTilesDoubleWide(BattleSide::ATTACKER) },
+	   { BattleSide::DEFENDER, precalculateNeighbouringTilesDoubleWide(BattleSide::DEFENDER) }
 	};
 
 VCMI_LIB_NAMESPACE_END

+ 16 - 16
lib/battle/BattleHexArray.h

@@ -60,7 +60,7 @@ public:
 	{
 		if(tile.isAvailable() && !contains(tile))
 		{
-			presenceFlags[tile] = 1;
+			presenceFlags[tile.toInt()] = true;
 			internalStorage.emplace_back(tile);
 		}
 	}
@@ -70,7 +70,7 @@ public:
 		if(contains(hex))
 			return;
 
-		presenceFlags[hex] = 1;
+		presenceFlags[hex.toInt()] = true;
 		internalStorage.emplace_back(hex);
 	}
 
@@ -87,7 +87,7 @@ public:
 		if(contains(hex))
 			return;
 
-		presenceFlags[hex] = 1;
+		presenceFlags[hex.toInt()] = true;
 		internalStorage[index] = hex;
 	}
 
@@ -96,7 +96,7 @@ public:
 		if(contains(hex))
 			return pos;
 
-		presenceFlags[hex] = 1;
+		presenceFlags[hex.toInt()] = true;
 		return internalStorage.insert(pos, hex);
 	}
 
@@ -122,7 +122,7 @@ public:
 	void erase(iterator first, iterator last) noexcept;
 	inline void pop_back() noexcept
 	{
-		presenceFlags[internalStorage.back()] = 0;
+		presenceFlags[internalStorage.back().toInt()] = false;
 		internalStorage.pop_back();
 	}
 
@@ -158,33 +158,33 @@ public:
 	}
 
 	/// get (precomputed) all possible surrounding tiles
-	static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex)
+	static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept
 	{
 		assert(hex.isValid());
 
-		return allNeighbouringTiles[hex];
+		return allNeighbouringTiles[hex.toInt()];
 	}
 
 	/// get (precomputed) only valid and available surrounding tiles
-	static const BattleHexArray & getNeighbouringTiles(BattleHex hex)
+	static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept
 	{
 		assert(hex.isValid());
 
-		return neighbouringTiles[hex];
+		return neighbouringTiles[hex.toInt()];
 	}
 
 	/// get (precomputed) only valid and available surrounding tiles for double wide creatures
-	static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex hex, BattleSide side)
+	static const BattleHexArray & getNeighbouringTilesDoubleWide(BattleHex hex, BattleSide side) noexcept
 	{
 		assert(hex.isValid() && (side == BattleSide::ATTACKER || side == BattleSide::DEFENDER));
 
-		return neighbouringTilesDblWide.at(side)[hex];
+		return neighbouringTilesDoubleWide.at(side)[hex.toInt()];
 	}
 
 	[[nodiscard]] inline bool contains(BattleHex hex) const noexcept
 	{
 		if(hex.isValid())
-			return presenceFlags[hex];
+			return presenceFlags[hex.toInt()];
 		/*
 		if(!isTower(hex))
 			logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex);
@@ -198,10 +198,10 @@ public:
 	void serialize(Serializer & s)
 	{
 		s & internalStorage;
-		if(!internalStorage.empty() && presenceFlags[internalStorage.front()] == 0)
+		if(!s.saving)
 		{
 			for(auto hex : internalStorage)
-				presenceFlags[hex] = 1;
+				presenceFlags[hex.toInt()] = true;
 		}
 	}
 
@@ -307,11 +307,11 @@ private:
 
 	static const ArrayOfBattleHexArrays neighbouringTiles;
 	static const ArrayOfBattleHexArrays allNeighbouringTiles;
-	static const std::map<BattleSide, ArrayOfBattleHexArrays> neighbouringTilesDblWide;
+	static const std::map<BattleSide, ArrayOfBattleHexArrays> neighbouringTilesDoubleWide;
 
 	static ArrayOfBattleHexArrays precalculateNeighbouringTiles();
 	static ArrayOfBattleHexArrays precalculateAllNeighbouringTiles();
-	static ArrayOfBattleHexArrays precalculateNeighbouringTilesDblWide(BattleSide side);
+	static ArrayOfBattleHexArrays precalculateNeighbouringTilesDoubleWide(BattleSide side);
 };
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 2
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;
 }
@@ -264,7 +264,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 
 					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(blockedTiles.contains(blocked))
 							return false;

+ 37 - 37
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;
 	}
 
@@ -151,7 +148,7 @@ std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, B
 {
 	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(BattleHexArray(), 0);
 	}
@@ -162,10 +159,10 @@ std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, B
 	while(curElem != start)
 	{
 		path.insert(curElem);
-		curElem = reachability.predecessors[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) + " and " + std::to_string(dest) + ") 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)
 	{
@@ -225,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;
@@ -786,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);
 }
 
@@ -955,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
@@ -965,7 +962,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 	if(bFieldType != BattleField::NONE)
 	{
 		for(auto hex : bFieldType.getInfo()->impassableHexes)
-			ret[hex] = EAccessibility::UNAVAILABLE;
+			ret[hex.toInt()] = EAccessibility::UNAVAILABLE;
 	}
 
 	//gate -> should be before stacks
@@ -990,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
@@ -1020,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;
 		}
 	}
 
@@ -1029,15 +1026,15 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
 
 AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * stack) const
 {
-	return getAccessibility(& battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide()));
+	return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide()));
 }
 
-AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray * accessibleHexes) const
+AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray & accessibleHexes) const
 {
 	auto ret = getAccessibility();
-	for(auto hex : *accessibleHexes)
+	for(auto hex : accessibleHexes)
 		if(hex.isValid())
-			ret[hex] = EAccessibility::ACCESSIBLE;
+			ret[hex.toInt()] = EAccessibility::ACCESSIBLE;
 
 	return ret;
 }
@@ -1062,7 +1059,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
 
 	//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++)
@@ -1077,7 +1074,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
 		if(isInObstacle(curHex, obstacles, checkParams))
 			continue;
 
-		const int costToNeighbour = ret.distances.at(curHex) + 1;
+		const int costToNeighbour = ret.distances.at(curHex.toInt()) + 1;
 
 		for(BattleHex neighbour : curHex.getNeighbouringTiles())
 		{
@@ -1085,7 +1082,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
 
 			if(params.bypassEnemyStacks)
 			{
-				auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour);
+				auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour.toInt());
 
 				if(enemyToBypass >= 0)
 				{
@@ -1093,13 +1090,13 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
 				}
 			}
 
-			const int costFoundSoFar = ret.distances[neighbour];
+			const int costFoundSoFar = ret.distances[neighbour.toInt()];
 
-			if(accessibleCache[neighbour] && costToNeighbour + additionalCost < costFoundSoFar)
+			if(accessibleCache[neighbour.toInt()] && costToNeighbour + additionalCost < costFoundSoFar)
 			{
 				hexq.push(neighbour);
-				ret.distances[neighbour] = costToNeighbour + additionalCost;
-				ret.predecessors[neighbour] = curHex;
+				ret.distances[neighbour.toInt()] = costToNeighbour + additionalCost;
+				ret.predecessors[neighbour.toInt()] = curHex;
 			}
 		}
 	}
@@ -1181,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);
 			}
 	}
@@ -1272,9 +1269,12 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
 		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);
 	}
@@ -1283,7 +1283,7 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
 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++)
 	{
@@ -1326,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())

+ 1 - 1
lib/battle/CBattleInfoCallback.h

@@ -162,7 +162,7 @@ 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 BattleHexArray * 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

+ 7 - 3
lib/battle/CObstacleInstance.cpp

@@ -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);
@@ -215,9 +219,9 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
 		JsonArraySerializer customSizeJson = handler.enterArray("customSize");
 		customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER);
 
-		BattleHex hex;
 		for(size_t index = 0; index < customSizeJson.size(); index++)
 		{
+			si16 hex = customSize.at(index).toInt();
 			customSizeJson.serializeInt(index, hex);
 			customSize.set(index, hex);
 		}

+ 3 - 1
lib/battle/CUnitState.cpp

@@ -798,7 +798,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();

+ 3 - 3
lib/battle/ReachabilityInfo.cpp

@@ -33,7 +33,7 @@ ReachabilityInfo::ReachabilityInfo()
 
 bool ReachabilityInfo::isReachable(BattleHex hex) const
 {
-	return distances[hex] < INFINITE_DIST;
+	return distances[hex.toInt()] < INFINITE_DIST;
 }
 
 uint32_t ReachabilityInfo::distToNearestNeighbour(
@@ -46,9 +46,9 @@ uint32_t ReachabilityInfo::distToNearestNeighbour(
 	{
 		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;
 			}

+ 1 - 1
lib/battle/ReachabilityInfo.h

@@ -33,7 +33,7 @@ struct DLL_LINKAGE ReachabilityInfo
 		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
 		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)
-		std::array<int8_t, GameConstants::BFIELD_SIZE> destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex)
+		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

+ 11 - 9
lib/battle/Unit.cpp

@@ -53,7 +53,7 @@ const IBonusBearer* Unit::getBonusBearer() 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());
 }
@@ -63,7 +63,7 @@ const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex
 	if(!twoHex)
 		return position.getNeighbouringTiles();
 
-	return position.getNeighbouringTilesDblWide(side);
+	return position.getNeighbouringTilesDoubleWide(side);
 }
 
 BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
@@ -112,8 +112,8 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS
 	static BattleHexArray::ArrayOfBattleHexArrays precomputed[4];
 	int index = side == BattleSide::ATTACKER ? 0 : 2;
 
-	if(!precomputed[index + twoHex][assumedPos].empty())
-		return precomputed[index + twoHex][assumedPos];
+	if(!precomputed[index + twoHex][assumedPos.toInt()].empty())
+		return precomputed[index + twoHex][assumedPos.toInt()];
 
 	// first run, compute
 
@@ -123,9 +123,9 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS
 	if(twoHex)
 		hexes.insert(occupiedHex(assumedPos, twoHex, side));
 
-	precomputed[index + twoHex][assumedPos] = std::move(hexes);
+	precomputed[index + twoHex][assumedPos.toInt()] = std::move(hexes);
 
-	return precomputed[index + twoHex][assumedPos];
+	return precomputed[index + twoHex][assumedPos.toInt()];
 }
 
 BattleHex Unit::occupiedHex() const
@@ -143,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
 	{
@@ -201,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);
 }
 

+ 3 - 2
lib/battle/Unit.h

@@ -84,11 +84,14 @@ public:
 	bool isTurret() const;
 	virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
 
+	virtual bool isHypnotized() const = 0;
+
 	virtual bool isClone() const = 0;
 	virtual bool hasClone() const = 0;
 
 	virtual bool canCast() const = 0;
 	virtual bool isCaster() const = 0;
+	virtual bool canShootBlocked() const = 0;
 	virtual bool canShoot() const = 0;
 	virtual bool isShooter() const = 0;
 
@@ -112,8 +115,6 @@ public:
 	virtual BattleHex getPosition() const = 0;
 	virtual void setPosition(BattleHex hex) = 0;
 
-	virtual int32_t getInitiative(int turn = 0) const = 0;
-
 	virtual bool canMove(int turn = 0) const = 0; //if stack can move
 	virtual bool defended(int turn = 0) const = 0;
 	virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn

+ 1 - 1
lib/bonuses/Limiters.cpp

@@ -238,7 +238,7 @@ JsonNode UnitOnHexLimiter::toJsonNode() const
 
 	root["type"].String() = "UNIT_ON_HEXES";
 	for(auto hex : applicableHexes)
-		root["parameters"].Vector().emplace_back(hex);
+		root["parameters"].Vector().emplace_back(hex.toInt());
 
 	return root;
 }

+ 1 - 1
lib/spells/BattleSpellMechanics.cpp

@@ -517,7 +517,7 @@ BattleHexArray BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) con
 
 	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);

+ 8 - 1
lib/spells/CSpellHandler.cpp

@@ -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)

+ 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())
 		{

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

@@ -43,9 +43,9 @@ static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string
 			JsonArraySerializer inner = outer.enterArray(outerIndex);
 			inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER);
 
-			BattleHex hex;
 			for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
 			{
+				si16 hex = moatHexes.at(outerIndex).at(innerIndex).toInt();
 				inner.serializeInt(innerIndex, hex);
 				moatHexes.at(outerIndex).set(innerIndex, hex);
 			}

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

@@ -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;

+ 2 - 2
scripting/lua/LuaSpellEffect.cpp

@@ -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(static_cast<si16>(dest.hexValue));
+		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(static_cast<si16>(dest.hexValue));
+		targetData.Vector().emplace_back(dest.hexValue.toInt());
 
 		if(dest.unitValue)
 			targetData.Vector().emplace_back(dest.unitValue->unitId());

+ 4 - 4
test/scripting/LuaSpellEffectAPITest.cpp

@@ -84,7 +84,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField)
 	BattleHex hex(2,2);
 
 	JsonNode first;
-	first.Vector().emplace_back(static_cast<si16>(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(static_cast<si16>(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(static_cast<si16>(hex1));
+	unit.Vector().emplace_back(hex1.toInt());
 	unit.Vector().emplace_back(42);
 
 	BattleHex hex2(5,4);
 
 	JsonNode destination;
-	destination.Vector().emplace_back(static_cast<si16>(hex2));
+	destination.Vector().emplace_back(hex2.toInt());
 	destination.Vector().emplace_back(-1);
 
 	JsonNode targets;

+ 3 - 3
test/scripting/LuaSpellEffectTest.cpp

@@ -154,11 +154,11 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected)
 
 
 	JsonNode first;
-	first.Vector().emplace_back(static_cast<si16>(hex1));
+	first.Vector().emplace_back(hex1.toInt());
 	first.Vector().emplace_back(id1);
 
 	JsonNode second;
-	second.Vector().emplace_back(static_cast<si16>(hex2));
+	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(static_cast<si16>(hex1));
+	first.Vector().emplace_back(hex1.toInt());
 	first.Vector().emplace_back(id1);
 
 	JsonNode targets;