Page 1 of 2

DialogueManager.instance.activeConversation is null in the first node?

Posted: Fri Sep 10, 2021 8:47 am
by Nightkin
Hello,

I have recently bought Dialogue System and am currently learning to use it. I'm impressed with all the features but I am already running into several issues, probably coming from my misunderstanding of this or that feature, even after RTFM.

The biggest issue I have for now is the fact that during the first node of the conversation (which is on the player side, waiting for an input from the player), I want to be able to show the internal name of the other participant and I can't because I can't access its data, all this because

Code: Select all

DialogueManager.instance.activeConversation
is still null at that time. It is no longer null and therefore becomes usable from the second node, but not during the first one. Here is a little background to explain the problem and my need:

In my game (still in prototyping phase), my characters are all the same at design time, they are generated when starting the level, using scriptable objects describing the identity of each one. When one of them starts a conversation with another, it calls

Code: Select all

DialogueManager.StartConversation (conv_name, this.transform, other.transform)
, so far so good, the conversation starts.

However, I also need to access some internal variables of the characters directly from Lua without copying them into the variables space of the Dialogue System because they might change during the conversation and I don't want to have to preemptively expose variables that I may or may not use in a conversation (and to have to do it the other way around when the conversation ends if any of these variables has changed). To this end, I made a few functions to get the values I need directly from the objects, by name. For example,

Code: Select all

GetString ("char_1.nickname")
returns one of the three names each character has (that way I can vary depending on the familiarity level between the characters).

Of course, when writing the conversation in the first place, I do not know that a character's internal name is "char_1", that id is calculated automatically at runtime so that it is unique. I only know it is either the actor or the conversant so I need a way to dynamically replace an alias (for example "@you") with the actual id ("char_1") when the conversation takes place. In other words, I write 'Hello [lua(GetString("@you.nickname"))], how are you?' in the very first node ("@you" being the alias for the conversant in this case) and I want it to show to the player, as a choice, "Hello Kay, how are you?" (if the "char_1" character's nickname is "Kay" and Kay is the conversant). But it doesn't work because for that I need the conversation and it is null at that time.

My initial workaround was to call

Code: Select all

DialogueLua.SetVariable ("your_nickname", other_character.nickName)
before calling

Code: Select all

DialogueManager.StartConversation()
but since I don't know in advance what name they will use during the conversation, this would make me expose at least six names (three per participant) just to be able to use one of them during the very first node. Another workaround would be to create a bogus first node with nothing in it just to let the conversation object be created but frankly that's more a hack than a workaround.

Is there something didn't get correctly and how can I manage to get access to

Code: Select all

DialogueManager.instance.activeConversation
right from the start?

I have a second question, less blocking this one but it would be nice to have... Right now I write

Code: Select all

[lua(GetString("@alias.internal_variable"))]
in a node to get the value of an internal variable of one of my objects, not using DS's variables at all (because as I said, they may change whenever) but that's a big cumbersome. Ideally, I'd like to write something like

Code: Select all

[@@alias.internal_variable]
(the first '@' meaning "this is a call to GetString() in the program" and the second '@' meaning "this is an alias" but that part is already taken care of, having both symbols be the same allowing me to write such tokens more quickly). Is there a way to do that? My idea would be to have some kind of hook to dynamically replace the '[@' part with '[lua(GetString("' and the ']' part with '"))]', before DS even calls Lua to actually parse the '[]' token and call the functions.

Thanks!

Re: DialogueManager.instance.activeConversation is in the first node?

Posted: Fri Sep 10, 2021 10:32 am
by Tony Li
Hi,

For the first question, use DialogueManager.currentActor and currentConversant. DialogueManager.instance.activeConversation is not valid while it's still setting up the conversation (i.e., the first node) because it's possible that Conditions on the first node will prevent the conversation from starting.

For the second question, add a script with an OnConversationLine method to your Dialogue Manager. Example:

Code: Select all

void OnConversationLine(Subtitle subtitle)
{
    // Replace [@x] with [lua(GetString("x"))]:
    var regex = new Regex("\[@[^\]]+\]"); // (untested; may need to adjust slightly. Also, cache this.)
    subtitle.formattedText.text = regex.Replace(subtitle.formattedText.text, "[lua(GetString(\"$1\"))]");
    
    // Process [lua(code)] and [var=variable] tags:
    subtitle.formattedText.text = FormattedText.ParseCode(subtitle.formattedText.text);
}
I just typed that code into the reply; it may have typos.

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Fri Sep 10, 2021 10:39 am
by Nightkin
Oh, perfect!

For my first question, that makes sense. I didn't think that conditions placed on the first node would invalidate the conversation hence prevent it from starting at all. CurrentActor and CurrentConversant will do just fine. Thanks!

Edit: Indeed, I just tested and it works just fine.

As for my second question, I should have known that the answer to "could there be a hook" was "there's already a hook". I'll RTFM again. lol

Thank you very much for your very fast and clear answer, I'll try that as soon as I get the chance!

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Fri Sep 10, 2021 10:44 am
by Tony Li
Sounds good! If any other questions come up, just ask. :-)

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Mon Sep 13, 2021 5:32 am
by Nightkin
Ok I have tested the the answer to my second question (how to hook into the string replacement part of the dialogue line) and for the people who might have the same requirements, here is what I've observed:

OnConversationLine() added to a component in the DialogueManager will show the line after it has been processed, in fact after it has been said and heard, so it's too late there as I need to process it first to replace my aliases with lua commands.

However, using OnPrepareConversationLine() instead works wonders because it is called before processing the line. Write this function in a component placed in the same GameObject as the DialogueManager:

Code: Select all

        void OnPrepareConversationLine (DialogueEntry dialogueEntry) {
            Debug.Log ("DialogueManager prepares: \"" + dialogueEntry.currentDialogueText + "\"");
        }
I get this:
DialogueManager prepares: "Hey [lua(GetString("@you.nn"))], how are you doing?"

The debug line shows before the subtitle is even processed so the lua command is still in it. This is where my Regex replacements will go once I decide of the exact syntax to use.


I have other needs though, such as modifying a line before it is spoken, and modifying that same (already modified) line before it is heard. From what I observed, OnConversationLine() added to my Character script works fine:

Code: Select all

        public void OnConversationLine (Subtitle subtitle) {
            if (subtitle != null) {
                if (subtitle.speakerInfo.transform == transform) {
                    subtitle.formattedText.text = subtitle.formattedText.text + " (said)";
                    Debug.Log (ToString () + " says \"" + subtitle.formattedText.text + "\"");
                }
                if (subtitle.listenerInfo.transform == transform) {
                    subtitle.formattedText.text = subtitle.formattedText.text + " (heard)";
                    Debug.Log (ToString () + " hears \"" + subtitle.formattedText.text + "\"");
                }
            }
        }
I wrote the "(said)" and "(heard)" tokens expecting to see "XXX says "blah" (said)" then "YYY hears "blah" (said) (heard)" and never "YYY hears "blah" (heard)" then "XXX says "blah" (heard) (said)", and that's what I got. It is confirmed by looking into ConversationView.NotifyParticipants() that the speaker is notified before the listener so modifying the subtitle that way works well.

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Mon Sep 13, 2021 8:08 am
by Tony Li
Hi,

Sounds like you have it set up the way you need. Note that, in my little OnConversationLine code example above, I called FormattedText.ParseCode() after replacing special [@@...] tags. This method processes any [var=variable] and [lua(code)] tags in the replaced text so, if my understanding is correct, you wouldn't need OnPrepareConversationLine.

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Mon Sep 13, 2021 9:24 am
by Nightkin
That's my fault, I failed to mention that I need to "filter" incoming and outgoing text as well. It just didn't occur to me at that time and I remembered that I needed this feature during the weekend.

Since I need to filter already formatted text (i.e. after replacing "[@xxx]" with "[lua(GetString("xxx"))]" ), I need to format the text before filtering it, which means before NotifyParticipants() notifies the speaker and the listener. That's why OnPrepareConversationLine() is best for my needs, but your OnConversationLine() answer would have worked well too, if I didn't have this "filter" needs as well.

Sorry for the confusion.

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Tue Sep 14, 2021 12:39 pm
by Nightkin
Sorry, I'm still confused about a few things.

I've been testing the following code in a component placed in the DialogueManager's gameobject:

Code: Select all

    public class DialogueSystemReplaceTokens : MonoBehaviour
    {
        private Regex regex;

        private void Awake () {
            regex = new Regex (@"\[@([^\]]+)\]");
        }

        void OnPrepareConversationLine (DialogueEntry dialogueEntry) {
            // Replace [@x] with [lua(GetString("x"))]:
            dialogueEntry.currentDialogueText = regex.Replace (dialogueEntry.currentDialogueText, "[lua(GetString(\"$1\"))]");
            
            // Process [lua(code)] and [var=variable] tags:
            dialogueEntry.currentDialogueText = FormattedText.ParseCode (dialogueEntry.currentDialogueText);
        }

It works well, replacing "[@@you.nn]" with "[lua(GetString("@you.nn"))]" then evaluating "GetString("@you.nn")" to replace it with the actual nickname of the conversant.

But it works only once.

It seems that modifying dialogueEntry.currentDialogueText modifies the actual source text in the memory version of the conversation database... But that's not what I want, I need this conversation to be reusable with different characters so I can't let the source text be modified during the first conversation, I need to modify a copy of it that will be used only for this conversation and not persistent data. Is there a more "temporary" version of the text that I can modify without modifying the source text?

I also tried using OnConversationLine() of both participants (with "subtitle.formattedText.text" this time instead of "dialogueEntry.currentDialogueText") and it works better, the subtitle itself is temporary enough to be usable. On the speaker side I do the above process, then I apply further modifications to the replaced text if needed (right now it's just debug), and on the listener side I only apply modifications and do no replacement.

It works well, but it doesn't modify the on-screen responses that the player can choose from before the text is spoken, so I still see a list of responses containing "Hi [@@you.nn], how are you?", and the "[]" part is replaced only when the character speaks, which is to be expected since OnConversationLine() is called only after the player chooses the response.

So I have to replace the text of the responses in OnConversationResponseMenu() of the character, meaning I have to replace the text twice. Is it the correct way to do this? When choosing a response, is the modified text of the response forgotten and the spoken line coming from the database (hence requiring replacement)? It is not a problem if that's the case but I just wanted to be sure that I'm not doing the same replacements twice on the same string. It could cause issues if I had aliases containing aliases.

Thanks!

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Tue Sep 14, 2021 1:31 pm
by Tony Li
Hi,

Yes, use OnConversationLine() and OnConversationResponseMenu(). Treat the dialogue database (including its DialogueEntry objects) as read-only.

Re: DialogueManager.instance.activeConversation is null in the first node?

Posted: Tue Sep 14, 2021 6:52 pm
by Nightkin
Thank you for your answer, yes I know the database is to be treated as read-only, I just didn't know that OnPrepareConversationLine() modified the actual database, or at least its memory version. Good to know.