Пример #1
0
        internal static List <MerinoTreeElement> GetAllCachedChildren(int parentID)
        {
            var search = new List <int>()
            {
                parentID
            };
            var children = new List <MerinoTreeElement>();

            if (MerinoData.GetNode(parentID).leafType == MerinoTreeElement.LeafType.Node)
            {
                children.Add(MerinoData.GetNode(parentID));
            }
            // search for all children, and children of children, etc
            for (int i = 0; i < search.Count; i++)
            {
                var newChildren = MerinoData.TreeElements.Where(e => e.cachedParentID == search[i]);
                foreach (var child in newChildren)
                {
                    if (!children.Contains(child))
                    {
                        children.Add(child);
                    }
                    if (!search.Contains(child.id))
                    {
                        search.Add(child.id);
                    }
                }
            }
            return(children);
        }
        /// <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);
        }
        void PlaytestFrom_Internal(string startPassageName, bool reset = true, int onlyFromThisNodeID = -1)
        {
            if (reset)
            {
                MerinoEditorWindow.errorLog.Clear();
                dialogue.Stop();
                dialogue.UnloadAll();
                varStorage.ResetToDefaults();

                try
                {
                    var program = MerinoCore.SaveAllNodesAsString(onlyFromThisNodeID);
                    if (!string.IsNullOrEmpty(program))
                    {
                        string filename = MerinoData.CurrentFiles.Count > 1 ? "<input>" : MerinoData.CurrentFiles[0].name;
                        if (onlyFromThisNodeID > 0 && MerinoData.GetNode(onlyFromThisNodeID).leafType == MerinoTreeElement.LeafType.File)
                        {
                            filename = MerinoData.GetNode(onlyFromThisNodeID).name;
                        }
                        dialogue.LoadString(program, filename);
                    }
                }
                catch (Exception ex)
                {
                    validDialogue = false;
                    PlaytestErrorLog(ex.Message);
                    return;
                }
            }

            validDialogue = true;
            this.StopAllCoroutines();
            this.StartCoroutine(RunDialogue(startPassageName));
        }
Пример #4
0
        // used for file saving
        public static string SaveFileNodesAsString(int fileNodeID)
        {
            var nodeInfoList = new List <YarnSpinnerLoader.NodeInfo>();
            var toTraverse   = new List <int>()
            {
                fileNodeID
            };
            var filterList = new List <int>();

            while (toTraverse.Count > 0)
            {
                if (filterList.Contains(toTraverse[0]) == false)
                {
                    filterList.Add(toTraverse[0]);
                    var node = MerinoData.GetNode(toTraverse[0]);
                    if (node != null && node.hasChildren)
                    {
                        toTraverse.AddRange(node.children.Select(x => x.id));
                    }
                }
                toTraverse.RemoveAt(0);
            }

            // export these nodes
            //todo: move back over to linq, we were getting null ref exception so using this for the time being
            var treeNodes = new List <MerinoTreeElement>();

            foreach (var merinoTreeElement in MerinoData.TreeElements)
            {
                if (filterList.Contains(merinoTreeElement.id))
                {
                    treeNodes.Add(merinoTreeElement);
                }
            }

            // save data to string
            foreach (var treeNode in treeNodes)
            {
                // skip the root, and skip any non-node nodes
                if (treeNode.depth == -1 || treeNode.leafType != MerinoTreeElement.LeafType.Node)
                {
                    continue;
                }

                nodeInfoList.Add(TreeNodeToYarnNode(treeNode));
            }

            return(YarnSpinnerFileFormatConverter.ConvertNodesToYarnText(nodeInfoList));
        }
Пример #5
0
        public static int GetPlaytestParentID(int playtestNodeID)
        {
            switch (MerinoPrefs.playtestScope)
            {
            case MerinoPrefs.PlaytestScope.AllFiles:
                return(-1);

            case MerinoPrefs.PlaytestScope.SameFile:
                return(MerinoData.GetFileParent(playtestNodeID).id);

            case MerinoPrefs.PlaytestScope.NodeOnly:
                return(playtestNodeID);

            default:
                return(-1);
            }
        }
        /// <summary>
        /// Logs errors from the playtest engine and Yarn Loader.
        /// </summary>
        public void PlaytestErrorLog(string message)
        {
            string fileName   = "unknown";
            string nodeName   = "unknown";
            int    lineNumber = -1;

            // detect file name
            if (message.Contains("file"))
            {
                fileName = message.Split(new string[] { "file" }, StringSplitOptions.None)[1].Split(new string[] { " ", ":" }, StringSplitOptions.None)[1];
            }

            // detect node name
            if (message.Contains("node"))
            {
                nodeName = message.Split(new string[] { "node" }, StringSplitOptions.None)[1].Split(new string[] { " ", ":" }, StringSplitOptions.None)[1];

                // detect line numbers, if any, by grabbing the first digit after nodeName
                string numberLog = "";
                for (int index = message.IndexOf(nodeName); index < message.Length; index++)
                {
                    if (Char.IsDigit(message[index]))
                    {
                        numberLog += message[index];
                    }
                    else if (numberLog.Length > 0)                      // did we hit a non-number, after already hitting numbers? then stop
                    {
                        break;
                    }
                }

                int.TryParse(numberLog, out lineNumber);
            }

            var nodeRef = MerinoData.TreeElements.Find(x => x.name == nodeName);

            // v0.6, resolved: "todo: replace "<input>" in parsing errors with the name of the file instead."
            // if filename is default "<input>" then guess filename via cached parentID data in TreeElements
            if (fileName == "<input>" && nodeRef != null)
            {
                fileName = MerinoData.GetFileParent(nodeRef.id).name;
            }

            MerinoEditorWindow.errorLog.Add(new MerinoEditorWindow.MerinoErrorLine(message, fileName, nodeRef != null ? nodeRef.id : -1, Mathf.Max(0, lineNumber)));
            MerinoDebug.Log(LoggingLevel.Error, message);
        }
