Browse Source

Merge pull request #3035 from Nordsoft91/quests

Quests rework
Nordsoft91 2 years ago
parent
commit
aa25ab6488

+ 13 - 26
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -37,42 +37,29 @@ TGoalVec CompleteQuest::decompose() const
 	}
 	}
 
 
 	logAi->debug("Trying to realize quest: %s", questToString());
 	logAi->debug("Trying to realize quest: %s", questToString());
-
-	switch(q.quest->missionType)
-	{
-	case CQuest::MISSION_ART:
+	
+	if(!q.quest->mission.artifacts.empty())
 		return missionArt();
 		return missionArt();
 
 
-	case CQuest::MISSION_HERO:
+	if(!q.quest->mission.heroes.empty())
 		return missionHero();
 		return missionHero();
 
 
-	case CQuest::MISSION_ARMY:
+	if(!q.quest->mission.creatures.empty())
 		return missionArmy();
 		return missionArmy();
 
 
-	case CQuest::MISSION_RESOURCES:
+	if(q.quest->mission.resources.nonZero())
 		return missionResources();
 		return missionResources();
 
 
-	case CQuest::MISSION_KILL_HERO:
-	case CQuest::MISSION_KILL_CREATURE:
+	if(q.quest->killTarget != ObjectInstanceID::NONE)
 		return missionDestroyObj();
 		return missionDestroyObj();
 
 
-	case CQuest::MISSION_PRIMARY_STAT:
-		return missionIncreasePrimaryStat();
+	for(auto & s : q.quest->mission.primary)
+		if(s)
+			return missionIncreasePrimaryStat();
 
 
-	case CQuest::MISSION_LEVEL:
+	if(q.quest->mission.heroLevel > 0)
 		return missionLevel();
 		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();
 	return TGoalVec();
 }
 }
 
 
@@ -107,7 +94,7 @@ std::string CompleteQuest::questToString() const
 		return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
 		return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
 	}
 	}
 
 
-	if(q.quest->missionType == CQuest::MISSION_NONE)
+	if(q.quest->questName == CQuest::missionName(0))
 		return "inactive quest";
 		return "inactive quest";
 
 
 	MetaString ms;
 	MetaString ms;
@@ -137,7 +124,7 @@ TGoalVec CompleteQuest::missionArt() const
 
 
 	CaptureObjectsBehavior findArts;
 	CaptureObjectsBehavior findArts;
 
 
-	for(auto art : q.quest->m5arts)
+	for(auto art : q.quest->mission.artifacts)
 	{
 	{
 		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
 		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
 	}
 	}
@@ -223,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
 
 
 TGoalVec CompleteQuest::missionDestroyObj() const
 TGoalVec CompleteQuest::missionDestroyObj() const
 {
 {
-	auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
+	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
 
 
 	if(!obj)
 	if(!obj)
 		return CaptureObjectsBehavior(q.obj).decompose();
 		return CaptureObjectsBehavior(q.obj).decompose();

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp

@@ -25,7 +25,7 @@ namespace AIPathfinding
 			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
 			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
 		}
 		}
 
 
-		return questInfo.quest->progress == CQuest::NOT_ACTIVE 
+		return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
 			|| questInfo.quest->checkQuest(node->actor->hero);
 			|| questInfo.quest->checkQuest(node->actor->hero);
 	}
 	}
 
 

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

@@ -130,7 +130,9 @@ namespace AIPathfinding
 		auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
 		auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
 		QuestAction questAction(questInfo);
 		QuestAction questAction(questInfo);
 
 
-		if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE)
+		if(destination.nodeObject->ID == Obj::QUEST_GUARD
+		   && questObj->quest->mission == Rewardable::Limiter{}
+		   && questObj->quest->killTarget == ObjectInstanceID::NONE)
 		{
 		{
 			return false;
 			return false;
 		}
 		}

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

@@ -21,48 +21,43 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
 	return q.quest->qid == other.q.quest->qid;
 	return q.quest->qid == other.q.quest->qid;
 }
 }
 
 
+bool isKeyMaster(const QuestInfo & q)
+{
+	return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
+}
+
 TGoalVec CompleteQuest::getAllPossibleSubgoals()
 TGoalVec CompleteQuest::getAllPossibleSubgoals()
 {
 {
 	TGoalVec solutions;
 	TGoalVec solutions;
 
 
-	if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
+	if(!q.quest->isCompleted)
 	{
 	{
 		logAi->debug("Trying to realize quest: %s", questToString());
 		logAi->debug("Trying to realize quest: %s", questToString());
+		
+		if(isKeyMaster(q))
+			return missionKeymaster();
 
 
-		switch(q.quest->missionType)
-		{
-		case CQuest::MISSION_ART:
+		if(!q.quest->mission.artifacts.empty())
 			return missionArt();
 			return missionArt();
 
 
-		case CQuest::MISSION_HERO:
+		if(!q.quest->mission.heroes.empty())
 			return missionHero();
 			return missionHero();
 
 
-		case CQuest::MISSION_ARMY:
+		if(!q.quest->mission.creatures.empty())
 			return missionArmy();
 			return missionArmy();
 
 
-		case CQuest::MISSION_RESOURCES:
+		if(q.quest->mission.resources.nonZero())
 			return missionResources();
 			return missionResources();
 
 
-		case CQuest::MISSION_KILL_HERO:
-		case CQuest::MISSION_KILL_CREATURE:
+		if(q.quest->killTarget != ObjectInstanceID::NONE)
 			return missionDestroyObj();
 			return missionDestroyObj();
 
 
-		case CQuest::MISSION_PRIMARY_STAT:
-			return missionIncreasePrimaryStat();
+		for(auto & s : q.quest->mission.primary)
+			if(s)
+				return missionIncreasePrimaryStat();
 
 
-		case CQuest::MISSION_LEVEL:
+		if(q.quest->mission.heroLevel > 0)
 			return missionLevel();
 			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();
 	return TGoalVec();
@@ -70,7 +65,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
 
 
 TSubgoal CompleteQuest::whatToDoToAchieve()
 TSubgoal CompleteQuest::whatToDoToAchieve()
 {
 {
-	if(q.quest->missionType == CQuest::MISSION_NONE)
+	if(q.quest->mission == Rewardable::Limiter{})
 	{
 	{
 		throw cannotFulfillGoalException("Can not complete inactive quest");
 		throw cannotFulfillGoalException("Can not complete inactive quest");
 	}
 	}
@@ -104,7 +99,7 @@ std::string CompleteQuest::completeMessage() const
 
 
 std::string CompleteQuest::questToString() const
 std::string CompleteQuest::questToString() const
 {
 {
-	if(q.quest->missionType == CQuest::MISSION_NONE)
+	if(q.quest->questName == CQuest::missionName(0))
 		return "inactive quest";
 		return "inactive quest";
 
 
 	MetaString ms;
 	MetaString ms;
@@ -137,7 +132,7 @@ TGoalVec CompleteQuest::missionArt() const
 	if(!solutions.empty())
 	if(!solutions.empty())
 		return solutions;
 		return solutions;
 
 
-	for(auto art : q.quest->m5arts)
+	for(auto art : q.quest->mission.artifacts)
 	{
 	{
 		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
 		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
 	}
 	}
@@ -165,7 +160,7 @@ TGoalVec CompleteQuest::missionArmy() const
 	if(!solutions.empty())
 	if(!solutions.empty())
 		return solutions;
 		return solutions;
 
 
-	for(auto creature : q.quest->m6creatures)
+	for(auto creature : q.quest->mission.creatures)
 	{
 	{
 		solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
 		solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
 	}
 	}
@@ -179,7 +174,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
 
 
 	if(solutions.empty())
 	if(solutions.empty())
 	{
 	{
-		for(int i = 0; i < q.quest->m2stats.size(); ++i)
+		for(int i = 0; i < q.quest->mission.primary.size(); ++i)
 		{
 		{
 			// TODO: library, school and other boost objects
 			// TODO: library, school and other boost objects
 			logAi->debug("Don't know how to increase primary stat %d", i);
 			logAi->debug("Don't know how to increase primary stat %d", i);
@@ -195,7 +190,7 @@ TGoalVec CompleteQuest::missionLevel() const
 
 
 	if(solutions.empty())
 	if(solutions.empty())
 	{
 	{
-		logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
+		logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
 	}
 	}
 
 
 	return solutions;
 	return solutions;
@@ -227,10 +222,10 @@ TGoalVec CompleteQuest::missionResources() const
 		}
 		}
 		else
 		else
 		{
 		{
-			for(int i = 0; i < q.quest->m7resources.size(); ++i)
+			for(int i = 0; i < q.quest->mission.resources.size(); ++i)
 			{
 			{
-				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
+				if(q.quest->mission.resources[i])
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
 			}
 			}
 		}
 		}
 	}
 	}
@@ -246,7 +241,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 {
 {
 	TGoalVec solutions;
 	TGoalVec solutions;
 
 
-	auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
+	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
 
 
 	if(!obj)
 	if(!obj)
 		return ai->ah->howToVisitObj(q.obj);
 		return ai->ah->howToVisitObj(q.obj);

+ 14 - 8
client/windows/CQuestLog.cpp

@@ -148,11 +148,11 @@ void CQuestLog::recreateLabelList()
 	int currentLabel = 0;
 	int currentLabel = 0;
 	for (int i = 0; i < quests.size(); ++i)
 	for (int i = 0; i < quests.size(); ++i)
 	{
 	{
-		// Quests with MISSION_NONE type don't have text for them and can't be displayed
-		if (quests[i].quest->missionType == CQuest::MISSION_NONE)
+		// Quests without mision don't have text for them and can't be displayed
+		if (quests[i].quest->mission == Rewardable::Limiter{})
 			continue;
 			continue;
 
 
-		if (quests[i].quest->progress == CQuest::COMPLETE)
+		if (quests[i].quest->isCompleted)
 		{
 		{
 			completeMissing = false;
 			completeMissing = false;
 			if (hideComplete)
 			if (hideComplete)
@@ -180,7 +180,7 @@ void CQuestLog::recreateLabelList()
 		labels.push_back(label);
 		labels.push_back(label);
 
 
 		// Select latest active quest
 		// Select latest active quest
-		if (quests[i].quest->progress != CQuest::COMPLETE)
+		if(!quests[i].quest->isCompleted)
 			selectQuest(i, currentLabel);
 			selectQuest(i, currentLabel);
 
 
 		currentLabel = static_cast<int>(labels.size());
 		currentLabel = static_cast<int>(labels.size());
@@ -236,7 +236,7 @@ void CQuestLog::selectQuest(int which, int labelId)
 
 
 	MetaString text;
 	MetaString text;
 	std::vector<Component> components;
 	std::vector<Component> components;
-	currentQuest->quest->getVisitText (text, components, currentQuest->quest->isCustomFirst, true);
+	currentQuest->quest->getVisitText(text, components, true);
 	if(description->slider)
 	if(description->slider)
 		description->slider->scrollToMin(); // scroll text to start position
 		description->slider->scrollToMin(); // scroll text to start position
 	description->setText(text.toString()); //TODO: use special log entry text
 	description->setText(text.toString()); //TODO: use special log entry text
@@ -247,9 +247,15 @@ void CQuestLog::selectQuest(int which, int labelId)
 	int descriptionHeight = DESCRIPTION_HEIGHT_MAX;
 	int descriptionHeight = DESCRIPTION_HEIGHT_MAX;
 	if(componentsSize)
 	if(componentsSize)
 	{
 	{
-		descriptionHeight -= 15;
 		CComponent::ESize imageSize = CComponent::large;
 		CComponent::ESize imageSize = CComponent::large;
-		switch (currentQuest->quest->missionType)
+		if (componentsSize > 4)
+		{
+			imageSize = CComponent::small; // Only small icons can be used for resources as 4+ icons take too much space
+			descriptionHeight -= 155;
+		}
+		else
+			descriptionHeight -= 130;
+		/*switch (currentQuest->quest->missionType)
 		{
 		{
 			case CQuest::MISSION_ARMY:
 			case CQuest::MISSION_ARMY:
 			{
 			{
@@ -285,7 +291,7 @@ void CQuestLog::selectQuest(int which, int labelId)
 			default:
 			default:
 				descriptionHeight -= 115;
 				descriptionHeight -= 115;
 				break;
 				break;
-		}
+		}*/
 
 
 		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 
 

+ 31 - 4
docs/modders/Map_Objects/Rewardable.md

@@ -72,7 +72,7 @@ Rewardable object is defined similarly to other objects, with key difference bei
       // additional list of conditions. Limiter will be valid if any of these conditions are true
       // additional list of conditions. Limiter will be valid if any of these conditions are true
       "anyOf" : [
       "anyOf" : [
         {
         {
-          // See "Configurable Properties" section for additiona parameters
+          // See "Configurable Properties" section for additional parameters
           <additional properties>
           <additional properties>
         }
         }
       ]
       ]
@@ -80,12 +80,12 @@ Rewardable object is defined similarly to other objects, with key difference bei
       // additional list of conditions. Limiter will be valid only if none of these conditions are true
       // additional list of conditions. Limiter will be valid only if none of these conditions are true
       "noneOf" : [
       "noneOf" : [
         {
         {
-          // See "Configurable Properties" section for additiona parameters
+          // See "Configurable Properties" section for additional parameters
           <additional properties>
           <additional properties>
         }
         }
       ]
       ]
 
 
-      // See "Configurable Properties" section for additiona parameters
+      // See "Configurable Properties" section for additional parameters
       <additional properties>
       <additional properties>
     }
     }
     
     
@@ -95,7 +95,7 @@ Rewardable object is defined similarly to other objects, with key difference bei
     // object will be disappeared after taking reward is set to true
     // object will be disappeared after taking reward is set to true
     "removeObject": false
     "removeObject": false
 
 
-    // See "Configurable Properties" section for additiona parameters
+    // See "Configurable Properties" section for additional parameters
     <additional properties>
     <additional properties>
   }
   }
 ],
 ],
@@ -450,4 +450,31 @@ Keep in mind, that all randomization is performed on map load and on object rese
     "spell" : "townPortal",
     "spell" : "townPortal",
     "schoolLevel": 3
     "schoolLevel": 3
 }
 }
+```
+
+### Player color
+- Can be used as limiter
+- Can NOT be used as reward
+- Only players with specific color can pass the limiter
+
+```jsonc
+"colors" : [ "red", "blue", "tan", "green", "orange", "purple", "teal", "pink" ]
+```
+
+### Hero types
+- Can be used as limiter
+- Can NOT be used as reward
+- Only specific heroes can pass the limiter
+
+```jsonc
+"heroes" : [ "orrin" ]
+```
+
+### Hero classes
+- Can be used as limiter
+- Can NOT be used as reward
+- Only heroes belonging to specific classes can pass the limiter
+
+```jsonc
+"heroClasses" : [ "battlemage" ]
 ```
 ```

+ 8 - 0
lib/CCreatureSet.cpp

@@ -1029,6 +1029,14 @@ void CStackBasicDescriptor::setType(const CCreature * c)
 	type = c;
 	type = c;
 }
 }
 
 
+bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r)
+{
+	return (!l.type && !r.type)
+	|| (l.type && r.type
+		&& l.type->getId() == r.type->getId()
+		&& l.count == r.count);
+}
+
 void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
 void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
 {
 {
 	handler.serializeInt("amount", count);
 	handler.serializeInt("amount", count);

+ 2 - 0
lib/CCreatureSet.h

@@ -42,6 +42,8 @@ public:
 	TQuantity getCount() const;
 	TQuantity getCount() const;
 
 
 	virtual void setType(const CCreature * c);
 	virtual void setType(const CCreature * c);
+	
+	friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{

+ 3 - 3
lib/CGameInfoCallback.cpp

@@ -662,9 +662,9 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
 	case RumorState::TYPE_SPECIAL:
 	case RumorState::TYPE_SPECIAL:
 		text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first);
 		text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first);
 		if(rumor.first == RumorState::RUMOR_GRAIL)
 		if(rumor.first == RumorState::RUMOR_GRAIL)
-			text.replaceTextID(TextIdentifier("core", "genrltxt", "arraytxt", 158 + rumor.second).get());
+			text.replaceTextID(TextIdentifier("core", "arraytxt", 158 + rumor.second).get());
 		else
 		else
-			text.replaceTextID(TextIdentifier("core", "genrltxt", "capitalColors", rumor.second).get());
+			text.replaceTextID(TextIdentifier("core", "plcolors", rumor.second).get());
 
 
 		break;
 		break;
 	case RumorState::TYPE_MAP:
 	case RumorState::TYPE_MAP:
@@ -672,7 +672,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
 		break;
 		break;
 
 
 	case RumorState::TYPE_RAND:
 	case RumorState::TYPE_RAND:
-		text.replaceTextID(TextIdentifier("core", "genrltxt", "randtvrn", rumor.first).get());
+		text.replaceTextID(TextIdentifier("core", "randtvrn", rumor.first).get());
 		break;
 		break;
 	}
 	}
 
 

+ 6 - 6
lib/CGeneralTextHandler.cpp

@@ -264,21 +264,21 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo
 
 
 void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
 void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
 {
 {
-	subContainers.insert(&container);
+	subContainers.push_back(&container);
 }
 }
 
 
 void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container)
 void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container)
 {
 {
-	subContainers.erase(&container);
+	subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
 }
 }
 
 
 const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
 const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
 {
 {
 	if(stringsLocalizations.count(identifier.get()) == 0)
 	if(stringsLocalizations.count(identifier.get()) == 0)
 	{
 	{
-		for(const auto * container : subContainers)
-			if(container->identifierExists(identifier))
-				return container->deserialize(identifier);
+		for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
+			if((*containerIter)->identifierExists(identifier))
+				return (*containerIter)->deserialize(identifier);
 		
 		
 		logGlobal->error("Unable to find localization for string '%s'", identifier.get());
 		logGlobal->error("Unable to find localization for string '%s'", identifier.get());
 		return identifier.get();
 		return identifier.get();
@@ -547,7 +547,7 @@ CGeneralTextHandler::CGeneralTextHandler():
 
 
 		for (size_t i = 0; i < 9; ++i) //9 types of quests
 		for (size_t i = 0; i < 9; ++i) //9 types of quests
 		{
 		{
-			std::string questName = CQuest::missionName(static_cast<CQuest::Emission>(1+i));
+			std::string questName = CQuest::missionName(1+i);
 
 
 			for (size_t j = 0; j < 5; ++j)
 			for (size_t j = 0; j < 5; ++j)
 			{
 			{

+ 1 - 1
lib/CGeneralTextHandler.h

@@ -146,7 +146,7 @@ protected:
 	/// map identifier -> localization
 	/// map identifier -> localization
 	std::unordered_map<std::string, StringState> stringsLocalizations;
 	std::unordered_map<std::string, StringState> stringsLocalizations;
 	
 	
-	std::set<const TextLocalizationContainer *> subContainers;
+	std::vector<const TextLocalizationContainer *> subContainers;
 	
 	
 	/// add selected string to internal storage as high-priority strings
 	/// add selected string to internal storage as high-priority strings
 	void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized);
 	void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized);

+ 41 - 0
lib/JsonRandom.cpp

@@ -22,6 +22,7 @@
 #include "CCreatureSet.h"
 #include "CCreatureSet.h"
 #include "spells/CSpellHandler.h"
 #include "spells/CSpellHandler.h"
 #include "CSkillHandler.h"
 #include "CSkillHandler.h"
+#include "CHeroHandler.h"
 #include "IGameCallback.h"
 #include "IGameCallback.h"
 #include "mapObjects/IObjectInterface.h"
 #include "mapObjects/IObjectInterface.h"
 #include "modding/IdentifierStorage.h"
 #include "modding/IdentifierStorage.h"
@@ -282,6 +283,46 @@ namespace JsonRandom
 		return ret;
 		return ret;
 	}
 	}
 
 
+	std::vector<PlayerColor> loadColors(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<PlayerColor> ret;
+		std::set<std::string> def;
+		
+		for(auto & color : GameConstants::PLAYER_COLOR_NAMES)
+			def.insert(color);
+		
+		for(auto & entry : value.Vector())
+		{
+			auto key = loadKey(entry, rng, def);
+			auto pos = vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, key);
+			if(pos < 0)
+				logMod->warn("Unable to determine player color %s", key);
+			else
+				ret.emplace_back(pos);
+		}
+		return ret;
+	}
+
+	std::vector<HeroTypeID> loadHeroes(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<HeroTypeID> ret;
+		for(auto & entry : value.Vector())
+		{
+			ret.push_back(VLC->heroTypes()->getByIndex(VLC->identifiers()->getIdentifier("hero", entry.String()).value())->getId());
+		}
+		return ret;
+	}
+
+	std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<HeroClassID> ret;
+		for(auto & entry : value.Vector())
+		{
+			ret.push_back(VLC->heroClasses()->getByIndex(VLC->identifiers()->getIdentifier("heroClass", entry.String()).value())->getId());
+		}
+		return ret;
+	}
+
 	CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
 	CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
 	{
 	{
 		CStackBasicDescriptor stack;
 		CStackBasicDescriptor stack;

+ 4 - 0
lib/JsonRandom.h

@@ -48,6 +48,10 @@ namespace JsonRandom
 	DLL_LINKAGE std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng);
 	DLL_LINKAGE std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng);
 	DLL_LINKAGE std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value);
 	DLL_LINKAGE std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value);
 
 
+	DLL_LINKAGE std::vector<PlayerColor> loadColors(const JsonNode & value, CRandomGenerator & rng);
+	DLL_LINKAGE std::vector<HeroTypeID> loadHeroes(const JsonNode & value, CRandomGenerator & rng);
+	DLL_LINKAGE std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, CRandomGenerator & rng);
+
 	DLL_LINKAGE std::vector<Bonus> loadBonuses(const JsonNode & value);
 	DLL_LINKAGE std::vector<Bonus> loadBonuses(const JsonNode & value);
 	//DLL_LINKAGE std::vector<Component> loadComponents(const JsonNode & value);
 	//DLL_LINKAGE std::vector<Component> loadComponents(const JsonNode & value);
 }
 }

