From d4d3ddf6850ddc945728577ff1a65af38605a291 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:33:46 +0300 Subject: [PATCH 1/2] fused artifacts --- client/ArtifactsUIController.cpp | 2 +- .../Entities_Format/Artifact_Format.md | 3 +++ lib/CArtHandler.cpp | 23 ++++++++++++++++--- lib/CArtHandler.h | 7 +++++- lib/CArtifactInstance.cpp | 5 ++++ lib/CArtifactInstance.h | 1 + lib/networkPacks/NetPacksLib.cpp | 16 ++++++++----- lib/networkPacks/PacksForClient.h | 6 ++--- server/CGameHandler.cpp | 5 +++- 9 files changed, 53 insertions(+), 15 deletions(-) diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 73461a58a..30374ebc2 100644 --- a/client/ArtifactsUIController.cpp +++ b/client/ArtifactsUIController.cpp @@ -102,7 +102,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; diff --git a/docs/modders/Entities_Format/Artifact_Format.md b/docs/modders/Entities_Format/Artifact_Format.md index 928496dc7..83b1adca2 100644 --- a/docs/modders/Entities_Format/Artifact_Format.md +++ b/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" diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 07b83dec3..f96e17f83 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -61,6 +61,21 @@ const std::vector & 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(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,11 +621,11 @@ 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 @@ -619,6 +634,8 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) }); } } + if(!node["fusedComponents"].isNull()) + art->setFused(node["fusedComponents"].Bool()); } void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index e82f43147..f0bf7d8af 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -46,14 +46,19 @@ namespace ArtBearer class DLL_LINKAGE CCombinedArtifact { protected: - CCombinedArtifact() = default; + CCombinedArtifact() : fused(false) {}; std::vector constituents; // Artifacts IDs a combined artifact consists of, or nullptr. std::vector partOf; // Reverse map of constituents - combined arts that include this art + bool fused; + public: bool isCombined() const; const std::vector & getConstituents() const; const std::vector & getPartOf() const; + void setFused(bool isFused); + bool isFused() const; + bool hasParts() const; }; class DLL_LINKAGE CScrollArtifact diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index 6f191e46b..f65e6d95b 100644 --- a/lib/CArtifactInstance.cpp +++ b/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::getPartsInfo() const { return partsInfo; diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index f679f8b44..6ff0bdbe0 100644 --- a/lib/CArtifactInstance.h +++ b/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 & getPartsInfo() const; void addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6ea83ab8f..f44e6411e 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1806,6 +1806,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(); @@ -1832,7 +1833,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)) { @@ -1855,15 +1856,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 diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index b9f678ba6..0413113dc 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/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 void serialize(Handler & h) { h & al; - h & builtArt; + h & artId; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1f9549fa0..ab933a4f8 100644 --- a/server/CGameHandler.cpp +++ b/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!"); From 86d5c05ffa3535444783cc6de36435bba56e5736 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:20:29 +0300 Subject: [PATCH 2/2] Fused identical artifacts --- Mods/vcmi/config/vcmi/english.json | 1 + client/ArtifactsUIController.cpp | 5 ++- client/widgets/CArtPlace.cpp | 14 ++++-- client/widgets/CArtifactsOfHeroBase.cpp | 10 ++++- config/schemas/artifact.json | 4 ++ lib/ArtifactUtils.cpp | 14 +++--- lib/CArtHandler.cpp | 59 ++++++++++++++----------- lib/CArtHandler.h | 6 +-- lib/networkPacks/NetPacksLib.cpp | 12 ++--- 9 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index ce9bc9e45..f6cff35ca 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -356,6 +356,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", diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 30374ebc2..649cd7304 100644 --- a/client/ArtifactsUIController.cpp +++ b/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]() { diff --git a/client/widgets/CArtPlace.cpp b/client/widgets/CArtPlace.cpp index dbae173a2..954366315 100644 --- a/client/widgets/CArtPlace.cpp +++ b/client/widgets/CArtPlace.cpp @@ -217,9 +217,9 @@ void CArtPlace::setGestureCallback(const ClickFunctor & callback) void CArtPlace::addCombinedArtInfo(const std::map> & 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::mapgetId()); 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 { diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 8318fdf89..6d857b010 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -268,11 +268,17 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit std::map> arts; for(const auto combinedArt : slotInfo->artifact->artType->getPartOf()) { - arts.try_emplace(combinedArt->getId(), std::vector{}); + 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); diff --git a/config/schemas/artifact.json b/config/schemas/artifact.json index ab335a5ca..7143ddecc 100644 --- a/config/schemas/artifact.json +++ b/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", diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index 7c95e435a..bfdb48468 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -202,21 +202,23 @@ DLL_LINKAGE std::vector 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; } diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index f96e17f83..1cd5afdda 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -56,7 +56,7 @@ const std::vector & CCombinedArtifact::getConstituents() const return constituents; } -const std::vector & CCombinedArtifact::getPartOf() const +const std::set & CCombinedArtifact::getPartOf() const { return partOf; } @@ -630,7 +630,7 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) // 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); }); } } @@ -784,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; @@ -806,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 @@ -894,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 @@ -908,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) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index f0bf7d8af..a902f22c8 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -49,13 +49,13 @@ protected: CCombinedArtifact() : fused(false) {}; std::vector constituents; // Artifacts IDs a combined artifact consists of, or nullptr. - std::vector partOf; // Reverse map of constituents - combined arts that include this art + std::set partOf; // Reverse map of constituents - combined arts that include this art bool fused; public: bool isCombined() const; const std::vector & getConstituents() const; - const std::vector & getPartOf() const; + const std::set & getPartOf() const; void setFused(bool isFused); bool isFused() const; bool hasParts() const; @@ -229,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); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index f44e6411e..0e0331d82 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1818,14 +1818,11 @@ void AssembledArtifact::applyGs(CGameState *gs) // Find slots for all involved artifacts std::vector 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); } @@ -2487,10 +2484,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; }