Browse Source

Artifacts discharging

SoundSSGood 5 tháng trước cách đây
mục cha
commit
fcc9b8ecfa

+ 2 - 1
client/widgets/CArtifactsOfHeroBase.cpp

@@ -276,7 +276,8 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 
 		const auto curArt = slotInfo->getArt();
 		// If the artifact has charges, add charges information
-		artPlace->addChargesArtInfo(curArt->valOfBonuses(BonusType::ARTIFACT_CHARGE));
+		if(curArt->getType()->isCharged())
+			artPlace->addChargedArtInfo(curArt->getCharges());
 
 		if(curArt->isCombined())
 			return;

+ 8 - 9
client/widgets/CComponentHolder.cpp

@@ -263,16 +263,15 @@ void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<
 	}
 }
 
-void CArtPlace::addChargesArtInfo(const int charges)
+void CArtPlace::addChargedArtInfo(const uint16_t charges)
 {
-	if(charges > 0)
-	{
-		MetaString info;
-		info.appendTextID("vcmi.artifact.charges");
-		info.appendRawString(" %d");
-		info.replaceNumber(charges);
-		text += info.toString();
-	}
+	MetaString info;
+	info.appendEOL();
+	info.appendEOL();
+	info.appendTextID("vcmi.artifact.charges");
+	info.appendRawString(" %d");
+	info.replaceNumber(charges);
+	text += info.toString();
 }
 
 CSecSkillPlace::CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & newSkillId, const uint8_t level)

+ 1 - 1
client/widgets/CComponentHolder.h

@@ -44,7 +44,7 @@ public:
 	void lockSlot(bool on);
 	bool isLocked() const;
 	void addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts);
-	void addChargesArtInfo(const int charges);
+	void addChargedArtInfo(const uint16_t charges);
 
 private:
 	ArtifactID artId;

+ 19 - 0
config/schemas/artifact.json

@@ -128,6 +128,25 @@
 		"onlyOnWaterMap" : {
 			"type" : "boolean",
 			"description" : "If set to true, artifact won't spawn on a map without water"
+		},
+		"charged": {
+			"type" : "object",
+			"additionalProperties" : false,
+			"description" : "Determines charged artifact behavior",
+			"required" : ["usageType"],
+			"properties" : {
+				"usageType": {
+					"type" : "string",
+					"enum" : ["SPELLCAST", "BATTLE", "BUILDING"],
+				},
+				"removeOnDepletion" : {
+					"type" : "boolean",
+				},
+				"val" : {
+					"type" : "number",
+					"description" : "Default number of charges"
+				}
+			}
 		}
 	}
 }

+ 26 - 0
lib/entities/artifact/CArtHandler.cpp

@@ -152,6 +152,21 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
 			JsonUtils::parseBonus(bonus["bonus"], &art->thresholdBonuses.back().second);
 		}
 	}
+	if(!node["charged"].isNull())
+	{
+		art->setCondition(stringToDischargeCond(node["charged"]["usageType"].String()));
+		if(!node["charged"]["removeOnDepletion"].isNull())
+			art->setRemoveOnDepletion(node["charged"]["removeOnDepletion"].Bool());
+		if(!node["charged"]["val"].isNull())
+		{
+			const auto charges = node["charged"]["val"].Integer();
+			if(charges < 0)
+				logMod->warn("Warning! Charged artifact number of charges cannot be less than zero %d!", charges);
+			else
+				art->setDefaultStartCharges(charges);
+		}
+	}
+
 	art->id = ArtifactID(index);
 	art->identifier = identifier;
 	art->modScope = scope;
@@ -311,6 +326,17 @@ EArtifactClass CArtHandler::stringToClass(const std::string & className)
 	return EArtifactClass::ART_SPECIAL;
 }
 
+DischargeArtifactCondition CArtHandler::stringToDischargeCond(const std::string & cond)
+{
+	const std::unordered_map<std::string, DischargeArtifactCondition> growingConditionsMap =
+	{
+		{"SPELLCAST", DischargeArtifactCondition::SPELLCAST},
+		{"BATTLE", DischargeArtifactCondition::BATTLE},
+		{"BUILDING", DischargeArtifactCondition::BUILDING},
+	};
+	return growingConditionsMap.at(cond);
+}
+
 void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) const
 {
 	art->aClass = stringToClass(node["class"].String());

+ 1 - 0
lib/entities/artifact/CArtHandler.h

@@ -24,6 +24,7 @@ public:
 	void addBonuses(CArtifact * art, const JsonNode & bonusList);
 
 	static EArtifactClass stringToClass(const std::string & className); //TODO: rework EartClass to make this a constructor
+	DischargeArtifactCondition stringToDischargeCond(const std::string & cond);
 
 	bool legalArtifact(const ArtifactID & id) const;
 	static void makeItCreatureArt(CArtifact * a, bool onlyCreature = true);

+ 36 - 0
lib/entities/artifact/CArtifact.cpp

@@ -246,6 +246,42 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
 	}
 }
 