+ 21 - 0
lib/constants/EntityIdentifiers.cpp

@@ -19,6 +19,8 @@
 #include <vcmi/FactionService.h>
 #include <vcmi/FactionService.h>
 #include <vcmi/HeroType.h>
 #include <vcmi/HeroType.h>
 #include <vcmi/HeroTypeService.h>
 #include <vcmi/HeroTypeService.h>
+#include <vcmi/HeroClass.h>
+#include <vcmi/HeroClassService.h>
 
 
 #include <vcmi/spells/Spell.h>
 #include <vcmi/spells/Spell.h>
 #include <vcmi/spells/Service.h>
 #include <vcmi/spells/Service.h>
@@ -103,6 +105,25 @@ namespace GameConstants
 #endif
 #endif
 }
 }
 
 
+si32 HeroClassID::decode(const std::string & identifier)
+{
+	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "heroClass", identifier);
+	if(rawId)
+		return rawId.value();
+	else
+		return -1;
+}
+
+std::string HeroClassID::encode(const si32 index)
+{
+	return VLC->heroClasses()->getByIndex(index)->getJsonKey();
+}
+
+std::string HeroClassID::entityType()
+{
+	return "heroClass";
+}
+
 si32 HeroTypeID::decode(const std::string & identifier)
 si32 HeroTypeID::decode(const std::string & identifier)
 {
 {
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", identifier);
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", identifier);

+ 4 - 0
lib/constants/EntityIdentifiers.h

@@ -223,6 +223,10 @@ class HeroClassID : public Identifier<HeroClassID>
 {
 {
 public:
 public:
 	using Identifier<HeroClassID>::Identifier;
 	using Identifier<HeroClassID>::Identifier;
+	///json serialization helpers
+	DLL_LINKAGE static si32 decode(const std::string & identifier);
+	DLL_LINKAGE static std::string encode(const si32 index);
+	static std::string entityType();
 };
 };
 
 
 class HeroTypeID : public Identifier<HeroTypeID>
 class HeroTypeID : public Identifier<HeroTypeID>

+ 292 - 433
lib/mapObjects/CQuest.cpp

@@ -28,6 +28,7 @@
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"
 #include "../modding/ModScope.h"
 #include "../modding/ModScope.h"
 #include "../modding/ModUtility.h"
 #include "../modding/ModUtility.h"
+#include "../spells/CSpellHandler.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -37,16 +38,17 @@ std::map <PlayerColor, std::set <ui8> > CGKeys::playerKeyMap;
 //TODO: Remove constructor
 //TODO: Remove constructor
 CQuest::CQuest():
 CQuest::CQuest():
 	qid(-1),
 	qid(-1),
-	missionType(MISSION_NONE),
-	progress(NOT_ACTIVE),
+	isCompleted(false),
 	lastDay(-1),
 	lastDay(-1),
-	m13489val(0),
+	killTarget(ObjectInstanceID::NONE),
 	textOption(0),
 	textOption(0),
 	completedOption(0),
 	completedOption(0),
 	stackDirection(0),
 	stackDirection(0),
 	isCustomFirst(false),
 	isCustomFirst(false),
 	isCustomNext(false),
 	isCustomNext(false),
-	isCustomComplete(false)
+	isCustomComplete(false),
+	repeatedQuest(false),
+	questName(CQuest::missionName(0))
 {
 {
 }
 }
 
 
@@ -56,9 +58,9 @@ static std::string visitedTxt(const bool visited)
 	return VLC->generaltexth->allTexts[id];
 	return VLC->generaltexth->allTexts[id];
 }
 }
 
 
-const std::string & CQuest::missionName(CQuest::Emission mission)
+const std::string & CQuest::missionName(int mission)
 {
 {
-	static const std::array<std::string, 11> names = {
+	static const std::array<std::string, 13> names = {
 		"empty",
 		"empty",
 		"heroLevel",
 		"heroLevel",
 		"primarySkill",
 		"primarySkill",
@@ -69,7 +71,9 @@ const std::string & CQuest::missionName(CQuest::Emission mission)
 		"bringResources",
 		"bringResources",
 		"bringHero",
 		"bringHero",
 		"bringPlayer",
 		"bringPlayer",
-		"keymaster"
+		"keymaster",
+		"hota",
+		"other"
 	};
 	};
 
 
 	if(static_cast<size_t>(mission) < names.size())
 	if(static_cast<size_t>(mission) < names.size())
@@ -99,7 +103,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
 	ui32 count = 0;
 	ui32 count = 0;
 	ui32 slotsCount = 0;
 	ui32 slotsCount = 0;
 	bool hasExtraCreatures = false;
 	bool hasExtraCreatures = false;
-	for(cre = q->m6creatures.begin(); cre != q->m6creatures.end(); ++cre)
+	for(cre = q->mission.creatures.begin(); cre != q->mission.creatures.end(); ++cre)
 	{
 	{
 		for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
 		for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
 		{
 		{
@@ -121,348 +125,212 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
 
 
 bool CQuest::checkQuest(const CGHeroInstance * h) const
 bool CQuest::checkQuest(const CGHeroInstance * h) const
 {
 {
-	switch (missionType)
+	if(!mission.heroAllowed(h))
+		return false;
+	
+	if(killTarget != ObjectInstanceID::NONE)
 	{
 	{
-		case MISSION_NONE:
-			return true;
-		case MISSION_LEVEL:
-			return m13489val <= h->level;
-		case MISSION_PRIMARY_STAT:
-			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
-			{
-				if(h->getPrimSkillLevel(static_cast<PrimarySkill>(i)) < static_cast<int>(m2stats[i]))
-					return false;
-			}
-			return true;
-		case MISSION_KILL_HERO:
-		case MISSION_KILL_CREATURE:
-			if(!CGHeroInstance::cb->getObjByQuestIdentifier(m13489val))
-				return true;
+		if(CGHeroInstance::cb->getObjByQuestIdentifier(killTarget))
 			return false;
 			return false;
-		case MISSION_ART:
+	}
+	
+	return true;
+}
+
+void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
+{
+	for(auto & elem : mission.artifacts)
+	{
+		if(h->hasArt(elem))
 		{
 		{
-			// if the object was deserialized
-			if(artifactsRequirements.empty())
-				for(const auto & id : m5arts)
-					++artifactsRequirements[id];
+			cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
+		}
+		else
+		{
+			const auto * assembly = h->getAssemblyByConstituent(elem);
+			assert(assembly);
+			auto parts = assembly->getPartsInfo();
+
+			// Remove the assembly
+			cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));
 
 
-			size_t reqSlots = 0;
-			for(const auto & elem : artifactsRequirements)
+			// Disassemble this backpack artifact
+			for(const auto & ci : parts)
 			{
 			{
-				// check required amount of artifacts
-				if(h->getArtPosCount(elem.first, false, true, true) < elem.second)
-					return false;
-				if(!h->hasArt(elem.first))
-					reqSlots += h->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
+				if(ci.art->getTypeId() != elem)
+					cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::BACKPACK_START);
 			}
 			}
-			if(ArtifactUtils::isBackpackFreeSlots(h, reqSlots))
-				return true;
-			else
-				return false;
 		}
 		}
-		case MISSION_ARMY:
-			return checkMissionArmy(this, h);
-		case MISSION_RESOURCES:
-			for(GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i) //including Mithril ?
-			{	//Quest has no direct access to callback
-				if(CGHeroInstance::cb->getResource(h->tempOwner, i) < static_cast<int>(m7resources[i]))
-					return false;
-			}
-			return true;
-		case MISSION_HERO:
-			return m13489val == h->type->getIndex();
-		case MISSION_PLAYER:
-			return m13489val == h->getOwner().getNum();
-		default:
-			return false;
 	}
 	}
+			
+	cb->takeCreatures(h->id, mission.creatures);
+	cb->giveResources(h->getOwner(), mission.resources);
 }
 }
 
 
-void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
+void CQuest::addTextReplacements(MetaString & text, std::vector<Component> & components) const
 {
 {
-	MetaString text;
-	bool failRequirements = (h ? !checkQuest(h) : true);
-
-	if(firstVisit)
+	if(mission.heroLevel > 0)
+		text.replaceNumber(mission.heroLevel);
+	
+	if(mission.heroExperience > 0)
+		text.replaceNumber(mission.heroExperience);
+	
+	{ //primary skills
+		MetaString loot;
+		for(int i = 0; i < 4; ++i)
+		{
+			if(mission.primary[i])
+			{
+				loot.appendRawString("%d %s");
+				loot.replaceNumber(mission.primary[i]);
+				loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
+			}
+		}
+		
+		for(auto & skill : mission.secondary)
+		{
+			loot.appendTextID(VLC->skillh->getById(skill.first)->getNameTextID());
+		}
+		
+		for(auto & spell : mission.spells)
+		{
+			loot.appendTextID(VLC->spellh->getById(spell)->getNameTextID());
+		}
+		
+		if(!loot.empty())
+			text.replaceRawString(loot.buildList());
+	}
+	
+	if(killTarget != ObjectInstanceID::NONE && !heroName.empty())
 	{
 	{
-		isCustom = isCustomFirst;
-		text = firstVisitText;
-		iwText.appendRawString(text.toString());
+		components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0);
+		addKillTargetReplacements(text);
 	}
 	}
-	else if(failRequirements)
+	
+	if(killTarget != ObjectInstanceID::NONE && stackToKill.type)
 	{
 	{
-		isCustom = isCustomNext;
-		text = nextVisitText;
-		iwText.appendRawString(text.toString());
+		components.emplace_back(stackToKill);
+		addKillTargetReplacements(text);
 	}
 	}
-	switch (missionType)
+	
+	if(!mission.heroes.empty())
+		text.replaceRawString(VLC->heroh->getById(mission.heroes.front())->getNameTranslated());
+	
+	if(!mission.artifacts.empty())
 	{
 	{
-		case MISSION_LEVEL:
-			components.emplace_back(Component::EComponentType::EXPERIENCE, 0, m13489val, 0);
-			if(!isCustom)
-				iwText.replaceNumber(m13489val);
-			break;
-		case MISSION_PRIMARY_STAT:
-		{
-			MetaString loot;
-			for(int i = 0; i < 4; ++i)
-			{
-				if(m2stats[i])
-				{
-					components.emplace_back(Component::EComponentType::PRIM_SKILL, i, m2stats[i], 0);
-					loot.appendRawString("%d %s");
-					loot.replaceNumber(m2stats[i]);
-					loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
-				}
-			}
-			if (!isCustom)
-				iwText.replaceRawString(loot.buildList());
-		}
-			break;
-		case MISSION_KILL_HERO:
-			components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0);
-			if(!isCustom)
-				addReplacements(iwText, text.toString());
-			break;
-		case MISSION_HERO:
-			//FIXME: portrait may not match hero, if custom portrait was set in map editor
-			components.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0);
-			if(!isCustom)
-				iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
-			break;
-		case MISSION_KILL_CREATURE:
-			{
-				components.emplace_back(stackToKill);
-				if(!isCustom)
-				{
-					addReplacements(iwText, text.toString());
-				}
-			}
-			break;
-		case MISSION_ART:
+		MetaString loot;
+		for(const auto & elem : mission.artifacts)
 		{
 		{
-			MetaString loot;
-			for(const auto & elem : m5arts)
-			{
-				components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
-				loot.appendRawString("%s");
-				loot.replaceLocalString(EMetaText::ART_NAMES, elem);
-			}
-			if(!isCustom)
-				iwText.replaceRawString(loot.buildList());
+			loot.appendRawString("%s");
+			loot.replaceLocalString(EMetaText::ART_NAMES, elem);
 		}
 		}
-			break;
-		case MISSION_ARMY:
+		text.replaceRawString(loot.buildList());
+	}
+	
+	if(!mission.creatures.empty())
+	{
+		MetaString loot;
+		for(const auto & elem : mission.creatures)
 		{
 		{
-			MetaString loot;
-			for(const auto & elem : m6creatures)
-			{
-				components.emplace_back(elem);
-				loot.appendRawString("%s");
-				loot.replaceCreatureName(elem);
-			}
-			if(!isCustom)
-				iwText.replaceRawString(loot.buildList());
+			loot.appendRawString("%s");
+			loot.replaceCreatureName(elem);
 		}
 		}
-			break;
-		case MISSION_RESOURCES:
+		text.replaceRawString(loot.buildList());
+	}
+	
+	if(mission.resources.nonZero())
+	{
+		MetaString loot;
+		for(int i = 0; i < 7; ++i)
 		{
 		{
-			MetaString loot;
-			for(int i = 0; i < 7; ++i)
+			if(mission.resources[i])
 			{
 			{
-				if(m7resources[i])
-				{
-					components.emplace_back(Component::EComponentType::RESOURCE, i, m7resources[i], 0);
-					loot.appendRawString("%d %s");
-					loot.replaceNumber(m7resources[i]);
-					loot.replaceLocalString(EMetaText::RES_NAMES, i);
-				}
+				loot.appendRawString("%d %s");
+				loot.replaceNumber(mission.resources[i]);
+				loot.replaceLocalString(EMetaText::RES_NAMES, i);
 			}
 			}
-			if(!isCustom)
-				iwText.replaceRawString(loot.buildList());
 		}
 		}
-			break;
-		case MISSION_PLAYER:
-			components.emplace_back(Component::EComponentType::FLAG, m13489val, 0, 0);
-			if(!isCustom)
-				iwText.replaceLocalString(EMetaText::COLOR, m13489val);
-			break;
+		text.replaceRawString(loot.buildList());
+	}
+	
+	if(!mission.players.empty())
+	{
+		MetaString loot;
+		for(auto & p : mission.players)
+			loot.appendLocalString(EMetaText::COLOR, p);
+		
+		text.replaceRawString(loot.buildList());
 	}
 	}
+	
+	if(lastDay >= 0)
+		text.replaceNumber(lastDay - IObjectInterface::cb->getDate(Date::DAY));
 }
 }
 
 
