ソースを参照

- Various improvements for exploration
* Heroes will try to use SectorMap if there are no accessible tiles
* Remove some loopholes and pitfalls when all the ways are blocked
* Fixed AI not conquering some (reserved) objects
Still missing: SectorMap does not use Subterranean Gates :(

- Improvements for army exchange
* Fixed exchange condition
* Bidirectional exchange is possible

DjWarmonger 11 年 前
コミット
6ee823298a
4 ファイル変更201 行追加30 行削除
  1. 67 11
      AI/VCAI/Goals.cpp
  2. 123 10
      AI/VCAI/VCAI.cpp
  3. 3 1
      AI/VCAI/VCAI.h
  4. 8 8
      lib/HeroBonus.h

+ 67 - 11
AI/VCAI/Goals.cpp

@@ -486,6 +486,10 @@ TGoalVec Explore::getAllPossibleSubgoals()
 		heroes = cb->getHeroesInfo();
 		erase_if (heroes, [](const HeroPtr h)
 		{
+			if (vstd::contains(ai->lockedHeroes, h))
+				if (ai->lockedHeroes[h]->goalType == Goals::EXPLORE) //do not reassign hero who is already explorer
+					return true;
+
 			return !h->movement; //saves time, immobile heroes are useless anyway
 		});
 	}
@@ -514,7 +518,6 @@ TGoalVec Explore::getAllPossibleSubgoals()
 			auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
 			if (t.valid())
 			{
-				assert(cb->isInTheMap(t));
 				if (isSafeToVisit(h, t))
 				{
 					ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
@@ -530,14 +533,49 @@ TGoalVec Explore::getAllPossibleSubgoals()
 		int3 t = whereToExplore(h);
 		if (t.valid())
 		{
-			assert(cb->isInTheMap(t));
 			ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
 		}
+		else if (hero.h == h || (!hero && h == ai->primaryHero().h)) //check this only ONCE, high cost
+		{
+			t = ai->explorationDesperate(h->getSightRadious(), h);
+			if (t.valid())
+			{
+				if (isSafeToVisit(h, t))
+				{
+					ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
+				}
+				else
+				{
+					ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT).
+						sethero(h).setisAbstract(true)));
+				}
+			}
+		}
 	}
 	//we either don't have hero yet or none of heroes can explore
 	if ((!hero || ret.empty()) && ai->canRecruitAnyHero())
 		ret.push_back (sptr(Goals::RecruitHero()));
 
+	//if (ret.empty())
+	//{
+	//	for (auto h : heroes) //this is costly function, use only when there is no other way
+	//	{
+	//		auto t = ai->explorationDesperate (h->getSightRadious(), h); //we assume that no more than one tile on the way is guarded
+	//		if (t.valid())
+	//		{
+	//			if (isSafeToVisit(h, t))
+	//			{
+	//				ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
+	//			}
+	//			else
+	//			{
+	//				ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT).
+	//					sethero(h).setisAbstract(true)));
+	//			}
+	//		}
+	//	}
+	//}
+
 	if (ret.empty())
 	{
 		throw goalFulfilledException (sptr(Goals::Explore().sethero(hero)));
@@ -784,11 +822,9 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 {
 	TGoalVec ret;
 
-	std::vector<const CGObjectInstance *> objs;
-	for (auto obj : ai->visitableObjs)
+	auto conquerable = [](const CGObjectInstance * obj) -> bool
 	{
-		if (!vstd::contains (ai->reservedObjs, obj) && //no need to capture same object twice
-			cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) //only enemy objects are interesting
+		if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES)
 		{
 			switch (obj->ID.num)
 			{
@@ -796,15 +832,30 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 				case Obj::HERO:
 				case Obj::CREATURE_GENERATOR1:
 				case Obj::MINE: //TODO: check ai->knownSubterraneanGates
-					objs.push_back (obj);
+					return true;
 			}
 		}
+		return false;
+	};
+
+	std::vector<const CGObjectInstance *> objs;
+	for (auto obj : ai->visitableObjs)
+	{
+		if (conquerable(obj)) 
+			objs.push_back (obj);
 	}
 
 	for (auto h : cb->getHeroesInfo())
 	{
 		SectorMap sm(h);
-		for (auto obj : objs) //double loop, performance risk?
+		std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
+
+		for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
+		{
+			if (conquerable(obj))
+				ourObjs.push_back(obj);
+		}
+		for (auto obj : ourObjs) //double loop, performance risk?
 		{
 			auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
 			if (t.valid())
@@ -881,11 +932,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 	auto heroDummy = hero;
 	erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h)
 	{
-		return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h));
+		return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)
+			|| !ai->canGetArmy(heroDummy.h, h) || ai->getGoal(h)->goalType == Goals::GATHER_ARMY);
 	});
 	for (auto h : otherHeroes)
 	{
-
 		ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
 				//go to the other hero if we are faster
 		ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
@@ -928,7 +979,12 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 		}
 	}
 	if (ret.empty())
