Forráskód Böngészése

Pandora and events work as rewardable object

nordsoft 2 éve
szülő
commit
c1c13cfafb

+ 144 - 364
lib/mapObjects/CGPandoraBox.cpp

@@ -25,297 +25,117 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-void CGPandoraBox::initObj(CRandomGenerator & rand)
+void CGPandoraBox::init()
 {
-	blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class)
-	hasGuardians = stacks.size();
+	blockVisit = true;
+	
+	for(auto & i : configuration.info)
+		i.reward.removeObject = true;
 }
 
-void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
+void CGPandoraBox::initObj(CRandomGenerator & rand)
 {
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.text.appendLocalString (EMetaText::ADVOB_TXT, 14);
-		cb->showBlockingDialog (&bd);
+	init();
+	
+	CRewardableObject::initObj(rand);
 }
 
-void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const
+void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
 {
-	afterSuccessfulVisit();
-
-	InfoWindow iw;
-	iw.type = EInfoWindowMode::AUTO;
-	iw.player = h->getOwner();
-
-	bool changesPrimSkill = false;
-	for(const auto & elem : primskills)
+	auto setText = [](MetaString & text, int tId, const CGHeroInstance * h)
 	{
-		if(elem)
-		{
-			changesPrimSkill = true;
-			break;
-		}
-	}
-
-	std::vector<std::pair<SecondarySkill, ui8>> unpossessedAbilities; //ability + ability level
-	int abilitiesRequiringSlot = 0;
-
-	//filter out unnecessary secondary skills
-	for (int i = 0; i < abilities.size(); i++)
+		text.appendLocalString(EMetaText::ADVOB_TXT, tId);
+		text.replaceRawString(h->getNameTranslated());
+	};
+	
+	for(auto i : getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT))
 	{
-		int curLev = h->getSecSkillLevel(abilities[i]);
-		bool abilityCanUseSlot = !curLev && ((h->secSkills.size() + abilitiesRequiringSlot) < GameConstants::SKILL_PER_HERO); //limit new abilities to number of slots
-
-		if (abilityCanUseSlot)
-			abilitiesRequiringSlot++;
-
-		if ((curLev && curLev < abilityLevels[i]) || abilityCanUseSlot)
+		MetaString txt;
+		auto & r = configuration.info[i];
+		
+		if(r.reward.spells.size() == 1)
+			setText(txt, 184, h);
+		else if(!r.reward.spells.empty())
+			setText(txt, 188, h);
+		
+		if(r.reward.heroExperience || !r.reward.secondary.empty())
+			setText(txt, 175, h);
+		
+		for(int ps : r.reward.primary)
 		{
-			unpossessedAbilities.emplace_back(abilities[i], abilityLevels[i]);
+			if(ps)
+			{
+				setText(txt, 175, h);
+				break;
+			}
 		}
-	}
-
-	if(gainedExp || changesPrimSkill || !unpossessedAbilities.empty())
-	{
-		TExpType expVal = h->calculateXp(gainedExp);
-		//getText(iw,afterBattle,175,h); //wtf?
-		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 175); //%s learns something
-		iw.text.replaceRawString(h->getNameTranslated());
-
-		if(expVal)
-			iw.components.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(expVal), 0);
-
-		for(int i=0; i<primskills.size(); i++)
-			if(primskills[i])
-				iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, i, primskills[i], 0);
-
-		for(const auto & abilityData : unpossessedAbilities)
-			iw.components.emplace_back(Component::EComponentType::SEC_SKILL, abilityData.first, abilityData.second, 0);
-
-		cb->showInfoDialog(&iw);
-
-		//give sec skills
-		for(const auto & abilityData : unpossessedAbilities)
-			cb->changeSecSkill(h, abilityData.first, abilityData.second, true);
-
-		assert(h->secSkills.size() <= GameConstants::SKILL_PER_HERO);
-
-		//give prim skills
-		for(int i=0; i<primskills.size(); i++)
-			if(primskills[i])
-				cb->changePrimSkill(h,static_cast<PrimarySkill>(i),primskills[i],false);
-
-		assert(!cb->isVisitCoveredByAnotherQuery(this, h));
-
-		//give exp
-		if(expVal)
-			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
-	}
-	//else { } //TODO:Create information that box was empty for now, and deliver to CGPandoraBox::giveContentsAfterExp or refactor
-
-	if(!cb->isVisitCoveredByAnotherQuery(this, h))
-		giveContentsAfterExp(h);
-	//Otherwise continuation occurs via post-level-up callback.
-}
-
-void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
-{
-	bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message
-
-	std::string msg = message; //in case box is removed in the meantime
-	InfoWindow iw;
-	iw.type = EInfoWindowMode::AUTO;
-	iw.player = h->getOwner();
-
-	//TODO: reuse this code for Scholar skill
-	if(!spells.empty())
-	{
-		std::set<SpellID> spellsToGive;
-
-		auto i = spells.cbegin();
-		while (i != spells.cend())
+		
+		if(r.reward.manaDiff < 0)
+			setText(txt, 176, h);
+		if(r.reward.manaDiff > 0)
+			setText(txt, 177, h);
+		
+		for(auto b : r.reward.bonuses)
 		{
-			iw.components.clear();
-			iw.text.clear();
-			spellsToGive.clear();
-
-			for (; i != spells.cend(); i++)
+			if(b.type == BonusType::MORALE)
 			{
-				const auto * spell = (*i).toSpell(VLC->spells());
-				if(h->canLearnSpell(spell))
-				{
-					iw.components.emplace_back(Component::EComponentType::SPELL, *i, 0, 0);
-					spellsToGive.insert(*i);
-				}
-				if(spellsToGive.size() == 8) //display up to 8 spells at once
-				{
-					break;
-				}
+				if(b.val < 0)
+					setText(txt, 178, h);
+				if(b.val > 0)
+					setText(txt, 179, h);
 			}
-			if (!spellsToGive.empty())
+			if(b.type == BonusType::LUCK)
 			{
-				if (spellsToGive.size() > 1)
-				{
-					iw.text.appendLocalString(EMetaText::ADVOB_TXT, 188); //%s learns spells
-				}
-				else
-				{
-					iw.text.appendLocalString(EMetaText::ADVOB_TXT, 184); //%s learns a spell
-				}
-				iw.text.replaceRawString(h->getNameTranslated());
-				cb->changeSpells(h, true, spellsToGive);
-				cb->showInfoDialog(&iw);
+				if(b.val < 0)
+					setText(txt, 180, h);
+				if(b.val > 0)
+					setText(txt, 181, h);
 			}
 		}
-	}
-
-	if(manaDiff)
-	{
-		getText(iw,hadGuardians,manaDiff,176,177,h);
-		iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 5, manaDiff, 0);
-		cb->showInfoDialog(&iw);
-		cb->setManaPoints(h->id, h->mana + manaDiff);
-	}
-
-	if(moraleDiff)
-	{
-		getText(iw,hadGuardians,moraleDiff,178,179,h);
-		iw.components.emplace_back(Component::EComponentType::MORALE, 0, moraleDiff, 0);
-		cb->showInfoDialog(&iw);
-		GiveBonus gb;
-		gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::MORALE,BonusSource::OBJECT,moraleDiff,id.getNum(),"");
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-	}
-
-	if(luckDiff)
-	{
-		getText(iw,hadGuardians,luckDiff,180,181,h);
-		iw.components.emplace_back(Component::EComponentType::LUCK, 0, luckDiff, 0);
-		cb->showInfoDialog(&iw);
-		GiveBonus gb;
-		gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::LUCK,BonusSource::OBJECT,luckDiff,id.getNum(),"");
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-	}
-
-	iw.components.clear();
-	iw.text.clear();
-	for(int i=0; i<resources.size(); i++)
-	{
-		if(resources[i] < 0)
-			iw.components.emplace_back(Component::EComponentType::RESOURCE, i, resources[i], 0);
-	}
-	if(!iw.components.empty())
-	{
-		getText(iw,hadGuardians,182,h);
-		cb->showInfoDialog(&iw);
-	}
-
-	iw.components.clear();
-	iw.text.clear();
-	for(int i=0; i<resources.size(); i++)
-	{
-		if(resources[i] > 0)
-			iw.components.emplace_back(Component::EComponentType::RESOURCE, i, resources[i], 0);
-	}
-	if(!iw.components.empty())
-	{
-		getText(iw,hadGuardians,183,h);
-		cb->showInfoDialog(&iw);
-	}
-
-	iw.components.clear();
-	// 	getText(iw,afterBattle,183,h);
-	iw.text.appendLocalString(EMetaText::ADVOB_TXT, 183); //% has found treasure
-	iw.text.replaceRawString(h->getNameTranslated());
-	for(const auto & elem : artifacts)
-	{
-		iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
-		if(iw.components.size() >= 14)
+		
+		for(auto res : r.reward.resources)
 		{
-			cb->showInfoDialog(&iw);
-			iw.components.clear();
-			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 183); //% has found treasure - once more?
-			iw.text.replaceRawString(h->getNameTranslated());
+			if(res < 0)
+				setText(txt, 182, h);
+			if(res > 0)
+				setText(txt, 183, h);
 		}
