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.Linq;
14 using System.Reflection;
15 using OpenRA.GameRules;
16 using OpenRA.Traits;
17 
18 namespace OpenRA.Mods.Common.Lint
19 {
20 	public class CheckActorReferences : ILintRulesPass
21 	{
22 		Action<string> emitError;
23 
Run(Action<string> emitError, Action<string> emitWarning, Ruleset rules)24 		public void Run(Action<string> emitError, Action<string> emitWarning, Ruleset rules)
25 		{
26 			this.emitError = emitError;
27 
28 			foreach (var actorInfo in rules.Actors)
29 				foreach (var traitInfo in actorInfo.Value.TraitInfos<ITraitInfo>())
30 					CheckTrait(actorInfo.Value, traitInfo, rules);
31 		}
32 
CheckTrait(ActorInfo actorInfo, ITraitInfo traitInfo, Ruleset rules)33 		void CheckTrait(ActorInfo actorInfo, ITraitInfo traitInfo, Ruleset rules)
34 		{
35 			var actualType = traitInfo.GetType();
36 			foreach (var field in actualType.GetFields())
37 			{
38 				if (field.HasAttribute<ActorReferenceAttribute>())
39 					CheckActorReference(actorInfo, traitInfo, field, rules.Actors,
40 						field.GetCustomAttributes<ActorReferenceAttribute>(true)[0]);
41 
42 				if (field.HasAttribute<WeaponReferenceAttribute>())
43 					CheckWeaponReference(actorInfo, traitInfo, field, rules.Weapons,
44 						field.GetCustomAttributes<WeaponReferenceAttribute>(true)[0]);
45 
46 				if (field.HasAttribute<VoiceSetReferenceAttribute>())
47 					CheckVoiceReference(actorInfo, traitInfo, field, rules.Voices,
48 						field.GetCustomAttributes<VoiceSetReferenceAttribute>(true)[0]);
49 			}
50 		}
51 
CheckActorReference(ActorInfo actorInfo, ITraitInfo traitInfo, FieldInfo fieldInfo, IReadOnlyDictionary<string, ActorInfo> dict, ActorReferenceAttribute attribute)52 		void CheckActorReference(ActorInfo actorInfo,
53 			ITraitInfo traitInfo,
54 			FieldInfo fieldInfo,
55 			IReadOnlyDictionary<string, ActorInfo> dict,
56 			ActorReferenceAttribute attribute)
57 		{
58 			var values = LintExts.GetFieldValues(traitInfo, fieldInfo, emitError);
59 			foreach (var value in values)
60 			{
61 				if (value == null)
62 					continue;
63 
64 				// NOTE: Once https://github.com/OpenRA/OpenRA/issues/4124 is resolved we won't
65 				//       have to .ToLower* anything here.
66 				var v = value.ToLowerInvariant();
67 
68 				if (!dict.ContainsKey(v))
69 				{
70 					emitError("{0}.{1}.{2}: Missing actor `{3}`."
71 						.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
72 
73 					continue;
74 				}
75 
76 				foreach (var requiredTrait in attribute.RequiredTraits)
77 					if (!dict[v].TraitsInConstructOrder().Any(t => t.GetType() == requiredTrait || t.GetType().IsSubclassOf(requiredTrait)))
78 						emitError("Actor type {0} does not have trait {1} which is required by {2}.{3}."
79 							.F(value, requiredTrait.Name, traitInfo.GetType().Name, fieldInfo.Name));
80 			}
81 		}
82 
CheckWeaponReference(ActorInfo actorInfo, ITraitInfo traitInfo, FieldInfo fieldInfo, IReadOnlyDictionary<string, WeaponInfo> dict, WeaponReferenceAttribute attribute)83 		void CheckWeaponReference(ActorInfo actorInfo,
84 			ITraitInfo traitInfo,
85 			FieldInfo fieldInfo,
86 			IReadOnlyDictionary<string, WeaponInfo> dict,
87 			WeaponReferenceAttribute attribute)
88 		{
89 			var values = LintExts.GetFieldValues(traitInfo, fieldInfo, emitError);
90 			foreach (var value in values)
91 			{
92 				if (value == null)
93 					continue;
94 
95 				if (!dict.ContainsKey(value.ToLower()))
96 					emitError("{0}.{1}.{2}: Missing weapon `{3}`."
97 						.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
98 			}
99 		}
100 
CheckVoiceReference(ActorInfo actorInfo, ITraitInfo traitInfo, FieldInfo fieldInfo, IReadOnlyDictionary<string, SoundInfo> dict, VoiceSetReferenceAttribute attribute)101 		void CheckVoiceReference(ActorInfo actorInfo,
102 			ITraitInfo traitInfo,
103 			FieldInfo fieldInfo,
104 			IReadOnlyDictionary<string, SoundInfo> dict,
105 			VoiceSetReferenceAttribute attribute)
106 		{
107 			var values = LintExts.GetFieldValues(traitInfo, fieldInfo, emitError);
108 			foreach (var value in values)
109 			{
110 				if (value == null)
111 					continue;
112 
113 				if (!dict.ContainsKey(value.ToLower()))
114 					emitError("{0}.{1}.{2}: Missing voice `{3}`."
115 						.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
116 			}
117 		}
118 	}
119 }
120