-void CQuest::getRolloverText(MetaString &ms, bool onHover) const
+void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
 {
 {
-	// Quests with MISSION_NONE type don't have a text for them
-	assert(missionType != MISSION_NONE);
+	bool failRequirements = (h ? !checkQuest(h) : true);
+	mission.loadComponents(components, h);
+
+	if(firstVisit)
+		iwText.appendRawString(firstVisitText.toString());
+	else if(failRequirements)
+		iwText.appendRawString(nextVisitText.toString());
+	
+	if(lastDay >= 0)
+		iwText.appendTextID(TextIdentifier("core", "seerhut", "time", textOption).get());
+	
+	addTextReplacements(iwText, components);
+}
 
 
+void CQuest::getRolloverText(MetaString &ms, bool onHover) const
+{
 	if(onHover)
 	if(onHover)
 		ms.appendRawString("\n\n");
 		ms.appendRawString("\n\n");
 
 
-	std::string questName = missionName(missionType);
 	std::string questState = missionState(onHover ? 3 : 4);
 	std::string questState = missionState(onHover ? 3 : 4);
 
 
-	ms.appendRawString(VLC->generaltexth->translate("core.seerhut.quest", questName, questState,textOption));
+	ms.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, questState, textOption).get());
 
 
-	switch(missionType)
-	{
-		case MISSION_LEVEL:
-			ms.replaceNumber(m13489val);
-			break;
-		case MISSION_PRIMARY_STAT:
-			{
-				MetaString loot;
-				for (int i = 0; i < 4; ++i)
-				{
-					if (m2stats[i])
-					{
-						loot.appendRawString("%d %s");
-						loot.replaceNumber(m2stats[i]);
-						loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
-					}
-				}
-				ms.replaceRawString(loot.buildList());
-			}
-			break;
-		case MISSION_KILL_HERO:
-			ms.replaceRawString(heroName);
-			break;
-		case MISSION_KILL_CREATURE:
-			ms.replaceCreatureName(stackToKill);
-			break;
-		case MISSION_ART:
-			{
-				MetaString loot;
-				for(const auto & elem : m5arts)
-				{
-					loot.appendRawString("%s");
-					loot.replaceLocalString(EMetaText::ART_NAMES, elem);
-				}
-				ms.replaceRawString(loot.buildList());
-			}
-			break;
-		case MISSION_ARMY:
-			{
-				MetaString loot;
-				for(const auto & elem : m6creatures)
-				{
-					loot.appendRawString("%s");
-					loot.replaceCreatureName(elem);
-				}
-				ms.replaceRawString(loot.buildList());
-			}
-			break;
-		case MISSION_RESOURCES:
-			{
-				MetaString loot;
-				for (int i = 0; i < 7; ++i)
-				{
-					if (m7resources[i])
-					{
-						loot.appendRawString("%d %s");
-						loot.replaceNumber(m7resources[i]);
-						loot.replaceLocalString(EMetaText::RES_NAMES, i);
-					}
-				}
-				ms.replaceRawString(loot.buildList());
-			}
-			break;
-		case MISSION_HERO:
-			ms.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
-			break;
-		case MISSION_PLAYER:
-			ms.replaceRawString(VLC->generaltexth->colors[m13489val]);
-			break;
-		default:
-			break;
-	}
+	std::vector<Component> components;
+	addTextReplacements(ms, components);
 }
 }
 
 
 void CQuest::getCompletionText(MetaString &iwText) const
 void CQuest::getCompletionText(MetaString &iwText) const
 {
 {
 	iwText.appendRawString(completedText.toString());
 	iwText.appendRawString(completedText.toString());
-	switch(missionType)
-	{
-		case CQuest::MISSION_LEVEL:
-			if (!isCustomComplete)
-				iwText.replaceNumber(m13489val);
-			break;
-		case CQuest::MISSION_PRIMARY_STAT:
-		{
-			MetaString loot;
-			assert(m2stats.size() <= 4);
-			for (int i = 0; i < m2stats.size(); ++i)
-			{
-				if (m2stats[i])
-				{
-					loot.appendRawString("%d %s");
-					loot.replaceNumber(m2stats[i]);
-					loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
-				}
-			}
-			if (!isCustomComplete)
-				iwText.replaceRawString(loot.buildList());
-			break;
-		}
-		case CQuest::MISSION_ART:
-		{
-			MetaString loot;
-			for(const auto & elem : m5arts)
-			{
-				loot.appendRawString("%s");
-				loot.replaceLocalString(EMetaText::ART_NAMES, elem);
-			}
-			if (!isCustomComplete)
-				iwText.replaceRawString(loot.buildList());
-		}
-			break;
-		case CQuest::MISSION_ARMY:
-		{
-			MetaString loot;
-			for(const auto & elem : m6creatures)
-			{
-				loot.appendRawString("%s");
-				loot.replaceCreatureName(elem);
-			}
-			if (!isCustomComplete)
-				iwText.replaceRawString(loot.buildList());
-		}
-			break;
-		case CQuest::MISSION_RESOURCES:
-		{
-			MetaString loot;
-			for (int i = 0; i < 7; ++i)
-			{
-				if (m7resources[i])
-				{
-					loot.appendRawString("%d %s");
-					loot.replaceNumber(m7resources[i]);
-					loot.replaceLocalString(EMetaText::RES_NAMES, i);
-				}
-			}
-			if (!isCustomComplete)
-				iwText.replaceRawString(loot.buildList());
-		}
-			break;
-		case MISSION_KILL_HERO:
-		case MISSION_KILL_CREATURE:
-			if (!isCustomComplete)
-				addReplacements(iwText, completedText.toString());
-			break;
-		case MISSION_HERO:
-			if (!isCustomComplete)
-				iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
-			break;
-		case MISSION_PLAYER:
-			if (!isCustomComplete)
-				iwText.replaceRawString(VLC->generaltexth->colors[m13489val]);
-			break;
-	}
+	
+	std::vector<Component> components;
+	addTextReplacements(iwText, components);
+}
+
+void CQuest::defineQuestName()
+{
+	//standard quests
+	questName = CQuest::missionName(0);
+	if(mission != Rewardable::Limiter{}) questName = CQuest::missionName(12);
+	if(mission.heroLevel > 0) questName = CQuest::missionName(1);
+	for(auto & s : mission.primary) if(s) questName = CQuest::missionName(2);
+	if(!mission.spells.empty()) questName = CQuest::missionName(2);
+	if(!mission.secondary.empty()) questName = CQuest::missionName(2);
+	if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(3);
+	if(killTarget != ObjectInstanceID::NONE && stackToKill.getType()) questName = CQuest::missionName(4);
+	if(!mission.artifacts.empty()) questName = CQuest::missionName(5);
+	if(!mission.creatures.empty()) questName = CQuest::missionName(6);
+	if(mission.resources.nonZero()) questName = CQuest::missionName(7);
+	if(!mission.heroes.empty()) questName = CQuest::missionName(8);
+	if(!mission.players.empty()) questName = CQuest::missionName(9);
+	if(mission.daysPassed > 0 || !mission.heroClasses.empty()) questName = CQuest::missionName(11);
 }
 }
 
 
-void CQuest::addArtifactID(const ArtifactID & id)
+void CQuest::addKillTargetReplacements(MetaString &out) const
 {
 {
-	m5arts.push_back(id);
-	++artifactsRequirements[id];
+	if(!heroName.empty())
+		out.replaceTextID(heroName);
+	if(stackToKill.type)
+	{
+		out.replaceCreatureName(stackToKill);
+		out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]);
+	}
 }
 }
 
 
 void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
 void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
@@ -472,6 +340,7 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
 	handler.serializeStruct("firstVisitText", firstVisitText);
 	handler.serializeStruct("firstVisitText", firstVisitText);
 	handler.serializeStruct("nextVisitText", nextVisitText);
 	handler.serializeStruct("nextVisitText", nextVisitText);
 	handler.serializeStruct("completedText", completedText);
 	handler.serializeStruct("completedText", completedText);
+	handler.serializeBool("repeatedQuest", repeatedQuest, false);
 
 
 	if(!handler.saving)
 	if(!handler.saving)
 	{
 	{
@@ -479,87 +348,93 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
 		isCustomNext = !nextVisitText.empty();
 		isCustomNext = !nextVisitText.empty();
 		isCustomComplete = !completedText.empty();
 		isCustomComplete = !completedText.empty();
 	}
 	}
-
-	static const std::vector<std::string> MISSION_TYPE_JSON =
-	{
-		"None", "Level", "PrimaryStat", "KillHero", "KillCreature", "Artifact", "Army", "Resources", "Hero", "Player"
-	};
-
-	handler.serializeEnum("missionType", missionType, Emission::MISSION_NONE, MISSION_TYPE_JSON);
+	
 	handler.serializeInt("timeLimit", lastDay, -1);
 	handler.serializeInt("timeLimit", lastDay, -1);
+	handler.serializeStruct("limiter", mission);
+	handler.serializeInstance("killTarget", killTarget, ObjectInstanceID::NONE);
 
 
-	switch (missionType)
+	if(!handler.saving) //compatibility with legacy vmaps
 	{
 	{
-	case MISSION_NONE:
-		break;
-	case MISSION_LEVEL:
-		handler.serializeInt("heroLevel", m13489val, -1);
-		break;
-	case MISSION_PRIMARY_STAT:
+		std::string missionType = "None";
+		handler.serializeString("missionType", missionType);
+		if(missionType == "None")
+			return;
+		
+		if(missionType == "Level")
+			handler.serializeInt("heroLevel", mission.heroLevel);
+		
+		if(missionType == "PrimaryStat")
 		{
 		{
 			auto primarySkills = handler.enterStruct("primarySkills");
 			auto primarySkills = handler.enterStruct("primarySkills");
-			if(!handler.saving)
-				m2stats.resize(GameConstants::PRIMARY_SKILLS);
-
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
-				handler.serializeInt(NPrimarySkill::names[i], m2stats[i], 0);
+				handler.serializeInt(NPrimarySkill::names[i], mission.primary[i], 0);
 		}
 		}
-		break;
-	case MISSION_KILL_HERO:
-	case MISSION_KILL_CREATURE:
-		handler.serializeInstance<ui32>("killTarget", m13489val, static_cast<ui32>(-1));
-		break;
-	case MISSION_ART:
-		//todo: ban artifacts
-		handler.serializeIdArray<ArtifactID>("artifacts", m5arts);
-		break;
-	case MISSION_ARMY:
+		
+		if(missionType == "Artifact")
+			handler.serializeIdArray<ArtifactID>("artifacts", mission.artifacts);
+		
+		if(missionType == "Army")
 		{
 		{
 			auto a = handler.enterArray("creatures");
 			auto a = handler.enterArray("creatures");
-			a.serializeStruct(m6creatures);
+			a.serializeStruct(mission.creatures);
 		}
 		}
-		break;
-	case MISSION_RESOURCES:
+		
+		if(missionType == "Resources")
 		{
 		{
 			auto r = handler.enterStruct("resources");
 			auto r = handler.enterStruct("resources");
 
 
 			for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
 			for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
 			{
 			{
-				handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], m7resources[idx], 0);
+				handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mission.resources[idx], 0);
 			}
 			}
 		}
 		}
-		break;
-	case MISSION_HERO:
-		handler.serializeId<ui32, ui32, HeroTypeID>("hero", m13489val, 0);
-		break;
-	case MISSION_PLAYER:
-		handler.serializeId<ui32, ui32, PlayerColor>("player", m13489val, PlayerColor::NEUTRAL);
-		break;
-	default:
-		logGlobal->error("Invalid quest mission type");
-		break;
+		
+		if(missionType == "Hero")
+		{
+			ui32 temp;
+			handler.serializeId<ui32, ui32, HeroTypeID>("hero", temp, 0);
+			mission.heroes.emplace_back(temp);
+		}
+		
+		if(missionType == "Player")
+		{
+			ui32 temp;
+			handler.serializeId<ui32, ui32, PlayerColor>("player", temp, PlayerColor::NEUTRAL);
+			mission.players.emplace_back(temp);
+		}
 	}
 	}
 
 
 }
 }
 
 
+bool IQuestObject::checkQuest(const CGHeroInstance* h) const
+{
+	return quest->checkQuest(h);
+}
+
+void IQuestObject::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
+{
+	quest->getVisitText(text, components, FirstVisit, h);
+}
+
+void IQuestObject::afterAddToMapCommon(CMap * map) const
+{
+	map->addNewQuestInstance(quest);
+}
+
 void CGSeerHut::setObjToKill()
 void CGSeerHut::setObjToKill()
 {
 {
-	if(quest->missionType == CQuest::MISSION_KILL_CREATURE)
+	if(getCreatureToKill(true))
 	{
 	{
 		quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :?
 		quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :?
 		assert(quest->stackToKill.type);
 		assert(quest->stackToKill.type);
 		quest->stackToKill.count = 0; //no count in info window
 		quest->stackToKill.count = 0; //no count in info window
 		quest->stackDirection = checkDirection();
 		quest->stackDirection = checkDirection();
 	}
 	}
-	else if(quest->missionType == CQuest::MISSION_KILL_HERO)
+	else if(getHeroToKill(true))
 	{
 	{
 		quest->heroName = getHeroToKill(false)->getNameTranslated();
 		quest->heroName = getHeroToKill(false)->getNameTranslated();
 		quest->heroPortrait = getHeroToKill(false)->getPortraitSource();
 		quest->heroPortrait = getHeroToKill(false)->getPortraitSource();
 	}
 	}
-
-	quest->getCompletionText(configuration.onSelect);
-	for(auto & i : configuration.info)
-		quest->getCompletionText(i.message);
 }
 }
 
 
 void CGSeerHut::init(CRandomGenerator & rand)
 void CGSeerHut::init(CRandomGenerator & rand)
@@ -581,29 +456,35 @@ void CGSeerHut::initObj(CRandomGenerator & rand)
 	init(rand);
 	init(rand);
 	
 	
 	CRewardableObject::initObj(rand);
 	CRewardableObject::initObj(rand);
-
-	quest->progress = CQuest::NOT_ACTIVE;
-	if(quest->missionType)
+	
+	setObjToKill();
+	quest->defineQuestName();
+	
+	if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE)
+		quest->isCompleted = true;
+	
+	if(quest->questName == quest->missionName(0))
 	{
 	{
-		std::string questName  = quest->missionName(quest->missionType);
-
-		if(!quest->isCustomFirst)
-			quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(0), quest->textOption).get());
-		if(!quest->isCustomNext)
-			quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(1), quest->textOption).get());
-		if(!quest->isCustomComplete)
-			quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(2), quest->textOption).get());
+		quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
 	}
 	}
 	else
 	else
 	{
 	{
-		quest->progress = CQuest::COMPLETE;
-		quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
+		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());
 	}
 	}
+	
+	quest->getCompletionText(configuration.onSelect);
+	for(auto & i : configuration.info)
+		quest->getCompletionText(i.message);
 }
 }
 
 
 void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 {
 {
-	quest->getRolloverText (text, onHover);//TODO: simplify?
+	quest->getRolloverText(text, onHover);//TODO: simplify?
 	if(!onHover)
 	if(!onHover)
 		text.replaceRawString(seerName);
 		text.replaceRawString(seerName);
 }
 }
