浏览代码

Merge pull request #2465 from vcmi/beta

Merge beta -> master
Ivan Savenko 2 年之前
父节点
当前提交
07ab7c2f50
共有 100 个文件被更改,包括 1988 次插入894 次删除
  1. 6 2
      .github/workflows/github.yml
  2. 1 0
      .gitmodules
  3. 19 4
      AI/BattleAI/AttackPossibility.cpp
  4. 187 138
      AI/BattleAI/BattleAI.cpp
  5. 12 6
      AI/BattleAI/BattleAI.h
  6. 14 7
      AI/BattleAI/BattleExchangeVariant.cpp
  7. 2 8
      AI/BattleAI/BattleExchangeVariant.h
  8. 3 3
      AI/BattleAI/PotentialTargets.cpp
  9. 5 5
      AI/BattleAI/StackWithBonuses.cpp
  10. 2 8
      AI/BattleAI/StackWithBonuses.h
  11. 1 1
      AI/BattleAI/ThreatMap.cpp
  12. 11 0
      AI/EmptyAI/CEmptyAI.cpp
  13. 2 0
      AI/EmptyAI/CEmptyAI.h
  14. 1 1
      AI/FuzzyLite
  15. 71 55
      AI/Nullkiller/AIGateway.cpp
  16. 6 6
      AI/Nullkiller/AIGateway.h
  17. 14 16
      AI/Nullkiller/AIUtility.cpp
  18. 5 7
      AI/Nullkiller/AIUtility.h
  19. 87 34
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  20. 34 13
      AI/Nullkiller/Analyzers/ArmyManager.h
  21. 48 23
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  22. 5 2
      AI/Nullkiller/Analyzers/BuildAnalyzer.h
  23. 163 18
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  24. 14 1
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h
  25. 40 12
      AI/Nullkiller/Analyzers/HeroManager.cpp
  26. 4 0
      AI/Nullkiller/Analyzers/HeroManager.h
  27. 7 3
      AI/Nullkiller/Analyzers/ObjectClusterizer.cpp
  28. 5 11
      AI/Nullkiller/Analyzers/ObjectClusterizer.h
  29. 1 3
      AI/Nullkiller/Behaviors/BuildingBehavior.cpp
  30. 0 2
      AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp
  31. 10 6
      AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp
  32. 236 117
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  33. 5 0
      AI/Nullkiller/Behaviors/DefenceBehavior.h
  34. 105 22
      AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp
  35. 22 3
      AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp
  36. 2 4
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  37. 2 0
      AI/Nullkiller/CMakeLists.txt
  38. 2 2
      AI/Nullkiller/Engine/AIMemory.cpp
  39. 1 1
      AI/Nullkiller/Engine/DeepDecomposer.h
  40. 10 10
      AI/Nullkiller/Engine/FuzzyEngines.cpp
  41. 9 8
      AI/Nullkiller/Engine/FuzzyHelper.cpp
  42. 1 1
      AI/Nullkiller/Engine/FuzzyHelper.h
  43. 42 13
      AI/Nullkiller/Engine/Nullkiller.cpp
  44. 7 3
      AI/Nullkiller/Engine/Nullkiller.h
  45. 243 109
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  46. 6 0
      AI/Nullkiller/Engine/PriorityEvaluator.h
  47. 1 3
      AI/Nullkiller/Goals/AbstractGoal.cpp
  48. 4 5
      AI/Nullkiller/Goals/AbstractGoal.h
  49. 0 2
      AI/Nullkiller/Goals/AdventureSpellCast.cpp
  50. 7 7
      AI/Nullkiller/Goals/Build.cpp
  51. 4 7
      AI/Nullkiller/Goals/BuildBoat.cpp
  52. 0 2
      AI/Nullkiller/Goals/BuildThis.cpp
  53. 3 3
      AI/Nullkiller/Goals/BuyArmy.cpp
  54. 1 3
      AI/Nullkiller/Goals/CompleteQuest.cpp
  55. 1 1
      AI/Nullkiller/Goals/CompleteQuest.h
  56. 49 11
      AI/Nullkiller/Goals/Composition.cpp
  57. 2 6
      AI/Nullkiller/Goals/Composition.h
  58. 0 2
      AI/Nullkiller/Goals/DismissHero.cpp
  59. 0 2
      AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp
  60. 21 9
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  61. 2 2
      AI/Nullkiller/Goals/GatherArmy.cpp
  62. 12 11
      AI/Nullkiller/Goals/RecruitHero.cpp
  63. 7 5
      AI/Nullkiller/Goals/RecruitHero.h
  64. 0 2
      AI/Nullkiller/Goals/SaveResources.cpp
  65. 2 2
      AI/Nullkiller/Goals/Trade.h
  66. 68 0
      AI/Nullkiller/Helpers/ArmyFormation.cpp
  67. 38 0
      AI/Nullkiller/Helpers/ArmyFormation.h
  68. 8 1
      AI/Nullkiller/Markers/ArmyUpgrade.cpp
  69. 1 0
      AI/Nullkiller/Markers/ArmyUpgrade.h
  70. 2 2
      AI/Nullkiller/Markers/DefendTown.cpp
  71. 4 1
      AI/Nullkiller/Markers/DefendTown.h
  72. 1 1
      AI/Nullkiller/Markers/HeroExchange.cpp
  73. 67 40
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  74. 14 11
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  75. 5 0
      AI/Nullkiller/Pathfinding/AIPathfinder.cpp
  76. 4 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  77. 3 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.h
  78. 0 1
      AI/Nullkiller/Pathfinding/Actions/BattleAction.cpp
  79. 0 1
      AI/Nullkiller/Pathfinding/Actions/BattleAction.h
  80. 5 6
      AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp
  81. 0 1
      AI/Nullkiller/Pathfinding/Actions/BoatActions.h
  82. 0 1
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp
  83. 0 1
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.h
  84. 0 1
      AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp
  85. 1 1
      AI/Nullkiller/Pathfinding/Actions/QuestAction.h
  86. 63 0
      AI/Nullkiller/Pathfinding/Actions/SpecialAction.cpp
  87. 38 1
      AI/Nullkiller/Pathfinding/Actions/SpecialAction.h
  88. 0 1
      AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp
  89. 0 1
      AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h
  90. 11 10
      AI/Nullkiller/Pathfinding/Actors.cpp
  91. 2 5
      AI/Nullkiller/Pathfinding/Actors.h
  92. 4 4
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  93. 1 1
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h
  94. 11 6
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
  95. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  96. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp
  97. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h
  98. 4 2
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp
  99. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h
  100. 40 17
      AI/StupidAI/StupidAI.cpp

+ 6 - 2
.github/workflows/github.yml

@@ -71,11 +71,15 @@ jobs:
           - platform: linux-qt6
           - platform: linux-qt6
             os: ubuntu-22.04
             os: ubuntu-22.04
             test: 0
             test: 0
-            preset: linux-clang-release
+            preset: linux-clang-test
           - platform: linux
           - platform: linux
             os: ubuntu-20.04
             os: ubuntu-20.04
             test: 0
             test: 0
-            preset: linux-gcc-release
+            preset: linux-gcc-test
+          - platform: linux
+            os: ubuntu-20.04
+            test: 0
+            preset: linux-gcc-debug
           - platform: mac-intel
           - platform: mac-intel
             os: macos-12
             os: macos-12
             test: 0
             test: 0

+ 1 - 0
.gitmodules

@@ -1,6 +1,7 @@
 [submodule "test/googletest"]
 [submodule "test/googletest"]
 	path = test/googletest
 	path = test/googletest
 	url = https://github.com/google/googletest
 	url = https://github.com/google/googletest
+	branch = v1.13.x
 [submodule "AI/FuzzyLite"]
 [submodule "AI/FuzzyLite"]
 	path = AI/FuzzyLite
 	path = AI/FuzzyLite
 	url = https://github.com/fuzzylite/fuzzylite.git
 	url = https://github.com/fuzzylite/fuzzylite.git

+ 19 - 4
AI/BattleAI/AttackPossibility.cpp

@@ -50,12 +50,27 @@ int64_t AttackPossibility::calculateDamageReduce(
 	vstd::amin(damageDealt, defender->getAvailableHealth());
 	vstd::amin(damageDealt, defender->getAvailableHealth());
 
 
 	// FIXME: provide distance info for Jousting bonus
 	// FIXME: provide distance info for Jousting bonus
-	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
-	auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
+	auto attackerUnitForMeasurement = attacker;
+
+	if(attackerUnitForMeasurement->isTurret())
+	{
+		auto ourUnits = cb.battleGetUnitsIf([&](const battle::Unit * u) -> bool
+			{
+				return u->unitSide() == attacker->unitSide() && !u->isTurret();
+			});
+
+		if(ourUnits.empty())
+			attackerUnitForMeasurement = defender;
+		else
+			attackerUnitForMeasurement = ourUnits.front();
+	}
+
+	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attackerUnitForMeasurement, 0);
+	auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
 	auto damagePerEnemy = enemyDamage / (double)defender->getCount();
 	auto damagePerEnemy = enemyDamage / (double)defender->getCount();
 
 
-	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
+	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->getMaxHealth()));
 }
 }
 
 
 int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
 int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
@@ -97,7 +112,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
 	auto attacker = attackInfo.attacker;
 	auto attacker = attackInfo.attacker;
 	auto defender = attackInfo.defender;
 	auto defender = attackInfo.defender;
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
-	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
+	static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
 	const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
 	const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 
 

+ 187 - 138
AI/BattleAI/BattleAI.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
+#include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/CStack.h" // TODO: remove
 #include "../../lib/CStack.h" // TODO: remove
                               // Eventually only IBattleInfoCallback and battle::Unit should be used,
                               // Eventually only IBattleInfoCallback and battle::Unit should be used,
                               // CUnitState should be private and CStack should be removed completely
                               // CUnitState should be private and CStack should be removed completely
@@ -87,175 +88,211 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	playerID = *CB->getPlayerID(); //TODO should be sth in callback
 	playerID = *CB->getPlayerID(); //TODO should be sth in callback
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
-	CB->waitTillRealize = true;
+	CB->waitTillRealize = false;
 	CB->unlockGsWhenWaiting = false;
 	CB->unlockGsWhenWaiting = false;
 	movesSkippedByDefense = 0;
 	movesSkippedByDefense = 0;
 }
 }
 
 
-BattleAction CBattleAI::activeStack( const CStack * stack )
+BattleAction CBattleAI::useHealingTent(const CStack *stack)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
+	auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
+	std::map<int, const CStack*> woundHpToStack;
+	for(const auto * stack : healingTargets)
+	{
+		if(auto woundHp = stack->getMaxHealth() - stack->getFirstHPleft())
+			woundHpToStack[woundHp] = stack;
+	}
 
 
-	BattleAction result = BattleAction::makeDefend(stack);
-	setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
+	if(woundHpToStack.empty())
+		return BattleAction::makeDefend(stack);
+	else
+		return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
+}
 
 
-	try
+std::optional<PossibleSpellcast> CBattleAI::findBestCreatureSpell(const CStack *stack)
+{
+	//TODO: faerie dragon type spell should be selected by server
+	SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
+	if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
 	{
 	{
-		if(stack->type->idNumber == CreatureID::CATAPULT)
-			return useCatapult(stack);
-		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
-		{
-			auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
-			std::map<int, const CStack*> woundHpToStack;
-			for(auto stack : healingTargets)
-				if(auto woundHp = stack->MaxHealth() - stack->getFirstHPleft())
-					woundHpToStack[woundHp] = stack;
-			if(woundHpToStack.empty())
-				return BattleAction::makeDefend(stack);
-			else
-				return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
-		}
+		const CSpell * spell = creatureSpellToCast.toSpell();
 
 
-		attemptCastingSpell();
-
-		if(cb->battleIsFinished() || !stack->alive())
+		if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
 		{
 		{
-			//spellcast may finish battle or kill active stack
-			//send special preudo-action
-			BattleAction cancel;
-			cancel.actionType = EActionType::CANCEL;
-			return cancel;
-		}
+			std::vector<PossibleSpellcast> possibleCasts;
+			spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
+			for(auto & target : temp.findPotentialTargets())
+			{
+				PossibleSpellcast ps;
+				ps.dest = target;
+				ps.spell = spell;
+				evaluateCreatureSpellcast(stack, ps);
+				possibleCasts.push_back(ps);
+			}
 
 
-		if(auto action = considerFleeingOrSurrendering())
-			return *action;
-		//best action is from effective owner point if view, we are effective owner as we received "activeStack"
+			std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
+			if(!possibleCasts.empty() && possibleCasts.front().value > 0)
+			{
+				return possibleCasts.front();
+			}
+		}
+	}
+	return std::nullopt;
+}
 
 
+BattleAction CBattleAI::selectStackAction(const CStack * stack)
+{
+	//evaluate casting spell for spellcasting stack
+	std::optional<PossibleSpellcast> bestSpellcast = findBestCreatureSpell(stack);
 
 
-		//evaluate casting spell for spellcasting stack
-		boost::optional<PossibleSpellcast> bestSpellcast(boost::none);
-		//TODO: faerie dragon type spell should be selected by server
-		SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
-		if(stack->hasBonusOfType(Bonus::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
-		{
-			const CSpell * spell = creatureSpellToCast.toSpell();
+	HypotheticBattle hb(env.get(), cb);
 
 
-			if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
-			{
-				std::vector<PossibleSpellcast> possibleCasts;
-				spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
-				for(auto & target : temp.findPotentialTargets())
-				{
-					PossibleSpellcast ps;
-					ps.dest = target;
-					ps.spell = spell;
-					evaluateCreatureSpellcast(stack, ps);
-					possibleCasts.push_back(ps);
-				}
+	PotentialTargets targets(stack, hb);
+	BattleExchangeEvaluator scoreEvaluator(cb, env);
+	auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb);
 
 
-				std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
-				if(!possibleCasts.empty() && possibleCasts.front().value > 0)
-				{
-					bestSpellcast = boost::optional<PossibleSpellcast>(possibleCasts.front());
-				}
-			}
-		}
+	int64_t score = EvaluationResult::INEFFECTIVE_SCORE;
 
 
-		HypotheticBattle hb(env.get(), cb);
-		
-		PotentialTargets targets(stack, hb);
-		BattleExchangeEvaluator scoreEvaluator(cb, env);
-		auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb);
 
 
-		int64_t score = EvaluationResult::INEFFECTIVE_SCORE;
+	if(targets.possibleAttacks.empty() && bestSpellcast.has_value())
+	{
+		movesSkippedByDefense = 0;
+		return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
+	}
 
 
-		if(!targets.possibleAttacks.empty())
-		{
+	if(!targets.possibleAttacks.empty())
+	{
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
-			logAi->trace("Evaluating attack for %s", stack->getDescription());
+		logAi->trace("Evaluating attack for %s", stack->getDescription());
 #endif
 #endif
 
 
-			auto evaluationResult = scoreEvaluator.findBestTarget(stack, targets, hb);
-			auto & bestAttack = evaluationResult.bestAttack;
+		auto evaluationResult = scoreEvaluator.findBestTarget(stack, targets, hb);
+		auto & bestAttack = evaluationResult.bestAttack;
 
 
-			//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
-			if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
-			{
-				// return because spellcast value is damage dealt and score is dps reduce
-				movesSkippedByDefense = 0;
-				return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
-			}
+		//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
+		if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
+		{
+			// return because spellcast value is damage dealt and score is dps reduce
+			movesSkippedByDefense = 0;
+			return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
+		}
 
 
-			if(evaluationResult.score > score)
+		if(evaluationResult.score > score)
+		{
+			score = evaluationResult.score;
+
+			logAi->debug("BattleAI: %s -> %s x %d, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
+				bestAttack.attackerState->unitType()->getJsonKey(),
+				bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
+				(int)bestAttack.affectedUnits[0]->getCount(),
+				(int)bestAttack.from,
+				(int)bestAttack.attack.attacker->getPosition().hex,
+				bestAttack.attack.chargeDistance,
+				bestAttack.attack.attacker->speed(0, true),
+				bestAttack.defenderDamageReduce,
+				bestAttack.attackerDamageReduce, bestAttack.attackValue()
+			);
+
+			if (moveTarget.score <= score)
 			{
 			{
-				score = evaluationResult.score;
-				std::string action;
-
 				if(evaluationResult.wait)
 				if(evaluationResult.wait)
 				{
 				{
-					result = BattleAction::makeWait(stack);
-					action = "wait";
+					return BattleAction::makeWait(stack);
 				}
 				}
 				else if(bestAttack.attack.shooting)
 				else if(bestAttack.attack.shooting)
 				{
 				{
-					result = BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
-					action = "shot";
 					movesSkippedByDefense = 0;
 					movesSkippedByDefense = 0;
+					return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
 				}
 				}
 				else
 				else
 				{
 				{
-					result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
-					action = "melee";
 					movesSkippedByDefense = 0;
 					movesSkippedByDefense = 0;
+					return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
 				}
 				}
-
-				logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
-					bestAttack.attackerState->unitType()->getJsonKey(),
-					bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
-					(int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
-					bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true),
-					bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
-				);
 			}
 			}
 		}
 		}
-		else if(bestSpellcast.is_initialized())
+	}
+
+	//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
+	if(moveTarget.score > score)
+	{
+		score = moveTarget.score;
+
+		if(stack->waited())
+		{
+			return goTowardsNearest(stack, moveTarget.positions);
+		}
+		else
 		{
 		{
-			movesSkippedByDefense = 0;
-			return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
+			return BattleAction::makeWait(stack);
 		}
 		}
+	}
+
+	if(score <= EvaluationResult::INEFFECTIVE_SCORE
+		&& !stack->hasBonusOfType(BonusType::FLYING)
+		&& stack->unitSide() == BattleSide::ATTACKER
+		&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+	{
+		auto brokenWallMoat = getBrokenWallMoatHexes();
 
 
-			//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
-		if(moveTarget.score > score)
+		if(brokenWallMoat.size())
 		{
 		{
-			score = moveTarget.score;
+			movesSkippedByDefense = 0;
 
 
-			if(stack->waited())
-			{
-				result = goTowardsNearest(stack, moveTarget.positions);
-			}
+			if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
+				return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
 			else
 			else
-			{
-				result = BattleAction::makeWait(stack);
-			}
+				return goTowardsNearest(stack, brokenWallMoat);
 		}
 		}
+	}
+
+	return BattleAction::makeDefend(stack);
+}
+
+void CBattleAI::yourTacticPhase(int distance)
+{
+	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+}
+
+void CBattleAI::activeStack( const CStack * stack )
+{
+	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
+
+	BattleAction result = BattleAction::makeDefend(stack);
+	setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
 
 
-		if(score <= EvaluationResult::INEFFECTIVE_SCORE
-			&& !stack->hasBonusOfType(Bonus::FLYING)
-			&& stack->unitSide() == BattleSide::ATTACKER
-			&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+	try
+	{
+		if(stack->creatureId() == CreatureID::CATAPULT)
+		{
+			cb->battleMakeUnitAction(useCatapult(stack));
+			return;
+		}
+		if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
 		{
 		{
-			auto brokenWallMoat = getBrokenWallMoatHexes();
+			cb->battleMakeUnitAction(useHealingTent(stack));
+			return;
+		}
 
 
-			if(brokenWallMoat.size())
-			{
-				movesSkippedByDefense = 0;
+		attemptCastingSpell();
 
 
-				if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
-					result = BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
-				else
-					result = goTowardsNearest(stack, brokenWallMoat);
-			}
+		if(cb->battleIsFinished() || !stack->alive())
+		{
+			//spellcast may finish battle or kill active stack
+			//send special preudo-action
+			BattleAction cancel;
+			cancel.actionType = EActionType::CANCEL;
+			cb->battleMakeUnitAction(cancel);
+			return;
 		}
 		}
+
+		if(auto action = considerFleeingOrSurrendering())
+		{
+			cb->battleMakeUnitAction(*action);
+			return;
+		}
+
+		result = selectStackAction(stack);
 	}
 	}
 	catch(boost::thread_interrupted &)
 	catch(boost::thread_interrupted &)
 	{
 	{
@@ -275,13 +312,13 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		movesSkippedByDefense = 0;
 		movesSkippedByDefense = 0;
 	}
 	}
 
 
-	return result;
+	cb->battleMakeUnitAction(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, 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
 	{
 	{
@@ -320,27 +357,39 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
 
 
 	scoreEvaluator.updateReachabilityMap(hb);
 	scoreEvaluator.updateReachabilityMap(hb);
 
 
-	if(stack->hasBonusOfType(Bonus::FLYING))
+	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
 	{
-		std::set<BattleHex> moatHexes;
+		std::set<BattleHex> obstacleHexes;
 
 
-		if(hb.battleGetSiegeLevel() >= BuildingID::CITADEL)
-		{
-			auto townMoat = hb.getDefendedTown()->town->moatHexes;
+		auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
+			auto affectedHexes = spellObst.getAffectedTiles();
+			obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
+		};
+
+		const auto & obstacles = hb.battleGetAllObstacles();
+
+		for (const auto & obst: obstacles) {
 
 
-			moatHexes = std::set<BattleHex>(townMoat.begin(), townMoat.end());
+			if(obst->triggersEffects())
+			{
+				auto triggerAbility =  VLC->spells()->getById(obst->getTrigger());
+				auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
+
+				if(triggerIsNegative)
+					insertAffected(*obst, obstacleHexes);
+			}
 		}
 		}
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
 		// We just check all available hexes and pick the one closest to the target.
 		// We just check all available hexes and pick the one closest to the target.
 		auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
 		auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
 		{
 		{
-			const int MOAT_PENALTY = 100; // avoid landing on moat
+			const int NEGATIVE_OBSTACLE_PENALTY = 100; // avoid landing on negative obstacle (moat, fire wall, etc)
 			const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
 			const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
 
 
 			auto distance = BattleHex::getDistance(bestNeighbor, hex);
 			auto distance = BattleHex::getDistance(bestNeighbor, hex);
 
 
-			if(vstd::contains(moatHexes, hex))
-				distance += MOAT_PENALTY;
+			if(vstd::contains(obstacleHexes, hex))
+				distance += NEGATIVE_OBSTACLE_PENALTY;
 
 
 			return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
 			return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
 		});
 		});
@@ -407,7 +456,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 	attack.aimToHex(targetHex);
 	attack.aimToHex(targetHex);
 	attack.actionType = EActionType::CATAPULT;
 	attack.actionType = EActionType::CATAPULT;
 	attack.side = side;
 	attack.side = side;
-	attack.stackNumber = stack->ID;
+	attack.stackNumber = stack->unitId();
 
 
 	movesSkippedByDefense = 0;
 	movesSkippedByDefense = 0;
 
 
@@ -717,7 +766,7 @@ void CBattleAI::attemptCastingSpell()
 		spellcast.setTarget(castToPerform.dest);
 		spellcast.setTarget(castToPerform.dest);
 		spellcast.side = side;
 		spellcast.side = side;
 		spellcast.stackNumber = (!side) ? -1 : -2;
 		spellcast.stackNumber = (!side) ? -1 : -2;
-		cb->battleMakeAction(&spellcast);
+		cb->battleMakeSpellAction(spellcast);
 		movesSkippedByDefense = 0;
 		movesSkippedByDefense = 0;
 	}
 	}
 	else
 	else
@@ -777,7 +826,7 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas
 	ps.value = totalGain;
 	ps.value = totalGain;
 }
 }
 
 
-void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
+void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
 {
 {
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	side = Side;
 	side = Side;
@@ -788,7 +837,7 @@ void CBattleAI::print(const std::string &text) const
 	logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
 	logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
 }
 }
 
 
-boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
+std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 {
 {
 	BattleStateInfoForRetreat bs;
 	BattleStateInfoForRetreat bs;
 
 
@@ -802,7 +851,7 @@ boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 	{
 	{
 		if(stack->alive())
 		if(stack->alive())
 		{
 		{
-			if(stack->side == bs.ourSide)
+			if(stack->unitSide() == bs.ourSide)
 				bs.ourStacks.push_back(stack);
 				bs.ourStacks.push_back(stack);
 			else
 			else
 			{
 			{
@@ -814,9 +863,9 @@ boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 
 
 	bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size();
 	bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size();
 
 
-	if(!bs.canFlee || !bs.canSurrender)
+	if(!bs.canFlee && !bs.canSurrender)
 	{
 	{
-		return boost::none;
+		return std::nullopt;
 	}
 	}
 
 
 	auto result = cb->makeSurrenderRetreatDecision(bs);
 	auto result = cb->makeSurrenderRetreatDecision(bs);

+ 12 - 6
AI/BattleAI/BattleAI.h

@@ -31,7 +31,7 @@ struct CurrentOffensivePotential
 	{
 	{
 		for(auto stack : cbc->battleGetStacks())
 		for(auto stack : cbc->battleGetStacks())
 		{
 		{
-			if(stack->side == side)
+			if(stack->unitSide() == side)
 				ourAttacks[stack] = PotentialTargets(stack);
 				ourAttacks[stack] = PotentialTargets(stack);
 			else
 			else
 				enemyAttacks[stack] = PotentialTargets(stack);
 				enemyAttacks[stack] = PotentialTargets(stack);
@@ -59,7 +59,8 @@ class CBattleAI : public CBattleGameInterface
 	std::shared_ptr<Environment> env;
 	std::shared_ptr<Environment> env;
 
 
 	//Previous setting of cb
 	//Previous setting of cb
-	bool wasWaitingForRealize, wasUnlockingGs;
+	bool wasWaitingForRealize;
+	bool wasUnlockingGs;
 	int movesSkippedByDefense;
 	int movesSkippedByDefense;
 
 
 public:
 public:
@@ -71,18 +72,23 @@ public:
 
 
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
 
 
-	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
+	void activeStack(const CStack * stack) override; //called when it's turn of that stack
+	void yourTacticPhase(int distance) override;
 
 
-	boost::optional<BattleAction> considerFleeingOrSurrendering();
+	std::optional<BattleAction> considerFleeingOrSurrendering();
 
 
 	void print(const std::string &text) const;
 	void print(const std::string &text) const;
 	BattleAction useCatapult(const CStack *stack);
 	BattleAction useCatapult(const CStack *stack);
-	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side) override;
+	BattleAction useHealingTent(const CStack *stack);
+	BattleAction selectStackAction(const CStack * stack);
+	std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack *stack);
+
+	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
 	//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
 	//void actionFinished(const BattleAction &action) override;//occurs AFTER 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 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

+ 14 - 7
AI/BattleAI/BattleExchangeVariant.cpp

@@ -65,7 +65,7 @@ int64_t BattleExchangeVariant::trackAttack(
 	bool evaluateOnly)
 	bool evaluateOnly)
 {
 {
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
-	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
+	static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 
 
 	DamageEstimation retaliation;
 	DamageEstimation retaliation;
@@ -205,7 +205,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
 	if(targets.unreachableEnemies.empty())
 	if(targets.unreachableEnemies.empty())
 		return result;
 		return result;
 
 
-	auto speed = activeStack->Speed();
+	auto speed = activeStack->speed();
 
 
 	if(speed == 0)
 	if(speed == 0)
 		return result;
 		return result;
@@ -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))
 			{
 			{
@@ -386,9 +386,13 @@ int64_t BattleExchangeEvaluator::calculateExchange(
 
 
 	for(auto unit : exchangeUnits)
 	for(auto unit : exchangeUnits)
 	{
 	{
+		if(unit->isTurret())
+			continue;
+
 		bool isOur = cb->battleMatchOwner(ap.attack.attacker, unit, true);
 		bool isOur = cb->battleMatchOwner(ap.attack.attacker, unit, true);
 		auto & attackerQueue = isOur ? ourStacks : enemyStacks;
 		auto & attackerQueue = isOur ? ourStacks : enemyStacks;
 
 
+
 		if(!vstd::contains(attackerQueue, unit))
 		if(!vstd::contains(attackerQueue, unit))
 		{
 		{
 			attackerQueue.push_back(unit);
 			attackerQueue.push_back(unit);
@@ -593,6 +597,9 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
 
 
 		for(const battle::Unit * unit : turnQueue)
 		for(const battle::Unit * unit : turnQueue)
 		{
 		{
+			if(unit->isTurret())
+				continue;
+
 			if(turnBattle.battleCanShoot(unit))
 			if(turnBattle.battleCanShoot(unit))
 			{
 			{
 				for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 				for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
@@ -607,7 +614,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
 
 
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			{
 			{
-				bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
+				bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
 
 
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				{
 				{
@@ -617,7 +624,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
 					{
 					{
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						{
 						{
-							reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
+							reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
 
 
 							if(reachable) break;
 							if(reachable) break;
 						}
 						}
@@ -665,7 +672,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			{
 			{
 				bool enemyUnit = false;
 				bool enemyUnit = false;
-				bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
+				bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
 
 
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				{
 				{
@@ -677,7 +684,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 
 
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						{
 						{
-							reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
+							reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
 
 
 							if(reachable) break;
 							if(reachable) break;
 						}
 						}

+ 2 - 8
AI/BattleAI/BattleExchangeVariant.h

@@ -56,10 +56,7 @@ struct EvaluationResult
 class BattleExchangeVariant
 class BattleExchangeVariant
 {
 {
 public:
 public:
-	BattleExchangeVariant()
-		:dpsScore(0), attackerValue()
-	{
-	}
+	BattleExchangeVariant(): dpsScore(0) {}
 
 
 	int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle & state);
 	int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle & state);
 
 
@@ -92,10 +89,7 @@ private:
 	std::vector<battle::Units> turnOrder;
 	std::vector<battle::Units> turnOrder;
 
 
 public:
 public:
-	BattleExchangeEvaluator(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env)
-		:cb(cb), reachabilityMap(), env(env), turnOrder()
-	{
-	}
+	BattleExchangeEvaluator(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env): cb(cb), env(env) {}
 
 
 	EvaluationResult findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb);
 	EvaluationResult findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb);
 	int64_t calculateExchange(const AttackPossibility & ap, PotentialTargets & targets, HypotheticBattle & hb);
 	int64_t calculateExchange(const AttackPossibility & ap, PotentialTargets & targets, HypotheticBattle & hb);

+ 3 - 3
AI/BattleAI/PotentialTargets.cpp

@@ -15,14 +15,14 @@ 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, false);
 
 
 	//FIXME: this should part of battleGetAvailableHexes
 	//FIXME: this should part of battleGetAvailableHexes
 	bool forceTarget = false;
 	bool forceTarget = false;
 	const battle::Unit * forcedTarget = nullptr;
 	const battle::Unit * forcedTarget = nullptr;
 	BattleHex forcedHex;
 	BattleHex forcedHex;
 
 
-	if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
+	if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
 	{
 	{
 		forceTarget = true;
 		forceTarget = true;
 		auto nearest = state.getNearestStack(attackerInfo);
 		auto nearest = state.getNearestStack(attackerInfo);
@@ -89,7 +89,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 	{
 	{
 		auto & bestAp = possibleAttacks[0];
 		auto & bestAp = possibleAttacks[0];
 
 
-		logGlobal->info("Battle AI best: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
+		logGlobal->debug("Battle AI best: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
 			bestAp.attack.attacker->unitType()->getJsonKey(),
 			bestAp.attack.attacker->unitType()->getJsonKey(),
 			state.battleGetUnitByPos(bestAp.dest)->unitType()->getJsonKey(),
 			state.battleGetUnitByPos(bestAp.dest)->unitType()->getJsonKey(),
 			(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 			(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),

+ 5 - 5
AI/BattleAI/StackWithBonuses.cpp

@@ -24,7 +24,7 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 {
 {
 	for(auto & bonus : *target) //TODO: optimize
 	for(auto & bonus : *target) //TODO: optimize
 	{
 	{
-		if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
+		if(bonus->source == BonusSource::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
 		{
 		{
 			if(bonus->turnsRemain < ef.turnsRemain)
 			if(bonus->turnsRemain < ef.turnsRemain)
 			{
 			{
@@ -36,9 +36,9 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 	}
 	}
 }
 }
 
 
-StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
+StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack)
 	: battle::CUnitState(),
 	: battle::CUnitState(),
-	origBearer(Stack),
+	origBearer(Stack->getBonusBearer()),
 	owner(Owner),
 	owner(Owner),
 	type(Stack->unitType()),
 	type(Stack->unitType()),
 	baseAmount(Stack->unitBaseAmount()),
 	baseAmount(Stack->unitBaseAmount()),
@@ -126,7 +126,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 	{
 	{
 		if(selector(&bonus) && (!limit || !limit(&bonus)))
 		if(selector(&bonus) && (!limit || !limit(&bonus)))
 		{
 		{
-			if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
+			if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
 			{
 			{
 				actualizeEffect(ret, bonus);
 				actualizeEffect(ret, bonus);
 			}
 			}
@@ -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

+ 2 - 8
AI/BattleAI/StackWithBonuses.h

@@ -14,16 +14,10 @@
 #include <vcmi/Environment.h>
 #include <vcmi/Environment.h>
 #include <vcmi/ServerCallback.h>
 #include <vcmi/ServerCallback.h>
 
 
-#include "../../lib/HeroBonus.h"
+#include "../../lib/bonuses/Bonus.h"
 #include "../../lib/battle/BattleProxy.h"
 #include "../../lib/battle/BattleProxy.h"
 #include "../../lib/battle/CUnitState.h"
 #include "../../lib/battle/CUnitState.h"
 
 
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CStack;
-
-VCMI_LIB_NAMESPACE_END
-
 class HypotheticBattle;
 class HypotheticBattle;
 
 
 ///Fake random generator, used by AI to evaluate random server behavior
 ///Fake random generator, used by AI to evaluate random server behavior
@@ -54,7 +48,7 @@ public:
 	std::vector<Bonus> bonusesToUpdate;
 	std::vector<Bonus> bonusesToUpdate;
 	std::set<std::shared_ptr<Bonus>> bonusesToRemove;
 	std::set<std::shared_ptr<Bonus>> bonusesToRemove;
 
 
-	StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack);
+	StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack);
 
 
 	StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
 	StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
 
 

+ 1 - 1
AI/BattleAI/ThreatMap.cpp

@@ -30,7 +30,7 @@ ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
 	for(const CStack *enemy : getCbc()->battleGetStacks())
 	for(const CStack *enemy : getCbc()->battleGetStacks())
 	{
 	{
 		//Consider only stacks of different owner
 		//Consider only stacks of different owner
-		if(enemy->side == endangered->side)
+		if(enemy->unitSide() == endangered->unitSide())
 			continue;
 			continue;
 
 
 		//Look-up which tiles can be melee-attacked
 		//Look-up which tiles can be melee-attacked

+ 11 - 0
AI/EmptyAI/CEmptyAI.cpp

@@ -11,6 +11,7 @@
 #include "CEmptyAI.h"
 #include "CEmptyAI.h"
 
 
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CRandomGenerator.h"
+#include "../../lib/CStack.h"
 
 
 void CEmptyAI::saveGame(BinarySerializer & h, const int version)
 void CEmptyAI::saveGame(BinarySerializer & h, const int version)
 {
 {
@@ -33,6 +34,16 @@ void CEmptyAI::yourTurn()
 	cb->endTurn();
 	cb->endTurn();
 }
 }
 
 
+void CEmptyAI::activeStack(const CStack * stack)
+{
+	cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+}
+
+void CEmptyAI::yourTacticPhase(int distance)
+{
+	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+}
+
 void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)
 void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)
 {
 {
 	cb->selectionMade(CRandomGenerator::getDefault().nextInt((int)skills.size() - 1), queryID);
 	cb->selectionMade(CRandomGenerator::getDefault().nextInt((int)skills.size() - 1), queryID);

+ 2 - 0
AI/EmptyAI/CEmptyAI.h

@@ -24,6 +24,8 @@ public:
 
 
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn() override;
 	void yourTurn() override;
+	void yourTacticPhase(int distance) override;
+	void activeStack(const CStack * stack) override;
 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;

+ 1 - 1
AI/FuzzyLite

@@ -1 +1 @@
-Subproject commit 9751a751a17c0682ed5d02e583c6a0cda8bc88e5
+Subproject commit 7aee562d6ca17f3cf42588ffb5116e03017c3c50

+ 71 - 55
AI/Nullkiller/AIGateway.cpp

@@ -11,10 +11,11 @@
 
 
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/MapObjects.h"
+#include "../../lib/mapObjects/ObjectTemplate.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
-#include "../../lib/CGameState.h"
+#include "../../lib/gameState/CGameState.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/BinarySerializer.h"
 #include "../../lib/serializer/BinarySerializer.h"
@@ -28,7 +29,7 @@ namespace NKAI
 {
 {
 
 
 // our to enemy strength ratio constants
 // our to enemy strength ratio constants
-const float SAFE_ATTACK_CONSTANT = 1.2f;
+const float SAFE_ATTACK_CONSTANT = 1.1f;
 const float RETREAT_THRESHOLD = 0.3f;
 const float RETREAT_THRESHOLD = 0.3f;
 const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
 const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
 
 
@@ -89,9 +90,11 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 
 
-	validateObject(details.id); //enemy hero may have left visible area
 	auto hero = cb->getHero(details.id);
 	auto hero = cb->getHero(details.id);
 
 
+	if(!hero)
+		validateObject(details.id); //enemy hero may have left visible area
+
 	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
 	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 
 
@@ -187,7 +190,7 @@ void AIGateway::showShipyardDialog(const IShipyard * obj)
 
 
 void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult)
 void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf);
+	LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf.toString());
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.getStr(), player, player.getStr(), (victoryLossCheckResult.victory() ? "won" : "lost"));
 	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.getStr(), player, player.getStr(), (victoryLossCheckResult.victory() ? "won" : "lost"));
 
 
@@ -256,7 +259,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 }
 }
 
 
-void AIGateway::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
+void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
 {
 {
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
@@ -264,7 +267,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 }
 }
 
 
-void AIGateway::tileRevealed(const std::unordered_set<int3, ShashInt3> & pos)
+void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
 {
 {
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
@@ -447,6 +450,12 @@ void AIGateway::battleResultsApplied()
 	status.setBattle(NO_BATTLE);
 	status.setBattle(NO_BATTLE);
 }
 }
 
 
+void AIGateway::beforeObjectPropertyChanged(const SetObjectProperty * sop)
+{
+
+}
+
+
 void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
 void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
 {
 {
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
@@ -501,8 +510,7 @@ void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositio
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 }
 }
 
 
-boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
-	const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
 {
 {
 	LOG_TRACE(logAi);
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
@@ -516,7 +524,7 @@ boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
 		return BattleAction::makeRetreat(battleState.ourSide);
 		return BattleAction::makeRetreat(battleState.ourSide);
 	}
 	}
 
 
-	return boost::none;
+	return std::nullopt;
 }
 }
 
 
 
 
@@ -699,7 +707,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	//you can't request action from action-response thread
 	//you can't request action from action-response thread
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		if(removableUnits)
+		if(removableUnits && up->tempOwner == down->tempOwner)
 			pickBestCreatures(down, up);
 			pickBestCreatures(down, up);
 
 
 		answerQuery(queryID, 0);
 		answerQuery(queryID, 0);
@@ -771,28 +779,21 @@ void AIGateway::makeTurn()
 	boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
 	boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
 	setThreadName("AIGateway::makeTurn");
 	setThreadName("AIGateway::makeTurn");
 
 
+	cb->sendMessage("vcmieagles");
+
+	retrieveVisitableObjs();
+
 	if(cb->getDate(Date::DAY_OF_WEEK) == 1)
 	if(cb->getDate(Date::DAY_OF_WEEK) == 1)
 	{
 	{
-		std::vector<const CGObjectInstance *> objs;
-		retrieveVisitableObjs(objs, true);
-
-		for(const CGObjectInstance * obj : objs)
+		for(const CGObjectInstance * obj : nullkiller->memory->visitableObjs)
 		{
 		{
 			if(isWeeklyRevisitable(obj))
 			if(isWeeklyRevisitable(obj))
 			{
 			{
-				addVisitableObj(obj);
 				nullkiller->memory->markObjectUnvisited(obj);
 				nullkiller->memory->markObjectUnvisited(obj);
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	cb->sendMessage("vcmieagles");
-
-	if(cb->getDate(Date::DAY) == 1)
-	{
-		retrieveVisitableObjs();
-	}
-
 #if NKAI_TRACE_LEVEL == 0
 #if NKAI_TRACE_LEVEL == 0
 	try
 	try
 	{
 	{
@@ -802,8 +803,8 @@ void AIGateway::makeTurn()
 		//for debug purpose
 		//for debug purpose
 		for (auto h : cb->getHeroesInfo())
 		for (auto h : cb->getHeroesInfo())
 		{
 		{
-			if (h->movement)
-				logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
+			if (h->movementPointsRemaining())
+				logAi->info("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining());
 		}
 		}
 #if NKAI_TRACE_LEVEL == 0
 #if NKAI_TRACE_LEVEL == 0
 	}
 	}
@@ -866,6 +867,19 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
 
 
 	auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
 	auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
 
 
+	for(auto army : armies)
+	{
+		// move first stack at first slot if empty to avoid can not take away last creature
+		if(!army->hasStackAtSlot(SlotID(0)) && army->stacksCount() > 0)
+		{
+			cb->mergeOrSwapStacks(
+				army,
+				army,
+				SlotID(0),
+				army->Slots().begin()->first);
+		}
+	}
+
 	//foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs
 	//foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs
 	for(SlotID i = SlotID(0); i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot
 	for(SlotID i = SlotID(0); i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot
 	{
 	{
@@ -1000,7 +1014,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				//FIXME: why are the above possible to be null?
 				//FIXME: why are the above possible to be null?
 
 
 				bool emptySlotFound = false;
 				bool emptySlotFound = false;
-				for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				{
 				{
 					ArtifactLocation destLocation(target, slot);
 					ArtifactLocation destLocation(target, slot);
 					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
 					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
@@ -1013,7 +1027,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				}
 				}
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				{
 				{
-					for(auto slot : artifact->artType->possibleSlots.at(target->bearerType()))
+					for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 					{
 					{
 						auto otherSlot = target->getSlot(slot);
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
@@ -1053,23 +1067,28 @@ 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);
+		if(!recruiter->getSlotFor(creID).validSlot())
+		{
+			continue;
+		}
+
+		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);
 	}
 	}
 }
 }
 
 
-void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side)
+void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
 {
 {
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
 	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
 	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
-	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
+	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side, replayAllowed);
 }
 }
 
 
-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);
@@ -1077,7 +1096,17 @@ 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);
+
+	if (queryID != -1)
+	{
+		status.addQuery(queryID, "Combat result dialog");
+		const int confirmAction = 0;
+		requestActionASAP([=]()
+		{
+			answerQuery(queryID, confirmAction);
+		});
+	}
+	CAdventureAI::battleEnd(br, queryID);
 }
 }
 
 
 void AIGateway::waitTillFree()
 void AIGateway::waitTillFree()
@@ -1086,26 +1115,13 @@ void AIGateway::waitTillFree()
 	status.waitTillFree();
 	status.waitTillFree();
 }
 }
 
 
-void AIGateway::retrieveVisitableObjs(std::vector<const CGObjectInstance *> & out, bool includeOwned) const
-{
-	foreach_tile_pos([&](const int3 & pos)
-	{
-		for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
-		{
-			if(includeOwned || obj->tempOwner != playerID)
-				out.push_back(obj);
-		}
-	});
-}
-
 void AIGateway::retrieveVisitableObjs()
 void AIGateway::retrieveVisitableObjs()
 {
 {
 	foreach_tile_pos([&](const int3 & pos)
 	foreach_tile_pos([&](const int3 & pos)
 	{
 	{
 		for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
 		for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
 		{
 		{
-			if(obj->tempOwner != playerID)
-				addVisitableObj(obj);
+			addVisitableObj(obj);
 		}
 		}
 	});
 	});
 }
 }
@@ -1163,7 +1179,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 	if(startHpos == dst)
 	if(startHpos == dst)
 	{
 	{
 		//FIXME: this assertion fails also if AI moves onto defeated guarded object
 		//FIXME: this assertion fails also if AI moves onto defeated guarded object
-		assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
+		//assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
 		cb->moveHero(*h, h->convertFromVisitablePos(dst));
 		cb->moveHero(*h, h->convertFromVisitablePos(dst));
 		afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly?
 		afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly?
 		// If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared
 		// If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared
@@ -1190,11 +1206,11 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
 			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
 		};
 		};
 
 
-		auto isTeleportAction = [&](CGPathNode::ENodeAction action) -> bool
+		auto isTeleportAction = [&](EPathNodeAction action) -> bool
 		{
 		{
-			if(action != CGPathNode::TELEPORT_NORMAL && action != CGPathNode::TELEPORT_BLOCKING_VISIT)
+			if(action != EPathNodeAction::TELEPORT_NORMAL && action != EPathNodeAction::TELEPORT_BLOCKING_VISIT)
 			{
 			{
-				if(action != CGPathNode::TELEPORT_BATTLE)
+				if(action != EPathNodeAction::TELEPORT_BATTLE)
 				{
 				{
 					return false;
 					return false;
 				}
 				}
@@ -1305,7 +1321,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 				doChannelProbing();
 				doChannelProbing();
 		}
 		}
 
 
-		if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT || path.nodes[0].action == CGPathNode::BATTLE)
+		if(path.nodes[0].action == EPathNodeAction::BLOCKING_VISIT || path.nodes[0].action == EPathNodeAction::BATTLE)
 		{
 		{
 			// when we take resource we do not reach its position. We even might not move
 			// when we take resource we do not reach its position. We even might not move
 			// also guarded town is not get visited automatically after capturing
 			// also guarded town is not get visited automatically after capturing
@@ -1359,7 +1375,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;
@@ -1368,10 +1384,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;
@@ -1380,11 +1396,11 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 				//TODO trade only as much as needed
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				if (toGive) //don't try to sell 0 resources
 				{
 				{
-					cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
+					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
 					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
 			}
 			}
 
 

+ 6 - 6
AI/Nullkiller/AIGateway.h

@@ -127,7 +127,7 @@ public:
 	void heroMoved(const TryMoveHero & details, bool verbose = true) override;
 	void heroMoved(const TryMoveHero & details, bool verbose = true) override;
 	void heroInGarrisonChange(const CGTownInstance * town) override;
 	void heroInGarrisonChange(const CGTownInstance * town) override;
 	void centerView(int3 pos, int focusTime) override;
 	void centerView(int3 pos, int focusTime) override;
-	void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
+	void tileHidden(const std::unordered_set<int3> & pos) override;
 	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	void artifactAssembled(const ArtifactLocation & al) override;
 	void artifactAssembled(const ArtifactLocation & al) override;
 	void showTavernWindow(const CGObjectInstance * townOrTavern) override;
 	void showTavernWindow(const CGObjectInstance * townOrTavern) override;
@@ -142,7 +142,7 @@ public:
 	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
 	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
 	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
 	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
-	void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
+	void tileRevealed(const std::unordered_set<int3> & pos) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
 	void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
 	void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
 	void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
 	void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
@@ -161,15 +161,16 @@ public:
 	void heroManaPointsChanged(const CGHeroInstance * hero) override;
 	void heroManaPointsChanged(const CGHeroInstance * hero) override;
 	void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
 	void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
 	void battleResultsApplied() override;
 	void battleResultsApplied() override;
+	void beforeObjectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectPropertyChanged(const SetObjectProperty * sop) override;
 	void objectPropertyChanged(const SetObjectProperty * sop) override;
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
-	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
+	std::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 battleEnd(const BattleResult * br) override;
+	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
+	void battleEnd(const BattleResult * br, QueryID queryID) override;
 
 
 	void makeTurn();
 	void makeTurn();
 
 
@@ -194,7 +195,6 @@ public:
 
 
 	void validateObject(const CGObjectInstance * obj); //checks if object is still visible and if not, removes references to it
 	void validateObject(const CGObjectInstance * obj); //checks if object is still visible and if not, removes references to it
 	void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it
 	void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it
-	void retrieveVisitableObjs(std::vector<const CGObjectInstance *> & out, bool includeOwned = false) const;
 	void retrieveVisitableObjs();
 	void retrieveVisitableObjs();
 	virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
 	virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
 
 

+ 14 - 16
AI/Nullkiller/AIUtility.cpp

@@ -17,9 +17,11 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/mapping/CMapDefines.h"
-
+#include "../../lib/gameState/QuestInfo.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
 
 
+#include <vcmi/CreatureService.h>
+
 namespace NKAI
 namespace NKAI
 {
 {
 
 
@@ -253,7 +255,7 @@ bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, Pla
 	{
 	{
 		auto quest = dynamic_cast<const CGKeys *>(obj);
 		auto quest = dynamic_cast<const CGKeys *>(obj);
 
 
-		if(quest->passableFor(playerColor))
+		if(quest->wasMyColorVisited(playerColor))
 			return true;
 			return true;
 	}
 	}
 
 
@@ -264,7 +266,7 @@ bool isBlockVisitObj(const int3 & pos)
 {
 {
 	if(auto obj = cb->getTopObj(pos))
 	if(auto obj = cb->getTopObj(pos))
 	{
 	{
-		if(obj->blockVisit) //we can't stand on that object
+		if(obj->isBlockedVisitable()) //we can't stand on that object
 			return true;
 			return true;
 	}
 	}
 
 
@@ -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
 	{
 	{
@@ -304,10 +306,10 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	auto art1 = a1->artType;
 	auto art1 = a1->artType;
 	auto art2 = a2->artType;
 	auto art2 = a2->artType;
 
 
-	if(art1->price == art2->price)
-		return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
+	if(art1->getPrice() == art2->getPrice())
+		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
 	else
 	else
-		return art1->price > art2->price;
+		return art1->getPrice() > art2->getPrice();
 }
 }
 
 
 bool isWeeklyRevisitable(const CGObjectInstance * obj)
 bool isWeeklyRevisitable(const CGObjectInstance * obj)
@@ -317,17 +319,13 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
 
 
 	//TODO: allow polling of remaining creatures in dwelling
 	//TODO: allow polling of remaining creatures in dwelling
 	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
 	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
-		return rewardable->getResetDuration() == 7;
+		return rewardable->configuration.getResetDuration() == 7;
 
 
 	if(dynamic_cast<const CGDwelling *>(obj))
 	if(dynamic_cast<const CGDwelling *>(obj))
 		return true;
 		return true;
-	if(dynamic_cast<const CBank *>(obj)) //banks tend to respawn often in mods
-		return true;
 
 
 	switch(obj->ID)
 	switch(obj->ID)
 	{
 	{
-	case Obj::STABLES:
-	case Obj::MAGIC_WELL:
 	case Obj::HILL_FORT:
 	case Obj::HILL_FORT:
 		return true;
 		return true;
 	case Obj::BORDER_GATE:
 	case Obj::BORDER_GATE:
@@ -398,7 +396,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 +409,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 +436,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;
 	}
 	}

+ 5 - 7
AI/Nullkiller/AIUtility.h

@@ -45,16 +45,14 @@
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/CStopWatch.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../lib/CPathfinder.h"
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 
 
 #include <chrono>
 #include <chrono>
 
 
 using namespace tbb;
 using namespace tbb;
 
 
-typedef std::pair<ui32, std::vector<CreatureID>> dwellingContent;
+using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -161,7 +159,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);
@@ -305,10 +303,10 @@ public:
 public:
 public:
 	using ptr_type = std::unique_ptr<T, External_Deleter>;
 	using ptr_type = std::unique_ptr<T, External_Deleter>;
 
 
-	SharedPool(std::function<std::unique_ptr<T>()> elementFactory)
-		: elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool<T>*(this))
+	SharedPool(std::function<std::unique_ptr<T>()> elementFactory):
+		elementFactory(elementFactory), pool(), instance_tracker(new SharedPool<T> *(this))
 	{}
 	{}
-	
+
 	void add(std::unique_ptr<T> t)
 	void add(std::unique_ptr<T> t)
 	{
 	{
 		boost::lock_guard<boost::mutex> lock(sync);
 		boost::lock_guard<boost::mutex> lock(sync);

+ 87 - 34
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -13,6 +13,7 @@
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/mapObjects/MapObjects.h"
+#include "../../../lib/GameConstants.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -28,11 +29,50 @@ 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;
 	}
 	}
 };
 };
 
 
+void ArmyUpgradeInfo::addArmyToBuy(std::vector<SlotInfo> army)
+{
+	for(auto slot : army)
+	{
+		resultingArmy.push_back(slot);
+
+		upgradeValue += slot.power;
+		upgradeCost += slot.creature->getFullRecruitCost() * slot.count;
+	}
+}
+
+void ArmyUpgradeInfo::addArmyToGet(std::vector<SlotInfo> army)
+{
+	for(auto slot : army)
+	{
+		resultingArmy.push_back(slot);
+
+		upgradeValue += slot.power;
+	}
+}
+
+std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
+{
+	std::vector<SlotInfo> result;
+
+	for(auto i : army)
+	{
+		SlotInfo slot;
+
+		slot.creature = VLC->creh->objects[i.cre->getId()];
+		slot.count = i.count;
+		slot.power = evaluateStackPower(i.cre, i.count);
+
+		result.push_back(slot);
+	}
+
+	return result;
+}
+
 uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
 uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
 {
 {
 	return howManyReinforcementsCanGet(hero, hero, source);
 	return howManyReinforcementsCanGet(hero, hero, source);
@@ -50,9 +90,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,10 +114,10 @@ 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();
 	});
 	});
 
 
 	return weakest;
 	return weakest;
@@ -95,24 +136,24 @@ 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;
 
 
 	TemporaryArmy newArmyInstance;
 	TemporaryArmy newArmyInstance;
-	auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(Bonus::MORALE));
+	auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE));
 
 
 	for(auto bonus : *bonusModifiers)
 	for(auto bonus : *bonusModifiers)
 	{
 	{
 		// army bonuses will change and object bonuses are temporary
 		// army bonuses will change and object bonuses are temporary
-		if(bonus->source != Bonus::ARMY && bonus->source != Bonus::OBJECT)
+		if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT)
 		{
 		{
 			newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
 			newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
 		}
 		}
@@ -120,7 +161,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 +174,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->getId());
 
 
 				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);
 				}
 				}
 			}
 			}
@@ -149,7 +190,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 
 		for(auto & slot : newArmyInstance.Slots())
 		for(auto & slot : newArmyInstance.Slots())
 		{
 		{
-			auto morale = slot.second->MoraleVal();
+			auto morale = slot.second->moraleVal();
 			auto multiplier = 1.0f;
 			auto multiplier = 1.0f;
 
 
 			const float BadMoraleChance = 0.083f;
 			const float BadMoraleChance = 0.083f;
@@ -217,7 +258,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 +269,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;
@@ -237,14 +278,15 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
 ui64 ArmyManager::howManyReinforcementsCanBuy(
 ui64 ArmyManager::howManyReinforcementsCanBuy(
 	const CCreatureSet * targetArmy,
 	const CCreatureSet * targetArmy,
 	const CGDwelling * dwelling,
 	const CGDwelling * dwelling,
-	const TResources & availableResources) const
+	const TResources & availableResources,
+	uint8_t turn) const
 {
 {
 	ui64 aivalue = 0;
 	ui64 aivalue = 0;
 	auto army = getArmyAvailableToBuy(targetArmy, dwelling, availableResources);
 	auto army = getArmyAvailableToBuy(targetArmy, dwelling, availableResources);
 
 
 	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;
@@ -258,17 +300,29 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * her
 std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 	const CCreatureSet * hero,
 	const CCreatureSet * hero,
 	const CGDwelling * dwelling,
 	const CGDwelling * dwelling,
-	TResources availableRes) const
+	TResources availableRes,
+	uint8_t turn) const
 {
 {
 	std::vector<creInfo> creaturesInDwellings;
 	std::vector<creInfo> creaturesInDwellings;
 	int freeHeroSlots = GameConstants::ARMY_SIZE - hero->stacksCount();
 	int freeHeroSlots = GameConstants::ARMY_SIZE - hero->stacksCount();
+	bool countGrowth = (cb->getDate(Date::DAY_OF_WEEK) + turn) > 7;
+
+	const CGTownInstance * town = dwelling->ID == Obj::TOWN
+		? dynamic_cast<const CGTownInstance *>(dwelling)
+		: nullptr;
 
 
 	for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
 	for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
 	{
 	{
 		auto ci = infoFromDC(dwelling->creatures[i]);
 		auto ci = infoFromDC(dwelling->creatures[i]);
 
 
-		if(!ci.count || ci.creID == -1)
-			continue;
+		if(ci.creID == -1) continue;
+
+		if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
+		{
+			ci.count += town ? town->creatureGrowth(i) : ci.cre->getGrowth();
+		}
+
+		if(!ci.count) continue;
 
 
 		SlotID dst = hero->getSlotFor(ci.creID);
 		SlotID dst = hero->getSlotFor(ci.creID);
 		if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
 		if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
@@ -279,14 +333,13 @@ 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)
-			continue;
+		if(!ci.count) 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;
@@ -306,9 +359,9 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier,
 	return newArmy > oldArmy ? newArmy - oldArmy : 0;
 	return newArmy > oldArmy ? newArmy - oldArmy : 0;
 }
 }
 
 
-uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const
+uint64_t ArmyManager::evaluateStackPower(const Creature * 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 +431,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 +470,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 +541,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;

+ 34 - 13
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -32,13 +32,11 @@ struct SlotInfo
 struct ArmyUpgradeInfo
 struct ArmyUpgradeInfo
 {
 {
 	std::vector<SlotInfo> resultingArmy;
 	std::vector<SlotInfo> resultingArmy;
-	uint64_t upgradeValue;
+	uint64_t upgradeValue = 0;
 	TResources upgradeCost;
 	TResources upgradeCost;
 
 
-	ArmyUpgradeInfo()
-		: resultingArmy(), upgradeValue(0), upgradeCost()
-	{
-	}
+	void addArmyToBuy(std::vector<SlotInfo> army);
+	void addArmyToGet(std::vector<SlotInfo> army);
 };
 };
 
 
 class DLL_EXPORT IArmyManager //: public: IAbstractManager
 class DLL_EXPORT IArmyManager //: public: IAbstractManager
@@ -50,20 +48,33 @@ public:
 	virtual	ui64 howManyReinforcementsCanBuy(
 	virtual	ui64 howManyReinforcementsCanBuy(
 		const CCreatureSet * targetArmy,
 		const CCreatureSet * targetArmy,
 		const CGDwelling * dwelling,
 		const CGDwelling * dwelling,
-		const TResources & availableResources) const = 0;
+		const TResources & availableResources,
+		uint8_t turn = 0) const = 0;
+
 	virtual ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const = 0;
 	virtual ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const = 0;
-	virtual ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
+	virtual ui64 howManyReinforcementsCanGet(
+		const IBonusBearer * armyCarrier,
+		const CCreatureSet * target,
+		const CCreatureSet * source) const = 0;
+
 	virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
 	virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
 	virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
 	virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
 	virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
 	virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
-	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const = 0;
-	virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
+	virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
+
+	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
+	virtual std::vector<creInfo> getArmyAvailableToBuy(
+		const CCreatureSet * hero,
+		const CGDwelling * dwelling,
+		TResources availableRes,
+		uint8_t turn = 0) const = 0;
+
+	virtual uint64_t evaluateStackPower(const Creature * creature, int count) const = 0;
 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
 	virtual ArmyUpgradeInfo calculateCreaturesUpgrade(
 	virtual ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army,
 		const CCreatureSet * army,
 		const CGObjectInstance * upgrader,
 		const CGObjectInstance * upgrader,
 		const TResources & availableResources) const = 0;
 		const TResources & availableResources) const = 0;
-	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
 	virtual std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const = 0;
 	virtual std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const = 0;
 };
 };
 
 
@@ -79,20 +90,30 @@ private:
 public:
 public:
 	ArmyManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai): cb(CB), ai(ai) {}
 	ArmyManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai): cb(CB), ai(ai) {}
 	void update() override;
 	void update() override;
+
 	ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override;
 	ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override;
 	ui64 howManyReinforcementsCanBuy(
 	ui64 howManyReinforcementsCanBuy(
 		const CCreatureSet * targetArmy,
 		const CCreatureSet * targetArmy,
 		const CGDwelling * dwelling,
 		const CGDwelling * dwelling,
-		const TResources & availableResources) const override;
+		const TResources & availableResources,
+		uint8_t turn = 0) const override;
+
 	ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
 	ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
 	ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
 	ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
 	std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
 	std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
 	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
 	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
 	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
 	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
-	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const override;
+	std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;
+
 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
+	std::vector<creInfo> getArmyAvailableToBuy(
+		const CCreatureSet * hero,
+		const CGDwelling * dwelling,
+		TResources availableRes,
+		uint8_t turn = 0) const override;
+
 	std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override;
 	std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override;
-	uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
+	uint64_t evaluateStackPower(const Creature * creature, int count) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
 	ArmyUpgradeInfo calculateCreaturesUpgrade(
 	ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army, 
 		const CCreatureSet * army, 

+ 48 - 23
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -9,7 +9,6 @@
 */
 */
 #include "../StdInc.h"
 #include "../StdInc.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -69,19 +68,22 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 	logAi->trace("Checking other buildings");
 	logAi->trace("Checking other buildings");
 
 
 	std::vector<std::vector<BuildingID>> otherBuildings = {
 	std::vector<std::vector<BuildingID>> otherBuildings = {
-		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
+		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL},
+		{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
 	};
 	};
 
 
 	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
 	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
 	{
 	{
 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
+		otherBuildings.push_back({BuildingID::HORDE_1});
+		otherBuildings.push_back({BuildingID::HORDE_2});
 	}
 	}
 
 
 	for(auto & buildingSet : otherBuildings)
 	for(auto & buildingSet : otherBuildings)
 	{
 	{
 		for(auto & buildingID : buildingSet)
 		for(auto & buildingID : buildingSet)
 		{
 		{
-			if(!developmentInfo.town->hasBuilt(buildingID))
+			if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
 			{
 			{
 				developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
 				developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
 
 
@@ -93,9 +95,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 +166,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] / 5000.0f
+			+ (float)armyCost[EGameResID::GOLD] / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
 	}
 	}
 
 
 	logAi->trace("Gold preasure: %f", goldPreasure);
 	logAi->trace("Gold preasure: %f", goldPreasure);
@@ -191,12 +193,28 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 	const CCreature * creature = nullptr;
 	const CCreature * creature = nullptr;
 	CreatureID baseCreatureID;
 	CreatureID baseCreatureID;
 
 
+	int creatureLevel = -1;
+	int creatureUpgrade = 0;
+
 	if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
 	if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
 	{
 	{
-		int level = toBuild - BuildingID::DWELL_FIRST;
-		auto creatures = townInfo->creatures.at(level % GameConstants::CREATURES_PER_TOWN);
-		auto creatureID = creatures.size() > level / GameConstants::CREATURES_PER_TOWN
-			? creatures.at(level / GameConstants::CREATURES_PER_TOWN)
+		creatureLevel = (toBuild - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
+		creatureUpgrade = (toBuild - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
+	}
+	else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
+	{
+		creatureLevel = townInfo->hordeLvl.at(0);
+	}
+	else if(toBuild == BuildingID::HORDE_2 || toBuild == BuildingID::HORDE_2_UPGR)
+	{
+		creatureLevel = townInfo->hordeLvl.at(1);
+	}
+
+	if(creatureLevel >=  0)
+	{
+		auto creatures = townInfo->creatures.at(creatureLevel);
+		auto creatureID = creatures.size() > creatureUpgrade
+			? creatures.at(creatureUpgrade)
 			: creatures.front();
 			: creatures.front();
 
 
 		baseCreatureID = creatures.front();
 		baseCreatureID = creatures.front();
@@ -280,7 +298,7 @@ void BuildAnalyzer::updateDailyIncome()
 
 
 		if(mine)
 		if(mine)
 		{
 		{
-			dailyIncome[mine->producedResource] += mine->producedQuantity;
+			dailyIncome[mine->producedResource.getNum()] += mine->producedQuantity;
 		}
 		}
 	}
 	}
 
 
@@ -294,7 +312,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
 {
 {
 	for(auto tdi : developmentInfos)
 	for(auto tdi : developmentInfos)
 	{
 	{
-		if(tdi.town->alignment == alignment && tdi.town->hasBuilt(bid))
+		if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
 			return true;
 			return true;
 	}
 	}
 
 
@@ -355,10 +373,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,12 +385,19 @@ BuildingInfo::BuildingInfo(
 		}
 		}
 		else
 		else
 		{
 		{
-			creatureGrows = creature->growth;
+			if(BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST)
+			{
+				creatureGrows = creature->getGrowth();
 
 
-			if(town->hasBuilt(BuildingID::CASTLE))
-				creatureGrows *= 2;
-			else if(town->hasBuilt(BuildingID::CITADEL))
-				creatureGrows += creatureGrows / 2;
+				if(town->hasBuilt(BuildingID::CASTLE))
+					creatureGrows *= 2;
+				else if(town->hasBuilt(BuildingID::CITADEL))
+					creatureGrows += creatureGrows / 2;
+			}
+			else
+			{
+				creatureGrows = creature->getHorde();
+			}
 		}
 		}
 
 
 		armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);
 		armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);

+ 5 - 2
AI/Nullkiller/Analyzers/BuildAnalyzer.h

@@ -62,8 +62,11 @@ public:
 	HeroRole townRole;
 	HeroRole townRole;
 	bool hasSomethingToBuild;
 	bool hasSomethingToBuild;
 
 
-	TownDevelopmentInfo(const CGTownInstance* town)
-		:town(town), armyStrength(0), toBuild(), townDevelopmentCost(), requiredResources(), townRole(HeroRole::SCOUT), hasSomethingToBuild(false)
+	TownDevelopmentInfo(const CGTownInstance * town):
+		town(town),
+		armyStrength(0),
+		townRole(HeroRole::SCOUT),
+		hasSomethingToBuild(false)
 	{
 	{
 	}
 	}
 
 

+ 163 - 18
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -8,7 +8,8 @@
 *
 *
 */
 */
 #include "../StdInc.h"
 #include "../StdInc.h"
-#include "lib/mapping/CMap.h" //for victory conditions
+#include "DangerHitMapAnalyzer.h"
+
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -16,20 +17,29 @@ namespace NKAI
 
 
 HitMapInfo HitMapInfo::NoTreat;
 HitMapInfo HitMapInfo::NoTreat;
 
 
+double HitMapInfo::value() const
+{
+	return danger / std::sqrt(turn / 3.0f + 1);
+}
+
 void DangerHitMapAnalyzer::updateHitMap()
 void DangerHitMapAnalyzer::updateHitMap()
 {
 {
-	if(upToDate)
+	if(hitMapUpToDate)
 		return;
 		return;
 
 
 	logAi->trace("Update danger hitmap");
 	logAi->trace("Update danger hitmap");
 
 
-	upToDate = true;
+	hitMapUpToDate = true;
 	auto start = std::chrono::high_resolution_clock::now();
 	auto start = std::chrono::high_resolution_clock::now();
 
 
 	auto cb = ai->cb.get();
 	auto cb = ai->cb.get();
 	auto mapSize = ai->cb->getMapSize();
 	auto mapSize = ai->cb->getMapSize();
-	hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+	
+	if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
+		hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+
 	enemyHeroAccessibleObjects.clear();
 	enemyHeroAccessibleObjects.clear();
+	townTreats.clear();
 
 
 	std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
 	std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
 
 
@@ -66,27 +76,26 @@ void DangerHitMapAnalyzer::updateHitMap()
 				if(path.getFirstBlockedAction())
 				if(path.getFirstBlockedAction())
 					continue;
 					continue;
 
 
-				auto tileDanger = path.getHeroStrength();
-				auto turn = path.turn();
 				auto & node = hitMap[pos.x][pos.y][pos.z];
 				auto & node = hitMap[pos.x][pos.y][pos.z];
 
 
-				if(tileDanger / (turn / 3 + 1) > node.maximumDanger.danger / (node.maximumDanger.turn / 3 + 1)
-					|| (tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn))
+				HitMapInfo newTreat;
+
+				newTreat.hero = path.targetHero;
+				newTreat.turn = path.turn();
+				newTreat.danger = path.getHeroStrength();
+
+				if(newTreat.value() > node.maximumDanger.value())
 				{
 				{
-					node.maximumDanger.danger = tileDanger;
-					node.maximumDanger.turn = turn;
-					node.maximumDanger.hero = path.targetHero;
+					node.maximumDanger = newTreat;
 				}
 				}
 
 
-				if(turn < node.fastestDanger.turn
-					|| (turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger))
+				if(newTreat.turn < node.fastestDanger.turn
+					|| (newTreat.turn == node.fastestDanger.turn && node.fastestDanger.danger < newTreat.danger))
 				{
 				{
-					node.fastestDanger.danger = tileDanger;
-					node.fastestDanger.turn = turn;
-					node.fastestDanger.hero = path.targetHero;
+					node.fastestDanger = newTreat;
 				}
 				}
 
 
-				if(turn == 0)
+				if(newTreat.turn == 0)
 				{
 				{
 					auto objects = cb->getVisitableObjs(pos, false);
 					auto objects = cb->getVisitableObjs(pos, false);
 					
 					
@@ -94,6 +103,26 @@ void DangerHitMapAnalyzer::updateHitMap()
 					{
 					{
 						if(cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES)
 						if(cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES)
 							enemyHeroAccessibleObjects[path.targetHero].insert(obj);
 							enemyHeroAccessibleObjects[path.targetHero].insert(obj);
+
+						if(obj->ID == Obj::TOWN && obj->getOwner() == ai->playerID)
+						{
+							auto & treats = townTreats[obj->id];
+							auto treat = std::find_if(treats.begin(), treats.end(), [&](const HitMapInfo & i) -> bool
+								{
+									return i.hero.hid == path.targetHero->id;
+								});
+
+							if(treat == treats.end())
+							{
+								treats.emplace_back();
+								treat = std::prev(treats.end(), 1);
+							}
+
+							if(newTreat.value() > treat->value())
+							{
+								*treat = newTreat;
+							}
+						}
 					}
 					}
 				}
 				}
 			}
 			}
@@ -103,6 +132,122 @@ void DangerHitMapAnalyzer::updateHitMap()
 	logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
 	logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
 }
 }
 
 
+void DangerHitMapAnalyzer::calculateTileOwners()
+{
+	if(tileOwnersUpToDate) return;
+
+	tileOwnersUpToDate = true;
+
+	auto cb = ai->cb.get();
+	auto mapSize = ai->cb->getMapSize();
+
+	if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
+		hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+
+	std::map<const CGHeroInstance *, HeroRole> townHeroes;
+	std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
+	PathfinderSettings pathfinderSettings;
+
+	pathfinderSettings.mainTurnDistanceLimit = 5;
+
+	auto addTownHero = [&](const CGTownInstance * town)
+	{
+			auto townHero = new CGHeroInstance();
+			CRandomGenerator rng;
+			auto visitablePos = town->visitablePos();
+			
+			townHero->setOwner(ai->playerID); // lets avoid having multiple colors
+			townHero->initHero(rng, static_cast<HeroTypeID>(0));
+			townHero->pos = townHero->convertFromVisitablePos(visitablePos);
+			townHero->initObj(rng);
+			
+			heroTownMap[townHero] = town;
+			townHeroes[townHero] = HeroRole::MAIN;
+	};
+
+	for(auto obj : ai->memory->visitableObjs)
+	{
+		if(obj && obj->ID == Obj::TOWN)
+		{
+			addTownHero(dynamic_cast<const CGTownInstance *>(obj));
+		}
+	}
+
+	for(auto town : cb->getTownsInfo())
+	{
+		addTownHero(town);
+	}
+
+	ai->pathfinder->updatePaths(townHeroes, PathfinderSettings());
+
+	pforeachTilePos(mapSize, [&](const int3 & pos)
+		{
+			float ourDistance = std::numeric_limits<float>::max();
+			float enemyDistance = std::numeric_limits<float>::max();
+			const CGTownInstance * enemyTown = nullptr;
+			const CGTownInstance * ourTown = nullptr;
+
+			for(AIPath & path : ai->pathfinder->getPathInfo(pos))
+			{
+				if(!path.targetHero || path.getFirstBlockedAction())
+					continue;
+
+				auto town = heroTownMap[path.targetHero];
+
+				if(town->getOwner() == ai->playerID)
+				{
+					if(ourDistance > path.movementCost())
+					{
+						ourDistance = path.movementCost();
+						ourTown = town;
+					}
+				}
+				else
+				{
+					if(enemyDistance > path.movementCost())
+					{
+						enemyDistance = path.movementCost();
+						enemyTown = town;
+					}
+				}
+			}
+
+			if(ourDistance == enemyDistance)
+			{
+				hitMap[pos.x][pos.y][pos.z].closestTown = nullptr;
+			}
+			else if(!enemyTown || ourDistance < enemyDistance)
+			{
+				hitMap[pos.x][pos.y][pos.z].closestTown = ourTown;
+			}
+			else
+			{
+				hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown;
+			}
+		});
+}
+
+const std::vector<HitMapInfo> & DangerHitMapAnalyzer::getTownTreats(const CGTownInstance * town) const
+{
+	static const std::vector<HitMapInfo> empty = {};
+
+	auto result = townTreats.find(town->id);
+
+	return result == townTreats.end() ? empty : result->second;
+}
+
+PlayerColor DangerHitMapAnalyzer::getTileOwner(const int3 & tile) const
+{
+	auto town = hitMap[tile.x][tile.y][tile.z].closestTown;
+
+	return town ? town->getOwner() : PlayerColor::NEUTRAL;
+}
+
+const CGTownInstance * DangerHitMapAnalyzer::getClosestTown(const int3 & tile) const
+{
+	return hitMap[tile.x][tile.y][tile.z].closestTown;
+}
+
 uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const
 uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const
 {
 {
 	int3 tile = path.targetTile();
 	int3 tile = path.targetTile();
@@ -143,7 +288,7 @@ const std::set<const CGObjectInstance *> & DangerHitMapAnalyzer::getOneTurnAcces
 
 
 void DangerHitMapAnalyzer::reset()
 void DangerHitMapAnalyzer::reset()
 {
 {
-	upToDate = false;
+	hitMapUpToDate = false;
 }
 }
 
 
 }
 }

+ 14 - 1
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h

@@ -14,6 +14,8 @@
 namespace NKAI
 namespace NKAI
 {
 {
 
 
+struct AIPath;
+
 struct HitMapInfo
 struct HitMapInfo
 {
 {
 	static HitMapInfo NoTreat;
 	static HitMapInfo NoTreat;
@@ -33,6 +35,8 @@ struct HitMapInfo
 		turn = 255;
 		turn = 255;
 		hero = HeroPtr();
 		hero = HeroPtr();
 	}
 	}
+
+	double value() const;
 };
 };
 
 
 struct HitMapNode
 struct HitMapNode
@@ -40,6 +44,8 @@ struct HitMapNode
 	HitMapInfo maximumDanger;
 	HitMapInfo maximumDanger;
 	HitMapInfo fastestDanger;
 	HitMapInfo fastestDanger;
 
 
+	const CGTownInstance * closestTown = nullptr;
+
 	HitMapNode() = default;
 	HitMapNode() = default;
 
 
 	void reset()
 	void reset()
@@ -54,18 +60,25 @@ class DangerHitMapAnalyzer
 private:
 private:
 	boost::multi_array<HitMapNode, 3> hitMap;
 	boost::multi_array<HitMapNode, 3> hitMap;
 	std::map<const CGHeroInstance *, std::set<const CGObjectInstance *>> enemyHeroAccessibleObjects;
 	std::map<const CGHeroInstance *, std::set<const CGObjectInstance *>> enemyHeroAccessibleObjects;
-	bool upToDate;
+	bool hitMapUpToDate = false;
+	bool tileOwnersUpToDate = false;
 	const Nullkiller * ai;
 	const Nullkiller * ai;
+	std::map<ObjectInstanceID, std::vector<HitMapInfo>> townTreats;
 
 
 public:
 public:
 	DangerHitMapAnalyzer(const Nullkiller * ai) :ai(ai) {}
 	DangerHitMapAnalyzer(const Nullkiller * ai) :ai(ai) {}
 
 
 	void updateHitMap();
 	void updateHitMap();
+	void calculateTileOwners();
 	uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
 	uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
 	const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const;
 	const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const;
 	const HitMapNode & getTileTreat(const int3 & tile) const;
 	const HitMapNode & getTileTreat(const int3 & tile) const;
 	const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
 	const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
 	void reset();
 	void reset();
+	void resetTileOwners() { tileOwnersUpToDate = false; }
+	PlayerColor getTileOwner(const int3 & tile) const;
+	const CGTownInstance * getClosestTown(const int3 & tile) const;
+	const std::vector<HitMapInfo> & getTownTreats(const CGTownInstance * town) const;
 };
 };
 
 
 }
 }

+ 40 - 12
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -13,7 +13,6 @@
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/CHeroHandler.h"
 #include "../../../lib/CHeroHandler.h"
 #include "../../../lib/GameSettings.h"
 #include "../../../lib/GameSettings.h"
-#include "../../../lib/CGameState.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -72,10 +71,10 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
 {
-	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex());
-	auto secondarySkillBonus = Selector::targetSourceType()(Bonus::SECONDARY_SKILL);
+	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, hero->type->getIndex());
+	auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
-	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(Bonus::SECONDARY_SKILL));
+	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
 	float specialityScore = 0.0f;
 	float specialityScore = 0.0f;
 
 
 	for(auto bonus : *secondarySkillBonuses)
 	for(auto bonus : *secondarySkillBonuses)
@@ -126,6 +125,7 @@ void HeroManager::update()
 	}
 	}
 
 
 	std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
 	std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
+	heroRoles.clear();
 
 
 	for(auto hero : myHeroes)
 	for(auto hero : myHeroes)
 	{
 	{
@@ -181,6 +181,15 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const
 	return evaluateFightingStrength(hero);
 	return evaluateFightingStrength(hero);
 }
 }
 
 
+bool HeroManager::heroCapReached() const
+{
+	const bool includeGarnisoned = true;
+	int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
+
+	return heroCount >= ALLOWED_ROAMING_HEROES
+		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
+}
+
 bool HeroManager::canRecruitHero(const CGTownInstance * town) const
 bool HeroManager::canRecruitHero(const CGTownInstance * town) const
 {
 {
 	if(!town)
 	if(!town)
@@ -189,16 +198,10 @@ bool HeroManager::canRecruitHero(const CGTownInstance * town) const
 	if(!town || !townHasFreeTavern(town))
 	if(!town || !townHasFreeTavern(town))
 		return false;
 		return false;
 
 
-	if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
+	if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST)
 		return false;
 		return false;
 
 
-	const bool includeGarnisoned = true;
-	int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
-
-	if(heroCount >= ALLOWED_ROAMING_HEROES)
-		return false;
-
-	if(heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
+	if(heroCapReached())
 		return false;
 		return false;
 
 
 	if(!cb->getAvailableHeroes(town).size())
 	if(!cb->getAvailableHeroes(town).size())
@@ -226,6 +229,31 @@ const CGHeroInstance * HeroManager::findHeroWithGrail() const
 	return nullptr;
 	return nullptr;
 }
 }
 
 
+const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) const
+{
+	const CGHeroInstance * weakestHero = nullptr;
+	auto myHeroes = ai->cb->getHeroesInfo();
+
+	for(auto existingHero : myHeroes)
+	{
+		if(ai->getHeroLockedReason(existingHero) == HeroLockedReason::DEFENCE
+			|| existingHero->getArmyStrength() >armyLimit
+			|| getHeroRole(existingHero) == HeroRole::MAIN
+			|| existingHero->movementPointsRemaining()
+			|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
+		{
+			continue;
+		}
+
+		if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
+		{
+			weakestHero = existingHero;
+		}
+	}
+
+	return weakestHero;
+}
+
 SecondarySkillScoreMap::SecondarySkillScoreMap(std::map<SecondarySkill, float> scoreMap)
 SecondarySkillScoreMap::SecondarySkillScoreMap(std::map<SecondarySkill, float> scoreMap)
 	:scoreMap(scoreMap)
 	:scoreMap(scoreMap)
 {
 {

+ 4 - 0
AI/Nullkiller/Analyzers/HeroManager.h

@@ -31,7 +31,9 @@ public:
 	virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
 	virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
 	virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
 	virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
 	virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0;
 	virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0;
+	virtual bool heroCapReached() const = 0;
 	virtual const CGHeroInstance * findHeroWithGrail() const = 0;
 	virtual const CGHeroInstance * findHeroWithGrail() const = 0;
+	virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0;
 };
 };
 
 
 class DLL_EXPORT ISecondarySkillRule
 class DLL_EXPORT ISecondarySkillRule
@@ -71,7 +73,9 @@ public:
 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
 	float evaluateHero(const CGHeroInstance * hero) const override;
 	float evaluateHero(const CGHeroInstance * hero) const override;
 	bool canRecruitHero(const CGTownInstance * t = nullptr) const override;
 	bool canRecruitHero(const CGTownInstance * t = nullptr) const override;
+	bool heroCapReached() const override;
 	const CGHeroInstance * findHeroWithGrail() const override;
 	const CGHeroInstance * findHeroWithGrail() const override;
+	const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override;
 
 
 private:
 private:
 	float evaluateFightingStrength(const CGHeroInstance * hero) const;
 	float evaluateFightingStrength(const CGHeroInstance * hero) const;

+ 7 - 3
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -12,7 +12,6 @@
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
-#include "lib/mapping/CMap.h" //for victory conditions
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -114,7 +113,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 
 
 			if(blockerObject)
 			if(blockerObject)
 			{
 			{
-				blockers.push_back(blockerObject);
+				blockers.insert(blockers.begin(), blockerObject);
 			}
 			}
 		}
 		}
 
 
@@ -228,7 +227,12 @@ void ObjectClusterizer::clusterize()
 			auto obj = objs[i];
 			auto obj = objs[i];
 
 
 			if(!shouldVisitObject(obj))
 			if(!shouldVisitObject(obj))
-				return;
+			{
+#if NKAI_TRACE_LEVEL >= 2
+				logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
+#endif
+				continue;
+			}
 
 
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
 			logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());

+ 5 - 11
AI/Nullkiller/Analyzers/ObjectClusterizer.h

@@ -22,7 +22,7 @@ struct ClusterObjectInfo
 	uint8_t turn;
 	uint8_t turn;
 };
 };
 
 
-typedef tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo> ClusterObjects;
+using ClusterObjects = tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo>;
 
 
 struct ObjectCluster
 struct ObjectCluster
 {
 {
@@ -36,11 +36,8 @@ public:
 	}
 	}
 
 
 	void addObject(const CGObjectInstance * object, const AIPath & path, float priority);
 	void addObject(const CGObjectInstance * object, const AIPath & path, float priority);
-	
-	ObjectCluster(const CGObjectInstance * blocker)
-		:objects(), blocker(blocker)
-	{
-	}
+
+	ObjectCluster(const CGObjectInstance * blocker): blocker(blocker) {}
 
 
 	ObjectCluster() : ObjectCluster(nullptr)
 	ObjectCluster() : ObjectCluster(nullptr)
 	{
 	{
@@ -50,7 +47,7 @@ public:
 	const CGObjectInstance * calculateCenter() const;
 	const CGObjectInstance * calculateCenter() const;
 };
 };
 
 
-typedef tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> ClusterMap;
+using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
 
 
 class ObjectClusterizer
 class ObjectClusterizer
 {
 {
@@ -67,10 +64,7 @@ public:
 	std::vector<std::shared_ptr<ObjectCluster>> getLockedClusters() const;
 	std::vector<std::shared_ptr<ObjectCluster>> getLockedClusters() const;
 	const CGObjectInstance * getBlocker(const AIPath & path) const;
 	const CGObjectInstance * getBlocker(const AIPath & path) const;
 
 
-	ObjectClusterizer(const Nullkiller * ai)
-		:nearObjects(), farObjects(), blockedObjects(), ai(ai)
-	{
-	}
+	ObjectClusterizer(const Nullkiller * ai): ai(ai) {}
 
 
 private:
 private:
 	bool shouldVisitObject(const CGObjectInstance * obj) const;
 	bool shouldVisitObject(const CGObjectInstance * obj) const;

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

@@ -15,8 +15,6 @@
 #include "../Goals/Composition.h"
 #include "../Goals/Composition.h"
 #include "../Goals/BuildThis.h"
 #include "../Goals/BuildThis.h"
 #include "../Goals/SaveResources.h"
 #include "../Goals/SaveResources.h"
-#include "lib/mapping/CMap.h" //for victory conditions
-#include "lib/CPathfinder.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -58,7 +56,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)
 				{
 				{

+ 0 - 2
AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp

@@ -13,8 +13,6 @@
 #include "../AIUtility.h"
 #include "../AIUtility.h"
 #include "../Goals/BuyArmy.h"
 #include "../Goals/BuyArmy.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
-#include "lib/mapping/CMap.h" //for victory conditions
-#include "lib/CPathfinder.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 10 - 6
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -56,7 +56,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 
 
 	tasks.reserve(paths.size());
 	tasks.reserve(paths.size());
 
 
-	const AIPath * closestWay = nullptr;
+	std::unordered_map<HeroRole, const AIPath *> closestWaysByRole;
 	std::vector<ExecuteHeroChain *> waysToVisitObj;
 	std::vector<ExecuteHeroChain *> waysToVisitObj;
 
 
 	for(auto & path : paths)
 	for(auto & path : paths)
@@ -128,8 +128,9 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 
 
 			auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
 			auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
 
 
-			if(heroRole == HeroRole::SCOUT
-				&& (!closestWay || closestWay->movementCost() > path.movementCost()))
+			auto & closestWay = closestWaysByRole[heroRole];
+
+			if(!closestWay || closestWay->movementCost() > path.movementCost())
 			{
 			{
 				closestWay = &path;
 				closestWay = &path;
 			}
 			}
@@ -142,9 +143,12 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 		}
 		}
 	}
 	}
 
 
-	if(closestWay)
+	for(auto way : waysToVisitObj)
 	{
 	{
-		for(auto way : waysToVisitObj)
+		auto heroRole = ai->nullkiller->heroManager->getHeroRole(way->getPath().targetHero);
+		auto closestWay = closestWaysByRole[heroRole];
+
+		if(closestWay)
 		{
 		{
 			way->closestWayRatio
 			way->closestWayRatio
 				= closestWay->movementCost() / way->getPath().movementCost();
 				= closestWay->movementCost() / way->getPath().movementCost();
@@ -209,7 +213,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
 	{
 	{
 		captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
 		captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
 
 
-		if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL)
+		if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL)
 			captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
 			captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
 	}
 	}
 
 

+ 236 - 117
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -21,8 +21,6 @@
 #include "../Goals/CaptureObject.h"
 #include "../Goals/CaptureObject.h"
 #include "../Markers/DefendTown.h"
 #include "../Markers/DefendTown.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
-#include "lib/mapping/CMap.h" //for victory conditions
-#include "lib/CPathfinder.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -51,37 +49,119 @@ Goals::TGoalVec DefenceBehavior::decompose() const
 	return tasks;
 	return tasks;
 }
 }
 
 
-void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
+bool isTreatUnderControl(const CGTownInstance * town, const HitMapInfo & treat, const std::vector<AIPath> & paths)
 {
 {
-	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
-
-	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
-	auto treats = { treatNode.maximumDanger, treatNode.fastestDanger };
-
 	int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
 	int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
 
 
-	if(town->garrisonHero)
+	for(const AIPath & path : paths)
 	{
 	{
-		if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
+		bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
+		bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
+
+		if(treatIsWeak && !needToSaveGrowth)
 		{
 		{
-			if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
+			if((path.exchangeCount == 1 && path.turn() < treat.turn)
+				|| path.turn() < treat.turn - 1
+				|| (path.turn() < treat.turn && treat.turn >= 2))
 			{
 			{
+#if NKAI_TRACE_LEVEL >= 1
 				logAi->trace(
 				logAi->trace(
-					"Extracting hero %s from garrison of town %s",
-					town->garrisonHero->getNameTranslated(),
-					town->getNameTranslated());
-
-				tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
+					"Hero %s can eliminate danger for town %s using path %s.",
+					path.targetHero->getObjectName(),
+					town->getObjectName(),
+					path.toString());
+#endif
 
 
-				return;
+				return true;
 			}
 			}
 		}
 		}
+	}
+
+	return false;
+}
+
+void handleCounterAttack(
+	const CGTownInstance * town,
+	const HitMapInfo & treat,
+	const HitMapInfo & maximumDanger,
+	Goals::TGoalVec & tasks)
+{
+	if(treat.hero.validAndSet()
+		&& treat.turn <= 1
+		&& (treat.danger == maximumDanger.danger || treat.turn < maximumDanger.turn))
+	{
+		auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
+		auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
+
+		for(int i = 0; i < heroCapturingPaths.size(); i++)
+		{
+			AIPath & path = heroCapturingPaths[i];
+			TSubgoal goal = goals[i];
+
+			if(!goal || goal->invalid() || !goal->isElementar()) continue;
+
+			Composition composition;
+
+			composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
+
+			tasks.push_back(Goals::sptr(composition));
+		}
+	}
+}
 
 
+bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks)
+{
+	if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
+	{
 		logAi->trace(
 		logAi->trace(
 			"Hero %s in garrison of town %s is suposed to defend the town",
 			"Hero %s in garrison of town %s is suposed to defend the town",
 			town->garrisonHero->getNameTranslated(),
 			town->garrisonHero->getNameTranslated(),
 			town->getNameTranslated());
 			town->getNameTranslated());
 
 
+		return true;
+	}
+
+	if(!town->visitingHero)
+	{
+		if(cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
+		{
+			logAi->trace(
+				"Extracting hero %s from garrison of town %s",
+				town->garrisonHero->getNameTranslated(),
+				town->getNameTranslated());
+
+			tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
+
+			return true;
+		}
+		else if(ai->nullkiller->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
+		{
+			auto armyDismissLimit = 1000;
+			auto heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(armyDismissLimit);
+
+			if(heroToDismiss)
+			{
+				tasks.push_back(Goals::sptr(Goals::DismissHero(heroToDismiss).setpriority(5)));
+
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
+{
+	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
+
+	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
+	std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
+	
+	treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
+
+	if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks))
+	{
 		return;
 		return;
 	}
 	}
 
 
@@ -111,103 +191,15 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			std::to_string(treat.turn),
 			std::to_string(treat.turn),
 			treat.hero->getNameTranslated());
 			treat.hero->getNameTranslated());
 
 
-		bool treatIsUnderControl = false;
+		handleCounterAttack(town, treat, treatNode.maximumDanger, tasks);
 
 
-		for(AIPath & path : paths)
+		if(isTreatUnderControl(town, treat, paths))
 		{
 		{
-			if(town->visitingHero && path.targetHero != town->visitingHero.get())
-				continue;
-
-			if(town->visitingHero && path.getHeroStrength() < town->visitingHero->getHeroStrength())
-				continue;
-
-			if(treat.hero.validAndSet()
-				&& treat.turn <= 1
-				&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn)
-				&& isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
-			{
-				Composition composition;
-
-				composition.addNext(DefendTown(town, treat, path)).addNext(CaptureObject(treat.hero.get()));
-
-				tasks.push_back(Goals::sptr(composition));
-			}
-
-			bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
-			bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
-
-			if(treatIsWeak && !needToSaveGrowth)
-			{
-				if((path.exchangeCount == 1 && path.turn() < treat.turn)
-					|| path.turn() < treat.turn - 1
-					|| (path.turn() < treat.turn && treat.turn >= 2))
-				{
-#if NKAI_TRACE_LEVEL >= 1
-					logAi->trace(
-						"Hero %s can eliminate danger for town %s using path %s.",
-						path.targetHero->getObjectName(),
-						town->getObjectName(),
-						path.toString());
-#endif
-
-					treatIsUnderControl = true;
-
-					break;
-				}
-			}
-		}
-
-		if(treatIsUnderControl)
 			continue;
 			continue;
-
-		if(!town->visitingHero
-			&& town->hasBuilt(BuildingID::TAVERN)
-			&& cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST)
-		{
-			auto heroesInTavern = cb->getAvailableHeroes(town);
-
-			for(auto hero : heroesInTavern)
-			{
-				if(hero->getTotalStrength() > treat.danger)
-				{
-					auto myHeroes = cb->getHeroesInfo();
-
-					if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
-					{
-#if NKAI_TRACE_LEVEL >= 1
-						logAi->trace("Hero %s can be recruited to defend %s", hero->getObjectName(), town->getObjectName());
-#endif
-						tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(1)));
-						continue;
-					}
-					else
-					{
-						const CGHeroInstance * weakestHero = nullptr;
-
-						for(auto existingHero : myHeroes)
-						{
-							if(ai->nullkiller->isHeroLocked(existingHero)
-								|| existingHero->getArmyStrength() > hero->getArmyStrength()
-								|| ai->nullkiller->heroManager->getHeroRole(existingHero) == HeroRole::MAIN
-								|| existingHero->movement
-								|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
-								continue;
-
-							if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
-							{
-								weakestHero = existingHero;
-							}
-
-							if(weakestHero)
-							{
-								tasks.push_back(Goals::sptr(Goals::DismissHero(weakestHero)));
-							}
-						}
-					}
-				}
-			}
 		}
 		}
 
 
+		evaluateRecruitingHero(tasks, treat, town);
+
 		if(paths.empty())
 		if(paths.empty())
 		{
 		{
 			logAi->trace("No ways to defend town %s", town->getNameTranslated());
 			logAi->trace("No ways to defend town %s", town->getNameTranslated());
@@ -231,6 +223,22 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				path.movementCost(),
 				path.movementCost(),
 				path.toString());
 				path.toString());
 #endif
 #endif
+
+			auto townDefenseStrength = town->garrisonHero
+				? town->garrisonHero->getTotalStrength()
+				: (town->visitingHero ? town->visitingHero->getTotalStrength() : town->getUpperArmy()->getArmyStrength());
+
+			if(town->visitingHero && path.targetHero == town->visitingHero.get())
+			{
+				if(path.getHeroStrength() < townDefenseStrength)
+					continue;
+			}
+			else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
+			{
+				if(path.getHeroStrength() < townDefenseStrength)
+					continue;
+			}
+
 			if(path.turn() <= treat.turn - 2)
 			if(path.turn() <= treat.turn - 2)
 			{
 			{
 #if NKAI_TRACE_LEVEL >= 1
 #if NKAI_TRACE_LEVEL >= 1
@@ -277,9 +285,11 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				tasks.push_back(
 				tasks.push_back(
 					Goals::sptr(Composition()
 					Goals::sptr(Composition()
 						.addNext(DefendTown(town, treat, path))
 						.addNext(DefendTown(town, treat, path))
-						.addNext(ExchangeSwapTownHeroes(town, town->visitingHero.get()))
-						.addNext(ExecuteHeroChain(path, town))
-						.addNext(ExchangeSwapTownHeroes(town, path.targetHero, HeroLockedReason::DEFENCE))));
+						.addNextSequence({
+								sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())),
+								sptr(ExecuteHeroChain(path, town)),
+								sptr(ExchangeSwapTownHeroes(town, path.targetHero, HeroLockedReason::DEFENCE))
+							})));
 
 
 				continue;
 				continue;
 			}
 			}
@@ -315,15 +325,58 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					continue;
 					continue;
 				}
 				}
 			}
 			}
+			Composition composition;
+
+			composition.addNext(DefendTown(town, treat, path));
+			TGoalVec sequence;
+
+			if(town->garrisonHero && path.targetHero == town->garrisonHero.get() && path.exchangeCount == 1)
+			{
+				composition.addNext(ExchangeSwapTownHeroes(town, town->garrisonHero.get(), HeroLockedReason::DEFENCE));
+				tasks.push_back(Goals::sptr(composition));
 
 
 #if NKAI_TRACE_LEVEL >= 1
 #if NKAI_TRACE_LEVEL >= 1
-			logAi->trace("Move %s to defend town %s",
-				path.targetHero->getObjectName(),
-				town->getObjectName());
+				logAi->trace("Locking hero %s in garrison of %s",
+					town->garrisonHero.get()->getObjectName(),
+					town->getObjectName());
 #endif
 #endif
-			Composition composition;
 
 
-			composition.addNext(DefendTown(town, treat, path)).addNext(ExecuteHeroChain(path, town));
+				continue;
+			}
+			else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
+			{
+				if(town->garrisonHero)
+				{
+					if(ai->nullkiller->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT
+						&& town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20)
+					{
+						if(path.turn() == 0)
+							sequence.push_back(sptr(DismissHero(town->visitingHero.get())));
+					}
+					else
+					{
+#if NKAI_TRACE_LEVEL >= 1
+						logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
+							path.targetHero->getObjectName(),
+							town->getObjectName());
+#endif
+						continue;
+					}
+				}
+				else if(path.turn() == 0)
+				{
+					sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())));
+				}
+			}
+
+#if NKAI_TRACE_LEVEL >= 1
+				logAi->trace("Move %s to defend town %s",
+					path.targetHero->getObjectName(),
+					town->getObjectName());
+#endif
+
+			sequence.push_back(sptr(ExecuteHeroChain(path, town)));
+			composition.addNextSequence(sequence);
 
 
 			auto firstBlockedAction = path.getFirstBlockedAction();
 			auto firstBlockedAction = path.getFirstBlockedAction();
 			if(firstBlockedAction)
 			if(firstBlockedAction)
@@ -352,4 +405,70 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	logAi->debug("Found %d tasks", tasks.size());
 	logAi->debug("Found %d tasks", tasks.size());
 }
 }
 
 
+void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & treat, const CGTownInstance * town) const
+{
+	if(town->hasBuilt(BuildingID::TAVERN)
+		&& cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
+	{
+		auto heroesInTavern = cb->getAvailableHeroes(town);
+
+		for(auto hero : heroesInTavern)
+		{
+			if(hero->getTotalStrength() < treat.danger)
+				continue;
+
+			auto myHeroes = cb->getHeroesInfo();
+
+#if NKAI_TRACE_LEVEL >= 1
+			logAi->trace("Hero %s can be recruited to defend %s", hero->getObjectName(), town->getObjectName());
+#endif
+			bool needSwap = false;
+			const CGHeroInstance * heroToDismiss = nullptr;
+
+			if(town->visitingHero)
+			{
+				if(!town->garrisonHero)
+					needSwap = true;
+				else
+				{
+					if(town->visitingHero->getArmyStrength() < town->garrisonHero->getArmyStrength())
+					{
+						if(town->visitingHero->getArmyStrength() >= hero->getArmyStrength())
+							continue;
+
+						heroToDismiss = town->visitingHero.get();
+					}
+					else if(town->garrisonHero->getArmyStrength() >= hero->getArmyStrength())
+						continue;
+					else
+					{
+						needSwap = true;
+						heroToDismiss = town->garrisonHero.get();
+					}
+				}
+			}
+			else if(ai->nullkiller->heroManager->heroCapReached())
+			{
+				heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
+
+				if(!heroToDismiss)
+					continue;
+			}
+
+			TGoalVec sequence;
+			Goals::Composition recruitHeroComposition;
+
+			if(needSwap)
+				sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())));
+
+			if(heroToDismiss)
+				sequence.push_back(sptr(DismissHero(heroToDismiss)));
+
+			sequence.push_back(sptr(Goals::RecruitHero(town, hero)));
+
+			tasks.push_back(sptr(Goals::Composition().addNext(DefendTown(town, treat, hero)).addNextSequence(sequence)));
+		}
+	}
+}
+
 }
 }

+ 5 - 0
AI/Nullkiller/Behaviors/DefenceBehavior.h

@@ -15,8 +15,12 @@
 
 
 namespace NKAI
 namespace NKAI
 {
 {
+
+struct HitMapInfo;
+
 namespace Goals
 namespace Goals
 {
 {
+
 	class DefenceBehavior : public CGoal<DefenceBehavior>
 	class DefenceBehavior : public CGoal<DefenceBehavior>
 	{
 	{
 	public:
 	public:
@@ -35,6 +39,7 @@ namespace Goals
 
 
 	private:
 	private:
 		void evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const;
 		void evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const;
+		void evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & treat, const CGTownInstance * town) const;
 	};
 	};
 }
 }
 
 

+ 105 - 22
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -12,12 +12,13 @@
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/Composition.h"
 #include "../Goals/Composition.h"
+#include "../Goals/RecruitHero.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/ArmyUpgrade.h"
 #include "../Markers/ArmyUpgrade.h"
 #include "GatherArmyBehavior.h"
 #include "GatherArmyBehavior.h"
+#include "CaptureObjectsBehavior.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
-#include "lib/mapping/CMap.h" //for victory conditions
-#include "lib/CPathfinder.h"
+#include "../Goals/ExchangeSwapTownHeroes.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -80,20 +81,27 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 	for(const AIPath & path : paths)
 	for(const AIPath & path : paths)
 	{
 	{
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
-		logAi->trace("Path found %s", path.toString());
+		logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
 #endif
 #endif
 		
 		
-		if(path.containsHero(hero)) continue;
+		if(path.containsHero(hero))
+		{
+#if NKAI_TRACE_LEVEL >= 2
+			logAi->trace("Selfcontaining path. Ignore");
+#endif
+			continue;
+		}
+
+		bool garrisoned = false;
 
 
 		if(path.turn() == 0 && hero->inTownGarrison)
 		if(path.turn() == 0 && hero->inTownGarrison)
 		{
 		{
 #if NKAI_TRACE_LEVEL >= 1
 #if NKAI_TRACE_LEVEL >= 1
-			logAi->trace("Skipping garnisoned hero %s, %s", hero->getObjectName(), pos.toString());
+			garrisoned = true;
 #endif
 #endif
-			continue;
 		}
 		}
 
 
-		if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
+		if(path.turn() > 0 && ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
 		{
 		{
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
 			logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
@@ -111,10 +119,11 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 
 
 		HeroExchange heroExchange(hero, path);
 		HeroExchange heroExchange(hero, path);
 
 
-		float armyValue = (float)heroExchange.getReinforcementArmyStrength() / hero->getArmyStrength();
+		uint64_t armyValue = heroExchange.getReinforcementArmyStrength();
+		float armyRatio = (float)armyValue / hero->getArmyStrength();
 
 
 		// avoid transferring very small amount of army
 		// avoid transferring very small amount of army
-		if(armyValue < 0.1f && armyValue < 20000)
+		if((armyRatio < 0.1f && armyValue < 20000) || armyValue < 500)
 		{
 		{
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Army value is too small.");
 			logAi->trace("Army value is too small.");
@@ -174,7 +183,21 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 			exchangePath.closestWayRatio = 1;
 			exchangePath.closestWayRatio = 1;
 
 
 			composition.addNext(heroExchange);
 			composition.addNext(heroExchange);
-			composition.addNext(exchangePath);
+
+			if(garrisoned && path.turn() == 0)
+			{
+				auto lockReason = ai->nullkiller->getHeroLockedReason(hero);
+
+				composition.addNextSequence({
+					sptr(ExchangeSwapTownHeroes(hero->visitedTown)),
+					sptr(exchangePath),
+					sptr(ExchangeSwapTownHeroes(hero->visitedTown, hero, lockReason))
+				});
+			}
+			else
+			{
+				composition.addNext(exchangePath);
+			}
 
 
 			auto blockedAction = path.getFirstBlockedAction();
 			auto blockedAction = path.getFirstBlockedAction();
 
 
@@ -214,18 +237,42 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 #endif
 #endif
 	
 	
 	auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
 	auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
+	auto goals = CaptureObjectsBehavior::getVisitGoals(paths);
+
 	std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
 	std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
 
 
 #if NKAI_TRACE_LEVEL >= 1
 #if NKAI_TRACE_LEVEL >= 1
 	logAi->trace("Found %d paths", paths.size());
 	logAi->trace("Found %d paths", paths.size());
 #endif
 #endif
 
 
+	bool hasMainAround = false;
+
 	for(const AIPath & path : paths)
 	for(const AIPath & path : paths)
 	{
 	{
+		auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
+
+		if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
+			hasMainAround = true;
+	}
+
+	for(int i = 0; i < paths.size(); i++)
+	{
+		auto & path = paths[i];
+		auto visitGoal = goals[i];
+
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
-		logAi->trace("Path found %s", path.toString());
+		logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
 #endif
 #endif
-		if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
+
+		if(visitGoal->invalid())
+		{
+#if NKAI_TRACE_LEVEL >= 2
+			logAi->trace("Ignore path. Not valid way.");
+#endif
+			continue;
+		}
+
+		if(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1))
 		{
 		{
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Town has visiting hero.");
 			logAi->trace("Ignore path. Town has visiting hero.");
@@ -263,18 +310,58 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 
 
 		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 
 
-		if(!upgrader->garrisonHero && ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
+		if(!upgrader->garrisonHero
+			&& (
+				hasMainAround
+				|| ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
 		{
 		{
-			upgrade.upgradeValue +=	
-				ai->nullkiller->armyManager->howManyReinforcementsCanGet(
+			ArmyUpgradeInfo armyToGetOrBuy;
+
+			armyToGetOrBuy.addArmyToGet(
+				ai->nullkiller->armyManager->getBestArmy(
 					path.targetHero,
 					path.targetHero,
 					path.heroArmy,
 					path.heroArmy,
-					upgrader->getUpperArmy());	
+					upgrader->getUpperArmy()));
+
+			armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
+
+			armyToGetOrBuy.addArmyToBuy(
+				ai->nullkiller->armyManager->toSlotInfo(
+					ai->nullkiller->armyManager->getArmyAvailableToBuy(
+						path.heroArmy,
+						upgrader,
+						ai->nullkiller->getFreeResources(),
+						path.turn())));
+
+			upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
+			upgrade.upgradeCost += armyToGetOrBuy.upgradeCost;
+			vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy);
+
+			if(!upgrade.upgradeValue
+				&& armyToGetOrBuy.upgradeValue > 20000
+				&& ai->nullkiller->heroManager->canRecruitHero(town)
+				&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
+			{
+				for(auto hero : cb->getAvailableHeroes(town))
+				{
+					auto scoutReinforcement =  ai->nullkiller->armyManager->howManyReinforcementsCanBuy(hero, town)
+						+ ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero, town);
+
+					if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
+						&& ai->nullkiller->getFreeGold() >20000
+						&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)
+					{
+						Composition recruitHero;
+
+						recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(town, hero));
+					}
+				}
+			}
 		}
 		}
 
 
 		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 
 
-		if((armyValue < 0.1f && armyValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades
+		if((armyValue < 0.25f && upgrade.upgradeValue < 40000) || upgrade.upgradeValue < 2000) // avoid small upgrades
 		{
 		{
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
 			logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
@@ -299,11 +386,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 
 
 		if(isSafe)
 		if(isSafe)
 		{
 		{
-			ExecuteHeroChain newWay(path, upgrader);
-			
-			newWay.closestWayRatio = 1;
-
-			tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(newWay)));
+			tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(visitGoal)));
 		}
 		}
 	}
 	}
 
 

+ 22 - 3
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -13,8 +13,6 @@
 #include "../AIUtility.h"
 #include "../AIUtility.h"
 #include "../Goals/RecruitHero.h"
 #include "../Goals/RecruitHero.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
-#include "lib/mapping/CMap.h" //for victory conditions
-#include "lib/CPathfinder.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -68,8 +66,29 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
 				}
 				}
 			}
 			}
 
 
+			int treasureSourcesCount = 0;
+
+			for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
+			{
+				if((obj->ID == Obj::RESOURCE)
+					|| obj->ID == Obj::TREASURE_CHEST
+					|| obj->ID == Obj::CAMPFIRE
+					|| isWeeklyRevisitable(obj)
+					|| obj->ID ==Obj::ARTIFACT)
+				{
+					auto tile = obj->visitablePos();
+					auto closestTown = ai->nullkiller->dangerHitMap->getClosestTown(tile);
+
+					if(town == closestTown)
+						treasureSourcesCount++;
+				}
+			}
+
+			if(treasureSourcesCount < 5)
+				continue;
+
 			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)));

+ 2 - 4
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -15,9 +15,7 @@
 #include "../Goals/RecruitHero.h"
 #include "../Goals/RecruitHero.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
-#include "lib/mapping/CMap.h" //for victory conditions
 #include "lib/mapObjects/MapObjects.h" //for victory conditions
 #include "lib/mapObjects/MapObjects.h" //for victory conditions
-#include "lib/CPathfinder.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -76,7 +74,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)
@@ -208,7 +206,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
 		for(const CGTownInstance * town : towns)
 		for(const CGTownInstance * town : towns)
 		{
 		{
 			if(town->garrisonHero
 			if(town->garrisonHero
-				&& town->garrisonHero->movement
+				&& town->garrisonHero->movementPointsRemaining()
 				&& !town->visitingHero
 				&& !town->visitingHero
 				&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
 				&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
 			{
 			{

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -52,6 +52,7 @@ set(Nullkiller_SRCS
 		Behaviors/BuildingBehavior.cpp
 		Behaviors/BuildingBehavior.cpp
 		Behaviors/GatherArmyBehavior.cpp
 		Behaviors/GatherArmyBehavior.cpp
 		Behaviors/ClusterBehavior.cpp
 		Behaviors/ClusterBehavior.cpp
+		Helpers/ArmyFormation.cpp
 		AIGateway.cpp
 		AIGateway.cpp
 )
 )
 
 
@@ -114,6 +115,7 @@ set(Nullkiller_HEADERS
 		Behaviors/BuildingBehavior.h
 		Behaviors/BuildingBehavior.h
 		Behaviors/GatherArmyBehavior.h
 		Behaviors/GatherArmyBehavior.h
 		Behaviors/ClusterBehavior.h
 		Behaviors/ClusterBehavior.h
+		Helpers/ArmyFormation.h
 		AIGateway.h
 		AIGateway.h
 )
 )
 
 

+ 2 - 2
AI/Nullkiller/Engine/AIMemory.cpp

@@ -72,10 +72,10 @@ void AIMemory::markObjectVisited(const CGObjectInstance * obj)
 	// TODO: maybe this logic belongs to CaptureObjects::shouldVisit
 	// TODO: maybe this logic belongs to CaptureObjects::shouldVisit
 	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
 	if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
 	{
 	{
-		if (rewardable->getVisitMode() == CRewardableObject::VISIT_HERO) //we may want to visit it with another hero
+		if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_HERO) //we may want to visit it with another hero
 			return;
 			return;
 
 
-		if (rewardable->getVisitMode() == CRewardableObject::VISIT_BONUS) //or another time
+		if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_BONUS) //or another time
 			return;
 			return;
 	}
 	}
 
 

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

@@ -22,7 +22,7 @@ struct GoalHash
 	}
 	}
 };
 };
 
 
-typedef std::unordered_map<Goals::TSubgoal, Goals::TGoalVec, GoalHash> TGoalHashSet;
+using TGoalHashSet = std::unordered_map<Goals::TSubgoal, Goals::TGoalVec, GoalHash>;
 
 
 class DeepDecomposer
 class DeepDecomposer
 {
 {

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

@@ -53,25 +53,25 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	double shootersStrength = 0;
 	double shootersStrength = 0;
 	ui32 maxSpeed = 0;
 	ui32 maxSpeed = 0;
 
 
-	static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
-	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
+	static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
+	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);
 
 
-	static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
-	static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
+	static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
+	static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);
 
 
-	static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
-	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
+	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
+	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
 
 
 	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);

+ 9 - 8
AI/Nullkiller/Engine/FuzzyHelper.cpp

@@ -10,10 +10,13 @@
 #include "../StdInc.h"
 #include "../StdInc.h"
 #include "FuzzyHelper.h"
 #include "FuzzyHelper.h"
 
 
-#include "../../../lib/mapObjects/CommonConstructors.h"
 #include "../Goals/Goals.h"
 #include "../Goals/Goals.h"
 #include "Nullkiller.h"
 #include "Nullkiller.h"
 
 
+#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
+#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
+#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
+
 namespace NKAI
 namespace NKAI
 {
 {
 
 
@@ -136,7 +139,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 	{
 	{
 		if(!vstd::contains(ai->memory->alreadyVisited, obj))
 		if(!vstd::contains(ai->memory->alreadyVisited, obj))
 			return 0;
 			return 0;
-		FALLTHROUGH;
+		[[fallthrough]];
 	}
 	}
 	case Obj::MONSTER:
 	case Obj::MONSTER:
 	case Obj::HERO:
 	case Obj::HERO:
@@ -147,17 +150,15 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 	case Obj::MINE:
 	case Obj::MINE:
 	case Obj::ABANDONED_MINE:
 	case Obj::ABANDONED_MINE:
 	case Obj::PANDORAS_BOX:
 	case Obj::PANDORAS_BOX:
-	{
-		const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
-		return a->getArmyStrength();
-	}
 	case Obj::CRYPT: //crypt
 	case Obj::CRYPT: //crypt
 	case Obj::CREATURE_BANK: //crebank
 	case Obj::CREATURE_BANK: //crebank
 	case Obj::DRAGON_UTOPIA:
 	case Obj::DRAGON_UTOPIA:
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::DERELICT_SHIP: //derelict ship
 	case Obj::DERELICT_SHIP: //derelict ship
-							 //	case Obj::PYRAMID:
-		return estimateBankDanger(dynamic_cast<const CBank *>(obj));
+	{
+		const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
+		return a->getArmyStrength();
+	}
 	case Obj::PYRAMID:
 	case Obj::PYRAMID:
 	{
 	{
 		if(obj->subID == 0)
 		if(obj->subID == 0)

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

@@ -28,7 +28,7 @@ private:
 	TacticalAdvantageEngine tacticalAdvantageEngine;
 	TacticalAdvantageEngine tacticalAdvantageEngine;
 
 
 public:
 public:
-	FuzzyHelper(const Nullkiller * ai) : ai(ai), tacticalAdvantageEngine() {}
+	FuzzyHelper(const Nullkiller * ai): ai(ai) {}
 
 
 	ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
 	ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
 
 

+ 42 - 13
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -61,6 +61,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
 	armyManager.reset(new ArmyManager(cb.get(), this));
 	armyManager.reset(new ArmyManager(cb.get(), this));
 	heroManager.reset(new HeroManager(cb.get(), this));
 	heroManager.reset(new HeroManager(cb.get(), this));
 	decomposer.reset(new DeepDecomposer());
 	decomposer.reset(new DeepDecomposer());
+	armyFormation.reset(new ArmyFormation(cb, this));
 }
 }
 
 
 Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
 Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
@@ -117,7 +118,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
 void Nullkiller::resetAiState()
 void Nullkiller::resetAiState()
 {
 {
 	lockedResources = TResources();
 	lockedResources = TResources();
-	scanDepth = ScanDepth::FULL;
+	scanDepth = ScanDepth::MAIN_FULL;
 	playerID = ai->playerID;
 	playerID = ai->playerID;
 	lockedHeroes.clear();
 	lockedHeroes.clear();
 	dangerHitMap->reset();
 	dangerHitMap->reset();
@@ -133,10 +134,14 @@ void Nullkiller::updateAiState(int pass, bool fast)
 	activeHero = nullptr;
 	activeHero = nullptr;
 	setTargetObject(-1);
 	setTargetObject(-1);
 
 
+	decomposer->reset();
+	buildAnalyzer->update();
+
 	if(!fast)
 	if(!fast)
 	{
 	{
 		memory->removeInvisibleObjects(cb.get());
 		memory->removeInvisibleObjects(cb.get());
 
 
+		dangerHitMap->calculateTileOwners();
 		dangerHitMap->updateHitMap();
 		dangerHitMap->updateHitMap();
 
 
 		boost::this_thread::interruption_point();
 		boost::this_thread::interruption_point();
@@ -156,11 +161,15 @@ void Nullkiller::updateAiState(int pass, bool fast)
 
 
 		PathfinderSettings cfg;
 		PathfinderSettings cfg;
 		cfg.useHeroChain = useHeroChain;
 		cfg.useHeroChain = useHeroChain;
-		cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
 
 
-		if(scanDepth != ScanDepth::FULL)
+		if(scanDepth == ScanDepth::SMALL)
+		{
+			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
+		}
+
+		if(scanDepth != ScanDepth::ALL_FULL)
 		{
 		{
-			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
+			cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
 		}
 		}
 
 
 		boost::this_thread::interruption_point();
 		boost::this_thread::interruption_point();
@@ -173,8 +182,6 @@ void Nullkiller::updateAiState(int pass, bool fast)
 	}
 	}
 
 
 	armyManager->update();
 	armyManager->update();
-	buildAnalyzer->update();
-	decomposer->reset();
 
 
 	logAi->debug("AI state updated in %ld", timeElapsed(start));
 	logAi->debug("AI state updated in %ld", timeElapsed(start));
 }
 }
@@ -222,7 +229,7 @@ void Nullkiller::makeTurn()
 	boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
 	boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
 
 
 	const int MAX_DEPTH = 10;
 	const int MAX_DEPTH = 10;
-	const float FAST_TASK_MINIMAL_PRIORITY = 0.7;
+	const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
 
 
 	resetAiState();
 	resetAiState();
 
 
@@ -231,8 +238,8 @@ void Nullkiller::makeTurn()
 		updateAiState(i);
 		updateAiState(i);
 
 
 		Goals::TTask bestTask = taskptr(Goals::Invalid());
 		Goals::TTask bestTask = taskptr(Goals::Invalid());
-		
-		do
+
+		for(;i <= MAXPASS; i++)
 		{
 		{
 			Goals::TTaskVec fastTasks = {
 			Goals::TTaskVec fastTasks = {
 				choseBestTask(sptr(BuyArmyBehavior()), 1),
 				choseBestTask(sptr(BuyArmyBehavior()), 1),
@@ -246,7 +253,11 @@ void Nullkiller::makeTurn()
 				executeTask(bestTask);
 				executeTask(bestTask);
 				updateAiState(i, true);
 				updateAiState(i, true);
 			}
 			}
-		} while(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY);
+			else
+			{
+				break;
+			}
+		}
 
 
 		Goals::TTaskVec bestTasks = {
 		Goals::TTaskVec bestTasks = {
 			bestTask,
 			bestTask,
@@ -265,7 +276,6 @@ void Nullkiller::makeTurn()
 		bestTask = choseBestTask(bestTasks);
 		bestTask = choseBestTask(bestTasks);
 
 
 		HeroPtr hero = bestTask->getHero();
 		HeroPtr hero = bestTask->getHero();
-
 		HeroRole heroRole = HeroRole::MAIN;
 		HeroRole heroRole = HeroRole::MAIN;
 
 
 		if(hero.validAndSet())
 		if(hero.validAndSet())
@@ -274,20 +284,39 @@ void Nullkiller::makeTurn()
 		if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
 		if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
 			useHeroChain = false;
 			useHeroChain = false;
 
 
+		// TODO: better to check turn distance here instead of priority
 		if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
 		if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
-			&& scanDepth == ScanDepth::FULL)
+			&& scanDepth == ScanDepth::MAIN_FULL)
 		{
 		{
 			useHeroChain = false;
 			useHeroChain = false;
 			scanDepth = ScanDepth::SMALL;
 			scanDepth = ScanDepth::SMALL;
 
 
 			logAi->trace(
 			logAi->trace(
-				"Goal %s has too low priority %f so increasing scan depth",
+				"Goal %s has low priority %f so decreasing  scan depth to gain performance.",
 				bestTask->toString(),
 				bestTask->toString(),
 				bestTask->priority);
 				bestTask->priority);
 		}
 		}
 
 
 		if(bestTask->priority < MIN_PRIORITY)
 		if(bestTask->priority < MIN_PRIORITY)
 		{
 		{
+			auto heroes = cb->getHeroesInfo();
+			auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
+				{
+					return h->movementPointsRemaining() > 100;
+				});
+
+			if(hasMp && scanDepth != ScanDepth::ALL_FULL)
+			{
+				logAi->trace(
+					"Goal %s has too low priority %f so increasing scan depth to full.",
+					bestTask->toString(),
+					bestTask->priority);
+
+				scanDepth = ScanDepth::ALL_FULL;
+				useHeroChain = false;
+				continue;
+			}
+
 			logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
 			logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
 
 
 			return;
 			return;

+ 7 - 3
AI/Nullkiller/Engine/Nullkiller.h

@@ -18,6 +18,7 @@
 #include "../Analyzers/ArmyManager.h"
 #include "../Analyzers/ArmyManager.h"
 #include "../Analyzers/HeroManager.h"
 #include "../Analyzers/HeroManager.h"
 #include "../Analyzers/ObjectClusterizer.h"
 #include "../Analyzers/ObjectClusterizer.h"
+#include "../Helpers/ArmyFormation.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -39,9 +40,11 @@ enum class HeroLockedReason
 
 
 enum class ScanDepth
 enum class ScanDepth
 {
 {
-	FULL = 0,
+	MAIN_FULL = 0,
 
 
-	SMALL = 1
+	SMALL = 1,
+
+	ALL_FULL = 2
 };
 };
 
 
 class Nullkiller
 class Nullkiller
@@ -67,6 +70,7 @@ public:
 	std::unique_ptr<AIMemory> memory;
 	std::unique_ptr<AIMemory> memory;
 	std::unique_ptr<FuzzyHelper> dangerEvaluator;
 	std::unique_ptr<FuzzyHelper> dangerEvaluator;
 	std::unique_ptr<DeepDecomposer> decomposer;
 	std::unique_ptr<DeepDecomposer> decomposer;
+	std::unique_ptr<ArmyFormation> armyFormation;
 	PlayerColor playerID;
 	PlayerColor playerID;
 	std::shared_ptr<CCallback> cb;
 	std::shared_ptr<CCallback> cb;
 
 
@@ -85,7 +89,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; }
 	ScanDepth getScanDepth() const { return scanDepth; }
 	ScanDepth getScanDepth() const { return scanDepth; }

+ 243 - 109
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -11,11 +11,11 @@
 #include <limits>
 #include <limits>
 
 
 #include "Nullkiller.h"
 #include "Nullkiller.h"
+#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
+#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
+#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/mapObjects/MapObjects.h"
-#include "../../../lib/mapObjects/CommonConstructors.h"
 #include "../../../lib/CCreatureHandler.h"
 #include "../../../lib/CCreatureHandler.h"
-#include "../../../lib/CPathfinder.h"
-#include "../../../lib/CGameStateFwd.h"
 #include "../../../lib/VCMI_Lib.h"
 #include "../../../lib/VCMI_Lib.h"
 #include "../../../lib/StartInfo.h"
 #include "../../../lib/StartInfo.h"
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
@@ -23,6 +23,7 @@
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/BuildThis.h"
 #include "../Goals/BuildThis.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
+#include "../Goals/DismissHero.h"
 #include "../Markers/UnlockCluster.h"
 #include "../Markers/UnlockCluster.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/ArmyUpgrade.h"
 #include "../Markers/ArmyUpgrade.h"
@@ -33,6 +34,7 @@ namespace NKAI
 
 
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
+const float MIN_CRITICAL_VALUE = 2.0f;
 
 
 EvaluationContext::EvaluationContext(const Nullkiller * ai)
 EvaluationContext::EvaluationContext(const Nullkiller * ai)
 	: movementCost(0.0),
 	: movementCost(0.0),
@@ -49,10 +51,16 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
 	turn(0),
 	turn(0),
 	strategicalValue(0),
 	strategicalValue(0),
 	evaluator(ai),
 	evaluator(ai),
-	enemyHeroDangerRatio(0)
+	enemyHeroDangerRatio(0),
+	armyGrowth(0)
 {
 {
 }
 }
 
 
+void EvaluationContext::addNonCriticalStrategicalValue(float value)
+{
+	vstd::amax(strategicalValue, std::min(value, MIN_CRITICAL_VALUE));
+}
+
 PriorityEvaluator::~PriorityEvaluator()
 PriorityEvaluator::~PriorityEvaluator()
 {
 {
 	delete engine;
 	delete engine;
@@ -64,6 +72,7 @@ void PriorityEvaluator::initVisitTile()
 	std::string str = std::string((char *)file.first.get(), file.second);
 	std::string str = std::string((char *)file.first.get(), file.second);
 	engine = fl::FllImporter().fromString(str);
 	engine = fl::FllImporter().fromString(str);
 	armyLossPersentageVariable = engine->getInputVariable("armyLoss");
 	armyLossPersentageVariable = engine->getInputVariable("armyLoss");
+	armyGrowthVariable = engine->getInputVariable("armyGrowth");
 	heroRoleVariable = engine->getInputVariable("heroRole");
 	heroRoleVariable = engine->getInputVariable("heroRole");
 	dangerVariable = engine->getInputVariable("danger");
 	dangerVariable = engine->getInputVariable("danger");
 	turnVariable = engine->getInputVariable("turn");
 	turnVariable = engine->getInputVariable("turn");
@@ -99,7 +108,8 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
 	auto town = cb->getTown(target->id);
 	auto town = cb->getTown(target->id);
 	auto fortLevel = town->fortLevel();
 	auto fortLevel = town->fortLevel();
 
 
-	if(town->hasCapitol()) return booster * 2000;
+	if(town->hasCapitol())
+		return booster * 2000;
 
 
 	// probably well developed town will have city hall
 	// probably well developed town will have city hall
 	if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
 	if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
@@ -148,22 +158,23 @@ 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
 
 
 	return result;
 	return result;
 }
 }
 
 
-uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool checkGold)
+uint64_t getDwellingArmyValue(CCallback * cb, const CGObjectInstance * target, bool checkGold)
 {
 {
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
 	uint64_t score = 0;
 	uint64_t score = 0;
@@ -173,11 +184,32 @@ 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;
+		}
+	}
+
+	return score;
+}
+
+uint64_t getDwellingArmyGrowth(CCallback * cb, const CGObjectInstance * target, PlayerColor myColor)
+{
+	auto dwelling = dynamic_cast<const CGDwelling *>(target);
+	uint64_t score = 0;
+
+	if(dwelling->getOwner() == myColor)
+		return 0;
+
+	for(auto & creLevel : dwelling->creatures)
+	{
+		if(creLevel.second.size())
+		{
+			auto creature = creLevel.second.back().toCreature();
+
+			score += creature->getAIValue() * creature->getGrowth();
 		}
 		}
 	}
 	}
 
 
@@ -194,9 +226,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;
 		}
 		}
 	}
 	}
 
 
