1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using OpenRA.GameRules; 16 17 namespace OpenRA.Mods.Common.Lint 18 { 19 class CheckUnknownWeaponFields : ILintPass, ILintMapPass 20 { NormalizeName(string key)21 string NormalizeName(string key) 22 { 23 var name = key.Split('@')[0]; 24 if (name.StartsWith("-", StringComparison.Ordinal)) 25 return name.Substring(1); 26 27 return name; 28 } 29 CheckWeapons(IEnumerable<MiniYamlNode> weapons, Action<string> emitError, Action<string> emitWarning, ModData modData)30 void CheckWeapons(IEnumerable<MiniYamlNode> weapons, Action<string> emitError, Action<string> emitWarning, ModData modData) 31 { 32 var weaponInfo = typeof(WeaponInfo); 33 foreach (var weapon in weapons) 34 { 35 foreach (var field in weapon.Value.Nodes) 36 { 37 // Removals can never define children or values 38 if (field.Key.StartsWith("-", StringComparison.Ordinal)) 39 { 40 if (field.Value.Nodes.Any()) 41 emitError("{0} {1} defines child nodes, which is not valid for removals.".F(field.Location, field.Key)); 42 43 if (!string.IsNullOrEmpty(field.Value.Value)) 44 emitError("{0} {1} defines a value, which is not valid for removals.".F(field.Location, field.Key)); 45 46 continue; 47 } 48 49 var fieldName = NormalizeName(field.Key); 50 if (fieldName == "Projectile" && !string.IsNullOrEmpty(field.Value.Value)) 51 { 52 var projectileName = NormalizeName(field.Value.Value); 53 var projectileInfo = modData.ObjectCreator.FindType(projectileName + "Info"); 54 foreach (var projectileField in field.Value.Nodes) 55 { 56 var projectileFieldName = NormalizeName(projectileField.Key); 57 if (projectileInfo.GetField(projectileFieldName) == null) 58 emitError("{0} refers to a projectile field `{1}` that does not exist on `{2}`.".F(projectileField.Location, projectileFieldName, projectileName)); 59 } 60 } 61 else if (fieldName == "Warhead") 62 { 63 if (string.IsNullOrEmpty(field.Value.Value)) 64 { 65 emitWarning("{0} does not define a warhead type. Skipping unknown field check.".F(field.Location)); 66 continue; 67 } 68 69 var warheadName = NormalizeName(field.Value.Value); 70 var warheadInfo = modData.ObjectCreator.FindType(warheadName + "Warhead"); 71 foreach (var warheadField in field.Value.Nodes) 72 { 73 var warheadFieldName = NormalizeName(warheadField.Key); 74 if (warheadInfo.GetField(warheadFieldName) == null) 75 emitError("{0} refers to a warhead field `{1}` that does not exist on `{2}`.".F(warheadField.Location, warheadFieldName, warheadName)); 76 } 77 } 78 else if (fieldName != "Inherits" && weaponInfo.GetField(fieldName) == null) 79 emitError("{0} refers to a weapon field `{1}` that does not exist.".F(field.Location, fieldName)); 80 } 81 } 82 } 83 ILintPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData)84 void ILintPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData) 85 { 86 foreach (var f in modData.Manifest.Weapons) 87 CheckWeapons(MiniYaml.FromStream(modData.DefaultFileSystem.Open(f), f), emitError, emitWarning, modData); 88 } 89 ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map)90 void ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map) 91 { 92 if (map.WeaponDefinitions != null && map.WeaponDefinitions.Value != null) 93 { 94 var mapFiles = FieldLoader.GetValue<string[]>("value", map.WeaponDefinitions.Value); 95 foreach (var f in mapFiles) 96 CheckWeapons(MiniYaml.FromStream(map.Open(f), f), emitError, emitWarning, modData); 97 98 if (map.WeaponDefinitions.Nodes.Any()) 99 CheckWeapons(map.WeaponDefinitions.Nodes, emitError, emitWarning, modData); 100 } 101 } 102 } 103 } 104