-	}
-	if(!iw.components.empty())
-	{
-		cb->showInfoDialog(&iw);
-	}
-
-	cb->giveResources(h->getOwner(), resources);
-
-	for(const auto & elem : artifacts)
-		cb->giveHeroNewArtifact(h, VLC->arth->objects[elem],ArtifactPosition::FIRST_AVAILABLE);
-
-	iw.components.clear();
-	iw.text.clear();
-
-	if(creatures.stacksCount())
-	{ //this part is taken straight from creature bank
-		MetaString loot;
-		for(const auto & elem : creatures.Slots())
-		{ //build list of joined creatures
-			iw.components.emplace_back(*elem.second);
-			loot.appendRawString("%s");
-			loot.replaceCreatureName(*elem.second);
+		
+		if(!r.reward.artifacts.empty())
+			setText(txt, 183, h);
+		
+		if(!r.reward.creatures.empty())
+		{
+			MetaString loot;
+			for(auto c : r.reward.creatures)
+			{
+				loot.appendRawString("%s");
+				loot.replaceCreatureName(c);
+			}
+			
+			if(r.reward.creatures.size() == 1 && r.reward.creatures[0].count == 1)
+				txt.appendLocalString(EMetaText::ADVOB_TXT, 185);
+			else
+				txt.appendLocalString(EMetaText::ADVOB_TXT, 186);
+			
+			txt.replaceRawString(loot.buildList());
+			txt.replaceRawString(h->getNameTranslated());
 		}
-
-		if(creatures.stacksCount() == 1 && creatures.Slots().begin()->second->count == 1)
-			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 185);
-		else
-			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 186);
-
-		iw.text.replaceRawString(loot.buildList());
-		iw.text.replaceRawString(h->getNameTranslated());
-
-		cb->showInfoDialog(&iw);
-		cb->giveCreatures(this, h, creatures, false);
-	}
-	if(!hasGuardians && !msg.empty())
-	{
-		iw.text.appendRawString(msg);
-		cb->showInfoDialog(&iw);
-	}
-}
-
-void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const
-{
-	if(afterBattle || message.empty())
-	{
-		iw.text.appendLocalString(EMetaText::ADVOB_TXT,text);//%s has lost treasure.
-		iw.text.replaceRawString(h->getNameTranslated());
-	}
-	else
-	{
-		iw.text.appendRawString(message);
-		afterBattle = true;
-	}
-}
-
-void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const
-{
-	iw.components.clear();
-	iw.text.clear();
-	if(afterBattle || message.empty())
-	{
-		iw.text.appendLocalString(EMetaText::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases
-		iw.text.replaceRawString(h->getNameTranslated());
-	}
-	else
-	{
-		iw.text.appendRawString(message);
-		afterBattle = true;
+		
+		const_cast<MetaString&>(r.message) = txt;
 	}
+	
+	BlockingDialog bd (true, false);
+	bd.player = h->getOwner();
+	bd.text.appendLocalString(EMetaText::ADVOB_TXT, 14);
+	cb->showBlockingDialog(&bd);
 }
 
 void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
 {
 	if(result.winner == 0)
 	{
-		giveContentsUpToExp(hero);
+		CRewardableObject::onHeroVisit(hero);
 	}
 }
 
@@ -328,117 +148,87 @@ void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answe
 			hero->showInfoDialog(16, 0, EInfoWindowMode::MODAL);
 			cb->startBattleI(hero, this); //grants things after battle
 		}
