Grey out read dialogue topics

Everything about development and the OpenMW source code.
User avatar
wareya
Posts: 338
Joined: 09 May 2015, 13:07

Re: Grey out read dialogue topics

Post by wareya »

At worst, the system doesn't have to leave mods in the dark. OpenMW could expand the dialogue system so that dialogue mods running on OpenMW could control greying-out themselves.
Golken
Posts: 27
Joined: 04 May 2015, 05:03

Re: Grey out read dialogue topics

Post by Golken »

Chris wrote:Like I said in the other thread, I'm not sure how much of a problem this will even be (for a single dialog response to give different script/function results on different invocations). I think it would be silly to make such responses never gray out just because they contain a script or global var, when the odds of a repeat response having a different effect is (next to) nil.
It's not even necessarily a matter of different effects, but of having a repeatable effect at all. Since those by definition affect* things (opposed to ordinary dialog only outputting the same text again), and the player would likely want to repeat them at some point, it wouldn't make much sense to gray out such topics the same way as regular topics which simply give information, which the player would be less likely to want to repeat if he has already read it. Easy examples could be the player purchasing or exchanging things via a dialog topic. I agree with everything zini has said.
*: The proper verb is probably "(to) effect" here, but that's less readable, haha.

Consider bed renting in taverns, which is done through dialog (with script functions in the results box). While graying out things excessively is definitely no critical loss, Oblivion for example "does it wrong". After the player selects the beds topic once, it will be grayed out forever; I think it even does the same to the choice topic/line that follows (i.e., "Yes I'll take the bed" or "No thank you" will also become grayed out...). That only serves the purpose of letting the player know he has clicked that topic before in his current game, not whether or not doing it again would actually be useful or something he would probably want to do.

And that "next to nil" sentence is wrong since we are dealing with vars and scripts here, which are neither seldom-used in dialog nor are they constrained in scope or number of possible effects. And we can't and shouldn't count on mods being limited in how they function. On the contrary, we want to support more possibilities rather than stagnation.
Curiously/oddly, I've also read that if statements can be used in dialog results in some modding guide, but I have not verified or tried this.

@wareya: Sure, that could be a good idea, as an extra thing. "At worst", as you've said.
Chris
Posts: 1625
Joined: 04 Sep 2011, 08:33

Re: Grey out read dialogue topics

Post by Chris »

Golken wrote:Consider bed renting in taverns, which is done through dialog (with script functions in the results box). While graying out things excessively is definitely no critical loss, Oblivion for example "does it wrong". After the player selects the beds topic once, it will be grayed out forever; I think it even does the same to the choice topic/line that follows (i.e., "Yes I'll take the bed" or "No thank you" will also become grayed out...). That only serves the purpose of letting the player know he has clicked that topic before in his current game, not whether or not doing it again would actually be useful or something he would probably want to do.
But that is the purpose, to let you know that you've got the dialog response before. Clicking a topic will always do something, whether it's just displaying dialog text or more (such as marking a bed as yours, or playing a voice clip), so not graying out a repeated response just because it has a scripted function will make it look inconsistent. Yeah, the Bed topic will be forever grayed out the first time you use it, and that's exactly what I expect; if it wasn't grayed out, I would expect picking it to respond with something new, not do the same thing as when I last selected it.
And that "next to nil" sentence is wrong since we are dealing with vars and scripts here, which are neither seldom-used in dialog nor are they constrained in scope or number of possible effects. And we can't and shouldn't count on mods being limited in how they function. On the contrary, we want to support more possibilities rather than stagnation.
The 'next to nil' statement was not about dialog scripts that merely use variables, but dialog scripts that specifically produce different results on multiple executions. I'm in no way suggesting to limit what mods can do, but we should also consider the best way to allow something instead of going with whatever hacky approach that can interfere with other parts of the engine. The idea of differing script results in a single dialog entry is one such thing that needs extra consideration, since it can interfere with being able to show the player if they've gotten a particular response before, and thus we should look at what exactly modders need that behavior for and make something that more properly addresses their needs (we have the engine code available, after all).
User avatar
Okulo
Posts: 672
Joined: 05 Feb 2012, 16:11

Re: Grey out read dialogue topics

Post by Okulo »

Golken wrote:Since those by definition affect* things
*: The proper verb is probably "(to) effect" here, but that's less readable, haha.
Oooh, my turn!

Effect is a noun, which means an influence. Effect is also a verb, which means to cause. Affect is only a verb that means to have an influence on something.

