/* * CArtHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "CArtHandler.h" #include "ArtifactUtils.h" #include "CGeneralTextHandler.h" #include "CModHandler.h" #include "GameSettings.h" #include "spells/CSpellHandler.h" #include "mapObjects/MapObjects.h" #include "NetPacksBase.h" #include "StringConstants.h" #include "serializer/JsonSerializeFormat.h" // Note: list must match entries in ArtTraits.txt #define ART_POS_LIST \ ART_POS(SPELLBOOK) \ ART_POS(MACH4) \ ART_POS(MACH3) \ ART_POS(MACH2) \ ART_POS(MACH1) \ ART_POS(MISC5) \ ART_POS(MISC4) \ ART_POS(MISC3) \ ART_POS(MISC2) \ ART_POS(MISC1) \ ART_POS(FEET) \ ART_POS(LEFT_RING) \ ART_POS(RIGHT_RING) \ ART_POS(TORSO) \ ART_POS(LEFT_HAND) \ ART_POS(RIGHT_HAND) \ ART_POS(NECK) \ ART_POS(SHOULDERS) \ ART_POS(HEAD) VCMI_LIB_NAMESPACE_BEGIN int32_t CArtifact::getIndex() const { return id.toEnum(); } int32_t CArtifact::getIconIndex() const { return iconIndex; } std::string CArtifact::getJsonKey() const { return modScope + ':' + identifier; } void CArtifact::registerIcons(const IconRegistar & cb) const { cb(getIconIndex(), 0, "ARTIFACT", image); cb(getIconIndex(), 0, "ARTIFACTLARGE", large); } ArtifactID CArtifact::getId() const { return id; } const IBonusBearer * CArtifact::getBonusBearer() const { return this; } std::string CArtifact::getDescriptionTranslated() const { return VLC->generaltexth->translate(getDescriptionTextID()); } std::string CArtifact::getEventTranslated() const { return VLC->generaltexth->translate(getEventTextID()); } std::string CArtifact::getNameTranslated() const { return VLC->generaltexth->translate(getNameTextID()); } std::string CArtifact::getDescriptionTextID() const { return TextIdentifier("artifact", modScope, identifier, "description").get(); } std::string CArtifact::getEventTextID() const { return TextIdentifier("artifact", modScope, identifier, "event").get(); } std::string CArtifact::getNameTextID() const { return TextIdentifier("artifact", modScope, identifier, "name").get(); } uint32_t CArtifact::getPrice() const { return price; } CreatureID CArtifact::getWarMachine() const { return warMachine; } bool CArtifact::isBig() const { return warMachine != CreatureID::NONE; } bool CArtifact::isTradable() const { switch(id) { case ArtifactID::SPELLBOOK: case ArtifactID::GRAIL: return false; default: return !isBig(); } } bool CArtifact::canBeDisassembled() const { return !(constituents == nullptr); } bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) const { auto simpleArtCanBePutAt = [this](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool { if(ArtifactUtils::isSlotBackpack(slot)) { if(isBig() || !ArtifactUtils::isBackpackFreeSlots(artSet)) return false; return true; } if(!vstd::contains(possibleSlots.at(artSet->bearerType()), slot)) return false; return artSet->isPositionFree(slot, assumeDestRemoved); }; auto artCanBePutAt = [this, simpleArtCanBePutAt](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool { if(canBeDisassembled()) { if(!simpleArtCanBePutAt(artSet, slot, assumeDestRemoved)) return false; if(ArtifactUtils::isSlotBackpack(slot)) return true; CArtifactFittingSet fittingSet(artSet->bearerType()); fittingSet.artifactsWorn = artSet->artifactsWorn; if(assumeDestRemoved) fittingSet.removeArtifact(slot); assert(constituents); for(const auto art : *constituents) { auto possibleSlot = ArtifactUtils::getArtAnyPosition(&fittingSet, art->getId()); if(ArtifactUtils::isSlotEquipment(possibleSlot)) { fittingSet.setNewArtSlot(possibleSlot, nullptr, true); } else { return false; } } return true; } else { return simpleArtCanBePutAt(artSet, slot, assumeDestRemoved); } }; if(slot == ArtifactPosition::TRANSITION_POS) return true; if(slot == ArtifactPosition::FIRST_AVAILABLE) { for(const auto & slot : possibleSlots.at(artSet->bearerType())) { if(artCanBePutAt(artSet, slot, assumeDestRemoved)) return true; } return artCanBePutAt(artSet, GameConstants::BACKPACK_START, assumeDestRemoved); } else if(ArtifactUtils::isSlotBackpack(slot)) { return artCanBePutAt(artSet, GameConstants::BACKPACK_START, assumeDestRemoved); } else { return artCanBePutAt(artSet, slot, assumeDestRemoved); } } CArtifact::CArtifact() { setNodeType(ARTIFACT); possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty possibleSlots[ArtBearer::COMMANDER]; } //This destructor should be placed here to avoid side effects CArtifact::~CArtifact() = default; int CArtifact::getArtClassSerial() const { if(id == ArtifactID::SPELL_SCROLL) return 4; switch(aClass) { case ART_TREASURE: return 0; case ART_MINOR: return 1; case ART_MAJOR: return 2; case ART_RELIC: return 3; case ART_SPECIAL: return 5; } return -1; } std::string CArtifact::nodeName() const { return "Artifact: " + getNameTranslated(); } void CArtifact::addNewBonus(const std::shared_ptr& b) { b->source = BonusSource::ARTIFACT; b->duration = BonusDuration::PERMANENT; b->description = getNameTranslated(); CBonusSystemNode::addNewBonus(b); } void CArtifact::updateFrom(const JsonNode& data) { //TODO:CArtifact::updateFrom } void CArtifact::serializeJson(JsonSerializeFormat & handler) { } void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art) { auto b = std::make_shared(); b->type = BonusType::LEVEL_COUNTER; b->val = 1; b->duration = BonusDuration::COMMANDER_KILLED; art->accumulateBonus(b); for(const auto & bonus : bonusesPerLevel) { if (art->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0) //every n levels { art->accumulateBonus(std::make_shared(bonus.second)); } } for(const auto & bonus : thresholdBonuses) { if (art->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first) //every n levels { art->addNewBonus(std::make_shared(bonus.second)); } } } CArtHandler::~CArtHandler() = default; std::vector CArtHandler::loadLegacyData() { size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_ARTIFACT); objects.resize(dataSize); std::vector h3Data; h3Data.reserve(dataSize); #define ART_POS(x) #x , const std::vector artSlots = { ART_POS_LIST }; #undef ART_POS static std::map classes = {{'S',"SPECIAL"}, {'T',"TREASURE"},{'N',"MINOR"},{'J',"MAJOR"},{'R',"RELIC"},}; CLegacyConfigParser parser("DATA/ARTRAITS.TXT"); CLegacyConfigParser events("DATA/ARTEVENT.TXT"); parser.endLine(); // header parser.endLine(); for (size_t i = 0; i < dataSize; i++) { JsonNode artData; artData["text"]["name"].String() = parser.readString(); artData["text"]["event"].String() = events.readString(); artData["value"].Float() = parser.readNumber(); for(const auto & artSlot : artSlots) { if(parser.readString() == "x") { artData["slot"].Vector().push_back(JsonNode()); artData["slot"].Vector().back().String() = artSlot; } } artData["class"].String() = classes[parser.readString()[0]]; artData["text"]["description"].String() = parser.readString(); parser.endLine(); events.endLine(); h3Data.push_back(artData); } return h3Data; } void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { auto * object = loadFromJson(scope, data, name, objects.size()); object->iconIndex = object->getIndex() + 5; objects.emplace_back(object); registerObject(scope, "artifact", name, object->id); } void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { auto * object = loadFromJson(scope, data, name, index); object->iconIndex = object->getIndex(); assert(objects[index] == nullptr); // ensure that this id was not loaded before objects[index] = object; registerObject(scope, "artifact", name, object->id); } const std::vector & CArtHandler::getTypeNames() const { static const std::vector typeNames = { "artifact" }; return typeNames; } CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) { assert(identifier.find(':') == std::string::npos); assert(!scope.empty()); CArtifact * art = nullptr; if(!VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS) || node["growing"].isNull()) { art = new CArtifact(); } else { auto * growing = new CGrowingArtifact(); loadGrowingArt(growing, node); art = growing; } art->id = ArtifactID(index); art->identifier = identifier; art->modScope = scope; const JsonNode & text = node["text"]; VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"].String()); VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"].String()); VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"].String()); const JsonNode & graphics = node["graphics"]; art->image = graphics["image"].String(); if(!graphics["large"].isNull()) art->large = graphics["large"].String(); else art->large = art->image; art->advMapDef = graphics["map"].String(); art->price = static_cast(node["value"].Float()); loadSlots(art, node); loadClass(art, node); loadType(art, node); loadComponents(art, node); for(const auto & b : node["bonuses"].Vector()) { auto bonus = JsonUtils::parseBonus(b); art->addNewBonus(bonus); } const JsonNode & warMachine = node["warMachine"]; if(warMachine.getType() == JsonNode::JsonType::DATA_STRING && !warMachine.String().empty()) { VLC->modh->identifiers.requestIdentifier("creature", warMachine, [=](si32 id) { art->warMachine = CreatureID(id); //this assumes that creature object is stored before registration VLC->creh->objects.at(id)->warMachine = art->id; }); } VLC->modh->identifiers.requestIdentifier(scope, "object", "artifact", [=](si32 index) { JsonNode conf; conf.setMeta(scope); VLC->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex()); if(!art->advMapDef.empty()) { JsonNode templ; templ["animation"].String() = art->advMapDef; templ.setMeta(scope); // add new template. // Necessary for objects added via mods that don't have any templates in H3 VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->addTemplate(templ); } // object does not have any templates - this is not usable object (e.g. pseudo-art like lock) if(VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->getTemplates().empty()) VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->getIndex()); }); return art; } ArtifactPosition::ArtifactPosition(std::string slotName): num(ArtifactPosition::PRE_FIRST) { #define ART_POS(x) { #x, ArtifactPosition::x }, static const std::map artifactPositionMap = { ART_POS_LIST }; #undef ART_POS auto it = artifactPositionMap.find (slotName); if (it != artifactPositionMap.end()) num = it->second; else logMod->warn("Warning! Artifact slot %s not recognized!", slotName); } void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) const { static const std::vector miscSlots = { ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5 }; static const std::vector ringSlots = { ArtifactPosition::RIGHT_RING, ArtifactPosition::LEFT_RING }; if (slotID == "MISC") { vstd::concatenate(art->possibleSlots[ArtBearer::HERO], miscSlots); } else if (slotID == "RING") { vstd::concatenate(art->possibleSlots[ArtBearer::HERO], ringSlots); } else { auto slot = ArtifactPosition(slotID); if (slot != ArtifactPosition::PRE_FIRST) art->possibleSlots[ArtBearer::HERO].push_back(slot); } } void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node) const { if (!node["slot"].isNull()) //we assume non-hero slots are irrelevant? { if (node["slot"].getType() == JsonNode::JsonType::DATA_STRING) addSlot(art, node["slot"].String()); else { for (const JsonNode & slot : node["slot"].Vector()) addSlot(art, slot.String()); } std::sort(art->possibleSlots.at(ArtBearer::HERO).begin(), art->possibleSlots.at(ArtBearer::HERO).end()); } } CArtifact::EartClass CArtHandler::stringToClass(const std::string & className) { static const std::map artifactClassMap = { {"TREASURE", CArtifact::ART_TREASURE}, {"MINOR", CArtifact::ART_MINOR}, {"MAJOR", CArtifact::ART_MAJOR}, {"RELIC", CArtifact::ART_RELIC}, {"SPECIAL", CArtifact::ART_SPECIAL} }; auto it = artifactClassMap.find (className); if (it != artifactClassMap.end()) return it->second; logMod->warn("Warning! Artifact rarity %s not recognized!", className); return CArtifact::ART_SPECIAL; } void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) const { art->aClass = stringToClass(node["class"].String()); } void CArtHandler::loadType(CArtifact * art, const JsonNode & node) const { #define ART_BEARER(x) { #x, ArtBearer::x }, static const std::map artifactBearerMap = { ART_BEARER_LIST }; #undef ART_BEARER for (const JsonNode & b : node["type"].Vector()) { auto it = artifactBearerMap.find (b.String()); if (it != artifactBearerMap.end()) { int bearerType = it->second; switch (bearerType) { case ArtBearer::HERO://TODO: allow arts having several possible bearers break; case ArtBearer::COMMANDER: makeItCommanderArt (art); //original artifacts should have only one bearer type break; case ArtBearer::CREATURE: makeItCreatureArt (art); break; } } else logMod->warn("Warning! Artifact type %s not recognized!", b.String()); } } void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) { if (!node["components"].isNull()) { art->constituents = std::make_unique>(); for(const auto & component : node["components"].Vector()) { VLC->modh->identifiers.requestIdentifier("artifact", component, [=](si32 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(objects[id]); objects[id]->constituentOf.push_back(art); }); } } } void CArtHandler::loadGrowingArt(CGrowingArtifact * art, const JsonNode & node) const { for (auto b : node["growing"]["bonusesPerLevel"].Vector()) { art->bonusesPerLevel.emplace_back(static_cast(b["level"].Float()), Bonus()); JsonUtils::parseBonus(b["bonus"], &art->bonusesPerLevel.back().second); } for (auto b : node["growing"]["thresholdBonuses"].Vector()) { art->thresholdBonuses.emplace_back(static_cast(b["level"].Float()), Bonus()); JsonUtils::parseBonus(b["bonus"], &art->thresholdBonuses.back().second); } } ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts) { auto getAllowedArts = [&](std::vector > &out, std::vector *arts, CArtifact::EartClass flag) { if (arts->empty()) //restock available arts fillList(*arts, flag); for (auto & arts_i : *arts) { if (accepts(arts_i->id)) { CArtifact *art = arts_i; out.emplace_back(art); } } }; auto getAllowed = [&](std::vector > &out) { if (flags & CArtifact::ART_TREASURE) getAllowedArts (out, &treasures, CArtifact::ART_TREASURE); if (flags & CArtifact::ART_MINOR) getAllowedArts (out, &minors, CArtifact::ART_MINOR); if (flags & CArtifact::ART_MAJOR) getAllowedArts (out, &majors, CArtifact::ART_MAJOR); if (flags & CArtifact::ART_RELIC) getAllowedArts (out, &relics, CArtifact::ART_RELIC); if(out.empty()) //no artifact of specified rarity, we need to take another one { getAllowedArts (out, &treasures, CArtifact::ART_TREASURE); getAllowedArts (out, &minors, CArtifact::ART_MINOR); getAllowedArts (out, &majors, CArtifact::ART_MAJOR); getAllowedArts (out, &relics, CArtifact::ART_RELIC); } if(out.empty()) //no arts are available at all { out.resize (64); std::fill_n (out.begin(), 64, objects[2]); //Give Grail - this can't be banned (hopefully) } }; std::vector > out; getAllowed(out); ArtifactID artID = (*RandomGeneratorUtil::nextItem(out, rand))->id; erasePickedArt(artID); return artID; } ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, std::function accepts) { return pickRandomArtifact(rand, 0xff, std::move(accepts)); } ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) { return pickRandomArtifact(rand, flags, [](const ArtifactID &) { return true; }); } void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature) { if (onlyCreature) { a->possibleSlots[ArtBearer::HERO].clear(); a->possibleSlots[ArtBearer::COMMANDER].clear(); } a->possibleSlots[ArtBearer::CREATURE].push_back(ArtifactPosition::CREATURE_SLOT); } void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander) { if (onlyCommander) { a->possibleSlots[ArtBearer::HERO].clear(); a->possibleSlots[ArtBearer::CREATURE].clear(); } for (int i = ArtifactPosition::COMMANDER1; i <= ArtifactPosition::COMMANDER6; ++i) a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(i)); } bool CArtHandler::legalArtifact(const ArtifactID & id) { auto art = objects[id]; //assert ( (!art->constituents) || art->constituents->size() ); //artifacts is not combined or has some components if(art->constituents) return false; //no combo artifacts spawning if(art->aClass < CArtifact::ART_TREASURE || art->aClass > CArtifact::ART_RELIC) return false; // invalid class if(!art->possibleSlots[ArtBearer::HERO].empty()) return true; if(!art->possibleSlots[ArtBearer::CREATURE].empty() && VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT)) return true; if(!art->possibleSlots[ArtBearer::COMMANDER].empty() && VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS)) return true; return false; } void CArtHandler::initAllowedArtifactsList(const std::vector &allowed) { allowedArtifacts.clear(); treasures.clear(); minors.clear(); majors.clear(); relics.clear(); for (ArtifactID i=ArtifactID::SPELLBOOK; i(objects.size())); i.advance(1)) //try to allow all artifacts added by mods { if (legalArtifact(ArtifactID(i))) allowedArtifacts.push_back(objects[i]); //keep im mind that artifact can be worn by more than one type of bearer } } std::vector CArtHandler::getDefaultAllowed() const { std::vector allowedArtifacts; allowedArtifacts.resize(127, true); allowedArtifacts.resize(141, false); allowedArtifacts.resize(size(), true); return allowedArtifacts; } void CArtHandler::erasePickedArt(const ArtifactID & id) { CArtifact *art = objects[id]; std::vector * artifactList = nullptr; switch(art->aClass) { case CArtifact::ART_TREASURE: artifactList = &treasures; break; case CArtifact::ART_MINOR: artifactList = &minors; break; case CArtifact::ART_MAJOR: artifactList = &majors; break; case CArtifact::ART_RELIC: artifactList = &relics; break; default: logMod->warn("Problem: cannot find list for artifact %s, strange class. (special?)", art->getNameTranslated()); return; } if(artifactList->empty()) fillList(*artifactList, art->aClass); auto itr = vstd::find(*artifactList, art); if(itr != artifactList->end()) { artifactList->erase(itr); } else logMod->warn("Problem: cannot erase artifact %s from list, it was not present", art->getNameTranslated()); } void CArtHandler::fillList( std::vector &listToBeFilled, CArtifact::EartClass artifactClass ) { assert(listToBeFilled.empty()); for (auto & elem : allowedArtifacts) { if (elem->aClass == artifactClass) listToBeFilled.push_back(elem); } } void CArtHandler::afterLoadFinalization() { //All artifacts have their id, so we can properly update their bonuses' source ids. for(auto &art : objects) { for(auto &bonus : art->getExportedBonusList()) { assert(art == objects[art->id]); assert(bonus->source == BonusSource::ARTIFACT); bonus->sid = art->id; } } CBonusSystemNode::treeHasChanged(); } CArtifactInstance::CArtifactInstance() { init(); } CArtifactInstance::CArtifactInstance( CArtifact *Art) { init(); setType(Art); } void CArtifactInstance::setType( CArtifact *Art ) { artType = Art; attachTo(*Art); } std::string CArtifactInstance::nodeName() const { return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type"; } void CArtifactInstance::init() { id = ArtifactInstanceID(); id = static_cast(ArtifactID::NONE); //to be randomized setNodeType(ARTIFACT_INSTANCE); } std::string CArtifactInstance::getDescription() const { std::string text = artType->getDescriptionTranslated(); if (!vstd::contains(text, '{')) text = '{' + artType->getNameTranslated() + "}\n\n" + text; //workaround for new artifacts with single name, turns it to H3-style if(artType->getId() == ArtifactID::SPELL_SCROLL) { // we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll. // so we want to replace text in [...] with a spell name // however other language versions don't have name placeholder at all, so we have to be careful SpellID spellID = getScrollSpellID(); size_t nameStart = text.find_first_of('['); size_t nameEnd = text.find_first_of(']', nameStart); if(spellID.getNum() >= 0) { if(nameStart != std::string::npos && nameEnd != std::string::npos) text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getNameTranslated()); } } return text; } ArtifactID CArtifactInstance::getTypeId() const { return artType->getId(); } bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved) const { return artType->canBePutAt(al.getHolderArtSet(), al.slot, assumeDestRemoved); } void CArtifactInstance::putAt(ArtifactLocation al) { assert(canBePutAt(al)); al.getHolderArtSet()->CArtifactSet::putArtifact(al.slot, this); if(ArtifactUtils::isSlotEquipment(al.slot)) al.getHolderNode()->attachTo(*this); } void CArtifactInstance::removeFrom(ArtifactLocation al) { assert(al.getHolderArtSet()->getArt(al.slot) == this); al.getHolderArtSet()->removeArtifact(al.slot); if(ArtifactUtils::isSlotEquipment(al.slot)) al.getHolderNode()->detachFrom(*this); } bool CArtifactInstance::canBeDisassembled() const { return artType->canBeDisassembled(); } void CArtifactInstance::move(const ArtifactLocation & src, const ArtifactLocation & dst) { removeFrom(src); putAt(dst); } void CArtifactInstance::deserializationFix() { setType(artType); } SpellID CArtifactInstance::getScrollSpellID() const { const auto b = getBonusLocalFirst(Selector::type()(BonusType::SPELL)); if(!b) { logMod->warn("Warning: %s doesn't bear any spell!", nodeName()); return SpellID::NONE; } return SpellID(b->subtype); } bool CArtifactInstance::isPart(const CArtifactInstance *supposedPart) const { return supposedPart == this; } CCombinedArtifactInstance::CCombinedArtifactInstance(CArtifact *Art) : CArtifactInstance(Art) //TODO: seems unused, but need to be written { } void CCombinedArtifactInstance::createConstituents() { assert(artType); assert(artType->constituents); for(const CArtifact * art : *artType->constituents) { addAsConstituent(ArtifactUtils::createNewArtifactInstance(art->getId()), ArtifactPosition::PRE_FIRST); } } void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance * art, const ArtifactPosition & slot) { assert(vstd::contains_if(*artType->constituents, [=](const CArtifact * constituent){ return constituent->getId() == art->artType->getId(); })); assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->artType); constituentsInfo.emplace_back(art, slot); attachTo(*art); } void CCombinedArtifactInstance::removeFrom(ArtifactLocation al) { CArtifactInstance::removeFrom(al); for(auto & part : constituentsInfo) { if(part.slot != ArtifactPosition::PRE_FIRST) part.slot = ArtifactPosition::PRE_FIRST; } } void CCombinedArtifactInstance::deserializationFix() { for(ConstituentInfo &ci : constituentsInfo) attachTo(*ci.art); } bool CCombinedArtifactInstance::isPart(const CArtifactInstance *supposedPart) const { bool me = CArtifactInstance::isPart(supposedPart); if(me) return true; //check for constituents for(const ConstituentInfo &constituent : constituentsInfo) if(constituent.art == supposedPart) return true; return false; } CCombinedArtifactInstance::ConstituentInfo::ConstituentInfo(CArtifactInstance * Art, const ArtifactPosition & Slot): art(Art), slot(Slot) { } bool CCombinedArtifactInstance::ConstituentInfo::operator==(const ConstituentInfo &rhs) const { return art == rhs.art && slot == rhs.slot; } CArtifactSet::~CArtifactSet() = default; const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const { if(const ArtSlotInfo *si = getSlot(pos)) { if(si->artifact && (!excludeLocked || !si->locked)) return si->artifact; } return nullptr; } CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) { return const_cast((const_cast(this))->getArt(pos, excludeLocked)); } ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn, bool allowLocked) const { const auto result = getAllArtPositions(aid, onlyWorn, allowLocked, false); return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0]; } ArtifactPosition CArtifactSet::getArtBackpackPos(const ArtifactID & aid) const { const auto result = getBackpackArtPositions(aid); return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0]; } std::vector CArtifactSet::getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const { std::vector result; for(const auto & slotInfo : artifactsWorn) if(slotInfo.second.artifact->getTypeId() == aid && (allowLocked || !slotInfo.second.locked)) result.push_back(slotInfo.first); if(onlyWorn) return result; if(!getAll && !result.empty()) return result; auto backpackPositions = getBackpackArtPositions(aid); result.insert(result.end(), backpackPositions.begin(), backpackPositions.end()); return result; } std::vector CArtifactSet::getBackpackArtPositions(const ArtifactID & aid) const { std::vector result; si32 backpackPosition = GameConstants::BACKPACK_START; for(const auto & artInfo : artifactsInBackpack) { const auto * art = artInfo.getArt(); if(art && art->artType->getId() == aid) result.emplace_back(backpackPosition); backpackPosition++; } return result; } ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance *art) const { for(auto i : artifactsWorn) if(i.second.artifact == art) return i.first; for(int i = 0; i < artifactsInBackpack.size(); i++) if(artifactsInBackpack[i].artifact == art) return ArtifactPosition(GameConstants::BACKPACK_START + i); return ArtifactPosition::PRE_FIRST; } const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanceID & artInstId) const { for(auto i : artifactsWorn) if(i.second.artifact->id == artInstId) return i.second.artifact; for(auto i : artifactsInBackpack) if(i.artifact->id == artInstId) return i.artifact; return nullptr; } const ArtifactPosition CArtifactSet::getSlotByInstance(const CArtifactInstance * artInst) const { if(artInst) { for(auto & slot : artInst->artType->possibleSlots.at(bearerType())) if(getArt(slot) == artInst) return slot; auto backpackSlot = GameConstants::BACKPACK_START; for(auto & slotInfo : artifactsInBackpack) { if(slotInfo.getArt() == artInst) return backpackSlot; backpackSlot = ArtifactPosition(backpackSlot + 1); } } return ArtifactPosition::PRE_FIRST; } bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const { return getArtPosCount(aid, onlyWorn, searchBackpackAssemblies, allowLocked) > 0; } bool CArtifactSet::hasArtBackpack(const ArtifactID & aid) const { return !getBackpackArtPositions(aid).empty(); } unsigned CArtifactSet::getArtPosCount(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const { const auto allPositions = getAllArtPositions(aid, onlyWorn, allowLocked, true); if(!allPositions.empty()) return allPositions.size(); if(searchBackpackAssemblies && getHiddenArt(aid)) return 1; return 0; } void CArtifactSet::putArtifact(ArtifactPosition slot, CArtifactInstance * art) { setNewArtSlot(slot, art, false); if(art->artType->canBeDisassembled() && ArtifactUtils::isSlotEquipment(slot)) { const CArtifactInstance * mainPart = nullptr; auto & parts = dynamic_cast(art)->constituentsInfo; for(const auto & part : parts) if(vstd::contains(part.art->artType->possibleSlots.at(bearerType()), slot)) { mainPart = part.art; break; } for(auto & part : parts) { if(part.art != mainPart) { if(!part.art->artType->canBePutAt(this, part.slot)) part.slot = ArtifactUtils::getArtAnyPosition(this, part.art->getTypeId()); assert(ArtifactUtils::isSlotEquipment(part.slot)); setNewArtSlot(part.slot, art, true); } } } } void CArtifactSet::removeArtifact(ArtifactPosition slot) { auto art = getArt(slot, false); if(art) { if(art->canBeDisassembled()) { auto combinedArt = dynamic_cast(art); for(auto & part : combinedArt->constituentsInfo) { if(getArt(part.slot, false)) eraseArtSlot(part.slot); } } eraseArtSlot(slot); } } std::pair CArtifactSet::searchForConstituent(const ArtifactID & aid) const { for(const auto & slot : artifactsInBackpack) { auto art = slot.artifact; if(art->canBeDisassembled()) { auto * ass = dynamic_cast(art.get()); for(auto& ci : ass->constituentsInfo) { if(ci.art->getTypeId() == aid) { return {ass, ci.art}; } } } } return {nullptr, nullptr}; } const CArtifactInstance * CArtifactSet::getHiddenArt(const ArtifactID & aid) const { return searchForConstituent(aid).second; } const CCombinedArtifactInstance * CArtifactSet::getAssemblyByConstituent(const ArtifactID & aid) const { return searchForConstituent(aid).first; } const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const { if(pos == ArtifactPosition::TRANSITION_POS) { // Always add to the end. Always take from the beginning. if(artifactsTransitionPos.empty()) return nullptr; else return &(*artifactsTransitionPos.begin()); } if(vstd::contains(artifactsWorn, pos)) return &artifactsWorn.at(pos); if(pos >= ArtifactPosition::AFTER_LAST ) { int backpackPos = (int)pos - GameConstants::BACKPACK_START; if(backpackPos < 0 || backpackPos >= artifactsInBackpack.size()) return nullptr; else return &artifactsInBackpack[backpackPos]; } return nullptr; } bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const { if(const ArtSlotInfo *s = getSlot(pos)) return (onlyLockCheck || !s->artifact) && !s->locked; 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) { // Always add to the end. Always take from the beginning. artifactsTransitionPos.emplace_back(); slotInfo = &artifactsTransitionPos.back(); } else if(ArtifactUtils::isSlotEquipment(slot)) { slotInfo = &artifactsWorn[slot]; } else { auto position = artifactsInBackpack.begin() + slot - GameConstants::BACKPACK_START; slotInfo = &(*artifactsInBackpack.emplace(position, ArtSlotInfo())); } slotInfo->artifact = art; slotInfo->locked = locked; } void CArtifactSet::eraseArtSlot(const ArtifactPosition & slot) { if(slot == ArtifactPosition::TRANSITION_POS) { assert(!artifactsTransitionPos.empty()); artifactsTransitionPos.erase(artifactsTransitionPos.begin()); } else if(ArtifactUtils::isSlotBackpack(slot)) { auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START); assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end()); artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot); } else { artifactsWorn.erase(slot); } } void CArtifactSet::artDeserializationFix(CBonusSystemNode *node) { for(auto & elem : artifactsWorn) if(elem.second.artifact && !elem.second.locked) node->attachTo(*elem.second.artifact); } void CArtifactSet::serializeJsonArtifacts(JsonSerializeFormat & handler, const std::string & fieldName, CMap * map) { //todo: creature and commander artifacts if(handler.saving && artifactsInBackpack.empty() && artifactsWorn.empty()) return; if(!handler.saving) { assert(map); artifactsInBackpack.clear(); artifactsWorn.clear(); } auto s = handler.enterStruct(fieldName); switch(bearerType()) { case ArtBearer::HERO: serializeJsonHero(handler, map); break; case ArtBearer::CREATURE: serializeJsonCreature(handler, map); break; case ArtBearer::COMMANDER: serializeJsonCommander(handler, map); break; default: assert(false); break; } } void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map) { for(ArtifactPosition ap = ArtifactPosition::HEAD; ap < ArtifactPosition::AFTER_LAST; ap.advance(1)) { serializeJsonSlot(handler, ap, map); } std::vector backpackTemp; if(handler.saving) { backpackTemp.reserve(artifactsInBackpack.size()); for(const ArtSlotInfo & info : artifactsInBackpack) backpackTemp.push_back(info.artifact->getTypeId()); } handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp); if(!handler.saving) { for(const ArtifactID & artifactID : backpackTemp) { auto * artifact = ArtifactUtils::createArtifact(map, artifactID.toEnum()); auto slot = ArtifactPosition(GameConstants::BACKPACK_START + (si32)artifactsInBackpack.size()); if(artifact->artType->canBePutAt(this, slot)) putArtifact(slot, artifact); } } } void CArtifactSet::serializeJsonCreature(JsonSerializeFormat & handler, CMap * map) { logGlobal->error("CArtifactSet::serializeJsonCreature not implemented"); } void CArtifactSet::serializeJsonCommander(JsonSerializeFormat & handler, CMap * map) { logGlobal->error("CArtifactSet::serializeJsonCommander not implemented"); } void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const ArtifactPosition & slot, CMap * map) { ArtifactID artifactID; if(handler.saving) { const ArtSlotInfo * info = getSlot(slot); if(info != nullptr && !info->locked) { artifactID = info->artifact->getTypeId(); handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE); } } else { handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE); if(artifactID != ArtifactID::NONE) { auto * artifact = ArtifactUtils::createArtifact(map, artifactID.toEnum()); if(artifact->artType->canBePutAt(this, slot)) { putArtifact(slot, artifact); } else { logGlobal->debug("Artifact can't be put at the specified location."); //TODO add more debugging information } } } } CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer Bearer): Bearer(Bearer) { } ArtBearer::ArtBearer CArtifactFittingSet::bearerType() const { return this->Bearer; } VCMI_LIB_NAMESPACE_END