diff --git a/OpenRA.Game/GameRules/RulesetCache.cs b/OpenRA.Game/GameRules/RulesetCache.cs index e817689b3c..b308b18a8e 100644 --- a/OpenRA.Game/GameRules/RulesetCache.cs +++ b/OpenRA.Game/GameRules/RulesetCache.cs @@ -21,8 +21,6 @@ namespace OpenRA { public sealed class RulesetCache { - static readonly string[] NoMapRules = new string[0]; - readonly ModData modData; readonly Dictionary actorCache = new Dictionary(); @@ -44,11 +42,13 @@ namespace OpenRA this.modData = modData; } - /// - /// Cache and return the Ruleset for a given map. - /// If a map isn't specified then return the default mod Ruleset. - /// - public Ruleset Load(IReadOnlyFileSystem fileSystem, Map map = null) + public Ruleset Load(IReadOnlyFileSystem fileSystem, + MiniYaml additionalRules, + MiniYaml additionalWeapons, + MiniYaml additionalVoices, + MiniYaml additionalNotifications, + MiniYaml additionalMusic, + MiniYaml additionalSequences) { var m = modData.Manifest; @@ -60,46 +60,58 @@ namespace OpenRA Dictionary tileSets; using (new PerfTimer("Actors")) - actors = LoadYamlRules(fileSystem, actorCache, m.Rules, - map != null ? map.RuleDefinitions : NoMapRules, + actors = LoadYamlRules(fileSystem, actorCache, m.Rules, additionalRules, k => new ActorInfo(Game.ModData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value)); using (new PerfTimer("Weapons")) - weapons = LoadYamlRules(fileSystem, weaponCache, m.Weapons, - map != null ? map.WeaponDefinitions : NoMapRules, + weapons = LoadYamlRules(fileSystem, weaponCache, m.Weapons, additionalWeapons, k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); using (new PerfTimer("Voices")) - voices = LoadYamlRules(fileSystem, voiceCache, m.Voices, - map != null ? map.VoiceDefinitions : NoMapRules, + voices = LoadYamlRules(fileSystem, voiceCache, m.Voices, additionalVoices, k => new SoundInfo(k.Value)); using (new PerfTimer("Notifications")) - notifications = LoadYamlRules(fileSystem, notificationCache, m.Notifications, - map != null ? map.NotificationDefinitions : NoMapRules, + notifications = LoadYamlRules(fileSystem, notificationCache, m.Notifications, additionalNotifications, k => new SoundInfo(k.Value)); using (new PerfTimer("Music")) - music = LoadYamlRules(fileSystem, musicCache, m.Music, - map != null ? map.MusicDefinitions : NoMapRules, + music = LoadYamlRules(fileSystem, musicCache, m.Music, additionalMusic, k => new MusicInfo(k.Key, k.Value)); using (new PerfTimer("TileSets")) tileSets = LoadTileSets(fileSystem, tileSetCache, m.TileSets); // TODO: only initialize, and then cache, the provider for the given map - var sequences = tileSets.ToDictionary(t => t.Key, t => new SequenceProvider(fileSystem, modData, t.Value, map)); + var sequences = tileSets.ToDictionary(t => t.Key, t => new SequenceProvider(fileSystem, modData, t.Value, additionalSequences)); return new Ruleset(actors, weapons, voices, notifications, music, tileSets, sequences); } + /// + /// Cache and return the Ruleset for a given map. + /// If a map isn't specified then return the default mod Ruleset. + /// + public Ruleset Load(IReadOnlyFileSystem fileSystem, Map map = null) + { + return map != null ? Load(fileSystem, map.RuleDefinitions, map.WeaponDefinitions, + map.VoiceDefinitions, map.NotificationDefinitions, map.MusicDefinitions, + map.SequenceDefinitions) : Load(fileSystem, null, null, null, null, null, null); + } + Dictionary LoadYamlRules(IReadOnlyFileSystem fileSystem, Dictionary itemCache, - string[] files, string[] mapFiles, + IEnumerable files, MiniYaml mapRules, Func f) { RaiseProgress(); - var inputKey = string.Concat(string.Join("|", files.Append(mapFiles)), "|"); + if (mapRules != null && mapRules.Value != null) + files = files.Append(FieldLoader.GetValue("value", mapRules.Value)); + + var inputKey = string.Join("|", files); + if (mapRules != null && mapRules.Nodes.Any()) + inputKey += "|" + mapRules.Nodes.WriteToString(); + Func wrap = wkv => { var key = inputKey + wkv.Value.ToLines(wkv.Key).JoinWith("|"); @@ -114,8 +126,9 @@ namespace OpenRA return t; }; - var tree = MiniYaml.Merge(files.Append(mapFiles).Select(s => MiniYaml.FromStream(fileSystem.Open(s)))) - .ToDictionaryWithConflictLog(n => n.Key, n => n.Value, "LoadYamlRules", null, null); + var tree = MiniYaml.Load(fileSystem, files, mapRules).ToDictionaryWithConflictLog( + n => n.Key, n => n.Value, "LoadYamlRules", null, null); + RaiseProgress(); var itemSet = tree.ToDictionary(kv => kv.Key.ToLowerInvariant(), kv => wrap(new MiniYamlNode(kv.Key, kv.Value))); diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 97a31f8899..654a4b411c 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -53,11 +53,16 @@ namespace OpenRA.Graphics readonly Dictionary sequenceCache = new Dictionary(); - public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, Map map) + public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences) { this.modData = modData; this.tileSet = tileSet; - sequences = Exts.Lazy(() => LoadSequences(fileSystem, map)); + sequences = Exts.Lazy(() => + { + using (new Support.PerfTimer("LoadSequences")) + return Load(fileSystem, additionalSequences); + }); + spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed))); } @@ -97,17 +102,9 @@ namespace OpenRA.Graphics return unitSeq.Value.Keys; } - public Sequences LoadSequences(IReadOnlyFileSystem fileSystem, Map map) + Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences) { - using (new Support.PerfTimer("LoadSequences")) - return Load(fileSystem, map != null ? map.SequenceDefinitions : new string[0]); - } - - Sequences Load(IReadOnlyFileSystem fileSystem, string[] mapSequences) - { - var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Append(mapSequences) - .Select(s => MiniYaml.FromStream(fileSystem.Open(s)))); - + var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences); var items = new Dictionary(); foreach (var n in nodes) { diff --git a/OpenRA.Game/Graphics/VoxelProvider.cs b/OpenRA.Game/Graphics/VoxelProvider.cs index 0f6aca848b..804fffbf2f 100644 --- a/OpenRA.Game/Graphics/VoxelProvider.cs +++ b/OpenRA.Game/Graphics/VoxelProvider.cs @@ -21,13 +21,9 @@ namespace OpenRA.Graphics { static Dictionary> units; - public static void Initialize(VoxelLoader loader, IReadOnlyFileSystem fileSystem, IEnumerable voxelFiles) + public static void Initialize(VoxelLoader loader, IReadOnlyFileSystem fileSystem, List sequences) { units = new Dictionary>(); - - var sequences = MiniYaml.Merge(voxelFiles.Select( - s => MiniYaml.FromStream(fileSystem.Open(s)))); - foreach (var s in sequences) LoadVoxelsForUnit(loader, s.Key, s.Value); diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 4add5c064d..87e9cc664c 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -141,14 +141,14 @@ namespace OpenRA public Lazy SpawnPoints; // Yaml map data - [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 readonly MiniYaml RuleDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml SequenceDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml VoxelSequenceDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml WeaponDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml VoiceDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml MusicDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml NotificationDefinitions; + [FieldLoader.Ignore] public readonly MiniYaml TranslationDefinitions; [FieldLoader.Ignore] public List PlayerDefinitions = new List(); [FieldLoader.Ignore] public List ActorDefinitions = new List(); @@ -184,13 +184,6 @@ 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. @@ -260,14 +253,14 @@ namespace OpenRA return spawns.ToArray(); }); - 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); + RuleDefinitions = LoadRuleSection(yaml, "Rules"); + SequenceDefinitions = LoadRuleSection(yaml, "Sequences"); + VoxelSequenceDefinitions = LoadRuleSection(yaml, "VoxelSequences"); + WeaponDefinitions = LoadRuleSection(yaml, "Weapons"); + VoiceDefinitions = LoadRuleSection(yaml, "Voices"); + MusicDefinitions = LoadRuleSection(yaml, "Music"); + NotificationDefinitions = LoadRuleSection(yaml, "Notifications"); + TranslationDefinitions = LoadRuleSection(yaml, "Translations"); PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players"); ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors"); @@ -287,6 +280,12 @@ namespace OpenRA Uid = ComputeUID(Package); } + MiniYaml LoadRuleSection(MiniYaml yaml, string section) + { + var node = yaml.Nodes.FirstOrDefault(n => n.Key == section); + return node != null ? node.Value : null; + } + void PostInit() { rules = Exts.Lazy(() => @@ -448,21 +447,21 @@ namespace OpenRA root.Add(new MiniYamlNode("Players", null, PlayerDefinitions)); root.Add(new MiniYamlNode("Actors", null, ActorDefinitions)); - var fileFields = new[] + var ruleSections = 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) + new MiniYamlNode("Rules", RuleDefinitions), + new MiniYamlNode("Sequences", SequenceDefinitions), + new MiniYamlNode("VoxelSequences", VoxelSequenceDefinitions), + new MiniYamlNode("Weapons", WeaponDefinitions), + new MiniYamlNode("Voices", VoiceDefinitions), + new MiniYamlNode("Music", MusicDefinitions), + new MiniYamlNode("Notifications", NotificationDefinitions), + new MiniYamlNode("Translations", TranslationDefinitions) }; - foreach (var kv in fileFields) - if (kv.Second.Any()) - root.Add(new MiniYamlNode(kv.First, FieldSaver.FormatValue(kv.Second))); + foreach (var section in ruleSections) + if (section.Value != null && (section.Value.Value != null || section.Value.Nodes.Any())) + root.Add(section); // Saving to a new package: copy over all the content from the map if (Package != null && toPackage != Package) diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index 17d205ae6f..bcc4fe5e60 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using OpenRA.FileSystem; namespace OpenRA { @@ -375,6 +376,21 @@ namespace OpenRA foreach (var line in Nodes.ToLines(false)) yield return "\t" + line; } + + public static List Load(IReadOnlyFileSystem fileSystem, IEnumerable files, MiniYaml mapRules) + { + if (mapRules != null && mapRules.Value != null) + { + var mapFiles = FieldLoader.GetValue("value", mapRules.Value); + files = files.Append(mapFiles); + } + + var yaml = files.Select(s => MiniYaml.FromStream(fileSystem.Open(s))); + if (mapRules != null && mapRules.Nodes.Any()) + yaml = yaml.Append(mapRules.Nodes); + + return MiniYaml.Merge(yaml); + } } [Serializable] diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 605a823847..b20b31e25c 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -130,8 +130,7 @@ namespace OpenRA return; } - var yaml = MiniYaml.Merge(Manifest.Translations.Append(map.TranslationDefinitions) - .Select(t => MiniYaml.FromStream(map.Open(t)))); + var yaml = MiniYaml.Load(map, Manifest.Translations, map.TranslationDefinitions); Languages = yaml.Select(t => t.Key).ToArray(); foreach (var y in yaml) @@ -182,7 +181,7 @@ namespace OpenRA foreach (var entry in map.Rules.Music) entry.Value.Load(map); - VoxelProvider.Initialize(VoxelLoader, map, Manifest.VoxelSequences.Append(map.VoxelSequenceDefinitions)); + VoxelProvider.Initialize(VoxelLoader, map, MiniYaml.Load(map, Manifest.VoxelSequences, map.VoxelSequenceDefinitions)); VoxelLoader.Finish(); return map; diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 0d14b76022..d46e7b2522 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -392,7 +392,7 @@ namespace OpenRA.Server SendOrderTo(newConn, "Message", motd); } - if (Map.RuleDefinitions.Any() && !LobbyInfo.IsSinglePlayer) + if (Map.RuleDefinitions != null && !LobbyInfo.IsSinglePlayer) SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); if (Settings.DisableSinglePlayer) diff --git a/OpenRA.Mods.Common/Lint/CheckSequences.cs b/OpenRA.Mods.Common/Lint/CheckSequences.cs index 55d3b1dabc..d1ed7d0e72 100644 --- a/OpenRA.Mods.Common/Lint/CheckSequences.cs +++ b/OpenRA.Mods.Common/Lint/CheckSequences.cs @@ -26,18 +26,17 @@ namespace OpenRA.Mods.Common.Lint public void Run(Action emitError, Action emitWarning, Map map) { - if (map != null && !map.SequenceDefinitions.Any()) + if (map.SequenceDefinitions == null) return; var modData = Game.ModData; this.emitError = emitError; - var mapSequences = map != null ? map.SequenceDefinitions : new string[0]; - sequenceDefinitions = MiniYaml.Merge(modData.Manifest.Sequences.Append(mapSequences).Select(s => MiniYaml.FromStream(map.Open(s)))); + sequenceDefinitions = MiniYaml.Load(map, modData.Manifest.Sequences, map.SequenceDefinitions); - var rules = map == null ? modData.DefaultRules : map.Rules; + var rules = map.Rules; var factions = rules.Actors["world"].TraitInfos().Select(f => f.InternalName).ToArray(); - var sequenceProviders = map == null ? rules.Sequences.Values : new[] { rules.Sequences[map.Tileset] }; + var sequenceProviders = new[] { rules.Sequences[map.Tileset] }; foreach (var actorInfo in rules.Actors) { diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 5089f8b7ca..0285393c24 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -387,7 +387,7 @@ namespace OpenRA.Mods.Common.Server server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title)); - if (server.Map.RuleDefinitions.Any()) + if (server.Map.RuleDefinitions != null) server.SendMessage("This map contains custom rules. Game experience may change."); if (server.Settings.DisableSinglePlayer) diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs index 2a96940697..943fb2edb2 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs @@ -90,7 +90,7 @@ namespace OpenRA.Mods.Common.UtilityCommands testMap.PreloadRules(); // Run all rule checks on the map if it defines custom rules. - if (testMap.RuleDefinitions.Any() || testMap.VoiceDefinitions.Any() || testMap.WeaponDefinitions.Any()) + if (testMap.RuleDefinitions != null || testMap.VoiceDefinitions != null || testMap.WeaponDefinitions != null) CheckRules(modData, testMap.Rules, testMap); // Run all map-level checks here. diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs index cc9da3aac3..6e608c58fc 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs @@ -27,17 +27,23 @@ namespace OpenRA.Mods.Common.UtilityCommands delegate void UpgradeAction(int engineVersion, ref List nodes, MiniYamlNode parent, int depth); - static void ProcessYaml(Map map, IEnumerable files, int engineDate, UpgradeAction processFile) + static void ProcessYaml(Map map, MiniYaml yaml, int engineDate, UpgradeAction processYaml) { - foreach (var filename in files) - { - if (!map.Package.Contains(filename)) - continue; + if (yaml == null) + return; - var yaml = MiniYaml.FromStream(map.Package.GetStream(filename)); - processFile(engineDate, ref yaml, null, 0); - ((IReadWritePackage)map.Package).Update(filename, Encoding.ASCII.GetBytes(yaml.WriteToString())); - } + if (yaml.Value != null) + { + var files = FieldLoader.GetValue("value", yaml.Value); + foreach (var filename in files) + { + var fileNodes = MiniYaml.FromStream(map.Package.GetStream(filename)); + processYaml(engineDate, ref fileNodes, null, 0); + ((IReadWritePackage)map.Package).Update(filename, Encoding.ASCII.GetBytes(fileNodes.WriteToString())); + } + } + + processYaml(engineDate, ref yaml.Nodes, null, 1); } public static void UpgradeMap(ModData modData, IReadWritePackage package, int engineDate)