Sfoglia il codice sorgente

Merge branch 'develop' into split-client+develop

Simeon Manolov 1 anno fa
parent
commit
e9131538dd
100 ha cambiato i file con 1141 aggiunte e 861 eliminazioni
  1. 5 0
      .github/workflows/github.yml
  2. 8 1
      AI/BattleAI/AttackPossibility.cpp
  3. 10 1
      AI/BattleAI/BattleAI.cpp
  4. 101 44
      AI/BattleAI/BattleEvaluator.cpp
  5. 7 16
      AI/BattleAI/BattleEvaluator.h
  6. 294 129
      AI/BattleAI/BattleExchangeVariant.cpp
  7. 11 6
      AI/BattleAI/BattleExchangeVariant.h
  8. 2 2
      AI/BattleAI/StackWithBonuses.cpp
  9. 1 1
      AI/BattleAI/StackWithBonuses.h
  10. 2 3
      AI/Nullkiller/AIGateway.cpp
  11. 8 0
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  12. 1 1
      AI/VCAI/BuildingManager.cpp
  13. 1 3
      AI/VCAI/VCAI.cpp
  14. 4 4
      CCallback.cpp
  15. 4 5
      CCallback.h
  16. 12 0
      ChangeLog.md
  17. 10 0
      Mods/vcmi/config/vcmi/chinese.json
  18. 1 1
      Mods/vcmi/config/vcmi/german.json
  19. 25 13
      client/CPlayerInterface.cpp
  20. 1 1
      client/CPlayerInterface.h
  21. 7 61
      client/CServerHandler.cpp
  22. 0 3
      client/CServerHandler.h
  23. 12 69
      client/Client.cpp
  24. 1 7
      client/Client.h
  25. 2 3
      client/NetPacksClient.cpp
  26. 3 0
      client/globalLobby/GlobalLobbyLoginWindow.cpp
  27. 1 1
      client/globalLobby/GlobalLobbyWidget.cpp
  28. 2 2
      client/lobby/CBonusSelection.cpp
  29. 1 1
      client/lobby/CSelectionBase.cpp
  30. 0 1
      client/mainmenu/CMainMenu.cpp
  31. 61 20
      client/mainmenu/CStatisticScreen.cpp
  32. 2 3
      client/mainmenu/CStatisticScreen.h
  33. 2 2
      client/widgets/CComponent.cpp
  34. 2 2
      client/widgets/MiscWidgets.cpp
  35. 11 12
      client/widgets/markets/CAltarArtifacts.cpp
  36. 1 1
      client/widgets/markets/CAltarArtifacts.h
  37. 2 2
      client/widgets/markets/CAltarCreatures.cpp
  38. 3 12
      client/widgets/markets/CArtifactsBuying.cpp
  39. 1 1
      client/widgets/markets/CArtifactsBuying.h
  40. 4 12
      client/widgets/markets/CArtifactsSelling.cpp
  41. 1 1
      client/widgets/markets/CArtifactsSelling.h
  42. 2 2
      client/widgets/markets/CFreelancerGuild.cpp
  43. 2 2
      client/widgets/markets/CMarketResources.cpp
  44. 2 1
      client/widgets/markets/CTransferResources.cpp
  45. 12 11
      client/windows/CCastleInterface.cpp
  46. 1 0
      client/windows/CCreatureWindow.cpp
  47. 2 2
      client/windows/CKingdomInterface.cpp
  48. 31 7
      client/windows/CMarketWindow.cpp
  49. 1 0
      client/windows/CMarketWindow.h
  50. 2 2
      client/windows/GUIClasses.cpp
  51. 1 1
      config/factions/castle.json
  52. 2 2
      config/factions/conflux.json
  53. 2 2
      config/factions/dungeon.json
  54. 1 1
      config/factions/fortress.json
  55. 1 1
      config/factions/inferno.json
  56. 2 2
      config/factions/necropolis.json
  57. 1 1
      config/factions/rampart.json
  58. 2 2
      config/factions/stronghold.json
  59. 2 2
      config/factions/tower.json
  60. 5 0
      config/schemas/townBuilding.json
  61. 6 0
      debian/changelog
  62. 1 1
      docs/Readme.md
  63. 17 1
      docs/modders/Entities_Format/Town_Building_Format.md
  64. 1 0
      launcher/eu.vcmi.VCMI.metainfo.xml
  65. 9 0
      lib/CGameInfoCallback.cpp
  66. 4 2
      lib/CGameInfoCallback.h
  67. 0 3
      lib/CGameInterface.cpp
  68. 0 2
      lib/CGameInterface.h
  69. 3 6
      lib/CMakeLists.txt
  70. 8 16
      lib/IGameCallback.cpp
  71. 4 5
      lib/IGameCallback.h
  72. 1 1
      lib/IGameEventsReceiver.h
  73. 12 2
      lib/battle/AccessibilityInfo.cpp
  74. 2 0
      lib/battle/AccessibilityInfo.h
  75. 23 4
      lib/battle/CBattleInfoCallback.cpp
  76. 2 2
      lib/battle/CUnitState.cpp
  77. 1 1
      lib/battle/CUnitState.h
  78. 2 0
      lib/battle/ReachabilityInfo.h
  79. 5 28
      lib/bonuses/CBonusSystemNode.cpp
  80. 2 2
      lib/bonuses/CBonusSystemNode.h
  81. 3 3
      lib/bonuses/IBonusBearer.cpp
  82. 1 1
      lib/bonuses/IBonusBearer.h
  83. 8 0
      lib/constants/EntityIdentifiers.h
  84. 2 1
      lib/entities/building/CBuilding.h
  85. 5 0
      lib/entities/faction/CTownHandler.cpp
  86. 46 71
      lib/gameState/CGameState.cpp
  87. 1 5
      lib/gameState/CGameState.h
  88. 3 3
      lib/gameState/CGameStateCampaign.cpp
  89. 1 1
      lib/gameState/GameStatistics.cpp
  90. 1 1
      lib/gameState/HighScore.cpp
  91. 71 68
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  92. 2 2
      lib/mapObjectConstructors/CObjectClassesHandler.h
  93. 10 12
      lib/mapObjectConstructors/CommonConstructors.cpp
  94. 5 19
      lib/mapObjects/CGMarket.cpp
  95. 22 25
      lib/mapObjects/CGMarket.h
  96. 54 39
      lib/mapObjects/CGTownInstance.cpp
  97. 14 3
      lib/mapObjects/CGTownInstance.h
  98. 48 19
      lib/mapObjects/IMarket.cpp
  99. 40 10
      lib/mapObjects/IMarket.h
  100. 0 14
      lib/mapObjects/TownBuildingInstance.h

+ 5 - 0
.github/workflows/github.yml

@@ -183,6 +183,11 @@ jobs:
         distribution: 'temurin'
         distribution: 'temurin'
         java-version: '11'
         java-version: '11'
 
 
+    # a hack to build ID for x64 build in order for Google Play to allow upload of both 32 and 64 bit builds
+    - name: Bump Android x64 build ID
+      if: ${{ matrix.platform == 'android-64' }}
+      run: perl -i -pe 's/versionCode (\d+)/$x=$1+1; "versionCode $x"/e' android/vcmi-app/build.gradle
+
     - name: Build Number
     - name: Build Number
       run: |
       run: |
         source '${{github.workspace}}/CI/get_package_name.sh'
         source '${{github.workspace}}/CI/get_package_name.sh'

+ 8 - 1
AI/BattleAI/AttackPossibility.cpp

@@ -201,6 +201,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
 	if(attackInfo.shooting)
 	if(attackInfo.shooting)
 		return 0;
 		return 0;
 
 
+	std::set<uint32_t> checkedUnits;
+
 	auto attacker = attackInfo.attacker;
 	auto attacker = attackInfo.attacker;
 	auto hexes = attacker->getSurroundingHexes(hex);
 	auto hexes = attacker->getSurroundingHexes(hex);
 	for(BattleHex tile : hexes)
 	for(BattleHex tile : hexes)
@@ -208,9 +210,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
 		auto st = state->battleGetUnitByPos(tile, true);
 		auto st = state->battleGetUnitByPos(tile, true);
 		if(!st || !state->battleMatchOwner(st, attacker))
 		if(!st || !state->battleMatchOwner(st, attacker))
 			continue;
 			continue;
+		if(vstd::contains(checkedUnits, st->unitId()))
+			continue;
 		if(!state->battleCanShoot(st))
 		if(!state->battleCanShoot(st))
 			continue;
 			continue;
 
 
+		checkedUnits.insert(st->unitId());
+
 		// FIXME: provide distance info for Jousting bonus
 		// FIXME: provide distance info for Jousting bonus
 		BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
 		BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
 		rangeAttackInfo.defenderPos = hex;
 		rangeAttackInfo.defenderPos = hex;
@@ -220,9 +226,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
 
 
 		auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
 		auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
 		auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
 		auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
+		auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state);
 
 
 		int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
 		int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
-		res += gain;
+		res += gain * cachedDmg / std::max<uint64_t>(1, averageDmg(rangeDmg.damage));
 	}
 	}
 
 
 	return res;
 	return res;

+ 10 - 1
AI/BattleAI/BattleAI.cpp

@@ -23,6 +23,7 @@
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/CObstacleInstance.h"
+#include "../../lib/StartInfo.h"
 #include "../../lib/CStack.h" // TODO: remove
 #include "../../lib/CStack.h" // TODO: remove
                               // Eventually only IBattleInfoCallback and battle::Unit should be used,
                               // Eventually only IBattleInfoCallback and battle::Unit should be used,
                               // CUnitState should be private and CStack should be removed completely
                               // CUnitState should be private and CStack should be removed completely
@@ -122,6 +123,11 @@ static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, BattleSid
 	return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
 	return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
 }
 }
 
 
+int getSimulationTurnsCount(const StartInfo * startInfo)
+{
+	return startInfo->difficulty < 4 ? 2 : 10;
+}
+
 void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 {
 {
 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
@@ -154,7 +160,10 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 		logAi->trace("Build evaluator and targets");
 		logAi->trace("Build evaluator and targets");
 #endif
 #endif
 
 
-		BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
+		BattleEvaluator evaluator(
+			env, cb, stack, playerID, battleID, side, 
+			getStrengthRatio(cb->getBattle(battleID), side),
+			getSimulationTurnsCount(env->game()->getStartInfo()));
 
 
 		result = evaluator.selectStackAction(stack);
 		result = evaluator.selectStackAction(stack);
 
 

+ 101 - 44
AI/BattleAI/BattleEvaluator.cpp

@@ -49,6 +49,45 @@ SpellTypes spellType(const CSpell * spell)
 	return SpellTypes::OTHER;
 	return SpellTypes::OTHER;
 }
 }
 
 
+BattleEvaluator::BattleEvaluator(
+	std::shared_ptr<Environment> env,
+	std::shared_ptr<CBattleCallback> cb,
+	const battle::Unit * activeStack,
+	PlayerColor playerID,
+	BattleID battleID,
+	BattleSide side,
+	float strengthRatio,
+	int simulationTurnsCount)
+	:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
+	cachedAttack(), playerID(playerID), side(side), env(env),
+	cb(cb), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
+{
+	hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
+	damageCache.buildDamageCache(hb, side);
+
+	targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
+	cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
+}
+
+BattleEvaluator::BattleEvaluator(
+	std::shared_ptr<Environment> env,
+	std::shared_ptr<CBattleCallback> cb,
+	std::shared_ptr<HypotheticBattle> hb,
+	DamageCache & damageCache,
+	const battle::Unit * activeStack,
+	PlayerColor playerID,
+	BattleID battleID,
+	BattleSide side,
+	float strengthRatio,
+	int simulationTurnsCount)
+	:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
+	cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb),
+	damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
+{
+	targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
+	cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
+}
+
 std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
 std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
 {
 {
 	std::vector<BattleHex> result;
 	std::vector<BattleHex> result;
@@ -167,7 +206,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 				score
 				score
 			);
 			);
 
 
-			if (moveTarget.scorePerTurn <= score)
+			if (moveTarget.score <= score)
 			{
 			{
 				if(evaluationResult.wait)
 				if(evaluationResult.wait)
 				{
 				{
@@ -197,7 +236,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 	}
 	}
 
 
 	//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
 	//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
-	if(moveTarget.scorePerTurn > score)
+	if(moveTarget.score > score)
 	{
 	{
 		score = moveTarget.score;
 		score = moveTarget.score;
 		cachedAttack = moveTarget.cachedAttack;
 		cachedAttack = moveTarget.cachedAttack;
@@ -206,14 +245,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 		if(stack->waited())
 		if(stack->waited())
 		{
 		{
 			logAi->debug(
 			logAi->debug(
-				"Moving %s towards hex %s[%d], score: %2f/%2f",
+				"Moving %s towards hex %s[%d], score: %2f",
 				stack->getDescription(),
 				stack->getDescription(),
 				moveTarget.cachedAttack->attack.defender->getDescription(),
 				moveTarget.cachedAttack->attack.defender->getDescription(),
 				moveTarget.cachedAttack->attack.defender->getPosition().hex,
 				moveTarget.cachedAttack->attack.defender->getPosition().hex,
-				moveTarget.score,
-				moveTarget.scorePerTurn);
+				moveTarget.score);
 
 
-			return goTowardsNearest(stack, moveTarget.positions);
+			return goTowardsNearest(stack, moveTarget.positions, *targets);
 		}
 		}
 		else
 		else
 		{
 		{
@@ -235,7 +273,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 			if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
 			if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
 				return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
 				return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
 			else
 			else
-				return goTowardsNearest(stack, brokenWallMoat);
+				return goTowardsNearest(stack, brokenWallMoat, *targets);
 		}
 		}
 	}
 	}
 
 
@@ -249,7 +287,32 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
 	return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
 	return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
 }
 }
 
 
-BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
+BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets)
+{
+	auto additionalScore = 0;
+	std::optional<AttackPossibility> attackOnTheWay;
+
+	for(auto & target : targets.possibleAttacks)
+	{
+		if(!target.attack.shooting && target.from == hex && target.attackValue() > additionalScore)
+		{
+			additionalScore = target.attackValue();
+			attackOnTheWay = target;
+		}
+	}
+
+	if(attackOnTheWay)
+	{
+		activeActionMade = true;
+		return BattleAction::makeMeleeAttack(stack, attackOnTheWay->attack.defender->getPosition(), attackOnTheWay->from);
+	}
+	else
+	{
+		return BattleAction::makeMove(stack, hex);
+	}
+}
+
+BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
 {
 {
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto reachability = cb->getBattle(battleID)->getReachability(stack);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
 	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
@@ -261,49 +324,38 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 
 
 	std::vector<BattleHex> targetHexes = hexes;
 	std::vector<BattleHex> targetHexes = hexes;
 
 
-	for(int i = 0; i < 5; i++)
-	{
-		std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
-			{
-				return reachability.distances[h1] < reachability.distances[h2];
-			});
+	vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
 
 
-		for(auto hex : targetHexes)
+	std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
 		{
 		{
-			if(vstd::contains(avHexes, hex))
-			{
-				return BattleAction::makeMove(stack, hex);
-			}
-
-			if(stack->coversPos(hex))
-			{
-				logAi->warn("Warning: already standing on neighbouring tile!");
-				//We shouldn't even be here...
-				return BattleAction::makeDefend(stack);
-			}
-		}
-
-		if(reachability.distances[targetHexes.front()] <= GameConstants::BFIELD_SIZE)
-		{
-			break;
-		}
-
-		std::vector<BattleHex> copy = targetHexes;
-
-		for(auto hex : copy)
-			vstd::concatenate(targetHexes, hex.allNeighbouringTiles());
-
-		vstd::erase_if(targetHexes, [](const BattleHex & hex) {return !hex.isValid();});
-		vstd::removeDuplicates(targetHexes);
-	}
+			return reachability.distances[h1] < reachability.distances[h2];
+		});
 
 
 	BattleHex bestNeighbor = targetHexes.front();
 	BattleHex bestNeighbor = targetHexes.front();
 
 
 	if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
 	if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
 	{
 	{
+		logAi->trace("No richable hexes.");
 		return BattleAction::makeDefend(stack);
 		return BattleAction::makeDefend(stack);
 	}
 	}
 
 
+	// this turn
+	for(auto hex : targetHexes)
+	{
+		if(vstd::contains(avHexes, hex))
+		{
+			return moveOrAttack(stack, hex, targets);
+		}
+
+		if(stack->coversPos(hex))
+		{
+			logAi->warn("Warning: already standing on neighbouring hex!");
+			//We shouldn't even be here...
+			return BattleAction::makeDefend(stack);
+		}
+	}
+
+	// not this turn
 	scoreEvaluator.updateReachabilityMap(hb);
 	scoreEvaluator.updateReachabilityMap(hb);
 
 
 	if(stack->hasBonusOfType(BonusType::FLYING))
 	if(stack->hasBonusOfType(BonusType::FLYING))
@@ -343,7 +395,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 			return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
 			return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
 		});
 		});
 
 
-		return BattleAction::makeMove(stack, *nearestAvailableHex);
+		return moveOrAttack(stack, *nearestAvailableHex, targets);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -357,11 +409,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 
 
 			if(vstd::contains(avHexes, currentDest)
 			if(vstd::contains(avHexes, currentDest)
 				&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
 				&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
-				return BattleAction::makeMove(stack, currentDest);
+			{
+				return moveOrAttack(stack, currentDest, targets);
+			}
 
 
 			currentDest = reachability.predecessors[currentDest];
 			currentDest = reachability.predecessors[currentDest];
 		}
 		}
 	}
 	}
+	
+	logAi->error("We should either detect that hexes are unreachable or make a move!");
+	return BattleAction::makeDefend(stack);
 }
 }
 
 
 bool BattleEvaluator::canCastSpell()
 bool BattleEvaluator::canCastSpell()
@@ -600,7 +657,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 #endif
 #endif
 
 
 					PotentialTargets innerTargets(activeStack, innerCache, state);
 					PotentialTargets innerTargets(activeStack, innerCache, state);
-					BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
+					BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
 
 
 					if(!innerTargets.possibleAttacks.empty())
 					if(!innerTargets.possibleAttacks.empty())
 					{
 					{

+ 7 - 16
AI/BattleAI/BattleEvaluator.h

@@ -37,16 +37,18 @@ class BattleEvaluator
 	float cachedScore;
 	float cachedScore;
 	DamageCache damageCache;
 	DamageCache damageCache;
 	float strengthRatio;
 	float strengthRatio;
+	int simulationTurnsCount;
 
 
 public:
 public:
 	BattleAction selectStackAction(const CStack * stack);
 	BattleAction selectStackAction(const CStack * stack);
 	bool attemptCastingSpell(const CStack * stack);
 	bool attemptCastingSpell(const CStack * stack);
 	bool canCastSpell();
 	bool canCastSpell();
 	std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
 	std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
-	BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes);
+	BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
 	std::vector<BattleHex> getBrokenWallMoatHexes() const;
 	std::vector<BattleHex> getBrokenWallMoatHexes() const;
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
 	void print(const std::string & text) const;
 	void print(const std::string & text) const;
+	BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
 
 
 	BattleEvaluator(
 	BattleEvaluator(
 		std::shared_ptr<Environment> env,
 		std::shared_ptr<Environment> env,
@@ -55,15 +57,8 @@ public:
 		PlayerColor playerID,
 		PlayerColor playerID,
 		BattleID battleID,
 		BattleID battleID,
 		BattleSide side,
 		BattleSide side,
-		float strengthRatio)
-		:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio), battleID(battleID)
-	{
-		hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
-		damageCache.buildDamageCache(hb, side);
-
-		targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
-		cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
-	}
+		float strengthRatio,
+		int simulationTurnsCount);
 
 
 	BattleEvaluator(
 	BattleEvaluator(
 		std::shared_ptr<Environment> env,
 		std::shared_ptr<Environment> env,
@@ -74,10 +69,6 @@ public:
 		PlayerColor playerID,
 		PlayerColor playerID,
 		BattleID battleID,
 		BattleID battleID,
 		BattleSide side,
 		BattleSide side,
-		float strengthRatio)
-		:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID)
-	{
-		targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
-		cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
-	}
+		float strengthRatio,
+		int simulationTurnsCount);
 };
 };

+ 294 - 129
AI/BattleAI/BattleExchangeVariant.cpp

@@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
 }
 }
 
 
 MoveTarget::MoveTarget()
 MoveTarget::MoveTarget()
-	: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
+	: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
 {
 {
 	turnsToRich = 1;
 	turnsToRich = 1;
 }
 }