-		else if(message.empty() && resources.empty()
-			&& primskills.empty() && abilities.empty()
-			&& abilityLevels.empty() && artifacts.empty()
-			&& spells.empty() && creatures.stacksCount() == 0
-			&& gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle
+		else if(getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
 		{
 			hero->showInfoDialog(15);
 			cb->removeObject(this);
 		}
 		else //if it gives something without battle
 		{
-			giveContentsUpToExp(hero);
+			CRewardableObject::onHeroVisit(hero);
 		}
 	}
 }
 
-void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const
-{
-	giveContentsAfterExp(hero);
-}
-
-void CGPandoraBox::afterSuccessfulVisit() const
-{
-	cb->removeAfterVisit(this);
-}
-
 void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	CCreatureSet::serializeJson(handler, "guards", 7);
+	CRewardableObject::serializeJsonOptions(handler);
 	handler.serializeString("guardMessage", message);
-
-	handler.serializeInt("experience", gainedExp, 0);
-	handler.serializeInt("mana", manaDiff, 0);
-	handler.serializeInt("morale", moraleDiff, 0);
-	handler.serializeInt("luck", luckDiff, 0);
-
-	resources.serializeJson(handler, "resources");
-
+	
+	if(!handler.saving)
 	{
-		bool haveSkills = false;
-
-		if(handler.saving)
-		{
-			for(int primskill : primskills)
-				if(primskill != 0)
-					haveSkills = true;
-		}
-		else
-		{
-			primskills.resize(GameConstants::PRIMARY_SKILLS,0);
-			haveSkills = true;
-		}
-
-		if(haveSkills)
+		//backward compatibility
+		CCreatureSet::serializeJson(handler, "guards", 7);
+		
+		Rewardable::VisitInfo vinfo;
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		auto & reward = vinfo.reward;
+		int val;
+		handler.serializeInt("experience", reward.heroExperience, 0);
+		handler.serializeInt("mana", reward.manaDiff, 0);
+		handler.serializeInt("morale", val, 0);
+		if(val)
+			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id);
+		handler.serializeInt("luck", val, 0);
+		if(val)
+			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id);
+		
+		reward.resources.serializeJson(handler, "resources");
 		{
 			auto s = handler.enterStruct("primarySkills");
-			for(int idx = 0; idx < primskills.size(); idx ++)
-				handler.serializeInt(NPrimarySkill::names[idx], primskills[idx], 0);
+			for(int idx = 0; idx < reward.primary.size(); idx ++)
+				handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0);
 		}
