Преглед на файлове

New limiter based quests

nordsoft преди 2 години
родител
ревизия
1460541ee5

+ 2 - 5
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -63,9 +63,6 @@ TGoalVec CompleteQuest::decompose() const
 		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:
@@ -137,7 +134,7 @@ TGoalVec CompleteQuest::missionArt() const
 
 	CaptureObjectsBehavior findArts;
 
-	for(auto art : q.quest->m5arts)
+	for(auto art : q.quest->artifacts)
 	{
 		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
 	}
@@ -223,7 +220,7 @@ TGoalVec CompleteQuest::missionResources() const
 
 TGoalVec CompleteQuest::missionDestroyObj() const
 {
-	auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
+	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
 
 	if(!obj)
 		return CaptureObjectsBehavior(q.obj).decompose();

+ 8 - 11
AI/VCAI/Goals/CompleteQuest.cpp

@@ -54,9 +54,6 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
 			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:
@@ -137,7 +134,7 @@ TGoalVec CompleteQuest::missionArt() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto art : q.quest->m5arts)
+	for(auto art : q.quest->artifacts)
 	{
 		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
 	}
@@ -165,7 +162,7 @@ TGoalVec CompleteQuest::missionArmy() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto creature : q.quest->m6creatures)
+	for(auto creature : q.quest->creatures)
 	{
 		solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
 	}
@@ -179,7 +176,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
 
 	if(solutions.empty())
 	{
-		for(int i = 0; i < q.quest->m2stats.size(); ++i)
+		for(int i = 0; i < q.quest->primary.size(); ++i)
 		{
 			// TODO: library, school and other boost objects
 			logAi->debug("Don't know how to increase primary stat %d", i);
@@ -195,7 +192,7 @@ TGoalVec CompleteQuest::missionLevel() const
 
 	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->heroLevel);
 	}
 
 	return solutions;
@@ -227,10 +224,10 @@ TGoalVec CompleteQuest::missionResources() const
 		}
 		else
 		{
-			for(int i = 0; i < q.quest->m7resources.size(); ++i)
+			for(int i = 0; i < q.quest->resources.size(); ++i)
 			{
-				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
+				if(q.quest->resources[i])
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->resources[i])));
 			}
 		}
 	}
@@ -246,7 +243,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 {
 	TGoalVec solutions;
 
-	auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
+	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
 
 	if(!obj)
 		return ai->ah->howToVisitObj(q.obj);

+ 118 - 180
lib/mapObjects/CQuest.cpp

@@ -40,7 +40,7 @@ CQuest::CQuest():
 	missionType(MISSION_NONE),
 	progress(NOT_ACTIVE),
 	lastDay(-1),
-	m13489val(0),
+	killTarget(-1),
 	textOption(0),
 	completedOption(0),
 	stackDirection(0),
@@ -99,7 +99,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
 	ui32 count = 0;
 	ui32 slotsCount = 0;
 	bool hasExtraCreatures = false;
-	for(cre = q->m6creatures.begin(); cre != q->m6creatures.end(); ++cre)
+	for(cre = q->creatures.begin(); cre != q->creatures.end(); ++cre)
 	{
 		for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
 		{
@@ -121,110 +121,53 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
 
 bool CQuest::checkQuest(const CGHeroInstance * h) const
 {
-	switch (missionType)
+	if(!heroAllowed(h))
+		return false;
+	
+	if(killTarget >= 0)
 	{
-		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;
-			return false;
-		case MISSION_ART:
-		{
-			// if the object was deserialized
-			if(artifactsRequirements.empty())
-				for(const auto & id : m5arts)
-					++artifactsRequirements[id];
-
-			size_t reqSlots = 0;
-			for(const auto & elem : artifactsRequirements)
-			{
-				// 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(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:
+		if(!CGHeroInstance::cb->getObjByQuestIdentifier(killTarget))
 			return false;
 	}
+	
+	return true;
 }
 
 void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
 {
-	switch (missionType)
+	for(auto & elem : artifacts)
 	{
-		case CQuest::MISSION_ART:
-			for(auto & elem : m5arts)
-			{
-				if(h->hasArt(elem))
-				{
-					cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
-				}
-				else
-				{
-					const auto * assembly = h->getAssemblyByConstituent(elem);
-					assert(assembly);
-					auto parts = assembly->getPartsInfo();
+		if(h->hasArt(elem))
+		{
+			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)));
+			// Remove the assembly
+			cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));
 
-					// Disassemble this backpack artifact
-					for(const auto & ci : parts)
-					{
-						if(ci.art->getTypeId() != elem)
-							cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::BACKPACK_START);
-					}
-				}
-			}
-			break;
-		case CQuest::MISSION_ARMY:
-				cb->takeCreatures(h->id, m6creatures);
-			break;
-		case CQuest::MISSION_RESOURCES:
-			for (int i = 0; i < 7; ++i)
+			// Disassemble this backpack artifact
+			for(const auto & ci : parts)
 			{
-				cb->giveResource(h->getOwner(), static_cast<EGameResID>(i), -static_cast<int>(m7resources[i]));
+				if(ci.art->getTypeId() != elem)
+					cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::BACKPACK_START);
 			}
-			break;
-		default:
-			break;
+		}
 	}