@@ -611,13 +492,15 @@ void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 std::string CGSeerHut::getHoverText(PlayerColor player) const
 std::string CGSeerHut::getHoverText(PlayerColor player) const
 {
 {
 	std::string hoverName = getObjectName();
 	std::string hoverName = getObjectName();
-	if(ID == Obj::SEER_HUT && quest->progress != CQuest::NOT_ACTIVE)
+	if(ID == Obj::SEER_HUT && quest->activeForPlayers.count(player))
 	{
 	{
 		hoverName = VLC->generaltexth->allTexts[347];
 		hoverName = VLC->generaltexth->allTexts[347];
 		boost::algorithm::replace_first(hoverName, "%s", seerName);
 		boost::algorithm::replace_first(hoverName, "%s", seerName);
 	}
 	}
 
 
-	if(quest->progress & quest->missionType) //rollover when the quest is active
+	if(quest->activeForPlayers.count(player)
+	   && (quest->mission != Rewardable::Limiter{}
+		   || quest->killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
 	{
 	{
 		MetaString ms;
 		MetaString ms;
 		getRolloverText (ms, true);
 		getRolloverText (ms, true);
@@ -626,48 +509,21 @@ std::string CGSeerHut::getHoverText(PlayerColor player) const
 	return hoverName;
 	return hoverName;
 }
 }
 
 
-void CQuest::addReplacements(MetaString &out, const std::string &base) const
+void CGSeerHut::setPropertyDer(ui8 what, ui32 val)
 {
 {
-	switch(missionType)
+	switch(what)
 	{
 	{
-	case MISSION_KILL_CREATURE:
-		if(stackToKill.type)
+		case CGSeerHut::SEERHUT_VISITED:
 		{
 		{
-			out.replaceCreatureName(stackToKill);
-			if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
-			{
-				out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]);
-			}
+			quest->activeForPlayers.emplace(val);
+			break;
 		}
 		}
-		break;
-	case MISSION_KILL_HERO:
-		out.replaceTextID(heroName);
-		break;
-	}
-}
-
-bool IQuestObject::checkQuest(const CGHeroInstance* h) const
-{
-	return quest->checkQuest(h);
-}
-
-void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
-{
-	quest->getVisitText (text,components, isCustom, FirstVisit, h);
-}
-
-void IQuestObject::afterAddToMapCommon(CMap * map) const
-{
-	map->addNewQuestInstance(quest);
-}
-
-void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
-{
-	switch(what)
-	{
-		case 10:
-			quest->progress = static_cast<CQuest::Eprogress>(val);
+		case CGSeerHut::SEERHUT_COMPLETE:
+		{
+			quest->isCompleted = val;
+			quest->activeForPlayers.clear();
 			break;
 			break;
+		}
 	}
 	}
 }
 }
 
 
@@ -676,7 +532,7 @@ void CGSeerHut::newTurn(CRandomGenerator & rand) const
 	CRewardableObject::newTurn(rand);
 	CRewardableObject::newTurn(rand);
 	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
 	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
 	{
 	{
-		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE);
+		cb->setObjProperty (id, CGSeerHut::SEERHUT_COMPLETE, true);
 	}
 	}
 }
 }
 
 
@@ -684,30 +540,24 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 {
 {
 	InfoWindow iw;
 	InfoWindow iw;
 	iw.player = h->getOwner();
 	iw.player = h->getOwner();
-	if(quest->progress < CQuest::COMPLETE)
+	if(!quest->isCompleted)
 	{
 	{
-		bool firstVisit = !quest->progress;
+		bool firstVisit = !quest->activeForPlayers.count(h->getOwner());
 		bool failRequirements = !checkQuest(h);
 		bool failRequirements = !checkQuest(h);
-		bool isCustom = false;
 
 
 		if(firstVisit)
 		if(firstVisit)
 		{
 		{
-			isCustom = quest->isCustomFirst;
-			cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS);
+			cb->setObjProperty(id, CGSeerHut::SEERHUT_VISITED, h->getOwner());
 
 
 			AddQuest aq;
 			AddQuest aq;
 			aq.quest = QuestInfo (quest, this, visitablePos());
 			aq.quest = QuestInfo (quest, this, visitablePos());
 			aq.player = h->tempOwner;
 			aq.player = h->tempOwner;
 			cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
 			cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
 		}
 		}
-		else if(failRequirements)
-		{
-			isCustom = quest->isCustomNext;
-		}
 
 
 		if(firstVisit || failRequirements)
 		if(firstVisit || failRequirements)
 		{
 		{
-			getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
+			getVisitText (iw.text, iw.components, firstVisit, h);
 
 
 			cb->showInfoDialog(&iw);
 			cb->showInfoDialog(&iw);
 		}
 		}
@@ -758,26 +608,19 @@ int CGSeerHut::checkDirection() const
 	}
 	}
 }
 }
 
 
-void CGSeerHut::completeQuest() const //reward
-{
-	cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
-}
-
 const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
 const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
 {
 {
-	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
+	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget);
 	if(allowNull && !o)
 	if(allowNull && !o)
 		return nullptr;
 		return nullptr;
-	assert(o && (o->ID == Obj::HERO  ||  o->ID == Obj::PRISON));
 	return dynamic_cast<const CGHeroInstance *>(o);
 	return dynamic_cast<const CGHeroInstance *>(o);
 }
 }
 
 
 const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
 const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
 {
 {
-	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
+	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget);
 	if(allowNull && !o)
 	if(allowNull && !o)
 		return nullptr;
 		return nullptr;
-	assert(o && o->ID == Obj::MONSTER);
 	return dynamic_cast<const CGCreature *>(o);
 	return dynamic_cast<const CGCreature *>(o);
 }
 }
 
 
@@ -785,7 +628,10 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 {
 {
 	CRewardableObject::blockingDialogAnswered(hero, answer);
 	CRewardableObject::blockingDialogAnswered(hero, answer);
 	if(answer)
 	if(answer)
-		completeQuest();
+	{
+		quest->completeQuest(cb, hero);
+		cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, !quest->repeatedQuest); //mission complete
+	}
 }
 }
 
 
 void CGSeerHut::afterAddToMap(CMap* map)
 void CGSeerHut::afterAddToMap(CMap* map)
@@ -874,10 +720,23 @@ void CGQuestGuard::init(CRandomGenerator & rand)
 	
 	
 	configuration.info.push_back({});
 	configuration.info.push_back({});
 	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
-	configuration.info.back().reward.removeObject = true;
+	configuration.info.back().reward.removeObject = subID == 0 ? true : false;
 	configuration.canRefuse = true;
 	configuration.canRefuse = true;
 }
 }
 
 
+void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
+{
+	if(!quest->isCompleted)
+		CGSeerHut::onHeroVisit(h);
+	else
+		cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, false);
+}
+
+bool CGQuestGuard::passableFor(PlayerColor color) const
+{
+	return quest->isCompleted;
+}
+
 void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
 void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 {
 	//quest only, do not call base class
 	//quest only, do not call base class
@@ -938,12 +797,12 @@ void CGBorderGuard::initObj(CRandomGenerator & rand)
 	blockVisit = true;
 	blockVisit = true;
 }
 }
 
 
-void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
+void CGBorderGuard::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
 {
 {
-	text.appendLocalString(EMetaText::ADVOB_TXT,18);
+	text.appendLocalString(EMetaText::ADVOB_TXT, 18);
 }
 }
 
 
