Dialogue System 1.8.2 Released!

Announcements, support questions, and discussion for the Dialogue System.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

Hi @Racoon7,

I'm replying to this thread because it contains your original post, but this applies to version 2.0.
Racoon7 wrote: Fri May 25, 2018 7:20 pm
One-time dialogue
I have a bunch of "opening" dialogues I want to trigger only the first time a location is visited.
In Articy, I check a condition in the input pin of the dialogue and mark it "finished" in the output pin. However, DS does this in reverse, first setting the variable to false, and then checking whether it's true – therefore no conversation is triggered.

Image
This is fixed in version 2.0.1 (coming soon).
Racoon7 wrote: Fri May 25, 2018 7:20 pm
Connection colours and priority
DS allows you to set link priority by changing the colour of the connections.
Unfortunately, there was a bug in articy:draft which caused all colour values and even the colour table itself to change slightly every time you saved (rounding error).

Image

I reported it and it should be fixed in version 3.1.12, but it might still be an issue in earlier versions, and in current multi-user versions unless the new colour format is enforced, so I thought I'd let you know.
I'm going to leave this as a known issue with earlier versions of articy:draft because I don't want to implement +/- tolerance values that may apply unintended priorities.
Racoon7 wrote: Fri May 25, 2018 7:20 pm
Nested groups list

First of all, Nested Conversation Groups setting is amazing! Thank you for listening to my input back then – it makes organizing my dialogue much easier. :)

Just a suggestion though. My conversations exported from articy:draft end up in this list:

Image

The actual conversations are listed at the top and the groups which are inside them are all listed below.
Would it be possible to put them in the submenus where they actually belong?
I can't reproduce this. Here is an example:

Image

It imports into the Dialogue System like this:

Image

Is that what you're doing, too?
Racoon7 wrote: Fri May 25, 2018 7:20 pm
Articy markup
This is not a feedback per se, just a little script for anyone who might find it useful in any way.

I use italics quite a lot and didn't want to write <i>xxx</i> or emphasis tags all the time, so I wrote a script which (hopefully :D ) converts exported Articy:draft markup inside dialogue fragments into Unity's rich text – which should be enabled in the settings.

Image

Accessible via right-click on an exported XML (use before attempting to convert it to a database).

Image

It supports bold, italic and colourful text.

I wrote it in C#6 which can be turned on in Editor ► Project Settings ► Player / Scripting Runtime Version – but here's a C#4 version as well.
C#6

(Edit: Updated this to work with multiple paragraphs.)

Code: Select all

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;
using static System.Environment;


/// <summary>Parses the Articy markup in dialogue fragments and prepares it for use in Dialogue System with rich text – which must be supported by your UI.</summary>
public static class ArticyMarkupConverter {

    const string XmlExtension = ".xml";

    const string DialogueFragmentName = "DialogueFragment";
    const string TextElementName = "Text";
    const string LocalizedStringName = "LocalizedString";

    static string defaultNamespace;

    const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Singleline;
    static readonly Regex StylesRegex = new Regex(@"<style>(?<styles>.*?)</style>", Options); // Get the part of text dealing with styles
    static readonly Regex StyleRegex = new Regex(@"#(?<id>s[1-9]\d*) {(?<style>.*?)}", Options); // The first style "s0" is always a paragraph

    // Check a specific style for these
    static readonly Regex BoldRegex = new Regex(@"font-weight\s*?:\s*?bold", Options);
    static readonly Regex ItalicRegex = new Regex(@"font-style\s*?:\s*?italic", Options);
    static readonly Regex ColorRegex = new Regex(@"color\s*?:\s*?(?<color>#\w{6})", Options);

    // Apply the styles to the actual text. The style tags never overlap, so they can be processed in order.
    static readonly Regex TextRegex = new Regex(@"<p id=""s0"">(?<text>.*?)</p>", Options);
    static readonly Regex PartsRegex = new Regex(@"<span id=""(?<id>s[1-9]\d*)"">(?<text>.*?)</span>", Options); // Style id : Pure text  

