Browse Source

Merge remote-tracking branch 'upstream/develop' into vcmi-campaign

nordsoft 2 năm trước cách đây
mục cha
commit
30281d5991
100 tập tin đã thay đổi với 789 bổ sung451 xóa
  1. 3 1
      AI/BattleAI/AttackPossibility.cpp
  2. 37 12
      AI/BattleAI/BattleAI.cpp
  3. 2 1
      AI/BattleAI/BattleAI.h
  4. 8 1
      AI/BattleAI/BattleExchangeVariant.cpp
  5. 1 1
      AI/BattleAI/BattleExchangeVariant.h
  6. 1 1
      AI/BattleAI/PotentialTargets.cpp
  7. 1 1
      AI/BattleAI/StackWithBonuses.cpp
  8. 14 8
      AI/Nullkiller/AIGateway.cpp
  9. 1 1
      AI/Nullkiller/AIGateway.h
  10. 7 5
      AI/Nullkiller/AIUtility.cpp
  11. 1 1
      AI/Nullkiller/AIUtility.h
  12. 23 22
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  13. 11 11
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  14. 1 1
      AI/Nullkiller/Behaviors/BuildingBehavior.cpp
  15. 1 1
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  16. 1 1
      AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp
  17. 1 1
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  18. 4 4
      AI/Nullkiller/Engine/FuzzyEngines.cpp
  19. 1 1
      AI/Nullkiller/Engine/Nullkiller.h
  20. 21 20
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  21. 1 1
      AI/Nullkiller/Goals/AbstractGoal.cpp
  22. 2 2
      AI/Nullkiller/Goals/BuyArmy.cpp
  23. 1 1
      AI/Nullkiller/Goals/CompleteQuest.cpp
  24. 1 1
      AI/Nullkiller/Goals/GatherArmy.cpp
  25. 2 2
      AI/Nullkiller/Goals/Trade.h
  26. 1 1
      AI/Nullkiller/Markers/ArmyUpgrade.cpp
  27. 6 6
      AI/Nullkiller/Pathfinding/Actors.cpp
  28. 5 5
      AI/StupidAI/StupidAI.cpp
  29. 1 1
      AI/StupidAI/StupidAI.h
  30. 2 2
      AI/VCAI/AIUtility.cpp
  31. 1 1
      AI/VCAI/AIUtility.h
  32. 8 7
      AI/VCAI/ArmyManager.cpp
  33. 4 4
      AI/VCAI/FuzzyEngines.cpp
  34. 1 1
      AI/VCAI/Goals/BuyArmy.cpp
  35. 11 11
      AI/VCAI/Goals/CollectRes.cpp
  36. 2 2
      AI/VCAI/Goals/CollectRes.h
  37. 2 2
      AI/VCAI/Goals/CompleteQuest.cpp
  38. 1 1
      AI/VCAI/Goals/GatherArmy.cpp
  39. 8 8
      AI/VCAI/Goals/GatherTroops.cpp
  40. 1 1
      AI/VCAI/Goals/RecruitHero.cpp
  41. 2 2
      AI/VCAI/Goals/Trade.h
  42. 1 1
      AI/VCAI/Goals/Win.cpp
  43. 2 2
      AI/VCAI/MapObjectsEvaluator.cpp
  44. 16 16
      AI/VCAI/ResourceManager.cpp
  45. 19 13
      AI/VCAI/VCAI.cpp
  46. 1 1
      AI/VCAI/VCAI.h
  47. 6 0
      CCallback.cpp
  48. 2 0
      CCallback.h
  49. 2 0
      CMakePresets.json
  50. 7 1
      Global.h
  51. BIN
      Mods/vcmi/Data/UnitMaxMovementHighlight.png
  52. BIN
      Mods/vcmi/Data/UnitMovementHighlight.png
  53. 6 0
      Mods/vcmi/config/vcmi/english.json
  54. 1 0
      Mods/vcmi/mod.json
  55. 5 5
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java
  56. 1 1
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java
  57. 8 8
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java
  58. 0 40
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingDialog.java
  59. 8 8
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingController.java
  60. 47 0
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingDialog.java
  61. 3 3
      android/vcmi-app/src/main/res/values-de/strings.xml
  62. 3 3
      android/vcmi-app/src/main/res/values-pl/strings.xml
  63. 4 4
      android/vcmi-app/src/main/res/values-ru/strings.xml
  64. 4 4
      android/vcmi-app/src/main/res/values-uk/strings.xml
  65. 3 3
      android/vcmi-app/src/main/res/values/strings.xml
  66. 12 12
      client/CMusicHandler.cpp
  67. 27 6
      client/CPlayerInterface.cpp
  68. 4 1
      client/CPlayerInterface.h
  69. 46 18
      client/Client.cpp
  70. 7 1
      client/Client.h
  71. 3 1
      client/LobbyClientNetPackVisitors.h
  72. 3 1
      client/NetPacksClient.cpp
  73. 11 1
      client/adventureMap/CInGameConsole.cpp
  74. 2 4
      client/adventureMap/CInfoBar.cpp
  75. 2 2
      client/adventureMap/CInfoBar.h
  76. 1 1
      client/adventureMap/CResDataBar.cpp
  77. 33 6
      client/battle/BattleActionsController.cpp
  78. 5 1
      client/battle/BattleActionsController.h
  79. 3 3
      client/battle/BattleAnimationClasses.cpp
  80. 90 71
      client/battle/BattleFieldController.cpp
  81. 7 4
      client/battle/BattleFieldController.h
  82. 9 2
      client/battle/BattleInterface.cpp
  83. 1 1
      client/battle/BattleInterface.h
  84. 22 4
      client/battle/BattleInterfaceClasses.cpp
  85. 6 1
      client/battle/BattleInterfaceClasses.h
  86. 1 1
      client/battle/BattleProjectileController.cpp
  87. 1 1
      client/battle/BattleStacksController.cpp
  88. 30 3
      client/battle/BattleWindow.cpp
  89. 4 0
      client/battle/BattleWindow.h
  90. 5 1
      client/gui/CGuiHandler.cpp
  91. 11 3
      client/gui/InterfaceObjectConfigurable.cpp
  92. 22 22
      client/lobby/OptionsTab.cpp
  93. 12 2
      client/lobby/RandomMapTab.cpp
  94. 0 1
      client/render/IFont.cpp
  95. 19 1
      client/renderSDL/CBitmapFont.cpp
  96. 5 0
      client/renderSDL/CBitmapFont.h
  97. 26 3
      client/renderSDL/CTrueTypeFont.cpp
  98. 5 0
      client/renderSDL/CTrueTypeFont.h
  99. 1 1
      client/widgets/CArtifactHolder.cpp
  100. 1 1
      client/widgets/CreatureCostBox.h

+ 3 - 1
AI/BattleAI/AttackPossibility.cpp

@@ -212,11 +212,13 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
 	// check how much damage we gain from blocking enemy shooters on this hex
 	// check how much damage we gain from blocking enemy shooters on this hex
 	bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
 	bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
 
 
-	logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
+#if BATTLE_TRACE_LEVEL>=1
+	logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
 		attackInfo.attacker->unitType()->getJsonKey(),
 		attackInfo.attacker->unitType()->getJsonKey(),
 		attackInfo.defender->unitType()->getJsonKey(),
 		attackInfo.defender->unitType()->getJsonKey(),
 		(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 		(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 		bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
 		bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
+#endif
 
 
 	//TODO other damage related to attack (eg. fire shield and other abilities)
 	//TODO other damage related to attack (eg. fire shield and other abilities)
 	return bestAp;
 	return bestAp;

+ 37 - 12
AI/BattleAI/BattleAI.cpp

@@ -90,6 +90,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	CB->waitTillRealize = true;
 	CB->waitTillRealize = true;
 	CB->unlockGsWhenWaiting = false;
 	CB->unlockGsWhenWaiting = false;
+	movesSkippedByDefense = 0;
 }
 }
 
 
 BattleAction CBattleAI::activeStack( const CStack * stack )
 BattleAction CBattleAI::activeStack( const CStack * stack )
@@ -101,7 +102,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 
 
 	try
 	try
 	{
 	{
-		if(stack->type->idNumber == CreatureID::CATAPULT)
+		if(stack->type->getId() == CreatureID::CATAPULT)
 			return useCatapult(stack);
 			return useCatapult(stack);
 		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
 		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
 		{
 		{
@@ -182,6 +183,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
 			if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
 			{
 			{
 				// return because spellcast value is damage dealt and score is dps reduce
 				// return because spellcast value is damage dealt and score is dps reduce
+				movesSkippedByDefense = 0;
 				return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
 				return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
 			}
 			}
 
 
@@ -197,14 +199,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 				}
 				}
 				else if(bestAttack.attack.shooting)
 				else if(bestAttack.attack.shooting)
 				{
 				{
-
 					result = BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
 					result = BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
 					action = "shot";
 					action = "shot";
+					movesSkippedByDefense = 0;
 				}
 				}
 				else
 				else
 				{
 				{
 					result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
 					result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
 					action = "melee";
 					action = "melee";
+					movesSkippedByDefense = 0;
 				}
 				}
 
 
 				logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
 				logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
@@ -218,6 +221,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		}
 		}
 		else if(bestSpellcast.is_initialized())
 		else if(bestSpellcast.is_initialized())
 		{
 		{
+			movesSkippedByDefense = 0;
 			return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
 			return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
 		}
 		}
 
 
@@ -236,12 +240,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			}
 			}
 		}
 		}
 
 
-		if(score > EvaluationResult::INEFFECTIVE_SCORE)
-		{
-			return result;
-		}
-
-		if(!stack->hasBonusOfType(Bonus::FLYING)
+		if(score <= EvaluationResult::INEFFECTIVE_SCORE
+			&& !stack->hasBonusOfType(Bonus::FLYING)
 			&& stack->unitSide() == BattleSide::ATTACKER
 			&& stack->unitSide() == BattleSide::ATTACKER
 			&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
 			&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
 		{
 		{
@@ -249,10 +249,12 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 
 
 			if(brokenWallMoat.size())
 			if(brokenWallMoat.size())
 			{
 			{
+				movesSkippedByDefense = 0;
+
 				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));
+					result = BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
 				else
 				else
-					return goTowardsNearest(stack, brokenWallMoat);
+					result = goTowardsNearest(stack, brokenWallMoat);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -265,13 +267,22 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
 		logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
 	}
 	}
 
 
+	if(result.actionType == EActionType::DEFEND)
+	{
+		movesSkippedByDefense++;
+	}
+	else if(result.actionType != EActionType::WAIT)
+	{
+		movesSkippedByDefense = 0;
+	}
+
 	return result;
 	return result;
 }
 }
 
 
 BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
 BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
 {
 {
 	auto reachability = cb->getReachability(stack);
 	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, true);
 
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{
 	{
@@ -286,7 +297,9 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
 	for(auto hex : hexes)
 	for(auto hex : hexes)
 	{
 	{
 		if(vstd::contains(avHexes, hex))
 		if(vstd::contains(avHexes, hex))
+		{
 			return BattleAction::makeMove(stack, hex);
 			return BattleAction::makeMove(stack, hex);
+		}
 
 
 		if(stack->coversPos(hex))
 		if(stack->coversPos(hex))
 		{
 		{
@@ -409,6 +422,8 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 	attack.side = side;
 	attack.side = side;
 	attack.stackNumber = stack->ID;
 	attack.stackNumber = stack->ID;
 
 
+	movesSkippedByDefense = 0;
+
 	return attack;
 	return attack;
 }
 }
 
 
@@ -716,6 +731,7 @@ void CBattleAI::attemptCastingSpell()
 		spellcast.side = side;
 		spellcast.side = side;
 		spellcast.stackNumber = (!side) ? -1 : -2;
 		spellcast.stackNumber = (!side) ? -1 : -2;
 		cb->battleMakeAction(&spellcast);
 		cb->battleMakeAction(&spellcast);
+		movesSkippedByDefense = 0;
 	}
 	}
 	else
 	else
 	{
 	{
@@ -809,12 +825,21 @@ boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 		}
 		}
 	}
 	}
 
 
+	bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size();
+
 	if(!bs.canFlee || !bs.canSurrender)
 	if(!bs.canFlee || !bs.canSurrender)
 	{
 	{
 		return boost::none;
 		return boost::none;
 	}
 	}
 
 
-	return cb->makeSurrenderRetreatDecision(bs);
+	auto result = cb->makeSurrenderRetreatDecision(bs);
+
+	if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
+	{
+		return BattleAction::makeRetreat(bs.ourSide);
+	}
+
+	return result;
 }
 }
 
 
 
 

+ 2 - 1
AI/BattleAI/BattleAI.h

@@ -60,6 +60,7 @@ class CBattleAI : public CBattleGameInterface
 
 
 	//Previous setting of cb
 	//Previous setting of cb
 	bool wasWaitingForRealize, wasUnlockingGs;
 	bool wasWaitingForRealize, wasUnlockingGs;
+	int movesSkippedByDefense;
 
 
 public:
 public:
 	CBattleAI();
 	CBattleAI();
@@ -81,7 +82,7 @@ public:
 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
 	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	//void battleEnd(const BattleResult *br) override;
+	//void battleEnd(const BattleResult *br, QueryID queryID) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn

+ 8 - 1
AI/BattleAI/BattleExchangeVariant.cpp

@@ -271,7 +271,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(cons
 		auto hexes = stack->getSurroundingHexes();
 		auto hexes = stack->getSurroundingHexes();
 		for(auto hex : hexes)
 		for(auto hex : hexes)
 		{
 		{
-			auto neighbor = cb->battleGetStackByPos(hex);
+			auto neighbor = cb->battleGetUnitByPos(hex);
 
 
 			if(neighbor && neighbor->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbor))
 			if(neighbor && neighbor->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbor))
 			{
 			{
@@ -355,6 +355,13 @@ int64_t BattleExchangeEvaluator::calculateExchange(
 	logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from);
 	logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from);
 #endif
 #endif
 
 
+	if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
+		&& cb->battleGetGateState() == EGateState::BLOCKED
+		&& ap.attack.defender->coversPos(ESiegeHex::GATE_BRIDGE))
+	{
+		return EvaluationResult::INEFFECTIVE_SCORE;
+	}
+
 	std::vector<const battle::Unit *> ourStacks;
 	std::vector<const battle::Unit *> ourStacks;
 	std::vector<const battle::Unit *> enemyStacks;
 	std::vector<const battle::Unit *> enemyStacks;
 
 

+ 1 - 1
AI/BattleAI/BattleExchangeVariant.h

@@ -42,7 +42,7 @@ struct EvaluationResult
 	bool defend;
 	bool defend;
 
 
 	EvaluationResult(const AttackPossibility & ap)
 	EvaluationResult(const AttackPossibility & ap)
-		:wait(false), score(0), bestAttack(ap), defend(false)
+		:wait(false), score(INEFFECTIVE_SCORE), bestAttack(ap), defend(false)
 	{
 	{
 	}
 	}
 };
 };

+ 1 - 1
AI/BattleAI/PotentialTargets.cpp

@@ -15,7 +15,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 {
 {
 	auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
 	auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
 	auto reachability = state.getReachability(attackerInfo);
 	auto reachability = state.getReachability(attackerInfo);
-	auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo);
+	auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo, true);
 
 
 	//FIXME: this should part of battleGetAvailableHexes
 	//FIXME: this should part of battleGetAvailableHexes
 	bool forceTarget = false;
 	bool forceTarget = false;

+ 1 - 1
AI/BattleAI/StackWithBonuses.cpp

@@ -435,7 +435,7 @@ int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t at
 
 
 int64_t HypotheticBattle::getTreeVersion() const
 int64_t HypotheticBattle::getTreeVersion() const
 {
 {
-	return getBattleNode()->getTreeVersion() + bonusTreeVersion;
+	return getBonusBearer()->getTreeVersion() + bonusTreeVersion;
 }
 }
 
 
 #if SCRIPTING_ENABLED
 #if SCRIPTING_ENABLED

+ 14 - 8
AI/Nullkiller/AIGateway.cpp

@@ -1053,7 +1053,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 		int count = d->creatures[i].first;
 		int count = d->creatures[i].first;
 		CreatureID creID = d->creatures[i].second.back();
 		CreatureID creID = d->creatures[i].second.back();
 
 
-		vstd::amin(count, cb->getResourceAmount() / VLC->creh->objects[creID]->cost);
+		vstd::amin(count, cb->getResourceAmount() / creID.toCreature()->getFullRecruitCost());
 		if(count > 0)
 		if(count > 0)
 			cb->recruitCreatures(d, recruiter, creID, count, i);
 			cb->recruitCreatures(d, recruiter, creID, count, i);
 	}
 	}
@@ -1068,7 +1068,7 @@ bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const
 	if(!t || !townHasFreeTavern(t))
 	if(!t || !townHasFreeTavern(t))
 		return false;
 		return false;
 
 
-	if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
+	if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
 		return false;
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 		return false;
 		return false;
@@ -1090,7 +1090,7 @@ void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * arm
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 }
 
 
-void AIGateway::battleEnd(const BattleResult * br)
+void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
 {
 {
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
 	assert(status.getBattle() == ONGOING_BATTLE);
@@ -1098,7 +1098,13 @@ void AIGateway::battleEnd(const BattleResult * br)
 	bool won = br->winner == myCb->battleGetMySide();
 	bool won = br->winner == myCb->battleGetMySide();
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 	battlename.clear();
-	CAdventureAI::battleEnd(br);
+	status.addQuery(queryID, "Combat result dialog");
+	const int confirmAction = 0;
+	requestActionASAP([=]()
+	{
+		answerQuery(queryID, confirmAction);
+	});
+	CAdventureAI::battleEnd(br, queryID);
 }
 }
 
 
 void AIGateway::waitTillFree()
 void AIGateway::waitTillFree()
