瀏覽代碼

Merge pull request #2844 from Nordsoft91/rewardable-quests

New rewardable interface for SeerHuts and pandoras
Nordsoft91 2 年之前
父節點
當前提交
807f308c91

+ 3 - 2
lib/CCreatureSet.cpp

@@ -624,12 +624,13 @@ void CCreatureSet::armyChanged()
 
 }
 
-void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName, const std::optional<int> fixedSize)
+void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize)
 {
 	if(handler.saving && stacks.empty())
 		return;
 
-	auto a = handler.enterArray(fieldName);
+	handler.serializeEnum("formation", formation, NArmyFormation::names);
+	auto a = handler.enterArray(armyFieldName);
 
 
 	if(handler.saving)

+ 6 - 1
lib/CCreatureSet.h

@@ -206,6 +206,11 @@ enum class EArmyFormation : uint8_t
 	TIGHT
 };
 
+namespace NArmyFormation
+{
+	static const std::vector<std::string> names{ "wide", "tight" };
+}
+
 class DLL_LINKAGE CCreatureSet : public IArmyDescriptor //seven combined creatures
 {
 	CCreatureSet(const CCreatureSet &) = delete;
@@ -284,7 +289,7 @@ public:
 		h & formation;
 	}
 
-	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName, const std::optional<int> fixedSize = std::nullopt);
+	void serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize = std::nullopt);
 
 	operator bool() const
 	{

+ 11 - 0
lib/MetaString.cpp

@@ -19,6 +19,7 @@
 #include "VCMI_Lib.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "spells/CSpellHandler.h"
+#include "serializer/JsonSerializeFormat.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -385,4 +386,14 @@ void MetaString::jsonDeserialize(const JsonNode & source)
 		numbers.push_back(entry.Integer());
 }
 
+void MetaString::serializeJson(JsonSerializeFormat & handler)
+{
+	JsonNode attr;
+	if(handler.saving)
+		jsonSerialize(attr);
+	handler.serializeRaw("attributes", attr, std::nullopt);
+	if(!handler.saving)
+		jsonDeserialize(attr);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 3 - 0
lib/MetaString.h

@@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class JsonNode;
 class CreatureID;
 class CStackBasicDescriptor;
+class JsonSerializeFormat;
 using TQuantity = si32;
 
 /// Strings classes that can be used as replacement in MetaString
@@ -113,6 +114,8 @@ public:
 
 	void jsonSerialize(JsonNode & dest) const;
 	void jsonDeserialize(const JsonNode & dest);
+	
+	void serializeJson(JsonSerializeFormat & handler);
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{

+ 0 - 5
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -51,11 +51,6 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG
 			{
 				bonus.source = BonusSource::OBJECT;
 				bonus.sid = rewardableObject->ID;
-				//TODO: bonus.description = object->getObjectName();
-				if (bonus.type == BonusType::MORALE)
-					rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0);
-				if (bonus.type == BonusType::LUCK)
-					rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0);
 			}
 		}
 		assert(!rewardableObject->configuration.info.empty());

+ 6 - 0
lib/mapObjects/CArmedInstance.cpp

@@ -160,4 +160,10 @@ const IBonusBearer* CArmedInstance::getBonusBearer() const
 	return this;
 }
 
+void CArmedInstance::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	CGObjectInstance::serializeJsonOptions(handler);
+	CCreatureSet::serializeJson(handler, "army", 7);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 3 - 0
lib/mapObjects/CArmedInstance.h

@@ -18,6 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class BattleInfo;
 class CGameState;
+class JsonSerializeFormat;
 
 class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet, public IConstBonusProvider
 {
@@ -48,6 +49,8 @@ public:
 	{
 		return this->tempOwner;
 	}
+	
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 4
lib/mapObjects/CGHeroInstance.cpp

@@ -1709,10 +1709,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 			setHeroTypeName(typeName);
 	}
 
-	static const std::vector<std::string> FORMATIONS  =	{ "wide", "tight" };
-
-	CCreatureSet::serializeJson(handler, "army", 7);
-	handler.serializeEnum("formation", formation, FORMATIONS);
+	CArmedInstance::serializeJsonOptions(handler);
 
 	{
 		static constexpr int NO_PATROLING = -1;

+ 213 - 358
lib/mapObjects/CGPandoraBox.cpp

@@ -25,297 +25,162 @@
 
 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;
+	configuration.info.emplace_back();
+	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	
+	for(auto & i : configuration.info)
+	{
+		i.reward.removeObject = true;
+		if(!message.empty() && i.message.empty())
+			i.message = MetaString::createFromRawString(message);
+	}
 }
 
-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::grantRewardWithMessage(const CGHeroInstance * h, int index, bool markAsVisit) const
 {
-	afterSuccessfulVisit();
-
-	InfoWindow iw;
-	iw.type = EInfoWindowMode::AUTO;
-	iw.player = h->getOwner();
-
-	bool changesPrimSkill = false;
-	for(const auto & elem : primskills)
+	auto vi = configuration.info.at(index);
+	if(!vi.message.empty())
 	{
-		if(elem)
-		{
-			changesPrimSkill = true;
-			break;
-		}
+		CRewardableObject::grantRewardWithMessage(h, index, markAsVisit);
+		return;
 	}
-
-	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++)
+	
+	//split reward message for pandora box
+	auto setText = [](bool cond, int posId, int negId, const CGHeroInstance * h)
 	{
-		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)
-		{
-			unpossessedAbilities.emplace_back(abilities[i], abilityLevels[i]);
-		}
-	}
-
-	if(gainedExp || changesPrimSkill || !unpossessedAbilities.empty())
+		MetaString text;
+		text.appendLocalString(EMetaText::ADVOB_TXT, cond ? posId : negId);
+		text.replaceRawString(h->getNameTranslated());
+		return text;
+	};
+	
+	auto sendInfoWindow = [h](const MetaString & text, const Rewardable::Reward & reward)
 	{
-		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())
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		iw.text = text;
+		reward.loadComponents(iw.components, h);
+		iw.type = EInfoWindowMode::MODAL;
+		if(!iw.components.empty())
+			cb->showInfoDialog(&iw);
+	};
+
+	Rewardable::Reward temp;
+	temp.spells = vi.reward.spells;
+	temp.heroExperience = vi.reward.heroExperience;
+	temp.heroLevel = vi.reward.heroLevel;
+	temp.primary = vi.reward.primary;
+	temp.secondary = vi.reward.secondary;
+	temp.bonuses = vi.reward.bonuses;
+	temp.manaDiff = vi.reward.manaDiff;
+	temp.manaPercentage = vi.reward.manaPercentage;
+	
+	MetaString txt;
+	if(!vi.reward.spells.empty())
+		txt = setText(temp.spells.size() == 1, 184, 188, h);
+	
+	if(vi.reward.heroExperience || vi.reward.heroLevel || !vi.reward.secondary.empty())
+		txt = setText(true, 175, 175, h);
+	
+	for(int i : vi.reward.primary)
 	{
-		std::set<SpellID> spellsToGive;
-
-		auto i = spells.cbegin();
-		while (i != spells.cend())
+		if(i)
 		{
-			iw.components.clear();
-			iw.text.clear();
-			spellsToGive.clear();
-
-			for (; i != spells.cend(); i++)
-			{
-				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 (!spellsToGive.empty())
-			{
-				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);
-			}
+			txt = setText(true, 175, 175, h);
+			break;
 		}
 	}
-
-	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(vi.reward.manaDiff || vi.reward.manaPercentage)
+		txt = setText(temp.manaDiff > 0, 177, 176, h);
+	
+	for(auto b : vi.reward.bonuses)
 	{
-		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);
+		if(b.val && b.type == BonusType::MORALE)
+			txt = setText(b.val > 0, 179, 178, h);
+		if(b.val && b.type == BonusType::LUCK)
+			txt = setText(b.val > 0, 181, 180, h);
 	}
-
-	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)
+	sendInfoWindow(txt, temp);
+	
+	//resource message
+	temp = Rewardable::Reward{};
+	temp.resources = vi.reward.resources;
+	sendInfoWindow(setText(vi.reward.resources.marketValue() > 0, 183, 182, h), temp);
+	
+	//artifacts message
+	temp = Rewardable::Reward{};
+	temp.artifacts = vi.reward.artifacts;
+	sendInfoWindow(setText(true, 183, 183, h), temp);
+	
+	//creatures message
+	temp = Rewardable::Reward{};
+	temp.creatures = vi.reward.creatures;
+	txt.clear();
+	if(!vi.reward.creatures.empty())
 	{
-		iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
-		if(iw.components.size() >= 14)
-		{
-			cb->showInfoDialog(&iw);
-			iw.components.clear();
-			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 183); //% has found treasure - once more?
-			iw.text.replaceRawString(h->getNameTranslated());
-		}
-	}
-	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);
+		for(auto c : vi.reward.creatures)
+		{
 			loot.appendRawString("%s");
-			loot.replaceCreatureName(*elem.second);
+			loot.replaceCreatureName(c);
 		}
-
-		if(creatures.stacksCount() == 1 && creatures.Slots().begin()->second->count == 1)
-			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 185);
+		
+		if(vi.reward.creatures.size() == 1 && vi.reward.creatures[0].count == 1)
+			txt.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;
+			txt.appendLocalString(EMetaText::ADVOB_TXT, 186);
+		
+		txt.replaceRawString(loot.buildList());
+		txt.replaceRawString(h->getNameTranslated());
 	}
+	sendInfoWindow(txt, temp);
+	
+	//everything else
+	temp = vi.reward;
+	temp.heroExperience = 0;
+	temp.heroLevel = 0;
+	temp.secondary.clear();
+	temp.primary.clear();
+	temp.resources.amin(0);
+	temp.resources.amax(0);
+	temp.manaDiff = 0;
+	temp.manaPercentage = 0;
+	temp.spells.clear();
+	temp.creatures.clear();
+	temp.bonuses.clear();
+	temp.artifacts.clear();
+	sendInfoWindow(setText(true, 175, 175, h), temp);
+	
+	// grant reward afterwards. Note that it may remove object
+	if(markAsVisit)
+		markAsVisited(h);
+	grantReward(index, h);
 }
 
-void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const
+void CGPandoraBox::onHeroVisit(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;
-	}
+	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 +193,117 @@ 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 for VCMI maps that use old Pandora Box format
+		if(!handler.getCurrent()["guards"].Vector().empty())
+			CCreatureSet::serializeJson(handler, "guards", 7);
+		
+		bool hasSomething = false;
+		Rewardable::VisitInfo vinfo;
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+				
+		handler.serializeInt("experience", vinfo.reward.heroExperience, 0);
+		handler.serializeInt("mana", vinfo.reward.manaDiff, 0);
+		
+		int val;
+		handler.serializeInt("morale", val, 0);
+		if(val)
+			vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id);
+		
+		handler.serializeInt("luck", val, 0);
+		if(val)
+			vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id);
+		
+		vinfo.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);
-		}
-	}
-
-	if(handler.saving)
-	{
-		if(!abilities.empty())
-		{
-			auto s = handler.enterStruct("secondarySkills");
-
-			for(size_t idx = 0; idx < abilities.size(); idx++)
+			for(int idx = 0; idx < vinfo.reward.primary.size(); idx ++)
 			{
-				handler.serializeEnum(CSkillHandler::encodeSkill(abilities[idx]), abilityLevels[idx], NSecondarySkill::levels);
+				handler.serializeInt(NPrimarySkill::names[idx], vinfo.reward.primary[idx], 0);
+				if(vinfo.reward.primary[idx])
+					hasSomething = true;
 			}
 		}
-	}
-	else
-	{
-		auto s = handler.enterStruct("secondarySkills");
-
-		const JsonNode & skillMap = handler.getCurrent();
-
-		abilities.clear();
-		abilityLevels.clear();
-
-		for(const auto & p : skillMap.Struct())
+		
+		handler.serializeIdArray("artifacts", vinfo.reward.artifacts);
+		handler.serializeIdArray("spells", vinfo.reward.spells);
+		handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures);
+		
 		{
-			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)
+			auto s = handler.enterStruct("secondarySkills");
+			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;
+				}
+				
+				vinfo.reward.secondary[rawId] = level;
 			}
-
-			abilities.emplace_back(rawId);
-			abilityLevels.push_back(level);
 		}
+		
+		hasSomething = hasSomething
+		|| vinfo.reward.heroExperience
+		|| vinfo.reward.manaDiff
+		|| vinfo.reward.resources.nonZero()
+		|| !vinfo.reward.bonuses.empty()
+		|| !vinfo.reward.artifacts.empty()
+		|| !vinfo.reward.secondary.empty()
+		|| !vinfo.reward.artifacts.empty()
+		|| !vinfo.reward.creatures.empty();
+		
+		if(hasSomething)
+			configuration.info.push_back(vinfo);
 	}
+}
 