    [MenuItem("Assets/Tools/Replace Articy markup with rich text", true)]
    public static bool IsXmlFile() {
        // Validation        
        string relativeFilePath = AssetDatabase.GetAssetPath(Selection.activeObject);
        return Path.GetExtension(relativeFilePath)?.ToLower() == XmlExtension;
    }

    [MenuItem("Assets/Tools/Replace Articy markup with rich text")]
    public static void ReplaceArticyMarkup() {
        string projectFolder = Application.dataPath.Remove(Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
        string filePath = projectFolder + AssetDatabase.GetAssetPath(Selection.activeObject);

        XDocument articyXml = XDocument.Load(filePath);
        defaultNamespace = articyXml.Root?.GetDefaultNamespace().NamespaceName ?? string.Empty;

        var dialogueFragments = articyXml.Descendants(XName.Get(DialogueFragmentName, defaultNamespace));

        // For all texts in dialogue fragments
        int total = 0;
        foreach (XElement fragment in dialogueFragments) {
            XElement localizedString = fragment.Element(XName.Get(TextElementName, defaultNamespace))
                                              ?.Element(XName.Get(LocalizedStringName, defaultNamespace));
            ReplaceMarkup(ref localizedString);
            total++;
        }
        Debug.Log($"Converted {total} dialogue fragments to rich text.");
        articyXml.Save(filePath);
    }

    static void ReplaceMarkup(ref XElement localizedString) {
        if (localizedString == null) return;
        string text = localizedString.Value;
        localizedString.Value = string.Empty; // Clear to avoid duplicating values

        string editedText = ConvertToRichText(text); // TODO: Check for ']]>' in the string and optionally split in in two CDATA sections ']]]]><![CDATA[>'
        localizedString.Add(new XCData(editedText)); // = Don't escape special characters
    }

    /// <summary>Parses given text and converts the Articy markup to rich text.</summary>
    static string ConvertToRichText(string dialogueLine) {
        dialogueLine = dialogueLine.Replace(@"&#39;", "'"); // Apostrophe

        // Get styles
        if (!StylesRegex.IsMatch(dialogueLine)) return dialogueLine; // No styles, pure text
        // TODO: Purge styles from Dialogues and Entities
        string stylesText = StylesRegex.Match(dialogueLine).Groups["styles"].Value;
        var numberedStyles = StyleRegex.Matches(stylesText)
                                       .Cast<Match>()
                                       .Select(match => new {
                                                Id = match.Groups["id"].Value,
                                                Style = match.Groups["style"].Value
                                        });
        var styles = numberedStyles.Select(style => new {
                style.Id,
                Bold = BoldRegex.IsMatch(style.Style),
                Italic = ItalicRegex.IsMatch(style.Style),
                Color = ColorRegex.Match(style.Style).Groups["color"].Value
        });

        // Get texts
        var paragraphs = TextRegex.Matches(dialogueLine)
                                  .Cast<Match>()
                                  .Select(match => match.Groups["text"].Value);
        string finalDialogueLine = string.Join($"{NewLine}{NewLine}",
                paragraphs.Select(paragraph => {
                            var innerTexts = PartsRegex.Matches(paragraph)
                                                       .Cast<Match>()
                                                       .Select(match => new {
                                                                StyleId = match.Groups["id"].Value,
                                                                Text = match.Groups["text"].Value
                                                        });

                            // Apply the styles to the texts
                            var editedParts = innerTexts.Select(text => {
                                var currentStyle = styles.First(style => style.Id == text.StyleId);
                                return ApplyStyle(
                                        innerText: text.Text,
                                        bold: currentStyle.Bold,
                                        italic: currentStyle.Italic,
                                        color: currentStyle.Color
                                );
                            });
                            string editedLine = string.Join(string.Empty, editedParts);
                            return editedLine;
                        }
                ));
        return finalDialogueLine;
    }

    /// <summary>Wraps a given text in rich text tags.</summary>
    static string ApplyStyle(string innerText, bool bold, bool italic, string color) {
        var builder = new StringBuilder(innerText);

        if (bold) WrapInTag(ref builder, "b");
        if (italic) WrapInTag(ref builder, "i");
        if (color != string.Empty) WrapInTag(ref builder, "color", color);

        return builder.ToString();
    }

    static void WrapInTag(ref StringBuilder builder, string tag, string value = "") {
        builder.Insert(0, (value != string.Empty) // opening tag
                ? $@"<{tag}={value}>" // the tag has a value
                : $@"<{tag}>"); // no value
        builder.Append($@"</{tag}>"); // closing tag
    }

}
C#4

(Note: Unlike the C#6 version, this doesn't work when the dialogue line consists of more than one paragraph.)

Code: Select all

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;


/// <summary>Parses the Articy markup in dialogue fragments and prepares it for use in Dialogue System with rich text – which must be supported by your UI.</summary>
public static class ArticyMarkupConverterC4 {

    const string XmlExtension = ".xml";

    const string DialogueFragmentName = "DialogueFragment";
    const string TextElementName = "Text";
    const string LocalizedStringName = "LocalizedString";
    const string LangAttribute = "Lang";

    static string defaultNamespace;

    const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase;
    static readonly Regex StylesRegex = new Regex(@"<style>(?<styles>.*?)</style>", Options); // Get the part of text dealing with styles
    static readonly Regex StyleRegex = new Regex(@"#(?<id>s[1-9]\d*) {(?<style>.*?)}", Options); // The first style "s0" is always a paragraph

    // Check a specific style for these
    static readonly Regex BoldRegex = new Regex(@"font-weight\s*?:\s*?bold", Options);
    static readonly Regex ItalicRegex = new Regex(@"font-style\s*?:\s*?italic", Options);
    static readonly Regex ColorRegex = new Regex(@"color\s*?:\s*?(?<color>#\w{6})", Options);

    // Apply the styles to the actual text. The style tags never overlap, so they can be processed in order.
    static readonly Regex TextRegex = new Regex(@"<p id=""s0"">(?<text>.*?)</p>", Options);
    static readonly Regex PartsRegex = new Regex(@"<span id=""(?<id>s[1-9]\d*)"">(?<text>.*?)</span>", Options); // Style id : Pure text  

    [MenuItem("Assets/Tools/Replace Articy markup with rich text (C#4)", true)]
    public static bool IsXmlFile() {
        // Validation        
        string relativeFilePath = AssetDatabase.GetAssetPath(Selection.activeObject);
        return relativeFilePath != null && Path.GetExtension(relativeFilePath).ToLower() == XmlExtension;
    }

    [MenuItem("Assets/Tools/Replace Articy markup with rich text (C#4)")]
    public static void ReplaceArticyMarkup() {
        string projectFolder = Application.dataPath.Remove(Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
        string filePath = projectFolder + AssetDatabase.GetAssetPath(Selection.activeObject);

        XDocument articyXml = XDocument.Load(filePath);
        defaultNamespace = (articyXml.Root != null) ? articyXml.Root.GetDefaultNamespace().NamespaceName : string.Empty;

        var dialogueFragments = articyXml.Descendants(XName.Get(DialogueFragmentName, defaultNamespace));

        // For all texts in dialogue fragments
        int total = 0;
        foreach (XElement fragment in dialogueFragments) {
            var textElement = fragment.Element(XName.Get(TextElementName, defaultNamespace));
            XElement localizedString = (textElement != null)
                    ? textElement.Element(XName.Get(LocalizedStringName, defaultNamespace))
                    : null;
            ReplaceMarkup(ref localizedString);
            total++;
        }
        Debug.Log(string.Format("Converted {0} dialogue fragments to rich text.", total));
        articyXml.Save(filePath);
    }

    static void ReplaceMarkup(ref XElement localizedString) {
        if (localizedString == null) return;
        string text = localizedString.Value;
        localizedString.Value = string.Empty; // Clear to avoid duplicating values

        string editedText = ConvertToRichText(text);
        localizedString.Add(new XCData(editedText)); // = Don't escape special characters
    }

    /// <summary>Parses given text and converts the Articy markup to rich text.</summary>
    static string ConvertToRichText(string dialogueLine) {
        dialogueLine = dialogueLine.Replace(@"&#39;", "'"); // Apostrophe

        // Get styles
        if (!StylesRegex.IsMatch(dialogueLine)) return dialogueLine; // No styles, pure text
        string stylesText = StylesRegex.Match(dialogueLine).Value;
        var numberedStyles = StyleRegex.Matches(stylesText)
                                       .Cast<Match>()
                                       .Select(match => new {
                                                Id = match.Groups["id"].Value,
                                                Style = match.Groups["style"].Value
                                        });
        var styles = numberedStyles.Select(style => new {
                style.Id,
                Bold = BoldRegex.IsMatch(style.Style),
                Italic = ItalicRegex.IsMatch(style.Style),
                Color = ColorRegex.Match(style.Style).Groups["color"].Value
        });

        // Get texts
        var fullText = TextRegex.Match(dialogueLine).Value; // The dialogue text with <span> tags
        var innerTexts = PartsRegex.Matches(fullText)
                                   .Cast<Match>()
                                   .Select(match => new {
                                            StyleId = match.Groups["id"].Value,
                                            Text = match.Groups["text"].Value
                                    });

        // Apply the styles to the texts
        var editedParts = innerTexts.Select(text => {
            var currentStyle = styles.First(style => style.Id == text.StyleId);
            return ApplyStyle(
                    innerText: text.Text,
                    bold: currentStyle.Bold,
                    italic: currentStyle.Italic,
                    color: currentStyle.Color
            );
        }).ToArray();
        string editedLine = string.Join(string.Empty, editedParts);

        return editedLine;
    }

    /// <summary>Wraps a given text in rich text tags.</summary>
    static string ApplyStyle(string innerText, bool bold, bool italic, string color) {
        var builder = new StringBuilder(innerText);

        if (bold) WrapInTag(ref builder, "b");
        if (italic) WrapInTag(ref builder, "i");
        if (color != string.Empty) WrapInTag(ref builder, "color", color);

        return builder.ToString();
    }

    static void WrapInTag(ref StringBuilder builder, string tag, string value = "") {
        builder.Insert(0, (value != string.Empty) // opening tag
                ? string.Format(@"<{0}={1}>", tag, value) // the tag has a value
                : string.Format(@"<{0}>", tag)); // no value
        builder.Append(string.Format(@"</{0}>", tag)); // closing tag
    }

}
I'm working on incorporating this into the converter as an option.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

@Racoon7 - I'm releasing version 2.0.1. If there are still improvements to be made to nested conversation groups, I'll plan to get them into 2.0.2.
Racoon7
Posts: 7
Joined: Mon Oct 30, 2017 2:17 pm

Re: Dialogue System 1.8.2 Released!

Post by Racoon7 »

Hi Tony,
Tony Li wrote: Wed Jun 20, 2018 11:23 am “Articy:draft colours & priority”

I'm going to leave this as a known issue with earlier versions of articy:draft because I don't want to implement +/- tolerance values that may apply unintended priorities.
Yes, okay, I absolutely understand that.

In fact, I use the single-user version, so it won't affect me personally, but I mentioned it because if somebody works in a team and uses the multi-user version of articy:draft, the colours & priorities will eventually stop working even in the newer versions because you apparently need to manually enable the new colour format there.
Tony Li wrote: Wed Jun 20, 2018 11:23 am “Nested groups list”

I can't reproduce this…

Image
Image

(Flow > Flow > Dialogue > Dialogue Fragment)

Is that what you're doing, too?
I'm actually using something like Flow > Flow > Flow > Dialogue > Flow > Dialogue Fragment.

Looking at your example, I think the main difference might be having flow fragments inside the dialogues, to simplify navigation and avoid getting lost in a large cluster of dialogue fragments (= the Nested Conversation Groups Importer setting).

Image
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

Aha! Thank you. It's quite reproducible. I'll have a patch by the end of the week.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

The patch to fix conversation titles of nested flow fragments is on the customer download site. You can also download it here: DialogueSystem_2_0_1_Patch20180621.unitypackage
Post Reply