@@ -1390,7 +1396,7 @@ void AIGateway::tryRealize(Goals::DigAtTile & g)
 
 
 void AIGateway::tryRealize(Goals::Trade & g) //trade
 void AIGateway::tryRealize(Goals::Trade & g) //trade
 {
 {
-	if(cb->getResourceAmount((Res::ERes)g.resID) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
+	if(cb->getResourceAmount(GameResID(g.resID)) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
 		throw goalFulfilledException(sptr(g));
 		throw goalFulfilledException(sptr(g));
 
 
 	int accquiredResources = 0;
 	int accquiredResources = 0;
@@ -1399,10 +1405,10 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 		if(const IMarket * m = IMarket::castFrom(obj, false))
 		if(const IMarket * m = IMarket::castFrom(obj, false))
 		{
 		{
 			auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
 			auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
-			for(auto it = Res::ResourceSet::nziterator(freeRes); it.valid(); it++)
+			for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)
 			{
 			{
 				auto res = it->resType;
 				auto res = it->resType;
-				if(res == g.resID) //sell any other resource
+				if(res.getNum() == g.resID) //sell any other resource
 					continue;
 					continue;
 
 
 				int toGive, toGet;
 				int toGive, toGet;
@@ -1415,7 +1421,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
 				}
 				}
-				if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
+				if (cb->getResourceAmount(GameResID(g.resID)))
 					throw goalFulfilledException(sptr(g)); //we traded all we needed
 					throw goalFulfilledException(sptr(g)); //we traded all we needed
 			}
 			}
 
 

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -169,7 +169,7 @@ public:
 	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
-	void battleEnd(const BattleResult * br) override;
+	void battleEnd(const BattleResult * br, QueryID queryID) override;
 
 
 	void makeTurn();
 	void makeTurn();
 
 

+ 7 - 5
AI/Nullkiller/AIUtility.cpp

@@ -20,6 +20,8 @@
 
 
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
 
 
+#include <vcmi/CreatureService.h>
+
 namespace NKAI
 namespace NKAI
 {
 {
 
 
@@ -278,8 +280,8 @@ creInfo infoFromDC(const dwellingContent & dc)
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	if (ci.creID != -1)
 	if (ci.creID != -1)
 	{
 	{
-		ci.cre = VLC->creh->objects[ci.creID].get();
-		ci.level = ci.cre->level; //this is cretaure tier, while tryRealize expects dwelling level. Ignore.
+		ci.cre = VLC->creatures()->getById(ci.creID);
+		ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
 	}
 	}
 	else
 	else
 	{
 	{
@@ -398,7 +400,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 			{
 			{
 				if(level.first
 				if(level.first
 					&& h->getSlotFor(CreatureID(c)) != SlotID()
 					&& h->getSlotFor(CreatureID(c)) != SlotID()
-					&& ai->cb->getResourceAmount().canAfford(c.toCreature()->cost))
+					&& ai->cb->getResourceAmount().canAfford(c.toCreature()->getFullRecruitCost()))
 				{
 				{
 					return true;
 					return true;
 				}
 				}
@@ -411,7 +413,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 	{
 		for(auto slot : h->Slots())
 		for(auto slot : h->Slots())
 		{
 		{
-			if(slot.second->type->upgrades.size())
+			if(slot.second->type->hasUpgrades())
 				return true; //TODO: check price?
 				return true; //TODO: check price?
 		}
 		}
 		return false;
 		return false;
@@ -438,7 +440,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 			return false;
 			return false;
 
 
 		TResources myRes = ai->getFreeResources();
 		TResources myRes = ai->getFreeResources();
-		if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
+		if(myRes[EGameResID::GOLD] < 2000 || myRes[EGameResID::GEMS] < 10)
 			return false;
 			return false;
 		break;
 		break;
 	}
 	}

+ 1 - 1
AI/Nullkiller/AIUtility.h

@@ -161,7 +161,7 @@ struct creInfo
 {
 {
 	int count;
 	int count;
 	CreatureID creID;
 	CreatureID creID;
-	CCreature * cre;
+	const Creature * cre;
 	int level;
 	int level;
 };
 };
 creInfo infoFromDC(const dwellingContent & dc);
 creInfo infoFromDC(const dwellingContent & dc);

+ 23 - 22
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -28,8 +28,8 @@ public:
 	StackUpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
 	StackUpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
 		:initialCreature(initial), upgradedCreature(upgraded), count(count)
 		:initialCreature(initial), upgradedCreature(upgraded), count(count)
 	{
 	{
-		cost = (upgradedCreature.toCreature()->cost - initialCreature.toCreature()->cost) * count;
-		upgradeValue = (upgradedCreature.toCreature()->AIValue - initialCreature.toCreature()->AIValue) * count;
+		cost = (upgradedCreature.toCreature()->getFullRecruitCost() - initialCreature.toCreature()->getFullRecruitCost()) * count;
+		upgradeValue = (upgradedCreature.toCreature()->getAIValue() - initialCreature.toCreature()->getAIValue()) * count;
 	}
 	}
 };
 };
 
 
@@ -50,9 +50,10 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 	{
 	{
 		for(auto & i : armyPtr->Slots())
 		for(auto & i : armyPtr->Slots())
 		{
 		{
-			auto & slotInfp = creToPower[i.second->type];
+			auto cre = dynamic_cast<const CCreature*>(i.second->type);
+			auto & slotInfp = creToPower[cre];
 
 
-			slotInfp.creature = i.second->type;
+			slotInfp.creature = cre;
 			slotInfp.power += i.second->getPower();
 			slotInfp.power += i.second->getPower();
 			slotInfp.count += i.second->count;
 			slotInfp.count += i.second->count;
 		}
 		}
@@ -73,8 +74,8 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
 {
 {
 	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
 	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
 	{
 	{
-		if(left.creature->level != right.creature->level)
-			return left.creature->level < right.creature->level;
+		if(left.creature->getLevel() != right.creature->getLevel())
+			return left.creature->getLevel() < right.creature->getLevel();
 		
 		
 		return left.creature->Speed() > right.creature->Speed();
 		return left.creature->Speed() > right.creature->Speed();
 	});
 	});
@@ -95,14 +96,14 @@ public:
 std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
 std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
 {
 {
 	auto sortedSlots = getSortedSlots(target, source);
 	auto sortedSlots = getSortedSlots(target, source);
-	std::map<TFaction, uint64_t> alignmentMap;
+	std::map<FactionID, uint64_t> alignmentMap;
 
 
 	for(auto & slot : sortedSlots)
 	for(auto & slot : sortedSlots)
 	{
 	{
-		alignmentMap[slot.creature->faction] += slot.power;
+		alignmentMap[slot.creature->getFaction()] += slot.power;
 	}
 	}
 
 
-	std::set<TFaction> allowedFactions;
+	std::set<FactionID> allowedFactions;
 	std::vector<SlotInfo> resultingArmy;
 	std::vector<SlotInfo> resultingArmy;
 	uint64_t armyValue = 0;
 	uint64_t armyValue = 0;
 
 
@@ -120,7 +121,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 
 	while(allowedFactions.size() < alignmentMap.size())
 	while(allowedFactions.size() < alignmentMap.size())
 	{
 	{
-		auto strongestAlignment = vstd::maxElementByFun(alignmentMap, [&](std::pair<TFaction, uint64_t> pair) -> uint64_t
+		auto strongestAlignment = vstd::maxElementByFun(alignmentMap, [&](std::pair<FactionID, uint64_t> pair) -> uint64_t
 		{
 		{
 			return vstd::contains(allowedFactions, pair.first) ? 0 : pair.second;
 			return vstd::contains(allowedFactions, pair.first) ? 0 : pair.second;
 		});
 		});
@@ -133,13 +134,13 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 
 		for(auto & slot : sortedSlots)
 		for(auto & slot : sortedSlots)
 		{
 		{
-			if(vstd::contains(allowedFactions, slot.creature->faction))
+			if(vstd::contains(allowedFactions, slot.creature->getFaction()))
 			{
 			{
 				auto slotID = newArmyInstance.getSlotFor(slot.creature);
 				auto slotID = newArmyInstance.getSlotFor(slot.creature);
 
 
 				if(slotID.validSlot())
 				if(slotID.validSlot())
 				{
 				{
-					newArmyInstance.setCreature(slotID, slot.creature->idNumber, slot.count);
+					newArmyInstance.setCreature(slotID, slot.creature->getId(), slot.count);
 					newArmy.push_back(slot);
 					newArmy.push_back(slot);
 				}
 				}
 			}
 			}
@@ -217,7 +218,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
 		if(!ci.count || ci.creID == -1)
 		if(!ci.count || ci.creID == -1)
 			continue;
 			continue;
 
 
-		vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford
+		vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
 
 
 		if(!ci.count)
 		if(!ci.count)
 			continue;
 			continue;
@@ -228,7 +229,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
 			break;
 			break;
 
 
 		army->setCreature(dst, ci.creID, ci.count);
 		army->setCreature(dst, ci.creID, ci.count);
-		availableRes -= ci.cre->cost * ci.count;
+		availableRes -= ci.cre->getFullRecruitCost() * ci.count;
 	}
 	}
 
 
 	return army;
 	return army;
@@ -244,7 +245,7 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(
 
 
 	for(const creInfo & ci : army)
 	for(const creInfo & ci : army)
 	{
 	{
-		aivalue += ci.count * ci.cre->AIValue;
+		aivalue += ci.count * ci.cre->getAIValue();
 	}
 	}
 
 
 	return aivalue;
 	return aivalue;
@@ -279,14 +280,14 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 				freeHeroSlots--; //new slot will be occupied
 				freeHeroSlots--; //new slot will be occupied
 		}
 		}
 
 
-		vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford
+		vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
 
 
 		if(!ci.count)
 		if(!ci.count)
 			continue;
 			continue;
 
 
 		ci.level = i; //this is important for Dungeon Summoning Portal
 		ci.level = i; //this is important for Dungeon Summoning Portal
 		creaturesInDwellings.push_back(ci);
 		creaturesInDwellings.push_back(ci);
-		availableRes -= ci.cre->cost * ci.count;
+		availableRes -= ci.cre->getFullRecruitCost() * ci.count;
 	}
 	}
 
 
 	return creaturesInDwellings;
 	return creaturesInDwellings;
@@ -308,7 +309,7 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier,
 
 
 uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const
 uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const
 {
 {
-	return creature->AIValue * count;
+	return creature->getAIValue() * count;
 }
 }
 
 
 SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const
 SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const
@@ -378,12 +379,12 @@ std::vector<StackUpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSe
 
 
 		CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
 		CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
 		{
 		{
-			return cre.toCreature()->AIValue;
+			return cre.toCreature()->getAIValue();
 		});
 		});
 
 
 		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
 		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
 
 
-		if(initial.toCreature()->level == 1)
+		if(initial.toCreature()->getLevel() == 1)
 			upgrade.cost = TResources();
 			upgrade.cost = TResources();
 
 
 		upgrades.push_back(upgrade);
 		upgrades.push_back(upgrade);
@@ -417,7 +418,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSe
 
 
 		CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
 		CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
 		{
 		{
-			return cre.toCreature()->AIValue;
+			return cre.toCreature()->getAIValue();
 		});
 		});
 
 
 		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
 		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
@@ -488,7 +489,7 @@ ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
 			upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
 			upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
 
 
 			auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
 			auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
-				return slot.count == upgradedArmy.count && slot.creature->idNumber == upgrade.initialCreature;
+				return slot.count == upgradedArmy.count && slot.creature->getId() == upgrade.initialCreature;
 			});
 			});
 
 
 			resourcesLeft -= upgrade.cost;
 			resourcesLeft -= upgrade.cost;

+ 11 - 11
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -93,9 +93,9 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 
 
 int32_t convertToGold(const TResources & res)
 int32_t convertToGold(const TResources & res)
 {
 {
-	return res[Res::GOLD] 
-		+ 75 * (res[Res::WOOD] + res[Res::ORE]) 
-		+ 125 * (res[Res::GEMS] + res[Res::CRYSTAL] + res[Res::MERCURY] + res[Res::SULFUR]);
+	return res[EGameResID::GOLD] 
+		+ 75 * (res[EGameResID::WOOD] + res[EGameResID::ORE]) 
+		+ 125 * (res[EGameResID::GEMS] + res[EGameResID::CRYSTAL] + res[EGameResID::MERCURY] + res[EGameResID::SULFUR]);
 }
 }
 
 
 TResources BuildAnalyzer::getResourcesRequiredNow() const
 TResources BuildAnalyzer::getResourcesRequiredNow() const
@@ -164,8 +164,8 @@ void BuildAnalyzer::update()
 	}
 	}
 	else
 	else
 	{
 	{
-		goldPreasure = ai->getLockedResources()[Res::GOLD] / 10000.0f
-			+ (float)armyCost[Res::GOLD] / (1 + ai->getFreeGold() + (float)dailyIncome[Res::GOLD] * 7.0f);
+		goldPreasure = ai->getLockedResources()[EGameResID::GOLD] / 10000.0f
+			+ (float)armyCost[EGameResID::GOLD] / (1 + ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
 	}
 	}
 
 
 	logAi->trace("Gold preasure: %f", goldPreasure);
 	logAi->trace("Gold preasure: %f", goldPreasure);
@@ -280,7 +280,7 @@ void BuildAnalyzer::updateDailyIncome()
 
 
 		if(mine)
 		if(mine)
 		{
 		{
-			dailyIncome[mine->producedResource] += mine->producedQuantity;
+			dailyIncome[mine->producedResource.getNum()] += mine->producedQuantity;
 		}
 		}
 	}
 	}
 
 
@@ -355,10 +355,10 @@ BuildingInfo::BuildingInfo(
 
 
 	if(creature)
 	if(creature)
 	{
 	{
-		creatureGrows = creature->growth;
-		creatureID = creature->idNumber;
-		creatureCost = creature->cost;
-		creatureLevel = creature->level;
+		creatureGrows = creature->getGrowth();
+		creatureID = creature->getId();
+		creatureCost = creature->getFullRecruitCost();
+		creatureLevel = creature->getLevel();
 		baseCreatureID = baseCreature;
 		baseCreatureID = baseCreature;
 
 
 		if(exists)
 		if(exists)
@@ -367,7 +367,7 @@ BuildingInfo::BuildingInfo(
 		}
 		}
 		else
 		else
 		{
 		{
-			creatureGrows = creature->growth;
+			creatureGrows = creature->getGrowth();
 
 
 			if(town->hasBuilt(BuildingID::CASTLE))
 			if(town->hasBuilt(BuildingID::CASTLE))
 				creatureGrows *= 2;
 				creatureGrows *= 2;

+ 1 - 1
AI/Nullkiller/Behaviors/BuildingBehavior.cpp

@@ -58,7 +58,7 @@ Goals::TGoalVec BuildingBehavior::decompose() const
 	{
 	{
 		for(auto & buildingInfo : developmentInfo.toBuild)
 		for(auto & buildingInfo : developmentInfo.toBuild)
 		{
 		{
-			if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[Res::GOLD] > 0)
+			if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
 			{
 			{
 				if(buildingInfo.notEnoughRes)
 				if(buildingInfo.notEnoughRes)
 				{
 				{

+ 1 - 1
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -162,7 +162,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 
 		if(!town->visitingHero
 		if(!town->visitingHero
 			&& town->hasBuilt(BuildingID::TAVERN)
 			&& town->hasBuilt(BuildingID::TAVERN)
-			&& cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST)
+			&& cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
 		{
 		{
 			auto heroesInTavern = cb->getAvailableHeroes(town);
 			auto heroesInTavern = cb->getAvailableHeroes(town);
 
 

+ 1 - 1
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -69,7 +69,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
 			}
 			}
 
 
 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
-				|| (ai->nullkiller->getFreeResources()[Res::GOLD] > 10000
+				|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000
 					&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
 					&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
 			{
 			{
 				tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
 				tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));

+ 1 - 1
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -76,7 +76,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
 
 
 	for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
 	for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
 	{
 	{
-		if((obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD)
+		if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD))
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::CAMPFIRE
 			|| obj->ID == Obj::CAMPFIRE
 			|| obj->ID == Obj::WATER_WHEEL)
 			|| obj->ID == Obj::WATER_WHEEL)

+ 4 - 4
AI/Nullkiller/Engine/FuzzyEngines.cpp

@@ -65,13 +65,13 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	for(auto s : army->Slots())
 	for(auto s : army->Slots())
 	{
 	{
 		bool walker = true;
 		bool walker = true;
-		const CCreature * creature = s.second->type;
-		if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
+		auto bearer = s.second->getType()->getBonusBearer();
+		if(bearer->hasBonus(selectorSHOOTER, keySHOOTER))
 		{
 		{
 			shootersStrength += s.second->getPower();
 			shootersStrength += s.second->getPower();
 			walker = false;
 			walker = false;
 		}
 		}
-		if(creature->hasBonus(selectorFLYING, keyFLYING))
+		if(bearer->hasBonus(selectorFLYING, keyFLYING))
 		{
 		{
 			flyersStrength += s.second->getPower();
 			flyersStrength += s.second->getPower();
 			walker = false;
 			walker = false;
@@ -79,7 +79,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 		if(walker)
 		if(walker)
 			walkersStrength += s.second->getPower();
 			walkersStrength += s.second->getPower();
 
 
-		vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
+		vstd::amax(maxSpeed, bearer->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
 	}
 	}
 	armyStructure as;
 	armyStructure as;
 	as.walkers = static_cast<float>(walkersStrength / totalStrength);
 	as.walkers = static_cast<float>(walkersStrength / totalStrength);

+ 1 - 1
AI/Nullkiller/Engine/Nullkiller.h

@@ -85,7 +85,7 @@ public:
 	void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
 	void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
 	bool arePathHeroesLocked(const AIPath & path) const;
 	bool arePathHeroesLocked(const AIPath & path) const;
 	TResources getFreeResources() const;
 	TResources getFreeResources() const;
-	int32_t getFreeGold() const { return getFreeResources()[Res::GOLD]; }
+	int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; }
 	void lockResources(const TResources & res);
 	void lockResources(const TResources & res);
 	const TResources & getLockedResources() const { return lockedResources; }
 	const TResources & getLockedResources() const { return lockedResources; }
 
 

+ 21 - 20
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -148,14 +148,15 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
 	for (auto c : creatures)
 	for (auto c : creatures)
 	{
 	{
 		//Only if hero has slot for this creature in the army
 		//Only if hero has slot for this creature in the army
-		if (hero->getSlotFor(c.data.type).validSlot())
+		auto ccre = dynamic_cast<const CCreature*>(c.data.type);
+		if (hero->getSlotFor(ccre).validSlot())
 		{
 		{
-			result += (c.data.type->AIValue * c.data.count) * c.chance;
+			result += (c.data.type->getAIValue() * c.data.count) * c.chance;
 		}
 		}
 		else
 		else
 		{
 		{
 			//we will need to discard the weakest stack
 			//we will need to discard the weakest stack
-			result += (c.data.type->AIValue * c.data.count - weakestStackPower) * c.chance;
+			result += (c.data.type->getAIValue() * c.data.count - weakestStackPower) * c.chance;
 		}
 		}
 	}
 	}
 	result /= 100; //divide by total chance
 	result /= 100; //divide by total chance
@@ -173,11 +174,11 @@ uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool
 		if(creLevel.first && creLevel.second.size())
 		if(creLevel.first && creLevel.second.size())
 		{
 		{
 			auto creature = creLevel.second.back().toCreature();
 			auto creature = creLevel.second.back().toCreature();
-			auto creaturesAreFree = creature->level == 1;
-			if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
+			auto creaturesAreFree = creature->getLevel() == 1;
+			if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->getFullRecruitCost() * creLevel.first))
 				continue;
 				continue;
 
 
-			score += creature->AIValue * creLevel.first;
+			score += creature->getAIValue() * creLevel.first;
 		}
 		}
 	}
 	}
 
 
@@ -194,9 +195,9 @@ int getDwellingArmyCost(const CGObjectInstance * target)
 		if(creLevel.first && creLevel.second.size())
 		if(creLevel.first && creLevel.second.size())
 		{
 		{
 			auto creature = creLevel.second.back().toCreature();
 			auto creature = creLevel.second.back().toCreature();
-			auto creaturesAreFree = creature->level == 1;
+			auto creaturesAreFree = creature->getLevel() == 1;
 			if(!creaturesAreFree)
 			if(!creaturesAreFree)
-				cost += creature->cost[Res::GOLD] * creLevel.first;
+				cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first;
 		}
 		}
 	}
 	}
 
 
@@ -300,7 +301,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
 	case Obj::HILL_FORT:
 	case Obj::HILL_FORT:
-		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
+		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[EGameResID::GOLD];
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_WAR:
 	case Obj::SCHOOL_OF_WAR:
 		return 1000;
 		return 1000;
@@ -375,12 +376,12 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
 	case Obj::MINE:
 	case Obj::MINE:
-		return target->subID == Res::GOLD 
+		return target->subID == GameResID(EGameResID::GOLD)
 			? 0.5f 
 			? 0.5f 
 			: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
 			: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
 
 
 	case Obj::RESOURCE:
 	case Obj::RESOURCE:
-		return target->subID == Res::GOLD
+		return target->subID == GameResID(EGameResID::GOLD)
 			? 0
 			? 0
 			: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
 			: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
 
 
@@ -391,7 +392,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 		for (TResources::nziterator it (resourceReward); it.valid(); it++)
 		for (TResources::nziterator it (resourceReward); it.valid(); it++)
 		{
 		{
 			//Evaluate resources used for construction. Gold is evaluated separately.
 			//Evaluate resources used for construction. Gold is evaluated separately.
-			if (it->resType != Res::GOLD)
+			if (it->resType != EGameResID::GOLD)
 			{
 			{
 				sum += 0.1f * getResourceRequirementStrength(it->resType);
 				sum += 0.1f * getResourceRequirementStrength(it->resType);
 			}
 			}
@@ -502,7 +503,7 @@ int32_t getArmyCost(const CArmedInstance * army)
 
 
 	for(auto stack : army->Slots())
 	for(auto stack : army->Slots())
 	{
 	{
-		value += stack.second->getCreatureID().toCreature()->cost[Res::GOLD] * stack.second->count;
+		value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count;
 	}
 	}
 
 
 	return value;
 	return value;
@@ -517,7 +518,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	const int dailyIncomeMultiplier = 5;
 	const int dailyIncomeMultiplier = 5;
 	const float enemyArmyEliminationGoldRewardRatio = 0.2f;
 	const float enemyArmyEliminationGoldRewardRatio = 0.2f;
 	const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
 	const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
-	auto isGold = target->subID == Res::GOLD; // TODO: other resorces could be sold but need to evaluate market power
+	auto isGold = target->subID == GameResID(EGameResID::GOLD); // TODO: other resorces could be sold but need to evaluate market power
 
 
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
@@ -540,7 +541,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	case Obj::WAGON:
 	case Obj::WAGON:
 		return 100;
 		return 100;
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
-		return getCreatureBankResources(target, hero)[Res::GOLD];
+		return getCreatureBankResources(target, hero)[EGameResID::GOLD];
 	case Obj::CRYPT:
 	case Obj::CRYPT:
 	case Obj::DERELICT_SHIP:
 	case Obj::DERELICT_SHIP:
 		return 3000;
 		return 3000;
@@ -627,7 +628,7 @@ private:
 				continue;
 				continue;
 
 
 			auto creature = creatureInfo.second.back().toCreature();
 			auto creature = creatureInfo.second.back().toCreature();
-			result += creature->AIValue * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
+			result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
 		}
 		}
 
 
 		return result;
 		return result;
@@ -644,7 +645,7 @@ public:
 		auto & treat = defendTown.getTreat();
 		auto & treat = defendTown.getTreat();
 
 
 		auto armyIncome = townArmyIncome(town);
 		auto armyIncome = townArmyIncome(town);
-		auto dailyIncome = town->dailyIncome()[Res::GOLD];
+		auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
 
 
 		auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
 		auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
 
 
@@ -804,10 +805,10 @@ public:
 		Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
 		Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
 		auto & bi = buildThis.buildingInfo;
 		auto & bi = buildThis.buildingInfo;
 		
 		
-		evaluationContext.goldReward += 7 * bi.dailyIncome[Res::GOLD] / 2; // 7 day income but half we already have
+		evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
 		evaluationContext.heroRole = HeroRole::MAIN;
 		evaluationContext.heroRole = HeroRole::MAIN;
 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
-		evaluationContext.goldCost += bi.buildCostWithPrerequisits[Res::GOLD];
+		evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
 
 
 		if(bi.creatureID != CreatureID::NONE)
 		if(bi.creatureID != CreatureID::NONE)
 		{
 		{
@@ -921,7 +922,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
 		strategicalValueVariable->setValue(evaluationContext.strategicalValue);
 		strategicalValueVariable->setValue(evaluationContext.strategicalValue);
 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
-		goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[Res::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
+		goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
 		turnVariable->setValue(evaluationContext.turn);
 		turnVariable->setValue(evaluationContext.turn);
 		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
 		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
 
 

+ 1 - 1
AI/Nullkiller/Goals/AbstractGoal.cpp

@@ -60,7 +60,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize
 		desc = "GATHER TROOPS";
 		desc = "GATHER TROOPS";
 		break;
 		break;
 	case GET_ART_TYPE:
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + VLC->arth->objects[aid]->getNameTranslated();
+		desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getNameTranslated();
 		break;
 		break;
 	case DIG_AT_TILE:
 	case DIG_AT_TILE:
 		desc = "DIG AT TILE " + tile.toString();
 		desc = "DIG AT TILE " + tile.toString();

+ 2 - 2
AI/Nullkiller/Goals/BuyArmy.cpp

@@ -57,12 +57,12 @@ void BuyArmy::accept(AIGateway * ai)
 		if(objid != -1 && ci.creID != objid)
 		if(objid != -1 && ci.creID != objid)
 			continue;
 			continue;
 
 
-		vstd::amin(ci.count, res / ci.cre->cost);
+		vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
 
 
 		if(ci.count)
 		if(ci.count)
 		{
 		{
 			cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
 			cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
-			valueBought += ci.count * ci.cre->AIValue;
+			valueBought += ci.count * ci.cre->getAIValue();
 		}
 		}
 	}
 	}
 
 

+ 1 - 1
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -214,7 +214,7 @@ TGoalVec CompleteQuest::missionResources() const
 			for(int i = 0; i < q.quest->m7resources.size(); ++i)
 			for(int i = 0; i < q.quest->m7resources.size(); ++i)
 			{
 			{
 				if(q.quest->m7resources[i])
 				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(i, q.quest->m7resources[i])));
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
 			}
 			}
 		}
 		}
 	}
 	}

+ 1 - 1
AI/Nullkiller/Goals/GatherArmy.cpp

@@ -162,7 +162,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 							for(auto & creatureID : creLevel.second)
 							for(auto & creatureID : creLevel.second)
 							{
 							{
 								auto creature = VLC->creh->creatures[creatureID];
 								auto creature = VLC->creh->creatures[creatureID];
-								if(ai->ah->freeResources().canAfford(creature->cost))
+								if(ai->ah->freeResources().canAfford(creature->getFullRecruitCost()))
 									objs.push_back(obj); //TODO: reserve resources?
 									objs.push_back(obj); //TODO: reserve resources?
 							}
 							}
 						}
 						}

+ 2 - 2
AI/Nullkiller/Goals/Trade.h

@@ -27,10 +27,10 @@ namespace Goals
 			: CGoal(Goals::TRADE)
 			: CGoal(Goals::TRADE)
 		{
 		{
 		}
 		}
-		Trade(int rid, int val, int Objid)
+		Trade(GameResID rid, int val, int Objid)
 			: CGoal(Goals::TRADE)
 			: CGoal(Goals::TRADE)
 		{
 		{
-			resID = rid;
+			resID = rid.getNum();
 			value = val;
 			value = val;
 			objid = Objid;
 			objid = Objid;
 		}
 		}

+ 1 - 1
AI/Nullkiller/Markers/ArmyUpgrade.cpp

@@ -23,7 +23,7 @@ using namespace Goals;
 
 
 ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
 ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
 	: CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue),
 	: CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue),
