diff --git a/Makefile b/Makefile index c17639e7c6..ca1c56e8bc 100644 --- a/Makefile +++ b/Makefile @@ -362,8 +362,9 @@ all-dependencies: cli-dependencies windows-dependencies osx-dependencies version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modchooser/mod.yaml mods/all/mod.yaml @for i in $? ; do \ awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \ - awk '{sub("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i} && \ - rm $${i}.tmp ; \ + awk '{sub("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i}.tmp2 && \ + awk '{sub("/[^/]*@User$$", "/$(VERSION)@User"); print $0}' $${i}.tmp2 > $${i} && \ + rm $${i}.tmp $${i}.tmp2; \ done docs: utility mods version diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 50fa077c32..868fd757ae 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -109,7 +109,7 @@ namespace OpenRA public class Map { - public const int MinimumSupportedMapFormat = 6; + public const int SupportedMapFormat = 8; public const int MaxTilesInCircleRange = 50; public readonly MapGrid Grid; @@ -167,6 +167,34 @@ namespace OpenRA return videos; } + public static string ComputeUID(IReadOnlyPackage package) + { + // UID is calculated by taking an SHA1 of the yaml and binary data + using (var ms = new MemoryStream()) + { + // Read the relevant data into the buffer + using (var s = package.GetStream("map.yaml")) + { + if (s == null) + throw new FileNotFoundException("Required file map.yaml not present in this map"); + s.CopyTo(ms); + } + + using (var s = package.GetStream("map.bin")) + { + if (s == null) + throw new FileNotFoundException("Required file map.bin not present in this map"); + + s.CopyTo(ms); + } + + // Take the SHA1 + ms.Seek(0, SeekOrigin.Begin); + using (var csp = SHA1.Create()) + return new string(csp.ComputeHash(ms).SelectMany(a => a.ToString("x2")).ToArray()); + } + } + public Rectangle Bounds; /// @@ -284,73 +312,9 @@ namespace OpenRA var yaml = new MiniYaml(null, MiniYaml.FromStream(Container.GetStream("map.yaml"), path)); FieldLoader.Load(this, yaml); - // Support for formats 1-3 dropped 2011-02-11. - // Use release-20110207 to convert older maps to format 4 - // Use release-20110511 to convert older maps to format 5 - // Use release-20141029 to convert older maps to format 6 - if (MapFormat < MinimumSupportedMapFormat) + if (MapFormat != SupportedMapFormat) throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, path)); - var nd = yaml.ToDictionary(); - - // 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)) - Visibility = MapVisibility.Shellmap; - else if (Type == "Mission" || Type == "Campaign") - Visibility = MapVisibility.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: " + path); - } - } - else if (parts.Length != 1 || !HSLColor.TryParseRGB(parts[0], out dummy)) - throw new InvalidDataException("Invalid ColorRamp value.\n File: " + path); - - colorRampNode.Key = "Color"; - noteColorRamp = true; - } - } - - Console.WriteLine("Converted " + path + " to MapFormat 8."); - 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."); - } - } - SpawnPoints = Exts.Lazy(() => { var spawns = new List(); @@ -399,7 +363,7 @@ namespace OpenRA if (MapFormat < 8) Save(path); - Uid = ComputeHash(); + Uid = ComputeUID(Container); } void PostInit() @@ -604,7 +568,7 @@ namespace OpenRA Container.Write(entries); // Update UID to match the newly saved data - Uid = ComputeHash(); + Uid = ComputeUID(Container); } public CellLayer LoadMapTiles() @@ -915,24 +879,6 @@ namespace OpenRA ProjectedCellBounds = new ProjectedCellRegion(this, tl, br); } - string ComputeHash() - { - // UID is calculated by taking an SHA1 of the yaml and binary data - using (var ms = new MemoryStream()) - { - // Read the relevant data into the buffer - using (var s = Container.GetStream("map.yaml")) - s.CopyTo(ms); - using (var s = Container.GetStream("map.bin")) - s.CopyTo(ms); - - // Take the SHA1 - ms.Seek(0, SeekOrigin.Begin); - using (var csp = SHA1.Create()) - return new string(csp.ComputeHash(ms).SelectMany(a => a.ToString("x2")).ToArray()); - } - } - public void FixOpenAreas(Ruleset rules) { var r = new Random(); diff --git a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs index 359c6c9eac..3882fdb2b4 100644 --- a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs +++ b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs @@ -17,9 +17,9 @@ namespace OpenRA.Mods.Common.Lint { public void Run(Action emitError, Action emitWarning, Map map) { - if (map.MapFormat < Map.MinimumSupportedMapFormat) - emitError("Map format {0} is older than the minimum supported version {1}." - .F(map.MapFormat, Map.MinimumSupportedMapFormat)); + if (map.MapFormat != Map.SupportedMapFormat) + emitError("Map format {0} does not match the supported version {1}." + .F(map.MapFormat, Map.SupportedMapFormat)); if (map.Author == null) emitError("Map does not define a valid author."); diff --git a/OpenRA.Mods.Common/UtilityCommands/GetMapHashCommand.cs b/OpenRA.Mods.Common/UtilityCommands/GetMapHashCommand.cs index 1923c84600..6747373adf 100644 --- a/OpenRA.Mods.Common/UtilityCommands/GetMapHashCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/GetMapHashCommand.cs @@ -24,9 +24,8 @@ namespace OpenRA.Mods.Common.UtilityCommands [Desc("MAPFILE", "Generate hash of specified oramap file.")] public void Run(ModData modData, string[] args) { - Game.ModData = modData; - var result = new Map(args[1]).Uid; - Console.WriteLine(result); + using (var package = modData.ModFiles.OpenPackage(args[1])) + Console.WriteLine(Map.ComputeUID(package)); } } } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs index 1be5311942..831e3f8538 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs @@ -25,9 +25,10 @@ namespace OpenRA.Mods.Common.UtilityCommands // HACK: The engine code assumes that Game.modData is set. Game.ModData = modData; + UpgradeRules.UpgradeMapFormat(modData, args[1]); + var map = new Map(args[1]); var engineDate = Exts.ParseIntegerInvariant(args[2]); - UpgradeRules.UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0); UpgradeRules.UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0); UpgradeRules.UpgradePlayers(engineDate, ref map.PlayerDefinitions, null, 0); diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeModCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeModCommand.cs index 1b35b63570..f03f8857b1 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeModCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeModCommand.cs @@ -28,12 +28,12 @@ namespace OpenRA.Mods.Common.UtilityCommands { // HACK: The engine code assumes that Game.modData is set. Game.ModData = modData; - Game.ModData.MapCache.LoadMaps(); + modData.MapCache.LoadMaps(); var engineDate = Exts.ParseIntegerInvariant(args[1]); Console.WriteLine("Processing Rules:"); - foreach (var filename in Game.ModData.Manifest.Rules) + foreach (var filename in modData.Manifest.Rules) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Weapons:"); - foreach (var filename in Game.ModData.Manifest.Weapons) + foreach (var filename in modData.Manifest.Weapons) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Tilesets:"); - foreach (var filename in Game.ModData.Manifest.TileSets) + foreach (var filename in modData.Manifest.TileSets) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -66,7 +66,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Cursors:"); - foreach (var filename in Game.ModData.Manifest.Cursors) + foreach (var filename in modData.Manifest.Cursors) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -77,7 +77,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Chrome Metrics:"); - foreach (var filename in Game.ModData.Manifest.ChromeMetrics) + foreach (var filename in modData.Manifest.ChromeMetrics) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -88,7 +88,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Chrome Layout:"); - foreach (var filename in Game.ModData.Manifest.ChromeLayout) + foreach (var filename in modData.Manifest.ChromeLayout) { Console.WriteLine("\t" + filename); var yaml = MiniYaml.FromFile(filename); @@ -99,13 +99,16 @@ namespace OpenRA.Mods.Common.UtilityCommands } Console.WriteLine("Processing Maps:"); - var maps = Game.ModData.MapCache + var mapPaths = modData.MapCache .Where(m => m.Status == MapStatus.Available) - .Select(m => new Map(m.Path)); + .Select(m => m.Path); - foreach (var map in maps) + foreach (var path in mapPaths) { - Console.WriteLine("\t" + map.Path); + Console.WriteLine("\t" + path); + UpgradeRules.UpgradeMapFormat(modData, path); + + var map = new Map(path); UpgradeRules.UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0); UpgradeRules.UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0); UpgradeRules.UpgradePlayers(engineDate, ref map.PlayerDefinitions, null, 0); diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 5a9475ad5e..d770a2755d 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -12,7 +12,9 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; +using System.IO; using System.Linq; +using System.Text; using OpenRA.Graphics; using OpenRA.Traits; @@ -3598,5 +3600,96 @@ namespace OpenRA.Mods.Common.UtilityCommands UpgradeActors(engineVersion, ref node.Value.Nodes, node, depth + 1); } } + + internal static void UpgradeMapFormat(ModData modData, string path) + { + using (var package = modData.ModFiles.OpenWritablePackage(path)) + { + if (package == null) + return; + + var yamlStream = package.GetStream("map.yaml"); + if (yamlStream == null) + return; + + var yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, path)); + var nd = yaml.ToDictionary(); + var mapFormat = FieldLoader.GetValue("MapFormat", nd["MapFormat"].Value); + if (mapFormat < 6) + throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(mapFormat, path)); + + // Nothing to do + if (mapFormat >= 8) + return; + + // 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: " + path); + } + } + else if (parts.Length != 1 || !HSLColor.TryParseRGB(parts[0], out dummy)) + throw new InvalidDataException("Invalid ColorRamp value.\n File: " + path); + + colorRampNode.Key = "Color"; + noteColorRamp = true; + } + } + + Console.WriteLine("Converted " + path + " to MapFormat 8."); + 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."); + } + } + + var entries = new Dictionary(); + entries.Add("map.yaml", Encoding.UTF8.GetBytes(yaml.Nodes.WriteToString())); + foreach (var file in package.Contents) + { + if (file == "map.yaml") + continue; + + entries.Add(file, package.GetStream(file).ReadAllBytes()); + } + } + } } } diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 28eb0efd39..678824630b 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -33,7 +33,7 @@ Packages: MapFolders: ./mods/cnc/maps@System - ~^maps/cnc@User + ~^maps/cnc/{DEV_VERSION}@User Rules: ./mods/cnc/rules/misc.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 2eed4948b1..e4f7b3a036 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -22,7 +22,7 @@ Packages: MapFolders: d2k:maps@System - ~^maps/d2k@User + ~^maps/d2k/{DEV_VERSION}@User Rules: d2k:rules/misc.yaml diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 94c8dc2961..ca647d5529 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -32,7 +32,7 @@ Packages: MapFolders: ./mods/ra/maps@System - ~^maps/ra@User + ~^maps/ra/{DEV_VERSION}@User Rules: ./mods/ra/rules/misc.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 283f92dfa4..69242bbca7 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -55,7 +55,7 @@ Packages: MapFolders: ./mods/ts/maps@System - ~^maps/ts@User + ~^maps/ts/{DEV_VERSION}@User Rules: ./mods/ts/rules/ai.yaml