From bb238f9b72cf7e39451d0c239dd21a39aeb31b9b Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 9 Oct 2023 19:15:34 +0200 Subject: [PATCH] New quests work --- AI/Nullkiller/Goals/CompleteQuest.cpp | 33 +- .../Rules/AIMovementAfterDestinationRule.cpp | 2 +- AI/VCAI/Goals/CompleteQuest.cpp | 33 +- client/windows/CQuestLog.cpp | 16 +- lib/CGeneralTextHandler.cpp | 2 +- lib/mapObjects/CQuest.cpp | 567 ++++++------------ lib/mapObjects/CQuest.h | 40 +- lib/mapping/MapFormatH3M.cpp | 67 ++- lib/mapping/MapFormatH3M.h | 2 +- lib/rewardable/Limiter.cpp | 2 +- lib/rmg/modificators/TreasurePlacer.cpp | 12 +- mapeditor/inspector/inspector.cpp | 40 -- mapeditor/inspector/inspector.h | 1 - 13 files changed, 294 insertions(+), 523 deletions(-) diff --git a/AI/Nullkiller/Goals/CompleteQuest.cpp b/AI/Nullkiller/Goals/CompleteQuest.cpp index 00518d51b..96b9d317a 100644 --- a/AI/Nullkiller/Goals/CompleteQuest.cpp +++ b/AI/Nullkiller/Goals/CompleteQuest.cpp @@ -37,39 +37,32 @@ TGoalVec CompleteQuest::decompose() const } logAi->debug("Trying to realize quest: %s", questToString()); - - switch(q.quest->missionType) - { - case CQuest::MISSION_ART: + + if(!q.quest->artifacts.empty()) return missionArt(); - case CQuest::MISSION_HERO: + if(!q.quest->heroes.empty()) return missionHero(); - case CQuest::MISSION_ARMY: + if(!q.quest->creatures.empty()) return missionArmy(); - case CQuest::MISSION_RESOURCES: + if(q.quest->resources.nonZero()) return missionResources(); - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: + if(q.quest->killTarget >= 0) return missionDestroyObj(); - case CQuest::MISSION_PRIMARY_STAT: - return missionIncreasePrimaryStat(); + for(auto & s : q.quest->primary) + if(s) + return missionIncreasePrimaryStat(); - case CQuest::MISSION_LEVEL: + if(q.quest->heroLevel > 0) return missionLevel(); - - case CQuest::MISSION_PLAYER: - break; - - case CQuest::MISSION_KEYMASTER: + + if(q.quest->questName == CQuest::missionName(10)) return missionKeymaster(); - } //end of switch - return TGoalVec(); } @@ -104,7 +97,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; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index dafcb4758..68638efc5 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -130,7 +130,7 @@ 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->questName == CQuest::missionName(0)) { return false; } diff --git a/AI/VCAI/Goals/CompleteQuest.cpp b/AI/VCAI/Goals/CompleteQuest.cpp index fb851bf04..d915efd62 100644 --- a/AI/VCAI/Goals/CompleteQuest.cpp +++ b/AI/VCAI/Goals/CompleteQuest.cpp @@ -25,41 +25,34 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals() { TGoalVec solutions; - if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE) + if(q.quest->progress != CQuest::COMPLETE) { logAi->debug("Trying to realize quest: %s", questToString()); - switch(q.quest->missionType) - { - case CQuest::MISSION_ART: + if(!q.quest->artifacts.empty()) return missionArt(); - case CQuest::MISSION_HERO: + if(!q.quest->heroes.empty()) return missionHero(); - case CQuest::MISSION_ARMY: + if(!q.quest->creatures.empty()) return missionArmy(); - case CQuest::MISSION_RESOURCES: + if(q.quest->resources.nonZero()) return missionResources(); - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: + if(q.quest->killTarget >= 0) return missionDestroyObj(); - case CQuest::MISSION_PRIMARY_STAT: - return missionIncreasePrimaryStat(); + for(auto & s : q.quest->primary) + if(s) + return missionIncreasePrimaryStat(); - case CQuest::MISSION_LEVEL: + if(q.quest->heroLevel > 0) return missionLevel(); - - case CQuest::MISSION_PLAYER: - break; - case CQuest::MISSION_KEYMASTER: + if(q.quest->questName == CQuest::missionName(10)) return missionKeymaster(); - - } //end of switch } return TGoalVec(); @@ -67,7 +60,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals() TSubgoal CompleteQuest::whatToDoToAchieve() { - if(q.quest->missionType == CQuest::MISSION_NONE) + if(q.quest->questName == CQuest::missionName(0)) { throw cannotFulfillGoalException("Can not complete inactive quest"); } @@ -101,7 +94,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; diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index de1f9878e..208a74744 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -149,7 +149,7 @@ void CQuestLog::recreateLabelList() 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) + if (quests[i].quest->questName == CQuest::missionName(0)) continue; if (quests[i].quest->progress == CQuest::COMPLETE) @@ -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/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 09d046ef7..657fb564e 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -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/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 98f235e6b..d704c8850 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -37,7 +37,6 @@ std::map > CGKeys::playerKeyMap; //TODO: Remove constructor CQuest::CQuest(): qid(-1), - missionType(MISSION_NONE), progress(NOT_ACTIVE), lastDay(-1), killTarget(-1), @@ -47,7 +46,8 @@ CQuest::CQuest(): isCustomFirst(false), isCustomNext(false), isCustomComplete(false), - repeatedQuest(false) + repeatedQuest(false), + questName(CQuest::missionName(0)) { } @@ -57,7 +57,7 @@ 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 = { "empty", @@ -164,277 +164,137 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const cb->giveResources(h->getOwner(), resources); } -void CQuest::getVisitText(MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const +void CQuest::addTextReplacements(MetaString & text) const +{ + if(heroLevel > 0) + text.replaceNumber(heroLevel); + + { //primary skills + MetaString loot; + for(int i = 0; i < 4; ++i) + { + if(primary[i]) + { + loot.appendRawString("%d %s"); + loot.replaceNumber(primary[i]); + loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); + } + } + if(!loot.empty()) + text.replaceRawString(loot.buildList()); + } + + if(killTarget >= 0 && !heroName.empty()) + { + //components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0); + addKillTargetReplacements(text); + } + + if(killTarget >= 0 && stackToKill.type) + { + //components.emplace_back(stackToKill); + addKillTargetReplacements(text); + } + + if(!heroes.empty()) + text.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated()); + + if(!artifacts.empty()) + { + MetaString loot; + for(const auto & elem : artifacts) + { + loot.appendRawString("%s"); + loot.replaceLocalString(EMetaText::ART_NAMES, elem); + } + text.replaceRawString(loot.buildList()); + } + + if(!creatures.empty()) + { + MetaString loot; + for(const auto & elem : creatures) + { + loot.appendRawString("%s"); + loot.replaceCreatureName(elem); + } + text.replaceRawString(loot.buildList()); + } + + if(resources.nonZero()) + { + MetaString loot; + for(int i = 0; i < 7; ++i) + { + if(resources[i]) + { + loot.appendRawString("%d %s"); + loot.replaceNumber(resources[i]); + loot.replaceLocalString(EMetaText::RES_NAMES, i); + } + } + text.replaceRawString(loot.buildList()); + } + + if(!players.empty()) + text.replaceLocalString(EMetaText::COLOR, players.front()); +} + +void CQuest::getVisitText(MetaString &iwText, std::vector &components, bool firstVisit, const CGHeroInstance * h) const { - MetaString text; bool failRequirements = (h ? !checkQuest(h) : true); 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: - if(!isCustom) - iwText.replaceNumber(heroLevel); //TODO: heroLevel - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for(int i = 0; i < 4; ++i) - { - if(primary[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(primary[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: - if(!isCustom && !heroes.empty()) - iwText.replaceRawString(VLC->heroh->getById(heroes.front())->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 : artifacts) - { - 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 : creatures) - { - 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(resources[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(resources[i]); - loot.replaceLocalString(EMetaText::RES_NAMES, i); - } - } - if(!isCustom) - iwText.replaceRawString(loot.buildList()); - } - break; - case MISSION_PLAYER: - if(!isCustom && !players.empty()) - iwText.replaceLocalString(EMetaText::COLOR, players.front()); - break; - } + iwText.appendRawString(nextVisitText.toString()); + + addTextReplacements(iwText); } 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.appendRawString(VLC->generaltexth->translate("core.seerhut.quest", questName, questState, textOption)); - switch(missionType) - { - case MISSION_LEVEL: - ms.replaceNumber(heroLevel); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (primary[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(primary[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 : artifacts) - { - loot.appendRawString("%s"); - loot.replaceLocalString(EMetaText::ART_NAMES, elem); - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for(const auto & elem : creatures) - { - loot.appendRawString("%s"); - loot.replaceCreatureName(elem); - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (resources[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(resources[i]); - loot.replaceLocalString(EMetaText::RES_NAMES, i); - } - } - ms.replaceRawString(loot.buildList()); - } - break; - case MISSION_HERO: - ms.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated()); - break; - case MISSION_PLAYER: - ms.replaceRawString(VLC->generaltexth->colors[players.front()]); - break; - default: - break; - } + addTextReplacements(ms); } void CQuest::getCompletionText(MetaString &iwText) const { iwText.appendRawString(completedText.toString()); - switch(missionType) + + addTextReplacements(iwText); +} + +void CQuest::defineQuestName() +{ + //standard quests + questName = CQuest::missionName(0); + if(heroLevel > 0) questName = CQuest::missionName(1); + for(auto & s : primary) if(s) questName = CQuest::missionName(2); + if(killTarget >= 0 && !heroName.empty()) questName = CQuest::missionName(3); + if(killTarget >= 0 && stackToKill.getType()) questName = CQuest::missionName(4); + if(!artifacts.empty()) questName = CQuest::missionName(5); + if(!creatures.empty()) questName = CQuest::missionName(6); + if(resources.nonZero()) questName = CQuest::missionName(7); + if(!heroes.empty()) questName = CQuest::missionName(8); + if(!players.empty()) questName = CQuest::missionName(9); +} + +void CQuest::addKillTargetReplacements(MetaString &out) const +{ + if(!heroName.empty()) + out.replaceTextID(heroName); + if(stackToKill.type) { - case CQuest::MISSION_LEVEL: - if (!isCustomComplete) - iwText.replaceNumber(heroLevel); - break; - case CQuest::MISSION_PRIMARY_STAT: - { - MetaString loot; - assert(primary.size() <= 4); - for (int i = 0; i < primary.size(); ++i) - { - if (primary[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(primary[i]); - loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); - break; - } - case CQuest::MISSION_ART: - { - MetaString loot; - for(const auto & elem : artifacts) - { - 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 : creatures) - { - 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 (resources[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(resources[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 && !heroes.empty()) - iwText.replaceRawString(VLC->heroh->getById(heroes.front())->getNameTranslated()); - break; - case MISSION_PLAYER: - if (!isCustomComplete && !players.empty()) - iwText.replaceRawString(VLC->generaltexth->colors[players.front()]); - break; + out.replaceCreatureName(stackToKill); + out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); } } @@ -456,85 +316,87 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi Rewardable::Limiter::serializeJson(handler); - 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.serializeInstance("killTarget", killTarget, -1); - if(!handler.saving) + if(!handler.saving) //compatibility with legacy vmaps { - switch (missionType) + std::string missionType = "None"; + handler.serializeString("missionType", missionType); + if(missionType == "None") + return; + + if(missionType == "Level") + handler.serializeInt("heroLevel", heroLevel, -1); + + if(missionType == "PrimaryStat") { - case MISSION_NONE: - break; - case MISSION_LEVEL: - handler.serializeInt("heroLevel", heroLevel, -1); - break; - case MISSION_PRIMARY_STAT: - { - auto primarySkills = handler.enterStruct("primarySkills"); - for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - handler.serializeInt(NPrimarySkill::names[i], primary[i], 0); - } - break; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - break; - case MISSION_ART: - handler.serializeIdArray("artifacts", artifacts); - break; - case MISSION_ARMY: - { - auto a = handler.enterArray("creatures"); - a.serializeStruct(creatures); - } - break; - case MISSION_RESOURCES: - { - auto r = handler.enterStruct("resources"); + auto primarySkills = handler.enterStruct("primarySkills"); + for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) + handler.serializeInt(NPrimarySkill::names[i], primary[i], 0); + } + + if(missionType == "Artifact") + handler.serializeIdArray("artifacts", artifacts); + + if(missionType == "Army") + { + auto a = handler.enterArray("creatures"); + a.serializeStruct(creatures); + } + + 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], resources[idx], 0); - } - } - break; - case MISSION_HERO: + for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++) { - ui32 temp; - handler.serializeId("hero", temp, 0); - heroes.emplace_back(temp); + handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], resources[idx], 0); } - break; - case MISSION_PLAYER: - { - ui32 temp; - handler.serializeId("player", temp, PlayerColor::NEUTRAL); - players.emplace_back(temp); - } - break; - default: - logGlobal->error("Invalid quest mission type"); - break; + } + + if(missionType == "Hero") + { + ui32 temp; + handler.serializeId("hero", temp, 0); + heroes.emplace_back(temp); + } + + if(missionType == "Player") + { + ui32 temp; + handler.serializeId("player", temp, PlayerColor::NEUTRAL); + 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(); @@ -566,27 +428,28 @@ void CGSeerHut::initObj(CRandomGenerator & rand) CRewardableObject::initObj(rand); quest->progress = CQuest::NOT_ACTIVE; - if(quest->missionType) - { - 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->nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(1), quest->textOption).get()); - if(!quest->isCustomComplete) - quest->completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(2), quest->textOption).get()); - } - else + + quest->defineQuestName(); + + if(quest->questName == quest->missionName(0)) { quest->progress = CQuest::COMPLETE; quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get()); } + else + { + 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()); + } } 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); } @@ -600,7 +463,7 @@ std::string CGSeerHut::getHoverText(PlayerColor player) const boost::algorithm::replace_first(hoverName, "%s", seerName); } - if(quest->progress & quest->missionType) //rollover when the quest is active + if(quest->progress/* & quest->missionType*/) //rollover when the quest is active { MetaString ms; getRolloverText (ms, true); @@ -609,47 +472,12 @@ 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) { switch(what) { case 10: - quest->progress = static_cast(val); + quest->progress = static_cast(val); break; } } @@ -671,11 +499,9 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const { bool firstVisit = !quest->progress; bool failRequirements = !checkQuest(h); - bool isCustom = false; if(firstVisit) { - isCustom = quest->isCustomFirst; cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS); AddQuest aq; @@ -683,14 +509,10 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const 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); } @@ -765,7 +587,10 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) if(answer) { quest->completeQuest(cb, hero); - cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete + if(quest && quest->repeatedQuest) + cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::NOT_ACTIVE); + else + cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete } } @@ -919,12 +744,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 6f3976c07..b524c02fa 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -20,37 +20,21 @@ class CGCreature; class DLL_LINKAGE CQuest: public Rewardable::Limiter { 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 { + 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; + EProgress progress; si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit int killTarget; bool repeatedQuest; @@ -74,11 +58,13 @@ public: 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 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(IGameCallback *, const CGHeroInstance * h) const; - virtual void addReplacements(MetaString &out, const std::string &base) const; + virtual void addTextReplacements(MetaString &out) const; + virtual void addKillTargetReplacements(MetaString &out) const; + void defineQuestName(); bool operator== (const CQuest & quest) const { @@ -88,7 +74,6 @@ public: template void serialize(Handler &h, const int version) { h & qid; - h & missionType; h & progress; h & lastDay; h & textOption; @@ -103,6 +88,7 @@ public: h & isCustomNext; h & isCustomComplete; h & completedOption; + h & questName; h & static_cast(*this); } @@ -117,7 +103,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) @@ -215,7 +201,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/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 08b3e1545..97a2d6820 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1858,11 +1858,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 { @@ -1872,11 +1891,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con { //not none quest hut->quest->artifacts.push_back(artID); - hut->quest->missionType = CQuest::MISSION_ART; - } - else - { - hut->quest->missionType = CQuest::MISSION_NONE; + missionType = EQuestMission::ARTIFACT; } hut->quest->lastDay = -1; //no timeout hut->quest->isCustomFirst = false; @@ -1884,7 +1899,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,15 +1991,15 @@ 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: { for(int x = 0; x < 4; ++x) { @@ -1992,14 +2007,17 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } } break; - case CQuest::MISSION_LEVEL: - case CQuest::MISSION_KILL_HERO: - case CQuest::MISSION_KILL_CREATURE: + case EQuestMission::LEVEL: + { + guard->quest->heroLevel = reader->readUInt32(); + } + case EQuestMission::KILL_HERO: + case EQuestMission::KILL_CREATURE: { guard->quest->killTarget = reader->readUInt32(); break; } - case CQuest::MISSION_ART: + case EQuestMission::ARTIFACT: { int artNumber = reader->readUInt8(); for(int yy = 0; yy < artNumber; ++yy) @@ -2010,7 +2028,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } break; } - case CQuest::MISSION_ARMY: + case EQuestMission::ARMY: { int typeNumber = reader->readUInt8(); guard->quest->creatures.resize(typeNumber); @@ -2021,30 +2039,30 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } break; } - case CQuest::MISSION_RESOURCES: + case EQuestMission::RESOURCES: { for(int x = 0; x < 7; ++x) guard->quest->resources[x] = reader->readUInt32(); break; } - case CQuest::MISSION_HERO: + case EQuestMission::HERO: { guard->quest->heroes.push_back(reader->readHero()); break; } - case CQuest::MISSION_PLAYER: + case EQuestMission::PLAYER: { guard->quest->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_HOTA_HERO_CLASS; + missionId = int(EQuestMission::HOTA_HERO_CLASS); std::set heroClasses; reader->readBitmaskHeroClassesSized(heroClasses, false); for(auto & hc : heroClasses) @@ -2053,7 +2071,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } if(missionSubID == 1) { - guard->quest->missionType = CQuest::MISSION_HOTA_REACH_DATE; + missionId = int(EQuestMission::HOTA_REACH_DATE); guard->quest->daysPassed = reader->readUInt32(); break; } @@ -2072,6 +2090,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/Limiter.cpp b/lib/rewardable/Limiter.cpp index e32eeb778..6c82eeb2e 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -26,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) diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index f3d44ad8f..542c8294e 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->artifacts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; 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->artifacts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; 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->artifacts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; generator.banQuestArt(artid); zone.getModificator()->addQuestArtifact(artid); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index c19eda842..903cfaf22 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))}, @@ -411,12 +397,6 @@ void Inspector::updateProperties(CGEvent * o) void Inspector::updateProperties(CGSeerHut * o) { if(!o || !o->quest) return; - - { //Mission type - auto * delegate = new InspectorDelegate; - delegate->options = MissionIdentifiers; - addProperty("Mission type", o->quest->missionType, delegate, false); - } addProperty("First visit text", o->quest->firstVisitText, new MessageDelegate, false); addProperty("Next visit text", o->quest->nextVisitText, new MessageDelegate, false); @@ -678,8 +658,6 @@ 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())); if(key == "Next visit text") @@ -798,24 +776,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..5e2e1f96a 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -96,7 +96,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=======================================