+void CGEvent::init()
+{
+	blockVisit = false;
+	configuration.infoWindowType = EInfoWindowMode::MODAL;
+	
+	for(auto & i : configuration.info)
+	{
+		i.reward.removeObject = removeAfterVisit;
+		if(!message.empty() && i.message.empty())
+			i.message = MetaString::createFromRawString(message);
+	}
+}
 
-	handler.serializeIdArray("artifacts", artifacts);
-	handler.serializeIdArray("spells", spells);
-
-	creatures.serializeJson(handler, "creatures");
+void CGEvent::grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const
+{
+	CRewardableObject::grantRewardWithMessage(contextHero, rewardIndex, markAsVisit);
 }
 
 void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
@@ -470,27 +335,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);
 }
 

+ 9 - 37
lib/mapObjects/CGPandoraBox.h

@@ -9,63 +9,33 @@
  */
 #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;
+	void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const override;
+	
+	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 +57,12 @@ public:
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 protected:
+	void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const override;
+	
+	void init() override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 private:
 	void activated(const CGHeroInstance * h) const;
-	void afterSuccessfulVisit() const override;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 0 - 4
lib/mapObjects/CGTownBuilding.cpp

@@ -307,10 +307,6 @@ void CTownRewardableBuilding::initObj(CRandomGenerator & rand)
 		{
 			bonus.source = BonusSource::TOWN_STRUCTURE;
 			bonus.sid = bID;
-			if (bonus.type == BonusType::MORALE)
-				rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0);
-			if (bonus.type == BonusType::LUCK)
-				rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0);
 		}
 	}
 }

+ 3 - 4
lib/mapObjects/CGTownInstance.cpp

@@ -1093,11 +1093,10 @@ void CGTownInstance::reset()
 
 void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	static const std::vector<std::string> FORMATIONS  =	{ "wide", "tight" };
-
 	CGObjectInstance::serializeJsonOwner(handler);
-	CCreatureSet::serializeJson(handler, "army", 7);
-	handler.serializeEnum("tightFormation", formation, FORMATIONS);
+	if(!handler.saving)
+		handler.serializeEnum("tightFormation", formation, NArmyFormation::names); //for old format
+	CArmedInstance::serializeJsonOptions(handler);
 	handler.serializeString("name", name);
 
 	{

+ 74 - 293
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,163 +793,74 @@ void CGSeerHut::afterAddToMap(CMap* map)
 
 void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	static const std::map<ERewardType, std::string> REWARD_MAP =
-	{
-		{NOTHING,		""},
-		{EXPERIENCE,	"experience"},
-		{MANA_POINTS,	"mana"},
-		{MORALE_BONUS,	"morale"},
-		{LUCK_BONUS,	"luck"},
-		{RESOURCES,		"resource"},
-		{PRIMARY_SKILL,	"primarySkill"},
-		{SECONDARY_SKILL,"secondarySkill"},
-		{ARTIFACT,		"artifact"},
-		{SPELL,			"spell"},
-		{CREATURE,		"creature"}
-	};
-
-	static const std::map<std::string, ERewardType> REWARD_RMAP =
-	{
-		{"experience",    EXPERIENCE},
-		{"mana",          MANA_POINTS},
-		{"morale",        MORALE_BONUS},
-		{"luck",          LUCK_BONUS},
-		{"resource",      RESOURCES},
-		{"primarySkill",  PRIMARY_SKILL},
-		{"secondarySkill",SECONDARY_SKILL},
-		{"artifact",      ARTIFACT},
-		{"spell",         SPELL},
-		{"creature",      CREATURE}
-	};
-
 	//quest and reward
+	CRewardableObject::serializeJsonOptions(handler);
 	quest->serializeJson(handler, "quest");
-
-	//only one reward is supported
-	//todo: full reward format support after CRewardInfo integration
-
-	auto s = handler.enterStruct("reward");
-	std::string fullIdentifier;
-	std::string metaTypeName;
-	std::string scope;
-	std::string identifier;
-
-	if(handler.saving)
-	{
-		si32 amount = rVal;
-
-		metaTypeName = REWARD_MAP.at(rewardType);
-		switch (rewardType)
-		{
-		case NOTHING:
-			break;
-		case EXPERIENCE:
-		case MANA_POINTS:
-		case MORALE_BONUS:
-		case LUCK_BONUS:
-			identifier.clear();
-			break;
-		case RESOURCES:
-			identifier = GameConstants::RESOURCE_NAMES[rID];
-			break;
-		case PRIMARY_SKILL:
-			identifier = NPrimarySkill::names[rID];
-			break;
-		case SECONDARY_SKILL:
-			identifier = CSkillHandler::encodeSkill(rID);
-			break;
-		case ARTIFACT:
-			identifier = ArtifactID(rID).toArtifact(VLC->artifacts())->getJsonKey();
-			amount = 1;
-			break;
-		case SPELL:
-			identifier = SpellID(rID).toSpell(VLC->spells())->getJsonKey();
-			amount = 1;
-			break;
-		case CREATURE:
-			identifier = CreatureID(rID).toCreature(VLC->creatures())->getJsonKey();
-			break;
-		default:
-			assert(false);
-			break;
-		}
-		if(rewardType != NOTHING)
-		{
-			fullIdentifier = ModUtility::makeFullIdentifier(scope, metaTypeName, identifier);
-			handler.serializeInt(fullIdentifier, amount);
-		}
-	}
-	else
+	
+	if(!handler.saving)
 	{
-		rewardType = NOTHING;
-
+		//backward compatibility for VCMI maps that use old SeerHut format
+		auto s = handler.enterStruct("reward");
 		const JsonNode & rewardsJson = handler.getCurrent();
+		
+		std::string fullIdentifier;
+		std::string metaTypeName;
+		std::string scope;
+		std::string identifier;
 
-		fullIdentifier.clear();
+		auto iter = rewardsJson.Struct().begin();
+		fullIdentifier = iter->first;
 
-		if(rewardsJson.Struct().empty())
+		ModUtility::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier);
+		if(!std::set<std::string>{"resource", "primarySkill", "secondarySkill", "artifact", "spell", "creature", "experience", "mana", "morale", "luck"}.count(metaTypeName))
 			return;
-		else
+
+		int val = 0;
+		handler.serializeInt(fullIdentifier, val);
+		
+		Rewardable::VisitInfo vinfo;
+		auto & reward = vinfo.reward;
+		if(metaTypeName == "experience")
+		   reward.heroExperience = val;
+		if(metaTypeName == "mana")
+			reward.manaDiff = val;
+		if(metaTypeName == "morale")
+			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id);
+		if(metaTypeName == "luck")
+			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id);
+		if(metaTypeName == "resource")
 		{
-			auto iter = rewardsJson.Struct().begin();
-			fullIdentifier = iter->first;
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.resources[rawId] = val;
 		}
-
-		ModUtility::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier);
-
-		auto it = REWARD_RMAP.find(metaTypeName);
-
-		if(it == REWARD_RMAP.end())
+		if(metaTypeName == "primarySkill")
 		{
-			logGlobal->error("%s: invalid metatype in reward item %s", instanceName, fullIdentifier);
-			return;
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.primary.at(rawId) = val;
 		}
-		else
+		if(metaTypeName == "secondarySkill")
 		{
-			rewardType = it->second;
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.secondary[rawId] = val;
 		}
-
-		bool doRequest = false;
-
-		switch (rewardType)
+		if(metaTypeName == "artifact")
 		{
-		case NOTHING:
-			return;
-		case EXPERIENCE:
-		case MANA_POINTS:
-		case MORALE_BONUS:
-		case LUCK_BONUS:
-			break;
-		case PRIMARY_SKILL:
-			doRequest = true;
-			break;
-		case RESOURCES:
-		case SECONDARY_SKILL:
-		case ARTIFACT:
-		case SPELL:
-		case CREATURE:
-			doRequest = true;
-			break;
-		default:
-			assert(false);
-			break;
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.artifacts.push_back(rawId);
 		}
-
-		if(doRequest)
+		if(metaTypeName == "spell")
 		{
-			auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
-
-			if(rawId)
-			{
-				rID = rawId.value();
-			}
-			else
-			{
-				rewardType = NOTHING;//fallback in case of error
-				return;
-			}
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.spells.push_back(rawId);
+		}
+		if(metaTypeName == "creature")
+		{
+			auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
+			reward.creatures.emplace_back(rawId, val);
 		}
-		handler.serializeInt(fullIdentifier, rVal);
+		
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		configuration.info.push_back(vinfo);
 	}
 }
 
@@ -1087,11 +869,11 @@ void CGQuestGuard::init(CRandomGenerator & rand)
 	blockVisit = true;
 	quest->textOption = rand.nextInt(3, 5);
 	quest->completedOption = rand.nextInt(4, 5);
-}
-
-void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
-{
-	cb->removeObject(this);
+	
+	configuration.info.push_back({});
+	configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	configuration.info.back().reward.removeObject = true;
+	configuration.canRefuse = true;
 }
 
 void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
@@ -1151,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;
 }
 

+ 5 - 14
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
 {
 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,19 +155,15 @@ 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;
 
 	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:
@@ -186,7 +178,6 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
 {
 public:
 	void init(CRandomGenerator & rand) override;
-	void completeQuest (const CGHeroInstance * h) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 47 - 40
lib/mapObjects/CRewardableObject.cpp

@@ -17,6 +17,7 @@
 #include "../NetPacks.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../serializer/JsonSerializeFormat.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -27,44 +28,45 @@ static std::string visitedTxt(const bool visited)
 	return VLC->generaltexth->allTexts[id];
 }
 
-void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
+void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const
 {
-	auto grantRewardWithMessage = [&](int index, bool markAsVisit) -> void
-	{
-		auto vi = configuration.info.at(index);
-		logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
- 		// show message only if it is not empty or in infobox
-		if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
-		{
-			InfoWindow iw;
-			iw.player = h->tempOwner;
-			iw.text = vi.message;
-			vi.reward.loadComponents(iw.components, h);
-			iw.type = configuration.infoWindowType;
-			if(!iw.components.empty() || !iw.text.toString().empty())
-				cb->showInfoDialog(&iw);
-		}
-		// grant reward afterwards. Note that it may remove object
-		if(markAsVisit)
-			markAsVisited(h);
-		grantReward(index, h);
-	};
-	auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
+	auto vi = configuration.info.at(index);
+	logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
+	// show message only if it is not empty or in infobox
+	if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
 	{
-		BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
-		sd.player = h->tempOwner;
-		sd.text = dialog;
+		InfoWindow iw;
+		iw.player = contextHero->tempOwner;
+		iw.text = vi.message;
+		vi.reward.loadComponents(iw.components, contextHero);
+		iw.type = configuration.infoWindowType;
+		if(!iw.components.empty() || !iw.text.toString().empty())
+			cb->showInfoDialog(&iw);
+	}
+	// grant reward afterwards. Note that it may remove object
+	if(markAsVisit)
+		markAsVisited(contextHero);
+	grantReward(index, contextHero);
+}
+
+void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const
+{
+	BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1);
+	sd.player = contextHero->tempOwner;
+	sd.text = dialog;
 
-		if (rewards.size() > 1)
-			for (auto index : rewards)
-				sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
+	if (rewardIndices.size() > 1)
+		for (auto index : rewardIndices)
+			sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
 
-		if (rewards.size() == 1)
-			configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
+	if (rewardIndices.size() == 1)
+		configuration.info.at(rewardIndices.front()).reward.loadComponents(sd.components, contextHero);
 
-		cb->showBlockingDialog(&sd);
-	};
+	cb->showBlockingDialog(&sd);
+}
 
+void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
+{
 	if(!wasVisitedBefore(h))
 	{
 		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
@@ -82,7 +84,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 			{
 				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
 				if (!emptyRewards.empty())
-					grantRewardWithMessage(emptyRewards[0], false);
+					grantRewardWithMessage(h, emptyRewards[0], false);
 				else
 					logMod->warn("No applicable message for visiting empty object!");
 				break;
@@ -90,22 +92,22 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 			case 1: // one reward. Just give it with message
 			{
 				if (configuration.canRefuse)
-					selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
+					selectRewardWthMessage(h, rewards, configuration.info.at(rewards.front()).message);
 				else
-					grantRewardWithMessage(rewards.front(), true);
+					grantRewardWithMessage(h, rewards.front(), true);
 				break;
 			}
 			default: // multiple rewards. Act according to select mode
 			{
 				switch (configuration.selectMode) {
 					case Rewardable::SELECT_PLAYER: // player must select
-						selectRewardsMessage(rewards, configuration.onSelect);
+						selectRewardWthMessage(h, rewards, configuration.onSelect);
 						break;
 					case Rewardable::SELECT_FIRST: // give first available
-						grantRewardWithMessage(rewards.front(), true);
+						grantRewardWithMessage(h, rewards.front(), true);
 						break;
 					case Rewardable::SELECT_RANDOM: // give random
-						grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true);
+						grantRewardWithMessage(h, *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true);
 						break;
 				}
 				break;
@@ -124,7 +126,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 
 		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
 		if (!visitedRewards.empty())
-			grantRewardWithMessage(visitedRewards[0], false);
+			grantRewardWithMessage(h, visitedRewards[0], false);
 		else
 			logMod->warn("No applicable message for visiting already visited object!");
 	}
@@ -270,10 +272,15 @@ 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()
 {}
 
+void CRewardableObject::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	CArmedInstance::serializeJsonOptions(handler);
+	handler.serializeStruct("rewardable", static_cast<Rewardable::Interface&>(*this));
+}
+
 VCMI_LIB_NAMESPACE_END

+ 6 - 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
@@ -31,6 +31,11 @@ protected:
 	/// return true if this object was "cleared" before and no longer has rewards applicable to selected hero
 	/// unlike wasVisited, this method uses information not available to player owner, for example, if object was cleared by another player before
 	bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
+	
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
+	
+	virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const;
+	virtual void selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const;
 
 public:
 	/// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player)

+ 8 - 4
lib/mapObjects/MiscObjects.cpp

@@ -196,7 +196,7 @@ void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) con
 
 void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	CCreatureSet::serializeJson(handler, "army", 7);
