Reworked weapon documentation generation

Switched the Utility's ExtractWeaponDocsCommand output to JSON.
Added a Python script to generate documentation Markdown from JSON.
This commit is contained in:
penev92
2022-03-10 20:02:17 +02:00
committed by Matthias Mailänder
parent c21bf31ebc
commit a522457bb6
4 changed files with 170 additions and 55 deletions

View File

@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using OpenRA.GameRules;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
@@ -298,6 +299,23 @@ namespace OpenRA.Mods.Common
return t.Name;
}
public static string GetAttributeParameterValue(CustomAttributeTypedArgument value)
{
if (value.ArgumentType.IsEnum)
return Enum.Parse(value.ArgumentType, value.Value.ToString()).ToString();
if (value.ArgumentType == typeof(Type) && value.Value != null)
return (value.Value as Type).Name;
if (value.ArgumentType.IsArray)
{
var names = (value.Value as IReadOnlyCollection<CustomAttributeTypedArgument>).Select(x => (x.Value as Type).Name);
return string.Join(", ", names);
}
return value.Value?.ToString();
}
public static int GetProjectileInaccuracy(int baseInaccuracy, InaccuracyType inaccuracyType, ProjectileArgs args)
{
var inaccuracy = ApplyPercentageModifiers(baseInaccuracy, args.InaccuracyModifiers);

View File

@@ -10,9 +10,11 @@
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using OpenRA.GameRules;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.UtilityCommands
@@ -26,7 +28,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
return true;
}
[Desc("[VERSION]", "Generate weaponry documentation in MarkDown format.")]
[Desc("[VERSION]", "Generate weaponry documentation in JSON format.")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
@@ -36,64 +38,65 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (args.Length > 1)
version = args[1];
var doc = new StringBuilder();
doc.AppendLine(
"This documentation is aimed at modders. It displays a template for weapon definitions " +
"as well as its contained types (warheads and projectiles) with default values and developer commentary. " +
"Please do not edit it directly, but add new `[Desc(\"String\")]` tags to the source code. This file has been " +
$"automatically generated for version {version} of OpenRA.");
doc.AppendLine();
var currentNamespace = "";
var objectCreator = utility.ModData.ObjectCreator;
var weaponInfo = new[] { typeof(WeaponInfo) };
var warheads = objectCreator.GetTypesImplementing<IWarhead>().OrderBy(t => t.Namespace);
var projectiles = objectCreator.GetTypesImplementing<IProjectileInfo>().OrderBy(t => t.Namespace);
var weaponTypes = weaponInfo.Concat(projectiles).Concat(warheads);
foreach (var t in weaponTypes)
var json = GenerateJson(version, weaponTypes, objectCreator);
Console.WriteLine(json);
}
static string GenerateJson(string version, IEnumerable<Type> weaponTypes, ObjectCreator objectCreator)
{
var weaponTypesInfo = weaponTypes.Where(x => !x.ContainsGenericParameters && !x.IsAbstract)
.Select(type => new
{
type.Namespace,
Name = type.Name.EndsWith("Info") ? type.Name.Substring(0, type.Name.Length - 4) : type.Name,
Description = string.Join(" ", type.GetCustomAttributes<DescAttribute>(false).SelectMany(d => d.Lines)),
InheritedTypes = type.BaseTypes()
.Select(y => y.Name)
.Where(y => y != type.Name && y != $"{type.Name}Info" && y != "Object"),
Properties = FieldLoader.GetTypeLoadInfo(type)
.Where(fi => fi.Field.IsPublic && fi.Field.IsInitOnly && !fi.Field.IsStatic)
.Select(fi => new
{
PropertyName = fi.YamlName,
DefaultValue = FieldSaver.SaveField(objectCreator.CreateBasic(type), fi.Field.Name).Value.Value,
InternalType = Util.InternalTypeName(fi.Field.FieldType),
UserFriendlyType = Util.FriendlyTypeName(fi.Field.FieldType),
Description = string.Join(" ", fi.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines)),
OtherAttributes = fi.Field.CustomAttributes
.Where(a => a.AttributeType.Name != nameof(DescAttribute) && a.AttributeType.Name != nameof(FieldLoader.LoadUsingAttribute))
.Select(a =>
{
var name = a.AttributeType.Name;
name = name.EndsWith("Attribute") ? name.Substring(0, name.Length - 9) : name;
return new
{
Name = name,
Parameters = a.Constructor.GetParameters()
.Select(pi => new
{
pi.Name,
Value = Util.GetAttributeParameterValue(a.ConstructorArguments[pi.Position])
})
};
})
})
});
var result = new
{
// skip helpers like TraitInfo<T>
if (t.ContainsGenericParameters || t.IsAbstract)
continue;
Version = version,
WeaponTypes = weaponTypesInfo
};
if (currentNamespace != t.Namespace)
{
currentNamespace = t.Namespace;
doc.AppendLine();
doc.AppendLine($"## {currentNamespace}");
}
var traitName = t.Name.EndsWith("Info") ? t.Name.Substring(0, t.Name.Length - 4) : t.Name;
doc.AppendLine();
doc.AppendLine($"### {traitName}");
var traitDescLines = t.GetCustomAttributes<DescAttribute>(false).SelectMany(d => d.Lines);
foreach (var line in traitDescLines)
doc.AppendLine(line);
var infos = FieldLoader.GetTypeLoadInfo(t);
if (!infos.Any())
continue;
doc.AppendLine();
doc.AppendLine("| Property | Default Value | Type | Description |");
doc.AppendLine("| -------- | ------------- | ---- | ----------- |");
var liveTraitInfo = objectCreator.CreateBasic(t);
foreach (var info in infos)
{
var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value;
var fieldType = Util.FriendlyTypeName(info.Field.FieldType);
var fieldDescLines = info.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
doc.AppendLine($"| {info.YamlName} | {defaultValue} | {fieldType} | {string.Join(" ", fieldDescLines)} |");
}
}
Console.Write(doc.ToString());
return JsonConvert.SerializeObject(result);
}
}
}