Eg. "My bonking him over the head effects his suffering." "My bonking him over the head affects his health." and "My bonking him over the head had a detrimental effect to his well-being"
User avatar
sjek
Posts: 442
Joined: 22 Nov 2014, 10:51

Re: Grey out read dialogue topics

Post by sjek »

The idea of differing script results in a single dialog entry is one such thing that needs extra consideration, since it can interfere with being able to show the player if they've gotten a particular response before, and thus we should look at what exactly modders need that behavior for and make something that more properly addresses their needs (we have the engine code available, after all).
it would be good to implement the graying out scriptless topics or all to avoid gameplay spoilers (choiceable) first and make some

if (hasscripts::if==1)
if (hasjournalentry==1)

statements available as getting it compatible even with base game perfectly will require multiple iterations
Golken
Posts: 27
Joined: 04 May 2015, 05:03

Re: Grey out read dialogue topics

Post by Golken »

Chris wrote:But that is the purpose, to let you know that you've got the dialog response before.
The purpose for this, as I take it, is to signal to the player that the topic (more specifically dialog response) is "no longer relevant" or "stale" (for lack of a better word), or there is "no use for it", and so won't add to his experience anymore (he should click it only if he specifically wants to see the same text again, i.e. he forgot something). But dialog that causes effects isn't like that, it still has uses and effects game experience. It's probably a matter of taste. Like other things it could be user configurable and it wouldn't be trouble for it to be.
Chris
Posts: 1625
Joined: 04 Sep 2011, 08:33

Re: Grey out read dialogue topics

Post by Chris »

Golken wrote:The purpose for this, as I take it, is to signal to the player that the topic (more specifically dialog response) is "no longer relevant" or "stale" (for lack of a better word), or there is "no use for it", and so won't add to his experience anymore (he should click it only if he specifically wants to see the same text again, i.e. he forgot something).
I think that is an entirely subjective metric. Sometimes people need to go through old responses because they forgot something, or didn't notice something until they later realized they needed to know it, so such a response would be relevant or have use for some people some of the time. In a number of cases, 'no use for it' could even apply to responses the player never saw before (the majority of Background topics, for instance), but again, it depends on the player. This creates an inconsistent experience, with a grayed out topic merely indicating that someone else thinks it's unimportant at this time, not whether you would be interested in it or not.