+	CArmedInstance::serializeJsonOptions(handler);
 
 	if(isAbandoned())
 	{
@@ -316,7 +316,9 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 
 void CGResource::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	CCreatureSet::serializeJson(handler, "guards", 7);
+	CArmedInstance::serializeJsonOptions(handler);
+	if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty())
+		CCreatureSet::serializeJson(handler, "guards", 7);
 	handler.serializeInt("amount", amount, 0);
 	handler.serializeString("guardMessage", message);
 }
@@ -827,7 +829,9 @@ void CGArtifact::afterAddToMap(CMap * map)
 void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
 {
 	handler.serializeString("guardMessage", message);
-	CCreatureSet::serializeJson(handler, "guards" ,7);
+	CArmedInstance::serializeJsonOptions(handler);
+	if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty())
+		CCreatureSet::serializeJson(handler, "guards", 7);
 
 	if(handler.saving && ID == Obj::SPELL_SCROLL)
 	{
@@ -1233,7 +1237,7 @@ void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler)
 {
 	handler.serializeBool("removableUnits", removableUnits);
 	serializeJsonOwner(handler);
-	CCreatureSet::serializeJson(handler, "army", 7);
+	CArmedInstance::serializeJsonOptions(handler);
 }
 
 void CGMagi::reset()

+ 99 - 58
lib/mapping/MapFormatH3M.cpp

@@ -989,11 +989,11 @@ void CMapLoaderH3M::readObjectTemplates()
 	}
 }
 
-CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition)
+CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven)
 {
 	auto * object = new CGEvent();
 
-	readBoxContent(object, mapPosition);
+	readBoxContent(object, mapPosition, idToBeGiven);
 
 	reader->readBitmaskPlayers(object->availableFor, false);
 	object->computerActivate = reader->readBool();
@@ -1009,44 +1009,58 @@ CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition)
 	return object;
 }
 
-CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition)
+CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven)
 {
 	auto * object = new CGPandoraBox();
-	readBoxContent(object, mapPosition);
+	readBoxContent(object, mapPosition, idToBeGiven);
 	return object;
 }
 
-void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition)
+void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition, const ObjectInstanceID & idToBeGiven)
 {
 	readMessageAndGuards(object->message, object, mapPosition);
-
-	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);
+	Rewardable::VisitInfo vinfo;
+	auto & reward = vinfo.reward;
+	
+	reward.heroExperience = reader->readUInt32();
+	reward.manaDiff = reader->readInt32();
+	if(auto val = reader->readUInt8())
+		reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, idToBeGiven);
+	if(auto val = reader->readUInt8())
+		reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, idToBeGiven);
+
+	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);
 }
 
@@ -1405,7 +1419,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
 	switch(objectTemplate->id)
 	{
 		case Obj::EVENT:
-			return readEvent(mapPosition);
+			return readEvent(mapPosition, objectInstanceID);
 
 		case Obj::HERO:
 		case Obj::RANDOM_HERO:
@@ -1428,7 +1442,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
 			return readSign(mapPosition);
 
 		case Obj::SEER_HUT:
-			return readSeerHut(mapPosition);
+			return readSeerHut(mapPosition, objectInstanceID);
 
 		case Obj::WITCH_HUT:
 			return readWitchHut();
@@ -1471,7 +1485,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
 			return readShrine();
 
 		case Obj::PANDORAS_BOX:
-			return readPandora(mapPosition);
+			return readPandora(mapPosition, objectInstanceID);
 
 		case Obj::GRAIL:
 			return readGrail(mapPosition, objectTemplate);
@@ -1782,7 +1796,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 	return object;
 }
 
-CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position)
+CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position, const ObjectInstanceID & idToBeGiven)
 {
 	auto * hut = new CGSeerHut();
 
@@ -1796,7 +1810,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position)
 		logGlobal->warn("Map '%s': Seer Hut at %s - %d quests are not implemented!", mapName, position.toString(), questsCount);
 
 	for(size_t i = 0; i < questsCount; ++i)
-		readSeerHutQuest(hut, position);
+		readSeerHutQuest(hut, position, idToBeGiven);
 
 	if(features.levelHOTA3)
 	{
@@ -1806,7 +1820,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position)
 			logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
 
 		for(size_t i = 0; i < repeateableQuestsCount; ++i)
-			readSeerHutQuest(hut, position);
+			readSeerHutQuest(hut, position, idToBeGiven);
 	}
 
 	reader->skipZero(2);
@@ -1814,7 +1828,22 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position)
 	return hut;
 }
 
-void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
+enum class ESeerHutRewardType : uint8_t
+{
+	NOTHING = 0,
+	EXPERIENCE = 1,
+	MANA_POINTS = 2,
+	MORALE = 3,
+	LUCK = 4,
+	RESOURCES = 5,
+	PRIMARY_SKILL = 6,
+	SECONDARY_SKILL = 7,
+	ARTIFACT = 8,
+	SPELL = 9,
+	CREATURE = 10,
+};
+
+void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven)
 {
 	if(features.levelAB)
 	{
@@ -1842,70 +1871,79 @@ 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 = static_cast<ESeerHutRewardType>(reader->readUInt8());
+		Rewardable::VisitInfo vinfo;
+		auto & reward = vinfo.reward;
 		switch(rewardType)
 		{
-			case CGSeerHut::EXPERIENCE:
+			case ESeerHutRewardType::NOTHING:
 			{
-				hut->rVal = reader->readUInt32();
+				// no-op
 				break;
 			}
-			case CGSeerHut::MANA_POINTS:
+			case ESeerHutRewardType::EXPERIENCE:
 			{
-				hut->rVal = reader->readUInt32();
+				reward.heroExperience = reader->readUInt32();
 				break;
 			}
-			case CGSeerHut::MORALE_BONUS:
+			case ESeerHutRewardType::MANA_POINTS:
 			{
-				hut->rVal = reader->readUInt8();
+				reward.manaDiff = reader->readUInt32();
 				break;
 			}
-			case CGSeerHut::LUCK_BONUS:
+			case ESeerHutRewardType::MORALE:
 			{
-				hut->rVal = reader->readUInt8();
+				reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven);
 				break;
 			}
-			case CGSeerHut::RESOURCES:
+			case ESeerHutRewardType::LUCK:
 			{
-				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(), idToBeGiven);
 				break;
 			}
-			case CGSeerHut::PRIMARY_SKILL:
+			case ESeerHutRewardType::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 ESeerHutRewardType::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 ESeerHutRewardType::SECONDARY_SKILL:
 			{
-				hut->rID = reader->readArtifact();
+				auto rId = reader->readSkill();
+				auto rVal = reader->readUInt8();
+				
+				reward.secondary[rId] = rVal;
 				break;
 			}
-			case CGSeerHut::SPELL:
+			case ESeerHutRewardType::ARTIFACT:
 			{
-				hut->rID = reader->readSpell();
+				reward.artifacts.push_back(reader->readArtifact());
 				break;
 			}
-			case CGSeerHut::CREATURE:
+			case ESeerHutRewardType::SPELL:
 			{
-				hut->rID = reader->readCreature();
-				hut->rVal = reader->readUInt16();
+				reward.spells.push_back(reader->readSpell());
 				break;
 			}
-			case CGSeerHut::NOTHING:
+			case ESeerHutRewardType::CREATURE:
 			{
-				// no-op
+				auto rId = reader->readCreature();
+				auto rVal = reader->readUInt16();
+				
+				reward.creatures.emplace_back(rId, rVal);
 				break;
 			}
 			default:
@@ -1913,6 +1951,9 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position)
 				assert(0);
 			}
 		}
