Merge pull request #6493 from pchote/utility-commands

Move utility commands into the mod assemblies.
This commit is contained in:
Matthias Mailänder
2014-09-21 07:10:45 +02:00
25 changed files with 982 additions and 701 deletions

View File

@@ -302,8 +302,8 @@ version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/modchooser/mo
# Documentation (d2k depends on all mod libraries)
docs: utility
@mono --debug OpenRA.Utility.exe --docs d2k > DOCUMENTATION.md
@mono --debug OpenRA.Utility.exe --lua-docs ra > Lua-API.md
@mono --debug OpenRA.Utility.exe d2k --docs > DOCUMENTATION.md
@mono --debug OpenRA.Utility.exe ra --lua-docs > Lua-API.md
install: install-core

View File

@@ -0,0 +1,22 @@
#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
namespace OpenRA
{
public interface IUtilityCommand
{
/// <summary>
/// The string used to invoke the command.
/// </summary>
string Name { get; }
void Run(ModData modData, string[] args);
}
}

View File

@@ -109,6 +109,7 @@
<Compile Include="Graphics\Viewport.cs" />
<Compile Include="Graphics\WorldRenderer.cs" />
<Compile Include="Group.cs" />
<Compile Include="IUtilityCommand.cs" />
<Compile Include="ModData.cs" />
<Compile Include="Network\Connection.cs" />
<Compile Include="Network\FrameData.cs" />

View File

@@ -38,6 +38,22 @@
<Compile Include="ServerTraits\ColorValidator.cs" />
<Compile Include="ServerTraits\MasterServerPinger.cs" />
<Compile Include="ServerTraits\PlayerPinger.cs" />
<Compile Include="UtilityCommands\ConvertPngToShpCommand.cs" />
<Compile Include="UtilityCommands\ConvertSpriteToPngCommand.cs" />
<Compile Include="UtilityCommands\ExtractFilesCommand.cs" />
<Compile Include="UtilityCommands\ExtractLanguageStringsCommand.cs" />
<Compile Include="UtilityCommands\ExtractLuaDocsCommand.cs" />
<Compile Include="UtilityCommands\ExtractTraitDocsCommand.cs" />
<Compile Include="UtilityCommands\GenerateMinimapCommand.cs" />
<Compile Include="UtilityCommands\GetMapHashCommand.cs" />
<Compile Include="UtilityCommands\Glob.cs" />
<Compile Include="UtilityCommands\ImportLegacyMapCommand.cs" />
<Compile Include="UtilityCommands\LegacyMapImporter.cs" />
<Compile Include="UtilityCommands\RemapShpCommand.cs" />
<Compile Include="UtilityCommands\TransposeShpCommand.cs" />
<Compile Include="UtilityCommands\UpgradeMapCommand.cs" />
<Compile Include="UtilityCommands\UpgradeModCommand.cs" />
<Compile Include="UtilityCommands\UpgradeRules.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@@ -0,0 +1,66 @@
#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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using OpenRA.FileFormats;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ConvertPngToShpCommand : IUtilityCommand
{
public string Name { get { return "--shp"; } }
[Desc("PNGFILE [PNGFILE ...]", "Combine a list of PNG images into a SHP")]
public void Run(ModData modData, string[] args)
{
var inputFiles = GlobArgs(args).OrderBy(a => a).ToList();
var dest = inputFiles[0].Split('-').First() + ".shp";
var frames = inputFiles.Select(a => PngLoader.Load(a));
var size = frames.First().Size;
if (frames.Any(f => f.Size != size))
throw new InvalidOperationException("All frames must be the same size");
using (var destStream = File.Create(dest))
ShpReader.Write(destStream, size, frames.Select(f => ToBytes(f)));
Console.WriteLine(dest + " saved.");
}
static IEnumerable<string> GlobArgs(string[] args, int startIndex = 1)
{
for (var i = startIndex; i < args.Length; i++)
foreach (var path in Glob.Expand(args[i]))
yield return path;
}
static byte[] ToBytes(Bitmap bitmap)
{
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
var bytes = new byte[bitmap.Width * bitmap.Height];
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
bytes, i * bitmap.Width, bitmap.Width);
bitmap.UnlockBits(data);
return bytes;
}
}
}

View File

