Ejemplo n.º 1
0
        internal static void UpgradeMapFormat(ModData modData, IReadWritePackage package)
        {
            if (package == null)
            {
                return;
            }

            var yamlStream = package.GetStream("map.yaml");

            if (yamlStream == null)
            {
                return;
            }

            var yaml      = new MiniYaml(null, MiniYaml.FromStream(yamlStream, package.Name));
            var nd        = yaml.ToDictionary();
            var mapFormat = FieldLoader.GetValue <int>("MapFormat", nd["MapFormat"].Value);

            if (mapFormat < 11)
            {
                throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(mapFormat, package.Name));
            }

            if (mapFormat < Map.SupportedMapFormat)
            {
                yaml.Nodes.First(n => n.Key == "MapFormat").Value = new MiniYaml(Map.SupportedMapFormat.ToString());
                Console.WriteLine("Converted {0} to MapFormat {1}.", package.Name, Map.SupportedMapFormat);
            }

            package.Update("map.yaml", Encoding.UTF8.GetBytes(yaml.Nodes.WriteToString()));
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Run a given update rule on a map.
        /// The rule is only applied to internal files - external includes are assumed to be handled separately
        /// but are noted in the externalFilenames list for informational purposes.
        /// </summary>
        public static List <string> UpdateMap(ModData modData, IReadWritePackage mapPackage, UpdateRule rule, out YamlFileSet files, HashSet <string> externalFilenames)
        {
            var manualSteps = new List <string>();

            using (var mapStream = mapPackage.GetStream("map.yaml"))
            {
                if (mapStream == null)
                {
                    // Not a valid map
                    files = new YamlFileSet();
                    return(manualSteps);
                }

                var yaml = new MiniYaml(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false));
                files = new YamlFileSet()
                {
                    Tuple.Create(mapPackage, "map.yaml", yaml.Nodes)
                };

                manualSteps.AddRange(rule.BeforeUpdate(modData));

                var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
                if (mapRulesNode != null)
                {
                    var mapRules = LoadInternalMapYaml(modData, mapPackage, mapRulesNode.Value, externalFilenames);
                    manualSteps.AddRange(ApplyTopLevelTransform(modData, mapRules, rule.UpdateActorNode));
                    files.AddRange(mapRules);
                }

                var mapWeaponsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Weapons");
                if (mapWeaponsNode != null)
                {
                    var mapWeapons = LoadInternalMapYaml(modData, mapPackage, mapWeaponsNode.Value, externalFilenames);
                    manualSteps.AddRange(ApplyTopLevelTransform(modData, mapWeapons, rule.UpdateWeaponNode));
                    files.AddRange(mapWeapons);
                }

                var mapSequencesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sequences");
                if (mapSequencesNode != null)
                {
                    var mapSequences = LoadInternalMapYaml(modData, mapPackage, mapSequencesNode.Value, externalFilenames);
                    manualSteps.AddRange(ApplyTopLevelTransform(modData, mapSequences, rule.UpdateWeaponNode));
                    files.AddRange(mapSequences);
                }

                manualSteps.AddRange(rule.AfterUpdate(modData));
            }

            return(manualSteps);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Loads a YamlFileSet containing any internal definitions yaml referenced by a map yaml block.
        /// External references or internal references to missing files are ignored.
        /// </summary>
        static YamlFileSet LoadInternalMapYaml(ModData modData, IReadWritePackage mapPackage, MiniYaml yaml, HashSet <string> externalFilenames)
        {
            var fileSet = new YamlFileSet()
            {
                (null, "map.yaml", yaml.Nodes)
            };

            var files = FieldLoader.GetValue <string[]>("value", yaml.Value);

            foreach (var filename in files)
            {
                // Ignore any files that aren't in the map bundle
                if (!filename.Contains("|") && mapPackage.Contains(filename))
                {
                    fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false)));
                }
                else if (modData.ModFiles.Exists(filename))
                {
                    externalFilenames.Add(filename);
                }
            }

            return(fileSet);
        }