+CChargedArtifact::CChargedArtifact()
+	: removeOnDepletion(false)
+	, defaultStartCharges(0)
+{
+}
+
+bool CChargedArtifact::isCharged() const
+{
+	return condition.has_value();
+}
+
+void CChargedArtifact::setCondition(const DischargeArtifactCondition & condition)
+{
+	this->condition = condition;
+}
+
+void CChargedArtifact::setRemoveOnDepletion(const bool remove)
+{
+	removeOnDepletion = remove;
+}
+
+void CChargedArtifact::setDefaultStartCharges(const uint16_t charges)
+{
+	defaultStartCharges = charges;
+}
+
+uint16_t CChargedArtifact::getDefaultStartCharges() const
+{
+	return defaultStartCharges;
+}
+
+std::optional<DischargeArtifactCondition> CChargedArtifact::getDischargeCondition() const
+{
+	return condition;
+}
+
 CArtifact::CArtifact()
 	: iconIndex(ArtifactID::NONE),
 	price(0)

+ 24 - 1
lib/entities/artifact/CArtifact.h

@@ -9,10 +9,13 @@
  */
 #pragma once
 
+#include "StdInc.h"
+
 #include "ArtBearer.h"
 #include "EArtifactClass.h"
 
 #include "../../bonuses/CBonusSystemNode.h"
+#include "../../networkPacks/ArtifactLocation.h"
 
 #include <vcmi/Artifact.h>
 
@@ -64,8 +67,28 @@ public:
 	const std::vector<std::pair<ui16, Bonus>> & getThresholdBonuses() const;
 };
 
+class DLL_LINKAGE CChargedArtifact
+{
+	std::optional<DischargeArtifactCondition> condition;
+	bool removeOnDepletion;
+	uint16_t defaultStartCharges;
+
+protected:
+	CChargedArtifact();
+
+public:
+	bool isCharged() const;
+
+	void setCondition(const DischargeArtifactCondition & condition);
+	void setRemoveOnDepletion(const bool remove);
+	void setDefaultStartCharges(const uint16_t charges);
+	uint16_t getDefaultStartCharges() const;
+	std::optional<DischargeArtifactCondition> getDischargeCondition() const;
+};
+
 // Container for artifacts. Not for instances.
-class DLL_LINKAGE CArtifact final : public Artifact, public CBonusSystemNode, public CCombinedArtifact, public CScrollArtifact, public CGrowingArtifact
+class DLL_LINKAGE CArtifact final : public Artifact, public CBonusSystemNode,
+		public CCombinedArtifact, public CScrollArtifact, public CGrowingArtifact, public CChargedArtifact
 {
 	ArtifactID id;
 	std::string image;

+ 35 - 0
lib/entities/artifact/CArtifactInstance.cpp

@@ -124,10 +124,45 @@ void CGrowingArtifactInstance::growingUp()
 	}
 }
 
+void CChargedArtifactInstance::discharge(const uint16_t charges)
+{
+	auto artInst = static_cast<CArtifactInstance*>(this);
+
+	if(const auto chargeBonuses = artInst->getAllBonuses(Selector::type()(BonusType::ARTIFACT_CHARGE), nullptr))
+	{
+		if(chargeBonuses->front()->val > charges)
+			chargeBonuses->front()->val -= charges;
+		else
+			chargeBonuses->front()->val = 0;
+	}
+}
+
+void CChargedArtifactInstance::addCharges(const uint16_t charges)
+{
+	auto artInst = static_cast<CArtifactInstance*>(this);
+
+	if(artInst->getType()->isCharged())
+	{
+		auto bonus = std::make_shared<Bonus>();
+		bonus->type = BonusType::ARTIFACT_CHARGE;
+		bonus->val = charges;
+		bonus->duration = BonusDuration::PERMANENT;
+		artInst->accumulateBonus(bonus);
+	}
+}
+
+uint16_t CChargedArtifactInstance::getCharges() const
+{
+	auto artInst = static_cast<const CArtifactInstance*>(this);
+
+	return artInst->valOfBonuses(BonusType::ARTIFACT_CHARGE);
+}
+
 CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb, const CArtifact * art)
 	:CArtifactInstance(cb)
 {
 	setType(art);
+	addCharges(getType()->getDefaultStartCharges());
 }
 
 CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb)

+ 11 - 1
lib/entities/artifact/CArtifactInstance.h

@@ -75,8 +75,18 @@ public:
 	void growingUp();
 };
 
+class DLL_LINKAGE CChargedArtifactInstance
+{
+protected:
+	CChargedArtifactInstance() = default;
+public:
+	void discharge(const uint16_t charges);
+	void addCharges(const uint16_t charges);
+	uint16_t getCharges() const;
+};
+
 class DLL_LINKAGE CArtifactInstance final
-	: public CBonusSystemNode, public CCombinedArtifactInstance, public CScrollArtifactInstance, public CGrowingArtifactInstance
+	: public CBonusSystemNode, public CCombinedArtifactInstance, public CScrollArtifactInstance, public CGrowingArtifactInstance, public CChargedArtifactInstance
 {
 	ArtifactInstanceID id;
 	ArtifactID artTypeID;

+ 22 - 18
lib/gameState/GameStatePackVisitor.cpp

@@ -820,6 +820,13 @@ void GameStatePackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
 		move.visit(*this);
 }
 
+void GameStatePackVisitor::visitGrowUpArtifact(GrowUpArtifact & pack)
+{
+	auto artInst = gs.getArtInstance(pack.id);
+	assert(artInst);
+	artInst->growingUp();
+}
+
 void GameStatePackVisitor::visitPutArtifact(PutArtifact & pack)
 {
 	auto art = gs.getArtInstance(pack.id);
@@ -914,6 +921,13 @@ void GameStatePackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 	bulkArtsPut(pack.artsPack0, artInitialSetLeft, *rightSet);
 }
 
+void GameStatePackVisitor::visitDischargeArtifact(DischargeArtifact & pack)
+{
+	auto artInst = gs.getArtInstance(pack.id);
+	assert(artInst);
+	artInst->discharge(pack.charges);
+}
+
 void GameStatePackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
 {
 	auto artSet = gs.getArtSet(pack.al.artHolder);
@@ -1217,22 +1231,6 @@ void GameStatePackVisitor::visitBattleResultAccepted(BattleResultAccepted & pack
 	if(const auto defenderHero = gs.getHero(pack.heroResult[BattleSide::DEFENDER].heroID))
 		defenderHero->removeBonusesRecursive(Bonus::OneBattle);
 
-	if(pack.winnerSide != BattleSide::NONE)
-	{
-		// Grow up growing artifacts
-		if(const auto winnerHero = gs.getHero(pack.heroResult[pack.winnerSide].heroID))
-		{
-			if(winnerHero->getCommander() && winnerHero->getCommander()->alive)
-
-			{
-				for(auto & art : winnerHero->getCommander()->artifactsWorn)
-					gs.getArtInstance(art.second.getID())->growingUp();
-			}
-			for(auto & art : winnerHero->artifactsWorn)
-				gs.getArtInstance(art.second.getID())->growingUp();
-		}
-	}
-
 	if(gs.getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 	{
 		if(const auto attackerArmy = gs.getArmyInstance(pack.heroResult[BattleSide::ATTACKER].armyID))
@@ -1343,8 +1341,14 @@ void GameStatePackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack
 {
 	pack.learnedSpells.visit(*this);
 
-	for(auto & artPack : pack.artifacts)
-		artPack.visit(*this);
+	for(auto & movingPack : pack.movingArtifacts)
+		movingPack.visit(*this);
+
+	for(auto & growing : pack.growingArtifacts)
+		growing.visit(*this);
+
+	for(auto & discharging : pack.dischargingArtifacts)
+		discharging.visit(*this);
 
 	const auto currentBattle = std::find_if(gs.currentBattles.begin(), gs.currentBattles.end(),
 											[&](const auto & battle)

+ 2 - 0
lib/gameState/GameStatePackVisitor.h

@@ -41,11 +41,13 @@ public:
 	void visitInsertNewStack(InsertNewStack & pack) override;
 	void visitRebalanceStacks(RebalanceStacks & pack) override;
 	void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
+	void visitGrowUpArtifact(GrowUpArtifact & pack) override;
 	void visitPutArtifact(PutArtifact & pack) override;
 	void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) override;
 	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
 	void visitAssembledArtifact(AssembledArtifact & pack) override;
 	void visitDisassembledArtifact(DisassembledArtifact & pack) override;
+	void visitDischargeArtifact(DischargeArtifact & pack) override;
 	void visitHeroVisit(HeroVisit & pack) override;
 	void visitNewTurn(NewTurn & pack) override;
 	void visitGiveBonus(GiveBonus & pack) override;

+ 7 - 0
lib/networkPacks/ArtifactLocation.h

@@ -79,4 +79,11 @@ struct MoveArtifactInfo
 	}
 };
 
+enum class DischargeArtifactCondition
+{
+		SPELLCAST,
+		BATTLE,
+		BUILDING
+};
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/networkPacks/NetPackVisitor.h

@@ -77,9 +77,11 @@ public:
 	virtual void visitInsertNewStack(InsertNewStack & pack) {}
 	virtual void visitRebalanceStacks(RebalanceStacks & pack) {}
 	virtual void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) {}
+	virtual void visitGrowUpArtifact(GrowUpArtifact & pack) {}
 	virtual void visitPutArtifact(PutArtifact & pack) {}
 	virtual void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) {}
 	virtual void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) {}
+	virtual void visitDischargeArtifact(DischargeArtifact & pack) {}
 	virtual void visitAssembledArtifact(AssembledArtifact & pack) {}
 	virtual void visitDisassembledArtifact(DisassembledArtifact & pack) {}
 	virtual void visitHeroVisit(HeroVisit & pack) {}

+ 10 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -302,6 +302,11 @@ void BulkRebalanceStacks::visitTyped(ICPackVisitor & visitor)
 	visitor.visitBulkRebalanceStacks(*this);
 }
 
+void GrowUpArtifact::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitGrowUpArtifact(*this);
+}
+
 void PutArtifact::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitPutArtifact(*this);
@@ -322,6 +327,11 @@ void AssembledArtifact::visitTyped(ICPackVisitor & visitor)
 	visitor.visitAssembledArtifact(*this);
 }
 
+void DischargeArtifact::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitDischargeArtifact(*this);
+}
+
 void DisassembledArtifact::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitDisassembledArtifact(*this);

+ 23 - 1
lib/networkPacks/PacksForClient.h

@@ -916,7 +916,8 @@ struct DLL_LINKAGE GrowUpArtifact : CArtifactOperationPack
 		: id(id)
 	{
 	}
-	void applyGs(CGameState * gs) override;
+
+	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1022,6 +1023,27 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
 	}
 };
 
+struct DLL_LINKAGE DischargeArtifact : CArtifactOperationPack
+{
+	ArtifactInstanceID id;
+	uint16_t charges;
+
+	DischargeArtifact() = default;
+	DischargeArtifact(const ArtifactInstanceID & id, const uint16_t charges)
+		: id(id)
+		, charges(charges)
+	{
+	}
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & id;
+		h & charges;
+	}
+};
+
 struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack
 {
 	ArtifactLocation al;

+ 2 - 0
lib/networkPacks/PacksForClientBattle.h

@@ -387,6 +387,7 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 	ChangeSpells learnedSpells;
 	std::vector<BulkMoveArtifacts> movingArtifacts;
 	std::vector<GrowUpArtifact> growingArtifacts;
+	std::vector<DischargeArtifact> dischargingArtifacts;
 	CStackBasicDescriptor raisedStack;
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -398,6 +399,7 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 		h & learnedSpells;
 		h & movingArtifacts;
 		h & growingArtifacts;
+		h & dischargingArtifacts;
 		h & raisedStack;
 		assert(battleID != BattleID::NONE);
 	}

+ 1 - 0
lib/serializer/RegisterTypes.h

@@ -227,6 +227,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<BulkMoveArtifacts>(173);
 	s.template registerType<PlayerMessageClient>(174);
 	s.template registerType<BulkRebalanceStacks>(175);
+	s.template registerType<DischargeArtifact>(176);
 	s.template registerType<SetRewardableConfiguration>(177);
 	s.template registerType<CPackForServer>(179);
 	s.template registerType<EndTurn>(180);

+ 24 - 0
server/battles/BattleResultProcessor.cpp

@@ -500,6 +500,30 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
 		addArtifactToGrowing(winnerHero->artifactsWorn);
 	}
 
+	// Charged artifacts handling
+	const auto addArtifactToDischarging = [&resultsApplied](const std::map<ArtifactPosition, ArtSlotInfo> & artMap)
+	{
+		for(const auto & [slot, slotInfo] : artMap)
+		{
+			auto artInst = slotInfo.getArt();
+			assert(artInst);
+			if(const auto condition = artInst->getType()->getDischargeCondition(); condition && condition.value() == DischargeArtifactCondition::BATTLE)
+				resultsApplied.dischargingArtifacts.emplace_back(artInst->getId(), 1);
+		}
+	};
+	if(winnerHero)
+	{
+		addArtifactToDischarging(winnerHero->artifactsWorn);
+		if(const auto commander = winnerHero->getCommander())
+			addArtifactToDischarging(commander->artifactsWorn);
+	}
+	if(loserHero)
+	{
+		addArtifactToDischarging(loserHero->artifactsWorn);
+		if(const auto commander = loserHero->getCommander())
+			addArtifactToDischarging(commander->artifactsWorn);
+	}
+
 	// Necromancy handling
 	// Give raised units to winner, if any were raised, units will be given after casualties are taken
 	if(winnerHero)