Browse Source

Remove quests from CMap, now solely owned by quest objects

Ivan Savenko 7 months ago
parent
commit
ab11d2b075

+ 3 - 3
AI/Nullkiller/AIUtility.cpp

@@ -657,7 +657,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 		for(auto q : ai->cb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.obj == obj->id)
 			{
 				return false; // do not visit guards or gates when wandering
 			}
@@ -670,9 +670,9 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 		for(auto q : ai->cb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.obj == obj->id)
 			{
-				if(q.quest->checkQuest(h))
+				if(q.getQuest(cb)->checkQuest(h))
 					return true; //we completed the quest
 				else
 					return false; //we can't complete this quest

+ 35 - 33
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -22,7 +22,8 @@ using namespace Goals;
 
 bool isKeyMaster(const QuestInfo & q)
 {
-	return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
+	auto object = q.getObject(cb);
+	return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
 }
 
 std::string CompleteQuest::toString() const
@@ -38,27 +39,28 @@ TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const
 	}
 
 	logAi->debug("Trying to realize quest: %s", questToString());
-	
-	if(!q.quest->mission.artifacts.empty())
+	auto quest = q.getQuest(cb);
+
+	if(!quest->mission.artifacts.empty())
 		return missionArt(ai);
 
-	if(!q.quest->mission.heroes.empty())
+	if(!quest->mission.heroes.empty())
 		return missionHero(ai);
 
-	if(!q.quest->mission.creatures.empty())
+	if(!quest->mission.creatures.empty())
 		return missionArmy(ai);
 
-	if(q.quest->mission.resources.nonZero())
+	if(quest->mission.resources.nonZero())
 		return missionResources(ai);
 
-	if(q.quest->killTarget != ObjectInstanceID::NONE)
+	if(quest->killTarget != ObjectInstanceID::NONE)
 		return missionDestroyObj(ai);
 
-	for(auto & s : q.quest->mission.primary)
+	for(auto & s : quest->mission.primary)
 		if(s)
 			return missionIncreasePrimaryStat(ai);
 
-	if(q.quest->mission.heroLevel > 0)
+	if(quest->mission.heroLevel > 0)
 		return missionLevel(ai);
 
 	return TGoalVec();
@@ -68,52 +70,52 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
 {
 	if(isKeyMaster(q))
 	{
-		return isKeyMaster(other.q) && q.obj->subID == other.q.obj->subID;
+		return isKeyMaster(other.q) && q.getObject(cb)->subID == other.q.getObject(cb)->subID;
 	}
 	else if(isKeyMaster(other.q))
 	{
 		return false;
 	}
 
-	return q.quest->qid == other.q.quest->qid;
+	return q.getQuest(cb) == other.q.getQuest(cb);
 }
 
 uint64_t CompleteQuest::getHash() const
 {
 	if(isKeyMaster(q))
 	{
-		return q.obj->subID;
+		return q.getObject(cb)->subID;
 	}
 
-	return q.quest->qid;
+	return q.getObject(cb)->id;
 }
 
 std::string CompleteQuest::questToString() const
 {
 	if(isKeyMaster(q))
 	{
-		return "find " + LIBRARY->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
+		return "find " + LIBRARY->generaltexth->tentColors[q.getObject(cb)->subID] + " keymaster tent";
 	}
 
-	if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
+	if(q.getQuest(cb)->questName == CQuest::missionName(EQuestMission::NONE))
 		return "inactive quest";
 
 	MetaString ms;
-	q.quest->getRolloverText(q.obj->cb, ms, false);
+	q.getQuest(cb)->getRolloverText(cb, ms, false);
 
 	return ms.toString();
 }
 
 TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * ai) const
 {
-	auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
+	auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
 
 	vstd::erase_if(paths, [&](const AIPath & path) -> bool
 	{
-		return !q.quest->checkQuest(path.targetHero);
+		return !q.getQuest(cb)->checkQuest(path.targetHero);
 	});
 	
-	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
+	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
 }
 
 TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
@@ -125,7 +127,7 @@ TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
 
 	CaptureObjectsBehavior findArts;
 
-	for(auto art : q.quest->mission.artifacts)
+	for(auto art : q.getQuest(cb)->mission.artifacts)
 	{
 		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
 	}
@@ -148,14 +150,14 @@ TGoalVec CompleteQuest::missionHero(const Nullkiller * ai) const
 
 TGoalVec CompleteQuest::missionArmy(const Nullkiller * ai) const
 {
-	auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
+	auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
 
 	vstd::erase_if(paths, [&](const AIPath & path) -> bool
 	{
-		return !CQuest::checkMissionArmy(q.quest, path.heroArmy);
+		return !CQuest::checkMissionArmy(q.getQuest(cb), path.heroArmy);
 	});
 
-	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
+	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
 }
 
 TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * ai) const
@@ -170,13 +172,13 @@ TGoalVec CompleteQuest::missionLevel(const Nullkiller * ai) const
 
 TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * ai) const
 {
-	if(isObjectPassable(ai, q.obj))
+	if(isObjectPassable(ai, q.getObject(cb)))
 	{
-		return CaptureObjectsBehavior(q.obj).decompose(ai);
+		return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
 	}
 	else
 	{
-		return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose(ai);
+		return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.getObject(cb)->subID).decompose(ai);
 	}
 }
 
@@ -188,16 +190,16 @@ TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
 
 	if(heroes.size())
 	{
-		if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
+		if(q.getQuest(cb)->checkQuest(heroes.front())) //it doesn't matter which hero it is
 		{
-			return solutions;// ai->ah->howToVisitObj(q.obj);
+			return solutions;// ai->ah->howToVisitObj(q.getObject(cb));
 		}
 		else
 		{
-			for(int i = 0; i < q.quest->m7resources.size(); ++i)
+			for(int i = 0; i < q.getQuest(cb)->m7resources.size(); ++i)
 			{
-				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
+				if(q.getQuest(cb)->m7resources[i])
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.getQuest(cb)->m7resources[i])));
 			}
 		}
 	}
@@ -211,10 +213,10 @@ TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
 
 TGoalVec CompleteQuest::missionDestroyObj(const Nullkiller * ai) const
 {
-	auto obj = ai->cb->getObj(q.quest->killTarget);
+	auto obj = ai->cb->getObj(q.getQuest(cb)->killTarget);
 
 	if(!obj)
-		return CaptureObjectsBehavior(q.obj).decompose(ai);
+		return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
 
 	auto relations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner);
 

+ 8 - 6
AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp

@@ -31,16 +31,18 @@ namespace AIPathfinding
 
 	bool QuestAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero) const
 	{
-		if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
+		auto object = questInfo.getObject(cb);
+		auto quest = questInfo.getQuest(cb);
+		if(object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD)
 		{
-			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
+			return dynamic_cast<const IQuestObject *>(object)->checkQuest(hero);
 		}
 
-		auto notActivated = !questInfo.obj->wasVisited(ai->playerID)
-			&& !questInfo.quest->activeForPlayers.count(hero->getOwner());
+		auto notActivated = !object->wasVisited(ai->playerID)
+			&& !quest->activeForPlayers.count(hero->getOwner());
 		
 		return notActivated
-			|| questInfo.quest->checkQuest(hero);
+			|| quest->checkQuest(hero);
 	}
 
 	Goals::TSubgoal QuestAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