@@ -42,7 +42,7 @@ float BattleExchangeVariant::trackAttack(
 	for(auto affectedUnit : affectedUnits)
 	for(auto affectedUnit : affectedUnits)
 	{
 	{
 		auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
 		auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
-		auto damageDealt = unitToUpdate->getTotalHealth() - affectedUnit->getTotalHealth();
+		auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
 
 
 		if(damageDealt > 0)
 		if(damageDealt > 0)
 		{
 		{
@@ -58,7 +58,7 @@ float BattleExchangeVariant::trackAttack(
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
 				logAi->trace(
 				logAi->trace(
 					"%s -> %s, ap retaliation, %s, dps: %lld",
 					"%s -> %s, ap retaliation, %s, dps: %lld",
-					ap.attack.defender->getDescription(),
+					hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
 					ap.attack.attacker->getDescription(),
 					ap.attack.attacker->getDescription(),
 					ap.attack.shooting ? "shot" : "mellee",
 					ap.attack.shooting ? "shot" : "mellee",
 					damageDealt);
 					damageDealt);
@@ -277,6 +277,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
 	return result;
 	return result;
 }
 }
 
 
+ReachabilityInfo getReachabilityWithEnemyBypass(
+	const battle::Unit * activeStack,
+	DamageCache & damageCache,
+	std::shared_ptr<HypotheticBattle> state)
+{
+	ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
+
+	if(!params.flying)
+	{
+		for(const auto * unit : state->battleAliveUnits())
+		{
+			if(unit->unitSide() == activeStack->unitSide())
+				continue;
+
+			auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
+			auto turnsToKill = unit->getAvailableHealth() / dmg;
+
+			vstd::amin(turnsToKill, 100);
+
+			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.bypassEnemyStacks = true;
+	}
+
+	return state->getReachability(params);
+}
+
 MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 	const battle::Unit * activeStack,
 	const battle::Unit * activeStack,
 	PotentialTargets & targets,
 	PotentialTargets & targets,
@@ -286,6 +316,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 	MoveTarget result;
 	MoveTarget result;
 	BattleExchangeVariant ev;
 	BattleExchangeVariant ev;
 
 
+	logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
+
 	if(targets.unreachableEnemies.empty())
 	if(targets.unreachableEnemies.empty())
 		return result;
 		return result;
 
 
@@ -296,17 +328,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 
 
 	updateReachabilityMap(hb);
 	updateReachabilityMap(hb);
 
 
-	auto dists = cb->getReachability(activeStack);
+	auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
+	auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
 
 
 	for(const battle::Unit * enemy : targets.unreachableEnemies)
 	for(const battle::Unit * enemy : targets.unreachableEnemies)
 	{
 	{
-		std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
-		auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
-			{
-				return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
-			});
+		logAi->trace(
+			"Checking movement towards %d of %s",
+			enemy->getCount(),
+			enemy->creatureId().toCreature()->getNameSingularTranslated());
 
 
-		auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
+		auto distance = dists.distToNearestNeighbour(activeStack, enemy);
 
 
 		if(distance >= GameConstants::BFIELD_SIZE)
 		if(distance >= GameConstants::BFIELD_SIZE)
 			continue;
 			continue;
@@ -315,30 +347,84 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 			continue;
 			continue;
 
 
 		auto turnsToRich = (distance - 1) / speed + 1;
 		auto turnsToRich = (distance - 1) / speed + 1;
-		auto hexes = closestStack->getSurroundingHexes();
-		auto enemySpeed = closestStack->getMovementRange();
+		auto hexes = enemy->getSurroundingHexes();
+		auto enemySpeed = enemy->getMovementRange();
 		auto speedRatio = speed / static_cast<float>(enemySpeed);
 		auto speedRatio = speed / static_cast<float>(enemySpeed);
 		auto multiplier = speedRatio > 1 ? 1 : speedRatio;
 		auto multiplier = speedRatio > 1 ? 1 : speedRatio;
 
 
-		if(enemy->canShoot())
-			multiplier *= 1.5f;
-
-		for(auto hex : hexes)
+		for(auto & hex : hexes)
 		{
 		{
 			// FIXME: provide distance info for Jousting bonus
 			// FIXME: provide distance info for Jousting bonus
-			auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
+			auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
 			auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
 			auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
 
 
 			attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
 			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, turnsToRich, targets, damageCache, hb);
-			auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
 
 
-			if(result.scorePerTurn < scoreValue(scorePerTurn))
+			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));
+#endif
+
+			if(result.score < scoreValue(score)
+				|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
 			{
 			{
-				result.scorePerTurn = scoreValue(scorePerTurn);
 				result.score = scoreValue(score);
 				result.score = scoreValue(score);
-				result.positions = closestStack->getAttackableHexes(activeStack);
+				result.positions.clear();
+
+#if BATTLE_TRACE_LEVEL >= 1
+				logAi->trace("New high score");
+#endif
+
+				for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
+				{
+					while(!flying && dists.distances[enemyHex] > speed)
+					{
+						enemyHex = dists.predecessors.at(enemyHex);
+						if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
+						{
+							auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
+
+							if(defenderToBypass)
+							{
+#if BATTLE_TRACE_LEVEL >= 1
+								logAi->trace("Found target to bypass at %d", enemyHex.hex);
+#endif
+
+								auto attackHex = dists.predecessors[enemyHex];
+								auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
+								auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
+
+								auto adjacentStacks = getAdjacentUnits(enemy);
+
+								adjacentStacks.push_back(defenderToBypass);
+								vstd::removeDuplicates(adjacentStacks);
+
+								auto bypassScore = calculateExchange(
+									attackBypass,
+									dists.distances[attackHex],
+									targets,
+									damageCache,
+									hb,
+									adjacentStacks);
+
+								if(scoreValue(bypassScore) > result.score)
+								{
+									result.score = scoreValue(bypassScore);
+
+#if BATTLE_TRACE_LEVEL >= 1
+									logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
+#endif
+								}
+							}
+						}
+					}
+
+					result.positions.push_back(enemyHex);
+				}
+
 				result.cachedAttack = attack;
 				result.cachedAttack = attack;
 				result.turnsToRich = turnsToRich;
 				result.turnsToRich = turnsToRich;
 			}
 			}
@@ -382,7 +468,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 	const AttackPossibility & ap,
 	const AttackPossibility & ap,
 	uint8_t turn,
 	uint8_t turn,
 	PotentialTargets & targets,
 	PotentialTargets & targets,
-	std::shared_ptr<HypotheticBattle> hb) const
+	std::shared_ptr<HypotheticBattle> hb,
+	std::vector<const battle::Unit *> additionalUnits) const
 {
 {
 	ReachabilityData result;
 	ReachabilityData result;
 
 
@@ -390,13 +477,26 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 
 
 	if(!ap.attack.shooting) hexes.push_back(ap.from);
 	if(!ap.attack.shooting) hexes.push_back(ap.from);
 
 
-	std::vector<const battle::Unit *> allReachableUnits;
-
+	std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
+	
 	for(auto hex : hexes)
 	for(auto hex : hexes)
 	{
 	{
 		vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
 		vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
 	}
 	}
 
 
+	for(auto hex : ap.attack.attacker->getHexes())
+	{
+		auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
+		for(auto unit : unitsReachingAttacker)
+		{
+			if(unit->unitSide() != ap.attack.attacker->unitSide())
+			{
+				allReachableUnits.push_back(unit);
+				result.enemyUnitsReachingAttacker.insert(unit->unitId());
+			}
+		}
+	}
+
 	vstd::removeDuplicates(allReachableUnits);
 	vstd::removeDuplicates(allReachableUnits);
 
 
 	auto copy = allReachableUnits;
 	auto copy = allReachableUnits;
@@ -432,7 +532,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 
 
 	for(auto unit : allReachableUnits)
 	for(auto unit : allReachableUnits)
 	{
 	{
-		auto accessible = !unit->canShoot();
+		auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
 
 
 		if(!accessible)
 		if(!accessible)
 		{
 		{
@@ -456,14 +556,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
 		for(auto unit : turnOrder[turn])
 		for(auto unit : turnOrder[turn])
 		{
 		{
 			if(vstd::contains(allReachableUnits, unit))
 			if(vstd::contains(allReachableUnits, unit))
-				result.units.push_back(unit);
+				result.units[turn].push_back(unit);
 		}
 		}
-	}
 
 
-	vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
-		{
-			return !hb->battleGetUnitByID(u->unitId())->alive();
-		});
+		vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
+			{
+				return !hb->battleGetUnitByID(u->unitId())->alive();
+			});
+	}
 
 
 	return result;
 	return result;
 }
 }
@@ -494,7 +594,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
 	uint8_t turn,
 	uint8_t turn,
 	PotentialTargets & targets,
 	PotentialTargets & targets,
 	DamageCache & damageCache,
 	DamageCache & damageCache,
-	std::shared_ptr<HypotheticBattle> hb) const
+	std::shared_ptr<HypotheticBattle> hb,
+	std::vector<const battle::Unit *> additionalUnits) const
 {
 {
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
 	logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
 	logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
@@ -513,7 +614,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
 	if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
 	if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
 		enemyStacks.push_back(ap.attack.defender);
 		enemyStacks.push_back(ap.attack.defender);
 
 
-	ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
+	ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
 
 
 	if(exchangeUnits.units.empty())
 	if(exchangeUnits.units.empty())
 	{
 	{
@@ -523,22 +624,25 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
 	auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
 	auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
 	BattleExchangeVariant v;
 	BattleExchangeVariant v;
 
 
-	for(auto unit : exchangeUnits.units)
+	for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
 	{
 	{
-		if(unit->isTurret())
-			continue;
+		for(auto unit : exchangeUnits.units.at(exchangeTurn))
+		{
+			if(unit->isTurret())
+				continue;
 
 
-		bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
-		auto & attackerQueue = isOur ? ourStacks : enemyStacks;
-		auto u = exchangeBattle->getForUpdate(unit->unitId());
+			bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
+			auto & attackerQueue = isOur ? ourStacks : enemyStacks;
+			auto u = exchangeBattle->getForUpdate(unit->unitId());
 
 
-		if(u->alive() && !vstd::contains(attackerQueue, unit))
-		{
-			attackerQueue.push_back(unit);
+			if(u->alive() && !vstd::contains(attackerQueue, unit))
+			{
+				attackerQueue.push_back(unit);
 
 
 #if BATTLE_TRACE_LEVEL
 #if BATTLE_TRACE_LEVEL
-			logAi->trace("Exchanging: %s", u->getDescription());
+				logAi->trace("Exchanging: %s", u->getDescription());
 #endif
 #endif
+			}
 		}
 		}
 	}
 	}
 
 
@@ -552,122 +656,166 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
 
 
 	bool canUseAp = true;
 	bool canUseAp = true;
 
 
-	for(auto activeUnit : exchangeUnits.units)
-	{
-		bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
-		battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
-		battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
+	std::set<uint32_t> blockedShooters;
 
 
-		auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
+	int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
+		? simulationTurnsCount
+		: turn + turnOrder.size();
+
+	for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
+	{
+		bool isMovingTurm = exchangeTurn < turn;
+		int queueTurn = exchangeTurn >= exchangeUnits.units.size()
+			? exchangeUnits.units.size() - 1
+			: exchangeTurn;
 
 
-		if(!attacker->alive())
+		for(auto activeUnit : exchangeUnits.units.at(queueTurn))
 		{
 		{
+			bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
+			battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
+			battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
+
+			auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
+			auto shooting = exchangeBattle->battleCanShoot(attacker.get())
+				&& !vstd::contains(blockedShooters, attacker->unitId());
+
+			if(!attacker->alive())
+			{
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
-			logAi->trace(	"Attacker is dead");
+				logAi->trace("Attacker is dead");
 #endif
 #endif
 
 
-			continue;
-		}
-
-		auto targetUnit = ap.attack.defender;
+				continue;
+			}
 
 
-		if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
-		{
-			auto estimateAttack = [&](const battle::Unit * u) -> float
+			if(isMovingTurm && !shooting
+				&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
 			{
 			{
-				auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
-				auto score = v.trackAttack(
-					attacker,
-					stackWithBonuses,
-					exchangeBattle->battleCanShoot(stackWithBonuses.get()),
-					isOur,
-					damageCache,
-					hb,
-					true);
-
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
-				logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
+				logAi->trace("Attacker is moving");
 #endif
 #endif
 
 
-				return score;
-			};
+				continue;
+			}
 
 
-			auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
+			auto targetUnit = ap.attack.defender;
 
 
-			vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
-				{
-					return vstd::contains(exchangeUnits.shooters, u);
-				});
-
-			if(!unitsInOppositeQueueExceptInaccessible.empty())
+			if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
 			{
 			{
-				targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
-			}
-			else
-			{
-				auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
-					{
-						if(u->unitSide() == attacker->unitSide())
-							return false;
+#if BATTLE_TRACE_LEVEL>=2
+				logAi->trace("Best target selector for %s", attacker->getDescription());
+#endif
+				auto estimateAttack = [&](const battle::Unit * u) -> float
+				{
+					auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
+					auto score = v.trackAttack(
+						attacker,
+						stackWithBonuses,
+						exchangeBattle->battleCanShoot(stackWithBonuses.get()),
+						isOur,
+						damageCache,
+						hb,
+						true);
+
+#if BATTLE_TRACE_LEVEL>=2
+					logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
+#endif
 
 
-						if(!exchangeBattle->getForUpdate(u->unitId())->alive())
-							return false;
+					return score;
+				};
 
 
-						if (!u->getPosition().isValid())
-							return false; // e.g. tower shooters
+				auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
 
 
-						return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
-							{
-								return attacker->unitId() == other->unitId();
-							});
+				vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
+					{
+						return vstd::contains(exchangeUnits.shooters, u);
 					});
 					});
 
 
-				if(!reachable.empty())
+				if(!isOur
+					&& exchangeTurn == 0
+					&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
+					&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
+				{
+					vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
+						{
+							return u->unitId() == ap.attack.attacker->unitId();
+						});
+				}
+
+				if(!unitsInOppositeQueueExceptInaccessible.empty())
 				{
 				{
-					targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
+					targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
 				}
 				}
 				else
 				else
 				{
 				{
+					auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
+						{
+							if(u->unitSide() == attacker->unitSide())
+								return false;
+
+							if(!exchangeBattle->getForUpdate(u->unitId())->alive())
+								return false;
+
+							if(!u->getPosition().isValid())
+								return false; // e.g. tower shooters
+
+							return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
+								{
+									return attacker->unitId() == other->unitId();
+								});
+						});
+
+					if(!reachable.empty())
+					{
+						targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
+					}
+					else
+					{
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
-					logAi->trace("Battle queue is empty and no reachable enemy.");
+						logAi->trace("Battle queue is empty and no reachable enemy.");
 #endif
 #endif
 
 
-					continue;
+						continue;
+					}
 				}
 				}
 			}
 			}
-		}
 
 
-		auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
-		auto shooting = exchangeBattle->battleCanShoot(attacker.get());
-		const int totalAttacks = attacker->getTotalAttacks(shooting);
+			auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
+			const int totalAttacks = attacker->getTotalAttacks(shooting);
 
 
-		if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
-			&& targetUnit->unitId() == ap.attack.defender->unitId())
-		{
-			v.trackAttack(ap, exchangeBattle, damageCache);
-		}
-		else
-		{
-			for(int i = 0; i < totalAttacks; i++)
+			if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
+				&& targetUnit->unitId() == ap.attack.defender->unitId())
 			{
 			{
-				v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
+				v.trackAttack(ap, exchangeBattle, damageCache);
+			}
+			else
+			{
+				for(int i = 0; i < totalAttacks; i++)
+				{
+					v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
 
 
-				if(!attacker->alive() || !defender->alive())
-					break;
+					if(!attacker->alive() || !defender->alive())
+						break;
+				}
 			}
 			}
-		}
 
 
-		canUseAp = false;
+			if(!shooting)
+				blockedShooters.insert(defender->unitId());
 
 
-		vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
-			{
-				return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
-			});
+			canUseAp = false;
 
 
-		vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
-			{
-				return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
-			});
+			vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
+				{
+					return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
+				});
+
+			vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
+				{
+					return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
+				});
+		}
+
+		exchangeBattle->nextRound();
 	}
 	}
 
 
 	// avoid blocking path for stronger stack by weaker stack
 	// avoid blocking path for stronger stack by weaker stack
@@ -679,11 +827,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
 	for(auto hex : hexes)
 	for(auto hex : hexes)
 		reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
 		reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
 
 
+	auto score = v.getScore();
+
+	if(simulationTurnsCount < totalTurnsCount)
+	{
+		float scalingRatio = simulationTurnsCount / static_cast<float>(totalTurnsCount);
+
+		score.enemyDamageReduce *= scalingRatio;
+		score.ourDamageReduce *= scalingRatio;
+	}
+
+	if(turn > 0)
+	{
+		auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
+
+		score.enemyDamageReduce *= turnMultiplier;
+	}
+
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
-	logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
+	logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
 #endif
 #endif
 
 
-	return v.getScore();
+	return score;
 }
 }
 
 
 bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
 bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
@@ -756,7 +921,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
 
 
 			ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
 			ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
 
 
-			bool reachable = unitReachability.distances[hex] <= radius;
+			bool reachable = unitReachability.distances.at(hex) <= radius;
 
 
 			if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 			if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 			{
 			{
@@ -766,7 +931,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
 				{
 				{
 					for(BattleHex neighbor : hex.neighbouringTiles())
 					for(BattleHex neighbor : hex.neighbouringTiles())
 					{
 					{
-						reachable = unitReachability.distances[neighbor] <= radius;
+						reachable = unitReachability.distances.at(neighbor) <= radius;
 
 
 						if(reachable) break;
 						if(reachable) break;
 					}
 					}
@@ -816,7 +981,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			{
 			{
 				bool enemyUnit = false;
 				bool enemyUnit = false;
-				bool reachable = unitReachability.distances[hex] <= unitSpeed;
+				bool reachable = unitReachability.distances.at(hex) <= unitSpeed;
 
 
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				{
 				{
@@ -828,7 +993,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 
 
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						{
 						{
-							reachable = unitReachability.distances[neighbor] <= unitSpeed;
+							reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
 
 
 							if(reachable) break;
 							if(reachable) break;
 						}
 						}

+ 11 - 6
AI/BattleAI/BattleExchangeVariant.h

@@ -54,7 +54,6 @@ struct AttackerValue
 struct MoveTarget
 struct MoveTarget
 {
 {
 	float score;
 	float score;
-	float scorePerTurn;
 	std::vector<BattleHex> positions;
 	std::vector<BattleHex> positions;
 	std::optional<AttackPossibility> cachedAttack;
 	std::optional<AttackPossibility> cachedAttack;
 	uint8_t turnsToRich;
 	uint8_t turnsToRich;
@@ -64,7 +63,7 @@ struct MoveTarget
 
 
 struct EvaluationResult
 struct EvaluationResult
 {
 {
-	static const int64_t INEFFECTIVE_SCORE = -10000;
+	static const int64_t INEFFECTIVE_SCORE = -100000000;
 
 
 	AttackPossibility bestAttack;
 	AttackPossibility bestAttack;
 	MoveTarget bestMove;
 	MoveTarget bestMove;
@@ -113,13 +112,15 @@ private:
 
 
 struct ReachabilityData
 struct ReachabilityData
 {
 {
-	std::vector<const battle::Unit *> units;
+	std::map<int, std::vector<const battle::Unit *>> units;
 
 
 	// shooters which are within mellee attack and mellee units
 	// shooters which are within mellee attack and mellee units
 	std::vector<const battle::Unit *> melleeAccessible;
 	std::vector<const battle::Unit *> melleeAccessible;
 
 
 	// far shooters
 	// far shooters
 	std::vector<const battle::Unit *> shooters;
 	std::vector<const battle::Unit *> shooters;
+
+	std::set<uint32_t> enemyUnitsReachingAttacker;
 };
 };
 
 
 class BattleExchangeEvaluator
 class BattleExchangeEvaluator
@@ -131,6 +132,7 @@ private:
 	std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
 	std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
 	std::vector<battle::Units> turnOrder;
 	std::vector<battle::Units> turnOrder;
 	float negativeEffectMultiplier;
 	float negativeEffectMultiplier;
+	int simulationTurnsCount;
 
 
 	float scoreValue(const BattleScore & score) const;
 	float scoreValue(const BattleScore & score) const;
 
 
@@ -139,7 +141,8 @@ private:
 		uint8_t turn,
 		uint8_t turn,
 		PotentialTargets & targets,
 		PotentialTargets & targets,
 		DamageCache & damageCache,
 		DamageCache & damageCache,
-		std::shared_ptr<HypotheticBattle> hb) const;
+		std::shared_ptr<HypotheticBattle> hb,
+		std::vector<const battle::Unit *> additionalUnits = {}) const;
 
 
 	bool canBeHitThisTurn(const AttackPossibility & ap);
 	bool canBeHitThisTurn(const AttackPossibility & ap);
 
 
@@ -147,7 +150,8 @@ public:
 	BattleExchangeEvaluator(
 	BattleExchangeEvaluator(
 		std::shared_ptr<CBattleInfoCallback> cb,
 		std::shared_ptr<CBattleInfoCallback> cb,
 		std::shared_ptr<Environment> env,
 		std::shared_ptr<Environment> env,
-		float strengthRatio): cb(cb), env(env) {
+		float strengthRatio,
+		int simulationTurnsCount): cb(cb), env(env), simulationTurnsCount(simulationTurnsCount){
 		negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
 		negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
 	}
 	}
 
 
@@ -171,7 +175,8 @@ public:
 		const AttackPossibility & ap,
 		const AttackPossibility & ap,
 		uint8_t turn,
 		uint8_t turn,
 		PotentialTargets & targets,
 		PotentialTargets & targets,
-		std::shared_ptr<HypotheticBattle> hb) const;
+		std::shared_ptr<HypotheticBattle> hb,
+		std::vector<const battle::Unit *> additionalUnits = {}) const;
 
 
 	bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
 	bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
 
 

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -132,10 +132,10 @@ SlotID StackWithBonuses::unitSlot() const
 }
 }
 
 
 TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
 TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
-	const CBonusSystemNode * root, const std::string & cachingStr) const
+	const std::string & cachingStr) const
 {
 {
 	auto ret = std::make_shared<BonusList>();
 	auto ret = std::make_shared<BonusList>();
-	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
+	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
 
 
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	{
 	{

+ 1 - 1
AI/BattleAI/StackWithBonuses.h

@@ -91,7 +91,7 @@ public:
 
 
 	///IBonusBearer
 	///IBonusBearer
 	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
 	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
-		const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
+		const std::string & cachingStr = "") const override;
 
 
 	int64_t getTreeVersion() const override;
 	int64_t getTreeVersion() const override;
 
 

+ 2 - 3
AI/Nullkiller/AIGateway.cpp

@@ -21,8 +21,6 @@
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/CTypeList.h"
-#include "../../lib/serializer/BinarySerializer.h"
-#include "../../lib/serializer/BinaryDeserializer.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/PacksForServer.h"
 #include "../../lib/networkPacks/PacksForServer.h"
@@ -570,6 +568,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	myCb = CB;
 	myCb = CB;
 	cbc = CB;
 	cbc = CB;
+	this->env = env;
 
 
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	playerID = *myCb->getPlayerID();
 	playerID = *myCb->getPlayerID();
@@ -1490,7 +1489,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 				//TODO trade only as much as needed
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				if (toGive) //don't try to sell 0 resources
 				{
 				{
-					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
+					cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
 					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 				}
 				}

+ 8 - 0
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -89,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap()
 
 
 			heroes[hero->tempOwner][hero] = HeroRole::MAIN;
 			heroes[hero->tempOwner][hero] = HeroRole::MAIN;
 		}
 		}
+
+		if(obj->ID == Obj::TOWN)
+		{
+			auto town = dynamic_cast<const CGTownInstance *>(obj);
+
+			if(town->garrisonHero)
+				heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
+		}
 	}
 	}
 
 
 	auto ourTowns = cb->getTownsInfo();
 	auto ourTowns = cb->getTownsInfo();

+ 1 - 1
AI/VCAI/BuildingManager.cpp

@@ -196,7 +196,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
 		return true;
 		return true;
 
 
 	//workaround for mantis #2696 - build capitol with separate algorithm if it is available
 	//workaround for mantis #2696 - build capitol with separate algorithm if it is available
-	if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
+	if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
 	{
 	{
 		if(tryBuildNextStructure(t, capitolAndRequirements))
 		if(tryBuildNextStructure(t, capitolAndRequirements))
 			return true;
 			return true;

+ 1 - 3
AI/VCAI/VCAI.cpp

@@ -31,8 +31,6 @@
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/PacksForServer.h"
 #include "../../lib/networkPacks/PacksForServer.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/CTypeList.h"
-#include "../../lib/serializer/BinarySerializer.h"
-#include "../../lib/serializer/BinaryDeserializer.h"
 
 
 #include "AIhelper.h"
 #include "AIhelper.h"
 
 
@@ -2130,7 +2128,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 				//TODO trade only as much as needed
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				if (toGive) //don't try to sell 0 resources
 				{
 				{
-					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
+					cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
 					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 				}
 				}

+ 4 - 4
CCallback.cpp

@@ -266,15 +266,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
 	sendRequest(&pack);
 	sendRequest(&pack);
 }
 }
 
 
-void CCallback::trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
+void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
 {
 {
-	trade(market, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
+	trade(marketId, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
 }
 }
 
 
-void CCallback::trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
+void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
 {
 {
 	TradeOnMarketplace pack;
 	TradeOnMarketplace pack;
-	pack.marketId = dynamic_cast<const CGObjectInstance *>(market)->id;
+	pack.marketId = marketId;
 	pack.heroId = hero ? hero->id : ObjectInstanceID();
 	pack.heroId = hero ? hero->id : ObjectInstanceID();
 	pack.mode = mode;
 	pack.mode = mode;
 	pack.r1 = id1;
 	pack.r1 = id1;

+ 4 - 5
CCallback.h

@@ -34,7 +34,6 @@ class IBattleEventsReceiver;
 class IGameEventsReceiver;
 class IGameEventsReceiver;
 struct ArtifactLocation;
 struct ArtifactLocation;
 class BattleStateInfoForRetreat;
 class BattleStateInfoForRetreat;
-class IMarket;
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
@@ -81,8 +80,8 @@ public:
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 
 
-	virtual void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
-	virtual void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
+	virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
+	virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)=0;
 
 
 	virtual int selectionMade(int selection, QueryID queryID) =0;
 	virtual int selectionMade(int selection, QueryID queryID) =0;
 	virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0;
 	virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0;
@@ -190,8 +189,8 @@ public:
 	void endTurn() override;
 	void endTurn() override;
 	void swapGarrisonHero(const CGTownInstance *town) override;
 	void swapGarrisonHero(const CGTownInstance *town) override;
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
-	void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
-	void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
+	void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
+	void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
 	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
 	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
 	void save(const std::string &fname) override;
 	void save(const std::string &fname) override;

+ 12 - 0
ChangeLog.md

@@ -70,6 +70,18 @@
 * Added support for HotA bank building from Factory
 * Added support for HotA bank building from Factory
 * Added support for HotA-style 8th creature in town
 * Added support for HotA-style 8th creature in town
 
 
+# 1.5.6 -> 1.5.7
+
+* Fixed game freeze if player is attacked in online multiplayer game by another player when he has unread dialogs, such as new week notification
+* Fixed possible game crash after being attacked by enemy with artifact that blocks spellcasting
+* Fixed heroes on map limit game setting not respected when moving hero from town garrison.
+* Add workaround to fix possible crash on attempt to start previously generated random map that has players without owned heroes or towns
+* Fixed crash on right-clicking spell icon when receiving unlearnable spells from Pandora
+* Fixed possible text overflow in match information box in online lobby
+* Fixed overlapping text in lobby login window
+* Fixed excessive removal of open dialogs such as new week or map events on new turn
+* Fixed objects like Mystical Gardens not resetting their state on new week correctly
+
 # 1.5.5 -> 1.5.6
 # 1.5.5 -> 1.5.6
 
 
 ### Stability
 ### Stability

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

@@ -72,6 +72,11 @@
 	"vcmi.lobby.noUnderground" : "无地下部分",
 	"vcmi.lobby.noUnderground" : "无地下部分",
 	"vcmi.lobby.sortDate" : "以修改时间排序地图",
 	"vcmi.lobby.sortDate" : "以修改时间排序地图",
 	"vcmi.lobby.backToLobby" : "返回大厅",
 	"vcmi.lobby.backToLobby" : "返回大厅",
+	"vcmi.lobby.author" : "作者",
+	"vcmi.lobby.handicap" : "障碍",
+	"vcmi.lobby.handicap.resource" : "给予玩家起始资源以外的更多资源,允许负值,但总量不会低于0(玩家永远不会能以负资源开始游戏)。",
+	"vcmi.lobby.handicap.income" : "按百分比改变玩家的各种收入,向上取整。",
+	"vcmi.lobby.handicap.growth" : "改变玩家拥有的城镇的生物增长率,向上取整。",
 
 
 	"vcmi.lobby.login.title" : "VCMI大厅",
 	"vcmi.lobby.login.title" : "VCMI大厅",
 	"vcmi.lobby.login.username" : "用户名:",
 	"vcmi.lobby.login.username" : "用户名:",
@@ -237,6 +242,8 @@
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
 	"vcmi.battleOptions.endWithAutocombat.hover": "结束战斗",
 	"vcmi.battleOptions.endWithAutocombat.hover": "结束战斗",
 	"vcmi.battleOptions.endWithAutocombat.help": "{结束战斗}\n\n以自动战斗立即结束剩余战斗过程",
 	"vcmi.battleOptions.endWithAutocombat.help": "{结束战斗}\n\n以自动战斗立即结束剩余战斗过程",
+	"vcmi.battleOptions.showQuickSpell.hover": "展示快捷法术面板",
+	"vcmi.battleOptions.showQuickSpell.help": "{展示快捷法术面板}\n\n展示快捷选择法术的面板。",
 
 
 	"vcmi.adventureMap.revisitObject.hover" : "重新访问",
 	"vcmi.adventureMap.revisitObject.hover" : "重新访问",
 	"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
 	"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
@@ -286,6 +293,9 @@
 	"vcmi.townHall.missingBase"             : "必须先建造基础建筑 %s",
 	"vcmi.townHall.missingBase"             : "必须先建造基础建筑 %s",
 	"vcmi.townHall.noCreaturesToRecruit"    : "没有可供招募的生物。",
 	"vcmi.townHall.noCreaturesToRecruit"    : "没有可供招募的生物。",
 
 
+	"vcmi.townStructure.bank.borrow" : "你走进银行。一位银行职员看到你,说道:“我们为您提供了一个特别优惠。您可以向我们借2500金币,期限为5天。您每天需要偿还500金币。”",
+	"vcmi.townStructure.bank.payBack" : "你走进银行。一位银行职员看到你,说道:“您已经获得了贷款。还清之前,不能再申请新的贷款。”",
+
 	"vcmi.logicalExpressions.anyOf"  : "以下任一前提:",
 	"vcmi.logicalExpressions.anyOf"  : "以下任一前提:",
 	"vcmi.logicalExpressions.allOf"  : "以下所有前提:",
 	"vcmi.logicalExpressions.allOf"  : "以下所有前提:",
 	"vcmi.logicalExpressions.noneOf" : "与此建筑冲突:",
 	"vcmi.logicalExpressions.noneOf" : "与此建筑冲突:",

+ 1 - 1
Mods/vcmi/config/vcmi/german.json

@@ -163,7 +163,7 @@
 	"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
 	"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
 
 
 	"vcmi.statisticWindow.statistics" : "Statistik",
 	"vcmi.statisticWindow.statistics" : "Statistik",
-	"vcmi.statisticWindow.tsvCopy" : "Daten in Zwischenabl.",
+	"vcmi.statisticWindow.tsvCopy" : "In Zwischenablage",
 	"vcmi.statisticWindow.selectView" : "Ansicht wählen",
 	"vcmi.statisticWindow.selectView" : "Ansicht wählen",
 	"vcmi.statisticWindow.value" : "Wert",
 	"vcmi.statisticWindow.value" : "Wert",
 	"vcmi.statisticWindow.title.overview" : "Überblick",
 	"vcmi.statisticWindow.title.overview" : "Überblick",

+ 25 - 13
client/CPlayerInterface.cpp

@@ -99,9 +99,8 @@
 
 
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/pathfinder/CGPathNode.h"
 
 
-#include "../lib/serializer/BinaryDeserializer.h"
-#include "../lib/serializer/BinarySerializer.h"
 #include "../lib/serializer/CTypeList.h"
 #include "../lib/serializer/CTypeList.h"
+#include "../lib/serializer/ESerializationVersion.h"
 
 
 #include "../lib/spells/CSpellHandler.h"
 #include "../lib/spells/CSpellHandler.h"
 
 
@@ -202,6 +201,11 @@ void CPlayerInterface::playerEndsTurn(PlayerColor player)
 	{
 	{
 		makingTurn = false;
 		makingTurn = false;
 		closeAllDialogs();
 		closeAllDialogs();
+
+		// remove all pending dialogs that do not expect query answer
+		vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
+						   return window->ID == QueryID::NONE;
+					   });
 	}
 	}
 }
 }
 
 
@@ -618,9 +622,7 @@ void CPlayerInterface::battleStartBefore(const BattleID & battleID, const CCreat
 {
 {
 	movementController->onBattleStarted();
 	movementController->onBattleStarted();
 
 
-	//Don't wait for dialogs when we are non-active hot-seat player
-	if (LOCPLINT == this)
-		waitForAllDialogs();
+	waitForAllDialogs();
 }
 }
 
 
 void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed)
 void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed)
@@ -643,9 +645,7 @@ void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet
 		cb->registerBattleInterface(autofightingAI);
 		cb->registerBattleInterface(autofightingAI);
 	}
 	}
 
 