-	initialValue(upgradePath.heroArmy->getArmyStrength()), goldCost(upgrade.upgradeCost[Res::GOLD])
+	initialValue(upgradePath.heroArmy->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD])
 {
 {
 	sethero(upgradePath.targetHero);
 	sethero(upgradePath.targetHero);
 }
 }

+ 6 - 6
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -350,7 +350,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 		{
 		{
 			auto targetSlot = target->getFreeSlot();
 			auto targetSlot = target->getFreeSlot();
 
 
-			target->addToSlot(targetSlot, slotInfo.creature->idNumber, TQuantity(slotInfo.count));
+			target->addToSlot(targetSlot, slotInfo.creature->getId(), TQuantity(slotInfo.count));
 		}
 		}
 
 
 		resources -= upgradeInfo.upgradeCost;
 		resources -= upgradeInfo.upgradeCost;
@@ -372,10 +372,10 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 
 
 		for(auto & creatureToBuy : buyArmy)
 		for(auto & creatureToBuy : buyArmy)
 		{
 		{
-			auto targetSlot = target->getSlotFor(creatureToBuy.cre);
+			auto targetSlot = target->getSlotFor(dynamic_cast<const CCreature*>(creatureToBuy.cre));
 
 
 			target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
 			target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
-			target->armyCost += creatureToBuy.cre->cost * creatureToBuy.count;
+			target->armyCost += creatureToBuy.cre->getFullRecruitCost() * creatureToBuy.count;
 			target->requireBuyArmy = true;
 			target->requireBuyArmy = true;
 		}
 		}
 	}
 	}
@@ -399,7 +399,7 @@ HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1
 	{
 	{
 		auto targetSlot = target->getFreeSlot();
 		auto targetSlot = target->getFreeSlot();
 
 
-		target->addToSlot(targetSlot, slotInfo.creature->idNumber, TQuantity(slotInfo.count));
+		target->addToSlot(targetSlot, slotInfo.creature->getId(), TQuantity(slotInfo.count));
 	}
 	}
 
 
 	return target;
 	return target;
@@ -420,7 +420,7 @@ DwellingActor::DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bo
 {
 {
 	for(auto & slot : creatureSet->Slots())
 	for(auto & slot : creatureSet->Slots())
 	{
 	{
-		armyCost += slot.second->getCreatureID().toCreature()->cost * slot.second->count;
+		armyCost += slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->count;
 	}
 	}
 }
 }
 
 
@@ -454,7 +454,7 @@ CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling,
 		auto creature = creatureInfo.second.back().toCreature();
 		auto creature = creatureInfo.second.back().toCreature();
 		dwellingCreatures->addToSlot(
 		dwellingCreatures->addToSlot(
 			dwellingCreatures->getSlotFor(creature),
 			dwellingCreatures->getSlotFor(creature),
-			creature->idNumber,
+			creature->getId(),
 			TQuantity(creatureInfo.first));
 			TQuantity(creatureInfo.first));
 	}
 	}
 
 

+ 5 - 5
AI/StupidAI/StupidAI.cpp

@@ -80,7 +80,7 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 	for(int i = 0; i < 2; i++)
 	for(int i = 0; i < 2; i++)
 	{
 	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const CStack * s = cbc->battleGetStackByPos(neighbour))
+			if(const auto * s = cbc->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 				if(s->isShooter())
 					shooters[i]++;
 					shooters[i]++;
 	}
 	}
@@ -95,7 +95,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 	ReachabilityInfo dists = cb->getReachability(stack);
 	ReachabilityInfo dists = cb->getReachability(stack);
 	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
 	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
 
 
-	if(stack->type->idNumber == CreatureID::CATAPULT)
+	if(stack->type->getId() == CreatureID::CATAPULT)
 	{
 	{
 		BattleAction attack;
 		BattleAction attack;
 		static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
 		static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
@@ -120,7 +120,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 		}
 		}
 		else
 		else
 		{
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
+			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, true);
 
 
 			for (BattleHex hex : avHexes)
 			for (BattleHex hex : avHexes)
 			{
 			{
@@ -184,7 +184,7 @@ void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bs
 	print("battleStacksAttacked called");
 	print("battleStacksAttacked called");
 }
 }
 
 
-void CStupidAI::battleEnd(const BattleResult *br)
+void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
 {
 {
 	print("battleEnd called");
 	print("battleEnd called");
 }
 }
@@ -238,7 +238,7 @@ void CStupidAI::print(const std::string &text) const
 BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> hexes) const
 BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> hexes) const
 {
 {
 	auto reachability = cb->getReachability(stack);
 	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
 
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{
 	{

+ 1 - 1
AI/StupidAI/StupidAI.h

@@ -32,7 +32,7 @@ public:
 
 
 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
 	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
 	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	void battleEnd(const BattleResult *br) override;
+	void battleEnd(const BattleResult *br, QueryID queryID) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn

+ 2 - 2
AI/VCAI/AIUtility.cpp

@@ -230,8 +230,8 @@ creInfo infoFromDC(const dwellingContent & dc)
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	if (ci.creID != -1)
 	if (ci.creID != -1)
 	{
 	{
-		ci.cre = VLC->creh->objects[ci.creID];
-		ci.level = ci.cre->level; //this is cretaure tier, while tryRealize expects dwelling level. Ignore.
+		ci.cre = VLC->creatures()->getById(ci.creID);
+		ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
 	}
 	}
 	else
 	else
 	{
 	{

+ 1 - 1
AI/VCAI/AIUtility.h

@@ -152,7 +152,7 @@ struct creInfo
 {
 {
 	int count;
 	int count;
 	CreatureID creID;
 	CreatureID creID;
-	CCreature * cre;
+	const Creature * cre;
 	int level;
 	int level;
 };
 };
 creInfo infoFromDC(const dwellingContent & dc);
 creInfo infoFromDC(const dwellingContent & dc);

+ 8 - 7
AI/VCAI/ArmyManager.cpp

@@ -36,9 +36,10 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 	{
 	{
 		for(auto & i : armyPtr->Slots())
 		for(auto & i : armyPtr->Slots())
 		{
 		{
-			auto & slotInfp = creToPower[i.second->type];
+			auto cre = dynamic_cast<const CCreature*>(i.second->type);
+			auto & slotInfp = creToPower[cre];
 
 
-			slotInfp.creature = i.second->type;
+			slotInfp.creature = cre;
 			slotInfp.power += i.second->getPower();
 			slotInfp.power += i.second->getPower();
 			slotInfp.count += i.second->count;
 			slotInfp.count += i.second->count;
 		}
 		}
@@ -59,8 +60,8 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
 {
 {
 	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
 	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
 	{
 	{
-		if(left.creature->level != right.creature->level)
-			return left.creature->level < right.creature->level;
+		if(left.creature->getLevel() != right.creature->getLevel())
+			return left.creature->getLevel() < right.creature->getLevel();
 		
 		
 		return left.creature->Speed() > right.creature->Speed();
 		return left.creature->Speed() > right.creature->Speed();
 	});
 	});
@@ -120,7 +121,7 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDw
 		if(!ci.count || ci.creID == -1)
 		if(!ci.count || ci.creID == -1)
 			continue;
 			continue;
 
 
-		vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford
+		vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
 
 
 		if(ci.count && ci.creID != -1) //valid creature at this level
 		if(ci.count && ci.creID != -1) //valid creature at this level
 		{
 		{
@@ -135,8 +136,8 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDw
 			}
 			}
 
 
 			//we found matching occupied or free slot
 			//we found matching occupied or free slot
-			aivalue += ci.count * ci.cre->AIValue;
-			availableRes -= ci.cre->cost * ci.count;
+			aivalue += ci.count * ci.cre->getAIValue();
+			availableRes -= ci.cre->getFullRecruitCost() * ci.count;
 		}
 		}
 	}
 	}
 
 

+ 4 - 4
AI/VCAI/FuzzyEngines.cpp

@@ -64,13 +64,13 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	for(auto s : army->Slots())
 	for(auto s : army->Slots())
 	{
 	{
 		bool walker = true;
 		bool walker = true;
-		const CCreature * creature = s.second->type;
-		if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
+		auto bearer = s.second->getType()->getBonusBearer();
+		if(bearer->hasBonus(selectorSHOOTER, keySHOOTER))
 		{
 		{
 			shootersStrength += s.second->getPower();
 			shootersStrength += s.second->getPower();
 			walker = false;
 			walker = false;
 		}
 		}
-		if(creature->hasBonus(selectorFLYING, keyFLYING))
+		if(bearer->hasBonus(selectorFLYING, keyFLYING))
 		{
 		{
 			flyersStrength += s.second->getPower();
 			flyersStrength += s.second->getPower();
 			walker = false;
 			walker = false;
@@ -78,7 +78,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 		if(walker)
 		if(walker)
 			walkersStrength += s.second->getPower();
 			walkersStrength += s.second->getPower();
 
 
-		vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
+		vstd::amax(maxSpeed, bearer->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
 	}
 	}
 	armyStructure as;
 	armyStructure as;
 	as.walkers = static_cast<float>(walkersStrength / totalStrength);
 	as.walkers = static_cast<float>(walkersStrength / totalStrength);

+ 1 - 1
AI/VCAI/Goals/BuyArmy.cpp

@@ -35,7 +35,7 @@ TSubgoal BuyArmy::whatToDoToAchieve()
 {
 {
 	//TODO: calculate the actual cost of units instead
 	//TODO: calculate the actual cost of units instead
 	TResources price;
 	TResources price;
-	price[Res::GOLD] = static_cast<int>(value * 0.4f); //some approximate value
+	price[EGameResID::GOLD] = static_cast<int>(value * 0.4f); //some approximate value
 	return ai->ah->whatToDo(price, iAmElementar()); //buy right now or gather resources
 	return ai->ah->whatToDo(price, iAmElementar()); //buy right now or gather resources
 }
 }
 
 

+ 11 - 11
AI/VCAI/Goals/CollectRes.cpp

@@ -46,7 +46,7 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 		switch (obj->ID.num)
 		switch (obj->ID.num)
 		{
 		{
 		case Obj::TREASURE_CHEST:
 		case Obj::TREASURE_CHEST:
-			return resID == Res::GOLD;
+			return resID == GameResID(EGameResID::GOLD);
 			break;
 			break;
 		case Obj::RESOURCE:
 		case Obj::RESOURCE:
 			return obj->subID == resID;
 			return obj->subID == resID;
@@ -59,24 +59,24 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 			return true; //contains all resources
 			return true; //contains all resources
 			break;
 			break;
 		case Obj::WINDMILL:
 		case Obj::WINDMILL:
-			switch (resID)
+			switch (GameResID(resID).toEnum())
 			{
 			{
-			case Res::GOLD:
-			case Res::WOOD:
+			case EGameResID::GOLD:
+			case EGameResID::WOOD:
 				return false;
 				return false;
 			}
 			}
 			break;
 			break;
 		case Obj::WATER_WHEEL:
 		case Obj::WATER_WHEEL:
-			if (resID != Res::GOLD)
+			if (resID != GameResID(EGameResID::GOLD))
 				return false;
 				return false;
 			break;
 			break;
 		case Obj::MYSTICAL_GARDEN:
 		case Obj::MYSTICAL_GARDEN:
-			if ((resID != Res::GOLD) && (resID != Res::GEMS))
+			if ((resID != GameResID(EGameResID::GOLD)) && (resID != GameResID(EGameResID::GEMS)))
 				return false;
 				return false;
 			break;
 			break;
 		case Obj::LEAN_TO:
 		case Obj::LEAN_TO:
 		case Obj::WAGON:
 		case Obj::WAGON:
-			if (resID != Res::GOLD)
+			if (resID != GameResID(EGameResID::GOLD))
 				return false;
 				return false;
 			break;
 			break;
 		default:
 		default:
@@ -170,12 +170,12 @@ TSubgoal CollectRes::whatToDoToTrade()
 		const IMarket * m = markets.back();
 		const IMarket * m = markets.back();
 		//attempt trade at back (best prices)
 		//attempt trade at back (best prices)
 		int howManyCanWeBuy = 0;
 		int howManyCanWeBuy = 0;
-		for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1))
+		for (auto i = EGameResID::WOOD; i <= EGameResID::GOLD; vstd::advance(i, 1))
 		{
 		{
-			if (i == resID)
+			if (GameResID(i) == resID)
 				continue;
 				continue;
 			int toGive = -1, toReceive = -1;
 			int toGive = -1, toReceive = -1;
-			m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
+			m->getOffer(GameResID(i), resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
 			assert(toGive > 0 && toReceive > 0);
 			assert(toGive > 0 && toReceive > 0);
 			howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
 			howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
 		}
 		}
@@ -191,7 +191,7 @@ TSubgoal CollectRes::whatToDoToTrade()
 			}
 			}
 			else //either it's our town, or we have hero there
 			else //either it's our town, or we have hero there
 			{
 			{
-				return sptr(Trade(resID, value, objid).setisElementar(true)); //we can do this immediately
+				return sptr(Trade(static_cast<EGameResID>(resID), value, objid).setisElementar(true)); //we can do this immediately
 			}
 			}
 		}
 		}
 	}
 	}

+ 2 - 2
AI/VCAI/Goals/CollectRes.h

@@ -24,10 +24,10 @@ namespace Goals
 			: CGoal(Goals::COLLECT_RES)
 			: CGoal(Goals::COLLECT_RES)
 		{
 		{
 		}
 		}
-		CollectRes(int rid, int val)
+		CollectRes(GameResID rid, int val)
 			: CGoal(Goals::COLLECT_RES)
 			: CGoal(Goals::COLLECT_RES)
 		{
 		{
-			resID = rid;
+			resID = rid.getNum();
 			value = val;
 			value = val;
 			priority = 2;
 			priority = 2;
 		}
 		}

+ 2 - 2
AI/VCAI/Goals/CompleteQuest.cpp

@@ -172,7 +172,7 @@ TGoalVec CompleteQuest::missionArmy() const
 
 
 	for(auto creature : q.quest->m6creatures)
 	for(auto creature : q.quest->m6creatures)
 	{
 	{
-		solutions.push_back(sptr(GatherTroops(creature.type->idNumber, creature.count)));
+		solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
 	}
 	}
 
 
 	return solutions;
 	return solutions;
@@ -235,7 +235,7 @@ TGoalVec CompleteQuest::missionResources() const
 			for(int i = 0; i < q.quest->m7resources.size(); ++i)
 			for(int i = 0; i < q.quest->m7resources.size(); ++i)
 			{
 			{
 				if(q.quest->m7resources[i])
 				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(i, q.quest->m7resources[i])));
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
 			}
 			}
 		}
 		}
 	}
 	}

+ 1 - 1
AI/VCAI/Goals/GatherArmy.cpp

@@ -159,7 +159,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 							for(auto & creatureID : creLevel.second)
 							for(auto & creatureID : creLevel.second)
 							{
 							{
 								auto creature = VLC->creh->objects[creatureID];
 								auto creature = VLC->creh->objects[creatureID];
-								if(ai->ah->freeResources().canAfford(creature->cost))
+								if(ai->ah->freeResources().canAfford(creature->getFullRecruitCost()))
 									objs.push_back(obj); //TODO: reserve resources?
 									objs.push_back(obj); //TODO: reserve resources?
 							}
 							}
 						}
 						}

+ 8 - 8
AI/VCAI/Goals/GatherTroops.cpp

@@ -93,21 +93,21 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 			continue;
 			continue;
 		}
 		}
 
 
