mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	AI: CompleteQuest goal and summon boat spell support
This commit is contained in:
		
				
					committed by
					
						 ArseniyShestakov
						ArseniyShestakov
					
				
			
			
				
	
			
			
			
						parent
						
							e996879733
						
					
				
				
					commit
					5d022ba77c
				
			| @@ -29,6 +29,7 @@ set(VCAI_SRCS | ||||
| 		Goals/GatherArmy.cpp | ||||
| 		Goals/GatherTroops.cpp | ||||
| 		Goals/BuyArmy.cpp | ||||
| 		Goals/AdventureSpellCast.cpp | ||||
| 		Goals/Win.cpp | ||||
| 		Goals/VisitTile.cpp | ||||
| 		Goals/VisitObj.cpp | ||||
| @@ -41,6 +42,7 @@ set(VCAI_SRCS | ||||
| 		Goals/DigAtTile.cpp | ||||
| 		Goals/GetArtOfType.cpp | ||||
| 		Goals/FindObj.cpp | ||||
| 		Goals/CompleteQuest.cpp | ||||
| 		main.cpp | ||||
| 		VCAI.cpp | ||||
| ) | ||||
| @@ -71,6 +73,7 @@ set(VCAI_HEADERS | ||||
| 		Goals/GatherArmy.h | ||||
| 		Goals/GatherTroops.h | ||||
| 		Goals/BuyArmy.h | ||||
| 		Goals/AdventureSpellCast.h | ||||
| 		Goals/Win.h | ||||
| 		Goals/VisitTile.h | ||||
| 		Goals/VisitObj.h | ||||
| @@ -83,6 +86,7 @@ set(VCAI_HEADERS | ||||
| 		Goals/DigAtTile.h | ||||
| 		Goals/GetArtOfType.h | ||||
| 		Goals/FindObj.h | ||||
| 		Goals/CompleteQuest.h | ||||
| 		Goals/Goals.h | ||||
| 		VCAI.h | ||||
| ) | ||||
|   | ||||
| @@ -93,6 +93,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g) | ||||
| 	return g.parent->accept(this) - buildBoatPenalty; | ||||
| } | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::CompleteQuest & g) | ||||
| { | ||||
| 	// TODO: How to evaluate quest complexity? | ||||
| 	const float questPenalty = 0.2; | ||||
|  | ||||
| 	if(!g.parent) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	return g.parent->accept(this) * questPenalty; | ||||
| } | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::VisitObj & g) | ||||
| { | ||||
| 	return visitObjEngine.evaluate(g); | ||||
|   | ||||
| @@ -32,6 +32,7 @@ public: | ||||
| 	float evaluate(Goals::BuildBoat & g); | ||||
| 	float evaluate(Goals::GatherArmy & g); | ||||
| 	float evaluate(Goals::ClearWayTo & g); | ||||
| 	float evaluate(Goals::CompleteQuest & g); | ||||
| 	float evaluate(Goals::Invalid & g); | ||||
| 	float evaluate(Goals::AbstractGoal & g); | ||||
| 	void setPriority(Goals::TSubgoal & g); | ||||
|   | ||||
| @@ -40,8 +40,6 @@ std::string AbstractGoal::name() const //TODO: virtualize | ||||
| 		return "INVALID"; | ||||
| 	case WIN: | ||||
| 		return "WIN"; | ||||
| 	case DO_NOT_LOSE: | ||||
| 		return "DO NOT LOOSE"; | ||||
| 	case CONQUER: | ||||
| 		return "CONQUER"; | ||||
| 	case BUILD: | ||||
| @@ -95,8 +93,6 @@ std::string AbstractGoal::name() const //TODO: virtualize | ||||
| 	case GET_ART_TYPE: | ||||
| 		desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name(); | ||||
| 		break; | ||||
| 	case ISSUE_COMMAND: | ||||
| 		return "ISSUE COMMAND (unsupported)"; | ||||
| 	case VISIT_TILE: | ||||
| 		desc = "VISIT TILE " + tile.toString(); | ||||
| 		break; | ||||
|   | ||||
| @@ -37,6 +37,35 @@ namespace Goals | ||||
| 	class ClearWayTo; | ||||
| 	class Invalid; | ||||
| 	class Trade; | ||||
| 	class CompleteQuest; | ||||
| 	class AdventureSpellCast; | ||||
|  | ||||
| 	enum EGoals | ||||
| 	{ | ||||
| 		INVALID = -1, | ||||
| 		WIN, CONQUER, BUILD, //build needs to get a real reasoning | ||||
| 		EXPLORE, GATHER_ARMY, | ||||
| 		BOOST_HERO, | ||||
| 		RECRUIT_HERO, | ||||
| 		BUILD_STRUCTURE, //if hero set, then in visited town | ||||
| 		COLLECT_RES, | ||||
| 		GATHER_TROOPS, // val of creatures with objid | ||||
|  | ||||
| 		VISIT_OBJ, //visit or defeat or collect the object | ||||
| 		FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid) | ||||
| 		VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn | ||||
|  | ||||
| 		GET_ART_TYPE, | ||||
|  | ||||
| 		VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable | ||||
| 		CLEAR_WAY_TO, | ||||
| 		DIG_AT_TILE,//elementar with hero on tile | ||||
| 		BUY_ARMY, //at specific town | ||||
| 		TRADE, //val resID at object objid | ||||
| 		BUILD_BOAT, | ||||
| 		COMPLETE_QUEST, | ||||
| 		ADVENTURE_SPELL_CAST | ||||
| 	}; | ||||
|  | ||||
| 	class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal> | ||||
| 	{ | ||||
| @@ -48,35 +77,6 @@ namespace Goals | ||||
|  | ||||
| 	typedef std::vector<TSubgoal> TGoalVec; | ||||
|  | ||||
| 	enum EGoals | ||||
| 	{ | ||||
| 		INVALID = -1, | ||||
| 		WIN, DO_NOT_LOSE, CONQUER, BUILD, //build needs to get a real reasoning | ||||
| 		EXPLORE, GATHER_ARMY, | ||||
| 		BOOST_HERO, | ||||
| 		RECRUIT_HERO, | ||||
| 		BUILD_STRUCTURE, //if hero set, then in visited town | ||||
| 		COLLECT_RES, | ||||
| 		GATHER_TROOPS, // val of creatures with objid | ||||
|  | ||||
| 		OBJECT_GOALS_BEGIN, | ||||
| 		VISIT_OBJ, //visit or defeat or collect the object | ||||
| 		FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid) | ||||
| 		VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn | ||||
|  | ||||
| 		GET_ART_TYPE, | ||||
|  | ||||
| 		//BUILD_STRUCTURE, | ||||
| 		ISSUE_COMMAND, | ||||
|  | ||||
| 		VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable | ||||
| 		CLEAR_WAY_TO, | ||||
| 		DIG_AT_TILE,//elementar with hero on tile | ||||
| 		BUY_ARMY, //at specific town | ||||
| 		TRADE, //val resID at object objid | ||||
| 		BUILD_BOAT | ||||
| 	}; | ||||
|  | ||||
| 	//method chaining + clone pattern | ||||
| #define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;}; | ||||
| #define OSETTER(type, field) CGoal<T> & set ## field(const type &rhs) override { field = rhs; return *this; }; | ||||
|   | ||||
							
								
								
									
										63
									
								
								AI/VCAI/Goals/AdventureSpellCast.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								AI/VCAI/Goals/AdventureSpellCast.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