-	//Don't wait for dialogs when we are non-active hot-seat player
-	if (LOCPLINT == this)
-		waitForAllDialogs();
+	waitForAllDialogs();
 
 
 	BATTLE_EVENT_POSSIBLE_RETURN;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 }
 }
@@ -1012,7 +1012,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
 	}
 	}
 	std::shared_ptr<CInfoWindow> temp = CInfoWindow::create(text, playerID, components);
 	std::shared_ptr<CInfoWindow> temp = CInfoWindow::create(text, playerID, components);
 
 
-	if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
+	if ((makingTurn || (battleInt && battleInt->curInt && battleInt->curInt.get() == this)) && GH.windows().count() > 0 && LOCPLINT == this)
 	{
 	{
 		CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
 		CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
 		showingDialog->setBusy();
 		showingDialog->setBusy();
@@ -1631,14 +1631,14 @@ void CPlayerInterface::battleNewRoundFirst(const BattleID & battleID)
 	battleInt->newRoundFirst();
 	battleInt->newRoundFirst();
 }
 }
 
 
-void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
+void CPlayerInterface::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	auto onWindowClosed = [this, queryID](){
 	auto onWindowClosed = [this, queryID](){
 		cb->selectionMade(0, queryID);
 		cb->selectionMade(0, queryID);
 	};
 	};
 
 
-	if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && dynamic_cast<const CGArtifactsAltar*>(market) == nullptr)
+	if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && market->getArtifactsStorage() == nullptr)
 	{
 	{
 		// compatibility check, safe to remove for 1.6
 		// compatibility check, safe to remove for 1.6
 		// 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class
 		// 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class
@@ -1653,8 +1653,17 @@ void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInsta
 		GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::CREATURE_EXP);
 		GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::CREATURE_EXP);
 	else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
 	else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
 		GH.windows().createAndPushWindow<CTransformerWindow>(market, visitor, onWindowClosed);
 		GH.windows().createAndPushWindow<CTransformerWindow>(market, visitor, onWindowClosed);
-	else if(!market->availableModes().empty())
-		GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, market->availableModes().front());
+	else if (!market->availableModes().empty())
+		for(auto mode = EMarketMode::RESOURCE_RESOURCE; mode != EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; mode = vstd::next(mode, 1))
+		{
+			if(vstd::contains(market->availableModes(), mode))
+			{
+				GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, mode);
+				break;
+			}
+		}
+	else
+		onWindowClosed();
 }
 }
 
 
 void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
 void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
@@ -1759,6 +1768,9 @@ void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
 
 
 void CPlayerInterface::waitForAllDialogs()
 void CPlayerInterface::waitForAllDialogs()
 {
 {
+	if (!makingTurn)
+		return;
+
 	while(!dialogs.empty())
 	while(!dialogs.empty())
 	{
 	{
 		auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
 		auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);

+ 1 - 1
client/CPlayerInterface.h

@@ -120,7 +120,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
+	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
 	void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
 	void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
 	void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
 	void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
 	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell
 	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell

+ 7 - 61
client/CServerHandler.cpp

@@ -35,6 +35,7 @@
 #include "../lib/TurnTimerInfo.h"
 #include "../lib/TurnTimerInfo.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/campaign/CampaignState.h"
 #include "../lib/campaign/CampaignState.h"
+#include "../lib/gameState/CGameState.h"
 #include "../lib/gameState/HighScore.h"
 #include "../lib/gameState/HighScore.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/mapping/CMapInfo.h"
 #include "../lib/mapping/CMapInfo.h"
@@ -44,7 +45,6 @@
 #include "../lib/rmg/CMapGenOptions.h"
 #include "../lib/rmg/CMapGenOptions.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
-#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
 #include "../lib/serializer/CMemorySerializer.h"
 #include "../lib/serializer/CMemorySerializer.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/UnlockGuard.h"
 
 
@@ -56,61 +56,6 @@
 
 
 #include <vcmi/events/EventBus.h>
 #include <vcmi/events/EventBus.h>
 
 
-template<typename T> class CApplyOnLobby;
-
-class CBaseForLobbyApply
-{
-public:
-	virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
-	virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
-	virtual ~CBaseForLobbyApply(){};
-	template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
-	{
-		return new CApplyOnLobby<U>();
-	}
-};
-
-template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
-{
-public:
-	bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
-	{
-		auto & ref = static_cast<T&>(pack);
-		ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
-
-		logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
-		ref.visit(visitor);
-
-		return visitor.getResult();
-	}
-
-	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
-	{
-		auto & ref = static_cast<T &>(pack);
-		ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby);
-
-		logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
-		ref.visit(visitor);
-	}
-};
-
-template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
-{
-public:
-	bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
-	{
-		logGlobal->error("Cannot apply plain CPack!");
-		assert(0);
-		return false;
-	}
-
-	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
-	{
-		logGlobal->error("Cannot apply plain CPack!");
-		assert(0);
-	}
-};
-
 CServerHandler::~CServerHandler()
 CServerHandler::~CServerHandler()
 {
 {
 	if (serverRunner)
 	if (serverRunner)
@@ -148,7 +93,6 @@ CServerHandler::CServerHandler()
 	: networkHandler(INetworkHandler::createHandler())
 	: networkHandler(INetworkHandler::createHandler())
 	, lobbyClient(std::make_unique<GlobalLobbyClient>())
 	, lobbyClient(std::make_unique<GlobalLobbyClient>())
 	, gameChat(std::make_unique<GameChatHandler>())
 	, gameChat(std::make_unique<GameChatHandler>())
-	, applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
 	, threadNetwork(&CServerHandler::threadRunNetwork, this)
 	, threadNetwork(&CServerHandler::threadRunNetwork, this)
 	, state(EClientState::NONE)
 	, state(EClientState::NONE)
 	, serverPort(0)
 	, serverPort(0)
@@ -159,7 +103,6 @@ CServerHandler::CServerHandler()
 	, client(nullptr)
 	, client(nullptr)
 {
 {
 	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
 	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
-	registerTypesLobbyPacks(*applier);
 }
 }
 
 
 void CServerHandler::threadRunNetwork()
 void CServerHandler::threadRunNetwork()
@@ -320,8 +263,8 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon
 
 
 void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
 void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
 {
 {
-	const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
-	apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
+	ApplyOnLobbyScreenNetPackVisitor visitor(*this, dynamic_cast<CLobbyScreen *>(SEL));
+	pack.visit(visitor);
 	GH.windows().totalRedraw();
 	GH.windows().totalRedraw();
 }
 }
 
 
@@ -960,7 +903,10 @@ void CServerHandler::waitForServerShutdown()
 
 
 void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
 void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
 {
 {
-	if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack))
+	ApplyOnLobbyHandlerNetPackVisitor visitor(*this);
+	lobbyPack.visit(visitor);
+
+	if(visitor.getResult())
 	{
 	{
 		if(!settings["session"]["headless"].Bool())
 		if(!settings["session"]["headless"].Bool())
 			applyPackOnLobbyScreen(lobbyPack);
 			applyPackOnLobbyScreen(lobbyPack);

+ 0 - 3
client/CServerHandler.h

@@ -31,8 +31,6 @@ struct CPackForClient;
 
 
 class HighScoreParameter;
 class HighScoreParameter;
 
 
-template<typename T> class CApplier;
-
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
 class CClient;
 class CClient;
@@ -102,7 +100,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
 	std::shared_ptr<INetworkConnection> networkConnection;
 	std::shared_ptr<INetworkConnection> networkConnection;
 	std::unique_ptr<GlobalLobbyClient> lobbyClient;
 	std::unique_ptr<GlobalLobbyClient> lobbyClient;
 	std::unique_ptr<GameChatHandler> gameChat;
 	std::unique_ptr<GameChatHandler> gameChat;
-	std::unique_ptr<CApplier<CBaseForLobbyApply>> applier;
 	std::unique_ptr<IServerRunner> serverRunner;
 	std::unique_ptr<IServerRunner> serverRunner;
 	std::shared_ptr<CMapInfo> mapToStart;
 	std::shared_ptr<CMapInfo> mapToStart;
 	std::vector<std::string> localPlayerNames;
 	std::vector<std::string> localPlayerNames;

+ 12 - 69
client/Client.cpp

@@ -29,13 +29,10 @@
 #include "../lib/VCMIDirs.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/battle/BattleInfo.h"
-#include "../lib/serializer/BinaryDeserializer.h"
-#include "../lib/serializer/BinarySerializer.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
-#include "../lib/registerTypes/RegisterTypesClientPacks.h"
 
 
 #include <memory>
 #include <memory>
 #include <vcmi/events/EventBus.h>
 #include <vcmi/events/EventBus.h>
@@ -50,53 +47,6 @@
 
 
 ThreadSafeVector<int> CClient::waitingRequest;
 ThreadSafeVector<int> CClient::waitingRequest;
 
 
-template<typename T> class CApplyOnCL;
-
-class CBaseForCLApply
-{
-public:
-	virtual void applyOnClAfter(CClient * cl, CPack * pack) const =0;
-	virtual void applyOnClBefore(CClient * cl, CPack * pack) const =0;
-	virtual ~CBaseForCLApply(){}
-
-	template<typename U> static CBaseForCLApply * getApplier(const U * t = nullptr)
-	{
-		return new CApplyOnCL<U>();
-	}
-};
-
-template<typename T> class CApplyOnCL : public CBaseForCLApply
-{
-public:
-	void applyOnClAfter(CClient * cl, CPack * pack) const override
-	{
-		T * ptr = static_cast<T *>(pack);
-		ApplyClientNetPackVisitor visitor(*cl, *cl->gameState());
-		ptr->visit(visitor);
-	}
-	void applyOnClBefore(CClient * cl, CPack * pack) const override
-	{
-		T * ptr = static_cast<T *>(pack);
-		ApplyFirstClientNetPackVisitor visitor(*cl, *cl->gameState());
-		ptr->visit(visitor);
-	}
-};
-
-template<> class CApplyOnCL<CPack>: public CBaseForCLApply
-{
-public:
-	void applyOnClAfter(CClient * cl, CPack * pack) const override
-	{
-		logGlobal->error("Cannot apply on CL plain CPack!");
-		assert(0);
-	}
-	void applyOnClBefore(CClient * cl, CPack * pack) const override
-	{
-		logGlobal->error("Cannot apply on CL plain CPack!");
-		assert(0);
-	}
-};
-
 CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
 CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
 	: player(player_),
 	: player(player_),
 	cl(cl_),
 	cl(cl_),
@@ -130,12 +80,9 @@ const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
 	return mainCallback.get();
 	return mainCallback.get();
 }
 }
 
 
-
 CClient::CClient()
 CClient::CClient()
 {
 {
 	waitingRequest.clear();
 	waitingRequest.clear();
-	applier = std::make_shared<CApplier<CBaseForCLApply>>();
-	registerTypesClientPacks(*applier);
 	gs = nullptr;
 	gs = nullptr;
 }
 }
 
 
@@ -400,25 +347,21 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
 	}
 	}
 }
 }
 
 
-void CClient::handlePack(CPack * pack)
+void CClient::handlePack(CPackForClient * pack)
 {
 {
-	CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
-	if(apply)
-	{
-		apply->applyOnClBefore(this, pack);
-		logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
-		{
-			boost::unique_lock lock(CGameState::mutex);
-			gs->apply(pack);
-		}
-		logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
-		apply->applyOnClAfter(this, pack);
-		logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
-	}
-	else
+	ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
+	ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
+
+	pack->visit(beforeVisitor);
+	logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
 	{
 	{
-		logNetwork->error("Message %s cannot be applied, cannot find applier!", typeid(*pack).name());
+		boost::unique_lock lock(CGameState::mutex);
+		gs->apply(pack);
 	}
 	}
+	logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
+	pack->visit(afterVisitor);
+	logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
+
 	delete pack;
 	delete pack;
 }
 }
 
 

+ 1 - 7
client/Client.h

@@ -21,14 +21,10 @@ struct CPackForServer;
 class IBattleEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;
 class CBattleGameInterface;
 class CGameInterface;
 class CGameInterface;
-class BinaryDeserializer;
-class BinarySerializer;
 class BattleAction;
 class BattleAction;
 class BattleInfo;
 class BattleInfo;
 struct BankConfig;
 struct BankConfig;
 
 
-template<typename T> class CApplier;
-
 #if SCRIPTING_ENABLED
 #if SCRIPTING_ENABLED
 namespace scripting
 namespace scripting
 {
 {
@@ -147,7 +143,7 @@ public:
 
 
 	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
 	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
 
 
-	void handlePack(CPack * pack); //applies the given pack and deletes it
+	void handlePack(CPackForClient * pack); //applies the given pack and deletes it
 	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
 	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
 
 
 	void battleStarted(const BattleInfo * info);
 	void battleStarted(const BattleInfo * info);
@@ -237,8 +233,6 @@ private:
 #endif
 #endif
 	std::unique_ptr<events::EventBus> clientEventBus;
 	std::unique_ptr<events::EventBus> clientEventBus;
 
 
-	std::shared_ptr<CApplier<CBaseForCLApply>> applier;
-
 	mutable boost::mutex pathCacheMutex;
 	mutable boost::mutex pathCacheMutex;
 	std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
 	std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
 
 

+ 2 - 3
client/NetPacksClient.cpp

@@ -29,7 +29,6 @@
 #include "../CCallback.h"
 #include "../CCallback.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/FileInfo.h"
 #include "../lib/filesystem/FileInfo.h"
-#include "../lib/serializer/BinarySerializer.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CHeroHandler.h"
@@ -999,7 +998,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
 	case EOpenWindowMode::UNIVERSITY_WINDOW:
 	case EOpenWindowMode::UNIVERSITY_WINDOW:
 		{
 		{
 			//displays University window (when hero enters University on adventure map)
 			//displays University window (when hero enters University on adventure map)
-			const auto * market = dynamic_cast<const IMarket*>(cl.getObj(ObjectInstanceID(pack.object)));
+			const auto * market = cl.getMarket(ObjectInstanceID(pack.object));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
 			callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID);
 			callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID);
 		}
 		}
@@ -1009,7 +1008,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
 			//displays Thieves' Guild window (when hero enters Den of Thieves)
 			//displays Thieves' Guild window (when hero enters Den of Thieves)
 			const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
 			const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
-			const auto *market = dynamic_cast<const IMarket*>(obj);
+			const auto market = cl.getMarket(pack.object);
 			callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
 			callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
 		}
 		}
 		break;
 		break;

+ 3 - 0
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -68,7 +68,10 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
 		onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currently selected option
 		onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currently selected option
 	}
 	}
 	else
 	else