-		auto creature = VLC->creh->objects[objid];
-		if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O
+		auto creature = VLC->creatures()->getByIndex(objid);
+		if(t->subID == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
 		{
 		{
-			auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1);
+			auto creatures = vstd::tryAt(t->town->creatures, creature->getLevel() - 1);
 			if(!creatures)
 			if(!creatures)
 				continue;
 				continue;
 
 
-			int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber);
+			int upgradeNumber = vstd::find_pos(*creatures, creature->getId());
 			if(upgradeNumber < 0)
 			if(upgradeNumber < 0)
 				continue;
 				continue;
 
 
-			BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN);
-			if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->cost)) //this assumes only creatures with dwellings are assigned to faction
+			BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN);
+			if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction
 			{
 			{
-				solutions.push_back(sptr(BuyArmy(t, creature->AIValue * this->value).setobjid(objid)));
+				solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));
 			}
 			}
 			/*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm
 			/*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm
 			{
 			{
@@ -129,7 +129,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 			{
 			{
 				for(auto type : creature.second)
 				for(auto type : creature.second)
 				{
 				{
-					if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->objects[type]->cost))
+					if(type == objid && ai->ah->freeResources().canAfford(VLC->creatures()->getById(type)->getFullRecruitCost()))
 						vstd::concatenate(solutions, ai->ah->howToVisitObj(obj));
 						vstd::concatenate(solutions, ai->ah->howToVisitObj(obj));
 				}
 				}
 			}
 			}

+ 1 - 1
AI/VCAI/Goals/RecruitHero.cpp

@@ -33,6 +33,6 @@ TSubgoal RecruitHero::whatToDoToAchieve()
 		return sptr(BuildThis(BuildingID::TAVERN).setpriority(2));
 		return sptr(BuildThis(BuildingID::TAVERN).setpriority(2));
 
 
 	TResources res;
 	TResources res;
-	res[Res::GOLD] = GameConstants::HERO_GOLD_COST;
+	res[EGameResID::GOLD] = GameConstants::HERO_GOLD_COST;
 	return ai->ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res
 	return ai->ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res
 }
 }

+ 2 - 2
AI/VCAI/Goals/Trade.h

@@ -24,10 +24,10 @@ namespace Goals
 			: CGoal(Goals::TRADE)
 			: CGoal(Goals::TRADE)
 		{
 		{
 		}
 		}
-		Trade(int rid, int val, int Objid)
+		Trade(GameResID rid, int val, int Objid)
 			: CGoal(Goals::TRADE)
 			: CGoal(Goals::TRADE)
 		{
 		{
-			resID = rid;
+			resID = rid.getNum();
 			value = val;
 			value = val;
 			objid = Objid;
 			objid = Objid;
 			priority = 3; //trading is instant, but picking resources is free
 			priority = 3; //trading is instant, but picking resources is free

+ 1 - 1
AI/VCAI/Goals/Win.cpp

@@ -154,7 +154,7 @@ TSubgoal Win::whatToDoToAchieve()
 		case EventCondition::HAVE_RESOURCES:
 		case EventCondition::HAVE_RESOURCES:
 			//TODO mines? piles? marketplace?
 			//TODO mines? piles? marketplace?
 			//save?
 			//save?
-			return sptr(CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value));
+			return sptr(CollectRes(static_cast<EGameResID>(goal.objectType), goal.value));
 		case EventCondition::HAVE_CREATURES:
 		case EventCondition::HAVE_CREATURES:
 			return sptr(GatherTroops(goal.objectType, goal.value));
 			return sptr(GatherTroops(goal.objectType, goal.value));
 		case EventCondition::TRANSPORT:
 		case EventCondition::TRANSPORT:

+ 2 - 2
AI/VCAI/MapObjectsEvaluator.cpp

@@ -73,8 +73,8 @@ boost::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance
 		{
 		{
 			for(auto & creatureID : creLevel.second)
 			for(auto & creatureID : creLevel.second)
 			{
 			{
-				auto creature = VLC->creh->objects[creatureID];
-				aiValue += (creature->AIValue * creature->growth);
+				auto creature = VLC->creatures()->getById(creatureID);
+				aiValue += (creature->getAIValue() * creature->getGrowth());
 			}
 			}
 		}
 		}
 		return aiValue;
 		return aiValue;

+ 16 - 16
AI/VCAI/ResourceManager.cpp

@@ -58,15 +58,15 @@ TResources ResourceManager::estimateIncome() const
 	{
 	{
 		if (obj->ID == Obj::MINE)
 		if (obj->ID == Obj::MINE)
 		{
 		{
-			switch (obj->subID)
+			auto mine = dynamic_cast<const CGMine*>(obj);
+			switch (mine->producedResource.toEnum())
 			{
 			{
-			case Res::WOOD:
-			case Res::ORE:
+			case EGameResID::WOOD:
+			case EGameResID::ORE:
 				ret[obj->subID] += WOOD_ORE_MINE_PRODUCTION;
 				ret[obj->subID] += WOOD_ORE_MINE_PRODUCTION;
 				break;
 				break;
-			case Res::GOLD:
-			case 7: //abandoned mine -> also gold
-				ret[Res::GOLD] += GOLD_MINE_PRODUCTION;
+			case EGameResID::GOLD:
+				ret[EGameResID::GOLD] += GOLD_MINE_PRODUCTION;
 				break;
 				break;
 			default:
 			default:
 				ret[obj->subID] += RESOURCE_MINE_PRODUCTION;
 				ret[obj->subID] += RESOURCE_MINE_PRODUCTION;
@@ -90,11 +90,11 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
 {
 {
 	auto allResources = cb->getResourceAmount();
 	auto allResources = cb->getResourceAmount();
 	auto income = estimateIncome();
 	auto income = estimateIncome();
-	Res::ERes resourceType = Res::INVALID;
+	GameResID resourceType = EGameResID::INVALID;
 	TResource amountToCollect = 0;
 	TResource amountToCollect = 0;
 
 
-	typedef std::pair<Res::ERes, TResource> resPair;
-	std::map<Res::ERes, TResource> missingResources;
+	using resPair = std::pair<GameResID, TResource>;
+	std::map<GameResID, TResource> missingResources;
 
 
 	//TODO: unit test for complex resource sets
 	//TODO: unit test for complex resource sets
 
 
@@ -102,10 +102,10 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
 	for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
 	for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
 	{
 	{
 		//choose specific resources we need for this goal (not 0)
 		//choose specific resources we need for this goal (not 0)
-		for (auto r = Res::ResourceSet::nziterator(o.resources); r.valid(); r++)
+		for (auto r = ResourceSet::nziterator(o.resources); r.valid(); r++)
 			missingResources[r->resType] += it->resources[r->resType]; //goal it costs r units of resType
 			missingResources[r->resType] += it->resources[r->resType]; //goal it costs r units of resType
 	}
 	}
-	for (auto it = Res::ResourceSet::nziterator(o.resources); it.valid(); it++)
+	for (auto it = ResourceSet::nziterator(o.resources); it.valid(); it++)
 	{
 	{
 		missingResources[it->resType] -= allResources[it->resType]; //missing = (what we need) - (what we have)
 		missingResources[it->resType] -= allResources[it->resType]; //missing = (what we need) - (what we have)
 		vstd::amax(missingResources[it->resType], 0); // if we have more resources than reserved, we don't need them
 		vstd::amax(missingResources[it->resType], 0); // if we have more resources than reserved, we don't need them
@@ -129,11 +129,11 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
 			break;
 			break;
 		}
 		}
 	}
 	}
-	if (resourceType == Res::INVALID) //no needed resources has 0 income,
+	if (resourceType == EGameResID::INVALID) //no needed resources has 0 income,
 	{
 	{
 		//find the one which takes longest to collect
 		//find the one which takes longest to collect
-		typedef std::pair<Res::ERes, float> timePair;
-		std::map<Res::ERes, float> daysToEarn;
+		using timePair = std::pair<GameResID, float>;
+		std::map<GameResID, float> daysToEarn;
 		for (auto it : missingResources)
 		for (auto it : missingResources)
 			daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first];
 			daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first];
 		auto incomeComparer = [](const timePair & lhs, const timePair & rhs) -> bool
 		auto incomeComparer = [](const timePair & lhs, const timePair & rhs) -> bool
@@ -345,7 +345,7 @@ TResources ResourceManager::freeResources() const
 
 
 TResource ResourceManager::freeGold() const
 TResource ResourceManager::freeGold() const
 {
 {
-	return freeResources()[Res::GOLD];
+	return freeResources()[EGameResID::GOLD];
 }
 }
 
 
 TResources ResourceManager::allResources() const
 TResources ResourceManager::allResources() const
@@ -355,5 +355,5 @@ TResources ResourceManager::allResources() const
 
 
 TResource ResourceManager::allGold() const
 TResource ResourceManager::allGold() const
 {
 {
-	return cb->getResourceAmount()[Res::GOLD];
+	return cb->getResourceAmount()[EGameResID::GOLD];
 }
 }

+ 19 - 13
AI/VCAI/VCAI.cpp

@@ -1235,7 +1235,7 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
 		int count = d->creatures[i].first;
 		int count = d->creatures[i].first;
 		CreatureID creID = d->creatures[i].second.back();
 		CreatureID creID = d->creatures[i].second.back();
 
 
-		vstd::amin(count, ah->freeResources() / VLC->creh->objects[creID]->cost);
+		vstd::amin(count, ah->freeResources() / VLC->creatures()->getById(creID)->getFullRecruitCost());
 		if(count > 0)
 		if(count > 0)
 			cb->recruitCreatures(d, recruiter, creID, count, i);
 			cb->recruitCreatures(d, recruiter, creID, count, i);
 	}
 	}
@@ -1314,7 +1314,7 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
 		t = findTownWithTavern();
 		t = findTownWithTavern();
 	if(!t)
 	if(!t)
 		return false;
 		return false;
-	if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
+	if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
 		return false;
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 		return false;
 		return false;
@@ -1434,7 +1434,7 @@ void VCAI::wander(HeroPtr h)
 				}
 				}
 				break;
 				break;
 			}
 			}
-			else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST)
+			else if(cb->getResourceAmount(EGameResID::GOLD) >= GameConstants::HERO_GOLD_COST)
 			{
 			{
 				std::vector<const CGTownInstance *> towns = cb->getTownsInfo();
 				std::vector<const CGTownInstance *> towns = cb->getTownsInfo();
 				vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 				vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
@@ -1580,7 +1580,7 @@ void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, i
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 }
 
 
-void VCAI::battleEnd(const BattleResult * br)
+void VCAI::battleEnd(const BattleResult * br, QueryID queryID)
 {
 {
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
 	assert(status.getBattle() == ONGOING_BATTLE);
@@ -1588,7 +1588,13 @@ void VCAI::battleEnd(const BattleResult * br)
 	bool won = br->winner == myCb->battleGetMySide();
 	bool won = br->winner == myCb->battleGetMySide();
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 	battlename.clear();
-	CAdventureAI::battleEnd(br);
+	status.addQuery(queryID, "Combat result dialog");
+	const int confirmAction = 0;
+	requestActionASAP([=]()
+	{
+		answerQuery(queryID, confirmAction);
+	});
+	CAdventureAI::battleEnd(br, queryID);
 }
 }
 
 
 void VCAI::waitTillFree()
 void VCAI::waitTillFree()
@@ -2117,10 +2123,10 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 		if(const IMarket * m = IMarket::castFrom(obj, false))
 		if(const IMarket * m = IMarket::castFrom(obj, false))
 		{
 		{
 			auto freeRes = ah->freeResources(); //trade only resources which are not reserved
 			auto freeRes = ah->freeResources(); //trade only resources which are not reserved
-			for(auto it = Res::ResourceSet::nziterator(freeRes); it.valid(); it++)
+			for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)
 			{
 			{
 				auto res = it->resType;
 				auto res = it->resType;
-				if(res == g.resID) //sell any other resource
+				if(res.getNum() == g.resID) //sell any other resource
 					continue;
 					continue;
 
 
 				int toGive, toGet;
 				int toGive, toGet;
@@ -2174,7 +2180,7 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
 				|| t->getUpperArmy()->getSlotFor(ci.creID) == SlotID())
 				|| t->getUpperArmy()->getSlotFor(ci.creID) == SlotID())
 				continue;
 				continue;
 
 
-			vstd::amin(ci.count, res / ci.cre->cost); //max count we can afford
+			vstd::amin(ci.count, res / ci.cre->getFullRecruitCost()); //max count we can afford
 
 
 			if(!ci.count)
 			if(!ci.count)
 				continue;
 				continue;
@@ -2190,15 +2196,15 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
 			*boost::max_element(creaturesInDwellings, [](const creInfo & lhs, const creInfo & rhs)
 			*boost::max_element(creaturesInDwellings, [](const creInfo & lhs, const creInfo & rhs)
 		{
 		{
 			//max value of creatures we can buy with our res
 			//max value of creatures we can buy with our res
-			int value1 = lhs.cre->AIValue * lhs.count,
-				value2 = rhs.cre->AIValue * rhs.count;
+			int value1 = lhs.cre->getAIValue() * lhs.count,
+				value2 = rhs.cre->getAIValue() * rhs.count;
 
 
 			return value1 < value2;
 			return value1 < value2;
 		});
 		});
 
 
 
 
 		cb->recruitCreatures(t, t->getUpperArmy(), ci.creID, ci.count, ci.level);
 		cb->recruitCreatures(t, t->getUpperArmy(), ci.creID, ci.count, ci.level);
-		valueBought += ci.count * ci.cre->AIValue;
+		valueBought += ci.count * ci.cre->getAIValue();
 	}
 	}
 
 
 	throw goalFulfilledException(sptr(g)); //we bought as many creatures as we wanted
 	throw goalFulfilledException(sptr(g)); //we bought as many creatures as we wanted
@@ -2820,7 +2826,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	{
 	{
 		for(auto slot : h->Slots())
 		for(auto slot : h->Slots())
 		{
 		{
-			if(slot.second->type->upgrades.size())
+			if(slot.second->type->hasUpgrades())
 				return true; //TODO: check price?
 				return true; //TODO: check price?
 		}
 		}
 		return false;
 		return false;
@@ -2844,7 +2850,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	case Obj::TREE_OF_KNOWLEDGE:
 	case Obj::TREE_OF_KNOWLEDGE:
 	{
 	{
 		TResources myRes = ai->ah->freeResources();
 		TResources myRes = ai->ah->freeResources();
-		if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
+		if(myRes[EGameResID::GOLD] < 2000 || myRes[EGameResID::GEMS] < 10)
 			return false;
 			return false;
 		break;
 		break;
 	}
 	}

+ 1 - 1
AI/VCAI/VCAI.h

@@ -201,7 +201,7 @@ public:
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
-	void battleEnd(const BattleResult * br) override;
+	void battleEnd(const BattleResult * br, QueryID queryID) override;
 
 
 	void makeTurn();
 	void makeTurn();
 	void mainLoop();
 	void mainLoop();

+ 6 - 0
CCallback.cpp

@@ -185,6 +185,12 @@ void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dst
 	sendRequest(&bma);
 	sendRequest(&bma);
 }
 }
 
 
+void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
+{
+	EraseArtifactByClient ea(al);
+	sendRequest(&ea);
+}
+
 bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 {
 {
 	if(town->tempOwner!=player)
 	if(town->tempOwner!=player)

+ 2 - 0
CCallback.h

@@ -85,6 +85,7 @@ public:
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
+	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
 	virtual void endTurn()=0;
 	virtual void endTurn()=0;
 	virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
 	virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
@@ -159,6 +160,7 @@ public:
 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
 	bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) override;
+	void eraseArtifactByClient(const ArtifactLocation & al) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
 	void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
 	void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
 	bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
 	bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;

+ 2 - 0
CMakePresets.json

@@ -61,6 +61,8 @@
             "description": "VCMI Linux GCC",
             "description": "VCMI Linux GCC",
             "inherits": "linux-release",
             "inherits": "linux-release",
             "cacheVariables": {
             "cacheVariables": {
+                "ENABLE_LUA" : "ON",
+                "ENABLE_PCH" : "OFF",
                 "CMAKE_C_COMPILER": "/usr/bin/gcc",
                 "CMAKE_C_COMPILER": "/usr/bin/gcc",
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
             }
             }

+ 7 - 1
Global.h

@@ -279,7 +279,7 @@ template<typename T, size_t N> char (&_ArrayCountObj(const T (&)[N]))[N];
 #define VCMI_LIB_NAMESPACE_BEGIN
 #define VCMI_LIB_NAMESPACE_BEGIN
 #define VCMI_LIB_NAMESPACE_END
 #define VCMI_LIB_NAMESPACE_END
 #define VCMI_LIB_USING_NAMESPACE
 #define VCMI_LIB_USING_NAMESPACE
-#define VCMI_LIB_WRAP_NAMESPACE(x) x
+#define VCMI_LIB_WRAP_NAMESPACE(x) ::x
 #endif
 #endif
 
 
 /* ---------------------------------------------------------------------------- */
 /* ---------------------------------------------------------------------------- */
@@ -753,6 +753,12 @@ namespace vstd
 		if(i < 0) return -i;
 		if(i < 0) return -i;
 		return i;
 		return i;
 	}
 	}
+
+	///C++23
+	template< class Enum > constexpr std::underlying_type_t<Enum> to_underlying( Enum e ) noexcept
+	{
+		return static_cast<std::underlying_type_t<Enum>>(e);
+	}
 }
 }
 using vstd::operator-=;
 using vstd::operator-=;
 
 

BIN
Mods/vcmi/Data/UnitMaxMovementHighlight.png


BIN
Mods/vcmi/Data/UnitMovementHighlight.png


+ 6 - 0
Mods/vcmi/config/vcmi/english.json

@@ -79,6 +79,10 @@
 	"vcmi.battleOptions.animationsSpeed1.help": "Set animation speed to very slow",
 	"vcmi.battleOptions.animationsSpeed1.help": "Set animation speed to very slow",
 	"vcmi.battleOptions.animationsSpeed5.help": "Set animation speed to very fast",
 	"vcmi.battleOptions.animationsSpeed5.help": "Set animation speed to very fast",
 	"vcmi.battleOptions.animationsSpeed6.help": "Set animation speed to instantaneous",
 	"vcmi.battleOptions.animationsSpeed6.help": "Set animation speed to instantaneous",
+	"vcmi.battleOptions.touchscreenMode.hover": "Touchscreen mode",
+	"vcmi.battleOptions.touchscreenMode.help": "{Touchscreen mode}\n\nIf enabled, second click is required to confirm and execute action. This is more suitable for touchscreen devices.",
+	"vcmi.battleOptions.movementHighlightOnHover.hover": "Movement Highlight on Hover",
+	"vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nHighlight unit's movement range when you hover over it.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle",
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately",
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately",
@@ -94,6 +98,8 @@
 	"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
 
 
+	"vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result",
+
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\nShow the number of creatures available to purchase instead of their growth in town summary (bottom-left corner of town screen).",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\nShow the number of creatures available to purchase instead of their growth in town summary (bottom-left corner of town screen).",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Show Weekly Growth of Creatures",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Show Weekly Growth of Creatures",

+ 1 - 0
Mods/vcmi/mod.json

@@ -50,6 +50,7 @@
 		"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
 		"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
 		"author" : "Команда VCMI",
 		"author" : "Команда VCMI",
 		
 		
+		"skipValidation" : true,
 		"translations" : [
 		"translations" : [
 			"config/vcmi/ukrainian.json"
 			"config/vcmi/ukrainian.json"
 		]
 		]

+ 5 - 5
android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java

@@ -18,7 +18,7 @@ import java.util.List;
 
 
 import eu.vcmi.vcmi.content.AsyncLauncherInitialization;
 import eu.vcmi.vcmi.content.AsyncLauncherInitialization;
 import eu.vcmi.vcmi.settings.AdventureAiController;
 import eu.vcmi.vcmi.settings.AdventureAiController;
-import eu.vcmi.vcmi.settings.CodepageSettingController;
+import eu.vcmi.vcmi.settings.LanguageSettingController;
 import eu.vcmi.vcmi.settings.CopyDataController;
 import eu.vcmi.vcmi.settings.CopyDataController;
 import eu.vcmi.vcmi.settings.ExportDataController;
 import eu.vcmi.vcmi.settings.ExportDataController;
 import eu.vcmi.vcmi.settings.LauncherSettingController;
 import eu.vcmi.vcmi.settings.LauncherSettingController;
@@ -45,7 +45,7 @@ public class ActivityLauncher extends ActivityWithToolbar
     private TextView mErrorMessage;
     private TextView mErrorMessage;
     private Config mConfig;
     private Config mConfig;
     private LauncherSettingController<ScreenResSettingController.ScreenRes, Config> mCtrlScreenRes;
     private LauncherSettingController<ScreenResSettingController.ScreenRes, Config> mCtrlScreenRes;
-    private LauncherSettingController<String, Config> mCtrlCodepage;
+    private LauncherSettingController<String, Config> mCtrlLanguage;
     private LauncherSettingController<PointerModeSettingController.PointerMode, Config> mCtrlPointerMode;
     private LauncherSettingController<PointerModeSettingController.PointerMode, Config> mCtrlPointerMode;
     private LauncherSettingController<Void, Void> mCtrlStart;
     private LauncherSettingController<Void, Void> mCtrlStart;
     private LauncherSettingController<Float, Config> mCtrlPointerMulti;
     private LauncherSettingController<Float, Config> mCtrlPointerMulti;
@@ -203,7 +203,7 @@ public class ActivityLauncher extends ActivityWithToolbar
         (mCtrlExport = new ExportDataController(this)).init(R.id.launcher_btn_export);
         (mCtrlExport = new ExportDataController(this)).init(R.id.launcher_btn_export);
         new ModsBtnController(this, v -> startActivity(new Intent(ActivityLauncher.this, ActivityMods.class))).init(R.id.launcher_btn_mods);
         new ModsBtnController(this, v -> startActivity(new Intent(ActivityLauncher.this, ActivityMods.class))).init(R.id.launcher_btn_mods);
         mCtrlScreenRes = new ScreenResSettingController(this).init(R.id.launcher_btn_res, mConfig);
         mCtrlScreenRes = new ScreenResSettingController(this).init(R.id.launcher_btn_res, mConfig);
-        mCtrlCodepage = new CodepageSettingController(this).init(R.id.launcher_btn_cp, mConfig);
+        mCtrlLanguage = new LanguageSettingController(this).init(R.id.launcher_btn_cp, mConfig);
         mCtrlPointerMode = new PointerModeSettingController(this).init(R.id.launcher_btn_pointer_mode, mConfig);
         mCtrlPointerMode = new PointerModeSettingController(this).init(R.id.launcher_btn_pointer_mode, mConfig);
         mCtrlPointerMulti = new PointerMultiplierSettingController(this).init(R.id.launcher_btn_pointer_multi, mConfig);
         mCtrlPointerMulti = new PointerMultiplierSettingController(this).init(R.id.launcher_btn_pointer_multi, mConfig);
         mCtrlSoundVol = new SoundSettingController(this).init(R.id.launcher_btn_volume_sound, mConfig);
         mCtrlSoundVol = new SoundSettingController(this).init(R.id.launcher_btn_volume_sound, mConfig);
@@ -211,7 +211,7 @@ public class ActivityLauncher extends ActivityWithToolbar
         mAiController = new AdventureAiController(this).init(R.id.launcher_btn_adventure_ai, mConfig);
         mAiController = new AdventureAiController(this).init(R.id.launcher_btn_adventure_ai, mConfig);
 
 
         mActualSettings.clear();
         mActualSettings.clear();
-        mActualSettings.add(mCtrlCodepage);
+        mActualSettings.add(mCtrlLanguage);
         mActualSettings.add(mCtrlScreenRes);
         mActualSettings.add(mCtrlScreenRes);
         mActualSettings.add(mCtrlPointerMode);
         mActualSettings.add(mCtrlPointerMode);
         mActualSettings.add(mCtrlPointerMulti);
         mActualSettings.add(mCtrlPointerMulti);