-		ret.push_back (sptr(Goals::Explore()));
+	{
+		if (hero == ai->primaryHero() || value >= 1.1f)
+			ret.push_back (sptr(Goals::Explore()));
+		else //workaround to break loop - seemingly there are no ways to explore left
+			throw goalFulfilledException (sptr(Goals::GatherArmy(0).sethero(hero)));
+	}
 
 	return ret;
 }

+ 123 - 10
AI/VCAI/VCAI.cpp

@@ -307,14 +307,30 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
 
 	requestActionASAP([=]()
 	{
-		if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero))
+		float goalpriority1 = 0, goalpriority2 = 0;
+
+		auto firstGoal = getGoal(firstHero);
+		if (firstGoal->goalType == Goals::GATHER_ARMY)
+			goalpriority1 = firstGoal->priority;
+		auto secondGoal = getGoal(secondHero);
+		if (secondGoal->goalType == Goals::GATHER_ARMY)
+			goalpriority2 = secondGoal->priority;
+
+		if (goalpriority1 > goalpriority2)
 			pickBestCreatures (firstHero, secondHero);
-		else if (canGetArmy (secondHero, firstHero))
+		else if (goalpriority1 < goalpriority2)
 			pickBestCreatures (secondHero, firstHero);
+		else //regular criteria
+		{
+			if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero))
+				pickBestCreatures (firstHero, secondHero);
+			else if (canGetArmy (secondHero, firstHero))
+				pickBestCreatures (secondHero, firstHero);
 
-		completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime?
-		completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum())));
+			completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime?
+			completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum())));
 		//TODO: exchange artifacts
+		}
 
 		answerQuery(query, 0);
 	});
@@ -866,7 +882,7 @@ bool VCAI::canGetArmy (const CGHeroInstance * army, const CGHeroInstance * sourc
 		for(auto armyPtr : armies)
 			for (int j = 0; j < GameConstants::ARMY_SIZE; j++)
 			{
-				if(armyPtr->getCreature(SlotID(j)) == bestArmy[i]  &&  (i != j || armyPtr != army)) //it's a searched creature not in dst slot
+				if(armyPtr->getCreature(SlotID(j)) == bestArmy[i]  &&  armyPtr != army) //it's a searched creature not in dst ARMY
 					if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army)) //can't take away last creature
 						return true; //at least one exchange will be performed
 			}
@@ -910,7 +926,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * army, const CArmedInstance *
 		for(auto armyPtr : armies)
 			for (int j = 0; j < GameConstants::ARMY_SIZE; j++)
 			{
-				if(armyPtr->getCreature(SlotID(j)) == bestArmy[i]  &&  (i != j || armyPtr != army)) //it's a searched creature not in dst slot
+				if(armyPtr->getCreature(SlotID(j)) == bestArmy[i]  &&  (i != j || armyPtr != army)) //it's a searched creature not in dst SLOT
 					if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army))
 						cb->mergeOrSwapStacks(armyPtr, army, SlotID(j), SlotID(i));
 			}
@@ -1833,6 +1849,16 @@ const CGTownInstance * VCAI::findTownWithTavern() const
 	return nullptr;
 }
 
+Goals::TSubgoal VCAI::getGoal (HeroPtr h) const
+{
+	auto it = lockedHeroes.find(h);
+	if (it != lockedHeroes.end())
+		return it->second;
+	else
+		return sptr(Goals::Invalid());
+}
+
+
 std::vector<HeroPtr> VCAI::getUnblockedHeroes() const
 {
 	std::vector<HeroPtr> ret;
@@ -2179,7 +2205,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 	throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
 }
 