| * AdventureSpellCast.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 "AdventureSpellCast.h" | ||||
| #include "../VCAI.h" | ||||
| #include "../FuzzyHelper.h" | ||||
| #include "../AIhelper.h" | ||||
| #include "../../lib/mapping/CMap.h" //for victory conditions | ||||
| #include "../../lib/CPathfinder.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| extern boost::thread_specific_ptr<VCAI> ai; | ||||
| extern FuzzyHelper * fh; | ||||
|  | ||||
| using namespace Goals; | ||||
|  | ||||
| bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const | ||||
| { | ||||
| 	return hero.h == other.hero.h; | ||||
| } | ||||
|  | ||||
| TSubgoal AdventureSpellCast::whatToDoToAchieve() | ||||
| { | ||||
| 	if(!hero.validAndSet()) | ||||
| 		throw cannotFulfillGoalException("Invalid hero!"); | ||||
|  | ||||
| 	auto spell = spellID.toSpell(); | ||||
|  | ||||
| 	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name); | ||||
|  | ||||
| 	if(!spell->isAdventureSpell()) | ||||
| 		throw cannotFulfillGoalException(spell->name + " is not an adventure spell."); | ||||
|  | ||||
| 	if(!vstd::contains(hero->spells, spellID)) | ||||
| 		throw cannotFulfillGoalException("Hero has no " + spell->name); | ||||
|  | ||||
| 	if(hero->mana < hero->getSpellCost(spellID.toSpell())) | ||||
| 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name); | ||||
|  | ||||
| 	return iAmElementar(); | ||||
| } | ||||
|  | ||||
| void AdventureSpellCast::accept(VCAI * ai) | ||||
| { | ||||
| 	cb->castSpell(hero.h, spellID, tile); | ||||
| } | ||||
|  | ||||
| std::string AdventureSpellCast::name() const | ||||
| { | ||||
| 	return "AdventureSpellCast " + spellID.toSpell()->name; | ||||
| } | ||||
|  | ||||
| std::string AdventureSpellCast::completeMessage() const | ||||
| { | ||||
| 	return "Spell casted successfully  " + spellID.toSpell()->name; | ||||
| } | ||||
							
								
								
									
										39
									
								
								AI/VCAI/Goals/AdventureSpellCast.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								AI/VCAI/Goals/AdventureSpellCast.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| /* | ||||
| * AdventureSpellCast.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 "CGoal.h" | ||||
|  | ||||
| namespace Goals | ||||
| { | ||||
| 	class DLL_EXPORT AdventureSpellCast : public CGoal<AdventureSpellCast> | ||||
| 	{ | ||||
| 	private: | ||||
| 		SpellID spellID; | ||||
|  | ||||
| 	public: | ||||
| 		AdventureSpellCast(HeroPtr hero, SpellID spellID) | ||||
| 			: CGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID) | ||||
| 		{ | ||||
| 			sethero(hero); | ||||
| 		} | ||||
|  | ||||
| 		TGoalVec getAllPossibleSubgoals() override | ||||
| 		{ | ||||
| 			return TGoalVec(); | ||||
| 		} | ||||
|  | ||||
| 		TSubgoal whatToDoToAchieve() override; | ||||
| 		void accept(VCAI * ai) override; | ||||
| 		std::string name() const override; | ||||
| 		std::string completeMessage() const override; | ||||
| 		virtual bool operator==(const AdventureSpellCast & other) const override; | ||||
| 	}; | ||||
| } | ||||
| @@ -60,11 +60,13 @@ namespace Goals | ||||
| 		{ | ||||
| 			return new T(static_cast<T const &>(*this)); //casting enforces template instantiation | ||||
| 		} | ||||
| 		TSubgoal iAmElementar() | ||||
| 		TSubgoal iAmElementar() const | ||||
| 		{ | ||||
| 			setisElementar(true); //FIXME: it's not const-correct, maybe we shoudl only set returned clone? | ||||
| 			TSubgoal ptr; | ||||
|  | ||||
| 			ptr.reset(clone()); | ||||
| 			ptr->setisElementar(true); | ||||
|  | ||||
| 			return ptr; | ||||
| 		} | ||||
| 		template<typename Handler> void serialize(Handler & h, const int version) | ||||
| @@ -79,7 +81,7 @@ namespace Goals | ||||
| 			if(goalType != g.goalType) | ||||
| 				return false; | ||||
|  | ||||
| 			return (*this) == (dynamic_cast<const T &>(g)); | ||||
| 			return (*this) == (static_cast<const T &>(g)); | ||||
| 		} | ||||
|  | ||||
| 		virtual bool operator==(const T & other) const = 0; | ||||
|   | ||||
							
								
								
									
										276
									
								
								AI/VCAI/Goals/CompleteQuest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								AI/VCAI/Goals/CompleteQuest.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,276 @@ | ||||
| /* | ||||
| * CompleteQuest.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 "Goals.h" | ||||
| #include "../VCAI.h" | ||||
| #include "../FuzzyHelper.h" | ||||
| #include "../AIhelper.h" | ||||
| #include "../../lib/mapping/CMap.h" //for victory conditions | ||||
| #include "../../lib/CPathfinder.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| extern boost::thread_specific_ptr<VCAI> ai; | ||||
| extern FuzzyHelper * fh; | ||||
|  | ||||
| using namespace Goals; | ||||
|  | ||||
| bool CompleteQuest::operator==(const CompleteQuest & other) const | ||||
| { | ||||
| 	return q.quest->qid == other.q.quest->qid; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::getAllPossibleSubgoals() | ||||
| { | ||||
| 	TGoalVec solutions; | ||||
|  | ||||
| 	if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE) | ||||
| 	{ | ||||
| 		logAi->debug("Trying to realize quest: %s", questToString()); | ||||
|  | ||||
| 		switch(q.quest->missionType) | ||||
| 		{ | ||||
| 		case CQuest::MISSION_ART: | ||||
| 			return missionArt(); | ||||
|  | ||||
| 		case CQuest::MISSION_HERO: | ||||
| 			return missionHero(); | ||||
|  | ||||
| 		case CQuest::MISSION_ARMY: | ||||
| 			return missionArmy(); | ||||
|  | ||||
| 		case CQuest::MISSION_RESOURCES: | ||||
| 			return missionResources(); | ||||
|  | ||||
| 		case CQuest::MISSION_KILL_HERO: | ||||
| 		case CQuest::MISSION_KILL_CREATURE: | ||||
| 			return missionDestroyObj(); | ||||
|  | ||||
| 		case CQuest::MISSION_PRIMARY_STAT: | ||||
| 			return missionIncreasePrimaryStat(); | ||||
|  | ||||
| 		case CQuest::MISSION_LEVEL: | ||||
| 			return missionLevel(); | ||||
|  | ||||
| 		case CQuest::MISSION_PLAYER: | ||||
| 			if(ai->playerID.getNum() != q.quest->m13489val) | ||||
| 				logAi->debug("Can't be player of color %d", q.quest->m13489val); | ||||
|  | ||||
| 			break; | ||||
| 		 | ||||
| 		case CQuest::MISSION_KEYMASTER: | ||||
| 			return missionKeymaster(); | ||||
|  | ||||
| 		} //end of switch | ||||
| 	} | ||||
|  | ||||
| 	return TGoalVec(); | ||||
| } | ||||
|  | ||||
| TSubgoal CompleteQuest::whatToDoToAchieve() | ||||
| { | ||||
| 	if(q.quest->missionType == CQuest::MISSION_NONE) | ||||
| 	{ | ||||
| 		throw cannotFulfillGoalException("Can not complete inactive quest"); | ||||
| 	} | ||||
|  | ||||
| 	TGoalVec solutions = getAllPossibleSubgoals(); | ||||
|  | ||||
| 	if(solutions.empty()) | ||||
| 		throw cannotFulfillGoalException("Can not complete quest " + questToString()); | ||||
|  | ||||
| 	TSubgoal result = fh->chooseSolution(solutions); | ||||
|  | ||||
| 	logAi->trace( | ||||
| 		"Returning %s, tile: %s, objid: %d, hero: %s", | ||||
| 		result->name(), | ||||
| 		result->tile.toString(), | ||||
| 		result->objid, | ||||
| 		result->hero.validAndSet() ? result->hero->name : "not specified"); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::string CompleteQuest::name() const | ||||
| { | ||||
| 	return "CompleteQuest"; | ||||
| } | ||||
|  | ||||
| std::string CompleteQuest::completeMessage() const | ||||
| { | ||||
| 	return "Completed quest " + questToString(); | ||||
| } | ||||
|  | ||||
| std::string CompleteQuest::questToString() const | ||||
| { | ||||
| 	if(q.quest->missionType == CQuest::MISSION_NONE) | ||||
| 		return "inactive quest"; | ||||
|  | ||||
| 	MetaString ms; | ||||
| 	q.quest->getRolloverText(ms, false); | ||||
|  | ||||
| 	return ms.toString(); | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::tryCompleteQuest() const | ||||
| { | ||||
| 	TGoalVec solutions; | ||||
|  | ||||
| 	auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities? | ||||
|  | ||||
| 	for(auto hero : heroes) | ||||
| 	{ | ||||
| 		if(q.quest->checkQuest(hero)) | ||||
| 		{ | ||||
| 			vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id))); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionArt() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(!solutions.empty()) | ||||
| 		return solutions; | ||||
|  | ||||
| 	for(auto art : q.quest->m5arts) | ||||
| 	{ | ||||
| 		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport? | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionHero() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(solutions.empty()) | ||||
| 	{ | ||||
| 		//rule of a thumb - quest heroes usually are locked in prisons | ||||
| 		solutions.push_back(sptr(FindObj(Obj::PRISON))); | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionArmy() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(!solutions.empty()) | ||||
| 		return solutions; | ||||
|  | ||||
| 	for(auto creature : q.quest->m6creatures) | ||||
| 	{ | ||||
| 		solutions.push_back(sptr(GatherTroops(creature.type->idNumber, creature.count))); | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionIncreasePrimaryStat() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(solutions.empty()) | ||||
| 	{ | ||||
| 		for(int i = 0; i < q.quest->m2stats.size(); ++i) | ||||
| 		{ | ||||
| 			// TODO: library, school and other boost objects | ||||
| 			logAi->debug("Don't know how to increase primary stat %d", i); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionLevel() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(solutions.empty()) | ||||
| 	{ | ||||
| 		logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val); | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionKeymaster() const | ||||
| { | ||||
| 	TGoalVec solutions = tryCompleteQuest(); | ||||
|  | ||||
| 	if(solutions.empty()) | ||||
| 	{ | ||||
| 		solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID))); | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionResources() const | ||||
| { | ||||
| 	TGoalVec solutions; | ||||
|  | ||||
| 	auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities? | ||||
|  | ||||
| 	if(heroes.size()) | ||||
| 	{ | ||||
| 		if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is | ||||
| 		{ | ||||
| 			return ai->ah->howToVisitObj(q.obj); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			for(int i = 0; i < q.quest->m7resources.size(); ++i) | ||||
| 			{ | ||||
| 				if(q.quest->m7resources[i]) | ||||
| 					solutions.push_back(sptr(CollectRes(i, q.quest->m7resources[i]))); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		solutions.push_back(sptr(Goals::RecruitHero())); //FIXME: checkQuest requires any hero belonging to player :( | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
|  | ||||
| TGoalVec CompleteQuest::missionDestroyObj() const | ||||
| { | ||||
| 	TGoalVec solutions; | ||||
|  | ||||
| 	auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val); | ||||
|  | ||||
| 	if(!obj) | ||||
| 		return ai->ah->howToVisitObj(q.obj); | ||||
|  | ||||
| 	if(obj->ID == Obj::HERO) | ||||
| 	{ | ||||
| 		auto relations = cb->getPlayerRelations(ai->playerID, obj->tempOwner); | ||||
|  | ||||
| 		if(relations == PlayerRelations::SAME_PLAYER) | ||||
| 		{ | ||||
| 			auto heroToProtect = cb->getHero(obj->id); | ||||
|  | ||||
| 			solutions.push_back(sptr(GatherArmy().sethero(heroToProtect))); | ||||
| 		} | ||||
| 		else if(relations == PlayerRelations::ENEMIES) | ||||
| 		{ | ||||
| 			solutions = ai->ah->howToVisitObj(obj); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return solutions; | ||||
| } | ||||
							
								
								
									
										46
									
								
								AI/VCAI/Goals/CompleteQuest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								AI/VCAI/Goals/CompleteQuest.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| /* | ||||
| * CompleteQuest.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 "CGoal.h" | ||||
| #include "../../../lib/VCMI_Lib.h" | ||||
|  | ||||
| namespace Goals | ||||
| { | ||||
| 	class DLL_EXPORT CompleteQuest : public CGoal<CompleteQuest> | ||||
| 	{ | ||||
| 	private: | ||||
| 		const QuestInfo q; | ||||
|  | ||||
| 	public: | ||||
| 		CompleteQuest(const QuestInfo quest) | ||||
| 			: CGoal(Goals::COMPLETE_QUEST), q(quest) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		TGoalVec getAllPossibleSubgoals() override; | ||||
| 		TSubgoal whatToDoToAchieve() override; | ||||
| 		std::string name() const override; | ||||
| 		std::string completeMessage() const override; | ||||
| 		virtual bool operator==(const CompleteQuest & other) const override; | ||||
|  | ||||
| 	private: | ||||
| 		TGoalVec tryCompleteQuest() const; | ||||
| 		TGoalVec missionArt() const; | ||||
| 		TGoalVec missionHero() const; | ||||
| 		TGoalVec missionArmy() const; | ||||
| 		TGoalVec missionResources() const; | ||||
| 		TGoalVec missionDestroyObj() const; | ||||
| 		TGoalVec missionIncreasePrimaryStat() const; | ||||
| 		TGoalVec missionLevel() const; | ||||
| 		TGoalVec missionKeymaster() const; | ||||
| 		std::string questToString() const; | ||||
| 	}; | ||||
| } | ||||
| @@ -33,6 +33,8 @@ bool Conquer::operator==(const Conquer & other) const | ||||
|  | ||||
| TSubgoal Conquer::whatToDoToAchieve() | ||||
| { | ||||
| 	logAi->trace("Entering goal CONQUER"); | ||||
|  | ||||
| 	return fh->chooseSolution(getAllPossibleSubgoals()); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -48,6 +48,8 @@ int GatherTroops::getCreaturesCount(const CArmedInstance * army) | ||||
|  | ||||
| TSubgoal GatherTroops::whatToDoToAchieve() | ||||
| { | ||||
| 	logAi->trace("Entering GatherTroops::whatToDoToAchieve"); | ||||
|  | ||||
| 	auto heroes = cb->getHeroesInfo(true); | ||||
|  | ||||
| 	for(auto hero : heroes) | ||||
|   | ||||
| @@ -29,4 +29,6 @@ | ||||
| #include "GetArtOfType.h" | ||||
| #include "ClearWayTo.h" | ||||
| #include "DigAtTile.h" | ||||
| #include "FindObj.h" | ||||
| #include "FindObj.h" | ||||
| #include "CompleteQuest.h" | ||||
| #include "AdventureSpellCast.h" | ||||
| @@ -82,6 +82,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat | ||||
|  | ||||
| 		heroNode.chainMask = 0; | ||||
| 		heroNode.danger = 0; | ||||
| 		heroNode.manaCost = 0; | ||||
| 		heroNode.specialAction.reset(); | ||||
| 		heroNode.update(coord, layer, accessibility); | ||||
| 	} | ||||
| @@ -97,6 +98,12 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf | ||||
| 		dstNode->danger = srcNode->danger; | ||||
| 		dstNode->action = destination.action; | ||||
| 		dstNode->theNodeBefore = srcNode->theNodeBefore; | ||||
| 		dstNode->manaCost = srcNode->manaCost; | ||||
| 		 | ||||
| 		if(dstNode->specialAction) | ||||
| 		{ | ||||
| 			dstNode->specialAction->applyOnDestination(getHero(), destination, source, dstNode, srcNode); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @@ -186,6 +193,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const | ||||
| { | ||||
| 	std::vector<AIPath> paths; | ||||
| 	auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL]; | ||||
| 	auto initialPos = hero->visitablePos(); | ||||
|  | ||||
| 	for(const AIPathNode & node : chains) | ||||
| 	{ | ||||
| @@ -197,7 +205,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const | ||||
| 		AIPath path; | ||||
| 		const AIPathNode * current = &node; | ||||
|  | ||||
| 		while(current != nullptr) | ||||
| 		while(current != nullptr && current->coord != initialPos) | ||||
| 		{ | ||||
| 			AIPathNodeInfo pathNode; | ||||
|  | ||||
|   | ||||
| @@ -15,16 +15,28 @@ | ||||
| #include "../AIUtility.h" | ||||
| #include "../Goals/AbstractGoal.h" | ||||
|  | ||||
| class AIPathNode; | ||||
|  | ||||
| class ISpecialAction | ||||
| { | ||||
| public: | ||||
| 	virtual Goals::TSubgoal whatToDo(HeroPtr hero) const = 0; | ||||
|  | ||||
| 	virtual void applyOnDestination( | ||||
| 		HeroPtr hero, | ||||
| 		CDestinationNodeInfo & destination,  | ||||
| 		const PathNodeInfo & source, | ||||
| 		AIPathNode * dstMode, | ||||
| 		const AIPathNode * srcNode) const | ||||
| 	{ | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct AIPathNode : public CGPathNode | ||||
| { | ||||
| 	uint32_t chainMask; | ||||
| 	uint64_t danger; | ||||
| 	uint32_t manaCost; | ||||
| 	std::shared_ptr<const ISpecialAction> specialAction; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -48,10 +48,10 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) | ||||
| 		} | ||||
|  | ||||
| 		storageMap[hero] = nodeStorage; | ||||
| 		nodeStorage->setHero(hero.get()); | ||||
| 		 | ||||
| 		auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage); | ||||
|  | ||||
| 		nodeStorage->setHero(hero.get()); | ||||
| 		cb->calculatePaths(config, hero.get()); | ||||
| 	} | ||||
| 	else | ||||
|   | ||||
| @@ -16,14 +16,31 @@ | ||||
|  | ||||
| namespace AIPathfinding | ||||
| { | ||||
| 	class BuildBoatAction : public ISpecialAction | ||||
| 	class VirtualBoatAction : public ISpecialAction | ||||
| 	{ | ||||
| 	private: | ||||
| 		uint64_t specialChain; | ||||
|  | ||||
| 	public: | ||||
| 		VirtualBoatAction(uint64_t specialChain) | ||||
| 			:specialChain(specialChain) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		uint64_t getSpecialChain() const | ||||
| 		{ | ||||
| 			return specialChain; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	class BuildBoatAction : public VirtualBoatAction | ||||
| 	{ | ||||
| 	private: | ||||
| 		const IShipyard * shipyard; | ||||
|  | ||||
| 	public: | ||||
| 		BuildBoatAction(const IShipyard * shipyard) | ||||
| 			:shipyard(shipyard) | ||||
| 			:VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| @@ -33,6 +50,51 @@ namespace AIPathfinding | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	class SummonBoatAction : public VirtualBoatAction | ||||
| 	{ | ||||
| 	public: | ||||
| 		SummonBoatAction() | ||||
| 			:VirtualBoatAction(AINodeStorage::CAST_CHAIN) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override | ||||
| 		{ | ||||
| 			return sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT)); | ||||
| 		} | ||||
|  | ||||
| 		virtual void applyOnDestination( | ||||
| 			HeroPtr hero, | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathNodeInfo & source, | ||||
| 			AIPathNode * dstMode, | ||||
| 			const AIPathNode * srcNode) const override | ||||
| 		{ | ||||
| 			dstMode->manaCost = srcNode->manaCost + getManaCost(hero); | ||||
| 			dstMode->theNodeBefore = source.node; | ||||
| 		} | ||||
|  | ||||
| 		bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const | ||||
| 		{ | ||||
| 			logAi->trace( | ||||
| 				"Hero %s has %d mana and needed %d and already spent %d",  | ||||
| 				hero->name,  | ||||
| 				hero->mana,  | ||||
| 				getManaCost(hero), | ||||
| 				source->manaCost); | ||||
|  | ||||
| 			return hero->mana >= source->manaCost + getManaCost(hero); | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint32_t getManaCost(HeroPtr hero) const | ||||
| 		{ | ||||
| 			SpellID summonBoat = SpellID::SUMMON_BOAT; | ||||
|  | ||||
| 			return hero->getSpellCost(summonBoat.toSpell()); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	class BattleAction : public ISpecialAction | ||||
| 	{ | ||||
| 	private: | ||||
| @@ -58,6 +120,7 @@ namespace AIPathfinding | ||||
| 		VCAI * ai; | ||||
| 		std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats; | ||||
| 		std::shared_ptr<AINodeStorage> nodeStorage; | ||||
| 		std::shared_ptr<const SummonBoatAction> summonableVirtualBoat; | ||||
|  | ||||
| 	public: | ||||
| 		AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage) | ||||
| @@ -79,37 +142,14 @@ namespace AIPathfinding | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL | ||||
| 				&& vstd::contains(virtualBoats, destination.coord)) | ||||
| 			if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL) | ||||
| 			{ | ||||
| 				logAi->trace("Bypassing virtual boat at %s!", destination.coord.toString()); | ||||
| 				std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source); | ||||
|  | ||||
| 				nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) | ||||
| 				if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat)) | ||||
| 				{ | ||||
| 					std::shared_ptr<const BuildBoatAction> virtualBoat = virtualBoats.at(destination.coord); | ||||
|  | ||||
| 					auto boatNodeOptional = nodeStorage->getOrCreateNode( | ||||
| 						node->coord, | ||||
| 						node->layer, | ||||
| 						node->chainMask | AINodeStorage::RESOURCE_CHAIN); | ||||
|  | ||||
| 					if(boatNodeOptional) | ||||
| 					{ | ||||
| 						AIPathNode * boatNode = boatNodeOptional.get(); | ||||
|  | ||||
| 						boatNode->specialAction = virtualBoat; | ||||
| 						destination.blocked = false; | ||||
| 						destination.action = CGPathNode::ENodeAction::EMBARK; | ||||
| 						destination.node = boatNode; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						logAi->trace( | ||||
| 							"Can not allocate boat node while moving %s -> %s", | ||||
| 							source.coord.toString(), | ||||
| 							destination.coord.toString()); | ||||
| 					} | ||||
| 				}); | ||||
| 					logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -142,6 +182,84 @@ namespace AIPathfinding | ||||
| 					logAi->debug("Virtual boat added at %s", boatLocation.toString()); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			auto hero = nodeStorage->getHero(); | ||||
|  | ||||
| 			if(vstd::contains(hero->spells, SpellID::SUMMON_BOAT)) | ||||
| 			{ | ||||
| 				auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell(); | ||||
|  | ||||
| 				if(hero->getSpellSchoolLevel(summonBoatSpell) == SecSkillLevel::EXPERT) | ||||
| 				{ | ||||
| 					summonableVirtualBoat.reset(new SummonBoatAction()); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		std::shared_ptr<const VirtualBoatAction> findVirtualBoat( | ||||
| 			CDestinationNodeInfo &destination, | ||||
| 			const PathNodeInfo &source) const | ||||
| 		{ | ||||
| 			std::shared_ptr<const VirtualBoatAction> virtualBoat; | ||||
|  | ||||
| 			if(vstd::contains(virtualBoats, destination.coord)) | ||||
| 			{ | ||||
| 				virtualBoat = virtualBoats.at(destination.coord); | ||||
| 			} | ||||
| 			else if( | ||||
| 				summonableVirtualBoat | ||||
| 				&& summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node))) | ||||
| 			{ | ||||
| 				virtualBoat = summonableVirtualBoat; | ||||
| 			} | ||||
|  | ||||
| 			return virtualBoat; | ||||
| 		} | ||||
|  | ||||
| 		bool tryEmbarkVirtualBoat( | ||||
| 			CDestinationNodeInfo &destination,  | ||||
| 			const PathNodeInfo &source, | ||||
| 			std::shared_ptr<const VirtualBoatAction> virtualBoat) const | ||||
| 		{ | ||||
| 			bool result = false; | ||||
|  | ||||
| 			nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) | ||||
| 			{ | ||||
| 				auto boatNodeOptional = nodeStorage->getOrCreateNode( | ||||
| 					node->coord, | ||||
| 					node->layer, | ||||
| 					node->chainMask | virtualBoat->getSpecialChain()); | ||||
|  | ||||
| 				if(boatNodeOptional) | ||||
| 				{ | ||||
| 					AIPathNode * boatNode = boatNodeOptional.get(); | ||||
|  | ||||
| 					if(boatNode->action == CGPathNode::NOT_SET) | ||||
| 					{ | ||||
| 						boatNode->specialAction = virtualBoat; | ||||
| 						destination.blocked = false; | ||||
| 						destination.action = CGPathNode::ENodeAction::EMBARK; | ||||
| 						destination.node = boatNode; | ||||
| 						result = true; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						logAi->trace( | ||||
| 							"Special transition node already allocated. Blocked moving %s -> %s", | ||||
| 							source.coord.toString(), | ||||
| 							destination.coord.toString()); | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					logAi->trace( | ||||
| 						"Can not allocate special transition node while moving %s -> %s", | ||||
| 						source.coord.toString(), | ||||
| 						destination.coord.toString()); | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			return result; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| #include "PathfindingManager.h" | ||||
| #include "AIPathfinder.h" | ||||
| #include "AIPathfinderConfig.h" | ||||
| #include "Goals/Goals.h" | ||||
| #include "../Goals/Goals.h" | ||||
| #include "../../../lib/CGameInfoCallback.h" | ||||
| #include "../../../lib/mapping/CMap.h" | ||||
|  | ||||
| @@ -130,8 +130,6 @@ Goals::TGoalVec PathfindingManager::findPath( | ||||
|  | ||||
| 			if(isSafeToVisit(hero, danger)) | ||||
| 			{ | ||||
| 				logAi->trace("It's safe for %s to visit tile %s with danger %s", hero->name, dest.toString(), std::to_string(danger)); | ||||
|  | ||||
| 				Goals::TSubgoal solution; | ||||
|  | ||||
| 				if(path.specialAction) | ||||
| @@ -153,6 +151,8 @@ Goals::TGoalVec PathfindingManager::findPath( | ||||
| 				 | ||||
| 				solution->evaluationContext.movementCost += path.movementCost(); | ||||
|  | ||||
| 				logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name()); | ||||
|  | ||||
| 				result.push_back(solution); | ||||
|  | ||||
| 				continue; | ||||
| @@ -212,11 +212,15 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet | ||||
| 				return sptr(Goals::VisitObj(topObj->id.getNum()).sethero(hero)); | ||||
| 			} | ||||
|  | ||||
| 			//TODO: we should be able to return apriopriate quest here | ||||
| 			//ret.push_back(ai->questToGoal()); | ||||
| 			//however, visiting obj for firts time will give us quest | ||||
| 			//do not access quets guard if we can't complete the quest | ||||
| 			logAi->trace("Can not visit this quest guard! Not ready!"); | ||||
| 			auto questObj = dynamic_cast<const IQuestObject*>(topObj); | ||||
| 			 | ||||
| 			if(questObj) | ||||
| 			{ | ||||
| 				auto questInfo = QuestInfo(questObj->quest, topObj, topObj->visitablePos()); | ||||
|  | ||||
| 				return sptr(Goals::CompleteQuest(questInfo)); | ||||
| 			} | ||||
|  | ||||
| 			return sptr(Goals::Invalid()); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										168
									
								
								AI/VCAI/VCAI.cpp
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								AI/VCAI/VCAI.cpp
									
									
									
									
									
								
							| @@ -873,7 +873,7 @@ void VCAI::mainLoop() | ||||
| 		basicGoals.push_back(ah->whatToDo()); | ||||
| 	for (auto quest : myCb->getMyQuests()) | ||||
| 	{ | ||||
| 		basicGoals.push_back(questToGoal(quest)); | ||||
| 		basicGoals.push_back(sptr(Goals::CompleteQuest(quest))); | ||||
| 	} | ||||
| 	basicGoals.push_back(sptr(Goals::Build())); | ||||
|  | ||||
| @@ -1013,11 +1013,13 @@ void VCAI::mainLoop() | ||||
| 			//remove goals we couldn't decompose | ||||
| 			for (auto goal : goalsToRemove) | ||||
| 				vstd::erase_if_present(basicGoals, goal); | ||||
| 			 | ||||
| 			//add abstract goals | ||||
| 			boost::sort(goalsToAdd, [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool | ||||
| 			{ | ||||
| 				return lhs->priority > rhs->priority; //highest priority at the beginning | ||||
| 			}); | ||||
| 			 | ||||
| 			//max number of goals = 10 | ||||
| 			int i = 0; | ||||
| 			while (basicGoals.size() < 10 && goalsToAdd.size() > i) | ||||
| @@ -2434,170 +2436,6 @@ Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal) | ||||
| 	return abstractGoal; | ||||
| } | ||||
|  | ||||
| Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) | ||||
| { | ||||
| 	Goals::TSubgoal result = sptr(Goals::Invalid()); | ||||
|  | ||||
| 	if (q.quest->missionType && q.quest->progress != CQuest::COMPLETE) | ||||
| 	{ | ||||
| 		MetaString ms; | ||||
| 		q.quest->getRolloverText(ms, false); | ||||
| 		logAi->debug("Trying to realize quest: %s", ms.toString()); | ||||
| 		auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities? | ||||
|  | ||||
| 		switch (q.quest->missionType) | ||||
| 		{ | ||||
| 		case CQuest::MISSION_ART: | ||||
| 		{ | ||||
| 			for (auto hero : heroes) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(hero)) | ||||
| 				{ | ||||
| 					return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); | ||||
| 				} | ||||
| 			} | ||||
| 			for (auto art : q.quest->m5arts) | ||||
| 			{ | ||||
| 				return sptr(Goals::GetArtOfType(art)); //TODO: transport? | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_HERO: | ||||
| 		{ | ||||
| 			//striveToGoal (CGoal(RECRUIT_HERO)); | ||||
| 			for (auto hero : heroes) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(hero)) | ||||
| 				{ | ||||
| 					return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); | ||||
| 				} | ||||
| 			} | ||||
| 			return sptr(Goals::FindObj(Obj::PRISON)); //rule of a thumb - quest heroes usually are locked in prisons | ||||
| 															 //BNLOG ("Don't know how to recruit hero with id %d\n", q.quest->m13489val); | ||||
| 		} | ||||
| 		case CQuest::MISSION_ARMY: | ||||
| 		{ | ||||
| 			for (auto hero : heroes) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :( | ||||
| 				{ | ||||
| 					result = sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(result->invalid()) | ||||
| 			{ | ||||
| 				for(auto creature : q.quest->m6creatures) | ||||
| 				{ | ||||
| 					result = sptr(Goals::GatherTroops(creature.type->idNumber, creature.count)); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 			//TODO: exchange armies... oh my | ||||
| 			//BNLOG ("Don't know how to recruit %d of %s\n", (int)(creature.count) % creature.type->namePl); | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_RESOURCES: | ||||
| 		{ | ||||
| 			if (heroes.size()) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is | ||||
| 				{ | ||||
| 					return sptr(Goals::VisitObj(q.obj->id.getNum())); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					for (int i = 0; i < q.quest->m7resources.size(); ++i) | ||||
| 					{ | ||||
| 						if (q.quest->m7resources[i]) | ||||
| 							return sptr(Goals::CollectRes(i, q.quest->m7resources[i])); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 				return sptr(Goals::RecruitHero()); //FIXME: checkQuest requires any hero belonging to player :( | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_KILL_HERO: | ||||
| 		case CQuest::MISSION_KILL_CREATURE: | ||||
| 		{ | ||||
| 			auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val); | ||||
|  | ||||
| 			if(!obj) | ||||
| 				return sptr(Goals::VisitObj(q.obj->id.getNum())); //visit seer hut | ||||
|  | ||||
| 			if(obj->ID == Obj::HERO) | ||||
| 			{ | ||||
| 				auto relations = myCb->getPlayerRelations(playerID, obj->tempOwner); | ||||
|  | ||||
| 				if(relations == PlayerRelations::SAME_PLAYER) | ||||
| 				{ | ||||
| 					auto heroToProtect = cb->getHero(obj->id); | ||||
|  | ||||
| 					return sptr(Goals::GatherArmy().sethero(heroToProtect)); | ||||
| 				} | ||||
| 				else if(relations == PlayerRelations::ALLIES) | ||||
| 				{ | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return sptr(Goals::VisitObj(obj->id.getNum())); | ||||
| 		} | ||||
| 		case CQuest::MISSION_PRIMARY_STAT: | ||||
| 		{ | ||||
| 			auto heroes = cb->getHeroesInfo(); | ||||
| 			for (auto hero : heroes) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(hero)) | ||||
| 				{ | ||||
| 					return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); | ||||
| 				} | ||||
| 			} | ||||
| 			for (int i = 0; i < q.quest->m2stats.size(); ++i) | ||||
| 			{ | ||||
| 				logAi->debug("Don't know how to increase primary stat %d", i); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_LEVEL: | ||||
| 		{ | ||||
| 			auto heroes = cb->getHeroesInfo(); | ||||
| 			for (auto hero : heroes) | ||||
| 			{ | ||||
| 				if (q.quest->checkQuest(hero)) | ||||
| 				{ | ||||
| 					return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); //TODO: causes infinite loop :/ | ||||
| 				} | ||||
| 			} | ||||
| 			logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val); | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_PLAYER: | ||||
| 		{ | ||||
| 			if (playerID.getNum() != q.quest->m13489val) | ||||
| 				logAi->debug("Can't be player of color %d", q.quest->m13489val); | ||||
| 			break; | ||||
| 		} | ||||
| 		case CQuest::MISSION_KEYMASTER: | ||||
| 		{ | ||||
| 			return sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)); | ||||
| 			break; | ||||
| 		} | ||||
| 		} //end of switch | ||||
| 	} | ||||
|  | ||||
| 	logAi->trace( | ||||
| 		"Returning %s, tile: %s, objid: %d, hero: %s",  | ||||
| 		result->name(),  | ||||
| 		result->tile.toString(),  | ||||
| 		result->objid,  | ||||
| 		result->hero.validAndSet() ? result->hero->name : "not specified"); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void VCAI::performTypicalActions() | ||||
| { | ||||
| 	for(auto h : getUnblockedHeroes()) | ||||
|   | ||||
| @@ -210,7 +210,6 @@ public: | ||||
| 	void setGoal(HeroPtr h, Goals::TSubgoal goal); | ||||
| 	void evaluateGoal(HeroPtr h); //evaluates goal assigned to hero, if any | ||||
| 	void completeGoal(Goals::TSubgoal goal); //safely removes goal from reserved hero | ||||
| 	Goals::TSubgoal questToGoal(const QuestInfo & q); | ||||
|  | ||||
| 	void recruitHero(const CGTownInstance * t, bool throwing = false); | ||||
| 	bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional<uint32_t> movementCostLimit = boost::none); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user