diff --git a/AI/Nullkiller/Goals/CompleteQuest.cpp b/AI/Nullkiller/Goals/CompleteQuest.cpp index e4d3fda47..d0e981a78 100644 --- a/AI/Nullkiller/Goals/CompleteQuest.cpp +++ b/AI/Nullkiller/Goals/CompleteQuest.cpp @@ -37,42 +37,29 @@ TGoalVec CompleteQuest::decompose() const } logAi->debug("Trying to realize quest: %s", questToString()); - - switch(q.quest->missionType) - { - case CQuest::MISSION_ART: + + if(!q.quest->mission.artifacts.empty()) return missionArt(); - case CQuest::MISSION_HERO: + if(!q.quest->mission.heroes.empty()) return missionHero(); - case CQuest::MISSION_ARMY: + if(!q.quest->mission.creatures.empty()) return missionArmy(); - case CQuest::MISSION_RESOURCES: + if(q.quest->mission.resources.nonZero()) return missionResources(); - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: + if(q.quest->killTarget != ObjectInstanceID::NONE) return missionDestroyObj(); - case CQuest::MISSION_PRIMARY_STAT: - return missionIncreasePrimaryStat(); + for(auto & s : q.quest->mission.primary) + if(s) + return missionIncreasePrimaryStat(); - case CQuest::MISSION_LEVEL: + if(q.quest->mission.heroLevel > 0) return missionLevel(); - case CQuest::MISSION_PLAYER: - if(ai->playerID.getNum() != q.quest->m13489val) - logAi->debug("Can't be player of color %d", q.quest->m13489val); - - break; - - case CQuest::MISSION_KEYMASTER: - return missionKeymaster(); - - } //end of switch - return TGoalVec(); } @@ -107,7 +94,7 @@ std::string CompleteQuest::questToString() const return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent"; } - if(q.quest->missionType == CQuest::MISSION_NONE) + if(q.quest->questName == CQuest::missionName(0)) return "inactive quest"; MetaString ms; @@ -137,7 +124,7 @@ TGoalVec CompleteQuest::missionArt() const CaptureObjectsBehavior findArts; - for(auto art : q.quest->m5arts) + for(auto art : q.quest->mission.artifacts) { solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art))); } @@ -223,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const TGoalVec CompleteQuest::missionDestroyObj() const { - auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val); + auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget); if(!obj) return CaptureObjectsBehavior(q.obj).decompose(); diff --git a/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp b/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp index dae8c7bb2..1efebafca 100644 --- a/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp @@ -25,7 +25,7 @@ namespace AIPathfinding return dynamic_cast(questInfo.obj)->checkQuest(node->actor->hero); } - return questInfo.quest->progress == CQuest::NOT_ACTIVE + return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner()) || questInfo.quest->checkQuest(node->actor->hero); } diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index dafcb4758..7f6664a22 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -130,7 +130,9 @@ namespace AIPathfinding auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); QuestAction questAction(questInfo); - if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE) + if(destination.nodeObject->ID == Obj::QUEST_GUARD + && questObj->quest->mission == Rewardable::Limiter{} + && questObj->quest->killTarget == ObjectInstanceID::NONE) { return false; } diff --git a/AI/VCAI/Goals/CompleteQuest.cpp b/AI/VCAI/Goals/CompleteQuest.cpp index f724e308b..cb78ee8db 100644 --- a/AI/VCAI/Goals/CompleteQuest.cpp +++ b/AI/VCAI/Goals/CompleteQuest.cpp @@ -21,48 +21,43 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const return q.quest->qid == other.q.quest->qid; } +bool isKeyMaster(const QuestInfo & q) +{ + return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD); +} + TGoalVec CompleteQuest::getAllPossibleSubgoals() { TGoalVec solutions; - if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE) + if(!q.quest->isCompleted) { logAi->debug("Trying to realize quest: %s", questToString()); - - switch(q.quest->missionType) - { - case CQuest::MISSION_ART: - return missionArt(); - - case CQuest::MISSION_HERO: - return missionHero(); - - case CQuest::MISSION_ARMY: - return missionArmy(); - - case CQuest::MISSION_RESOURCES: - return missionResources(); - - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: - return missionDestroyObj(); - - case CQuest::MISSION_PRIMARY_STAT: - return missionIncreasePrimaryStat(); - - case CQuest::MISSION_LEVEL: - return missionLevel(); - - case CQuest::MISSION_PLAYER: - if(ai->playerID.getNum() != q.quest->m13489val) - logAi->debug("Can't be player of color %d", q.quest->m13489val); - - break; - case CQuest::MISSION_KEYMASTER: + if(isKeyMaster(q)) return missionKeymaster(); - } //end of switch + if(!q.quest->mission.artifacts.empty()) + return missionArt(); + + if(!q.quest->mission.heroes.empty()) + return missionHero(); + + if(!q.quest->mission.creatures.empty()) + return missionArmy(); + + if(q.quest->mission.resources.nonZero()) + return missionResources(); + + if(q.quest->killTarget != ObjectInstanceID::NONE) + return missionDestroyObj(); + + for(auto & s : q.quest->mission.primary) + if(s) + return missionIncreasePrimaryStat(); + + if(q.quest->mission.heroLevel > 0) + return missionLevel(); } return TGoalVec(); @@ -70,7 +65,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals() TSubgoal CompleteQuest::whatToDoToAchieve() { - if(q.quest->missionType == CQuest::MISSION_NONE) + if(q.quest->mission == Rewardable::Limiter{}) { throw cannotFulfillGoalException("Can not complete inactive quest"); } @@ -104,7 +99,7 @@ std::string CompleteQuest::completeMessage() const std::string CompleteQuest::questToString() const { - if(q.quest->missionType == CQuest::MISSION_NONE) + if(q.quest->questName == CQuest::missionName(0)) return "inactive quest"; MetaString ms; @@ -137,7 +132,7 @@ TGoalVec CompleteQuest::missionArt() const if(!solutions.empty()) return solutions; - for(auto art : q.quest->m5arts) + for(auto art : q.quest->mission.artifacts) { solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport? } @@ -165,7 +160,7 @@ TGoalVec CompleteQuest::missionArmy() const if(!solutions.empty()) return solutions; - for(auto creature : q.quest->m6creatures) + for(auto creature : q.quest->mission.creatures) { solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count))); } @@ -179,7 +174,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const if(solutions.empty()) { - for(int i = 0; i < q.quest->m2stats.size(); ++i) + for(int i = 0; i < q.quest->mission.primary.size(); ++i) { // TODO: library, school and other boost objects logAi->debug("Don't know how to increase primary stat %d", i); @@ -195,7 +190,7 @@ TGoalVec CompleteQuest::missionLevel() const if(solutions.empty()) { - logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val); + logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel); } return solutions; @@ -227,10 +222,10 @@ TGoalVec CompleteQuest::missionResources() const } else { - for(int i = 0; i < q.quest->m7resources.size(); ++i) + for(int i = 0; i < q.quest->mission.resources.size(); ++i) { - if(q.quest->m7resources[i]) - solutions.push_back(sptr(CollectRes(static_cast(i), q.quest->m7resources[i]))); + if(q.quest->mission.resources[i]) + solutions.push_back(sptr(CollectRes(static_cast(i), q.quest->mission.resources[i]))); } } } @@ -246,7 +241,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const { TGoalVec solutions; - auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val); + auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget); if(!obj) return ai->ah->howToVisitObj(q.obj); diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index de1f9878e..c43870c9b 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -148,11 +148,11 @@ void CQuestLog::recreateLabelList() int currentLabel = 0; for (int i = 0; i < quests.size(); ++i) { - // Quests with MISSION_NONE type don't have text for them and can't be displayed - if (quests[i].quest->missionType == CQuest::MISSION_NONE) + // Quests without mision don't have text for them and can't be displayed + if (quests[i].quest->mission == Rewardable::Limiter{}) continue; - if (quests[i].quest->progress == CQuest::COMPLETE) + if (quests[i].quest->isCompleted) { completeMissing = false; if (hideComplete) @@ -180,7 +180,7 @@ void CQuestLog::recreateLabelList() labels.push_back(label); // Select latest active quest - if (quests[i].quest->progress != CQuest::COMPLETE) + if(!quests[i].quest->isCompleted) selectQuest(i, currentLabel); currentLabel = static_cast(labels.size()); @@ -236,7 +236,7 @@ void CQuestLog::selectQuest(int which, int labelId) MetaString text; std::vector components; - currentQuest->quest->getVisitText (text, components, currentQuest->quest->isCustomFirst, true); + currentQuest->quest->getVisitText(text, components, true); if(description->slider) description->slider->scrollToMin(); // scroll text to start position description->setText(text.toString()); //TODO: use special log entry text @@ -247,9 +247,15 @@ void CQuestLog::selectQuest(int which, int labelId) int descriptionHeight = DESCRIPTION_HEIGHT_MAX; if(componentsSize) { - descriptionHeight -= 15; CComponent::ESize imageSize = CComponent::large; - switch (currentQuest->quest->missionType) + if (componentsSize > 4) + { + imageSize = CComponent::small; // Only small icons can be used for resources as 4+ icons take too much space + descriptionHeight -= 155; + } + else + descriptionHeight -= 130; + /*switch (currentQuest->quest->missionType) { case CQuest::MISSION_ARMY: { @@ -285,7 +291,7 @@ void CQuestLog::selectQuest(int which, int labelId) default: descriptionHeight -= 115; break; - } + }*/ OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); diff --git a/docs/modders/Map_Objects/Rewardable.md b/docs/modders/Map_Objects/Rewardable.md index 1694b7e7a..9be9b6fcd 100644 --- a/docs/modders/Map_Objects/Rewardable.md +++ b/docs/modders/Map_Objects/Rewardable.md @@ -72,7 +72,7 @@ Rewardable object is defined similarly to other objects, with key difference bei // additional list of conditions. Limiter will be valid if any of these conditions are true "anyOf" : [ { - // See "Configurable Properties" section for additiona parameters + // See "Configurable Properties" section for additional parameters } ] @@ -80,12 +80,12 @@ Rewardable object is defined similarly to other objects, with key difference bei // additional list of conditions. Limiter will be valid only if none of these conditions are true "noneOf" : [ { - // See "Configurable Properties" section for additiona parameters + // See "Configurable Properties" section for additional parameters } ] - // See "Configurable Properties" section for additiona parameters + // See "Configurable Properties" section for additional parameters } @@ -95,7 +95,7 @@ Rewardable object is defined similarly to other objects, with key difference bei // object will be disappeared after taking reward is set to true "removeObject": false - // See "Configurable Properties" section for additiona parameters + // See "Configurable Properties" section for additional parameters } ], @@ -450,4 +450,31 @@ Keep in mind, that all randomization is performed on map load and on object rese "spell" : "townPortal", "schoolLevel": 3 } +``` + +### Player color +- Can be used as limiter +- Can NOT be used as reward +- Only players with specific color can pass the limiter + +```jsonc +"colors" : [ "red", "blue", "tan", "green", "orange", "purple", "teal", "pink" ] +``` + +### Hero types +- Can be used as limiter +- Can NOT be used as reward +- Only specific heroes can pass the limiter + +```jsonc +"heroes" : [ "orrin" ] +``` + +### Hero classes +- Can be used as limiter +- Can NOT be used as reward +- Only heroes belonging to specific classes can pass the limiter + +```jsonc +"heroClasses" : [ "battlemage" ] ``` \ No newline at end of file diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 317cabaf8..9f8a3b20f 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -1029,6 +1029,14 @@ void CStackBasicDescriptor::setType(const CCreature * c) type = c; } +bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r) +{ + return (!l.type && !r.type) + || (l.type && r.type + && l.type->getId() == r.type->getId() + && l.count == r.count); +} + void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler) { handler.serializeInt("amount", count); diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index 6dff6b7fa..abc2d09d1 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -42,6 +42,8 @@ public: TQuantity getCount() const; virtual void setType(const CCreature * c); + + friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r); template void serialize(Handler &h, const int version) { diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index bc3dfebe3..077f862c5 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -662,9 +662,9 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav case RumorState::TYPE_SPECIAL: text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first); if(rumor.first == RumorState::RUMOR_GRAIL) - text.replaceTextID(TextIdentifier("core", "genrltxt", "arraytxt", 158 + rumor.second).get()); + text.replaceTextID(TextIdentifier("core", "arraytxt", 158 + rumor.second).get()); else - text.replaceTextID(TextIdentifier("core", "genrltxt", "capitalColors", rumor.second).get()); + text.replaceTextID(TextIdentifier("core", "plcolors", rumor.second).get()); break; case RumorState::TYPE_MAP: @@ -672,7 +672,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav break; case RumorState::TYPE_RAND: - text.replaceTextID(TextIdentifier("core", "genrltxt", "randtvrn", rumor.first).get()); + text.replaceTextID(TextIdentifier("core", "randtvrn", rumor.first).get()); break; } diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 09d046ef7..f68d6b3d4 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -264,21 +264,21 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) { - subContainers.insert(&container); + subContainers.push_back(&container); } void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container) { - subContainers.erase(&container); + subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end()); } const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const { if(stringsLocalizations.count(identifier.get()) == 0) { - for(const auto * container : subContainers) - if(container->identifierExists(identifier)) - return container->deserialize(identifier); + for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter) + if((*containerIter)->identifierExists(identifier)) + return (*containerIter)->deserialize(identifier); logGlobal->error("Unable to find localization for string '%s'", identifier.get()); return identifier.get(); @@ -547,7 +547,7 @@ CGeneralTextHandler::CGeneralTextHandler(): for (size_t i = 0; i < 9; ++i) //9 types of quests { - std::string questName = CQuest::missionName(static_cast(1+i)); + std::string questName = CQuest::missionName(1+i); for (size_t j = 0; j < 5; ++j) { diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 3807ddc7a..7bc7b3b94 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -146,7 +146,7 @@ protected: /// map identifier -> localization std::unordered_map stringsLocalizations; - std::set subContainers; + std::vector subContainers; /// add selected string to internal storage as high-priority strings void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized); diff --git a/lib/JsonRandom.cpp b/lib/JsonRandom.cpp index 270c47b80..95ce1f4f4 100644 --- a/lib/JsonRandom.cpp +++ b/lib/JsonRandom.cpp @@ -22,6 +22,7 @@ #include "CCreatureSet.h" #include "spells/CSpellHandler.h" #include "CSkillHandler.h" +#include "CHeroHandler.h" #include "IGameCallback.h" #include "mapObjects/IObjectInterface.h" #include "modding/IdentifierStorage.h" @@ -282,6 +283,46 @@ namespace JsonRandom return ret; } + std::vector loadColors(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + std::set def; + + for(auto & color : GameConstants::PLAYER_COLOR_NAMES) + def.insert(color); + + for(auto & entry : value.Vector()) + { + auto key = loadKey(entry, rng, def); + auto pos = vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, key); + if(pos < 0) + logMod->warn("Unable to determine player color %s", key); + else + ret.emplace_back(pos); + } + return ret; + } + + std::vector loadHeroes(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for(auto & entry : value.Vector()) + { + ret.push_back(VLC->heroTypes()->getByIndex(VLC->identifiers()->getIdentifier("hero", entry.String()).value())->getId()); + } + return ret; + } + + std::vector loadHeroClasses(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for(auto & entry : value.Vector()) + { + ret.push_back(VLC->heroClasses()->getByIndex(VLC->identifiers()->getIdentifier("heroClass", entry.String()).value())->getId()); + } + return ret; + } + CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng) { CStackBasicDescriptor stack; diff --git a/lib/JsonRandom.h b/lib/JsonRandom.h index 2ac9e1d65..ef066d14e 100644 --- a/lib/JsonRandom.h +++ b/lib/JsonRandom.h @@ -48,6 +48,10 @@ namespace JsonRandom DLL_LINKAGE std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng); DLL_LINKAGE std::vector evaluateCreatures(const JsonNode & value); + DLL_LINKAGE std::vector loadColors(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadHeroes(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadHeroClasses(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadBonuses(const JsonNode & value); //DLL_LINKAGE std::vector loadComponents(const JsonNode & value); } diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 4fe197b5c..eaa790c9b 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -103,6 +105,25 @@ namespace GameConstants #endif } +si32 HeroClassID::decode(const std::string & identifier) +{ + auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "heroClass", identifier); + if(rawId) + return rawId.value(); + else + return -1; +} + +std::string HeroClassID::encode(const si32 index) +{ + return VLC->heroClasses()->getByIndex(index)->getJsonKey(); +} + +std::string HeroClassID::entityType() +{ + return "heroClass"; +} + si32 HeroTypeID::decode(const std::string & identifier) { auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", identifier); diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 7545d5a2a..7ca9a1333 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -223,6 +223,10 @@ class HeroClassID : public Identifier { public: using Identifier::Identifier; + ///json serialization helpers + DLL_LINKAGE static si32 decode(const std::string & identifier); + DLL_LINKAGE static std::string encode(const si32 index); + static std::string entityType(); }; class HeroTypeID : public Identifier diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index b32d833f8..85e154128 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -28,6 +28,7 @@ #include "../mapping/CMap.h" #include "../modding/ModScope.h" #include "../modding/ModUtility.h" +#include "../spells/CSpellHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -37,16 +38,17 @@ std::map > CGKeys::playerKeyMap; //TODO: Remove constructor CQuest::CQuest(): qid(-1), - missionType(MISSION_NONE), - progress(NOT_ACTIVE), + isCompleted(false), lastDay(-1), - m13489val(0), + killTarget(ObjectInstanceID::NONE), textOption(0), completedOption(0), stackDirection(0), isCustomFirst(false), isCustomNext(false), - isCustomComplete(false) + isCustomComplete(false), + repeatedQuest(false), + questName(CQuest::missionName(0)) { } @@ -56,9 +58,9 @@ static std::string visitedTxt(const bool visited) return VLC->generaltexth->allTexts[id]; } -const std::string & CQuest::missionName(CQuest::Emission mission) +const std::string & CQuest::missionName(int mission) { - static const std::array names = { + static const std::array names = { "empty", "heroLevel", "primarySkill", @@ -69,7 +71,9 @@ const std::string & CQuest::missionName(CQuest::Emission mission) "bringResources", "bringHero", "bringPlayer", - "keymaster" + "keymaster", + "hota", + "other" }; if(static_cast(mission) < names.size()) @@ -99,7 +103,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army) ui32 count = 0; ui32 slotsCount = 0; bool hasExtraCreatures = false; - for(cre = q->m6creatures.begin(); cre != q->m6creatures.end(); ++cre) + for(cre = q->mission.creatures.begin(); cre != q->mission.creatures.end(); ++cre) { for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it) { @@ -121,348 +125,212 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army) bool CQuest::checkQuest(const CGHeroInstance * h) const { - switch (missionType) + if(!mission.heroAllowed(h)) + return false; + + if(killTarget != ObjectInstanceID::NONE) { - case MISSION_NONE: - return true; - case MISSION_LEVEL: - return m13489val <= h->level; - case MISSION_PRIMARY_STAT: - for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - { - if(h->getPrimSkillLevel(static_cast(i)) < static_cast(m2stats[i])) - return false; - } - return true; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if(!CGHeroInstance::cb->getObjByQuestIdentifier(m13489val)) - return true; - return false; - case MISSION_ART: - { - // if the object was deserialized - if(artifactsRequirements.empty()) - for(const auto & id : m5arts) - ++artifactsRequirements[id]; - - size_t reqSlots = 0; - for(const auto & elem : artifactsRequirements) - { - // check required amount of artifacts - if(h->getArtPosCount(elem.first, false, true, true) < elem.second) - return false; - if(!h->hasArt(elem.first)) - reqSlots += h->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2; - } - if(ArtifactUtils::isBackpackFreeSlots(h, reqSlots)) - return true; - else - return false; - } - case MISSION_ARMY: - return checkMissionArmy(this, h); - case MISSION_RESOURCES: - for(GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i) //including Mithril ? - { //Quest has no direct access to callback - if(CGHeroInstance::cb->getResource(h->tempOwner, i) < static_cast(m7resources[i])) - return false; - } - return true; - case MISSION_HERO: - return m13489val == h->type->getIndex(); - case MISSION_PLAYER: - return m13489val == h->getOwner().getNum(); - default: + if(CGHeroInstance::cb->getObjByQuestIdentifier(killTarget)) return false; } + + return true; } -void CQuest::getVisitText(MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const +void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const +{ + for(auto & elem : mission.artifacts) + { + if(h->hasArt(elem)) + { + cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); + } + else + { + const auto * assembly = h->getAssemblyByConstituent(elem); + assert(assembly); + auto parts = assembly->getPartsInfo(); + + // Remove the assembly + cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly))); + + // Disassemble this backpack artifact + for(const auto & ci : parts) + { + if(ci.art->getTypeId() != elem) + cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::BACKPACK_START); + } + } + } + + cb->takeCreatures(h->id, mission.creatures); + cb->giveResources(h->getOwner(), mission.resources); +} + +void CQuest::addTextReplacements(MetaString & text, std::vector & components) const +{ + if(mission.heroLevel > 0) + text.replaceNumber(mission.heroLevel); + + if(mission.heroExperience > 0) + text.replaceNumber(mission.heroExperience); + + { //primary skills + MetaString loot; + for(int i = 0; i < 4; ++i) + { + if(mission.primary[i]) + { + loot.appendRawString("%d %s"); + loot.replaceNumber(mission.primary[i]); + loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); + } + } + + for(auto & skill : mission.secondary) + { + loot.appendTextID(VLC->skillh->getById(skill.first)->getNameTextID()); + } + + for(auto & spell : mission.spells) + { + loot.appendTextID(VLC->spellh->getById(spell)->getNameTextID()); + } + + if(!loot.empty()) + text.replaceRawString(loot.buildList()); + } + + if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) + { + components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0); + addKillTargetReplacements(text); + } + + if(killTarget != ObjectInstanceID::NONE && stackToKill.type) + { + components.emplace_back(stackToKill); + addKillTargetReplacements(text); + } + + if(!mission.heroes.empty()) + text.replaceRawString(VLC->heroh->getById(mission.heroes.front())->getNameTranslated()); + + if(!mission.artifacts.empty()) + { + MetaString loot; + for(const auto & elem : mission.artifacts) + { + loot.appendRawString("%s"); + loot.replaceLocalString(EMetaText::ART_NAMES, elem); + } + text.replaceRawString(loot.buildList()); + } + + if(!mission.creatures.empty()) + { + MetaString loot; + for(const auto & elem : mission.creatures) + { + loot.appendRawString("%s"); + loot.replaceCreatureName(elem); + } + text.replaceRawString(loot.buildList()); + } + + if(mission.resources.nonZero()) + { + MetaString loot; + for(int i = 0; i < 7; ++i) + { + if(mission.resources[i]) + { + loot.appendRawString("%d %s"); + loot.replaceNumber(mission.resources[i]); + loot.replaceLocalString(EMetaText::RES_NAMES, i); + } + } + text.replaceRawString(loot.buildList()); + } + + if(!mission.players.empty()) + { + MetaString loot; + for(auto & p : mission.players) + loot.appendLocalString(EMetaText::COLOR, p); + + text.replaceRawString(loot.buildList()); + } + + if(lastDay >= 0) + text.replaceNumber(lastDay - IObjectInterface::cb->getDate(Date::DAY)); +} + +void CQuest::getVisitText(MetaString &iwText, std::vector &components, bool firstVisit, const CGHeroInstance * h) const { - MetaString text; bool failRequirements = (h ? !checkQuest(h) : true); + mission.loadComponents(components, h); if(firstVisit) - { - isCustom = isCustomFirst; - text = firstVisitText; - iwText.appendRawString(text.toString()); - } + iwText.appendRawString(firstVisitText.toString()); else if(failRequirements) - { - isCustom = isCustomNext; - text = nextVisitText; - iwText.appendRawString(text.toString()); - } - switch (missionType) - { - case MISSION_LEVEL: - components.emplace_back(Component::EComponentType::EXPERIENCE, 0, m13489val, 0); - if(!isCustom) - iwText.replaceNumber(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for(int i = 0; i < 4; ++i) - { - if(m2stats[i]) - { - components.emplace_back(Component::EComponentType::PRIM_SKILL, i, m2stats[i], 0); - loot.appendRawString("%d %s"); - loot.replaceNumber(m2stats[i]); - loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustom) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0); - if(!isCustom) - addReplacements(iwText, text.toString()); - break; - case MISSION_HERO: - //FIXME: portrait may not match hero, if custom portrait was set in map editor - components.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0); - if(!isCustom) - iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated()); - break; - case MISSION_KILL_CREATURE: - { - components.emplace_back(stackToKill); - if(!isCustom) - { - addReplacements(iwText, text.toString()); - } - } - break; - case MISSION_ART: - { - MetaString loot; - for(const auto & elem : m5arts) - { - components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0); - loot.appendRawString("%s"); - loot.replaceLocalString(EMetaText::ART_NAMES, elem); - } - if(!isCustom) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for(const auto & elem : m6creatures) - { - components.emplace_back(elem); - loot.appendRawString("%s"); - loot.replaceCreatureName(elem); - } - if(!isCustom) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for(int i = 0; i < 7; ++i) - { - if(m7resources[i]) - { - components.emplace_back(Component::EComponentType::RESOURCE, i, m7resources[i], 0); - loot.appendRawString("%d %s"); - loot.replaceNumber(m7resources[i]); - loot.replaceLocalString(EMetaText::RES_NAMES, i); - } - } - if(!isCustom) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_PLAYER: - components.emplace_back(Component::EComponentType::FLAG, m13489val, 0, 0); - if(!isCustom) - iwText.replaceLocalString(EMetaText::COLOR, m13489val); - break; - } + iwText.appendRawString(nextVisitText.toString()); + + if(lastDay >= 0) + iwText.appendTextID(TextIdentifier("core", "seerhut", "time", textOption).get()); + + addTextReplacements(iwText, components); } void CQuest::getRolloverText(MetaString &ms, bool onHover) const { - // Quests with MISSION_NONE type don't have a text for them - assert(missionType != MISSION_NONE); - if(onHover) ms.appendRawString("\n\n"); - std::string questName = missionName(missionType); std::string questState = missionState(onHover ? 3 : 4); - ms.appendRawString(VLC->generaltexth->translate("core.seerhut.quest", questName, questState,textOption)); + ms.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, questState, textOption).get()); - switch(missionType) - { - case MISSION_LEVEL: - ms.replaceNumber(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(m2stats[i]); - loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); - } - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - ms.replaceRawString(heroName); - break; - case MISSION_KILL_CREATURE: - ms.replaceCreatureName(stackToKill); - break; - case MISSION_ART: - { - MetaString loot; - for(const auto & elem : m5arts) - { - loot.appendRawString("%s"); - loot.replaceLocalString(EMetaText::ART_NAMES, elem); - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for(const auto & elem : m6creatures) - { - loot.appendRawString("%s"); - loot.replaceCreatureName(elem); - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(m7resources[i]); - loot.replaceLocalString(EMetaText::RES_NAMES, i); - } - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_HERO: - ms.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated()); - break; - case MISSION_PLAYER: - ms.replaceRawString(VLC->generaltexth->colors[m13489val]); - break; - default: - break; - } + std::vector components; + addTextReplacements(ms, components); } void CQuest::getCompletionText(MetaString &iwText) const { iwText.appendRawString(completedText.toString()); - switch(missionType) - { - case CQuest::MISSION_LEVEL: - if (!isCustomComplete) - iwText.replaceNumber(m13489val); - break; - case CQuest::MISSION_PRIMARY_STAT: - { - MetaString loot; - assert(m2stats.size() <= 4); - for (int i = 0; i < m2stats.size(); ++i) - { - if (m2stats[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(m2stats[i]); - loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); - break; - } - case CQuest::MISSION_ART: - { - MetaString loot; - for(const auto & elem : m5arts) - { - loot.appendRawString("%s"); - loot.replaceLocalString(EMetaText::ART_NAMES, elem); - } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); - } - break; - case CQuest::MISSION_ARMY: - { - MetaString loot; - for(const auto & elem : m6creatures) - { - loot.appendRawString("%s"); - loot.replaceCreatureName(elem); - } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); - } - break; - case CQuest::MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(m7resources[i]); - loot.replaceLocalString(EMetaText::RES_NAMES, i); - } - } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if (!isCustomComplete) - addReplacements(iwText, completedText.toString()); - break; - case MISSION_HERO: - if (!isCustomComplete) - iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated()); - break; - case MISSION_PLAYER: - if (!isCustomComplete) - iwText.replaceRawString(VLC->generaltexth->colors[m13489val]); - break; - } + + std::vector components; + addTextReplacements(iwText, components); } -void CQuest::addArtifactID(const ArtifactID & id) +void CQuest::defineQuestName() { - m5arts.push_back(id); - ++artifactsRequirements[id]; + //standard quests + questName = CQuest::missionName(0); + if(mission != Rewardable::Limiter{}) questName = CQuest::missionName(12); + if(mission.heroLevel > 0) questName = CQuest::missionName(1); + for(auto & s : mission.primary) if(s) questName = CQuest::missionName(2); + if(!mission.spells.empty()) questName = CQuest::missionName(2); + if(!mission.secondary.empty()) questName = CQuest::missionName(2); + if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(3); + if(killTarget != ObjectInstanceID::NONE && stackToKill.getType()) questName = CQuest::missionName(4); + if(!mission.artifacts.empty()) questName = CQuest::missionName(5); + if(!mission.creatures.empty()) questName = CQuest::missionName(6); + if(mission.resources.nonZero()) questName = CQuest::missionName(7); + if(!mission.heroes.empty()) questName = CQuest::missionName(8); + if(!mission.players.empty()) questName = CQuest::missionName(9); + if(mission.daysPassed > 0 || !mission.heroClasses.empty()) questName = CQuest::missionName(11); +} + +void CQuest::addKillTargetReplacements(MetaString &out) const +{ + if(!heroName.empty()) + out.replaceTextID(heroName); + if(stackToKill.type) + { + out.replaceCreatureName(stackToKill); + out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); + } } void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName) @@ -472,6 +340,7 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi handler.serializeStruct("firstVisitText", firstVisitText); handler.serializeStruct("nextVisitText", nextVisitText); handler.serializeStruct("completedText", completedText); + handler.serializeBool("repeatedQuest", repeatedQuest, false); if(!handler.saving) { @@ -479,87 +348,93 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi isCustomNext = !nextVisitText.empty(); isCustomComplete = !completedText.empty(); } - - static const std::vector MISSION_TYPE_JSON = - { - "None", "Level", "PrimaryStat", "KillHero", "KillCreature", "Artifact", "Army", "Resources", "Hero", "Player" - }; - - handler.serializeEnum("missionType", missionType, Emission::MISSION_NONE, MISSION_TYPE_JSON); + handler.serializeInt("timeLimit", lastDay, -1); + handler.serializeStruct("limiter", mission); + handler.serializeInstance("killTarget", killTarget, ObjectInstanceID::NONE); - switch (missionType) + if(!handler.saving) //compatibility with legacy vmaps { - case MISSION_NONE: - break; - case MISSION_LEVEL: - handler.serializeInt("heroLevel", m13489val, -1); - break; - case MISSION_PRIMARY_STAT: + std::string missionType = "None"; + handler.serializeString("missionType", missionType); + if(missionType == "None") + return; + + if(missionType == "Level") + handler.serializeInt("heroLevel", mission.heroLevel); + + if(missionType == "PrimaryStat") { auto primarySkills = handler.enterStruct("primarySkills"); - if(!handler.saving) - m2stats.resize(GameConstants::PRIMARY_SKILLS); - for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - handler.serializeInt(NPrimarySkill::names[i], m2stats[i], 0); + handler.serializeInt(NPrimarySkill::names[i], mission.primary[i], 0); } - break; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - handler.serializeInstance("killTarget", m13489val, static_cast(-1)); - break; - case MISSION_ART: - //todo: ban artifacts - handler.serializeIdArray("artifacts", m5arts); - break; - case MISSION_ARMY: + + if(missionType == "Artifact") + handler.serializeIdArray("artifacts", mission.artifacts); + + if(missionType == "Army") { auto a = handler.enterArray("creatures"); - a.serializeStruct(m6creatures); + a.serializeStruct(mission.creatures); } - break; - case MISSION_RESOURCES: + + if(missionType == "Resources") { auto r = handler.enterStruct("resources"); for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++) { - handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], m7resources[idx], 0); + handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mission.resources[idx], 0); } } - break; - case MISSION_HERO: - handler.serializeId("hero", m13489val, 0); - break; - case MISSION_PLAYER: - handler.serializeId("player", m13489val, PlayerColor::NEUTRAL); - break; - default: - logGlobal->error("Invalid quest mission type"); - break; + + if(missionType == "Hero") + { + ui32 temp; + handler.serializeId("hero", temp, 0); + mission.heroes.emplace_back(temp); + } + + if(missionType == "Player") + { + ui32 temp; + handler.serializeId("player", temp, PlayerColor::NEUTRAL); + mission.players.emplace_back(temp); + } } } +bool IQuestObject::checkQuest(const CGHeroInstance* h) const +{ + return quest->checkQuest(h); +} + +void IQuestObject::getVisitText(MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h) const +{ + quest->getVisitText(text, components, FirstVisit, h); +} + +void IQuestObject::afterAddToMapCommon(CMap * map) const +{ + map->addNewQuestInstance(quest); +} + void CGSeerHut::setObjToKill() { - if(quest->missionType == CQuest::MISSION_KILL_CREATURE) + if(getCreatureToKill(true)) { quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? assert(quest->stackToKill.type); quest->stackToKill.count = 0; //no count in info window quest->stackDirection = checkDirection(); } - else if(quest->missionType == CQuest::MISSION_KILL_HERO) + else if(getHeroToKill(true)) { quest->heroName = getHeroToKill(false)->getNameTranslated(); quest->heroPortrait = getHeroToKill(false)->getPortraitSource(); } - - quest->getCompletionText(configuration.onSelect); - for(auto & i : configuration.info) - quest->getCompletionText(i.message); } void CGSeerHut::init(CRandomGenerator & rand) @@ -581,29 +456,35 @@ void CGSeerHut::initObj(CRandomGenerator & rand) init(rand); CRewardableObject::initObj(rand); - - quest->progress = CQuest::NOT_ACTIVE; - if(quest->missionType) + + setObjToKill(); + quest->defineQuestName(); + + if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE) + quest->isCompleted = true; + + if(quest->questName == quest->missionName(0)) { - std::string questName = quest->missionName(quest->missionType); - - if(!quest->isCustomFirst) - quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(0), quest->textOption).get()); - if(!quest->isCustomNext) - quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(1), quest->textOption).get()); - if(!quest->isCustomComplete) - quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(2), quest->textOption).get()); + quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get()); } else { - quest->progress = CQuest::COMPLETE; - quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get()); + if(!quest->isCustomFirst) + quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(0), quest->textOption).get()); + if(!quest->isCustomNext) + quest->nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(1), quest->textOption).get()); + if(!quest->isCustomComplete) + quest->completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest-> questName, quest->missionState(2), quest->textOption).get()); } + + quest->getCompletionText(configuration.onSelect); + for(auto & i : configuration.info) + quest->getCompletionText(i.message); } void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const { - quest->getRolloverText (text, onHover);//TODO: simplify? + quest->getRolloverText(text, onHover);//TODO: simplify? if(!onHover) text.replaceRawString(seerName); } @@ -611,13 +492,15 @@ void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const std::string CGSeerHut::getHoverText(PlayerColor player) const { std::string hoverName = getObjectName(); - if(ID == Obj::SEER_HUT && quest->progress != CQuest::NOT_ACTIVE) + if(ID == Obj::SEER_HUT && quest->activeForPlayers.count(player)) { hoverName = VLC->generaltexth->allTexts[347]; boost::algorithm::replace_first(hoverName, "%s", seerName); } - if(quest->progress & quest->missionType) //rollover when the quest is active + if(quest->activeForPlayers.count(player) + && (quest->mission != Rewardable::Limiter{} + || quest->killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active { MetaString ms; getRolloverText (ms, true); @@ -626,48 +509,21 @@ std::string CGSeerHut::getHoverText(PlayerColor player) const return hoverName; } -void CQuest::addReplacements(MetaString &out, const std::string &base) const -{ - switch(missionType) - { - case MISSION_KILL_CREATURE: - if(stackToKill.type) - { - out.replaceCreatureName(stackToKill); - if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster - { - out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); - } - } - break; - case MISSION_KILL_HERO: - out.replaceTextID(heroName); - break; - } -} - -bool IQuestObject::checkQuest(const CGHeroInstance* h) const -{ - return quest->checkQuest(h); -} - -void IQuestObject::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const -{ - quest->getVisitText (text,components, isCustom, FirstVisit, h); -} - -void IQuestObject::afterAddToMapCommon(CMap * map) const -{ - map->addNewQuestInstance(quest); -} - -void CGSeerHut::setPropertyDer (ui8 what, ui32 val) +void CGSeerHut::setPropertyDer(ui8 what, ui32 val) { switch(what) { - case 10: - quest->progress = static_cast(val); + case CGSeerHut::SEERHUT_VISITED: + { + quest->activeForPlayers.emplace(val); break; + } + case CGSeerHut::SEERHUT_COMPLETE: + { + quest->isCompleted = val; + quest->activeForPlayers.clear(); + break; + } } } @@ -676,7 +532,7 @@ void CGSeerHut::newTurn(CRandomGenerator & rand) const CRewardableObject::newTurn(rand); if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up { - cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); + cb->setObjProperty (id, CGSeerHut::SEERHUT_COMPLETE, true); } } @@ -684,30 +540,24 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const { InfoWindow iw; iw.player = h->getOwner(); - if(quest->progress < CQuest::COMPLETE) + if(!quest->isCompleted) { - bool firstVisit = !quest->progress; + bool firstVisit = !quest->activeForPlayers.count(h->getOwner()); bool failRequirements = !checkQuest(h); - bool isCustom = false; if(firstVisit) { - isCustom = quest->isCustomFirst; - cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS); + cb->setObjProperty(id, CGSeerHut::SEERHUT_VISITED, h->getOwner()); AddQuest aq; aq.quest = QuestInfo (quest, this, visitablePos()); aq.player = h->tempOwner; cb->sendAndApply(&aq); //TODO: merge with setObjProperty? } - else if(failRequirements) - { - isCustom = quest->isCustomNext; - } if(firstVisit || failRequirements) { - getVisitText (iw.text, iw.components, isCustom, firstVisit, h); + getVisitText (iw.text, iw.components, firstVisit, h); cb->showInfoDialog(&iw); } @@ -758,26 +608,19 @@ int CGSeerHut::checkDirection() const } } -void CGSeerHut::completeQuest() const //reward -{ - cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete -} - const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const { - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget); if(allowNull && !o) return nullptr; - assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON)); return dynamic_cast(o); } const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const { - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget); if(allowNull && !o) return nullptr; - assert(o && o->ID == Obj::MONSTER); return dynamic_cast(o); } @@ -785,7 +628,10 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) { CRewardableObject::blockingDialogAnswered(hero, answer); if(answer) - completeQuest(); + { + quest->completeQuest(cb, hero); + cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, !quest->repeatedQuest); //mission complete + } } void CGSeerHut::afterAddToMap(CMap* map) @@ -874,10 +720,23 @@ void CGQuestGuard::init(CRandomGenerator & rand) configuration.info.push_back({}); configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - configuration.info.back().reward.removeObject = true; + configuration.info.back().reward.removeObject = subID == 0 ? true : false; configuration.canRefuse = true; } +void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const +{ + if(!quest->isCompleted) + CGSeerHut::onHeroVisit(h); + else + cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, false); +} + +bool CGQuestGuard::passableFor(PlayerColor color) const +{ + return quest->isCompleted; +} + void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler) { //quest only, do not call base class @@ -938,12 +797,12 @@ void CGBorderGuard::initObj(CRandomGenerator & rand) blockVisit = true; } -void CGBorderGuard::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const +void CGBorderGuard::getVisitText(MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h) const { - text.appendLocalString(EMetaText::ADVOB_TXT,18); + text.appendLocalString(EMetaText::ADVOB_TXT, 18); } -void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const +void CGBorderGuard::getRolloverText(MetaString &text, bool onHover) const { if (!onHover) { diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 82a6abd7e..3f76d3550 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -19,47 +19,21 @@ class CGCreature; class DLL_LINKAGE CQuest final { - mutable std::unordered_map artifactsRequirements; // artifact ID -> required count - public: - enum Emission { - MISSION_NONE = 0, - MISSION_LEVEL = 1, - MISSION_PRIMARY_STAT = 2, - MISSION_KILL_HERO = 3, - MISSION_KILL_CREATURE = 4, - MISSION_ART = 5, - MISSION_ARMY = 6, - MISSION_RESOURCES = 7, - MISSION_HERO = 8, - MISSION_PLAYER = 9, - MISSION_HOTA_MULTI = 10, - // end of H3 missions - MISSION_KEYMASTER = 100, - MISSION_HOTA_HERO_CLASS = 101, - MISSION_HOTA_REACH_DATE = 102 - }; - enum Eprogress { - NOT_ACTIVE, - IN_PROGRESS, - COMPLETE - }; - - static const std::string & missionName(Emission mission); - static const std::string & missionState(int index); + static const std::string & missionName(int index); + static const std::string & missionState(int index); + + std::string questName; si32 qid; //unique quest id for serialization / identification - Emission missionType; - Eprogress progress; si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit - - ui32 m13489val; - std::vector m2stats; - std::vector m5arts; // artifact IDs. Add IDs through addArtifactID(), not directly to the field. - std::vector m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant - TResources m7resources; + ObjectInstanceID killTarget; + Rewardable::Limiter mission; + bool repeatedQuest; + bool isCompleted; + std::set activeForPlayers; // following fields are used only for kill creature/hero missions, the original // objects became inaccessible after their removal, so we need to store info @@ -79,13 +53,14 @@ public: CQuest(); //TODO: Remove constructor static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army); - virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual bool checkQuest(const CGHeroInstance * h) const; //determines whether the quest is complete or not + virtual void getVisitText(MetaString &text, std::vector & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const; virtual void getCompletionText(MetaString &text) const; virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry - virtual void completeQuest (const CGHeroInstance * h) const {}; - virtual void addReplacements(MetaString &out, const std::string &base) const; - void addArtifactID(const ArtifactID & id); + virtual void completeQuest(IGameCallback *, const CGHeroInstance * h) const; + virtual void addTextReplacements(MetaString &out, std::vector & components) const; + virtual void addKillTargetReplacements(MetaString &out) const; + void defineQuestName(); bool operator== (const CQuest & quest) const { @@ -95,14 +70,9 @@ public: template void serialize(Handler &h, const int version) { h & qid; - h & missionType; - h & progress; + h & isCompleted; + h & activeForPlayers; h & lastDay; - h & m13489val; - h & m2stats; - h & m5arts; - h & m6creatures; - h & m7resources; h & textOption; h & stackToKill; h & stackDirection; @@ -115,6 +85,9 @@ public: h & isCustomNext; h & isCustomComplete; h & completedOption; + h & questName; + h & mission; + h & killTarget; } void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName); @@ -128,7 +101,7 @@ public: ///Information about quest should remain accessible even if IQuestObject removed from map ///All CQuest objects are freed in CMap destructor virtual ~IQuestObject() = default; - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual void getVisitText (MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const; virtual bool checkQuest (const CGHeroInstance * h) const; template void serialize(Handler &h, const int version) @@ -156,8 +129,6 @@ public: const CGHeroInstance *getHeroToKill(bool allowNull = false) const; const CGCreature *getCreatureToKill(bool allowNull = false) const; void getRolloverText (MetaString &text, bool onHover) const; - void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects - virtual void completeQuest() const; void afterAddToMap(CMap * map) override; @@ -168,7 +139,8 @@ public: h & seerName; } protected: - static constexpr int OBJPROP_VISITED = 10; + static constexpr int SEERHUT_VISITED = 10; + static constexpr int SEERHUT_COMPLETE = 11; void setPropertyDer(ui8 what, ui32 val) override; @@ -179,6 +151,9 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut { public: void init(CRandomGenerator & rand) override; + + void onHeroVisit(const CGHeroInstance * h) const override; + bool passableFor(PlayerColor color) const override; template void serialize(Handler &h, const int version) { @@ -228,7 +203,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const override; + void getVisitText (MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override; void getRolloverText (MetaString &text, bool onHover) const; bool checkQuest (const CGHeroInstance * h) const override; diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index c86a923b8..2ca1ee8e0 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -144,6 +144,9 @@ ui8 CMapHeader::levels() const void CMapHeader::registerMapStrings() { + VLC->generaltexth->removeSubContainer(*this); + VLC->generaltexth->addSubContainer(*this); + //get supported languages. Assuming that translation containing most strings is the base language std::set mapLanguages, mapBaseLanguages; int maxStrings = 0; diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index f037ef7d4..8fecd28e9 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -105,7 +105,10 @@ std::string CMapInfo::getNameTranslated() const if(campaign && !campaign->getNameTranslated().empty()) return campaign->getNameTranslated(); else if(mapHeader && !mapHeader->name.empty()) + { + mapHeader->registerMapStrings(); return mapHeader->name.toString(); + } else return VLC->generaltexth->allTexts[508]; } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 70646288f..b5c2ded6e 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1830,6 +1830,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position, const Objec if(features.levelHOTA3) { uint32_t repeateableQuestsCount = reader->readUInt32(); + hut->quest->repeatedQuest = repeateableQuestsCount != 0; if(repeateableQuestsCount != 0) logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount); @@ -1858,11 +1859,30 @@ enum class ESeerHutRewardType : uint8_t CREATURE = 10, }; +enum class EQuestMission { + NONE = 0, + LEVEL = 1, + PRIMARY_SKILL = 2, + KILL_HERO = 3, + KILL_CREATURE = 4, + ARTIFACT = 5, + ARMY = 6, + RESOURCES = 7, + HERO = 8, + PLAYER = 9, + HOTA_MULTI = 10, + // end of H3 missions + KEYMASTER = 100, + HOTA_HERO_CLASS = 101, + HOTA_REACH_DATE = 102 +}; + void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven) { + EQuestMission missionType = EQuestMission::NONE; if(features.levelAB) { - readQuest(hut, position); + missionType = static_cast(readQuest(hut, position)); } else { @@ -1871,12 +1891,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con if(artID != ArtifactID::NONE) { //not none quest - hut->quest->addArtifactID(artID); - hut->quest->missionType = CQuest::MISSION_ART; - } - else - { - hut->quest->missionType = CQuest::MISSION_NONE; + hut->quest->mission.artifacts.push_back(artID); + missionType = EQuestMission::ARTIFACT; } hut->quest->lastDay = -1; //no timeout hut->quest->isCustomFirst = false; @@ -1884,7 +1900,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con hut->quest->isCustomComplete = false; } - if(hut->quest->missionType) + if(missionType != EQuestMission::NONE) { auto rewardType = static_cast(reader->readUInt8()); Rewardable::VisitInfo vinfo; @@ -1976,91 +1992,91 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con } } -void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) +int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) { - guard->quest->missionType = static_cast(reader->readUInt8()); + auto missionId = reader->readUInt8(); - switch(guard->quest->missionType) + switch(static_cast(missionId)) { - case CQuest::MISSION_NONE: - return; - case CQuest::MISSION_PRIMARY_STAT: + case EQuestMission::NONE: + return missionId; + case EQuestMission::PRIMARY_SKILL: { - guard->quest->m2stats.resize(4); for(int x = 0; x < 4; ++x) { - guard->quest->m2stats[x] = reader->readUInt8(); + guard->quest->mission.primary[x] = reader->readUInt8(); } - } - break; - case CQuest::MISSION_LEVEL: - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: - { - guard->quest->m13489val = reader->readUInt32(); break; } - case CQuest::MISSION_ART: + case EQuestMission::LEVEL: + { + guard->quest->mission.heroLevel = reader->readUInt32(); + break; + } + case EQuestMission::KILL_HERO: + case EQuestMission::KILL_CREATURE: + { + guard->quest->killTarget = ObjectInstanceID(reader->readUInt32()); + break; + } + case EQuestMission::ARTIFACT: { int artNumber = reader->readUInt8(); for(int yy = 0; yy < artNumber; ++yy) { auto artid = reader->readArtifact(); - guard->quest->addArtifactID(artid); + guard->quest->mission.artifacts.push_back(artid); map->allowedArtifact[artid] = false; //these are unavailable for random generation } break; } - case CQuest::MISSION_ARMY: + case EQuestMission::ARMY: { int typeNumber = reader->readUInt8(); - guard->quest->m6creatures.resize(typeNumber); + guard->quest->mission.creatures.resize(typeNumber); for(int hh = 0; hh < typeNumber; ++hh) { - guard->quest->m6creatures[hh].type = VLC->creh->objects[reader->readCreature()]; - guard->quest->m6creatures[hh].count = reader->readUInt16(); + guard->quest->mission.creatures[hh].type = VLC->creh->objects[reader->readCreature()]; + guard->quest->mission.creatures[hh].count = reader->readUInt16(); } break; } - case CQuest::MISSION_RESOURCES: + case EQuestMission::RESOURCES: { for(int x = 0; x < 7; ++x) - guard->quest->m7resources[x] = reader->readUInt32(); + guard->quest->mission.resources[x] = reader->readUInt32(); break; } - case CQuest::MISSION_HERO: + case EQuestMission::HERO: { - guard->quest->m13489val = reader->readHero().getNum(); + guard->quest->mission.heroes.push_back(reader->readHero()); break; } - case CQuest::MISSION_PLAYER: + case EQuestMission::PLAYER: { - guard->quest->m13489val = reader->readPlayer().getNum(); + guard->quest->mission.players.push_back(reader->readPlayer()); break; } - case CQuest::MISSION_HOTA_MULTI: + case EQuestMission::HOTA_MULTI: { uint32_t missionSubID = reader->readUInt32(); if(missionSubID == 0) { - guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_HERO_CLASS; + missionId = int(EQuestMission::HOTA_HERO_CLASS); std::set heroClasses; reader->readBitmaskHeroClassesSized(heroClasses, false); - - logGlobal->warn("Map '%s': Quest at %s 'Belong to one of %d classes' is not implemented!", mapName, position.toString(), heroClasses.size()); + for(auto & hc : heroClasses) + guard->quest->mission.heroClasses.push_back(hc); break; } if(missionSubID == 1) { - guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_REACH_DATE; - uint32_t daysPassed = reader->readUInt32(); - - logGlobal->warn("Map '%s': Quest at %s 'Wait till %d days passed' is not implemented!", mapName, position.toString(), daysPassed); + missionId = int(EQuestMission::HOTA_REACH_DATE); + guard->quest->mission.daysPassed = reader->readUInt32() + 1; break; } - assert(0); break; } default: @@ -2076,6 +2092,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty(); guard->quest->isCustomNext = !guard->quest->nextVisitText.empty(); guard->quest->isCustomComplete = !guard->quest->completedText.empty(); + return missionId; } CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr objectTemplate) diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 6a0180b54..3eb7a3226 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -204,7 +204,7 @@ private: * * @param guard the quest guard where that quest should be applied to */ - void readQuest(IQuestObject * guard, const int3 & position); + int readQuest(IQuestObject * guard, const int3 & position); void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven); diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index c82fca352..b9a8f1b2b 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -122,6 +122,10 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng); limiter.spells = JsonRandom::loadSpells(source["spells"], rng, spells); limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng); + + limiter.players = JsonRandom::loadColors(source["colors"], rng); + limiter.heroes = JsonRandom::loadHeroes(source["heroes"], rng); + limiter.heroClasses = JsonRandom::loadHeroClasses(source["heroClasses"], rng); limiter.allOf = configureSublimiters(object, rng, source["allOf"] ); limiter.anyOf = configureSublimiters(object, rng, source["anyOf"] ); diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index bf3fb2911..b23c50610 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -16,7 +16,9 @@ #include "../mapObjects/CGHeroInstance.h" #include "../serializer/JsonSerializeFormat.h" #include "../constants/StringConstants.h" +#include "../CHeroHandler.h" #include "../CSkillHandler.h" +#include "../ArtifactUtils.h" VCMI_LIB_NAMESPACE_BEGIN @@ -24,7 +26,7 @@ Rewardable::Limiter::Limiter() : dayOfWeek(0) , daysPassed(0) , heroExperience(0) - , heroLevel(0) + , heroLevel(-1) , manaPercentage(0) , manaPoints(0) , primary(GameConstants::PRIMARY_SKILLS, 0) @@ -33,6 +35,33 @@ Rewardable::Limiter::Limiter() Rewardable::Limiter::~Limiter() = default; +bool operator==(const Rewardable::Limiter & l, const Rewardable::Limiter & r) +{ + return l.dayOfWeek == r.dayOfWeek + && l.daysPassed == r.daysPassed + && l.heroLevel == r.heroLevel + && l.heroExperience == r.heroExperience + && l.manaPoints == r.manaPoints + && l.manaPercentage == r.manaPercentage + && l.secondary == r.secondary + && l.creatures == r.creatures + && l.spells == r.spells + && l.artifacts == r.artifacts + && l.players == r.players + && l.heroes == r.heroes + && l.heroClasses == r.heroClasses + && l.resources == r.resources + && l.primary == r.primary + && l.noneOf == r.noneOf + && l.allOf == r.allOf + && l.anyOf == r.anyOf; +} + +bool operator!=(const Rewardable::Limiter & l, const Rewardable::Limiter & r) +{ + return !(l == r); +} + bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const { if(dayOfWeek != 0) @@ -93,12 +122,34 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const return false; } - for(const auto & art : artifacts) { - if (!hero->hasArt(art)) + std::unordered_map artifactsRequirements; // artifact ID -> required count + for(const auto & art : artifacts) + ++artifactsRequirements[art]; + + size_t reqSlots = 0; + for(const auto & elem : artifactsRequirements) + { + // check required amount of artifacts + if(hero->getArtPosCount(elem.first, false, true, true) < elem.second) + return false; + if(!hero->hasArt(elem.first)) + reqSlots += hero->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2; + } + if(!ArtifactUtils::isBackpackFreeSlots(hero, reqSlots)) return false; } - + + if(!players.empty() && !vstd::contains(players, hero->getOwner())) + return false; + + if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId())) + return false; + + if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId())) + return false; + + for(const auto & sublimiter : noneOf) { if (sublimiter->heroAllowed(hero)) @@ -122,6 +173,56 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const return false; } +void Rewardable::Limiter::loadComponents(std::vector & comps, + const CGHeroInstance * h) const +{ + if (heroExperience) + comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast(h->calculateXp(heroExperience)), 0); + + if (heroLevel > 0) + comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0); + + if (manaPoints || manaPercentage > 0) + { + int absoluteMana = h->manaLimit() ? (manaPercentage * h->mana / h->manaLimit() / 100) : 0; + comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, absoluteMana + manaPoints, 0); + } + + for (size_t i=0; i(i), primary[i], 0); + } + + for(const auto & entry : secondary) + comps.emplace_back(Component::EComponentType::SEC_SKILL, entry.first, entry.second, 0); + + for(const auto & entry : artifacts) + comps.emplace_back(Component::EComponentType::ARTIFACT, entry, 1, 0); + + for(const auto & entry : spells) + comps.emplace_back(Component::EComponentType::SPELL, entry, 1, 0); + + for(const auto & entry : creatures) + comps.emplace_back(Component::EComponentType::CREATURE, entry.type->getId(), entry.count, 0); + + for(const auto & entry : players) + comps.emplace_back(Component::EComponentType::FLAG, entry, 0, 0); + + //FIXME: portrait may not match hero, if custom portrait was set in map editor + for(const auto & entry : heroes) + comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroTypes()->getById(entry)->getIconIndex(), 0, 0); + + for(const auto & entry : heroClasses) + comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroClasses()->getById(entry)->getIconIndex(), 0, 0); + + for (size_t i=0; i(i), resources[i], 0); + } +} + void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler) { handler.serializeInt("dayOfWeek", dayOfWeek); @@ -130,6 +231,9 @@ void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("manaPercentage", manaPercentage); handler.serializeInt("heroExperience", heroExperience); handler.serializeInt("heroLevel", heroLevel); + handler.serializeIdArray("heroes", heroes); + handler.serializeIdArray("heroClasses", heroClasses); + handler.serializeIdArray("colors", players); handler.serializeInt("manaPoints", manaPoints); handler.serializeIdArray("artifacts", artifacts); handler.enterArray("creatures").serializeStruct(creatures); diff --git a/lib/rewardable/Limiter.h b/lib/rewardable/Limiter.h index 68639bb68..6fa52925e 100644 --- a/lib/rewardable/Limiter.h +++ b/lib/rewardable/Limiter.h @@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class CStackBasicDescriptor; +struct Component; namespace Rewardable { @@ -25,8 +26,7 @@ using LimitersList = std::vector>; /// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements /// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures) -/// NOTE: in future should (partially) replace seer hut/quest guard quests checks -struct DLL_LINKAGE Limiter +struct DLL_LINKAGE Limiter final { /// day of week, unused if 0, 1-7 will test for current day of week si32 dayOfWeek; @@ -52,7 +52,7 @@ struct DLL_LINKAGE Limiter std::map secondary; /// artifacts that hero needs to have (equipped or in backpack) to trigger this - /// Note: does not checks for multiple copies of the same arts + /// checks for artifacts copies if same artifact id is included multiple times std::vector artifacts; /// Spells that hero must have in the spellbook @@ -60,6 +60,13 @@ struct DLL_LINKAGE Limiter /// creatures that hero needs to have std::vector creatures; + + /// only heroes/hero classes from list could pass limiter + std::vector heroes; + std::vector heroClasses; + + /// only player colors can pass limiter + std::vector players; /// sub-limiters, all must pass for this limiter to pass LimitersList allOf; @@ -74,6 +81,10 @@ struct DLL_LINKAGE Limiter ~Limiter(); bool heroAllowed(const CGHeroInstance * hero) const; + + /// Generates list of components that describes reward for a specific hero + void loadComponents(std::vector & comps, + const CGHeroInstance * h) const; template void serialize(Handler &h, const int version) { @@ -88,6 +99,9 @@ struct DLL_LINKAGE Limiter h & secondary; h & artifacts; h & creatures; + h & heroes; + h & heroClasses; + h & players; h & allOf; h & anyOf; h & noneOf; @@ -98,4 +112,7 @@ struct DLL_LINKAGE Limiter } +bool DLL_LINKAGE operator== (const Rewardable::Limiter & l, const Rewardable::Limiter & r); +bool DLL_LINKAGE operator!= (const Rewardable::Limiter & l, const Rewardable::Limiter & r); + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Reward.h b/lib/rewardable/Reward.h index 3167a3546..9493083f1 100644 --- a/lib/rewardable/Reward.h +++ b/lib/rewardable/Reward.h @@ -29,7 +29,7 @@ using RewardsList = std::vector>; /// Reward that can be granted to a hero /// NOTE: eventually should replace seer hut rewards and events/pandoras -struct DLL_LINKAGE Reward +struct DLL_LINKAGE Reward final { /// resources that will be given to player TResources resources; @@ -78,7 +78,7 @@ struct DLL_LINKAGE Reward bool removeObject; /// Generates list of components that describes reward for a specific hero - virtual void loadComponents(std::vector & comps, + void loadComponents(std::vector & comps, const CGHeroInstance * h) const; Component getDisplayedComponent(const CGHeroInstance * h) const; diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index eff834612..20dc751b2 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -460,13 +460,9 @@ void TreasurePlacer::addAllPossibleObjects() reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount); reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->configuration.info.push_back(reward); - - obj->quest->missionType = CQuest::MISSION_ART; - + ArtifactID artid = qap->drawRandomArtifact(); - obj->quest->addArtifactID(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; + obj->quest->mission.artifacts.push_back(artid); generator.banQuestArt(artid); zone.getModificator()->addQuestArtifact(artid); @@ -513,11 +509,8 @@ void TreasurePlacer::addAllPossibleObjects() reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->configuration.info.push_back(reward); - obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); - obj->quest->addArtifactID(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; + obj->quest->mission.artifacts.push_back(artid); generator.banQuestArt(artid); zone.getModificator()->addQuestArtifact(artid); @@ -538,11 +531,8 @@ void TreasurePlacer::addAllPossibleObjects() reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->configuration.info.push_back(reward); - obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); - obj->quest->addArtifactID(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; + obj->quest->mission.artifacts.push_back(artid); generator.banQuestArt(artid); zone.getModificator()->addQuestArtifact(artid); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index d6fa5183b..613aa2c94 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -29,20 +29,6 @@ #include "PickObjectDelegate.h" #include "../mapcontroller.h" -static QList> MissionIdentifiers -{ - {QObject::tr("None"), QVariant::fromValue(int(CQuest::Emission::MISSION_NONE))}, - {QObject::tr("Reach level"), QVariant::fromValue(int(CQuest::Emission::MISSION_LEVEL))}, - {QObject::tr("Stats"), QVariant::fromValue(int(CQuest::Emission::MISSION_PRIMARY_STAT))}, - {QObject::tr("Kill hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_HERO))}, - {QObject::tr("Kill monster"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_CREATURE))}, - {QObject::tr("Artifact"), QVariant::fromValue(int(CQuest::Emission::MISSION_ART))}, - {QObject::tr("Army"), QVariant::fromValue(int(CQuest::Emission::MISSION_ARMY))}, - {QObject::tr("Resources"), QVariant::fromValue(int(CQuest::Emission::MISSION_RESOURCES))}, - {QObject::tr("Hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_HERO))}, - {QObject::tr("Player"), QVariant::fromValue(int(CQuest::Emission::MISSION_PLAYER))}, -}; - static QList> CharacterIdentifiers { {QObject::tr("Compliant"), QVariant::fromValue(int(CGCreature::Character::COMPLIANT))}, @@ -410,24 +396,28 @@ void Inspector::updateProperties(CGEvent * o) void Inspector::updateProperties(CGSeerHut * o) { - if(!o) return; - - { //Mission type - auto * delegate = new InspectorDelegate; - delegate->options = MissionIdentifiers; - addProperty("Mission type", o->quest->missionType, delegate, false); - } + if(!o || !o->quest) return; addProperty("First visit text", o->quest->firstVisitText, new MessageDelegate, false); addProperty("Next visit text", o->quest->nextVisitText, new MessageDelegate, false); addProperty("Completed text", o->quest->completedText, new MessageDelegate, false); + addProperty("Repeat quest", o->quest->repeatedQuest, false); + addProperty("Time limit", o->quest->lastDay, false); { //Quest - auto * delegate = new QuestDelegate(*controller.map(), *o); + auto * delegate = new QuestDelegate(controller, *o->quest); addProperty("Quest", PropertyEditorPlaceholder(), delegate, false); } } +void Inspector::updateProperties(CGQuestGuard * o) +{ + if(!o || !o->quest) return; + + addProperty("Reward", PropertyEditorPlaceholder(), nullptr, true); + addProperty("Repeat quest", o->quest->repeatedQuest, true); +} + void Inspector::updateProperties() { if(!obj) @@ -470,6 +460,7 @@ void Inspector::updateProperties() UPDATE_OBJ_PROPERTIES(CGPandoraBox); UPDATE_OBJ_PROPERTIES(CGEvent); UPDATE_OBJ_PROPERTIES(CGSeerHut); + UPDATE_OBJ_PROPERTIES(CGQuestGuard); table->show(); } @@ -516,6 +507,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value) SET_PROPERTIES(CGPandoraBox); SET_PROPERTIES(CGEvent); SET_PROPERTIES(CGSeerHut); + SET_PROPERTIES(CGQuestGuard); } void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVariant & value) @@ -538,7 +530,8 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -560,7 +553,8 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); + o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) @@ -568,7 +562,8 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -584,7 +579,8 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); if(o->storedArtifact && key == "Spell") { @@ -623,10 +619,12 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->gender = EHeroGender(value.toInt()); if(key == "Name") - o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); + o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); if(key == "Biography") - o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); + o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); if(key == "Experience") o->exp = value.toString().toInt(); @@ -662,7 +660,8 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -677,14 +676,24 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & { if(!o) return; - if(key == "Mission type") - o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); + o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); if(key == "Next visit text") - o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); + o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); if(key == "Completed text") - o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); + o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), + TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); + if(key == "Repeat quest") + o->quest->repeatedQuest = value.toBool(); + if(key == "Time limit") + o->quest->lastDay = value.toString().toInt(); +} + +void Inspector::setProperty(CGQuestGuard * o, const QString & key, const QVariant & value) +{ + if(!o) return; } @@ -797,24 +806,6 @@ QTableWidgetItem * Inspector::addProperty(CGCreature::Character value) return item; } -QTableWidgetItem * Inspector::addProperty(CQuest::Emission value) -{ - auto * item = new QTableWidgetItem; - item->setFlags(Qt::NoItemFlags); - item->setData(Qt::UserRole, QVariant::fromValue(int(value))); - - for(auto & i : MissionIdentifiers) - { - if(i.second.toInt() == value) - { - item->setText(i.first); - break; - } - } - - return item; -} - //======================================================================== Inspector::Inspector(MapController & c, CGObjectInstance * o, QTableWidget * t): obj(o), table(t), controller(c) diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 69633a3db..e4a85715f 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -82,6 +82,7 @@ protected: DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox); DECLARE_OBJ_PROPERTY_METHODS(CGEvent); DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut); + DECLARE_OBJ_PROPERTY_METHODS(CGQuestGuard); //===============DECLARE PROPERTY VALUE TYPE============================== QTableWidgetItem * addProperty(unsigned int value); @@ -96,7 +97,6 @@ protected: QTableWidgetItem * addProperty(bool value); QTableWidgetItem * addProperty(CGObjectInstance * value); QTableWidgetItem * addProperty(CGCreature::Character value); - QTableWidgetItem * addProperty(CQuest::Emission value); QTableWidgetItem * addProperty(PropertyEditorPlaceholder value); //===============END OF DECLARATION======================================= diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index a21392983..97e22f586 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -10,21 +10,124 @@ #include "StdInc.h" #include "questwidget.h" #include "ui_questwidget.h" +#include "../mapcontroller.h" #include "../lib/VCMI_Lib.h" #include "../lib/CSkillHandler.h" +#include "../lib/spells/CSpellHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/constants/StringConstants.h" #include "../lib/mapping/CMap.h" +#include "../lib/mapObjects/CGHeroInstance.h" +#include "../lib/mapObjects/CGCreature.h" -QuestWidget::QuestWidget(const CMap & _map, CGSeerHut & _sh, QWidget *parent) : +QuestWidget::QuestWidget(MapController & _controller, CQuest & _sh, QWidget *parent) : QDialog(parent), - map(_map), - seerhut(_sh), + controller(_controller), + quest(_sh), ui(new Ui::QuestWidget) { + setAttribute(Qt::WA_DeleteOnClose, true); ui->setupUi(this); + + ui->lDayOfWeek->addItem(tr("None")); + for(int i = 1; i <= 7; ++i) + ui->lDayOfWeek->addItem(tr("Day %1").arg(i)); + + //fill resources + ui->lResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1); + for(int i = 0; i < GameConstants::RESOURCE_QUANTITY - 1; ++i) + { + auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i])); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + ui->lResources->setItem(i, 0, item); + auto * spinBox = new QSpinBox; + spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999); + ui->lResources->setCellWidget(i, 1, spinBox); + } + + //fill artifacts + for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i) + { + auto * item = new QListWidgetItem(QString::fromStdString(VLC->artifacts()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + if(!controller.map()->allowedArtifact[i]) + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + ui->lArtifacts->addItem(item); + } + + //fill spells + for(int i = 0; i < controller.map()->allowedSpells.size(); ++i) + { + auto * item = new QListWidgetItem(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + if(!controller.map()->allowedSpells[i]) + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + ui->lSpells->addItem(item); + } + + //fill skills + ui->lSkills->setRowCount(controller.map()->allowedAbilities.size()); + for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i) + { + auto * item = new QTableWidgetItem(QString::fromStdString(VLC->skills()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + + auto * widget = new QComboBox; + for(auto & s : NSecondarySkill::levels) + widget->addItem(QString::fromStdString(s)); + + if(!controller.map()->allowedAbilities[i]) + { + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + widget->setEnabled(false); + } + + ui->lSkills->setItem(i, 0, item); + ui->lSkills->setCellWidget(i, 1, widget); + } + + //fill creatures + for(auto & creature : VLC->creh->objects) + { + ui->lCreatureId->addItem(QString::fromStdString(creature->getNameSingularTranslated())); + ui->lCreatureId->setItemData(ui->lCreatureId->count() - 1, creature->getIndex()); + } + + //fill heroes + VLC->heroTypes()->forEach([this](const HeroType * hero, bool &) + { + auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lHeroes->addItem(item); + }); + + //fill hero classes + VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &) + { + auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lHeroClasses->addItem(item); + }); + + //fill players + for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color) + { + auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()])); + item->setData(Qt::UserRole, QVariant::fromValue(color.getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lPlayers->addItem(item); + } } QuestWidget::~QuestWidget() @@ -34,137 +137,259 @@ QuestWidget::~QuestWidget() void QuestWidget::obtainData() { - assert(seerhut.quest); - bool activeId = false; - bool activeAmount = false; - switch(seerhut.quest->missionType) { - case CQuest::Emission::MISSION_LEVEL: - activeAmount = true; - ui->targetId->addItem("Reach level"); - ui->targetAmount->setText(QString::number(seerhut.quest->m13489val)); - break; - case CQuest::Emission::MISSION_PRIMARY_STAT: - activeId = true; - activeAmount = true; - for(auto s : NPrimarySkill::names) - ui->targetId->addItem(QString::fromStdString(s)); - for(int i = 0; i < seerhut.quest->m2stats.size(); ++i) - { - if(seerhut.quest->m2stats[i] > 0) - { - ui->targetId->setCurrentIndex(i); - ui->targetAmount->setText(QString::number(seerhut.quest->m2stats[i])); - break; //TODO: support multiple stats - } - } - break; - case CQuest::Emission::MISSION_KILL_HERO: - activeId = true; - //TODO: implement - break; - case CQuest::Emission::MISSION_KILL_CREATURE: - activeId = true; - //TODO: implement - break; - case CQuest::Emission::MISSION_ART: - activeId = true; - for(int i = 0; i < map.allowedArtifact.size(); ++i) - ui->targetId->addItem(QString::fromStdString(VLC->arth->objects.at(i)->getNameTranslated())); - if(!seerhut.quest->m5arts.empty()) - ui->targetId->setCurrentIndex(seerhut.quest->m5arts.front()); - //TODO: support multiple artifacts - break; - case CQuest::Emission::MISSION_ARMY: - activeId = true; - activeAmount = true; - break; - case CQuest::Emission::MISSION_RESOURCES: - activeId = true; - activeAmount = true; - for(auto s : GameConstants::RESOURCE_NAMES) - ui->targetId->addItem(QString::fromStdString(s)); - for(int i = 0; i < seerhut.quest->m7resources.size(); ++i) - { - if(seerhut.quest->m7resources[i] > 0) - { - ui->targetId->setCurrentIndex(i); - ui->targetAmount->setText(QString::number(seerhut.quest->m7resources[i])); - break; //TODO: support multiple resources - } - } - break; - case CQuest::Emission::MISSION_HERO: - activeId = true; - for(int i = 0; i < map.allowedHeroes.size(); ++i) - ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getNameTranslated())); - ui->targetId->setCurrentIndex(seerhut.quest->m13489val); - break; - case CQuest::Emission::MISSION_PLAYER: - activeId = true; - for(auto s : GameConstants::PLAYER_COLOR_NAMES) - ui->targetId->addItem(QString::fromStdString(s)); - ui->targetId->setCurrentIndex(seerhut.quest->m13489val); - break; - case CQuest::Emission::MISSION_KEYMASTER: - break; - default: - break; + ui->lDayOfWeek->setCurrentIndex(quest.mission.dayOfWeek); + ui->lDaysPassed->setValue(quest.mission.daysPassed); + ui->lHeroLevel->setValue(quest.mission.heroLevel); + ui->lHeroExperience->setValue(quest.mission.heroExperience); + ui->lManaPoints->setValue(quest.mission.manaPoints); + ui->lManaPercentage->setValue(quest.mission.manaPercentage); + ui->lAttack->setValue(quest.mission.primary[0]); + ui->lDefence->setValue(quest.mission.primary[1]); + ui->lPower->setValue(quest.mission.primary[2]); + ui->lKnowledge->setValue(quest.mission.primary[3]); + for(int i = 0; i < ui->lResources->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lResources->cellWidget(i, 1))) + widget->setValue(quest.mission.resources[i]); } - ui->targetId->setEnabled(activeId); - ui->targetAmount->setEnabled(activeAmount); -} - -QString QuestWidget::commitChanges() -{ - assert(seerhut.quest); - switch(seerhut.quest->missionType) { - case CQuest::Emission::MISSION_LEVEL: - seerhut.quest->m13489val = ui->targetAmount->text().toInt(); - return QString("Reach lvl ").append(ui->targetAmount->text()); - case CQuest::Emission::MISSION_PRIMARY_STAT: - seerhut.quest->m2stats.resize(sizeof(NPrimarySkill::names), 0); - seerhut.quest->m2stats[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt(); - //TODO: support multiple stats - return ui->targetId->currentText().append(ui->targetAmount->text()); - case CQuest::Emission::MISSION_KILL_HERO: - //TODO: implement - return QString("N/A"); - case CQuest::Emission::MISSION_KILL_CREATURE: - //TODO: implement - return QString("N/A"); - case CQuest::Emission::MISSION_ART: - seerhut.quest->m5arts.clear(); - seerhut.quest->m5arts.push_back(ArtifactID(ui->targetId->currentIndex())); - //TODO: support multiple artifacts - return ui->targetId->currentText(); - case CQuest::Emission::MISSION_ARMY: - //TODO: implement - return QString("N/A"); - case CQuest::Emission::MISSION_RESOURCES: - seerhut.quest->m7resources[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt(); - //TODO: support resources - return ui->targetId->currentText().append(ui->targetAmount->text()); - case CQuest::Emission::MISSION_HERO: - seerhut.quest->m13489val = ui->targetId->currentIndex(); - return ui->targetId->currentText(); - case CQuest::Emission::MISSION_PLAYER: - seerhut.quest->m13489val = ui->targetId->currentIndex(); - return ui->targetId->currentText(); - case CQuest::Emission::MISSION_KEYMASTER: - return QString("N/A"); - default: - return QString("N/A"); + for(auto i : quest.mission.artifacts) + ui->lArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto i : quest.mission.spells) + ui->lArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto & i : quest.mission.secondary) + { + int index = VLC->skills()->getById(i.first)->getIndex(); + if(auto * widget = qobject_cast(ui->lSkills->cellWidget(index, 1))) + widget->setCurrentIndex(i.second); } + for(auto & i : quest.mission.creatures) + { + int index = i.type->getIndex(); + ui->lCreatureId->setCurrentIndex(index); + ui->lCreatureAmount->setValue(i.count); + onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); + } + for(auto & i : quest.mission.heroes) + { + for(int e = 0; e < ui->lHeroes->count(); ++e) + { + if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lHeroes->item(e)->setCheckState(Qt::Checked); + break; + } + } + } + for(auto & i : quest.mission.heroClasses) + { + for(int e = 0; e < ui->lHeroClasses->count(); ++e) + { + if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lHeroClasses->item(e)->setCheckState(Qt::Checked); + break; + } + } + } + for(auto & i : quest.mission.players) + { + for(int e = 0; e < ui->lPlayers->count(); ++e) + { + if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lPlayers->item(e)->setCheckState(Qt::Checked); + break; + } + } + } + + if(quest.killTarget != ObjectInstanceID::NONE && quest.killTarget < controller.map()->objects.size()) + ui->lKillTarget->setText(QString::fromStdString(controller.map()->objects[quest.killTarget]->instanceName)); + else + quest.killTarget = ObjectInstanceID::NONE; } -QuestDelegate::QuestDelegate(const CMap & m, CGSeerHut & t): map(m), seerhut(t), QStyledItemDelegate() +bool QuestWidget::commitChanges() +{ + quest.mission.dayOfWeek = ui->lDayOfWeek->currentIndex(); + quest.mission.daysPassed = ui->lDaysPassed->value(); + quest.mission.heroLevel = ui->lHeroLevel->value(); + quest.mission.heroExperience = ui->lHeroExperience->value(); + quest.mission.manaPoints = ui->lManaPoints->value(); + quest.mission.manaPercentage = ui->lManaPercentage->value(); + quest.mission.primary[0] = ui->lAttack->value(); + quest.mission.primary[1] = ui->lDefence->value(); + quest.mission.primary[2] = ui->lPower->value(); + quest.mission.primary[3] = ui->lKnowledge->value(); + for(int i = 0; i < ui->lResources->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lResources->cellWidget(i, 1))) + quest.mission.resources[i] = widget->value(); + } + + quest.mission.artifacts.clear(); + for(int i = 0; i < ui->lArtifacts->count(); ++i) + { + if(ui->lArtifacts->item(i)->checkState() == Qt::Checked) + quest.mission.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId()); + } + quest.mission.spells.clear(); + for(int i = 0; i < ui->lSpells->count(); ++i) + { + if(ui->lSpells->item(i)->checkState() == Qt::Checked) + quest.mission.spells.push_back(VLC->spells()->getByIndex(i)->getId()); + } + + quest.mission.secondary.clear(); + for(int i = 0; i < ui->lSkills->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lSkills->cellWidget(i, 1))) + { + if(widget->currentIndex() > 0) + quest.mission.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex(); + } + } + + quest.mission.creatures.clear(); + for(int i = 0; i < ui->lCreatures->rowCount(); ++i) + { + int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt(); + if(auto * widget = qobject_cast(ui->lCreatures->cellWidget(i, 1))) + if(widget->value()) + quest.mission.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value()); + } + + quest.mission.heroes.clear(); + for(int i = 0; i < ui->lHeroes->count(); ++i) + { + if(ui->lHeroes->item(i)->checkState() == Qt::Checked) + quest.mission.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt()); + } + + quest.mission.heroClasses.clear(); + for(int i = 0; i < ui->lHeroClasses->count(); ++i) + { + if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked) + quest.mission.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt()); + } + + quest.mission.players.clear(); + for(int i = 0; i < ui->lPlayers->count(); ++i) + { + if(ui->lPlayers->item(i)->checkState() == Qt::Checked) + quest.mission.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt()); + } + + //quest.killTarget is set directly in object picking + + return true; +} + +void QuestWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget) +{ + QTableWidgetItem * item = nullptr; + QSpinBox * widget = nullptr; + for(int i = 0; i < listWidget->rowCount(); ++i) + { + if(auto * cname = listWidget->item(i, 0)) + { + if(cname->data(Qt::UserRole).toInt() == comboWidget->currentData().toInt()) + { + item = cname; + widget = qobject_cast(listWidget->cellWidget(i, 1)); + break; + } + } + } + + if(!item) + { + listWidget->setRowCount(listWidget->rowCount() + 1); + item = new QTableWidgetItem(comboWidget->currentText()); + listWidget->setItem(listWidget->rowCount() - 1, 0, item); + } + + item->setData(Qt::UserRole, comboWidget->currentData()); + + if(!widget) + { + widget = new QSpinBox; + widget->setRange(spinWidget->minimum(), spinWidget->maximum()); + listWidget->setCellWidget(listWidget->rowCount() - 1, 1, widget); + } + + widget->setValue(spinWidget->value()); +} + +void QuestWidget::on_lKillTargetSelect_clicked() +{ + auto pred = [](const CGObjectInstance * obj) -> bool + { + if(auto * o = dynamic_cast(obj)) + return o->ID != Obj::PRISON; + if(dynamic_cast(obj)) + return true; + return false; + }; + + for(int lvl : {0, 1}) + { + auto & l = controller.scene(lvl)->objectPickerView; + l.highlight(pred); + l.update(); + QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked); + } + + hide(); +} + +void QuestWidget::onTargetPicked(const CGObjectInstance * obj) +{ + show(); + + for(int lvl : {0, 1}) + { + auto & l = controller.scene(lvl)->objectPickerView; + l.clear(); + l.update(); + QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked); + } + + if(!obj) //discarded + { + quest.killTarget = ObjectInstanceID::NONE; + ui->lKillTarget->setText(""); + return; + } + + ui->lKillTarget->setText(QString::fromStdString(obj->instanceName)); + quest.killTarget = obj->id; +} + +void QuestWidget::on_lCreatureAdd_clicked() +{ + onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); +} + + +void QuestWidget::on_lCreatureRemove_clicked() +{ + std::set> rowsToRemove; + for(auto * i : ui->lCreatures->selectedItems()) + rowsToRemove.insert(i->row()); + + for(auto i : rowsToRemove) + ui->lCreatures->removeRow(i); +} + +QuestDelegate::QuestDelegate(MapController & c, CQuest & t): controller(c), quest(t), QStyledItemDelegate() { } QWidget * QuestDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { - return new QuestWidget(map, seerhut, parent); + return new QuestWidget(controller, quest, parent); } void QuestDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const @@ -183,11 +408,26 @@ void QuestDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, c { if(auto *ed = qobject_cast(editor)) { - auto quest = ed->commitChanges(); - model->setData(index, quest); + ed->commitChanges(); } else { QStyledItemDelegate::setModelData(editor, model, index); } } + +bool QuestDelegate::eventFilter(QObject * object, QEvent * event) +{ + if(auto * ed = qobject_cast(object)) + { + if(event->type() == QEvent::Hide || event->type() == QEvent::FocusOut) + return false; + if(event->type() == QEvent::Close) + { + emit commitData(ed); + emit closeEditor(ed); + return true; + } + } + return QStyledItemDelegate::eventFilter(object, event); +} diff --git a/mapeditor/inspector/questwidget.h b/mapeditor/inspector/questwidget.h index cef1ad551..3237c0b65 100644 --- a/mapeditor/inspector/questwidget.h +++ b/mapeditor/inspector/questwidget.h @@ -16,20 +16,33 @@ namespace Ui { class QuestWidget; } +class MapController; + class QuestWidget : public QDialog { Q_OBJECT public: - explicit QuestWidget(const CMap &, CGSeerHut &, QWidget *parent = nullptr); + explicit QuestWidget(MapController &, CQuest &, QWidget *parent = nullptr); ~QuestWidget(); void obtainData(); - QString commitChanges(); + bool commitChanges(); + +private slots: + void onTargetPicked(const CGObjectInstance *); + + void on_lKillTargetSelect_clicked(); + + void on_lCreatureAdd_clicked(); + + void on_lCreatureRemove_clicked(); private: - CGSeerHut & seerhut; - const CMap & map; + void onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget); + + CQuest & quest; + MapController & controller; Ui::QuestWidget *ui; }; @@ -39,13 +52,16 @@ class QuestDelegate : public QStyledItemDelegate public: using QStyledItemDelegate::QStyledItemDelegate; - QuestDelegate(const CMap &, CGSeerHut &); + QuestDelegate(MapController &, CQuest &); QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; void setEditorData(QWidget * editor, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; +protected: + bool eventFilter(QObject * object, QEvent * event) override; + private: - CGSeerHut & seerhut; - const CMap & map; + CQuest & quest; + MapController & controller; }; diff --git a/mapeditor/inspector/questwidget.ui b/mapeditor/inspector/questwidget.ui index c5f2931b8..f08b3bf94 100644 --- a/mapeditor/inspector/questwidget.ui +++ b/mapeditor/inspector/questwidget.ui @@ -9,8 +9,8 @@ 0 0 - 429 - 89 + 531 + 495 @@ -19,34 +19,616 @@ true - + - - - - 0 - 0 - + + + + + Day of week + + + + + + + + 120 + 0 + + + + + + + + Days passed + + + + + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Hero level + + + + + + + + 40 + 0 + + + + + + + + Hero experience + + + + + + + + 80 + 0 + + + + 100000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Spell points + + + + + + + + 60 + 0 + + + + 999 + + + + + + + % + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Kill hero/monster + + + + + + + true + + + + + + + ... + + + + + + + + + Primary skills + + + 12 + + + 3 + + + 3 + + + + + Attack + + + + + + + + + + Defence + + + + + + + + + + Spell power + + + + + + + + + + Knowledge + + + + + + + - - - - 0 - 0 - + + + Qt::LeftToRight - - - 60 - 16777215 - + + QTabWidget::North - - Qt::ImhDigitsOnly + + QTabWidget::Rounded + + 0 + + + Qt::ElideNone + + + true + + + false + + + true + + + + Resources + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + 2 + + + false + + + 180 + + + true + + + false + + + 24 + + + + + + + + + + Artifacts + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Spells + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Skills + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 2 + + + false + + + 180 + + + false + + + 24 + + + + + + + + + + Creatures + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + 0 + 0 + + + + + + + + + 60 + 0 + + + + 9999 + + + QAbstractSpinBox::AdaptiveDecimalStepType + + + + + + + Add + + + + + + + Remove + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + QAbstractItemView::SelectRows + + + 2 + + + false + + + 180 + + + false + + + 24 + + + + + + + + + + Heroes + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Hero classes + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Players + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index cfd900326..92f773f01 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -13,6 +13,7 @@ #include "../lib/VCMI_Lib.h" #include "../lib/CSkillHandler.h" #include "../lib/spells/CSpellHandler.h" +#include "../lib/CHeroHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/constants/StringConstants.h" @@ -55,7 +56,11 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) : auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i])); item->setData(Qt::UserRole, QVariant::fromValue(i)); w->setItem(i, 0, item); - w->setCellWidget(i, 1, new QSpinBox); + auto * spinBox = new QSpinBox; + spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999); + if(w == ui->rResources) + spinBox->setMinimum(i == GameResID::GOLD ? -999999 : -999); + w->setCellWidget(i, 1, spinBox); } } @@ -131,6 +136,36 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) : } } + //fill heroes + VLC->heroTypes()->forEach([this](const HeroType * hero, bool &) + { + auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lHeroes->addItem(item); + }); + + //fill hero classes + VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &) + { + auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lHeroClasses->addItem(item); + }); + + //fill players + for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color) + { + auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()])); + item->setData(Qt::UserRole, QVariant::fromValue(color.getNum())); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->lPlayers->addItem(item); + } + //fill spell cast for(auto & s : NSecondarySkill::levels) ui->castLevel->addItem(QString::fromStdString(s)); @@ -347,7 +382,28 @@ void RewardsWidget::saveCurrentVisitInfo(int index) int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt(); if(auto * widget = qobject_cast(ui->lCreatures->cellWidget(i, 1))) if(widget->value()) - vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value()); + vinfo.limiter.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value()); + } + + vinfo.limiter.heroes.clear(); + for(int i = 0; i < ui->lHeroes->count(); ++i) + { + if(ui->lHeroes->item(i)->checkState() == Qt::Checked) + vinfo.limiter.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt()); + } + + vinfo.limiter.heroClasses.clear(); + for(int i = 0; i < ui->lHeroClasses->count(); ++i) + { + if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked) + vinfo.limiter.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt()); + } + + vinfo.limiter.players.clear(); + for(int i = 0; i < ui->lPlayers->count(); ++i) + { + if(ui->lPlayers->item(i)->checkState() == Qt::Checked) + vinfo.limiter.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt()); } } @@ -445,10 +501,10 @@ void RewardsWidget::loadCurrentVisitInfo(int index) ui->lHeroExperience->setValue(vinfo.limiter.heroExperience); ui->lManaPoints->setValue(vinfo.limiter.manaPoints); ui->lManaPercentage->setValue(vinfo.limiter.manaPercentage); - ui->lAttack->setValue(vinfo.reward.primary[0]); - ui->lDefence->setValue(vinfo.reward.primary[1]); - ui->lPower->setValue(vinfo.reward.primary[2]); - ui->lKnowledge->setValue(vinfo.reward.primary[3]); + ui->lAttack->setValue(vinfo.limiter.primary[0]); + ui->lDefence->setValue(vinfo.limiter.primary[1]); + ui->lPower->setValue(vinfo.limiter.primary[2]); + ui->lKnowledge->setValue(vinfo.limiter.primary[3]); for(int i = 0; i < ui->lResources->rowCount(); ++i) { if(auto * widget = qobject_cast(ui->lResources->cellWidget(i, 1))) @@ -472,6 +528,40 @@ void RewardsWidget::loadCurrentVisitInfo(int index) ui->lCreatureAmount->setValue(i.count); onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); } + + for(auto & i : vinfo.limiter.heroes) + { + for(int e = 0; e < ui->lHeroes->count(); ++e) + { + if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lHeroes->item(e)->setCheckState(Qt::Checked); + break; + } + } + } + for(auto & i : vinfo.limiter.heroClasses) + { + for(int e = 0; e < ui->lHeroClasses->count(); ++e) + { + if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lHeroClasses->item(e)->setCheckState(Qt::Checked); + break; + } + } + } + for(auto & i : vinfo.limiter.players) + { + for(int e = 0; e < ui->lPlayers->count(); ++e) + { + if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum()) + { + ui->lPlayers->item(e)->setCheckState(Qt::Checked); + break; + } + } + } } void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget) diff --git a/mapeditor/inspector/rewardswidget.ui b/mapeditor/inspector/rewardswidget.ui index c3681e258..2ae3c6676 100644 --- a/mapeditor/inspector/rewardswidget.ui +++ b/mapeditor/inspector/rewardswidget.ui @@ -487,6 +487,12 @@ 0 + + Qt::ElideNone + + + true + true @@ -843,7 +849,7 @@ false - 21 + 24 @@ -1185,6 +1191,12 @@ 0 + + Qt::ElideNone + + + true + true @@ -1229,7 +1241,7 @@ false - 21 + 24 @@ -1333,7 +1345,7 @@ false - 21 + 24 @@ -1429,7 +1441,7 @@ false - 21 + 24 @@ -1437,6 +1449,102 @@ + + + Heroes + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Hero classes + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Players + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 273ea0514..88eeda6d0 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1095,8 +1095,6 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo if (isInTheMap(guardPos)) guardian = getTile(guardPos)->visitableObjects.back(); - assert(guardian == nullptr || dynamic_cast(guardian) != nullptr); - const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT; const bool disembarking = h->boat && t.terType->isLand()