瀏覽代碼

Nullkiller: update / fix build, core changes required for Nullkiller AI

Andrii Danylchenko 4 年之前
父節點
當前提交
3fa7e0976f

+ 12 - 1
AI/BattleAI/BattleAI.cpp

@@ -167,11 +167,22 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
 				return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
 			else if(bestAttack.attack.shooting)
+			{
+				auto &target = bestAttack;
+				logAi->debug("BattleAI: %s -> %s x %d, shot, from %d curpos %d dist %d speed %d: %lld %lld %lld",
+					target.attackerState->unitType()->identifier,
+					target.affectedUnits[0]->unitType()->identifier,
+					(int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex,
+					bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true),
+					target.damageDealt, target.damageReceived, target.attackValue()
+				);
+
 				return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
+			}
 			else
 			{
 				auto &target = bestAttack;
-				logAi->debug("BattleAI: %s -> %s %d from, %d curpos %d dist %d speed %d: %lld %lld %lld",
+				logAi->debug("BattleAI: %s -> %s x %d, mellee, from %d curpos %d dist %d speed %d: %lld %lld %lld",
 					target.attackerState->unitType()->identifier,
 					target.affectedUnits[0]->unitType()->identifier,
 					(int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex,

+ 6 - 3
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -146,13 +146,16 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 			auto morale = slot.second->MoraleVal();
 			auto multiplier = 1.0f;
 
+			const float BadMoraleChance = 0.083f;
+			const float HighMoraleChance = 0.04f;
+
 			if(morale < 0)
 			{
-				multiplier += morale * 0.083f;
+				multiplier += morale * BadMoraleChance;
 			}
 			else if(morale > 0)
 			{
-				multiplier += morale * 0.04f;
+				multiplier += morale * HighMoraleChance;
 			}
 
 			newValue += multiplier * slot.second->getPower();
@@ -439,7 +442,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSe
 	return upgrades;
 }
 
-ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
+ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
 	const CCreatureSet * army,
 	const CGObjectInstance * upgrader,
 	const TResources & availableResources) const

+ 2 - 2
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -55,7 +55,7 @@ public:
 	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 SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
-	virtual ArmyUpgradeInfo calculateCreateresUpgrade(
+	virtual ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army,
 		const CGObjectInstance * upgrader,
 		const TResources & availableResources) const = 0;
@@ -90,7 +90,7 @@ public:
 	std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override;
 	uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
-	ArmyUpgradeInfo calculateCreateresUpgrade(
+	ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army, 
 		const CGObjectInstance * upgrader,
 		const TResources & availableResources) const override;

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

@@ -224,7 +224,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 			continue;
 		}
 
-		auto upgrade = ai->nullkiller->armyManager->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources);
+		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 
 		if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades

+ 12 - 4
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -95,8 +95,16 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 	auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	auto resources = bankInfo->getPossibleResourcesReward();
+	TResources result = TResources();
+	int sum = 0;
 
-	return resources;
+	for(auto & reward : resources)
+	{
+		result += reward.data * reward.chance;
+		sum += reward.chance;
+	}
+
+	return sum > 1 ? result / sum : result;
 }
 
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
@@ -108,7 +116,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
 
 	for(auto c : creatures)
 	{
-		result += c.type->AIValue * c.count;
+		result += c.data.type->AIValue * c.data.count * c.chance / 100;
 	}
 
 	return result;
@@ -205,7 +213,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::TOWN:
 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
 	case Obj::HILL_FORT:
-		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
+		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
 	case Obj::CREATURE_BANK:
 		return getCreatureBankArmyReward(target, hero);
 	case Obj::CREATURE_GENERATOR1:
@@ -239,7 +247,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
 	switch(target->ID)
 	{
 	case Obj::HILL_FORT:
-		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
+		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_WAR:
 		return 1000;

+ 1 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -1267,7 +1267,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 		path.targetHero = node.actor->hero;
 		path.heroArmy = node.actor->creatureSet;
 		path.armyLoss = node.armyLoss;
-		path.targetObjectDanger = evaluateDanger(pos, path.targetHero, false);
+		path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
 		path.targetObjectArmyLoss = evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
 		path.chainMask = node.actor->chainMask;
 		path.exchangeCount = node.actor->actorExchangeCount;

+ 1 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -11,7 +11,7 @@
 #pragma once
 
 #define PATHFINDER_TRACE_LEVEL 0
-#define AI_TRACE_LEVEL 1
+#define AI_TRACE_LEVEL 0
 #define SCOUT_TURN_DISTANCE_LIMIT 3
 #define MAIN_TURN_DISTANCE_LIMIT 5
 

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

@@ -343,7 +343,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 	TResources resources) const
 {
 	HeroExchangeArmy * target = new HeroExchangeArmy();
-	auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(army, upgrader, resources);
+	auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
 
 	if(upgradeInfo.upgradeValue)
 	{

+ 3 - 3
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -26,7 +26,7 @@ AINodeStorage::AINodeStorage(const int3 & Sizes)
 
 AINodeStorage::~AINodeStorage() = default;
 
-void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
+void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
 {
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 
@@ -109,7 +109,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, c
 	return boost::none;
 }
 
-CGPathNode * AINodeStorage::getInitialNode()
+std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 {
 	auto hpos = hero->getPosition(false);
 	auto initialNode =
@@ -121,7 +121,7 @@ CGPathNode * AINodeStorage::getInitialNode()
 	initialNode->danger = 0;
 	initialNode->setCost(0.0);
 
-	return initialNode;
+	return {initialNode};
 }
 
 void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)

+ 3 - 7
AI/VCAI/Pathfinding/AINodeStorage.h

@@ -80,9 +80,9 @@ public:
 	AINodeStorage(const int3 & sizes);
 	~AINodeStorage();
 
-	void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
+	void initialize(const PathfinderOptions & options, const CGameState * gs) override;
 
-	virtual CGPathNode * getInitialNode() override;
+	virtual std::vector<CGPathNode *> getInitialNodes() override;
 
 	virtual std::vector<CGPathNode *> calculateNeighbours(
 		const PathNodeInfo & source,
@@ -106,11 +106,7 @@ public:
 	bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const;
 
 	void setHero(HeroPtr heroPtr, const VCAI * ai);
-
-	const CGHeroInstance * getHero() const
-	{
-		return hero;
-	}
+	const CGHeroInstance * getHero() const { return hero; }
 
 	uint64_t evaluateDanger(const int3 &  tile) const
 	{

+ 2 - 2
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -56,8 +56,8 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 	auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
 	{
 		logAi->debug("Recalculate paths for %s", hero->name);
-
-		cb->calculatePaths(config, hero);
+		
+		cb->calculatePaths(config);
 	};
 
 	std::vector<CThreadHelper::Task> calculationTasks;

+ 11 - 1
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp

@@ -37,7 +37,17 @@ namespace AIPathfinding
 		CPlayerSpecificInfoCallback * cb,
 		VCAI * ai,
 		std::shared_ptr<AINodeStorage> nodeStorage)
-		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
+		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
 	{
 	}
+
+	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
+	{
+		if(!helper)
+		{
+			helper.reset(new CPathfinderHelper(gs, hero, options));
+		}
+
+		return helper.get();
+	}
 }

+ 6 - 0
AI/VCAI/Pathfinding/AIPathfinderConfig.h

@@ -17,10 +17,16 @@ namespace AIPathfinding
 {
 	class AIPathfinderConfig : public PathfinderConfig
 	{
+	private:
+		const CGHeroInstance * hero;
+		std::unique_ptr<CPathfinderHelper> helper;
+
 	public:
 		AIPathfinderConfig(
 			CPlayerSpecificInfoCallback * cb,
 			VCAI * ai,
 			std::shared_ptr<AINodeStorage> nodeStorage);
+
+		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 	};
 }

+ 10 - 4
AI/VCAI/VCAI.cpp

@@ -2670,10 +2670,16 @@ void AIStatus::attemptedAnsweringQuery(QueryID queryID, int answerRequestID)
 
 void AIStatus::receivedAnswerConfirmation(int answerRequestID, int result)
 {
-	assert(vstd::contains(requestToQueryID, answerRequestID));
-	QueryID query = requestToQueryID[answerRequestID];
-	assert(vstd::contains(remainingQueries, query));
-	requestToQueryID.erase(answerRequestID);
+	QueryID query;
+
+	{
+		boost::unique_lock<boost::mutex> lock(mx);
+
+		assert(vstd::contains(requestToQueryID, answerRequestID));
+		query = requestToQueryID[answerRequestID];
+		assert(vstd::contains(remainingQueries, query));
+		requestToQueryID.erase(answerRequestID);
+	}
 
 	if(result)
 	{

+ 1 - 1
CI/msvc/install.sh

@@ -4,7 +4,7 @@ git submodule update --init --recursive
 cd ..
 
 curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \
-	"https://github.com/nullkiller/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
+	"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
 7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
 
 rmdir vcpkg\installed\${VCMI_BUILD_PLATFORM}-windows\debug /S/Q

+ 2 - 1
CI/mxe/before_install.sh

@@ -15,7 +15,7 @@ sudo apt-get install -f --yes
 
 if false; then
 	# Add MXE repository and key
-	echo "deb http://pkg.mxe.cc/repos/apt trusty main" \
+	echo "deb http://pkg.mxe.cc/repos/apt/debian wheezy main" \
 		| sudo tee /etc/apt/sources.list.d/mxeapt.list
 
 	sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D43A795B73B16ABE9643FE1AFD8FFF16DB45C6AB
@@ -35,6 +35,7 @@ if false; then
 	mxe-$MXE_TARGET-ffmpeg \
 	mxe-$MXE_TARGET-qt \
 	mxe-$MXE_TARGET-qtbase \
+	mxe-$MXE_TARGET-intel-tbb \
 	mxe-i686-w64-mingw32.static-luajit
 
 fi # Disable

+ 1 - 1
client/CMakeLists.txt

@@ -159,7 +159,7 @@ if(ENABLE_DEBUG_CONSOLE)
 else()
 	add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON})
 endif(ENABLE_DEBUG_CONSOLE)
-add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI)
+add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller)
 
 if(WIN32)
 	set_target_properties(vcmiclient

+ 2 - 2
lib/CGameInfoCallback.cpp

@@ -923,9 +923,9 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3, ShashInt
 	gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
 }
 
-void CGameInfoCallback::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
+void CGameInfoCallback::calculatePaths(std::shared_ptr<PathfinderConfig> config)
 {
-	gs->calculatePaths(config, hero);
+	gs->calculatePaths(config);
 }
 
 const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const

+ 1 - 1
lib/CGameInfoCallback.h

@@ -188,7 +188,7 @@ public:
 	virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
 	virtual bool isInTheMap(const int3 &pos) const;
 	virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
-	virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero);
+	virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config);
 
 	//town
 	virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;

+ 2 - 2
lib/CGameState.cpp

@@ -2056,9 +2056,9 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
 	pathfinder.calculatePaths();
 }
 
-void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
+void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config)
 {
-	CPathfinder pathfinder(this, hero, config);
+	CPathfinder pathfinder(this, config);
 	pathfinder.calculatePaths();
 }
 

+ 1 - 1
lib/CGameState.h

@@ -184,7 +184,7 @@ public:
 	PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
-	void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero) override;
+	void calculatePaths(std::shared_ptr<PathfinderConfig> config) override;
 	int3 guardingCreaturePosition (int3 pos) const;
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
 	void updateRumor();

+ 119 - 93
lib/CPathfinder.cpp