+	{
 		toggleMode->setSelected(1);
 		toggleMode->setSelected(1);
+		onLoginModeChanged(1);
+	}
 
 
 	filledBackground->setPlayerColor(PlayerColor(1));
 	filledBackground->setPlayerColor(PlayerColor(1));
 	inputUsername->setCallback([this](const std::string & text)
 	inputUsername->setCallback([this](const std::string & text)

+ 1 - 1
client/globalLobby/GlobalLobbyWidget.cpp

@@ -286,5 +286,5 @@ GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const Glo
 		opponentDescription.replaceNumber(matchDescription.participants.size());
 		opponentDescription.replaceNumber(matchDescription.participants.size());
 	}
 	}
 
 
-	labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString());
+	labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString(), 120);
 }
 }

+ 2 - 2
client/lobby/CBonusSelection.cpp

@@ -86,14 +86,14 @@ CBonusSelection::CBonusSelection()
 	buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), playVideo, EShortcut::LOBBY_REPLAY_VIDEO);
 	buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), playVideo, EShortcut::LOBBY_REPLAY_VIDEO);
 	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 
 
-	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
+	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName(), 250);
 
 
 	iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 735, 26);
 	iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 735, 26);
 
 
 	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
 	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
 	campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1);
 	campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1);
 
 
-	mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated());
+	mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), 285);
 	labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
 	labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
 	mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
 	mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
 
 

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -135,7 +135,7 @@ InfoCard::InfoCard()
 
 
 	labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
 	labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
 	labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
 	labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
-	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", 285);
+	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", SEL->screenType == ESelectionScreen::campaignList ? 325 : 285);
 	Rect descriptionRect(26, 149, 320, 115);
 	Rect descriptionRect(26, 149, 320, 115);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);

+ 0 - 1
client/mainmenu/CMainMenu.cpp

@@ -47,7 +47,6 @@
 
 
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/campaign/CampaignHandler.h"
 #include "../../lib/campaign/CampaignHandler.h"
-#include "../../lib/serializer/CTypeList.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CCompressedStream.h"
 #include "../../lib/filesystem/CCompressedStream.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapInfo.h"

+ 61 - 20
client/mainmenu/CStatisticScreen.cpp

@@ -130,13 +130,9 @@ TIcons CStatisticScreen::extractIcons() const
 	std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
 	std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
 
 
 	auto imageTown = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 3, 0, EImageBlitMode::COLORKEY);
 	auto imageTown = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 3, 0, EImageBlitMode::COLORKEY);
-	imageTown->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
 	auto imageBattle = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 5, 0, EImageBlitMode::COLORKEY);
 	auto imageBattle = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 5, 0, EImageBlitMode::COLORKEY);
-	imageBattle->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
-	auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("tpthchk"), 1, 0, EImageBlitMode::COLORKEY);
-	imageDefeated->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
+	auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("crcombat"), 0, 0, EImageBlitMode::COLORKEY);
 	auto imageGrail = GH.renderHandler().loadImage(AnimationPath::builtin("vwsymbol"), 2, 0, EImageBlitMode::COLORKEY);
 	auto imageGrail = GH.renderHandler().loadImage(AnimationPath::builtin("vwsymbol"), 2, 0, EImageBlitMode::COLORKEY);
-	imageGrail->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
 
 
 	std::map<PlayerColor, bool> foundDefeated;
 	std::map<PlayerColor, bool> foundDefeated;
 	std::map<PlayerColor, bool> foundGrail;
 	std::map<PlayerColor, bool> foundGrail;
@@ -273,7 +269,11 @@ OverviewPanel::OverviewPanel(Rect position, std::string title, const StatisticDa
 		},
 		},
 		{
 		{
 			CGI->generaltexth->translate("vcmi.statisticWindow.param.daysSurvived"), [this](PlayerColor color){
 			CGI->generaltexth->translate("vcmi.statisticWindow.param.daysSurvived"), [this](PlayerColor color){
-				return CStatisticScreen::getDay(playerDataFilter(color).size());
+				auto playerData = playerDataFilter(color);
+				for(int i = 0; i < playerData.size(); i++)
+					if(playerData[i].status == EPlayerStatus::LOSER)
+						return CStatisticScreen::getDay(i + 1);
+				return CStatisticScreen::getDay(playerData.size());
 			}
 			}
 		},
 		},
 		{
 		{
@@ -424,12 +424,25 @@ void OverviewPanel::update(int to)
 	}
 	}
 }
 }
 
 
+int computeGridStep(int maxAmount, int linesLimit)
+{
+	for (int lineInterval = 1;;lineInterval *= 10)
+	{
+		for (int factor : { 1, 2, 5 } )
+		{
+			int lineIntervalToTest = lineInterval * factor;
+			if (maxAmount / lineIntervalToTest <= linesLimit)
+				return lineIntervalToTest;
+		}
+	}
+}
+
 LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY)
 LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY)
 	: CIntObject(), maxVal(0), maxDay(0)
 	: CIntObject(), maxVal(0), maxDay(0)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	addUsedEvents(LCLICK | MOVE);
+	addUsedEvents(LCLICK | MOVE | GESTURE);
 
 
 	pos = position + pos.topLeft();
 	pos = position + pos.topLeft();
 
 
@@ -455,15 +468,47 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
 			maxDay = line.second.size();
 			maxDay = line.second.size();
 	}
 	}
 
 
+	//calculate nice maxVal
+	int gridLineCount = 10;
+	int gridStep = computeGridStep(maxVal, gridLineCount);
+	niceMaxVal = gridStep * std::ceil(maxVal / gridStep);
+
+	// calculate points in chart
+	auto getPoint = [this](int i, std::vector<float> data){
+		float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
+		float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / niceMaxVal) * data[i];
+		return Point(x, y);
+	};
+
+	// draw grid (vertical lines)
+	int dayGridInterval = maxDay < 700 ? 7 : 28;
+	for(const auto & line : data)
+	{
+		for(int i = 0; i < line.second.size(); i += dayGridInterval)
+		{
+			Point p = getPoint(i, line.second) + chartArea.topLeft();
+			canvas->addLine(Point(p.x, chartArea.topLeft().y), Point(p.x, chartArea.topLeft().y + chartArea.h), ColorRGBA(70, 70, 70));
+		}
+	}
+
+	// draw grid (horizontal lines)
+	if(maxVal > 0)
+	{
+		int gridStepPx = int((static_cast<float>(chartArea.h) / niceMaxVal) * gridStep);
+		for(int i = 0; i < std::ceil(maxVal / gridStep) + 1; i++)
+		{
+			canvas->addLine(chartArea.topLeft() + Point(0, chartArea.h - gridStepPx * i), chartArea.topLeft() + Point(chartArea.w, chartArea.h - gridStepPx * i), ColorRGBA(70, 70, 70));
+			layout.emplace_back(std::make_shared<CLabel>(chartArea.topLeft().x - 5, chartArea.topLeft().y + 10 + chartArea.h - gridStepPx * i, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, TextOperations::formatMetric(i * gridStep, 5)));
+		}
+	}
+
 	// draw
 	// draw
 	for(const auto & line : data)
 	for(const auto & line : data)
 	{
 	{
 		Point lastPoint(-1, -1);
 		Point lastPoint(-1, -1);
 		for(int i = 0; i < line.second.size(); i++)
 		for(int i = 0; i < line.second.size(); i++)
 		{
 		{
-			float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
-			float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / maxVal) * line.second[i];
-			Point p = Point(x, y) + chartArea.topLeft();
+			Point p = getPoint(i, line.second) + chartArea.topLeft();
 
 
 			if(lastPoint.x != -1)
 			if(lastPoint.x != -1)
 				canvas->addLine(lastPoint, p, line.first);
 				canvas->addLine(lastPoint, p, line.first);
@@ -472,7 +517,7 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
 			for(auto & icon : icons)
 			for(auto & icon : icons)
 				if(std::get<0>(icon) == line.first && std::get<1>(icon) == i + 1) // color && day
 				if(std::get<0>(icon) == line.first && std::get<1>(icon) == i + 1) // color && day
 				{
 				{
-					pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(x - (CHART_ICON_SIZE / 2), y - (CHART_ICON_SIZE / 2)) + chartArea.topLeft()));
+					pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(p.x - (std::get<2>(icon)->width() / 2), p.y - (std::get<2>(icon)->height() / 2))));
 					pictures.back()->addRClickCallback([icon](){ CRClickPopup::createAndPush(std::get<3>(icon)); });
 					pictures.back()->addRClickCallback([icon](){ CRClickPopup::createAndPush(std::get<3>(icon)); });
 				}
 				}
 
 
@@ -484,12 +529,8 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
 	canvas->addLine(chartArea.topLeft() + Point(0, -10), chartArea.topLeft() + Point(0, chartArea.h + 10), Colors::WHITE);
 	canvas->addLine(chartArea.topLeft() + Point(0, -10), chartArea.topLeft() + Point(0, chartArea.h + 10), Colors::WHITE);
 	canvas->addLine(chartArea.topLeft() + Point(-10, chartArea.h), chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h), Colors::WHITE);
 	canvas->addLine(chartArea.topLeft() + Point(-10, chartArea.h), chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h), Colors::WHITE);
 
 
-	Point p = chartArea.topLeft() + Point(-5, chartArea.h + 10);
-	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, "0"));
-	p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
+	Point p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
 	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CStatisticScreen::getDay(maxDay)));
 	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CStatisticScreen::getDay(maxDay)));
-	p = chartArea.topLeft() + Point(-5, -10);
-	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, std::to_string(static_cast<int>(maxVal))));
 	p = chartArea.bottomLeft() + Point(chartArea.w / 2, + 20);
 	p = chartArea.bottomLeft() + Point(chartArea.w / 2, + 20);
 	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.64")));
 	layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.64")));
 }
 }
@@ -502,8 +543,8 @@ void LineChart::updateStatusBar(const Point & cursorPosition)
 	statusBar->setEnabled(r.isInside(cursorPosition));
 	statusBar->setEnabled(r.isInside(cursorPosition));
 	if(r.isInside(cursorPosition))
 	if(r.isInside(cursorPosition))
 	{
 	{
-		float x = (static_cast<float>(maxDay) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
-		float y = maxVal - (maxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
+		float x = (static_cast<float>(maxDay - 1) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
+		float y = niceMaxVal - (niceMaxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
 		statusBar->write(CGI->generaltexth->translate("core.genrltxt.64") + ": " + CStatisticScreen::getDay(x) + "   " + CGI->generaltexth->translate("vcmi.statisticWindow.value") + ": " + (static_cast<int>(y) > 0 ? std::to_string(static_cast<int>(y)) : std::to_string(y)));
 		statusBar->write(CGI->generaltexth->translate("core.genrltxt.64") + ": " + CStatisticScreen::getDay(x) + "   " + CGI->generaltexth->translate("vcmi.statisticWindow.value") + ": " + (static_cast<int>(y) > 0 ? std::to_string(static_cast<int>(y)) : std::to_string(y)));
 	}
 	}
 	setRedrawParent(true);
 	setRedrawParent(true);
@@ -515,7 +556,7 @@ void LineChart::mouseMoved(const Point & cursorPosition, const Point & lastUpdat
 	updateStatusBar(cursorPosition);
 	updateStatusBar(cursorPosition);
 }
 }
 
 
-void LineChart::clickPressed(const Point & cursorPosition)
+void LineChart::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
 {
 {
-	updateStatusBar(cursorPosition);
+	updateStatusBar(currentPosition);
 }
 }

+ 2 - 3
client/mainmenu/CStatisticScreen.h

@@ -24,8 +24,6 @@ class CPicture;
 using TData = std::vector<std::pair<ColorRGBA, std::vector<float>>>;
 using TData = std::vector<std::pair<ColorRGBA, std::vector<float>>>;
 using TIcons = std::vector<std::tuple<ColorRGBA, int, std::shared_ptr<IImage>, std::string>>; // Color, Day, Image, Helptext
 using TIcons = std::vector<std::tuple<ColorRGBA, int, std::shared_ptr<IImage>, std::string>>; // Color, Day, Image, Helptext
 
 
-const int CHART_ICON_SIZE = 32;
-
 class CStatisticScreen : public CWindowObject
 class CStatisticScreen : public CWindowObject
 {
 {
 	enum Content {
 	enum Content {
@@ -123,6 +121,7 @@ class LineChart : public CIntObject
 
 
 	Rect chartArea;
 	Rect chartArea;
 	float maxVal;
 	float maxVal;
+	int niceMaxVal;
 	int maxDay;
 	int maxDay;
 
 
 	void updateStatusBar(const Point & cursorPosition);
 	void updateStatusBar(const Point & cursorPosition);
@@ -130,5 +129,5 @@ public:
 	LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY);
 	LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY);
 
 
 	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
-	void clickPressed(const Point & cursorPosition) override;
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
 };
 };

+ 2 - 2
client/widgets/CComponent.cpp

@@ -233,7 +233,7 @@ std::string CComponent::getDescription() const
 			return description;
 			return description;
 		}
 		}
 		case ComponentType::SPELL:
 		case ComponentType::SPELL:
-			return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(data.value.value_or(0));
+			return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(std::max(0, data.value.value_or(0)));
 		case ComponentType::MORALE:
 		case ComponentType::MORALE:
 			return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
 			return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
 		case ComponentType::LUCK:
 		case ComponentType::LUCK:
@@ -293,7 +293,7 @@ std::string CComponent::getSubtitle() const
 			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
 			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
 		case ComponentType::SPELL_SCROLL:
 		case ComponentType::SPELL_SCROLL:
 		case ComponentType::SPELL:
 		case ComponentType::SPELL:
-			if (data.value < 0)
+			if (data.value.value_or(0) < 0)
 				return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
 				return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
 			else
 			else
 				return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
 				return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();

+ 2 - 2
client/widgets/MiscWidgets.cpp

@@ -464,7 +464,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		for(auto & town : towns)
 		for(auto & town : towns)
 		{
 		{
-			if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN))
+			if(town->id == townId && town->hasBuilt(BuildingID::TAVERN))
 				LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 				LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 		}
 		}
 	}, [town]{
 	}, [town]{
@@ -476,7 +476,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		for(auto & town : towns)
 		for(auto & town : towns)
 		{
 		{
-			if(town->builtBuildings.count(BuildingID::MARKETPLACE))
+			if(town->hasBuilt(BuildingID::MARKETPLACE))
 			{
 			{
 				GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 				GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 				return;
 				return;

+ 11 - 12
client/widgets/markets/CAltarArtifacts.cpp

@@ -24,16 +24,15 @@
 #include "../../../lib/networkPacks/ArtifactLocation.h"
 #include "../../../lib/networkPacks/ArtifactLocation.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../../../lib/mapObjects/CGMarket.h"
+#include "../../../lib/mapObjects/IMarket.h"
 
 
 CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
 CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	assert(dynamic_cast<const CGArtifactsAltar*>(market));
-	auto altarObj = dynamic_cast<const CGArtifactsAltar*>(market);
-	altarArtifacts = altarObj;
+	assert(market->getArtifactsStorage());
+	altarArtifactsStorage = market->getArtifactsStorage();
 
 
 	deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
 	deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
 		CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL);
 		CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL);
@@ -51,7 +50,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
 	// Hero's artifacts
 	// Hero's artifacts
 	heroArts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -11));
 	heroArts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -11));
 	heroArts->setHero(hero);
 	heroArts->setHero(hero);
-	heroArts->altarId = altarObj->id;
+	heroArts->altarId = market->getObjInstanceID();
 
 
 	// Altar
 	// Altar
 	offerTradePanel = std::make_shared<ArtifactsAltarPanel>([this](const std::shared_ptr<CTradeableItem> & altarSlot)
 	offerTradePanel = std::make_shared<ArtifactsAltarPanel>([this](const std::shared_ptr<CTradeableItem> & altarSlot)
@@ -104,7 +103,7 @@ void CAltarArtifacts::makeDeal()
 	{
 	{
 		positions.push_back(artInst->getId());
 		positions.push_back(artInst->getId());
 	}
 	}
-	LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
+	LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
 	deselect();
 	deselect();
 }
 }
 
 
@@ -125,7 +124,7 @@ std::shared_ptr<CArtifactsOfHeroAltar> CAltarArtifacts::getAOHset() const
 
 
 void CAltarArtifacts::updateAltarSlots()
 void CAltarArtifacts::updateAltarSlots()
 {
 {
-	assert(altarArtifacts->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
+	assert(altarArtifactsStorage->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
 	assert(tradeSlotsMap.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
 	assert(tradeSlotsMap.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
 	
 	
 	auto tradeSlotsMapNewArts = tradeSlotsMap;
 	auto tradeSlotsMapNewArts = tradeSlotsMap;
@@ -146,12 +145,12 @@ void CAltarArtifacts::updateAltarSlots()
 	for(auto & tradeSlot : tradeSlotsMapNewArts)
 	for(auto & tradeSlot : tradeSlotsMapNewArts)
 	{
 	{
 		assert(tradeSlot.first->id == -1);
 		assert(tradeSlot.first->id == -1);
-		assert(altarArtifacts->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
+		assert(altarArtifactsStorage->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
 		tradeSlot.first->setID(tradeSlot.second->getTypeId().num);
 		tradeSlot.first->setID(tradeSlot.second->getTypeId().num);
 		tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId())));
 		tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId())));
 	}
 	}
 
 
-	auto newArtsFromBulkMove = altarArtifacts->artifactsInBackpack;
+	auto newArtsFromBulkMove = altarArtifactsStorage->artifactsInBackpack;
 	for(const auto & [altarSlot, art] : tradeSlotsMap)
 	for(const auto & [altarSlot, art] : tradeSlotsMap)
 	{
 	{
 		newArtsFromBulkMove.erase(std::remove_if(newArtsFromBulkMove.begin(), newArtsFromBulkMove.end(), [artForRemove = art](auto & slotInfo)
 		newArtsFromBulkMove.erase(std::remove_if(newArtsFromBulkMove.begin(), newArtsFromBulkMove.end(), [artForRemove = art](auto & slotInfo)
@@ -179,7 +178,7 @@ void CAltarArtifacts::putBackArtifacts()
 {
 {
 	// TODO: If the backpack capacity limit is enabled, artifacts may remain on the altar.
 	// TODO: If the backpack capacity limit is enabled, artifacts may remain on the altar.
 	// Perhaps should be erased in CGameHandler::objectVisitEnded if id of visited object will be available
 	// Perhaps should be erased in CGameHandler::objectVisitEnded if id of visited object will be available
-	if(!altarArtifacts->artifactsInBackpack.empty())
+	if(!altarArtifactsStorage->artifactsInBackpack.empty())
 		LOCPLINT->cb->bulkMoveArtifacts(heroArts->altarId, heroArts->getHero()->id, false, true, true);
 		LOCPLINT->cb->bulkMoveArtifacts(heroArts->altarId, heroArts->getHero()->id, false, true, true);
 }
 }
 
 
@@ -200,7 +199,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 
 
 	if(const auto pickedArtInst = heroArts->getPickedArtifact())
 	if(const auto pickedArtInst = heroArts->getPickedArtifact())
 	{
 	{
-		if(pickedArtInst->canBePutAt(altarArtifacts))
+		if(pickedArtInst->canBePutAt(altarArtifactsStorage.get()))
 		{
 		{
 			if(pickedArtInst->artType->isTradable())
 			if(pickedArtInst->artType->isTradable())
 			{
 			{
@@ -221,7 +220,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 	else if(altarSlot->id != -1)
 	else if(altarSlot->id != -1)
 	{
 	{
 		assert(tradeSlotsMap.at(altarSlot));
 		assert(tradeSlotsMap.at(altarSlot));
-		const auto slot = altarArtifacts->getArtPos(tradeSlotsMap.at(altarSlot));
+		const auto slot = altarArtifactsStorage->getArtPos(tradeSlotsMap.at(altarSlot));
 		assert(slot != ArtifactPosition::PRE_FIRST);
 		assert(slot != ArtifactPosition::PRE_FIRST);
 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot),
 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot),
 			ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS));
 			ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS));

+ 1 - 1
client/widgets/markets/CAltarArtifacts.h

@@ -26,7 +26,7 @@ public:
 	void putBackArtifacts();
 	void putBackArtifacts();
 
 
 private:
 private:
-	const CArtifactSet * altarArtifacts;
+	std::shared_ptr<CArtifactSet> altarArtifactsStorage;
 	std::shared_ptr<CButton> sacrificeBackpackButton;
 	std::shared_ptr<CButton> sacrificeBackpackButton;
 	std::shared_ptr<CArtifactsOfHeroAltar> heroArts;
 	std::shared_ptr<CArtifactsOfHeroAltar> heroArts;
 	std::map<std::shared_ptr<CTradeableItem>, const CArtifactInstance*> tradeSlotsMap;
 	std::map<std::shared_ptr<CTradeableItem>, const CArtifactInstance*> tradeSlotsMap;

+ 2 - 2
client/widgets/markets/CAltarCreatures.cpp

@@ -23,7 +23,7 @@
 
 
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../../../lib/mapObjects/CGMarket.h"
+#include "../../../lib/mapObjects/IMarket.h"
 
 
 CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
 CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
@@ -157,7 +157,7 @@ void CAltarCreatures::makeDeal()
 		}
 		}
 	}
 	}
 
 
-	LOCPLINT->cb->trade(market, EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
+	LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
 
 
 	for(int & units : unitsOnAltar)
 	for(int & units : unitsOnAltar)
 		units = 0;
 		units = 0;

+ 3 - 12
client/widgets/markets/CArtifactsBuying.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "CArtifactsBuying.h"
 #include "CArtifactsBuying.h"
 
 
-#include "../../gui/CGuiHandler.h"
 #include "../../gui/Shortcut.h"
 #include "../../gui/Shortcut.h"
 #include "../../widgets/Buttons.h"
 #include "../../widgets/Buttons.h"
 #include "../../widgets/TextControls.h"
 #include "../../widgets/TextControls.h"
@@ -21,24 +20,16 @@
 
 
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 
 
-#include "../../../lib/entities/building/CBuilding.h"
-#include "../../../lib/entities/faction/CTownHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../../../lib/mapObjects/CGMarket.h"
-#include "../../../lib/mapObjects/CGTownInstance.h"
+#include "../../../lib/mapObjects/IMarket.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 
 
-CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
+CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
 	, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, bidTradePanel);})
 	, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, bidTradePanel);})
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	std::string title;
-	if(auto townMarket = dynamic_cast<const CGTownInstance*>(market))
-		title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
-	else
-		title = CGI->generaltexth->allTexts[349];
 	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
 	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
 	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
 	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
 		CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL);
 		CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL);
