mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	AI: town portal support
This commit is contained in:
		
				
					committed by
					
						 ArseniyShestakov
						ArseniyShestakov
					
				
			
			
				
	
			
			
			
						parent
						
							2e20dce71f
						
					
				
				
					commit
					8fee46de7c
				
			| @@ -98,6 +98,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g) | ||||
| 	return g.parent->accept(this) - buildBoatPenalty; | ||||
| } | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::AdventureSpellCast & g) | ||||
| { | ||||
| 	if(!g.parent) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	const CSpell * spell = g.getSpell(); | ||||
| 	const float spellCastPenalty = (float)g.hero->getSpellCost(spell) / g.hero->mana; | ||||
|  | ||||
| 	return g.parent->accept(this) - spellCastPenalty; | ||||
| } | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::CompleteQuest & g) | ||||
| { | ||||
| 	// TODO: How to evaluate quest complexity? | ||||
| @@ -120,6 +133,7 @@ float FuzzyHelper::evaluate(Goals::VisitObj & g) | ||||
|  | ||||
| 	return visitObjEngine.evaluate(g); | ||||
| } | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::VisitHero & g) | ||||
| { | ||||
| 	auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar | ||||
|   | ||||
| @@ -33,6 +33,7 @@ public: | ||||
| 	float evaluate(Goals::GatherArmy & g); | ||||
| 	float evaluate(Goals::ClearWayTo & g); | ||||
| 	float evaluate(Goals::CompleteQuest & g); | ||||
| 	float evaluate(Goals::AdventureSpellCast & g); | ||||
| 	float evaluate(Goals::Invalid & g); | ||||
| 	float evaluate(Goals::AbstractGoal & g); | ||||
| 	void setPriority(Goals::TSubgoal & g); | ||||
|   | ||||
| @@ -31,7 +31,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() | ||||
| 	if(!hero.validAndSet()) | ||||
| 		throw cannotFulfillGoalException("Invalid hero!"); | ||||
|  | ||||
| 	auto spell = spellID.toSpell(); | ||||
| 	auto spell = getSpell(); | ||||
|  | ||||
| 	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name); | ||||
|  | ||||
| @@ -49,7 +49,26 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() | ||||
|  | ||||
| void AdventureSpellCast::accept(VCAI * ai) | ||||
| { | ||||
| 	if(town && spellID == SpellID::TOWN_PORTAL) | ||||
| 	{ | ||||
| 		ai->selectedObject = town->id; | ||||
| 	} | ||||
|  | ||||
| 	auto wait = cb->waitTillRealize; | ||||
|  | ||||
| 	cb->waitTillRealize = true; | ||||
| 	cb->castSpell(hero.h, spellID, tile); | ||||
| 	ai->ah->resetPaths(); | ||||
|  | ||||
| 	if(town && spellID == SpellID::TOWN_PORTAL) | ||||
| 	{ | ||||
| 		// visit town | ||||
| 		ai->moveHeroToTile(town->visitablePos(), hero); | ||||
| 	} | ||||
|  | ||||
| 	cb->waitTillRealize = wait; | ||||
|  | ||||
| 	throw goalFulfilledException(sptr(*this)); | ||||
| } | ||||
|  | ||||
| std::string AdventureSpellCast::name() const | ||||
|   | ||||
| @@ -30,6 +30,11 @@ namespace Goals | ||||
| 			return TGoalVec(); | ||||
| 		} | ||||
|  | ||||
| 		const CSpell * getSpell() const | ||||
| 		{  | ||||
| 			return spellID.toSpell(); | ||||
| 		} | ||||
|  | ||||
| 		TSubgoal whatToDoToAchieve() override; | ||||
| 		void accept(VCAI * ai) override; | ||||
| 		std::string name() const override; | ||||
|   | ||||
| @@ -71,6 +71,8 @@ void BuildBoat::accept(VCAI * ai) | ||||
| 		shipyard->bestLocation().toString()); | ||||
|  | ||||
| 	cb->buildBoat(shipyard); | ||||
|  | ||||
| 	throw goalFulfilledException(sptr(*this)); | ||||
| } | ||||
|  | ||||
| std::string BuildBoat::name() const | ||||
|   | ||||
| @@ -57,15 +57,17 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() | ||||
| 	//TODO: include evaluation of monsters gather in calculation | ||||
| 	for(auto t : cb->getTownsInfo()) | ||||
| 	{ | ||||
| 		auto pos = t->visitablePos(); | ||||
| 		if(ai->isAccessibleForHero(pos, hero)) | ||||
| 		auto waysToVisit = ai->ah->howToVisitObj(hero, t); | ||||
|  | ||||
| 		if(waysToVisit.size()) | ||||
| 		{ | ||||
| 			//grab army from town | ||||
| 			if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t)) | ||||
| 			{ | ||||
| 				if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) | ||||
| 					ret.push_back(sptr(VisitTile(pos).sethero(hero))); | ||||
| 					vstd::concatenate(ret, waysToVisit); | ||||
| 			} | ||||
|  | ||||
| 			//buy army in town | ||||
| 			if (!t->visitingHero || t->visitingHero == hero.get(true)) | ||||
| 			{ | ||||
| @@ -118,15 +120,20 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() | ||||
| 		else | ||||
| 		   return false; | ||||
| 	}); | ||||
|  | ||||
| 	for(auto h : otherHeroes) | ||||
| 	{ | ||||
| 		// Go to the other hero if we are faster | ||||
| 		if (!vstd::contains(ai->visitedHeroes[hero], h) | ||||
| 			&& ai->isAccessibleForHero(h->visitablePos(), hero, true)) //visit only once each turn //FIXME: this is only bug workaround | ||||
| 			ret.push_back(sptr(VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero))); | ||||
| 		// Let the other hero come to us | ||||
| 		if (!vstd::contains(ai->visitedHeroes[h], hero)) | ||||
| 			ret.push_back(sptr(VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h))); | ||||
| 		if(!vstd::contains(ai->visitedHeroes[hero], h)) | ||||
| 		{ | ||||
| 			vstd::concatenate(ret, ai->ah->howToVisitObj(hero, h)); | ||||
| 		} | ||||
|  | ||||
| 		// Go to the other hero if we are faster | ||||
| 		if(!vstd::contains(ai->visitedHeroes[h], hero)) | ||||
| 		{ | ||||
| 			vstd::concatenate(ret, ai->ah->howToVisitObj(h, hero.get())); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::vector<const CGObjectInstance *> objs; | ||||
| @@ -161,6 +168,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(auto h : cb->getHeroesInfo()) | ||||
| 	{ | ||||
| 		for(auto obj : objs) | ||||
|   | ||||
| @@ -9,7 +9,12 @@ | ||||
| */ | ||||
| #include "StdInc.h" | ||||
| #include "AINodeStorage.h" | ||||
| #include "../Goals/Goals.h" | ||||
| #include "../../../CCallback.h" | ||||
| #include "../../../lib/mapping/CMap.h" | ||||
| #include "../../../lib/mapObjects/MapObjects.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
|  | ||||
| AINodeStorage::AINodeStorage(const int3 & Sizes) | ||||
| 	: sizes(Sizes) | ||||
| @@ -133,28 +138,128 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours( | ||||
| 	return neighbours; | ||||
| } | ||||
|  | ||||
| void AINodeStorage::setHero(HeroPtr heroPtr) | ||||
| { | ||||
| 	hero = heroPtr.get(); | ||||
| } | ||||
|  | ||||
| class TownPortalAction : public ISpecialAction | ||||
| { | ||||
| private: | ||||
| 	const CGTownInstance * target; | ||||
| 	const HeroPtr  hero; | ||||
|  | ||||
| public: | ||||
| 	TownPortalAction(const CGTownInstance * target) | ||||
| 		:target(target) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override | ||||
| 	{ | ||||
| 		const CGTownInstance * targetTown = target; // const pointer is not allowed in settown | ||||
|  | ||||
| 		return sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos())); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( | ||||
| 	const PathNodeInfo & source, | ||||
| 	const PathfinderConfig * pathfinderConfig, | ||||
| 	const CPathfinderHelper * pathfinderHelper) | ||||
| { | ||||
| 	std::vector<CGPathNode *> neighbours; | ||||
| 	auto accessibleExits = pathfinderHelper->getTeleportExits(source); | ||||
| 	auto srcNode = getAINode(source.node); | ||||
|  | ||||
| 	for(auto & neighbour : accessibleExits) | ||||
| 	if(source.isNodeObjectVisitable()) | ||||
| 	{ | ||||
| 		auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); | ||||
| 		auto accessibleExits = pathfinderHelper->getTeleportExits(source); | ||||
| 		auto srcNode = getAINode(source.node); | ||||
|  | ||||
| 		if(!node) | ||||
| 			continue; | ||||
| 		for(auto & neighbour : accessibleExits) | ||||
| 		{ | ||||
| 			auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); | ||||
|  | ||||
| 		neighbours.push_back(node.get()); | ||||
| 			if(!node) | ||||
| 				continue; | ||||
|  | ||||
| 			neighbours.push_back(node.get()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(hero->getPosition(false) == source.coord) | ||||
| 	{ | ||||
| 		calculateTownPortalTeleportations(source, neighbours); | ||||
| 	} | ||||
|  | ||||
| 	return neighbours; | ||||
| } | ||||
|  | ||||
| void AINodeStorage::calculateTownPortalTeleportations( | ||||
| 	const PathNodeInfo & source, | ||||
| 	std::vector<CGPathNode *> & neighbours) | ||||
| { | ||||
| 	SpellID spellID = SpellID::TOWN_PORTAL; | ||||
| 	const CSpell * townPortal = spellID.toSpell(); | ||||
| 	auto srcNode = getAINode(source.node); | ||||
|  | ||||
| 	if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal)) | ||||
| 	{ | ||||
| 		auto towns = cb->getTownsInfo(false); | ||||
|  | ||||
| 		vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool | ||||
| 		{ | ||||
| 			return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES; | ||||
| 		}); | ||||
|  | ||||
| 		if(!towns.size()) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// TODO: Copy/Paste from TownPortalMechanics | ||||
| 		auto skillLevel = hero->getSpellSchoolLevel(townPortal); | ||||
| 		auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3); | ||||
|  | ||||
| 		if(hero->movement < movementCost) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if(skillLevel < SecSkillLevel::ADVANCED) | ||||
| 		{ | ||||
| 			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int | ||||
| 			{ | ||||
| 				return hero->visitablePos().dist2dSQ(t->visitablePos()); | ||||
| 			}); | ||||
|  | ||||
| 			towns = std::vector<const CGTownInstance *>{ nearestTown }; | ||||
| 		} | ||||
|  | ||||
| 		for(const CGTownInstance * targetTown : towns) | ||||
| 		{ | ||||
| 			if(targetTown->visitingHero) | ||||
| 				continue; | ||||
|  | ||||
| 			auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->chainMask | CAST_CHAIN); | ||||
|  | ||||
| 			if(nodeOptional) | ||||
| 			{ | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 				logAi->trace("Adding town portal node at %s", targetTown->name); | ||||
| #endif | ||||
|  | ||||
| 				AIPathNode * node = nodeOptional.get(); | ||||
|  | ||||
| 				node->theNodeBefore = source.node; | ||||
| 				node->specialAction.reset(new TownPortalAction(targetTown)); | ||||
| 				node->moveRemains = source.node->moveRemains; | ||||
| 				 | ||||
| 				neighbours.push_back(node); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const | ||||
| { | ||||
| 	auto pos = destination.coord; | ||||
| @@ -174,12 +279,14 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode | ||||
| 			if(node.turns < destinationNode->turns | ||||
| 				|| (node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains)) | ||||
| 			{ | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 				logAi->trace( | ||||
| 					"Block ineficient move %s:->%s, mask=%i, mp diff: %i", | ||||
| 					source.coord.toString(), | ||||
| 					destination.coord.toString(), | ||||
| 					destinationNode->chainMask, | ||||
| 					node.moveRemains - destinationNode->moveRemains); | ||||
| #endif | ||||
|  | ||||
| 				return true; | ||||
| 			} | ||||
|   | ||||
| @@ -113,13 +113,13 @@ public: | ||||
| 	std::vector<AIPath> getChainInfo(int3 pos, bool isOnLand) const; | ||||
| 	bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const; | ||||
|  | ||||
| 	void setHero(HeroPtr heroPtr) | ||||
| 	{ | ||||
| 		hero = heroPtr.get(); | ||||
| 	} | ||||
| 	void setHero(HeroPtr heroPtr); | ||||
|  | ||||
| 	const CGHeroInstance * getHero() const | ||||
| 	{ | ||||
| 		return hero; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours); | ||||
| }; | ||||
|   | ||||
| @@ -741,9 +741,7 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons | ||||
| { | ||||
| 	NET_EVENT_HANDLER; | ||||
| 	status.addQuery(askID, "Map object select query"); | ||||
| 	requestActionASAP([=](){ answerQuery(askID, 0); }); | ||||
|  | ||||
| 	//TODO: Town portal destination selection goes here | ||||
| 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); }); | ||||
| } | ||||
|  | ||||
| void VCAI::saveGame(BinarySerializer & h, const int version) | ||||
|   | ||||
| @@ -112,6 +112,7 @@ public: | ||||
| 	std::shared_ptr<CCallback> myCb; | ||||
|  | ||||
| 	std::unique_ptr<boost::thread> makingTurn; | ||||
| 	ObjectInstanceID selectedObject; | ||||
|  | ||||
| 	AIhelper * ah; | ||||
|  | ||||
|   | ||||
| @@ -710,7 +710,7 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo( | ||||
| 	{ | ||||
| 		for(const auto & town : i.second.towns) | ||||
| 		{ | ||||
| 			if (i.first==player || (isVisible(town, player) && !onlyOur)) | ||||
| 			if(i.first == player || (!onlyOur && isVisible(town, player))) | ||||
| 			{ | ||||
| 				ret.push_back(town); | ||||
| 			} | ||||
|   | ||||
| @@ -56,6 +56,10 @@ std::vector<CGPathNode *> NodeStorage::calculateTeleportations( | ||||
| 	const CPathfinderHelper * pathfinderHelper) | ||||
| { | ||||
| 	std::vector<CGPathNode *> neighbours; | ||||
|  | ||||
| 	if(!source.isNodeObjectVisitable()) | ||||
| 		return neighbours; | ||||
|  | ||||
| 	auto accessibleExits = pathfinderHelper->getTeleportExits(source); | ||||
|  | ||||
| 	for(auto & neighbour : accessibleExits) | ||||
| @@ -315,7 +319,7 @@ void CPathfinder::calculatePaths() | ||||
|  | ||||
| 		/// For now we disable teleports usage for patrol movement | ||||
| 		/// VCAI not aware about patrol and may stuck while attempt to use teleport | ||||
| 		if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS) | ||||
| 		if(patrolState == PATROL_RADIUS) | ||||
| 			continue; | ||||
|  | ||||
| 		auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get()); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user