@@ -267,7 +267,7 @@ public class ActivityLauncher extends ActivityWithToolbar
     private void onConfigUpdated()
     private void onConfigUpdated()
     {
     {
         updateCtrlConfig(mCtrlScreenRes, mConfig);
         updateCtrlConfig(mCtrlScreenRes, mConfig);
-        updateCtrlConfig(mCtrlCodepage, mConfig);
+        updateCtrlConfig(mCtrlLanguage, mConfig);
         updateCtrlConfig(mCtrlPointerMode, mConfig);
         updateCtrlConfig(mCtrlPointerMode, mConfig);
         updateCtrlConfig(mCtrlPointerMulti, mConfig);
         updateCtrlConfig(mCtrlPointerMulti, mConfig);
         updateCtrlConfig(mCtrlSoundVol, mConfig);
         updateCtrlConfig(mCtrlSoundVol, mConfig);

+ 1 - 1
android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java

@@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse;
 public class ActivityMods extends ActivityWithToolbar
 public class ActivityMods extends ActivityWithToolbar
 {
 {
     private static final boolean ENABLE_REPO_DOWNLOADING = true;
     private static final boolean ENABLE_REPO_DOWNLOADING = true;
-    private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/github.json";
+    private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.2.json";
     private VCMIModsRepo mRepo;
     private VCMIModsRepo mRepo;
     private RecyclerView mRecycler;
     private RecyclerView mRecycler;
 
 

+ 8 - 8
android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java

@@ -14,13 +14,13 @@ import eu.vcmi.vcmi.util.Log;
  */
  */
 public class Config
 public class Config
 {
 {
-    public static final String DEFAULT_CODEPAGE = "CP1250";
+    public static final String DEFAULT_LANGUAGE = "english";
     public static final int DEFAULT_MUSIC_VALUE = 5;
     public static final int DEFAULT_MUSIC_VALUE = 5;
     public static final int DEFAULT_SOUND_VALUE = 5;
     public static final int DEFAULT_SOUND_VALUE = 5;
     public static final int DEFAULT_SCREEN_RES_W = 800;
     public static final int DEFAULT_SCREEN_RES_W = 800;
     public static final int DEFAULT_SCREEN_RES_H = 600;
     public static final int DEFAULT_SCREEN_RES_H = 600;
 
 
-    public String mCodepage;
+    public String mLanguage;
     public int mResolutionWidth;
     public int mResolutionWidth;
     public int mResolutionHeight;
     public int mResolutionHeight;
     public boolean mSwipeEnabled;
     public boolean mSwipeEnabled;
@@ -85,7 +85,7 @@ public class Config
         final Config config = new Config();
         final Config config = new Config();
         final JSONObject general = accessNode(obj, "general");
         final JSONObject general = accessNode(obj, "general");
         final JSONObject server = accessNode(obj, "server");
         final JSONObject server = accessNode(obj, "server");
-        config.mCodepage = loadEntry(general, "encoding", DEFAULT_CODEPAGE);
+        config.mLanguage = loadEntry(general, "language", DEFAULT_LANGUAGE);
         config.mVolumeSound = loadEntry(general, "sound", DEFAULT_SOUND_VALUE);
         config.mVolumeSound = loadEntry(general, "sound", DEFAULT_SOUND_VALUE);
         config.mVolumeMusic = loadEntry(general, "music", DEFAULT_MUSIC_VALUE);
         config.mVolumeMusic = loadEntry(general, "music", DEFAULT_MUSIC_VALUE);
         config.mSwipeEnabled = loadEntry(general, "swipe", true);
         config.mSwipeEnabled = loadEntry(general, "swipe", true);
@@ -101,9 +101,9 @@ public class Config
         return config;
         return config;
     }
     }
 
 
-    public void updateCodepage(final String s)
+    public void updateLanguage(final String s)
     {
     {
-        mCodepage = s;
+        mLanguage = s;
         mIsModified = true;
         mIsModified = true;
     }
     }
 
 
@@ -202,9 +202,9 @@ public class Config
         final JSONObject screenRes = screenResNode == null ? new JSONObject() : screenResNode;
         final JSONObject screenRes = screenResNode == null ? new JSONObject() : screenResNode;
         final JSONObject server = serverNode == null ? new JSONObject() : serverNode;
         final JSONObject server = serverNode == null ? new JSONObject() : serverNode;
 
 
-        if (mCodepage != null)
+        if (mLanguage != null)
         {
         {
-            general.put("encoding", mCodepage);
+            general.put("language", mLanguage);
         }
         }
 
 
         general.put("swipe", mSwipeEnabled);
         general.put("swipe", mSwipeEnabled);
@@ -230,4 +230,4 @@ public class Config
 
 
         return root.toString();
         return root.toString();
     }
     }
-}
+}

+ 0 - 40
android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingDialog.java

@@ -1,40 +0,0 @@
-package eu.vcmi.vcmi.settings;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import eu.vcmi.vcmi.R;
-
-/**
- * @author F
- */
-public class CodepageSettingDialog extends LauncherSettingDialog<String>
-{
-    private static final List<String> AVAILABLE_CODEPAGES = new ArrayList<>();
-
-    static
-    {
-        AVAILABLE_CODEPAGES.add("CP1250");
-        AVAILABLE_CODEPAGES.add("CP1251");
-        AVAILABLE_CODEPAGES.add("CP1252");
-        AVAILABLE_CODEPAGES.add("GBK");
-        AVAILABLE_CODEPAGES.add("GB2312");
-    }
-
-    public CodepageSettingDialog()
-    {
-        super(AVAILABLE_CODEPAGES);
-    }
-
-    @Override
-    protected int dialogTitleResId()
-    {
-        return R.string.launcher_btn_cp_title;
-    }
-
-    @Override
-    protected CharSequence itemName(final String item)
-    {
-        return item;
-    }
-}

+ 8 - 8
android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingController.java → android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingController.java

@@ -8,9 +8,9 @@ import eu.vcmi.vcmi.R;
 /**
 /**
  * @author F
  * @author F
  */
  */
-public class CodepageSettingController extends LauncherSettingWithDialogController<String, Config>
+public class LanguageSettingController extends LauncherSettingWithDialogController<String, Config>
 {
 {
-    public CodepageSettingController(final AppCompatActivity activity)
+    public LanguageSettingController(final AppCompatActivity activity)
     {
     {
         super(activity);
         super(activity);
     }
     }
@@ -18,20 +18,20 @@ public class CodepageSettingController extends LauncherSettingWithDialogControll
     @Override
     @Override
     protected LauncherSettingDialog<String> dialog()
     protected LauncherSettingDialog<String> dialog()
     {
     {
-        return new CodepageSettingDialog();
+        return new LanguageSettingDialog();
     }
     }
 
 
     @Override
     @Override
     public void onItemChosen(final String item)
     public void onItemChosen(final String item)
     {
     {
-        mConfig.updateCodepage(item);
+        mConfig.updateLanguage(item);
         updateContent();
         updateContent();
     }
     }
 
 
     @Override
     @Override
     protected String mainText()
     protected String mainText()
     {
     {
-        return mActivity.getString(R.string.launcher_btn_cp_title);
+        return mActivity.getString(R.string.launcher_btn_language_title);
     }
     }
 
 
     @Override
     @Override
@@ -41,8 +41,8 @@ public class CodepageSettingController extends LauncherSettingWithDialogControll
         {
         {
             return "";
             return "";
         }
         }
-        return mConfig.mCodepage == null || mConfig.mCodepage.isEmpty()
-               ? mActivity.getString(R.string.launcher_btn_cp_subtitle_unknown)
-               : mActivity.getString(R.string.launcher_btn_cp_subtitle, mConfig.mCodepage);
+        return mConfig.mLanguage == null || mConfig.mLanguage.isEmpty()
+               ? mActivity.getString(R.string.launcher_btn_language_subtitle_unknown)
+               : mActivity.getString(R.string.launcher_btn_language_subtitle, mConfig.mLanguage);
     }
     }
 }
 }

+ 47 - 0
android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingDialog.java

@@ -0,0 +1,47 @@
+package eu.vcmi.vcmi.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.vcmi.vcmi.R;
+
+/**
+ * @author F
+ */
+public class LanguageSettingDialog extends LauncherSettingDialog<String>
+{
+    private static final List<String> AVAILABLE_LANGUAGES = new ArrayList<>();
+
+    static
+    {
+        AVAILABLE_LANGUAGES.add("english");
+        AVAILABLE_LANGUAGES.add("chinese");
+        AVAILABLE_LANGUAGES.add("french");
+        AVAILABLE_LANGUAGES.add("german");
+        AVAILABLE_LANGUAGES.add("korean");
+        AVAILABLE_LANGUAGES.add("polish");
+        AVAILABLE_LANGUAGES.add("russian");
+        AVAILABLE_LANGUAGES.add("spanish");
+        AVAILABLE_LANGUAGES.add("ukrainian");
+        AVAILABLE_LANGUAGES.add("other_cp1250");
+        AVAILABLE_LANGUAGES.add("other_cp1251");
+        AVAILABLE_LANGUAGES.add("other_cp1252");
+    }
+
+    public LanguageSettingDialog()
+    {
+        super(AVAILABLE_LANGUAGES);
+    }
+
+    @Override
+    protected int dialogTitleResId()
+    {
+        return R.string.launcher_btn_language_title;
+    }
+
+    @Override
+    protected CharSequence itemName(final String item)
+    {
+        return item;
+    }
+}

+ 3 - 3
android/vcmi-app/src/main/res/values-de/strings.xml

@@ -10,9 +10,9 @@
     <string name="launcher_btn_start_subtitle">Aktuelle VCMI-Version: %1$s</string>
     <string name="launcher_btn_start_subtitle">Aktuelle VCMI-Version: %1$s</string>
     <string name="launcher_btn_mods_title">Mods</string>
     <string name="launcher_btn_mods_title">Mods</string>
     <string name="launcher_btn_mods_subtitle">Neue Burgen, Kreaturen, Objekte und Erweiterungen hinzufügen</string>
     <string name="launcher_btn_mods_subtitle">Neue Burgen, Kreaturen, Objekte und Erweiterungen hinzufügen</string>
-    <string name="launcher_btn_cp_title">Zeichensatz</string>
-    <string name="launcher_btn_cp_subtitle_unknown">Aktuell: unbekannt</string>
-    <string name="launcher_btn_cp_subtitle">Aktuell: %1$s</string>
+    <string name="launcher_btn_language_title">Sprache</string>
+    <string name="launcher_btn_language_subtitle_unknown">Aktuell: unbekannt</string>
+    <string name="launcher_btn_language_subtitle">Aktuell: %1$s</string>
     <string name="launcher_btn_pointermode_title">Zeigermodus ändern</string>
     <string name="launcher_btn_pointermode_title">Zeigermodus ändern</string>
     <string name="launcher_btn_pointermode_subtitle">Aktuell: %1$s</string>
     <string name="launcher_btn_pointermode_subtitle">Aktuell: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Aktuell: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Aktuell: %1$s</string>

+ 3 - 3
android/vcmi-app/src/main/res/values-pl/strings.xml

@@ -10,9 +10,9 @@
     <string name="launcher_btn_start_subtitle">Obecna wersja VCMI: %1$s</string>
     <string name="launcher_btn_start_subtitle">Obecna wersja VCMI: %1$s</string>
     <string name="launcher_btn_mods_title">Mody</string>
     <string name="launcher_btn_mods_title">Mody</string>
     <string name="launcher_btn_mods_subtitle">Zainstaluj nowe frakcje, obiekty, dodatki</string>
     <string name="launcher_btn_mods_subtitle">Zainstaluj nowe frakcje, obiekty, dodatki</string>
-    <string name="launcher_btn_cp_title">Strona kodowa</string>
-    <string name="launcher_btn_cp_subtitle_unknown">Obecnie: nieznane</string>
-    <string name="launcher_btn_cp_subtitle">Obecnie: %1$s</string>
+    <string name="launcher_btn_language_title">Język</string>
+    <string name="launcher_btn_language_subtitle_unknown">Obecnie: nieznane</string>
+    <string name="launcher_btn_language_subtitle">Obecnie: %1$s</string>
     <string name="launcher_btn_pointermode_title">Zmień tryb kursora</string>
     <string name="launcher_btn_pointermode_title">Zmień tryb kursora</string>
     <string name="launcher_btn_pointermode_subtitle">Obecnie: %1$s</string>
     <string name="launcher_btn_pointermode_subtitle">Obecnie: %1$s</string>
     <string name="launcher_btn_pointermulti_title">Mnożnik prędkości kursora</string>
     <string name="launcher_btn_pointermulti_title">Mnożnik prędkości kursora</string>

+ 4 - 4
android/vcmi-app/src/main/res/values-ru/strings.xml

@@ -10,9 +10,9 @@
     <string name="launcher_btn_start_subtitle">Текущая версия VCMI: %1$s</string>
     <string name="launcher_btn_start_subtitle">Текущая версия VCMI: %1$s</string>
     <string name="launcher_btn_mods_title">Моды</string>
     <string name="launcher_btn_mods_title">Моды</string>
     <string name="launcher_btn_mods_subtitle">Добавить новые замки, существа, объекты, расширения</string>
     <string name="launcher_btn_mods_subtitle">Добавить новые замки, существа, объекты, расширения</string>
-    <string name="launcher_btn_cp_title">Кодовая страница</string>
-    <string name="launcher_btn_cp_subtitle_unknown">Текущая: неизвестно</string>
-    <string name="launcher_btn_cp_subtitle">Текущая: %1$s</string>
+    <string name="launcher_btn_language_title">Язык</string>
+    <string name="launcher_btn_language_subtitle_unknown">Текущая: неизвестно</string>
+    <string name="launcher_btn_language_subtitle">Текущая: %1$s</string>
     <string name="launcher_btn_pointermode_title">Изменить режим управления указателем</string>
     <string name="launcher_btn_pointermode_title">Изменить режим управления указателем</string>
     <string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
     <string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Текущая: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Текущая: %1$s</string>
@@ -60,4 +60,4 @@
     <string name="launcher_btn_import_title">Загрузить данные VCMI во внутреннее хранилище</string>
     <string name="launcher_btn_import_title">Загрузить данные VCMI во внутреннее хранилище</string>
     <string name="launcher_btn_import_description">Скопировать данные VCMI во внутреннее хранилище. Вы можете загрузить старую папку vcmi-data от версии 0.99 или файлы Героев</string>
     <string name="launcher_btn_import_description">Скопировать данные VCMI во внутреннее хранилище. Вы можете загрузить старую папку vcmi-data от версии 0.99 или файлы Героев</string>
     <string name="launcher_progress_copy">Копируем %1$s</string>
     <string name="launcher_progress_copy">Копируем %1$s</string>
-</resources>
+</resources>

+ 4 - 4
android/vcmi-app/src/main/res/values-uk/strings.xml

@@ -10,9 +10,9 @@
     <string name="launcher_btn_start_subtitle">Поточна версія VCMI: %1$s</string>
     <string name="launcher_btn_start_subtitle">Поточна версія VCMI: %1$s</string>
     <string name="launcher_btn_mods_title">Моди</string>
     <string name="launcher_btn_mods_title">Моди</string>
     <string name="launcher_btn_mods_subtitle">Додати нові замки, істот, об’єкти, розширення</string>
     <string name="launcher_btn_mods_subtitle">Додати нові замки, істот, об’єкти, розширення</string>
-    <string name="launcher_btn_cp_title">Кодова сторінка</string>
-    <string name="launcher_btn_cp_subtitle_unknown">Поточна: невідомо</string>
-    <string name="launcher_btn_cp_subtitle">Поточна: %1$s</string>
+    <string name="launcher_btn_language_title">Мова</string>
+    <string name="launcher_btn_language_subtitle_unknown">Поточна: невідомо</string>
+    <string name="launcher_btn_language_subtitle">Поточна: %1$s</string>
     <string name="launcher_btn_pointermode_title">Змінити режим керування курсором</string>
     <string name="launcher_btn_pointermode_title">Змінити режим керування курсором</string>
     <string name="launcher_btn_pointermode_subtitle">Поточна: %1$s</string>
     <string name="launcher_btn_pointermode_subtitle">Поточна: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Поточна: %1$s</string>
     <string name="launcher_btn_pointermulti_subtitle">Поточна: %1$s</string>
@@ -60,4 +60,4 @@
     <string name="launcher_btn_import_title">Завантажити дані VCMI у внутрішнє сховище</string>
     <string name="launcher_btn_import_title">Завантажити дані VCMI у внутрішнє сховище</string>
     <string name="launcher_btn_import_description">Копіювати дані VCMI у внутрішнє сховище. Ви можете завантажити стару папку vcmi-data від версії 0.99 чи файли героїв</string>
     <string name="launcher_btn_import_description">Копіювати дані VCMI у внутрішнє сховище. Ви можете завантажити стару папку vcmi-data від версії 0.99 чи файли героїв</string>
     <string name="launcher_progress_copy">Копіюємо %1$s</string>
     <string name="launcher_progress_copy">Копіюємо %1$s</string>
-</resources>
+</resources>

+ 3 - 3
android/vcmi-app/src/main/res/values/strings.xml

@@ -15,9 +15,9 @@
     <string name="launcher_btn_start_subtitle">Current VCMI version: %1$s</string>
     <string name="launcher_btn_start_subtitle">Current VCMI version: %1$s</string>
     <string name="launcher_btn_mods_title">Mods</string>
     <string name="launcher_btn_mods_title">Mods</string>
     <string name="launcher_btn_mods_subtitle">Install new factions, objects, extras</string>
     <string name="launcher_btn_mods_subtitle">Install new factions, objects, extras</string>
-    <string name="launcher_btn_cp_title">Codepage</string>
-    <string name="launcher_btn_cp_subtitle_unknown">Currently: unknown</string>
-    <string name="launcher_btn_cp_subtitle">Currently: %1$s</string>
+    <string name="launcher_btn_language_title">Language</string>
+    <string name="launcher_btn_language_subtitle_unknown">Currently: unknown</string>
+    <string name="launcher_btn_language_subtitle">Currently: %1$s</string>
     <string name="launcher_btn_pointermode_title">Change pointer mode</string>
     <string name="launcher_btn_pointermode_title">Change pointer mode</string>
     <string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
     <string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
     <string name="launcher_btn_pointermulti_title">Relative pointer speed multiplier</string>
     <string name="launcher_btn_pointermulti_title">Relative pointer speed multiplier</string>

+ 12 - 12
client/CMusicHandler.cpp

@@ -446,15 +446,7 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
 
 
 void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
 void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
 {
 {
-	try
-	{
-		queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
-	}
-	catch(std::exception &e)
-	{
-		logGlobal->error("Failed to queue music. setName=%s\tmusicURI=%s", setName, musicURI);
-		logGlobal->error("Exception: %s", e.what());
-	}
+	queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
 }
 }
 
 
 void CMusicHandler::stopMusic(int fade_ms)
 void CMusicHandler::stopMusic(int fade_ms)
@@ -563,12 +555,20 @@ void MusicEntry::load(std::string musicURI)
 	}
 	}
 
 
 	currentName = musicURI;
 	currentName = musicURI;
+	music = nullptr;
 
 
 	logGlobal->trace("Loading music file %s", musicURI);
 	logGlobal->trace("Loading music file %s", musicURI);
 
 
-	auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::MUSIC)));
-
-	music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
+	try
+	{
+		auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::MUSIC)));
+		music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
+	}
+	catch(std::exception &e)
+	{
+		logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, musicURI);
+		logGlobal->error("Exception: %s", e.what());
+	}
 
 
 	if(!music)
 	if(!music)
 	{
 	{

+ 27 - 6
client/CPlayerInterface.cpp

@@ -693,7 +693,15 @@ void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreat
 void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
 void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (settings["adventure"]["quickCombat"].Bool())
+	bool autoBattleResultRefused = (lastBattleArmies.first == army1 && lastBattleArmies.second == army2);
+	lastBattleArmies.first = army1;
+	lastBattleArmies.second = army2;
+	//quick combat with neutral creatures only
+	auto * army2_object = dynamic_cast<const CGObjectInstance *>(army2);
+	if((!autoBattleResultRefused && !allowBattleReplay && army2_object
+		&& army2_object->getOwner() == PlayerColor::UNFLAGGABLE
+		&& settings["adventure"]["quickCombat"].Bool())
+		|| settings["adventure"]["alwaysSkipCombat"].Bool())
 	{
 	{
 		autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
 		autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
 		autofightingAI->initBattleInterface(env, cb);
 		autofightingAI->initBattleInterface(env, cb);
@@ -702,6 +710,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 		cb->registerBattleInterface(autofightingAI);
 		cb->registerBattleInterface(autofightingAI);
 		// Player shouldn't be able to move on adventure map if quick combat is going
 		// Player shouldn't be able to move on adventure map if quick combat is going
 		adventureInt->quickCombatLock();
 		adventureInt->quickCombatLock();
+		allowBattleReplay = true;
 	}
 	}
 
 
 	//Don't wait for dialogs when we are non-active hot-seat player
 	//Don't wait for dialogs when we are non-active hot-seat player
@@ -889,7 +898,7 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	return ret;
 	return ret;
 }
 }
 
 
-void CPlayerInterface::battleEnd(const BattleResult *br)
+void CPlayerInterface::battleEnd(const BattleResult *br, QueryID queryID)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	if(isAutoFightOn || autofightingAI)
 	if(isAutoFightOn || autofightingAI)
