Browse Source

Implemented selectable messages for visited & empty state

Ivan Savenko 2 years ago
parent
commit
44bdd2cbf3

+ 16 - 1
config/objects/rewardableBonusing.json

@@ -313,9 +313,24 @@
 					"rarity"	: 40
 				},
 				
-				"onVisitedMessage" : 136, // TODO: alternative message with Cavalier -> Champions upgrade & text ID 139
 				"visitMode" : "bonus",
 				"selectMode" : "selectFirst",
+
+				"onVisited" : [
+					{
+						"message" : 139,
+						"limiter" : {
+							"creatures" : [ { "type" : "cavalier", "amount" : 1 } ],
+						},
+						"changeCreatures" : {
+							"cavalier" : "champion"
+						}
+					},
+					{
+						"message" : 136
+					},
+				],
+
 				"rewards" : [
 					{
 						"limiter" : {

+ 13 - 3
config/objects/rewardableOncePerHero.json

@@ -215,7 +215,17 @@
 					"rarity"	: 50
 				},
 
-				"onEmptyMessage" : 150, //TODO: ID 150 should be used for Gold version, ID 152 - for Gems version
+				"onEmpty" : [
+					{
+						"message" : 150,
+						"appearChance" : { "min" : 34, "max" : 67 }
+					},
+					{
+						"message" : 152,
+						"appearChance" : { "min" : 67, "max" : 67 }
+					}
+				],
+				"onVisitedMessage" : 147,
 				"visitMode" : "hero",
 				"selectMode" : "selectFirst",
 				"canRefuse" : true,
@@ -271,12 +281,12 @@
 					{
 						"limiter" : { "resources" : { "gold" : 1000 } },
 						"resources" : { "gold" : -1000 },
-						"primary" : { "spellpower" : 2 }
+						"primary" : { "spellpower" : 1 }
 					},
 					{
 						"limiter" : { "resources" : { "gold" : 1000 } },
 						"resources" : { "gold" : -1000 },
-						"primary" : { "knowledge" : 2 }
+						"primary" : { "knowledge" : 1 }
 					}
 				]
 			}

+ 4 - 4
config/objects/rewardableOncePerWeek.json

@@ -93,7 +93,7 @@
 					"rarity"	: 50
 				},
 				
-				"onEmptyMessage" : 93,
+				"onVisitedMessage" : 93,
 				"resetParameters" : {
 					"period" : 7,
 					"visitors" : true,
@@ -134,7 +134,7 @@
 					"rarity"	: 80
 				},
 				
-				"onEmptyMessage" : 169,
+				"onVisitedMessage" : 169,
 				"resetParameters" : {
 					"period" : 7,
 					"visitors" : true,
@@ -175,7 +175,7 @@
 					"rarity"	: 50
 				},
 				
-				"onEmptyMessage" : 165,
+				"onVisitedMessage" : 165,
 				"resetParameters" : {
 					"period" : 7,
 					"visitors" : true
@@ -187,7 +187,7 @@
 						"limiter" : { "daysPassed" : 8 },
 						"message" : 164,
 						"resources" : { "gold" : 1000 }
-					}
+					},
 					{
 						"message" : 164,
 						"resources" : { "gold" : 500 }

+ 10 - 4
config/objects/rewardableOnceVisitable.json

@@ -18,7 +18,7 @@
 					"rarity"	: 100
 				},
 				
-				"onEmptyMessage" : 65,
+				"onVisitedMessage" : 65,
 				"visitMode" : "once",
 				"selectMode" : "selectFirst",
 				"rewards" : [
@@ -53,7 +53,7 @@
 					"rarity"	: 100
 				},
 				
-				"onEmptyMessage" : 38,
+				"onVisitedMessage" : 38,
 				"blockedVisitable" : true,
 				"visitMode" : "once",
 				"selectMode" : "selectFirst",
@@ -89,7 +89,7 @@
 					"rarity"	: 50
 				},
 				
-				"onEmptyMessage" : 156,
+				"onVisitedMessage" : 156,
 				"visitMode" : "once",
 				"selectMode" : "selectFirst",
 				"rewards" : [
@@ -136,9 +136,15 @@
 				},
 				
 				"onSelectMessage" : 161,
-				"onEmptyMessage" : 163,
 				"visitMode" : "once",
 				"selectMode" : "selectFirst",
+
+				"onVisited" : [
+					{
+						"message" : 163,
+						"bonuses" : [ { "type" : "MORALE", "val" : -3, "duration" : "ONE_BATTLE" } ]
+					}
+				],
 				"rewards" : [
 					{
 						"appearChance" : { "max" : 30 },

+ 1 - 1
lib/NetPacks.h

@@ -1178,7 +1178,7 @@ namespace ObjProperty
 		BANK_DAYCOUNTER, BANK_RESET, BANK_CLEAR,
 
 		//object with reward
-		REWARD_RANDOMIZE, REWARD_SELECT
+		REWARD_RANDOMIZE, REWARD_SELECT, REWARD_CLEARED
 	};
 }
 

+ 36 - 9
lib/mapObjects/CRewardableConstructor.cpp

@@ -138,13 +138,14 @@ void CRandomRewardObjectInfo::configureResetInfo(CRewardableObject * object, CRa
 	resetParameters.rewards  = source["rewards"].Bool();
 }
 
-void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRandomGenerator & rng) const
+void CRandomRewardObjectInfo::configureRewards(
+		CRewardableObject * object,
+		CRandomGenerator & rng, const
+		JsonNode & source,
+		std::map<si32, si32> & thrownDice,
+		CRewardVisitInfo::ERewardEventType event ) const
 {
-	object->info.clear();
-
-	std::map<si32, si32> thrownDice;
-
-	for (const JsonNode & reward : parameters["rewards"].Vector())
+	for (const JsonNode & reward : source.Vector())
 	{
 		if (!reward["appearChance"].isNull())
 		{
@@ -172,21 +173,47 @@ void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRando
 		configureLimiter(object, rng, info.limiter, reward["limiter"]);
 		configureReward(object, rng, info.reward, reward);
 
+		info.visitType = event;
 		info.message = loadMessage(reward["message"]);
 
 		for (const auto & artifact : info.reward.artifacts )
 			info.message.addReplacement(MetaString::ART_NAMES, artifact.getNum());
-		
+
 		for (const auto & artifact : info.reward.spells )
 			info.message.addReplacement(MetaString::SPELL_NAME, artifact.getNum());
 
 		object->info.push_back(info);
 	}
+}
+
+void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRandomGenerator & rng) const
+{
+	object->info.clear();
+
+	std::map<si32, si32> thrownDice;
+
+	configureRewards(object, rng, parameters["rewards"], thrownDice, CRewardVisitInfo::EVENT_FIRST_VISIT);
+	configureRewards(object, rng, parameters["onVisited"], thrownDice, CRewardVisitInfo::EVENT_ALREADY_VISITED);
+	configureRewards(object, rng, parameters["onEmpty"], thrownDice, CRewardVisitInfo::EVENT_NOT_AVAILABLE);
 
 	object->blockVisit= parameters["blockedVisitable"].Bool();
 	object->onSelect  = loadMessage(parameters["onSelectMessage"]);
-	object->onVisited = loadMessage(parameters["onVisitedMessage"]);
-	object->onEmpty   = loadMessage(parameters["onEmptyMessage"]);
+
+	if (!parameters["onVisitedMessage"].isNull())
+	{
+		CRewardVisitInfo onVisited;
+		onVisited.visitType = CRewardVisitInfo::EVENT_ALREADY_VISITED;
+		onVisited.message = loadMessage(parameters["onVisitedMessage"]);
+		object->info.push_back(onVisited);
+	}
+
+	if (!parameters["onEmptyMessage"].isNull())
+	{
+		CRewardVisitInfo onEmpty;
+		onEmpty.visitType = CRewardVisitInfo::EVENT_NOT_AVAILABLE;
+		onEmpty.message = loadMessage(parameters["onEmptyMessage"]);
+		object->info.push_back(onEmpty);
+	}
 
 	configureResetInfo(object, rng, object->resetParameters, parameters["resetParameters"]);
 

+ 2 - 0
lib/mapObjects/CRewardableConstructor.h

@@ -20,6 +20,8 @@ class DLL_LINKAGE CRandomRewardObjectInfo : public IObjectInfo
 {
 	JsonNode parameters;
 
+	void configureRewards(CRewardableObject * object, CRandomGenerator & rng, const JsonNode & source, std::map<si32, si32> & thrownDice, CRewardVisitInfo::ERewardEventType mode) const;
+
 	void configureLimiter(CRewardableObject * object, CRandomGenerator & rng, CRewardLimiter & limiter, const JsonNode & source) const;
 	TRewardLimitersList configureSublimiters(CRewardableObject * object, CRandomGenerator & rng, const JsonNode & source) const;
 

+ 64 - 51
lib/mapObjects/CRewardableObject.cpp

@@ -102,7 +102,7 @@ bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const
 	return false;
 }
 
-std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance * hero) const
+std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance * hero, CRewardVisitInfo::ERewardEventType event) const
 {
 	std::vector<ui32> ret;
 
@@ -110,7 +110,7 @@ std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance *
 	{
 		const CRewardVisitInfo & visit = info[i];
 
-		if(visit.limiter.heroAllowed(hero))
+		if(event == visit.visitType && visit.limiter.heroAllowed(hero))
 		{
 			logGlobal->trace("Reward %d is allowed", i);
 			ret.push_back(static_cast<ui32>(i));
@@ -119,16 +119,11 @@ std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance *
 	return ret;
 }
 
-CRewardVisitInfo CRewardableObject::getVisitInfo(int index, const CGHeroInstance *) const
-{
-	return info[index];
-}
-
 void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 {
-	auto grantRewardWithMessage = [&](int index) -> void
+	auto grantRewardWithMessage = [&](int index, bool markAsVisit) -> void
 	{
-		auto vi = getVisitInfo(index, h);
+		auto vi = info[index];
 		logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
 		// show message only if it is not empty
 		if (!vi.message.toString().empty())
@@ -140,7 +135,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 			cb->showInfoDialog(&iw);
 		}
 		// grant reward afterwards. Note that it may remove object
-		grantReward(index, h);
+		grantReward(index, h, markAsVisit);
 	};
 	auto selectRewardsMessage = [&](std::vector<ui32> rewards, const MetaString & dialog) -> void
 	{
@@ -148,40 +143,38 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 		sd.player = h->tempOwner;
 		sd.text = dialog;
 		for (auto index : rewards)
-			sd.components.push_back(getVisitInfo(index, h).reward.getDisplayedComponent(h));
+			sd.components.push_back(info[index].reward.getDisplayedComponent(h));
 		cb->showBlockingDialog(&sd);
 	};
 
-	if(!wasVisited(h))
+	if(!wasVisitedBefore(h))
 	{
-		auto rewards = getAvailableRewards(h);
+		auto rewards = getAvailableRewards(h, CRewardVisitInfo::EVENT_FIRST_VISIT);
 		bool objectRemovalPossible = false;
 		for(auto index : rewards)
 		{
-			if(getVisitInfo(index, h).reward.removeObject)
+			if(info[index].reward.removeObject)
 				objectRemovalPossible = true;
 		}
 
 		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
 		switch (rewards.size())
 		{
-			case 0: // no available rewards, e.g. empty flotsam
+			case 0: // no available rewards, e.g. visiting School of War without gold
 			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				if (!onEmpty.toString().empty())
-					iw.text = onEmpty;
+				auto emptyRewards = getAvailableRewards(h, CRewardVisitInfo::EVENT_NOT_AVAILABLE);
+				if (!emptyRewards.empty())
+					grantRewardWithMessage(emptyRewards[0], false);
 				else
-					iw.text = onVisited;
-				cb->showInfoDialog(&iw);
+					logMod->warn("No applicable message for visiting empty object!");
 				break;
 			}
 			case 1: // one reward. Just give it with message
 			{
 				if (canRefuse)
-					selectRewardsMessage(rewards, getVisitInfo(rewards[0], h).message);
+					selectRewardsMessage(rewards, info[rewards[0]].message);
 				else
-					grantRewardWithMessage(rewards[0]);
+					grantRewardWithMessage(rewards[0], true);
 				break;
 			}
 			default: // multiple rewards. Act according to select mode
@@ -191,14 +184,14 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 						selectRewardsMessage(rewards, onSelect);
 						break;
 					case SELECT_FIRST: // give first available
-						grantRewardWithMessage(rewards[0]);
+						grantRewardWithMessage(rewards[0], true);
 						break;
 				}
 				break;
 			}
 		}
 
-		if(!objectRemovalPossible && getAvailableRewards(h).size() == 0)
+		if(!objectRemovalPossible && getAvailableRewards(h, CRewardVisitInfo::EVENT_FIRST_VISIT).size() == 0)
 		{
 			ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, h->id);
 			cb->sendAndApply(&cov);
@@ -207,19 +200,18 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 	else
 	{
 		logGlobal->debug("Revisiting already visited object");
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		if (!onVisited.toString().empty())
-			iw.text = onVisited;
+
+		auto visitedRewards = getAvailableRewards(h, CRewardVisitInfo::EVENT_ALREADY_VISITED);
+		if (!visitedRewards.empty())
+			grantRewardWithMessage(visitedRewards[0], false);
 		else
-			iw.text = onEmpty;
-		cb->showInfoDialog(&iw);
+			logMod->warn("No applicable message for visiting already visited object!");
 	}
 }
 
 void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
 {
-	grantRewardAfterLevelup(getVisitInfo(selectedReward, hero), hero);
+	grantRewardAfterLevelup(info[selectedReward], hero);
 }
 
 void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
@@ -229,8 +221,8 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32
 
 	if(answer > 0 && answer-1 < info.size())
 	{
-		auto list = getAvailableRewards(hero);
-		grantReward(list[answer - 1], hero);
+		auto list = getAvailableRewards(hero, CRewardVisitInfo::EVENT_FIRST_VISIT);
+		grantReward(list[answer - 1], hero, true);
 	}
 	else
 	{
@@ -238,18 +230,18 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32
 	}
 }
 
-void CRewardableObject::onRewardGiven(const CGHeroInstance * hero) const
+void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero, bool markVisited) const
 {
-	// no implementation, virtual function for overrides
-}
+	if (markVisited)
+	{
+		cb->setObjProperty(id, ObjProperty::REWARD_CLEARED, true);
 
-void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
-{
-	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id);
-	cb->sendAndApply(&cov);
-	cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewardID);
+		ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id);
+		cb->sendAndApply(&cov);
+	}
 
-	grantRewardBeforeLevelup(getVisitInfo(rewardID, hero), hero);
+	cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewardID);
+	grantRewardBeforeLevelup(info[rewardID], hero);
 }
 
 void CRewardableObject::grantRewardBeforeLevelup(const CRewardVisitInfo & info, const CGHeroInstance * hero) const
@@ -366,23 +358,39 @@ void CRewardableObject::grantRewardAfterLevelup(const CRewardVisitInfo & info, c
 		cb->giveCreatures(this, hero, creatures, false);
 	}
 
-	onRewardGiven(hero);
-
 	if(info.reward.removeObject)
 		cb->removeObject(this);
 }
 
-bool CRewardableObject::wasVisited(PlayerColor player) const
+bool CRewardableObject::wasVisitedBefore(const CGHeroInstance * contextHero) const
 {
 	switch (visitMode)
 	{
 		case VISIT_UNLIMITED:
-		case VISIT_BONUS:
 			return false;
 		case VISIT_ONCE:
-			return vstd::contains(cb->getPlayerState(player)->visitedObjects, ObjectInstanceID(id));
+			return onceVisitableObjectCleared;
+		case VISIT_PLAYER:
+			return vstd::contains(cb->getPlayerState(contextHero->getOwner())->visitedObjects, ObjectInstanceID(id));
+		case VISIT_BONUS:
+			return contextHero->hasBonusFrom(Bonus::OBJECT, ID);
 		case VISIT_HERO:
+			return contextHero->visitedObjects.count(ObjectInstanceID(id));
+		default:
 			return false;
+	}
+
+}
+
+bool CRewardableObject::wasVisited(PlayerColor player) const
+{
+	switch (visitMode)
+	{
+		case VISIT_UNLIMITED:
+		case VISIT_BONUS:
+		case VISIT_HERO:
+			return false;
+		case VISIT_ONCE:
 		case VISIT_PLAYER:
 			return vstd::contains(cb->getPlayerState(player)->visitedObjects, ObjectInstanceID(id));
 		default:
@@ -394,8 +402,6 @@ bool CRewardableObject::wasVisited(const CGHeroInstance * h) const
 {
 	switch (visitMode)
 	{
-		case VISIT_UNLIMITED:
-			return false;
 		case VISIT_BONUS:
 			return h->hasBonusFrom(Bonus::OBJECT, ID);
 		case VISIT_HERO:
@@ -429,7 +435,8 @@ void CRewardInfo::loadComponents(std::vector<Component> & comps,
 	if (gainedLevels)
 		comps.push_back(Component(Component::EXPERIENCE, 1, gainedLevels, 0));
 
-	if (manaDiff) comps.push_back(Component(Component::PRIM_SKILL, 5, manaDiff, 0));
+	if (manaDiff || manaPercentage >= 0)
+		comps.push_back(Component(Component::PRIM_SKILL, 5, manaDiff, 0));
 
 	for (size_t i=0; i<primary.size(); i++)
 	{
@@ -495,6 +502,9 @@ void CRewardableObject::setPropertyDer(ui8 what, ui32 val)
 		case ObjProperty::REWARD_SELECT:
 			selectedReward = val;
 			break;
+		case ObjProperty::REWARD_CLEARED:
+			onceVisitableObjectCleared = val;
+			break;
 	}
 }
 
@@ -506,6 +516,8 @@ void CRewardableObject::triggerReset() const
 	}
 	if (resetParameters.visitors)
 	{
+		cb->setObjProperty(id, ObjProperty::REWARD_CLEARED, false);
+
 		ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_CLEAR, id);
 		cb->sendAndApply(&cov);
 	}
@@ -526,6 +538,7 @@ CRewardableObject::CRewardableObject():
 	selectMode(0),
 	visitMode(0),
 	selectedReward(0),
+	onceVisitableObjectCleared(false),
 	canRefuse(false)
 {}
 

+ 27 - 15
lib/mapObjects/CRewardableObject.h

@@ -207,12 +207,23 @@ public:
 class DLL_LINKAGE CRewardVisitInfo
 {
 public:
+	enum ERewardEventType
+	{
+		EVENT_INVALID,
+		EVENT_FIRST_VISIT,
+		EVENT_ALREADY_VISITED,
+		EVENT_NOT_AVAILABLE
+	};
+
 	CRewardLimiter limiter;
 	CRewardInfo reward;
 
 	/// Message that will be displayed on granting of this reward, if not empty
 	MetaString message;
 
+	/// Event to which this reward is assigned
+	ERewardEventType visitType;
+
 	CRewardVisitInfo() = default;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -220,6 +231,7 @@ public:
 		h & limiter;
 		h & reward;
 		h & message;
+		h & visitType;
 	}
 };
 
@@ -258,26 +270,24 @@ protected:
 	};
 
 	/// filters list of visit info and returns rewards that can be granted to current hero
-	virtual std::vector<ui32> getAvailableRewards(const CGHeroInstance * hero) const;
+	virtual std::vector<ui32> getAvailableRewards(const CGHeroInstance * hero, CRewardVisitInfo::ERewardEventType event ) const;
 
-	virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
-
-	virtual CRewardVisitInfo getVisitInfo(int index, const CGHeroInstance *h) const;
+	virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero, bool markVisited) const;
 
 	virtual void triggerReset() const;
 
-	/// Rewards that can be granted by an object
-	std::vector<CRewardVisitInfo> info;
-
-	/// MetaString's that contain text for messages for specific situations
+	/// Message that will be shown if player needs to select one of multiple rewards
 	MetaString onSelect;
-	MetaString onVisited;
-	MetaString onEmpty;
+
+	/// Rewards that can be applied by an object
+	std::vector<CRewardVisitInfo> info;
 
 	/// how reward will be selected, uses ESelectMode enum
 	ui8 selectMode;
+
 	/// contols who can visit an object, uses EVisitMode enum
 	ui8 visitMode;
+
 	/// reward selected by player
 	ui16 selectedReward;
 
@@ -287,6 +297,12 @@ protected:
 	/// if true - player can refuse visiting an object (e.g. Tomb)
 	bool canRefuse;
 
+	/// 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;
+
+	bool onceVisitableObjectCleared;
+
 public:
 	EVisitMode getVisitMode() const;
 	ui16 getResetDuration() const;
@@ -311,9 +327,6 @@ public:
 	/// applies player selection of reward
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
-	/// function that will be called once reward is fully granted to hero
-	virtual void onRewardGiven(const CGHeroInstance * hero) const;
-	
 	void initObj(CRandomGenerator & rand) override;
 
 	CRewardableObject();
@@ -325,11 +338,10 @@ public:
 		h & canRefuse;
 		h & resetParameters;
 		h & onSelect;
-		h & onVisited;
-		h & onEmpty;
 		h & visitMode;
 		h & selectMode;
 		h & selectedReward;
+		h & onceVisitableObjectCleared;
 	}
 
 	// for configuration/object setup