/// <summary>
        /// Guesses the line number of a given line. Used for the View Node Source button.
        /// </summary>
        int GuessLineNumber(int nodeID, string lineText)
        {
            var node = MerinoData.GetNode(nodeID);

            // this is a really bad way of doing it, but Yarn Spinner's DialogueRunner doesn't offer any access to line numbers.
            if (node != null && node.nodeBody.Contains(lineText))
            {
                var lines = node.nodeBody.Split('\n');
                for (int i = 0; i < lines.Length; i++)
                {
                    if (lines[i].Contains(lineText))
                    {
                        return(i + 1);
                    }
                }
                return(-1);
            }

            if (node == null)
            {
                MerinoDebug.LogFormat(LoggingLevel.Warning, "Couldn't find node ID {0}. It might've been deleted or the Yarn file might be corrupted.", nodeID);
            }

            return(-1);
        }
示例#2
0
        public static int ConvertToYarn(ConvertFormatOptions options)
        {
            foreach (var file in options.files)
            {
                if (YarnSpinnerLoader.GetFormatFromFileName(file) == NodeFormat.Text)
                {
                    MerinoDebug.LogFormat(LoggingLevel.Warning, "Not converting file {0}, because its name implies it's already in Yarn format", file);
                    continue;
                }

                ConvertNodesInFile(options, file, "yarn.txt", ConvertNodesToYarnText);
            }
            return(0);
        }
示例#3
0
        public static int ConvertToJSON(ConvertFormatOptions options)
        {
            foreach (var file in options.files)
            {
                if (YarnSpinnerLoader.GetFormatFromFileName(file) == NodeFormat.JSON)
                {
                    MerinoDebug.LogFormat(LoggingLevel.Warning, "Not converting file {0}, because its name implies it's already in JSON format", file);
                    continue;
                }

                ConvertNodesInFile(options, file, "json", (IEnumerable <YarnSpinnerLoader.NodeInfo> nodes) => JsonConvert.SerializeObject(nodes, Formatting.Indented));
            }
            return(0);
        }
        /// Erase all variables and reset to default values
        public void ResetToDefaults()
        {
            Clear();

            // For each default variable that's been defined, parse the string
            // that the user typed in in Unity and store the variable
            foreach (var variable in defaultVariables)
            {
                object value;

                switch (variable.type)
                {
                case Yarn.Value.Type.Number:
                    float f = 0.0f;
                    float.TryParse(variable.value, out f);
                    value = f;
                    break;

                case Yarn.Value.Type.String:
                    value = variable.value;
                    break;

                case Yarn.Value.Type.Bool:
                    bool b = false;
                    bool.TryParse(variable.value, out b);
                    value = b;
                    break;

                case Yarn.Value.Type.Variable:
                    // We don't support assigning default variables from other variables
                    // yet
                    MerinoDebug.LogFormat(LoggingLevel.Error,
                                          "Can't set variable {0} to {1}: You can't set a default variable to be another variable, because it may not have been initialised yet.",
                                          variable.name, variable.value);
                    continue;

                case Yarn.Value.Type.Null:
                    value = null;
                    break;

                default:
                    throw new System.ArgumentOutOfRangeException();
                }

                var v = new Yarn.Value(value);

                SetValue("$" + variable.name, v);
            }
        }
