Răsfoiți Sursa

Add new rewards for configurable objects

Ivan Savenko 5 luni în urmă
părinte
comite
62e774c91e

+ 3 - 3
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -265,9 +265,9 @@ uint64_t RewardEvaluator::getArmyReward(
 
 			auto rewardValue = 0;
 
-			if(!info.reward.artifacts.empty())
+			if(!info.reward.grantedArtifacts.empty())
 			{
-				for(auto artID : info.reward.artifacts)
+				for(auto artID : info.reward.grantedArtifacts)
 				{
 					const auto * art = artID.toArtifact();
 
@@ -283,7 +283,7 @@ uint64_t RewardEvaluator::getArmyReward(
 				}
 			}
 
-			totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0;
+			totalValue += rewardValue > 0 ? rewardValue / (info.reward.grantedArtifacts.size() + info.reward.creatures.size()) : 0;
 		}
 
 		return totalValue;

+ 11 - 5
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -47,6 +47,15 @@ std::shared_ptr<CGObjectInstance> CRewardableConstructor::create(IGameCallback *
 	return ret;
 }
 
+void CRewardableConstructor::assignBonuses(std::vector<Bonus> & bonuses, MapObjectID objectID) const
+{
+	for (auto & bonus : bonuses)
+	{
+		bonus.source = BonusSource::OBJECT_TYPE;
+		bonus.sid = BonusSourceID(objectID);
+	}
+}
+
 Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
 {
 	Rewardable::Configuration result;
@@ -62,11 +71,8 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCal
 
 	for(auto & rewardInfo : result.info)
 	{
-		for (auto & bonus : rewardInfo.reward.bonuses)
-		{
-			bonus.source = BonusSource::OBJECT_TYPE;
-			bonus.sid = BonusSourceID(objectID);
-		}
+		assignBonuses(rewardInfo.reward.heroBonuses, objectID);
+		assignBonuses(rewardInfo.reward.playerBonuses, objectID);
 	}
 
 	return result;

+ 3 - 0
lib/mapObjectConstructors/CRewardableConstructor.h

@@ -14,10 +14,13 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+struct Bonus;
+
 class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler
 {
 	Rewardable::Info objectInfo;
 
+	void assignBonuses(std::vector<Bonus> & bonuses, MapObjectID objectID) const;
 	void initTypeData(const JsonNode & config) override;
 	
 	bool blockVisit = false;

+ 10 - 10
lib/mapObjects/CGPandoraBox.cpp

@@ -83,7 +83,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
 	temp.heroLevel = vi.reward.heroLevel;
 	temp.primary = vi.reward.primary;
 	temp.secondary = vi.reward.secondary;
-	temp.bonuses = vi.reward.bonuses;
+	temp.heroBonuses = vi.reward.heroBonuses;
 	temp.manaDiff = vi.reward.manaDiff;
 	temp.manaPercentage = vi.reward.manaPercentage;
 	
@@ -106,7 +106,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
 	if(vi.reward.manaDiff || vi.reward.manaPercentage >= 0)
 		txt = setText(temp.manaDiff > 0, 177, 176, h);
 	
-	for(auto b : vi.reward.bonuses)
+	for(auto b : vi.reward.heroBonuses)
 	{
 		if(b.val && b.type == BonusType::MORALE)
 			txt = setText(b.val > 0, 179, 178, h);
@@ -122,7 +122,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
 	
 	//artifacts message
 	temp = Rewardable::Reward{};
-	temp.artifacts = vi.reward.artifacts;
+	temp.grantedArtifacts = vi.reward.grantedArtifacts;
 	sendInfoWindow(setText(true, 183, 183, h), temp);
 	
 	//creatures message
@@ -160,8 +160,8 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
 	temp.manaPercentage = -1;
 	temp.spells.clear();
 	temp.creatures.clear();
-	temp.bonuses.clear();
-	temp.artifacts.clear();
+	temp.heroBonuses.clear();
+	temp.grantedArtifacts.clear();
 	sendInfoWindow(setText(true, 175, 175, h), temp);
 	
 	// grant reward afterwards. Note that it may remove object
@@ -229,11 +229,11 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
 		int val = 0;
 		handler.serializeInt("morale", val, 0);
 		if(val)
-			vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
+			vinfo.reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
 		
 		handler.serializeInt("luck", val, 0);
 		if(val)
-			vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
+			vinfo.reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
 		
 		vinfo.reward.resources.serializeJson(handler, "resources");
 		{
@@ -246,7 +246,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
 			}
 		}
 		
-		handler.serializeIdArray("artifacts", vinfo.reward.artifacts);
+		handler.serializeIdArray("artifacts", vinfo.reward.grantedArtifacts);
 		handler.serializeIdArray("spells", vinfo.reward.spells);
 		handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures);
 		
@@ -279,8 +279,8 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
 		|| vinfo.reward.heroExperience
 		|| vinfo.reward.manaDiff
 		|| vinfo.reward.resources.nonZero()
-		|| !vinfo.reward.artifacts.empty()
-		|| !vinfo.reward.bonuses.empty()
+		|| !vinfo.reward.grantedArtifacts.empty()
+		|| !vinfo.reward.heroBonuses.empty()
 		|| !vinfo.reward.creatures.empty()
 		|| !vinfo.reward.secondary.empty();
 		

+ 3 - 3
lib/mapObjects/CQuest.cpp

@@ -714,9 +714,9 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 		if(metaTypeName == "mana")
 			reward.manaDiff = val;
 		if(metaTypeName == "morale")
-			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
+			reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
 		if(metaTypeName == "luck")
-			reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
+			reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
 		if(metaTypeName == "resource")
 		{
 			auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
@@ -735,7 +735,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
 		if(metaTypeName == "artifact")
 		{
 			auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
-			reward.artifacts.push_back(rawId);
+			reward.grantedArtifacts.push_back(rawId);
 		}
 		if(metaTypeName == "spell")
 		{

+ 21 - 13
lib/mapObjects/TownBuildingInstance.cpp

@@ -72,6 +72,25 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance *
 	configuration = generateConfiguration(rand);
 }
 
+void TownRewardableBuildingInstance::assignBonuses(std::vector<Bonus> & bonuses) const
+{
+	const auto & building = town->getTown()->buildings.at(getBuildingType());
+
+	for (auto & bonus : bonuses)
+	{
+		if (building->mapObjectLikeBonuses.hasValue())
+		{
+			bonus.source = BonusSource::OBJECT_TYPE;
+			bonus.sid = BonusSourceID(building->mapObjectLikeBonuses);
+		}
+		else
+		{
+			bonus.source = BonusSource::TOWN_STRUCTURE;
+			bonus.sid = BonusSourceID(building->getUniqueTypeID());
+		}
+	}
+}
+
 Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
 {
 	Rewardable::Configuration result;
@@ -82,19 +101,8 @@ Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(
 	building->rewardableObjectInfo.configureObject(result, rand, cb);
 	for(auto & rewardInfo : result.info)
 	{
-		for (auto & bonus : rewardInfo.reward.bonuses)
-		{
-			if (building->mapObjectLikeBonuses.hasValue())
-			{
-				bonus.source = BonusSource::OBJECT_TYPE;
-				bonus.sid = BonusSourceID(building->mapObjectLikeBonuses);
-			}
-			else
-			{
-				bonus.source = BonusSource::TOWN_STRUCTURE;
-				bonus.sid = BonusSourceID(building->getUniqueTypeID());
-			}
-		}
+		assignBonuses(rewardInfo.reward.heroBonuses);
+		assignBonuses(rewardInfo.reward.playerBonuses);
 	}
 	return result;
 }

+ 1 - 0
lib/mapObjects/TownBuildingInstance.h

@@ -58,6 +58,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
 	bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
 	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
 	Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
+	void assignBonuses(std::vector<Bonus> & bonuses) const;
 
 	const IObjectInterface * getObject() const override;
 	bool wasVisited(PlayerColor player) const override;

+ 8 - 8
lib/mapping/MapFormatH3M.cpp

@@ -1111,9 +1111,9 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
 	reward.heroExperience = reader->readUInt32();
 	reward.manaDiff = reader->readInt32();
 	if(auto val = reader->readInt8Checked(-3, 3))
-		reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
+		reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
 	if(auto val = reader->readInt8Checked(-3, 3))
-		reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
+		reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
 
 	reader->readResources(reward.resources);
 	for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x)
@@ -1130,11 +1130,11 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
 	size_t gart = reader->readUInt8(); //number of gained artifacts
 	for(size_t oo = 0; oo < gart; ++oo)
 	{
-		reward.artifacts.push_back(reader->readArtifact());
+		reward.grantedArtifacts.push_back(reader->readArtifact());
 		if (features.levelHOTA5)
 		{
 			SpellID scrollSpell = reader->readSpell16();
-			if (reward.artifacts.back() == ArtifactID::SPELL_SCROLL)
+			if (reward.grantedArtifacts.back() == ArtifactID::SPELL_SCROLL)
 				logGlobal->warn("Map '%s': Pandora/Event at %s Option to give spell scroll (%s) via event or pandora is not implemented!", mapName, mapPosition.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
 		}
 	}
@@ -2300,12 +2300,12 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 			}
 			case ESeerHutRewardType::MORALE:
 			{
-				reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
+				reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
 				break;
 			}
 			case ESeerHutRewardType::LUCK:
 			{
-				reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
+				reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
 				break;
 			}
 			case ESeerHutRewardType::RESOURCES:
@@ -2334,11 +2334,11 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 			}
 			case ESeerHutRewardType::ARTIFACT:
 			{
-				reward.artifacts.push_back(reader->readArtifact());
+				reward.grantedArtifacts.push_back(reader->readArtifact());
 				if (features.levelHOTA5)
 				{
 					SpellID scrollSpell = reader->readSpell16();
-					if (reward.artifacts.back() == ArtifactID::SPELL_SCROLL)
+					if (reward.grantedArtifacts.back() == ArtifactID::SPELL_SCROLL)
 						logGlobal->warn("Map '%s': Seer Hut at %s: Option to give spell scroll (%s) as a reward is not implemented!", mapName, position.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
 
 				}

+ 9 - 1
lib/networkPacks/PacksForClient.h

@@ -408,6 +408,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
 
 struct DLL_LINKAGE GiveBonus : public CPackForClient
 {
+	using VariantType = VariantIdentifier<ObjectInstanceID, PlayerColor, BattleID>;
 	enum class ETarget : int8_t { OBJECT, PLAYER, BATTLE };
 	
 	explicit GiveBonus(ETarget Who = ETarget::OBJECT)
@@ -415,10 +416,17 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
 	{
 	}
 
+	GiveBonus(ETarget who, const VariantType & id, const Bonus & bonus)
+		: who(who)
+		, id(id)
+		, bonus(bonus)
+	{
+	}
+
 	void applyGs(CGameState * gs) override;
 
 	ETarget who = ETarget::OBJECT;
-	VariantIdentifier<ObjectInstanceID, PlayerColor, BattleID> id;
+	VariantType id;
 	Bonus bonus;
 
 	void visitTyped(ICPackVisitor & visitor) override;

+ 7 - 1
lib/rewardable/Configuration.h

@@ -29,7 +29,8 @@ enum EVisitMode
 	VISIT_HERO,      // every hero can visit object once
 	VISIT_BONUS,     // can be visited by any hero that don't have bonus from this object
 	VISIT_LIMITER,   // can be visited by heroes that don't fulfill provided limiter
-	VISIT_PLAYER     // every player can visit object once
+	VISIT_PLAYER,     // every player can visit object once
+	VISIT_PLAYER_GLOBAL // every player can visit object once. All objects of the same type will be considered as visited
 };
 
 /// controls selection of reward granted to player
@@ -70,6 +71,9 @@ struct DLL_LINKAGE ResetInfo
 	/// if true - re-randomize rewards on a new week
 	bool rewards;
 	
+	/// Reset object after visit by a hero, whether hero accepted reward or not
+	bool resetAfterVisit = false;
+
 	void serializeJson(JsonSerializeFormat & handler);
 	
 	template <typename Handler> void serialize(Handler &h)
@@ -77,6 +81,8 @@ struct DLL_LINKAGE ResetInfo
 		h & period;
 		h & visitors;
 		h & rewards;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+			h & resetAfterVisit;
 	}
 };
 

+ 5 - 4
lib/rewardable/Info.cpp

@@ -174,14 +174,15 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd:
 	reward.movePercentage = randomizer.loadValue(source["movePercentage"], rng, variables, -1);
 
 	reward.removeObject = source["removeObject"].Bool();
-	reward.bonuses = randomizer.loadBonuses(source["bonuses"]);
+	reward.heroBonuses = randomizer.loadBonuses(source["bonuses"]);
+	reward.playerBonuses = randomizer.loadBonuses(source["playerBonuses"]);
 
 	reward.guards = randomizer.loadCreatures(source["guards"], rng, variables);
 
 	reward.primary = randomizer.loadPrimaries(source["primary"], rng, variables);
 	reward.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables);
 
-	reward.artifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables);
+	reward.grantedArtifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables);
 	reward.spells = randomizer.loadSpells(source["spells"], rng, variables);
 	reward.creatures = randomizer.loadCreatures(source["creatures"], rng, variables);
 	if(!source["spellCast"].isNull() && source["spellCast"].isStruct())
@@ -293,7 +294,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
 			}
 		}
 
-		for (const auto & artifact : info.reward.artifacts )
+		for (const auto & artifact : info.reward.grantedArtifacts )
 		{
 			loot.appendRawString("%s");
 			loot.replaceName(artifact);
@@ -315,7 +316,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
 	}
 	else
 	{
-		for (const auto & artifact : info.reward.artifacts )
+		for (const auto & artifact : info.reward.grantedArtifacts )
 			target.replaceName(artifact);
 
 		for (const auto & spell : info.reward.spells )

+ 9 - 6
lib/rewardable/Interface.cpp

@@ -151,16 +151,19 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo
 		cb->setMovePoints(&smp);
 	}
 
-	for(const Bonus & bonus : info.reward.bonuses)
+	for(const Bonus & bonus : info.reward.heroBonuses)
 	{
-		GiveBonus gb;
-		gb.who = GiveBonus::ETarget::OBJECT;
-		gb.bonus = bonus;
-		gb.id = hero->id;
+		GiveBonus gb(GiveBonus::ETarget::OBJECT, hero->id, bonus);
 		cb->giveHeroBonus(&gb);
 	}
 
-	for(const ArtifactID & art : info.reward.artifacts)
+	for(const Bonus & bonus : info.reward.playerBonuses)
+	{
+		GiveBonus gb(GiveBonus::ETarget::PLAYER, hero->getOwner(), bonus);
+		cb->giveHeroBonus(&gb);
+	}
+
+	for(const ArtifactID & art : info.reward.grantedArtifacts)
 		cb->giveHeroNewArtifact(hero, art, ArtifactPosition::FIRST_AVAILABLE);
 
 	if(!info.reward.spells.empty())

+ 16 - 0
lib/rewardable/Limiter.h

@@ -48,6 +48,9 @@ struct DLL_LINKAGE Limiter final : public Serializeable
 	/// Number of free secondary slots that hero needs to have
 	bool canLearnSkills;
 
+	/// Hero has commander, and commander is currently alive
+	bool commanderAlive;
+
 	/// resources player needs to have in order to trigger reward
 	TResources resources;
 
@@ -59,6 +62,12 @@ struct DLL_LINKAGE Limiter final : public Serializeable
 	/// checks for artifacts copies if same artifact id is included multiple times
 	std::vector<ArtifactID> artifacts;
 
+	/// artifact slots that hero needs to have available (not locked and without any artifact) to pass the limiter
+	std::vector<ArtifactPosition> availableSlots;
+
+	/// Spell scrolls that hero must have in inventory (equipped or in backpack)
+	std::vector<SpellID> scrolls;
+
 	/// Spells that hero must have in the spellbook
 	std::vector<SpellID> spells;
 
@@ -102,10 +111,17 @@ struct DLL_LINKAGE Limiter final : public Serializeable
 		h & manaPoints;
 		h & manaPercentage;
 		h & canLearnSkills;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+			h & commanderAlive;
 		h & resources;
 		h & primary;
 		h & secondary;
 		h & artifacts;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+		{
+			h & availableSlots;
+			h & scrolls;
+		}
 		h & spells;
 		h & canLearnSpells;
 		h & creatures;

+ 3 - 3
lib/rewardable/Reward.cpp

@@ -79,7 +79,7 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
 	for (auto comp : extraComponents)
 		comps.push_back(comp);
 	
-	for (auto & bonus : bonuses)
+	for (auto & bonus : heroBonuses)
 	{
 		if (bonus.type == BonusType::MORALE)
 			comps.emplace_back(ComponentType::MORALE, bonus.val);
@@ -111,7 +111,7 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
 		comps.emplace_back(ComponentType::SEC_SKILL, entry.first, finalLevel);
 	}
 
-	for(const auto & entry : artifacts)
+	for(const auto & entry : grantedArtifacts)
 		comps.emplace_back(ComponentType::ARTIFACT, entry);
 
 	for(const auto & entry : spells)
@@ -141,7 +141,7 @@ void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("manaDiff", manaDiff);
 	handler.serializeInt("manaOverflowFactor", manaOverflowFactor);
 	handler.serializeInt("movePoints", movePoints);
-	handler.serializeIdArray("artifacts", artifacts);
+	handler.serializeIdArray("artifacts", grantedArtifacts);
 	handler.serializeIdArray("spells", spells);
 	handler.enterArray("creatures").serializeStruct(creatures);
 	handler.enterArray("primary").serializeArray(primary);

+ 25 - 4
lib/rewardable/Reward.h

@@ -65,6 +65,8 @@ struct DLL_LINKAGE Reward final
 
 	/// received experience
 	si32 heroExperience;
+	si32 commanderExperience;
+	si32 unitsExperience;
 	/// received levels (converted into XP during grant)
 	si32 heroLevel;
 
@@ -86,7 +88,8 @@ struct DLL_LINKAGE Reward final
 	std::vector<CStackBasicDescriptor> guards;
 
 	/// list of bonuses, e.g. morale/luck
-	std::vector<Bonus> bonuses;
+	std::vector<Bonus> heroBonuses;
+	std::vector<Bonus> playerBonuses;
 
 	/// skills that hero may receive or lose
 	std::vector<si32> primary;
@@ -96,7 +99,10 @@ struct DLL_LINKAGE Reward final
 	std::map<CreatureID, CreatureID> creaturesChange;
 
 	/// objects that hero may receive
-	std::vector<ArtifactID> artifacts;
+	std::vector<ArtifactID> grantedArtifacts;
+	std::vector<ArtifactID> takenArtifacts;
+	std::vector<ArtifactPosition> takenArtifactSlots;
+	std::vector<SpellID> scrolls;
 	std::vector<SpellID> spells;
 	std::vector<CStackBasicDescriptor> creatures;
 	
@@ -131,14 +137,29 @@ struct DLL_LINKAGE Reward final
 		h & movePercentage;
 		h & guards;
 		h & heroExperience;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+		{
+			h & commanderExperience;
+			h & unitsExperience;
+		}
 		h & heroLevel;
 		h & manaDiff;
 		h & manaOverflowFactor;
 		h & movePoints;
 		h & primary;
 		h & secondary;
-		h & bonuses;
-		h & artifacts;
+		h & heroBonuses;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+		{
+			h & playerBonuses;
+		}
+		h & grantedArtifacts;
+		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
+		{
+			h & takenArtifacts;
+			h & takenArtifactSlots;
+			h & scrolls;
+		}
 		h & spells;
 		h & creatures;
 		h & creaturesChange;

+ 2 - 2
lib/serializer/ESerializationVersion.h

@@ -39,9 +39,9 @@ enum class ESerializationVersion : int32_t
 	STACK_INSTANCE_EXPERIENCE_FIX, // stack experience is stored as total, not as average
 	STACK_INSTANCE_ARMY_FIX, // remove serialization of army that owns stack instance
 	STORE_UID_COUNTER_IN_CMAP,  // fix crash caused by conflicting instanceName after loading game
+	REWARDABLE_EXTENSIONS, // new functionality for rewardable objects
 
-	
-	CURRENT = STORE_UID_COUNTER_IN_CMAP,
+	CURRENT = REWARDABLE_EXTENSIONS,
 };
 
 static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

+ 8 - 8
mapeditor/inspector/rewardswidget.cpp

@@ -297,11 +297,11 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
 			vinfo.reward.resources[i] = widget->value();
 	}
 	
-	vinfo.reward.artifacts.clear();
+	vinfo.reward.grantedArtifacts.clear();
 	for(int i = 0; i < ui->rArtifacts->count(); ++i)
 	{
 		if(ui->rArtifacts->item(i)->checkState() == Qt::Checked)
-			vinfo.reward.artifacts.push_back(LIBRARY->artifacts()->getByIndex(i)->getId());
+			vinfo.reward.grantedArtifacts.push_back(LIBRARY->artifacts()->getByIndex(i)->getId());
 	}
 	vinfo.reward.spells.clear();
 	for(int i = 0; i < ui->rSpells->count(); ++i)
@@ -336,13 +336,13 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
 		vinfo.reward.spellCast.second = ui->castLevel->currentIndex();
 	}
 	
-	vinfo.reward.bonuses.clear();
+	vinfo.reward.heroBonuses.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_INSTANCE, val, BonusSourceID(object.id));
+		vinfo.reward.heroBonuses.emplace_back(dur, typ, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(object.id));
 	}
 	
 	vinfo.limiter.dayOfWeek = ui->lDayOfWeek->currentIndex();
@@ -452,7 +452,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
 			widget->setValue(vinfo.reward.resources[i]);
 	}
 	
-	for(auto i : vinfo.reward.artifacts)
+	for(auto i : vinfo.reward.grantedArtifacts)
 		ui->rArtifacts->item(LIBRARY->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
 	for(auto i : vinfo.reward.spells)
 		ui->rArtifacts->item(LIBRARY->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
@@ -478,7 +478,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
 		ui->castLevel->setCurrentIndex(vinfo.reward.spellCast.second);
 	}
 	
-	for(auto & i : vinfo.reward.bonuses)
+	for(auto & i : vinfo.reward.heroBonuses)
 	{
 		auto dur = vstd::findKey(bonusDurationMap, i.duration);
 		for(int i = 0; i < ui->bonusDuration->count(); ++i)
@@ -786,7 +786,7 @@ void RewardsDelegate::updateModelData(QAbstractItemModel * model, const QModelIn
 		}
 		textList += QObject::tr("Resources: %1").arg(resourcesList.join(", "));
 		QStringList artifactsList;
-		for (auto artifact : vinfo.reward.artifacts)
+		for (auto artifact : vinfo.reward.grantedArtifacts)
 		{
 			artifactsList += QString::fromStdString(LIBRARY->artifacts()->getById(artifact)->getNameTranslated());
 		}
@@ -814,7 +814,7 @@ void RewardsDelegate::updateModelData(QAbstractItemModel * model, const QModelIn
 			textList += QObject::tr("Spell Cast: %1 (%2)").arg(QString::fromStdString(LIBRARY->spells()->getById(vinfo.reward.spellCast.first)->getNameTranslated())).arg(vinfo.reward.spellCast.second);
 		}
 		QStringList bonusesList;
-		for (auto & bonus : vinfo.reward.bonuses)
+		for (auto & bonus : vinfo.reward.heroBonuses)
 		{
 			bonusesList += QString("%1 %2 (%3)").arg(QString::fromStdString(vstd::findKey(bonusDurationMap, bonus.duration))).arg(QString::fromStdString(vstd::findKey(bonusNameMap, bonus.type))).arg(bonus.val);
 		}