diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index 5d207956df..978220e072 100755
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -83,6 +83,7 @@
+
@@ -277,7 +278,4 @@
-->
-
-
-
\ No newline at end of file
diff --git a/OpenRA.Game/Traits/AttackBase.cs b/OpenRA.Game/Traits/AttackBase.cs
index 7d2a5ada2e..4001cf4254 100644
--- a/OpenRA.Game/Traits/AttackBase.cs
+++ b/OpenRA.Game/Traits/AttackBase.cs
@@ -29,7 +29,9 @@ namespace OpenRA.Traits
{
public class AttackBaseInfo : ITraitInfo
{
+ [WeaponReference]
public readonly string PrimaryWeapon = null;
+ [WeaponReference]
public readonly string SecondaryWeapon = null;
public readonly int Recoil = 0;
public readonly int[] PrimaryLocalOffset = { };
diff --git a/OpenRA.Game/Traits/LintAttributes.cs b/OpenRA.Game/Traits/LintAttributes.cs
new file mode 100644
index 0000000000..b0aeac5a92
--- /dev/null
+++ b/OpenRA.Game/Traits/LintAttributes.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenRA.Traits
+{
+ /* attributes used by RALint to understand the rules */
+
+ [AttributeUsage(AttributeTargets.Field)]
+ public class ActorReferenceAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field)]
+ public class WeaponReferenceAttribute : Attribute { }
+}
diff --git a/OpenRA.sln b/OpenRA.sln
index 5bb2026243..f198a7e3bf 100644
--- a/OpenRA.sln
+++ b/OpenRA.sln
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Editor", "OpenRA.Edi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.TilesetBuilder", "OpenRA.TilesetBuilder\OpenRA.TilesetBuilder.csproj", "{56B1073B-AE14-499A-BB98-43A8DE9A39CA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RALint", "RALint\RALint.csproj", "{F9FA4D9F-2302-470A-8A07-6E37F488C124}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -108,6 +110,14 @@ Global
{56B1073B-AE14-499A-BB98-43A8DE9A39CA}.Release|Any CPU.Build.0 = Release|Any CPU
{56B1073B-AE14-499A-BB98-43A8DE9A39CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{56B1073B-AE14-499A-BB98-43A8DE9A39CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/RALint/Program.cs b/RALint/Program.cs
new file mode 100644
index 0000000000..2b3be11ad6
--- /dev/null
+++ b/RALint/Program.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using OpenRA.FileFormats;
+using OpenRA;
+using OpenRA.GameRules;
+using OpenRA.Traits;
+using System.Reflection;
+
+namespace RALint
+{
+ static class Program
+ {
+ /* todo: move this into the engine? dpstool, seqed, editor, etc all need it (or something similar) */
+ static void InitializeEngineWithMods(string[] mods)
+ {
+ AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly;
+ var manifest = new Manifest(mods);
+ Game.LoadModAssemblies(manifest);
+
+ FileSystem.UnmountAll();
+ foreach (var folder in manifest.Folders) FileSystem.Mount(folder);
+ foreach (var pkg in manifest.Packages) FileSystem.Mount(pkg);
+
+ Rules.LoadRules(manifest, new Map());
+ }
+
+ static int errors = 0;
+
+ static void EmitError(string e)
+ {
+ Console.WriteLine(e);
+ ++errors;
+ }
+
+ static int Main(string[] args)
+ {
+ InitializeEngineWithMods(args);
+
+ foreach (var actorInfo in Rules.Info)
+ foreach (var traitInfo in actorInfo.Value.Traits.WithInterface())
+ CheckTrait(actorInfo.Value, traitInfo);
+
+ if (errors > 0)
+ {
+ Console.WriteLine("Errors: {0}", errors);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ static void CheckTrait(ActorInfo actorInfo, ITraitInfo traitInfo)
+ {
+ var actualType = traitInfo.GetType();
+ foreach (var field in actualType.GetFields())
+ {
+ if (field.HasAttribute())
+ CheckReference(actorInfo, traitInfo, field, Rules.Info, "actor");
+ if (field.HasAttribute())
+ CheckReference(actorInfo, traitInfo, field, Rules.Weapons, "weapon");
+ }
+ }
+
+ static string[] GetFieldValues(ITraitInfo traitInfo, FieldInfo fieldInfo)
+ {
+ var type = fieldInfo.FieldType;
+ if (type == typeof(string))
+ return new string[] { (string)fieldInfo.GetValue(traitInfo) };
+ if (type == typeof(string[]))
+ return (string[])fieldInfo.GetValue(traitInfo);
+
+ EmitError("Bad type for reference on {0}.{1}. Supported types: string, string[]"
+ .F(traitInfo.GetType().Name, fieldInfo.Name));
+
+ return new string[] { };
+ }
+
+ static void CheckReference(ActorInfo actorInfo, ITraitInfo traitInfo, FieldInfo fieldInfo,
+ Dictionary dict, string type)
+ {
+ var values = GetFieldValues(traitInfo, fieldInfo);
+ foreach (var v in values)
+ if (v != null && !dict.ContainsKey(v.ToLowerInvariant()))
+ EmitError("{0}.{1}.{2}: Missing {3} `{4}`."
+ .F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, type, v));
+ }
+
+ static bool HasAttribute(this MemberInfo mi)
+ {
+ return mi.GetCustomAttributes(typeof(T), true).Length != 0;
+ }
+ }
+}
diff --git a/RALint/Properties/AssemblyInfo.cs b/RALint/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2713d0bd41
--- /dev/null
+++ b/RALint/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("RALint")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RALint")]
+[assembly: AssemblyCopyright("Copyright © 2010")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("722ef098-e9f3-4e3c-b374-3388dfad8979")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/RALint/RALint.csproj b/RALint/RALint.csproj
new file mode 100644
index 0000000000..4cc79d0a91
--- /dev/null
+++ b/RALint/RALint.csproj
@@ -0,0 +1,71 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {F9FA4D9F-2302-470A-8A07-6E37F488C124}
+ Exe
+ Properties
+ RALint
+ RALint
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ ..\
+ DEBUG;TRACE
+ prompt
+ 4
+ x86
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+ 3.5
+
+
+ 3.5
+
+
+ 3.5
+
+
+
+
+
+
+
+
+
+
+ {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}
+ OpenRA.FileFormats
+
+
+ {0DFB103F-2962-400F-8C6D-E2C28CCBA633}
+ OpenRA.Game
+
+
+
+
+
\ No newline at end of file