+			
+	cb->takeCreatures(h->id, creatures);
+	cb->giveResources(h->getOwner(), resources);
 }
 
 void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
 {
 	MetaString text;
 	bool failRequirements = (h ? !checkQuest(h) : true);
+	loadComponents(components, h);
 
 	if(firstVisit)
 	{
@@ -241,20 +184,18 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 	switch (missionType)
 	{
 		case MISSION_LEVEL:
-			components.emplace_back(Component::EComponentType::EXPERIENCE, 0, m13489val, 0);
 			if(!isCustom)
-				iwText.replaceNumber(m13489val);
+				iwText.replaceNumber(heroLevel); //TODO: heroLevel
 			break;
 		case MISSION_PRIMARY_STAT:
 		{
 			MetaString loot;
 			for(int i = 0; i < 4; ++i)
 			{
-				if(m2stats[i])
+				if(primary[i])
 				{
-					components.emplace_back(Component::EComponentType::PRIM_SKILL, i, m2stats[i], 0);
 					loot.appendRawString("%d %s");
-					loot.replaceNumber(m2stats[i]);
+					loot.replaceNumber(primary[i]);
 					loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
 				}
 			}
@@ -268,10 +209,8 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 				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());
+			if(!isCustom && !heroes.empty())
+				iwText.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated());
 			break;
 		case MISSION_KILL_CREATURE:
 			{
@@ -285,9 +224,8 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 		case MISSION_ART:
 		{
 			MetaString loot;
-			for(const auto & elem : m5arts)
+			for(const auto & elem : artifacts)
 			{
-				components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
 				loot.appendRawString("%s");
 				loot.replaceLocalString(EMetaText::ART_NAMES, elem);
 			}
@@ -298,9 +236,8 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 		case MISSION_ARMY:
 		{
 			MetaString loot;
-			for(const auto & elem : m6creatures)
+			for(const auto & elem : creatures)
 			{
-				components.emplace_back(elem);
 				loot.appendRawString("%s");
 				loot.replaceCreatureName(elem);
 			}
@@ -313,11 +250,10 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 			MetaString loot;
 			for(int i = 0; i < 7; ++i)
 			{
-				if(m7resources[i])
+				if(resources[i])
 				{
-					components.emplace_back(Component::EComponentType::RESOURCE, i, m7resources[i], 0);
 					loot.appendRawString("%d %s");
-					loot.replaceNumber(m7resources[i]);
+					loot.replaceNumber(resources[i]);
 					loot.replaceLocalString(EMetaText::RES_NAMES, i);
 				}
 			}
@@ -326,9 +262,8 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 		}
 			break;
 		case MISSION_PLAYER:
-			components.emplace_back(Component::EComponentType::FLAG, m13489val, 0, 0);
-			if(!isCustom)
-				iwText.replaceLocalString(EMetaText::COLOR, m13489val);
+			if(!isCustom && !players.empty())
+				iwText.replaceLocalString(EMetaText::COLOR, players.front());
 			break;
 	}
 }
@@ -349,17 +284,17 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 	switch(missionType)
 	{
 		case MISSION_LEVEL:
-			ms.replaceNumber(m13489val);
+			ms.replaceNumber(heroLevel);
 			break;
 		case MISSION_PRIMARY_STAT:
 			{
 				MetaString loot;
 				for (int i = 0; i < 4; ++i)
 				{
-					if (m2stats[i])
+					if (primary[i])
 					{
 						loot.appendRawString("%d %s");
-						loot.replaceNumber(m2stats[i]);
+						loot.replaceNumber(primary[i]);
 						loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
 					}
 				}
@@ -375,7 +310,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 		case MISSION_ART:
 			{
 				MetaString loot;
-				for(const auto & elem : m5arts)
+				for(const auto & elem : artifacts)
 				{
 					loot.appendRawString("%s");
 					loot.replaceLocalString(EMetaText::ART_NAMES, elem);
@@ -386,7 +321,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 		case MISSION_ARMY:
 			{
 				MetaString loot;
-				for(const auto & elem : m6creatures)
+				for(const auto & elem : creatures)
 				{
 					loot.appendRawString("%s");
 					loot.replaceCreatureName(elem);
@@ -399,10 +334,10 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 				MetaString loot;
 				for (int i = 0; i < 7; ++i)
 				{
-					if (m7resources[i])
+					if (resources[i])
 					{
 						loot.appendRawString("%d %s");
-						loot.replaceNumber(m7resources[i]);
+						loot.replaceNumber(resources[i]);
 						loot.replaceLocalString(EMetaText::RES_NAMES, i);
 					}
 				}
@@ -410,10 +345,10 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 			}
 			break;
 		case MISSION_HERO:
-			ms.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
+			ms.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated());
 			break;
 		case MISSION_PLAYER:
-			ms.replaceRawString(VLC->generaltexth->colors[m13489val]);
+			ms.replaceRawString(VLC->generaltexth->colors[players.front()]);
 			break;
 		default:
 			break;
@@ -427,18 +362,18 @@ void CQuest::getCompletionText(MetaString &iwText) const
 	{
 		case CQuest::MISSION_LEVEL:
 			if (!isCustomComplete)
-				iwText.replaceNumber(m13489val);
+				iwText.replaceNumber(heroLevel);
 			break;
 		case CQuest::MISSION_PRIMARY_STAT:
 		{
 			MetaString loot;
-			assert(m2stats.size() <= 4);
-			for (int i = 0; i < m2stats.size(); ++i)
+			assert(primary.size() <= 4);
+			for (int i = 0; i < primary.size(); ++i)
 			{
-				if (m2stats[i])
+				if (primary[i])
 				{
 					loot.appendRawString("%d %s");
-					loot.replaceNumber(m2stats[i]);
+					loot.replaceNumber(primary[i]);
 					loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
 				}
 			}
@@ -449,7 +384,7 @@ void CQuest::getCompletionText(MetaString &iwText) const
 		case CQuest::MISSION_ART:
 		{
 			MetaString loot;
-			for(const auto & elem : m5arts)
+			for(const auto & elem : artifacts)
 			{
 				loot.appendRawString("%s");
 				loot.replaceLocalString(EMetaText::ART_NAMES, elem);
@@ -461,7 +396,7 @@ void CQuest::getCompletionText(MetaString &iwText) const
 		case CQuest::MISSION_ARMY:
 		{
 			MetaString loot;
-			for(const auto & elem : m6creatures)
+			for(const auto & elem : creatures)
 			{
 				loot.appendRawString("%s");
 				loot.replaceCreatureName(elem);
@@ -475,10 +410,10 @@ void CQuest::getCompletionText(MetaString &iwText) const
 			MetaString loot;
 			for (int i = 0; i < 7; ++i)
 			{
-				if (m7resources[i])
+				if (resources[i])
 				{
 					loot.appendRawString("%d %s");
-					loot.replaceNumber(m7resources[i]);
+					loot.replaceNumber(resources[i]);
 					loot.replaceLocalString(EMetaText::RES_NAMES, i);
 				}
 			}
@@ -492,22 +427,16 @@ void CQuest::getCompletionText(MetaString &iwText) const
 				addReplacements(iwText, completedText.toString());
 			break;
 		case MISSION_HERO:
-			if (!isCustomComplete)
-				iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
+			if (!isCustomComplete && !heroes.empty())
+				iwText.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated());
 			break;
 		case MISSION_PLAYER:
-			if (!isCustomComplete)
-				iwText.replaceRawString(VLC->generaltexth->colors[m13489val]);
+			if (!isCustomComplete && !players.empty())
+				iwText.replaceRawString(VLC->generaltexth->colors[players.front()]);
 			break;
 	}
 }
 
-void CQuest::addArtifactID(const ArtifactID & id)
-{
-	m5arts.push_back(id);
-	++artifactsRequirements[id];
-}
-
 void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
 {
 	auto q = handler.enterStruct(fieldName);
@@ -522,6 +451,8 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
 		isCustomNext = !nextVisitText.empty();
 		isCustomComplete = !completedText.empty();
 	}
+	
+	Rewardable::Limiter::serializeJson(handler);
 
 	static const std::vector<std::string> MISSION_TYPE_JSON =
 	{
@@ -530,57 +461,64 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
 
 	handler.serializeEnum("missionType", missionType, Emission::MISSION_NONE, MISSION_TYPE_JSON);
 	handler.serializeInt("timeLimit", lastDay, -1);
+	handler.serializeInstance<int>("killTarget", killTarget, -1);
 
-	switch (missionType)
+	if(!handler.saving)
 	{
-	case MISSION_NONE:
-		break;
-	case MISSION_LEVEL:
-		handler.serializeInt("heroLevel", m13489val, -1);
-		break;
-	case MISSION_PRIMARY_STAT:
-		{
-			auto primarySkills = handler.enterStruct("primarySkills");
-			if(!handler.saving)
-				m2stats.resize(GameConstants::PRIMARY_SKILLS);
-
-			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
-				handler.serializeInt(NPrimarySkill::names[i], m2stats[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:
-		{
-			auto a = handler.enterArray("creatures");
-			a.serializeStruct(m6creatures);
-		}
-		break;
-	case MISSION_RESOURCES:
+		switch (missionType)
 		{
-			auto r = handler.enterStruct("resources");
+			case MISSION_NONE:
+				break;
+			case MISSION_LEVEL:
+				handler.serializeInt("heroLevel", heroLevel, -1);
+				break;
+			case MISSION_PRIMARY_STAT:
+				{
+					auto primarySkills = handler.enterStruct("primarySkills");
+					for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
+						handler.serializeInt(NPrimarySkill::names[i], primary[i], 0);
+				}
+				break;
+			case MISSION_KILL_HERO:
+			case MISSION_KILL_CREATURE:
+				break;
+			case MISSION_ART:
+				handler.serializeIdArray<ArtifactID>("artifacts", artifacts);
+				break;
+			case MISSION_ARMY:
+			{
+				auto a = handler.enterArray("creatures");
+				a.serializeStruct(creatures);
+			}
+				break;
+			case MISSION_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], resources[idx], 0);
+				}
+			}
+				break;
+			case MISSION_HERO:
+			{
+				ui32 temp;
+				handler.serializeId<ui32, ui32, HeroTypeID>("hero", temp, 0);
+				heroes.emplace_back(temp);
+			}
+				break;
+			case MISSION_PLAYER:
 			{
-				handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], m7resources[idx], 0);
+				ui32 temp;
+				handler.serializeId<ui32, ui32, PlayerColor>("player", temp, PlayerColor::NEUTRAL);
+				players.emplace_back(temp);
 			}
+				break;
+			default:
+				logGlobal->error("Invalid quest mission type");
+				break;
 		}
-		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;
 	}
 
 }
@@ -803,7 +741,7 @@ int CGSeerHut::checkDirection() 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)
 		return nullptr;
 	assert(o && (o->ID == Obj::HERO  ||  o->ID == Obj::PRISON));
@@ -812,7 +750,7 @@ const CGHeroInstance * CGSeerHut::getHeroToKill(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)
 		return nullptr;
 	assert(o && o->ID == Obj::MONSTER);

+ 3 - 15
lib/mapObjects/CQuest.h

@@ -17,10 +17,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class CGCreature;
 
-class DLL_LINKAGE CQuest final
+class DLL_LINKAGE CQuest: public Rewardable::Limiter
 {
-	mutable std::unordered_map<ArtifactID, unsigned, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
-
 public:
 	enum Emission {
 		MISSION_NONE = 0,
@@ -54,12 +52,7 @@ public:
 	Emission missionType;
 	Eprogress progress;
 	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;
+	int killTarget;
 
 	// following fields are used only for kill creature/hero missions, the original
 	// objects became inaccessible after their removal, so we need to store info
@@ -85,7 +78,6 @@ public:
 	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
 	virtual void completeQuest(IGameCallback *, const CGHeroInstance * h) const;
 	virtual void addReplacements(MetaString &out, const std::string &base) const;
-	void addArtifactID(const ArtifactID & id);
 
 	bool operator== (const CQuest & quest) const
 	{
@@ -98,11 +90,6 @@ public:
 		h & missionType;
 		h & progress;
 		h & lastDay;
-		h & m13489val;
-		h & m2stats;
-		h & m5arts;
-		h & m6creatures;
-		h & m7resources;
 		h & textOption;
 		h & stackToKill;
 		h & stackDirection;
@@ -115,6 +102,7 @@ public:
 		h & isCustomNext;
 		h & isCustomComplete;
 		h & completedOption;
+		h & static_cast<Rewardable::Limiter&>(*this);
 	}
 
 	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);

+ 15 - 19
lib/mapping/MapFormatH3M.cpp

@@ -1871,7 +1871,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 		if(artID != ArtifactID::NONE)
 		{
 			//not none quest
-			hut->quest->addArtifactID(artID);
+			hut->quest->artifacts.push_back(artID);
 			hut->quest->missionType = CQuest::MISSION_ART;
 		}
 		else
@@ -1986,10 +1986,9 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 			return;
 		case CQuest::MISSION_PRIMARY_STAT:
 		{
-			guard->quest->m2stats.resize(4);
 			for(int x = 0; x < 4; ++x)
 			{
-				guard->quest->m2stats[x] = reader->readUInt8();
+				guard->quest->primary[x] = reader->readUInt8();
 			}
 		}
 		break;
@@ -1997,7 +1996,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 		case CQuest::MISSION_KILL_HERO:
 		case CQuest::MISSION_KILL_CREATURE:
 		{
-			guard->quest->m13489val = reader->readUInt32();
+			guard->quest->killTarget = reader->readUInt32();
 			break;
 		}
 		case CQuest::MISSION_ART:
@@ -2006,7 +2005,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 			for(int yy = 0; yy < artNumber; ++yy)
 			{
 				auto artid = reader->readArtifact();
-				guard->quest->addArtifactID(artid);
+				guard->quest->artifacts.push_back(artid);
 				map->allowedArtifact[artid] = false; //these are unavailable for random generation
 			}
 			break;
@@ -2014,29 +2013,29 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 		case CQuest::MISSION_ARMY:
 		{
 			int typeNumber = reader->readUInt8();
-			guard->quest->m6creatures.resize(typeNumber);
+			guard->quest->creatures.resize(typeNumber);
 			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->creatures[hh].type = VLC->creh->objects[reader->readCreature()];
+				guard->quest->creatures[hh].count = reader->readUInt16();
 			}
 			break;
 		}
 		case CQuest::MISSION_RESOURCES:
 		{
 			for(int x = 0; x < 7; ++x)
-				guard->quest->m7resources[x] = reader->readUInt32();
+				guard->quest->resources[x] = reader->readUInt32();
 
 			break;
 		}
 		case CQuest::MISSION_HERO:
 		{
-			guard->quest->m13489val = reader->readHero().getNum();
+			guard->quest->heroes.push_back(reader->readHero());
 			break;
 		}
 		case CQuest::MISSION_PLAYER:
 		{
-			guard->quest->m13489val = reader->readPlayer().getNum();
+			guard->quest->players.push_back(reader->readPlayer());
 			break;
 		}
 		case CQuest::MISSION_HOTA_MULTI:
@@ -2045,22 +2044,19 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
 
 			if(missionSubID == 0)
 			{
-				guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_HERO_CLASS;
+				guard->quest->missionType = CQuest::MISSION_HOTA_HERO_CLASS;
 				std::set<HeroClassID> heroClasses;
 				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->heroClasses.push_back(hc);
 				break;
 			}
 			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);
+				guard->quest->missionType = CQuest::MISSION_HOTA_REACH_DATE;
+				guard->quest->daysPassed = reader->readUInt32();
 				break;
 			}
-			assert(0);
 			break;
 		}
 		default:

+ 48 - 0
lib/rewardable/Limiter.cpp

@@ -146,6 +146,54 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 	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)
+		comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0);
+
+	if (manaPoints || manaPercentage > 0)
+		comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, 0, 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)
 {
 	handler.serializeInt("dayOfWeek", dayOfWeek);

+ 6 - 1
lib/rewardable/Limiter.h

@@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class CGHeroInstance;
 class CStackBasicDescriptor;
+struct Component;
 
 namespace Rewardable {
 
@@ -78,9 +79,13 @@ struct DLL_LINKAGE Limiter
 	LimitersList noneOf;
 
 	Limiter();
-	~Limiter();
+	virtual ~Limiter();
 
 	bool heroAllowed(const CGHeroInstance * hero) const;
+	
+	/// Generates list of components that describes reward for a specific hero
+	virtual void loadComponents(std::vector<Component> & comps,
+								const CGHeroInstance * h) const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 1
lib/rewardable/Reward.h

@@ -86,7 +86,7 @@ struct DLL_LINKAGE Reward
 	si32 calculateManaPoints(const CGHeroInstance * h) const;
 
 	Reward();
-	~Reward();
+	virtual ~Reward();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

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

@@ -464,7 +464,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				obj->quest->missionType = CQuest::MISSION_ART;
 				
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
+				obj->quest->artifacts.push_back(artid);
 				obj->quest->lastDay = -1;
 				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
 				
@@ -515,7 +515,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
+				obj->quest->artifacts.push_back(artid);
 				obj->quest->lastDay = -1;
 				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
 				
@@ -540,7 +540,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->addArtifactID(artid);
+				obj->quest->artifacts.push_back(artid);
 				obj->quest->lastDay = -1;
 				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;