@@ -209,14 +241,14 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 		return 1500;
 		return 1500;
 
 
 	auto statsValue =
 	auto statsValue =
-		10 * art->valOfBonuses(Bonus::MOVEMENT, 1)
-		+ 1200 * art->valOfBonuses(Bonus::STACKS_SPEED)
-		+ 700 * art->valOfBonuses(Bonus::MORALE)
-		+ 700 * art->getAttack(false)
-		+ 700 * art->getDefense(false)
-		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
-		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
-		+ 500 * art->valOfBonuses(Bonus::LUCK);
+		10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
+		+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
+		+ 700 * art->valOfBonuses(BonusType::MORALE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
+		+ 500 * art->valOfBonuses(BonusType::LUCK);
 
 
 	auto classValue = 0;
 	auto classValue = 0;
 
 
@@ -246,23 +278,13 @@ uint64_t RewardEvaluator::getArmyReward(
 {
 {
 	const float enemyArmyEliminationRewardRatio = 0.5f;
 	const float enemyArmyEliminationRewardRatio = 0.5f;
 
 
+	auto relations = ai->cb->getPlayerRelations(target->tempOwner, ai->playerID);
+
 	if(!target)
 	if(!target)
 		return 0;
 		return 0;
 
 
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
-	case Obj::TOWN:
-	{
-		auto town = dynamic_cast<const CGTownInstance *>(target);
-		auto fortLevel = town->fortLevel();
-		auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2;
-
-		if(fortLevel < CGTownInstance::CITADEL)
-			return town->hasFort() ? booster * 500 : 0;
-		else
-			return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
-	}
-
 	case Obj::HILL_FORT:
 	case Obj::HILL_FORT:
 		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
 		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
@@ -271,7 +293,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR3:
 	case Obj::CREATURE_GENERATOR3:
 	case Obj::CREATURE_GENERATOR4:
 	case Obj::CREATURE_GENERATOR4:
-		return getDwellingScore(ai->cb.get(), target, checkGold);
+		return getDwellingArmyValue(ai->cb.get(), target, checkGold);
 	case Obj::CRYPT:
 	case Obj::CRYPT:
 	case Obj::SHIPWRECK:
 	case Obj::SHIPWRECK:
 	case Obj::SHIPWRECK_SURVIVOR:
 	case Obj::SHIPWRECK_SURVIVOR:
@@ -282,7 +304,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::DRAGON_UTOPIA:
 	case Obj::DRAGON_UTOPIA:
 		return 10000;
 		return 10000;
 	case Obj::HERO:
 	case Obj::HERO:
-		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return  relations == PlayerRelations::ENEMIES
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
 			: 0;
 			: 0;
 	case Obj::PANDORAS_BOX:
 	case Obj::PANDORAS_BOX:
@@ -292,20 +314,65 @@ uint64_t RewardEvaluator::getArmyReward(
 	}
 	}
 }
 }
 
 
+uint64_t RewardEvaluator::getArmyGrowth(
+	const CGObjectInstance * target,
+	const CGHeroInstance * hero,
+	const CCreatureSet * army) const
+{
+	if(!target)
+		return 0;
+
+	auto relations = ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner);
+
+	if(relations != PlayerRelations::ENEMIES)
+		return 0;
+
+	switch(target->ID)
+	{
+	case Obj::TOWN:
+	{
+		auto town = dynamic_cast<const CGTownInstance *>(target);
+		auto fortLevel = town->fortLevel();
+		auto neutral = !town->getOwner().isValidPlayer();
+		auto booster = isAnotherAi(town, *ai->cb) ||  neutral ? 1 : 2;
+
+		if(fortLevel < CGTownInstance::CITADEL)
+			return town->hasFort() ? booster * 500 : 0;
+		else
+			return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
+	}
+
+	case Obj::CREATURE_GENERATOR1:
+	case Obj::CREATURE_GENERATOR2:
+	case Obj::CREATURE_GENERATOR3:
+	case Obj::CREATURE_GENERATOR4:
+		return getDwellingArmyGrowth(ai->cb.get(), target, hero->getOwner());
+	case Obj::ARTIFACT:
+		// it is not supported now because hero will not sit in town on 7th day but later parts of legion may be counted as army growth as well.
+		return 0;
+	default:
+		return 0;
+	}
+}
+
 int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const
 int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const
 {
 {
 	if(!target)
 	if(!target)
 		return 0;
 		return 0;
+	
+	if(auto * m = dynamic_cast<const IMarket *>(target))
+	{
+		if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
+			return 2000;
+	}
 
 
 	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;
-	case Obj::UNIVERSITY:
-		return 2000;
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR3:
 	case Obj::CREATURE_GENERATOR3:
@@ -333,7 +400,7 @@ float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy
 	  2. The formula quickly approaches 1.0 as hero level increases,
 	  2. The formula quickly approaches 1.0 as hero level increases,
 	  but higher level always means higher value and the minimal value for level 1 hero is 0.5
 	  but higher level always means higher value and the minimal value for level 1 hero is 0.5
 	*/
 	*/
-	return std::min(1.0f, objectValue * 0.9f + (1.0f - (1.0f / (1 + enemy->level))));
+	return std::min(1.5f, objectValue * 0.9f + (1.5f - (1.5f / (1 + enemy->level))));
 }
 }
 
 
 float RewardEvaluator::getResourceRequirementStrength(int resType) const
 float RewardEvaluator::getResourceRequirementStrength(int resType) const