-	}
+		
+		handler.serializeIdArray("artifacts", reward.artifacts);
+		handler.serializeIdArray("spells", reward.spells);
 
-	if(handler.saving)
-	{
-		if(!abilities.empty())
+		handler.enterArray("creatures").serializeStruct(reward.creatures);
+		
 		{
 			auto s = handler.enterStruct("secondarySkills");
-
-			for(size_t idx = 0; idx < abilities.size(); idx++)
-			{
-				handler.serializeEnum(CSkillHandler::encodeSkill(abilities[idx]), abilityLevels[idx], NSecondarySkill::levels);
-			}
-		}
-	}
-	else
-	{
-		auto s = handler.enterStruct("secondarySkills");
-
-		const JsonNode & skillMap = handler.getCurrent();
-
-		abilities.clear();
-		abilityLevels.clear();
-
-		for(const auto & p : skillMap.Struct())
-		{
-			const std::string skillName = p.first;
-			const std::string levelId = p.second.String();
-
-			const int rawId = CSkillHandler::decodeSkill(skillName);
-			if(rawId < 0)
-			{
-				logGlobal->error("Invalid secondary skill %s", skillName);
-				continue;
-			}
-
-			const int level = vstd::find_pos(NSecondarySkill::levels, levelId);
-			if(level < 0)
+			for(const auto & p : handler.getCurrent().Struct())
 			{
-				logGlobal->error("Invalid secondary skill level %s", levelId);
-				continue;
+				const std::string skillName = p.first;
+				const std::string levelId = p.second.String();
+				
+				const int rawId = CSkillHandler::decodeSkill(skillName);
+				if(rawId < 0)
+				{
+					logGlobal->error("Invalid secondary skill %s", skillName);
+					continue;
+				}
+				
+				const int level = vstd::find_pos(NSecondarySkill::levels, levelId);
+				if(level < 0)
+				{
+					logGlobal->error("Invalid secondary skill level %s", levelId);
+					continue;
+				}
+				
+				reward.secondary[rawId] = level;
 			}
-
-			abilities.emplace_back(rawId);
-			abilityLevels.push_back(level);
 		}
+		configuration.info.push_back(vinfo);
 	}
+}
 