@@ -50,7 +52,7 @@ namespace AIPathfinding
 
 	void QuestAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
 	{
-		ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
+		ai->moveHeroToTile(questInfo.getObject(cb)->visitablePos(), hero);
 	}
 
 	std::string QuestAction::toString() const

+ 3 - 3
AI/Nullkiller/Pathfinding/GraphPaths.cpp

@@ -83,11 +83,11 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 				|| node.obj->ID == Obj::BORDER_GATE)
 			{
 				auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
-				auto questInfo = QuestInfo(questObj->getQuest(), node.obj, pos.coord);
+				auto questInfo = QuestInfo(node.obj->id);
 
 				if(node.obj->ID == Obj::QUEST_GUARD
-					&& questObj->getQuest()->mission == Rewardable::Limiter{}
-					&& questObj->getQuest()->killTarget == ObjectInstanceID::NONE)
+					&& questObj->getQuest().mission == Rewardable::Limiter{}
+					&& questObj->getQuest().killTarget == ObjectInstanceID::NONE)
 				{
 					continue;
 				}

+ 3 - 3
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -166,12 +166,12 @@ namespace AIPathfinding
 	{
 		const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node);
 		auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
-		auto questInfo = QuestInfo(questObj->getQuest(), destination.nodeObject, destination.coord);
+		auto questInfo = QuestInfo(destination.nodeObject->id);
 		QuestAction questAction(questInfo);
 
 		if(destination.nodeObject->ID == Obj::QUEST_GUARD
-		   && questObj->getQuest()->mission == Rewardable::Limiter{}
-		   && questObj->getQuest()->killTarget == ObjectInstanceID::NONE)
+		   && questObj->getQuest().mission == Rewardable::Limiter{}
+		   && questObj->getQuest().killTarget == ObjectInstanceID::NONE)
 		{
 			return false;
 		}

+ 1 - 0
AI/VCAI/FuzzyHelper.cpp

@@ -11,6 +11,7 @@
 #include "FuzzyHelper.h"
 
 #include "Goals/Goals.h"
+#include "Goals/CompleteQuest.h"
 #include "VCAI.h"
 
 #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"

+ 40 - 28
AI/VCAI/Goals/CompleteQuest.cpp

@@ -8,7 +8,15 @@
 *
 */
 #include "StdInc.h"
-#include "Goals.h"
+#include "CompleteQuest.h"
+
+#include "CollectRes.h"
+#include "FindObj.h"
+#include "GatherArmy.h"
+#include "GatherTroops.h"
+#include "GetArtOfType.h"
+#include "RecruitHero.h"
+
 #include "../VCAI.h"
 #include "../FuzzyHelper.h"
 #include "../AIhelper.h"
@@ -18,45 +26,47 @@ using namespace Goals;
 
 bool CompleteQuest::operator==(const CompleteQuest & other) const
 {
-	return q.quest->qid == other.q.quest->qid;
+	return q.getQuest(cb) == other.q.getQuest(cb);
 }
 
 bool isKeyMaster(const QuestInfo & q)
 {
-	return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
+	auto object = q.getObject(cb);
+	return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
 }
 
 TGoalVec CompleteQuest::getAllPossibleSubgoals()
 {
 	TGoalVec solutions;
+	auto quest = q.getQuest(cb);
 
-	if(!q.quest->isCompleted)
+	if(!quest->isCompleted)
 	{
 		logAi->debug("Trying to realize quest: %s", questToString());
 		
 		if(isKeyMaster(q))
 			return missionKeymaster();
 
-		if(!q.quest->mission.artifacts.empty())
+		if(!quest->mission.artifacts.empty())
 			return missionArt();
 
-		if(!q.quest->mission.heroes.empty())
+		if(!quest->mission.heroes.empty())
 			return missionHero();
 
-		if(!q.quest->mission.creatures.empty())
+		if(!quest->mission.creatures.empty())
 			return missionArmy();
 
-		if(q.quest->mission.resources.nonZero())
+		if(quest->mission.resources.nonZero())
 			return missionResources();
 
-		if(q.quest->killTarget != ObjectInstanceID::NONE)
+		if(quest->killTarget != ObjectInstanceID::NONE)
 			return missionDestroyObj();
 
-		for(auto & s : q.quest->mission.primary)
+		for(auto & s : quest->mission.primary)
 			if(s)
 				return missionIncreasePrimaryStat();
 
-		if(q.quest->mission.heroLevel > 0)
+		if(quest->mission.heroLevel > 0)
 			return missionLevel();
 	}
 
@@ -65,7 +75,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
 
 TSubgoal CompleteQuest::whatToDoToAchieve()
 {
-	if(q.quest->mission == Rewardable::Limiter{})
+	if(q.getQuest(cb)->mission == Rewardable::Limiter{})
 	{
 		throw cannotFulfillGoalException("Can not complete inactive quest");
 	}
@@ -99,11 +109,13 @@ std::string CompleteQuest::completeMessage() const
 
 std::string CompleteQuest::questToString() const
 {
-	if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
+	auto quest = q.getQuest(cb);
+
+	if(quest->questName == CQuest::missionName(EQuestMission::NONE))
 		return "inactive quest";
 
 	MetaString ms;
-	q.quest->getRolloverText(q.obj->cb, ms, false);
+	quest->getRolloverText(cb, ms, false);
 
 	return ms.toString();
 }
@@ -116,9 +128,9 @@ TGoalVec CompleteQuest::tryCompleteQuest() const
 
 	for(auto hero : heroes)
 	{
-		if(q.quest->checkQuest(hero))
+		if(q.getQuest(cb)->checkQuest(hero))
 		{
-			vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
+			vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.getObject(cb)->id)));
 		}
 	}
 
@@ -132,7 +144,7 @@ TGoalVec CompleteQuest::missionArt() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto art : q.quest->mission.artifacts)
+	for(auto art : q.getQuest(cb)->mission.artifacts)
 	{
 		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
 	}
@@ -160,7 +172,7 @@ TGoalVec CompleteQuest::missionArmy() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto creature : q.quest->mission.creatures)
+	for(auto creature : q.getQuest(cb)->mission.creatures)
 	{
 		solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count)));
 	}
@@ -174,7 +186,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
 
 	if(solutions.empty())
 	{
-		for(int i = 0; i < q.quest->mission.primary.size(); ++i)
+		for(int i = 0; i < q.getQuest(cb)->mission.primary.size(); ++i)
 		{
 			// TODO: library, school and other boost objects
 			logAi->debug("Don't know how to increase primary stat %d", i);
@@ -190,7 +202,7 @@ TGoalVec CompleteQuest::missionLevel() const
 
 	if(solutions.empty())
 	{
-		logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
+		logAi->debug("Don't know how to reach hero level %d", q.getQuest(cb)->mission.heroLevel);
 	}
 
 	return solutions;
@@ -202,7 +214,7 @@ TGoalVec CompleteQuest::missionKeymaster() const
 
 	if(solutions.empty())
 	{
-		solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
+		solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.getObject(cb)->subID)));
 	}
 
 	return solutions;
@@ -216,16 +228,16 @@ TGoalVec CompleteQuest::missionResources() const
 
 	if(heroes.size())
 	{
-		if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
+		if(q.getQuest(cb)->checkQuest(heroes.front())) //it doesn't matter which hero it is
 		{
-			return ai->ah->howToVisitObj(q.obj);
+			return ai->ah->howToVisitObj(q.getObject(cb));
 		}
 		else
 		{
-			for(int i = 0; i < q.quest->mission.resources.size(); ++i)
+			for(int i = 0; i < q.getQuest(cb)->mission.resources.size(); ++i)
 			{
-				if(q.quest->mission.resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
+				if(q.getQuest(cb)->mission.resources[i])
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.getQuest(cb)->mission.resources[i])));
 			}
 		}
 	}