@@ -26,14 +26,14 @@ bool canSeeObj(const CGObjectInstance * obj)
 	return obj != nullptr && obj->ID != Obj::EVENT;
 }
 
-void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
+void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
 {
 	//TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline
 
 	int3 pos;
+	const PlayerColor player = out.hero->tempOwner;
 	const int3 sizes = gs->getMapSize();
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
-	const PlayerColor player = hero->tempOwner;
+	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
 
 	//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
 	const bool useFlying = options.useFlying;
@@ -155,15 +155,20 @@ void NodeStorage::resetTile(
 	getNode(tile, layer)->update(tile, layer, accessibility);
 }
 
-CGPathNode * NodeStorage::getInitialNode()
+std::vector<CGPathNode *> NodeStorage::getInitialNodes()
 {
-	auto initialNode =  getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
+	auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
 
 	initialNode->turns = 0;
 	initialNode->moveRemains = out.hero->movement;
 	initialNode->setCost(0.0);
 
-	return initialNode;
+	if(!initialNode->coord.valid())
+	{
+		initialNode->coord = out.hpos;
+	}
+
+	return std::vector<CGPathNode *> { initialNode };
 }
 
 void NodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
@@ -256,36 +261,40 @@ CPathfinder::CPathfinder(
 	const CGHeroInstance * _hero)
 	: CPathfinder(
 		_gs,
-		_hero,
-		std::make_shared<PathfinderConfig>(
-			std::make_shared<NodeStorage>(_out, _hero),
-			std::vector<std::shared_ptr<IPathfindingRule>>{
-				std::make_shared<LayerTransitionRule>(),
-				std::make_shared<DestinationActionRule>(),
-				std::make_shared<MovementToDestinationRule>(),
-				std::make_shared<MovementCostRule>(),
-				std::make_shared<MovementAfterDestinationRule>()
-			}))
+		std::make_shared<SingleHeroPathfinderConfig>(_out, _gs, _hero))
+{
+}
+
+std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::buildRuleSet()
+{
+	return std::vector<std::shared_ptr<IPathfindingRule>>{
+		std::make_shared<LayerTransitionRule>(),
+			std::make_shared<DestinationActionRule>(),
+			std::make_shared<MovementToDestinationRule>(),
+			std::make_shared<MovementCostRule>(),
+			std::make_shared<MovementAfterDestinationRule>()
+	};
+}
+
+SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
+	: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), buildRuleSet())
+{
+	pathfinderHelper.reset(new CPathfinderHelper(gs, hero, options));
+}
+
+CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
 {
+	return pathfinderHelper.get();
 }
 
 CPathfinder::CPathfinder(
 	CGameState * _gs,
-	const CGHeroInstance * _hero,
 	std::shared_ptr<PathfinderConfig> config)
 	: CGameInfoCallback(_gs, boost::optional<PlayerColor>())
-	, hero(_hero)
-	, patrolTiles({})
 	, config(config)
 	, source()
 	, destination()
 {
-	assert(hero);
-	assert(hero == getHero(hero->id));
-
-	hlp = make_unique<CPathfinderHelper>(_gs, hero, config->options);
-
-	initializePatrol();
 	initializeGraph();
 }
 
@@ -316,31 +325,40 @@ void CPathfinder::calculatePaths()
 	//logGlobal->info("Calculating paths for hero %s (adress  %d) of player %d", hero->name, hero , hero->tempOwner);
 
 	//initial tile - set cost on 0 and add to the queue
-	CGPathNode * initialNode = config->nodeStorage->getInitialNode();
+	std::vector<CGPathNode *> initialNodes = config->nodeStorage->getInitialNodes();
+	int counter = 0;
 
-	if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input
+	for(auto initialNode : initialNodes)
 	{
-		logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
-		throw std::runtime_error("Wrong checksum");
-	}
+		if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input
+		{
+			logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
+			throw std::runtime_error("Wrong checksum");
+		}
 
-	if(isHeroPatrolLocked())
-		return;
+		source.setNode(gs, initialNode);
+		auto hlp = config->getOrCreatePathfinderHelper(source, gs);
 
-	push(initialNode);
+		if(hlp->isHeroPatrolLocked())
+			break;
+
+		pq.push(initialNode);
+	}
 
 	while(!pq.empty())
 	{
+		counter++;
 		auto node = topAndPop();
-		auto excludeOurHero = node->coord == initialNode->coord;
 
-		source.setNode(gs, node, excludeOurHero);
+		source.setNode(gs, node);
 		source.node->locked = true;
 
 		int movement = source.node->moveRemains;
 		uint8_t turn = source.node->turns;
 		float cost = source.node->getCost();
 
+		auto hlp = config->getOrCreatePathfinderHelper(source, gs);
+
 		hlp->updateTurnInfo(turn);
 		if(!movement)
 		{
@@ -350,24 +368,24 @@ void CPathfinder::calculatePaths()
 				continue;
 		}
 
-		source.guarded = isSourceGuarded();
-		if(source.nodeObject)
-			source.objectRelations = gs->getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner);
+		source.isInitialPosition = source.nodeHero == hlp->hero;
+		source.updateInfo(hlp, gs);
 
 		//add accessible neighbouring nodes to the queue
-		auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp.get());
+		auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp);
 		for(CGPathNode * neighbour : neighbourNodes)
 		{
 			if(neighbour->locked)
 				continue;
 
-			if(!isPatrolMovementAllowed(neighbour->coord))
-				continue;
-
 			if(!hlp->isLayerAvailable(neighbour->layer))
 				continue;
 
 			destination.setNode(gs, neighbour);
+			hlp = config->getOrCreatePathfinderHelper(destination, gs);
+
+			if(!hlp->isPatrolMovementAllowed(neighbour->coord))
+				continue;
 
 			/// Check transition without tile accessability rules
 			if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
@@ -376,14 +394,12 @@ void CPathfinder::calculatePaths()
 			destination.turn = turn;
 			destination.movementLeft = movement;
 			destination.cost = cost;
-			destination.guarded = isDestinationGuarded();
+			destination.updateInfo(hlp, gs);
 			destination.isGuardianTile = destination.guarded && isDestinationGuardian();
-			if(destination.nodeObject)
-				destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner);
 
 			for(auto rule : config->rules)
 			{
-				rule->process(source, destination, config.get(), hlp.get());
+				rule->process(source, destination, config.get(), hlp);
 
 				if(destination.blocked)
 					break;
@@ -395,13 +411,14 @@ void CPathfinder::calculatePaths()
 		} //neighbours loop
 
 		//just add all passable teleport exits