-void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
+void CGBorderGuard::getRolloverText(MetaString &text, bool onHover) const
 {
 {
 	if (!onHover)
 	if (!onHover)
 	{
 	{

+ 28 - 53
lib/mapObjects/CQuest.h

@@ -19,47 +19,21 @@ class CGCreature;
 
 
 class DLL_LINKAGE CQuest final
 class DLL_LINKAGE CQuest final
 {
 {
-	mutable std::unordered_map<ArtifactID, unsigned, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
-
 public:
 public:
-	enum Emission {
-		MISSION_NONE = 0,
-		MISSION_LEVEL = 1,
-		MISSION_PRIMARY_STAT = 2,
-		MISSION_KILL_HERO = 3,
-		MISSION_KILL_CREATURE = 4,
-		MISSION_ART = 5,
-		MISSION_ARMY = 6,
-		MISSION_RESOURCES = 7,
-		MISSION_HERO = 8,
-		MISSION_PLAYER = 9,
-		MISSION_HOTA_MULTI = 10,
-		// end of H3 missions
-		MISSION_KEYMASTER = 100,
-		MISSION_HOTA_HERO_CLASS = 101,
-		MISSION_HOTA_REACH_DATE = 102
-	};
-
-	enum Eprogress {
-		NOT_ACTIVE,
-		IN_PROGRESS,
-		COMPLETE
-	};
-
-	static const std::string  & missionName(Emission mission);
-	static const std::string  & missionState(int index);
+
+	static const std::string & missionName(int index);
+	static const std::string & missionState(int index);
+	
+	std::string questName;
 
 
 	si32 qid; //unique quest id for serialization / identification
 	si32 qid; //unique quest id for serialization / identification
 
 
-	Emission missionType;
-	Eprogress progress;
 	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
 	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
-
-	ui32 m13489val;
-	std::vector<ui32> m2stats;
-	std::vector<ArtifactID> m5arts; // artifact IDs. Add IDs through addArtifactID(), not directly to the field.
-	std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
-	TResources m7resources;
+	ObjectInstanceID killTarget;
+	Rewardable::Limiter mission;
+	bool repeatedQuest;
+	bool isCompleted;
+	std::set<PlayerColor> activeForPlayers;
 
 
 	// following fields are used only for kill creature/hero missions, the original
 	// following fields are used only for kill creature/hero missions, the original
 	// objects became inaccessible after their removal, so we need to store info
 	// objects became inaccessible after their removal, so we need to store info
@@ -79,13 +53,14 @@ public:
 	CQuest(); //TODO: Remove constructor
 	CQuest(); //TODO: Remove constructor
 
 
 	static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army);
 	static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army);
-	virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not
-	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	virtual bool checkQuest(const CGHeroInstance * h) const; //determines whether the quest is complete or not
+	virtual void getVisitText(MetaString &text, std::vector<Component> & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
 	virtual void getCompletionText(MetaString &text) const;
 	virtual void getCompletionText(MetaString &text) const;
 	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
 	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
-	virtual void completeQuest (const CGHeroInstance * h) const {};
-	virtual void addReplacements(MetaString &out, const std::string &base) const;
-	void addArtifactID(const ArtifactID & id);
+	virtual void completeQuest(IGameCallback *, const CGHeroInstance * h) const;
+	virtual void addTextReplacements(MetaString &out, std::vector<Component> & components) const;
+	virtual void addKillTargetReplacements(MetaString &out) const;
+	void defineQuestName();
 
 
 	bool operator== (const CQuest & quest) const
 	bool operator== (const CQuest & quest) const
 	{
 	{
@@ -95,14 +70,9 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
 		h & qid;
 		h & qid;
-		h & missionType;
-		h & progress;
+		h & isCompleted;
+		h & activeForPlayers;
 		h & lastDay;
 		h & lastDay;
-		h & m13489val;
-		h & m2stats;
-		h & m5arts;
-		h & m6creatures;
-		h & m7resources;
 		h & textOption;
 		h & textOption;
 		h & stackToKill;
 		h & stackToKill;
 		h & stackDirection;
 		h & stackDirection;
@@ -115,6 +85,9 @@ public:
 		h & isCustomNext;
 		h & isCustomNext;
 		h & isCustomComplete;
 		h & isCustomComplete;
 		h & completedOption;
 		h & completedOption;
+		h & questName;
+		h & mission;
+		h & killTarget;
 	}
 	}
 
 
 	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
 	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
@@ -128,7 +101,7 @@ public:
 	///Information about quest should remain accessible even if IQuestObject removed from map
 	///Information about quest should remain accessible even if IQuestObject removed from map
 	///All CQuest objects are freed in CMap destructor
 	///All CQuest objects are freed in CMap destructor
 	virtual ~IQuestObject() = default;
 	virtual ~IQuestObject() = default;
-	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
 	virtual bool checkQuest (const CGHeroInstance * h) const;
 	virtual bool checkQuest (const CGHeroInstance * h) const;
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -156,8 +129,6 @@ public:
 	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
 	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
 	const CGCreature *getCreatureToKill(bool allowNull = false) const;
 	const CGCreature *getCreatureToKill(bool allowNull = false) const;
 	void getRolloverText (MetaString &text, bool onHover) const;
 	void getRolloverText (MetaString &text, bool onHover) const;
-	void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
-	virtual void completeQuest() const;
 
 
 	void afterAddToMap(CMap * map) override;
 	void afterAddToMap(CMap * map) override;
 
 
@@ -168,7 +139,8 @@ public:
 		h & seerName;
 		h & seerName;
 	}
 	}
 protected:
 protected:
-	static constexpr int OBJPROP_VISITED = 10;
+	static constexpr int SEERHUT_VISITED = 10;
+	static constexpr int SEERHUT_COMPLETE = 11;
 
 
 	void setPropertyDer(ui8 what, ui32 val) override;
 	void setPropertyDer(ui8 what, ui32 val) override;
 
 
@@ -179,6 +151,9 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
 {
 {
 public:
 public:
 	void init(CRandomGenerator & rand) override;
 	void init(CRandomGenerator & rand) override;
+	
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	bool passableFor(PlayerColor color) const override;
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
@@ -228,7 +203,7 @@ public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
 
-	void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const override;
+	void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override;
 	void getRolloverText (MetaString &text, bool onHover) const;
 	void getRolloverText (MetaString &text, bool onHover) const;
 	bool checkQuest (const CGHeroInstance * h) const override;
 	bool checkQuest (const CGHeroInstance * h) const override;
 
 

+ 3 - 0
lib/mapping/CMapHeader.cpp

@@ -144,6 +144,9 @@ ui8 CMapHeader::levels() const
 
 
 void CMapHeader::registerMapStrings()
 void CMapHeader::registerMapStrings()
 {
 {
+	VLC->generaltexth->removeSubContainer(*this);
+	VLC->generaltexth->addSubContainer(*this);
+	
 	//get supported languages. Assuming that translation containing most strings is the base language
 	//get supported languages. Assuming that translation containing most strings is the base language
 	std::set<std::string> mapLanguages, mapBaseLanguages;
 	std::set<std::string> mapLanguages, mapBaseLanguages;
 	int maxStrings = 0;
 	int maxStrings = 0;

+ 3 - 0
lib/mapping/CMapInfo.cpp

@@ -105,7 +105,10 @@ std::string CMapInfo::getNameTranslated() const
 	if(campaign && !campaign->getNameTranslated().empty())
 	if(campaign && !campaign->getNameTranslated().empty())
 		return campaign->getNameTranslated();
 		return campaign->getNameTranslated();
 	else if(mapHeader && !mapHeader->name.empty())
 	else if(mapHeader && !mapHeader->name.empty())
+	{
+		mapHeader->registerMapStrings();
 		return mapHeader->name.toString();
 		return mapHeader->name.toString();
+	}
 	else
 	else
 		return VLC->generaltexth->allTexts[508];
 		return VLC->generaltexth->allTexts[508];
 }
 }

+ 59 - 42
lib/mapping/MapFormatH3M.cpp

@@ -1830,6 +1830,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position, const Objec
 	if(features.levelHOTA3)
 	if(features.levelHOTA3)
 	{
 	{
 		uint32_t repeateableQuestsCount = reader->readUInt32();
 		uint32_t repeateableQuestsCount = reader->readUInt32();
+		hut->quest->repeatedQuest = repeateableQuestsCount != 0;
 
 
 		if(repeateableQuestsCount != 0)
 		if(repeateableQuestsCount != 0)
 			logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
 			logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
@@ -1858,11 +1859,30 @@ enum class ESeerHutRewardType : uint8_t
 	CREATURE = 10,
 	CREATURE = 10,
 };
 };
 
 
+enum class EQuestMission {
+	NONE = 0,
+	LEVEL = 1,
+	PRIMARY_SKILL = 2,
+	KILL_HERO = 3,
+	KILL_CREATURE = 4,
+	ARTIFACT = 5,
+	ARMY = 6,
+	RESOURCES = 7,
+	HERO = 8,
+	PLAYER = 9,
+	HOTA_MULTI = 10,
+	// end of H3 missions
+	KEYMASTER = 100,
+	HOTA_HERO_CLASS = 101,
+	HOTA_REACH_DATE = 102
+};
+
 void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven)
 void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven)
 {
 {
+	EQuestMission missionType = EQuestMission::NONE;
 	if(features.levelAB)
 	if(features.levelAB)
 	{
 	{
-		readQuest(hut, position);
+		missionType = static_cast<EQuestMission>(readQuest(hut, position));
 	}
 	}
 	else
 	else
 	{
 	{
@@ -1871,12 +1891,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 		if(artID != ArtifactID::NONE)
 		if(artID != ArtifactID::NONE)
 		{
 		{
 			//not none quest
 			//not none quest
-			hut->quest->addArtifactID(artID);
-			hut->quest->missionType = CQuest::MISSION_ART;
-		}
-		else
-		{
-			hut->quest->missionType = CQuest::MISSION_NONE;
+			hut->quest->mission.artifacts.push_back(artID);
+			missionType = EQuestMission::ARTIFACT;
 		}
 		}
 		hut->quest->lastDay = -1; //no timeout
 		hut->quest->lastDay = -1; //no timeout
 		hut->quest->isCustomFirst = false;
 		hut->quest->isCustomFirst = false;
@@ -1884,7 +1900,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 		hut->quest->isCustomComplete = false;
 		hut->quest->isCustomComplete = false;
 	}
 	}
 
 
-	if(hut->quest->missionType)
+	if(missionType != EQuestMission::NONE)
 	{
 	{
 		auto rewardType = static_cast<ESeerHutRewardType>(reader->readUInt8());
 		auto rewardType = static_cast<ESeerHutRewardType>(reader->readUInt8());
 		Rewardable::VisitInfo vinfo;
 		Rewardable::VisitInfo vinfo;
@@ -1976,91 +1992,91 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 	}
 	}
 }
 }
 
 
-void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
+int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 {
 {
-	guard->quest->missionType = static_cast<CQuest::Emission>(reader->readUInt8());
+	auto missionId = reader->readUInt8();
 
 
-	switch(guard->quest->missionType)
+	switch(static_cast<EQuestMission>(missionId))
 	{
 	{
-		case CQuest::MISSION_NONE:
-			return;
-		case CQuest::MISSION_PRIMARY_STAT:
+		case EQuestMission::NONE:
+			return missionId;
+		case EQuestMission::PRIMARY_SKILL:
 		{
 		{
-			guard->quest->m2stats.resize(4);
 			for(int x = 0; x < 4; ++x)
 			for(int x = 0; x < 4; ++x)
 			{
 			{
-				guard->quest->m2stats[x] = reader->readUInt8();
+				guard->quest->mission.primary[x] = reader->readUInt8();
 			}
 			}
+			break;
 		}
 		}
-		break;
-		case CQuest::MISSION_LEVEL:
-		case CQuest::MISSION_KILL_HERO:
-		case CQuest::MISSION_KILL_CREATURE:
+		case EQuestMission::LEVEL:
 		{
 		{
-			guard->quest->m13489val = reader->readUInt32();
+			guard->quest->mission.heroLevel = reader->readUInt32();
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_ART:
+		case EQuestMission::KILL_HERO:
+		case EQuestMission::KILL_CREATURE:
+		{
+			guard->quest->killTarget = ObjectInstanceID(reader->readUInt32());
+			break;
+		}
+		case EQuestMission::ARTIFACT:
 		{
 		{
 			int artNumber = reader->readUInt8();
 			int artNumber = reader->readUInt8();
 			for(int yy = 0; yy < artNumber; ++yy)
 			for(int yy = 0; yy < artNumber; ++yy)
 			{
 			{
 				auto artid = reader->readArtifact();
 				auto artid = reader->readArtifact();
-				guard->quest->addArtifactID(artid);
+				guard->quest->mission.artifacts.push_back(artid);
 				map->allowedArtifact[artid] = false; //these are unavailable for random generation
 				map->allowedArtifact[artid] = false; //these are unavailable for random generation
 			}
 			}
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_ARMY:
+		case EQuestMission::ARMY:
 		{
 		{
 			int typeNumber = reader->readUInt8();
 			int typeNumber = reader->readUInt8();
-			guard->quest->m6creatures.resize(typeNumber);
+			guard->quest->mission.creatures.resize(typeNumber);
 			for(int hh = 0; hh < typeNumber; ++hh)
 			for(int hh = 0; hh < typeNumber; ++hh)
 			{
 			{
-				guard->quest->m6creatures[hh].type = VLC->creh->objects[reader->readCreature()];
-				guard->quest->m6creatures[hh].count = reader->readUInt16();
+				guard->quest->mission.creatures[hh].type = VLC->creh->objects[reader->readCreature()];
+				guard->quest->mission.creatures[hh].count = reader->readUInt16();
 			}
 			}
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_RESOURCES:
+		case EQuestMission::RESOURCES:
 		{
 		{
 			for(int x = 0; x < 7; ++x)
 			for(int x = 0; x < 7; ++x)
-				guard->quest->m7resources[x] = reader->readUInt32();
+				guard->quest->mission.resources[x] = reader->readUInt32();
 
 
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_HERO:
+		case EQuestMission::HERO:
 		{
 		{
-			guard->quest->m13489val = reader->readHero().getNum();
+			guard->quest->mission.heroes.push_back(reader->readHero());
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_PLAYER:
+		case EQuestMission::PLAYER:
 		{
 		{
-			guard->quest->m13489val = reader->readPlayer().getNum();
+			guard->quest->mission.players.push_back(reader->readPlayer());
 			break;
 			break;
 		}
 		}
-		case CQuest::MISSION_HOTA_MULTI:
+		case EQuestMission::HOTA_MULTI:
 		{
 		{
 			uint32_t missionSubID = reader->readUInt32();
 			uint32_t missionSubID = reader->readUInt32();
 
 
 			if(missionSubID == 0)
 			if(missionSubID == 0)
 			{
 			{
-				guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_HERO_CLASS;
+				missionId = int(EQuestMission::HOTA_HERO_CLASS);
 				std::set<HeroClassID> heroClasses;
 				std::set<HeroClassID> heroClasses;
 				reader->readBitmaskHeroClassesSized(heroClasses, false);
 				reader->readBitmaskHeroClassesSized(heroClasses, false);
-
-				logGlobal->warn("Map '%s': Quest at %s 'Belong to one of %d classes' is not implemented!", mapName, position.toString(), heroClasses.size());
+				for(auto & hc : heroClasses)
+					guard->quest->mission.heroClasses.push_back(hc);
 				break;
 				break;
 			}
 			}
 			if(missionSubID == 1)
 			if(missionSubID == 1)
 			{
 			{
-				guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_REACH_DATE;
-				uint32_t daysPassed = reader->readUInt32();
-
-				logGlobal->warn("Map '%s': Quest at %s 'Wait till %d days passed' is not implemented!", mapName, position.toString(), daysPassed);
+				missionId = int(EQuestMission::HOTA_REACH_DATE);
+				guard->quest->mission.daysPassed = reader->readUInt32() + 1;
 				break;
 				break;
 			}
 			}
-			assert(0);
 			break;
 			break;
 		}
 		}
 		default:
 		default:
@@ -2076,6 +2092,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 	guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty();
 	guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty();
 	guard->quest->isCustomNext = !guard->quest->nextVisitText.empty();
 	guard->quest->isCustomNext = !guard->quest->nextVisitText.empty();
 	guard->quest->isCustomComplete = !guard->quest->completedText.empty();
 	guard->quest->isCustomComplete = !guard->quest->completedText.empty();
+	return missionId;
 }
 }
 
 
 CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
 CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)

+ 1 - 1
lib/mapping/MapFormatH3M.h

@@ -204,7 +204,7 @@ private:
 	 *
 	 *
 	 * @param guard the quest guard where that quest should be applied to
 	 * @param guard the quest guard where that quest should be applied to
 	 */
 	 */
-	void readQuest(IQuestObject * guard, const int3 & position);
+	int readQuest(IQuestObject * guard, const int3 & position);
 
 
 	void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven);
 	void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven);
 
 

+ 4 - 0
lib/rewardable/Info.cpp

@@ -122,6 +122,10 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan
 	limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng);
 	limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng);
 	limiter.spells  = JsonRandom::loadSpells(source["spells"], rng, spells);
 	limiter.spells  = JsonRandom::loadSpells(source["spells"], rng, spells);
 	limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng);
 	limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng);
+	
+	limiter.players = JsonRandom::loadColors(source["colors"], rng);
+	limiter.heroes = JsonRandom::loadHeroes(source["heroes"], rng);
+	limiter.heroClasses = JsonRandom::loadHeroClasses(source["heroClasses"], rng);
 
 
 	limiter.allOf  = configureSublimiters(object, rng, source["allOf"] );
 	limiter.allOf  = configureSublimiters(object, rng, source["allOf"] );
 	limiter.anyOf  = configureSublimiters(object, rng, source["anyOf"] );
 	limiter.anyOf  = configureSublimiters(object, rng, source["anyOf"] );

+ 108 - 4
lib/rewardable/Limiter.cpp

@@ -16,7 +16,9 @@
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../constants/StringConstants.h"
 #include "../constants/StringConstants.h"
+#include "../CHeroHandler.h"
 #include "../CSkillHandler.h"
 #include "../CSkillHandler.h"
+#include "../ArtifactUtils.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -24,7 +26,7 @@ Rewardable::Limiter::Limiter()
 	: dayOfWeek(0)
 	: dayOfWeek(0)
 	, daysPassed(0)
 	, daysPassed(0)
 	, heroExperience(0)
 	, heroExperience(0)
-	, heroLevel(0)
+	, heroLevel(-1)
 	, manaPercentage(0)
 	, manaPercentage(0)
 	, manaPoints(0)
 	, manaPoints(0)
 	, primary(GameConstants::PRIMARY_SKILLS, 0)
 	, primary(GameConstants::PRIMARY_SKILLS, 0)
@@ -33,6 +35,33 @@ Rewardable::Limiter::Limiter()
 
 
 Rewardable::Limiter::~Limiter() = default;
 Rewardable::Limiter::~Limiter() = default;
 
 
+bool operator==(const Rewardable::Limiter & l, const Rewardable::Limiter & r)
+{
+	return l.dayOfWeek == r.dayOfWeek
+	&& l.daysPassed == r.daysPassed
+	&& l.heroLevel == r.heroLevel
+	&& l.heroExperience == r.heroExperience
+	&& l.manaPoints == r.manaPoints
+	&& l.manaPercentage == r.manaPercentage
+	&& l.secondary == r.secondary
+	&& l.creatures == r.creatures
+	&& l.spells == r.spells
+	&& l.artifacts == r.artifacts
+	&& l.players == r.players
+	&& l.heroes == r.heroes
+	&& l.heroClasses == r.heroClasses
+	&& l.resources == r.resources
+	&& l.primary == r.primary
+	&& l.noneOf == r.noneOf
+	&& l.allOf == r.allOf
+	&& l.anyOf == r.anyOf;
+}
+
+bool operator!=(const Rewardable::Limiter & l, const Rewardable::Limiter & r)
+{
+	return !(l == r);
+}
+
 bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 {
 {
 	if(dayOfWeek != 0)
 	if(dayOfWeek != 0)
@@ -93,12 +122,34 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 			return false;
 			return false;
 	}
 	}
 
 
-	for(const auto & art : artifacts)
 	{
 	{
-		if (!hero->hasArt(art))
+		std::unordered_map<ArtifactID, unsigned int, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
+		for(const auto & art : artifacts)
+			++artifactsRequirements[art];
+		
+		size_t reqSlots = 0;
+		for(const auto & elem : artifactsRequirements)
+		{
+			// check required amount of artifacts
+			if(hero->getArtPosCount(elem.first, false, true, true) < elem.second)
+				return false;
+			if(!hero->hasArt(elem.first))
+				reqSlots += hero->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
+		}
+		if(!ArtifactUtils::isBackpackFreeSlots(hero, reqSlots))
 			return false;
 			return false;
 	}
 	}
-
+	
+	if(!players.empty() && !vstd::contains(players, hero->getOwner()))
+		return false;
+	
+	if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId()))
+		return false;
+	
+	if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId()))
+		return false;
+		
+	
 	for(const auto & sublimiter : noneOf)
 	for(const auto & sublimiter : noneOf)
 	{
 	{
 		if (sublimiter->heroAllowed(hero))
 		if (sublimiter->heroAllowed(hero))
@@ -122,6 +173,56 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 	return false;
 	return false;
 }
 }
 
 
+void Rewardable::Limiter::loadComponents(std::vector<Component> & comps,
+								 const CGHeroInstance * h) const
+{
+	if (heroExperience)
+		comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h->calculateXp(heroExperience)), 0);
+
+	if (heroLevel > 0)
+		comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0);
+
+	if (manaPoints || manaPercentage > 0)
+	{
+		int absoluteMana = h->manaLimit() ? (manaPercentage * h->mana / h->manaLimit() / 100) : 0;
+		comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, absoluteMana + manaPoints, 0);
+	}
+
+	for (size_t i=0; i<primary.size(); i++)
+	{
+		if (primary[i] != 0)
+			comps.emplace_back(Component::EComponentType::PRIM_SKILL, static_cast<ui16>(i), primary[i], 0);
+	}
+
+	for(const auto & entry : secondary)
+		comps.emplace_back(Component::EComponentType::SEC_SKILL, entry.first, entry.second, 0);
+
+	for(const auto & entry : artifacts)
+		comps.emplace_back(Component::EComponentType::ARTIFACT, entry, 1, 0);
+
+	for(const auto & entry : spells)
+		comps.emplace_back(Component::EComponentType::SPELL, entry, 1, 0);
+
+	for(const auto & entry : creatures)
+		comps.emplace_back(Component::EComponentType::CREATURE, entry.type->getId(), entry.count, 0);
+	
+	for(const auto & entry : players)
+		comps.emplace_back(Component::EComponentType::FLAG, entry, 0, 0);
+	
+	//FIXME: portrait may not match hero, if custom portrait was set in map editor
+	for(const auto & entry : heroes)
+		comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroTypes()->getById(entry)->getIconIndex(), 0, 0);
+	
+	for(const auto & entry : heroClasses)
+		comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroClasses()->getById(entry)->getIconIndex(), 0, 0);
+
+	for (size_t i=0; i<resources.size(); i++)
+	{
+		if (resources[i] !=0)
+			comps.emplace_back(Component::EComponentType::RESOURCE, static_cast<ui16>(i), resources[i], 0);
+	}
+}
+
 void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
 void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
 {
 {
 	handler.serializeInt("dayOfWeek", dayOfWeek);
 	handler.serializeInt("dayOfWeek", dayOfWeek);
@@ -130,6 +231,9 @@ void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("manaPercentage", manaPercentage);
 	handler.serializeInt("manaPercentage", manaPercentage);
 	handler.serializeInt("heroExperience", heroExperience);
 	handler.serializeInt("heroExperience", heroExperience);
 	handler.serializeInt("heroLevel", heroLevel);
 	handler.serializeInt("heroLevel", heroLevel);
+	handler.serializeIdArray("heroes", heroes);
+	handler.serializeIdArray("heroClasses", heroClasses);
+	handler.serializeIdArray("colors", players);
 	handler.serializeInt("manaPoints", manaPoints);
 	handler.serializeInt("manaPoints", manaPoints);
 	handler.serializeIdArray("artifacts", artifacts);
 	handler.serializeIdArray("artifacts", artifacts);
 	handler.enterArray("creatures").serializeStruct(creatures);
 	handler.enterArray("creatures").serializeStruct(creatures);

+ 20 - 3
lib/rewardable/Limiter.h

@@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 class CGHeroInstance;
 class CGHeroInstance;
 class CStackBasicDescriptor;
 class CStackBasicDescriptor;
+struct Component;
 
 
 namespace Rewardable {
 namespace Rewardable {
 
 
@@ -25,8 +26,7 @@ using LimitersList = std::vector<std::shared_ptr<Rewardable::Limiter>>;
 
 
 /// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements
 /// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements
 /// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures)
 /// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures)
-/// NOTE: in future should (partially) replace seer hut/quest guard quests checks
-struct DLL_LINKAGE Limiter
+struct DLL_LINKAGE Limiter final
 {
 {
 	/// day of week, unused if 0, 1-7 will test for current day of week
 	/// day of week, unused if 0, 1-7 will test for current day of week
 	si32 dayOfWeek;
 	si32 dayOfWeek;
@@ -52,7 +52,7 @@ struct DLL_LINKAGE Limiter
 	std::map<SecondarySkill, si32> secondary;
 	std::map<SecondarySkill, si32> secondary;
 
 
 	/// artifacts that hero needs to have (equipped or in backpack) to trigger this
 	/// artifacts that hero needs to have (equipped or in backpack) to trigger this
-	/// Note: does not checks for multiple copies of the same arts
+	/// checks for artifacts copies if same artifact id is included multiple times
 	std::vector<ArtifactID> artifacts;
 	std::vector<ArtifactID> artifacts;
 
 
 	/// Spells that hero must have in the spellbook
 	/// Spells that hero must have in the spellbook
@@ -60,6 +60,13 @@ struct DLL_LINKAGE Limiter
 
 
 	/// creatures that hero needs to have
 	/// creatures that hero needs to have
 	std::vector<CStackBasicDescriptor> creatures;
 	std::vector<CStackBasicDescriptor> creatures;
+	
+	/// only heroes/hero classes from list could pass limiter
+	std::vector<HeroTypeID> heroes;
+	std::vector<HeroClassID> heroClasses;
+	
+	/// only player colors can pass limiter
+	std::vector<PlayerColor> players;
 
 
 	/// sub-limiters, all must pass for this limiter to pass
 	/// sub-limiters, all must pass for this limiter to pass
 	LimitersList allOf;
 	LimitersList allOf;
@@ -74,6 +81,10 @@ struct DLL_LINKAGE Limiter
 	~Limiter();
 	~Limiter();
 
 
 	bool heroAllowed(const CGHeroInstance * hero) const;
 	bool heroAllowed(const CGHeroInstance * hero) const;
+	
+	/// Generates list of components that describes reward for a specific hero
+	void loadComponents(std::vector<Component> & comps,
+								const CGHeroInstance * h) const;
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
@@ -88,6 +99,9 @@ struct DLL_LINKAGE Limiter
 		h & secondary;
 		h & secondary;
 		h & artifacts;
 		h & artifacts;
 		h & creatures;
 		h & creatures;
+		h & heroes;
+		h & heroClasses;
+		h & players;
 		h & allOf;
 		h & allOf;
 		h & anyOf;
 		h & anyOf;
 		h & noneOf;
 		h & noneOf;
@@ -98,4 +112,7 @@ struct DLL_LINKAGE Limiter
 
 
 }
 }
 
 
+bool DLL_LINKAGE operator== (const Rewardable::Limiter & l, const Rewardable::Limiter & r);
+bool DLL_LINKAGE operator!= (const Rewardable::Limiter & l, const Rewardable::Limiter & r);
+
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/rewardable/Reward.h

@@ -29,7 +29,7 @@ using RewardsList = std::vector<std::shared_ptr<Rewardable::Reward>>;
 
 
 /// Reward that can be granted to a hero
 /// Reward that can be granted to a hero
 /// NOTE: eventually should replace seer hut rewards and events/pandoras
 /// NOTE: eventually should replace seer hut rewards and events/pandoras
-struct DLL_LINKAGE Reward
+struct DLL_LINKAGE Reward final
 {
 {
 	/// resources that will be given to player
 	/// resources that will be given to player
 	TResources resources;
 	TResources resources;
@@ -78,7 +78,7 @@ struct DLL_LINKAGE Reward
 	bool removeObject;
 	bool removeObject;
 
 
 	/// Generates list of components that describes reward for a specific hero
 	/// Generates list of components that describes reward for a specific hero
-	virtual void loadComponents(std::vector<Component> & comps,
+	void loadComponents(std::vector<Component> & comps,
 								const CGHeroInstance * h) const;
 								const CGHeroInstance * h) const;
 	
 	
 	Component getDisplayedComponent(const CGHeroInstance * h) const;
 	Component getDisplayedComponent(const CGHeroInstance * h) const;

+ 4 - 14
lib/rmg/modificators/TreasurePlacer.cpp

@@ -460,13 +460,9 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount);
 				reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount);
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
 				obj->configuration.info.push_back(reward);
-				
-				obj->quest->missionType = CQuest::MISSION_ART;
-				
+								
 				ArtifactID artid = qap->drawRandomArtifact();
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				obj->quest->mission.artifacts.push_back(artid);
 				
 				
 				generator.banQuestArt(artid);
 				generator.banQuestArt(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
@@ -513,11 +509,8 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
 				obj->configuration.info.push_back(reward);
 				
 				
-				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				obj->quest->mission.artifacts.push_back(artid);
 				
 				
 				generator.banQuestArt(artid);
 				generator.banQuestArt(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
@@ -538,11 +531,8 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
 				obj->configuration.info.push_back(reward);
 				
 				
-				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				obj->quest->mission.artifacts.push_back(artid);
 				
 				
 				generator.banQuestArt(artid);
 				generator.banQuestArt(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
 				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);

+ 43 - 52
mapeditor/inspector/inspector.cpp

@@ -29,20 +29,6 @@
 #include "PickObjectDelegate.h"
 #include "PickObjectDelegate.h"
 #include "../mapcontroller.h"
 #include "../mapcontroller.h"
 
 
-static QList<std::pair<QString, QVariant>> MissionIdentifiers
-{
-	{QObject::tr("None"), QVariant::fromValue(int(CQuest::Emission::MISSION_NONE))},
-	{QObject::tr("Reach level"), QVariant::fromValue(int(CQuest::Emission::MISSION_LEVEL))},
-	{QObject::tr("Stats"), QVariant::fromValue(int(CQuest::Emission::MISSION_PRIMARY_STAT))},
-	{QObject::tr("Kill hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_HERO))},
-	{QObject::tr("Kill monster"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_CREATURE))},
-	{QObject::tr("Artifact"), QVariant::fromValue(int(CQuest::Emission::MISSION_ART))},
-	{QObject::tr("Army"), QVariant::fromValue(int(CQuest::Emission::MISSION_ARMY))},
-	{QObject::tr("Resources"), QVariant::fromValue(int(CQuest::Emission::MISSION_RESOURCES))},
-	{QObject::tr("Hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_HERO))},
-	{QObject::tr("Player"), QVariant::fromValue(int(CQuest::Emission::MISSION_PLAYER))},
-};
-
 static QList<std::pair<QString, QVariant>> CharacterIdentifiers
 static QList<std::pair<QString, QVariant>> CharacterIdentifiers
 {
 {
 	{QObject::tr("Compliant"), QVariant::fromValue(int(CGCreature::Character::COMPLIANT))},
 	{QObject::tr("Compliant"), QVariant::fromValue(int(CGCreature::Character::COMPLIANT))},
@@ -410,24 +396,28 @@ void Inspector::updateProperties(CGEvent * o)
 
 
 void Inspector::updateProperties(CGSeerHut * o)
 void Inspector::updateProperties(CGSeerHut * o)
 {
 {
-	if(!o) return;
-
-	{ //Mission type
-		auto * delegate = new InspectorDelegate;
-		delegate->options = MissionIdentifiers;
-		addProperty<CQuest::Emission>("Mission type", o->quest->missionType, delegate, false);
-	}
+	if(!o || !o->quest) return;
 	
 	
 	addProperty("First visit text", o->quest->firstVisitText, new MessageDelegate, false);
 	addProperty("First visit text", o->quest->firstVisitText, new MessageDelegate, false);
 	addProperty("Next visit text", o->quest->nextVisitText, new MessageDelegate, false);
 	addProperty("Next visit text", o->quest->nextVisitText, new MessageDelegate, false);
 	addProperty("Completed text", o->quest->completedText, new MessageDelegate, false);
 	addProperty("Completed text", o->quest->completedText, new MessageDelegate, false);
+	addProperty("Repeat quest", o->quest->repeatedQuest, false);
+	addProperty("Time limit", o->quest->lastDay, false);
 	
 	
 	{ //Quest
 	{ //Quest
-		auto * delegate = new QuestDelegate(*controller.map(), *o);
+		auto * delegate = new QuestDelegate(controller, *o->quest);
 		addProperty("Quest", PropertyEditorPlaceholder(), delegate, false);
 		addProperty("Quest", PropertyEditorPlaceholder(), delegate, false);
 	}
 	}
 }
 }
 
 
+void Inspector::updateProperties(CGQuestGuard * o)
+{
+	if(!o || !o->quest) return;
+	
+	addProperty("Reward", PropertyEditorPlaceholder(), nullptr, true);
+	addProperty("Repeat quest", o->quest->repeatedQuest, true);
+}
+
 void Inspector::updateProperties()
 void Inspector::updateProperties()
 {
 {
 	if(!obj)
 	if(!obj)
@@ -470,6 +460,7 @@ void Inspector::updateProperties()
 	UPDATE_OBJ_PROPERTIES(CGPandoraBox);
 	UPDATE_OBJ_PROPERTIES(CGPandoraBox);
 	UPDATE_OBJ_PROPERTIES(CGEvent);
 	UPDATE_OBJ_PROPERTIES(CGEvent);
 	UPDATE_OBJ_PROPERTIES(CGSeerHut);
 	UPDATE_OBJ_PROPERTIES(CGSeerHut);
+	UPDATE_OBJ_PROPERTIES(CGQuestGuard);
 	
 	
 	table->show();
 	table->show();
 }
 }
@@ -516,6 +507,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value)
 	SET_PROPERTIES(CGPandoraBox);
 	SET_PROPERTIES(CGPandoraBox);
 	SET_PROPERTIES(CGEvent);
 	SET_PROPERTIES(CGEvent);
 	SET_PROPERTIES(CGSeerHut);
 	SET_PROPERTIES(CGSeerHut);
+	SET_PROPERTIES(CGQuestGuard);
 }
 }
 
 
 void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVariant & value)
 void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVariant & value)
@@ -538,7 +530,8 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian
 	if(!o) return;
 	if(!o) return;
 	
 	
 	if(key == "Message")
 	if(key == "Message")
-		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
+		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
 }
 }
 
 
 void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value)
 void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value)
@@ -560,7 +553,8 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari
 	if(!o) return;
 	if(!o) return;
 	
 	
 	if(key == "Town name")
 	if(key == "Town name")
-		o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString()));
+		o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString()));
 }
 }
 
 
 void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value)
 void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value)