-
-	handler.serializeIdArray("artifacts", artifacts);
-	handler.serializeIdArray("spells", spells);
-
-	creatures.serializeJson(handler, "creatures");
+void CGEvent::init()
+{
+	blockVisit = false;
+	
+	for(auto & i : configuration.info)
+		i.reward.removeObject = removeAfterVisit;
 }
 
 void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
@@ -470,27 +260,17 @@ void CGEvent::activated( const CGHeroInstance * h ) const
 	}
 	else
 	{
-		giveContentsUpToExp(h);
-	}
-}
-
-void CGEvent::afterSuccessfulVisit() const
-{
-	if(removeAfterVisit)
-	{
-		cb->removeAfterVisit(this);
+		CRewardableObject::onHeroVisit(h);
 	}
-	else if(hasGuardians)
-		hasGuardians = false;
 }
 
 void CGEvent::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	CGPandoraBox::serializeJsonOptions(handler);
 
-	handler.serializeBool<bool>("aIActivable", computerActivate, true, false, false);
-	handler.serializeBool<bool>("humanActivable", humanActivate, true, false, true);
-	handler.serializeBool<bool>("removeAfterVisit", removeAfterVisit, true, false, false);
+	handler.serializeBool("aIActivable", computerActivate, false);
+	handler.serializeBool("humanActivable", humanActivate, true);
+	handler.serializeBool("removeAfterVisit", removeAfterVisit, false);
 	handler.serializeIdArray("availableFor", availableFor);
 }
 

+ 5 - 37
lib/mapObjects/CGPandoraBox.h

@@ -9,63 +9,31 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
+#include "CRewardableObject.h"
 #include "../ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct InfoWindow;
 
-class DLL_LINKAGE CGPandoraBox : public CArmedInstance
+class DLL_LINKAGE CGPandoraBox : public CRewardableObject
 {
 public:
 	std::string message;
-	mutable bool hasGuardians = false; //helper - after battle even though we have no stacks, allows us to know that there was battle
-
-	//gained things:
-	ui32 gainedExp = 0;
-	si32 manaDiff = 0; //amount of gained / lost mana
-	si32 moraleDiff = 0; //morale modifier
-	si32 luckDiff = 0; //luck modifier
-	TResources resources;//gained / lost resources
-	std::vector<si32> primskills;//gained / lost prim skills
-	std::vector<SecondarySkill> abilities; //gained abilities
-	std::vector<si32> abilityLevels; //levels of gained abilities
-	std::vector<ArtifactID> artifacts; //gained artifacts
-	std::vector<SpellID> spells; //gained spells
-	CCreatureSet creatures; //gained creatures
 
 	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-	void heroLevelUpDone(const CGHeroInstance *hero) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & static_cast<CArmedInstance&>(*this);
+		h & static_cast<CRewardableObject&>(*this);
 		h & message;
-		h & hasGuardians;
-		h & gainedExp;
-		h & manaDiff;
-		h & moraleDiff;
-		h & luckDiff;
-		h & resources;
-		h & primskills;
-		h & abilities;
-		h & abilityLevels;
-		h & artifacts;
-		h & spells;
-		h & creatures;
 	}
 protected:
-	void giveContentsUpToExp(const CGHeroInstance *h) const;
-	void giveContentsAfterExp(const CGHeroInstance *h) const;
+	virtual void init();
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
-private:
-	void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const;
-	void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const;
-	virtual void afterSuccessfulVisit() const;
 };
 
 class DLL_LINKAGE CGEvent : public CGPandoraBox  //event objects
@@ -87,10 +55,10 @@ public:
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 protected:
+	void init() override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 private:
 	void activated(const CGHeroInstance * h) const;
-	void afterSuccessfulVisit() const override;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 8 - 22
lib/mapObjects/CQuest.cpp

@@ -794,20 +794,11 @@ void CGSeerHut::afterAddToMap(CMap* map)
 void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	//quest and reward
+	CRewardableObject::serializeJsonOptions(handler);
 	quest->serializeJson(handler, "quest");