+		hlp = config->getOrCreatePathfinderHelper(source, gs);
 
 		/// For now we disable teleports usage for patrol movement
 		/// VCAI not aware about patrol and may stuck while attempt to use teleport
-		if(patrolState == PATROL_RADIUS)
+		if(hlp->patrolState == CPathfinderHelper::PATROL_RADIUS)
 			continue;
 
-		auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get());
+		auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp);
 		for(CGPathNode * teleportNode : teleportationNodes)
 		{
 			if(teleportNode->locked)
@@ -429,6 +446,8 @@ void CPathfinder::calculatePaths()
 			}
 		}
 	} //queue loop
+
+	logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter));
 }
 
 std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(TeleportChannelID channelID) const
@@ -498,12 +517,12 @@ std::vector<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & sourc
 	return teleportationExits;
 }
 
-bool CPathfinder::isHeroPatrolLocked() const
+bool CPathfinderHelper::isHeroPatrolLocked() const
 {
 	return patrolState == PATROL_LOCKED;
 }
 
-bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const
+bool CPathfinderHelper::isPatrolMovementAllowed(const int3 & dst) const
 {
 	if(patrolState == PATROL_RADIUS)
 	{
@@ -527,7 +546,7 @@ bool CPathfinder::isLayerTransitionPossible() const
 	case ELayer::LAND:
 		if(destLayer == ELayer::AIR)
 		{
-			if(!config->options.lightweightFlyingMode || isSourceInitialPosition())
+			if(!config->options.lightweightFlyingMode || source.isInitialPosition)
 				return true;
 		}
 		else if(destLayer == ELayer::SAIL)
@@ -667,7 +686,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea
 			if(!destination.isNodeObjectVisitable())
 				return BlockingReason::DESTINATION_BLOCKED;
 
-			if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO)
+			if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero)
 				return BlockingReason::DESTINATION_BLOCKED;
 		}
 		else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT)
@@ -811,9 +830,9 @@ void DestinationActionRule::process(
 
 			if(destination.nodeObject->ID == Obj::BOAT)
 				action = CGPathNode::EMBARK;
-			else if(destination.nodeObject->ID == Obj::HERO)
+			else if(destination.nodeHero)
 			{
-				if(objRel == PlayerRelations::ENEMIES)
+				if(destination.heroRelations == PlayerRelations::ENEMIES)
 					action = CGPathNode::BATTLE;
 				else
 					action = CGPathNode::BLOCKING_VISIT;
@@ -870,10 +889,10 @@ void DestinationActionRule::process(
 CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
 {
 	CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL;
-	if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::HERO)
+
+	if(destination.isNodeObjectVisitable() && destination.nodeHero)
 	{
-		auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner);
-		if(objRel == PlayerRelations::ENEMIES)
+		if(destination.heroRelations == PlayerRelations::ENEMIES)
 			action = CGPathNode::TELEPORT_BATTLE;
 		else
 			action = CGPathNode::TELEPORT_BLOCKING_VISIT;
@@ -882,41 +901,15 @@ CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
 	return action;
 }
 
-bool CPathfinder::isSourceInitialPosition() const
-{
-	return source.node->coord == config->nodeStorage->getInitialNode()->coord;
-}
-
-bool CPathfinder::isSourceGuarded() const
-{
-	/// Hero can move from guarded tile if movement started on that tile
-	/// It's possible at least in these cases:
-	/// - Map start with hero on guarded tile
-	/// - Dimention door used
-	/// TODO: check what happen when there is several guards
-	if(gs->guardingCreaturePosition(source.node->coord).valid() && !isSourceInitialPosition())
-	{
-		return true;
-	}
-
-	return false;
-}
-
-bool CPathfinder::isDestinationGuarded() const
-{
-	/// isDestinationGuarded is exception needed for garrisons.
-	/// When monster standing behind garrison it's visitable and guarded at the same time.
-	return gs->guardingCreaturePosition(destination.node->coord).valid();
-}
-
 bool CPathfinder::isDestinationGuardian() const
 {
 	return gs->guardingCreaturePosition(source.node->coord) == destination.node->coord;
 }
 
-void CPathfinder::initializePatrol()
+void CPathfinderHelper::initializePatrol()
 {
 	auto state = PATROL_NONE;
+
 	if(hero->patrol.patrolling && !getPlayerState(hero->tempOwner)->human)
 	{
 		if(hero->patrol.patrolRadius)
@@ -934,7 +927,7 @@ void CPathfinder::initializePatrol()
 void CPathfinder::initializeGraph()
 {
 	INodeStorage * nodeStorage = config->nodeStorage.get();
-	nodeStorage->initialize(config->options, gs, hero);
+	nodeStorage->initialize(config->options, gs);
 }
 
 bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const
@@ -1101,10 +1094,11 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const
 }
 
 CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options)
-	: CGameInfoCallback(gs, boost::optional<PlayerColor>()), turn(-1), hero(Hero), options(Options)
+	: CGameInfoCallback(gs, boost::optional<PlayerColor>()), turn(-1), hero(Hero), options(Options), owner(Hero->tempOwner)
 {
 	turnsInfo.reserve(16);
 	updateTurnInfo();
+	initializePatrol();
 }
 
 CPathfinderHelper::~CPathfinderHelper()
@@ -1349,11 +1343,11 @@ const CGPathNode * CPathsInfo::getNode(const int3 & coord) const
 }
 
 PathNodeInfo::PathNodeInfo()
-	: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false)
+	: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false),	isInitialPosition(false)
 {
 }
 