+		
+		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+		hut->configuration.info.push_back(vinfo);
 	}
 	else
 	{

+ 5 - 5
lib/mapping/MapFormatH3M.h

@@ -158,10 +158,10 @@ private:
 	/// Reads single object from input stream based on template
 	CGObjectInstance * readObject(std::shared_ptr<const ObjectTemplate> objectTemplate, const int3 & objectPosition, const ObjectInstanceID & idToBeGiven);
 
-	CGObjectInstance * readEvent(const int3 & objectPosition);
+	CGObjectInstance * readEvent(const int3 & objectPosition, const ObjectInstanceID & idToBeGiven);
 	CGObjectInstance * readMonster(const int3 & objectPosition, const ObjectInstanceID & idToBeGiven);
 	CGObjectInstance * readHero(const int3 & initialPos, const ObjectInstanceID & idToBeGiven);
-	CGObjectInstance * readSeerHut(const int3 & initialPos);
+	CGObjectInstance * readSeerHut(const int3 & initialPos, const ObjectInstanceID & idToBeGiven);
 	CGObjectInstance * readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readSign(const int3 & position);
 	CGObjectInstance * readWitchHut();
@@ -170,7 +170,7 @@ private:
 	CGObjectInstance * readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readMine(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
-	CGObjectInstance * readPandora(const int3 & position);
+	CGObjectInstance * readPandora(const int3 & position, const ObjectInstanceID & idToBeGiven);
 	CGObjectInstance * readDwelling(const int3 & position);
 	CGObjectInstance * readDwellingRandom(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readShrine();
@@ -196,7 +196,7 @@ private:
 	 *
 	 * @param guard the quest guard where that quest should be applied to
 	 */
-	void readBoxContent(CGPandoraBox * object, const int3 & position);
+	void readBoxContent(CGPandoraBox * object, const int3 & position, const ObjectInstanceID & idToBeGiven);
 
 	/**
 	 * Reads a quest for the given quest guard.
@@ -205,7 +205,7 @@ private:
 	 */
 	void readQuest(IQuestObject * guard, const int3 & position);
 
-	void readSeerHutQuest(CGSeerHut * hut, const int3 & position);
+	void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven);
 
 	/**
 	 * Reads events.

+ 27 - 0
lib/rewardable/Configuration.cpp

@@ -10,6 +10,7 @@
 
 #include "StdInc.h"
 #include "Configuration.h"
+#include "../serializer/JsonSerializeFormat.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -23,4 +24,30 @@ ui16 Rewardable::Configuration::getResetDuration() const
 	return resetParameters.period;
 }
 
+void Rewardable::ResetInfo::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("period", period);
+	handler.serializeBool("visitors", visitors);
+	handler.serializeBool("rewards", rewards);
+}
+
+void Rewardable::VisitInfo::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeStruct("limiter", limiter);
+	handler.serializeStruct("reward", reward);
+	handler.serializeStruct("message", message);
+	handler.serializeInt("visitType", visitType);
+}
+
+void Rewardable::Configuration::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeStruct("onSelect", onSelect);
+	handler.enterArray("info").serializeStruct(info);
+	handler.serializeEnum("selectMode", selectMode, std::vector<std::string>{SelectModeString.begin(), SelectModeString.end()});
+	handler.serializeEnum("visitMode", visitMode, std::vector<std::string>{VisitModeString.begin(), VisitModeString.end()});
+	handler.serializeStruct("resetParameters", resetParameters);
+	handler.serializeBool("canRefuse", canRefuse);
+	handler.serializeInt("infoWindowType", infoWindowType);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 7 - 1
lib/rewardable/Configuration.h

@@ -65,7 +65,9 @@ struct DLL_LINKAGE ResetInfo
 
 	/// if true - re-randomize rewards on a new week
 	bool rewards;
-
+	
+	void serializeJson(JsonSerializeFormat & handler);
+	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & period;
@@ -84,6 +86,8 @@ struct DLL_LINKAGE VisitInfo
 
 	/// Event to which this reward is assigned
 	EEventType visitType;
+	
+	void serializeJson(JsonSerializeFormat & handler);
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -121,6 +125,8 @@ struct DLL_LINKAGE Configuration
 	EVisitMode getVisitMode() const;
 	ui16 getResetDuration() const;
 	
+	void serializeJson(JsonSerializeFormat & handler);
+	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & info;

+ 5 - 0
lib/rewardable/Interface.cpp

@@ -147,4 +147,9 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re
 			cb->removeAfterVisit(instance);
 }
 
+void Rewardable::Interface::serializeJson(JsonSerializeFormat & handler)
+{
+	configuration.serializeJson(handler);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/rewardable/Interface.h

@@ -44,6 +44,8 @@ public:
 	
 	Rewardable::Configuration configuration;
 	
+	void serializeJson(JsonSerializeFormat & handler);
+	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & configuration;

+ 44 - 0
lib/rewardable/Limiter.cpp

@@ -14,6 +14,9 @@
 #include "../IGameCallback.h"
 #include "../CPlayerState.h"
 #include "../mapObjects/CGHeroInstance.h"
+#include "../serializer/JsonSerializeFormat.h"
+#include "../constants/StringConstants.h"
+#include "../CSkillHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -119,4 +122,45 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 	return false;
 }
 
+void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("dayOfWeek", dayOfWeek);
+	handler.serializeInt("daysPassed", daysPassed);
+	resources.serializeJson(handler, "resources");
+	handler.serializeInt("manaPercentage", manaPercentage);
+	handler.serializeInt("heroExperience", heroExperience);
+	handler.serializeInt("heroLevel", heroLevel);
+	handler.serializeInt("manaPoints", manaPoints);
+	handler.serializeIdArray("artifacts", artifacts);
+	handler.enterArray("creatures").serializeStruct(creatures);
+	handler.enterArray("primary").serializeArray(primary);
+	{
+		auto a = handler.enterArray("secondary");
+		std::vector<std::pair<SecondarySkill, si32>> fieldValue(secondary.begin(), secondary.end());
+		a.serializeStruct<std::pair<SecondarySkill, si32>>(fieldValue, [](JsonSerializeFormat & h, std::pair<SecondarySkill, si32> & e)
+		{
+			h.serializeId("skill", e.first, SecondarySkill{}, VLC->skillh->decodeSkill, VLC->skillh->encodeSkill);
+			h.serializeId("level", e.second, 0, [](const std::string & i){return vstd::find_pos(NSecondarySkill::levels, i);}, [](si32 i){return NSecondarySkill::levels.at(i);});
+		});
+		a.syncSize(fieldValue);
+		secondary = std::map<SecondarySkill, si32>(fieldValue.begin(), fieldValue.end());
+	}
+	//sublimiters
+	auto serializeSublimitersList = [&handler](const std::string & field, LimitersList & container)
+	{
+		auto a = handler.enterArray(field);
+		a.syncSize(container);
+		for(int i = 0; i < container.size(); ++i)
+		{
+			if(!handler.saving)
+				container[i] = std::make_shared<Rewardable::Limiter>();
+			auto e = a.enterStruct(i);
+			container[i]->serializeJson(handler);
+		}
+	};
+	serializeSublimitersList("allOf", allOf);
+	serializeSublimitersList("anyOf", anyOf);
+	serializeSublimitersList("noneOf", noneOf);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/rewardable/Limiter.h

@@ -92,6 +92,8 @@ struct DLL_LINKAGE Limiter
 		h & anyOf;
 		h & noneOf;
 	}
+	
+	void serializeJson(JsonSerializeFormat & handler);
 };
 
 }

+ 57 - 1
lib/rewardable/Reward.cpp

@@ -12,6 +12,9 @@
 #include "Reward.h"
 
 #include "../mapObjects/CGHeroInstance.h"
+#include "../serializer/JsonSerializeFormat.h"
+#include "../constants/StringConstants.h"
+#include "../CSkillHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -58,7 +61,15 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps,
 {
 	for (auto comp : extraComponents)
 		comps.push_back(comp);
-
+	
+	for (auto & bonus : bonuses)
+	{
+		if (bonus.type == BonusType::MORALE)
+			comps.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0);
+		if (bonus.type == BonusType::LUCK)
+			comps.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0);
+	}
+	
 	if (heroExperience)
 	{
 		comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h->calculateXp(heroExperience)), 0);
@@ -94,4 +105,49 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps,
 	}
 }
 
+void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler)
+{
+	resources.serializeJson(handler, "resources");
+	handler.serializeBool("removeObject", removeObject);
+	handler.serializeInt("manaPercentage", manaPercentage);
+	handler.serializeInt("movePercentage", movePercentage);
+	handler.serializeInt("heroExperience", heroExperience);
+	handler.serializeInt("heroLevel", heroLevel);
+	handler.serializeInt("manaDiff", manaDiff);
+	handler.serializeInt("manaOverflowFactor", manaOverflowFactor);
+	handler.serializeInt("movePoints", movePoints);
+	handler.serializeIdArray("artifacts", artifacts);
+	handler.serializeIdArray("spells", spells);
+	handler.enterArray("creatures").serializeStruct(creatures);
+	handler.enterArray("primary").serializeArray(primary);
+	{
+		auto a = handler.enterArray("secondary");
+		std::vector<std::pair<SecondarySkill, si32>> fieldValue(secondary.begin(), secondary.end());
+		a.serializeStruct<std::pair<SecondarySkill, si32>>(fieldValue, [](JsonSerializeFormat & h, std::pair<SecondarySkill, si32> & e)
+		{
+			h.serializeId("skill", e.first, SecondarySkill{}, VLC->skillh->decodeSkill, VLC->skillh->encodeSkill);
+			h.serializeId("level", e.second, 0, [](const std::string & i){return vstd::find_pos(NSecondarySkill::levels, i);}, [](si32 i){return NSecondarySkill::levels.at(i);});
+		});
+		a.syncSize(fieldValue);
+		secondary = std::map<SecondarySkill, si32>(fieldValue.begin(), fieldValue.end());
+	}
+	
+	{
+		auto a = handler.enterArray("creaturesChange");
+		std::vector<std::pair<CreatureID, CreatureID>> fieldValue(creaturesChange.begin(), creaturesChange.end());
+		a.serializeStruct<std::pair<CreatureID, CreatureID>>(fieldValue, [](JsonSerializeFormat & h, std::pair<CreatureID, CreatureID> & e)
+		{
+			h.serializeId("creature", e.first, CreatureID{});
+			h.serializeId("amount", e.second, CreatureID{});
+		});
+		creaturesChange = std::map<CreatureID, CreatureID>(fieldValue.begin(), fieldValue.end());
+	}
+	
+	{
+		auto a = handler.enterStruct("spellCast");
+		a->serializeId("spell", spellCast.first, SpellID{});
+		a->serializeInt("level", spellCast.second);
+	}
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/rewardable/Reward.h

@@ -110,6 +110,8 @@ struct DLL_LINKAGE Reward
 		if(version >= 821)
 			h & spellCast;
 	}
+	
+	void serializeJson(JsonSerializeFormat & handler);
 };
 }
 

+ 45 - 17
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;
 	};
@@ -441,9 +464,11 @@ 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::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;
 				
@@ -490,10 +515,11 @@ 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::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();
@@ -513,9 +539,11 @@ 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::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();

+ 29 - 3
lib/serializer/JsonSerializeFormat.h

@@ -74,18 +74,44 @@ public:
 	///String <-> Json string
 	void serializeString(const size_t index, std::string & value);
 
-	///vector of serializable <-> Json vector of structs
+	///vector of anything int-convertible <-> Json vector of integers
+	template<typename T>
+	void serializeArray(std::vector<T> & value)
+	{
+		syncSize(value, JsonNode::JsonType::DATA_STRUCT);
+
+		for(size_t idx = 0; idx < size(); idx++)
+			serializeInt(idx, value[idx]);
+	}
+	
+	///vector of strings <-> Json vector of strings
+	void serializeArray(std::vector<std::string> & value)
+	{
+		syncSize(value, JsonNode::JsonType::DATA_STRUCT);
+
+		for(size_t idx = 0; idx < size(); idx++)
+			serializeString(idx, value[idx]);
+	}
+	
+	///vector of anything with custom serializing function <-> Json vector of structs
 	template <typename Element>
-	void serializeStruct(std::vector<Element> & value)
+	void serializeStruct(std::vector<Element> & value, std::function<void(JsonSerializeFormat&, Element&)> serializer)
 	{
 		syncSize(value, JsonNode::JsonType::DATA_STRUCT);
 
 		for(size_t idx = 0; idx < size(); idx++)
 		{
 			auto s = enterStruct(idx);
-			value[idx].serializeJson(*owner);
+			serializer(*owner, value[idx]);
 		}
 	}
+	
+	///vector of serializable <-> Json vector of structs
+	template <typename Element>
+	void serializeStruct(std::vector<Element> & value)
+	{
+		serializeStruct<Element>(value, [](JsonSerializeFormat & h, Element & e){e.serializeJson(h);});
+	}
 
 	void resize(const size_t newSize);
 	void resize(const size_t newSize, JsonNode::JsonType type);

+ 16 - 8
mapeditor/inspector/inspector.cpp

@@ -67,6 +67,7 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default
 	INIT_OBJ_TYPE(CGHeroInstance);
 	INIT_OBJ_TYPE(CGSignBottle);
 	INIT_OBJ_TYPE(CGLighthouse);
+	//INIT_OBJ_TYPE(CRewardableObject);
 	//INIT_OBJ_TYPE(CGPandoraBox);
 	//INIT_OBJ_TYPE(CGEvent);
 	//INIT_OBJ_TYPE(CGSeerHut);
@@ -375,14 +376,19 @@ void Inspector::updateProperties(CGCreature * o)
 	//addProperty("Resources reward", o->resources); //TODO: implement in setProperty
 }
 
+void Inspector::updateProperties(CRewardableObject * o)
+{
+	if(!o) return;
+		
+	auto * delegate = new RewardsDelegate(*map, *o);
+	addProperty("Reward", PropertyEditorPlaceholder(), delegate, false);
+}
+
 void Inspector::updateProperties(CGPandoraBox * o)
 {
 	if(!o) return;
 	
 	addProperty("Message", o->message, new MessageDelegate, false);
-	
-	auto * delegate = new RewardsPandoraDelegate(*map, *o);
-	addProperty("Reward", PropertyEditorPlaceholder(), delegate, false);
 }
 
 void Inspector::updateProperties(CGEvent * o)
@@ -413,11 +419,6 @@ void Inspector::updateProperties(CGSeerHut * o)
 		auto * delegate = new QuestDelegate(*map, *o);
 		addProperty("Quest", PropertyEditorPlaceholder(), delegate, false);
 	}
-	
-	{ //Reward
-		auto * delegate = new RewardsSeerhutDelegate(*map, *o);
-		addProperty("Reward", PropertyEditorPlaceholder(), delegate, false);
-	}
 }
 
 void Inspector::updateProperties()
@@ -458,6 +459,7 @@ void Inspector::updateProperties()
 	UPDATE_OBJ_PROPERTIES(CGHeroInstance);
 	UPDATE_OBJ_PROPERTIES(CGSignBottle);
 	UPDATE_OBJ_PROPERTIES(CGLighthouse);
+	UPDATE_OBJ_PROPERTIES(CRewardableObject);
 	UPDATE_OBJ_PROPERTIES(CGPandoraBox);
 	UPDATE_OBJ_PROPERTIES(CGEvent);
 	UPDATE_OBJ_PROPERTIES(CGSeerHut);
@@ -503,6 +505,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value)
 	SET_PROPERTIES(CGShipyard);
 	SET_PROPERTIES(CGSignBottle);
 	SET_PROPERTIES(CGLighthouse);
+	SET_PROPERTIES(CRewardableObject);
 	SET_PROPERTIES(CGPandoraBox);
 	SET_PROPERTIES(CGEvent);
 	SET_PROPERTIES(CGSeerHut);
@@ -518,6 +521,11 @@ void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVarian
 	if(!o) return;
 }
 
+void Inspector::setProperty(CRewardableObject * o, const QString & key, const QVariant & value)
+{
+	if(!o) return;
+}
+
 void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVariant & value)
 {
 	if(!o) return;

+ 3 - 0
mapeditor/inspector/inspector.h

@@ -17,6 +17,7 @@
 #include "../lib/GameConstants.h"
 #include "../lib/mapObjects/CGCreature.h"
 #include "../lib/mapObjects/MapObjects.h"
+#include "../lib/mapObjects/CRewardableObject.h"
 #include "../lib/ResourceSet.h"
 
 #define DECLARE_OBJ_TYPE(x) void initialize(x*);
@@ -45,6 +46,7 @@ public:
 	DECLARE_OBJ_TYPE(CGCreature);
 	DECLARE_OBJ_TYPE(CGSignBottle);
 	DECLARE_OBJ_TYPE(CGLighthouse);
+	//DECLARE_OBJ_TYPE(CRewardableObject);
 	//DECLARE_OBJ_TYPE(CGEvent);
 	//DECLARE_OBJ_TYPE(CGPandoraBox);
 	//DECLARE_OBJ_TYPE(CGSeerHut);
@@ -73,6 +75,7 @@ protected:
 	DECLARE_OBJ_PROPERTY_METHODS(CGCreature);
 	DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle);
 	DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse);
+	DECLARE_OBJ_PROPERTY_METHODS(CRewardableObject);
 	DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
 	DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
 	DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut);

+ 536 - 300
mapeditor/inspector/rewardswidget.cpp

@@ -17,31 +17,160 @@
 #include "../lib/CCreatureHandler.h"
 #include "../lib/constants/StringConstants.h"
 #include "../lib/mapping/CMap.h"
+#include "../lib/rewardable/Configuration.h"
+#include "../lib/rewardable/Limiter.h"
+#include "../lib/rewardable/Reward.h"
+#include "../lib/mapObjects/CGPandoraBox.h"
+#include "../lib/mapObjects/CQuest.h"
 
-RewardsWidget::RewardsWidget(const CMap & m, CGPandoraBox & p, QWidget *parent) :
+RewardsWidget::RewardsWidget(const CMap & m, CRewardableObject & p, QWidget *parent) :
 	QDialog(parent),
 	map(m),
-	pandora(&p),
-	seerhut(nullptr),
+	object(p),
 	ui(new Ui::RewardsWidget)
 {
 	ui->setupUi(this);
 	
-	for(auto & type : rewardTypes)
-		ui->rewardType->addItem(QString::fromStdString(type));
-}
-
-RewardsWidget::RewardsWidget(const CMap & m, CGSeerHut & p, QWidget *parent) :
-	QDialog(parent),
-	map(m),
-	pandora(nullptr),
-	seerhut(&p),
-	ui(new Ui::RewardsWidget)
-{
-	ui->setupUi(this);
+	//fill core elements
+	for(const auto & s : Rewardable::VisitModeString)
+		ui->visitMode->addItem(QString::fromStdString(s));
+	
+	for(const auto & s : Rewardable::SelectModeString)
+		ui->selectMode->addItem(QString::fromStdString(s));
+	
+	for(const std::string & s : {"AUTO", "MODAL", "INFO"})
+		ui->windowMode->addItem(QString::fromStdString(s));
+	
+	ui->lDayOfWeek->addItem(tr("None"));
+	for(int i = 1; i <= 7; ++i)
+		ui->lDayOfWeek->addItem(tr("Day %1").arg(i));
+	
+	//fill resources
+	ui->rResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1);
+	ui->lResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1);
+	for(int i = 0; i < GameConstants::RESOURCE_QUANTITY - 1; ++i)
+	{
+		for(auto * w : {ui->rResources, ui->lResources})
+		{
+			auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i]));
+			item->setData(Qt::UserRole, QVariant::fromValue(i));
+			w->setItem(i, 0, item);
+			w->setCellWidget(i, 1, new QSpinBox);
+		}
+	}
+	
+	//fill artifacts
+	for(int i = 0; i < map.allowedArtifact.size(); ++i)
+	{
+		for(auto * w : {ui->rArtifacts, ui->lArtifacts})
+		{
+			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(!map.allowedArtifact[i])
+				item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+			w->addItem(item);
+		}
+	}
+	
+	//fill spells
+	for(int i = 0; i < map.allowedSpells.size(); ++i)
+	{
+		for(auto * w : {ui->rSpells, ui->lSpells})
+		{
+			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(!map.allowedSpells[i])
+				item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+			w->addItem(item);
+		}
+		
+		//spell cast
+		if(VLC->spells()->getByIndex(i)->isAdventure())
+		{
+			ui->castSpell->addItem(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated()));
+			ui->castSpell->setItemData(ui->castSpell->count() - 1, QVariant::fromValue(i));
+		}
+	}
+	
+	//fill skills
+	ui->rSkills->setRowCount(map.allowedAbilities.size());
+	ui->lSkills->setRowCount(map.allowedAbilities.size());
+	for(int i = 0; i < map.allowedAbilities.size(); ++i)
+	{
+		for(auto * w : {ui->rSkills, ui->lSkills})
+		{
+			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(!map.allowedAbilities[i])
+			{
+				item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+				widget->setEnabled(false);
+			}
+			
+			w->setItem(i, 0, item);
+			w->setCellWidget(i, 1, widget);
+		}
+	}
+	
+	//fill creatures
+	for(auto & creature : VLC->creh->objects)
+	{
+		for(auto * w : {ui->rCreatureId, ui->lCreatureId})
+		{
+			w->addItem(QString::fromStdString(creature->getNameSingularTranslated()));
+			w->setItemData(w->count() - 1, creature->getIndex());
+		}
+	}
+	
+	//fill spell cast
+	for(auto & s : NSecondarySkill::levels)
+		ui->castLevel->addItem(QString::fromStdString(s));
+	on_castSpellCheck_toggled(false);
+	
+	//fill bonuses
+	for(auto & s : bonusDurationMap)
+		ui->bonusDuration->addItem(QString::fromStdString(s.first));
+	for(auto & s : bonusNameMap)
+		ui->bonusType->addItem(QString::fromStdString(s.first));
+	
+	//set default values
+	if(dynamic_cast<CGPandoraBox*>(&object))
+	{
+		ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once"));
+		ui->visitMode->setEnabled(false);
+		ui->selectMode->setCurrentIndex(vstd::find_pos(Rewardable::SelectModeString, "selectFirst"));
+		ui->selectMode->setEnabled(false);
+		ui->windowMode->setEnabled(false);
+		ui->canRefuse->setEnabled(false);
+	}
+	
+	if(auto * e = dynamic_cast<CGEvent*>(&object))
+	{
+		ui->selectMode->setEnabled(true);
+		if(!e->removeAfterVisit)
+			ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "unlimited"));
+	}
+	
+	if(dynamic_cast<CGSeerHut*>(&object))
+	{
+		ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once"));
+		ui->visitMode->setEnabled(false);
+		ui->windowMode->setEnabled(false);
+		ui->canRefuse->setChecked(true);
+		ui->canRefuse->setEnabled(false);
+	}
 	
-	for(auto & type : rewardTypes)
-		ui->rewardType->addItem(QString::fromStdString(type));
+	//hide elements
+	ui->eventInfoGroup->hide();
 }
 
 RewardsWidget::~RewardsWidget()
@@ -49,337 +178,453 @@ RewardsWidget::~RewardsWidget()
 	delete ui;
 }
 
-QList<QString> RewardsWidget::getListForType(RewardType typeId)
+
+void RewardsWidget::obtainData()
 {
-	assert(typeId < rewardTypes.size());
-	QList<QString> result;
-	
-	switch (typeId) {
-		case RewardType::RESOURCE:
-			//to convert string to index WOOD = 0, MERCURY, ORE, SULFUR, CRYSTAL, GEMS, GOLD, MITHRIL,
-			result.append("Wood");
-			result.append("Mercury");
-			result.append("Ore");
-			result.append("Sulfur");
-			result.append("Crystals");
-			result.append("Gems");
-			result.append("Gold");
-			break;
-			
-		case RewardType::PRIMARY_SKILL:
-			for(auto s : NPrimarySkill::names)
-				result.append(QString::fromStdString(s));
-			break;
-			
-		case RewardType::SECONDARY_SKILL:
-			for(int i = 0; i < map.allowedAbilities.size(); ++i)
-			{
-				if(map.allowedAbilities[i])
-					result.append(QString::fromStdString(VLC->skills()->getByIndex(i)->getNameTranslated()));
-			}
-			break;
-			
-		case RewardType::ARTIFACT:
-			for(int i = 0; i < map.allowedArtifact.size(); ++i)
-			{
-				if(map.allowedArtifact[i])
-					result.append(QString::fromStdString(VLC->artifacts()->getByIndex(i)->getNameTranslated()));
-			}
-			break;
-			
-		case RewardType::SPELL:
-			for(int i = 0; i < map.allowedSpells.size(); ++i)
-			{
-				if(map.allowedSpells[i])
-					result.append(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated()));
-			}
-			break;
-			
-		case RewardType::CREATURE:
-			for(auto creature : VLC->creh->objects)
-			{
-				result.append(QString::fromStdString(creature->getNameSingularTranslated()));
-			}
-			break;
-	}
-	return result;
+	//common parameters
+	ui->visitMode->setCurrentIndex(object.configuration.visitMode);
+	ui->selectMode->setCurrentIndex(object.configuration.selectMode);
+	ui->windowMode->setCurrentIndex(int(object.configuration.infoWindowType));
+	ui->onSelectText->setText(QString::fromStdString(object.configuration.onSelect.toString()));
+	ui->canRefuse->setChecked(object.configuration.canRefuse);
+	
+	//reset parameters
+	ui->resetPeriod->setValue(object.configuration.resetParameters.period);
+	ui->resetVisitors->setChecked(object.configuration.resetParameters.visitors);
+	ui->resetRewards->setChecked(object.configuration.resetParameters.rewards);
+	
+	ui->visitInfoList->clear();
+	
+	for([[maybe_unused]] auto & a : object.configuration.info)
+		ui->visitInfoList->addItem(tr("Reward %1").arg(ui->visitInfoList->count() + 1));
+	
+	if(ui->visitInfoList->currentItem())
+		loadCurrentVisitInfo(ui->visitInfoList->currentRow());
 }
 
-void RewardsWidget::on_rewardType_activated(int index)
+bool RewardsWidget::commitChanges()
 {
-	ui->rewardList->clear();
-	ui->rewardList->setEnabled(true);
-	assert(index < rewardTypes.size());
+	//common parameters
+	object.configuration.visitMode = ui->visitMode->currentIndex();
+	object.configuration.selectMode = ui->selectMode->currentIndex();
+	object.configuration.infoWindowType = EInfoWindowMode(ui->windowMode->currentIndex());
+	if(ui->onSelectText->text().isEmpty())
+		object.configuration.onSelect.clear();
+	else
+		object.configuration.onSelect = MetaString::createFromRawString(ui->onSelectText->text().toStdString());
+	object.configuration.canRefuse = ui->canRefuse->isChecked();
+	
+	//reset parameters
+	object.configuration.resetParameters.period = ui->resetPeriod->value();
+	object.configuration.resetParameters.visitors = ui->resetVisitors->isChecked();
+	object.configuration.resetParameters.rewards = ui->resetRewards->isChecked();
 	
-	auto l = getListForType(RewardType(index));
-	if(l.empty())
-		ui->rewardList->setEnabled(false);
+	if(ui->visitInfoList->currentItem())
+		saveCurrentVisitInfo(ui->visitInfoList->currentRow());
 	
-	for(auto & s : l)
-		ui->rewardList->addItem(s);
+	return true;
 }
 
-void RewardsWidget::obtainData()
+void RewardsWidget::saveCurrentVisitInfo(int index)
 {
-	if(pandora)
-	{
-		if(pandora->gainedExp > 0)
-			addReward(RewardType::EXPERIENCE, 0, pandora->gainedExp);
-		if(pandora->manaDiff)
-			addReward(RewardType::MANA, 0, pandora->manaDiff);
-		if(pandora->moraleDiff)
-			addReward(RewardType::MORALE, 0, pandora->moraleDiff);
-		if(pandora->luckDiff)
-			addReward(RewardType::LUCK, 0, pandora->luckDiff);
-		if(pandora->resources.nonZero())
-		{
-			for(ResourceSet::nziterator resiter(pandora->resources); resiter.valid(); ++resiter)
-				addReward(RewardType::RESOURCE, resiter->resType, resiter->resVal);
-		}
-		for(int idx = 0; idx < pandora->primskills.size(); ++idx)
-		{
-			if(pandora->primskills[idx])
-				addReward(RewardType::PRIMARY_SKILL, idx, pandora->primskills[idx]);
-		}
-		assert(pandora->abilities.size() == pandora->abilityLevels.size());
-		for(int idx = 0; idx < pandora->abilities.size(); ++idx)
-		{
-			addReward(RewardType::SECONDARY_SKILL, pandora->abilities[idx].getNum(), pandora->abilityLevels[idx]);
-		}
-		for(auto art : pandora->artifacts)
-		{
-			addReward(RewardType::ARTIFACT, art.getNum(), 1);
-		}
-		for(auto spell : pandora->spells)
+	auto & vinfo = object.configuration.info.at(index);
+	vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
+	if(ui->rewardMessage->text().isEmpty())
+		vinfo.message.clear();
+	else
+		vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString());
+	
+	vinfo.reward.heroLevel = ui->rHeroLevel->value();
+	vinfo.reward.heroExperience = ui->rHeroExperience->value();
+	vinfo.reward.manaDiff = ui->rManaDiff->value();
+	vinfo.reward.manaPercentage = ui->rManaPercentage->value();
+	vinfo.reward.manaOverflowFactor = ui->rOverflowFactor->value();
+	vinfo.reward.movePoints = ui->rMovePoints->value();
+	vinfo.reward.movePercentage = ui->rMovePercentage->value();
+	vinfo.reward.removeObject = ui->removeObject->isChecked();
+	vinfo.reward.primary.resize(4);
+	vinfo.reward.primary[0] = ui->rAttack->value();
+	vinfo.reward.primary[1] = ui->rDefence->value();
+	vinfo.reward.primary[2] = ui->rPower->value();
+	vinfo.reward.primary[3] = ui->rKnowledge->value();
+	for(int i = 0; i < ui->rResources->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->rResources->cellWidget(i, 1)))
+			vinfo.reward.resources[i] = widget->value();
+	}
+	
+	vinfo.reward.artifacts.clear();
+	for(int i = 0; i < ui->rArtifacts->count(); ++i)
+	{
+		if(ui->rArtifacts->item(i)->checkState() == Qt::Checked)
+			vinfo.reward.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId());
+	}
+	vinfo.reward.spells.clear();
+	for(int i = 0; i < ui->rSpells->count(); ++i)
+	{
+		if(ui->rSpells->item(i)->checkState() == Qt::Checked)
+			vinfo.reward.spells.push_back(VLC->spells()->getByIndex(i)->getId());
+	}
+	
+	vinfo.reward.secondary.clear();
+	for(int i = 0; i < ui->rSkills->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QComboBox*>(ui->rSkills->cellWidget(i, 1)))
 		{
-			addReward(RewardType::SPELL, spell.getNum(), 1);
+			if(widget->currentIndex() > 0)
+				vinfo.reward.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex();
 		}
-		for(int i = 0; i < pandora->creatures.Slots().size(); ++i)
+	}
+	
+	vinfo.reward.creatures.clear();
+	for(int i = 0; i < ui->rCreatures->rowCount(); ++i)
+	{
+		int index = ui->rCreatures->item(i, 0)->data(Qt::UserRole).toInt();
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->rCreatures->cellWidget(i, 1)))
+			if(widget->value())
+				vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
+	}
+	
+	vinfo.reward.spellCast.first = SpellID::NONE;
+	if(ui->castSpellCheck->isChecked())
+	{
+		vinfo.reward.spellCast.first = VLC->spells()->getByIndex(ui->castSpell->itemData(ui->castSpell->currentIndex()).toInt())->getId();
+		vinfo.reward.spellCast.second = ui->castLevel->currentIndex();
+	}
+	
+	vinfo.reward.bonuses.clear();
+	for(int i = 0; i < ui->bonuses->rowCount(); ++i)
+	{
+		auto dur = bonusDurationMap.at(ui->bonuses->item(i, 0)->text().toStdString());
+		auto typ = bonusNameMap.at(ui->bonuses->item(i, 1)->text().toStdString());
+		auto val = ui->bonuses->item(i, 2)->data(Qt::UserRole).toInt();
+		vinfo.reward.bonuses.emplace_back(dur, typ, BonusSource::OBJECT, val, object.id);
+	}
+	
+	vinfo.limiter.dayOfWeek = ui->lDayOfWeek->currentIndex();
+	vinfo.limiter.daysPassed = ui->lDaysPassed->value();
+	vinfo.limiter.heroLevel = ui->lHeroLevel->value();
+	vinfo.limiter.heroExperience = ui->lHeroExperience->value();
+	vinfo.limiter.manaPoints = ui->lManaPoints->value();
+	vinfo.limiter.manaPercentage = ui->lManaPercentage->value();
+	vinfo.limiter.primary.resize(4);
+	vinfo.limiter.primary[0] = ui->lAttack->value();
+	vinfo.limiter.primary[1] = ui->lDefence->value();
+	vinfo.limiter.primary[2] = ui->lPower->value();
+	vinfo.limiter.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)))
+			vinfo.limiter.resources[i] = widget->value();
+	}
+	
+	vinfo.limiter.artifacts.clear();
+	for(int i = 0; i < ui->lArtifacts->count(); ++i)
+	{
+		if(ui->lArtifacts->item(i)->checkState() == Qt::Checked)
+			vinfo.limiter.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId());
+	}
+	vinfo.limiter.spells.clear();
+	for(int i = 0; i < ui->lSpells->count(); ++i)
+	{
+		if(ui->lSpells->item(i)->checkState() == Qt::Checked)
+			vinfo.limiter.spells.push_back(VLC->spells()->getByIndex(i)->getId());
+	}
+	
+	vinfo.limiter.secondary.clear();
+	for(int i = 0; i < ui->lSkills->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QComboBox*>(ui->lSkills->cellWidget(i, 1)))
 		{
-			if(auto c = pandora->creatures.getCreature(SlotID(i)))
-				addReward(RewardType::CREATURE, c->getId(), pandora->creatures.getStackCount(SlotID(i)));
+			if(widget->currentIndex() > 0)
+				vinfo.limiter.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex();
 		}
 	}
 	
-	if(seerhut)
+	vinfo.limiter.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())
+				vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
+	}
+}
+
+void RewardsWidget::loadCurrentVisitInfo(int index)
+{
+	for(auto * w : {ui->rArtifacts, ui->rSpells, ui->lArtifacts, ui->lSpells})
+		for(int i = 0; i < w->count(); ++i)
+			w->item(i)->setCheckState(Qt::Unchecked);
+	
+	for(auto * w : {ui->rSkills, ui->lSkills})
+		for(int i = 0; i < w->rowCount(); ++i)
+			if(auto * widget = qobject_cast<QComboBox*>(ui->rSkills->cellWidget(i, 1)))
+				widget->setCurrentIndex(0);
+	
+	ui->rCreatures->setRowCount(0);
+	ui->lCreatures->setRowCount(0);
+	ui->bonuses->setRowCount(0);
+	
+	const auto & vinfo = object.configuration.info.at(index);
+	ui->rewardMessage->setText(QString::fromStdString(vinfo.message.toString()));
+	
+	ui->rHeroLevel->setValue(vinfo.reward.heroLevel);
+	ui->rHeroExperience->setValue(vinfo.reward.heroExperience);
+	ui->rManaDiff->setValue(vinfo.reward.manaDiff);
+	ui->rManaPercentage->setValue(vinfo.reward.manaPercentage);
+	ui->rOverflowFactor->setValue(vinfo.reward.manaOverflowFactor);
+	ui->rMovePoints->setValue(vinfo.reward.movePoints);
+	ui->rMovePercentage->setValue(vinfo.reward.movePercentage);
+	ui->removeObject->setChecked(vinfo.reward.removeObject);
+	ui->rAttack->setValue(vinfo.reward.primary[0]);
+	ui->rDefence->setValue(vinfo.reward.primary[1]);
+	ui->rPower->setValue(vinfo.reward.primary[2]);
+	ui->rKnowledge->setValue(vinfo.reward.primary[3]);
+	for(int i = 0; i < ui->rResources->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->rResources->cellWidget(i, 1)))
+			widget->setValue(vinfo.reward.resources[i]);
+	}
+	
+	for(auto i : vinfo.reward.artifacts)
+		ui->rArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto i : vinfo.reward.spells)
+		ui->rArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto & i : vinfo.reward.secondary)
+	{
+		int index = VLC->skills()->getById(i.first)->getIndex();
+		if(auto * widget = qobject_cast<QComboBox*>(ui->rSkills->cellWidget(index, 1)))
+			widget->setCurrentIndex(i.second);
+	}
+	for(auto & i : vinfo.reward.creatures)
+	{
+		int index = i.type->getIndex();
+		ui->rCreatureId->setCurrentIndex(index);
+		ui->rCreatureAmount->setValue(i.count);
+		onCreatureAdd(ui->rCreatures, ui->rCreatureId, ui->rCreatureAmount);
+	}
+	
+	ui->castSpellCheck->setChecked(vinfo.reward.spellCast.first != SpellID::NONE);
+	if(ui->castSpellCheck->isChecked())
+	{
+		int index = VLC->spells()->getById(vinfo.reward.spellCast.first)->getIndex();
+		ui->castSpell->setCurrentIndex(index);
+		ui->castLevel->setCurrentIndex(vinfo.reward.spellCast.second);
+	}
+	
+	for(auto & i : vinfo.reward.bonuses)
 	{
-		switch(seerhut->rewardType)
+		auto dur = vstd::findKey(bonusDurationMap, i.duration);
+		for(int i = 0; i < ui->bonusDuration->count(); ++i)
 		{
-			case CGSeerHut::ERewardType::EXPERIENCE:
-				addReward(RewardType::EXPERIENCE, 0, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::MANA_POINTS:
-				addReward(RewardType::MANA, 0, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::MORALE_BONUS:
-				addReward(RewardType::MORALE, 0, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::LUCK_BONUS:
-				addReward(RewardType::LUCK, 0, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::RESOURCES:
-				addReward(RewardType::RESOURCE, seerhut->rID, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::PRIMARY_SKILL:
-				addReward(RewardType::PRIMARY_SKILL, seerhut->rID, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::SECONDARY_SKILL:
-				addReward(RewardType::SECONDARY_SKILL, seerhut->rID, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::ARTIFACT:
-				addReward(RewardType::ARTIFACT, seerhut->rID, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::SPELL:
-				addReward(RewardType::SPELL, seerhut->rID, seerhut->rVal);
-				break;
-				
-			case CGSeerHut::ERewardType::CREATURE:
-				addReward(RewardType::CREATURE, seerhut->rID, seerhut->rVal);
+			if(ui->bonusDuration->itemText(i) == QString::fromStdString(dur))
+			{
+				ui->bonusDuration->setCurrentIndex(i);
 				break;
-				
-			default:
+			}
+		}
+		
+		auto typ = vstd::findKey(bonusNameMap, i.type);
+		for(int i = 0; i < ui->bonusType->count(); ++i)
+		{
+			if(ui->bonusType->itemText(i) == QString::fromStdString(typ))
+			{
+				ui->bonusType->setCurrentIndex(i);
 				break;
+			}
 		}
+		
+		ui->bonusValue->setValue(i.val);
+		on_bonusAdd_clicked();
+	}
+	
+	ui->lDayOfWeek->setCurrentIndex(vinfo.limiter.dayOfWeek);
+	ui->lDaysPassed->setValue(vinfo.limiter.daysPassed);
+	ui->lHeroLevel->setValue(vinfo.limiter.heroLevel);
+	ui->lHeroExperience->setValue(vinfo.limiter.heroExperience);
+	ui->lManaPoints->setValue(vinfo.limiter.manaPoints);
+	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]);
+	for(int i = 0; i < ui->lResources->rowCount(); ++i)
+	{
+		if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
+			widget->setValue(vinfo.limiter.resources[i]);
+	}
+	
+	for(auto i : vinfo.limiter.artifacts)
+		ui->lArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto i : vinfo.limiter.spells)
+		ui->lArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
+	for(auto & i : vinfo.limiter.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 : vinfo.limiter.creatures)
+	{
+		int index = i.type->getIndex();
+		ui->lCreatureId->setCurrentIndex(index);
+		ui->lCreatureAmount->setValue(i.count);
+		onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
 	}
 }
 
-bool RewardsWidget::commitChanges()
+void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget)
 {
-	bool haveRewards = false;
-	if(pandora)
-	{
-		pandora->abilities.clear();
-		pandora->abilityLevels.clear();
-		pandora->primskills.resize(GameConstants::PRIMARY_SKILLS, 0);
-		pandora->resources = ResourceSet();
-		pandora->artifacts.clear();
-		pandora->spells.clear();
-		pandora->creatures.clearSlots();
-		
-		for(int row = 0; row < rewards; ++row)
+	QTableWidgetItem * item = nullptr;
+	QSpinBox * widget = nullptr;
+	for(int i = 0; i < listWidget->rowCount(); ++i)
+	{
+		if(auto * cname = listWidget->item(i, 0))
 		{
-			haveRewards = true;
-			int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt();
-			int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0;
-			int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt();
-			switch(typeId)
+			if(cname->data(Qt::UserRole).toInt() == comboWidget->currentData().toInt())
 			{
-				case RewardType::EXPERIENCE:
-					pandora->gainedExp = amount;
-					break;
-					
-				case RewardType::MANA:
-					pandora->manaDiff = amount;
-					break;
-					
-				case RewardType::MORALE:
-					pandora->moraleDiff = amount;
-					break;
-					
-				case RewardType::LUCK:
-					pandora->luckDiff = amount;
-					break;
-					
-				case RewardType::RESOURCE:
-					pandora->resources[listId] = amount;
-					break;
-					
-				case RewardType::PRIMARY_SKILL:
-					pandora->primskills[listId] = amount;
-					break;
-					
-				case RewardType::SECONDARY_SKILL:
-					pandora->abilities.push_back(SecondarySkill(listId));
-					pandora->abilityLevels.push_back(amount);
-					break;
-					
-				case RewardType::ARTIFACT:
-					pandora->artifacts.push_back(ArtifactID(listId));
-					break;
-					
-				case RewardType::SPELL:
-					pandora->spells.push_back(SpellID(listId));
-					break;
-					
-				case RewardType::CREATURE:
-					auto slot = pandora->creatures.getFreeSlot();
-					if(slot != SlotID() && amount > 0)
-						pandora->creatures.addToSlot(slot, CreatureID(listId), amount);
-					break;
+				item = cname;
+				widget = qobject_cast<QSpinBox*>(listWidget->cellWidget(i, 1));
+				break;
 			}
 		}
 	}
-	if(seerhut)
+	
+	if(!item)
 	{
-		for(int row = 0; row < rewards; ++row)
-		{
-			haveRewards = true;
-			int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt();
-			int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0;
-			int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt();
-			seerhut->rewardType = CGSeerHut::ERewardType(typeId + 1);
-			seerhut->rID = listId;
-			seerhut->rVal = amount;
-		}
+		listWidget->setRowCount(listWidget->rowCount() + 1);
+		item = new QTableWidgetItem(comboWidget->currentText());
+		listWidget->setItem(listWidget->rowCount() - 1, 0, item);
 	}
-	return haveRewards;
+	
+	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 RewardsWidget::on_rewardList_activated(int index)
+void RewardsWidget::on_addVisitInfo_clicked()
 {
-	ui->rewardAmount->setText(QStringLiteral("1"));
+	ui->visitInfoList->addItem(tr("Reward %1").arg(ui->visitInfoList->count() + 1));
+	object.configuration.info.emplace_back();
 }
 
-void RewardsWidget::addReward(RewardsWidget::RewardType typeId, int listId, int amount)
+
+void RewardsWidget::on_removeVisitInfo_clicked()
 {
-	//for seerhut there could be the only one reward
-	if(!pandora && seerhut && rewards)
-		return;
-	
-	ui->rewardsTable->setRowCount(++rewards);
-	
-	auto itemType = new QTableWidgetItem(QString::fromStdString(rewardTypes[typeId]));
-	itemType->setData(Qt::UserRole, typeId);
-	ui->rewardsTable->setItem(rewards - 1, 0, itemType);
-	
-	auto l = getListForType(typeId);
-	if(!l.empty())
+	int index = ui->visitInfoList->currentRow();
+	object.configuration.info.erase(std::next(object.configuration.info.begin(), index));
+	ui->visitInfoList->blockSignals(true);
+	delete ui->visitInfoList->currentItem();
+	ui->visitInfoList->blockSignals(false);
+	on_visitInfoList_itemSelectionChanged();
+	if(ui->visitInfoList->currentItem())
+		loadCurrentVisitInfo(ui->visitInfoList->currentRow());
+}
+
+void RewardsWidget::on_selectMode_currentIndexChanged(int index)
+{
+	ui->onSelectText->setEnabled(index == vstd::find_pos(Rewardable::SelectModeString, "selectPlayer"));
+}
+
+void RewardsWidget::on_resetPeriod_valueChanged(int arg1)
+{
+	ui->resetRewards->setEnabled(arg1);
+	ui->resetVisitors->setEnabled(arg1);
+}
+
+
+void RewardsWidget::on_visitInfoList_itemSelectionChanged()
+{
+	if(ui->visitInfoList->currentItem() == nullptr)
 	{
-		auto itemCurr = new QTableWidgetItem(getListForType(typeId)[listId]);
-		itemCurr->setData(Qt::UserRole, listId);
-		ui->rewardsTable->setItem(rewards - 1, 1, itemCurr);
+		ui->eventInfoGroup->hide();
+		return;
 	}
 	
-	QString am = QString::number(amount);
-	switch(ui->rewardType->currentIndex())
-	{
-		case 6:
-			if(amount <= 1)
-				am = "Basic";
-			if(amount == 2)
-				am = "Advanced";
-			if(amount >= 3)
-				am = "Expert";
-			break;
-			
-		case 7:
-		case 8:
-			am = "";
-			amount = 1;
-			break;
-	}
-	auto itemCount = new QTableWidgetItem(am);
-	itemCount->setData(Qt::UserRole, amount);
-	ui->rewardsTable->setItem(rewards - 1, 2, itemCount);
+	ui->eventInfoGroup->show();
+}
+
+void RewardsWidget::on_visitInfoList_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous)
+{
+	if(previous)
+		saveCurrentVisitInfo(ui->visitInfoList->row(previous));
+	
+	if(current)
+		loadCurrentVisitInfo(ui->visitInfoList->currentRow());
 }
 
 
-void RewardsWidget::on_buttonAdd_clicked()
+void RewardsWidget::on_rCreatureAdd_clicked()
 {
-	addReward(RewardType(ui->rewardType->currentIndex()), ui->rewardList->currentIndex(), ui->rewardAmount->text().toInt());
+	onCreatureAdd(ui->rCreatures, ui->rCreatureId, ui->rCreatureAmount);
 }
 
 
-void RewardsWidget::on_buttonRemove_clicked()
+void RewardsWidget::on_rCreatureRemove_clicked()
 {
-	auto currentRow = ui->rewardsTable->currentRow();
-	if(currentRow != -1)
-	{
-		ui->rewardsTable->removeRow(currentRow);
-		--rewards;
-	}
+	std::set<int, std::greater<int>> rowsToRemove;
+	for(auto * i : ui->rCreatures->selectedItems())
+		rowsToRemove.insert(i->row());
+	
+	for(auto i : rowsToRemove)
+		ui->rCreatures->removeRow(i);
 }
 
 
-void RewardsWidget::on_buttonClear_clicked()
+void RewardsWidget::on_lCreatureAdd_clicked()
 {
-	ui->rewardsTable->clear();
-	rewards = 0;
+	onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
 }
 
 
-void RewardsWidget::on_rewardsTable_itemSelectionChanged()
+void RewardsWidget::on_lCreatureRemove_clicked()
 {
-	/*auto type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 0);
-	ui->rewardType->setCurrentIndex(type->data(Qt::UserRole).toInt());
-	ui->rewardType->activated(ui->rewardType->currentIndex());
+	std::set<int, std::greater<int>> rowsToRemove;
+	for(auto * i : ui->lCreatures->selectedItems())
+		rowsToRemove.insert(i->row());
 	
-	type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 1);
-	ui->rewardList->setCurrentIndex(type->data(Qt::UserRole).toInt());
-	ui->rewardList->activated(ui->rewardList->currentIndex());
+	for(auto i : rowsToRemove)
+		ui->lCreatures->removeRow(i);
+}
+
+void RewardsWidget::on_castSpellCheck_toggled(bool checked)
+{
+	ui->castSpell->setEnabled(checked);
+	ui->castLevel->setEnabled(checked);
+}
+
+void RewardsWidget::on_bonusAdd_clicked()
+{
+	auto * itemType = new QTableWidgetItem(ui->bonusType->currentText());
+	auto * itemDur = new QTableWidgetItem(ui->bonusDuration->currentText());
+	auto * itemVal = new QTableWidgetItem(QString::number(ui->bonusValue->value()));
+	itemVal->setData(Qt::UserRole, ui->bonusValue->value());
 	
-	type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 2);
-	ui->rewardAmount->setText(QString::number(type->data(Qt::UserRole).toInt()));*/
+	ui->bonuses->setRowCount(ui->bonuses->rowCount() + 1);
+	ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 0, itemDur);
+	ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 1, itemType);
+	ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 2, itemVal);
 }
 