@@ -77,7 +68,7 @@ void CArtifactsBuying::makeDeal()
 {
 {
 	if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
 	if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
 	{
 	{
-		LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
 			ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
 			ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
 		CMarketTraderText::makeDeal();
 		CMarketTraderText::makeDeal();
 		deselect();
 		deselect();

+ 1 - 1
client/widgets/markets/CArtifactsBuying.h

@@ -14,7 +14,7 @@
 class CArtifactsBuying : public CResourcesSelling, public CMarketTraderText
 class CArtifactsBuying : public CResourcesSelling, public CMarketTraderText
 {
 {
 public:
 public:
-	CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
+	CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
 	void deselect() override;
 	void deselect() override;
 	void makeDeal() override;
 	void makeDeal() override;
 
 

+ 4 - 12
client/widgets/markets/CArtifactsSelling.cpp

@@ -22,14 +22,11 @@
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 
 
 #include "../../../lib/CArtifactInstance.h"
 #include "../../../lib/CArtifactInstance.h"
-#include "../../../lib/entities/building/CBuilding.h"
-#include "../../../lib/entities/faction/CTownHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../../../lib/mapObjects/CGMarket.h"
-#include "../../../lib/mapObjects/CGTownInstance.h"
+#include "../../../lib/mapObjects/IMarket.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 
 
-CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero)
+CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
 	, CResourcesBuying(
 	, CResourcesBuying(
 		[this](const std::shared_ptr<CTradeableItem> & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, offerTradePanel);},
 		[this](const std::shared_ptr<CTradeableItem> & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, offerTradePanel);},
@@ -37,12 +34,6 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	std::string title;
-	if(const auto townMarket = dynamic_cast<const CGTownInstance*>(market))
-		title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
-	else if(const auto mapMarket = dynamic_cast<const CGMarket*>(market))
-		title = mapMarket->title;
-
 	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
 	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
 	labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
 	labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
 	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
 	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
@@ -87,7 +78,8 @@ void CArtifactsSelling::makeDeal()
 {
 {
 	const auto art = hero->getArt(selectedHeroSlot);
 	const auto art = hero->getArt(selectedHeroSlot);
 	assert(art);
 	assert(art);
-	LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_RESOURCE, art->getId(), GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
+	LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(),
+		GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
 	CMarketTraderText::makeDeal();
 	CMarketTraderText::makeDeal();
 }
 }
 
 

+ 1 - 1
client/widgets/markets/CArtifactsSelling.h

@@ -15,7 +15,7 @@
 class CArtifactsSelling : public CResourcesBuying, public CMarketTraderText
 class CArtifactsSelling : public CResourcesBuying, public CMarketTraderText
 {
 {
 public:
 public:
-	CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);
+	CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
 	void deselect() override;
 	void deselect() override;
 	void makeDeal() override;
 	void makeDeal() override;
 	void updateShowcases() override;
 	void updateShowcases() override;

+ 2 - 2
client/widgets/markets/CFreelancerGuild.cpp

@@ -23,7 +23,7 @@
 
 
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../../../lib/mapObjects/CGMarket.h"
+#include "../../../lib/mapObjects/IMarket.h"
 
 
 CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero)
 CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
@@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal()
 {
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
 	{
-		LOCPLINT->cb->trade(market, EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
 		CMarketTraderText::makeDeal();
 		CMarketTraderText::makeDeal();
 		deselect();
 		deselect();
 	}
 	}

+ 2 - 2
client/widgets/markets/CMarketResources.cpp

@@ -22,7 +22,7 @@
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 
 
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
-#include "../../../lib/mapObjects/CGMarket.h"
+#include "../../../lib/mapObjects/IMarket.h"
 
 
 CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero)
 CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero)
 	: CMarketBase(market, hero)
 	: CMarketBase(market, hero)
@@ -60,7 +60,7 @@ void CMarketResources::makeDeal()
 {
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
 	{
-		LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
 			GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
 			GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
 		CMarketTraderText::makeDeal();
 		CMarketTraderText::makeDeal();
 		deselect();
 		deselect();

+ 2 - 1
client/widgets/markets/CTransferResources.cpp

@@ -22,6 +22,7 @@
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 
 
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
+#include "../../../lib/mapObjects/IMarket.h"
 #include "../../../lib/texts/MetaString.h"
 #include "../../../lib/texts/MetaString.h"
 
 
 CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero)
 CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero)
@@ -63,7 +64,7 @@ void CTransferResources::makeDeal()
 {
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
 	{
-		LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
 			PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
 			PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
 		CMarketTraderText::makeDeal();
 		CMarketTraderText::makeDeal();
 		deselect();
 		deselect();

+ 12 - 11
client/windows/CCastleInterface.cpp

@@ -586,9 +586,9 @@ void CCastleBuildings::recreate()
 
 
 	//Generate buildings list
 	//Generate buildings list
 
 
-	auto buildingsCopy = town->builtBuildings;// a bit modified copy of built buildings
+	auto buildingsCopy = town->getBuildings();// a bit modified copy of built buildings
 
 
-	if(vstd::contains(town->builtBuildings, BuildingID::SHIPYARD))
+	if(town->hasBuilt(BuildingID::SHIPYARD))
 	{
 	{
 		auto bayPos = town->bestLocation();
 		auto bayPos = town->bestLocation();
 		if(!bayPos.valid())
 		if(!bayPos.valid())
@@ -996,7 +996,7 @@ void CCastleBuildings::enterMagesGuild()
 void CCastleBuildings::enterTownHall()
 void CCastleBuildings::enterTownHall()
 {
 {
 	if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) &&
 	if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) &&
-		!vstd::contains(town->builtBuildings, BuildingID::GRAIL)) //hero has grail, but town does not have it
+		!town->hasBuilt(BuildingID::GRAIL)) //hero has grail, but town does not have it
 	{
 	{
 		if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL))
 		if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL))
 		{
 		{
@@ -1033,7 +1033,7 @@ void CCastleBuildings::enterAnyThievesGuild()
 	std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 	std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 	for(auto & ownedTown : towns)
 	for(auto & ownedTown : towns)
 	{
 	{
-		if(ownedTown->builtBuildings.count(BuildingID::TAVERN))
+		if(ownedTown->hasBuilt(BuildingID::TAVERN))
 		{
 		{
 			LOCPLINT->showThievesGuildWindow(ownedTown);
 			LOCPLINT->showThievesGuildWindow(ownedTown);
 			return;
 			return;
@@ -1059,7 +1059,7 @@ void CCastleBuildings::enterBank()
 
 
 void CCastleBuildings::enterAnyMarket()
 void CCastleBuildings::enterAnyMarket()
 {
 {
-	if(town->builtBuildings.count(BuildingID::MARKETPLACE))
+	if(town->hasBuilt(BuildingID::MARKETPLACE))
 	{
 	{
 		GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 		GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 		return;
 		return;
@@ -1068,7 +1068,7 @@ void CCastleBuildings::enterAnyMarket()
 	std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 	std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 	for(auto & town : towns)
 	for(auto & town : towns)
 	{
 	{
-		if(town->builtBuildings.count(BuildingID::MARKETPLACE))
+		if(town->hasBuilt(BuildingID::MARKETPLACE))
 		{
 		{
 			GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 			GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 			return;
 			return;
@@ -1385,7 +1385,7 @@ void CCastleInterface::recreateIcons()
 	fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); });
 	fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); });
 	fastTavern = std::make_shared<LRClickableArea>(Rect(15, 387, 58, 64), [&]()
 	fastTavern = std::make_shared<LRClickableArea>(Rect(15, 387, 58, 64), [&]()
 	{
 	{
-		if(town->builtBuildings.count(BuildingID::TAVERN))
+		if(town->hasBuilt(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 	}, [this]{
 	}, [this]{
 		if(!town->town->faction->getDescriptionTranslated().empty())
 		if(!town->town->faction->getDescriptionTranslated().empty())
@@ -1563,7 +1563,7 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
 				}
 				}
 
 
 				const CBuilding * current = town->town->buildings.at(buildingID);
 				const CBuilding * current = town->town->buildings.at(buildingID);
-				if(vstd::contains(town->builtBuildings, buildingID))
+				if(town->hasBuilt(buildingID))
 				{
 				{
 					building = current;
 					building = current;
 				}
 				}
@@ -1776,7 +1776,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 		{
 		{
 			BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
 			BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
 
 
-			if(vstd::contains(town->builtBuildings, dwelling))
+			if(town->hasBuilt(dwelling))
 				buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 1));
 				buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 1));
 			else
 			else
 				buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 0));
 				buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 0));
@@ -1841,7 +1841,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 		buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
 		buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
 		buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
 		buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
 
 
-		if(vstd::contains(town->builtBuildings, getMyBuilding()->bid))
+		if(town->hasBuilt(getMyBuilding()->bid))
 		{
 		{
 			ui32 available = town->creatures[level].first;
 			ui32 available = town->creatures[level].first;
 			std::string availableText = CGI->generaltexth->allTexts[217]+ std::to_string(available);
 			std::string availableText = CGI->generaltexth->allTexts[217]+ std::to_string(available);
@@ -1894,8 +1894,9 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
 	{
 	{
 		if (town->hasBuilt(myID))
 		if (town->hasBuilt(myID))
 			build = town->town->buildings.at(myID);
 			build = town->town->buildings.at(myID);
-		myID.advance(town->town->creatures.size());
+		BuildingID::advanceDwelling(myID);
 	}
 	}
+
 	return build;
 	return build;
 }
 }
 
 

+ 1 - 0
client/windows/CCreatureWindow.cpp

@@ -619,6 +619,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 			parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
 			parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
 			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
 			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
 			parent->stackArtifactHelp->component.subType = art->artType->getId();
 			parent->stackArtifactHelp->component.subType = art->artType->getId();
+			parent->stackArtifactHelp->text = art->getDescription();
 
 
 			if(parent->info->owner)
 			if(parent->info->owner)
 			{
 			{

+ 2 - 2
client/windows/CKingdomInterface.cpp

@@ -822,7 +822,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 
 
 	fastTavern = std::make_shared<LRClickableArea>(Rect(5, 6, 58, 64), [&]()
 	fastTavern = std::make_shared<LRClickableArea>(Rect(5, 6, 58, 64), [&]()
 	{
 	{
-		if(town->builtBuildings.count(BuildingID::TAVERN))
+		if(town->hasBuilt(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 	}, [&]{
 	}, [&]{
 		if(!town->town->faction->getDescriptionTranslated().empty())
 		if(!town->town->faction->getDescriptionTranslated().empty())
@@ -833,7 +833,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
 		for(auto & town : towns)
 		for(auto & town : towns)
 		{
 		{
-			if(town->builtBuildings.count(BuildingID::MARKETPLACE))
+			if(town->hasBuilt(BuildingID::MARKETPLACE))
 			{
 			{
 				GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 				GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
 				return;
 				return;

+ 31 - 7
client/windows/CMarketWindow.cpp

@@ -27,11 +27,14 @@
 #include "../CGameInfo.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
 
 
+#include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGMarket.h"
 #include "../../lib/mapObjects/CGMarket.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
 
+#include "../../CCallback.h"
+
 CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode)
 CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode)
 	: CWindowObject(PLAYER_COLORED)
 	: CWindowObject(PLAYER_COLORED)
 	, windowClosedCallback(onWindowClosed)
 	, windowClosedCallback(onWindowClosed)
@@ -111,6 +114,11 @@ void CMarketWindow::createChangeModeButtons(EMarketMode currentMode, const IMark
 		if(!market->allowsTrade(modeButton))
 		if(!market->allowsTrade(modeButton))
 			return false;
 			return false;
 
 
+		if(currentMode == EMarketMode::ARTIFACT_EXP && modeButton != EMarketMode::CREATURE_EXP)
+			return false;
+		if(currentMode == EMarketMode::CREATURE_EXP && modeButton != EMarketMode::ARTIFACT_EXP)
+			return false;
+
 		if(modeButton == EMarketMode::RESOURCE_RESOURCE || modeButton == EMarketMode::RESOURCE_PLAYER)
 		if(modeButton == EMarketMode::RESOURCE_RESOURCE || modeButton == EMarketMode::RESOURCE_PLAYER)
 		{
 		{
 			if(const auto town = dynamic_cast<const CGTownInstance*>(market))
 			if(const auto town = dynamic_cast<const CGTownInstance*>(market))
@@ -175,12 +183,28 @@ void CMarketWindow::initWidgetInternals(const EMarketMode mode, const std::pair<
 	redraw();
 	redraw();
 }
 }
 
 
+std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const
+{
+	assert(LOCPLINT->cb->getMarket(marketId));
+	assert(vstd::contains(LOCPLINT->cb->getMarket(marketId)->availableModes(), mode));
+
+	if(const auto town = LOCPLINT->cb->getTown(marketId))
+	{
+		for(const auto & buildingId : town->getBuildings())
+		{
+			if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode))
+				return building->getNameTranslated();
+		}
+	}
+	return LOCPLINT->cb->getObj(marketId)->getObjectName();
+}
+
 void CMarketWindow::createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
 void CMarketWindow::createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
 	background = createBg(ImagePath::builtin("TPMRKABS.bmp"), PLAYER_COLORED);
 	background = createBg(ImagePath::builtin("TPMRKABS.bmp"), PLAYER_COLORED);
-	marketWidget = std::make_shared<CArtifactsBuying>(market, hero);
+	marketWidget = std::make_shared<CArtifactsBuying>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT));
 	initWidgetInternals(EMarketMode::RESOURCE_ARTIFACT, CGI->generaltexth->zelp[600]);
 	initWidgetInternals(EMarketMode::RESOURCE_ARTIFACT, CGI->generaltexth->zelp[600]);
 }
 }
 
 
@@ -192,13 +216,13 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
 	// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 	// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 	artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 0, 0);
 	artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 0, 0);
 	artSlotBack->moveTo(pos.topLeft() + Point(18, 339));
 	artSlotBack->moveTo(pos.topLeft() + Point(18, 339));
-	auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero);
+	auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE));
 	artSets.clear();
 	artSets.clear();
 	const auto heroArts = artsSellingMarket->getAOHset();
 	const auto heroArts = artsSellingMarket->getAOHset();
 	heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
 	heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
 	addSet(heroArts);
 	addSet(heroArts);
 	marketWidget = artsSellingMarket;
 	marketWidget = artsSellingMarket;
-	initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);	
+	initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);
 }
 }
 
 
 void CMarketWindow::createMarketResources(const IMarket * market, const CGHeroInstance * hero)
 void CMarketWindow::createMarketResources(const IMarket * market, const CGHeroInstance * hero)
@@ -233,10 +257,10 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
 	background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED);
 	background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED);
-	auto altarArtifacts = std::make_shared<CAltarArtifacts>(market, hero);
-	marketWidget = altarArtifacts;
+	auto altarArtifactsStorage = std::make_shared<CAltarArtifacts>(market, hero);
+	marketWidget = altarArtifactsStorage;
 	artSets.clear();
 	artSets.clear();
-	const auto heroArts = altarArtifacts->getAOHset();
+	const auto heroArts = altarArtifactsStorage->getAOHset();
 	heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition)
 	heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition)
 	{
 	{
 		clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false);
 		clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false);
@@ -252,7 +276,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
 	addSet(heroArts);
 	addSet(heroArts);
 	initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]);
 	initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]);
 	updateExperience();
 	updateExperience();
-	quitButton->addCallback([altarArtifacts](){altarArtifacts->putBackArtifacts();});
+	quitButton->addCallback([altarArtifactsStorage](){altarArtifactsStorage->putBackArtifacts();});
 }
 }
 
 
 void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
 void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero)

+ 1 - 0
client/windows/CMarketWindow.h

@@ -27,6 +27,7 @@ public:
 private:
 private:
 	void createChangeModeButtons(EMarketMode currentMode, const IMarket * market, const CGHeroInstance * hero);
 	void createChangeModeButtons(EMarketMode currentMode, const IMarket * market, const CGHeroInstance * hero);
 	void initWidgetInternals(const EMarketMode mode, const std::pair<std::string, std::string> & quitButtonHelpContainer);
 	void initWidgetInternals(const EMarketMode mode, const std::pair<std::string, std::string> & quitButtonHelpContainer);
+	std::string getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const;
 
 
 	void createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
 	void createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
 	void createArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);
 	void createArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -819,7 +819,7 @@ void CTransformerWindow::makeDeal()
 	for(auto & elem : items)
 	for(auto & elem : items)
 	{
 	{
 		if(!elem->left)
 		if(!elem->left)
-			LOCPLINT->cb->trade(market, EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
+			LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
 	}
 	}
 }
 }
 
 
@@ -1005,7 +1005,7 @@ void CUniversityWindow::updateSecondarySkills()
 
 
 void CUniversityWindow::makeDeal(SecondarySkill skill)
 void CUniversityWindow::makeDeal(SecondarySkill skill)
 {
 {
-	LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
+	LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
 }
 }
 
 
 CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available)
 CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available)

+ 1 - 1
config/factions/castle.json

@@ -166,7 +166,7 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 

+ 2 - 2
config/factions/conflux.json