-void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
+void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n)
 {
 	node = n;
 
@@ -1363,12 +1357,43 @@ void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObjec
 
 		coord = node->coord;
 		tile = gs->getTile(coord);
-		nodeObject = tile->topVisitableObj(excludeTopObject);
+		nodeObject = tile->topVisitableObj();
+		
+		if(nodeObject && nodeObject->ID == Obj::HERO)
+		{
+			nodeHero = dynamic_cast<const CGHeroInstance *>(nodeObject);
+			nodeObject = tile->topVisitableObj(true);
+
+			if(!nodeObject)
+				nodeObject = nodeHero;
+		}
+		else
+		{
+			nodeHero = nullptr;
+		}
 	}
 
 	guarded = false;
 }
 
+void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs)
+{
+	if(gs->guardingCreaturePosition(node->coord).valid() && !isInitialPosition)
+	{
+		guarded = true;
+	}
+
+	if(nodeObject)
+	{
+		objectRelations = gs->getPlayerRelations(hlp->owner, nodeObject->tempOwner);
+	}
+
+	if(nodeHero)
+	{
+		heroRelations = gs->getPlayerRelations(hlp->owner, nodeHero->tempOwner);
+	}
+}
+
 CDestinationNodeInfo::CDestinationNodeInfo()
 	: PathNodeInfo(),
 	blocked(false),
@@ -1376,9 +1401,9 @@ CDestinationNodeInfo::CDestinationNodeInfo()
 {
 }
 
-void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
+void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n)
 {
-	PathNodeInfo::setNode(gs, n, excludeTopObject);
+	PathNodeInfo::setNode(gs, n);
 
 	blocked = false;
 	action = CGPathNode::ENodeAction::UNKNOWN;
@@ -1395,5 +1420,6 @@ bool CDestinationNodeInfo::isBetterWay() const
 bool PathNodeInfo::isNodeObjectVisitable() const
 {
 	/// Hero can't visit objects while walking on water or flying
-	return canSeeObj(nodeObject) && (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL);
+	return (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL)
+		&& (canSeeObj(nodeObject) || canSeeObj(nodeHero));
 }

+ 38 - 23
lib/CPathfinder.h

@@ -196,14 +196,19 @@ struct DLL_LINKAGE PathNodeInfo
 {
 	CGPathNode * node;
 	const CGObjectInstance * nodeObject;
+	const CGHeroInstance * nodeHero;
 	const TerrainTile * tile;
 	int3 coord;
 	bool guarded;
 	PlayerRelations::PlayerRelations objectRelations;
+	PlayerRelations::PlayerRelations heroRelations;
+	bool isInitialPosition;
 
 	PathNodeInfo();
 
-	virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
+	virtual void setNode(CGameState * gs, CGPathNode * n);
+
+	void updateInfo(CPathfinderHelper * hlp, CGameState * gs);
 
 	bool isNodeObjectVisitable() const;
 };
@@ -219,7 +224,7 @@ struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo
 
 	CDestinationNodeInfo();
 
-	virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override;
+	virtual void setNode(CGameState * gs, CGPathNode * n) override;
 
 	virtual bool isBetterWay() const;
 };
@@ -379,7 +384,7 @@ class DLL_LINKAGE INodeStorage
 {
 public:
 	using ELayer = EPathfindingLayer;
-	virtual CGPathNode * getInitialNode() = 0;
+	virtual std::vector<CGPathNode *> getInitialNodes() = 0;
 
 	virtual std::vector<CGPathNode *> calculateNeighbours(
 		const PathNodeInfo & source,
@@ -393,7 +398,7 @@ public:
 
 	virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0;
 
-	virtual void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) = 0;
+	virtual void initialize(const PathfinderOptions & options, const CGameState * gs) = 0;
 };
 
 class DLL_LINKAGE NodeStorage : public INodeStorage
@@ -413,9 +418,9 @@ public:
 		return out.getNode(coord, layer);
 	}
 
-	void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
+	void initialize(const PathfinderOptions & options, const CGameState * gs) override;
 
-	virtual CGPathNode * getInitialNode() override;
+	virtual std::vector<CGPathNode *> getInitialNodes() override;
 
 	virtual std::vector<CGPathNode *> calculateNeighbours(
 		const PathNodeInfo & source,
@@ -440,6 +445,21 @@ public:
 	PathfinderConfig(
 		std::shared_ptr<INodeStorage> nodeStorage,
 		std::vector<std::shared_ptr<IPathfindingRule>> rules);
+
+	virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0;
+};
+
+class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
+{
+private:
+	std::unique_ptr<CPathfinderHelper> pathfinderHelper;
+
+public:
+	SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero);
+
+	virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
+
+	static std::vector<std::shared_ptr<IPathfindingRule>> buildRuleSet();
 };
 
 class CPathfinder : private CGameInfoCallback
@@ -450,7 +470,6 @@ public:
 	CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero);
 	CPathfinder(
 		CGameState * _gs,
-		const CGHeroInstance * _hero,
 		std::shared_ptr<PathfinderConfig> config);
 
 	void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
@@ -458,34 +477,18 @@ public:
 private:
 	typedef EPathfindingLayer ELayer;
 
-	const CGHeroInstance * hero;
-	std::unique_ptr<CPathfinderHelper> hlp;
 	std::shared_ptr<PathfinderConfig> config;
 
-	enum EPatrolState {
-		PATROL_NONE = 0,
-		PATROL_LOCKED = 1,
-		PATROL_RADIUS
-	} patrolState;
-	std::unordered_set<int3, ShashInt3> patrolTiles;
-
 	boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>> > pq;
 
 	PathNodeInfo source; //current (source) path node -> we took it from the queue
 	CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider
 
