diff --git a/Makefile b/Makefile
index 08505885fd..d407aa0682 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,7 @@
CSC = dmcs
CSFLAGS = -nologo -warn:4 -debug:full -optimize- -codepage:utf8 -unsafe -warnaserror
DEFINE = DEBUG;TRACE
-COMMON_LIBS = System.dll System.Core.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll thirdparty/Mono.Nat.dll thirdparty/MaxMind.Db.dll thirdparty/MaxMind.GeoIP2.dll thirdparty/Eluant.dll
+COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll thirdparty/Mono.Nat.dll thirdparty/MaxMind.Db.dll thirdparty/MaxMind.GeoIP2.dll thirdparty/Eluant.dll
diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
index 2fb5a9408d..080772d489 100644
--- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
+++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
@@ -534,6 +534,9 @@
+
+
+
diff --git a/OpenRA.Mods.RA/UtilityCommands/ActorStatsExport.cs b/OpenRA.Mods.RA/UtilityCommands/ActorStatsExport.cs
new file mode 100644
index 0000000000..5c405328ba
--- /dev/null
+++ b/OpenRA.Mods.RA/UtilityCommands/ActorStatsExport.cs
@@ -0,0 +1,99 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using OpenRA.GameRules;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.RA.UtilityCommands
+{
+ public static class ActorStatsExport
+ {
+ public static DataTable GenerateTable()
+ {
+ var rules = Game.modData.RulesetCache.LoadDefaultRules();
+
+ var table = new DataTable();
+ table.Columns.Add("Name", typeof(string));
+ table.Columns.Add("Cost", typeof(int));
+ table.Columns.Add("HitPoints", typeof(int));
+ table.Columns.Add("Armor", typeof(string));
+ table.Columns.Add("Damage /s", typeof(float));
+
+ var armorList = new List();
+ foreach (var actorInfo in rules.Actors.Values)
+ {
+ var armor = actorInfo.Traits.GetOrDefault();
+ if (armor != null)
+ if (!armorList.Contains(armor.Type))
+ armorList.Add(armor.Type);
+ }
+
+ armorList.Sort();
+ foreach (var armorType in armorList)
+ table.Columns.Add("vs. " + armorType, typeof(float));
+
+ foreach (var actorInfo in rules.Actors.Values)
+ {
+ if (actorInfo.Name.StartsWith("^"))
+ continue;
+
+ var buildable = actorInfo.Traits.GetOrDefault();
+ if (buildable == null)
+ continue;
+
+ var row = table.NewRow();
+ var tooltip = actorInfo.Traits.GetOrDefault();
+ row["Name"] = tooltip != null ? tooltip.Name : actorInfo.Name;
+
+ var value = actorInfo.Traits.GetOrDefault();
+ row["Cost"] = value != null ? value.Cost : 0;
+
+ var health = actorInfo.Traits.GetOrDefault();
+ row["HitPoints"] = health != null ? health.HP : 0;
+
+ var armor = actorInfo.Traits.GetOrDefault();
+ row["Armor"] = armor != null ? armor.Type : "";
+
+ var armaments = actorInfo.Traits.WithInterface();
+ if (armaments.Any())
+ {
+ var weapons = armaments.Select(a => rules.Weapons[a.Weapon.ToLowerInvariant()]);
+ foreach (var weapon in weapons)
+ {
+ var warhead = weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead;
+ if (warhead != null)
+ {
+ var rateOfFire = weapon.ReloadDelay > 1 ? weapon.ReloadDelay : 1;
+ var burst = weapon.Burst;
+ var delay = weapon.BurstDelay;
+ var damage = warhead.Damage;
+ var damagePerSecond = (1000f / Game.Timestep) * (damage * burst) / (delay + burst * rateOfFire);
+ row["Damage /s"] = Math.Round(damagePerSecond, 1, MidpointRounding.AwayFromZero);
+
+ foreach (var armorType in armorList)
+ {
+ var vs = warhead.Versus.ContainsKey(armorType) ? warhead.Versus[armorType] : 100;
+ row["vs. " + armorType] = Math.Round(damagePerSecond * vs / 100, 1, MidpointRounding.AwayFromZero);
+ }
+ }
+ }
+ }
+
+ table.Rows.Add(row);
+ }
+
+ return table;
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenRA.Mods.RA/UtilityCommands/ExportCharacterSeparatedRules.cs b/OpenRA.Mods.RA/UtilityCommands/ExportCharacterSeparatedRules.cs
new file mode 100644
index 0000000000..23f12bf7b0
--- /dev/null
+++ b/OpenRA.Mods.RA/UtilityCommands/ExportCharacterSeparatedRules.cs
@@ -0,0 +1,32 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using System.IO;
+
+namespace OpenRA.Mods.RA.UtilityCommands
+{
+ class ExportCharacterSeparatedRules : IUtilityCommand
+ {
+ public string Name { get { return "--generate-dps-table"; } }
+
+ [Desc("Export the damage per second evaluation into a CSV file for inspection.")]
+ public void Run(ModData modData, string[] args)
+ {
+ Game.modData = modData;
+ var table = ActorStatsExport.GenerateTable();
+ var filename = "{0}-mod-dps.csv".F(Game.modData.Manifest.Mod.Id);
+ using (var outfile = new StreamWriter(filename))
+ outfile.Write(table.ToCharacterSeparatedValues(";", true));
+ Console.WriteLine("{0} has been saved.".F(filename));
+ Console.WriteLine("Open as values separated by semicolon.");
+ }
+ }
+}
diff --git a/OpenRA.Mods.RA/UtilityCommands/Extensions.cs b/OpenRA.Mods.RA/UtilityCommands/Extensions.cs
new file mode 100644
index 0000000000..0d89099819
--- /dev/null
+++ b/OpenRA.Mods.RA/UtilityCommands/Extensions.cs
@@ -0,0 +1,54 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using System.Data;
+using System.Text;
+
+namespace OpenRA.Mods.RA.UtilityCommands
+{
+ public static class Extensions
+ {
+ public static string ToCharacterSeparatedValues(this DataTable table, string delimiter, bool includeHeader)
+ {
+ var result = new StringBuilder();
+
+ if (includeHeader)
+ {
+ foreach (DataColumn column in table.Columns)
+ {
+ result.Append(column.ColumnName);
+ result.Append(delimiter);
+ }
+
+ result.Remove(result.Length, 0);
+ result.AppendLine();
+ }
+
+ foreach (DataRow row in table.Rows)
+ {
+ for (var x = 0; x < table.Columns.Count; x++)
+ {
+ if (x != 0)
+ result.Append(delimiter);
+
+ result.Append(row[table.Columns[x]]);
+ }
+
+ result.AppendLine();
+ }
+
+ result.Remove(result.Length, 0);
+ result.AppendLine();
+
+ return result.ToString();
+ }
+ }
+}
\ No newline at end of file