-int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
+int3 VCAI::explorationNewPoint(int radius, HeroPtr h)
 {
     //logAi->debugStream() << "Looking for an another place for exploration...";
 	cb->setSelection(h.h);
@@ -2205,7 +2231,7 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
 		{
 			if (cb->getTile(tile)->blocked) //does it shorten the time?
 				continue;
-			if (!cb->getPathInfo(tile)->reachable())
+			if (!cb->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
 				continue;
 
 			CGPath path;
@@ -2214,14 +2240,101 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
 
 			if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
 			{
-				if((isSafeToVisit(h, tile) || breakUnsafe) && !isBlockedBorderGate(tile))
+				if(isSafeToVisit(h, tile) && !isBlockedBorderGate(tile))
 				{
-					bestTile = tile; //return first tile that will discover anything
+					bestTile = tile;
 					bestValue = ourValue;
 				}
 			}
 		}
 	}
+	//if (!bestValue) //no free spot, we need to fight
+	//{
+	//	SectorMap sm(h);
+
+	//	ui64 lowestDanger = -1;
+
+	//	for (int i = 1; i < radius; i++)
+	//	{
+	//		getVisibleNeighbours(tiles[i-1], tiles[i]);
+	//		removeDuplicates(tiles[i]);
+
+	//		for(const int3 &tile : tiles[i])
+	//		{
+	//			if (cb->getTile(tile)->blocked) //does it shorten the time?
+	//				continue;
+	//			if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much
+	//				continue;
+
+	//			auto t = sm.firstTileToGet(h, tile);
+	//			if (t.valid())
+	//			{
+	//				ui64 ourDanger = evaluateDanger(tile, h.h);
+	//				if (ourDanger < lowestDanger)
+	//				{
+	//					if(!isBlockedBorderGate(tile))
+	//					{
+	//						if (!ourDanger) //at least one safe place found
+	//							return tile;
+
+	//						bestTile = tile;
+	//						lowestDanger = ourDanger;
+	//					}
+	//				}
+	//			}
+	//		}
+	//	}
+	//}
+	return bestTile;
+}
+
+int3 VCAI::explorationDesperate(int radius, HeroPtr h)
+{
+    //logAi->debugStream() << "Looking for an another place for exploration...";
+	SectorMap sm(h);
+	
+	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
+	tiles.resize(radius);
+
+	foreach_tile_pos([&](const int3 &pos)
+	{
+		if(!cb->isVisible(pos))
+			tiles[0].push_back(pos);
+	});
+
+	ui64 lowestDanger = -1;
+	int3 bestTile(-1,-1,-1);
+
+	for (int i = 1; i < radius; i++)
+	{
+		getVisibleNeighbours(tiles[i-1], tiles[i]);
+		removeDuplicates(tiles[i]);
+
+		for(const int3 &tile : tiles[i])
+		{
+			if (cb->getTile(tile)->blocked) //does it shorten the time?
+				continue;
+			if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much
+				continue;
+
+			auto t = sm.firstTileToGet(h, tile);
+			if (t.valid())
+			{
+				ui64 ourDanger = evaluateDanger(t, h.h);
+				if (ourDanger < lowestDanger)
+				{
+					if(!isBlockedBorderGate(t))
+					{
+						if (!ourDanger) //at least one safe place found
+							return t;
+
+						bestTile = t;
+						lowestDanger = ourDanger;
+					}
+				}
+			}
+		}
+	}
 	return bestTile;
 }
 

+ 3 - 1
AI/VCAI/VCAI.h

@@ -178,7 +178,8 @@ public:
 	void tryRealize(Goals::AbstractGoal & g);
 
 	int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
-	int3 explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe = false);
+	int3 explorationNewPoint(int radius, HeroPtr h);
+	int3 explorationDesperate(int radius, HeroPtr h);
 	void recruitHero();
 
 	virtual std::string getBattleAIName() const override;
@@ -296,6 +297,7 @@ public:
 	const CGTownInstance *findTownWithTavern() const;
 	bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
 
+	Goals::TSubgoal getGoal (HeroPtr h) const;
 	bool canAct(HeroPtr h) const;
 	std::vector<HeroPtr> getUnblockedHeroes() const;
 	HeroPtr primaryHero() const;

+ 8 - 8
lib/HeroBonus.h

@@ -964,14 +964,14 @@ namespace Selector
 
 extern DLL_LINKAGE const std::map<std::string, Bonus::BonusType> bonusNameMap;
 extern DLL_LINKAGE const std::map<std::string, Bonus::ValueType> bonusValueMap;
-extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
-extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
-extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
-extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
-extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
-
-
-// BonusList template that requires full interface of CBonusSystemNode
+extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
+extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
+extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
+extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
+extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
+
+
+// BonusList template that requires full interface of CBonusSystemNode
 template <class InputIterator>
 void BonusList::insert(const int position, InputIterator first, InputIterator last)
 {