@@ -361,10 +428,26 @@ float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
 		return 0;
 		return 0;
 
 
 	float ratio = dailyIncome[resType] == 0
 	float ratio = dailyIncome[resType] == 0
-		? (float)requiredResources[resType] / 50.0f
-		: (float)requiredResources[resType] / dailyIncome[resType] / 50.0f;
+		? (float)requiredResources[resType] / 10.0f
+		: (float)requiredResources[resType] / dailyIncome[resType] / 20.0f;
 
 
-	return std::min(ratio, 1.0f);
+	return std::min(ratio, 2.0f);
+}
+
+uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
+{
+	uint64_t result = 0;
+
+	for(auto creatureInfo : town->creatures)
+	{
+		if(creatureInfo.second.empty())
+			continue;
+
+		auto creature = creatureInfo.second.back().toCreature();
+		result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
+	}
+
+	return result;
 }
 }
 
 
 float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
 float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
@@ -375,12 +458,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 +474,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);
 			}
 			}
@@ -402,18 +485,28 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	case Obj::TOWN:
 	case Obj::TOWN:
 	{
 	{
 		if(ai->buildAnalyzer->getDevelopmentInfo().empty())
 		if(ai->buildAnalyzer->getDevelopmentInfo().empty())
-			return 1;
+			return 10.0f;
 
 
 		auto town = dynamic_cast<const CGTownInstance *>(target);
 		auto town = dynamic_cast<const CGTownInstance *>(target);
+
+		if(town->getOwner() == ai->playerID)
+		{
+			auto armyIncome = townArmyGrowth(town);
+			auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
+
+			return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
+		}
+
 		auto fortLevel = town->fortLevel();
 		auto fortLevel = town->fortLevel();
-		auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1;
+		auto booster = isAnotherAi(town, *ai->cb) ? 0.4f : 1.0f;
 
 
-		if(town->hasCapitol()) return 1;
+		if(town->hasCapitol())
+			return booster * 1.5;
 
 
 		if(fortLevel < CGTownInstance::CITADEL)
 		if(fortLevel < CGTownInstance::CITADEL)
-			return booster * (town->hasFort() ? 0.6 : 0.4);
+			return booster * (town->hasFort() ? 1.0 : 0.8);
 		else
 		else
-			return booster * (fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8);
+			return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
 	}
 	}
 
 
 	case Obj::HERO:
 	case Obj::HERO:
@@ -458,15 +551,18 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	case Obj::GARDEN_OF_REVELATION:
 	case Obj::GARDEN_OF_REVELATION:
 	case Obj::MARLETTO_TOWER:
 	case Obj::MARLETTO_TOWER:
 	case Obj::MERCENARY_CAMP:
 	case Obj::MERCENARY_CAMP:
-	case Obj::SHRINE_OF_MAGIC_GESTURE:
-	case Obj::SHRINE_OF_MAGIC_INCANTATION:
 	case Obj::TREE_OF_KNOWLEDGE:
 	case Obj::TREE_OF_KNOWLEDGE:
 		return 1;
 		return 1;
 	case Obj::LEARNING_STONE:
 	case Obj::LEARNING_STONE:
 		return 1.0f / std::sqrt(hero->level);
 		return 1.0f / std::sqrt(hero->level);
 	case Obj::ARENA:
 	case Obj::ARENA:
-	case Obj::SHRINE_OF_MAGIC_THOUGHT:
 		return 2;
 		return 2;
+	case Obj::SHRINE_OF_MAGIC_INCANTATION:
+		return 0.2f;
+	case Obj::SHRINE_OF_MAGIC_GESTURE:
+		return 0.3f;
+	case Obj::SHRINE_OF_MAGIC_THOUGHT:
+		return 0.5f;
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
 		return 8;
 		return 8;
 	case Obj::WITCH_HUT:
 	case Obj::WITCH_HUT:
@@ -502,22 +598,23 @@ 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;
 }
 }
 
 
-/// Gets aproximated reward in gold. Daily income is multiplied by 5
 int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
 int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
 {
 {
 	if(!target)
 	if(!target)
 		return 0;
 		return 0;
 
 
+	auto relations = ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner);
+
 	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 +637,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;
@@ -554,7 +651,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 		//Objectively saves us 2500 to hire hero
 		//Objectively saves us 2500 to hire hero
 		return GameConstants::HERO_GOLD_COST;
 		return GameConstants::HERO_GOLD_COST;
 	case Obj::HERO:
 	case Obj::HERO:
-		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return relations == PlayerRelations::ENEMIES
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 			: 0;
 	default:
 	default:
@@ -574,7 +671,8 @@ public:
 
 
 		uint64_t armyStrength = heroExchange.getReinforcementArmyStrength();
 		uint64_t armyStrength = heroExchange.getReinforcementArmyStrength();
 
 
-		evaluationContext.strategicalValue += 0.5f * armyStrength / heroExchange.hero.get()->getArmyStrength();
+		evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero.get()->getArmyStrength());
+		evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero.get());
 	}
 	}
 };
 };
 
 
@@ -591,7 +689,7 @@ public:
 		uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
 		uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
 
 
 		evaluationContext.armyReward += upgradeValue;
 		evaluationContext.armyReward += upgradeValue;
-		evaluationContext.strategicalValue += upgradeValue / (float)armyUpgrade.hero->getArmyStrength();
+		evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
 	}
 	}
 };
 };
 
 
@@ -616,23 +714,6 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
 
 
 class DefendTownEvaluator : public IEvaluationContextBuilder
 class DefendTownEvaluator : public IEvaluationContextBuilder
 {
 {
-private:
-	uint64_t townArmyIncome(const CGTownInstance * town) const
-	{
-		uint64_t result = 0;
-
-		for(auto creatureInfo : town->creatures)
-		{
-			if(creatureInfo.second.empty())
-				continue;
-
-			auto creature = creatureInfo.second.back().toCreature();
-			result += creature->AIValue * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
-		}
-
-		return result;
-	}
-
 public:
 public:
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	{
 	{
@@ -643,22 +724,34 @@ public:
 		const CGTownInstance * town = defendTown.town;
 		const CGTownInstance * town = defendTown.town;
 		auto & treat = defendTown.getTreat();
 		auto & treat = defendTown.getTreat();
 
 
-		auto armyIncome = townArmyIncome(town);
-		auto dailyIncome = town->dailyIncome()[Res::GOLD];
-
-		auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
-
-		if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
-			strategicalValue = 1;
+		auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
 
 
 		float multiplier = 1;
 		float multiplier = 1;
 
 
 		if(treat.turn < defendTown.getTurn())
 		if(treat.turn < defendTown.getTurn())
 			multiplier /= 1 + (defendTown.getTurn() - treat.turn);
 			multiplier /= 1 + (defendTown.getTurn() - treat.turn);
 
 
-		evaluationContext.armyReward += armyIncome * multiplier;
+		multiplier /= 1.0f + treat.turn / 5.0f;
+
+		if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
+		{
+			auto ourSpeed = defendTown.hero->movementPointsLimit(true);
+			auto enemySpeed = treat.hero->movementPointsLimit(true);
+
+			if(enemySpeed > ourSpeed) multiplier *= 0.7f;
+		}
+
+		auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
+		auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town);
+
+		evaluationContext.armyGrowth += armyGrowth * multiplier;
 		evaluationContext.goldReward += dailyIncome * 5 * multiplier;
 		evaluationContext.goldReward += dailyIncome * 5 * multiplier;
-		evaluationContext.strategicalValue += strategicalValue * multiplier;
+
+		if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
+			vstd::amax(evaluationContext.strategicalValue, 2.5f * multiplier * strategicalValue);
+		else
+			evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
+
 		vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
 		vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
 		addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
 		addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
 	}
 	}
@@ -704,18 +797,22 @@ public:
 		auto army = path.heroArmy;
 		auto army = path.heroArmy;
 
 
 		const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
 		const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
+		auto heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr);
+
+		if(heroRole == HeroRole::MAIN)
+			evaluationContext.heroRole = heroRole;
 
 
-		if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
+		if (target)
 		{
 		{
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
-			evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole);
-			evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target);
+			evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
+			evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
+			evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
 			evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
 			evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
 		}
 		}
 
 
 		vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
 		vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
-		evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr);
 		addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
 		addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
 		vstd::amax(evaluationContext.turn, path.turn());
 		vstd::amax(evaluationContext.turn, path.turn());
 	}
 	}
@@ -755,7 +852,7 @@ public:
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost;
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost;
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
 			evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
 			evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
-			evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target) / boost;
+			evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
 			evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
 			evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
 			evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
 			evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
 			evaluationContext.movementCost += objInfo.second.movementCost / boost;
 			evaluationContext.movementCost += objInfo.second.movementCost / boost;
@@ -784,7 +881,7 @@ public:
 		if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
 		if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
 		{
 		{
 			auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
 			auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
-			auto mpLeft = garrisonHero->movement / (float)garrisonHero->maxMovePoints(true);
+			auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
 
 
 			evaluationContext.movementCost += mpLeft;
 			evaluationContext.movementCost += mpLeft;
 			evaluationContext.movementCostByRole[defenderRole] += mpLeft;
 			evaluationContext.movementCostByRole[defenderRole] += mpLeft;
@@ -793,6 +890,31 @@ public:
 	}
 	}
 };
 };
 
 
+class DismissHeroContextBuilder : public IEvaluationContextBuilder
+{
+private:
+	const Nullkiller * ai;
+
+public:
+	DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
+
+	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
+	{
+		if(task->goalType != Goals::DISMISS_HERO)
+			return;
+
+		Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
+		const CGHeroInstance * dismissedHero = dismissCommand.getHero().get();
+
+		auto role = ai->heroManager->getHeroRole(dismissedHero);
+		auto mpLeft = dismissedHero->movementPointsRemaining();
+			
+		evaluationContext.movementCost += mpLeft;
+		evaluationContext.movementCostByRole[role] += mpLeft;
+		evaluationContext.goldCost += GameConstants::HERO_GOLD_COST + getArmyCost(dismissedHero);
+	}
+};
+
 class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
 class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
 {
 {
 public:
 public:
@@ -804,50 +926,58 @@ 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];
+		evaluationContext.closestWayRatio = 1;
 
 
 		if(bi.creatureID != CreatureID::NONE)
 		if(bi.creatureID != CreatureID::NONE)
 		{
 		{
-			evaluationContext.strategicalValue += buildThis.townInfo.armyStrength / 50000.0;
+			evaluationContext.addNonCriticalStrategicalValue(buildThis.townInfo.armyStrength / 50000.0);
 
 
 			if(bi.baseCreatureID == bi.creatureID)
 			if(bi.baseCreatureID == bi.creatureID)
 			{
 			{
-				evaluationContext.strategicalValue += (0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount;
+				evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
 				evaluationContext.armyReward += bi.armyStrength;
 				evaluationContext.armyReward += bi.armyStrength;
 			}
 			}
 			else
 			else
 			{
 			{
 				auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
 				auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
 				
 				
-				evaluationContext.strategicalValue += potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount;
+				evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
 				evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
 				evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
 			}
 			}
 		}
 		}
 		else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
 		else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
 		{
 		{
-			evaluationContext.strategicalValue += buildThis.town->creatures.size() * 0.2f;
+			evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
 			evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
 			evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
 		}
 		}
-		else
+		else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
+		{
+			evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
+		}
+		
+		if(evaluationContext.goldReward)
 		{
 		{
 			auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
 			auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
 
 
-			evaluationContext.strategicalValue += evaluationContext.goldReward * goldPreasure / 3500.0f / bi.prerequisitesCount;
+			evaluationContext.addNonCriticalStrategicalValue(evaluationContext.goldReward * goldPreasure / 3500.0f / bi.prerequisitesCount);
 		}
 		}
 
 
 		if(bi.notEnoughRes && bi.prerequisitesCount == 1)
 		if(bi.notEnoughRes && bi.prerequisitesCount == 1)
 		{
 		{
-			evaluationContext.strategicalValue /= 2;
+			evaluationContext.strategicalValue /= 3;
+			evaluationContext.movementCostByRole[evaluationContext.heroRole] += 5;
+			evaluationContext.turn += 5;
 		}
 		}
 	}
 	}
 };
 };
 
 
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 {
 {
-	if(ai->buildAnalyzer->hasAnyBuilding(town->alignment, bi.id))
+	if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
 		return 0;
 		return 0;
 
 
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
@@ -867,6 +997,7 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
 	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
 	evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
+	evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
 }
 }
 
 
 EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
 EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
@@ -904,6 +1035,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		+ (evaluationContext.armyReward > 0 ? 1 : 0)
 		+ (evaluationContext.armyReward > 0 ? 1 : 0)
 		+ (evaluationContext.skillReward > 0 ? 1 : 0)
 		+ (evaluationContext.skillReward > 0 ? 1 : 0)
 		+ (evaluationContext.strategicalValue > 0 ? 1 : 0);
 		+ (evaluationContext.strategicalValue > 0 ? 1 : 0);
+
+	float goldRewardPerTurn = evaluationContext.goldReward / std::log2f(2 + evaluationContext.movementCost * 10);
 	
 	
 	double result = 0;
 	double result = 0;
 
 
@@ -913,15 +1046,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		heroRoleVariable->setValue(evaluationContext.heroRole);
 		heroRoleVariable->setValue(evaluationContext.heroRole);
 		mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
 		mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
 		scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
 		scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
-		goldRewardVariable->setValue(evaluationContext.goldReward);
+		goldRewardVariable->setValue(goldRewardPerTurn);
 		armyRewardVariable->setValue(evaluationContext.armyReward);
 		armyRewardVariable->setValue(evaluationContext.armyReward);
+		armyGrowthVariable->setValue(evaluationContext.armyGrowth);
 		skillRewardVariable->setValue(evaluationContext.skillReward);
 		skillRewardVariable->setValue(evaluationContext.skillReward);
 		dangerVariable->setValue(evaluationContext.danger);
 		dangerVariable->setValue(evaluationContext.danger);
 		rewardTypeVariable->setValue(rewardType);
 		rewardTypeVariable->setValue(rewardType);
 		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);
 
 
@@ -935,13 +1069,13 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 	}
 	}
 
 
 #if NKAI_TRACE_LEVEL >= 2
 #if NKAI_TRACE_LEVEL >= 2
-	logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
+	logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
 		task->toString(),
 		task->toString(),
 		evaluationContext.armyLossPersentage,
 		evaluationContext.armyLossPersentage,
 		(int)evaluationContext.turn,
 		(int)evaluationContext.turn,
 		evaluationContext.movementCostByRole[HeroRole::MAIN],
 		evaluationContext.movementCostByRole[HeroRole::MAIN],
 		evaluationContext.movementCostByRole[HeroRole::SCOUT],
 		evaluationContext.movementCostByRole[HeroRole::SCOUT],
-		evaluationContext.goldReward,
+		goldRewardPerTurn,
 		evaluationContext.goldCost,
 		evaluationContext.goldCost,
 		evaluationContext.armyReward,
 		evaluationContext.armyReward,
 		evaluationContext.danger,
 		evaluationContext.danger,

+ 6 - 0
AI/Nullkiller/Engine/PriorityEvaluator.h

@@ -33,6 +33,7 @@ public:
 	RewardEvaluator(const Nullkiller * ai) : ai(ai) {}
 	RewardEvaluator(const Nullkiller * ai) : ai(ai) {}
 
 
 	uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold) const;
 	uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold) const;
+	uint64_t getArmyGrowth(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
 	float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
 	float getResourceRequirementStrength(int resType) const;
 	float getResourceRequirementStrength(int resType) const;
@@ -43,6 +44,7 @@ public:
 	int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
 	int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
 	uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
 	uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
 	const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
 	const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
+	uint64_t townArmyGrowth(const CGTownInstance * town) const;
 };
 };
 
 
 struct DLL_EXPORT EvaluationContext
 struct DLL_EXPORT EvaluationContext
@@ -54,6 +56,7 @@ struct DLL_EXPORT EvaluationContext
 	float closestWayRatio;
 	float closestWayRatio;
 	float armyLossPersentage;
 	float armyLossPersentage;
 	float armyReward;
 	float armyReward;
+	uint64_t armyGrowth;
 	int32_t goldReward;
 	int32_t goldReward;
 	int32_t goldCost;
 	int32_t goldCost;
 	float skillReward;
 	float skillReward;
@@ -64,6 +67,8 @@ struct DLL_EXPORT EvaluationContext
 	float enemyHeroDangerRatio;
 	float enemyHeroDangerRatio;
 
 
 	EvaluationContext(const Nullkiller * ai);
 	EvaluationContext(const Nullkiller * ai);
+
+	void addNonCriticalStrategicalValue(float value);
 };
 };
 
 
 class IEvaluationContextBuilder
 class IEvaluationContextBuilder
@@ -95,6 +100,7 @@ private:
 	fl::InputVariable * turnVariable;
 	fl::InputVariable * turnVariable;
 	fl::InputVariable * goldRewardVariable;
 	fl::InputVariable * goldRewardVariable;
 	fl::InputVariable * armyRewardVariable;
 	fl::InputVariable * armyRewardVariable;
+	fl::InputVariable * armyGrowthVariable;
 	fl::InputVariable * dangerVariable;
 	fl::InputVariable * dangerVariable;
 	fl::InputVariable * skillRewardVariable;
 	fl::InputVariable * skillRewardVariable;
 	fl::InputVariable * strategicalValueVariable;
 	fl::InputVariable * strategicalValueVariable;

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

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "AbstractGoal.h"
 #include "AbstractGoal.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/StringConstants.h"
 #include "../../../lib/StringConstants.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -60,7 +58,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();

+ 4 - 5
AI/Nullkiller/Goals/AbstractGoal.h

@@ -81,9 +81,9 @@ namespace Goals
 		bool operator<(const TSubgoal & rhs) const;
 		bool operator<(const TSubgoal & rhs) const;
 	};
 	};
 
 
-	typedef std::shared_ptr<ITask> TTask;
-	typedef std::vector<TTask> TTaskVec;
-	typedef std::vector<TSubgoal> TGoalVec;
+	using TTask = std::shared_ptr<ITask>;
+	using TTaskVec = std::vector<TTask>;
+	using TGoalVec = std::vector<TSubgoal>;
 
 
 	//method chaining + clone pattern
 	//method chaining + clone pattern
 #define SETTER(type, field) AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
 #define SETTER(type, field) AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
@@ -107,8 +107,7 @@ namespace Goals
 		const CGTownInstance *town; SETTER(CGTownInstance *, town)
 		const CGTownInstance *town; SETTER(CGTownInstance *, town)
 		int bid; SETTER(int, bid)
 		int bid; SETTER(int, bid)
 
 
-		AbstractGoal(EGoals goal = EGoals::INVALID)
-			: goalType(goal), hero()
+		AbstractGoal(EGoals goal = EGoals::INVALID): goalType(goal)
 		{
 		{
 			isAbstract = false;
 			isAbstract = false;
 			value = 0;
 			value = 0;

+ 0 - 2
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "AdventureSpellCast.h"
 #include "AdventureSpellCast.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 7 - 7
AI/Nullkiller/Goals/Build.cpp

@@ -42,10 +42,10 @@ TGoalVec Build::getAllPossibleSubgoals()
 		auto expensiveBuilding = ai->ah->expensiveBuilding();
 		auto expensiveBuilding = ai->ah->expensiveBuilding();
 
 
 		//handling for early town development to save money and focus on income
 		//handling for early town development to save money and focus on income
-		if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.is_initialized())
+		if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.has_value())
 		{
 		{
-			auto potentialBuilding = expensiveBuilding.get();
-			switch(expensiveBuilding.get().bid)
+			auto potentialBuilding = expensiveBuilding.value();
+			switch(expensiveBuilding.value().bid)
 			{
 			{
 			case BuildingID::TOWN_HALL:
 			case BuildingID::TOWN_HALL:
 			case BuildingID::CITY_HALL:
 			case BuildingID::CITY_HALL:
@@ -61,15 +61,15 @@ TGoalVec Build::getAllPossibleSubgoals()
 			}
 			}
 		}
 		}
 
 
-		if(immediateBuilding.is_initialized())
+		if(immediateBuilding.has_value())
 		{
 		{
-			ret.push_back(sptr(BuildThis(immediateBuilding.get().bid, t).setpriority(2))); //prioritize buildings we can build quick
+			ret.push_back(sptr(BuildThis(immediateBuilding.value().bid, t).setpriority(2))); //prioritize buildings we can build quick
 		}
 		}
 		else //try build later
 		else //try build later
 		{
 		{
-			if(expensiveBuilding.is_initialized())
+			if(expensiveBuilding.has_value())
 			{
 			{
-				auto potentialBuilding = expensiveBuilding.get(); //gather resources for any we can't afford
+				auto potentialBuilding = expensiveBuilding.value(); //gather resources for any we can't afford
 				auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(0.5)));
 				auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(0.5)));
 				ret.push_back(goal);
 				ret.push_back(goal);
 			}
 			}

+ 4 - 7
AI/Nullkiller/Goals/BuildBoat.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "BuildBoat.h"
 #include "BuildBoat.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -24,7 +22,7 @@ using namespace Goals;
 
 
 bool BuildBoat::operator==(const BuildBoat & other) const
 bool BuildBoat::operator==(const BuildBoat & other) const
 {
 {
-	return shipyard->o->id == other.shipyard->o->id;
+	return shipyard == other.shipyard;
 }
 }
 //
 //
 //TSubgoal BuildBoat::decomposeSingle() const
 //TSubgoal BuildBoat::decomposeSingle() const
@@ -55,7 +53,7 @@ void BuildBoat::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("Can not afford boat");
 		throw cannotFulfillGoalException("Can not afford boat");
 	}
 	}
 
 
-	if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
+	if(cb->getPlayerRelations(ai->playerID, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
 	{
 	{
 		throw cannotFulfillGoalException("Can not build boat in enemy shipyard");
 		throw cannotFulfillGoalException("Can not build boat in enemy shipyard");
 	}
 	}
@@ -66,9 +64,8 @@ void BuildBoat::accept(AIGateway * ai)
 	}
 	}
 
 
 	logAi->trace(
 	logAi->trace(
-		"Building boat at shipyard %s located at %s, estimated boat position %s", 
-		shipyard->o->getObjectName(),
-		shipyard->o->visitablePos().toString(),
+		"Building boat at shipyard located at %s, estimated boat position %s",
+		shipyard->getObject()->visitablePos().toString(),
 		shipyard->bestLocation().toString());
 		shipyard->bestLocation().toString());
 
 
 	cb->buildBoat(shipyard);
 	cb->buildBoat(shipyard);

+ 0 - 2
AI/Nullkiller/Goals/BuildThis.cpp

@@ -11,8 +11,6 @@
 #include "BuildThis.h"
 #include "BuildThis.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/StringConstants.h"
 #include "../../../lib/StringConstants.h"
 
 
 
 

+ 3 - 3
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();
 		}
 		}
 	}
 	}
 
 
@@ -71,7 +71,7 @@ void BuyArmy::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("No creatures to buy.");
 		throw cannotFulfillGoalException("No creatures to buy.");
 	}
 	}
 
 
-	if(town->visitingHero)
+	if(town->visitingHero && !town->garrisonHero)
 	{
 	{
 		ai->moveHeroToTile(town->visitablePos(), town->visitingHero.get());
 		ai->moveHeroToTile(town->visitablePos(), town->visitingHero.get());
 	}
 	}

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

@@ -11,8 +11,6 @@
 #include "CompleteQuest.h"
 #include "CompleteQuest.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/VCMI_Lib.h"
 #include "../../../lib/VCMI_Lib.h"
 #include "../../../lib/CGeneralTextHandler.h"
 #include "../../../lib/CGeneralTextHandler.h"
 
 
@@ -214,7 +212,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/CompleteQuest.h

@@ -12,7 +12,7 @@
 #include "../AIUtility.h"
 #include "../AIUtility.h"
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 #include "../Goals/CGoal.h"
 #include "../Goals/CGoal.h"
-#include "../../../lib/CGameState.h"
+#include "../../../lib/gameState/QuestInfo.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 49 - 11
AI/Nullkiller/Goals/Composition.cpp

@@ -11,8 +11,6 @@
 #include "Composition.h"
 #include "Composition.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/StringConstants.h"
 #include "../../../lib/StringConstants.h"
 
 
 
 
@@ -33,9 +31,17 @@ std::string Composition::toString() const
 {
 {
 	std::string result = "Composition";
 	std::string result = "Composition";
 
 
-	for(auto goal : subtasks)
+	for(auto step : subtasks)
 	{
 	{
-		result += " " + goal->toString();
+		result += "[";
+		for(auto goal : step)
+		{
+			if(goal->isElementar())
+				result +=  goal->toString() + " => ";
+			else
+				result += goal->toString() + ", ";
+		}
+		result += "] ";
 	}
 	}
 
 
 	return result;
 	return result;
@@ -43,17 +49,34 @@ std::string Composition::toString() const
 
 
 void Composition::accept(AIGateway * ai)
 void Composition::accept(AIGateway * ai)
 {
 {
-	taskptr(*subtasks.back())->accept(ai);
+	for(auto task : subtasks.back())
+	{
+		if(task->isElementar())
+		{
+			taskptr(*task)->accept(ai);
+		}
+		else
+		{
+			break;
+		}
+	}
 }
 }
 
 
 TGoalVec Composition::decompose() const
 TGoalVec Composition::decompose() const
 {
 {
-	return subtasks;
+	TGoalVec result;
+
+	for(const TGoalVec & step : subtasks)
+		vstd::concatenate(result, step);
+
+	return result;
 }
 }
 
 
-Composition & Composition::addNext(const AbstractGoal & goal)
+Composition & Composition::addNextSequence(const TGoalVec & taskSequence)
 {
 {
-	return addNext(sptr(goal));
+	subtasks.push_back(taskSequence);
+
+	return *this;
 }
 }
 
 
 Composition & Composition::addNext(TSubgoal goal)
 Composition & Composition::addNext(TSubgoal goal)
@@ -66,20 +89,35 @@ Composition & Composition::addNext(TSubgoal goal)
 	}
 	}
 	else
 	else
 	{
 	{
-		subtasks.push_back(goal);
+		subtasks.push_back({goal});
 	}
 	}
 
 
 	return *this;
 	return *this;
 }
 }
 
 
+Composition & Composition::addNext(const AbstractGoal & goal)
+{
+	return addNext(sptr(goal));
+}
+
 bool Composition::isElementar() const
 bool Composition::isElementar() const
 {
 {
-	return subtasks.back()->isElementar();
+	return subtasks.back().front()->isElementar();
 }
 }
 
 
 int Composition::getHeroExchangeCount() const
 int Composition::getHeroExchangeCount() const
 {
 {
-	return isElementar() ? taskptr(*subtasks.back())->getHeroExchangeCount() : 0;
+	auto result = 0;
+
+	for(auto task : subtasks.back())
+	{
+		if(task->isElementar())
+		{
+			result += taskptr(*task)->getHeroExchangeCount();
+		}
+	}
+	
+	return result;
 }
 }
 
 
 }
 }

+ 2 - 6
AI/Nullkiller/Goals/Composition.h