-
-	if(handler.saving)
-	{
-		CRewardableObject::serializeJsonOptions(handler);
-	}
-	else
+	
+	if(!handler.saving)
 	{
-		if(handler.getCurrent()["reward"].isNull() || handler.getCurrent()["reward"].Struct().empty())
-		{
-			CRewardableObject::serializeJsonOptions(handler);
-			return;
-		}
-		
 		//backward compatibility
 		auto s = handler.enterStruct("reward");
 		const JsonNode & rewardsJson = handler.getCurrent();
@@ -827,7 +818,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 		int val = 0;
 		handler.serializeInt(fullIdentifier, val);
 		
-		Rewardable::Reward reward;
+		Rewardable::VisitInfo vinfo;
+		auto & reward = vinfo.reward;
 		if(metaTypeName == "experience")
 		   reward.heroExperience = val;
 		if(metaTypeName == "mana")
@@ -867,9 +859,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 			reward.creatures.emplace_back(rawId, val);
 		}
 		
-		configuration.info.push_back({});
-		configuration.info.back().reward = reward;
-		configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		configuration.info.push_back(vinfo);
 	}
 }
 
@@ -881,14 +872,10 @@ void CGQuestGuard::init(CRandomGenerator & rand)
 	
 	configuration.info.push_back({});
 	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	configuration.info.back().reward.removeObject = true;
 	configuration.canRefuse = true;
 }
 
-void CGQuestGuard::completeQuest() const
-{
-	cb->removeObject(this);
-}
-
 void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	//quest only, do not call base class
@@ -946,7 +933,6 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
 
 void CGBorderGuard::initObj(CRandomGenerator & rand)
 {
-	//ui32 m13489val = subID; //store color as quest info
 	blockVisit = true;
 }
 

+ 1 - 5
lib/mapObjects/CQuest.h

@@ -162,11 +162,8 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & static_cast<CArmedInstance&>(*this);
+		h & static_cast<CRewardableObject&>(*this);
 		h & static_cast<IQuestObject&>(*this);
-		//h & rewardType;
-		//h & rID;
-		//h & rVal;
 		h & seerName;
 	}
 protected:
@@ -181,7 +178,6 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
 {
 public:
 	void init(CRandomGenerator & rand) override;
-	void completeQuest() const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 0 - 1
lib/mapObjects/CRewardableObject.cpp

@@ -271,7 +271,6 @@ void CRewardableObject::newTurn(CRandomGenerator & rand) const
 void CRewardableObject::initObj(CRandomGenerator & rand)
 {
 	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
-	assert(!configuration.info.empty());
 }
 
 CRewardableObject::CRewardableObject()

+ 30 - 18
lib/mapping/MapFormatH3M.cpp

@@ -1019,34 +1019,46 @@ CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition)
 void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition)
 {
 	readMessageAndGuards(object->message, object, mapPosition);
+	Rewardable::VisitInfo vinfo;
+	auto & reward = vinfo.reward;
+	
+	reward.heroExperience = reader->readUInt32();
+	reward.manaDiff = reader->readInt32();
+	reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), object->id);
+	reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), object->id);
 
-	object->gainedExp = reader->readUInt32();
-	object->manaDiff = reader->readInt32();
-	object->moraleDiff = reader->readInt8();
-	object->luckDiff = reader->readInt8();
-
-	reader->readResourses(object->resources);
-
-	object->primskills.resize(GameConstants::PRIMARY_SKILLS);
+	reader->readResourses(reward.resources);
 	for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x)
-		object->primskills[x] = reader->readUInt8();
+		reward.primary.at(x) = reader->readUInt8();
 
 	int gabn = reader->readUInt8(); //number of gained abilities
 	for(int oo = 0; oo < gabn; ++oo)
 	{
-		object->abilities.emplace_back(reader->readSkill());
-		object->abilityLevels.push_back(reader->readUInt8());
+		auto rId = reader->readSkill();
+		auto rVal = reader->readUInt8();
+		
+		reward.secondary[rId] = rVal;
 	}
 	int gart = reader->readUInt8(); //number of gained artifacts
 	for(int oo = 0; oo < gart; ++oo)
-		object->artifacts.emplace_back(reader->readArtifact());
+		reward.artifacts.push_back(reader->readArtifact());
 
 	int gspel = reader->readUInt8(); //number of gained spells
 	for(int oo = 0; oo < gspel; ++oo)
-		object->spells.emplace_back(reader->readSpell());
+		reward.spells.push_back(reader->readSpell());
 
 	int gcre = reader->readUInt8(); //number of gained creatures
-	readCreatureSet(&object->creatures, gcre);
+	for(int oo = 0; oo < gcre; ++oo)
+	{
+		auto rId = reader->readCreature();
+		auto rVal = reader->readUInt16();
+		
+		reward.creatures.emplace_back(rId, rVal);
+	}
+	
+	vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	object->configuration.info.push_back(vinfo);
+	
 	reader->skipZero(8);
 }
 
