Exemplo n.º 1
0
        private void ReallyDeleteLevel(int levelIndex)
        {
            // Remove the level from the campaign
            var levelPath   = m_campaign.Levels[levelIndex];
            var newCampaign = m_campaign.Copy();

            newCampaign.Levels.RemoveAll((s) => (s == levelPath));

            // Save the campaign
            var assetsPath       = (m_mod != null) ? Path.Combine(m_mod.Path, "assets") : Path.Combine(App.AssetPath, "main");
            var fullCampaignPath = Path.Combine(assetsPath, m_campaign.Path);

            newCampaign.Save(fullCampaignPath);

            // Delete the Level
            var fullLevelPath = Path.Combine(assetsPath, levelPath);

            if (File.Exists(fullLevelPath))
            {
                File.Delete(fullLevelPath);
            }

            // Delete the Thumbnail
            var thumbnailPath     = AssetPath.ChangeExtension(levelPath, "png");
            var fullThumbnailPath = Path.Combine(assetsPath, thumbnailPath);

            if (File.Exists(fullThumbnailPath))
            {
                File.Delete(fullThumbnailPath);
            }

            // Reopen the level select screen
            Assets.Reload(m_campaign.Path);
            CutToState(new LevelSelectState(Game, m_mod, m_campaign, m_page, -1, m_editor));
        }
Exemplo n.º 2
0
        private void EditScript()
        {
            // Assign a script to the level if there isn't one
            if (Level.Info.ScriptPath == null)
            {
                var levelPath  = LevelSavePath;
                var scriptPath = AssetPath.ChangeExtension(levelPath, "lua");
                if (scriptPath.IndexOf("levels/") == 0)
                {
                    scriptPath = "scripts/" + scriptPath.Substring("levels/".Length);
                }
                Level.Info.ScriptPath = scriptPath;
                m_modified            = true;

                SetStatus(Game.Language.Translate("menus.editor.script_changed", scriptPath));
            }

            // Create the script if it doesn't exist
            var fullScriptPath = (m_mod != null) ?
                                 Path.Combine(m_mod.Path, "assets/" + Level.Info.ScriptPath) :
                                 Path.Combine(App.AssetPath, "main/" + Level.Info.ScriptPath);

            if (!File.Exists(fullScriptPath))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(fullScriptPath));
                File.Copy(
                    Path.Combine(App.AssetPath, "base/scripts/template.lua"),
                    fullScriptPath
                    );
                Assets.Reload(Level.Info.ScriptPath);
            }

            // Edit the script
            Game.Network.OpenTextEditor(fullScriptPath);
        }
Exemplo n.º 3
0
        private void DeleteCampaign(Campaign campaign, Mod mod)
        {
            var dialog = DialogBox.CreateQueryBox(
                Game.Screen,
                Game.Language.Translate("menus.delete_mod_prompt.title"),
                Game.Language.Translate("menus.delete_mod_prompt.info", campaign.Title),
                new string[] {
                Game.Language.Translate("menus.yes"),
                Game.Language.Translate("menus.no"),
            },
                true
                );

            dialog.OnClosed += delegate(object sender2, DialogBoxClosedEventArgs e)
            {
                switch (e.Result)
                {
                case 0:
                {
                    // YES
                    // Delete the campaign
                    var assetPath = campaign.Path;
                    var fullPath  = Path.Combine(mod.Path, "assets/" + assetPath);
                    File.Delete(fullPath);

                    // Delete the levels and their thumbnails
                    for (int i = 0; i < campaign.Levels.Count; ++i)
                    {
                        var levelPath     = campaign.Levels[i];
                        var fullLevelPath = Path.Combine(mod.Path, "assets/" + levelPath);
                        if (File.Exists(fullLevelPath))
                        {
                            File.Delete(fullLevelPath);
                        }

                        var thumbnailPath     = AssetPath.ChangeExtension(levelPath, "png");
                        var fullThumbnailPath = AssetPath.Combine(mod.Path, "assets/" + thumbnailPath);
                        if (File.Exists(fullThumbnailPath))
                        {
                            File.Delete(fullThumbnailPath);
                        }
                    }

                    // Unload the campaign
                    Assets.Reload(assetPath);
                    var sources = Assets.GetSources(assetPath);
                    if (sources.Count == 0 || (sources.Count == 1 && sources.First() == m_mod.Assets))
                    {
                        Assets.Unload(assetPath);
                    }
                    m_campaigns.Refresh();
                    break;
                }
                }
            };
            ShowDialog(dialog);
        }