-	bool isHeroPatrolLocked() const;
-	bool isPatrolMovementAllowed(const int3 & dst) const;
-
 	bool isLayerTransitionPossible() const;
 	CGPathNode::ENodeAction getTeleportDestAction() const;
 
-	bool isSourceInitialPosition() const;
-	bool isSourceGuarded() const;
-	bool isDestinationGuarded() const;
 	bool isDestinationGuardian() const;
 
-	void initializePatrol();
 	void initializeGraph();
 
 	STRONG_INLINE
@@ -527,13 +530,25 @@ struct DLL_LINKAGE TurnInfo
 class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback
 {
 public:
+	enum EPatrolState
+	{
+		PATROL_NONE = 0,
+		PATROL_LOCKED = 1,
+		PATROL_RADIUS
+	} patrolState;
+	std::unordered_set<int3, ShashInt3> patrolTiles;
+
 	int turn;
+	PlayerColor owner;
 	const CGHeroInstance * hero;
 	std::vector<TurnInfo *> turnsInfo;
 	const PathfinderOptions & options;
 
 	CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
 	~CPathfinderHelper();
+	void initializePatrol();
+	bool isHeroPatrolLocked() const;
+	bool isPatrolMovementAllowed(const int3 & dst) const;
 	void updateTurnInfo(const int turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer layer) const;
 	const TurnInfo * getTurnInfo() const;

+ 7 - 6
lib/HeroBonus.cpp

@@ -901,8 +901,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 	if (CBonusSystemNode::cachingEnabled && limitOnUs)
 	{
 		// Exclusive access for one thread
-		static boost::mutex m;
-		boost::mutex::scoped_lock lock(m);
+		boost::lock_guard<boost::mutex> lock(sync);
 
 		// If the bonus system tree changes(state of a single node or the relations to each other) then
 		// cache all bonus objects. Selector objects doesn't matter.
@@ -993,7 +992,8 @@ CBonusSystemNode::CBonusSystemNode()
 	: bonuses(true),
 	exportedBonuses(true),
 	nodeType(UNKNOWN),
-	cachedLast(0)
+	cachedLast(0),
+	sync()
 {
 }
 
@@ -1001,7 +1001,8 @@ CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType)
 	: bonuses(true),
 	exportedBonuses(true),
 	nodeType(NodeType),
-	cachedLast(0)
+	cachedLast(0),
+	sync()
 {
 }
 
@@ -1010,7 +1011,8 @@ CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other):
 	exportedBonuses(std::move(other.exportedBonuses)),
 	nodeType(other.nodeType),
 	description(other.description),
-	cachedLast(0)
+	cachedLast(0),
+	sync()
 {
 	std::swap(parents, other.parents);
 	std::swap(children, other.children);
@@ -1189,7 +1191,6 @@ void CBonusSystemNode::childDetached(CBonusSystemNode *child)
 		logBonus->error("Error! %s #cannot be detached from# %s", child->nodeName(), nodeName());
 		throw std::runtime_error("internal error");
 	}
-
 }
 
 void CBonusSystemNode::detachFromAll()

+ 1 - 0
lib/HeroBonus.h

@@ -761,6 +761,7 @@ private:
 	// This string needs to be unique, that's why it has to be setted in the following manner:
 	// [property key]_[value] => only for selector
 	mutable std::map<std::string, TBonusListPtr > cachedRequests;
+	mutable boost::mutex sync;
 
 	void getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const;
 	void getAllBonusesRec(BonusList &out) const;

+ 10 - 5
lib/mapObjects/CArmedInstance.cpp

@@ -89,19 +89,24 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 			factionsInArmy -= mixableFactions - 1;
 	}
 
+	std::string description;
+
 	if(factionsInArmy == 1)
 	{
 		b->val = +1;
-		b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
-		b->description = b->description.substr(0, b->description.size()-3);//trim "+1"
+		description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
+		description = description.substr(0, description.size()-3);//trim "+1"
 	}
 	else if (!factions.empty()) // no bonus from empty garrison
 	{
 	 	b->val = 2 - (si32)factionsInArmy;
-		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
-		b->description = b->description.substr(0, b->description.size()-2);//trim value
+		description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
+		description = b->description.substr(0, description.size()-2);//trim value
 	}
-	boost::algorithm::trim(b->description);
+	
+	boost::algorithm::trim(description);
+	b->description = description;
+
 	CBonusSystemNode::treeHasChanged();
 
 	//-1 modifier for any Undead unit in army

+ 102 - 56
lib/mapObjects/CommonConstructors.cpp

@@ -1,12 +1,12 @@
 /*
- * CommonConstructors.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
- *
- */
+* CommonConstructors.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 "CommonConstructors.h"
 
@@ -29,7 +29,7 @@ bool CObstacleConstructor::isStaticObject()
 	return true;
 }
 
-CTownInstanceConstructor::CTownInstanceConstructor():
+CTownInstanceConstructor::CTownInstanceConstructor() :
 	faction(nullptr)
 {
 }