A more objective approach, however, if far more doable. It leads to more consistent behavior, conveying the same information all players (i.e. you have seen this before; it doesn't say whether it's important or not, since that's different for different players), and it can be applied automatically without having to go through each individual response... after all, even if a dialog response has a script, that doesn't mean it does anything important (I've seen mods use dialog scripts to simply play a voice clip, for example).
Golken
Posts: 27
Joined: 04 May 2015, 05:03

Re: Grey out read dialogue topics

Post by Golken »

Chris wrote:I think that is an entirely subjective metric.
Not quite. What's subjective is the approach you think is best, i.e. graying out all clicked topics or only some, based upon the consistent criteria.
Chris wrote:Sometimes people need to go through old responses because they forgot something, or didn't notice something until they later realized they needed to know it [...] , so such a response would be relevant or have use for some people some of the time
Yeah, I already included that in the post; it doesn't change anything. This still falls under an already used topic with normally no further "effects on experience". Obviously that description doesn't mean the topic is completely useless, and doesn't necessarily apply outside of normal conditions (i.e. you load your save after a few days but completely forgot the contents of some lore topic). That would be why the topic is merely discolored and not completely removed/hidden, because there could still be a reason for the player to want to read it again, such as if he forgot something or didn't really read it the first time, or roleplaying, or whatever.
Chris wrote:In a number of cases, 'no use for it' could even apply to responses the player never saw before (the majority of Background topics, for instance), but again, it depends on the player.
Nope. The 'used' or 'stale' designation depends on whether the topic presents any further content, with the assumption that a topic that the only effect of is displaying text the player had already read does not count as further content (it's stale content). Thus, a response the player had never seen before is non-stale by definition.
Chris wrote: This creates an inconsistent experience, with a grayed out topic merely indicating that someone else thinks it's unimportant at this time, not whether you would be interested in it or not.
A more objective approach, however, if far more doable. It leads to more consistent behavior, conveying the same information all players (i.e. you have seen this before; it doesn't say whether it's important or not, since that's different for different players), and it can be applied automatically without having to go through each individual response... after all, even if a dialog response has a script, that doesn't mean it does anything important (I've seen mods use dialog scripts to simply play a voice clip, for example).
I guess my slang and terms made it unclear. The solution I proposed in my first post in this thread is automatically applied, and has no subjective definitions. The result for the player is:
-A normally-colored topic offers some sort of content, either in the form of text I haven't seen yet, or it may cause some effect other than the display of dialog.
-A discolored topic never offers anything other than stale content, in the form of text I've already seen.
Naturally, he only really needs to remember or be aware of the second part. There doesn't seem to be room for any notable confusion or inconsistency, here.

As originally said, if you wanted, you could filter specific functions found in response Results, i.e. so that functions like Say or PlaySound don't cause flagging as a special effect by themselves. In fact it would even be possible for OpenMW to scan the functions used in scripts referred to with StartScript, if we really wanted.
Though it doesn't have to be perfect, only good enough, since after all the only effect here is whether a 'visited hyperlink' is created or not (and only when the setting for it is turned on), it's not gamebreaking stuff.

I think the distinction of whether a topic can still offer anything of use is more useful than the distinction of whether I've simply clicked it before or not. And with the latter approach, eventually all topics will be discolored, with no distinction as to what they may offer. Of course, you could always have a configuration that combines the two approaches as well, meaning you have a color for 'never clicked yet' topics, a color for 'already clicked but stale" topics (i.e. lore/flavor/other text-only) and a color for 'already clicked but has a special effect' topics. Or you could still use 2 colors and italicize already clicked topics.

BTW, I don't remember whether this was mentioned, Oblivion (or only Skyrim?) has another feature it's worthwhile to implement, where you'll be able to tell if a topic has further possible responses you haven't seen due to not high enough Disposition (I think the topic is painted gold??). Not completely realistic (you automatically always being able to tell if someone is lying or withholding info - it's realistic to be able to tell some of the time), but better from having no idea if you're doing something wrong (or that can't be done) in a quest, or it's possible but you simply don't have enough disposition for that. In Morrowind, it could happen that a player retries a topic over and over, expecting different results (i.e. an NPC gives up/submits, or gives you a gift or information) with increased Disposition, but those different results aren't actually programmed into the game and he'll only know for certain that he had wasted his time with it when he retries with 100 Disposition and it still doesn't work.
If you wanted, you could turn it from an interface advantage thing into a character advantage thing, by making the PC only be able to tell there are more possible responses if his stats (Speechcraft, Personality) are good enough, or even good enough compared to the NPC's...
Chris
Posts: 1625
Joined: 04 Sep 2011, 08:33

Re: Grey out read dialogue topics

Post by Chris »

Golken wrote:Not quite. What's subjective is the approach you think is best, i.e. graying out all clicked topics or only some, based upon the consistent criteria.
I mean the criteria is subjective. Different people will find different "effects on experience" important enough to keep the topic from being grayed out. I, for instance, don't see bed renting as an important effect, since I know and expect it to happen when clicking on 'beds'. Conversely, if the innkeeper's 'beds' is kept highlighted because of its side-effect, and someone else has a unique response to the 'beds' topic, I'll be less likely to click the unique response since I'll think they're just offering me a bed.
The solution I proposed in my first post in this thread is automatically applied, and has no subjective definitions. The result for the player is:
-A normally-colored topic offers some sort of content, either in the form of text I haven't seen yet, or it may cause some effect other than the display of dialog.
...
As originally said, if you wanted, you could filter specific functions found in response Results, i.e. so that functions like Say or PlaySound don't cause flagging as a special effect by themselves.
I think this is an inherently limiting approach. As the scripting engine and dialog system becomes more complex and featured, scanning the script for specific behaviors is going to become much harder, if not impractical. In some cases, you might not even be able to tell if some specific function or script chunk causes an appreciable effect or not. It's a design that will inherently hit false positives.

With my approach, however, it behaves 100% as designed. And by having this solid foundation, it makes it easier to extend and improve on in the future.
I think the distinction of whether a topic can still offer anything of use is more useful than the distinction of whether I've simply clicked it before or not. And with the latter approach, eventually all topics will be discolored, with no distinction as to what they may offer.
You will have the distinction of what they offer given the topic name and the NPC you're talking to. If all topics become discolored/grayed out, it will appear just like vanilla (but with a different color).
BTW, I don't remember whether this was mentioned, Oblivion (or only Skyrim?) has another feature it's worthwhile to implement, where you'll be able to tell if a topic has further possible responses you haven't seen due to not high enough Disposition (I think the topic is painted gold??). Not completely realistic (you automatically always being able to tell if someone is lying or withholding info - it's realistic to be able to tell some of the time), but better from having no idea if you're doing something wrong (or that can't be done) in a quest, or it's possible but you simply don't have enough disposition for that.
Yes, Oblivion does that (don't know if Skyrim does it, but Skyrim's disposition system isn't nearly that good anyway, so..). The topic remains in its ungrayed out state if you could get a different response with a higher disposition.

I'm not sure how I feel about that, honestly. On the one hand, it does serve a practical purpose (informing you that there's more for them to tell you about the topic), but on the other hand, it's a giant flag telling you "hey, get their disposition up if you want the real response!" This could easily be handled by having a color for responses you've already read but which could be different with a higher disposition, so people that want Oblivion's behavior can set the color to the default color (or even a unique one, like red), and people that don't want to know can set it to the 'grayed out' color.
If you wanted, you could turn it from an interface advantage thing into a character advantage thing, by making the PC only be able to tell there are more possible responses if his stats (Speechcraft, Personality) are good enough, or even good enough compared to the NPC's...
I like that idea.
CyberShadow
Posts: 17
Joined: 03 Mar 2019, 17:35
Gitlab profile: https://gitlab.com/CyberShadow

Re: Grey out read dialogue topics

Post by CyberShadow »

Instead of graying out topics, how about graying out the actual responses? Seems more straight-forward.

Here's a quick patch which adds [NEW!] or [seen] markers to responses (changing text color is a bit more involved):

Code: Select all

diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp
index 2ecab1c4c..fc3f5ad21 100644
--- a/apps/openmw/mwbase/dialoguemanager.hpp
+++ b/apps/openmw/mwbase/dialoguemanager.hpp
@@ -72,6 +72,8 @@ namespace MWBase
 
             virtual bool checkServiceRefused (ResponseCallback* callback) = 0;
 
+            virtual bool isNewResponse (const std::string& text) = 0;
+
             virtual void persuade (int type, ResponseCallback* callback) = 0;
             virtual int getTemporaryDispositionChange () const = 0;
 
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
index 1ada7b949..e9a09c9b7 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -542,6 +542,17 @@ namespace MWDialogue
         return false;
     }
 
+    bool DialogueManager::isNewResponse(const std::string& text)
+    {
+        std::unordered_set<std::string>::const_iterator iter = mSeenResponses.find(text);
+        if (iter == mSeenResponses.end())
+        {
+            mSeenResponses.insert(text);
+            return true;
+        }
+        return false;
+    }
+
     void DialogueManager::say(const MWWorld::Ptr &actor, const std::string &topic)
     {
         MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@@ -596,6 +607,7 @@ namespace MWDialogue
             state.mKnownTopics.push_back (*iter);
         }
 
+        state.mSeenResponses = mSeenResponses;
         state.mChangedFactionReaction = mChangedFactionReaction;
 
         writer.startRecord (ESM::REC_DIAS);
@@ -617,6 +629,7 @@ namespace MWDialogue
                 if (store.get<ESM::Dialogue>().search (*iter))
                     mKnownTopics.insert (*iter);
 
+            mSeenResponses = state.mSeenResponses;
             mChangedFactionReaction = state.mChangedFactionReaction;
         }
     }
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
index 29a90082c..2d9025d65 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
@@ -5,6 +5,7 @@
 
 #include <map>
 #include <set>
+#include <unordered_set>
 
 #include <components/compiler/streamerrorhandler.hpp>
 #include <components/translation/translation.hpp>
@@ -24,6 +25,7 @@ namespace MWDialogue
     class DialogueManager : public MWBase::DialogueManager
     {
             std::set<std::string, Misc::StringUtils::CiComp> mKnownTopics;// Those are the topics the player knows.
+            std::unordered_set<std::string> mSeenResponses;
 
             // Modified faction reactions. <Faction1, <Faction2, Difference> >
             typedef std::map<std::string, std::map<std::string, int> > ModFactionReactionMap;
@@ -84,6 +86,8 @@ namespace MWDialogue
 
             virtual bool checkServiceRefused (ResponseCallback* callback);
 
+            virtual bool isNewResponse (const std::string& text);
+
             virtual void say(const MWWorld::Ptr &actor, const std::string &topic);
 
             //calbacks for the GUI
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index 6b400c172..e8dff965f 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -124,8 +124,8 @@ namespace MWGui
 
     // --------------------------------------------------------------------------------------------------
 
-    Response::Response(const std::string &text, const std::string &title, bool needMargin)
-        : mTitle(title), mNeedMargin(needMargin)
+    Response::Response(const std::string &text, const std::string &title, bool needMargin, Response::Seen seen)
+        : mTitle(title), mNeedMargin(needMargin), mSeen(seen)
     {
         mText = text;
     }
@@ -142,6 +142,27 @@ namespace MWGui
             typesetter->sectionBreak();
         }
 
+        switch (mSeen)
+        {
+            case Response::Seen_New:
+            {
+                BookTypesetter::Style* title = typesetter->createStyle("", MyGUI::Colour::Red, false);
+                typesetter->write(title, to_utf8_span("[NEW!]"));
+                typesetter->sectionBreak();
+                break;
+            }
+            case Response::Seen_Seen:
+            {
+                MyGUI::Colour colour = MyGUI::Colour(0.25, 0.25, 0.25);
+                BookTypesetter::Style* title = typesetter->createStyle("", colour, false);
+                typesetter->write(title, to_utf8_span("[seen]"));
+                typesetter->sectionBreak();
+                break;
+            }
+            case Response::Seen_Unknown:
+                break;
+        }
+
         typedef std::pair<size_t, size_t> Range;
         std::map<Range, intptr_t> hyperLinks;
 
@@ -667,7 +688,9 @@ namespace MWGui
 
     void DialogueWindow::addResponse(const std::string &title, const std::string &text, bool needMargin)
     {
-        mHistoryContents.push_back(new Response(text, title, needMargin));
+        bool isNew = MWBase::Environment::get().getDialogueManager()->isNewResponse(text);
+        Response::Seen seen = isNew ? Response::Seen_New : Response::Seen_Seen;
+        mHistoryContents.push_back(new Response(text, title, needMargin, seen));
         updateHistory();
         updateTopics();
     }
diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp
index 2538602c6..5ef16d8df 100644
--- a/apps/openmw/mwgui/dialogue.hpp
+++ b/apps/openmw/mwgui/dialogue.hpp
@@ -92,11 +92,13 @@ namespace MWGui
 
     struct Response : DialogueText
     {
-        Response(const std::string& text, const std::string& title = "", bool needMargin = true);
+        enum Seen { Seen_Unknown, Seen_New, Seen_Seen };
+        Response(const std::string& text, const std::string& title = "", bool needMargin = true, Seen seen = Seen_Unknown);
         virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const;
         void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const;
         std::string mTitle;
         bool mNeedMargin;
+        Seen mSeen;
     };
 
     struct Message : DialogueText
diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp
index 2b1887e4e..93a0bf69d 100644
--- a/components/esm/dialoguestate.cpp
+++ b/components/esm/dialoguestate.cpp
@@ -28,6 +28,9 @@ void ESM::DialogueState::load (ESMReader &esm)
             esm.skipHSub();
         }
     }
+
+    while (esm.isNextSub ("SERE"))
+        mSeenResponses.insert (esm.getHString());
 }
 
 void ESM::DialogueState::save (ESMWriter &esm) const
@@ -50,4 +53,10 @@ void ESM::DialogueState::save (ESMWriter &esm) const
             esm.writeHNT ("INTV", reactIter->second);
         }
     }
+
+    for (std::unordered_set<std::string>::const_iterator iter (mSeenResponses.begin());
+        iter!=mSeenResponses.end(); ++iter)
+    {
+        esm.writeHNString ("SERE", *iter);
+    }
 }
diff --git a/components/esm/dialoguestate.hpp b/components/esm/dialoguestate.hpp
index d7cdb941c..eb2be89e9 100644
--- a/components/esm/dialoguestate.hpp
+++ b/components/esm/dialoguestate.hpp
@@ -4,6 +4,7 @@
 #include <string>
 #include <vector>
 #include <map>
+#include <unordered_set>
 
 namespace ESM
 {
@@ -17,6 +18,8 @@ namespace ESM
         // must be lower case topic IDs
         std::vector<std::string> mKnownTopics;
 
+        std::unordered_set<std::string> mSeenResponses;
+
         // must be lower case faction IDs
         std::map<std::string, std::map<std::string, int> > mChangedFactionReaction;
 
Screenshot:
Image

The seen responses are saved to the save file as usual.
Post Reply