public void Transcode()
        {
            if (this.Transcoder == null)
            {
                throw new System.NotImplementedException("StoryImporter.Transcoder must be set by the importer implementation.");
            }

            this.Metadata = this.Transcoder.GetMetadata();

            // Load macro types
            Macros    = new Dictionary <string, MacroDef>(StringComparer.OrdinalIgnoreCase);
            MacroLibs = new HashSet <MacroLib>(LoadMacrosInto(Macros, this.Metadata.StoryBaseType));

            this.Transcoder.Init();

            for (int i = 0; i < this.Passages.Count; i++)
            {
                CurrentPassage = this.Passages[i];

                CurrentPassage.Tags = Regex.Replace(CurrentPassage.Tags, @"([^\s]+)", "\"$&\",");

                try
                {
                    CurrentPassage.Code = this.Transcoder.PassageToCode(CurrentPassage);
                }
                catch (StoryFormatTranscodeException ex)
                {
                    ex.Passage = CurrentPassage.Name;
                    throw;
                }
            }
        }
Beispiel #2
0
        static void OnPostprocessAllAssets(
            string[] importedAssets,
            string[] deletedAssets,
            string[] movedAssets,
            string[] movedFromAssetPaths)
        {
            foreach (string assetPath in importedAssets)
            {
                // ======================================
                // Choose the importer for this file type

                string ext = Path.GetExtension(assetPath).ToLower();
                if (string.IsNullOrEmpty(ext))
                {
                    continue;
                }

                // Get the right importer for this type
                ext = ext.Substring(1);
                StoryImporter importer = null;
                Type          importerType;
                if (!ImporterTypes.TryGetValue(ext, out importerType))
                {
                    continue;
                }

                importer           = (StoryImporter)Activator.CreateInstance(importerType);
                importer.AssetPath = assetPath;

                // ======================================
                // Validate the file

                // Check that the file is relevant
                if (!importer.IsAssetRelevant())
                {
                    return;
                }

                // Check that the story name is valid
                string fileNameExt = Path.GetFileName(assetPath);
                string fileName    = Path.GetFileNameWithoutExtension(assetPath);
                string storyName   = NameSanitizer.Replace(fileName, string.Empty);
                if (storyName != fileName)
                {
                    Debug.LogErrorFormat("The story cannot be imported because \"{0}\" is not a valid Unity script name.", fileName);
                    continue;
                }

                // ======================================
                // Initialize the importer - load data and choose transcoder

                try
                {
                    importer.Initialize();
                }
                catch (StoryImportException ex)
                {
                    Debug.LogErrorFormat("Story import failed: {0} ({1})", ex.Message, fileNameExt);
                    continue;
                }

                // ======================================
                // Run the transcoder

                try
                {
                    importer.Transcode();
                }
                catch (StoryFormatTranscodeException ex)
                {
                    Debug.LogErrorFormat("Story format transcoding failed at passage {0}: {1} ({2})", ex.Passage, ex.Message, fileNameExt);
                    continue;
                }

                // ======================================
                // Generate code

                StoryFormatMetadata   storyFormatMetadata = importer.Metadata;
                TemplatePassageData[] passageData         = importer.Passages.Select(p => new TemplatePassageData()
                {
                    Pid       = p.Pid,
                    Name      = p.Name.Replace("\"", "\"\""),
                    Tags      = p.Tags,
                    Code      = p.Code.Main.Split(new string[] { Environment.NewLine }, StringSplitOptions.None),
                    Fragments = p.Code.Fragments.Select((frag, i) => new TemplatePassageFragment()
                    {
                        Pid    = p.Pid,
                        FragId = i,
                        Code   = frag.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
                    }).ToArray()
                }).ToArray();

                // Get template file from this editor script's directory
                string output = Nustache.Core.Render.FileToString(
                    EditorFileUtil.FindFile("Story.template"),
                    new Dictionary <string, object>()
                {
                    { "version", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() },
                    { "originalFile", Path.GetFileName(assetPath) },
                    { "storyFormatName", storyFormatMetadata.StoryFormatName },
                    { "storyFormatNamespace", storyFormatMetadata.StoryBaseType.Namespace },
                    { "storyFormatClass", storyFormatMetadata.StoryBaseType.FullName },
                    { "storyName", storyName },
                    { "startPassage", storyFormatMetadata.StartPassage ?? "Start" },
                    { "vars", importer.Vars },
                    { "macroLibs", importer.MacroLibs },
                    { "strictMode", storyFormatMetadata.StrictMode ? "true" : "false" },
                    { "passages", passageData }
                }
                    );

                // ======================================
                // Compile

                // Unity bug fix: This environment variable is not set on Mac for some reason - https://github.com/AngryAnt/Behave-release/issues/21
                if (Application.platform == RuntimePlatform.OSXEditor)
                {
                    string monoBinPath = Path.Combine(EditorApplication.applicationContentsPath, "Frameworks/Mono/bin");

                    // Unity 5.4 and up
                    if (!Directory.Exists(monoBinPath))
                    {
                        monoBinPath = Path.Combine(EditorApplication.applicationContentsPath, "Mono/bin");
                    }

                    // Huh?
                    if (!Directory.Exists(monoBinPath))
                    {
                        Debug.LogError("For some reason I can't find the Mono directory inside Unity.app. Please open an issue on github.com/daterre/Cradle");
                    }

                    Environment.SetEnvironmentVariable("PATH", string.Format("{0}:{1}",
                                                                             Environment.GetEnvironmentVariable("PATH"),
                                                                             monoBinPath
                                                                             ));
                }

                // Detect syntax errors

                var compilerSettings = new CompilerParameters()
                {
                    GenerateInMemory   = true,
                    GenerateExecutable = false
                };
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    if (!string.IsNullOrEmpty(assembly.Location))
                    {
                        compilerSettings.ReferencedAssemblies.Add(assembly.Location);
                    }
                }

                var results = new CSharpCodeProvider().CompileAssemblyFromSource(compilerSettings, output);

                if (results.Errors.Count > 0)
                {
                    int errorLineOffset = Application.platform == RuntimePlatform.OSXEditor ? 3 : 4;

                    bool errors = false;
                    for (int i = 0; i < results.Errors.Count; i++)
                    {
                        CompilerError error = results.Errors[i];

                        switch (error.ErrorNumber)
                        {
                        //case "CS0246":
                        case "CS0103":
                        case "":
                            continue;

                        // Single quotes instead of double quotes
                        case "CS1012":
                            error.ErrorText = "Strings must use \"double-quotes\", not 'single-quotes'.";
                            break;

                        // Double var accessor
                        case "CS1612":
                            error.ErrorText = "Can't set a nested property directly like that. Use a temporary variable in-between.";
                            break;
                        }

                        // Error only if not a warning
                        errors |= !error.IsWarning;

                        try
                        {
                            // Get some compilation metadata - relies on the template using the #frag# token
                            string[]            errorDirective = error.FileName.Split(new string[] { "#frag#" }, StringSplitOptions.None);
                            string              errorPassage   = errorDirective[0];
                            int                 errorFragment  = errorDirective.Length > 1 ? int.Parse(errorDirective[1]) : -1;
                            TemplatePassageData passage        = passageData.Where(p => p.Name == errorPassage).FirstOrDefault();
                            string              lineCode       = passage == null || error.Line < errorLineOffset ? "(code not available)" : errorFragment >= 0 ?
                                                                 passage.Fragments[errorFragment].Code[error.Line - errorLineOffset] :
                                                                 passage.Code[error.Line - errorLineOffset];

                            if (error.IsWarning)
                            {
                                Debug.LogWarningFormat("Story compilation warning at passage '{0}': {1}\n\n\t{2}\n",
                                                       errorPassage,
                                                       error.ErrorText,
                                                       lineCode
                                                       );
                            }
                            else
                            {
                                Debug.LogErrorFormat("Story compilation error at passage '{0}': {1}\n\n\t{2}\n",
                                                     errorPassage,
                                                     error.ErrorText,
                                                     lineCode
                                                     );
                            }
                        }
                        catch
                        {
                            if (error.IsWarning)
                            {
                                Debug.LogWarningFormat("Story compilation warning: {0}\n",
                                                       error.ErrorText
                                                       );
                            }
                            else
                            {
                                Debug.LogErrorFormat("Story compilation error: {0}\n",
                                                     error.ErrorText
                                                     );
                            }
                        }
                    }

                    if (errors)
                    {
                        Debug.LogErrorFormat("The story {0} has some errors and can't be imported (see console for details).",
                                             Path.GetFileName(assetPath)
                                             );
                        continue;
                    }
                }

                // Remove custom line directives so they won't interfere with debugging the final script
                output = Regex.Replace(output, @"^\s*\#line\b.*$", string.Empty, RegexOptions.Multiline);

                // Passed syntax check, save to file
                string csFile = Path.Combine(Path.GetDirectoryName(assetPath), Path.GetFileNameWithoutExtension(assetPath) + ".cs");
                File.WriteAllText(csFile, output, Encoding.UTF8);

                // Need to do it twice - on the second time it compiles the script
                // http://answers.unity3d.com/questions/14367/how-can-i-wait-for-unity-to-recompile-during-the-e.html
                AssetDatabase.ImportAsset(csFile, ImportAssetOptions.ForceSynchronousImport);
                AssetDatabase.ImportAsset(csFile, ImportAssetOptions.ForceSynchronousImport);

                // ======================================
                // Organize the assets

                #region Disabled prefab creation because the story class can't be added during this asset import

                /*
                 * // Find the story class
                 * string projectDir = Directory.GetParent((Path.GetFullPath(Application.dataPath))).FullName;
                 * Type storyClass = null;
                 * foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                 * {
                 *  // Skip references external to the project
                 *  if (!string.IsNullOrEmpty(assembly.Location) && !Path.GetFullPath(assembly.Location).StartsWith(projectDir, StringComparison.OrdinalIgnoreCase))
                 *      continue;
                 *  foreach (Type type in assembly.GetTypes())
                 *  {
                 *      if (type.Name == storyName)
                 *      {
                 *          storyClass = type;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (storyClass != null)
                 *      break;
                 * }
                 *
                 * if (storyClass == null)
                 * {
                 *  Debug.LogWarning("UnityTwine successfully imported the story, but a prefab couldn't be made for you. Sorry :(");
                 *  continue;
                 * }
                 *
                 * // Create a prefab
                 * var prefab = new GameObject();
                 * prefab.name = storyName;
                 * prefab.AddComponent(storyClass);
                 *
                 * PrefabUtility.CreatePrefab(Path.Combine(Path.GetDirectoryName(assetPath), Path.GetFileNameWithoutExtension(assetPath) + ".prefab"), prefab, ReplacePrefabOptions.Default);
                 */
                #endregion
            }
        }