public static void CheckFileList(IList <string> paths, List <string> allowedExtensions) { if (paths.Count == 0) { MerinoDebug.Log(LoggingLevel.Warning, "No files provided."); return; } var invalid = new List <string>(); foreach (var path in paths) { // Does this file exist? var exists = System.IO.File.Exists(path); // Does this file have the right extension? var hasAllowedExtension = allowedExtensions.FindIndex(item => path.EndsWith(item)) != -1; if (!exists || !hasAllowedExtension) { invalid.Add(string.Format("\"{0}\"", path)); } } if (invalid.Count != 0) { var message = string.Format("The file{0} {1} {2}.", invalid.Count == 1 ? "" : "s", string.Join(", ", invalid.ToArray()), invalid.Count == 1 ? "is not valid" : "are not valid" ); MerinoDebug.Log(LoggingLevel.Error, message); } }
IEnumerator RunDialogue(string startNode = "Start") { // Get lines, options and commands from the Dialogue object, one at a time. foreach (Yarn.Dialogue.RunnerResult step in dialogue.Run(startNode)) { CurrentNode = dialogue.currentNode; if (step is Yarn.Dialogue.LineResult) { // Wait for line to finish displaying var lineResult = step as Yarn.Dialogue.LineResult; yield return(this.StartCoroutine(RunLine(lineResult.line))); } else if (step is Yarn.Dialogue.OptionSetResult) { // Wait for user to finish picking an option var optionSetResult = step as Yarn.Dialogue.OptionSetResult; RunOptions(optionSetResult.options, optionSetResult.setSelectedOptionDelegate); yield return(new WaitWhile(() => inputOption < 0)); } else if (step is Yarn.Dialogue.CommandResult) { // Wait for command to finish running var commandResult = step as Yarn.Dialogue.CommandResult; yield return(this.StartCoroutine(RunCommand(commandResult.command))); } } MerinoDebug.Log(LoggingLevel.Info, "Reached the end of the dialogue."); CurrentNode = null; // No more results! The dialogue is done. yield return(new WaitUntil(() => MerinoPrefs.stopOnDialogueEnd)); StopPlaytest_Internal(); }
public static TextAsset GetDefaultData() { var defaultData = Resources.Load<TextAsset>(MerinoPrefs.newFileTemplatePath); if (defaultData == null) { MerinoDebug.Log(LoggingLevel.Warning, "Merino couldn't find the new file template at Resources/" + MerinoPrefs.newFileTemplatePath + ". Double-check the file exists there, or you can override this path in EditorPrefs."); return null; } return defaultData; }
// strip forbidden characters from node titles public static string CleanNodeTitle(string newName) { // v0.6, added regex to disallow forbidden characters in node titles string newNameClean = Regex.Replace(newName, forbiddenCharactersInNodeTitles_regex, ""); if (newName.Length != newNameClean.Length) { // GUI.Label( GUILayoutUtility.GetLastRect(), new GUIContent("Yarn node titles cannot use <>[]{}|:#$ or whitespace", helpIcon), EditorStyles.helpBox ); MerinoDebug.Log(LoggingLevel.Verbose, "Merino stripped forbidden characters <>[]{}|:#$ (and whitespace) from your node title.\n" + newName + " ... " + newNameClean); return(newNameClean); } return(newName); }
public static TextAsset LoadYarnFileAtFullPath(string path, bool isRelativePath=false) { var newFile = AssetDatabase.LoadAssetAtPath<TextAsset>( isRelativePath ? path : "Assets" + path.Substring(Application.dataPath.Length) ); if (MerinoData.CurrentFiles.Contains(newFile) == false) { MerinoData.CurrentFiles.Add(newFile); } else { MerinoDebug.Log(LoggingLevel.Warning, "Merino: file at " + path + " is already loaded!"); } return newFile; }
private void InitIfNeeded() { // create the main Dialogue runner, and pass our variableStorage to it varStorage = new MerinoVariableStorage(); dialogue = new Yarn.Dialogue(varStorage); // setup the logging system. dialogue.LogDebugMessage = message => MerinoDebug.Log(LoggingLevel.Verbose, message); dialogue.LogErrorMessage = PlaytestErrorLog; // icons if (errorIcon == null) { errorIcon = EditorGUIUtility.Load("icons/d_console.erroricon.sml.png") as Texture; } }
/// <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 ConvertNodesInFile(ConvertFormatOptions options, string file, string fileExtension, ConvertNodesToText convert) { // var d = new Dialogue(null); var text = File.ReadAllText(file); IEnumerable <YarnSpinnerLoader.NodeInfo> nodes; try { nodes = YarnSpinnerLoader.GetNodesFromText(text, YarnSpinnerLoader.GetFormatFromFileName(file)); } catch (FormatException e) { MerinoDebug.Log(LoggingLevel.Error, e.Message); return; } var serialisedText = convert(nodes); var destinationDirectory = options.outputDirectory; if (destinationDirectory == null) { destinationDirectory = Path.GetDirectoryName(file); } var fileName = Path.GetFileName(file); // ChangeExtension thinks that the file "Foo.yarn.txt" has the extension "txt", so // to simplify things, just lop that extension off right away if it's there fileName = fileName.Replace(".yarn.txt", ""); // change the filename's extension fileName = Path.ChangeExtension(fileName, fileExtension); // figure out where we're writing this file var destinationFilePath = Path.Combine(destinationDirectory, fileName); File.WriteAllText(destinationFilePath, serialisedText); if (options.verbose) { MerinoDebug.Log(LoggingLevel.Verbose, "Wrote " + destinationFilePath); } }
public static void SaveDataToFiles() { if (MerinoData.CurrentFiles.Count > 0) { foreach (var file in MerinoData.CurrentFiles) { if (MerinoData.FileToNodeID.ContainsKey(file)) { 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."); } } } }
/// <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); } // also output to Unity console var nodeRef = MerinoData.TreeElements.Find(x => x.name == nodeName); 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); } }
// ensure unique node titles, very important for YarnSpinner private static void ValidateNodeTitles(List <MerinoTreeElement> nodes = null) { if (!MerinoPrefs.validateNodeTitles) { return; } if (nodes == null) // if null, then let's just use all currently loaded nodes { nodes = MerinoData.TreeElements; } // make sure we're not doing this to any folder or file nodes, ONLY YARN NODES nodes = nodes.Where(x => x.leafType == MerinoTreeElement.LeafType.Node).ToList(); // validate data: ensure unique node names var nodeTitles = new Dictionary <int, string>(); // index, newTitle var duplicateCount = new Dictionary <string, int>(); // increment counter for each duplicate name, and use for rename suffix bool foundDuplicate = false; for (int i = 0; i < nodes.Count; i++) { // if there's a node already with that name, then append unique suffix if (nodeTitles.Values.Contains(nodes[i].name)) { // count duplicates if (!duplicateCount.ContainsKey(nodes[i].name)) { duplicateCount.Add(nodes[i].name, 2); } // when we make a new name, we have to ensure it's unique... if (nodeTitles.ContainsKey(nodes[i].id) == false) { nodeTitles.Add(nodes[i].id, nodes[i].name); } nodeTitles[nodes[i].id] = nodes[i].name + "_" + duplicateCount[nodes[i].name]++; foundDuplicate = true; } // but if there's not already a node with that name, we should still make a note of it else if (nodeTitles.ContainsKey(nodes[i].id) == false) { nodeTitles.Add(nodes[i].id, nodes[i].name); } } if (foundDuplicate) { string renamedNodes = "Merino found nodes with duplicate names (which aren't allowed for Yarn) and renamed them. This might break node links, you can undo it. The following nodes were renamed: "; Undo.RecordObject(MerinoData.Instance, "Merino: AutoRename"); foreach (var kvp in nodeTitles) { if (MerinoData.TreeElements[kvp.Key].name != kvp.Value) { renamedNodes += string.Format("\n* {0} > {1}", MerinoData.TreeElements[kvp.Key].name, kvp.Value); MerinoData.TreeElements[kvp.Key].name = kvp.Value; } } EditorUtility.SetDirty(MerinoData.Instance); MerinoDebug.Log(LoggingLevel.Warning, renamedNodes); //todo: repaint MerinoEditorWindow tree view so names get updated // this is bad, but we're gonna do some recursion here, just to make extra sure there's STILL no duplicates... ValidateNodeTitles(MerinoData.TreeElements); } else if (MerinoPrefs.useAutosave) { SaveDataToFiles(); } }