+void RewardsWidget::on_bonusRemove_clicked()
+{
+	std::set<int, std::greater<int>> rowsToRemove;
+	for(auto * i : ui->bonuses->selectedItems())
+		rowsToRemove.insert(i->row());
+	
+	for(auto i : rowsToRemove)
+		ui->bonuses->removeRow(i);
+}
+
+
 void RewardsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
 {
 	if(auto * ed = qobject_cast<RewardsWidget *>(editor))
@@ -404,20 +649,11 @@ void RewardsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, c
 	}
 }
 
-RewardsPandoraDelegate::RewardsPandoraDelegate(const CMap & m, CGPandoraBox & t): map(m), pandora(t), RewardsDelegate()
-{
-}
-
-QWidget * RewardsPandoraDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
-{
-	return new RewardsWidget(map, pandora, parent);
-}
-
-RewardsSeerhutDelegate::RewardsSeerhutDelegate(const CMap & m, CGSeerHut & t): map(m), seerhut(t), RewardsDelegate()
+RewardsDelegate::RewardsDelegate(const CMap & m, CRewardableObject & t): map(m), object(t)
 {
 }
 
-QWidget * RewardsSeerhutDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+QWidget * RewardsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
-	return new RewardsWidget(map, seerhut, parent);
+	return new RewardsWidget(map, object, parent);
 }