@@ -241,10 +253,10 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 {
 	TGoalVec solutions;
 
-	auto obj = cb->getObj(q.quest->killTarget);
+	auto obj = cb->getObj(q.getQuest(cb)->killTarget);
 
 	if(!obj)
-		return ai->ah->howToVisitObj(q.obj);
+		return ai->ah->howToVisitObj(q.getObject(cb));
 
 	if(obj->ID == Obj::HERO)
 	{

+ 1 - 2
AI/VCAI/Goals/Goals.h

@@ -30,5 +30,4 @@
 #include "ClearWayTo.h"
 #include "DigAtTile.h"
 #include "FindObj.h"
-#include "CompleteQuest.h"
-#include "AdventureSpellCast.h"
+#include "AdventureSpellCast.h"

+ 3 - 1
AI/VCAI/Pathfinding/PathfindingManager.cpp

@@ -12,7 +12,9 @@
 #include "AIPathfinder.h"
 #include "AIPathfinderConfig.h"
 #include "../Goals/Goals.h"
+#include "../Goals/CompleteQuest.h"
 #include "../../../lib/CGameInfoCallback.h"
+#include "../../../lib/gameState/QuestInfo.h"
 #include "../../../lib/mapping/CMapDefines.h"
 #include "../../../lib/mapObjects/CQuest.h"
 
@@ -224,7 +226,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 
 			if(questObj)
 			{
-				auto questInfo = QuestInfo(questObj->getQuest(), topObj, topObj->visitablePos());
+				auto questInfo = QuestInfo(topObj->id);
 
 				return sptr(Goals::CompleteQuest(questInfo));
 			}

+ 4 - 3
AI/VCAI/VCAI.cpp

@@ -13,6 +13,7 @@
 #include "ResourceManager.h"
 #include "BuildingManager.h"
 #include "Goals/Goals.h"
+#include "Goals/CompleteQuest.h"
 
 #include "../../lib/ArtifactUtils.h"
 #include "../../lib/AsyncRunner.h"
@@ -2809,7 +2810,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	{
 		for(auto q : ai->myCb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.getObject(cb) == obj)
 			{
 				return false; // do not visit guards or gates when wandering
 			}
@@ -2823,9 +2824,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	{
 		for(auto q : ai->myCb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.getObject(cb) == obj)
 			{
-				if(q.quest->checkQuest(h.h))
+				if(q.getQuest(cb)->checkQuest(h.h))
 					return true; //we completed the quest
 				else
 					return false; //we can't complete this quest

+ 14 - 13
client/windows/CQuestLog.cpp

@@ -13,6 +13,8 @@
 #include "../CPlayerInterface.h"
 
 #include "../GameEngine.h"
+#include "../GameInstance.h"
+#include "../CPlayerInterface.h"
 #include "../gui/Shortcut.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
@@ -74,11 +76,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q)
 	OBJECT_CONSTRUCTION;
 	icons.clear();
 
-	int3 tile;
-	if (q->obj)
-		tile = q->obj->visitablePos();
-	else
-		tile = q->tile;
+	int3 tile = q->getPosition(GAME->interface()->cb.get());
 
 	Point offset = tileToPixels(tile);
 
@@ -102,7 +100,7 @@ void CQuestMinimap::update()
 void CQuestMinimap::iconClicked()
 {
 	if(currentQuest->obj)
-		adventureInt->centerOnTile(currentQuest->obj->visitablePos());
+		adventureInt->centerOnTile(currentQuest->getObject(GAME->interface()->cb.get())->visitablePos());
 	//moveAdvMapSelection();
 }
 
@@ -145,11 +143,14 @@ void CQuestLog::recreateLabelList()
 	int currentLabel = 0;
 	for (int i = 0; i < quests.size(); ++i)
 	{
+		auto questPtr = quests[i].getQuest(GAME->interface()->cb.get());
+		auto questObject = quests[i].getObject(GAME->interface()->cb.get());
+
 		// Quests without mision don't have text for them and can't be displayed
-		if (quests[i].quest->mission == Rewardable::Limiter{})
+		if (quests[i].getQuest(GAME->interface()->cb.get())->mission == Rewardable::Limiter{})
 			continue;
 
-		if (quests[i].quest->isCompleted)
+		if (questPtr->isCompleted)
 		{
 			completeMissing = false;
 			if (hideComplete)
@@ -157,10 +158,10 @@ void CQuestLog::recreateLabelList()
 		}
 
 		MetaString text;
-		quests[i].quest->getRolloverText (quests[i].obj->cb, text, false);
+		questPtr->getRolloverText(GAME->interface()->cb.get(), text, false);
 		if (quests[i].obj)
 		{
-			if (auto seersHut = dynamic_cast<const CGSeerHut *>(quests[i].obj))
+			if (auto seersHut = dynamic_cast<const CGSeerHut *>(questObject))
 			{
 				MetaString toSeer;
 				toSeer.appendRawString(LIBRARY->generaltexth->allTexts[347]);
@@ -168,7 +169,7 @@ void CQuestLog::recreateLabelList()
 				text.replaceRawString(toSeer.toString());
 			}
 			else
-				text.replaceRawString(quests[i].obj->getObjectName()); //get name of the object
+				text.replaceRawString(questObject->getObjectName()); //get name of the object
 		}
 		auto label = std::make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, text.toString());
 		label->disable();
@@ -177,7 +178,7 @@ void CQuestLog::recreateLabelList()
 		labels.push_back(label);
 
 		// Select latest active quest
-		if(!quests[i].quest->isCompleted)
+		if(!questPtr->isCompleted)
 			selectQuest(i, currentLabel);
 
 		currentLabel = static_cast<int>(labels.size());
@@ -233,7 +234,7 @@ void CQuestLog::selectQuest(int which, int labelId)
 
 	MetaString text;
 	std::vector<Component> components;
-	currentQuest->quest->getVisitText(currentQuest->obj->cb, text, components, true);
+	currentQuest->getQuest(GAME->interface()->cb.get())->getVisitText(GAME->interface()->cb.get(), text, components, true);
 	if(description->slider)
 		description->slider->scrollToMin(); // scroll text to start position
 	description->setText(text.toString()); //TODO: use special log entry text

+ 1 - 0
lib/CMakeLists.txt

@@ -107,6 +107,7 @@ set(lib_MAIN_SRCS
 	gameState/CGameStateCampaign.cpp
 	gameState/HighScore.cpp
 	gameState/InfoAboutArmy.cpp
+	gameState/QuestInfo.cpp
 	gameState/RumorState.cpp
 	gameState/TavernHeroesPool.cpp
 	gameState/GameStatistics.cpp

+ 2 - 2
lib/IGameCallback.h

@@ -78,8 +78,8 @@ public:
 	void pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand);
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 
-	void saveCommonState(CSaveFile &out) const; //stores GS and LIBRARY
-	void loadCommonState(CLoadFile &in); //loads GS and LIBRARY
+	void saveCommonState(CSaveFile &out) const; //stores GS
+	void loadCommonState(CLoadFile &in); //loads GS
 };
 
 class DLL_LINKAGE IGameEventCallback

+ 11 - 0
lib/constants/EntityIdentifiers.h

@@ -66,6 +66,7 @@ public:
 	using StaticIdentifier<BattleID>::StaticIdentifier;
 	DLL_LINKAGE static const BattleID NONE;
 };
+
 class DLL_LINKAGE ObjectInstanceID : public StaticIdentifier<ObjectInstanceID>
 {
 public:
@@ -76,6 +77,16 @@ public:
 	static std::string encode(const si32 index);
 };
 
+class DLL_LINKAGE QuestInstanceID : public StaticIdentifier<QuestInstanceID>
+{
+public:
+	using StaticIdentifier<QuestInstanceID>::StaticIdentifier;
+	static const QuestInstanceID NONE;
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
+};
+
 class HeroClassID : public EntityIdentifier<HeroClassID>
 {
 public:

+ 36 - 0
lib/gameState/QuestInfo.cpp

@@ -0,0 +1,36 @@
+/*
+ * QuestInfo.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 "QuestInfo.h"
+
+#include "../mapObjects/CQuest.h"
+#include "../CGameInfoCallback.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const CQuest * QuestInfo::getQuest(CGameInfoCallback *cb) const
+{
+	auto questObject = dynamic_cast<const IQuestObject*>(getObject(cb));
+	assert(questObject);
+
+	return &questObject->getQuest();
+}
+
+const CGObjectInstance * QuestInfo::getObject(CGameInfoCallback *cb) const
+{
+	return cb->getObjInstance(obj);
+}
+
+int3 QuestInfo::getPosition(CGameInfoCallback *cb) const
+{
+	return getObject(cb)->visitablePos();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 13 - 21
lib/gameState/QuestInfo.h

@@ -10,45 +10,37 @@
 #pragma once
 
 #include "int3.h"
+#include "../constants/EntityIdentifiers.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CQuest;
 class CGObjectInstance;
+class CGameInfoCallback;
 
 struct DLL_LINKAGE QuestInfo //universal interface for human and AI
 {
-	const CQuest * quest;
-	const CGObjectInstance * obj; //related object, most likely Seer Hut
-	int3 tile;
+	ObjectInstanceID obj; //related object, Seer Hut or Border Guard
 
-	QuestInfo()
-		: quest(nullptr), obj(nullptr), tile(-1,-1,-1)
-	{};
-	QuestInfo (const CQuest * Quest, const CGObjectInstance * Obj, int3 Tile) :
-		quest (Quest), obj (Obj), tile (Tile){};
+	QuestInfo() = default;
+	explicit QuestInfo(ObjectInstanceID Obj)
+		: obj(Obj)
+	{}
 
-	QuestInfo (const QuestInfo &qi) : quest(qi.quest), obj(qi.obj), tile(qi.tile)
-	{};
-
-	const QuestInfo& operator= (const QuestInfo &qi)
-	{
-		quest = qi.quest;
-		obj = qi.obj;
-		tile = qi.tile;
-		return *this;
-	}
+	const CQuest * getQuest(CGameInfoCallback *cb) const;
+	const CGObjectInstance * getObject(CGameInfoCallback *cb) const;
+	int3 getPosition(CGameInfoCallback *cb) const;
 
 	bool operator== (const QuestInfo & qi) const
 	{
-		return (quest == qi.quest && obj == qi.obj);
+		return obj == qi.obj;
 	}
 
 	template <typename Handler> void serialize(Handler &h)
 	{
-		h & quest;
+		//h & quest;
 		h & obj;
-		h & tile;
+		//h & tile;
 	}
 };
 

+ 62 - 71
lib/mapObjects/CQuest.cpp

@@ -178,7 +178,7 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
 	cb->giveResources(h->getOwner(), -mission.resources);
 }
 
-void CQuest::addTextReplacements(IGameCallback * cb, MetaString & text, std::vector<Component> & components) const
+void CQuest::addTextReplacements(const CGameInfoCallback * cb, MetaString & text, std::vector<Component> & components) const
 {
 	if(mission.heroLevel > 0)
 		text.replaceNumber(mission.heroLevel);
@@ -277,7 +277,7 @@ void CQuest::addTextReplacements(IGameCallback * cb, MetaString & text, std::vec
 		text.replaceNumber(lastDay - cb->getDate(Date::DAY));
 }
 
-void CQuest::getVisitText(IGameCallback * cb, MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
+void CQuest::getVisitText(const CGameInfoCallback * cb, MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
 {
 	bool failRequirements = (h ? !checkQuest(h) : true);
 	mission.loadComponents(components, h);
@@ -293,7 +293,7 @@ void CQuest::getVisitText(IGameCallback * cb, MetaString &iwText, std::vector<Co
 	addTextReplacements(cb, iwText, components);
 }
 
-void CQuest::getRolloverText(IGameCallback * cb, MetaString &ms, bool onHover) const
+void CQuest::getRolloverText(const CGameInfoCallback * cb, MetaString &ms, bool onHover) const
 {
 	if(onHover)
 		ms.appendRawString("\n\n");
@@ -306,7 +306,7 @@ void CQuest::getRolloverText(IGameCallback * cb, MetaString &ms, bool onHover) c
 	addTextReplacements(cb, ms, components);
 }
 
-void CQuest::getCompletionText(IGameCallback * cb, MetaString &iwText) const
+void CQuest::getCompletionText(const CGameInfoCallback * cb, MetaString &iwText) const
 {
 	iwText.appendRawString(completedText.toString());
 	
@@ -415,36 +415,37 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
 
 }
 
+IQuestObject::IQuestObject()
+	:quest(std::make_unique<CQuest>())
+{}
+
+IQuestObject::~IQuestObject() = default;
+
 bool IQuestObject::checkQuest(const CGHeroInstance* h) const
 {
-	return quest->checkQuest(h);
+	return getQuest().checkQuest(h);
 }
 
 void CGSeerHut::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
 {
-	quest->getVisitText(cb, text, components, FirstVisit, h);
-}
-
-void IQuestObject::afterAddToMapCommon(CMap * map) const
-{
-	map->addNewQuestInstance(quest);
+	getQuest().getVisitText(cb, text, components, FirstVisit, h);
 }
 
 void CGSeerHut::setObjToKill()
 {
-	if(quest->killTarget == ObjectInstanceID::NONE)
+	if(getQuest().killTarget == ObjectInstanceID::NONE)
 		return;
 	
 	if(getCreatureToKill(true))
 	{
-		quest->stackToKill = getCreatureToKill(false)->getCreatureID();
-		assert(quest->stackToKill != CreatureID::NONE);
-		quest->stackDirection = checkDirection();
+		getQuest().stackToKill = getCreatureToKill(false)->getCreatureID();
+		assert(getQuest().stackToKill != CreatureID::NONE);
+		getQuest().stackDirection = checkDirection();
 	}
 	else if(getHeroToKill(true))
 	{
-		quest->heroName = getHeroToKill(false)->getNameTranslated();
-		quest->heroPortrait = getHeroToKill(false)->getPortraitSource();
+		getQuest().heroName = getHeroToKill(false)->getNameTranslated();
+		getQuest().heroPortrait = getHeroToKill(false)->getPortraitSource();
 	}
 }
 
@@ -454,8 +455,8 @@ void CGSeerHut::init(vstd::RNG & rand)
 
 	auto seerNameID = *RandomGeneratorUtil::nextItem(names, rand);
 	seerName = LIBRARY->generaltexth->translate(seerNameID);
-	quest->textOption = rand.nextInt(2);
-	quest->completedOption = rand.nextInt(1, 3);
+	getQuest().textOption = rand.nextInt(2);
+	getQuest().completedOption = rand.nextInt(1, 3);
 	
 	configuration.canRefuse = true;
 	configuration.visitMode = Rewardable::EVisitMode::VISIT_ONCE;
@@ -469,33 +470,33 @@ void CGSeerHut::initObj(vstd::RNG & rand)
 	CRewardableObject::initObj(rand);
 	
 	setObjToKill();
-	quest->defineQuestName();
+	getQuest().defineQuestName();
 	
-	if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE)
-		quest->isCompleted = true;
+	if(getQuest().mission == Rewardable::Limiter{} && getQuest().killTarget == ObjectInstanceID::NONE)
+		getQuest().isCompleted = true;
 	
-	if(quest->questName == quest->missionName(EQuestMission::NONE))
+	if(getQuest().questName == getQuest().missionName(EQuestMission::NONE))
 	{
-		quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
+		getQuest().firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", getQuest().completedOption).get());
 	}
 	else
 	{
-		if(!quest->isCustomFirst)
-			quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(0), quest->textOption).get());
-		if(!quest->isCustomNext)
-			quest->nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(1), quest->textOption).get());
-		if(!quest->isCustomComplete)
-			quest->completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest-> questName, quest->missionState(2), quest->textOption).get());
+		if(!getQuest().isCustomFirst)
+			getQuest().firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest().questName, getQuest().missionState(0), getQuest().textOption).get());
+		if(!getQuest().isCustomNext)
+			getQuest().nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest().questName, getQuest().missionState(1), getQuest().textOption).get());
+		if(!getQuest().isCustomComplete)
+			getQuest().completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest(). questName, getQuest().missionState(2), getQuest().textOption).get());
 	}
 	
-	quest->getCompletionText(cb, configuration.onSelect);
+	getQuest().getCompletionText(cb, configuration.onSelect);
 	for(auto & i : configuration.info)
-		quest->getCompletionText(cb, i.message);
+		getQuest().getCompletionText(cb, i.message);
 }
 
 void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 {
-	quest->getRolloverText(cb, text, onHover);//TODO: simplify?
+	getQuest().getRolloverText(cb, text, onHover);//TODO: simplify?
 	if(!onHover)
 		text.replaceRawString(seerName);
 }
@@ -503,15 +504,15 @@ void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 std::string CGSeerHut::getHoverText(PlayerColor player) const
 {
 	std::string hoverName = getObjectName();
-	if(ID == Obj::SEER_HUT && quest->activeForPlayers.count(player))
+	if(ID == Obj::SEER_HUT && getQuest().activeForPlayers.count(player))
 	{
 		hoverName = LIBRARY->generaltexth->allTexts[347];
 		boost::algorithm::replace_first(hoverName, "%s", seerName);
 	}
 
-	if(quest->activeForPlayers.count(player)
-	   && (quest->mission != Rewardable::Limiter{}
-		   || quest->killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
+	if(getQuest().activeForPlayers.count(player)
+	   && (getQuest().mission != Rewardable::Limiter{}
+		   || getQuest().killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
 	{
 		MetaString ms;
 		getRolloverText (ms, true);
@@ -538,16 +539,16 @@ std::string CGSeerHut::getPopupText(const CGHeroInstance * hero) const
 std::vector<Component> CGSeerHut::getPopupComponents(PlayerColor player) const
 {
 	std::vector<Component> result;
-	if (quest->activeForPlayers.count(player))
-		quest->mission.loadComponents(result, nullptr);
+	if (getQuest().activeForPlayers.count(player))
+		getQuest().mission.loadComponents(result, nullptr);
 	return result;
 }
 
 std::vector<Component> CGSeerHut::getPopupComponents(const CGHeroInstance * hero) const
 {
 	std::vector<Component> result;
-	if (quest->activeForPlayers.count(hero->getOwner()))
-		quest->mission.loadComponents(result, hero);
+	if (getQuest().activeForPlayers.count(hero->getOwner()))
+		getQuest().mission.loadComponents(result, hero);
 	return result;
 }
 
@@ -557,13 +558,13 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 	{
 		case ObjProperty::SEERHUT_VISITED:
 		{
-			quest->activeForPlayers.emplace(identifier.as<PlayerColor>());
+			getQuest().activeForPlayers.emplace(identifier.as<PlayerColor>());
 			break;
 		}
 		case ObjProperty::SEERHUT_COMPLETE:
 		{
-			quest->isCompleted = identifier.getNum();
-			quest->activeForPlayers.clear();
+			getQuest().isCompleted = identifier.getNum();
+			getQuest().activeForPlayers.clear();
 			break;
 		}
 	}
@@ -572,7 +573,7 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 void CGSeerHut::newTurn(vstd::RNG & rand) const
 {
 	CRewardableObject::newTurn(rand);
-	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
+	if(getQuest().lastDay >= 0 && getQuest().lastDay <= cb->getDate() - 1) //time is up
 	{
 		cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, true);
 	}
@@ -582,9 +583,9 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 {
 	InfoWindow iw;
 	iw.player = h->getOwner();
-	if(!quest->isCompleted)
+	if(!getQuest().isCompleted)
 	{
-		bool firstVisit = !quest->activeForPlayers.count(h->getOwner());
+		bool firstVisit = !getQuest().activeForPlayers.count(h->getOwner());
 		bool failRequirements = !checkQuest(h);
 
 		if(firstVisit)
@@ -592,7 +593,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 			cb->setObjPropertyID(id, ObjProperty::SEERHUT_VISITED, h->getOwner());
 
 			AddQuest aq;
-			aq.quest = QuestInfo(quest.get(), this, visitablePos());
+			aq.quest = QuestInfo(id);
 			aq.player = h->tempOwner;
 			cb->sendAndApply(aq); //TODO: merge with setObjProperty?
 		}
@@ -611,7 +612,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 	}
 	else
 	{
-		iw.text.appendRawString(LIBRARY->generaltexth->seerEmpty[quest->completedOption]);
+		iw.text.appendRawString(LIBRARY->generaltexth->seerEmpty[getQuest().completedOption]);
 		if (ID == Obj::SEER_HUT)
 			iw.text.replaceRawString(seerName);
 		cb->showInfoDialog(&iw);
@@ -652,7 +653,7 @@ int CGSeerHut::checkDirection() const
 
 const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
 {
-	const CGObjectInstance *o = cb->getObj(quest->killTarget);
+	const CGObjectInstance *o = cb->getObj(getQuest().killTarget);
 	if(allowNull && !o)
 		return nullptr;
 	return dynamic_cast<const CGHeroInstance *>(o);
@@ -660,7 +661,7 @@ const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
 
 const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
 {
-	const CGObjectInstance *o = cb->getObj(quest->killTarget);
+	const CGObjectInstance *o = cb->getObj(getQuest().killTarget);
 	if(allowNull && !o)
 		return nullptr;
 	return dynamic_cast<const CGCreature *>(o);
@@ -671,21 +672,16 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answe
 	CRewardableObject::blockingDialogAnswered(hero, answer);
 	if(answer)
 	{
-		quest->completeQuest(cb, hero);
-		cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, !quest->repeatedQuest); //mission complete
+		getQuest().completeQuest(cb, hero);
+		cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, !getQuest().repeatedQuest); //mission complete
 	}
 }
 
-void CGSeerHut::afterAddToMap(CMap* map)
-{
-	IQuestObject::afterAddToMapCommon(map);
-}
-
 void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	//quest and reward
 	CRewardableObject::serializeJsonOptions(handler);
-	quest->serializeJson(handler, "quest");
+	getQuest().serializeJson(handler, "quest");
 	
 	if(!handler.saving)
 	{
@@ -760,8 +756,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 void CGQuestGuard::init(vstd::RNG & rand)
 {
 	blockVisit = true;
-	quest->textOption = rand.nextInt(3, 5);
-	quest->completedOption = rand.nextInt(4, 5);
+	getQuest().textOption = rand.nextInt(3, 5);
+	getQuest().completedOption = rand.nextInt(4, 5);
 	
 	configuration.info.push_back({});
 	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
@@ -771,7 +767,7 @@ void CGQuestGuard::init(vstd::RNG & rand)
 
 void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
 {
-	if(!quest->isCompleted)
+	if(!getQuest().isCompleted)
 		CGSeerHut::onHeroVisit(h);
 	else
 		cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, false);
@@ -779,13 +775,13 @@ void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
 
 bool CGQuestGuard::passableFor(PlayerColor color) const
 {
-	return quest->isCompleted;
+	return getQuest().isCompleted;
 }
 
 void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	//quest only, do not call base class
-	quest->serializeJson(handler, "quest");
+	getQuest().serializeJson(handler, "quest");
 }
 
 bool CGKeys::wasMyColorVisited(const PlayerColor & player) const
@@ -869,7 +865,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
 		h->showInfoDialog(18);
 
 		AddQuest aq;
-		aq.quest = QuestInfo (quest.get(), this, visitablePos());
+		aq.quest = QuestInfo(id);
 		aq.player = h->tempOwner;
 		cb->sendAndApply(aq);
 		//TODO: add this quest only once OR check for multiple instances later
@@ -882,11 +878,6 @@ void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, int32_t a
 		cb->removeObject(this, hero->getOwner());
 }
 
-void CGBorderGuard::afterAddToMap(CMap * map)
-{
-	IQuestObject::afterAddToMapCommon(map);
-}
-
 void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passability
 {
 	if (!wasMyColorVisited (h->getOwner()) )
@@ -894,7 +885,7 @@ void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passabili
 		h->showInfoDialog(18);
 
 		AddQuest aq;
-		aq.quest = QuestInfo (quest.get(), this, visitablePos());
+		aq.quest = QuestInfo(id);
 		aq.player = h->tempOwner;
 		cb->sendAndApply(aq);
 	}

+ 12 - 33
lib/mapObjects/CQuest.h

@@ -47,7 +47,7 @@ public:
 	
 	std::string questName;
 
-	si32 qid; //unique quest id for serialization / identification
+	QuestInstanceID qid;
 
 	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
 	ObjectInstanceID killTarget;
@@ -77,11 +77,11 @@ public:
 
 	static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army);
 	bool checkQuest(const CGHeroInstance * h) const; //determines whether the quest is complete or not
-	void getVisitText(IGameCallback * cb, MetaString &text, std::vector<Component> & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
-	void getCompletionText(IGameCallback * cb, MetaString &text) const;
-	void getRolloverText (IGameCallback * cb, MetaString &text, bool onHover) const; //hover or quest log entry
+	void getVisitText(const CGameInfoCallback * cb, MetaString &text, std::vector<Component> & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	void getCompletionText(const CGameInfoCallback * cb, MetaString &text) const;
+	void getRolloverText (const CGameInfoCallback * cb, MetaString &text, bool onHover) const; //hover or quest log entry
 	void completeQuest(IGameCallback *, const CGHeroInstance * h) const;
-	void addTextReplacements(IGameCallback * cb, MetaString &out, std::vector<Component> & components) const;
+	void addTextReplacements(const CGameInfoCallback * cb, MetaString &out, std::vector<Component> & components) const;
 	void addKillTargetReplacements(MetaString &out) const;
 	void defineQuestName();
 
@@ -116,34 +116,21 @@ public:
 	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
 };
 
-class DLL_LINKAGE IQuestObject : public virtual Serializeable
+class DLL_LINKAGE IQuestObject
 {
-	friend class CMapLoaderH3M;
-	friend class TreasurePlacer; // RMG
-	friend class Inspector; // Map editor
-
-protected:
-	std::shared_ptr<CQuest> quest;
-
+	std::unique_ptr<CQuest> quest;
 public:
-	IQuestObject()
-		:quest(std::make_shared<CQuest>())
-	{}
-
-	virtual ~IQuestObject() = default;
+	IQuestObject();
+	virtual ~IQuestObject();
 	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const = 0;
 	virtual bool checkQuest (const CGHeroInstance * h) const;
-	const CQuest * getQuest() const
-	{
-		return quest.get();
-	}
+	virtual const CQuest & getQuest() const { return *quest; }
+	virtual CQuest & getQuest() { return *quest; }
 
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & quest;
 	}
-protected:
-	void afterAddToMapCommon(CMap * map) const;
 };
 
 class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject
@@ -172,8 +159,6 @@ public:
 	const CGCreature *getCreatureToKill(bool allowNull) const;
 	void getRolloverText (MetaString &text, bool onHover) const;
 
-	void afterAddToMap(CMap * map) override;
-
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<CRewardableObject&>(*this);
@@ -237,6 +222,7 @@ public:
 
 class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
 {
+	QuestInstanceID qid;
 public:
 	using CGKeys::CGKeys;
 
@@ -248,8 +234,6 @@ public:
 	void getRolloverText (MetaString &text, bool onHover) const;
 	bool checkQuest (const CGHeroInstance * h) const override;
 
-	void afterAddToMap(CMap * map) override;
-
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<IQuestObject&>(*this);
@@ -265,11 +249,6 @@ public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 
 	bool passableFor(PlayerColor color) const override;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CGBorderGuard&>(*this); //need to serialize or object will be empty
-	}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 0 - 24
lib/mapping/CMap.cpp

@@ -497,19 +497,6 @@ void CMap::removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & s
 	artifact->addPlacementMap(partsMap);
 }
 
-void CMap::addNewQuestInstance(std::shared_ptr<CQuest> quest)
-{
-	quest->qid = static_cast<si32>(quests.size());
-	quests.emplace_back(quest);
-}
-
-void CMap::clearQuestInstance(const CQuest * quest)
-{
-	assert(quests.at(quest->qid).get() == quest);
-
-	quests.at(quest->qid) = nullptr;
-}
-
 void CMap::generateUniqueInstanceName(CGObjectInstance * target)
 {
 	//this gives object unique name even if objects are removed later
@@ -727,17 +714,6 @@ CMapEditManager * CMap::getEditManager()
 	return editManager.get();
 }
 
-void CMap::resolveQuestIdentifiers()
-{
-	//FIXME: move to CMapLoaderH3M
-	for (auto & quest : quests)
-	{
-		if (quest && quest->killTarget != ObjectInstanceID::NONE)
-			quest->killTarget = questIdentifierToId[quest->killTarget.getNum()];
-	}
-	questIdentifierToId.clear();
-}
-
 void CMap::reindexObjects()
 {
 	// Only reindex at editor / RMG operations

+ 1 - 9
lib/mapping/CMap.h

@@ -62,8 +62,6 @@ class DLL_LINKAGE CMap : public CMapHeader, public GameCallbackHolder
 
 	std::unique_ptr<GameSettings> gameSettings;
 
-	/// All quests that are currently present on map
-	std::vector<std::shared_ptr<CQuest>> quests;
 	/// All artifacts that exists on map, whether on map, in hero inventory, or stored in some object
 	std::vector<std::shared_ptr<CArtifactInstance>> artInstances;
 	/// All heroes that are currently free for recruitment in taverns and are not present on map
@@ -78,7 +76,7 @@ class DLL_LINKAGE CMap : public CMapHeader, public GameCallbackHolder
 public:
 	/// Central lists of items in game. Position of item in the vectors below is their (instance) id.
 	/// TODO: make private
-	std::vector< std::shared_ptr<CGObjectInstance> > objects;
+	std::vector<std::shared_ptr<CGObjectInstance>> objects;
 
 	explicit CMap(IGameCallback *cb);
 	~CMap();
@@ -110,9 +108,6 @@ public:
 	void putArtifactInstance(CArtifactSet & set, const ArtifactInstanceID art, const ArtifactPosition & slot);
 	void removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot);
 
-	void addNewQuestInstance(std::shared_ptr<CQuest> quest);
-	void clearQuestInstance(const CQuest * quest);
-
 	void generateUniqueInstanceName(CGObjectInstance * target);
 
 	///Use only this method when creating new map object instances
@@ -213,8 +208,6 @@ public:
 	//Helper lists
 	std::map<TeleportChannelID, std::shared_ptr<TeleportChannel> > teleportChannels;
 
-	/// associative list to identify which hero/creature id belongs to which object id(index for objects)
-	std::map<si32, ObjectInstanceID> questIdentifierToId;
 
 	std::unique_ptr<CMapEditManager> editManager;
 	boost::multi_array<int3, 3> guardingCreaturePositions;
@@ -254,7 +247,6 @@ public:
 		h & events;
 		h & grailPos;
 		h & artInstances;
-		h & quests;
 
 		//TODO: viccondetails
 		h & terrain;

+ 30 - 28
lib/mapping/MapFormatH3M.cpp

@@ -1186,7 +1186,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readMonster(const int3 & mapPos
 	if(features.levelAB)
 	{
 		object->identifier = reader->readUInt32();
-		map->questIdentifierToId[object->identifier] = objectInstanceID;
+		questIdentifierToId[object->identifier] = objectInstanceID;
 	}
 
 	auto hlp = std::make_unique<CStackInstance>();
@@ -2035,7 +2035,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readHero(const int3 & mapPositi
 	if(features.levelAB)
 	{
 		unsigned int identifier = reader->readUInt32();
-		map->questIdentifierToId[identifier] = objectInstanceID;
+		questIdentifierToId[identifier] = objectInstanceID;
 	}
 
 	PlayerColor owner = reader->readPlayer();
@@ -2224,7 +2224,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readSeerHut(const int3 & positi
 	if(features.levelHOTA3)
 	{
 		uint32_t repeateableQuestsCount = reader->readUInt32();
-		hut->quest->repeatedQuest = repeateableQuestsCount != 0;
+		hut->getQuest().repeatedQuest = repeateableQuestsCount != 0;
 
 		if(repeateableQuestsCount != 0)
 			logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
@@ -2267,13 +2267,13 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 		if(artID != ArtifactID::NONE)
 		{
 			//not none quest
-			hut->quest->mission.artifacts.push_back(artID);
+			hut->getQuest().mission.artifacts.push_back(artID);
 			missionType = EQuestMission::ARTIFACT;
 		}
-		hut->quest->lastDay = -1; //no timeout
-		hut->quest->isCustomFirst = false;
-		hut->quest->isCustomNext = false;
-		hut->quest->isCustomComplete = false;
+		hut->getQuest().lastDay = -1; //no timeout
+		hut->getQuest().isCustomFirst = false;
+		hut->getQuest().isCustomNext = false;
+		hut->getQuest().isCustomComplete = false;
 	}
 
 	if(missionType != EQuestMission::NONE)
@@ -2386,19 +2386,20 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 		{
 			for(int x = 0; x < 4; ++x)
 			{
-				guard->quest->mission.primary[x] = reader->readUInt8();
+				guard->getQuest().mission.primary[x] = reader->readUInt8();
 			}
 			break;
 		}
 		case EQuestMission::LEVEL:
 		{
-			guard->quest->mission.heroLevel = reader->readUInt32();
+			guard->getQuest().mission.heroLevel = reader->readUInt32();
 			break;
 		}
 		case EQuestMission::KILL_HERO:
 		case EQuestMission::KILL_CREATURE:
 		{
-			guard->quest->killTarget = ObjectInstanceID(reader->readUInt32());
+			assert(questsToResolve.count(guard) == 0);
+			questsToResolve[guard] = reader->readUInt32();
 			break;
 		}
 		case EQuestMission::ARTIFACT:
@@ -2414,7 +2415,7 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 						logGlobal->warn("Map '%s': Seer Hut at %s: Quest to find scroll '%s' is not implemented!", mapName, position.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
 
 				}
-				guard->quest->mission.artifacts.push_back(artid);
+				guard->getQuest().mission.artifacts.push_back(artid);
 				map->allowedArtifact.erase(artid); //these are unavailable for random generation
 			}
 			break;
@@ -2422,29 +2423,29 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 		case EQuestMission::ARMY:
 		{
 			size_t typeNumber = reader->readUInt8();
-			guard->quest->mission.creatures.resize(typeNumber);
+			guard->getQuest().mission.creatures.resize(typeNumber);
 			for(size_t hh = 0; hh < typeNumber; ++hh)
 			{
-				guard->quest->mission.creatures[hh].setType(reader->readCreature().toCreature());
-				guard->quest->mission.creatures[hh].count = reader->readUInt16();
+				guard->getQuest().mission.creatures[hh].setType(reader->readCreature().toCreature());
+				guard->getQuest().mission.creatures[hh].count = reader->readUInt16();
 			}
 			break;
 		}
 		case EQuestMission::RESOURCES:
 		{
 			for(int x = 0; x < 7; ++x)
-				guard->quest->mission.resources[x] = reader->readUInt32();
+				guard->getQuest().mission.resources[x] = reader->readUInt32();
 
 			break;
 		}
 		case EQuestMission::HERO:
 		{
-			guard->quest->mission.heroes.push_back(reader->readHero());
+			guard->getQuest().mission.heroes.push_back(reader->readHero());
 			break;
 		}
 		case EQuestMission::PLAYER:
 		{
-			guard->quest->mission.players.push_back(reader->readPlayer());
+			guard->getQuest().mission.players.push_back(reader->readPlayer());
 			break;
 		}
 		case EQuestMission::HOTA_MULTI:
@@ -2458,13 +2459,13 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 				std::set<HeroClassID> heroClasses;
 				reader->readBitmaskHeroClassesSized(heroClasses, false);
 				for(auto & hc : heroClasses)
-					guard->quest->mission.heroClasses.push_back(hc);
+					guard->getQuest().mission.heroClasses.push_back(hc);
 				break;
 			}
 			if(missionSubID == 1)
 			{
 				missionId = EQuestMission::HOTA_REACH_DATE;
-				guard->quest->mission.daysPassed = reader->readUInt32() + 1;
+				guard->getQuest().mission.daysPassed = reader->readUInt32() + 1;
 				break;
 			}
 			if(missionSubID == 2)
@@ -2483,13 +2484,13 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 		}
 	}
 
-	guard->quest->lastDay = reader->readInt32();
-	guard->quest->firstVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit")));
-	guard->quest->nextVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit")));
-	guard->quest->completedText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed")));
-	guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty();
-	guard->quest->isCustomNext = !guard->quest->nextVisitText.empty();
-	guard->quest->isCustomComplete = !guard->quest->completedText.empty();
+	guard->getQuest().lastDay = reader->readInt32();
+	guard->getQuest().firstVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit")));
+	guard->getQuest().nextVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit")));
+	guard->getQuest().completedText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed")));
+	guard->getQuest().isCustomFirst = !guard->getQuest().firstVisitText.empty();
+	guard->getQuest().isCustomNext = !guard->getQuest().nextVisitText.empty();
+	guard->getQuest().isCustomComplete = !guard->getQuest().completedText.empty();
 	return missionId;
 }
 
@@ -2756,7 +2757,8 @@ void CMapLoaderH3M::afterRead()
 		}
 	}
 
-	map->resolveQuestIdentifiers();
+	for (auto & quest : questsToResolve)
+		quest.first->getQuest().killTarget = questIdentifierToId.at(quest.second);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 4 - 0
lib/mapping/MapFormatH3M.h

@@ -268,6 +268,10 @@ private:
 	std::vector<std::shared_ptr<ObjectTemplate>> originalTemplates;
 	std::vector<std::shared_ptr<ObjectTemplate>> remappedTemplates;
 
+	/// associative list to identify which hero/creature id belongs to which object id(index for objects)
+	std::map<si32, ObjectInstanceID> questIdentifierToId;
+	std::map<IQuestObject*, si32> questsToResolve;
+
 	/** ptr to the map object which gets filled by data from the buffer */
 	CMap * map;
 

+ 1 - 11
lib/mapping/MapFormatJson.cpp

@@ -77,17 +77,7 @@ si32 MapObjectResolver::decode(const std::string & identifier) const
 
 std::string MapObjectResolver::encode(si32 identifier) const
 {
-	ObjectInstanceID id;
-
-	//use h3m questIdentifiers if they are present
-	if(owner->map->questIdentifierToId.empty())
-	{
-		id = ObjectInstanceID(identifier);
-	}
-	else
-	{
-		id = owner->map->questIdentifierToId[identifier];
-	}
+	ObjectInstanceID id(identifier);
 
 	auto object = owner->map->getObject(id);
 

+ 1 - 2
lib/networkPacks/NetPacksLib.cpp

@@ -1238,11 +1238,10 @@ void RemoveObject::applyGs(CGameState *gs)
 	const auto * quest = dynamic_cast<const IQuestObject *>(obj);
 	if (quest)
 	{
-		gs->getMap().clearQuestInstance(quest->getQuest());
 		for (auto &player : gs->players)
 		{
 			vstd::erase_if(player.second.quests, [obj](const QuestInfo & q){
-				return q.obj == obj;
+				return q.obj == obj->id;
 			});
 		}
 	}

+ 2 - 2
lib/rmg/modificators/TreasurePlacer.cpp

@@ -515,14 +515,14 @@ void TreasurePlacer::addSeerHuts()
 		auto setRandomArtifact = [qap](CGSeerHut * obj)
 		{
 			ArtifactID artid = qap->drawRandomArtifact();
-			obj->quest->mission.artifacts.push_back(artid);
+			obj->getQuest().mission.artifacts.push_back(artid);
 			qap->addQuestArtifact(artid);
 		};
 		auto destroyObject = [qap](CGObjectInstance & obj)
 		{
 			auto & seer = dynamic_cast<CGSeerHut &>(obj);
 			// Artifact can be used again
-			ArtifactID artid = seer.getQuest()->mission.artifacts.front();
+			ArtifactID artid = seer.getQuest().mission.artifacts.front();
 			qap->addRandomArtifact(artid);
 			qap->removeQuestArtifact(artid);
 		};

+ 0 - 2
lib/serializer/CSerializer.cpp

@@ -27,8 +27,6 @@ void CSerializer::addStdVecItems(CGameState *gs, GameLibrary *lib)
 		[](const CGObjectInstance &obj){ return obj.id; });
 	registerVectoredType<CArtifactInstance, ArtifactInstanceID>(&gs->getMap().artInstances,
 		[](const CArtifactInstance &artInst){ return artInst.getId(); });
-	registerVectoredType<CQuest, si32>(&gs->getMap().quests,
-		[](const CQuest &q){ return q.qid; });
 
 	smartVectorMembersSerialization = true;
 }

+ 14 - 14
mapeditor/inspector/inspector.cpp

@@ -456,26 +456,26 @@ void Inspector::updateProperties(CGEvent * o)
 
 void Inspector::updateProperties(CGSeerHut * o)
 {
-	if(!o || !o->getQuest()) return;
+	if(!o) return;
 	
-	addProperty(QObject::tr("First visit text"), o->getQuest()->firstVisitText, new MessageDelegate, false);
-	addProperty(QObject::tr("Next visit text"), o->getQuest()->nextVisitText, new MessageDelegate, false);
-	addProperty(QObject::tr("Completed text"), o->getQuest()->completedText, new MessageDelegate, false);
-	addProperty(QObject::tr("Repeat quest"), o->getQuest()->repeatedQuest, false);
-	addProperty(QObject::tr("Time limit"), o->getQuest()->lastDay, false);
+	addProperty(QObject::tr("First visit text"), o->getQuest().firstVisitText, new MessageDelegate, false);
+	addProperty(QObject::tr("Next visit text"), o->getQuest().nextVisitText, new MessageDelegate, false);
+	addProperty(QObject::tr("Completed text"), o->getQuest().completedText, new MessageDelegate, false);
+	addProperty(QObject::tr("Repeat quest"), o->getQuest().repeatedQuest, false);
+	addProperty(QObject::tr("Time limit"), o->getQuest().lastDay, false);
 	
 	{ //Quest
-		auto * delegate = new QuestDelegate(controller, *o->quest);
+		auto * delegate = new QuestDelegate(controller, o->getQuest());
 		addProperty(QObject::tr("Quest"), PropertyEditorPlaceholder(), delegate, false);
 	}
 }
 
 void Inspector::updateProperties(CGQuestGuard * o)
 {
-	if(!o || !o->getQuest()) return;
+	if(!o) return;
 	
 	addProperty(QObject::tr("Reward"), PropertyEditorPlaceholder(), nullptr, true);
-	addProperty(QObject::tr("Repeat quest"), o->getQuest()->repeatedQuest, true);
+	addProperty(QObject::tr("Repeat quest"), o->getQuest().repeatedQuest, true);
 }
 
 void Inspector::updateProperties()
@@ -773,18 +773,18 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
 	if(!o) return;
 	
 	if(key == QObject::tr("First visit text"))
-		o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+		o->getQuest().firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
 			TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
 	if(key == QObject::tr("Next visit text"))
-		o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+		o->getQuest().nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
 			TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
 	if(key == QObject::tr("Completed text"))
-		o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+		o->getQuest().completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
 			TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
 	if(key == QObject::tr("Repeat quest"))
-		o->quest->repeatedQuest = value.toBool();
+		o->getQuest().repeatedQuest = value.toBool();
 	if(key == QObject::tr("Time limit"))
-		o->quest->lastDay = value.toString().toInt();
+		o->getQuest().lastDay = value.toString().toInt();
 }
 
 void Inspector::setProperty(CGQuestGuard * o, const QString & key, const QVariant & value)