diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index 66c868730e..07d2387672 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -110,13 +110,8 @@ namespace OpenRA if (!md.TryGetValue(yamlName, out yaml)) return false; - if (yaml.Nodes.Count == 0) - { - ret = GetValue(field.Name, field.FieldType, yaml.Value, field); - return true; - } - - throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(yamlName, field.FieldType)); + ret = GetValue(field.Name, field.FieldType, yaml, field); + return true; } public static T Load(MiniYaml y) where T : new() @@ -165,6 +160,12 @@ namespace OpenRA public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field) { + return GetValue(fieldName, fieldType, new MiniYaml(value), field); + } + + public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field) + { + var value = yaml.Value; if (value != null) value = value.Trim(); if (fieldType == typeof(int)) @@ -441,6 +442,21 @@ namespace OpenRA addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) }); return set; } + else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var dict = Activator.CreateInstance(fieldType); + var arguments = fieldType.GetGenericArguments(); + var addMethod = fieldType.GetMethod("Add", arguments); + + foreach (var node in yaml.Nodes) + { + var key = GetValue(fieldName, arguments[0], node.Key, field); + var val = GetValue(fieldName, arguments[1], node.Value, field); + addMethod.Invoke(dict, new[] { key, val }); + } + + return dict; + } else if (fieldType == typeof(Size)) { if (value != null) @@ -588,7 +604,7 @@ namespace OpenRA var loader = sa.GetLoader(type); if (loader == null && sa.FromYamlKey) - loader = (yaml) => GetValue(yamlName, field.FieldType, yaml.Value, field); + loader = yaml => GetValue(yamlName, field.FieldType, yaml, field); var fli = new FieldLoadInfo(field, sa, yamlName, loader); ret.Add(fli); @@ -632,6 +648,7 @@ namespace OpenRA public string YamlName; public string Loader; public bool FromYamlKey; + public bool DictionaryFromYamlKey; public bool Required; public SerializeAttribute(bool serialize = true, bool required = false) @@ -694,6 +711,17 @@ namespace OpenRA } } + // Special-cases FieldFromYamlKeyAttribute for use with Dictionary. + [AttributeUsage(AttributeTargets.Field)] + public sealed class DictionaryFromYamlKeyAttribute : FieldLoader.SerializeAttribute + { + public DictionaryFromYamlKeyAttribute() + { + FromYamlKey = true; + DictionaryFromYamlKey = true; + } + } + // mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere. [AttributeUsage(AttributeTargets.All)] public sealed class DescAttribute : Attribute diff --git a/OpenRA.Game/FieldSaver.cs b/OpenRA.Game/FieldSaver.cs index 70b17ca936..e082074d46 100644 --- a/OpenRA.Game/FieldSaver.cs +++ b/OpenRA.Game/FieldSaver.cs @@ -28,7 +28,18 @@ namespace OpenRA foreach (var info in FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)) { - if (info.Attribute.FromYamlKey) + if (info.Attribute.DictionaryFromYamlKey) + { + var dict = (System.Collections.IDictionary)info.Field.GetValue(o); + foreach (var kvp in dict) + { + var key = ((System.Collections.DictionaryEntry)kvp).Key; + var value = ((System.Collections.DictionaryEntry)kvp).Value; + + nodes.Add(new MiniYamlNode(FormatValue(key, key.GetType()), FormatValue(value, value.GetType()))); + } + } + else if (info.Attribute.FromYamlKey) root = FormatValue(o, info.Field); else nodes.Add(new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))); @@ -99,6 +110,25 @@ namespace OpenRA return ((System.Collections.IEnumerable)v).Cast().JoinWith(", "); } + // This is only for documentation generation + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var result = ""; + var dict = (System.Collections.IDictionary)v; + foreach (var kvp in dict) + { + var key = ((System.Collections.DictionaryEntry)kvp).Key; + var value = ((System.Collections.DictionaryEntry)kvp).Value; + + var formattedKey = FormatValue(key, key.GetType()); + var formattedValue = FormatValue(value, value.GetType()); + + result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine); + } + + return result; + } + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(OpenRA.Primitives.Cache<,>)) return ""; // TODO diff --git a/OpenRA.Game/GameRules/SoundInfo.cs b/OpenRA.Game/GameRules/SoundInfo.cs index 8a94e7d45d..a4f71abff8 100644 --- a/OpenRA.Game/GameRules/SoundInfo.cs +++ b/OpenRA.Game/GameRules/SoundInfo.cs @@ -16,33 +16,21 @@ namespace OpenRA.GameRules { public class SoundInfo { - [FieldLoader.Ignore] public readonly Dictionary Variants; - [FieldLoader.Ignore] public readonly Dictionary Prefixes; - [FieldLoader.Ignore] public readonly Dictionary Voices; - [FieldLoader.Ignore] public readonly Dictionary Notifications; + public readonly Dictionary Variants = new Dictionary(); + public readonly Dictionary Prefixes = new Dictionary(); + public readonly Dictionary Voices = new Dictionary(); + public readonly Dictionary Notifications = new Dictionary(); public readonly string DefaultVariant = ".aud"; public readonly string DefaultPrefix = ""; public readonly string[] DisableVariants = { }; public readonly string[] DisablePrefixes = { }; - static Dictionary Load(MiniYaml y, string name) - { - var nd = y.ToDictionary(); - return nd.ContainsKey(name) - ? nd[name].ToDictionary(my => FieldLoader.GetValue("(value)", my.Value)) - : new Dictionary(); - } - public readonly Lazy> VoicePools; public readonly Lazy> NotificationsPools; public SoundInfo(MiniYaml y) { FieldLoader.Load(this, y); - Variants = Load(y, "Variants"); - Prefixes = Load(y, "Prefixes"); - Voices = Load(y, "Voices"); - Notifications = Load(y, "Notifications"); VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(a.Value))); NotificationsPools = Exts.Lazy(() => Notifications.ToDictionary(a => a.Key, a => new SoundPool(a.Value))); diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index 5f674cd543..883bde6886 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -114,23 +114,18 @@ namespace OpenRA.Mods.Common.AI string IBotInfo.Name { get { return this.Name; } } [Desc("What units to the AI should build.", "What % of the total army must be this type of unit.")] - [FieldLoader.LoadUsing("LoadUnits")] public readonly Dictionary UnitsToBuild = null; [Desc("What buildings to the AI should build.", "What % of the total base must be this type of building.")] - [FieldLoader.LoadUsing("LoadBuildings")] public readonly Dictionary BuildingFractions = null; [Desc("Tells the AI what unit types fall under the same common name.")] - [FieldLoader.LoadUsing("LoadUnitsCommonNames")] public readonly Dictionary UnitsCommonNames = null; [Desc("Tells the AI what building types fall under the same common name.")] - [FieldLoader.LoadUsing("LoadBuildingsCommonNames")] public readonly Dictionary BuildingCommonNames = null; [Desc("What buildings should the AI have max limits n.", "What is the limit of the building.")] - [FieldLoader.LoadUsing("LoadBuildingLimits")] public readonly Dictionary BuildingLimits = null; // TODO Update OpenRA.Utility/Command.cs#L300 to first handle lists and also read nested ones @@ -138,22 +133,6 @@ namespace OpenRA.Mods.Common.AI [FieldLoader.LoadUsing("LoadDecisions")] public readonly List PowerDecisions = new List(); - static object LoadList(MiniYaml y, string field) - { - var nd = y.ToDictionary(); - return nd.ContainsKey(field) - ? nd[field].ToDictionary(my => FieldLoader.GetValue(field, my.Value)) - : new Dictionary(); - } - - static object LoadUnits(MiniYaml y) { return LoadList(y, "UnitsToBuild"); } - static object LoadBuildings(MiniYaml y) { return LoadList(y, "BuildingFractions"); } - - static object LoadUnitsCommonNames(MiniYaml y) { return LoadList(y, "UnitsCommonNames"); } - static object LoadBuildingsCommonNames(MiniYaml y) { return LoadList(y, "BuildingCommonNames"); } - - static object LoadBuildingLimits(MiniYaml y) { return LoadList(y, "BuildingLimits"); } - static object LoadDecisions(MiniYaml yaml) { var ret = new List(); diff --git a/OpenRA.Mods.Common/InstallUtils.cs b/OpenRA.Mods.Common/InstallUtils.cs index c123785c90..c74daa0ed1 100644 --- a/OpenRA.Mods.Common/InstallUtils.cs +++ b/OpenRA.Mods.Common/InstallUtils.cs @@ -28,40 +28,19 @@ namespace OpenRA.Mods.Common public readonly string PackageToExtractFromCD = null; public readonly bool OverwriteFiles = true; - [FieldLoader.LoadUsing("LoadFilesToExtract")] - public readonly Dictionary ExtractFilesFromCD = new Dictionary(); - - [FieldLoader.LoadUsing("LoadFilesToCopy")] public readonly Dictionary CopyFilesFromCD = new Dictionary(); + public readonly Dictionary ExtractFilesFromCD = new Dictionary(); public readonly string PackageMirrorList = null; public readonly string MusicPackageMirrorList = null; public readonly int ShippedSoundtracks = 0; - /// InstallShield .cab File Ids, used to extract Mod specific files + /// InstallShield .cab File Ids, used to extract Mod specific files. public readonly int[] InstallShieldCABFileIds = { }; - /// InstallShield .cab File Ids, used to extract Mod specific archives and extract contents of ExtractFilesFromCD + /// InstallShield .cab File Ids, used to extract Mod specific archives and extract contents of ExtractFilesFromCD. public readonly string[] InstallShieldCABFilePackageIds = { }; - - public static Dictionary LoadFilesToExtract(MiniYaml yaml) - { - var md = yaml.ToDictionary(); - - return md.ContainsKey("ExtractFilesFromCD") - ? md["ExtractFilesFromCD"].ToDictionary(my => FieldLoader.GetValue("(value)", my.Value)) - : new Dictionary(); - } - - public static Dictionary LoadFilesToCopy(MiniYaml yaml) - { - var md = yaml.ToDictionary(); - - return md.ContainsKey("CopyFilesFromCD") - ? md["CopyFilesFromCD"].ToDictionary(my => FieldLoader.GetValue("(value)", my.Value)) - : new Dictionary(); - } } public static class InstallUtils diff --git a/OpenRA.Mods.Common/Traits/GainsExperience.cs b/OpenRA.Mods.Common/Traits/GainsExperience.cs index bf81233b17..f1865ce280 100644 --- a/OpenRA.Mods.Common/Traits/GainsExperience.cs +++ b/OpenRA.Mods.Common/Traits/GainsExperience.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; -using System.Linq; using OpenRA.Mods.Common.Effects; using OpenRA.Primitives; using OpenRA.Traits; @@ -20,7 +19,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("This actor's experience increases when it has killed a GivesExperience actor.")] public class GainsExperienceInfo : ITraitInfo, Requires, Requires { - [FieldLoader.LoadUsing("LoadUpgrades", true)] + [FieldLoader.Require] [Desc("Upgrades to grant at each level.", "Key is the XP requirements for each level as a percentage of our own value.", "Value is a list of the upgrade types to grant")] @@ -33,15 +32,6 @@ namespace OpenRA.Mods.Common.Traits public readonly bool SuppressLevelupAnimation = true; public object Create(ActorInitializer init) { return new GainsExperience(init, this); } - - static object LoadUpgrades(MiniYaml y) - { - MiniYaml upgrades = y.ToDictionary()["Upgrades"]; - - return upgrades.Nodes.ToDictionary( - kv => FieldLoader.GetValue("(key)", kv.Key), - kv => FieldLoader.GetValue("(value)", kv.Value.Value)); - } } public class GainsExperience : ISync, IResolveOrder diff --git a/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs b/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs index 56b9a7170e..9f93e34d01 100644 --- a/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs +++ b/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs @@ -28,7 +28,6 @@ namespace OpenRA.Mods.Common.Traits [Desc("Damage types that trigger prone state. Defined on the warheads.")] public readonly string[] DamageTriggers = null; - [FieldLoader.LoadUsing("LoadModifiers")] [Desc("Damage modifiers for each damage type (defined on the warheads) while the unit is prone.")] public readonly Dictionary DamageModifiers = new Dictionary(); @@ -37,15 +36,6 @@ namespace OpenRA.Mods.Common.Traits [SequenceReference(null, true)] public readonly string ProneSequencePrefix = "prone-"; public override object Create(ActorInitializer init) { return new TakeCover(init, this); } - - public static object LoadModifiers(MiniYaml yaml) - { - var md = yaml.ToDictionary(); - - return md.ContainsKey("DamageModifiers") - ? md["DamageModifiers"].ToDictionary(my => FieldLoader.GetValue("(value)", my.Value)) - : new Dictionary(); - } } public class TakeCover : Turreted, INotifyDamage, IDamageModifier, ISpeedModifier, ISync, IRenderInfantrySequenceModifier diff --git a/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs b/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs index 81a9cd082f..aeac81ef69 100644 --- a/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs +++ b/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs @@ -9,7 +9,6 @@ #endregion using System.Collections.Generic; -using System.Linq; using OpenRA.Mods.Common.Warheads; using OpenRA.Traits; @@ -17,7 +16,7 @@ namespace OpenRA.Mods.Common.Traits { public class TerrainModifiesDamageInfo : ITraitInfo { - [FieldLoader.LoadUsing("LoadPercents", true)] + [FieldLoader.Require] [Desc("Damage percentage for specific terrain types. 120 = 120%, 80 = 80%, etc.")] public readonly Dictionary TerrainModifier = null; @@ -25,13 +24,6 @@ namespace OpenRA.Mods.Common.Traits public readonly bool ModifyHealing = false; public object Create(ActorInitializer init) { return new TerrainModifiesDamage(init.Self, this); } - - static object LoadPercents(MiniYaml y) - { - return y.ToDictionary()["TerrainModifier"].Nodes.ToDictionary( - kv => FieldLoader.GetValue("(key)", kv.Key), - kv => FieldLoader.GetValue("(value)", kv.Value.Value)); - } } public class TerrainModifiesDamage : IDamageModifier diff --git a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs index 551e638fcd..fab7f4f625 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs @@ -29,7 +29,6 @@ namespace OpenRA.Mods.Common.Traits [Desc("The sequence name that defines the actor sprites. Defaults to the actor name.")] public readonly string Image = null; - [FieldLoader.LoadUsing("LoadFactionImages")] [Desc("A dictionary of faction-specific image overrides.")] public readonly Dictionary FactionImages = null; @@ -42,16 +41,6 @@ namespace OpenRA.Mods.Common.Traits [Desc("Change the sprite image size.")] public readonly float Scale = 1f; - protected static object LoadFactionImages(MiniYaml y) - { - MiniYaml images; - - if (!y.ToDictionary().TryGetValue("FactionImages", out images)) - return null; - - return images.Nodes.ToDictionary(kv => kv.Key, kv => kv.Value.Value); - } - public virtual object Create(ActorInitializer init) { return new RenderSprites(init, this); } public IEnumerable RenderPreview(ActorPreviewInitializer init)