@@ -0,0 +1,95 @@
#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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using OpenRA.FileFormats;
using OpenRA.Graphics;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ConvertSpriteToPngCommand : IUtilityCommand
{
public string Name { get { return "--png"; } }
[Desc("SPRITEFILE PALETTE [--noshadow] [--nopadding]",
"Convert a shp/tmp/R8 to a series of PNGs, optionally removing shadow")]
public void Run(ModData modData, string[] args)
{
var src = args[1];
var shadowIndex = new int[] { };
if (args.Contains("--noshadow"))
{
Array.Resize(ref shadowIndex, shadowIndex.Length + 3);
shadowIndex[shadowIndex.Length - 1] = 1;
shadowIndex[shadowIndex.Length - 2] = 3;
shadowIndex[shadowIndex.Length - 3] = 4;
}
var palette = new ImmutablePalette(args[2], shadowIndex);
ISpriteSource source;
using (var stream = File.OpenRead(src))
source = SpriteSource.LoadSpriteSource(stream, src);
// The r8 padding requires external information that we can't access here.
var usePadding = !(args.Contains("--nopadding") || source is R8Reader);
var count = 0;
var prefix = Path.GetFileNameWithoutExtension(src);
foreach (var frame in source.Frames)
{
var frameSize = usePadding ? frame.FrameSize : frame.Size;
var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
// shp(ts) may define empty frames
if (frameSize.Width == 0 && frameSize.Height == 0)
{
count++;
continue;
}
using (var bitmap = new Bitmap(frameSize.Width, frameSize.Height, PixelFormat.Format8bppIndexed))
{
bitmap.Palette = palette.AsSystemPalette();
var data = bitmap.LockBits(new Rectangle(0, 0, frameSize.Width, frameSize.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// Clear the frame
if (usePadding)
{
var clearRow = new byte[data.Stride];
for (var i = 0; i < frameSize.Height; i++)
Marshal.Copy(clearRow, 0, new IntPtr(data.Scan0.ToInt64() + i * data.Stride), data.Stride);
}
for (var i = 0; i < frame.Size.Height; i++)
{
var destIndex = new IntPtr(data.Scan0.ToInt64() + (i + offset.Y) * data.Stride + offset.X);
Marshal.Copy(frame.Data, i * frame.Size.Width, destIndex, frame.Size.Width);
}
bitmap.UnlockBits(data);
var filename = "{0}-{1:D4}.png".F(prefix, count++);
bitmap.Save(filename);
}
}
Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
}
}
}

View File

@@ -0,0 +1,41 @@
#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.IO;
using System.Linq;
using System.Text;
using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ExtractFilesCommand : IUtilityCommand
{
public string Name { get { return "--extract"; } }
[Desc("Extract files from mod packages to the current directory")]
public void Run(ModData modData, string[] args)
{
var files = args.Skip(1);
GlobalFileSystem.LoadFromManifest(modData.Manifest);
foreach (var f in files)
{
var src = GlobalFileSystem.Open(f);
if (src == null)
throw new InvalidOperationException("File not found: {0}".F(f));
var data = src.ReadAllBytes();
File.WriteAllBytes(f, data);
Console.WriteLine(f + " saved.");
}
}
}
}

View File