@@ -568,7 +562,8 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian
 	if(!o) return;
 	if(!o) return;
 	
 	
 	if(key == "Message")
 	if(key == "Message")
-		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString()));
+		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString()));
 }
 }
 
 
 void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value)
 void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value)
@@ -584,7 +579,8 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant
 	if(!o) return;
 	if(!o) return;
 	
 	
 	if(key == "Message")
 	if(key == "Message")
-		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
+		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
 	
 	
 	if(o->storedArtifact && key == "Spell")
 	if(o->storedArtifact && key == "Spell")
 	{
 	{
@@ -623,10 +619,12 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 		o->gender = EHeroGender(value.toInt());
 		o->gender = EHeroGender(value.toInt());
 	
 	
 	if(key == "Name")
 	if(key == "Name")
-		o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString());
+		o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString());
 	
 	
 	if(key == "Biography")
 	if(key == "Biography")
-		o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString());
+		o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString());
 	
 	
 	if(key == "Experience")
 	if(key == "Experience")
 		o->exp = value.toString().toInt();
 		o->exp = value.toString().toInt();
@@ -662,7 +660,8 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant
 	if(!o) return;
 	if(!o) return;
 	
 	
 	if(key == "Message")
 	if(key == "Message")
-		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString()));
+		o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString()));
 	if(key == "Character")
 	if(key == "Character")
 		o->character = CGCreature::Character(value.toInt());
 		o->character = CGCreature::Character(value.toInt());
 	if(key == "Never flees")
 	if(key == "Never flees")
@@ -677,14 +676,24 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
 {
 {
 	if(!o) return;
 	if(!o) return;
 	
 	
-	if(key == "Mission type")
-		o->quest->missionType = CQuest::Emission(value.toInt());
 	if(key == "First visit text")
 	if(key == "First visit text")
-		o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
+		o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
 	if(key == "Next visit text")
 	if(key == "Next visit text")
-		o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
+		o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
 	if(key == "Completed text")
 	if(key == "Completed text")
-		o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
+		o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
+			TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
+	if(key == "Repeat quest")
+		o->quest->repeatedQuest = value.toBool();
+	if(key == "Time limit")
+		o->quest->lastDay = value.toString().toInt();
+}
+
+void Inspector::setProperty(CGQuestGuard * o, const QString & key, const QVariant & value)
+{
+	if(!o) return;
 }
 }
 
 
 
 
@@ -797,24 +806,6 @@ QTableWidgetItem * Inspector::addProperty(CGCreature::Character value)
 	return item;
 	return item;
 }
 }
 
 
-QTableWidgetItem * Inspector::addProperty(CQuest::Emission value)
-{
-	auto * item = new QTableWidgetItem;
-	item->setFlags(Qt::NoItemFlags);
-	item->setData(Qt::UserRole, QVariant::fromValue(int(value)));
-	
-	for(auto & i : MissionIdentifiers)
-	{
-		if(i.second.toInt() == value)
-		{
-			item->setText(i.first);
-			break;
-		}
-	}
-	
-	return item;
-}
-
 //========================================================================
 //========================================================================
 
 
 Inspector::Inspector(MapController & c, CGObjectInstance * o, QTableWidget * t): obj(o), table(t), controller(c)
 Inspector::Inspector(MapController & c, CGObjectInstance * o, QTableWidget * t): obj(o), table(t), controller(c)

+ 1 - 1
mapeditor/inspector/inspector.h

@@ -82,6 +82,7 @@ protected:
 	DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
 	DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
 	DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
 	DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
 	DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut);
 	DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut);
+	DECLARE_OBJ_PROPERTY_METHODS(CGQuestGuard);
 
 
 //===============DECLARE PROPERTY VALUE TYPE==============================
 //===============DECLARE PROPERTY VALUE TYPE==============================
 	QTableWidgetItem * addProperty(unsigned int value);
 	QTableWidgetItem * addProperty(unsigned int value);
@@ -96,7 +97,6 @@ protected:
 	QTableWidgetItem * addProperty(bool value);
 	QTableWidgetItem * addProperty(bool value);
 	QTableWidgetItem * addProperty(CGObjectInstance * value);
 	QTableWidgetItem * addProperty(CGObjectInstance * value);
 	QTableWidgetItem * addProperty(CGCreature::Character value);
 	QTableWidgetItem * addProperty(CGCreature::Character value);
-	QTableWidgetItem * addProperty(CQuest::Emission value);
 	QTableWidgetItem * addProperty(PropertyEditorPlaceholder value);
 	QTableWidgetItem * addProperty(PropertyEditorPlaceholder value);
 	
 	
 //===============END OF DECLARATION=======================================
 //===============END OF DECLARATION=======================================

+ 361 - 121
mapeditor/inspector/questwidget.cpp

@@ -10,21 +10,124 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "questwidget.h"
 #include "questwidget.h"
 #include "ui_questwidget.h"
 #include "ui_questwidget.h"
+#include "../mapcontroller.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/CSkillHandler.h"
 #include "../lib/CSkillHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/constants/StringConstants.h"
 #include "../lib/constants/StringConstants.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/mapping/CMap.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
+#include "../lib/mapObjects/CGCreature.h"
 
 
-QuestWidget::QuestWidget(const CMap & _map, CGSeerHut & _sh, QWidget *parent) :
+QuestWidget::QuestWidget(MapController & _controller, CQuest & _sh, QWidget *parent) :
 	QDialog(parent),
 	QDialog(parent),
-	map(_map),
-	seerhut(_sh),
+	controller(_controller),
+	quest(_sh),
 	ui(new Ui::QuestWidget)
 	ui(new Ui::QuestWidget)
 {
 {
+	setAttribute(Qt::WA_DeleteOnClose, true);
 	ui->setupUi(this);
 	ui->setupUi(this);
+
+	ui->lDayOfWeek->addItem(tr("None"));
+	for(int i = 1; i <= 7; ++i)
+		ui->lDayOfWeek->addItem(tr("Day %1").arg(i));
+	
+	//fill resources
+	ui->lResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1);
+	for(int i = 0; i < GameConstants::RESOURCE_QUANTITY - 1; ++i)
+	{
+		auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i]));
+		item->setData(Qt::UserRole, QVariant::fromValue(i));
+		ui->lResources->setItem(i, 0, item);
+		auto * spinBox = new QSpinBox;
+		spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999);
+		ui->lResources->setCellWidget(i, 1, spinBox);
+	}
+	
+	//fill artifacts
+	for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(VLC->artifacts()->getByIndex(i)->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(i));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		if(!controller.map()->allowedArtifact[i])
+			item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+		ui->lArtifacts->addItem(item);
+	}
+	
+	//fill spells
+	for(int i = 0; i < controller.map()->allowedSpells.size(); ++i)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(i));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		if(!controller.map()->allowedSpells[i])
+			item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+		ui->lSpells->addItem(item);
+	}
+	
+	//fill skills
+	ui->lSkills->setRowCount(controller.map()->allowedAbilities.size());
+	for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
+	{
+		auto * item = new QTableWidgetItem(QString::fromStdString(VLC->skills()->getByIndex(i)->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(i));
+		
+		auto * widget = new QComboBox;
+		for(auto & s : NSecondarySkill::levels)
+			widget->addItem(QString::fromStdString(s));
+		
+		if(!controller.map()->allowedAbilities[i])
+		{
+			item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+			widget->setEnabled(false);
+		}
+			
+		ui->lSkills->setItem(i, 0, item);
+		ui->lSkills->setCellWidget(i, 1, widget);
+	}
+	
+	//fill creatures
+	for(auto & creature : VLC->creh->objects)
+	{
+		ui->lCreatureId->addItem(QString::fromStdString(creature->getNameSingularTranslated()));
+		ui->lCreatureId->setItemData(ui->lCreatureId->count() - 1, creature->getIndex());
+	}
+	
+	//fill heroes
+	VLC->heroTypes()->forEach([this](const HeroType * hero, bool &)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lHeroes->addItem(item);
+	});
+	
+	//fill hero classes
+	VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lHeroClasses->addItem(item);
+	});
+	
+	//fill players
+	for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()]));
+		item->setData(Qt::UserRole, QVariant::fromValue(color.getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lPlayers->addItem(item);
+	}
 }
 }
 
 
 QuestWidget::~QuestWidget()
 QuestWidget::~QuestWidget()
