diff --git a/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs b/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs index df3f8490ce..97d319b411 100644 --- a/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs +++ b/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs @@ -12,8 +12,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; -using OpenRA.Mods.Common.Widgets; using OpenRA.Primitives; using OpenRA.Traits; using OpenRA.Widgets; @@ -30,6 +28,9 @@ namespace OpenRA.Mods.Common.Lint } } + [AttributeUsage(AttributeTargets.Method)] + public sealed class CustomLintableHotkeyNames : Attribute { } + class CheckChromeHotkeys : ILintPass { public void Run(Action emitError, Action emitWarning, ModData modData) @@ -48,12 +49,28 @@ namespace OpenRA.Mods.Common.Lint .Select(f => Pair.New(w.Name.Substring(0, w.Name.Length - 6), f.Name))) .ToArray(); + var customLintMethods = new Dictionary>(); + + foreach (var w in modData.ObjectCreator.GetTypesImplementing()) + { + foreach (var m in w.GetMethods().Where(m => m.HasAttribute())) + { + var p = m.GetParameters(); + if (p.Length == 3 && p[0].ParameterType == typeof(MiniYamlNode) && p[1].ParameterType == typeof(Action) + && p[2].ParameterType == typeof(Action)) + customLintMethods.GetOrAdd(w.Name.Substring(0, w.Name.Length - 6)).Add(m.Name); + } + } + foreach (var filename in modData.Manifest.ChromeLayout) - CheckInner(modData, namedKeys, checkWidgetFields, MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename), filename, null, emitError); + { + var yaml = MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename); + CheckInner(modData, namedKeys, checkWidgetFields, customLintMethods, yaml, filename, null, emitError, emitWarning); + } } - void CheckInner(ModData modData, string[] namedKeys, Pair[] checkWidgetFields, - List nodes, string filename, MiniYamlNode parent, Action emitError) + void CheckInner(ModData modData, string[] namedKeys, Pair[] checkWidgetFields, Dictionary> customLintMethods, + List nodes, string filename, MiniYamlNode parent, Action emitError, Action emitWarning) { foreach (var node in nodes) { @@ -71,6 +88,20 @@ namespace OpenRA.Mods.Common.Lint } } + // Check runtime-defined hotkey names + List checkMethods; + var widgetType = node.Key.Split('@')[0]; + if (customLintMethods.TryGetValue(widgetType, out checkMethods)) + { + var type = modData.ObjectCreator.FindType(widgetType + "Widget"); + var keyNames = checkMethods.SelectMany(m => (IEnumerable)type.GetMethod(m).Invoke(null, new object[] { node, emitError, emitWarning })); + + Hotkey unused; + foreach (var name in keyNames) + if (!namedKeys.Contains(name) && !Hotkey.TryParse(name, out unused)) + emitError("{0} refers to a Key named `{1}` that does not exist".F(node.Location, name)); + } + // Logic classes can declare the data key names that specify hotkeys if (node.Key == "Logic" && node.Value.Nodes.Any()) { @@ -93,7 +124,7 @@ namespace OpenRA.Mods.Common.Lint } if (node.Value.Nodes != null) - CheckInner(modData, namedKeys, checkWidgetFields, node.Value.Nodes, filename, node, emitError); + CheckInner(modData, namedKeys, checkWidgetFields, customLintMethods, node.Value.Nodes, filename, node, emitError, emitWarning); } } }