Exemplo n.º 4
0
        public LevelThumbnail(string levelPath, float width, float height, Language language) : base(Core.Render.Texture.White, width, height)
        {
            m_levelPath = levelPath;
            if (m_levelPath == "NEW")
            {
                m_levelTitle = language.Translate("menus.mod_select.create_new");
            }
            else if (Assets.Exists <LevelData>(levelPath))
            {
                m_levelTitle = Assets.Get <LevelData>(levelPath).Title;
            }
            else
            {
                m_levelTitle = "Untitled";
            }

            if (m_levelPath == "NEW")
            {
                m_texture = Core.Render.Texture.Get("gui/newlevel.png", false);
            }
            else
            {
                var thumbnailPath = AssetPath.ChangeExtension(levelPath, "png");
                if (Assets.Exists <Texture>(thumbnailPath))
                {
                    m_texture = Core.Render.Texture.Get(thumbnailPath, true);
                }
                else
                {
                    m_texture = Core.Render.Texture.Get("levels/template.png", true);
                }
            }
            m_geometry = new Geometry(Primitive.Triangles, 4, 6);

            m_deleteGeometry = new Geometry(Primitive.Triangles, 4, 6);
            m_moveGeometry   = new Geometry(Primitive.Triangles, 8, 12);
            m_iconGeometry   = new Geometry(Primitive.Triangles, 4, 6);

            m_highlight = false;
            m_locked    = false;
            m_completed = false;
            m_canDelete = false;

            m_justUnlocked  = false;
            m_justCompleted = false;
            m_animTime      = 0.0f;

            Colour = BORDER_COLOUR;
        }