@@ -47,7 +47,7 @@ void CTownInstanceConstructor::initTypeData(const JsonNode & input)
 void CTownInstanceConstructor::afterLoadFinalization()
 {
 	assert(faction);
-	for (auto entry : filtersJson.Struct())
+	for(auto entry : filtersJson.Struct())
 	{
 		filters[entry.first] = LogicalExpression<BuildingID>(entry.second, [this](const JsonNode & node)
 		{
@@ -79,7 +79,7 @@ CGObjectInstance * CTownInstanceConstructor::create(const ObjectTemplate & tmpl)
 void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
 {
 	auto templ = getOverride(object->cb->getTile(object->pos)->terType, object);
-	if (templ)
+	if(templ)
 		object->appearance = templ.get();
 }
 
@@ -91,15 +91,17 @@ CHeroInstanceConstructor::CHeroInstanceConstructor()
 
 void CHeroInstanceConstructor::initTypeData(const JsonNode & input)
 {
-	VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"],
-			[&](si32 index) { heroClass = VLC->heroh->classes[index]; });
+	VLC->modh->identifiers.requestIdentifier(
+		"heroClass",
+		input["heroClass"],
+		[&](si32 index) { heroClass = VLC->heroh->classes[index]; });
 
 	filtersJson = input["filters"];
 }
 
 void CHeroInstanceConstructor::afterLoadFinalization()
 {
-	for (auto entry : filtersJson.Struct())
+	for(auto entry : filtersJson.Struct())
 	{
 		filters[entry.first] = LogicalExpression<HeroTypeID>(entry.second, [](const JsonNode & node)
 		{
@@ -117,7 +119,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, con
 		return hero->type->ID == id;
 	};
 
-	if (filters.count(templ.stringID))
+	if(filters.count(templ.stringID))
 	{
 		return filters.at(templ.stringID).test(heroTest);
 	}
@@ -175,9 +177,9 @@ CGObjectInstance * CDwellingInstanceConstructor::create(const ObjectTemplate & t
 	CGDwelling * obj = createTyped(tmpl);
 
 	obj->creatures.resize(availableCreatures.size());
-	for (auto & entry : availableCreatures)
+	for(auto & entry : availableCreatures)
 	{
-		for (const CCreature * cre : entry)
+		for(const CCreature * cre : entry)
 			obj->creatures.back().second.push_back(cre->idNumber);
 	}
 	return obj;
@@ -190,34 +192,34 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR
 	dwelling->creatures.clear();
 	dwelling->creatures.reserve(availableCreatures.size());
 
-	for (auto & entry : availableCreatures)
+	for(auto & entry : availableCreatures)
 	{
 		dwelling->creatures.resize(dwelling->creatures.size() + 1);
-		for (const CCreature * cre : entry)
+		for(const CCreature * cre : entry)
 			dwelling->creatures.back().second.push_back(cre->idNumber);
 	}
 
 	bool guarded = false; //TODO: serialize for sanity
 
-	if (guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch
+	if(guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch
 	{
-		if (guards.Bool())
+		if(guards.Bool())
 		{
 			guarded = true;
 		}
 	}
-	else if (guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux)
+	else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux)
 	{
-		for (auto & stack : JsonRandom::loadCreatures(guards, rng))
+		for(auto & stack : JsonRandom::loadCreatures(guards, rng))
 		{
 			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count));
 		}
 	}
 	else //default condition - creatures are of level 5 or higher
 	{
-		for (auto creatureEntry : availableCreatures)
+		for(auto creatureEntry : availableCreatures)
 		{
-			if (creatureEntry.at(0)->level >= 5)
+			if(creatureEntry.at(0)->level >= 5)
 			{
 				guarded = true;
 				break;
@@ -225,22 +227,22 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR
 		}
 	}
 
-	if (guarded)
+	if(guarded)
 	{
-		for (auto creatureEntry : availableCreatures)
+		for(auto creatureEntry : availableCreatures)
 		{
 			const CCreature * crea = creatureEntry.at(0);
-			dwelling->putStack (SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3));
+			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3));
 		}
 	}
 }
 
 bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) const
 {
-	for (auto & entry : availableCreatures)
+	for(auto & entry : availableCreatures)
 	{
-		for (const CCreature * cre : entry)
-			if (crea == cre)
+		for(const CCreature * cre : entry)
+			if(crea == cre)
 				return true;
 	}
 	return false;
@@ -249,9 +251,9 @@ bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) cons
 std::vector<const CCreature *> CDwellingInstanceConstructor::getProducedCreatures() const
 {
 	std::vector<const CCreature *> creatures; //no idea why it's 2D, to be honest
-	for (auto & entry : availableCreatures)
+	for(auto & entry : availableCreatures)
 	{
-		for (const CCreature * cre : entry)
+		for(const CCreature * cre : entry)
 			creatures.push_back(cre);
 	}
 	return creatures;
@@ -292,7 +294,7 @@ BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRan
 	bc.resources = Res::ResourceSet(level["reward"]["resources"]);
 	bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng);
 	bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng);
-	bc.spells    = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells);
+	bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells);
 
 	bc.value = static_cast<ui32>(level["value"].Float());
 
@@ -314,18 +316,18 @@ void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRando
 	si32 selectedChance = rng.nextInt(totalChance - 1);
 
 	int cumulativeChance = 0;
-	for (auto & node : levels)
+	for(auto & node : levels)
 	{
 		cumulativeChance += static_cast<int>(node["chance"].Float());
-		if (selectedChance < cumulativeChance)
+		if(selectedChance < cumulativeChance)
 		{
-			 bank->setConfig(generateConfig(node, rng));
-			 break;
+			bank->setConfig(generateConfig(node, rng));
+			break;
 		}
 	}
 }
 