@@ -18,7 +18,7 @@ namespace Goals
 	class DLL_EXPORT Composition : public ElementarGoal<Composition>
 	class DLL_EXPORT Composition : public ElementarGoal<Composition>
 	{
 	{
 	private:
 	private:
-		TGoalVec subtasks;
+		std::vector<TGoalVec> subtasks; // things we want to do now
 
 
 	public:
 	public:
 		Composition()
 		Composition()
@@ -26,16 +26,12 @@ namespace Goals
 		{
 		{
 		}
 		}
 
 
-		Composition(TGoalVec subtasks)
-			: ElementarGoal(Goals::COMPOSITION), subtasks(subtasks)
-		{
-		}
-
 		virtual bool operator==(const Composition & other) const override;
 		virtual bool operator==(const Composition & other) const override;
 		virtual std::string toString() const override;
 		virtual std::string toString() const override;
 		void accept(AIGateway * ai) override;
 		void accept(AIGateway * ai) override;
 		Composition & addNext(const AbstractGoal & goal);
 		Composition & addNext(const AbstractGoal & goal);
 		Composition & addNext(TSubgoal goal);
 		Composition & addNext(TSubgoal goal);
+		Composition & addNextSequence(const TGoalVec & taskSequence);
 		virtual TGoalVec decompose() const override;
 		virtual TGoalVec decompose() const override;
 		virtual bool isElementar() const override;
 		virtual bool isElementar() const override;
 		virtual int getHeroExchangeCount() const override;
 		virtual int getHeroExchangeCount() const override;

+ 0 - 2
AI/Nullkiller/Goals/DismissHero.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "DismissHero.h"
 #include "DismissHero.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 0 - 2
AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp

@@ -11,8 +11,6 @@
 #include "ExchangeSwapTownHeroes.h"
 #include "ExchangeSwapTownHeroes.h"
 #include "ExecuteHeroChain.h"
 #include "ExecuteHeroChain.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI

+ 21 - 9
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "ExecuteHeroChain.h"
 #include "ExecuteHeroChain.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -54,6 +52,20 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 	ai->nullkiller->setActive(chainPath.targetHero, tile);
 	ai->nullkiller->setActive(chainPath.targetHero, tile);
 	ai->nullkiller->setTargetObject(objid);
 	ai->nullkiller->setTargetObject(objid);
 
 
+	auto targetObject = ai->myCb->getObj(static_cast<ObjectInstanceID>(objid), false);
+
+	if(chainPath.turn() == 0 && targetObject && targetObject->ID == Obj::TOWN)
+	{
+		auto relations = ai->myCb->getPlayerRelations(ai->playerID, targetObject->getOwner());
+
+		if(relations == PlayerRelations::ENEMIES)
+		{
+			ai->nullkiller->armyFormation->rearrangeArmyForSiege(
+				dynamic_cast<const CGTownInstance *>(targetObject),
+				chainPath.targetHero);
+		}
+	}
+
 	std::set<int> blockedIndexes;
 	std::set<int> blockedIndexes;
 
 
 	for(int i = chainPath.nodes.size() - 1; i >= 0; i--)
 	for(int i = chainPath.nodes.size() - 1; i >= 0; i--)
@@ -80,7 +92,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 
 		try
 		try
 		{
 		{
-			if(hero->movement)
+			if(hero->movementPointsRemaining() > 0)
 			{
 			{
 				ai->nullkiller->setActive(hero, node.coord);
 				ai->nullkiller->setActive(hero, node.coord);
 
 
@@ -105,9 +117,9 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 				{
 				{
 					auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);
 					auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);
 
 
-					if(targetNode->accessible == CGPathNode::EAccessibility::NOT_SET
-						|| targetNode->accessible == CGPathNode::EAccessibility::BLOCKED
-						|| targetNode->accessible == CGPathNode::EAccessibility::FLYABLE
+					if(targetNode->accessible == EPathAccessibility::NOT_SET
+						|| targetNode->accessible == EPathAccessibility::BLOCKED
+						|| targetNode->accessible == EPathAccessibility::FLYABLE
 						|| targetNode->turns != 0)
 						|| targetNode->turns != 0)
 					{
 					{
 						logAi->error(
 						logAi->error(
@@ -119,7 +131,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					}
 					}
 				}
 				}
 
 
-				if(hero->movement)
+				if(hero->movementPointsRemaining())
 				{
 				{
 					try
 					try
 					{
 					{
@@ -137,14 +149,14 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 							return;
 							return;
 						}
 						}
 
 
-						if(hero->movement > 0)
+						if(hero->movementPointsRemaining() > 0)
 						{
 						{
 							CGPath path;
 							CGPath path;
 							bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord);
 							bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord);
 
 
 							if(isOk && path.nodes.back().turns > 0)
 							if(isOk && path.nodes.back().turns > 0)
 							{
 							{
-								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movement, node.coord.toString());
+								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node.coord.toString());
 
 
 								ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
 								ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
 								return;
 								return;

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

@@ -97,7 +97,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 
 
 			//Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm
 			//Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm
 			/*auto bid = ai->ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1);
 			/*auto bid = ai->ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1);
-			if (bid.is_initialized())
+			if (bid.has_value())
 			{
 			{
 				auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
 				auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
 				if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
 				if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
@@ -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?
 							}
 							}
 						}
 						}

+ 12 - 11
AI/Nullkiller/Goals/RecruitHero.cpp

@@ -11,8 +11,6 @@
 #include "Goals.h"
 #include "Goals.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/StringConstants.h"
 #include "../../../lib/StringConstants.h"
 
 
 
 
@@ -26,7 +24,10 @@ using namespace Goals;
 
 
 std::string RecruitHero::toString() const
 std::string RecruitHero::toString() const
 {
 {
-	return "Recruit hero at " + town->getNameTranslated();
+	if(heroToBuy)
+		return "Recruit " + heroToBuy->getNameTranslated() + " at " + town->getNameTranslated();
+	else
+		return "Recruit hero at " + town->getNameTranslated();
 }
 }
 
 
 void RecruitHero::accept(AIGateway * ai)
 void RecruitHero::accept(AIGateway * ai)
@@ -47,20 +48,20 @@ void RecruitHero::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
 		throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
 	}
 	}
 
 
-	auto heroToHire = heroes[0];
+	auto heroToHire = heroToBuy;
 
 
-	for(auto hero : heroes)
+	if(!heroToHire)
 	{
 	{
-		if(objid == hero->id.getNum())
+		for(auto hero : heroes)
 		{
 		{
-			heroToHire = hero;
-			break;
+			if(!heroToHire || hero->getTotalStrength() > heroToHire->getTotalStrength())
+				heroToHire = hero;
 		}
 		}
-
-		if(hero->getTotalStrength() > heroToHire->getTotalStrength())
-			heroToHire = hero;
 	}
 	}
 
 
+	if(!heroToHire)
+		throw cannotFulfillGoalException("No hero to hire!");
+
 	if(t->visitingHero)
 	if(t->visitingHero)
 	{
 	{
 		cb->swapGarrisonHero(t);
 		cb->swapGarrisonHero(t);

+ 7 - 5
AI/Nullkiller/Goals/RecruitHero.h

@@ -22,18 +22,20 @@ namespace Goals
 {
 {
 	class DLL_EXPORT RecruitHero : public ElementarGoal<RecruitHero>
 	class DLL_EXPORT RecruitHero : public ElementarGoal<RecruitHero>
 	{
 	{
+	private:
+		const CGHeroInstance * heroToBuy;
+
 	public:
 	public:
 		RecruitHero(const CGTownInstance * townWithTavern, const CGHeroInstance * heroToBuy)
 		RecruitHero(const CGTownInstance * townWithTavern, const CGHeroInstance * heroToBuy)
-			: RecruitHero(townWithTavern)
+			: ElementarGoal(Goals::RECRUIT_HERO), heroToBuy(heroToBuy)
 		{
 		{
-			objid = heroToBuy->id.getNum();
+			town = townWithTavern;
+			priority = 1;
 		}
 		}
 
 
 		RecruitHero(const CGTownInstance * townWithTavern)
 		RecruitHero(const CGTownInstance * townWithTavern)
-			: ElementarGoal(Goals::RECRUIT_HERO)
+			: RecruitHero(townWithTavern, nullptr)
 		{
 		{
-			priority = 1;
-			town = townWithTavern;
 		}
 		}
 
 
 		virtual bool operator==(const RecruitHero & other) const override
 		virtual bool operator==(const RecruitHero & other) const override

+ 0 - 2
AI/Nullkiller/Goals/SaveResources.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "SaveResources.h"
 #include "SaveResources.h"
 #include "../AIGateway.h"
 #include "../AIGateway.h"
-#include "../../../lib/mapping/CMap.h" //for victory conditions
-#include "../../../lib/CPathfinder.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../Behaviors/CaptureObjectsBehavior.h"
 
 
 namespace NKAI
 namespace NKAI

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

+ 68 - 0
AI/Nullkiller/Helpers/ArmyFormation.cpp

@@ -0,0 +1,68 @@
+/*
+* ArmyFormation.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+#include "StdInc.h"
+#include "ArmyFormation.h"
+#include "../../../lib/mapObjects/CGTownInstance.h"
+
+namespace NKAI
+{
+
+void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker)
+{
+	auto freeSlots = attacker->getFreeSlotsQueue();
+
+	while(!freeSlots.empty())
+	{
+		auto weakestCreature = vstd::minElementByFun(attacker->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int
+			{
+				return slot.second->getCount() == 1
+					? std::numeric_limits<int>::max()
+					: slot.second->getCreatureID().toCreature()->getAIValue();
+			});
+
+		if(weakestCreature == attacker->Slots().end() || weakestCreature->second->getCount() == 1)
+		{
+			break;
+		}
+
+		cb->splitStack(attacker, attacker, weakestCreature->first, freeSlots.front(), 1);
+		freeSlots.pop();
+	}
+
+	if(town->fortLevel() > CGTownInstance::FORT)
+	{
+		std::vector<CStackInstance *> stacks;
+
+		for(auto slot : attacker->Slots())
+			stacks.push_back(slot.second);
+
+		boost::sort(
+			stacks,
+			[](CStackInstance * slot1, CStackInstance * slot2) -> bool
+			{
+				auto cre1 = slot1->getCreatureID().toCreature();
+				auto cre2 = slot2->getCreatureID().toCreature();
+				auto flying = cre1->hasBonusOfType(BonusType::FLYING) - cre2->hasBonusOfType(BonusType::FLYING);
+			
+				if(flying != 0) return flying < 0;
+				else return cre1->getAIValue() < cre2->getAIValue();
+			});
+
+		for(int i = 0; i < stacks.size(); i++)
+		{
+			auto pos = vstd::findKey(attacker->Slots(), stacks[i]);
+
+			if(pos.getNum() != i)
+				cb->swapCreatures(attacker, attacker, static_cast<SlotID>(i), pos);
+		}
+	}
+}
+
+}

+ 38 - 0
AI/Nullkiller/Helpers/ArmyFormation.h

@@ -0,0 +1,38 @@
+/*
+* ArmyFormation.h, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+#pragma once
+
+#include "../AIUtility.h"
+
+#include "../../../lib/GameConstants.h"
+#include "../../../lib/VCMI_Lib.h"
+#include "../../../lib/CTownHandler.h"
+#include "../../../lib/CBuildingHandler.h"
+
+namespace NKAI
+{
+
+struct HeroPtr;
+class AIGateway;
+class FuzzyHelper;
+class Nullkiller;
+
+class DLL_EXPORT ArmyFormation
+{
+private:
+	std::shared_ptr<CCallback> cb; //this is enough, but we downcast from CCallback
+
+public:
+	ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {}
+
+	void rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker);
+};
+
+}

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

@@ -23,11 +23,18 @@ 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);
 }
 }
 
 
+ArmyUpgrade::ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
+	: CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue),
+	initialValue(targetMain->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD])
+{
+	sethero(targetMain);
+}
+
 bool ArmyUpgrade::operator==(const ArmyUpgrade & other) const
 bool ArmyUpgrade::operator==(const ArmyUpgrade & other) const
 {
 {
 	return false;
 	return false;

+ 1 - 0
AI/Nullkiller/Markers/ArmyUpgrade.h

@@ -27,6 +27,7 @@ namespace Goals
 
 
 	public:
 	public:
 		ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
 		ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
+		ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
 
 
 		virtual bool operator==(const ArmyUpgrade & other) const override;
 		virtual bool operator==(const ArmyUpgrade & other) const override;
 		virtual std::string toString() const override;
 		virtual std::string toString() const override;

+ 2 - 2
AI/Nullkiller/Markers/DefendTown.cpp

@@ -18,8 +18,8 @@ namespace NKAI
 
 
 using namespace Goals;
 using namespace Goals;
 
 
-DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath)
-	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn())
+DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack)
+	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()), counterattack(isCounterAttack)
 {
 {
 	settown(town);
 	settown(town);
 	sethero(defencePath.targetHero);
 	sethero(defencePath.targetHero);

+ 4 - 1
AI/Nullkiller/Markers/DefendTown.h

@@ -24,9 +24,10 @@ namespace Goals
 		uint64_t defenceArmyStrength;
 		uint64_t defenceArmyStrength;
 		HitMapInfo treat;
 		HitMapInfo treat;
 		uint8_t turn;
 		uint8_t turn;
+		bool counterattack;
 
 
 	public:
 	public:
-		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath);
+		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false);
 		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
 		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
 
 
 		virtual bool operator==(const DefendTown & other) const override;
 		virtual bool operator==(const DefendTown & other) const override;
@@ -37,6 +38,8 @@ namespace Goals
 		uint64_t getDefenceStrength() const { return defenceArmyStrength; }
 		uint64_t getDefenceStrength() const { return defenceArmyStrength; }
 
 
 		uint8_t getTurn() const { return turn; }
 		uint8_t getTurn() const { return turn; }
+
+		bool isCounterAttack() { return counterattack; }
 	};
 	};
 }
 }
 
 

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

@@ -29,7 +29,7 @@ bool HeroExchange::operator==(const HeroExchange & other) const
 
 
 std::string HeroExchange::toString() const
 std::string HeroExchange::toString() const
 {
 {
-	return "Hero exchange " + exchangePath.toString();
+	return "Hero exchange for " +hero.get()->getObjectName() + " by " + exchangePath.toString();
 }
 }
 
 
 uint64_t HeroExchange::getReinforcementArmyStrength() const
 uint64_t HeroExchange::getReinforcementArmyStrength() const

+ 67 - 40
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -16,7 +16,9 @@
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/mapObjects/MapObjects.h"
-#include "../../../lib/PathfinderUtil.h"
+#include "../../../lib/pathfinder/CPathfinder.h"
+#include "../../../lib/pathfinder/PathfinderUtil.h"
+#include "../../../lib/pathfinder/PathfinderOptions.h"
 #include "../../../lib/CPlayerState.h"
 #include "../../../lib/CPlayerState.h"
 
 
 namespace NKAI
 namespace NKAI
@@ -52,6 +54,27 @@ AISharedStorage::~AISharedStorage()
 	}
 	}
 }
 }
 
 
+void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
+{
+	if(!specialAction)
+	{
+		specialAction = action;
+	}
+	else
+	{
+		auto parts = specialAction->getParts();
+
+		if(parts.empty())
+		{
+			parts.push_back(specialAction);
+		}
+
+		parts.push_back(action);
+
+		specialAction = std::make_shared<CompositeAction>(parts);
+	}
+}
+
 AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
 AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
 	: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
 	: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
 {
 {
@@ -120,7 +143,7 @@ void AINodeStorage::clear()
 	turnDistanceLimit[HeroRole::SCOUT] = 255;
 	turnDistanceLimit[HeroRole::SCOUT] = 255;
 }
 }
 
 
-boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
+std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 	const int3 & pos, 
 	const int3 & pos, 
 	const EPathfindingLayer layer, 
 	const EPathfindingLayer layer, 
 	const ChainActor * actor)
 	const ChainActor * actor)
@@ -131,7 +154,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 
 
 	if(chains[0].blocked())
 	if(chains[0].blocked())
 	{
 	{
-		return boost::none;
+		return std::nullopt;
 	}
 	}
 
 
 	for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
 	for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
@@ -151,7 +174,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 		}
 		}
 	}
 	}
 
 
-	return boost::none;
+	return std::nullopt;
 }
 }
 
 
 std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
@@ -175,7 +198,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 		if(!allocated)
 		if(!allocated)
 			continue;
 			continue;
 
 
-		AIPathNode * initialNode = allocated.get();
+		AIPathNode * initialNode = allocated.value();
 
 
 		initialNode->inPQ = false;
 		initialNode->inPQ = false;
 		initialNode->pq = nullptr;
 		initialNode->pq = nullptr;
@@ -183,7 +206,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 		initialNode->moveRemains = actor->initialMovement;
 		initialNode->moveRemains = actor->initialMovement;
 		initialNode->danger = 0;
 		initialNode->danger = 0;
 		initialNode->setCost(actor->initialTurn);
 		initialNode->setCost(actor->initialTurn);
-		initialNode->action = CGPathNode::ENodeAction::NORMAL;
+		initialNode->action = EPathNodeAction::NORMAL;
 
 
 		if(actor->isMovable)
 		if(actor->isMovable)
 		{
 		{
@@ -201,7 +224,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 	return initialNodes;
 	return initialNodes;
 }
 }
 
 
-void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
+void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility)
 {
 {
 	for(AIPathNode & heroNode : nodes.get(coord, layer))
 	for(AIPathNode & heroNode : nodes.get(coord, layer))
 {
 {
@@ -239,7 +262,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 void AINodeStorage::commit(
 void AINodeStorage::commit(
 	AIPathNode * destination, 
 	AIPathNode * destination, 
 	const AIPathNode * source, 
 	const AIPathNode * source, 
-	CGPathNode::ENodeAction action, 
+	EPathNodeAction action, 
 	int turn, 
 	int turn, 
 	int movementLeft, 
 	int movementLeft, 
 	float cost) const
 	float cost) const
@@ -289,10 +312,10 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 		{
 		{
 			auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
 			auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
 
 
-			if(!nextNode || nextNode.get()->accessible == CGPathNode::NOT_SET)
+			if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
 				continue;
 				continue;
 
 
-			neighbours.push_back(nextNode.get());
+			neighbours.push_back(nextNode.value());
 		}
 		}
 	}
 	}
 	
 	
@@ -319,7 +342,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
 			{
 			{
 				for(AIPathNode & node : chains)
 				for(AIPathNode & node : chains)
 				{
 				{
-					if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
+					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
 					{
 					{
 						commitedTiles.insert(pos);
 						commitedTiles.insert(pos);
 						break;
 						break;
@@ -349,7 +372,7 @@ bool AINodeStorage::calculateHeroChainFinal()
 				{
 				{
 					if(node.turns > heroChainTurn
 					if(node.turns > heroChainTurn
 						&& !node.locked
 						&& !node.locked
-						&& node.action != CGPathNode::ENodeAction::UNKNOWN
+						&& node.action != EPathNodeAction::UNKNOWN
 						&& node.actor->actorExchangeCount > 1
 						&& node.actor->actorExchangeCount > 1
 						&& !hasBetterChain(&node, &node, chains))
 						&& !hasBetterChain(&node, &node, chains))
 					{
 					{
@@ -421,7 +444,7 @@ public:
 
 
 				for(AIPathNode & node : chains)
 				for(AIPathNode & node : chains)
 				{
 				{
-					if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
+					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
 						existingChains.push_back(&node);
 						existingChains.push_back(&node);
 				}
 				}
 
 
@@ -621,16 +644,16 @@ void HeroChainCalculationTask::calculateHeroChain(
 		if(node->actor->actorExchangeCount + srcNode->actor->actorExchangeCount > CHAIN_MAX_DEPTH)
 		if(node->actor->actorExchangeCount + srcNode->actor->actorExchangeCount > CHAIN_MAX_DEPTH)
 			continue;
 			continue;
 
 
-		if(node->action == CGPathNode::ENodeAction::BATTLE
-			|| node->action == CGPathNode::ENodeAction::TELEPORT_BATTLE
-			|| node->action == CGPathNode::ENodeAction::TELEPORT_NORMAL
-			|| node->action == CGPathNode::ENodeAction::TELEPORT_BLOCKING_VISIT)
+		if(node->action == EPathNodeAction::BATTLE
+			|| node->action == EPathNodeAction::TELEPORT_BATTLE
+			|| node->action == EPathNodeAction::TELEPORT_NORMAL
+			|| node->action == EPathNodeAction::TELEPORT_BLOCKING_VISIT)
 		{
 		{
 			continue;
 			continue;
 		}
 		}
 
 
 		if(node->turns > heroChainTurn 
 		if(node->turns > heroChainTurn 
-			|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
+			|| (node->action == EPathNodeAction::UNKNOWN && node->actor->hero)
 			|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)
 			|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)
 		{
 		{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
@@ -643,7 +666,7 @@ void HeroChainCalculationTask::calculateHeroChain(
 				srcNode->coord.toString(),
 				srcNode->coord.toString(),
 				(node->turns > heroChainTurn 
 				(node->turns > heroChainTurn 
 					? "turn limit" 
 					? "turn limit" 
-					: (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
+					: (node->action == EPathNodeAction::UNKNOWN && node->actor->hero)
 						? "action unknown"
 						? "action unknown"
 						: "chain mask"));
 						: "chain mask"));
 #endif
 #endif
@@ -670,8 +693,8 @@ void HeroChainCalculationTask::calculateHeroChain(
 	std::vector<ExchangeCandidate> & result)
 	std::vector<ExchangeCandidate> & result)
 {	
 {	
 	if(carrier->armyLoss < carrier->actor->armyValue
 	if(carrier->armyLoss < carrier->actor->armyValue
-		&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
-		&& carrier->action != CGPathNode::BLOCKING_VISIT
+		&& (carrier->action != EPathNodeAction::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
+		&& carrier->action != EPathNodeAction::BLOCKING_VISIT
 		&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
 		&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
 	{
 	{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
@@ -722,9 +745,9 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
 			continue;
 			continue;
 		}
 		}
 
 
-		auto exchangeNode = chainNodeOptional.get();
+		auto exchangeNode = chainNodeOptional.value();
 
 
-		if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
+		if(exchangeNode->action != EPathNodeAction::UNKNOWN)
 		{
 		{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
 			logAi->trace(
@@ -765,7 +788,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
 		if(exchangeNode->actor->actorAction)
 		if(exchangeNode->actor->actorAction)
 		{
 		{
 			exchangeNode->theNodeBefore = carrier;
 			exchangeNode->theNodeBefore = carrier;
-			exchangeNode->specialAction = exchangeNode->actor->actorAction;
+			exchangeNode->addSpecialAction(exchangeNode->actor->actorAction);
 		}
 		}
 
 
 		exchangeNode->chainOther = other;
 		exchangeNode->chainOther = other;
@@ -856,16 +879,20 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
 	for(auto & hero : heroes)
 	for(auto & hero : heroes)
 	{
 	{
 		// do not allow our own heroes in garrison to act on map
 		// do not allow our own heroes in garrison to act on map
-		if(hero.first->getOwner() == ai->playerID && hero.first->inTownGarrison)
+		if(hero.first->getOwner() == ai->playerID
+			&& hero.first->inTownGarrison
+			&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached()))
+		{
 			continue;
 			continue;
+		}
 
 
 		uint64_t mask = FirstActorMask << actors.size();
 		uint64_t mask = FirstActorMask << actors.size();
 		auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai);
 		auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai);
 
 
 		if(actor->hero->tempOwner != ai->playerID)
 		if(actor->hero->tempOwner != ai->playerID)
 		{
 		{
-			bool onLand = !actor->hero->boat;
-			actor->initialMovement = actor->hero->maxMovePoints(onLand);
+			bool onLand = !actor->hero->boat || actor->hero->boat->layer != EPathfindingLayer::SAIL;
+			actor->initialMovement = actor->hero->movementPointsLimit(onLand);
 		}
 		}
 
 
 		playerID = actor->hero->tempOwner;
 		playerID = actor->hero->tempOwner;
@@ -946,7 +973,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 			if(!node)
 			if(!node)
 				continue;
 				continue;
 
 
-			neighbours.push_back(node.get());
+			neighbours.push_back(node.value());
 		}
 		}
 	}
 	}
 
 
@@ -1017,35 +1044,35 @@ struct TowmPortalFinder
 		return nullptr;
 		return nullptr;
 	}
 	}
 
 
-	boost::optional<AIPathNode *> createTownPortalNode(const CGTownInstance * targetTown)
+	std::optional<AIPathNode *> createTownPortalNode(const CGTownInstance * targetTown)
 	{
 	{
 		auto bestNode = getBestInitialNodeForTownPortal(targetTown);
 		auto bestNode = getBestInitialNodeForTownPortal(targetTown);
 
 
 		if(!bestNode)
 		if(!bestNode)
-			return boost::none;
+			return std::nullopt;
 
 
 		auto nodeOptional = nodeStorage->getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
 		auto nodeOptional = nodeStorage->getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
 
 
 		if(!nodeOptional)
 		if(!nodeOptional)
-			return boost::none;
+			return std::nullopt;
 
 
-		AIPathNode * node = nodeOptional.get();
-		float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
+		AIPathNode * node = nodeOptional.value();
+		float movementCost = (float)movementNeeded / (float)hero->movementPointsLimit(EPathfindingLayer::LAND);
 
 
 		movementCost += bestNode->getCost();
 		movementCost += bestNode->getCost();
 
 
-		if(node->action == CGPathNode::UNKNOWN || node->getCost() > movementCost)
+		if(node->action == EPathNodeAction::UNKNOWN || node->getCost() > movementCost)
 		{
 		{
 			nodeStorage->commit(
 			nodeStorage->commit(
 				node,
 				node,
 				nodeStorage->getAINode(bestNode),
 				nodeStorage->getAINode(bestNode),
-				CGPathNode::TELEPORT_NORMAL,
+				EPathNodeAction::TELEPORT_NORMAL,
 				bestNode->turns,
 				bestNode->turns,
 				bestNode->moveRemains - movementNeeded,
 				bestNode->moveRemains - movementNeeded,
 				movementCost);
 				movementCost);
 
 
 			node->theNodeBefore = bestNode;
 			node->theNodeBefore = bestNode;
-			node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
+			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
 		}
 		}
 
 
 		return nodeOptional;
 		return nodeOptional;
@@ -1095,7 +1122,7 @@ void AINodeStorage::calculateTownPortal(
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
 				logAi->trace("Adding town portal node at %s", targetTown->getObjectName());
 				logAi->trace("Adding town portal node at %s", targetTown->getObjectName());
 #endif
 #endif
-				output.push_back(nodeOptional.get());
+				output.push_back(nodeOptional.value());
 			}
 			}
 		}
 		}
 	}
 	}
@@ -1167,7 +1194,7 @@ bool AINodeStorage::hasBetterChain(
 	{
 	{
 		auto sameNode = node.actor == candidateNode->actor;
 		auto sameNode = node.actor == candidateNode->actor;
 
 
-		if(sameNode	|| node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor || !node.actor->hero)
+		if(sameNode	|| node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
 		{
 		{
 			continue;
 			continue;
 		}
 		}
@@ -1250,7 +1277,7 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, con
 
 
 	for(const AIPathNode & node : chains)
 	for(const AIPathNode & node : chains)
 	{
 	{
-		if(node.action != CGPathNode::ENodeAction::UNKNOWN 
+		if(node.action != EPathNodeAction::UNKNOWN 
 			&& node.actor && node.actor->hero == hero.h)
 			&& node.actor && node.actor->hero == hero.h)
 		{
 		{
 			return true;
 			return true;
@@ -1270,7 +1297,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 
 
 	for(const AIPathNode & node : chains)
 	for(const AIPathNode & node : chains)
 	{
 	{
-		if(node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor || !node.actor->hero)
+		if(node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
 		{
 		{
 			continue;
 			continue;
 		}
 		}

+ 14 - 11
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -13,7 +13,8 @@
 #define NKAI_PATHFINDER_TRACE_LEVEL 0
 #define NKAI_PATHFINDER_TRACE_LEVEL 0
 #define NKAI_TRACE_LEVEL 0
 #define NKAI_TRACE_LEVEL 0
 
 
-#include "../../../lib/CPathfinder.h"
+#include "../../../lib/pathfinder/CGPathNode.h"
+#include "../../../lib/pathfinder/INodeStorage.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
 #include "../Engine/FuzzyHelper.h"
 #include "../Engine/FuzzyHelper.h"
@@ -23,8 +24,8 @@
 
 
 namespace NKAI
 namespace NKAI
 {
 {
-	const int SCOUT_TURN_DISTANCE_LIMIT = 3;
-	const int MAIN_TURN_DISTANCE_LIMIT = 5;
+	const int SCOUT_TURN_DISTANCE_LIMIT = 5;
+	const int MAIN_TURN_DISTANCE_LIMIT = 10;
 
 
 namespace AIPathfinding
 namespace AIPathfinding
 {
 {
@@ -52,9 +53,11 @@ struct AIPathNode : public CGPathNode
 	STRONG_INLINE
 	STRONG_INLINE
 	bool blocked() const
 	bool blocked() const
 	{
 	{
-		return accessible == CGPathNode::EAccessibility::NOT_SET
-			|| accessible == CGPathNode::EAccessibility::BLOCKED;
+		return accessible == EPathAccessibility::NOT_SET
+			|| accessible == EPathAccessibility::BLOCKED;
 	}
 	}
+
+	void addSpecialAction(std::shared_ptr<const SpecialAction> action);
 };
 };
 
 
 struct AIPathNodeInfo
 struct AIPathNodeInfo
@@ -193,7 +196,7 @@ public:
 	void commit(
 	void commit(
 		AIPathNode * destination,
 		AIPathNode * destination,
 		const AIPathNode * source,
 		const AIPathNode * source,
-		CGPathNode::ENodeAction action,
+		EPathNodeAction action,
 		int turn,
 		int turn,
 		int movementLeft,
 		int movementLeft,
 		float cost) const;
 		float cost) const;
@@ -205,14 +208,14 @@ public:
 
 
 	inline void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater)
 	inline void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater)
 	{
 	{
-		auto aiNode = static_cast<AIPathNode *>(node);
+		auto * aiNode = static_cast<AIPathNode *>(node);
 
 
 		updater(aiNode);
 		updater(aiNode);
 	}
 	}
 
 
 	inline const CGHeroInstance * getHero(const CGPathNode * node) const
 	inline const CGHeroInstance * getHero(const CGPathNode * node) const
 	{
 	{
-		auto aiNode = getAINode(node);
+		const auto * aiNode = getAINode(node);
 
 
 		return aiNode->actor->hero;
 		return aiNode->actor->hero;
 	}
 	}
@@ -232,7 +235,7 @@ public:
 		const AIPathNode * destinationNode,
 		const AIPathNode * destinationNode,
 		const NodeRange & chains) const;
 		const NodeRange & chains) const;
 
 
-	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
+	std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
 	std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
 	std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
 	bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
 	bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
 	void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
 	void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
@@ -255,11 +258,11 @@ public:
 	{
 	{
 		double ratio = (double)danger / (armyValue * hero->getFightingStrength());
 		double ratio = (double)danger / (armyValue * hero->getFightingStrength());
 
 
-		return (uint64_t)(armyValue * ratio * ratio * ratio);
+		return (uint64_t)(armyValue * ratio * ratio);
 	}
 	}
 
 
 	STRONG_INLINE
 	STRONG_INLINE
-	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
+	void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility);
 
 
 	STRONG_INLINE int getBucket(const ChainActor * actor) const
 	STRONG_INLINE int getBucket(const ChainActor * actor) const
 	{
 	{

+ 5 - 0
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -61,6 +61,11 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
 	storage->setScoutTurnDistanceLimit(pathfinderSettings.scoutTurnDistanceLimit);
 	storage->setScoutTurnDistanceLimit(pathfinderSettings.scoutTurnDistanceLimit);
 	storage->setMainTurnDistanceLimit(pathfinderSettings.mainTurnDistanceLimit);
 	storage->setMainTurnDistanceLimit(pathfinderSettings.mainTurnDistanceLimit);
 
 
+	logAi->trace(
+		"Scout turn distance: %s, main %s",
+		std::to_string(pathfinderSettings.scoutTurnDistanceLimit),
+		std::to_string(pathfinderSettings.mainTurnDistanceLimit));
+
 	if(pathfinderSettings.useHeroChain)
 	if(pathfinderSettings.useHeroChain)
 	{
 	{
 		storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs);
 		storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs);

+ 4 - 0
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -15,6 +15,8 @@
 #include "Rules/AIPreviousNodeRule.h"
 #include "Rules/AIPreviousNodeRule.h"
 #include "../Engine//Nullkiller.h"
 #include "../Engine//Nullkiller.h"
 
 
+#include "../../../lib/pathfinder/CPathfinder.h"
+
 namespace NKAI
 namespace NKAI
 {
 {
 namespace AIPathfinding
 namespace AIPathfinding
@@ -44,6 +46,8 @@ namespace AIPathfinding
 	{
 	{
 	}
 	}
 
 
+	AIPathfinderConfig::~AIPathfinderConfig() = default;
+
 	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
 	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
 	{
 	{
 		auto hero = aiNodeStorage->getHero(source.node);
 		auto hero = aiNodeStorage->getHero(source.node);

+ 3 - 0
AI/Nullkiller/Pathfinding/AIPathfinderConfig.h

@@ -11,6 +11,7 @@
 #pragma once
 #pragma once
 
 
 #include "AINodeStorage.h"
 #include "AINodeStorage.h"
+#include "../../../lib/pathfinder/PathfinderOptions.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -31,6 +32,8 @@ namespace AIPathfinding
 			Nullkiller * ai,
 			Nullkiller * ai,
 			std::shared_ptr<AINodeStorage> nodeStorage);
 			std::shared_ptr<AINodeStorage> nodeStorage);
 
 
+		~AIPathfinderConfig();
+
 		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 	};
 	};
 }
 }

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BattleAction.cpp

@@ -12,7 +12,6 @@
 #include "BattleAction.h"
 #include "BattleAction.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../Goals/CompleteQuest.h"
 #include "../../Goals/CompleteQuest.h"
-#include "../../../../lib/mapping/CMap.h" //for victory conditions
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BattleAction.h

@@ -11,7 +11,6 @@
 #pragma once
 #pragma once
 
 
 #include "SpecialAction.h"
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 5 - 6
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -14,7 +14,6 @@
 #include "../../Goals/CaptureObject.h"
 #include "../../Goals/CaptureObject.h"
 #include "../../Goals/Invalid.h"
 #include "../../Goals/Invalid.h"
 #include "../../Goals/BuildBoat.h"
 #include "../../Goals/BuildBoat.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "BoatActions.h"
 #include "BoatActions.h"
 
 
@@ -33,9 +32,9 @@ namespace AIPathfinding
 
 
 	Goals::TSubgoal BuildBoatAction::decompose(const CGHeroInstance * hero) const
 	Goals::TSubgoal BuildBoatAction::decompose(const CGHeroInstance * hero) const
 	{
 	{
-		if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
+		if(cb->getPlayerRelations(ai->playerID, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
 		{
 		{
-			return Goals::sptr(Goals::CaptureObject(shipyard->o));
+			return Goals::sptr(Goals::CaptureObject(targetObject()));
 		}
 		}
 		
 		
 		return Goals::sptr(Goals::Invalid());
 		return Goals::sptr(Goals::Invalid());
@@ -45,7 +44,7 @@ namespace AIPathfinding
 	{
 	{
 		auto hero = source->actor->hero;
 		auto hero = source->actor->hero;
 
 
-		if(cb->getPlayerRelations(hero->tempOwner, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
+		if(cb->getPlayerRelations(hero->tempOwner, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
 		{
 		{
 #if NKAI_TRACE_LEVEL > 1
 #if NKAI_TRACE_LEVEL > 1
 			logAi->trace("Can not build a boat. Shipyard is enemy.");
 			logAi->trace("Can not build a boat. Shipyard is enemy.");
@@ -71,7 +70,7 @@ namespace AIPathfinding
 
 
 	const CGObjectInstance * BuildBoatAction::targetObject() const
 	const CGObjectInstance * BuildBoatAction::targetObject() const
 	{
 	{
-		return shipyard->o;
+		return dynamic_cast<const CGObjectInstance*>(shipyard);
 	}
 	}
 
 
 	const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
 	const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
@@ -102,7 +101,7 @@ namespace AIPathfinding
 
 
 	std::string BuildBoatAction::toString() const
 	std::string BuildBoatAction::toString() const
 	{
 	{
-		return "Build Boat at " + shipyard->o->getObjectName();
+		return "Build Boat at " + shipyard->getObject()->visitablePos().toString();
 	}
 	}
 
 
 	bool SummonBoatAction::canAct(const AIPathNode * source) const
 	bool SummonBoatAction::canAct(const AIPathNode * source) const

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BoatActions.h

@@ -11,7 +11,6 @@
 #pragma once
 #pragma once
 
 
 #include "SpecialAction.h"
 #include "SpecialAction.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 
 
 namespace NKAI
 namespace NKAI

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp

@@ -12,7 +12,6 @@
 #include "BuyArmyAction.h"
 #include "BuyArmyAction.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../Goals/CompleteQuest.h"
 #include "../../Goals/CompleteQuest.h"
-#include "../../../../lib/mapping/CMap.h" //for victory conditions
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.h

@@ -11,7 +11,6 @@
 #pragma once
 #pragma once
 
 
 #include "SpecialAction.h"
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp

@@ -12,7 +12,6 @@
 #include "QuestAction.h"
 #include "QuestAction.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../Goals/CompleteQuest.h"
 #include "../../Goals/CompleteQuest.h"
-#include "../../../../lib/mapping/CMap.h" //for victory conditions
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/QuestAction.h

@@ -11,7 +11,7 @@
 #pragma once
 #pragma once
 
 
 #include "SpecialAction.h"
 #include "SpecialAction.h"
-#include "../../../../lib/CGameState.h"
+#include "../../../../lib/gameState/QuestInfo.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 63 - 0
AI/Nullkiller/Pathfinding/Actions/SpecialAction.cpp

@@ -27,4 +27,67 @@ void SpecialAction::execute(const CGHeroInstance * hero) const
 	throw cannotFulfillGoalException("Can not execute " + toString());
 	throw cannotFulfillGoalException("Can not execute " + toString());
 }
 }
 
 
+bool CompositeAction::canAct(const AIPathNode * source) const
+{
+	for(auto part : parts)
+	{
+		if(!part->canAct(source)) return false;
+	}
+
+	return true;
+}
+
+Goals::TSubgoal CompositeAction::decompose(const CGHeroInstance * hero) const
+{
+	for(auto part : parts)
+	{
+		auto goal = part->decompose(hero);
+
+		if(!goal->invalid()) return goal;
+	}
+
+	return SpecialAction::decompose(hero);
+}
+
+void CompositeAction::execute(const CGHeroInstance * hero) const
+{
+	for(auto part : parts)
+	{
+		part->execute(hero);
+	}
+}
+
+void CompositeAction::applyOnDestination(
+	const CGHeroInstance * hero,
+	CDestinationNodeInfo & destination,
+	const PathNodeInfo & source,
+	AIPathNode * dstNode,
+	const AIPathNode * srcNode) const
+{
+	for(auto part : parts)
+	{
+		part->applyOnDestination(hero, destination, source, dstNode, srcNode);
+	}
+}
+
+std::string CompositeAction::toString() const
+{
+	std::string result = "";
+
+	for(auto part : parts)
+	{
+		result += ", " + part->toString();
+	}
+
+	return result;
+}
+
+const CGObjectInstance * CompositeAction::targetObject() const
+{
+	if(parts.empty())
+		return nullptr;
+
+	return parts.front()->targetObject();
+}
+
 }
 }

+ 38 - 1
AI/Nullkiller/Pathfinding/Actions/SpecialAction.h

@@ -13,6 +13,11 @@
 #include "../../AIUtility.h"
 #include "../../AIUtility.h"
 #include "../../Goals/AbstractGoal.h"
 #include "../../Goals/AbstractGoal.h"
 
 
+VCMI_LIB_NAMESPACE_BEGIN
+struct PathNodeInfo;
+struct CDestinationNodeInfo;
+VCMI_LIB_NAMESPACE_END
+
 namespace NKAI
 namespace NKAI
 {
 {
 
 
@@ -36,7 +41,7 @@ public:
 		const CGHeroInstance * hero,
 		const CGHeroInstance * hero,
 		CDestinationNodeInfo & destination,
 		CDestinationNodeInfo & destination,
 		const PathNodeInfo & source,
 		const PathNodeInfo & source,
-		AIPathNode * dstMode,
+		AIPathNode * dstNode,
 		const AIPathNode * srcNode) const
 		const AIPathNode * srcNode) const
 	{
 	{
 	}
 	}
@@ -44,6 +49,38 @@ public:
 	virtual std::string toString() const = 0;
 	virtual std::string toString() const = 0;
 
 
 	virtual const CGObjectInstance * targetObject() const { return nullptr; }
 	virtual const CGObjectInstance * targetObject() const { return nullptr; }
+
+	virtual std::vector<std::shared_ptr<const SpecialAction>> getParts() const
+	{
+		return {};
+	}
+};
+
+class CompositeAction : public SpecialAction
+{
+private:
+	std::vector<std::shared_ptr<const SpecialAction>> parts;
+
+public:
+	CompositeAction(std::vector<std::shared_ptr<const SpecialAction>> parts) : parts(parts) {}
+
+	bool canAct(const AIPathNode * source) const override;
+	void execute(const CGHeroInstance * hero) const override;
+	std::string toString() const override;
+	const CGObjectInstance * targetObject() const override;
+	Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
+
+	std::vector<std::shared_ptr<const SpecialAction>> getParts() const override
+	{
+		return parts;
+	}
+
+	void applyOnDestination(
+		const CGHeroInstance * hero,
+		CDestinationNodeInfo & destination,
+		const PathNodeInfo & source,
+		AIPathNode * dstNode,
+		const AIPathNode * srcNode) const override;
 };
 };
 
 
 }
 }

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp

@@ -10,7 +10,6 @@
 
 
 #include "StdInc.h"
 #include "StdInc.h"
 #include "../../Goals/AdventureSpellCast.h"
 #include "../../Goals/AdventureSpellCast.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "TownPortalAction.h"
 #include "TownPortalAction.h"
 
 

+ 0 - 1
AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h

@@ -11,7 +11,6 @@
 #pragma once
 #pragma once
 
 
 #include "SpecialAction.h"
 #include "SpecialAction.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../Goals/AdventureSpellCast.h"
 #include "../../Goals/AdventureSpellCast.h"
 
 

+ 11 - 10
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -12,8 +12,8 @@
 #include "../AIGateway.h"
 #include "../AIGateway.h"
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../CCallback.h"
 #include "../../../CCallback.h"
-#include "../../../lib/mapping/CMap.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/mapObjects/MapObjects.h"
+#include "../../../lib/pathfinder/TurnInfo.h"
 #include "Actions/BuyArmyAction.h"
 #include "Actions/BuyArmyAction.h"
 
 
 using namespace NKAI;
 using namespace NKAI;
@@ -42,8 +42,8 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
 	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost(), actorAction()
 	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost(), actorAction()
 {
 {
 	initialPosition = hero->visitablePos();
 	initialPosition = hero->visitablePos();
-	layer = hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND;
-	initialMovement = hero->movement;
+	layer = hero->boat ? hero->boat->layer : EPathfindingLayer::LAND;
+	initialMovement = hero->movementPointsRemaining();
 	initialTurn = 0;
 	initialTurn = 0;
 	armyValue = hero->getArmyStrength();
 	armyValue = hero->getArmyStrength();
 	heroFightingStrength = hero->getFightingStrength();
 	heroFightingStrength = hero->getFightingStrength();
@@ -75,7 +75,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
 		throw std::logic_error("Asking movement points for static actor");
 		throw std::logic_error("Asking movement points for static actor");
 #endif
 #endif
 
 
-	return hero->maxMovePointsCached(layer, tiCache.get());
+	return hero->movementPointsLimitCached(layer, tiCache.get());
 }
 }
 
 
 std::string ChainActor::toString() const
 std::string ChainActor::toString() const
@@ -134,6 +134,7 @@ void ChainActor::setBaseActor(HeroActor * base)
 	armyCost = base->armyCost;
 	armyCost = base->armyCost;
 	actorAction = base->actorAction;
 	actorAction = base->actorAction;
 	tiCache = base->tiCache;
 	tiCache = base->tiCache;
+	actorExchangeCount = base->actorExchangeCount;
 }
 }
 
 
 void HeroActor::setupSpecialActors()
 void HeroActor::setupSpecialActors()
@@ -350,7 +351,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 +373,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 +400,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 +421,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 +455,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));
 	}
 	}
 
 

+ 2 - 5
AI/Nullkiller/Pathfinding/Actors.h

@@ -10,7 +10,6 @@
 
 
 #pragma once
 #pragma once
 
 
-#include "../../../lib/CPathfinder.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../AIUtility.h"
 #include "../AIUtility.h"
 #include "Actions/SpecialAction.h"
 #include "Actions/SpecialAction.h"
@@ -32,9 +31,7 @@ public:
 	virtual bool needsLastStack() const override;
 	virtual bool needsLastStack() const override;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 
 
-	HeroExchangeArmy() : CArmedInstance(true), armyCost(), requireBuyArmy(false)
-	{
-	}
+	HeroExchangeArmy(): CArmedInstance(true), requireBuyArmy(false) {}
 };
 };
 
 
 struct ExchangeResult
 struct ExchangeResult
@@ -85,7 +82,7 @@ public:
 	ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); }
 	ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); }
 	void setBaseActor(HeroActor * base);
 	void setBaseActor(HeroActor * base);
 	virtual const CGObjectInstance * getActorObject() const	{ return hero; }
 	virtual const CGObjectInstance * getActorObject() const	{ return hero; }
-	int maxMovePoints(CGPathNode::ELayer layer);
+	int maxMovePoints(EPathfindingLayer layer);
 
 
 protected:
 protected:
 	virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const;
 	virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const;

+ 4 - 4
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -129,13 +129,13 @@ namespace AIPathfinding
 
 
 			if(boatNodeOptional)
 			if(boatNodeOptional)
 			{
 			{
-				AIPathNode * boatNode = boatNodeOptional.get();
+				AIPathNode * boatNode = boatNodeOptional.value();
 
 
-				if(boatNode->action == CGPathNode::UNKNOWN)
+				if(boatNode->action == EPathNodeAction::UNKNOWN)
 				{
 				{
-					boatNode->specialAction = virtualBoat;
+					boatNode->addSpecialAction(virtualBoat);
 					destination.blocked = false;
 					destination.blocked = false;
-					destination.action = CGPathNode::ENodeAction::EMBARK;
+					destination.action = EPathNodeAction::EMBARK;
 					destination.node = boatNode;
 					destination.node = boatNode;
 					result = true;
 					result = true;
 				}
 				}

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h

@@ -14,8 +14,8 @@
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../Actions/BoatActions.h"
 #include "../Actions/BoatActions.h"
 #include "../../../../CCallback.h"
 #include "../../../../CCallback.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
+#include "../../../../lib/pathfinder/PathfindingRules.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 11 - 6
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -139,17 +139,17 @@ namespace AIPathfinding
 		{
 		{
 			if(!destinationNode->actor->allowUseResources)
 			if(!destinationNode->actor->allowUseResources)
 			{
 			{
-				boost::optional<AIPathNode *> questNode = nodeStorage->getOrCreateNode(
+				std::optional<AIPathNode *> questNode = nodeStorage->getOrCreateNode(
 					destination.coord,
 					destination.coord,
 					destination.node->layer,
 					destination.node->layer,
 					destinationNode->actor->resourceActor);
 					destinationNode->actor->resourceActor);
 
 
-				if(!questNode || questNode.get()->getCost() < destination.cost)
+				if(!questNode || questNode.value()->getCost() < destination.cost)
 				{
 				{
 					return false;
 					return false;
 				}
 				}
 
 
-				destination.node = questNode.get();
+				destination.node = questNode.value();
 
 
 				nodeStorage->commit(destination, source);
 				nodeStorage->commit(destination, source);
 				AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
 				AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
@@ -157,7 +157,7 @@ namespace AIPathfinding
 
 
 			nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
 			nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
 			{
 			{
-				node->specialAction.reset(new QuestAction(questAction));
+				node->addSpecialAction(std::make_shared<QuestAction>(questAction));
 			});
 			});
 		}
 		}
 
 
@@ -259,7 +259,7 @@ namespace AIPathfinding
 			return false;
 			return false;
 		}
 		}
 
 
-		AIPathNode * battleNode = battleNodeOptional.get();
+		auto * battleNode = battleNodeOptional.value();
 
 
 		if(battleNode->locked)
 		if(battleNode->locked)
 		{
 		{
@@ -279,6 +279,11 @@ namespace AIPathfinding
 
 
 		if(loss < actualArmyValue)
 		if(loss < actualArmyValue)
 		{
 		{
+			if(destNode->specialAction)
+			{
+				battleNode->specialAction = destNode->specialAction;
+			}
+
 			destination.node = battleNode;
 			destination.node = battleNode;
 			nodeStorage->commit(destination, source);
 			nodeStorage->commit(destination, source);
 
 
@@ -288,7 +293,7 @@ namespace AIPathfinding
 
 
 			AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
 			AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
 
 
-			battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
+			battleNode->addSpecialAction(std::make_shared<BattleAction>(destination.coord));
 
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
 			logAi->trace(
 			logAi->trace(

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h

@@ -13,8 +13,8 @@
 #include "../AINodeStorage.h"
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../../../CCallback.h"
 #include "../../../../CCallback.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
+#include "../../../../lib/pathfinder/PathfindingRules.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp

@@ -31,7 +31,7 @@ namespace AIPathfinding
 			return;
 			return;
 
 
 		if(blocker == BlockingReason::DESTINATION_BLOCKED
 		if(blocker == BlockingReason::DESTINATION_BLOCKED
-			&& destination.action == CGPathNode::EMBARK
+			&& destination.action == EPathNodeAction::EMBARK
 			&& nodeStorage->getAINode(destination.node)->specialAction)
 			&& nodeStorage->getAINode(destination.node)->specialAction)
 		{
 		{
 			return;
 			return;

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h

@@ -13,8 +13,8 @@
 #include "../AINodeStorage.h"
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../../../CCallback.h"
 #include "../../../../CCallback.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
+#include "../../../../lib/pathfinder/PathfindingRules.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 4 - 2
AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "AIPreviousNodeRule.h"
 #include "AIPreviousNodeRule.h"
 
 
+#include "../../../../lib/pathfinder/CPathfinder.h"
+
 namespace NKAI
 namespace NKAI
 {
 {
 namespace AIPathfinding
 namespace AIPathfinding
@@ -25,8 +27,8 @@ namespace AIPathfinding
 		const PathfinderConfig * pathfinderConfig,
 		const PathfinderConfig * pathfinderConfig,
 		CPathfinderHelper * pathfinderHelper) const
 		CPathfinderHelper * pathfinderHelper) const
 	{
 	{
-		if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT 
-			|| source.node->action == CGPathNode::ENodeAction::VISIT)
+		if(source.node->action == EPathNodeAction::BLOCKING_VISIT 
+			|| source.node->action == EPathNodeAction::VISIT)
 		{
 		{
 			if(source.nodeObject
 			if(source.nodeObject
 				&& isObjectPassable(source.nodeObject, pathfinderHelper->hero->tempOwner, source.objectRelations))
 				&& isObjectPassable(source.nodeObject, pathfinderHelper->hero->tempOwner, source.objectRelations))

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h

@@ -13,8 +13,8 @@
 #include "../AINodeStorage.h"
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
 #include "../../AIGateway.h"
 #include "../../../../CCallback.h"
 #include "../../../../CCallback.h"
-#include "../../../../lib/mapping/CMap.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
+#include "../../../../lib/pathfinder/PathfindingRules.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {

+ 40 - 17
AI/StupidAI/StupidAI.cpp

@@ -18,14 +18,21 @@ static std::shared_ptr<CBattleCallback> cbc;
 
 
 CStupidAI::CStupidAI()
 CStupidAI::CStupidAI()
 	: side(-1)
 	: side(-1)
+	, wasWaitingForRealize(false)
+	, wasUnlockingGs(false)
 {
 {
 	print("created");
 	print("created");
 }
 }
 
 
-
 CStupidAI::~CStupidAI()
 CStupidAI::~CStupidAI()
 {
 {
 	print("destroyed");
 	print("destroyed");
+	if(cb)
+	{
+		//Restore previous state of CB - it may be shared with the main AI (like VCAI)
+		cb->waitTillRealize = wasWaitingForRealize;
+		cb->unlockGsWhenWaiting = wasUnlockingGs;
+	}
 }
 }
 
 
 void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
 void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
@@ -33,6 +40,11 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	print("init called, saving ptr to IBattleCallback");
 	print("init called, saving ptr to IBattleCallback");
 	env = ENV;
 	env = ENV;
 	cbc = cb = CB;
 	cbc = cb = CB;
+
+	wasWaitingForRealize = CB->waitTillRealize;
+	wasUnlockingGs = CB->unlockGsWhenWaiting;
+	CB->waitTillRealize = false;
+	CB->unlockGsWhenWaiting = false;
 }
 }
 
 
 void CStupidAI::actionFinished(const BattleAction &action)
 void CStupidAI::actionFinished(const BattleAction &action)
@@ -80,7 +92,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]++;
 	}
 	}
@@ -88,14 +100,19 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 	return shooters[0] < shooters[1];
 	return shooters[0] < shooters[1];
 }
 }
 
 
-BattleAction CStupidAI::activeStack( const CStack * stack )
+void CStupidAI::yourTacticPhase(int distance)
+{
+	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+}
+
+void CStupidAI::activeStack( const CStack * stack )
 {
 {
 	//boost::this_thread::sleep(boost::posix_time::seconds(2));
 	//boost::this_thread::sleep(boost::posix_time::seconds(2));
 	print("activeStack called for " + stack->nodeName());
 	print("activeStack called for " + stack->nodeName());
 	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->creatureId() == 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};
@@ -103,13 +120,15 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 		attack.aimToHex(seletectedHex);
 		attack.aimToHex(seletectedHex);
 		attack.actionType = EActionType::CATAPULT;
 		attack.actionType = EActionType::CATAPULT;
 		attack.side = side;
 		attack.side = side;
-		attack.stackNumber = stack->ID;
+		attack.stackNumber = stack->unitId();
 
 
-		return attack;
+		cb->battleMakeUnitAction(attack);
+		return;
 	}
 	}
-	else if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON))
+	else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
 	{
 	{
-		return BattleAction::makeDefend(stack);
+		cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+		return;
 	}
 	}
 
 
 	for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
 	for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
@@ -120,7 +139,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 		}
 		}
 		else
 		else
 		{
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
+			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
 
 
 			for (BattleHex hex : avHexes)
 			for (BattleHex hex : avHexes)
 			{
 			{
@@ -151,12 +170,14 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 	if(enemiesShootable.size())
 	if(enemiesShootable.size())
 	{
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
 		const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
-		return BattleAction::makeShotAttack(stack, ei.s);
+		cb->battleMakeUnitAction(BattleAction::makeShotAttack(stack, ei.s));
+		return;
 	}
 	}
 	else if(enemiesReachable.size())
 	else if(enemiesReachable.size())
 	{
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		return BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters));
+		cb->battleMakeUnitAction(BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)));
+		return;
 	}
 	}
 	else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
 	else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
 	{
 	{
@@ -167,11 +188,13 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 
 
 		if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE)
 		if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE)
 		{
 		{
-			return goTowards(stack, closestEnemy->s->getAttackableHexes(stack));
+			cb->battleMakeUnitAction(goTowards(stack, closestEnemy->s->getAttackableHexes(stack)));
+			return;
 		}
 		}
 	}
 	}
 
 
-	return BattleAction::makeDefend(stack);
+	cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+	return;
 }
 }
 
 
 void CStupidAI::battleAttack(const BattleAttack *ba)
 void CStupidAI::battleAttack(const BattleAttack *ba)
@@ -184,7 +207,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");
 }
 }
@@ -219,7 +242,7 @@ void CStupidAI::battleStacksEffectsSet(const SetStackEffect & sse)
 	print("battleStacksEffectsSet called");
 	print("battleStacksEffectsSet called");
 }
 }
 
 
-void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
+void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
 {
 {
 	print("battleStart called");
 	print("battleStart called");
 	side = Side;
 	side = Side;
@@ -238,7 +261,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
 	{
 	{
@@ -270,7 +293,7 @@ BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> h
 		return BattleAction::makeDefend(stack);
 		return BattleAction::makeDefend(stack);
 	}
 	}
 
 
-	if(stack->hasBonusOfType(Bonus::FLYING))
+	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
 	{
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
 		// We just check all available hexes and pick the one closest to the target.
 		// We just check all available hexes and pick the one closest to the target.

部分文件因为文件数量过多而无法显示