Exemplo n.º 5
0
        public DiskSelector(Screen screen, string initialDiskPath, Mod initialDiskMod, Progress progress)
        {
            m_geometry      = new Geometry(Primitive.Triangles);
            m_disks         = ArcadeUtils.GetAllDisks().ToArray();
            m_disksUnlocked = m_disks.Select(disk => ArcadeUtils.IsDiskUnlocked(disk.Disk, disk.Mod, progress)).ToArray();

            m_page = 0;
            if (m_disks.Length <= COLUMNS_PER_PAGE)
            {
                m_numColumns = m_disks.Length;
                m_numRows    = 1;
            }
            else if (m_disks.Length < NUM_PER_PAGE)
            {
                m_numColumns = (m_disks.Length + ROWS_PER_PAGE - 1) / ROWS_PER_PAGE;
                m_numRows    = ROWS_PER_PAGE;
            }
            else
            {
                m_numColumns = COLUMNS_PER_PAGE;
                m_numRows    = ROWS_PER_PAGE;
            }
            m_highlight = -1;

            m_backPrompt                       = new InputPrompt(UIFonts.Smaller, screen.Language.Translate("menus.close"), TextAlignment.Right);
            m_backPrompt.Key                   = Key.Escape;
            m_backPrompt.MouseButton           = MouseButton.Left;
            m_backPrompt.GamepadButton         = GamepadButton.B;
            m_backPrompt.SteamControllerButton = SteamControllerButton.MenuBack;
            m_backPrompt.Anchor                = Anchor.BottomRight;
            m_backPrompt.LocalPosition         = new Vector2(-16.0f, -16.0f - m_backPrompt.Height);
            m_backPrompt.Parent                = this;
            m_backPrompt.OnClick              += delegate(object o, EventArgs args)
            {
                m_closeNextFrame = true;
            };

            m_selectPrompt                       = new InputPrompt(UIFonts.Smaller, screen.Language.Translate("menus.select"), TextAlignment.Left);
            m_selectPrompt.Key                   = Key.Return;
            m_selectPrompt.GamepadButton         = GamepadButton.A;
            m_selectPrompt.SteamControllerButton = SteamControllerButton.MenuSelect;
            m_selectPrompt.Anchor                = Anchor.BottomLeft;
            m_selectPrompt.LocalPosition         = new Vector2(16.0f, -16.0f - m_selectPrompt.Height);
            m_selectPrompt.Parent                = this;

            m_browseWorkshopPrompt                       = new InputPrompt(UIFonts.Smaller, screen.Language.Translate("menus.arcade.browse_workshop"), TextAlignment.Right);
            m_browseWorkshopPrompt.Key                   = Key.LeftCtrl;
            m_browseWorkshopPrompt.GamepadButton         = GamepadButton.Y;
            m_browseWorkshopPrompt.SteamControllerButton = SteamControllerButton.MenuAltSelect;
            m_browseWorkshopPrompt.Anchor                = Anchor.BottomRight;
            m_browseWorkshopPrompt.LocalPosition         = new Vector2(-16.0f, -16.0f - m_selectPrompt.Height - m_browseWorkshopPrompt.Height);
            m_browseWorkshopPrompt.Parent                = this;

            m_previousPageButton                               = new Button(Texture.Get("gui/arrows.png", true), 32.0f, 32.0f);
            m_previousPageButton.Region                        = new Quad(0.0f, 0.5f, 0.5f, 0.5f);
            m_previousPageButton.HighlightRegion               = m_previousPageButton.Region;
            m_previousPageButton.DisabledRegion                = m_previousPageButton.Region;
            m_previousPageButton.ShortcutButton                = GamepadButton.LeftBumper;
            m_previousPageButton.AltShortcutButton             = GamepadButton.LeftTrigger;
            m_previousPageButton.ShortcutSteamControllerButton = SteamControllerButton.MenuPreviousPage;
            m_previousPageButton.Colour                        = UIColours.Title;
            m_previousPageButton.HighlightColour               = UIColours.White;
            m_previousPageButton.DisabledColour                = m_previousPageButton.Colour;
            m_previousPageButton.Anchor                        = Anchor.CentreMiddle;
            m_previousPageButton.LocalPosition                 = new Vector2(
                -0.5f * (float)COLUMNS_PER_PAGE * (DISK_SIZE + DISK_PADDING) - m_previousPageButton.Width,
                -0.5f * m_previousPageButton.Height
                );
            m_previousPageButton.Parent     = this;
            m_previousPageButton.OnClicked += delegate(object o, EventArgs e)
            {
                PreviousPage();
            };

            m_nextPageButton                               = new Button(Texture.Get("gui/arrows.png", true), 32.0f, 32.0f);
            m_nextPageButton.Region                        = new Quad(0.0f, 0.0f, 0.5f, 0.5f);
            m_nextPageButton.HighlightRegion               = m_nextPageButton.Region;
            m_nextPageButton.DisabledRegion                = m_nextPageButton.Region;
            m_nextPageButton.ShortcutButton                = GamepadButton.RightBumper;
            m_nextPageButton.AltShortcutButton             = GamepadButton.RightTrigger;
            m_nextPageButton.ShortcutSteamControllerButton = SteamControllerButton.MenuNextPage;
            m_nextPageButton.Colour                        = UIColours.Title;
            m_nextPageButton.HighlightColour               = UIColours.White;
            m_nextPageButton.DisabledColour                = m_nextPageButton.Colour;
            m_nextPageButton.Anchor                        = Anchor.CentreMiddle;
            m_nextPageButton.LocalPosition                 = new Vector2(
                0.5f * (float)COLUMNS_PER_PAGE * (DISK_SIZE + DISK_PADDING),
                -0.5f * m_previousPageButton.Height
                );
            m_nextPageButton.Parent     = this;
            m_nextPageButton.OnClicked += delegate(object o, EventArgs e)
            {
                NextPage();
            };

            // Load labels
            m_diskLabels = new Texture[m_disks.Length];
            for (int i = 0; i < m_disks.Length; ++i)
            {
                var disk      = m_disks[i];
                var labelPath = AssetPath.ChangeExtension(disk.Disk.Path, "png");
                if (disk.Mod != null)
                {
                    if (disk.Mod.Assets.CanLoad(labelPath))
                    {
                        m_diskLabels[i]        = disk.Mod.Assets.Load <Texture>(labelPath);
                        m_diskLabels[i].Filter = false;
                    }
                }
                else
                {
                    m_diskLabels[i] = Texture.Get(labelPath, false);
                }
            }

            m_framesOpen     = 0;
            m_closeNextFrame = false;

            // Determine initial disk index
            m_initialDisk = -1;
            if (initialDiskPath != null && m_disks.Length > 0)
            {
                for (int i = 0; i < m_disks.Length; ++i)
                {
                    var disk = m_disks[i];
                    if (disk.Disk.Path == initialDiskPath &&
                        disk.Mod == initialDiskMod &&
                        m_disksUnlocked[i])
                    {
                        m_initialDisk = i;
                        break;
                    }
                }
            }
        }
