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)); }
// 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)); }
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); }
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(); }
// 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); } } }