@@ -12,16 +12,19 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace OpenRA.Utility
namespace OpenRA.Mods.Common.UtilityCommands
{
public class ExtractLanguageStrings
class ExtractLanguageStringsCommand : IUtilityCommand
{
[Desc("MOD", "Extract translatable strings that are not yet localized and update chrome layout.")]
public static void FromMod(string[] args)
public string Name { get { return "--extract-language-strings"; } }
[Desc("Extract translatable strings that are not yet localized and update chrome layout.")]
public void Run(ModData modData, string[] args)
{
var mod = args[1];
Game.modData = new ModData(mod);
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
Game.modData.RulesetCache.LoadDefaultRules();
var types = Game.modData.ObjectCreator.GetTypes();
@@ -32,7 +35,7 @@ namespace OpenRA.Utility
{
Console.WriteLine("# {0}:", filename);
var yaml = MiniYaml.FromFile(filename);
ExtractLanguageStrings.FromChromeLayout(ref yaml, null,
ExtractLanguageStringsCommand.FromChromeLayout(ref yaml, null,
translatableFields.Select(t => t.Name).Distinct(), null);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
@@ -41,7 +44,7 @@ namespace OpenRA.Utility
// TODO: Properties can also be translated.
}
public static void FromChromeLayout(ref List<MiniYamlNode> nodes, MiniYamlNode parent, IEnumerable<string> translatables, string container)
internal static void FromChromeLayout(ref List<MiniYamlNode> nodes, MiniYamlNode parent, IEnumerable<string> translatables, string container)
{
var parentNode = parent != null ? parent.Key.Split('@') : null;
var parentType = parent != null ? parentNode.First() : null;

View File

@@ -0,0 +1,175 @@
#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.Linq;
using System.Text;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ExtractLuaDocsCommand : IUtilityCommand
{
public string Name { get { return "--lua-docs"; } }
[Desc("Generate Lua API documentation in MarkDown format.")]
public void Run(ModData modData, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
Console.WriteLine("This is an automatically generated lising of the new Lua map scripting API, generated for {0} of OpenRA.", Game.modData.Manifest.Mod.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 ```<table name>.<function name>```.\n" +
"* Individual actors expose a collection of properties and commands that query information of modify their state.\n" +
" * Some commands, marked as <em>queued activity</em>, 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 explose a collection of properties and commands that query information of 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();
var tables = Game.modData.ObjectCreator.GetTypesImplementing<ScriptGlobal>()
.OrderBy(t => t.Name);
Console.WriteLine("<h3>Global Tables</h3>");
foreach (var t in tables)
{
var name = t.GetCustomAttributes<ScriptGlobalAttribute>(true).First().Name;
var members = ScriptMemberWrapper.WrappableMembers(t);
Console.WriteLine("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", name);
foreach (var m in members.OrderBy(m => m.Name))
{
var desc = m.HasAttribute<DescAttribute>() ? m.GetCustomAttributes<DescAttribute>(true).First().Lines.JoinWith("\n") : "";
Console.WriteLine("<tr><td align=\"right\" width=\"50%\"><strong>{0}</strong></td><td>{1}</td></tr>".F(m.LuaDocString(), desc));
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Actor Properties / Commands</h3>");
var actorCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptActorProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(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("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Player Properties / Commands</h3>");
var playerCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptPlayerProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(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("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
}
static string[] RequiredTraitNames(Type t)
{
// Returns the inner types of all the Requires<T> 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();
}
}
}

View File

@@ -0,0 +1,151 @@
#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.Linq;
using System.Text;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ExtractTraitDocsCommand : IUtilityCommand
{
public string Name { get { return "--docs"; } }
[Desc("Generate trait documentation in MarkDown format.")]
public void Run(ModData modData, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
Console.WriteLine(
"This documentation is aimed at modders. It displays all traits 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 {0} of OpenRA.", Game.modData.Manifest.Mod.Version);
Console.WriteLine();
var toc = new StringBuilder();
var doc = new StringBuilder();
foreach (var t in Game.modData.ObjectCreator.GetTypesImplementing<ITraitInfo>().OrderBy(t => t.Namespace))
{
if (t.ContainsGenericParameters || t.IsAbstract)
continue; // skip helpers like TraitInfo<T>
var traitName = t.Name.EndsWith("Info") ? t.Name.Substring(0, t.Name.Length - 4) : t.Name;
toc.AppendLine("* [{0}](#{1})".F(traitName, traitName.ToLowerInvariant()));
var traitDescLines = t.GetCustomAttributes<DescAttribute>(false).SelectMany(d => d.Lines);
doc.AppendLine();
doc.AppendLine("### {0}".F(traitName));
foreach (var line in traitDescLines)
doc.AppendLine(line);
var requires = RequiredTraitTypes(t);
var reqCount = requires.Length;
if (reqCount > 0)
{
if (t.HasAttribute<DescAttribute>())
doc.AppendLine("\n");
doc.Append("Requires trait{0}: ".F(reqCount > 1 ? "s" : ""));
var i = 0;
foreach (var require in requires)
{
var n = require.Name;
var name = n.EndsWith("Info") ? n.Remove(n.Length - 4, 4) : n;
doc.Append("`{0}`{1}".F(name, i + 1 == reqCount ? ".\n" : ", "));
i++;
}
}
var infos = FieldLoader.GetTypeLoadInfo(t);
if (!infos.Any())
continue;
doc.AppendLine("<table>");
doc.AppendLine("<tr><th>Property</th><th>Default Value</th><th>Type</th><th>Description</th></tr>");
var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t);
foreach (var info in infos)
{
var fieldDescLines = info.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
var fieldType = FriendlyTypeName(info.Field.FieldType);
var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value;
doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(info.YamlName, defaultValue, fieldType));
doc.Append("<td>");
foreach (var line in fieldDescLines)
doc.Append(line + " ");
doc.AppendLine("</td></tr>");
}
doc.AppendLine("</table>");
}
Console.Write(toc.ToString());
Console.Write(doc.ToString());
}
static Type[] RequiredTraitTypes(Type t)
{
return t.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Requires<>))
.SelectMany(i => i.GetGenericArguments())
.Where(i => !i.IsInterface && !t.IsSubclassOf(i))
.OrderBy(i => i.Name)
.ToArray();
}
static string FriendlyTypeName(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
return "Dictionary<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
if (t.IsSubclassOf(typeof(Array)))
return "Multiple {0}".F(FriendlyTypeName(t.GetElementType()));
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(OpenRA.Primitives.Cache<,>))
return "Cached<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
if (t == typeof(int) || t == typeof(uint))
return "Integer";
if (t == typeof(int2))
return "2D Integer";
if (t == typeof(float) || t == typeof(decimal))
return "Real Number";
if (t == typeof(float2))
return "2D Real Number";
if (t == typeof(CPos))
return "2D Cell Position";
if (t == typeof(CVec))
return "2D Cell Vector";
if (t == typeof(WAngle))
return "1D World Angle";
if (t == typeof(WRot))
return "3D World Rotation";
if (t == typeof(WPos))
return "3D World Position";
if (t == typeof(WRange))
return "1D World Range";
if (t == typeof(WVec))
return "3D World Vector";
return t.Name;
}
}
}

View File

@@ -0,0 +1,42 @@
#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.IO;
using System.Linq;
using System.Text;
using OpenRA.Graphics;
using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
{
class GenerateMinimapCommand : IUtilityCommand
{
public string Name { get { return "--map-preview"; } }
[Desc("MAPFILE", "Render PNG minimap of specified oramap file.")]
public void Run(ModData modData, string[] args)
{
Game.modData = modData;
var map = new Map(args[1]);
GlobalFileSystem.UnmountAll();
foreach (var dir in Game.modData.Manifest.Folders)
GlobalFileSystem.Mount(dir);
var minimap = Minimap.RenderMapPreview(map.Rules.TileSets[map.Tileset], map, true);
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".png";
minimap.Save(dest);
Console.WriteLine(dest + " saved.");
}
}
}

View File

@@ -0,0 +1,30 @@
#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.Linq;
using System.Text;
namespace OpenRA.Mods.Common.UtilityCommands
{
public class GetMapHashCommand : IUtilityCommand
{
public string Name { get { return "--map-hash"; } }
[Desc("MAPFILE", "Generate hash of specified oramap file.")]
public void Run(ModData modData, string[] args)
{
Game.modData = modData;
var result = new Map(args[1]).Uid;
Console.WriteLine(result);
}
}
}

View File

@@ -12,9 +12,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA.Utility
namespace OpenRA.Mods.Common.UtilityCommands
{
public static class Glob
static class Glob
{
public static bool Enabled = true;

View File

@@ -0,0 +1,35 @@
#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.Linq;
using System.Text;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ImportLegacyMapCommand : IUtilityCommand
{
public string Name { get { return "--map-import"; } }
[Desc("FILENAME", "Convert a legacy INI/MPR map to the OpenRA format.")]
public void Run(ModData modData, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
var rules = Game.modData.RulesetCache.LoadDefaultRules();
var map = LegacyMapImporter.Import(args[1], modData.Manifest.Mod.Id, rules, e => Console.WriteLine(e));
var dest = map.Title + ".oramap";
map.Save(dest);
Console.WriteLine(dest + " saved.");
}
}
}

View File

@@ -21,9 +21,9 @@ using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Utility
namespace OpenRA.Mods.Common.UtilityCommands
{
public class LegacyMapImporter
class LegacyMapImporter
{
// Mapping from ra overlay index to type string
static string[] redAlertOverlayNames =

View File

@@ -0,0 +1,85 @@
#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.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using OpenRA.Traits;
using OpenRA.Graphics;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
{
class RemapShpCommand : IUtilityCommand
{
public string Name { get { return "--remap"; } }
[Desc("SRCMOD:PAL DESTMOD:PAL SRCSHP DESTSHP", "Remap SHPs to another palette")]
public void Run(ModData modData, string[] args)
{
var remap = new Dictionary<int, int>();
/* the first 4 entries are fixed */
for (var i = 0; i < 4; i++)
remap[i] = i;
var srcMod = args[1].Split(':')[0];
Game.modData = new ModData(srcMod);
GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
var srcRules = Game.modData.RulesetCache.LoadDefaultRules();
var srcPaletteInfo = srcRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
var srcRemapIndex = srcPaletteInfo.RemapIndex;
var destMod = args[2].Split(':')[0];
Game.modData = new ModData(destMod);
GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
var destRules = Game.modData.RulesetCache.LoadDefaultRules();
var destPaletteInfo = destRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
var destRemapIndex = destPaletteInfo.RemapIndex;
var shadowIndex = new int[] { };
// the remap range is always 16 entries, but their location and order changes
for (var i = 0; i < 16; i++)
remap[PlayerColorRemap.GetRemapIndex(srcRemapIndex, i)]
= PlayerColorRemap.GetRemapIndex(destRemapIndex, i);
// map everything else to the best match based on channel-wise distance
var srcPalette = new ImmutablePalette(args[1].Split(':')[1], shadowIndex);
var destPalette = new ImmutablePalette(args[2].Split(':')[1], shadowIndex);
for (var i = 0; i < Palette.Size; i++)
if (!remap.ContainsKey(i))
remap[i] = Enumerable.Range(0, Palette.Size)
.Where(a => !remap.ContainsValue(a))
.MinBy(a => ColorDistance(destPalette[a], srcPalette[i]));
var srcImage = ShpReader.Load(args[3]);
using (var destStream = File.Create(args[4]))
ShpReader.Write(destStream, srcImage.Size,
srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
}
static int ColorDistance(uint a, uint b)
{
var ca = Color.FromArgb((int)a);
var cb = Color.FromArgb((int)b);
return Math.Abs((int)ca.R - (int)cb.R) +
Math.Abs((int)ca.G - (int)cb.G) +
Math.Abs((int)ca.B - (int)cb.B);
}
}
}

View File

@@ -0,0 +1,48 @@
#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.IO;
using System.Linq;
using System.Text;
using OpenRA.FileFormats;
namespace OpenRA.Mods.Common.UtilityCommands
{
class TransposeShpCommand : IUtilityCommand
{
public string Name { get { return "--transpose"; } }
[Desc("SRCSHP DESTSHP START N M [START N M ...]",
"Transpose the N*M block of frames starting at START.")]
public void Run(ModData modData, string[] args)
{
var srcImage = ShpReader.Load(args[1]);
var srcFrames = srcImage.Frames;
var destFrames = srcImage.Frames.ToArray();
for (var z = 3; z < args.Length - 2; z += 3)
{
var start = Exts.ParseIntegerInvariant(args[z]);
var m = Exts.ParseIntegerInvariant(args[z + 1]);
var n = Exts.ParseIntegerInvariant(args[z + 2]);
for (var i = 0; i < m; i++)
for (var j = 0; j < n; j++)
destFrames[start + i * n + j] = srcFrames[start + j * m + i];
}
using (var destStream = File.Create(args[2]))
ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data));
}
}
}

View File

@@ -0,0 +1,36 @@
#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.Linq;
using System.Text;
namespace OpenRA.Mods.Common.UtilityCommands
{
class UpgradeMapCommand : IUtilityCommand
{
public string Name { get { return "--upgrade-map"; } }
[Desc("MAP", "CURRENTENGINE", "Upgrade map rules to the latest engine version.")]
public void Run(ModData modData, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
var map = new Map(args[1]);
var engineDate = Exts.ParseIntegerInvariant(args[2]);
UpgradeRules.UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
UpgradeRules.UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
map.Save(args[1]);
}
}
}

View File

@@ -0,0 +1,79 @@
#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.IO;
using System.Linq;
using System.Text;
namespace OpenRA.Mods.Common.UtilityCommands
{
class UpgradeModCommand : IUtilityCommand
{
public string Name { get { return "--upgrade-mod"; } }
[Desc("CURRENTENGINE", "Upgrade mod rules to the latest engine version.")]
public void Run(ModData modData, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
Game.modData = modData;
Game.modData.MapCache.LoadMaps();
var engineDate = Exts.ParseIntegerInvariant(args[1]);
Console.WriteLine("Processing Rules:");
foreach (var filename in Game.modData.Manifest.Rules)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeRules.UpgradeActorRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Weapons:");
foreach (var filename in Game.modData.Manifest.Weapons)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeRules.UpgradeWeaponRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Tilesets:");
foreach (var filename in Game.modData.Manifest.TileSets)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeRules.UpgradeTileset(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Maps:");
var maps = Game.modData.MapCache
.Where(m => m.Status == MapStatus.Available)
.Select(m => m.Map);
foreach (var map in maps)
{
Console.WriteLine("\t" + map.Path);
UpgradeRules.UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
UpgradeRules.UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
map.Save(map.Path);
}
}
}
}

View File

@@ -13,11 +13,11 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA.Utility
namespace OpenRA.Mods.Common.UtilityCommands
{
public static class UpgradeRules
static class UpgradeRules
{
static void ConvertFloatToRange(ref string input)
internal static void ConvertFloatToRange(ref string input)
{
var value = float.Parse(input);
var cells = (int)value;
@@ -26,18 +26,18 @@ namespace OpenRA.Utility
input = "{0}c{1}".F(cells, subcells);
}
static void ConvertFloatArrayToPercentArray(ref string input)
internal static void ConvertFloatArrayToPercentArray(ref string input)
{
input = string.Join(", ", input.Split(',')
.Select(s => ((int)Math.Round(FieldLoader.GetValue<float>("(float value)", s) * 100)).ToString()));
}
static void ConvertPxToRange(ref string input)
internal static void ConvertPxToRange(ref string input)
{
ConvertPxToRange(ref input, 1, 1);
}
static void ConvertPxToRange(ref string input, int scaleMult, int scaleDiv)
internal static void ConvertPxToRange(ref string input, int scaleMult, int scaleDiv)
{
var value = Exts.ParseIntegerInvariant(input);
var ts = Game.modData.Manifest.TileSize;
@@ -48,13 +48,13 @@ namespace OpenRA.Utility
input = cells != 0 ? "{0}c{1}".F(cells, subcells) : subcells.ToString();
}
static void ConvertAngle(ref string input)
internal static void ConvertAngle(ref string input)
{
var value = float.Parse(input);
input = WAngle.ArcTan((int)(value * 4 * 1024), 1024).ToString();
}
static void ConvertInt2ToWVec(ref string input)
internal static void ConvertInt2ToWVec(ref string input)
{
var offset = FieldLoader.GetValue<int2>("(value)", input);
var ts = Game.modData.Manifest.TileSize;
@@ -62,7 +62,7 @@ namespace OpenRA.Utility
input = world.ToString();
}
static void UpgradeActorRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
internal static void UpgradeActorRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
@@ -567,7 +567,7 @@ namespace OpenRA.Utility
}
}
static void UpgradeWeaponRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
internal static void UpgradeWeaponRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
@@ -888,7 +888,7 @@ namespace OpenRA.Utility
}
}
static void UpgradeTileset(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
internal static void UpgradeTileset(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
var addNodes = new List<MiniYamlNode>();
@@ -906,72 +906,5 @@ namespace OpenRA.Utility
nodes.AddRange(addNodes);
}
[Desc("MAP", "CURRENTENGINE", "MOD", "Upgrade map rules to the latest engine version.")]
public static void UpgradeMap(string[] args)
{
Game.modData = new ModData(args[3]);
var map = new Map(args[1]);
var engineDate = Exts.ParseIntegerInvariant(args[2]);
UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
map.Save(args[1]);
}
[Desc("MOD", "CURRENTENGINE", "Upgrade mod rules to the latest engine version.")]
public static void UpgradeMod(string[] args)
{
var mod = args[1];
var engineDate = Exts.ParseIntegerInvariant(args[2]);
Game.modData = new ModData(mod);
Game.modData.MapCache.LoadMaps();
Console.WriteLine("Processing Rules:");
foreach (var filename in Game.modData.Manifest.Rules)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeActorRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Weapons:");
foreach (var filename in Game.modData.Manifest.Weapons)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeWeaponRules(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Tilesets:");
foreach (var filename in Game.modData.Manifest.TileSets)
{
Console.WriteLine("\t" + filename);
var yaml = MiniYaml.FromFile(filename);
UpgradeTileset(engineDate, ref yaml, null, 0);
using (var file = new StreamWriter(filename))
file.WriteLine(yaml.WriteToString());
}
Console.WriteLine("Processing Maps:");
var maps = Game.modData.MapCache
.Where(m => m.Status == MapStatus.Available)
.Select(m => m.Map);
foreach (var map in maps)
{
Console.WriteLine("\t" + map.Path);
UpgradeActorRules(engineDate, ref map.RuleDefinitions, null, 0);
UpgradeWeaponRules(engineDate, ref map.WeaponDefinitions, null, 0);
map.Save(map.Path);
}
}
}
}

View File

@@ -294,6 +294,7 @@
<Compile Include="Scripting\Properties\GuardProperties.cs" />
<Compile Include="Scripting\Properties\PlayerProperties.cs" />
<Compile Include="Power\ScalePowerWithHealth.cs" />
<Compile Include="ServerTraits\ColorValidator.cs" />
<Compile Include="Warheads\DestroyResourceWarhead.cs" />
<Compile Include="Warheads\CreateEffectWarhead.cs" />
<Compile Include="Warheads\CreateResourceWarhead.cs" />

View File

@@ -1,575 +0,0 @@
#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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Utility
{
public static class Command
{
static IEnumerable<string> GlobArgs(string[] args, int startIndex = 1)
{
for (var i = startIndex; i < args.Length; i++)
foreach (var path in Glob.Expand(args[i]))
yield return path;
}
[Desc("KEY", "Get value of KEY from settings.yaml")]
public static void Settings(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Error: Invalid syntax");
return;
}
var section = args[1].Split('.')[0];
var field = args[1].Split('.')[1];
var settings = new Settings(Platform.SupportDir + "settings.yaml", Arguments.Empty);
var result = settings.Sections[section].GetType().GetField(field).GetValue(settings.Sections[section]);
Console.WriteLine(result);
}
[Desc("PNGFILE [PNGFILE ...]", "Combine a list of PNG images into a SHP")]
public static void ConvertPngToShp(string[] args)
{
var inputFiles = GlobArgs(args).OrderBy(a => a).ToList();
var dest = inputFiles[0].Split('-').First() + ".shp";
var frames = inputFiles.Select(a => PngLoader.Load(a));
var size = frames.First().Size;
if (frames.Any(f => f.Size != size))
throw new InvalidOperationException("All frames must be the same size");
using (var destStream = File.Create(dest))
ShpReader.Write(destStream, size, frames.Select(f => f.ToBytes()));
Console.WriteLine(dest + " saved.");
}
static byte[] ToBytes(this Bitmap bitmap)
{
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
var bytes = new byte[bitmap.Width * bitmap.Height];
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
bytes, i * bitmap.Width, bitmap.Width);
bitmap.UnlockBits(data);
return bytes;
}
[Desc("SPRITEFILE PALETTE [--noshadow] [--nopadding]",
"Convert a shp/tmp/R8 to a series of PNGs, optionally removing shadow")]
public static void ConvertSpriteToPng(string[] args)
{
var src = args[1];
var shadowIndex = new int[] { };
if (args.Contains("--noshadow"))
{
Array.Resize(ref shadowIndex, shadowIndex.Length + 3);
shadowIndex[shadowIndex.Length - 1] = 1;
shadowIndex[shadowIndex.Length - 2] = 3;
shadowIndex[shadowIndex.Length - 3] = 4;
}
var palette = new ImmutablePalette(args[2], shadowIndex);
ISpriteSource source;
using (var stream = File.OpenRead(src))
source = SpriteSource.LoadSpriteSource(stream, src);
// The r8 padding requires external information that we can't access here.
var usePadding = !(args.Contains("--nopadding") || source is R8Reader);
var count = 0;
var prefix = Path.GetFileNameWithoutExtension(src);
foreach (var frame in source.Frames)
{
var frameSize = usePadding ? frame.FrameSize : frame.Size;
var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
// shp(ts) may define empty frames
if (frameSize.Width == 0 && frameSize.Height == 0)
{
count++;
continue;
}
using (var bitmap = new Bitmap(frameSize.Width, frameSize.Height, PixelFormat.Format8bppIndexed))
{
bitmap.Palette = palette.AsSystemPalette();
var data = bitmap.LockBits(new Rectangle(0, 0, frameSize.Width, frameSize.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// Clear the frame
if (usePadding)
{
var clearRow = new byte[data.Stride];
for (var i = 0; i < frameSize.Height; i++)
Marshal.Copy(clearRow, 0, new IntPtr(data.Scan0.ToInt64() + i * data.Stride), data.Stride);
}
for (var i = 0; i < frame.Size.Height; i++)
{
var destIndex = new IntPtr(data.Scan0.ToInt64() + (i + offset.Y) * data.Stride + offset.X);
Marshal.Copy(frame.Data, i * frame.Size.Width, destIndex, frame.Size.Width);
}
bitmap.UnlockBits(data);
var filename = "{0}-{1:D4}.png".F(prefix, count++);
bitmap.Save(filename);
}
}
Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
}
[Desc("MOD FILES", "Extract files from mod packages to the current directory")]
public static void ExtractFiles(string[] args)
{
var mod = args[1];
var files = args.Skip(2);
var manifest = new Manifest(mod);
GlobalFileSystem.LoadFromManifest(manifest);
foreach (var f in files)
{
var src = GlobalFileSystem.Open(f);
if (src == null)
throw new InvalidOperationException("File not found: {0}".F(f));
var data = src.ReadAllBytes();
File.WriteAllBytes(f, data);
Console.WriteLine(f + " saved.");
}
}
static int ColorDistance(uint a, uint b)
{
var ca = Color.FromArgb((int)a);
var cb = Color.FromArgb((int)b);
return Math.Abs((int)ca.R - (int)cb.R) +
Math.Abs((int)ca.G - (int)cb.G) +
Math.Abs((int)ca.B - (int)cb.B);
}
[Desc("SRCMOD:PAL DESTMOD:PAL SRCSHP DESTSHP", "Remap SHPs to another palette")]
public static void RemapShp(string[] args)
{
var remap = new Dictionary<int, int>();
/* the first 4 entries are fixed */
for (var i = 0; i < 4; i++)
remap[i] = i;
var srcMod = args[1].Split(':')[0];
Game.modData = new ModData(srcMod);
GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
var srcRules = Game.modData.RulesetCache.LoadDefaultRules();
var srcPaletteInfo = srcRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
var srcRemapIndex = srcPaletteInfo.RemapIndex;
var destMod = args[2].Split(':')[0];
Game.modData = new ModData(destMod);
GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
var destRules = Game.modData.RulesetCache.LoadDefaultRules();
var destPaletteInfo = destRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
var destRemapIndex = destPaletteInfo.RemapIndex;
var shadowIndex = new int[] { };
// the remap range is always 16 entries, but their location and order changes
for (var i = 0; i < 16; i++)
remap[PlayerColorRemap.GetRemapIndex(srcRemapIndex, i)]
= PlayerColorRemap.GetRemapIndex(destRemapIndex, i);
// map everything else to the best match based on channel-wise distance
var srcPalette = new ImmutablePalette(args[1].Split(':')[1], shadowIndex);
var destPalette = new ImmutablePalette(args[2].Split(':')[1], shadowIndex);
for (var i = 0; i < Palette.Size; i++)
if (!remap.ContainsKey(i))
remap[i] = Enumerable.Range(0, Palette.Size)
.Where(a => !remap.ContainsValue(a))
.MinBy(a => ColorDistance(destPalette[a], srcPalette[i]));
var srcImage = ShpReader.Load(args[3]);
using (var destStream = File.Create(args[4]))
ShpReader.Write(destStream, srcImage.Size,
srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
}
[Desc("SRCSHP DESTSHP START N M [START N M ...]",
"Transpose the N*M block of frames starting at START.")]
public static void TransposeShp(string[] args)
{
var srcImage = ShpReader.Load(args[1]);
var srcFrames = srcImage.Frames;
var destFrames = srcImage.Frames.ToArray();
for (var z = 3; z < args.Length - 2; z += 3)
{
var start = Exts.ParseIntegerInvariant(args[z]);
var m = Exts.ParseIntegerInvariant(args[z + 1]);
var n = Exts.ParseIntegerInvariant(args[z + 2]);
for (var i = 0; i < m; i++)
for (var j = 0; j < n; j++)
destFrames[start + i * n + j] = srcFrames[start + j * m + i];
}
using (var destStream = File.Create(args[2]))
ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data));
}
static string FriendlyTypeName(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
return "Dictionary<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
if (t.IsSubclassOf(typeof(Array)))
return "Multiple {0}".F(FriendlyTypeName(t.GetElementType()));
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(OpenRA.Primitives.Cache<,>))
return "Cached<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
if (t == typeof(int) || t == typeof(uint))
return "Integer";
if (t == typeof(int2))
return "2D Integer";
if (t == typeof(float) || t == typeof(decimal))
return "Real Number";
if (t == typeof(float2))
return "2D Real Number";
if (t == typeof(CPos))
return "2D Cell Position";
if (t == typeof(CVec))
return "2D Cell Vector";
if (t == typeof(WAngle))
return "1D World Angle";
if (t == typeof(WRot))
return "3D World Rotation";
if (t == typeof(WPos))
return "3D World Position";
if (t == typeof(WRange))
return "1D World Range";
if (t == typeof(WVec))
return "3D World Vector";
return t.Name;
}
[Desc("MOD", "Generate trait documentation in MarkDown format.")]
public static void ExtractTraitDocs(string[] args)
{
Game.modData = new ModData(args[1]);
Console.WriteLine(
"This documentation is aimed at modders. It displays all traits 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 {0} of OpenRA.", Game.modData.Manifest.Mod.Version);
Console.WriteLine();
var toc = new StringBuilder();
var doc = new StringBuilder();
foreach (var t in Game.modData.ObjectCreator.GetTypesImplementing<ITraitInfo>().OrderBy(t => t.Namespace))
{
if (t.ContainsGenericParameters || t.IsAbstract)
continue; // skip helpers like TraitInfo<T>
var traitName = t.Name.EndsWith("Info") ? t.Name.Substring(0, t.Name.Length - 4) : t.Name;
toc.AppendLine("* [{0}](#{1})".F(traitName, traitName.ToLowerInvariant()));
var traitDescLines = t.GetCustomAttributes<DescAttribute>(false).SelectMany(d => d.Lines);
doc.AppendLine();
doc.AppendLine("### {0}".F(traitName));
foreach (var line in traitDescLines)
doc.AppendLine(line);
var requires = RequiredTraitTypes(t);
var reqCount = requires.Length;
if (reqCount > 0)
{
if (t.HasAttribute<DescAttribute>())
doc.AppendLine("\n");
doc.Append("Requires trait{0}: ".F(reqCount > 1 ? "s" : ""));
var i = 0;
foreach (var require in requires)
{
var n = require.Name;
var name = n.EndsWith("Info") ? n.Remove(n.Length - 4, 4) : n;
doc.Append("`{0}`{1}".F(name, i + 1 == reqCount ? ".\n" : ", "));
i++;
}
}
var infos = FieldLoader.GetTypeLoadInfo(t);
if (!infos.Any())
continue;
doc.AppendLine("<table>");
doc.AppendLine("<tr><th>Property</th><th>Default Value</th><th>Type</th><th>Description</th></tr>");
var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t);
foreach (var info in infos)
{
var fieldDescLines = info.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
var fieldType = FriendlyTypeName(info.Field.FieldType);
var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value;
doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(info.YamlName, defaultValue, fieldType));
doc.Append("<td>");
foreach (var line in fieldDescLines)
doc.Append(line + " ");
doc.AppendLine("</td></tr>");
}
doc.AppendLine("</table>");
}
Console.Write(toc.ToString());
Console.Write(doc.ToString());
}
static string[] RequiredTraitNames(Type t)
{
// Returns the inner types of all the Requires<T> 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();
}
static Type[] RequiredTraitTypes(Type t)
{
return t.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Requires<>))
.SelectMany(i => i.GetGenericArguments())
.Where(i => !i.IsInterface && !t.IsSubclassOf(i))
.OrderBy(i => i.Name)
.ToArray();
}
[Desc("MOD", "Generate Lua API documentation in MarkDown format.")]
public static void ExtractLuaDocs(string[] args)
{
Game.modData = new ModData(args[1]);
Console.WriteLine("This is an automatically generated listing of the new Lua map scripting API, generated for {0} of OpenRA.", Game.modData.Manifest.Mod.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 ```<table name>.<function name>```.\n" +
"* Individual actors expose a collection of properties and commands that query information or modify their state.\n" +
" * Some commands, marked as <em>queued activity</em>, 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();
var tables = Game.modData.ObjectCreator.GetTypesImplementing<ScriptGlobal>()
.OrderBy(t => t.Name);
Console.WriteLine("<h3>Global Tables</h3>");
foreach (var t in tables)
{
var name = t.GetCustomAttributes<ScriptGlobalAttribute>(true).First().Name;
var members = ScriptMemberWrapper.WrappableMembers(t);
Console.WriteLine("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", name);
foreach (var m in members.OrderBy(m => m.Name))
{
var desc = m.HasAttribute<DescAttribute>() ? m.GetCustomAttributes<DescAttribute>(true).First().Lines.JoinWith("\n") : "";
Console.WriteLine("<tr><td align=\"right\" width=\"50%\"><strong>{0}</strong></td><td>{1}</td></tr>".F(m.LuaDocString(), desc));
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Actor Properties / Commands</h3>");
var actorCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptActorProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(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("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Player Properties / Commands</h3>");
var playerCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptPlayerProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(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("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
}
[Desc("MAPFILE", "Generate hash of specified oramap file.")]
public static void GetMapHash(string[] args)
{
var result = new Map(args[1]).Uid;
Console.WriteLine(result);
}
[Desc("MAPFILE", "Render PNG minimap of specified oramap file.")]
public static void GenerateMinimap(string[] args)
{
var map = new Map(args[1]);
Game.modData = new ModData(map.RequiresMod);
GlobalFileSystem.UnmountAll();
foreach (var dir in Game.modData.Manifest.Folders)
GlobalFileSystem.Mount(dir);
var minimap = Minimap.RenderMapPreview(map.Rules.TileSets[map.Tileset], map, true);
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".png";
minimap.Save(dest);
Console.WriteLine(dest + " saved.");
}
[Desc("MAPFILE", "MOD", "Upgrade a version 5 map to version 6.")]
public static void UpgradeV5Map(string[] args)
{
var map = args[1];
var mod = args[2];
Game.modData = new ModData(mod);
new Map(map, mod);
}
[Desc("MOD", "FILENAME", "Convert a legacy INI/MPR map to the OpenRA format.")]
public static void ImportLegacyMap(string[] args)
{
var mod = args[1];
var filename = args[2];
Game.modData = new ModData(mod);
var rules = Game.modData.RulesetCache.LoadDefaultRules();
var map = LegacyMapImporter.Import(filename, mod, rules, e => Console.WriteLine(e));
var dest = map.Title + ".oramap";
map.Save(dest);
Console.WriteLine(dest + " saved.");
}
}
}

View File

@@ -65,12 +65,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Command.cs" />
<Compile Include="Program.cs" />
<Compile Include="UpgradeRules.cs" />
<Compile Include="LegacyMapImporter.cs" />
<Compile Include="Glob.cs" />
<Compile Include="ExtractLanguageStrings.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">

View File

@@ -18,38 +18,39 @@ namespace OpenRA.Utility
{
class Program
{
static readonly Dictionary<string, Action<string[]>> Actions = new Dictionary<string, Action<string[]>>()
{
{ "--settings-value", Command.Settings },
{ "--shp", Command.ConvertPngToShp },
{ "--png", Command.ConvertSpriteToPng },
{ "--extract", Command.ExtractFiles },
{ "--remap", Command.RemapShp },
{ "--transpose", Command.TransposeShp },
{ "--docs", Command.ExtractTraitDocs },
{ "--lua-docs", Command.ExtractLuaDocs },
{ "--map-hash", Command.GetMapHash },
{ "--map-preview", Command.GenerateMinimap },
{ "--map-upgrade-v5", Command.UpgradeV5Map },
{ "--upgrade-map", UpgradeRules.UpgradeMap },
{ "--upgrade-mod", UpgradeRules.UpgradeMod },
{ "--map-import", Command.ImportLegacyMap },
{ "--extract-language-strings", ExtractLanguageStrings.FromMod }
};
static void Main(string[] args)
{
if (args.Length == 0) { PrintUsage(); return; }
if (args.Length == 0)
{
PrintUsage(null);
return;
}
AppDomain.CurrentDomain.AssemblyResolve += GlobalFileSystem.ResolveAssembly;
Log.LogPath = Platform.SupportDir + "Logs" + Path.DirectorySeparatorChar;
Log.AddChannel("perf", null);
var modName = args[0];
if (!ModMetadata.AllMods.Keys.Contains(modName))
{
PrintUsage(null);
return;
}
var modData = new ModData(modName);
args = args.Skip(1).ToArray();
var actions = new Dictionary<string, Action<ModData, string[]>>();
foreach (var commandType in modData.ObjectCreator.GetTypesImplementing<IUtilityCommand>())
{
var command = (IUtilityCommand)Activator.CreateInstance(commandType);
actions.Add(command.Name, command.Run);
}
try
{
var action = Exts.WithDefault(_ => PrintUsage(), () => Actions[args[0]]);
action(args);
var action = Exts.WithDefault((a,b) => PrintUsage(actions), () => actions[args[0]]);
action(modData, args);
}
catch (Exception e)
{
@@ -62,11 +63,15 @@ namespace OpenRA.Utility
}
}
static void PrintUsage()
static void PrintUsage(IDictionary<string, Action<ModData, string[]>> actions)
{
Console.WriteLine("Usage: OpenRA.Utility.exe [OPTION] [ARGS]");
Console.WriteLine("Run `OpenRA.Utility.exe [MOD]` to see a list of available commands.");
Console.WriteLine("The available mods are: " + string.Join(", ", ModMetadata.AllMods.Keys));
Console.WriteLine();
foreach (var a in Actions)
if (actions == null)
return;
foreach (var a in actions)
{
var descParts = a.Value.Method.GetCustomAttributes<DescAttribute>(true)
.SelectMany(d => d.Lines);

View File

@@ -7,10 +7,7 @@ echo "Updating https://github.com/OpenRA/OpenRA/wiki/Traits"
rm -rf $HOME/openra-wiki
git clone git@github.com:OpenRA/OpenRA.wiki.git $HOME/openra-wiki
cp -fr ../DOCUMENTATION.md $HOME/openra-wiki/Traits.md
pushd .. &> /dev/null
mono --debug OpenRA.Utility.exe --lua-docs d2k > $HOME/openra-wiki/New-Lua-API.md
popd &> /dev/null
cp -fr ../Lua-API.md $HOME/openra-wiki/New-Lua-API.md
pushd $HOME/openra-wiki
git add Traits.md