diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b126d854..da480ab676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,6 +225,7 @@ Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error Bug #5352: Light source items' duration is decremented while they aren't visible Feature #1724: Handle AvoidNode + Feature #2159: "Graying out" exhausted dialogue topics Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 2ecab1c4cc..e25762f329 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -53,6 +53,8 @@ namespace MWBase virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; + virtual bool inJournal (const std::string& topicId, const std::string& infoId) = 0; + virtual void addTopic (const std::string& topic) = 0; virtual void addChoice (const std::string& text,int choice) = 0; @@ -68,7 +70,14 @@ namespace MWBase virtual void goodbyeSelected() = 0; virtual void questionAnswered (int answer, ResponseCallback* callback) = 0; + enum TopicType + { + Specific = 1, + Exhausted = 2 + }; + virtual std::list<std::string> getAvailableTopics() = 0; + virtual int getTopicFlag(const std::string&) = 0; virtual bool checkServiceRefused (ResponseCallback* callback) = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 21bc067d73..3e0cbbad2e 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -228,6 +228,30 @@ namespace MWDialogue } } + bool DialogueManager::inJournal (const std::string& topicId, const std::string& infoId) + { + const MWDialogue::Topic *topicHistory = nullptr; + MWBase::Journal *journal = MWBase::Environment::get().getJournal(); + for (auto it = journal->topicBegin(); it != journal->topicEnd(); ++it) + { + if (it->first == topicId) + { + topicHistory = &it->second; + break; + } + } + + if (!topicHistory) + return false; + + for(const auto& topic : *topicHistory) + { + if (topic.mInfoId == infoId) + return true; + } + return false; + } + void DialogueManager::executeTopic (const std::string& topic, ResponseCallback* callback) { Filter filter (mActor, mChoice, mTalkedTo); @@ -300,22 +324,34 @@ namespace MWDialogue mActorKnownTopics.clear(); - const MWWorld::Store<ESM::Dialogue> &dialogs = - MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>(); + const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>(); Filter filter (mActor, -1, mTalkedTo); - for (MWWorld::Store<ESM::Dialogue>::iterator iter = dialogs.begin(); iter != dialogs.end(); ++iter) + for (const auto& dialog : dialogs) { - if (iter->mType == ESM::Dialogue::Topic) + if (dialog.mType == ESM::Dialogue::Topic) { - if (filter.responseAvailable (*iter)) + const auto* answer = filter.search(dialog, true); + auto topicId = Misc::StringUtils::lowerCase(dialog.mId); + + if (answer != nullptr) { - mActorKnownTopics.insert (iter->mId); + int flag = 0; + if(!inJournal(topicId, answer->mId)) + { + // Does this dialogue contains some actor-specific answer? + if (answer->mActor == mActor.getCellRef().getRefId()) + flag |= MWBase::DialogueManager::TopicType::Specific; + } + else + flag |= MWBase::DialogueManager::TopicType::Exhausted; + mActorKnownTopics.insert (dialog.mId); + mActorKnownTopicsFlag[dialog.mId] = flag; } + } } - } std::list<std::string> DialogueManager::getAvailableTopics() @@ -336,6 +372,11 @@ namespace MWDialogue return keywordList; } + int DialogueManager::getTopicFlag(const std::string& topicId) + { + return mActorKnownTopicsFlag[topicId]; + } + void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) { if(!mIsInChoice) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index fad0861833..23dd68e918 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -5,6 +5,7 @@ #include <map> #include <set> +#include <unordered_map> #include <components/compiler/streamerrorhandler.hpp> #include <components/translation/translation.hpp> @@ -30,6 +31,7 @@ namespace MWDialogue ModFactionReactionMap mChangedFactionReaction; std::set<std::string, Misc::StringUtils::CiComp> mActorKnownTopics; + std::unordered_map<std::string, int> mActorKnownTopicsFlag; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; @@ -71,6 +73,9 @@ namespace MWDialogue virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback); std::list<std::string> getAvailableTopics(); + int getTopicFlag(const std::string& topicId) final; + + bool inJournal (const std::string& topicId, const std::string& infoId) final; virtual void addTopic (const std::string& topic); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 042ccb019a..a3c326ab8f 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -681,15 +681,3 @@ std::vector<const ESM::DialInfo *> MWDialogue::Filter::list (const ESM::Dialogue return infos; } - -bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const -{ - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); - iter!=dialogue.mInfo.end(); ++iter) - { - if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) - return true; - } - - return false; -} diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 4e2ebe6e5a..d2747d59ae 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -66,9 +66,6 @@ namespace MWDialogue const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. - - bool responseAvailable (const ESM::Dialogue& dialogue) const; - ///< Does a matching response exist? (disposition is ignored for this check) }; } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index bb40bea33f..1dcd8d5ea9 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -339,6 +339,7 @@ namespace MWGui mTopicsList->adjustSize(); updateHistory(); + updateTopicFormat(); mCurrentWindowSize = _sender->getSize(); } @@ -448,7 +449,6 @@ namespace MWGui setTitle(mPtr.getClass().getName(mPtr)); updateTopics(); - updateTopicsPane(); // force update for new services updateDisposition(); restock(); @@ -489,8 +489,6 @@ namespace MWGui return; mIsCompanion = isCompanion(); mKeywords = keyWords; - - updateTopicsPane(); } void DialogueWindow::updateTopicsPane() @@ -540,15 +538,16 @@ namespace MWGui mTopicsList->addSeparator(); - for(std::string& keyword : mKeywords) + for(const auto& keyword : mKeywords) { + std::string topicId = Misc::StringUtils::lowerCase(keyword); mTopicsList->addItem(keyword); Topic* t = new Topic(keyword); t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); - mTopicLinks[Misc::StringUtils::lowerCase(keyword)] = t; + mTopicLinks[topicId] = t; - mKeywordSearch.seed(Misc::StringUtils::lowerCase(keyword), intptr_t(t)); + mKeywordSearch.seed(topicId, intptr_t(t)); } mTopicsList->adjustSize(); @@ -734,9 +733,28 @@ namespace MWGui updateHistory(); } + void DialogueWindow::updateTopicFormat() + { + std::string specialColour = Settings::Manager::getString("color topic specific", "GUI"); + std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI"); + + for (const std::string& keyword : mKeywords) + { + int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(keyword); + MyGUI::Button* button = mTopicsList->getItemWidget(keyword); + + if (!specialColour.empty() && flag & MWBase::DialogueManager::TopicType::Specific) + button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(specialColour)); + else if (!oldColour.empty() && flag & MWBase::DialogueManager::TopicType::Exhausted) + button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(oldColour)); + } + } + void DialogueWindow::updateTopics() { setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()); + updateTopicsPane(); + updateTopicFormat(); } bool DialogueWindow::isCompanion() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 2c3fb1a44c..d9c26ef203 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -186,6 +186,8 @@ namespace MWGui std::unique_ptr<ResponseCallback> mCallback; std::unique_ptr<ResponseCallback> mGreetingCallback; + + void updateTopicFormat(); }; } #endif diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index c8f4e16f80..b881221b33 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -140,3 +140,29 @@ The alpha value is currently ignored. This setting can only be configured by editing the settings configuration file. This setting has no effect if the crosshair setting in the HUD Settings Section is false. This setting has no effect if the show owned setting in the Game Settings Section is false. + +color topic specific +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting overrides the color of keywords in the dialogue topic window. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +The color is overriden if the actor is about to give an answer that is unique to him (that is, dialogue with their object ID in the Actor field) that wasn't seen yet. + +color topic exhausted +--------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting overrides the color of keywords in the dialogue topic window. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +The color is overridden if the next actor responses to the topic keyword has already been seen by the player. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6703e77326..3203d6743f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -190,6 +190,14 @@ color crosshair owned = 1.0 0.15 0.15 1.0 # Controls whether Arrow keys, Movement keys, Tab/Shift-Tab and Spacebar/Enter/Activate may be used to navigate GUI buttons. keyboard navigation = true +# The color of dialogue topic keywords that gives unique actor responses +# Format R G B A or empty for default +color topic specific = + +# The color of dialogue topic keywords that gives already read responses +# Format R G B A or empty for default +color topic exhausted = + [HUD] # Displays the crosshair or reticle when not in GUI mode.