@@ -900,7 +909,14 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 
 
 		if(!battleInt)
 		if(!battleInt)
 		{
 		{
-			GH.pushIntT<BattleResultWindow>(*br, *this);
+			bool allowManualReplay = allowBattleReplay && !settings["adventure"]["alwaysSkipCombat"].Bool();
+			allowBattleReplay = false;
+			auto wnd = std::make_shared<BattleResultWindow>(*br, *this, allowManualReplay);
+			wnd->resultCallback = [=](ui32 selection)
+			{
+				cb->selectionMade(selection, queryID);
+			};
+			GH.pushInt(wnd);
 			// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
 			// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
 			// Otherwise NewTurn causes freeze.
 			// Otherwise NewTurn causes freeze.
 			waitWhileDialog();
 			waitWhileDialog();
@@ -911,7 +927,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 
 
 	BATTLE_EVENT_POSSIBLE_RETURN;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 
-	battleInt->battleFinished(*br);
+	battleInt->battleFinished(*br, queryID);
 	adventureInt->quickCombatUnlock();
 	adventureInt->quickCombatUnlock();
 }
 }
 
 
@@ -947,7 +963,7 @@ void CPlayerInterface::battleStacksEffectsSet( const SetStackEffect & sse )
 void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
 void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	//TODO why is this different (no return on LOPLINT != this) ?
+	BATTLE_EVENT_POSSIBLE_RETURN;
 
 
 	RETURN_IF_QUICK_COMBAT;
 	RETURN_IF_QUICK_COMBAT;
 	battleInt->effectsController->battleTriggerEffect(bte);
 	battleInt->effectsController->battleTriggerEffect(bte);
@@ -1038,6 +1054,12 @@ void CPlayerInterface::yourTacticPhase(int distance)
 		boost::this_thread::sleep(boost::posix_time::millisec(1));
 		boost::this_thread::sleep(boost::posix_time::millisec(1));
 }
 }
 
 
+void CPlayerInterface::forceEndTacticPhase()
+{
+	if (battleInt)
+		battleInt->tacticsMode = false;
+}
+
 void CPlayerInterface::showInfoDialog(EInfoWindowMode type, const std::string &text, const std::vector<Component> & components, int soundID)
 void CPlayerInterface::showInfoDialog(EInfoWindowMode type, const std::string &text, const std::vector<Component> & components, int soundID)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -1912,7 +1934,6 @@ void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	auto hero = boost::apply_visitor(HeroObjectRetriever(), al.artHolder);
 	auto hero = boost::apply_visitor(HeroObjectRetriever(), al.artHolder);
 	updateInfo(hero);
 	updateInfo(hero);
-	askToAssembleArtifact(al);
 }
 }
 
 
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)

+ 4 - 1
client/CPlayerInterface.h

@@ -123,6 +123,8 @@ public:
 	//During battle is quick combat mode is used
 	//During battle is quick combat mode is used
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
+	bool allowBattleReplay = false;
+	std::pair<const CCreatureSet *, const CCreatureSet *> lastBattleArmies;
 
 
 	struct SpellbookLastSetting
 	struct SpellbookLastSetting
 	{
 	{
@@ -210,7 +212,7 @@ public:
 	void actionStarted(const BattleAction& action) override;//occurs BEFORE action taken by active stack or by the hero
 	void actionStarted(const BattleAction& action) override;//occurs BEFORE action taken by active stack or by the hero
 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
 	void battleAttack(const BattleAttack *ba) override; //stack performs attack
 	void battleAttack(const BattleAttack *ba) override; //stack performs attack
-	void battleEnd(const BattleResult *br) override; //end of battle
+	void battleEnd(const BattleResult *br, QueryID queryID) override; //end of battle
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 	void battleLogMessage(const std::vector<MetaString> & lines) override;
 	void battleLogMessage(const std::vector<MetaString> & lines) override;
@@ -226,6 +228,7 @@ public:
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
 	void battleGateStateChanged(const EGateState state) override;
 	void battleGateStateChanged(const EGateState state) override;
 	void yourTacticPhase(int distance) override;
 	void yourTacticPhase(int distance) override;
+	void forceEndTacticPhase() override;
 
 
 	//-------------//
 	//-------------//
 	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes);
 	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes);

+ 46 - 18
client/Client.cpp

@@ -7,6 +7,7 @@
  * Full text of license available in license.txt file, in main folder
  * Full text of license available in license.txt file, in main folder
  *
  *
  */
  */
+#include "Global.h"
 #include "StdInc.h"
 #include "StdInc.h"
 #include "Client.h"
 #include "Client.h"
 
 
@@ -31,6 +32,7 @@
 #include "../lib/registerTypes/RegisterTypes.h"
 #include "../lib/registerTypes/RegisterTypes.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/Connection.h"
 
 
+#include <memory>
 #include <vcmi/events/EventBus.h>
 #include <vcmi/events/EventBus.h>
 
 
 #if SCRIPTING_ENABLED
 #if SCRIPTING_ENABLED
@@ -369,6 +371,9 @@ void CClient::endGame()
 		logNetwork->info("Deleted mapHandler and gameState.");
 		logNetwork->info("Deleted mapHandler and gameState.");
 	}
 	}
 
 
+	//threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread
+	cleanThreads();
+
 	playerint.clear();
 	playerint.clear();
 	battleints.clear();
 	battleints.clear();
 	battleCallbacks.clear();
 	battleCallbacks.clear();
@@ -553,13 +558,29 @@ void CClient::battleStarted(const BattleInfo * info)
 	auto & leftSide = info->sides[0], & rightSide = info->sides[1];
 	auto & leftSide = info->sides[0], & rightSide = info->sides[1];
 
 
 	//If quick combat is not, do not prepare interfaces for battleint
 	//If quick combat is not, do not prepare interfaces for battleint
-	if(!settings["adventure"]["quickCombat"].Bool())
+	auto callBattleStart = [&](PlayerColor color, ui8 side)
 	{
 	{
-		if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
-			att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
+		if(vstd::contains(battleints, color))
+			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
+	};
+	
+	callBattleStart(leftSide.color, 0);
+	callBattleStart(rightSide.color, 1);
+	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
+		callBattleStart(PlayerColor::SPECTATOR, 1);
+	
+	if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
+		att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
 
 
-		if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
-			def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
+	if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
+		def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
+	
+	//Remove player interfaces for auto battle (quickCombat option)
+	if(att && att->isAutoFightOn)
+	{
+		att.reset();
+		def.reset();
 	}
 	}
 
 
 	if(!settings["session"]["headless"].Bool())
 	if(!settings["session"]["headless"].Bool())
@@ -579,21 +600,10 @@ void CClient::battleStarted(const BattleInfo * info)
 		}
 		}
 	}
 	}
 
 
-	auto callBattleStart = [&](PlayerColor color, ui8 side)
-	{
-		if(vstd::contains(battleints, color))
-			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
-	};
-
-	callBattleStart(leftSide.color, 0);
-	callBattleStart(rightSide.color, 1);
-	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
-	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
-		callBattleStart(PlayerColor::SPECTATOR, 1);
-
 	if(info->tacticDistance && vstd::contains(battleints, info->sides[info->tacticsSide].color))
 	if(info->tacticDistance && vstd::contains(battleints, info->sides[info->tacticsSide].color))
 	{
 	{
-		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);
+		PlayerColor color = info->sides[info->tacticsSide].color;
+		playerTacticThreads[color] = std::make_unique<boost::thread>(&CClient::commenceTacticPhaseForInt, this, battleints[color]);
 	}
 	}
 }
 }
 
 
@@ -626,6 +636,7 @@ void CClient::battleFinished()
 		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
 		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
 
 
 	setBattle(nullptr);
 	setBattle(nullptr);
+	gs->curB.dellNull();
 }
 }
 
 
 void CClient::startPlayerBattleAction(PlayerColor color)
 void CClient::startPlayerBattleAction(PlayerColor color)
@@ -754,6 +765,23 @@ void CClient::removeGUI()
 	LOCPLINT = nullptr;
 	LOCPLINT = nullptr;
 }
 }
 
 
+void CClient::cleanThreads()
+{
+	stopAllBattleActions();
+
+	while (!playerTacticThreads.empty())
+	{
+		PlayerColor color = playerTacticThreads.begin()->first;
+
+		//set tacticcMode of the players to false to stop tacticThread
+		if (vstd::contains(battleints, color))
+			battleints[color]->forceEndTacticPhase();
+
+		playerTacticThreads[color]->join();
+		playerTacticThreads.erase(color);
+	}
+}
+
 #ifdef VCMI_ANDROID
 #ifdef VCMI_ANDROID
 #ifndef SINGLE_PROCESS_APP
 #ifndef SINGLE_PROCESS_APP
 extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls)
 extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls)

+ 7 - 1
client/Client.h

@@ -9,6 +9,7 @@
  */
  */
 #pragma once
 #pragma once
 
 
+#include <memory>
 #include <vcmi/Environment.h>
 #include <vcmi/Environment.h>
 
 
 #include "../lib/IGameCallback.h"
 #include "../lib/IGameCallback.h"
@@ -195,7 +196,7 @@ public:
 	void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
 	void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
 	void showTeleportDialog(TeleportDialog * iw) override {};
 	void showTeleportDialog(TeleportDialog * iw) override {};
 	void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
 	void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
-	void giveResource(PlayerColor player, Res::ERes which, int val) override {};
+	void giveResource(PlayerColor player, GameResID which, int val) override {};
 	virtual void giveResources(PlayerColor player, TResources resources) override {};
 	virtual void giveResources(PlayerColor player, TResources resources) override {};
 
 
 	void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
 	void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
@@ -231,6 +232,7 @@ public:
 	void changeObjPos(ObjectInstanceID objid, int3 newPos) override {};
 	void changeObjPos(ObjectInstanceID objid, int3 newPos) override {};
 	void sendAndApply(CPackForClient * pack) override {};
 	void sendAndApply(CPackForClient * pack) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
+	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
 
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
 	void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
 	void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
@@ -241,6 +243,8 @@ public:
 	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
 	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
 	void removeGUI();
 	void removeGUI();
 
 
+	void cleanThreads();
+
 #if SCRIPTING_ENABLED
 #if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;
 	scripting::Pool * getGlobalContextPool() const override;
 	scripting::Pool * getContextPool() const override;
 	scripting::Pool * getContextPool() const override;
@@ -262,6 +266,8 @@ private:
 
 
 	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
 	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
 
 
+	std::map<PlayerColor, std::unique_ptr<boost::thread>> playerTacticThreads;
+
 	void waitForMoveAndSend(PlayerColor color);
 	void waitForMoveAndSend(PlayerColor color);
 	void reinitScripting();
 	void reinitScripting();
 };
 };

+ 3 - 1
client/LobbyClientNetPackVisitors.h

@@ -12,7 +12,9 @@
 #include "../lib/NetPackVisitor.h"
 #include "../lib/NetPackVisitor.h"
 
 
 class CClient;
 class CClient;
+VCMI_LIB_NAMESPACE_BEGIN
 class CGameState;
 class CGameState;
+VCMI_LIB_NAMESPACE_END
 
 
 class ApplyOnLobbyHandlerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 class ApplyOnLobbyHandlerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 {
 {
@@ -53,4 +55,4 @@ public:
 	virtual void visitLobbyStartGame(LobbyStartGame & pack) override;
 	virtual void visitLobbyStartGame(LobbyStartGame & pack) override;
 	virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override;
 	virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override;
 	virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override;
 	virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override;
-};
+};

+ 3 - 1
client/NetPacksClient.cpp

@@ -255,6 +255,8 @@ void ApplyClientNetPackVisitor::visitBulkSmartRebalanceStacks(BulkSmartRebalance
 void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 {
 {
 	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactPut, pack.al);
 	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactPut, pack.al);
+	if(pack.askAssemble)
+		callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
 void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
@@ -735,7 +737,7 @@ void ApplyFirstClientNetPackVisitor::visitBattleUpdateGateState(BattleUpdateGate
 
 
 void ApplyFirstClientNetPackVisitor::visitBattleResult(BattleResult & pack)
 void ApplyFirstClientNetPackVisitor::visitBattleResult(BattleResult & pack)
 {
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleEnd, &pack);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleEnd, &pack, pack.queryID);
 	cl.battleFinished();
 	cl.battleFinished();
 }
 }
 
 

+ 11 - 1
client/adventureMap/CInGameConsole.cpp