Exemplo n.º 6
0
        private void ReallyPublishMod()
        {
            // Update game_version
            m_mod.MinimumGameVersion = App.Info.Version;
            m_mod.SaveInfo();

            // Check every level has a thumbnail and has been completed
            foreach (var campaign in Assets.Find <Campaign>("campaigns", m_mod.Assets))
            {
                for (int i = 0; i < campaign.Levels.Count; ++i)
                {
                    var levelPath = campaign.Levels[i];
                    var levelData = LevelData.Get(levelPath);
                    if (!levelData.EverCompleted)
                    {
                        ShowDialog(DialogBox.CreateQueryBox(
                                       Game.Screen,
                                       Game.Language.Translate("menus.publishing_error.title"),
                                       Game.Language.Translate("menus.publishing_error.level_not_completed", TranslateTitle(levelData.Title)),
                                       new string[] {
                            Game.Language.Translate("menus.ok")
                        }, true)
                                   );
                        return;
                    }

                    var levelThumbnailPath     = AssetPath.ChangeExtension(levelPath, "png");
                    var levelThumbnailFullPath = Path.Combine(m_mod.Path, "assets/" + levelThumbnailPath);
                    if (!File.Exists(levelThumbnailFullPath))
                    {
                        ShowDialog(DialogBox.CreateQueryBox(
                                       Game.Screen,
                                       Game.Language.Translate("menus.publishing_error.title"),
                                       Game.Language.Translate("menus.publishing_error.level_no_thumbnail", TranslateTitle(levelData.Title)),
                                       new string[] {
                            Game.Language.Translate("menus.ok")
                        }, true)
                                   );
                        return;
                    }
                }
            }

            // Find source images for the thumbnail
            var thumbnailSources    = new List <string>();
            var manualThumbnailPath = Path.Combine(m_mod.Path, "thumbnail.png");

            if (File.Exists(manualThumbnailPath))
            {
                // Use thumbnail.png
                thumbnailSources.Add(manualThumbnailPath);
            }
            else
            {
                // Use the thumbnails from the levels
                var campaigns = Assets.List <Campaign>("campaigns", m_mod.Assets).ToArray();
                for (int i = 0; i < campaigns.Length; ++i)
                {
                    var campaign = campaigns[i];
                    for (int j = 0; j < campaign.Levels.Count; ++j)
                    {
                        var levelPath              = campaign.Levels[j];
                        var levelThumbnailPath     = AssetPath.ChangeExtension(levelPath, "png");
                        var levelThumbnailFullPath = Path.Combine(m_mod.Path, "assets/" + levelThumbnailPath);
                        if (File.Exists(levelThumbnailFullPath))
                        {
                            thumbnailSources.Add(levelThumbnailFullPath);
                        }
                    }
                }
            }
            if (thumbnailSources.Count == 0)
            {
                ShowDialog(DialogBox.CreateQueryBox(
                               Game.Screen,
                               Game.Language.Translate("menus.publishing_error.title"),
                               Game.Language.Translate("menus.publishing_error.no_thumbnail"),
                               new string[] {
                    Game.Language.Translate("menus.ok")
                }, true)
                           );
                return;
            }

            // Generate the thumbnail
            var thumbnailPath = GenerateThumbnail(thumbnailSources);

            // Determine the tags
            var tags = new HashSet <string>();

            if (Assets.Find <LuaScript>("animation", m_mod.Assets).Count() > 0)
            {
                tags.Add("Custom Animation");
            }
            if (Assets.List <Campaign>("campaigns", m_mod.Assets).Count() > 0 &&
                Assets.Find <LevelData>("levels", m_mod.Assets).Count() > 0)
            {
                tags.Add("New Levels");
            }
            if (Assets.List <Language>("languages", m_mod.Assets).Where(lang => !lang.IsEnglish).Count() > 0)
            {
                tags.Add("Localisation");
            }
            if (Assets.Find <Model>("models", m_mod.Assets).Count() > 0 ||
                Assets.Find <Sky>("skies", m_mod.Assets).Count() > 0)
            {
                tags.Add("Custom Art");
            }
            if (Assets.Find <Sound>("sound", m_mod.Assets).Count() > 0 ||
                Assets.Find <Sound>("music", m_mod.Assets).Count() > 0)
            {
                tags.Add("Custom Audio");
            }
            if (Assets.List <ArcadeDisk>("arcade", m_mod.Assets).Count() > 0)
            {
                tags.Add("Arcade Games");
            }

            // Publish
            if (m_mod.SteamWorkshopID.HasValue)
            {
                // Update existing mod
                ulong id      = m_mod.SteamWorkshopID.Value;
                var   promise = Game.Network.Workshop.UpdateItem(
                    id,
                    "Modified with the Redirection Mod Editor",
                    filePath: m_mod.Path,
                    previewImagePath: thumbnailPath,
                    title: m_mod.Title,
                    tags: tags.ToArray()
                    );
                var progressDialog = PromiseDialogBox.Create(
                    Game.Language.Translate("menus.publishing.title"),
                    promise
                    );
                progressDialog.OnClosed += delegate(object sender2, DialogBoxClosedEventArgs e2)
                {
                    if (promise.Status == Status.Complete)
                    {
                        // Show the user the mod
                        ShowInWorkshop();

                        // Show a dialog
                        var completeDialog = DialogBox.CreateQueryBox(
                            Game.Screen,
                            Game.Language.Translate("menus.mod_published.title"),
                            promise.Result.AgreementNeeded ?
                            Game.Language.Translate("menus.mod_published.info_agreement_needed", m_mod.Title) :
                            Game.Language.Translate("menus.mod_published.info", m_mod.Title),
                            new string[] {
                            Game.Language.Translate("menus.ok")
                        },
                            true
                            );
                        ShowDialog(completeDialog);
                    }
                    else if (promise.Status == Status.Error)
                    {
                        // Show the user the error
                        var errorDialog = DialogBox.CreateQueryBox(
                            Game.Screen,
                            Game.Language.Translate("menus.publishing_error.title"),
                            promise.Error,
                            new string[] {
                            Game.Language.Translate("menus.ok")
                        },
                            true
                            );
                        ShowDialog(errorDialog);
                    }
                };
                ShowDialog(progressDialog);
            }
            else
            {
                // Create new mod
                var description = "Created with the Redirection Mod Editor";
                var promise     = Game.Network.Workshop.CreateItem(
                    m_mod.Path,
                    thumbnailPath,
                    m_mod.Title,
                    description,
                    tags.ToArray(),
                    true
                    );
                var progressDialog = PromiseDialogBox.Create(
                    Game.Language.Translate("menus.publishing.title"),
                    promise
                    );
                progressDialog.OnClosed += delegate(object sender2, DialogBoxClosedEventArgs e2)
                {
                    if (promise.Status == Status.Complete)
                    {
                        // Save the mod
                        ulong id = promise.Result.ID;
                        m_mod.SteamWorkshopID = id;
                        m_mod.SaveInfo();

                        // Show the user the mod
                        ShowInWorkshop();

                        // Show a dialog
                        var completeDialog = DialogBox.CreateQueryBox(
                            Game.Screen,
                            Game.Language.Translate("menus.mod_published.title"),
                            promise.Result.AgreementNeeded ?
                            Game.Language.Translate("menus.mod_published.info_agreement_needed", m_mod.Title) :
                            Game.Language.Translate("menus.mod_published.info", m_mod.Title),
                            new string[] {
                            Game.Language.Translate("menus.ok")
                        },
                            true
                            );
                        completeDialog.OnClosed += delegate
                        {
                            // Re-enter state so the new "show in workshop" entry appears
                            CutToState(new ModEditorState(Game, m_mod));
                        };
                        ShowDialog(completeDialog);
                    }
                    else if (promise.Status == Status.Error)
                    {
                        // Show the user the error
                        var errorDialog = DialogBox.CreateQueryBox(
                            Game.Screen,
                            Game.Language.Translate("menus.publishing_error.title"),
                            promise.Error,
                            new string[] {
                            Game.Language.Translate("menus.ok")
                        },
                            true
                            );
                        ShowDialog(errorDialog);
                    }
                };
                ShowDialog(progressDialog);
            }
        }
