Browse Source

Rewardable seer hut and quest gate

nordsoft 2 năm trước cách đây
mục cha
commit
ef3f0174dd

+ 28 - 152
lib/mapObjects/CQuest.cpp

@@ -378,7 +378,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 	}
 }
 
-void CQuest::getCompletionText(MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+void CQuest::getCompletionText(MetaString &iwText) const
 {
 	iwText.appendRawString(completedText);
 	switch(missionType)
@@ -567,11 +567,17 @@ void CGSeerHut::init(CRandomGenerator & rand)
 	seerName = VLC->generaltexth->translate(seerNameID);
 	quest->textOption = rand.nextInt(2);
 	quest->completedOption = rand.nextInt(1, 3);
+	
+	configuration.canRefuse = true;
+	configuration.visitMode = Rewardable::EVisitMode::VISIT_ONCE;
+	configuration.selectMode = Rewardable::ESelectMode::SELECT_PLAYER;
 }
 
 void CGSeerHut::initObj(CRandomGenerator & rand)
 {
 	init(rand);
+	
+	CRewardableObject::initObj(rand);
 
 	quest->progress = CQuest::NOT_ACTIVE;
 	if(quest->missionType)
@@ -590,6 +596,10 @@ void CGSeerHut::initObj(CRandomGenerator & rand)
 		quest->progress = CQuest::COMPLETE;
 		quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption];
 	}
+	
+	quest->getCompletionText(configuration.onSelect);
+	for(auto & i : configuration.info)
+		quest->getCompletionText(i.message);
 }
 
 void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
@@ -649,44 +659,6 @@ void IQuestObject::afterAddToMapCommon(CMap * map) const
 	map->addNewQuestInstance(quest);
 }
 
-void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
-{
-	quest->getCompletionText (text, components, isCustom, h);
-	switch(rewardType)
-	{
-	case EXPERIENCE:
-		components.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h->calculateXp(rVal)), 0);
-		break;
-	case MANA_POINTS:
-		components.emplace_back(Component::EComponentType::PRIM_SKILL, 5, rVal, 0);
-		break;
-	case MORALE_BONUS:
-		components.emplace_back(Component::EComponentType::MORALE, 0, rVal, 0);
-		break;
-	case LUCK_BONUS:
-		components.emplace_back(Component::EComponentType::LUCK, 0, rVal, 0);
-		break;
-	case RESOURCES:
-		components.emplace_back(Component::EComponentType::RESOURCE, rID, rVal, 0);
-		break;
-	case PRIMARY_SKILL:
-		components.emplace_back(Component::EComponentType::PRIM_SKILL, rID, rVal, 0);
-		break;
-	case SECONDARY_SKILL:
-		components.emplace_back(Component::EComponentType::SEC_SKILL, rID, rVal, 0);
-		break;
-	case ARTIFACT:
-		components.emplace_back(Component::EComponentType::ARTIFACT, rID, 0, 0);
-		break;
-	case SPELL:
-		components.emplace_back(Component::EComponentType::SPELL, rID, 0, 0);
-		break;
-	case CREATURE:
-		components.emplace_back(Component::EComponentType::CREATURE, rID, rVal, 0);
-		break;
-	}
-}
-
 void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
 {
 	switch(what)
@@ -699,6 +671,7 @@ void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
 
 void CGSeerHut::newTurn(CRandomGenerator & rand) const
 {
+	CRewardableObject::newTurn(rand);
 	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
 	{
 		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE);
@@ -738,12 +711,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 		}
 		if(!failRequirements) // propose completion, also on first visit
 		{
-			BlockingDialog bd (true, false);
-			bd.player = h->getOwner();
-
-			getCompletionText (bd.text, bd.components, isCustom, h);
-
-			cb->showBlockingDialog (&bd);
+			CRewardableObject::onHeroVisit(h);
 			return;
 		}
 	}