-CBankInfo::CBankInfo(const JsonVector & Config):
+CBankInfo::CBankInfo(const JsonVector & Config) :
 	config(Config)
 {
 	assert(!Config.empty());
@@ -336,28 +338,28 @@ static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature *
 	army.totalStrength += crea->fightValue * amount;
 
 	bool walker = true;
-	if (crea->hasBonusOfType(Bonus::SHOOTER))
+	if(crea->hasBonusOfType(Bonus::SHOOTER))
 	{
 		army.shootersStrength += crea->fightValue * amount;
 		walker = false;
 	}
-	if (crea->hasBonusOfType(Bonus::FLYING))
+	if(crea->hasBonusOfType(Bonus::FLYING))
 	{
 		army.flyersStrength += crea->fightValue * amount;
 		walker = false;
 	}
-	if (walker)
+	if(walker)
 		army.walkersStrength += crea->fightValue * amount;
 }
 
 IObjectInfo::CArmyStructure CBankInfo::minGuards() const
 {
 	std::vector<IObjectInfo::CArmyStructure> armies;
-	for (auto configEntry : config)
+	for(auto configEntry : config)
 	{
 		auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
 		IObjectInfo::CArmyStructure army;
-		for (auto & stack : stacks)
+		for(auto & stack : stacks)
 		{
 			assert(!stack.allowedCreatures.empty());
 			auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
@@ -374,11 +376,11 @@ IObjectInfo::CArmyStructure CBankInfo::minGuards() const
 IObjectInfo::CArmyStructure CBankInfo::maxGuards() const
 {
 	std::vector<IObjectInfo::CArmyStructure> armies;
-	for (auto configEntry : config)
+	for(auto configEntry : config)
 	{
 		auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
 		IObjectInfo::CArmyStructure army;
-		for (auto & stack : stacks)
+		for(auto & stack : stacks)
 		{
 			assert(!stack.allowedCreatures.empty());
 			auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
@@ -396,14 +398,14 @@ TPossibleGuards CBankInfo::getPossibleGuards() const
 {
 	TPossibleGuards out;
 
-	for (const JsonNode & configEntry : config)
+	for(const JsonNode & configEntry : config)
 	{
 		const JsonNode & guardsInfo = configEntry["guards"];
 		auto stacks = JsonRandom::evaluateCreatures(guardsInfo);
 		IObjectInfo::CArmyStructure army;
 
 
-		for (auto stack : stacks)
+		for(auto stack : stacks)
 		{
 			army.totalStrength += stack.allowedCreatures.front()->AIValue * (stack.minAmount + stack.maxAmount) / 2;
 			//TODO: add fields for flyers, walkers etc...
@@ -415,34 +417,78 @@ TPossibleGuards CBankInfo::getPossibleGuards() const
 	return out;
 }
 
+std::vector<PossibleReward<TResources>> CBankInfo::getPossibleResourcesReward() const
+{
+	std::vector<PossibleReward<TResources>> result;
+
+	for(const JsonNode & configEntry : config)
+	{
+		const JsonNode & resourcesInfo = configEntry["reward"]["resources"];
+
+		if(!resourcesInfo.isNull())
+		{
+			result.push_back(
+				PossibleReward<TResources>(
+					configEntry["chance"].Integer(),
+					TResources(resourcesInfo)
+					));
+		}
+	}
+
+	return result;
+}
+
+std::vector<PossibleReward<CStackBasicDescriptor>> CBankInfo::getPossibleCreaturesReward() const
+{
+	std::vector<PossibleReward<CStackBasicDescriptor>> aproximateReward;
+
+	for(const JsonNode & configEntry : config)
+	{
+		const JsonNode & guardsInfo = configEntry["reward"]["creatures"];
+		auto stacks = JsonRandom::evaluateCreatures(guardsInfo);
+
+		for(auto stack : stacks)
+		{
+			auto creature = stack.allowedCreatures.front();
+
+			aproximateReward.push_back(
+				PossibleReward<CStackBasicDescriptor>(
+					configEntry["chance"].Integer(),
+					CStackBasicDescriptor(creature, (stack.minAmount + stack.maxAmount) / 2)));
+		}
+	}
+
+	return aproximateReward;
+}
+
 bool CBankInfo::givesResources() const
 {
-	for (const JsonNode & node : config)
-		if (!node["reward"]["resources"].isNull())
+	for(const JsonNode & node : config)
+		if(!node["reward"]["resources"].isNull())
 			return true;
 	return false;
 }
 
 bool CBankInfo::givesArtifacts() const
 {
-	for (const JsonNode & node : config)
-		if (!node["reward"]["artifacts"].isNull())
+	for(const JsonNode & node : config)
+		if(!node["reward"]["artifacts"].isNull())
 			return true;
 	return false;
 }
 
 bool CBankInfo::givesCreatures() const
 {
-	for (const JsonNode & node : config)
-		if (!node["reward"]["creatures"].isNull())
+	for(const JsonNode & node : config)
+		if(!node["reward"]["creatures"].isNull())
 			return true;
 	return false;
 }
 
 bool CBankInfo::givesSpells() const
 {
-	for (const JsonNode & node : config)
-		if (!node["reward"]["spells"].isNull())
+	for(const JsonNode & node : config)
+		if(!node["reward"]["spells"].isNull())
 			return true;
 	return false;
 }

+ 20 - 9
lib/mapObjects/CommonConstructors.h

@@ -1,12 +1,12 @@
 /*
- * CommonConstructors.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
- *
- */
+* CommonConstructors.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 "CObjectClassesHandler.h"
@@ -34,7 +34,7 @@ protected:
 		return obj;
 	}
 public:
-	CDefaultObjectTypeHandler(){}
+	CDefaultObjectTypeHandler() {}
 
 	CGObjectInstance * create(const ObjectTemplate & tmpl) const override
 	{
@@ -164,6 +164,15 @@ struct BankConfig
 
 typedef std::vector<std::pair<ui8, IObjectInfo::CArmyStructure>> TPossibleGuards;
 
+template <typename T>
+struct DLL_LINKAGE PossibleReward
+{
+	int chance;
+	T data;
+
+	PossibleReward(int chance, const T & data) : chance(chance), data(data) {}
+};
+
 class DLL_LINKAGE CBankInfo : public IObjectInfo
 {
 	const JsonVector & config;
@@ -171,6 +180,8 @@ public:
 	CBankInfo(const JsonVector & Config);
 
 	TPossibleGuards getPossibleGuards() const;
+	std::vector<PossibleReward<TResources>> getPossibleResourcesReward() const;
+	std::vector<PossibleReward<CStackBasicDescriptor>> getPossibleCreaturesReward() const;
 
 	// These functions should try to evaluate minimal possible/max possible guards to give provide information on possible thread to AI
 	CArmyStructure minGuards() const override;