Exemplo n.º 7
0
        protected override void OnUpdate(float dt)
        {
            base.OnUpdate(dt);
            switch (m_state)
            {
            case SubState.Waiting:
            {
                // Wait for camera press
                if (CheckCameraButton())
                {
                    m_state = SubState.Capturing;
                    m_timer = 0.0f;
                }
                else if (CheckBack())
                {
                    BackToEditor();
                }
                break;
            }

            case SubState.Capturing:
            {
                // Take the screenshot
                // Stop the action
                CameraController.AllowUserZoom   = false;
                CameraController.AllowUserRotate = false;

                // Clear the screen
                m_cameraHUD.ShowViewfinder = false;
                m_prompt.Visible           = false;

                // Request the screenshot
                m_screenshot = Game.QueueScreenshot(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
                m_state      = SubState.CameraFlash;
                break;
            }

            case SubState.CameraFlash:
            {
                // Wait for screenshot, then flash
                if (m_screenshot.Status != Status.Waiting)
                {
                    // Do the effects
                    Game.Audio.PlayMusic(null, 0.0f);
                    m_cameraHUD.Flash();
                    m_state = SubState.Approving;
                    m_timer = FLASH_DURATION;
                }
                break;
            }

            case SubState.Approving:
            {
                // Wait for a timer, then prompt to save the screenshot
                if (m_timer > 0.0f)
                {
                    m_timer -= dt;
                    if (m_timer <= 0.0f)
                    {
                        // Create the texture
                        var screenshot = m_screenshot.Result;
                        var bitmap     = new BitmapTexture(screenshot.Bitmap);
                        bitmap.Filter = true;

                        // Show the dialog
                        var dialog = DialogBox.CreateImageQueryBox(
                            Game.Language.Translate("menus.create_thumbnail.confirm_prompt"),
                            bitmap,
                            427.0f,
                            240.0f,
                            new string[] {
                                Game.Language.Translate("menus.yes"),
                                Game.Language.Translate("menus.no"),
                            }
                            );
                        dialog.OnClosed += delegate(object sender, DialogBoxClosedEventArgs e)
                        {
                            // Handle the result
                            if (e.Result == 0)
                            {
                                // Yes
                                // Save the screenshot
                                var screenshotPath = AssetPath.ChangeExtension(m_levelSavePath, "png");
                                if (m_mod != null)
                                {
                                    var fullPath = Path.Combine(m_mod.Path, "assets/" + screenshotPath);
                                    screenshot.Save(fullPath);
                                }
                                else
                                {
                                    var fullPath = Path.Combine(App.AssetPath, "main/" + screenshotPath);
                                    screenshot.Save(fullPath);
                                }
                                Assets.Reload(screenshotPath);

                                // Save the camera position
                                Level.Info.CameraPitch    = MathHelper.RadiansToDegrees(CameraController.Pitch);
                                Level.Info.CameraYaw      = MathHelper.RadiansToDegrees(CameraController.Yaw);
                                Level.Info.CameraDistance = CameraController.Distance;
                                m_modified = true;

                                // Return
                                BackToEditor();
                            }
                            else if (e.Result == 1)
                            {
                                // No
                                TryAgain();
                            }
                            else
                            {
                                // Escape
                                BackToEditor();
                            }

                            // Dispose things we no longer need
                            bitmap.Dispose();
                            screenshot.Dispose();
                        };
                        ShowDialog(dialog);
                    }
                }
                break;
            }
            }
        }
Exemplo n.º 8
0
        private void Load(IFileStore store)
        {
            // Load the material infos
            var      dir             = AssetPath.GetDirectoryName(m_path);
            Material currentMaterial = null;

            using (var reader = store.OpenTextFile(m_path))
            {
                // For each line:
                string line;
                var    whitespace = new char[] { ' ', '\t' };
                while ((line = reader.ReadLine()) != null)
                {
                    // Strip comment
                    var commentIdx = line.IndexOf('#');
                    if (commentIdx >= 0)
                    {
                        line = line.Substring(0, commentIdx);
                    }

                    // Segment
                    var parts = line.Split(whitespace, StringSplitOptions.RemoveEmptyEntries);
                    if (parts.Length == 0)
                    {
                        continue;
                    }

                    // Parse
                    var type = parts[0].ToLowerInvariant();
                    switch (type)
                    {
                    case "newmtl":
                    {
                        var name = parts[1];
                        currentMaterial = new Material();
                        m_materials.Add(name, currentMaterial);
                        break;
                    }

                    case "map_ka":
                    {
                        var path = AssetPath.Combine(dir, parts[1]);
                        currentMaterial.EmissiveColour  = Vector3.One;
                        currentMaterial.EmissiveTexture = path;
                        break;
                    }

                    case "map_kd":
                    {
                        var path = AssetPath.Combine(dir, parts[1]);
                        currentMaterial.DiffuseColour  = Vector4.One;
                        currentMaterial.DiffuseTexture = path;
                        break;
                    }

                    case "map_ks":
                    {
                        var path = AssetPath.Combine(dir, parts[1]);
                        currentMaterial.SpecularColour  = Vector3.One;
                        currentMaterial.SpecularTexture = path;
                        break;
                    }

                    case "map_bump":
                    case "bump":
                    {
                        var path = AssetPath.Combine(dir, parts[1]);
                        currentMaterial.NormalTexture = path;
                        break;
                    }
                    }
                }
            }

            // Add missing material paths
            foreach (var material in m_materials.Values)
            {
                if (material.EmissiveTexture == null)
                {
                    material.EmissiveColour  = Vector3.One;
                    material.EmissiveTexture = "black.png";
                    if (material.DiffuseTexture != null)
                    {
                        var potentialPath = AssetPath.ChangeExtension(material.DiffuseTexture, "emit.png");
                        if (store.FileExists(potentialPath))
                        {
                            material.EmissiveTexture = potentialPath;
                        }
                    }
                }

                if (material.SpecularTexture == null)
                {
                    material.SpecularColour  = Vector3.One;
                    material.SpecularTexture = "black.png";
                    if (material.DiffuseTexture != null)
                    {
                        var potentialPath = AssetPath.ChangeExtension(material.DiffuseTexture, "spec.png");
                        if (store.FileExists(potentialPath))
                        {
                            material.SpecularTexture = potentialPath;
                        }
                    }
                }

                if (material.NormalTexture == null)
                {
                    material.NormalTexture = "flat.png";
                    if (material.DiffuseTexture != null)
                    {
                        var potentialPath = AssetPath.ChangeExtension(material.DiffuseTexture, "norm.png");
                        if (store.FileExists(potentialPath))
                        {
                            material.NormalTexture = potentialPath;
                        }
                    }
                }

                if (material.DiffuseTexture == null)
                {
                    material.DiffuseColour  = Vector4.One;
                    material.DiffuseTexture = "white.png";
                }
            }
        }