Пример #7
0
        public static void SaveDataToFiles()
        {
            if (MerinoData.CurrentFiles.Count > 0)
            {
                for (int i = 0; i < MerinoData.CurrentFiles.Count; i++)
                {
                    TextAsset file = MerinoData.CurrentFiles[i];
                    if (MerinoData.FileToNodeID.ContainsKey(file))
                    {
                        if (file == null)
                        {
                            var missingDataID = MerinoData.FileToNodeID[file];
                            MerinoData.DirtyFiles.Remove(file);                             // wait how does this work? it's null, but the pointer isn't?
                            MerinoData.CurrentFiles.RemoveAt(i);
                            i--;
                            var missingFileName = MerinoData.GetNode(missingDataID) != null?MerinoData.GetNode(missingDataID).name : "<cannot recover filename>";

                            if (EditorUtility.DisplayDialog("Can't save file " + missingFileName, "The file has been deleted, moved outside of the project's assets folder, or otherwise hidden. Merino can't find it.", "Save backup of current data as new file", "Do nothing"))
                            {
                                MerinoEditorWindow.GetEditorWindow().CreateNewYarnFile("YarnBackupOfFile", SaveAllNodesAsString(missingDataID));
                            }
                            continue;
                        }
                        else
                        {
                            File.WriteAllText(AssetDatabase.GetAssetPath(file), SaveFileNodesAsString(MerinoData.FileToNodeID[file]));
                            EditorUtility.SetDirty(file);
                            LastSaveTime = EditorApplication.timeSinceStartup;
                        }
                    }
                    else
                    {
                        MerinoDebug.Log(LoggingLevel.Warning, file.name + " has not been mapped to a NodeID and cannot be saved, reload the file and try again.");
                    }
                }
                EditorUtility.SetDirty(MerinoData.Instance);
            }
        }
        void DrawBottomToolBar(Rect rect)
        {
            if (MerinoEditorWindow.errorLog == null || MerinoEditorWindow.errorLog.Count <= 0)
            {
                return;
            }

            GUILayout.BeginArea(rect);

            using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
            {
                var style = GUI.skin.button;                 //EditorStyles.miniButton;

                GUILayout.FlexibleSpace();

                if (MerinoEditorWindow.errorLog != null && MerinoEditorWindow.errorLog.Count > 0)
                {
                    var error = MerinoEditorWindow.errorLog[MerinoEditorWindow.errorLog.Count - 1];
                    var node  = MerinoData.GetNode(error.nodeID);
                    if (GUILayout.Button(new GUIContent(node == null ? " ERROR!" : " ERROR: " + node.name + ":" + error.lineNumber.ToString(), errorIcon, node == null ? error.message : string.Format("{2}\n\nclick to open node {0} at line {1}", node.name, error.lineNumber, error.message)), style, GUILayout.MaxWidth(position.width * 0.31f)))
                    {
                        if (node != null)
                        {
                            GetWindow <MerinoEditorWindow>(MerinoEditorWindow.windowTitle).SelectNodeAndZoomToLine(error.nodeID, error.lineNumber);
                        }
                        else
                        {
                            EditorUtility.DisplayDialog("Merino Error Message!", "Merino error message:\n\n" + error.message, "Close");
                        }
                        StopPlaytest_Internal();
                    }
                }

                GUILayout.FlexibleSpace();
            }

            GUILayout.EndArea();
        }
Пример #9
0
        // used internally for playtest preview
        public static string SaveAllNodesAsString(int onlyWithParentID = -1)
        {
            var treeNodes = onlyWithParentID >= 0 ? MerinoData.GetAllCachedChildren(onlyWithParentID) : MerinoData.TreeElements;

            if (MerinoPrefs.validateNodeTitles)
            {
                ValidateNodeTitles(treeNodes);
            }

            var nodeInfo = new List <YarnSpinnerLoader.NodeInfo>();

            foreach (var treeNode in treeNodes)
            {
                if (treeNode.depth == -1 || treeNode.leafType != MerinoTreeElement.LeafType.Node)
                {
                    continue;
                }

                nodeInfo.Add(TreeNodeToYarnNode(treeNode));
            }

            return(YarnSpinnerFileFormatConverter.ConvertNodesToYarnText(nodeInfo));
        }
        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);
                }
            }
        }