示例#5
0
        public static string ConvertNodesToYarnText(IEnumerable <YarnSpinnerLoader.NodeInfo> nodes)
        {
            var sb = new System.Text.StringBuilder();

            var properties = typeof(YarnSpinnerLoader.NodeInfo).GetProperties();

            foreach (var node in nodes)
            {
                foreach (var property in properties)
                {
                    // ignore the body attribute
                    if (property.Name == "body")
                    {
                        continue;
                    }

                    // piggy-back off the JsonIgnoreAttribute to sense items that should not be serialised
                    if (property.GetCustomAttributes(typeof(JsonIgnoreAttribute), false).Length > 0)
                    {
                        continue;
                    }

                    var field = property.Name;

                    string value;

                    var propertyType = property.PropertyType;
                    if (propertyType.IsAssignableFrom(typeof(string)))
                    {
                        value = (string)property.GetValue(node, null);

                        // avoid storing nulls when we could store the empty string instead
                        if (value == null)
                        {
                            value = "";
                        }
                    }
                    else if (propertyType.IsAssignableFrom(typeof(int)))
                    {
                        value = ((int)property.GetValue(node, null)).ToString();
                    }
                    else if (propertyType.IsAssignableFrom(typeof(YarnSpinnerLoader.NodeInfo.Position)))
                    {
                        var position = (YarnSpinnerLoader.NodeInfo.Position)property.GetValue(node, null);

                        value = string.Format("{0},{1}", position.x, position.y);
                    }
                    else
                    {
                        MerinoDebug.LogFormat(LoggingLevel.Error, "Internal error: Node {0}'s property {1} has unsupported type {2}", node.title, property.Name, propertyType.FullName);

                        // will never be run, but prevents the compiler being mean about us not returning a value
                        throw new Exception();
                    }

                    var header = string.Format("{0}: {1}", field, value);

                    sb.Append(MerinoPrefs.lineEnding + header);
                }
                // now write the body

                // 5 May 2019, changed all AppendLine to use regular Append, with manual line breaks
                // this is to preserve compatibility with the base Yarn Editor, which doesn't like "\r\n" (which AppendLine uses)
                // see: https://github.com/radiatoryang/merino/issues/26

                sb.Append(MerinoPrefs.lineEnding + "---");

                sb.Append(MerinoPrefs.lineEnding + node.body);

                sb.Append(MerinoPrefs.lineEnding + "===");
            }

            return(sb.ToString());
        }