+ 31 - 42
mapeditor/inspector/rewardswidget.h

@@ -10,88 +10,77 @@
 #pragma once
 #include "../StdInc.h"
 #include <QDialog>
-#include "../lib/mapObjects/CGPandoraBox.h"
-#include "../lib/mapObjects/CQuest.h"
+#include "../lib/mapObjects/CRewardableObject.h"
 
 namespace Ui {
 class RewardsWidget;
 }
 
-const std::array<std::string, 10> rewardTypes{"Experience", "Mana", "Morale", "Luck", "Resource", "Primary skill", "Secondary skill", "Artifact", "Spell", "Creature"};
-
 class RewardsWidget : public QDialog
 {
 	Q_OBJECT
 
 public:
-	enum RewardType
-	{
-		EXPERIENCE = 0, MANA, MORALE, LUCK, RESOURCE, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE
-	};
 	
-	explicit RewardsWidget(const CMap &, CGPandoraBox &, QWidget *parent = nullptr);
-	explicit RewardsWidget(const CMap &, CGSeerHut &, QWidget *parent = nullptr);
+	explicit RewardsWidget(const CMap &, CRewardableObject &, QWidget *parent = nullptr);
 	~RewardsWidget();
 	
 	void obtainData();
 	bool commitChanges();
 
 private slots:
-	void on_rewardType_activated(int index);
+	void on_addVisitInfo_clicked();
+
+	void on_removeVisitInfo_clicked();
+
+	void on_selectMode_currentIndexChanged(int index);
+
+	void on_resetPeriod_valueChanged(int arg1);
+
+	void on_visitInfoList_itemSelectionChanged();
+
+	void on_visitInfoList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+	void on_rCreatureAdd_clicked();
 
-	void on_rewardList_activated(int index);
+	void on_rCreatureRemove_clicked();
 
-	void on_buttonAdd_clicked();
+	void on_lCreatureAdd_clicked();
 
-	void on_buttonRemove_clicked();
+	void on_lCreatureRemove_clicked();
 
-	void on_buttonClear_clicked();
+	void on_castSpellCheck_toggled(bool checked);
 
-	void on_rewardsTable_itemSelectionChanged();
+	void on_bonusAdd_clicked();
+
+	void on_bonusRemove_clicked();
 
 private:
-	void addReward(RewardType typeId, int listId, int amount);
-	QList<QString> getListForType(RewardType typeId);
+	
+	void saveCurrentVisitInfo(int index);
+	void loadCurrentVisitInfo(int index);
+	
+	void onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget);
 	
 	Ui::RewardsWidget *ui;