@@ -788,108 +756,9 @@ int CGSeerHut::checkDirection() const
 	}
 }
 
-void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
-{
-	if (accept)
-	{
-		switch (quest->missionType)
-		{
-			case CQuest::MISSION_ART:
-				for(auto & elem : quest->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();
-
-						// 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, quest->m6creatures);
-				break;
-			case CQuest::MISSION_RESOURCES:
-				for (int i = 0; i < 7; ++i)
-				{
-					cb->giveResource(h->getOwner(), static_cast<EGameResID>(i), -static_cast<int>(quest->m7resources[i]));
-				}
-				break;
-			default:
-				break;
-		}
-		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
-		completeQuest(h); //make sure to remove QuestGuard at the very end
-	}
-}
-
-void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
+void CGSeerHut::completeQuest() const //reward
 {
-	switch (rewardType)
-	{
-		case EXPERIENCE:
-		{
-			TExpType expVal = h->calculateXp(rVal);
-			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
-			break;
-		}
-		case MANA_POINTS:
-		{
-			cb->setManaPoints(h->id, h->mana+rVal);
-			break;
-		}
-		case MORALE_BONUS: case LUCK_BONUS:
-		{
-			Bonus hb(BonusDuration::ONE_WEEK, (rewardType == 3 ? BonusType::MORALE : BonusType::LUCK),
-				BonusSource::OBJECT, rVal, h->id.getNum(), "", -1);
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = hb;
-			cb->giveHeroBonus(&gb);
-		}
-			break;
-		case RESOURCES:
-			cb->giveResource(h->getOwner(), static_cast<EGameResID>(rID), rVal);
-			break;
-		case PRIMARY_SKILL:
-			cb->changePrimSkill(h, static_cast<PrimarySkill>(rID), rVal, false);
-			break;
-		case SECONDARY_SKILL:
-			cb->changeSecSkill(h, SecondarySkill(rID), rVal, false);
-			break;
-		case ARTIFACT:
-			cb->giveHeroNewArtifact(h, VLC->arth->objects[rID],ArtifactPosition::FIRST_AVAILABLE);
-			break;
-		case SPELL:
-		{
-			std::set<SpellID> spell;
-			spell.insert (SpellID(rID));
-			cb->changeSpells(h, true, spell);
-		}
-			break;
-		case CREATURE:
-			{
-				CCreatureSet creatures;
-				creatures.setCreature(SlotID(0), CreatureID(rID), rVal);
-				cb->giveCreatures(this, h, creatures, false);
-			}
-			break;
-		default:
-			break;
-	}
+	cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
 }
 
 const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
@@ -912,7 +781,9 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
 
 void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
 {
-	finishQuest(hero, answer);
+	CRewardableObject::blockingDialogAnswered(hero, answer);
+	if(answer)
+		completeQuest();
 }
 
 void CGSeerHut::afterAddToMap(CMap* map)
@@ -922,7 +793,7 @@ void CGSeerHut::afterAddToMap(CMap* map)
 
 void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	static const std::map<ERewardType, std::string> REWARD_MAP =
+	/*static const std::map<ERewardType, std::string> REWARD_MAP =
 	{
 		{NOTHING,		""},
 		{EXPERIENCE,	"experience"},
@@ -949,15 +820,16 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 		{"artifact",      ARTIFACT},
 		{"spell",         SPELL},
 		{"creature",      CREATURE}
-	};
+	};*/
 
 	//quest and reward
 	quest->serializeJson(handler, "quest");
+	CRewardableObject::serializeJsonOptions(handler);
 
 	//only one reward is supported
 	//todo: full reward format support after CRewardInfo integration
 
-	auto s = handler.enterStruct("reward");
+	/*auto s = handler.enterStruct("reward");
 	std::string fullIdentifier;
 	std::string metaTypeName;
 	std::string scope;
@@ -1079,7 +951,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 			}
 		}
 		handler.serializeInt(fullIdentifier, rVal);
-	}
+	}*/
 }
 
 void CGQuestGuard::init(CRandomGenerator & rand)
