Bläddra i källkod

Merge pull request #4766 from SoundSSGood/fused-artifacts

Fused artifacts
Ivan Savenko 1 år sedan
förälder
incheckning
c25aef8f48

+ 1 - 0
Mods/vcmi/config/vcmi/english.json

@@ -358,6 +358,7 @@
 	"vcmi.heroWindow.sortBackpackBySlot.help"  : "Sort artifacts in backpack by equipped slot.",
 	"vcmi.heroWindow.sortBackpackByClass.hover"  : "Sort by class",
 	"vcmi.heroWindow.sortBackpackByClass.help"  : "Sort artifacts in backpack by artifact class. Treasure, Minor, Major, Relic",
+	"vcmi.heroWindow.fusingArtifact.fusing" : "You possess all of the components needed for the fusion of the %s. Do you wish to perform the fusion? {All components will be consumed upon fusion.}",
 
 	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
 

+ 5 - 2
client/ArtifactsUIController.cpp

@@ -74,7 +74,10 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art
 					MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
 					message.appendEOL();
 					message.appendEOL();
-					message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the
+					if(combinedArt->isFused())
+						message.appendRawString(CGI->generaltexth->translate("vcmi.heroWindow.fusingArtifact.fusing"));
+					else
+						message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the
 					message.replaceName(ArtifactID(combinedArt->getId()));
 					LOCPLINT->showYesNoDialog(message.toString(), [&assembleConfirmed, hero, slot, combinedArt]()
 						{
@@ -102,7 +105,7 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const
 	if(hero->tempOwner != LOCPLINT->playerID)
 		return false;
 
-	if(art->isCombined())
+	if(art->hasParts())
 	{
 		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
 			return false;

+ 10 - 4
client/widgets/CArtPlace.cpp

@@ -217,9 +217,9 @@ void CArtPlace::setGestureCallback(const ClickFunctor & callback)
 
 void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts)
 {
-	for(const auto & availableArts : arts)
+	for(auto [combinedId, availableArts] : arts)
 	{
-		const auto combinedArt = availableArts.first.toArtifact();
+		const auto combinedArt = combinedId.toArtifact();
 		MetaString info;
 		info.appendEOL();
 		info.appendEOL();
@@ -227,14 +227,20 @@ void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<
 		info.appendName(combinedArt->getId());
 		info.appendRawString("}");
 		info.appendRawString(" (%d/%d)");
-		info.replaceNumber(availableArts.second.size());
+		info.replaceNumber(availableArts.size());
 		info.replaceNumber(combinedArt->getConstituents().size());
 		for(const auto part : combinedArt->getConstituents())
 		{
+			const auto found = std::find_if(availableArts.begin(), availableArts.end(), [part](const auto & availablePart) -> bool
+				{
+					return availablePart == part->getId() ? true : false;
+				});
+
 			info.appendEOL();
-			if(vstd::contains(availableArts.second, part->getId()))
+			if(found < availableArts.end())
 			{
 				info.appendName(part->getId());
+				availableArts.erase(found);
 			}
 			else
 			{

+ 8 - 2
client/widgets/CArtifactsOfHeroBase.cpp

@@ -268,11 +268,17 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 		std::map<const ArtifactID, std::vector<ArtifactID>> arts;
 		for(const auto combinedArt : slotInfo->artifact->artType->getPartOf())
 		{
-			arts.try_emplace(combinedArt->getId(), std::vector<ArtifactID>{});
+			assert(combinedArt->isCombined());
+			arts.try_emplace(combinedArt->getId());
+			CArtifactFittingSet fittingSet(*curHero);
 			for(const auto part : combinedArt->getConstituents())
 			{
-				if(curHero->hasArt(part->getId(), false, false))
+				const auto partSlot = fittingSet.getArtPos(part->getId(), false, false);
+				if(partSlot != ArtifactPosition::PRE_FIRST)
+				{
 					arts.at(combinedArt->getId()).emplace_back(part->getId());
+					fittingSet.lockSlot(partSlot);
+				}
 			}
 		}
 		artPlace->addCombinedArtInfo(arts);

+ 4 - 0
config/schemas/artifact.json

@@ -61,6 +61,10 @@
 			"description" : "Optional, list of components for combinational artifacts",
 			"items" : { "type" : "string" }
 		},
+		"fusedComponents" : {
+			"type" : "boolean",
+			"description" : "Used together with components fild. Marks the artifact as fused. Cannot be disassembled."
+		},
 		"bonuses" : {
 			"type" : "array",
 			"description" : "Bonuses provided by this artifact using bonus system",

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

@@ -67,6 +67,9 @@ In order to make functional artifact you also need:
 		"artifact2",
 		"artifact3"
 	],
+	
+	// Optional, by default is false. Set to true if components are supposed to be fused. 
+	"fusedComponents" : true,
 
 	// Creature id to use on battle field. If set, this artifact is war machine
 	"warMachine" : "some.creature" 

+ 8 - 6
lib/ArtifactUtils.cpp

@@ -202,21 +202,23 @@ DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities(
 	if(art->isCombined())
 		return arts;
 
-	for(const auto artifact : art->getPartOf())
+	for(const auto combinedArt : art->getPartOf())
 	{
-		assert(artifact->isCombined());
+		assert(combinedArt->isCombined());
 		bool possible = true;
-
-		for(const auto constituent : artifact->getConstituents()) //check if all constituents are available
+		CArtifactFittingSet fittingSet(*artSet);
+		for(const auto part : combinedArt->getConstituents()) // check if all constituents are available
 		{
-			if(!artSet->hasArt(constituent->getId(), onlyEquiped, false))
+			const auto slot = fittingSet.getArtPos(part->getId(), onlyEquiped, false);
+			if(slot == ArtifactPosition::PRE_FIRST)
 			{
 				possible = false;
 				break;
 			}
+			fittingSet.lockSlot(slot);
 		}
 		if(possible)
-			arts.push_back(artifact);
+			arts.push_back(combinedArt);
 	}
 	return arts;
 }

+ 52 - 30
lib/CArtHandler.cpp

@@ -56,11 +56,26 @@ const std::vector<const CArtifact*> & CCombinedArtifact::getConstituents() const
 	return constituents;
 }
 
-const std::vector<const CArtifact*> & CCombinedArtifact::getPartOf() const
+const std::set<const CArtifact*> & CCombinedArtifact::getPartOf() const
 {
 	return partOf;
 }
 
+void CCombinedArtifact::setFused(bool isFused)
+{
+	fused = isFused;
+}
+
+bool CCombinedArtifact::isFused() const
+{
+	return fused;
+}
+
+bool CCombinedArtifact::hasParts() const
+{
+	return isCombined() && !isFused();
+}
+
 bool CScrollArtifact::isScroll() const
 {
 	return static_cast<const CArtifact*>(this)->getId() == ArtifactID::SPELL_SCROLL;
@@ -203,7 +218,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
 
 	auto artCanBePutAt = [this, simpleArtCanBePutAt](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool
 	{
-		if(isCombined())
+		if(hasParts())
 		{
 			if(!simpleArtCanBePutAt(artSet, slot, assumeDestRemoved))
 				return false;
@@ -606,19 +621,21 @@ void CArtHandler::loadType(CArtifact * art, const JsonNode & node) const
 
 void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node)
 {
-	if (!node["components"].isNull())
+	if(!node["components"].isNull())
 	{
 		for(const auto & component : node["components"].Vector())
 		{
-			VLC->identifiers()->requestIdentifier("artifact", component, [=](si32 id)
+			VLC->identifiers()->requestIdentifier("artifact", component, [this, art](int32_t id)
 			{
 				// when this code is called both combinational art as well as component are loaded
 				// so it is safe to access any of them
 				art->constituents.push_back(ArtifactID(id).toArtifact());
-				objects[id]->partOf.push_back(art);
+				objects[id]->partOf.insert(art);
 			});
 		}
 	}
+	if(!node["fusedComponents"].isNull())
+		art->setFused(node["fusedComponents"].Bool());
 }
 
 void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature)
@@ -767,8 +784,27 @@ bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchComb
 CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & slot, CArtifactInstance * art)
 {
 	ArtPlacementMap resArtPlacement;
+	const auto putToSlot = [this](const ArtifactPosition & targetSlot, CArtifactInstance * targetArt, bool locked)
+	{
+		ArtSlotInfo * slotInfo;
+		if(targetSlot == ArtifactPosition::TRANSITION_POS)
+		{
+			slotInfo = &artifactsTransitionPos;
+		}
+		else if(ArtifactUtils::isSlotEquipment(targetSlot))
+		{
+			slotInfo = &artifactsWorn[targetSlot];
+		}
+		else
+		{
+			auto position = artifactsInBackpack.begin() + targetSlot - ArtifactPosition::BACKPACK_START;
+			slotInfo = &(*artifactsInBackpack.emplace(position));
+		}
+		slotInfo->artifact = targetArt;
+		slotInfo->locked = locked;
+	};
 
-	setNewArtSlot(slot, art, false);
+	putToSlot(slot, art, false);
 	if(art->artType->isCombined() && ArtifactUtils::isSlotEquipment(slot))
 	{
 		const CArtifactInstance * mainPart = nullptr;
@@ -789,7 +825,7 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition &
 					partSlot = ArtifactUtils::getArtAnyPosition(this, part.art->getTypeId());
 
 				assert(ArtifactUtils::isSlotEquipment(partSlot));
-				setNewArtSlot(partSlot, part.art, true);
+				putToSlot(partSlot, part.art, true);
 				resArtPlacement.emplace(part.art, partSlot);
 			}
 			else
@@ -877,7 +913,15 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const
 
 void CArtifactSet::lockSlot(const ArtifactPosition & pos)
 {
-	setNewArtSlot(pos, nullptr, true);
+	if(pos == ArtifactPosition::TRANSITION_POS)
+		artifactsTransitionPos.locked = true;
+	else if(ArtifactUtils::isSlotEquipment(pos))
+		artifactsWorn[pos].locked = true;
+	else
+	{
+		assert(artifactsInBackpack.size() > pos - ArtifactPosition::BACKPACK_START);
+		(artifactsInBackpack.begin() + pos - ArtifactPosition::BACKPACK_START)->locked = true;
+	}
 }
 
 bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const
@@ -891,28 +935,6 @@ bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockChe
 	return true; //no slot means not used
 }
 
-void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked)
-{
-	assert(!vstd::contains(artifactsWorn, slot));
-
-	ArtSlotInfo * slotInfo;
-	if(slot == ArtifactPosition::TRANSITION_POS)
-	{
-		slotInfo = &artifactsTransitionPos;
-	}
-	else if(ArtifactUtils::isSlotEquipment(slot))
-	{
-		slotInfo =  &artifactsWorn[slot];
-	}
-	else
-	{
-		auto position = artifactsInBackpack.begin() + slot - ArtifactPosition::BACKPACK_START;
-		slotInfo = &(*artifactsInBackpack.emplace(position));
-	}
-	slotInfo->artifact = art;
-	slotInfo->locked = locked;
-}
-
 void CArtifactSet::artDeserializationFix(CBonusSystemNode *node)
 {
 	for(auto & elem : artifactsWorn)

+ 8 - 5
lib/CArtHandler.h

@@ -46,14 +46,19 @@ namespace ArtBearer
 class DLL_LINKAGE CCombinedArtifact
 {
 protected:
-	CCombinedArtifact() = default;
+	CCombinedArtifact() : fused(false) {};
 
 	std::vector<const CArtifact*> constituents; // Artifacts IDs a combined artifact consists of, or nullptr.
-	std::vector<const CArtifact*> partOf; // Reverse map of constituents - combined arts that include this art
+	std::set<const CArtifact*> partOf; // Reverse map of constituents - combined arts that include this art
+	bool fused;
+
 public:
 	bool isCombined() const;
 	const std::vector<const CArtifact*> & getConstituents() const;
-	const std::vector<const CArtifact*> & getPartOf() const;
+	const std::set<const CArtifact*> & getPartOf() const;
+	void setFused(bool isFused);
+	bool isFused() const;
+	bool hasParts() const;
 };
 
 class DLL_LINKAGE CScrollArtifact
@@ -224,8 +229,6 @@ public:
 	const CArtifactInstance * getCombinedArtWithPart(const ArtifactID & partId) const;
 
 private:
-	void setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked);
-
 	void serializeJsonHero(JsonSerializeFormat & handler);
 	void serializeJsonCreature(JsonSerializeFormat & handler);
 	void serializeJsonCommander(JsonSerializeFormat & handler);

+ 5 - 0
lib/CArtifactInstance.cpp

@@ -44,6 +44,11 @@ bool CCombinedArtifactInstance::isPart(const CArtifactInstance * supposedPart) c
 	return false;
 }
 
+bool CCombinedArtifactInstance::hasParts() const
+{
+	return !partsInfo.empty();
+}
+
 const std::vector<CCombinedArtifactInstance::PartInfo> & CCombinedArtifactInstance::getPartsInfo() const
 {
 	return partsInfo;

+ 1 - 0
lib/CArtifactInstance.h

@@ -38,6 +38,7 @@ public:
 	void addPart(CArtifactInstance * art, const ArtifactPosition & slot);
 	// Checks if supposed part inst is part of this combined art inst
 	bool isPart(const CArtifactInstance * supposedPart) const;
+	bool hasParts() const;
 	const std::vector<PartInfo> & getPartsInfo() const;
 	void addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap);
 

+ 13 - 15
lib/networkPacks/NetPacksLib.cpp

@@ -1805,6 +1805,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
 	assert(hero);
 	const auto transformedArt = hero->getArt(al.slot);
 	assert(transformedArt);
+	const auto builtArt = artId.toArtifact();
 	assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(hero, transformedArt->getTypeId()), [=](const CArtifact * art)->bool
 		{
 			return art->getId() == builtArt->getId();
@@ -1816,14 +1817,11 @@ void AssembledArtifact::applyGs(CGameState *gs)
 
 	// Find slots for all involved artifacts
 	std::vector<ArtifactPosition> slotsInvolved;
+	CArtifactFittingSet artSet(*hero);
 	for(const auto constituent : builtArt->getConstituents())
 	{
-		ArtifactPosition slot;
-		if(transformedArt->getTypeId() == constituent->getId())
-			slot = transformedArtSlot;
-		else
-			slot = hero->getArtPos(constituent->getId(), false, false);
-
+		const auto slot = artSet.getArtPos(constituent->getId(), false, false);
+		artSet.lockSlot(slot);
 		assert(slot != ArtifactPosition::PRE_FIRST);
 		slotsInvolved.emplace_back(slot);
 	}
@@ -1831,7 +1829,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
 
 	// Find a slot for combined artifact
 	al.slot = transformedArtSlot;
-	for(const auto slot : slotsInvolved)
+	for(const auto & slot : slotsInvolved)
 	{
 		if(ArtifactUtils::isSlotEquipment(transformedArtSlot))
 		{
@@ -1854,15 +1852,18 @@ void AssembledArtifact::applyGs(CGameState *gs)
 	}
 
 	// Delete parts from hero
-	for(const auto slot : slotsInvolved)
+	for(const auto & slot : slotsInvolved)
 	{
 		const auto constituentInstance = hero->getArt(slot);
 		gs->map->removeArtifactInstance(*hero, slot);
 
-		if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot)
-			combinedArt->addPart(constituentInstance, slot);
-		else
-			combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST);
+		if(!combinedArt->artType->isFused())
+		{
+			if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot)
+				combinedArt->addPart(constituentInstance, slot);
+			else
+				combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST);
+		}
 	}
 
 	// Put new combined artifacts
@@ -2482,10 +2483,7 @@ void SetBankConfiguration::applyGs(CGameState *gs)
 const CArtifactInstance * ArtSlotInfo::getArt() const
 {
 	if(locked)
-	{
-		logNetwork->warn("ArtifactLocation::getArt: This location is locked!");
 		return nullptr;
-	}
 	return artifact;
 }
 

+ 3 - 3
lib/networkPacks/PacksForClient.h

@@ -1108,8 +1108,8 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
 
 struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack
 {
-	ArtifactLocation al; //where assembly will be put
-	const CArtifact * builtArt;
+	ArtifactLocation al;
+	ArtifactID artId;
 
 	void applyGs(CGameState * gs) override;
 
@@ -1118,7 +1118,7 @@ struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack
 	template <typename Handler> void serialize(Handler & h)
 	{
 		h & al;
-		h & builtArt;
+		h & artId;
 	}
 };
 

+ 4 - 1
server/CGameHandler.cpp

@@ -2913,7 +2913,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a
 
 		AssembledArtifact aa;
 		aa.al = dstLoc;
-		aa.builtArt = combinedArt;
+		aa.artId = assembleTo;
 		sendAndApply(aa);
 	}
 	else
@@ -2921,6 +2921,9 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a
 		if(!destArtifact->isCombined())
 			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is not a combined artifact!");
 
+		if(!destArtifact->hasParts())
+			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is fused combined artifact!");
+
 		if(ArtifactUtils::isSlotBackpack(artifactSlot)
 			&& !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->artType->getConstituents().size() - 1))
 			COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble but backpack is full!");