@@ -1843,7 +1855,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
 	if(hut->quest->missionType)
 	{
 		auto rewardType = reader->readUInt8();
-		Rewardable::Reward reward;
+		Rewardable::VisitInfo vinfo;
+		auto & reward = vinfo.reward;
 		switch(rewardType)
 		{
 			case 0: //NOTHING
@@ -1922,9 +1935,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
 			}
 		}
 		
-		hut->configuration.info.push_back({});
-		hut->configuration.info.back().reward = reward;
-		hut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		hut->configuration.info.push_back(vinfo);
 	}
 	else
 	{

+ 42 - 22
lib/rmg/modificators/TreasurePlacer.cpp

@@ -235,7 +235,12 @@ void TreasurePlacer::addAllPossibleObjects()
 		{
 			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
 			auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
-			obj->resources[EGameResID::GOLD] = i * 5000;
+			
+			Rewardable::VisitInfo reward;
+			reward.reward.resources[EGameResID::GOLD] = i * 5000;
+			reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+			obj->configuration.info.push_back(reward);
+			
 			return obj;
 		};
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
@@ -251,7 +256,12 @@ void TreasurePlacer::addAllPossibleObjects()
 		{
 			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
 			auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
-			obj->gainedExp = i * 5000;
+			
+			Rewardable::VisitInfo reward;
+			reward.reward.heroExperience = i * 5000;
+			reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+			obj->configuration.info.push_back(reward);
+			
 			return obj;
 		};
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
@@ -307,8 +317,12 @@ void TreasurePlacer::addAllPossibleObjects()
 		{
 			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
 			auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
-			auto * stack = new CStackInstance(creature, creaturesAmount);
-			obj->creatures.putStack(SlotID(0), stack);
+			
+			Rewardable::VisitInfo reward;
+			reward.reward.creatures.emplace_back(creature, creaturesAmount);
+			reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+			obj->configuration.info.push_back(reward);
+			
 			return obj;
 		};
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
@@ -333,10 +347,13 @@ void TreasurePlacer::addAllPossibleObjects()
 			}
 			
 			RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
+			Rewardable::VisitInfo reward;
 			for(int j = 0; j < std::min(12, static_cast<int>(spells.size())); j++)
 			{
-				obj->spells.push_back(spells[j]->id);
+				reward.reward.spells.push_back(spells[j]->id);
 			}
+			reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+			obj->configuration.info.push_back(reward);
 			
 			return obj;
 		};
@@ -362,10 +379,13 @@ void TreasurePlacer::addAllPossibleObjects()
 			}
 			
 			RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
+			Rewardable::VisitInfo reward;
 			for(int j = 0; j < std::min(15, static_cast<int>(spells.size())); j++)
 			{
-				obj->spells.push_back(spells[j]->id);
+				reward.reward.spells.push_back(spells[j]->id);
 			}
+			reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+			obj->configuration.info.push_back(reward);
 			
 			return obj;
 		};
@@ -390,10 +410,13 @@ void TreasurePlacer::addAllPossibleObjects()
 		}
 		
 		RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
+		Rewardable::VisitInfo reward;
 		for(int j = 0; j < std::min(60, static_cast<int>(spells.size())); j++)
 		{
-			obj->spells.push_back(spells[j]->id);
+			reward.reward.spells.push_back(spells[j]->id);
 		}
+		reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		obj->configuration.info.push_back(reward);
 		
 		return obj;
 	};
@@ -442,11 +465,10 @@ void TreasurePlacer::addAllPossibleObjects()
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
 				
-				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;
+				Rewardable::VisitInfo reward;
+				reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount);
+				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+				obj->configuration.info.push_back(reward);
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				
@@ -494,11 +516,10 @@ void TreasurePlacer::addAllPossibleObjects()
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
 				
-				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;
+				Rewardable::VisitInfo reward;
+				reward.reward.heroExperience = generator.getConfig().questRewardValues[i];
+				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+				obj->configuration.info.push_back(reward);
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();
@@ -519,11 +540,10 @@ void TreasurePlacer::addAllPossibleObjects()
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
 				
-				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;
+				Rewardable::VisitInfo reward;
+				reward.reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i];
+				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+				obj->configuration.info.push_back(reward);
 				
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = qap->drawRandomArtifact();