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