-	CGPandoraBox * pandora;
-	CGSeerHut * seerhut;
+	CRewardableObject & object;
 	const CMap & map;
-	int rewards = 0;
 };
 
 class RewardsDelegate : public QStyledItemDelegate
 {
 	Q_OBJECT
 public:
+	RewardsDelegate(const CMap &, CRewardableObject &);
+
 	using QStyledItemDelegate::QStyledItemDelegate;
 	
 	void setEditorData(QWidget *editor, const QModelIndex &index) const override;
 	void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
-};
 
-class RewardsPandoraDelegate : public RewardsDelegate
-{
-	Q_OBJECT
-public:
-	RewardsPandoraDelegate(const CMap &, CGPandoraBox &);
-	
 	QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
-	
-private:
-	CGPandoraBox & pandora;
-	const CMap & map;
-};
 
-class RewardsSeerhutDelegate : public RewardsDelegate
-{
-	Q_OBJECT
-public:
-	RewardsSeerhutDelegate(const CMap &, CGSeerHut &);
-	
-	QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
-	
 private:
-	CGSeerHut & seerhut;
+	CRewardableObject & object;
 	const CMap & map;
 };

+ 1424 - 60
mapeditor/inspector/rewardswidget.ui

@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>645</width>
-    <height>335</height>
+    <width>806</width>
+    <height>561</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -19,69 +19,1433 @@
   <property name="modal">
    <bool>true</bool>
   </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="1">