Ejemplo n.º 4
0
        internal static void UpgradeMapFormat(ModData modData, IReadWritePackage package)
        {
            if (package == null)
            {
                return;
            }

            var yamlStream = package.GetStream("map.yaml");

            if (yamlStream == null)
            {
                return;
            }

            var yaml      = new MiniYaml(null, MiniYaml.FromStream(yamlStream, package.Name));
            var nd        = yaml.ToDictionary();
            var mapFormat = FieldLoader.GetValue <int>("MapFormat", nd["MapFormat"].Value);

            if (mapFormat < 6)
            {
                throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(mapFormat, package.Name));
            }

            // Format 6 -> 7 combined the Selectable and UseAsShellmap flags into the Class enum
            if (mapFormat < 7)
            {
                MiniYaml useAsShellmap;
                if (nd.TryGetValue("UseAsShellmap", out useAsShellmap) && bool.Parse(useAsShellmap.Value))
                {
                    yaml.Nodes.Add(new MiniYamlNode("Visibility", new MiniYaml("Shellmap")));
                }
                else if (nd["Type"].Value == "Mission" || nd["Type"].Value == "Campaign")
                {
                    yaml.Nodes.Add(new MiniYamlNode("Visibility", new MiniYaml("MissionSelector")));
                }
            }

            // Format 7 -> 8 replaced normalized HSL triples with rgb(a) hex colors
            if (mapFormat < 8)
            {
                var players = yaml.Nodes.FirstOrDefault(n => n.Key == "Players");
                if (players != null)
                {
                    bool noteHexColors = false;
                    bool noteColorRamp = false;
                    foreach (var player in players.Value.Nodes)
                    {
                        var colorRampNode = player.Value.Nodes.FirstOrDefault(n => n.Key == "ColorRamp");
                        if (colorRampNode != null)
                        {
                            Color dummy;
                            var   parts = colorRampNode.Value.Value.Split(',');
                            if (parts.Length == 3 || parts.Length == 4)
                            {
                                // Try to convert old normalized HSL value to a rgb hex color
                                try
                                {
                                    HSLColor color = new HSLColor(
                                        (byte)Exts.ParseIntegerInvariant(parts[0].Trim()).Clamp(0, 255),
                                        (byte)Exts.ParseIntegerInvariant(parts[1].Trim()).Clamp(0, 255),
                                        (byte)Exts.ParseIntegerInvariant(parts[2].Trim()).Clamp(0, 255));
                                    colorRampNode.Value.Value = FieldSaver.FormatValue(color);
                                    noteHexColors             = true;
                                }
                                catch (Exception)
                                {
                                    throw new InvalidDataException("Invalid ColorRamp value.\n File: " + package.Name);
                                }
                            }
                            else if (parts.Length != 1 || !HSLColor.TryParseRGB(parts[0], out dummy))
                            {
                                throw new InvalidDataException("Invalid ColorRamp value.\n File: " + package.Name);
                            }

                            colorRampNode.Key = "Color";
                            noteColorRamp     = true;
                        }
                    }

                    if (noteHexColors)
                    {
                        Console.WriteLine("ColorRamp is now called Color and uses rgb(a) hex value - rrggbb[aa].");
                    }
                    else if (noteColorRamp)
                    {
                        Console.WriteLine("ColorRamp is now called Color.");
                    }
                }
            }

            // Format 8 -> 9 moved map options and videos from the map file itself to traits
            if (mapFormat < 9)
            {
                var rules     = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
                var worldNode = rules.Value.Nodes.FirstOrDefault(n => n.Key == "World");
                if (worldNode == null)
                {
                    worldNode = new MiniYamlNode("World", new MiniYaml("", new List <MiniYamlNode>()));
                }

                var playerNode = rules.Value.Nodes.FirstOrDefault(n => n.Key == "Player");
                if (playerNode == null)
                {
                    playerNode = new MiniYamlNode("Player", new MiniYaml("", new List <MiniYamlNode>()));
                }

                var visibilityNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Visibility");
                if (visibilityNode != null)
                {
                    var visibility = FieldLoader.GetValue <MapVisibility>("Visibility", visibilityNode.Value.Value);
                    if (visibility.HasFlag(MapVisibility.MissionSelector))
                    {
                        var missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List <MiniYamlNode>()));
                        worldNode.Value.Nodes.Add(missionData);

                        var description = yaml.Nodes.FirstOrDefault(n => n.Key == "Description");
                        if (description != null)
                        {
                            missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", description.Value.Value));
                        }

                        var videos = yaml.Nodes.FirstOrDefault(n => n.Key == "Videos");
                        if (videos != null && videos.Value.Nodes.Any())
                        {
                            var backgroundVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "BackgroundInfo");
                            if (backgroundVideo != null)
                            {
                                missionData.Value.Nodes.Add(new MiniYamlNode("BackgroundVideo", backgroundVideo.Value.Value));
                            }

                            var briefingVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "Briefing");
                            if (briefingVideo != null)
                            {
                                missionData.Value.Nodes.Add(new MiniYamlNode("BriefingVideo", briefingVideo.Value.Value));
                            }

                            var startVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameStart");
                            if (startVideo != null)
                            {
                                missionData.Value.Nodes.Add(new MiniYamlNode("StartVideo", startVideo.Value.Value));
                            }

                            var winVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameWon");
                            if (winVideo != null)
                            {
                                missionData.Value.Nodes.Add(new MiniYamlNode("WinVideo", winVideo.Value.Value));
                            }

                            var lossVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameLost");
                            if (lossVideo != null)
                            {
                                missionData.Value.Nodes.Add(new MiniYamlNode("LossVideo", lossVideo.Value.Value));
                            }
                        }
                    }
                }

                var mapOptions = yaml.Nodes.FirstOrDefault(n => n.Key == "Options");
                if (mapOptions != null)
                {
                    var cheats = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Cheats");
                    if (cheats != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("DeveloperMode", new MiniYaml("", new List <MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                            new MiniYamlNode("Enabled", cheats.Value.Value)
                        })));
                    }

                    var crates = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Crates");
                    if (crates != null && !worldNode.Value.Nodes.Any(n => n.Key == "-CrateSpawner"))
                    {
                        if (!FieldLoader.GetValue <bool>("crates", crates.Value.Value))
                        {
                            worldNode.Value.Nodes.Add(new MiniYamlNode("-CrateSpawner", new MiniYaml("")));
                        }
                    }

                    var creeps = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Creeps");
                    if (creeps != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("MapCreeps", new MiniYaml("", new List <MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                            new MiniYamlNode("Enabled", creeps.Value.Value)
                        })));
                    }

                    var fog    = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Fog");
                    var shroud = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Shroud");
                    if (fog != null || shroud != null)
                    {
                        var shroudNode = new MiniYamlNode("Shroud", new MiniYaml("", new List <MiniYamlNode>()));
                        playerNode.Value.Nodes.Add(shroudNode);

                        if (fog != null)
                        {
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("FogLocked", "True"));
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("FogEnabled", fog.Value.Value));
                        }

                        if (shroud != null)
                        {
                            var enabled = FieldLoader.GetValue <bool>("shroud", shroud.Value.Value);
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("ExploredMapLocked", "True"));
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("ExploredMapEnabled", FieldSaver.FormatValue(!enabled)));
                        }
                    }

                    var allyBuildRadius = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "AllyBuildRadius");
                    if (allyBuildRadius != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("MapBuildRadius", new MiniYaml("", new List <MiniYamlNode>()
                        {
                            new MiniYamlNode("AllyBuildRadiusLocked", "True"),
                            new MiniYamlNode("AllyBuildRadiusEnabled", allyBuildRadius.Value.Value)
                        })));
                    }

                    var startingCash = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "StartingCash");
                    if (startingCash != null)
                    {
                        playerNode.Value.Nodes.Add(new MiniYamlNode("PlayerResources", new MiniYaml("", new List <MiniYamlNode>()
                        {
                            new MiniYamlNode("DefaultCashLocked", "True"),
                            new MiniYamlNode("DefaultCash", startingCash.Value.Value)
                        })));
                    }

                    var startingUnits = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "ConfigurableStartingUnits");
                    if (startingUnits != null && !worldNode.Value.Nodes.Any(n => n.Key == "-SpawnMPUnits"))
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("SpawnMPUnits", new MiniYaml("", new List <MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                        })));
                    }

                    var techLevel    = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "TechLevel");
                    var difficulties = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Difficulties");
                    var shortGame    = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "ShortGame");
                    if (techLevel != null || difficulties != null || shortGame != null)
                    {
                        var optionsNode = new MiniYamlNode("MapOptions", new MiniYaml("", new List <MiniYamlNode>()));
                        worldNode.Value.Nodes.Add(optionsNode);

                        if (techLevel != null)
                        {
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("TechLevelLocked", "True"));
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("TechLevel", techLevel.Value.Value));
                        }

                        if (difficulties != null)
                        {
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("Difficulties", difficulties.Value.Value));
                        }

                        if (shortGame != null)
                        {
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("ShortGameLocked", "True"));
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("ShortGameEnabled", shortGame.Value.Value));
                        }
                    }
                }

                if (worldNode.Value.Nodes.Any() && !rules.Value.Nodes.Contains(worldNode))
                {
                    rules.Value.Nodes.Add(worldNode);
                }

                if (playerNode.Value.Nodes.Any() && !rules.Value.Nodes.Contains(playerNode))
                {
                    rules.Value.Nodes.Add(playerNode);
                }
            }

            // Format 9 -> 10 moved smudges to SmudgeLayer, and uses map.png for all maps
            if (mapFormat < 10)
            {
                ExtractSmudges(yaml);
                if (package.Contains("map.png"))
                {
                    yaml.Nodes.Add(new MiniYamlNode("LockPreview", new MiniYaml("True")));
                }
            }

            // Format 10 -> 11 replaced the single map type field with a list of categories
            if (mapFormat < 11)
            {
                var type = yaml.Nodes.First(n => n.Key == "Type");
                yaml.Nodes.Add(new MiniYamlNode("Categories", type.Value));
                yaml.Nodes.Remove(type);
            }

            if (mapFormat < Map.SupportedMapFormat)
            {
                yaml.Nodes.First(n => n.Key == "MapFormat").Value = new MiniYaml(Map.SupportedMapFormat.ToString());
                Console.WriteLine("Converted {0} to MapFormat {1}.", package.Name, Map.SupportedMapFormat);
            }

            package.Update("map.yaml", Encoding.UTF8.GetBytes(yaml.Nodes.WriteToString()));
        }