@@ -34,137 +137,259 @@ QuestWidget::~QuestWidget()
 
 
 void QuestWidget::obtainData()
 void QuestWidget::obtainData()
 {
 {
-	assert(seerhut.quest);
-	bool activeId = false;
-	bool activeAmount = false;
-	switch(seerhut.quest->missionType) {
-		case CQuest::Emission::MISSION_LEVEL:
-			activeAmount = true;
-			ui->targetId->addItem("Reach level");
-			ui->targetAmount->setText(QString::number(seerhut.quest->m13489val));
-			break;
-		case CQuest::Emission::MISSION_PRIMARY_STAT:
-			activeId = true;
-			activeAmount = true;
-			for(auto s : NPrimarySkill::names)
-				ui->targetId->addItem(QString::fromStdString(s));
-			for(int i = 0; i < seerhut.quest->m2stats.size(); ++i)
+	ui->lDayOfWeek->setCurrentIndex(quest.mission.dayOfWeek);
+	ui->lDaysPassed->setValue(quest.mission.daysPassed);
+	ui->lHeroLevel->setValue(quest.mission.heroLevel);
+	ui->lHeroExperience->setValue(quest.mission.heroExperience);
+	ui->lManaPoints->setValue(quest.mission.manaPoints);
+	ui->lManaPercentage->setValue(quest.mission.manaPercentage);
+	ui->lAttack->setValue(quest.mission.primary[0]);
+	ui->lDefence->setValue(quest.mission.primary[1]);
+	ui->lPower->setValue(quest.mission.primary[2]);
+	ui->lKnowledge->setValue(quest.mission.primary[3]);
+	for(int i = 0; i < ui->lResources->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
+			widget->setValue(quest.mission.resources[i]);
+	}
+	
+	for(auto i : quest.mission.artifacts)
+		ui->lArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto i : quest.mission.spells)
+		ui->lArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto & i : quest.mission.secondary)
+	{
+		int index = VLC->skills()->getById(i.first)->getIndex();
+		if(auto * widget = qobject_cast<QComboBox*>(ui->lSkills->cellWidget(index, 1)))
+			widget->setCurrentIndex(i.second);
+	}
+	for(auto & i : quest.mission.creatures)
+	{
+		int index = i.type->getIndex();
+		ui->lCreatureId->setCurrentIndex(index);
+		ui->lCreatureAmount->setValue(i.count);
+		onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
+	}
+	for(auto & i : quest.mission.heroes)
+	{
+		for(int e = 0; e < ui->lHeroes->count(); ++e)
+		{
+			if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum())
+			{
+				ui->lHeroes->item(e)->setCheckState(Qt::Checked);
+				break;
+			}
+		}
+	}
+	for(auto & i : quest.mission.heroClasses)
+	{
+		for(int e = 0; e < ui->lHeroClasses->count(); ++e)
+		{
+			if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum())
 			{
 			{
-				if(seerhut.quest->m2stats[i] > 0)
-				{
-					ui->targetId->setCurrentIndex(i);
-					ui->targetAmount->setText(QString::number(seerhut.quest->m2stats[i]));
-					break; //TODO: support multiple stats
-				}
+				ui->lHeroClasses->item(e)->setCheckState(Qt::Checked);
+				break;
 			}
 			}
-			break;
-		case CQuest::Emission::MISSION_KILL_HERO:
-			activeId = true;
-			//TODO: implement
-			break;
-		case CQuest::Emission::MISSION_KILL_CREATURE:
-			activeId = true;
-			//TODO: implement
-			break;
-		case CQuest::Emission::MISSION_ART:
-			activeId = true;
-			for(int i = 0; i < map.allowedArtifact.size(); ++i)
-				ui->targetId->addItem(QString::fromStdString(VLC->arth->objects.at(i)->getNameTranslated()));
-			if(!seerhut.quest->m5arts.empty())
-				ui->targetId->setCurrentIndex(seerhut.quest->m5arts.front());
-			//TODO: support multiple artifacts
-			break;
-		case CQuest::Emission::MISSION_ARMY:
-			activeId = true;
-			activeAmount = true;
-			break;
-		case CQuest::Emission::MISSION_RESOURCES:
-			activeId = true;
-			activeAmount = true;
-			for(auto s : GameConstants::RESOURCE_NAMES)
-				ui->targetId->addItem(QString::fromStdString(s));
-			for(int i = 0; i < seerhut.quest->m7resources.size(); ++i)
+		}
+	}
+	for(auto & i : quest.mission.players)
+	{
+		for(int e = 0; e < ui->lPlayers->count(); ++e)
+		{
+			if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum())
 			{
 			{
-				if(seerhut.quest->m7resources[i] > 0)
-				{
-					ui->targetId->setCurrentIndex(i);
-					ui->targetAmount->setText(QString::number(seerhut.quest->m7resources[i]));
-					break; //TODO: support multiple resources
-				}
+				ui->lPlayers->item(e)->setCheckState(Qt::Checked);
+				break;
 			}
 			}
-			break;
-		case CQuest::Emission::MISSION_HERO:
-			activeId = true;
-			for(int i = 0; i < map.allowedHeroes.size(); ++i)
-				ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getNameTranslated()));
-			ui->targetId->setCurrentIndex(seerhut.quest->m13489val);
-			break;
-		case CQuest::Emission::MISSION_PLAYER:
-			activeId = true;
-			for(auto s : GameConstants::PLAYER_COLOR_NAMES)
-				ui->targetId->addItem(QString::fromStdString(s));
-			ui->targetId->setCurrentIndex(seerhut.quest->m13489val);
-			break;
-		case CQuest::Emission::MISSION_KEYMASTER:
-			break;
-		default:
-			break;
-	}
-	
-	ui->targetId->setEnabled(activeId);
-	ui->targetAmount->setEnabled(activeAmount);
+		}
+	}
+	
+	if(quest.killTarget != ObjectInstanceID::NONE && quest.killTarget < controller.map()->objects.size())
+		ui->lKillTarget->setText(QString::fromStdString(controller.map()->objects[quest.killTarget]->instanceName));
+	else
+		quest.killTarget = ObjectInstanceID::NONE;
 }
 }
 
 
-QString QuestWidget::commitChanges()
+bool QuestWidget::commitChanges()
 {
 {
-	assert(seerhut.quest);
-	switch(seerhut.quest->missionType) {
-		case CQuest::Emission::MISSION_LEVEL:
-			seerhut.quest->m13489val = ui->targetAmount->text().toInt();
-			return QString("Reach lvl ").append(ui->targetAmount->text());
-		case CQuest::Emission::MISSION_PRIMARY_STAT:
-			seerhut.quest->m2stats.resize(sizeof(NPrimarySkill::names), 0);
-			seerhut.quest->m2stats[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt();
-			//TODO: support multiple stats
-			return ui->targetId->currentText().append(ui->targetAmount->text());
-		case CQuest::Emission::MISSION_KILL_HERO:
-			//TODO: implement
-			return QString("N/A");
-		case CQuest::Emission::MISSION_KILL_CREATURE:
-			//TODO: implement
-			return QString("N/A");
-		case CQuest::Emission::MISSION_ART:
-			seerhut.quest->m5arts.clear();
-			seerhut.quest->m5arts.push_back(ArtifactID(ui->targetId->currentIndex()));
-			//TODO: support multiple artifacts
-			return ui->targetId->currentText();
-		case CQuest::Emission::MISSION_ARMY:
-			//TODO: implement
-			return QString("N/A");
-		case CQuest::Emission::MISSION_RESOURCES:
-			seerhut.quest->m7resources[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt();
-			//TODO: support resources
-			return ui->targetId->currentText().append(ui->targetAmount->text());
-		case CQuest::Emission::MISSION_HERO:
-			seerhut.quest->m13489val = ui->targetId->currentIndex();
-			return ui->targetId->currentText();
-		case CQuest::Emission::MISSION_PLAYER:
-			seerhut.quest->m13489val = ui->targetId->currentIndex();
-			return ui->targetId->currentText();
-		case CQuest::Emission::MISSION_KEYMASTER:
-			return QString("N/A");
-		default:
-			return QString("N/A");
+	quest.mission.dayOfWeek = ui->lDayOfWeek->currentIndex();
+	quest.mission.daysPassed = ui->lDaysPassed->value();
+	quest.mission.heroLevel = ui->lHeroLevel->value();
+	quest.mission.heroExperience = ui->lHeroExperience->value();
+	quest.mission.manaPoints = ui->lManaPoints->value();
+	quest.mission.manaPercentage = ui->lManaPercentage->value();
+	quest.mission.primary[0] = ui->lAttack->value();
+	quest.mission.primary[1] = ui->lDefence->value();
+	quest.mission.primary[2] = ui->lPower->value();
+	quest.mission.primary[3] = ui->lKnowledge->value();
+	for(int i = 0; i < ui->lResources->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
+			quest.mission.resources[i] = widget->value();
+	}
+	
+	quest.mission.artifacts.clear();
+	for(int i = 0; i < ui->lArtifacts->count(); ++i)
+	{
+		if(ui->lArtifacts->item(i)->checkState() == Qt::Checked)
+			quest.mission.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId());
+	}
+	quest.mission.spells.clear();
+	for(int i = 0; i < ui->lSpells->count(); ++i)
+	{
+		if(ui->lSpells->item(i)->checkState() == Qt::Checked)
+			quest.mission.spells.push_back(VLC->spells()->getByIndex(i)->getId());
+	}
+	
+	quest.mission.secondary.clear();
+	for(int i = 0; i < ui->lSkills->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QComboBox*>(ui->lSkills->cellWidget(i, 1)))
+		{
+			if(widget->currentIndex() > 0)
+				quest.mission.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex();
+		}
+	}
+	
+	quest.mission.creatures.clear();
+	for(int i = 0; i < ui->lCreatures->rowCount(); ++i)
+	{
+		int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt();
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->lCreatures->cellWidget(i, 1)))
+			if(widget->value())
+				quest.mission.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
+	}
+	
+	quest.mission.heroes.clear();
+	for(int i = 0; i < ui->lHeroes->count(); ++i)
+	{
+		if(ui->lHeroes->item(i)->checkState() == Qt::Checked)
+			quest.mission.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt());
 	}
 	}
+	
+	quest.mission.heroClasses.clear();
+	for(int i = 0; i < ui->lHeroClasses->count(); ++i)
+	{
+		if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked)
+			quest.mission.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt());
+	}
+	
+	quest.mission.players.clear();
+	for(int i = 0; i < ui->lPlayers->count(); ++i)
+	{
+		if(ui->lPlayers->item(i)->checkState() == Qt::Checked)
+			quest.mission.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt());
+	}
+	
+	//quest.killTarget is set directly in object picking
+	
+	return true;
+}
+
+void QuestWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget)
+{
+	QTableWidgetItem * item = nullptr;
+	QSpinBox * widget = nullptr;
+	for(int i = 0; i < listWidget->rowCount(); ++i)
+	{
+		if(auto * cname = listWidget->item(i, 0))
+		{
+			if(cname->data(Qt::UserRole).toInt() == comboWidget->currentData().toInt())
+			{
+				item = cname;
+				widget = qobject_cast<QSpinBox*>(listWidget->cellWidget(i, 1));
+				break;
+			}
+		}
+	}
+	
+	if(!item)
+	{
+		listWidget->setRowCount(listWidget->rowCount() + 1);
+		item = new QTableWidgetItem(comboWidget->currentText());
+		listWidget->setItem(listWidget->rowCount() - 1, 0, item);
+	}
+	
+	item->setData(Qt::UserRole, comboWidget->currentData());
+	
+	if(!widget)
+	{
+		widget = new QSpinBox;
+		widget->setRange(spinWidget->minimum(), spinWidget->maximum());
+		listWidget->setCellWidget(listWidget->rowCount() - 1, 1, widget);
+	}
+	
+	widget->setValue(spinWidget->value());
+}
+
+void QuestWidget::on_lKillTargetSelect_clicked()
+{
+	auto pred = [](const CGObjectInstance * obj) -> bool
+	{
+		if(auto * o = dynamic_cast<const CGHeroInstance*>(obj))
+			return o->ID != Obj::PRISON;
+		if(dynamic_cast<const CGCreature*>(obj))
+			return true;
+		return false;
+	};
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.highlight(pred);
+		l.update();
+		QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked);
+	}
+	
+	hide();
+}
+
+void QuestWidget::onTargetPicked(const CGObjectInstance * obj)
+{
+	show();
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.clear();
+		l.update();
+		QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked);
+	}
+	
+	if(!obj) //discarded
+	{
+		quest.killTarget = ObjectInstanceID::NONE;
+		ui->lKillTarget->setText("");
+		return;
+	}
+	
+	ui->lKillTarget->setText(QString::fromStdString(obj->instanceName));
+	quest.killTarget = obj->id;
 }
 }
 
 
-QuestDelegate::QuestDelegate(const CMap & m, CGSeerHut & t): map(m), seerhut(t), QStyledItemDelegate()
+void QuestWidget::on_lCreatureAdd_clicked()
+{
+	onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
+}
+
+
+void QuestWidget::on_lCreatureRemove_clicked()
+{
+	std::set<int, std::greater<int>> rowsToRemove;
+	for(auto * i : ui->lCreatures->selectedItems())
+		rowsToRemove.insert(i->row());
+	
+	for(auto i : rowsToRemove)
+		ui->lCreatures->removeRow(i);
+}
+
+QuestDelegate::QuestDelegate(MapController & c, CQuest & t): controller(c), quest(t), QStyledItemDelegate()
 {
 {
 }
 }
 
 
 QWidget * QuestDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
 QWidget * QuestDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
 {
 {
-	return new QuestWidget(map, seerhut, parent);
+	return new QuestWidget(controller, quest, parent);
 }
 }
 
 
 void QuestDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
 void QuestDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
@@ -183,11 +408,26 @@ void QuestDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, c
 {
 {
 	if(auto *ed = qobject_cast<QuestWidget *>(editor))
 	if(auto *ed = qobject_cast<QuestWidget *>(editor))
 	{
 	{
-		auto quest = ed->commitChanges();
-		model->setData(index, quest);
+		ed->commitChanges();
 	}
 	}
 	else
 	else
 	{
 	{
 		QStyledItemDelegate::setModelData(editor, model, index);
 		QStyledItemDelegate::setModelData(editor, model, index);
 	}
 	}
 }
 }
+
+bool QuestDelegate::eventFilter(QObject * object, QEvent * event)
+{
+	if(auto * ed = qobject_cast<QuestWidget *>(object))
+	{
+		if(event->type() == QEvent::Hide || event->type() == QEvent::FocusOut)
+			return false;
+		if(event->type() == QEvent::Close)
+		{
+			emit commitData(ed);
+			emit closeEditor(ed);
+			return true;
+		}
+	}
+	return QStyledItemDelegate::eventFilter(object, event);
+}

+ 23 - 7
mapeditor/inspector/questwidget.h

@@ -16,20 +16,33 @@ namespace Ui {
 class QuestWidget;
 class QuestWidget;
 }
 }
 
 
+class MapController;
+
 class QuestWidget : public QDialog
 class QuestWidget : public QDialog
 {
 {
 	Q_OBJECT
 	Q_OBJECT
 
 
 public:
 public:
-	explicit QuestWidget(const CMap &, CGSeerHut &, QWidget *parent = nullptr);
+	explicit QuestWidget(MapController &, CQuest &, QWidget *parent = nullptr);
 	~QuestWidget();
 	~QuestWidget();
 	
 	
 	void obtainData();
 	void obtainData();
-	QString commitChanges();
+	bool commitChanges();
+
+private slots:
+	void onTargetPicked(const CGObjectInstance *);
+	
+	void on_lKillTargetSelect_clicked();
+
+	void on_lCreatureAdd_clicked();
+
+	void on_lCreatureRemove_clicked();
 
 
 private:
 private:
-	CGSeerHut & seerhut;
-	const CMap & map;
+	void onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget);
+	
+	CQuest & quest;
+	MapController & controller;
 	Ui::QuestWidget *ui;
 	Ui::QuestWidget *ui;
 };
 };
 
 
@@ -39,13 +52,16 @@ class QuestDelegate : public QStyledItemDelegate
 public:
 public:
 	using QStyledItemDelegate::QStyledItemDelegate;
 	using QStyledItemDelegate::QStyledItemDelegate;
 	
 	
-	QuestDelegate(const CMap &, CGSeerHut &);
+	QuestDelegate(MapController &, CQuest &);
 	
 	
 	QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
 	QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
 	void setEditorData(QWidget * editor, const QModelIndex & index) const override;
 	void setEditorData(QWidget * editor, const QModelIndex & index) const override;
 	void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
 	void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
 	
 	
+protected:
+	bool eventFilter(QObject * object, QEvent * event) override;
+
 private:
 private:
-	CGSeerHut & seerhut;
-	const CMap & map;
+	CQuest & quest;
+	MapController & controller;
 };
 };

+ 606 - 24
mapeditor/inspector/questwidget.ui

@@ -9,8 +9,8 @@
    <rect>
    <rect>
     <x>0</x>
     <x>0</x>
     <y>0</y>
     <y>0</y>
-    <width>429</width>
-    <height>89</height>
+    <width>531</width>
+    <height>495</height>
    </rect>
    </rect>
   </property>
   </property>
   <property name="windowTitle">
   <property name="windowTitle">
@@ -19,34 +19,616 @@
   <property name="modal">
   <property name="modal">
    <bool>true</bool>
    <bool>true</bool>
   </property>
   </property>
-  <layout class="QHBoxLayout" name="horizontalLayout">
+  <layout class="QVBoxLayout" name="verticalLayout_2">
    <item>
    <item>
