diff --git a/OpenRA.Game/GameRules/RulesetCache.cs b/OpenRA.Game/GameRules/RulesetCache.cs index be63ffd5bf..e817689b3c 100644 --- a/OpenRA.Game/GameRules/RulesetCache.cs +++ b/OpenRA.Game/GameRules/RulesetCache.cs @@ -21,7 +21,7 @@ namespace OpenRA { public sealed class RulesetCache { - static readonly List NoMapRules = new List(); + static readonly string[] NoMapRules = new string[0]; readonly ModData modData; @@ -94,12 +94,12 @@ namespace OpenRA Dictionary LoadYamlRules(IReadOnlyFileSystem fileSystem, Dictionary itemCache, - string[] files, List nodes, + string[] files, string[] mapFiles, Func f) { RaiseProgress(); - var inputKey = string.Concat(string.Join("|", files), "|", nodes.WriteToString()); + var inputKey = string.Concat(string.Join("|", files.Append(mapFiles)), "|"); Func wrap = wkv => { var key = inputKey + wkv.Value.ToLines(wkv.Key).JoinWith("|"); @@ -114,7 +114,7 @@ namespace OpenRA return t; }; - var tree = MiniYaml.Merge(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s))).Append(nodes)) + var tree = MiniYaml.Merge(files.Append(mapFiles).Select(s => MiniYaml.FromStream(fileSystem.Open(s)))) .ToDictionaryWithConflictLog(n => n.Key, n => n.Value, "LoadYamlRules", null, null); RaiseProgress(); diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index c5c16e49d8..97a31f8899 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -100,14 +100,13 @@ namespace OpenRA.Graphics public Sequences LoadSequences(IReadOnlyFileSystem fileSystem, Map map) { using (new Support.PerfTimer("LoadSequences")) - return Load(fileSystem, map != null ? map.SequenceDefinitions : new List()); + return Load(fileSystem, map != null ? map.SequenceDefinitions : new string[0]); } - Sequences Load(IReadOnlyFileSystem fileSystem, List sequenceNodes) + Sequences Load(IReadOnlyFileSystem fileSystem, string[] mapSequences) { - var nodes = MiniYaml.Merge(modData.Manifest.Sequences - .Select(s => MiniYaml.FromStream(fileSystem.Open(s))) - .Append(sequenceNodes)); + var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Append(mapSequences) + .Select(s => MiniYaml.FromStream(fileSystem.Open(s)))); var items = new Dictionary(); foreach (var n in nodes) diff --git a/OpenRA.Game/Graphics/VoxelProvider.cs b/OpenRA.Game/Graphics/VoxelProvider.cs index d846eb31e9..0f6aca848b 100644 --- a/OpenRA.Game/Graphics/VoxelProvider.cs +++ b/OpenRA.Game/Graphics/VoxelProvider.cs @@ -21,7 +21,7 @@ namespace OpenRA.Graphics { static Dictionary> units; - public static void Initialize(VoxelLoader loader, IReadOnlyFileSystem fileSystem, string[] voxelFiles, List voxelNodes) + public static void Initialize(VoxelLoader loader, IReadOnlyFileSystem fileSystem, IEnumerable voxelFiles) { units = new Dictionary>(); diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 4c4d0fa5cf..e51cbd1c21 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -19,6 +19,7 @@ using System.Text; using OpenRA.FileSystem; using OpenRA.Graphics; using OpenRA.Network; +using OpenRA.Primitives; using OpenRA.Support; using OpenRA.Traits; @@ -139,16 +140,16 @@ namespace OpenRA public Lazy SpawnPoints; // Yaml map data - [FieldLoader.Ignore] public List RuleDefinitions = new List(); - [FieldLoader.Ignore] public List SequenceDefinitions = new List(); - [FieldLoader.Ignore] public List VoxelSequenceDefinitions = new List(); - [FieldLoader.Ignore] public List WeaponDefinitions = new List(); - [FieldLoader.Ignore] public List VoiceDefinitions = new List(); - [FieldLoader.Ignore] public List MusicDefinitions = new List(); - [FieldLoader.Ignore] public List NotificationDefinitions = new List(); - [FieldLoader.Ignore] public List TranslationDefinitions = new List(); - [FieldLoader.Ignore] public List PlayerDefinitions = new List(); + [FieldLoader.Ignore] public readonly string[] RuleDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] SequenceDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] VoxelSequenceDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] WeaponDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] VoiceDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] MusicDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] NotificationDefinitions = { }; + [FieldLoader.Ignore] public readonly string[] TranslationDefinitions = { }; + [FieldLoader.Ignore] public List PlayerDefinitions = new List(); [FieldLoader.Ignore] public List ActorDefinitions = new List(); // Binary map data @@ -182,6 +183,13 @@ namespace OpenRA throw new InvalidOperationException("Required file {0} not present in this map".F(filename)); } + void LoadFileList(MiniYaml yaml, string section, ref string[] files) + { + MiniYamlNode node; + if ((node = yaml.Nodes.FirstOrDefault(n => n.Key == section)) != null) + files = FieldLoader.GetValue(section, node.Value.Value); + } + /// /// Initializes a new map created by the editor or importer. /// The map will not receive a valid UID until after it has been saved and reloaded. @@ -251,16 +259,16 @@ namespace OpenRA return spawns.ToArray(); }); - RuleDefinitions = MiniYaml.NodesOrEmpty(yaml, "Rules"); - SequenceDefinitions = MiniYaml.NodesOrEmpty(yaml, "Sequences"); - VoxelSequenceDefinitions = MiniYaml.NodesOrEmpty(yaml, "VoxelSequences"); - WeaponDefinitions = MiniYaml.NodesOrEmpty(yaml, "Weapons"); - VoiceDefinitions = MiniYaml.NodesOrEmpty(yaml, "Voices"); - MusicDefinitions = MiniYaml.NodesOrEmpty(yaml, "Music"); - NotificationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Notifications"); - TranslationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Translations"); - PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players"); + LoadFileList(yaml, "Rules", ref RuleDefinitions); + LoadFileList(yaml, "Sequences", ref SequenceDefinitions); + LoadFileList(yaml, "VoxelSequences", ref VoxelSequenceDefinitions); + LoadFileList(yaml, "Weapons", ref WeaponDefinitions); + LoadFileList(yaml, "Voices", ref VoiceDefinitions); + LoadFileList(yaml, "Music", ref MusicDefinitions); + LoadFileList(yaml, "Notifications", ref NotificationDefinitions); + LoadFileList(yaml, "Translations", ref TranslationDefinitions); + PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players"); ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors"); MapTiles = Exts.Lazy(LoadMapTiles); @@ -438,14 +446,22 @@ namespace OpenRA root.Add(new MiniYamlNode("Players", null, PlayerDefinitions)); root.Add(new MiniYamlNode("Actors", null, ActorDefinitions)); - root.Add(new MiniYamlNode("Rules", null, RuleDefinitions)); - root.Add(new MiniYamlNode("Sequences", null, SequenceDefinitions)); - root.Add(new MiniYamlNode("VoxelSequences", null, VoxelSequenceDefinitions)); - root.Add(new MiniYamlNode("Weapons", null, WeaponDefinitions)); - root.Add(new MiniYamlNode("Voices", null, VoiceDefinitions)); - root.Add(new MiniYamlNode("Music", null, MusicDefinitions)); - root.Add(new MiniYamlNode("Notifications", null, NotificationDefinitions)); - root.Add(new MiniYamlNode("Translations", null, TranslationDefinitions)); + + var fileFields = new[] + { + Pair.New("Rules", RuleDefinitions), + Pair.New("Sequences", SequenceDefinitions), + Pair.New("VoxelSequences", VoxelSequenceDefinitions), + Pair.New("Weapons", WeaponDefinitions), + Pair.New("Voices", VoiceDefinitions), + Pair.New("Music", MusicDefinitions), + Pair.New("Notifications", NotificationDefinitions), + Pair.New("Translations", TranslationDefinitions) + }; + + foreach (var kv in fileFields) + if (kv.Second.Any()) + root.Add(new MiniYamlNode(kv.First, FieldSaver.FormatValue(kv.Second))); // Saving to a new package: copy over all the content from the map if (Package != null && toPackage != Package) diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 9ef3870f9a..605a823847 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -130,9 +130,8 @@ namespace OpenRA return; } - var yaml = MiniYaml.Merge(Manifest.Translations - .Select(t => MiniYaml.FromStream(ModFiles.Open(t))) - .Append(map.TranslationDefinitions)); + var yaml = MiniYaml.Merge(Manifest.Translations.Append(map.TranslationDefinitions) + .Select(t => MiniYaml.FromStream(map.Open(t)))); Languages = yaml.Select(t => t.Key).ToArray(); foreach (var y in yaml) @@ -183,7 +182,7 @@ namespace OpenRA foreach (var entry in map.Rules.Music) entry.Value.Load(map); - VoxelProvider.Initialize(VoxelLoader, map, Manifest.VoxelSequences, map.VoxelSequenceDefinitions); + VoxelProvider.Initialize(VoxelLoader, map, Manifest.VoxelSequences.Append(map.VoxelSequenceDefinitions)); VoxelLoader.Finish(); return map; diff --git a/OpenRA.Mods.Common/Lint/CheckSequences.cs b/OpenRA.Mods.Common/Lint/CheckSequences.cs index c51b6b0fb0..55d3b1dabc 100644 --- a/OpenRA.Mods.Common/Lint/CheckSequences.cs +++ b/OpenRA.Mods.Common/Lint/CheckSequences.cs @@ -32,8 +32,8 @@ namespace OpenRA.Mods.Common.Lint var modData = Game.ModData; this.emitError = emitError; - var sequenceSource = map != null ? map.SequenceDefinitions : new List(); - sequenceDefinitions = MiniYaml.Merge(modData.Manifest.Sequences.Select(s => MiniYaml.FromStream(map.Open(s))).Append(sequenceSource)); + var mapSequences = map != null ? map.SequenceDefinitions : new string[0]; + sequenceDefinitions = MiniYaml.Merge(modData.Manifest.Sequences.Append(mapSequences).Select(s => MiniYaml.FromStream(map.Open(s)))); var rules = map == null ? modData.DefaultRules : map.Rules; var factions = rules.Actors["world"].TraitInfos().Select(f => f.InternalName).ToArray(); diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index 1c0b73a4cb..9d469750b3 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -33,6 +33,7 @@ namespace OpenRA.Mods.Common.UtilityCommands public ModData ModData; public Map Map; + public IReadWritePackage Package; public List Players = new List(); public MapPlayers MapPlayers; public MiniYaml Rules = new MiniYaml(""); @@ -51,6 +52,8 @@ namespace OpenRA.Mods.Common.UtilityCommands Game.ModData = modData; var filename = args[1]; + var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; + Package = new ZipFile(modData.ModFiles, dest, true); using (var stream = modData.DefaultFileSystem.Open(filename)) { var file = new IniFile(stream); @@ -79,7 +82,7 @@ namespace OpenRA.Mods.Common.UtilityCommands ReadActors(file); - LoadSmudges(file, "SMUDGE", MapSize, Map); + LoadSmudges(file, "SMUDGE"); var waypoints = file.GetSection("Waypoints"); LoadWaypoints(Map, waypoints, MapSize); @@ -93,11 +96,16 @@ namespace OpenRA.Mods.Common.UtilityCommands Map.FixOpenAreas(); - Map.RuleDefinitions = Rules.Nodes; + if (Rules.Nodes.Any()) + { + // HACK: bypassing the readonly modifier here is still better than leaving this mutable by everyone + typeof(Map).GetField("RuleDefinitions").SetValue(Map, new[] { "rules.yaml" }); - var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - var package = new ZipFile(modData.ModFiles, dest, true); - Map.Save(package); + var rulesText = Rules.Nodes.ToLines(false).JoinWith("\n"); + Package.Update("rules.yaml", System.Text.Encoding.ASCII.GetBytes(rulesText)); + } + + Map.Save(Package); Console.WriteLine(dest + " saved."); } @@ -271,7 +279,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } } - static void LoadSmudges(IniFile file, string section, int mapSize, Map map) + void LoadSmudges(IniFile file, string section) { var scorches = new List(); var craters = new List(); @@ -281,7 +289,7 @@ namespace OpenRA.Mods.Common.UtilityCommands var parts = s.Value.Split(','); var loc = Exts.ParseIntegerInvariant(parts[1]); var type = parts[0].ToLowerInvariant(); - var key = "{0},{1}".F(loc % mapSize, loc / mapSize); + var key = "{0},{1}".F(loc % MapSize, loc / MapSize); var value = "{0},{1}".F(type, parts[2]); var node = new MiniYamlNode(key, value); if (type.StartsWith("sc")) @@ -290,7 +298,10 @@ namespace OpenRA.Mods.Common.UtilityCommands craters.Add(node); } - var worldNode = new MiniYamlNode("World", new MiniYaml("", new List())); + var worldNode = Rules.Nodes.FirstOrDefault(n => n.Key == "World"); + if (worldNode == null) + worldNode = new MiniYamlNode("World", new MiniYaml("", new List())); + if (scorches.Any()) { var initialScorches = new MiniYamlNode("InitialSmudges", new MiniYaml("", scorches)); @@ -305,8 +316,8 @@ namespace OpenRA.Mods.Common.UtilityCommands worldNode.Value.Nodes.Add(smudgeLayer); } - if (worldNode.Value.Nodes.Any()) - map.RuleDefinitions.Add(worldNode); + if (worldNode.Value.Nodes.Any() && !Rules.Nodes.Contains(worldNode)) + Rules.Nodes.Add(worldNode); } // TODO: fix this -- will have bitrotted pretty badly. diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs index 3070183e4c..cc9da3aac3 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs @@ -25,6 +25,21 @@ namespace OpenRA.Mods.Common.UtilityCommands return args.Length >= 3; } + delegate void UpgradeAction(int engineVersion, ref List nodes, MiniYamlNode parent, int depth); + + static void ProcessYaml(Map map, IEnumerable files, int engineDate, UpgradeAction processFile) + { + foreach (var filename in files) + { + if (!map.Package.Contains(filename)) + continue; + + var yaml = MiniYaml.FromStream(map.Package.GetStream(filename)); + processFile(engineDate, ref yaml, null, 0); + ((IReadWritePackage)map.Package).Update(filename, Encoding.ASCII.GetBytes(yaml.WriteToString())); + } + } + public static void UpgradeMap(ModData modData, IReadWritePackage package, int engineDate) { UpgradeRules.UpgradeMapFormat(modData, package); @@ -37,8 +52,8 @@ namespace OpenRA.Mods.Common.UtilityCommands } var map = new Map(modData, package); - UpgradeRules.UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0); - UpgradeRules.UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0); + ProcessYaml(map, map.WeaponDefinitions, engineDate, UpgradeRules.UpgradeWeaponRules); + ProcessYaml(map, map.RuleDefinitions, engineDate, UpgradeRules.UpgradeActorRules); UpgradeRules.UpgradePlayers(engineDate, ref map.PlayerDefinitions, null, 0); UpgradeRules.UpgradeActors(engineDate, ref map.ActorDefinitions, null, 0); map.Save(package); diff --git a/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs index 9407683635..48eda4f34c 100644 --- a/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs @@ -165,6 +165,8 @@ namespace OpenRA.Mods.TS.UtilityCommands var filename = args[1]; var file = new IniFile(File.Open(args[1], FileMode.Open)); var map = GenerateMapHeader(filename, file, modData); + var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; + var package = new ZipFile(modData.DefaultFileSystem, dest, true); ReadTiles(map, file); ReadActors(map, file, "Structures"); @@ -173,13 +175,11 @@ namespace OpenRA.Mods.TS.UtilityCommands ReadTerrainActors(map, file); ReadWaypoints(map, file); ReadOverlay(map, file); - ReadLighting(map, file); + ReadLighting(map, package, file); var mapPlayers = new MapPlayers(map.Rules, spawnCount); map.PlayerDefinitions = mapPlayers.ToMiniYaml(); - var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - var package = new ZipFile(modData.DefaultFileSystem, dest, true); map.Save(package); Console.WriteLine(dest + " saved."); } @@ -431,29 +431,32 @@ namespace OpenRA.Mods.TS.UtilityCommands } } - void ReadLighting(Map map, IniFile file) + void ReadLighting(Map map, IReadWritePackage package, IniFile file) { var lightingTypes = new[] { "Red", "Green", "Blue", "Ambient" }; var lightingSection = file.GetSection("Lighting"); - var lightingNodes = new List(); + var lightingNode = new MiniYamlNode("GlobalLightingPaletteEffect", new MiniYaml("", new List())); + var worldNode = new MiniYamlNode("World", new MiniYaml("", new List() { lightingNode })); + foreach (var kv in lightingSection) { if (lightingTypes.Contains(kv.Key)) { var val = FieldLoader.GetValue(kv.Key, kv.Value); if (val != 1.0f) - lightingNodes.Add(new MiniYamlNode(kv.Key, FieldSaver.FormatValue(val))); + lightingNode.Value.Nodes.Add(new MiniYamlNode(kv.Key, FieldSaver.FormatValue(val))); } else Console.WriteLine("Ignoring unknown lighting type: `{0}`".F(kv.Key)); } - if (lightingNodes.Any()) + if (lightingNode.Value.Nodes.Any()) { - map.RuleDefinitions.Add(new MiniYamlNode("World", new MiniYaml("", new List() - { - new MiniYamlNode("GlobalLightingPaletteEffect", new MiniYaml("", lightingNodes)) - }))); + // HACK: bypassing the readonly modifier here is still better than leaving this mutable by everyone + typeof(Map).GetField("RuleDefinitions").SetValue(map, new[] { "rules.yaml" }); + + var rulesText = new List() { worldNode }.ToLines(false).JoinWith("\n"); + package.Update("rules.yaml", System.Text.Encoding.ASCII.GetBytes(rulesText)); } } }