@@ -171,11 +171,11 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "mercury": 1 } },
 				"resourceSilo":   { "produce": { "mercury": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 
-				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },

+ 2 - 2
config/factions/dungeon.json

@@ -167,11 +167,11 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "sulfur": 1 } },
 				"resourceSilo":   { "produce": { "sulfur": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 
-				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"special2":       {
 				"special2":       {

+ 1 - 1
config/factions/fortress.json

@@ -165,7 +165,7 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "wood": 1, "ore": 1 } },
 				"resourceSilo":   { "produce": { "wood": 1, "ore": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 

+ 1 - 1
config/factions/inferno.json

@@ -167,7 +167,7 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "mercury": 1 } },
 				"resourceSilo":   { "produce": { "mercury": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 

+ 2 - 2
config/factions/necropolis.json

@@ -172,7 +172,7 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 
@@ -182,7 +182,7 @@
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"special2":       { "requires" : [ "mageGuild1" ],
 				"special2":       { "requires" : [ "mageGuild1" ],
 					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
 					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
-				"special3":       { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
+				"special3":       { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
 					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
 					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
 
 

+ 1 - 1
config/factions/rampart.json

@@ -170,7 +170,7 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "crystal": 1 } },
 				"resourceSilo":   { "produce": { "crystal": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 

+ 2 - 2
config/factions/stronghold.json

@@ -162,14 +162,14 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 
 				"special1":       { "type" : "escapeTunnel", "requires" : [ "fort" ] },
 				"special1":       { "type" : "escapeTunnel", "requires" : [ "fort" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
+				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ], "marketModes" : ["creature-resource"] },
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special4":       { 
 				"special4":       { 
 					"requires" : [ "fort" ],
 					"requires" : [ "fort" ],

+ 2 - 2
config/factions/tower.json

@@ -165,11 +165,11 @@
 				"townHall":       { },
 				"townHall":       { },
 				"cityHall":       { },
 				"cityHall":       { },
 				"capitol":        { },
 				"capitol":        { },
-				"marketplace":    { },
+				"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
 				"resourceSilo":   { "produce" : { "gems": 1 } },
 				"resourceSilo":   { "produce" : { "gems": 1 } },
 				"blacksmith":     { },
 				"blacksmith":     { },
 
 
-				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
 				"special2":       { "height" : "high", "requires" : [ "fort" ] },
 				"special2":       { "height" : "high", "requires" : [ "fort" ] },

+ 5 - 0
config/schemas/townBuilding.json

@@ -97,6 +97,11 @@
 			"type" : "array",
 			"type" : "array",
 			"description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators",
 			"description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators",
 			"items" : { "$ref" : "bonus.json" }
 			"items" : { "$ref" : "bonus.json" }
+		},
+		"marketModes" : {
+			"type" : "array",
+			"enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill"],
+			"description" : "List of modes available in this market"
 		}
 		}
 	}
 	}
 }
 }

+ 6 - 0
debian/changelog

@@ -4,6 +4,12 @@ vcmi (1.6.0) jammy; urgency=medium
 
 
  -- Ivan Savenko <[email protected]>  Fri, 30 Aug 2024 12:00:00 +0200
  -- Ivan Savenko <[email protected]>  Fri, 30 Aug 2024 12:00:00 +0200
 
 
+vcmi (1.5.7) jammy; urgency=medium
+
+  * New upstream release
+
+ -- Ivan Savenko <[email protected]>  Mon, 26 Aug 2024 12:00:00 +0200
+
 vcmi (1.5.6) jammy; urgency=medium
 vcmi (1.5.6) jammy; urgency=medium
 
 
   * New upstream release
   * New upstream release

+ 1 - 1
docs/Readme.md

@@ -1,7 +1,7 @@
 [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
 [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.7/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 
 
 # VCMI Project
 # VCMI Project

+ 17 - 1
docs/modders/Entities_Format/Town_Building_Format.md

@@ -171,6 +171,9 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
 	
 	
     // If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building"
     // If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building"
 	"upgradeReplacesBonuses" : false,
 	"upgradeReplacesBonuses" : false,
+	
+	// If the building is a market, it requires market mode.
+	"marketModes" : [ "resource-resource", "resource-player" ],
 }
 }
 ```
 ```
 
 
@@ -217,7 +220,6 @@ Following HotA buildings can be used as unique building for a town. Functionalit
 #### Custom buildings
 #### Custom buildings
 In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config.
 In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config.
 
 
-```
 
 
 ### Town Structure node
 ### Town Structure node
 
 
@@ -248,3 +250,17 @@ In addition to above, it is possible to use same format as [Rewardable](../Map_O
 	"hidden" : false 
 	"hidden" : false 
 }
 }
 ```
 ```
+
+
+#### Markets in towns
+Market buildings require list of available [modes](../Map_Objects/Market.md)
+
+##### Marketplace
+```jsonc
+	"marketplace":    { "marketModes" : ["resource-resource", "resource-player"] },
+```
+
+##### Artifact merchant
+```jsonc
+	"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
+```

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

@@ -91,6 +91,7 @@
 	<launchable type="desktop-id">vcmilauncher.desktop</launchable>
 	<launchable type="desktop-id">vcmilauncher.desktop</launchable>
 	<releases>
 	<releases>
 		<release version="1.6.0" date="2024-08-30" type="development"/>
 		<release version="1.6.0" date="2024-08-30" type="development"/>
+		<release version="1.5.7" date="2024-08-26" type="stable"/>
 		<release version="1.5.6" date="2024-08-04" type="stable"/>
 		<release version="1.5.6" date="2024-08-04" type="stable"/>
 		<release version="1.5.5" date="2024-07-17" type="stable"/>
 		<release version="1.5.5" date="2024-07-17" type="stable"/>
 		<release version="1.5.4" date="2024-07-12" type="stable"/>
 		<release version="1.5.4" date="2024-07-12" type="stable"/>

+ 9 - 0
lib/CGameInfoCallback.cpp

@@ -174,6 +174,15 @@ const CGTownInstance* CGameInfoCallback::getTown(ObjectInstanceID objid) const
 		return nullptr;
 		return nullptr;
 }
 }
 
 
+const IMarket * CGameInfoCallback::getMarket(ObjectInstanceID objid) const
+{
+	const CGObjectInstance * obj = getObj(objid, false);
+	if(obj)
+		return dynamic_cast<const IMarket*>(obj);
+	else
+		return nullptr;
+}
+
 void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
 void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
 {
 {
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);

+ 4 - 2
lib/CGameInfoCallback.h

@@ -48,6 +48,7 @@ class CGHeroInstance;
 class CGDwelling;
 class CGDwelling;
 class CGTeleport;
 class CGTeleport;
 class CGTownInstance;
 class CGTownInstance;
+class IMarket;
 
 
 class DLL_LINKAGE IGameInfoCallback : boost::noncopyable
 class DLL_LINKAGE IGameInfoCallback : boost::noncopyable
 {
 {
@@ -56,7 +57,7 @@ public:
 
 
 //	//various
 //	//various
 	virtual int getDate(Date mode=Date::DAY) const = 0; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
 	virtual int getDate(Date mode=Date::DAY) const = 0; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
-//	const StartInfo * getStartInfo(bool beforeRandomization = false)const;
+	virtual const StartInfo * getStartInfo(bool beforeRandomization = false) const = 0;
 	virtual bool isAllowed(SpellID id) const = 0;
 	virtual bool isAllowed(SpellID id) const = 0;
 	virtual bool isAllowed(ArtifactID id) const = 0;
 	virtual bool isAllowed(ArtifactID id) const = 0;
 	virtual bool isAllowed(SecondarySkill id) const = 0;
 	virtual bool isAllowed(SecondarySkill id) const = 0;
@@ -143,7 +144,7 @@ protected:
 public:
 public:
 	//various
 	//various
 	int getDate(Date mode=Date::DAY)const override; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
 	int getDate(Date mode=Date::DAY)const override; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
-	virtual const StartInfo * getStartInfo(bool beforeRandomization = false)const;
+	const StartInfo * getStartInfo(bool beforeRandomization = false) const override;
 	bool isAllowed(SpellID id) const override;
 	bool isAllowed(SpellID id) const override;
 	bool isAllowed(ArtifactID id) const override;
 	bool isAllowed(ArtifactID id) const override;
 	bool isAllowed(SecondarySkill id) const override;
 	bool isAllowed(SecondarySkill id) const override;
@@ -189,6 +190,7 @@ public:
 	virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
 	virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
 	virtual const CGObjectInstance * getTopObj (int3 pos) const;
 	virtual const CGObjectInstance * getTopObj (int3 pos) const;
 	virtual PlayerColor getOwner(ObjectInstanceID heroID) const;
 	virtual PlayerColor getOwner(ObjectInstanceID heroID) const;
+	virtual const IMarket * getMarket(ObjectInstanceID objid) const;
 
 
 	//map
 	//map
 	virtual int3 guardingCreaturePosition (int3 pos) const;
 	virtual int3 guardingCreaturePosition (int3 pos) const;

+ 0 - 3
lib/CGameInterface.cpp

@@ -13,9 +13,6 @@
 #include "CStack.h"
 #include "CStack.h"
 #include "VCMIDirs.h"
 #include "VCMIDirs.h"
 
 
-#include "serializer/BinaryDeserializer.h"
-#include "serializer/BinarySerializer.h"
-
 #ifdef STATIC_AI
 #ifdef STATIC_AI
 # include "AI/VCAI/VCAI.h"
 # include "AI/VCAI/VCAI.h"
 # include "AI/Nullkiller/AIGateway.h"
 # include "AI/Nullkiller/AIGateway.h"

+ 0 - 2
lib/CGameInterface.h

@@ -53,8 +53,6 @@ class CStack;
 class CCreature;
 class CCreature;
 class CLoadFile;
 class CLoadFile;
 class CSaveFile;
 class CSaveFile;
-class BinaryDeserializer;
-class BinarySerializer;
 class BattleStateInfo;
 class BattleStateInfo;
 struct ArtifactLocation;
 struct ArtifactLocation;
 class BattleStateInfoForRetreat;
 class BattleStateInfoForRetreat;

+ 3 - 6
lib/CMakeLists.txt

@@ -217,6 +217,7 @@ set(lib_MAIN_SRCS
 	serializer/JsonSerializeFormat.cpp
 	serializer/JsonSerializeFormat.cpp
 	serializer/JsonSerializer.cpp
 	serializer/JsonSerializer.cpp
 	serializer/JsonUpdater.cpp
 	serializer/JsonUpdater.cpp
+	serializer/SerializerReflection.cpp
 
 
 	spells/AbilityCaster.cpp
 	spells/AbilityCaster.cpp
 	spells/AdventureSpellMechanics.cpp
 	spells/AdventureSpellMechanics.cpp
@@ -563,12 +564,6 @@ set(lib_MAIN_HEADERS
 	pathfinder/PathfindingRules.h
 	pathfinder/PathfindingRules.h
 	pathfinder/TurnInfo.h
 	pathfinder/TurnInfo.h
 
 
-	registerTypes/RegisterTypes.h
-	registerTypes/RegisterTypesClientPacks.h
-	registerTypes/RegisterTypesLobbyPacks.h
-	registerTypes/RegisterTypesMapObjects.h
-	registerTypes/RegisterTypesServerPacks.h
-
 	rewardable/Configuration.h
 	rewardable/Configuration.h
 	rewardable/Info.h
 	rewardable/Info.h
 	rewardable/Interface.h
 	rewardable/Interface.h
@@ -625,7 +620,9 @@ set(lib_MAIN_HEADERS
 	serializer/JsonUpdater.h
 	serializer/JsonUpdater.h
 	serializer/Cast.h
 	serializer/Cast.h
 	serializer/ESerializationVersion.h
 	serializer/ESerializationVersion.h
+	serializer/RegisterTypes.h
 	serializer/Serializeable.h
 	serializer/Serializeable.h
+	serializer/SerializerReflection.h
 
 
 	spells/AbilityCaster.h
 	spells/AbilityCaster.h
 	spells/AdventureSpellMechanics.h
 	spells/AdventureSpellMechanics.h

+ 8 - 16
lib/IGameCallback.cpp

@@ -180,8 +180,7 @@ CGameState * CPrivilegedInfoCallback::gameState()
 	return gs;
 	return gs;
 }
 }
 
 
-template<typename Loader>
-void CPrivilegedInfoCallback::loadCommonState(Loader & in)
+void CPrivilegedInfoCallback::loadCommonState(CLoadFile & in)
 {
 {
 	logGlobal->info("Loading lib part of game...");
 	logGlobal->info("Loading lib part of game...");
 	in.checkMagicBytes(SAVEGAME_MAGIC);
 	in.checkMagicBytes(SAVEGAME_MAGIC);
@@ -203,8 +202,7 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in)
 	in.serializer & gs;
 	in.serializer & gs;
 }
 }
 
 
-template<typename Saver>
-void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
+void CPrivilegedInfoCallback::saveCommonState(CSaveFile & out) const
 {
 {
 	ActiveModsInSaveList activeMods;
 	ActiveModsInSaveList activeMods;
 
 
@@ -220,10 +218,6 @@ void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
 	out.serializer & gs;
 	out.serializer & gs;
 }
 }
 
 
-// hardly memory usage for `-gdwarf-4` flag
-template DLL_LINKAGE void CPrivilegedInfoCallback::loadCommonState<CLoadFile>(CLoadFile &);
-template DLL_LINKAGE void CPrivilegedInfoCallback::saveCommonState<CSaveFile>(CSaveFile &) const;
-
 TerrainTile * CNonConstInfoCallback::getTile(const int3 & pos)
 TerrainTile * CNonConstInfoCallback::getTile(const int3 & pos)
 {
 {
 	if(!gs->map->isInTheMap(pos))
 	if(!gs->map->isInTheMap(pos))
@@ -287,18 +281,16 @@ CArtifactSet * CNonConstInfoCallback::getArtSet(const ArtifactLocation & loc)
 			return hero;
 			return hero;
 		}
 		}
 	}
 	}
-	else if(auto army = getArmyInstance(loc.artHolder))
-	{
-		return army->getStackPtr(loc.creature.value());
-	}
-	else if(auto market = dynamic_cast<CGArtifactsAltar*>(getObjInstance(loc.artHolder)))
+	else if(auto market = getMarket(loc.artHolder))
 	{
 	{
-		return market;
+		if(auto artSet = market->getArtifactsStorage())
+			return artSet.get();
 	}
 	}
-	else
+	else if(auto army = getArmyInstance(loc.artHolder))
 	{
 	{
-		return nullptr;
+		return army->getStackPtr(loc.creature.value());
 	}
 	}
+	return nullptr;
 }
 }
 
 
 bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)
 bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)

+ 4 - 5
lib/IGameCallback.h

@@ -31,6 +31,8 @@ struct BankConfig;
 class CCreatureSet;
 class CCreatureSet;
 class CStackBasicDescriptor;
 class CStackBasicDescriptor;
 class CGCreature;
 class CGCreature;
+class CSaveFile;
+class CLoadFile;
 enum class EOpenWindowMode : uint8_t;
 enum class EOpenWindowMode : uint8_t;
 
 
 namespace spells
 namespace spells
@@ -74,11 +76,8 @@ public:
 	void pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand);
 	void pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand);
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 
 
-	template<typename Saver>
-	void saveCommonState(Saver &out) const; //stores GS and VLC
-
-	template<typename Loader>
-	void loadCommonState(Loader &in); //loads GS and VLC
+	void saveCommonState(CSaveFile &out) const; //stores GS and VLC
+	void loadCommonState(CLoadFile &in); //loads GS and VLC
 };
 };
 
 
 class DLL_LINKAGE IGameEventCallback
 class DLL_LINKAGE IGameEventCallback

+ 1 - 1
lib/IGameEventsReceiver.h

@@ -109,7 +109,7 @@ public:
 
 
 	virtual void showPuzzleMap(){};
 	virtual void showPuzzleMap(){};
 	virtual void viewWorldMap(){};
 	virtual void viewWorldMap(){};
-	virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
+	virtual void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID){};
 	virtual void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
 	virtual void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
 	virtual void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor){};
 	virtual void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor){};
 	virtual void showThievesGuildWindow (const CGObjectInstance * obj){};
 	virtual void showThievesGuildWindow (const CGObjectInstance * obj){};

+ 12 - 2
lib/battle/AccessibilityInfo.cpp

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

+ 2 - 0
lib/battle/AccessibilityInfo.h

@@ -35,6 +35,8 @@ using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZ
 
 
 struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
 struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
 {
 {
+	std::map<BattleHex, ui8> destructibleEnemyTurns;
+
 	public:
 	public:
 		bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
 		bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
 		bool accessible(BattleHex tile, bool doubleWide, BattleSide side) const; //checks for both tiles if stack is double wide
 		bool accessible(BattleHex tile, bool doubleWide, BattleSide side) const; //checks for both tiles if stack is double wide

+ 23 - 4
lib/battle/CBattleInfoCallback.cpp

@@ -1051,17 +1051,30 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 		if(isInObstacle(curHex, obstacles, checkParams))
 		if(isInObstacle(curHex, obstacles, checkParams))
 			continue;
 			continue;
 
 
-		const int costToNeighbour = ret.distances[curHex.hex] + 1;
+		const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
+
 		for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
 		for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
 		{
 		{
 			if(neighbour.isValid())
 			if(neighbour.isValid())
 			{
 			{
+				auto additionalCost = 0;
+
+				if(params.bypassEnemyStacks)
+				{
+					auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour);
+
+					if(enemyToBypass != params.destructibleEnemyTurns.end())
+					{
+						additionalCost = enemyToBypass->second;
+					}
+				}
+
 				const int costFoundSoFar = ret.distances[neighbour.hex];
 				const int costFoundSoFar = ret.distances[neighbour.hex];
 
 
-				if(accessibleCache[neighbour.hex] && costToNeighbour < costFoundSoFar)
+				if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar)
 				{
 				{
 					hexq.push(neighbour);
 					hexq.push(neighbour);
-					ret.distances[neighbour.hex] = costToNeighbour;
+					ret.distances[neighbour.hex] = costToNeighbour + additionalCost;
 					ret.predecessors[neighbour.hex] = curHex;
 					ret.predecessors[neighbour.hex] = curHex;
 				}
 				}
 			}
 			}
@@ -1236,7 +1249,13 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
 	if(params.flying)
 	if(params.flying)
 		return getFlyingReachability(params);
 		return getFlyingReachability(params);
 	else
 	else
-		return makeBFS(getAccessibility(params.knownAccessible), params);
+	{
+		auto accessibility = getAccessibility(params.knownAccessible);
+
+		accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns;
+
+		return makeBFS(accessibility, params);
+	}
 }
 }
 
 
 ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters &params) const
 ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters &params) const

+ 2 - 2
lib/battle/CUnitState.cpp

@@ -900,9 +900,9 @@ CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBear
 {
 {
 }
 }
 
 
-TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root, const std::string & cachingStr) const
+TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
 {
 {
-	return bonus->getAllBonuses(selector, limit, root, cachingStr);
+	return bonus->getAllBonuses(selector, limit, cachingStr);
 }
 }
 
 
 int64_t CUnitStateDetached::getTreeVersion() const
 int64_t CUnitStateDetached::getTreeVersion() const

+ 1 - 1
lib/battle/CUnitState.h

@@ -282,7 +282,7 @@ public:
 	explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_);
 	explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_);
 
 
 	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
 	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
-		const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
+		const std::string & cachingStr = "") const override;
 
 
 	int64_t getTreeVersion() const override;
 	int64_t getTreeVersion() const override;
 
 

+ 2 - 0
lib/battle/ReachabilityInfo.h

@@ -29,7 +29,9 @@ struct DLL_LINKAGE ReachabilityInfo
 		bool doubleWide = false;
 		bool doubleWide = false;
 		bool flying = false;
 		bool flying = false;
 		bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
 		bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
+		bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
 		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::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
 
 
 		BattleHex startPosition; //assumed position of stack
 		BattleHex startPosition; //assumed position of stack
 		BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side
 		BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side

+ 5 - 28
lib/bonuses/CBonusSystemNode.cpp

@@ -107,10 +107,9 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & select
 	}
 	}
 }
 }
 
 
-TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
+TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
 {
 {
-	bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
-	if (CBonusSystemNode::cachingEnabled && limitOnUs)
+	if (CBonusSystemNode::cachingEnabled)
 	{
 	{
 		// Exclusive access for one thread
 		// Exclusive access for one thread
 		boost::lock_guard<boost::mutex> lock(sync);
 		boost::lock_guard<boost::mutex> lock(sync);
@@ -157,11 +156,11 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 	}
 	}
 	else
 	else
 	{
 	{
-		return getAllBonusesWithoutCaching(selector, limit, root);
+		return getAllBonusesWithoutCaching(selector, limit);
 	}
 	}
 }
 }
 
 
-TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const
+TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
 {
 {
 	auto ret = std::make_shared<BonusList>();
 	auto ret = std::make_shared<BonusList>();
 
 
@@ -169,29 +168,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector
 	BonusList beforeLimiting;
 	BonusList beforeLimiting;
 	BonusList afterLimiting;
 	BonusList afterLimiting;
 	getAllBonusesRec(beforeLimiting, selector);
 	getAllBonusesRec(beforeLimiting, selector);
-
-	if(!root || root == this)
-	{
-		limitBonuses(beforeLimiting, afterLimiting);
-	}
-	else if(root)
-	{
-		//We want to limit our query against an external node. We get all its bonuses,
-		// add the ones we're considering and see if they're cut out by limiters
-		BonusList rootBonuses;
-		BonusList limitedRootBonuses;
-		getAllBonusesRec(rootBonuses, selector);
-
-		for(const auto & b : beforeLimiting)
-			rootBonuses.push_back(b);
-
-		root->limitBonuses(rootBonuses, limitedRootBonuses);
-
-		for(const auto & b : beforeLimiting)
-			if(vstd::contains(limitedRootBonuses, b))
-				afterLimiting.push_back(b);
-
-	}
+	limitBonuses(beforeLimiting, afterLimiting);
 	afterLimiting.getBonuses(*ret, selector, limit);
 	afterLimiting.getBonuses(*ret, selector, limit);
 	ret->stackBonuses();
 	ret->stackBonuses();
 	return ret;
 	return ret;

+ 2 - 2
lib/bonuses/CBonusSystemNode.h

@@ -53,7 +53,7 @@ private:
 	mutable boost::mutex sync;
 	mutable boost::mutex sync;
 
 
 	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
 	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
-	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
+	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
 
 
 	void getRedParents(TCNodes &out) const;  //retrieves list of red parent nodes (nodes bonuses propagate from)
 	void getRedParents(TCNodes &out) const;  //retrieves list of red parent nodes (nodes bonuses propagate from)
@@ -86,7 +86,7 @@ public:
 
 
 	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
 	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
 	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convenience
 	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convenience
-	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 
 
 	/// Returns first bonus matching selector
 	/// Returns first bonus matching selector

+ 3 - 3
lib/bonuses/IBonusBearer.cpp

@@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
 int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
 {
 {
-	TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, nullptr, cachingStr);
+	TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, cachingStr);
 	return hlp->totalValue();
 	return hlp->totalValue();
 }
 }
 
 
@@ -34,12 +34,12 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, c
 
 
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
 {
 {
-	return getAllBonuses(selector, nullptr, nullptr, cachingStr);
+	return getAllBonuses(selector, nullptr, cachingStr);
 }
 }
 
 
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
 {
 {
-	return getAllBonuses(selector, limit, nullptr, cachingStr);
+	return getAllBonuses(selector, limit, cachingStr);
 }
 }
 
 
 int IBonusBearer::valOfBonuses(BonusType type) const
 int IBonusBearer::valOfBonuses(BonusType type) const

+ 1 - 1
lib/bonuses/IBonusBearer.h

@@ -22,7 +22,7 @@ public:
 	//interface
 	//interface
 	IBonusBearer() = default;
 	IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
-	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
+	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0;
 	int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
 	int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
 	bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const;
 	bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const;
 	bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;
 	bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;

+ 8 - 0
lib/constants/EntityIdentifiers.h

@@ -352,6 +352,14 @@ public:
 		return (dwelling - DWELL_FIRST) / (GameConstants::CREATURES_PER_TOWN - 1);
 		return (dwelling - DWELL_FIRST) / (GameConstants::CREATURES_PER_TOWN - 1);
 	}
 	}
 
 
+	static void advanceDwelling(BuildingIDBase & dwelling)
+	{
+		if(dwelling != BuildingIDBase::DWELL_LVL_8)
+			dwelling.advance(GameConstants::CREATURES_PER_TOWN - 1);
+		else
+			dwelling.advance(1);
+	}
+
 	bool IsSpecialOrGrail() const
 	bool IsSpecialOrGrail() const
 	{
 	{
 		return num == SPECIAL_1 || num == SPECIAL_2 || num == SPECIAL_3 || num == SPECIAL_4 || num == GRAIL;
 		return num == SPECIAL_1 || num == SPECIAL_2 || num == SPECIAL_3 || num == SPECIAL_4 || num == GRAIL;

+ 2 - 1
lib/entities/building/CBuilding.h

@@ -34,6 +34,7 @@ public:
 	TResources resources;
 	TResources resources;
 	TResources produce;
 	TResources produce;
 	TRequired requirements;
 	TRequired requirements;
+	std::set<EMarketMode> marketModes;
 
 
 	BuildingID bid; //structure ID
 	BuildingID bid; //structure ID
 	BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
 	BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
@@ -85,7 +86,7 @@ public:
 	STRONG_INLINE
 	STRONG_INLINE
 		bool IsTradeBuilding() const
 		bool IsTradeBuilding() const
 	{
 	{
-		return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD;
+		return !marketModes.empty();
 	}
 	}
 
 
 	void addNewBonus(const std::shared_ptr<Bonus> & b, BonusList & bonusList) const;
 	void addNewBonus(const std::shared_ptr<Bonus> & b, BonusList & bonusList) const;

+ 5 - 0
lib/entities/faction/CTownHandler.cpp

@@ -342,6 +342,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 		ret->upgrade = BuildingID::NONE;
 		ret->upgrade = BuildingID::NONE;
 
 
 	ret->town->buildings[ret->bid] = ret;
 	ret->town->buildings[ret->bid] = ret;
+	for(const auto & element : source["marketModes"].Vector())
+	{
+		if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String()))
+			ret->marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String()));
+	}
 
 
 	registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
 	registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
 }
 }

+ 46 - 71
lib/gameState/CGameState.cpp

@@ -45,12 +45,11 @@
 #include "../mapping/CMapService.h"
 #include "../mapping/CMapService.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/ModScope.h"
 #include "../modding/ModScope.h"
+#include "../networkPacks/NetPacksBase.h"
 #include "../pathfinder/CPathfinder.h"
 #include "../pathfinder/CPathfinder.h"
 #include "../pathfinder/PathfinderOptions.h"
 #include "../pathfinder/PathfinderOptions.h"
-#include "../registerTypes/RegisterTypesClientPacks.h"
 #include "../rmg/CMapGenerator.h"
 #include "../rmg/CMapGenerator.h"
 #include "../serializer/CMemorySerializer.h"
 #include "../serializer/CMemorySerializer.h"
-#include "../serializer/CTypeList.h"
 #include "../spells/CSpellHandler.h"
 #include "../spells/CSpellHandler.h"
 
 
 #include <vstd/RNG.h>
 #include <vstd/RNG.h>
@@ -59,29 +58,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 boost::shared_mutex CGameState::mutex;
 boost::shared_mutex CGameState::mutex;
 
 
-template <typename T> class CApplyOnGS;
-
-class CBaseForGSApply
-{
-public:
-	virtual void applyOnGS(CGameState *gs, CPack * pack) const =0;
-	virtual ~CBaseForGSApply() = default;
-	template<typename U> static CBaseForGSApply *getApplier(const U * t=nullptr)
-	{
-		return new CApplyOnGS<U>();
-	}
-};
-
-template <typename T> class CApplyOnGS : public CBaseForGSApply
-{
-public:
-	void applyOnGS(CGameState *gs, CPack * pack) const override
-	{
-		T *ptr = static_cast<T*>(pack);
-		ptr->applyGs(gs);
-	}
-};
-
 HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
 HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
 {
 {
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@@ -165,8 +141,6 @@ CGameState::CGameState()
 {
 {
 	gs = this;
 	gs = this;
 	heroesPool = std::make_unique<TavernHeroesPool>();
 	heroesPool = std::make_unique<TavernHeroesPool>();
-	applier = std::make_shared<CApplier<CBaseForGSApply>>();
-	registerTypesClientPacks(*applier);
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
 }
 }
 
 
@@ -303,6 +277,27 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 		std::unique_ptr<CMap> randomMap = mapGenerator.generate();
 		std::unique_ptr<CMap> randomMap = mapGenerator.generate();
 		progressTracking.exclude(mapGenerator);
 		progressTracking.exclude(mapGenerator);
 
 
+		// Update starting options
+		for(int i = 0; i < randomMap->players.size(); ++i)
+		{
+			const auto & playerInfo = randomMap->players[i];
+			if(playerInfo.canAnyonePlay())
+			{
+				PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
+				playerSettings.compOnly = !playerInfo.canHumanPlay;
+				playerSettings.castle = playerInfo.defaultCastle();
+				if(playerSettings.isControlledByAI() && playerSettings.name.empty())
+				{
+					playerSettings.name = VLC->generaltexth->allTexts[468];
+				}
+				playerSettings.color = PlayerColor(i);
+			}
+			else
+			{
+				scenarioOps->playerInfos.erase(PlayerColor(i));
+			}
+		}
+
 		if(allowSavingRandomMap)
 		if(allowSavingRandomMap)
 		{
 		{
 			try
 			try
@@ -332,26 +327,6 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 		}
 		}
 
 
 		map = randomMap.release();
 		map = randomMap.release();
-		// Update starting options
-		for(int i = 0; i < map->players.size(); ++i)
-		{
-			const auto & playerInfo = map->players[i];
-			if(playerInfo.canAnyonePlay())
-			{
-				PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
-				playerSettings.compOnly = !playerInfo.canHumanPlay;
-				playerSettings.castle = playerInfo.defaultCastle();
-				if(playerSettings.isControlledByAI() && playerSettings.name.empty())
-				{
-					playerSettings.name = VLC->generaltexth->allTexts[468];
-				}
-				playerSettings.color = PlayerColor(i);
-			}
-			else
-			{
-				scenarioOps->playerInfos.erase(PlayerColor(i));
-			}
-		}
 
 
 		logGlobal->info("Generated random map in %i ms.", sw.getDiff());
 		logGlobal->info("Generated random map in %i ms.", sw.getDiff());
 	}
 	}
@@ -807,12 +782,12 @@ void CGameState::initTowns()
 		constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7, BuildingID::HORDE_PLACEHOLDER8 };
 		constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7, BuildingID::HORDE_PLACEHOLDER8 };
 
 
 		//init buildings
 		//init buildings
-		if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings
+		if(vti->hasBuilt(BuildingID::DEFAULT)) //give standard set of buildings
 		{
 		{
-			vti->builtBuildings.erase(BuildingID::DEFAULT);
-			vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
+			vti->removeBuilding(BuildingID::DEFAULT);
+			vti->addBuilding(BuildingID::VILLAGE_HALL);
 			if(vti->tempOwner != PlayerColor::NEUTRAL)
 			if(vti->tempOwner != PlayerColor::NEUTRAL)
-				vti->builtBuildings.insert(BuildingID::TAVERN);
+				vti->addBuilding(BuildingID::TAVERN);
 
 
 			auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
 			auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
 
 
@@ -820,48 +795,49 @@ void CGameState::initTowns()
 			{
 			{
 				if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i]))
 				if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i]))
 				{
 				{
-					vti->builtBuildings.insert(basicDwellings[i]);
+					vti->addBuilding(basicDwellings[i]);
 				}
 				}
 			}
 			}
 		}
 		}
 
 
 		// village hall must always exist
 		// village hall must always exist
-		vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
+		vti->addBuilding(BuildingID::VILLAGE_HALL);
 
 
 		//init hordes
 		//init hordes
 		for (int i = 0; i < vti->town->creatures.size(); i++)
 		for (int i = 0; i < vti->town->creatures.size(); i++)
 		{
 		{
-			if (vstd::contains(vti->builtBuildings, hordes[i])) //if we have horde for this level
+			if(vti->hasBuilt(hordes[i])) //if we have horde for this level
 			{
 			{
-				vti->builtBuildings.erase(hordes[i]);//remove old ID
+				vti->removeBuilding(hordes[i]);//remove old ID
 				if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one
 				if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one
 				{
 				{
-					vti->builtBuildings.insert(BuildingID::HORDE_1);//add it
+					vti->addBuilding(BuildingID::HORDE_1);//add it
 					//if we have upgraded dwelling as well
 					//if we have upgraded dwelling as well
-					if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
-						vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well
+					if(vti->hasBuilt(upgradedDwellings[i]))
+						vti->addBuilding(BuildingID::HORDE_1_UPGR);//add it as well
 				}
 				}
 				if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one
 				if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one
 				{
 				{
-					vti->builtBuildings.insert(BuildingID::HORDE_2);
-					if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
-						vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR);
+					vti->addBuilding(BuildingID::HORDE_2);
+					if(vti->hasBuilt(upgradedDwellings[i]))
+						vti->addBuilding(BuildingID::HORDE_2_UPGR);
 				}
 				}
 			}
 			}
 		}
 		}
 
 
 		//#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings)
 		//#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings)
 		//But DO NOT remove horde placeholders before they are replaced
 		//But DO NOT remove horde placeholders before they are replaced
-		vstd::erase_if(vti->builtBuildings, [vti](const BuildingID & bid)
-			{
-				return !vti->getTown()->buildings.count(bid) || !vti->getTown()->buildings.at(bid);
-			});
+		for(const auto & building : vti->getBuildings())
+		{
+			if(!vti->getTown()->buildings.count(building) || !vti->getTown()->buildings.at(building))
+				vti->removeBuilding(building);
+		}
 
 
-		if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
-			vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
+		if(vti->hasBuilt(BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
+			vti->removeBuilding(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
 
 
 		//Early check for #1444-like problems
 		//Early check for #1444-like problems
-		for([[maybe_unused]] const auto & building : vti->builtBuildings)
+		for([[maybe_unused]] const auto & building : vti->getBuildings())
 		{
 		{
 			assert(vti->getTown()->buildings.at(building) != nullptr);
 			assert(vti->getTown()->buildings.at(building) != nullptr);
 		}
 		}
@@ -1144,10 +1120,9 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor
 	return PlayerRelations::ENEMIES;
 	return PlayerRelations::ENEMIES;
 }
 }
 
 
-void CGameState::apply(CPack *pack)
+void CGameState::apply(CPackForClient *pack)
 {
 {
-	ui16 typ = CTypeList::getInstance().getTypeID(pack);
-	applier->getApplier(typ)->applyOnGS(this, pack);
+	pack->applyGs(this);
 }
 }
 
 
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)

+ 1 - 5
lib/gameState/CGameState.h

@@ -38,9 +38,6 @@ class TavernHeroesPool;
 struct SThievesGuildInfo;
 struct SThievesGuildInfo;
 class CRandomGenerator;
 class CRandomGenerator;
 
 
-template<typename T> class CApplier;
-class CBaseForGSApply;
-
 struct UpgradeInfo
 struct UpgradeInfo
 {
 {
 	CreatureID oldID; //creature to be upgraded
 	CreatureID oldID; //creature to be upgraded
@@ -101,7 +98,7 @@ public:
 	/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	HeroTypeID pickNextHeroType(const PlayerColor & owner);
 	HeroTypeID pickNextHeroType(const PlayerColor & owner);
 
 
-	void apply(CPack *pack);
+	void apply(CPackForClient *pack);
 	BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
 	BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
 
 
 	void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
 	void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
@@ -215,7 +212,6 @@ private:
 	UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
 	UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
 
 
 	// ---- data -----
 	// ---- data -----
-	std::shared_ptr<CApplier<CBaseForGSApply>> applier;
 	Services * services;
 	Services * services;
 
 
 	/// Pointer to campaign state manager. Nullptr for single scenarios
 	/// Pointer to campaign state manager. Nullptr for single scenarios

+ 3 - 3
lib/gameState/CGameStateCampaign.cpp

@@ -656,7 +656,7 @@ void CGameStateCampaign::initTowns()
 		if(gameState->scenarioOps->campState->formatVCMI())
 		if(gameState->scenarioOps->campState->formatVCMI())
 			newBuilding = BuildingID(chosenBonus->info1);
 			newBuilding = BuildingID(chosenBonus->info1);
 		else
 		else
-			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->builtBuildings);
+			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings());
 
 
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		while(true)
 		while(true)
@@ -664,10 +664,10 @@ void CGameStateCampaign::initTowns()
 			if (newBuilding == BuildingID::NONE)
 			if (newBuilding == BuildingID::NONE)
 				break;
 				break;
 
 
-			if (town->builtBuildings.count(newBuilding) != 0)
+			if(town->hasBuilt(newBuilding))
 				break;
 				break;
 
 
-			town->builtBuildings.insert(newBuilding);
+			town->addBuilding(newBuilding);
 
 
 			auto building = town->town->buildings.at(newBuilding);
 			auto building = town->town->buildings.at(newBuilding);
 			newBuilding = building->upgrade;
 			newBuilding = building->upgrade;

+ 1 - 1
lib/gameState/GameStatistics.cpp

@@ -372,7 +372,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps)
 
 
 	for(const auto & t : ps->towns)
 	for(const auto & t : ps->towns)
 	{
 	{
-		built += t->builtBuildings.size();
+		built += t->getBuildings().size();
 		for(const auto & b : t->town->buildings)
 		for(const auto & b : t->town->buildings)
 			if(!t->forbiddenBuildings.count(b.first))
 			if(!t->forbiddenBuildings.count(b.first))
 				total += 1;
 				total += 1;

+ 1 - 1
lib/gameState/HighScore.cpp

@@ -33,7 +33,7 @@ HighScoreParameter HighScore::prepareHighScores(const CGameState * gs, PlayerCol
 		if(h->hasArt(ArtifactID::GRAIL))
 		if(h->hasArt(ArtifactID::GRAIL))
 			param.hasGrail = true;
 			param.hasGrail = true;
 	for(const CGTownInstance * t : playerState->towns)
 	for(const CGTownInstance * t : playerState->towns)
-		if(t->builtBuildings.count(BuildingID::GRAIL))
+		if(t->hasBuilt(BuildingID::GRAIL))
 			param.hasGrail = true;
 			param.hasGrail = true;
 	param.allEnemiesDefeated = true;
 	param.allEnemiesDefeated = true;
 	for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer)
 	for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer)

+ 71 - 68
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -120,7 +120,7 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 		legacyTemplates.insert(std::make_pair(key, tmpl));
 		legacyTemplates.insert(std::make_pair(key, tmpl));
 	}
 	}
 
 
-	objects.resize(256);
+	mapObjectTypes.resize(256);
 
 
 	std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
 	std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
 	assert(dataSize == 256);
 	assert(dataSize == 256);
@@ -162,39 +162,39 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 	return ret;
 	return ret;
 }
 }
 
 
-void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj)
+void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject)
 {
 {
-	auto object = loadSubObjectFromJson(scope, identifier, entry, obj, obj->objects.size());
+	auto subObject = loadSubObjectFromJson(scope, identifier, entry, baseObject, baseObject->objectTypeHandlers.size());
 
 
-	assert(object);
-	obj->objects.push_back(object);
+	assert(subObject);
+	baseObject->objectTypeHandlers.push_back(subObject);
 
 
-	registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype);
+	registerObject(scope, baseObject->getJsonKey(), subObject->getSubTypeName(), subObject->subtype);
 	for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
 	for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
-		registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype);
+		registerObject(scope, baseObject->getJsonKey(), compatID.String(), subObject->subtype);
 }
 }
 
 
-void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index)
+void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index)
 {
 {
-	auto object = loadSubObjectFromJson(scope, identifier, entry, obj, index);
+	auto subObject = loadSubObjectFromJson(scope, identifier, entry, baseObject, index);
 
 
-	assert(object);
-	if (obj->objects.at(index) != nullptr)
+	assert(subObject);
+	if (baseObject->objectTypeHandlers.at(index) != nullptr)
 		throw std::runtime_error("Attempt to load already loaded object:" + identifier);
 		throw std::runtime_error("Attempt to load already loaded object:" + identifier);
 
 
-	obj->objects.at(index) = object;
+	baseObject->objectTypeHandlers.at(index) = subObject;
 
 
-	registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype);
+	registerObject(scope, baseObject->getJsonKey(), subObject->getSubTypeName(), subObject->subtype);
 	for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
 	for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
-		registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype);
+		registerObject(scope, baseObject->getJsonKey(), compatID.String(), subObject->subtype);
 }
 }
 
 
-TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index)
+TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index)
 {
 {
 	assert(identifier.find(':') == std::string::npos);
 	assert(identifier.find(':') == std::string::npos);
 	assert(!scope.empty());
 	assert(!scope.empty());
 
 
-	std::string handler = obj->handlerName;
+	std::string handler = baseObject->handlerName;
 	if(!handlerConstructors.count(handler))
 	if(!handlerConstructors.count(handler))
 	{
 	{
 		logMod->error("Handler with name %s was not found!", handler);
 		logMod->error("Handler with name %s was not found!", handler);
@@ -206,10 +206,10 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
 	auto createdObject = handlerConstructors.at(handler)();
 	auto createdObject = handlerConstructors.at(handler)();
 
 
 	createdObject->modScope = scope;
 	createdObject->modScope = scope;
-	createdObject->typeName = obj->identifier;
+	createdObject->typeName = baseObject->identifier;
 	createdObject->subTypeName = identifier;
 	createdObject->subTypeName = identifier;
 
 
-	createdObject->type = obj->id;
+	createdObject->type = baseObject->id;
 	createdObject->subtype = index;
 	createdObject->subtype = index;
 	createdObject->init(entry);
 	createdObject->init(entry);
 
 
@@ -223,7 +223,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
 		}
 		}
 	}
 	}
 
 
-	auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index));
+	auto range = legacyTemplates.equal_range(std::make_pair(baseObject->id, index));
 	for (auto & templ : boost::make_iterator_range(range.first, range.second))
 	for (auto & templ : boost::make_iterator_range(range.first, range.second))
 	{
 	{
 		if (staticObject)
 		if (staticObject)
@@ -238,7 +238,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
 	}
 	}
 	legacyTemplates.erase(range.first, range.second);
 	legacyTemplates.erase(range.first, range.second);
 
 
-	logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getJsonKey(), obj->id, identifier, index);
+	logGlobal->debug("Loaded object %s(%d)::%s(%d)", baseObject->getJsonKey(), baseObject->id, identifier, index);
 
 
 	return createdObject;
 	return createdObject;
 }
 }
@@ -263,17 +263,17 @@ std::string ObjectClass::getNameTranslated() const
 
 
 std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index)
 std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index)
 {
 {
-	auto obj = std::make_unique<ObjectClass>();
+	auto newObject = std::make_unique<ObjectClass>();
 
 
-	obj->modScope = scope;
-	obj->identifier = name;
-	obj->handlerName = json["handler"].String();
-	obj->base = json["base"];
-	obj->id = index;
+	newObject->modScope = scope;
+	newObject->identifier = name;
+	newObject->handlerName = json["handler"].String();
+	newObject->base = json["base"];
+	newObject->id = index;
 
 
-	VLC->generaltexth->registerString(scope, obj->getNameTextID(), json["name"].String());
+	VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"].String());
 
 
-	obj->objects.resize(json["lastReservedIndex"].Float() + 1);
+	newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1);
 
 
 	for (auto subData : json["types"].Struct())
 	for (auto subData : json["types"].Struct())
 	{
 	{
@@ -284,68 +284,71 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
 			if ( subMeta == "core")
 			if ( subMeta == "core")
 			{
 			{
 				size_t subIndex = subData.second["index"].Integer();
 				size_t subIndex = subData.second["index"].Integer();
-				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get(), subIndex);
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get(), subIndex);
 			}
 			}
 			else
 			else
 			{
 			{
 				logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
 				logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
-				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get());
 			}
 			}
 		}
 		}
 		else
 		else
-			loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
+			loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get());
 	}
 	}
 
 
-	if (obj->id == MapObjectID::MONOLITH_TWO_WAY)
-		generateExtraMonolithsForRMG(obj.get());
+	if (newObject->id == MapObjectID::MONOLITH_TWO_WAY)
+		generateExtraMonolithsForRMG(newObject.get());
 
 
-	return obj;
+	return newObject;
 }
 }
 
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
 {
-	objects.push_back(loadFromJson(scope, data, name, objects.size()));
+	mapObjectTypes.push_back(loadFromJson(scope, data, name, mapObjectTypes.size()));
 
 
-	VLC->identifiersHandler->registerObject(scope, "object", name, objects.back()->id);
+	VLC->identifiersHandler->registerObject(scope, "object", name, mapObjectTypes.back()->id);
 }
 }
 
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
 {
-	assert(objects.at(index) == nullptr); // ensure that this id was not loaded before
+	assert(mapObjectTypes.at(index) == nullptr); // ensure that this id was not loaded before
 
 
-	objects.at(index) = loadFromJson(scope, data, name, index);
-	VLC->identifiersHandler->registerObject(scope, "object", name, objects.at(index)->id);
+	mapObjectTypes.at(index) = loadFromJson(scope, data, name, index);
+	VLC->identifiersHandler->registerObject(scope, "object", name, mapObjectTypes.at(index)->id);
 }
 }
 
 
 void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID)
 void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID)
 {
 {
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
-	assert(objects.at(ID.getNum()));
 
 
-	if ( subID.getNum() >= objects.at(ID.getNum())->objects.size())
-		objects.at(ID.getNum())->objects.resize(subID.getNum()+1);
+	assert(mapObjectTypes.at(ID.getNum()));
 
 
-	JsonUtils::inherit(config, objects.at(ID.getNum())->base);
-	loadSubObject(config.getModScope(), identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
+	if (subID.getNum() >= mapObjectTypes.at(ID.getNum())->objectTypeHandlers.size())
+	{
+		mapObjectTypes.at(ID.getNum())->objectTypeHandlers.resize(subID.getNum() + 1);
+	}
+
+	JsonUtils::inherit(config, mapObjectTypes.at(ID.getNum())->base);
+	loadSubObject(config.getModScope(), identifier, config, mapObjectTypes.at(ID.getNum()).get(), subID.getNum());
 }
 }
 
 
 void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)
 void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)
 {
 {
-	assert(objects.at(ID.getNum()));
-	objects.at(ID.getNum())->objects.at(subID.getNum()) = nullptr;
+	assert(mapObjectTypes.at(ID.getNum()));
+	mapObjectTypes.at(ID.getNum())->objectTypeHandlers.at(subID.getNum()) = nullptr;
 }
 }
 
 
 TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) const
 TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) const
 {
 {
 	try
 	try
 	{
 	{
-		if (objects.at(type.getNum()) == nullptr)
-			return objects.front()->objects.front();
+		if (mapObjectTypes.at(type.getNum()) == nullptr)
+			return mapObjectTypes.front()->objectTypeHandlers.front();
 
 
 		auto subID = subtype.getNum();
 		auto subID = subtype.getNum();
 		if (type == Obj::PRISON)
 		if (type == Obj::PRISON)
 			subID = 0;
 			subID = 0;
-		auto result = objects.at(type.getNum())->objects.at(subID);
+		auto result = mapObjectTypes.at(type.getNum())->objectTypeHandlers.at(subID);
 
 
 		if (result != nullptr)
 		if (result != nullptr)
 			return result;
 			return result;
@@ -365,11 +368,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scop
 	std::optional<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type);
 	std::optional<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type);
 	if(id)
 	if(id)
 	{
 	{
-		const auto & object = objects.at(id.value());
+		const auto & object = mapObjectTypes.at(id.value());
 		std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
 		std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
 
 
 		if (subID)
 		if (subID)
-			return object->objects.at(subID.value());
+			return object->objectTypeHandlers.at(subID.value());
 	}
 	}
 
 
 	std::string errorString = "Failed to find object of type " + type + "::" + subtype;
 	std::string errorString = "Failed to find object of type " + type + "::" + subtype;
@@ -386,7 +389,7 @@ std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
 {
 {
 	std::set<MapObjectID> ret;
 	std::set<MapObjectID> ret;
 
 
-	for(auto & entry : objects)
+	for(auto & entry : mapObjectTypes)
 		if (entry)
 		if (entry)
 			ret.insert(entry->id);
 			ret.insert(entry->id);
 
 
@@ -397,13 +400,13 @@ std::set<MapObjectSubID> CObjectClassesHandler::knownSubObjects(MapObjectID prim
 {
 {
 	std::set<MapObjectSubID> ret;
 	std::set<MapObjectSubID> ret;
 
 
-	if (!objects.at(primaryID.getNum()))
+	if (!mapObjectTypes.at(primaryID.getNum()))
 	{
 	{
 		logGlobal->error("Failed to find object %d", primaryID);
 		logGlobal->error("Failed to find object %d", primaryID);
 		return ret;
 		return ret;
 	}
 	}
 
 
-	for(const auto & entry : objects.at(primaryID.getNum())->objects)
+	for(const auto & entry : mapObjectTypes.at(primaryID.getNum())->objectTypeHandlers)
 		if (entry)
 		if (entry)
 			ret.insert(entry->subtype);
 			ret.insert(entry->subtype);
 
 
@@ -436,12 +439,12 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object)
 
 
 void CObjectClassesHandler::afterLoadFinalization()
 void CObjectClassesHandler::afterLoadFinalization()
 {
 {
-	for(auto & entry : objects)
+	for(auto & entry : mapObjectTypes)
 	{
 	{
 		if (!entry)
 		if (!entry)
 			continue;
 			continue;
 
 
-		for(const auto & obj : entry->objects)
+		for(const auto & obj : entry->objectTypeHandlers)
 		{
 		{
 			if (!obj)
 			if (!obj)
 				continue;
 				continue;
@@ -456,7 +459,7 @@ void CObjectClassesHandler::afterLoadFinalization()
 void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container)
 void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container)
 {
 {
 	//duplicate existing two-way portals to make reserve for RMG
 	//duplicate existing two-way portals to make reserve for RMG
-	auto& portalVec = container->objects;
+	auto& portalVec = container->objectTypeHandlers;
 	//FIXME: Monoliths  in this vector can be already not useful for every terrain
 	//FIXME: Monoliths  in this vector can be already not useful for every terrain
 	const size_t portalCount = portalVec.size();
 	const size_t portalCount = portalVec.size();
 
 
@@ -500,10 +503,10 @@ std::string CObjectClassesHandler::getObjectName(MapObjectID type, MapObjectSubI
 	if (handler && handler->hasNameTextID())
 	if (handler && handler->hasNameTextID())
 		return handler->getNameTranslated();
 		return handler->getNameTranslated();
 
 
-	if (objects.at(type.getNum()))
-		return objects.at(type.getNum())->getNameTranslated();
+	if (mapObjectTypes.at(type.getNum()))
+		return mapObjectTypes.at(type.getNum())->getNameTranslated();
 
 
-	return objects.front()->getNameTranslated();
+	return mapObjectTypes.front()->getNameTranslated();
 }
 }
 
 
 SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObjectSubID subtype) const
 SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObjectSubID subtype) const
@@ -515,27 +518,27 @@ SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObject
 	if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
 	if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
 		subtype = 0;
 		subtype = 0;
 
 
-	if(objects.at(type.getNum()))
+	if(mapObjectTypes.at(type.getNum()))
 		return getHandlerFor(type, subtype)->getSounds();
 		return getHandlerFor(type, subtype)->getSounds();
 	else
 	else
-		return objects.front()->objects.front()->getSounds();
+		return mapObjectTypes.front()->objectTypeHandlers.front()->getSounds();
 }
 }
 
 
 std::string CObjectClassesHandler::getObjectHandlerName(MapObjectID type) const
 std::string CObjectClassesHandler::getObjectHandlerName(MapObjectID type) const
 {
 {
-	if (objects.at(type.getNum()))
-		return objects.at(type.getNum())->handlerName;
+	if (mapObjectTypes.at(type.getNum()))
+		return mapObjectTypes.at(type.getNum())->handlerName;
 	else
 	else
-		return objects.front()->handlerName;
+		return mapObjectTypes.front()->handlerName;
 }
 }
 
 
 std::string CObjectClassesHandler::getJsonKey(MapObjectID type) const
 std::string CObjectClassesHandler::getJsonKey(MapObjectID type) const
 {
 {
-	if (objects.at(type.getNum()) != nullptr)
-		return objects.at(type.getNum())->getJsonKey();
+	if (mapObjectTypes.at(type.getNum()) != nullptr)
+		return mapObjectTypes.at(type.getNum())->getJsonKey();
 
 
 	logGlobal->warn("Unknown object of type %d!", type);
 	logGlobal->warn("Unknown object of type %d!", type);
-	return objects.front()->getJsonKey();
+	return mapObjectTypes.front()->getJsonKey();
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -55,7 +55,7 @@ public:
 	std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map
 	std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map
 
 
 	JsonNode base;
 	JsonNode base;
-	std::vector<TObjectTypeHandler> objects;
+	std::vector<TObjectTypeHandler> objectTypeHandlers;
 
 
 	ObjectClass();
 	ObjectClass();
 	~ObjectClass();
 	~ObjectClass();
@@ -69,7 +69,7 @@ public:
 class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
 class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
 {
 {
 	/// list of object handlers, each of them handles only one type
 	/// list of object handlers, each of them handles only one type
-	std::vector< std::unique_ptr<ObjectClass> > objects;
+	std::vector< std::unique_ptr<ObjectClass> > mapObjectTypes;
 
 
 	/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function
 	/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function
 	std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
 	std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;

+ 10 - 12
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -238,25 +238,23 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const
 				return new CGUniversity(cb);
 				return new CGUniversity(cb);
 		}
 		}
 	}
 	}
-	else if(marketModes.size() == 2)
-	{
-		if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP))
-			return new CGArtifactsAltar(cb);
-	}
 	return new CGMarket(cb);
 	return new CGMarket(cb);
 }
 }
 
 
 void MarketInstanceConstructor::initializeObject(CGMarket * market) const
 void MarketInstanceConstructor::initializeObject(CGMarket * market) const
 {
 {
-	market->marketModes = marketModes;
+	market->addMarketMode(marketModes);
 	market->marketEfficiency = marketEfficiency;
 	market->marketEfficiency = marketEfficiency;
 	
 	
-	market->title = market->getObjectName();
-	if(!title.empty())
-		market->title = VLC->generaltexth->translate(title);
-	
-	if (!speech.empty())
-		market->speech = VLC->generaltexth->translate(speech);
+	if(auto university = dynamic_cast<CGUniversity*>(market))
+	{
+		university->title = market->getObjectName();
+		if(!title.empty())
+			university->title = VLC->generaltexth->translate(title);
+
+		if(!speech.empty())
+			university->speech = VLC->generaltexth->translate(speech);
+	}
 }
 }
 
 
 void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const
 void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const

+ 5 - 19
lib/mapObjects/CGMarket.cpp

@@ -23,6 +23,11 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+ObjectInstanceID CGMarket::getObjInstanceID() const
+{
+	return id;
+}
+
 void CGMarket::initObj(vstd::RNG & rand)
 void CGMarket::initObj(vstd::RNG & rand)
 {
 {
 	getObjectHandler()->configureObject(this, rand);
 	getObjectHandler()->configureObject(this, rand);
@@ -38,23 +43,11 @@ int CGMarket::getMarketEfficiency() const
 	return marketEfficiency;
 	return marketEfficiency;
 }
 }
 
 
-bool CGMarket::allowsTrade(EMarketMode mode) const
-{
-	return marketModes.count(mode);
-}
-
 int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
 int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
 {
 {
 	return -1;
 	return -1;
 }
 }
 
 
-std::vector<TradeItemBuy> CGMarket::availableItemsIds(EMarketMode mode) const
-{
-	if(allowsTrade(mode))
-		return IMarket::availableItemsIds(mode);
-	return std::vector<TradeItemBuy>();
-}
-
 CGMarket::CGMarket(IGameCallback *cb):
 CGMarket::CGMarket(IGameCallback *cb):
 	CGObjectInstance(cb)
 	CGObjectInstance(cb)
 {}
 {}
@@ -63,8 +56,6 @@ std::vector<TradeItemBuy> CGBlackMarket::availableItemsIds(EMarketMode mode) con
 {
 {
 	switch(mode)
 	switch(mode)
 	{
 	{
-	case EMarketMode::ARTIFACT_RESOURCE:
-		return IMarket::availableItemsIds(mode);
 	case EMarketMode::RESOURCE_ARTIFACT:
 	case EMarketMode::RESOURCE_ARTIFACT:
 		{
 		{
 			std::vector<TradeItemBuy> ret;
 			std::vector<TradeItemBuy> ret;
@@ -113,9 +104,4 @@ void CGUniversity::onHeroVisit(const CGHeroInstance * h) const
 	cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true);
 	cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true);
 }
 }
 
 
-ArtBearer::ArtBearer CGArtifactsAltar::bearerType() const
-{
-	return ArtBearer::ALTAR;
-}
-
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 22 - 25
lib/mapObjects/CGMarket.h

@@ -18,32 +18,36 @@ VCMI_LIB_NAMESPACE_BEGIN
 class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
 class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
 {
 {
 public:
 public:
-	
-	std::set<EMarketMode> marketModes;
 	int marketEfficiency;
 	int marketEfficiency;
 	
 	
-	//window variables
-	std::string title;
-	std::string speech; //currently shown only in university
-	
 	CGMarket(IGameCallback *cb);
 	CGMarket(IGameCallback *cb);
 	///IObjectInterface
 	///IObjectInterface
 	void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
 	void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
 	void initObj(vstd::RNG & rand) override;//set skills for trade
 	void initObj(vstd::RNG & rand) override;//set skills for trade
 
 
 	///IMarket
 	///IMarket
+	ObjectInstanceID getObjInstanceID() const override;
 	int getMarketEfficiency() const override;
 	int getMarketEfficiency() const override;
-	bool allowsTrade(EMarketMode mode) const override;
 	int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
 	int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
-	std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
 
 
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
 	{
 	{
 		h & static_cast<CGObjectInstance&>(*this);
 		h & static_cast<CGObjectInstance&>(*this);
-		h & marketModes;
+		h & static_cast<IMarket&>(*this);
 		h & marketEfficiency;
 		h & marketEfficiency;
-		h & title;
-		h & speech;
+		if (h.version < Handler::Version::NEW_MARKETS)
+		{
+			std::string speech;
+			std::string title;
+			h & speech;
+			h & title;
+		}
+	}
+
+	template <typename Handler> void serializeArtifactsAltar(Handler &h)
+	{
+		serialize(h);
+		IMarket::serializeArtifactsAltar(h);
 	}
 	}
 };
 };
 
 
@@ -68,6 +72,8 @@ class DLL_LINKAGE CGUniversity : public CGMarket
 {
 {
 public:
 public:
 	using CGMarket::CGMarket;
 	using CGMarket::CGMarket;
+	std::string speech; //currently shown only in university
+	std::string title;
 
 
 	std::vector<TradeItemBuy> skills; //available skills
 	std::vector<TradeItemBuy> skills; //available skills
 
 
@@ -78,20 +84,11 @@ public:
 	{
 	{
 		h & static_cast<CGMarket&>(*this);
 		h & static_cast<CGMarket&>(*this);
 		h & skills;
 		h & skills;
-	}
-};
-
-class DLL_LINKAGE CGArtifactsAltar : public CGMarket, public CArtifactSet
-{
-public:
-	using CGMarket::CGMarket;
-
-	ArtBearer::ArtBearer bearerType() const override;
-
-	template <typename Handler> void serialize(Handler & h)
-	{
-		h & static_cast<CGMarket&>(*this);
-		h & static_cast<CArtifactSet&>(*this);
+		if (h.version >= Handler::Version::NEW_MARKETS)
+		{
+			h & speech;
+			h & title;
+		}
 	}
 	}
 };
 };
 
 

+ 54 - 39
lib/mapObjects/CGTownInstance.cpp

@@ -690,35 +690,6 @@ int CGTownInstance::getMarketEfficiency() const
 	return marketCount;
 	return marketCount;
 }
 }
 
 
-bool CGTownInstance::allowsTrade(EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
-		return hasBuilt(BuildingID::MARKETPLACE);
-
-	case EMarketMode::ARTIFACT_RESOURCE:
-	case EMarketMode::RESOURCE_ARTIFACT:
-		return hasBuilt(BuildingSubID::ARTIFACT_MERCHANT);
-
-	case EMarketMode::CREATURE_RESOURCE:
-		return hasBuilt(BuildingSubID::FREELANCERS_GUILD);
-
-	case EMarketMode::CREATURE_UNDEAD:
-		return hasBuilt(BuildingSubID::CREATURE_TRANSFORMER);
-
-	case EMarketMode::RESOURCE_SKILL:
-		return hasBuilt(BuildingSubID::MAGIC_UNIVERSITY);
-	case EMarketMode::CREATURE_EXP:
-	case EMarketMode::ARTIFACT_EXP:
-		return false;
-	default:
-		assert(0);
-		return false;
-	}
-}
-
 std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) const
 std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) const
 {
 {
 	if(mode == EMarketMode::RESOURCE_ARTIFACT)
 	if(mode == EMarketMode::RESOURCE_ARTIFACT)
@@ -739,6 +710,11 @@ std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) co
 		return IMarket::availableItemsIds(mode);
 		return IMarket::availableItemsIds(mode);
 }
 }
 
 
+ObjectInstanceID CGTownInstance::getObjInstanceID() const
+{
+	return id;
+}
+
 void CGTownInstance::updateAppearance()
 void CGTownInstance::updateAppearance()
 {
 {
 	auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
 	auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
@@ -932,12 +908,7 @@ const CArmedInstance * CGTownInstance::getUpperArmy() const
 
 
 bool CGTownInstance::hasBuiltSomeTradeBuilding() const
 bool CGTownInstance::hasBuiltSomeTradeBuilding() const
 {
 {
-	for(const auto & bid : builtBuildings)
-	{
-		if(town->buildings.at(bid)->IsTradeBuilding())
-			return true;
-	}
-	return false;
+	return availableModes().empty() ? false : true;
 }
 }
 
 
 bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
 bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
@@ -962,6 +933,50 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) c
 	return false;
 	return false;
 }
 }
 
 
+void CGTownInstance::addBuilding(const BuildingID & buildingID)
+{
+	if(buildingID == BuildingID::NONE)
+		return;
+
+	const auto townType = (*VLC->townh)[getFaction()]->town;
+	if(const auto & building = townType->buildings.find(buildingID); building != townType->buildings.end())
+	{
+		builtBuildings.insert(buildingID);
+		addMarketMode(building->second->marketModes);
+	}
+}
+
+void CGTownInstance::postDeserializeMarketFix()
+{
+	// re-add all buildings to recreate existing market modes
+	auto buildingsBak = builtBuildings;
+	for (auto building : buildingsBak)
+		addBuilding(building);
+}
+
+void CGTownInstance::removeBuilding(const BuildingID & buildingID)
+{
+	if(!vstd::contains(builtBuildings, buildingID))
+		return;
+
+	if(const auto & building = town->buildings.find(buildingID); building != town->buildings.end())
+	{
+		builtBuildings.erase(buildingID);
+		removeMarketMode(building->second->marketModes);
+	}
+}
+
+void CGTownInstance::removeAllBuildings()
+{
+	builtBuildings.clear();
+	removeAllMarketModes();
+}
+
+std::set<BuildingID> CGTownInstance::getBuildings() const
+{
+	return builtBuildings;
+}
+
 TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
 TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
 {
 {
 	if (vstd::contains(town->buildings, buildingID))
 	if (vstd::contains(town->buildings, buildingID))
@@ -1132,23 +1147,23 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 		{
 		{
 			handler.serializeLIC("buildings", buildingsLIC);
 			handler.serializeLIC("buildings", buildingsLIC);
 
 
-			builtBuildings.insert(BuildingID::VILLAGE_HALL);
+			addBuilding(BuildingID::VILLAGE_HALL);
 
 
 			if(buildingsLIC.none.empty() && buildingsLIC.all.empty())
 			if(buildingsLIC.none.empty() && buildingsLIC.all.empty())
 			{
 			{
-				builtBuildings.insert(BuildingID::DEFAULT);
+				addBuilding(BuildingID::DEFAULT);
 
 
 				bool hasFort = false;
 				bool hasFort = false;
 				handler.serializeBool("hasFort",hasFort);
 				handler.serializeBool("hasFort",hasFort);
 				if(hasFort)
 				if(hasFort)
-					builtBuildings.insert(BuildingID::FORT);
+					addBuilding(BuildingID::FORT);
 			}
 			}
 			else
 			else
 			{
 			{
 				for(const si32 item : buildingsLIC.none)
 				for(const si32 item : buildingsLIC.none)
 					forbiddenBuildings.insert(BuildingID(item));
 					forbiddenBuildings.insert(BuildingID(item));
 				for(const si32 item : buildingsLIC.all)
 				for(const si32 item : buildingsLIC.all)
-					builtBuildings.insert(BuildingID(item));
+					addBuilding(BuildingID(item));
 			}
 			}
 		}
 		}
 	}
 	}

+ 14 - 3
lib/mapObjects/CGTownInstance.h

@@ -52,6 +52,8 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I
 	std::string nameTextId; // name of town
 	std::string nameTextId; // name of town
 
 
 	std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
 	std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
+	std::set<BuildingID> builtBuildings;
+
 public:
 public:
 	using CGDwelling::getPosition;
 	using CGDwelling::getPosition;
 
 
@@ -65,7 +67,6 @@ public:
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
 	PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player
 	PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player
 	std::set<BuildingID> forbiddenBuildings;
 	std::set<BuildingID> forbiddenBuildings;
-	std::set<BuildingID> builtBuildings;
 	std::map<BuildingID, TownRewardableBuildingInstance*> rewardableBuildings;
 	std::map<BuildingID, TownRewardableBuildingInstance*> rewardableBuildings;
 	std::vector<SpellID> possibleSpells, obligatorySpells;
 	std::vector<SpellID> possibleSpells, obligatorySpells;
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
@@ -76,6 +77,9 @@ public:
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
 	{
 	{
 		h & static_cast<CGDwelling&>(*this);
 		h & static_cast<CGDwelling&>(*this);
+		if (h.version >= Handler::Version::NEW_MARKETS)
+			h & static_cast<IMarket&>(*this);
+
 		h & nameTextId;
 		h & nameTextId;
 		h & built;
 		h & built;
 		h & destroyed;
 		h & destroyed;
@@ -114,6 +118,9 @@ public:
 			town = faction ? faction->town : nullptr;
 			town = faction ? faction->town : nullptr;
 		}
 		}
 
 
+		if (!h.saving && h.version < Handler::Version::NEW_MARKETS)
+			postDeserializeMarketFix();
+
 		h & townAndVis;
 		h & townAndVis;
 		BONUS_TREE_DESERIALIZATION_FIX
 		BONUS_TREE_DESERIALIZATION_FIX
 
 
@@ -133,6 +140,7 @@ public:
 	void updateMoraleBonusFromArmy() override;
 	void updateMoraleBonusFromArmy() override;
 	void deserializationFix();
 	void deserializationFix();
 	void postDeserialize();
 	void postDeserialize();
+	void postDeserializeMarketFix();
 	void recreateBuildingsBonuses();
 	void recreateBuildingsBonuses();
 	void setVisitingHero(CGHeroInstance *h);
 	void setVisitingHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
@@ -152,9 +160,8 @@ public:
 	EGeneratorState shipyardStatus() const override;
 	EGeneratorState shipyardStatus() const override;
 	const IObjectInterface * getObject() const override;
 	const IObjectInterface * getObject() const override;
 	int getMarketEfficiency() const override; //=market count
 	int getMarketEfficiency() const override; //=market count
-	bool allowsTrade(EMarketMode mode) const override;
 	std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
 	std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
-
+	ObjectInstanceID getObjInstanceID() const override;
 	void updateAppearance();
 	void updateAppearance();
 
 
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
@@ -174,6 +181,10 @@ public:
 	//checks if building is constructed and town has same subID
 	//checks if building is constructed and town has same subID
 	bool hasBuilt(const BuildingID & buildingID) const;
 	bool hasBuilt(const BuildingID & buildingID) const;
 	bool hasBuilt(const BuildingID & buildingID, FactionID townID) const;
 	bool hasBuilt(const BuildingID & buildingID, FactionID townID) const;
+	void addBuilding(const BuildingID & buildingID);
+	void removeBuilding(const BuildingID & buildingID);
+	void removeAllBuildings();
+	std::set<BuildingID> getBuildings() const;
 
 
 	TResources getBuildingCost(const BuildingID & buildingID) const;
 	TResources getBuildingCost(const BuildingID & buildingID) const;
 	TResources dailyIncome() const; //calculates daily income of this town
 	TResources dailyIncome() const; //calculates daily income of this town

+ 48 - 19
lib/mapObjects/IMarket.cpp

@@ -20,6 +20,11 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+bool IMarket::allowsTrade(const EMarketMode mode) const
+{
+	return vstd::contains(marketModes, mode);
+}
+
 bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const
 bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const
 {
 {
 	switch(mode)
 	switch(mode)
@@ -122,12 +127,7 @@ bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode)
 	return true;
 	return true;
 }
 }
 
 
-bool IMarket::allowsTrade(EMarketMode mode) const
-{
-	return false;
-}
-
-int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
+int IMarket::availableUnits(const EMarketMode mode, const int marketItemSerial) const
 {
 {
 	switch(mode)
 	switch(mode)
 	{
 	{
@@ -140,7 +140,45 @@ int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
 	}
 	}
 }
 }
 
 
-std::vector<TradeItemBuy> IMarket::availableItemsIds(EMarketMode mode) const
+void IMarket::addMarketMode(const EMarketMode mode)
+{
+	marketModes.insert(mode);
+
+	if(mode == EMarketMode::ARTIFACT_EXP)
+		altarArtifactsStorage = std::make_shared<CArtifactSetAltar>();
+}
+
+void IMarket::addMarketMode(const std::set<EMarketMode> & modes)
+{
+	for(const auto & mode : modes)
+		addMarketMode(mode);
+}
+
+void IMarket::removeMarketMode(const EMarketMode mode)
+{
+	marketModes.erase(mode);
+
+	if(mode == EMarketMode::ARTIFACT_EXP)
+		altarArtifactsStorage.reset();
+}
+
+void IMarket::removeMarketMode(const std::set<EMarketMode> & modes)
+{
+	for(const auto & mode : modes)
+		removeMarketMode(mode);
+}
+
+void IMarket::removeAllMarketModes()
+{
+	marketModes.clear();
+}
+
+std::shared_ptr<CArtifactSet> IMarket::getArtifactsStorage() const
+{
+	return altarArtifactsStorage;
+}
+
+std::vector<TradeItemBuy> IMarket::availableItemsIds(const EMarketMode mode) const
 {
 {
 	std::vector<TradeItemBuy> ret;
 	std::vector<TradeItemBuy> ret;
 	switch(mode)
 	switch(mode)
@@ -148,24 +186,15 @@ std::vector<TradeItemBuy> IMarket::availableItemsIds(EMarketMode mode) const
 	case EMarketMode::RESOURCE_RESOURCE:
 	case EMarketMode::RESOURCE_RESOURCE:
 	case EMarketMode::ARTIFACT_RESOURCE:
 	case EMarketMode::ARTIFACT_RESOURCE:
 	case EMarketMode::CREATURE_RESOURCE:
 	case EMarketMode::CREATURE_RESOURCE:
-		for (auto res : GameResID::ALL_RESOURCES())
+		for(const auto & res : GameResID::ALL_RESOURCES())
 			ret.push_back(res);
 			ret.push_back(res);
 	}
 	}
 	return ret;
 	return ret;
 }
 }
 
 
-IMarket::IMarket()
+std::set<EMarketMode> IMarket::availableModes() const
 {
 {
-}
-
-std::vector<EMarketMode> IMarket::availableModes() const
-{
-	std::vector<EMarketMode> ret;
-	for (EMarketMode i = static_cast<EMarketMode>(0); i < EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; i = vstd::next(i, 1))
-	if(allowsTrade(i))
-		ret.push_back(i);
-
-	return ret;
+	return marketModes;
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 40 - 10
lib/mapObjects/IMarket.h

@@ -11,24 +11,54 @@
 
 
 #include "../networkPacks/TradeItem.h"
 #include "../networkPacks/TradeItem.h"
 #include "../constants/Enumerations.h"
 #include "../constants/Enumerations.h"
+#include "../CArtHandler.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-class CGObjectInstance;
-
-class DLL_LINKAGE IMarket
+class DLL_LINKAGE IMarket : public virtual Serializeable
 {
 {
 public:
 public:
-	IMarket();
-	virtual ~IMarket() {}
+	class CArtifactSetAltar : public CArtifactSet
+	{
+	public:
+		ArtBearer::ArtBearer bearerType() const override {return ArtBearer::ALTAR;};
+	};
 
 
+	virtual ObjectInstanceID getObjInstanceID() const = 0;	// The market is always an object on the map
 	virtual int getMarketEfficiency() const = 0;
 	virtual int getMarketEfficiency() const = 0;
-	virtual bool allowsTrade(EMarketMode mode) const;
-	virtual int availableUnits(EMarketMode mode, int marketItemSerial) const; //-1 if unlimited
-	virtual std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const;
-
+	virtual bool allowsTrade(const EMarketMode mode) const;
+	virtual int availableUnits(const EMarketMode mode, const int marketItemSerial) const; //-1 if unlimited
+	virtual std::vector<TradeItemBuy> availableItemsIds(const EMarketMode mode) const;
+	void addMarketMode(const EMarketMode mode);
+	void addMarketMode(const std::set<EMarketMode> & modes);
+	void removeMarketMode(const EMarketMode mode);
+	void removeMarketMode(const std::set<EMarketMode> & modes);
+	void removeAllMarketModes();
+	std::set<EMarketMode> availableModes() const;
+	std::shared_ptr<CArtifactSet> getArtifactsStorage() const;
 	bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
 	bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
-	std::vector<EMarketMode> availableModes() const;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & marketModes;
+
+		if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP))
+		{
+			if (!h.saving)
+				altarArtifactsStorage = std::make_shared<CArtifactSetAltar>();
+
+			h & *altarArtifactsStorage;
+		}
+	}
+
+	template <typename Handler> void serializeArtifactsAltar(Handler & h)
+	{
+		h & *altarArtifactsStorage;
+	}
+
+private:
+	std::shared_ptr<CArtifactSetAltar> altarArtifactsStorage;
+	std::set<EMarketMode> marketModes;
 };
 };
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 0 - 14
lib/mapObjects/TownBuildingInstance.h

@@ -92,18 +92,4 @@ public:
 	}
 	}
 };
 };
 
 
-/// Compatibility for old code
-class DLL_LINKAGE CTownCompatBuilding1 : public TownRewardableBuildingInstance
-{
-public:
-	using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
-};
-
-/// Compatibility for old code
-class DLL_LINKAGE CTownCompatBuilding2 : public TownRewardableBuildingInstance
-{
-public:
-	using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
-};
-
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

Some files were not shown because too many files changed in this diff