Ejemplo n.º 5
0
        internal static void UpgradeMapFormat(ModData modData, IReadWritePackage package)
        {
            if (package == null)
                return;

            var yamlStream = package.GetStream("map.yaml");
            if (yamlStream == null)
                return;

            var yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, package.Name));
            var nd = yaml.ToDictionary();
            var mapFormat = FieldLoader.GetValue<int>("MapFormat", nd["MapFormat"].Value);
            if (mapFormat < 6)
                throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(mapFormat, package.Name));

            // Format 6 -> 7 combined the Selectable and UseAsShellmap flags into the Class enum
            if (mapFormat < 7)
            {
                MiniYaml useAsShellmap;
                if (nd.TryGetValue("UseAsShellmap", out useAsShellmap) && bool.Parse(useAsShellmap.Value))
                    yaml.Nodes.Add(new MiniYamlNode("Visibility", new MiniYaml("Shellmap")));
                else if (nd["Type"].Value == "Mission" || nd["Type"].Value == "Campaign")
                    yaml.Nodes.Add(new MiniYamlNode("Visibility", new MiniYaml("MissionSelector")));
            }

            // Format 7 -> 8 replaced normalized HSL triples with rgb(a) hex colors
            if (mapFormat < 8)
            {
                var players = yaml.Nodes.FirstOrDefault(n => n.Key == "Players");
                if (players != null)
                {
                    bool noteHexColors = false;
                    bool noteColorRamp = false;
                    foreach (var player in players.Value.Nodes)
                    {
                        var colorRampNode = player.Value.Nodes.FirstOrDefault(n => n.Key == "ColorRamp");
                        if (colorRampNode != null)
                        {
                            Color dummy;
                            var parts = colorRampNode.Value.Value.Split(',');
                            if (parts.Length == 3 || parts.Length == 4)
                            {
                                // Try to convert old normalized HSL value to a rgb hex color
                                try
                                {
                                    HSLColor color = new HSLColor(
                                        (byte)Exts.ParseIntegerInvariant(parts[0].Trim()).Clamp(0, 255),
                                        (byte)Exts.ParseIntegerInvariant(parts[1].Trim()).Clamp(0, 255),
                                        (byte)Exts.ParseIntegerInvariant(parts[2].Trim()).Clamp(0, 255));
                                    colorRampNode.Value.Value = FieldSaver.FormatValue(color);
                                    noteHexColors = true;
                                }
                                catch (Exception)
                                {
                                    throw new InvalidDataException("Invalid ColorRamp value.\n File: " + package.Name);
                                }
                            }
                            else if (parts.Length != 1 || !HSLColor.TryParseRGB(parts[0], out dummy))
                                throw new InvalidDataException("Invalid ColorRamp value.\n File: " + package.Name);

                            colorRampNode.Key = "Color";
                            noteColorRamp = true;
                        }
                    }

                    if (noteHexColors)
                        Console.WriteLine("ColorRamp is now called Color and uses rgb(a) hex value - rrggbb[aa].");
                    else if (noteColorRamp)
                        Console.WriteLine("ColorRamp is now called Color.");
                }
            }

            // Format 8 -> 9 moved map options and videos from the map file itself to traits
            if (mapFormat < 9)
            {
                var rules = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
                var worldNode = rules.Value.Nodes.FirstOrDefault(n => n.Key == "World");
                if (worldNode == null)
                    worldNode = new MiniYamlNode("World", new MiniYaml("", new List<MiniYamlNode>()));

                var playerNode = rules.Value.Nodes.FirstOrDefault(n => n.Key == "Player");
                if (playerNode == null)
                    playerNode = new MiniYamlNode("Player", new MiniYaml("", new List<MiniYamlNode>()));

                var visibilityNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Visibility");
                if (visibilityNode != null)
                {
                    var visibility = FieldLoader.GetValue<MapVisibility>("Visibility", visibilityNode.Value.Value);
                    if (visibility.HasFlag(MapVisibility.MissionSelector))
                    {
                        var missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List<MiniYamlNode>()));
                        worldNode.Value.Nodes.Add(missionData);

                        var description = yaml.Nodes.FirstOrDefault(n => n.Key == "Description");
                        if (description != null)
                            missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", description.Value.Value));

                        var videos = yaml.Nodes.FirstOrDefault(n => n.Key == "Videos");
                        if (videos != null && videos.Value.Nodes.Any())
                        {
                            var backgroundVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "BackgroundInfo");
                            if (backgroundVideo != null)
                                missionData.Value.Nodes.Add(new MiniYamlNode("BackgroundVideo", backgroundVideo.Value.Value));

                            var briefingVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "Briefing");
                            if (briefingVideo != null)
                                missionData.Value.Nodes.Add(new MiniYamlNode("BriefingVideo", briefingVideo.Value.Value));

                            var startVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameStart");
                            if (startVideo != null)
                                missionData.Value.Nodes.Add(new MiniYamlNode("StartVideo", startVideo.Value.Value));

                            var winVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameWon");
                            if (winVideo != null)
                                missionData.Value.Nodes.Add(new MiniYamlNode("WinVideo", winVideo.Value.Value));

                            var lossVideo = videos.Value.Nodes.FirstOrDefault(n => n.Key == "GameLost");
                            if (lossVideo != null)
                                missionData.Value.Nodes.Add(new MiniYamlNode("LossVideo", lossVideo.Value.Value));
                        }
                    }
                }

                var mapOptions = yaml.Nodes.FirstOrDefault(n => n.Key == "Options");
                if (mapOptions != null)
                {
                    var cheats = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Cheats");
                    if (cheats != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("DeveloperMode", new MiniYaml("", new List<MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                            new MiniYamlNode("Enabled", cheats.Value.Value)
                        })));
                    }

                    var crates = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Crates");
                    if (crates != null && !worldNode.Value.Nodes.Any(n => n.Key == "-CrateSpawner"))
                    {
                        if (!FieldLoader.GetValue<bool>("crates", crates.Value.Value))
                            worldNode.Value.Nodes.Add(new MiniYamlNode("-CrateSpawner", new MiniYaml("")));
                    }

                    var creeps = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Creeps");
                    if (creeps != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("MapCreeps", new MiniYaml("", new List<MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                            new MiniYamlNode("Enabled", creeps.Value.Value)
                        })));
                    }

                    var fog = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Fog");
                    var shroud = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Shroud");
                    if (fog != null || shroud != null)
                    {
                        var shroudNode = new MiniYamlNode("Shroud", new MiniYaml("", new List<MiniYamlNode>()));
                        playerNode.Value.Nodes.Add(shroudNode);

                        if (fog != null)
                        {
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("FogLocked", "True"));
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("FogEnabled", fog.Value.Value));
                        }

                        if (shroud != null)
                        {
                            var enabled = FieldLoader.GetValue<bool>("shroud", shroud.Value.Value);
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("ExploredMapLocked", "True"));
                            shroudNode.Value.Nodes.Add(new MiniYamlNode("ExploredMapEnabled", FieldSaver.FormatValue(!enabled)));
                        }
                    }

                    var allyBuildRadius = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "AllyBuildRadius");
                    if (allyBuildRadius != null)
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("MapBuildRadius", new MiniYaml("", new List<MiniYamlNode>()
                        {
                            new MiniYamlNode("AllyBuildRadiusLocked", "True"),
                            new MiniYamlNode("AllyBuildRadiusEnabled", allyBuildRadius.Value.Value)
                        })));
                    }

                    var startingCash = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "StartingCash");
                    if (startingCash != null)
                    {
                        playerNode.Value.Nodes.Add(new MiniYamlNode("PlayerResources", new MiniYaml("", new List<MiniYamlNode>()
                        {
                            new MiniYamlNode("DefaultCashLocked", "True"),
                            new MiniYamlNode("DefaultCash", startingCash.Value.Value)
                        })));
                    }

                    var startingUnits = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "ConfigurableStartingUnits");
                    if (startingUnits != null && !worldNode.Value.Nodes.Any(n => n.Key == "-SpawnMPUnits"))
                    {
                        worldNode.Value.Nodes.Add(new MiniYamlNode("SpawnMPUnits", new MiniYaml("", new List<MiniYamlNode>()
                        {
                            new MiniYamlNode("Locked", "True"),
                        })));
                    }

                    var techLevel = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "TechLevel");
                    var difficulties = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "Difficulties");
                    var shortGame = mapOptions.Value.Nodes.FirstOrDefault(n => n.Key == "ShortGame");
                    if (techLevel != null || difficulties != null || shortGame != null)
                    {
                        var optionsNode = new MiniYamlNode("MapOptions", new MiniYaml("", new List<MiniYamlNode>()));
                        worldNode.Value.Nodes.Add(optionsNode);

                        if (techLevel != null)
                        {
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("TechLevelLocked", "True"));
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("TechLevel", techLevel.Value.Value));
                        }

                        if (difficulties != null)
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("Difficulties", difficulties.Value.Value));

                        if (shortGame != null)
                        {
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("ShortGameLocked", "True"));
                            optionsNode.Value.Nodes.Add(new MiniYamlNode("ShortGameEnabled", shortGame.Value.Value));
                        }
                    }
                }

                if (worldNode.Value.Nodes.Any() && !rules.Value.Nodes.Contains(worldNode))
                    rules.Value.Nodes.Add(worldNode);

                if (playerNode.Value.Nodes.Any() && !rules.Value.Nodes.Contains(playerNode))
                    rules.Value.Nodes.Add(playerNode);
            }

            // Format 9 -> 10 moved smudges to SmudgeLayer, and uses map.png for all maps
            if (mapFormat < 10)
            {
                ExtractSmudges(yaml);
                if (package.Contains("map.png"))
                    yaml.Nodes.Add(new MiniYamlNode("LockPreview", new MiniYaml("True")));
            }

            // Format 10 -> 11 replaced the single map type field with a list of categories
            if (mapFormat < 11)
            {
                var type = yaml.Nodes.First(n => n.Key == "Type");
                yaml.Nodes.Add(new MiniYamlNode("Categories", type.Value));
                yaml.Nodes.Remove(type);
            }

            if (mapFormat < Map.SupportedMapFormat)
            {
                yaml.Nodes.First(n => n.Key == "MapFormat").Value = new MiniYaml(Map.SupportedMapFormat.ToString());
                Console.WriteLine("Converted {0} to MapFormat {1}.", package.Name, Map.SupportedMapFormat);
            }

            package.Update("map.yaml", Encoding.UTF8.GetBytes(yaml.Nodes.WriteToString()));
        }