@@ -1087,9 +959,13 @@ void CGQuestGuard::init(CRandomGenerator & rand)
 	blockVisit = true;
 	quest->textOption = rand.nextInt(3, 5);
 	quest->completedOption = rand.nextInt(4, 5);
+	
+	configuration.info.push_back({});
+	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	configuration.canRefuse = true;
 }
 
-void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
+void CGQuestGuard::completeQuest() const
 {
 	cb->removeObject(this);
 }

+ 8 - 13
lib/mapObjects/CQuest.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
+#include "CRewardableObject.h"
 #include "../ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -80,7 +80,7 @@ public:
 	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 void getCompletionText (MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
+	virtual void getCompletionText(MetaString &text) const;
 	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;
@@ -138,13 +138,9 @@ protected:
 	void afterAddToMapCommon(CMap * map) const;
 };
 
-class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward
+class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject //army is used when giving reward
 {
 public:
-	enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE};
-	ERewardType rewardType = ERewardType::NOTHING;
-	si32 rID = -1; //reward ID
-	si32 rVal = -1; //reward value
 	std::string seerName;
 
 	void initObj(CRandomGenerator & rand) override;
@@ -159,9 +155,8 @@ public:
 	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
 	const CGCreature *getCreatureToKill(bool allowNull = false) const;
 	void getRolloverText (MetaString &text, bool onHover) const;
-	void getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
 	void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
-	virtual void completeQuest (const CGHeroInstance * h) const;
+	virtual void completeQuest() const;
 
 	void afterAddToMap(CMap * map) override;
 
@@ -169,9 +164,9 @@ public:
 	{
 		h & static_cast<CArmedInstance&>(*this);
 		h & static_cast<IQuestObject&>(*this);
-		h & rewardType;
-		h & rID;
-		h & rVal;
+		//h & rewardType;
+		//h & rID;
+		//h & rVal;
 		h & seerName;
 	}
 protected:
@@ -186,7 +181,7 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
 {
 public:
 	void init(CRandomGenerator & rand) override;
-	void completeQuest (const CGHeroInstance * h) const override;
+	void completeQuest() const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 1
lib/mapObjects/CRewardableObject.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../mapObjects/CArmedInstance.h"
+#include "CArmedInstance.h"
 #include "../rewardable/Interface.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 43 - 31
lib/mapping/MapFormatH3M.cpp

@@ -1842,70 +1842,78 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
 
 	if(hut->quest->missionType)
 	{
-		auto rewardType = static_cast<CGSeerHut::ERewardType>(reader->readUInt8());
-		hut->rewardType = rewardType;
+		auto rewardType = reader->readUInt8();
+		Rewardable::Reward reward;
 		switch(rewardType)
 		{
-			case CGSeerHut::EXPERIENCE:
+			case 0: //NOTHING
 			{
-				hut->rVal = reader->readUInt32();
+				// no-op
 				break;
 			}
-			case CGSeerHut::MANA_POINTS:
+			case 1: //EXPERIENCE
 			{
-				hut->rVal = reader->readUInt32();
+				reward.heroExperience = reader->readUInt32();
 				break;
 			}
-			case CGSeerHut::MORALE_BONUS:
+			case 2: //MANA POINTS:
 			{
-				hut->rVal = reader->readUInt8();
+				reward.manaDiff = reader->readUInt32();
 				break;
 			}
-			case CGSeerHut::LUCK_BONUS:
+			case 3: //MORALE_BONUS
 			{
-				hut->rVal = reader->readUInt8();
+				reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), hut->id);
 				break;
 			}
-			case CGSeerHut::RESOURCES:
+			case 4: //LUCK_BONUS
 			{
-				hut->rID = reader->readUInt8();
-				hut->rVal = reader->readUInt32();
-
-				assert(hut->rID < features.resourcesCount);
-				assert((hut->rVal & 0x00ffffff) == hut->rVal);
+				reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), hut->id);
 				break;
 			}