-    <widget class="QPushButton" name="buttonRemove">
-     <property name="text">
-      <string>Remove selected</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="4">
-    <widget class="QLineEdit" name="rewardAmount">
-     <property name="maximumSize">
-      <size>
-       <width>80</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="2">
-    <widget class="QPushButton" name="buttonClear">
-     <property name="text">
-      <string>Delete all</string>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="0">
-    <widget class="QPushButton" name="buttonAdd">
-     <property name="text">
-      <string>Add or change</string>
-     </property>
-    </widget>
+  <layout class="QHBoxLayout" name="horizontalLayout_15">
+   <property name="spacing">
+    <number>3</number>
+   </property>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_4">
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_10">
+       <item>
+        <widget class="QPushButton" name="addVisitInfo">
+         <property name="text">
+          <string>Add</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="removeVisitInfo">
+         <property name="text">
+          <string>Remove</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QListWidget" name="visitInfoList">
+       <property name="editTriggers">
+        <set>QAbstractItemView::NoEditTriggers</set>
+       </property>
+       <property name="spacing">
+        <number>0</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_12">
+       <item>
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>Visit mode</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QComboBox" name="visitMode">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>1</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_11">
+       <item>
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Select mode</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QComboBox" name="selectMode">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>1</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="onSelectText">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="placeholderText">
+        <string>On select text</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QCheckBox" name="canRefuse">
+       <property name="text">
+        <string>Can refuse</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Reset parameters</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout_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>
+         <layout class="QHBoxLayout" name="horizontalLayout_14">
+          <item>
+           <widget class="QLabel" name="label_4">
+            <property name="text">
+             <string>Period</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QSpinBox" name="resetPeriod">
+            <property name="suffix">
+             <string> days</string>
+            </property>
+            <property name="maximum">
+             <number>99</number>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="resetVisitors">
+          <property name="text">
+           <string>Reset visitors</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="resetRewards">
+          <property name="text">
+           <string>Reset rewards</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_13">
+       <item>
+        <widget class="QLabel" name="label_3">
+         <property name="text">
+          <string>Window type</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QComboBox" name="windowMode"/>
+       </item>
+      </layout>
+     </item>
+    </layout>
    </item>
-   <item row="3" column="0" colspan="5">
-    <widget class="QTableWidget" name="rewardsTable">
-     <property name="editTriggers">
-      <set>QAbstractItemView::NoEditTriggers</set>
+   <item>
+    <widget class="QGroupBox" name="eventInfoGroup">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
      </property>
-     <property name="selectionMode">
-      <enum>QAbstractItemView::SingleSelection</enum>
+     <property name="title">
+      <string>Event info</string>
      </property>
-     <property name="selectionBehavior">
-      <enum>QAbstractItemView::SelectRows</enum>
-     </property>
-     <property name="columnCount">
-      <number>3</number>
-     </property>
-     <attribute name="horizontalHeaderVisible">
-      <bool>false</bool>
-     </attribute>
-     <column/>
-     <column/>
-     <column/>
+     <layout class="QVBoxLayout" name="verticalLayout_5">
+      <property name="leftMargin">
+       <number>6</number>
+      </property>
+      <property name="topMargin">
+       <number>6</number>
+      </property>
+      <property name="rightMargin">
+       <number>6</number>
+      </property>
+      <property name="bottomMargin">
+       <number>6</number>
+      </property>
+      <item>
+       <widget class="QLineEdit" name="rewardMessage">
+        <property name="placeholderText">
+         <string>Message to be displayed on granting of this reward</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QTabWidget" name="tabWidget">
+        <property name="currentIndex">
+         <number>0</number>
+        </property>
+        <widget class="QWidget" name="rewardTab">
+         <attribute name="title">
+          <string>Reward</string>
+         </attribute>
+         <layout class="QVBoxLayout" name="verticalLayout_7">
+          <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_23">
+            <item>
+             <widget class="QLabel" name="label_19">
+              <property name="text">
+               <string>Hero level</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rHeroLevel">
+              <property name="minimumSize">
+               <size>
+                <width>40</width>
+                <height>0</height>
+               </size>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_20">
+              <property name="text">
+               <string>Hero experience</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rHeroExperience">
+              <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_16">
+            <item>
+             <widget class="QLabel" name="label_14">
+              <property name="text">
+               <string>Spell points</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rManaDiff">
+              <property name="minimumSize">
+               <size>
+                <width>60</width>
+                <height>0</height>
+               </size>
+              </property>
+              <property name="minimum">
+               <number>-999</number>
+              </property>
+              <property name="maximum">
+               <number>999</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rManaPercentage">
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>-100</number>
+              </property>
+              <property name="maximum">
+               <number>1000</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_21">
+              <property name="text">
+               <string>Overflow</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rOverflowFactor">
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="maximum">
+               <number>100</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_24">
+            <item>
+             <widget class="QLabel" name="label_22">
+              <property name="text">
+               <string>Movement</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rMovePoints">
+              <property name="minimum">
+               <number>-999</number>
+              </property>
+              <property name="maximum">
+               <number>999</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="rMovePercentage">
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>-100</number>
+              </property>
+              <property name="maximum">
+               <number>1000</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="horizontalSpacer_5">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="removeObject">
+              <property name="text">
+               <string>Remove object</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <widget class="QGroupBox" name="groupBox_4">
+            <property name="title">
+             <string>Primary skills</string>
+            </property>
+            <layout class="QHBoxLayout" name="horizontalLayout_17">
+             <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_15">
+               <property name="text">
+                <string>Attack</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="rAttack">
+               <property name="minimum">
+                <number>-99</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="label_16">
+               <property name="text">
+                <string>Defence</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="rDefence">
+               <property name="minimum">
+                <number>-99</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="label_17">
+               <property name="text">
+                <string>Spell power</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="rPower">
+               <property name="minimum">
+                <number>-99</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="label_18">
+               <property name="text">
+                <string>Knowledge</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="rKnowledge">
+               <property name="minimum">
+                <number>-99</number>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+          </item>
+          <item>
+           <widget class="QTabWidget" name="tabWidget_3">
+            <property name="currentIndex">
+             <number>0</number>
+            </property>
+            <property name="tabBarAutoHide">
+             <bool>true</bool>
+            </property>
+            <widget class="QWidget" name="tab_8">
+             <attribute name="title">
+              <string>Resources</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_18">
+              <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="rResources">
+                <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_9">
+             <attribute name="title">
+              <string>Artifacts</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_19">
+              <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="rArtifacts">
+                <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_10">
+             <attribute name="title">
+              <string>Spells</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_20">
+              <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="rSpells">
+                <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_11">
+             <attribute name="title">
+              <string>Skills</string>
+             </attribute>
+             <layout class="QHBoxLayout" name="horizontalLayout_21">
+              <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="rSkills">
+                <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_12">
+             <attribute name="title">
+              <string>Creatures</string>
+             </attribute>
+             <layout class="QVBoxLayout" name="verticalLayout_6">
+              <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_22">
+                <item>
+                 <widget class="QComboBox" name="rCreatureId">
+                  <property name="sizePolicy">
+                   <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                    <horstretch>0</horstretch>
+                    <verstretch>0</verstretch>
+                   </sizepolicy>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QSpinBox" name="rCreatureAmount">
+                  <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="rCreatureAdd">
+                  <property name="text">
+                   <string>Add</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QPushButton" name="rCreatureRemove">
+                  <property name="text">
+                   <string>Remove</string>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
+              <item>
+               <widget class="QTableWidget" name="rCreatures">
+                <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>
+                <column/>
+                <column/>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+            <widget class="QWidget" name="tab_13">
+             <attribute name="title">
+              <string>Bonuses</string>
+             </attribute>
+             <layout class="QVBoxLayout" name="verticalLayout_9">
+              <property name="leftMargin">
+               <number>3</number>
+              </property>
+              <property name="rightMargin">
+               <number>3</number>
+              </property>
+              <property name="bottomMargin">
+               <number>3</number>
+              </property>
+              <item>
+               <layout class="QHBoxLayout" name="horizontalLayout_27">
+                <item>
+                 <widget class="QLabel" name="label_25">
+                  <property name="text">
+                   <string>Duration</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QComboBox" name="bonusDuration"/>
+                </item>
+                <item>
+                 <widget class="QLabel" name="label_26">
+                  <property name="text">
+                   <string>Type</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QComboBox" name="bonusType"/>
+                </item>
+                <item>
+                 <widget class="QLabel" name="label_27">
+                  <property name="text">
+                   <string>Value</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QSpinBox" name="bonusValue">
+                  <property name="minimum">
+                   <number>-999</number>
+                  </property>
+                  <property name="maximum">
+                   <number>999</number>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
+              <item>
+               <layout class="QHBoxLayout" name="horizontalLayout_28">
+                <item>
+                 <spacer name="horizontalSpacer_7">
+                  <property name="orientation">
+                   <enum>Qt::Horizontal</enum>
+                  </property>
+                  <property name="sizeHint" stdset="0">
+                   <size>
+                    <width>40</width>
+                    <height>20</height>
+                   </size>
+                  </property>
+                 </spacer>
+                </item>
+                <item>
+                 <widget class="QPushButton" name="bonusAdd">
+                  <property name="text">
+                   <string>Add</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QPushButton" name="bonusRemove">
+                  <property name="text">
+                   <string>Remove</string>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
+              <item>
+               <widget class="QTableWidget" name="bonuses">
+                <property name="editTriggers">
+                 <set>QAbstractItemView::NoEditTriggers</set>
+                </property>
+                <property name="selectionMode">
+                 <enum>QAbstractItemView::MultiSelection</enum>
+                </property>
+                <property name="selectionBehavior">
+                 <enum>QAbstractItemView::SelectRows</enum>
+                </property>
+                <attribute name="horizontalHeaderDefaultSectionSize">
+                 <number>150</number>
+                </attribute>
+                <attribute name="horizontalHeaderStretchLastSection">
+                 <bool>false</bool>
+                </attribute>
+                <attribute name="verticalHeaderVisible">
+                 <bool>false</bool>
+                </attribute>
+                <attribute name="verticalHeaderDefaultSectionSize">
+                 <number>21</number>
+                </attribute>
+                <column>
+                 <property name="text">
+                  <string>Duration</string>
+                 </property>
+                </column>
+                <column>
+                 <property name="text">
+                  <string>Type</string>
+                 </property>
+                </column>
+                <column>
+                 <property name="text">
+                  <string>Value</string>
+                 </property>
+                </column>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+            <widget class="QWidget" name="tab_14">
+             <attribute name="title">
+              <string>Cast</string>
+             </attribute>
+             <layout class="QVBoxLayout" name="verticalLayout_8">
+              <item>
+               <widget class="QCheckBox" name="castSpellCheck">
+                <property name="text">
+                 <string>Cast an adventure map spell</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <layout class="QHBoxLayout" name="horizontalLayout_25">
+                <item>
+                 <widget class="QLabel" name="label_23">
+                  <property name="text">
+                   <string>Spell</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QComboBox" name="castSpell">
+                  <property name="sizePolicy">
+                   <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                    <horstretch>0</horstretch>
+                    <verstretch>0</verstretch>
+                   </sizepolicy>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
+              <item>
+               <layout class="QHBoxLayout" name="horizontalLayout_26">
+                <item>
+                 <widget class="QLabel" name="label_24">
+                  <property name="text">
+                   <string>Magic school level</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QComboBox" name="castLevel">
+                  <property name="sizePolicy">
+                   <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                    <horstretch>0</horstretch>
+                    <verstretch>0</verstretch>
+                   </sizepolicy>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <spacer name="horizontalSpacer_6">
+                  <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>
+               <spacer name="verticalSpacer">
+                <property name="orientation">
+                 <enum>Qt::Vertical</enum>
+                </property>
+                <property name="sizeHint" stdset="0">
+                 <size>
+                  <width>20</width>
+                  <height>40</height>
+                 </size>
+                </property>
+               </spacer>
+              </item>
+             </layout>
+            </widget>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="limiterTab">
+         <attribute name="title">
+          <string>Limiter</string>
+         </attribute>
+         <layout class="QVBoxLayout" name="verticalLayout_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>
+           <layout class="QHBoxLayout" name="horizontalLayout_7">
+            <item>
+             <widget class="QLabel" name="label_5">
+              <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_6">
+              <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_2">
+              <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_8">
+            <item>
+             <widget class="QLabel" name="label_8">
+              <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_7">
+              <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_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_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>
+           <widget class="QGroupBox" name="groupBox_3">
+            <property name="title">
+             <string>Primary skills</string>
+            </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>
+          </item>
+          <item>
+           <widget class="QTabWidget" name="tabWidget_2">
+            <property name="currentIndex">
+             <number>0</number>
+            </property>
+            <property name="tabBarAutoHide">
+             <bool>true</bool>
+            </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>21</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>21</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>21</number>
+                </attribute>
+                <column/>
+                <column/>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
-   <item row="2" column="1" colspan="3">
-    <widget class="QComboBox" name="rewardList"/>
-   </item>
-   <item row="2" column="0">
-    <widget class="QComboBox" name="rewardType"/>
-   </item>
   </layout>
  </widget>
  <resources/>