@@ -38,6 +38,9 @@ void CInGameConsole::showAll(SDL_Surface * to)
 
 
 void CInGameConsole::show(SDL_Surface * to)
 void CInGameConsole::show(SDL_Surface * to)
 {
 {
+	if (LOCPLINT->cingconsole != this)
+		return;
+
 	int number = 0;
 	int number = 0;
 
 
 	boost::unique_lock<boost::mutex> lock(texts_mx);
 	boost::unique_lock<boost::mutex> lock(texts_mx);
@@ -107,7 +110,11 @@ void CInGameConsole::print(const std::string & txt)
 
 
 void CInGameConsole::keyPressed (const SDL_Keycode & key)
 void CInGameConsole::keyPressed (const SDL_Keycode & key)
 {
 {
-	if(!captureAllKeys && key != SDLK_TAB) return; //because user is not entering any text
+	if (LOCPLINT->cingconsole != this)
+		return;
+
+	if(!captureAllKeys && key != SDLK_TAB)
+		return; //because user is not entering any text
 
 
 	switch(key)
 	switch(key)
 	{
 	{
@@ -192,6 +199,9 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
 
 
 void CInGameConsole::textInputed(const std::string & inputtedText)
 void CInGameConsole::textInputed(const std::string & inputtedText)
 {
 {
+	if (LOCPLINT->cingconsole != this)
+		return;
+
 	if(!captureAllKeys || enteredText.empty())
 	if(!captureAllKeys || enteredText.empty())
 		return;
 		return;
 	enteredText.resize(enteredText.size()-1);
 	enteredText.resize(enteredText.size()-1);

+ 2 - 4
client/adventureMap/CInfoBar.cpp

@@ -173,7 +173,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
 	auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
 	auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
 	auto textRect = fullRect;
 	auto textRect = fullRect;
 	auto imageRect = fullRect;
 	auto imageRect = fullRect;
-	auto font = FONT_SMALL;
+	auto font = tiny ? FONT_TINY : FONT_SMALL;
 	auto maxComponents = 2; 
 	auto maxComponents = 2; 
 
 
 	if(!compsToDisplay.empty())
 	if(!compsToDisplay.empty())
@@ -210,11 +210,9 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
 
 
 		comps = std::make_shared<CComponentBox>(vect, imageRect, 4, 4, 1, maxComponents);
 		comps = std::make_shared<CComponentBox>(vect, imageRect, 4, 4, 1, maxComponents);
 	}
 	}
-	else
-		font = tiny ? FONT_TINY : font;
 
 
 	if(!message.empty())
 	if(!message.empty())
-		text = std::make_shared<CTextBox>(message, textRect, 0, font, ETextAlignment::CENTER, Colors::WHITE);
+		text = std::make_shared<CMultiLineLabel>(textRect, font, ETextAlignment::CENTER, Colors::WHITE, message);
 }
 }
 
 
 void CInfoBar::playNewDaySound()
 void CInfoBar::playNewDaySound()

+ 2 - 2
client/adventureMap/CInfoBar.h

@@ -27,7 +27,7 @@ class CComponentBox;
 class CHeroTooltip;
 class CHeroTooltip;
 class CTownTooltip;
 class CTownTooltip;
 class CLabel;
 class CLabel;
-class CTextBox;
+class CMultiLineLabel;
 
 
 /// Info box which shows next week/day information, hold the current date
 /// Info box which shows next week/day information, hold the current date
 class CInfoBar : public CIntObject
 class CInfoBar : public CIntObject
@@ -112,7 +112,7 @@ private:
 	class VisibleComponentInfo : public CVisibleInfo
 	class VisibleComponentInfo : public CVisibleInfo
 	{
 	{
 		std::shared_ptr<CComponentBox> comps;
 		std::shared_ptr<CComponentBox> comps;
-		std::shared_ptr<CTextBox> text;
+		std::shared_ptr<CMultiLineLabel> text;
 	public:
 	public:
 		struct Cache 
 		struct Cache 
 		{
 		{

+ 1 - 1
client/adventureMap/CResDataBar.cpp

@@ -80,7 +80,7 @@ std::string CResDataBar::buildDateString()
 void CResDataBar::draw(SDL_Surface * to)
 void CResDataBar::draw(SDL_Surface * to)
 {
 {
 	//TODO: all this should be labels, but they require proper text update on change
 	//TODO: all this should be labels, but they require proper text update on change
-	for (auto i=Res::WOOD; i<=Res::GOLD; vstd::advance(i, 1))
+	for (auto i=GameResID(EGameResID::WOOD); i <= GameResID(EGameResID::GOLD); vstd::advance(i, 1))
 	{
 	{
 		std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));
 		std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));
 
 

+ 33 - 6
client/battle/BattleActionsController.cpp

@@ -115,7 +115,9 @@ BattleActionsController::BattleActionsController(BattleInterface & owner):
 	owner(owner),
 	owner(owner),
 	selectedStack(nullptr),
 	selectedStack(nullptr),
 	heroSpellToCast(nullptr)
 	heroSpellToCast(nullptr)
-{}
+{
+	touchscreenMode = settings["battle"]["touchscreenMode"].Bool();
+}
 
 
 void BattleActionsController::endCastingSpell()
 void BattleActionsController::endCastingSpell()
 {
 {
@@ -128,6 +130,7 @@ void BattleActionsController::endCastingSpell()
 	if(owner.stacksController->getActiveStack())
 	if(owner.stacksController->getActiveStack())
 		possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
 		possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
 
 
+	selectedStack = nullptr;
 	GH.fakeMouseMove();
 	GH.fakeMouseMove();
 }
 }
 
 
@@ -621,7 +624,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 		{
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			{
 			{
-				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
+				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack(), true);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
 				if(vstd::contains(acc, targetHex))
 				if(vstd::contains(acc, targetHex))
 					owner.giveCommand(EActionType::WALK, targetHex);
 					owner.giveCommand(EActionType::WALK, targetHex);
@@ -826,6 +829,10 @@ void BattleActionsController::onHoverEnded()
 
 
 void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
 void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
 {
 {
+	static BattleHex lastSelectedHex;
+	static BattleHex lastDirectionalHex;
+	static PossiblePlayerBattleAction::Actions lastSelectedAction;
+	
 	if (owner.stacksController->getActiveStack() == nullptr)
 	if (owner.stacksController->getActiveStack() == nullptr)
 		return;
 		return;
 
 
@@ -835,10 +842,25 @@ void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
 
 
 	if (!actionIsLegal(action, clickedHex))
 	if (!actionIsLegal(action, clickedHex))
 		return;
 		return;
+	
+	auto directionalHex = lastDirectionalHex;
+	if(action.get() == PossiblePlayerBattleAction::ATTACK
+	   || action.get() == PossiblePlayerBattleAction::WALK_AND_ATTACK
+	   || action.get() == PossiblePlayerBattleAction::ATTACK_AND_RETURN)
+		directionalHex = owner.fieldController->fromWhichHexAttack(clickedHex);
+
+	if(!touchscreenMode || (lastSelectedAction == action.get() && lastSelectedHex == clickedHex && lastDirectionalHex == directionalHex))
+	{
+		actionRealize(action, clickedHex);
 
 
-	actionRealize(action, clickedHex);
-
-	GH.statusbar->clear();
+		GH.statusbar->clear();
+	}
+	else
+	{
+		lastSelectedAction = action.get();
+		lastSelectedHex = clickedHex;
+		lastDirectionalHex = directionalHex;
+	}
 }
 }
 
 
 void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack)
 void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack)
@@ -905,7 +927,7 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 
 
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 {
 {
-	std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
+	std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove, false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 
 
 	if (vstd::contains(acc, myNumber))
 	if (vstd::contains(acc, myNumber))
@@ -994,3 +1016,8 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction
 {
 {
 	possibleActions.insert(possibleActions.begin(), action);
 	possibleActions.insert(possibleActions.begin(), action);
 }
 }
+
+void BattleActionsController::setTouchScreenMode(bool enabled)
+{
+	touchscreenMode = enabled;
+}

+ 5 - 1
client/battle/BattleActionsController.h

@@ -34,7 +34,10 @@ enum class MouseHoveredHexContext
 class BattleActionsController
 class BattleActionsController
 {
 {
 	BattleInterface & owner;
 	BattleInterface & owner;
-
+	
+	/// mouse or touchscreen click mode
+	bool touchscreenMode = false;
+	
 	/// all actions possible to call at the moment by player
 	/// all actions possible to call at the moment by player
 	std::vector<PossiblePlayerBattleAction> possibleActions;
 	std::vector<PossiblePlayerBattleAction> possibleActions;
 
 
@@ -129,4 +132,5 @@ public:
 	/// inserts possible action in the beggining in order to prioritize it
 	/// inserts possible action in the beggining in order to prioritize it
 	void pushFrontPossibleAction(PossiblePlayerBattleAction);
 	void pushFrontPossibleAction(PossiblePlayerBattleAction);
 
 
+	void setTouchScreenMode(bool enabled);
 };
 };

+ 3 - 3
client/battle/BattleAnimationClasses.cpp

@@ -158,7 +158,7 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn
 
 
 const CCreature * AttackAnimation::getCreature() const
 const CCreature * AttackAnimation::getCreature() const
 {
 {
-	if (attackingStack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
+	if (attackingStack->getCreature()->getId() == CreatureID::ARROW_TOWERS)
 		return owner.siegeController->getTurretCreature();
 		return owner.siegeController->getTurretCreature();
 	else
 	else
 		return attackingStack->getCreature();
 		return attackingStack->getCreature();
@@ -970,13 +970,13 @@ bool EffectAnimation::init()
 		}
 		}
 		else
 		else
 		{
 		{
-			const CStack * destStack = owner.getCurrentPlayerInterface()->cb->battleGetStackByPos(battlehexes[i], false);
+			const auto * destStack = owner.getCurrentPlayerInterface()->cb->battleGetUnitByPos(battlehexes[i], false);
 			Rect tilePos = owner.fieldController->hexPositionLocal(battlehexes[i]);
 			Rect tilePos = owner.fieldController->hexPositionLocal(battlehexes[i]);
 
 
 			be.pos.x = tilePos.x + tilePos.w/2 - first->width()/2;
 			be.pos.x = tilePos.x + tilePos.w/2 - first->width()/2;
 
 
 			if(destStack && destStack->doubleWide()) // Correction for 2-hex creatures.
 			if(destStack && destStack->doubleWide()) // Correction for 2-hex creatures.
-				be.pos.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
+				be.pos.x += (destStack->unitSide() == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
 
 
 			if (alignToBottom())
 			if (alignToBottom())
 				be.pos.y = tilePos.y + tilePos.h - first->height();
 				be.pos.y = tilePos.y + tilePos.h - first->height();

+ 90 - 71
client/battle/BattleFieldController.cpp

@@ -44,6 +44,8 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	//preparing cells and hexes
 	//preparing cells and hexes
 	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
 	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
 	cellShade = IImage::createFromFile("CCELLSHD.BMP");
 	cellShade = IImage::createFromFile("CCELLSHD.BMP");
+	cellUnitMovementHighlight = IImage::createFromFile("UnitMovementHighlight.PNG", EImageBlitMode::COLORKEY);
+	cellUnitMaxMovementHighlight = IImage::createFromFile("UnitMaxMovementHighlight.PNG", EImageBlitMode::COLORKEY);
 
 
 	if(!owner.siegeController)
 	if(!owner.siegeController)
 	{
 	{
@@ -138,7 +140,6 @@ void BattleFieldController::showBackground(Canvas & canvas)
 		showBackgroundImage(canvas);
 		showBackgroundImage(canvas);
 
 
 	showHighlightedHexes(canvas);
 	showHighlightedHexes(canvas);
-
 }
 }
 
 
 void BattleFieldController::showBackgroundImage(Canvas & canvas)
 void BattleFieldController::showBackgroundImage(Canvas & canvas)
@@ -172,32 +173,34 @@ void BattleFieldController::redrawBackgroundWithHexes()
 {
 {
 	const CStack *activeStack = owner.stacksController->getActiveStack();
 	const CStack *activeStack = owner.stacksController->getActiveStack();
 	std::vector<BattleHex> attackableHexes;
 	std::vector<BattleHex> attackableHexes;
-	if (activeStack)
-		occupyableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
+	if(activeStack)
+		occupiableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, true, &attackableHexes);
 
 
-	//prepare background graphic with hexes and shaded hexes
+	// prepare background graphic with hexes and shaded hexes
 	backgroundWithHexes->draw(background, Point(0,0));
 	backgroundWithHexes->draw(background, Point(0,0));
 	owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes);
 	owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes);
-	if ( owner.siegeController )
+	if(owner.siegeController)
 		owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
 		owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
 
 
-	if (settings["battle"]["stackRange"].Bool())
+	// show shaded hexes for active's stack valid movement and the hexes that it can attack
+	if(settings["battle"]["stackRange"].Bool())
 	{
 	{
-		std::vector<BattleHex> hexesToShade = occupyableHexes;
+		std::vector<BattleHex> hexesToShade = occupiableHexes;
 		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
 		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
-		for (BattleHex hex : hexesToShade)
+		for(BattleHex hex : hexesToShade)
 		{
 		{
-			backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
+			showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
 		}
 		}
 	}
 	}
 
 
+	// draw cell borders
 	if(settings["battle"]["cellBorders"].Bool())
 	if(settings["battle"]["cellBorders"].Bool())
 	{
 	{
-		for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+		for(int i=0; i<GameConstants::BFIELD_SIZE; ++i)
 		{
 		{
-			if ( i % GameConstants::BFIELD_WIDTH == 0)
+			if(i % GameConstants::BFIELD_WIDTH == 0)
 				continue;
 				continue;
-			if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
+			if(i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
 				continue;
 				continue;
 
 
 			backgroundWithHexes->draw(cellBorder, hexPositionLocal(i).topLeft());
 			backgroundWithHexes->draw(cellBorder, hexPositionLocal(i).topLeft());
@@ -205,23 +208,23 @@ void BattleFieldController::redrawBackgroundWithHexes()
 	}
 	}
 }
 }
 
 
-void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
+void BattleFieldController::showHighlightedHex(Canvas & canvas, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder)
 {
 {
 	Point hexPos = hexPositionLocal(hex).topLeft();
 	Point hexPos = hexPositionLocal(hex).topLeft();
 
 
-	canvas.draw(cellShade, hexPos);
+	canvas.draw(highlight, hexPos);
 	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
 	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
 		canvas.draw(cellBorder, hexPos);
 		canvas.draw(cellBorder, hexPos);
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
+std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
 {
 {
 	std::set<BattleHex> result;
 	std::set<BattleHex> result;
 
 
-	if ( !owner.stacksController->getActiveStack())
+	if(!owner.stacksController->getActiveStack())
 		return result;
 		return result;
 
 
-	if ( !settings["battle"]["stackRange"].Bool())
+	if(!settings["battle"]["stackRange"].Bool())
 		return result;
 		return result;
 
 
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
@@ -230,18 +233,33 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
 	for(BattleHex hex : set)
 	for(BattleHex hex : set)
 		result.insert(hex);
 		result.insert(hex);
 
 
-	// display the movement shadow of stack under mouse
+	return result;
+}
+
+std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
+{
+	std::set<BattleHex> result;
+
+	if (!owner.stacksController->getActiveStack())
+		return result;
+
+	if (!settings["battle"]["movementHighlightOnHover"].Bool())
+		return result;
+
+	auto hoveredHex = getHoveredHex();
+
+	// add possible movement hexes for stack under mouse
 	const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
 	const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
-	if(hoveredStack && hoveredStack != owner.stacksController->getActiveStack())
+	if(hoveredStack)
 	{
 	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, nullptr);
+		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, false, true, nullptr);
 		for(BattleHex hex : v)
 		for(BattleHex hex : v)
 			result.insert(hex);
 			result.insert(hex);
 	}
 	}
 	return result;
 	return result;
 }
 }
 
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
+std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
 {
 {
 	std::set<BattleHex> result;
 	std::set<BattleHex> result;
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
@@ -260,9 +278,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
 	{
 	{
 		// printing shaded hex(es)
 		// printing shaded hex(es)
 		spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
 		spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
-		auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
+		auto shadedHexes = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
 
 
-		for(BattleHex shadedHex : shaded)
+		for(BattleHex shadedHex : shadedHexes)
 		{
 		{
 			if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
 			if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
 				result.insert(shadedHex);
 				result.insert(shadedHex);
@@ -276,72 +294,73 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
 	const CStack * stack = owner.stacksController->getActiveStack();
 	const CStack * stack = owner.stacksController->getActiveStack();
 	auto hoveredHex = getHoveredHex();
 	auto hoveredHex = getHoveredHex();
 
 
-	if (stack)
-	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(stack, false, nullptr);
+	if(!stack)
+		return {};
 
 
-		auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
-		if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
-		{
-			if (isTileAttackable(hoveredHex))
-			{
-				BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
-
-				if (stack->doubleWide())
-					return {attackFromHex, stack->occupiedHex(attackFromHex)};
-				else
-					return {attackFromHex};
-			}
-		}
+	std::vector<BattleHex> availableHexes = owner.curInt->cb->battleGetAvailableHexes(stack, true, false, nullptr);
 
 
-		if (vstd::contains(v,hoveredHex))
+	auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
+	{
+		if(isTileAttackable(hoveredHex))
 		{
 		{
-			if (stack->doubleWide())
-				return {hoveredHex, stack->occupiedHex(hoveredHex)};
+			BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
+
+			if(stack->doubleWide())
+				return {attackFromHex, stack->occupiedHex(attackFromHex)};
 			else
 			else
-				return {hoveredHex};
+				return {attackFromHex};
 		}
 		}
-		if (stack->doubleWide())
+	}
+
+	if(vstd::contains(availableHexes, hoveredHex))
+	{
+		if(stack->doubleWide())
+			return {hoveredHex, stack->occupiedHex(hoveredHex)};
+		else
+			return {hoveredHex};
+	}
+
+	if(stack->doubleWide())
+	{
+		for(auto const & hex : availableHexes)
 		{
 		{
-			for (auto const & hex : v)
-			{
-				if (stack->occupiedHex(hex) == hoveredHex)
-					return { hoveredHex, hex };
-			}
+			if(stack->occupiedHex(hex) == hoveredHex)
+				return {hoveredHex, hex};
 		}
 		}
 	}
 	}
+
 	return {};
 	return {};
 }
 }
 
 
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 {
 {
-	std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
-	std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
-	std::set<BattleHex> hoveredMove  = getHighlightedHexesMovementTarget();
+	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
+	std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
+	std::set<BattleHex> hoveredMoveHexes  = getHighlightedHexesMovementTarget();
 
 
-	if (getHoveredHex() == BattleHex::INVALID)
+	if(getHoveredHex() == BattleHex::INVALID)
 		return;
 		return;
 
 
-	auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove;
+	auto const & hoveredMouseHexes = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
 
 
-	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
+	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex)
 	{
 	{
-		bool stack = hoveredStack.count(b);
-		bool mouse = hoveredMouse.count(b);
+		bool stackMovement = hoveredStackMovementRangeHexes.count(hex);
+		bool mouse = hoveredMouseHexes.count(hex);
 
 
-		if ( stack && mouse )
+		if(stackMovement && mouse) // area where hovered stackMovement can move shown with highlight. Because also affected by mouse cursor, shade as well
 		{
 		{
-			// area where enemy stack can move AND affected by mouse cursor - create darker highlight by blitting twice
-			showHighlightedHex(canvas, b, true);
-			showHighlightedHex(canvas, b, true);
+			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
+			showHighlightedHex(canvas, cellShade, hex, true);
 		}
 		}
-		if ( !stack && mouse )
+		if(!stackMovement && mouse) // hexes affected only at mouse cursor shown as shaded
 		{
 		{
-			showHighlightedHex(canvas, b, true);
+			showHighlightedHex(canvas, cellShade, hex, true);
 		}
 		}
-		if ( stack && !mouse )
+		if(stackMovement && !mouse) // hexes where hovered stackMovement can move shown with highlight
 		{
 		{
-			showHighlightedHex(canvas, b, false);
+			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
 		}
 		}
 	}
 	}
 }
 }
@@ -438,18 +457,18 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber,
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 
 
 		for (size_t i : { 1, 2, 3})
 		for (size_t i : { 1, 2, 3})
-			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
+			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
 
 
 		for (size_t i : { 4, 5, 0})
 		for (size_t i : { 4, 5, 0})
-			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
+			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
 
 
-		attackAvailability[6] = vstd::contains(occupyableHexes, neighbours[0]) && vstd::contains(occupyableHexes, neighbours[1]);
-		attackAvailability[7] = vstd::contains(occupyableHexes, neighbours[3]) && vstd::contains(occupyableHexes, neighbours[4]);
+		attackAvailability[6] = vstd::contains(occupiableHexes, neighbours[0]) && vstd::contains(occupiableHexes, neighbours[1]);
+		attackAvailability[7] = vstd::contains(occupiableHexes, neighbours[3]) && vstd::contains(occupiableHexes, neighbours[4]);
 	}
 	}
 	else
 	else
 	{
 	{
 		for (size_t i = 0; i < 6; ++i)
 		for (size_t i = 0; i < 6; ++i)
-			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]);
+			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]);
 
 
 		attackAvailability[6] = false;
 		attackAvailability[6] = false;
 		attackAvailability[7] = false;
 		attackAvailability[7] = false;
@@ -561,7 +580,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 
 
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 {
 {
-	for (auto & elem : occupyableHexes)
+	for (auto & elem : occupiableHexes)
 	{
 	{
 		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
 		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
 			return true;
 			return true;

+ 7 - 4
client/battle/BattleFieldController.h

@@ -32,6 +32,8 @@ class BattleFieldController : public CIntObject
 
 
 	std::shared_ptr<IImage> background;
 	std::shared_ptr<IImage> background;
 	std::shared_ptr<IImage> cellBorder;
 	std::shared_ptr<IImage> cellBorder;
+	std::shared_ptr<IImage> cellUnitMovementHighlight;
+	std::shared_ptr<IImage> cellUnitMaxMovementHighlight;
 	std::shared_ptr<IImage> cellShade;
 	std::shared_ptr<IImage> cellShade;
 
 
 	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
 	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
@@ -41,15 +43,16 @@ class BattleFieldController : public CIntObject
 	BattleHex attackingHex;
 	BattleHex attackingHex;
 
 
 	/// hexes to which currently active stack can move
 	/// hexes to which currently active stack can move
-	std::vector<BattleHex> occupyableHexes;
+	std::vector<BattleHex> occupiableHexes;
 
 
 	/// hexes that when in front of a unit cause it's amount box to move back
 	/// hexes that when in front of a unit cause it's amount box to move back
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
 
 
-	void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
+	void showHighlightedHex(Canvas & to, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder);
 
 
-	std::set<BattleHex> getHighlightedHexesStackRange();
-	std::set<BattleHex> getHighlightedHexesSpellRange();
+	std::set<BattleHex> getHighlightedHexesForActiveStack();
+	std::set<BattleHex> getMovementRangeForHoveredStack();
+	std::set<BattleHex> getHighlightedHexesForSpellRange();
 	std::set<BattleHex> getHighlightedHexesMovementTarget();
 	std::set<BattleHex> getHighlightedHexesMovementTarget();
 
 
 	void showBackground(Canvas & canvas);
 	void showBackground(Canvas & canvas);

+ 9 - 2
client/battle/BattleInterface.cpp

@@ -308,7 +308,7 @@ void BattleInterface::gateStateChanged(const EGateState state)
 		siegeController->gateStateChanged(state);
 		siegeController->gateStateChanged(state);
 }
 }
 
 
-void BattleInterface::battleFinished(const BattleResult& br)
+void BattleInterface::battleFinished(const BattleResult& br, QueryID queryID)
 {
 {
 	checkForAnimations();
 	checkForAnimations();
 	stacksController->setActiveStack(nullptr);
 	stacksController->setActiveStack(nullptr);
@@ -318,11 +318,18 @@ void BattleInterface::battleFinished(const BattleResult& br)
 
 
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
 	{
 	{
+		curInt->cb->selectionMade(0, queryID);
 		windowObject->close();
 		windowObject->close();
 		return;
 		return;
 	}
 	}
 
 
-	GH.pushInt(std::make_shared<BattleResultWindow>(br, *(this->curInt)));
+	auto wnd = std::make_shared<BattleResultWindow>(br, *(this->curInt));
+	wnd->resultCallback = [=](ui32 selection)
+	{
+		curInt->cb->selectionMade(selection, queryID);
+	};
+	GH.pushInt(wnd);
+	
 	curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
 	curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
 	CPlayerInterface::battleInt = nullptr;
 	CPlayerInterface::battleInt = nullptr;
 }
 }

+ 1 - 1
client/battle/BattleInterface.h

@@ -200,7 +200,7 @@ public:
 	void newRoundFirst( int round );
 	void newRoundFirst( int round );
 	void newRound(int number); //caled when round is ended; number is the number of round
 	void newRound(int number); //caled when round is ended; number is the number of round
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
-	void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
+	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook

+ 22 - 4
client/battle/BattleInterfaceClasses.cpp

@@ -397,7 +397,7 @@ HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
 	labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
 	labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
 }
 }
 
 
-BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
+BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay)
 	: owner(_owner)
 	: owner(_owner)
 {
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@@ -408,6 +408,13 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 
 	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, SDLK_RETURN);
 	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, SDLK_RETURN);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
+	
+	if(allowReplay)
+	{
+		repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, SDLK_ESCAPE);
+		repeat->setBorderColor(Colors::METALLIC_GOLD);
+		labels.push_back(std::make_shared<CLabel>(232, 520, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.battleResultsWindow.applyResultsLabel")));
+	}
 
 
 	if(br.winner == 0) //attacker won
 	if(br.winner == 0) //attacker won
 	{
 	{
@@ -453,7 +460,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 
 			auto best = vstd::maxElementByFun(stacks, [](const CStack * stack)
 			auto best = vstd::maxElementByFun(stacks, [](const CStack * stack)
 			{
 			{
-				return stack->type->AIValue;
+				return stack->type->getAIValue();
 			});
 			});
 
 
 			if(best != stacks.end()) //should be always but to be safe...
 			if(best != stacks.end()) //should be always but to be safe...
@@ -569,8 +576,9 @@ void BattleResultWindow::show(SDL_Surface * to)
 	CCS->videoh->update(pos.x + 107, pos.y + 70, to, true, false);
 	CCS->videoh->update(pos.x + 107, pos.y + 70, to, true, false);
 }
 }
 
 
-void BattleResultWindow::bExitf()
+void BattleResultWindow::buttonPressed(int button)
 {
 {
+	resultCallback(button);
 	CPlayerInterface &intTmp = owner; //copy reference because "this" will be destructed soon
 	CPlayerInterface &intTmp = owner; //copy reference because "this" will be destructed soon
 
 
 	close();
 	close();
@@ -584,6 +592,16 @@ void BattleResultWindow::bExitf()
 	CCS->videoh->close();
 	CCS->videoh->close();
 }
 }
 
 
+void BattleResultWindow::bExitf()
+{
+	buttonPressed(0);
+}
+
+void BattleResultWindow::bRepeatf()
+{
+	buttonPressed(1);
+}
+
 StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	: embedded(Embedded),
 	: embedded(Embedded),
 	owner(owner)
 	owner(owner)
@@ -715,7 +733,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 		// if mod is not up to date and does have arrow tower icon yet - second setFrame call will fail and retain previously set image
 		// if mod is not up to date and does have arrow tower icon yet - second setFrame call will fail and retain previously set image
 		// for 1.2 release & later next line should be moved into 'else' block
 		// for 1.2 release & later next line should be moved into 'else' block
 		icon->setFrame(unit->creatureIconIndex(), 0);
 		icon->setFrame(unit->creatureIconIndex(), 0);
-		if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS)
+		if (unit->unitType()->getId() == CreatureID::ARROW_TOWERS)
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 
 
 		amount->setText(TextOperations::formatMetric(unit->getCount(), 4));
 		amount->setText(TextOperations::formatMetric(unit->getCount(), 4));

+ 6 - 1
client/battle/BattleInterfaceClasses.h

@@ -143,13 +143,18 @@ private:
 	std::shared_ptr<CPicture> background;
 	std::shared_ptr<CPicture> background;
 	std::vector<std::shared_ptr<CLabel>> labels;
 	std::vector<std::shared_ptr<CLabel>> labels;
 	std::shared_ptr<CButton> exit;
 	std::shared_ptr<CButton> exit;
+	std::shared_ptr<CButton> repeat;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 	std::shared_ptr<CTextBox> description;
 	std::shared_ptr<CTextBox> description;
 	CPlayerInterface & owner;
 	CPlayerInterface & owner;
+	
+	void buttonPressed(int button); //internal function for button callbacks
 public:
 public:
-	BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
+	BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay = false);
 
 
 	void bExitf(); //exit button callback
 	void bExitf(); //exit button callback
+	void bRepeatf(); //repeat button callback
+	std::function<void(int result)> resultCallback; //callback receiving which button was pressed
 
 
 	void activate() override;
 	void activate() override;
 	void show(SDL_Surface * to = 0) override;
 	void show(SDL_Surface * to = 0) override;

+ 1 - 1
client/battle/BattleProjectileController.cpp

@@ -148,7 +148,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
 {
 {
 	const CCreature * creature = stack->getCreature();
 	const CCreature * creature = stack->getCreature();
 
 
-	if(creature->idNumber == CreatureID::ARROW_TOWERS)
+	if(creature->getId() == CreatureID::ARROW_TOWERS)
 		creature = owner.siegeController->getTurretCreature();
 		creature = owner.siegeController->getTurretCreature();
 
 
 	if(creature->animation.missleFrameAngles.empty())
 	if(creature->animation.missleFrameAngles.empty())

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -200,7 +200,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
 		stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
 		stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
 
 
 		// FIXME: workaround for visible animation of Medusa tails (animation disabled in H3)
 		// FIXME: workaround for visible animation of Medusa tails (animation disabled in H3)
-		if (turretCreature->idNumber == CreatureID::MEDUSA )
+		if (turretCreature->getId() == CreatureID::MEDUSA )
 			stackAnimation[stack->ID]->pos.w = 250;
 			stackAnimation[stack->ID]->pos.w = 250;
 
 
 		coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
 		coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);

+ 30 - 3
client/battle/BattleWindow.cpp

@@ -89,6 +89,7 @@ void BattleWindow::createQueue()
 
 
 	//create stack queue and adjust our own position
 	//create stack queue and adjust our own position
 	bool embedQueue;
 	bool embedQueue;
+	bool showQueue = settings["battle"]["showQueue"].Bool();
 	std::string queueSize = settings["battle"]["queueSize"].String();
 	std::string queueSize = settings["battle"]["queueSize"].String();
 
 
 	if(queueSize == "auto")
 	if(queueSize == "auto")
@@ -97,13 +98,16 @@ void BattleWindow::createQueue()
 		embedQueue = GH.screenDimensions().y < 700 || queueSize == "small";
 		embedQueue = GH.screenDimensions().y < 700 || queueSize == "small";
 
 
 	queue = std::make_shared<StackQueue>(embedQueue, owner);
 	queue = std::make_shared<StackQueue>(embedQueue, owner);
-	if(!embedQueue && settings["battle"]["showQueue"].Bool())
+	if(!embedQueue && showQueue)
 	{
 	{
 		//re-center, taking into account stack queue position
 		//re-center, taking into account stack queue position
 		pos.y -= queue->pos.h;
 		pos.y -= queue->pos.h;
 		pos.h += queue->pos.h;
 		pos.h += queue->pos.h;
 		pos = center();
 		pos = center();
 	}
 	}
+
+	if (!showQueue)
+		queue->disable();
 }
 }
 
 
 BattleWindow::~BattleWindow()
 BattleWindow::~BattleWindow()
@@ -143,8 +147,8 @@ void BattleWindow::hideQueue()
 		pos.y += queue->pos.h;
 		pos.y += queue->pos.h;
 		pos.h -= queue->pos.h;
 		pos.h -= queue->pos.h;
 		pos = center();
 		pos = center();
-		GH.totalRedraw();
 	}
 	}
+	GH.totalRedraw();
 }
 }
 
 
 void BattleWindow::showQueue()
 void BattleWindow::showQueue()
@@ -198,6 +202,23 @@ void BattleWindow::keyPressed(const SDL_Keycode & key)
 	{
 	{
 		owner.actionsController->endCastingSpell();
 		owner.actionsController->endCastingSpell();
 	}
 	}
+	else if(GH.isKeyboardShiftDown())
+	{
+		// save and activate setting
+		Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
+		movementHighlightOnHoverCache = movementHighlightOnHover->Bool();
+		movementHighlightOnHover->Bool() = true;
+	}
+}
+
+void BattleWindow::keyReleased(const SDL_Keycode & key)
+{
+	if(!GH.isKeyboardShiftDown())
+	{
+		// set back to initial state
+		Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
+		movementHighlightOnHover->Bool() = movementHighlightOnHoverCache;
+	}
 }
 }
 
 
 void BattleWindow::clickRight(tribool down, bool previousState)
 void BattleWindow::clickRight(tribool down, bool previousState)
@@ -213,9 +234,12 @@ void BattleWindow::tacticPhaseStarted()
 	auto menuTactics = widget<CIntObject>("menuTactics");
 	auto menuTactics = widget<CIntObject>("menuTactics");
 	auto tacticNext = widget<CIntObject>("tacticNext");
 	auto tacticNext = widget<CIntObject>("tacticNext");
 	auto tacticEnd = widget<CIntObject>("tacticEnd");
 	auto tacticEnd = widget<CIntObject>("tacticEnd");
+	auto alternativeAction = widget<CIntObject>("alternativeAction");
 
 
 	menuBattle->disable();
 	menuBattle->disable();
 	console->disable();
 	console->disable();
+	if (alternativeAction)
+		alternativeAction->disable();
 
 
 	menuTactics->enable();
 	menuTactics->enable();
 	tacticNext->enable();
 	tacticNext->enable();
@@ -231,9 +255,12 @@ void BattleWindow::tacticPhaseEnded()
 	auto menuTactics = widget<CIntObject>("menuTactics");
 	auto menuTactics = widget<CIntObject>("menuTactics");
 	auto tacticNext = widget<CIntObject>("tacticNext");
 	auto tacticNext = widget<CIntObject>("tacticNext");
 	auto tacticEnd = widget<CIntObject>("tacticEnd");
 	auto tacticEnd = widget<CIntObject>("tacticEnd");
+	auto alternativeAction = widget<CIntObject>("alternativeAction");
 
 
 	menuBattle->enable();
 	menuBattle->enable();
 	console->enable();
 	console->enable();
+	if (alternativeAction)
+		alternativeAction->enable();
 
 
 	menuTactics->disable();
 	menuTactics->disable();
 	tacticNext->disable();
 	tacticNext->disable();
@@ -309,7 +336,7 @@ void BattleWindow::reallyFlee()
 
 
 void BattleWindow::reallySurrender()
 void BattleWindow::reallySurrender()
 {
 {
-	if (owner.curInt->cb->getResourceAmount(Res::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
+	if (owner.curInt->cb->getResourceAmount(EGameResID::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
 	{
 	{
 		owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
 		owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
 	}
 	}

+ 4 - 0
client/battle/BattleWindow.h

@@ -85,6 +85,7 @@ public:
 	void activate() override;
 	void activate() override;
 	void deactivate() override;
 	void deactivate() override;
 	void keyPressed(const SDL_Keycode & key) override;
 	void keyPressed(const SDL_Keycode & key) override;
+	void keyReleased(const SDL_Keycode& key) override;
 	void clickRight(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
 	void show(SDL_Surface *to) override;
 	void show(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;
@@ -98,5 +99,8 @@ public:
 	/// Set possible alternative options. If more than 1 - the last will be considered as default option
 	/// Set possible alternative options. If more than 1 - the last will be considered as default option
 	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
 	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
 
 
+private:
+	/// used to save the state of this setting on toggle.
+	bool movementHighlightOnHoverCache;
 };
 };
 
 

+ 5 - 1
client/gui/CGuiHandler.cpp

@@ -418,7 +418,7 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 		for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
 		for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
 			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(key.keysym.sym)))
 			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(key.keysym.sym)))
 			{
 			{
-				if (key.state == SDL_PRESSED)
+				if (key.state == SDL_PRESSED && key.repeat == 0) // function like key_DOWN, and not like a periodic key_Pressed check 
 					(**i).keyPressed(key.keysym.sym);
 					(**i).keyPressed(key.keysym.sym);
 				if (key.state == SDL_RELEASED)
 				if (key.state == SDL_RELEASED)
 					(**i).keyReleased(key.keysym.sym);
 					(**i).keyReleased(key.keysym.sym);
@@ -712,7 +712,11 @@ void CGuiHandler::moveCursorToPosition(const Point & position)
 
 
 bool CGuiHandler::isKeyboardCtrlDown() const
 bool CGuiHandler::isKeyboardCtrlDown() const
 {
 {
+#ifdef VCMI_MAC
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
+#else
 	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
 	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
+#endif
 }
 }
 
 
 bool CGuiHandler::isKeyboardAltDown() const
 bool CGuiHandler::isKeyboardAltDown() const

+ 11 - 3
client/gui/InterfaceObjectConfigurable.cpp

@@ -31,6 +31,8 @@ static std::map<std::string, int> KeycodeMap{
 	{"left", SDLK_LEFT},
 	{"left", SDLK_LEFT},
 	{"right", SDLK_RIGHT},
 	{"right", SDLK_RIGHT},
 	{"space", SDLK_SPACE},
 	{"space", SDLK_SPACE},
+	{"escape", SDLK_ESCAPE},
+	{"backspace", SDLK_BACKSPACE},
 	{"enter", SDLK_RETURN}
 	{"enter", SDLK_RETURN}
 };
 };
 
 
@@ -220,10 +222,16 @@ int InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const
 		auto s = config.String();
 		auto s = config.String();
 		if(s.size() == 1) //keyboard symbol
 		if(s.size() == 1) //keyboard symbol
 			return s[0];
 			return s[0];
-		return KeycodeMap[s];
+
+		if (KeycodeMap.count(s))
+			return KeycodeMap[s];
+
+		logGlobal->error("Invalid keycode '%s' in interface configuration!", config.String());
+		return SDLK_UNKNOWN;
 	}
 	}
-	
-	return 0;
+
+	logGlobal->error("Invalid keycode format in interface configuration! Expected string or integer!", config.String());
+	return SDLK_UNKNOWN;
 }
 }
 
 
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const