-    <widget class="QComboBox" name="targetId">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+    <layout class="QHBoxLayout" name="horizontalLayout_8">
+     <item>
+      <widget class="QLabel" name="label_7">
+       <property name="text">
+        <string>Day of week</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="lDayOfWeek">
+       <property name="minimumSize">
+        <size>
+         <width>120</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_8">
+       <property name="text">
+        <string>Days passed</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="lDaysPassed">
+       <property name="minimumSize">
+        <size>
+         <width>20</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_3">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_10">
+     <item>
+      <widget class="QLabel" name="label_14">
+       <property name="text">
+        <string>Hero level</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="lHeroLevel">
+       <property name="minimumSize">
+        <size>
+         <width>40</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_15">
+       <property name="text">
+        <string>Hero experience</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="lHeroExperience">
+       <property name="minimumSize">
+        <size>
+         <width>80</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximum">
+        <number>100000</number>
+       </property>
+       <property name="singleStep">
+        <number>100</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_4">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_9">
+     <item>
+      <widget class="QLabel" name="label_9">
+       <property name="text">
+        <string>Spell points</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="lManaPoints">
+       <property name="minimumSize">
+        <size>
+         <width>60</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximum">
+        <number>999</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="lManaPercentage">
+       <property name="suffix">
+        <string>%</string>
+       </property>
+       <property name="maximum">
+        <number>100</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_14">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Kill hero/monster</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="lKillTarget">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="lKillTargetSelect">
+       <property name="text">
+        <string>...</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_3">
+     <property name="title">
+      <string>Primary skills</string>
      </property>
      </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>12</number>
+      </property>
+      <property name="topMargin">
+       <number>3</number>
+      </property>
+      <property name="bottomMargin">
+       <number>3</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label_10">
+        <property name="text">
+         <string>Attack</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="lAttack"/>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_11">
+        <property name="text">
+         <string>Defence</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="lDefence"/>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_12">
+        <property name="text">
+         <string>Spell power</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="lPower"/>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_13">
+        <property name="text">
+         <string>Knowledge</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="lKnowledge"/>
+      </item>
+     </layout>
     </widget>
     </widget>
    </item>
    </item>
    <item>
    <item>
-    <widget class="QLineEdit" name="targetAmount">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="maximumSize">
-      <size>
-       <width>60</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
+    <widget class="QTabWidget" name="tabWidget_2">
+     <property name="layoutDirection">
+      <enum>Qt::LeftToRight</enum>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::North</enum>
+     </property>
+     <property name="tabShape">
+      <enum>QTabWidget::Rounded</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="elideMode">
+      <enum>Qt::ElideNone</enum>
+     </property>
+     <property name="usesScrollButtons">
+      <bool>true</bool>
+     </property>
+     <property name="documentMode">
+      <bool>false</bool>
+     </property>
+     <property name="tabBarAutoHide">
+      <bool>true</bool>
      </property>
      </property>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="title">
+       <string>Resources</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QTableWidget" name="lResources">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="columnCount">
+          <number>2</number>
+         </property>
+         <attribute name="horizontalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="horizontalHeaderDefaultSectionSize">
+          <number>180</number>
+         </attribute>
+         <attribute name="horizontalHeaderStretchLastSection">
+          <bool>true</bool>
+         </attribute>
+         <attribute name="verticalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="verticalHeaderDefaultSectionSize">
+          <number>24</number>
+         </attribute>
+         <column/>
+         <column/>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_4">
+      <attribute name="title">
+       <string>Artifacts</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QListWidget" name="lArtifacts">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="isWrapping" stdset="0">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_5">
+      <attribute name="title">
+       <string>Spells</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_4">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QListWidget" name="lSpells">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="isWrapping" stdset="0">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_7">
+      <attribute name="title">
+       <string>Skills</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_5">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QTableWidget" name="lSkills">
+         <property name="columnCount">
+          <number>2</number>
+         </property>
+         <attribute name="horizontalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="horizontalHeaderDefaultSectionSize">
+          <number>180</number>
+         </attribute>
+         <attribute name="verticalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="verticalHeaderDefaultSectionSize">
+          <number>24</number>
+         </attribute>
+         <column/>
+         <column/>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_6">
+      <attribute name="title">
+       <string>Creatures</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <item>
+          <widget class="QComboBox" name="lCreatureId">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QSpinBox" name="lCreatureAmount">
+           <property name="minimumSize">
+            <size>
+             <width>60</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximum">
+            <number>9999</number>
+           </property>
+           <property name="stepType">
+            <enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="lCreatureAdd">
+           <property name="text">
+            <string>Add</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="lCreatureRemove">
+           <property name="text">
+            <string>Remove</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QTableWidget" name="lCreatures">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::MultiSelection</enum>
+         </property>
+         <property name="selectionBehavior">
+          <enum>QAbstractItemView::SelectRows</enum>
+         </property>
+         <property name="columnCount">
+          <number>2</number>
+         </property>
+         <attribute name="horizontalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="horizontalHeaderDefaultSectionSize">
+          <number>180</number>
+         </attribute>
+         <attribute name="verticalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="verticalHeaderDefaultSectionSize">
+          <number>24</number>
+         </attribute>
+         <column/>
+         <column/>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>Heroes</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_12">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QListWidget" name="lHeroes">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="isWrapping" stdset="0">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>Hero classes</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_11">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QListWidget" name="lHeroClasses">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="isWrapping" stdset="0">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_8">
+      <attribute name="title">
+       <string>Players</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_13">
+       <property name="leftMargin">
+        <number>3</number>
+       </property>
+       <property name="topMargin">
+        <number>3</number>
+       </property>
+       <property name="rightMargin">
+        <number>3</number>
+       </property>
+       <property name="bottomMargin">
+        <number>3</number>
+       </property>
+       <item>
+        <widget class="QListWidget" name="lPlayers">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::NoSelection</enum>
+         </property>
+         <property name="isWrapping" stdset="0">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
     </widget>
     </widget>
    </item>
    </item>
   </layout>
   </layout>

+ 96 - 6
mapeditor/inspector/rewardswidget.cpp

@@ -13,6 +13,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/CSkillHandler.h"
 #include "../lib/CSkillHandler.h"
 #include "../lib/spells/CSpellHandler.h"
 #include "../lib/spells/CSpellHandler.h"
+#include "../lib/CHeroHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "../lib/constants/StringConstants.h"
 #include "../lib/constants/StringConstants.h"
@@ -55,7 +56,11 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) :
 			auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i]));
 			auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i]));
 			item->setData(Qt::UserRole, QVariant::fromValue(i));
 			item->setData(Qt::UserRole, QVariant::fromValue(i));
 			w->setItem(i, 0, item);
 			w->setItem(i, 0, item);
-			w->setCellWidget(i, 1, new QSpinBox);
+			auto * spinBox = new QSpinBox;
+			spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999);
+			if(w == ui->rResources)
+				spinBox->setMinimum(i == GameResID::GOLD ? -999999 : -999);
+			w->setCellWidget(i, 1, spinBox);
 		}
 		}
 	}
 	}
 	
 	
@@ -131,6 +136,36 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) :
 		}
 		}
 	}
 	}
 	
 	
+	//fill heroes
+	VLC->heroTypes()->forEach([this](const HeroType * hero, bool &)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lHeroes->addItem(item);
+	});
+	
+	//fill hero classes
+	VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated()));
+		item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lHeroClasses->addItem(item);
+	});
+	
+	//fill players
+	for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color)
+	{
+		auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()]));
+		item->setData(Qt::UserRole, QVariant::fromValue(color.getNum()));
+		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+		item->setCheckState(Qt::Unchecked);
+		ui->lPlayers->addItem(item);
+	}
+	
 	//fill spell cast
 	//fill spell cast
 	for(auto & s : NSecondarySkill::levels)
 	for(auto & s : NSecondarySkill::levels)
 		ui->castLevel->addItem(QString::fromStdString(s));
 		ui->castLevel->addItem(QString::fromStdString(s));
@@ -347,7 +382,28 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
 		int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt();
 		int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt();
 		if(auto * widget = qobject_cast<QSpinBox*>(ui->lCreatures->cellWidget(i, 1)))
 		if(auto * widget = qobject_cast<QSpinBox*>(ui->lCreatures->cellWidget(i, 1)))
 			if(widget->value())
 			if(widget->value())
-				vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
+				vinfo.limiter.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
+	}
+	
+	vinfo.limiter.heroes.clear();
+	for(int i = 0; i < ui->lHeroes->count(); ++i)
+	{
+		if(ui->lHeroes->item(i)->checkState() == Qt::Checked)
+			vinfo.limiter.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt());
+	}
+	
+	vinfo.limiter.heroClasses.clear();
+	for(int i = 0; i < ui->lHeroClasses->count(); ++i)
+	{
+		if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked)
+			vinfo.limiter.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt());
+	}
+	
+	vinfo.limiter.players.clear();
+	for(int i = 0; i < ui->lPlayers->count(); ++i)
+	{
+		if(ui->lPlayers->item(i)->checkState() == Qt::Checked)
+			vinfo.limiter.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt());
 	}
 	}
 }
 }
 
 
@@ -445,10 +501,10 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
 	ui->lHeroExperience->setValue(vinfo.limiter.heroExperience);
 	ui->lHeroExperience->setValue(vinfo.limiter.heroExperience);
 	ui->lManaPoints->setValue(vinfo.limiter.manaPoints);
 	ui->lManaPoints->setValue(vinfo.limiter.manaPoints);
 	ui->lManaPercentage->setValue(vinfo.limiter.manaPercentage);
 	ui->lManaPercentage->setValue(vinfo.limiter.manaPercentage);
-	ui->lAttack->setValue(vinfo.reward.primary[0]);
-	ui->lDefence->setValue(vinfo.reward.primary[1]);
-	ui->lPower->setValue(vinfo.reward.primary[2]);
-	ui->lKnowledge->setValue(vinfo.reward.primary[3]);
+	ui->lAttack->setValue(vinfo.limiter.primary[0]);
+	ui->lDefence->setValue(vinfo.limiter.primary[1]);
+	ui->lPower->setValue(vinfo.limiter.primary[2]);
+	ui->lKnowledge->setValue(vinfo.limiter.primary[3]);
 	for(int i = 0; i < ui->lResources->rowCount(); ++i)
 	for(int i = 0; i < ui->lResources->rowCount(); ++i)
 	{
 	{
 		if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
 		if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
@@ -472,6 +528,40 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
 		ui->lCreatureAmount->setValue(i.count);
 		ui->lCreatureAmount->setValue(i.count);
 		onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
 		onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
 	}
 	}
+	
+	for(auto & i : vinfo.limiter.heroes)
+	{
+		for(int e = 0; e < ui->lHeroes->count(); ++e)
+		{
+			if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum())
+			{
+				ui->lHeroes->item(e)->setCheckState(Qt::Checked);
+				break;
+			}
+		}
+	}
+	for(auto & i : vinfo.limiter.heroClasses)
+	{
+		for(int e = 0; e < ui->lHeroClasses->count(); ++e)
+		{
+			if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum())
+			{
+				ui->lHeroClasses->item(e)->setCheckState(Qt::Checked);
+				break;
+			}
+		}
+	}
+	for(auto & i : vinfo.limiter.players)
+	{
+		for(int e = 0; e < ui->lPlayers->count(); ++e)
+		{
+			if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum())
+			{
+				ui->lPlayers->item(e)->setCheckState(Qt::Checked);
+				break;
+			}
+		}
+	}
 }
 }
 
 
 void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget)
 void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget)

+ 112 - 4
mapeditor/inspector/rewardswidget.ui

@@ -487,6 +487,12 @@
             <property name="currentIndex">
             <property name="currentIndex">
              <number>0</number>
              <number>0</number>
             </property>
             </property>
+            <property name="elideMode">
+             <enum>Qt::ElideNone</enum>
+            </property>
+            <property name="usesScrollButtons">
+             <bool>true</bool>
+            </property>
             <property name="tabBarAutoHide">
             <property name="tabBarAutoHide">
              <bool>true</bool>
              <bool>true</bool>
             </property>
             </property>
@@ -843,7 +849,7 @@
                  <bool>false</bool>
                  <bool>false</bool>
                 </attribute>
                 </attribute>
                 <attribute name="verticalHeaderDefaultSectionSize">
                 <attribute name="verticalHeaderDefaultSectionSize">
-                 <number>21</number>
+                 <number>24</number>
                 </attribute>
                 </attribute>
                 <column>
                 <column>
                  <property name="text">
                  <property name="text">
@@ -1185,6 +1191,12 @@
             <property name="currentIndex">
             <property name="currentIndex">
              <number>0</number>
              <number>0</number>
             </property>
             </property>
+            <property name="elideMode">
+             <enum>Qt::ElideNone</enum>
+            </property>
+            <property name="usesScrollButtons">
+             <bool>true</bool>
+            </property>
             <property name="tabBarAutoHide">
             <property name="tabBarAutoHide">
              <bool>true</bool>
              <bool>true</bool>
             </property>
             </property>
@@ -1229,7 +1241,7 @@
                  <bool>false</bool>
                  <bool>false</bool>
                 </attribute>
                 </attribute>
                 <attribute name="verticalHeaderDefaultSectionSize">
                 <attribute name="verticalHeaderDefaultSectionSize">
-                 <number>21</number>
+                 <number>24</number>
                 </attribute>
                 </attribute>
                 <column/>
                 <column/>
                 <column/>
                 <column/>
@@ -1333,7 +1345,7 @@
                  <bool>false</bool>
                  <bool>false</bool>
                 </attribute>
                 </attribute>
                 <attribute name="verticalHeaderDefaultSectionSize">
                 <attribute name="verticalHeaderDefaultSectionSize">
-                 <number>21</number>
+                 <number>24</number>
                 </attribute>
                 </attribute>
                 <column/>
                 <column/>
                 <column/>
                 <column/>
@@ -1429,7 +1441,7 @@
                  <bool>false</bool>
                  <bool>false</bool>
                 </attribute>
                 </attribute>
                 <attribute name="verticalHeaderDefaultSectionSize">
                 <attribute name="verticalHeaderDefaultSectionSize">
-                 <number>21</number>
+                 <number>24</number>
                 </attribute>
                 </attribute>
                 <column/>
                 <column/>
                 <column/>
                 <column/>
@@ -1437,6 +1449,102 @@
               </item>
               </item>
              </layout>
              </layout>
             </widget>
             </widget>
+            <widget class="QWidget" name="tab">
+             <attribute name="title">
+              <string>Heroes</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_29">
+              <property name="leftMargin">
+               <number>3</number>
+              </property>
+              <property name="topMargin">
+               <number>3</number>
+              </property>
+              <property name="rightMargin">
+               <number>3</number>
+              </property>
+              <property name="bottomMargin">
+               <number>3</number>
+              </property>
+              <item>
+               <widget class="QListWidget" name="lHeroes">
+                <property name="editTriggers">
+                 <set>QAbstractItemView::NoEditTriggers</set>
+                </property>
+                <property name="selectionMode">
+                 <enum>QAbstractItemView::NoSelection</enum>
+                </property>
+                <property name="isWrapping" stdset="0">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+            <widget class="QWidget" name="tab_2">
+             <attribute name="title">
+              <string>Hero classes</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_30">
+              <property name="leftMargin">
+               <number>3</number>
+              </property>
+              <property name="topMargin">
+               <number>3</number>
+              </property>
+              <property name="rightMargin">
+               <number>3</number>
+              </property>
+              <property name="bottomMargin">
+               <number>3</number>
+              </property>
+              <item>
+               <widget class="QListWidget" name="lHeroClasses">
+                <property name="editTriggers">
+                 <set>QAbstractItemView::NoEditTriggers</set>
+                </property>
+                <property name="selectionMode">
+                 <enum>QAbstractItemView::NoSelection</enum>
+                </property>
+                <property name="isWrapping" stdset="0">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+            <widget class="QWidget" name="tab_15">
+             <attribute name="title">
+              <string>Players</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_31">
+              <property name="leftMargin">
+               <number>3</number>
+              </property>
+              <property name="topMargin">
+               <number>3</number>
+              </property>
+              <property name="rightMargin">
+               <number>3</number>
+              </property>
+              <property name="bottomMargin">
+               <number>3</number>
+              </property>
+              <item>
+               <widget class="QListWidget" name="lPlayers">
+                <property name="editTriggers">
+                 <set>QAbstractItemView::NoEditTriggers</set>
+                </property>
+                <property name="selectionMode">
+                 <enum>QAbstractItemView::NoSelection</enum>
+                </property>
+                <property name="isWrapping" stdset="0">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </widget>
            </widget>
            </widget>
           </item>
           </item>
          </layout>
          </layout>

+ 0 - 2
server/CGameHandler.cpp

@@ -1095,8 +1095,6 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	if (isInTheMap(guardPos))
 	if (isInTheMap(guardPos))
 		guardian = getTile(guardPos)->visitableObjects.back();
 		guardian = getTile(guardPos)->visitableObjects.back();
 
 
-	assert(guardian == nullptr || dynamic_cast<CGCreature*>(guardian) != nullptr);
-
 	const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
 	const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
 	const bool disembarking = h->boat
 	const bool disembarking = h->boat
 		&& t.terType->isLand()
 		&& t.terType->isLand()