-			case CGSeerHut::PRIMARY_SKILL:
+			case 5: //RESOURCES
 			{
-				hut->rID = reader->readUInt8();
-				hut->rVal = reader->readUInt8();
+				auto rId = reader->readUInt8();
+				auto rVal = reader->readUInt32();
+
+				assert(rId < features.resourcesCount);
+				assert((rVal & 0x00ffffff) == rVal);
+				
+				reward.resources[rId] = rVal;
 				break;
 			}
-			case CGSeerHut::SECONDARY_SKILL:
+			case 6: //PRIMARY_SKILL
 			{
-				hut->rID = reader->readSkill();
-				hut->rVal = reader->readUInt8();
+				auto rId = reader->readUInt8();
+				auto rVal = reader->readUInt8();
+				
+				reward.primary.at(rId) = rVal;
 				break;
 			}
-			case CGSeerHut::ARTIFACT:
+			case 7: //SECONDARY_SKILL
 			{
-				hut->rID = reader->readArtifact();
+				auto rId = reader->readSkill();
+				auto rVal = reader->readUInt8();
+				
+				reward.secondary[rId] = rVal;
 				break;
 			}
-			case CGSeerHut::SPELL:
+			case 8: //ARTIFACT
 			{
-				hut->rID = reader->readSpell();
+				reward.artifacts.push_back(reader->readArtifact());
 				break;
 			}
-			case CGSeerHut::CREATURE:
+			case 9: //SPELL
 			{
-				hut->rID = reader->readCreature();
-				hut->rVal = reader->readUInt16();
+				reward.spells.push_back(reader->readSpell());
 				break;
 			}
-			case CGSeerHut::NOTHING:
+			case 10: //CREATURE
 			{
-				// no-op
+				auto rId = reader->readCreature();
+				auto rVal = reader->readUInt16();
+				
+				reward.creatures.emplace_back(rId, rVal);
 				break;
 			}
 			default:
@@ -1913,6 +1921,10 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
 				assert(0);
 			}
 		}
+		
+		hut->configuration.info.push_back({});
+		hut->configuration.info.back().reward = reward;
+		hut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 	}
 	else
 	{

+ 18 - 10
lib/rmg/modificators/TreasurePlacer.cpp

@@ -441,9 +441,12 @@ void TreasurePlacer::addAllPossibleObjects()
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
-				obj->rewardType = CGSeerHut::CREATURE;
-				obj->rID = creature->getId();
-				obj->rVal = creaturesAmount;
+				
+				Rewardable::Reward reward;
+				reward.creatures.emplace_back(creature->getId(), creaturesAmount);
+				obj->configuration.info.push_back({});
+				obj->configuration.info.back().reward = reward;
+				obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				
@@ -490,10 +493,12 @@ void TreasurePlacer::addAllPossibleObjects()
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
-
-				obj->rewardType = CGSeerHut::EXPERIENCE;
-				obj->rID = 0; //unitialized?
-				obj->rVal = generator.getConfig().questRewardValues[i];
+				
+				Rewardable::Reward reward;
+				reward.heroExperience = generator.getConfig().questRewardValues[i];
+				obj->configuration.info.push_back({});
+				obj->configuration.info.back().reward = reward;
+				obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
@@ -513,9 +518,12 @@ void TreasurePlacer::addAllPossibleObjects()
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
-				obj->rewardType = CGSeerHut::RESOURCES;
-				obj->rID = GameResID(EGameResID::GOLD);
-				obj->rVal = generator.getConfig().questRewardValues[i];
+				
+				Rewardable::Reward reward;
+				reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i];
+				obj->configuration.info.push_back({});
+				obj->configuration.info.back().reward = reward;
+				obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();