#region Copyright & License Information /* * Copyright 2007-2020 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, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Linq; using OpenRA.Scripting; using OpenRA.Traits; namespace OpenRA.Mods.Common.UtilityCommands { class ExtractLuaDocsCommand : IUtilityCommand { string IUtilityCommand.Name { get { return "--lua-docs"; } } bool IUtilityCommand.ValidateArguments(string[] args) { return true; } [Desc("Generate Lua API documentation in MarkDown format.")] void IUtilityCommand.Run(Utility utility, string[] args) { // HACK: The engine code assumes that Game.modData is set. Game.ModData = utility.ModData; var version = utility.ModData.Manifest.Metadata.Version; if (args.Length > 1) version = args[1]; Console.WriteLine("This is an automatically generated listing of the Lua map scripting API for version {0} of OpenRA.", version); Console.WriteLine(); Console.WriteLine("OpenRA allows custom maps and missions to be scripted using Lua 5.1.\n" + "These scripts run in a sandbox that prevents access to unsafe functions (e.g. OS or file access), " + "and limits the memory and CPU usage of the scripts."); Console.WriteLine(); Console.WriteLine("You can access this interface by adding the [LuaScript](Traits#luascript) trait to the world actor in your map rules " + "(note, you must replace the spaces in the snippet below with a single tab for each level of indentation):"); Console.WriteLine("```\nRules:\n\tWorld:\n\t\tLuaScript:\n\t\t\tScripts: myscript.lua\n```"); Console.WriteLine(); Console.WriteLine("Map scripts can interact with the game engine in three ways:\n" + "* Global tables provide functions for interacting with the global world state, or performing general helper tasks.\n" + "They exist in the global namespace, and can be called directly using ```.```.\n" + "* Individual actors expose a collection of properties and commands that query information or modify their state.\n" + " * Some commands, marked as queued activity, are asynchronous. Activities are queued on the actor, and will run in " + "sequence until the queue is empty or the Stop command is called. Actors that are not performing an activity are Idle " + "(actor.IsIdle will return true). The properties and commands available on each actor depends on the traits that the actor " + "specifies in its rule definitions.\n" + "* Individual players expose a collection of properties and commands that query information or modify their state.\n" + "The properties and commands available on each actor depends on the traits that the actor specifies in its rule definitions.\n"); Console.WriteLine(); Console.WriteLine("For a basic guide about map scripts see the [`Map Scripting` wiki page](https://github.com/OpenRA/OpenRA/wiki/Map-scripting)."); Console.WriteLine(); var tables = utility.ModData.ObjectCreator.GetTypesImplementing() .OrderBy(t => t.Name); Console.WriteLine("

Global Tables

"); foreach (var t in tables) { var name = t.GetCustomAttributes(true).First().Name; var members = ScriptMemberWrapper.WrappableMembers(t); Console.WriteLine("
", name); foreach (var m in members.OrderBy(m => m.Name)) { var desc = m.HasAttribute() ? m.GetCustomAttributes(true).First().Lines.JoinWith("\n") : ""; Console.WriteLine("".F(m.LuaDocString(), desc)); } Console.WriteLine("
{0}
{0}{1}
"); } Console.WriteLine("

Actor Properties / Commands

"); var actorCategories = utility.ModData.ObjectCreator.GetTypesImplementing().SelectMany(cg => { var catAttr = cg.GetCustomAttributes(false).FirstOrDefault(); var category = catAttr != null ? catAttr.Category : "Unsorted"; var required = RequiredTraitNames(cg); return ScriptMemberWrapper.WrappableMembers(cg).Select(mi => Tuple.Create(category, mi, required)); }).GroupBy(g => g.Item1).OrderBy(g => g.Key); foreach (var kv in actorCategories) { Console.WriteLine("", kv.Key); foreach (var property in kv.OrderBy(p => p.Item2.Name)) { var mi = property.Item2; var required = property.Item3; var hasDesc = mi.HasAttribute(); var hasRequires = required.Any(); var isActivity = mi.HasAttribute(); Console.WriteLine(""); } Console.WriteLine("
{0}
{0}", mi.LuaDocString()); if (isActivity) Console.WriteLine("
Queued Activity"); Console.WriteLine("
"); if (hasDesc) Console.WriteLine(mi.GetCustomAttributes(false).First().Lines.JoinWith("\n")); if (hasDesc && hasRequires) Console.WriteLine("
"); if (hasRequires) Console.WriteLine("Requires {1}: {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits")); Console.WriteLine("
"); } Console.WriteLine("

Player Properties / Commands

"); var playerCategories = utility.ModData.ObjectCreator.GetTypesImplementing().SelectMany(cg => { var catAttr = cg.GetCustomAttributes(false).FirstOrDefault(); var category = catAttr != null ? catAttr.Category : "Unsorted"; var required = RequiredTraitNames(cg); return ScriptMemberWrapper.WrappableMembers(cg).Select(mi => Tuple.Create(category, mi, required)); }).GroupBy(g => g.Item1).OrderBy(g => g.Key); foreach (var kv in playerCategories) { Console.WriteLine("", kv.Key); foreach (var property in kv.OrderBy(p => p.Item2.Name)) { var mi = property.Item2; var required = property.Item3; var hasDesc = mi.HasAttribute(); var hasRequires = required.Any(); var isActivity = mi.HasAttribute(); Console.WriteLine(""); } Console.WriteLine("
{0}
{0}", mi.LuaDocString()); if (isActivity) Console.WriteLine("
Queued Activity"); Console.WriteLine("
"); if (hasDesc) Console.WriteLine(mi.GetCustomAttributes(false).First().Lines.JoinWith("\n")); if (hasDesc && hasRequires) Console.WriteLine("
"); if (hasRequires) Console.WriteLine("Requires {1}: {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits")); Console.WriteLine("
"); } } static string[] RequiredTraitNames(Type t) { // Returns the inner types of all the Requires interfaces on this type var outer = t.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Requires<>)); // Get the inner types var inner = outer.SelectMany(i => i.GetGenericArguments()).ToArray(); // Remove the namespace and the trailing "Info" return inner.Select(i => i.Name.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault()) .Select(s => s.EndsWith("Info") ? s.Remove(s.Length - 4, 4) : s) .ToArray(); } } }