示例#6
0
        public static IList <MerinoTreeElement> GetDataFromFile(TextAsset source, int startID = 1, bool useFastMode = false)
        {
            var treeElements = new List <MerinoTreeElement>();

            if (!useFastMode)
            {
                AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(source)); // TODO: only reload assets that need it? how to do that
                //var format = YarnSpinnerLoader.GetFormatFromFileName(AssetDatabase.GetAssetPath(currentFile)); // TODO: add JSON and ByteCode support?
            }

            // ROOT: create a root node for the file itself
            var fileRoot = new MerinoTreeElement(source.name, 0, startID);

            fileRoot.leafType = MerinoTreeElement.LeafType.File;
            fileRoot.children = new List <TreeElement>();
            treeElements.Add(fileRoot);
            if (MerinoData.FileToNodeID.ContainsKey(source))
            {
                MerinoData.FileToNodeID[source] = startID;
            }
            else
            {
                MerinoData.FileToNodeID.Add(source, startID);
            }

            // load nodes

            // if there's no header sentinel in the text file, then just return an empty list
            if (!source.text.Contains("---"))
            {
                return(treeElements);
            }

            // otherwise, load nodes from file
            var nodes   = YarnSpinnerLoader.GetNodesFromText(source.text, NodeFormat.Text);
            var parents = new Dictionary <MerinoTreeElement, string>();

            foreach (var node in nodes)
            {
                // clean some of the stuff to help prevent file corruption
                string cleanName   = MerinoUtils.CleanYarnField(node.title, true);
                string cleanBody   = MerinoUtils.CleanYarnField(node.body);
                string cleanTags   = MerinoUtils.CleanYarnField(node.tags, true);
                string cleanParent = string.IsNullOrEmpty(node.parent) ? "" : MerinoUtils.CleanYarnField(node.parent, true);

                // write data to the objects
                var newItem = new MerinoTreeElement(cleanName, 0, startID + treeElements.Count);
                newItem.nodeBody     = cleanBody;
                newItem.nodePosition = new Vector2Int(node.position.x, node.position.y);
                newItem.nodeTags     = cleanTags;
                if (string.IsNullOrEmpty(cleanParent) || cleanParent == "Root")
                {
                    newItem.parent         = fileRoot;
                    newItem.cachedParentID = fileRoot.id;
                    fileRoot.children.Add(newItem);
                }
                else
                {
                    parents.Add(newItem, cleanParent);                     // we have to assign parents in a second pass later on, not right now
                }
                treeElements.Add(newItem);
            }

            // second pass: now that all nodes have been created, we can finally assign parents
            foreach (var kvp in parents)
            {
                var parent = treeElements.Find(x => x.name == kvp.Value);
                if (parent == null)
                {
                    MerinoDebug.LogFormat(LoggingLevel.Error, "Merino couldn't assign parent for node {0}: can't find a parent called {1}", kvp.Key.name, kvp.Value);
                }
                else
                {
                    // tell child about it's parent
                    kvp.Key.parent         = parent;
                    kvp.Key.cachedParentID = parent.id;
                    // tell parent about it's child
                    if (kvp.Key.parent.children == null)                     // init parent's list of children if not already initialized
                    {
                        kvp.Key.parent.children = new List <TreeElement>();
                    }
                    kvp.Key.parent.children.Add(kvp.Key);
                }
            }
            return(treeElements);
        }
        void DrawToolbar(Event e)
        {
            EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);

            if (validDialogue)
            {
                GUILayout.Space(2);                 //small space to mimic unity editor

                // jump to node button
                var jumpOptions      = dialogue.allNodes.ToList();
                int currentJumpIndex = jumpOptions.IndexOf(dialogue.currentNode);
                if (!IsDialogueRunning)
                {
                    // if there is no current node, then inform the user that
                    currentJumpIndex = 0;
                    jumpOptions.Insert(0, "<Stopped> Jump to Node?...");
                }
                GUI.SetNextControlName(popupControl);
                int newJumpIndex = EditorGUILayout.Popup(
                    new GUIContent("", "The node you're currently playing; you can also jump to any other node."),
                    currentJumpIndex,
                    jumpOptions.Select(x => x.StartsWith("<Stopped>") ? new GUIContent(x) : new GUIContent("Node: " + x)).ToArray(),
                    EditorStyles.toolbarDropDown,
                    GUILayout.Width(160));
                if (currentJumpIndex != newJumpIndex)
                {
                    if (IsDialogueRunning || newJumpIndex > 0)
                    {
                        PlaytestFrom_Internal(jumpOptions[newJumpIndex], false);
                    }
                }
                GUILayout.Space(6);

                GUI.enabled = IsDialogueRunning;                 // disable if dialogue isn't running
                // attempt to get current node
                var matchingNode = MerinoData.GetNode(dialogue.currentNode);
                if (lastFileParent > 0)                     // if we know the file where the playtest started, we can be more specific
                {
                    matchingNode = MerinoData.GetAllCachedChildren(lastFileParent).Find(node => node.name == dialogue.currentNode);
                    // ok if that search failed for some reason, then just give up and fallback
                    if (matchingNode == null)
                    {
                        matchingNode = MerinoData.GetNode(dialogue.currentNode);
                    }
                }
                var content = new GUIContent(" View Node Source", MerinoEditorResources.TextAsset, "Click to see Yarn script code for this node.");
                if (GUILayout.Button(content, EditorStyles.toolbarButton, GUILayout.Width(EditorStyles.toolbarButton.CalcSize(content).x - 40)))
                {
                    if (matchingNode != null)
                    {
                        // display in yarn editor window
                        GetWindow <MerinoEditorWindow>(MerinoEditorWindow.windowTitle, true).
                        SelectNodeAndZoomToLine(matchingNode.id, GuessLineNumber(matchingNode.id, displayStringFull));
                    }
                    else
                    {
                        MerinoDebug.LogFormat(LoggingLevel.Warning, "Merino culdn't find any node called {0}. It might've been deleted or the Yarn file is corrupted.", dialogue.currentNode);
                    }
                }
                if (GUILayout.Button(new GUIContent(" View in Node Map", MerinoEditorResources.Nodemap, "Click to see this node in the node map window."), EditorStyles.toolbarButton))
                {
                    if (matchingNode != null)
                    {
                        MerinoNodemapWindow.GetNodemapWindow().FocusNode(matchingNode.id);
                    }
                    else
                    {
                        MerinoDebug.LogFormat(LoggingLevel.Warning, "Merino couldn't find any node called {0}. It might've been deleted or the Yarn file is corrupted.", dialogue.currentNode);
                    }
                }
                GUI.enabled = true;
            }

            GUILayout.FlexibleSpace();

            // playtesting preferences popup
            if (GUILayout.Button(new GUIContent("Preferences"), EditorStyles.toolbarDropDown))
            {
                PopupWindow.Show(prefButtonRect, new PlaytestPrefsPopup(IsDocked()));
            }
            //grab popup button's rect do determine the height of the toolbar
            if (e.type == EventType.Repaint)
            {
                prefButtonRect = GUILayoutUtility.GetLastRect();
            }

            GUILayout.Space(2);             //small space to mimic unity editor

            EditorGUILayout.EndHorizontal();

            // eat clicks
            if (e.type == EventType.MouseDown)
            {
                // clicked on toolbar
                if (e.mousePosition.y < prefButtonRect.height)
                {
                    e.Use();
                }

                // clicked out of popup
                if (GUI.GetNameOfFocusedControl() == popupControl)
                {
                    e.Use();
                    GUI.FocusControl(null);
                }
            }
        }