+ 22 - 22
client/lobby/OptionsTab.cpp

@@ -126,25 +126,25 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 			return GOLD;
 			return GOLD;
 		case PlayerSettings::RESOURCE:
 		case PlayerSettings::RESOURCE:
 		{
 		{
-			switch((*CGI->townh)[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes.toEnum())
 			{
 			{
-			case Res::WOOD_AND_ORE:
+			case EGameResID::WOOD_AND_ORE:
 				return WOOD_ORE;
 				return WOOD_ORE;
-			case Res::WOOD:
+			case EGameResID::WOOD:
 				return WOOD;
 				return WOOD;
-			case Res::MERCURY:
+			case EGameResID::MERCURY:
 				return MERCURY;
 				return MERCURY;
-			case Res::ORE:
+			case EGameResID::ORE:
 				return ORE;
 				return ORE;
-			case Res::SULFUR:
+			case EGameResID::SULFUR:
 				return SULFUR;
 				return SULFUR;
-			case Res::CRYSTAL:
+			case EGameResID::CRYSTAL:
 				return CRYSTAL;
 				return CRYSTAL;
-			case Res::GEMS:
+			case EGameResID::GEMS:
 				return GEM;
 				return GEM;
-			case Res::GOLD:
+			case EGameResID::GOLD:
 				return GOLD;
 				return GOLD;
-			case Res::MITHRIL:
+			case EGameResID::MITHRIL:
 				return MITHRIL;
 				return MITHRIL;
 			}
 			}
 		}
 		}
@@ -268,17 +268,17 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 			return CGI->generaltexth->allTexts[87]; //500-1000
 			return CGI->generaltexth->allTexts[87]; //500-1000
 		case PlayerSettings::RESOURCE:
 		case PlayerSettings::RESOURCE:
 		{
 		{
-			switch((*CGI->townh)[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes.toEnum())
 			{
 			{
-			case Res::MERCURY:
+			case EGameResID::MERCURY:
 				return CGI->generaltexth->allTexts[694];
 				return CGI->generaltexth->allTexts[694];
-			case Res::SULFUR:
+			case EGameResID::SULFUR:
 				return CGI->generaltexth->allTexts[695];
 				return CGI->generaltexth->allTexts[695];
-			case Res::CRYSTAL:
+			case EGameResID::CRYSTAL:
 				return CGI->generaltexth->allTexts[692];
 				return CGI->generaltexth->allTexts[692];
-			case Res::GEMS:
+			case EGameResID::GEMS:
 				return CGI->generaltexth->allTexts[693];
 				return CGI->generaltexth->allTexts[693];
-			case Res::WOOD_AND_ORE:
+			case EGameResID::WOOD_AND_ORE:
 				return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
 				return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
 			}
 			}
 		}
 		}
@@ -310,17 +310,17 @@ std::string OptionsTab::CPlayerSettingsHelper::getDescription()
 			return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
 			return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
 		case PlayerSettings::RESOURCE:
 		case PlayerSettings::RESOURCE:
 		{
 		{
-			switch((*CGI->townh)[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes.toEnum())
 			{
 			{
-			case Res::MERCURY:
+			case EGameResID::MERCURY:
 				return CGI->generaltexth->allTexts[690];
 				return CGI->generaltexth->allTexts[690];
-			case Res::SULFUR:
+			case EGameResID::SULFUR:
 				return CGI->generaltexth->allTexts[691];
 				return CGI->generaltexth->allTexts[691];
-			case Res::CRYSTAL:
+			case EGameResID::CRYSTAL:
 				return CGI->generaltexth->allTexts[688];
 				return CGI->generaltexth->allTexts[688];
-			case Res::GEMS:
+			case EGameResID::GEMS:
 				return CGI->generaltexth->allTexts[689];
 				return CGI->generaltexth->allTexts[689];
-			case Res::WOOD_AND_ORE:
+			case EGameResID::WOOD_AND_ORE:
 				return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
 				return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
 			}
 			}
 		}
 		}

+ 12 - 2
client/lobby/RandomMapTab.cpp

@@ -394,6 +394,10 @@ void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 	{
 	{
 		dropBox.setTemplate(item);
 		dropBox.setTemplate(item);
 	}
 	}
+	else 
+	{
+		dropBox.clickLeft(true, true);
+	}
 }
 }
 
 
 
 
@@ -449,8 +453,14 @@ void TemplatesDropBox::clickLeft(tribool down, bool previousState)
 {
 {
 	if(down && !hovered)
 	if(down && !hovered)
 	{
 	{
-		assert(GH.topInt().get() == this);
-		GH.popInt(GH.topInt());
+		auto w = widget<CSlider>("slider");
+
+		// pop the interface only if the mouse is not clicking on the slider
+		if (!w || !w->mouseState(MouseButton::LEFT))
+		{
+			assert(GH.topInt().get() == this);
+			GH.popInt(GH.topInt());
+		}
 	}
 	}
 }
 }
 
 

+ 0 - 1
client/render/IFont.cpp

@@ -13,7 +13,6 @@
 
 
 #include "../../lib/Point.h"
 #include "../../lib/Point.h"
 #include "../../lib/TextOperations.h"
 #include "../../lib/TextOperations.h"
-//
 
 
 size_t IFont::getStringWidth(const std::string & data) const
 size_t IFont::getStringWidth(const std::string & data) const
 {
 {

+ 19 - 1
client/renderSDL/CBitmapFont.cpp

@@ -70,7 +70,7 @@ CBitmapFont::CBitmapFont(const std::string & filename):
 
 
 	loadModFont("core", resource);
 	loadModFont("core", resource);
 
 
-	for (auto const & modName : VLC->modh->getActiveMods())
+	for(const auto & modName : VLC->modh->getActiveMods())
 	{
 	{
 		if (CResourceHandler::get(modName)->existsResource(resource))
 		if (CResourceHandler::get(modName)->existsResource(resource))
 			loadModFont(modName, resource);
 			loadModFont(modName, resource);
@@ -94,6 +94,24 @@ size_t CBitmapFont::getGlyphWidth(const char * data) const
 	return iter->second.leftOffset + iter->second.width + iter->second.rightOffset;
 	return iter->second.leftOffset + iter->second.width + iter->second.rightOffset;
 }
 }
 
 
+bool CBitmapFont::canRepresentCharacter(const char *data) const
+{
+	CodePoint localChar = TextOperations::getUnicodeCodepoint(data, 4);
+
+	auto iter = chars.find(localChar);
+
+	return iter != chars.end();
+}
+
+bool CBitmapFont::canRepresentString(const std::string & data) const
+{
+	for(size_t i=0; i<data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
+		if (!canRepresentCharacter(data.data() + i))
+			return false;
+
+	return true;
+}
+
 void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const
 void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const
 {
 {
 	Rect clipRect;
 	Rect clipRect;

+ 5 - 0
client/renderSDL/CBitmapFont.h

@@ -41,6 +41,11 @@ public:
 	size_t getLineHeight() const override;
 	size_t getLineHeight() const override;
 	size_t getGlyphWidth(const char * data) const override;
 	size_t getGlyphWidth(const char * data) const override;
 
 
+	/// returns true if this font contains provided utf-8 character
+	bool canRepresentCharacter(const char * data) const;
+	bool canRepresentString(const std::string & data) const;
+
 	friend class CBitmapHanFont;
 	friend class CBitmapHanFont;
+	friend class CTrueTypeFont;
 };
 };
 
 

+ 26 - 3
client/renderSDL/CTrueTypeFont.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "CTrueTypeFont.h"
 #include "CTrueTypeFont.h"
 
 
+#include "CBitmapFont.h"
+
 #include "../render/Colors.h"
 #include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../renderSDL/SDL_Extensions.h"
 
 
@@ -52,30 +54,45 @@ int CTrueTypeFont::getFontStyle(const JsonNode &config)
 CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
 CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
 	data(loadData(fontConfig)),
 	data(loadData(fontConfig)),
 	font(loadFont(fontConfig), TTF_CloseFont),
 	font(loadFont(fontConfig), TTF_CloseFont),
+	dropShadow(fontConfig["blend"].Bool()),
 	blended(fontConfig["blend"].Bool())
 	blended(fontConfig["blend"].Bool())
 {
 {
 	assert(font);
 	assert(font);
 
 
 	TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
 	TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
+
+	std::string fallbackName = fontConfig["fallback"].String();
+
+	if (!fallbackName.empty())
+		fallbackFont = std::make_unique<CBitmapFont>(fallbackName);
 }
 }
 
 
+CTrueTypeFont::~CTrueTypeFont() = default;
+
 size_t CTrueTypeFont::getLineHeight() const
 size_t CTrueTypeFont::getLineHeight() const
 {
 {
+	if (fallbackFont)
+		fallbackFont->getLineHeight();
+
 	return TTF_FontHeight(font.get());
 	return TTF_FontHeight(font.get());
 }
 }
 
 
 size_t CTrueTypeFont::getGlyphWidth(const char *data) const
 size_t CTrueTypeFont::getGlyphWidth(const char *data) const
 {
 {
+	if (fallbackFont && fallbackFont->canRepresentCharacter(data))
+		return fallbackFont->getGlyphWidth(data);
+
 	return getStringWidth(std::string(data, TextOperations::getUnicodeCharacterSize(*data)));
 	return getStringWidth(std::string(data, TextOperations::getUnicodeCharacterSize(*data)));
-	/*
 	int advance;
 	int advance;
 	TTF_GlyphMetrics(font.get(), *data, nullptr, nullptr, nullptr, nullptr, &advance);
 	TTF_GlyphMetrics(font.get(), *data, nullptr, nullptr, nullptr, nullptr, &advance);
 	return advance;
 	return advance;
-	*/
 }
 }
 
 
 size_t CTrueTypeFont::getStringWidth(const std::string & data) const
 size_t CTrueTypeFont::getStringWidth(const std::string & data) const
 {
 {
+	if (fallbackFont && fallbackFont->canRepresentString(data))
+		return fallbackFont->getStringWidth(data);
+
 	int width;
 	int width;
 	TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr);
 	TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr);
 	return width;
 	return width;
@@ -83,7 +100,13 @@ size_t CTrueTypeFont::getStringWidth(const std::string & data) const
 
 
 void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
 void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const
 {
 {
-	if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
+	if (fallbackFont && fallbackFont->canRepresentString(data))
+	{
+		fallbackFont->renderText(surface, data, color, pos);
+		return;
+	}
+
+	if (dropShadow && color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
 		renderText(surface, data, Colors::BLACK, pos + Point(1,1));
 		renderText(surface, data, Colors::BLACK, pos + Point(1,1));
 
 
 	if (!data.empty())
 	if (!data.empty())

+ 5 - 0
client/renderSDL/CTrueTypeFont.h

@@ -15,14 +15,18 @@ VCMI_LIB_NAMESPACE_BEGIN
 class JsonNode;
 class JsonNode;
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END
 
 
+class CBitmapFont;
+
 typedef struct _TTF_Font TTF_Font;
 typedef struct _TTF_Font TTF_Font;
 
 
 class CTrueTypeFont : public IFont
 class CTrueTypeFont : public IFont
 {
 {
+	std::unique_ptr<CBitmapFont> fallbackFont;
 	const std::pair<std::unique_ptr<ui8[]>, ui64> data;
 	const std::pair<std::unique_ptr<ui8[]>, ui64> data;
 
 
 	const std::unique_ptr<TTF_Font, void (*)(TTF_Font*)> font;
 	const std::unique_ptr<TTF_Font, void (*)(TTF_Font*)> font;
 	const bool blended;
 	const bool blended;
+	const bool dropShadow;
 
 
 	std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
 	std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
 	TTF_Font * loadFont(const JsonNode & config);
 	TTF_Font * loadFont(const JsonNode & config);
@@ -31,6 +35,7 @@ class CTrueTypeFont : public IFont
 	void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override;
 	void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override;
 public:
 public:
 	CTrueTypeFont(const JsonNode & fontConfig);
 	CTrueTypeFont(const JsonNode & fontConfig);
+	~CTrueTypeFont();
 
 
 	size_t getLineHeight() const override;
 	size_t getLineHeight() const override;
 	size_t getGlyphWidth(const char * data) const override;
 	size_t getGlyphWidth(const char * data) const override;

+ 1 - 1
client/widgets/CArtifactHolder.cpp

@@ -654,7 +654,7 @@ CArtifactsOfHero::~CArtifactsOfHero()
 		}
 		}
 		else
 		else
 		{
 		{
-			//TODO remove artifact
+			LOCPLINT->cb->eraseArtifactByClient(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 1
client/widgets/CreatureCostBox.h

@@ -26,5 +26,5 @@ private:
 	using ImagePtr = std::shared_ptr<CAnimImage>;
 	using ImagePtr = std::shared_ptr<CAnimImage>;
 
 
 	LabelPtr title;
 	LabelPtr title;
-	std::map<int, std::pair<LabelPtr, ImagePtr>> resources;
+	std::map<GameResID, std::pair<LabelPtr, ImagePtr>> resources;
 };
 };

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác