SoundSSGood 5 months ago
parent
commit
051381d4db

+ 1 - 1
config/schemas/artifact.json

@@ -144,7 +144,7 @@
 				},
 				"val" : {
 					"type" : "number",
-					"description" : "Default number of charges"
+					"description" : "Default starting charge amount"
 				}
 			}
 		}

+ 13 - 0
docs/modders/Entities_Format/Artifact_Format.md

@@ -87,5 +87,18 @@ In order to make functional artifact you also need:
 		"bonusesPerLevel" : {},
 		"thresholdBonuses" : {},
 	}
+
+	// Optional, used for artifacts with charges.
+	"charged" : {
+    // Artifact discharging action
+    // SPELLCAST - Consumes a charge for each spellcast. Applies to every spell added through the "bonuses" section.
+    // BATTLE - Consumes one charge per battle.
+    // BUILDING (not implemented)
+    "usageType": "BATTLE",
+    // Optional, by default is false. Remove when fully discharged
+    "removeOnDepletion" : true,
+    // Optional, by default is 0. Default starting charge amount.
+    "val" : 2,
+	}
 }
 ```

+ 18 - 15
lib/entities/artifact/CArtHandler.cpp

@@ -152,20 +152,6 @@ 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;
@@ -235,6 +221,23 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
 	if(art->isTradable())
 		art->possibleSlots.at(ArtBearer::ALTAR).push_back(ArtifactPosition::ALTAR);
 
+	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 %s number of charges cannot be less than zero %d!", art->getNameTranslated(), charges);
+			else
+				art->setDefaultStartCharges(charges);
+		}
+		if(art->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST && art->getBonusesOfType(BonusType::SPELL)->size() == 0)
+			logMod->warn("Warning! %s condition of discharge is \"SPELLCAST\", but there is not a single spell.", art->getNameTranslated());
+	}
+
 	return art;
 }
 
@@ -326,7 +329,7 @@ EArtifactClass CArtHandler::stringToClass(const std::string & className)
 	return EArtifactClass::ART_SPECIAL;
 }
 
-DischargeArtifactCondition CArtHandler::stringToDischargeCond(const std::string & cond)
+DischargeArtifactCondition CArtHandler::stringToDischargeCond(const std::string & cond) const
 {
 	const std::unordered_map<std::string, DischargeArtifactCondition> growingConditionsMap =
 	{

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

@@ -24,7 +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);
+	DischargeArtifactCondition stringToDischargeCond(const std::string & cond) const;
 
 	bool legalArtifact(const ArtifactID & id) const;
 	static void makeItCreatureArt(CArtifact * a, bool onlyCreature = true);

+ 2 - 2
lib/entities/artifact/CArtifact.cpp

@@ -257,9 +257,9 @@ bool CChargedArtifact::isCharged() const
 	return condition.has_value();
 }
 
-void CChargedArtifact::setCondition(const DischargeArtifactCondition & condition)
+void CChargedArtifact::setCondition(const DischargeArtifactCondition & dischargeCondition)
 {
-	this->condition = condition;
+	condition = dischargeCondition;
 }
 
 void CChargedArtifact::setRemoveOnDepletion(const bool remove)

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

@@ -79,7 +79,7 @@ protected:
 public:
 	bool isCharged() const;
 
-	void setCondition(const DischargeArtifactCondition & condition);
+	void setCondition(const DischargeArtifactCondition & dischargeCondition);
 	void setRemoveOnDepletion(const bool remove);
 	void setDefaultStartCharges(const uint16_t charges);
 	uint16_t getDefaultStartCharges() const;

+ 2 - 2
lib/entities/artifact/CArtifactInstance.cpp

@@ -101,7 +101,7 @@ void CGrowingArtifactInstance::growingUp()
 	if(artType->isGrowing())
 	{
 		const auto growingBonus = artInst->getBonusesOfType(BonusType::ARTIFACT_GROWING);
-		assert(!growingBonus.empty());
+		assert(!growingBonus->empty());
 		growingBonus->front()->val++;
 
 		for(const auto & bonus : artType->getBonusesPerLevel())
@@ -179,7 +179,7 @@ void CChargedArtifactInstance::addCharges(const uint16_t charges)
 	if(artInst->getType()->isCharged())
 	{
 		const auto chargedBonus = artInst->getBonusesOfType(BonusType::ARTIFACT_CHARGE);
-		assert(!chargedBonus.empty());
+		assert(!chargedBonus->empty());
 		chargedBonus->front()->val += charges;
 		onChargesChanged();
 	}

+ 35 - 0
server/CGameHandler.cpp

@@ -3931,6 +3931,9 @@ void CGameHandler::castSpell(const spells::Caster * caster, SpellID spellID, con
 
 	const CSpell * s = spellID.toSpell();
 	s->adventureCast(spellEnv, p);
+
+	if(const auto hero = caster->getHeroCaster())
+		verifyChargedArtifactUsed(hero->id, spellID);
 }
 
 bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & sl2)
@@ -4337,3 +4340,35 @@ void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance
 {
 	battles->startBattle(army1, army2);
 }
+
+void CGameHandler::verifyChargedArtifactUsed(const ObjectInstanceID & heroObjectID, const SpellID & spellID)
+{
+	if(const auto hero = getHero(heroObjectID))
+	{
+		assert(hero->canCastThisSpell(spellID.toSpell()));
+
+		if(vstd::contains(hero->getSpellsInSpellbook(), spellID))
+			return;
+
+		std::vector<std::pair<ArtifactPosition, ArtifactInstanceID>> chargedArts;
+		for(const auto & [slot, slotInfo] : hero->artifactsWorn)
+		{
+			const auto artInst = slotInfo.getArt();
+			const auto artType = artInst->getType();
+			if(artType->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST)
+			{
+				chargedArts.emplace_back(slot, artInst->getId());
+			}
+			else
+			{
+				if(const auto bonuses = artInst->getBonusesOfType(BonusType::SPELL, spellID); !bonuses->empty())
+					return;
+			}
+		}
+
+		assert(!chargedArts.empty());
+		DischargeArtifact msg(chargedArts.front().second, 1);
+		msg.artLoc.emplace(hero->id, chargedArts.front().first);
+		sendAndApply(msg);
+	}
+}

+ 1 - 0
server/CGameHandler.h

@@ -169,6 +169,7 @@ public:
 	void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player,ETileVisibility mode) override;
 	
 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override;
+	void verifyChargedArtifactUsed(const ObjectInstanceID & heroObjectID, const SpellID & spellID);
 
 	/// Returns hero that is currently visiting this object, or nullptr if no visit is active
 	const CGHeroInstance * getVisitingHero(const CGObjectInstance *obj);

+ 1 - 0
server/battles/BattleActionProcessor.cpp

@@ -124,6 +124,7 @@ bool BattleActionProcessor::doHeroSpellAction(const CBattleInfoCallback & battle
 	}
 
 	parameters.cast(gameHandler->spellEnv, ba.getTarget(&battle));
+	gameHandler->verifyChargedArtifactUsed(h->id, ba.spell);
 
 	return true;
 }