Added IUtilityCommand and move default commands into Mods.Common.
This commit is contained in:
committed by
Paul Chote
parent
ff7b3541f4
commit
08890b0994
@@ -1,550 +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("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("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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +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.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Utility
|
||||
{
|
||||
public class ExtractLanguageStrings
|
||||
{
|
||||
[Desc("MOD", "Extract translatable strings that are not yet localized and update chrome layout.")]
|
||||
public static void FromMod(string[] args)
|
||||
{
|
||||
var mod = args[1];
|
||||
Game.modData = new ModData(mod);
|
||||
Game.modData.RulesetCache.LoadDefaultRules();
|
||||
|
||||
var types = Game.modData.ObjectCreator.GetTypes();
|
||||
var translatableFields = types.SelectMany(t => t.GetFields())
|
||||
.Where(f => f.HasAttribute<TranslateAttribute>()).Distinct();
|
||||
|
||||
foreach (var filename in Game.modData.Manifest.ChromeLayout)
|
||||
{
|
||||
Console.WriteLine("# {0}:", filename);
|
||||
var yaml = MiniYaml.FromFile(filename);
|
||||
ExtractLanguageStrings.FromChromeLayout(ref yaml, null,
|
||||
translatableFields.Select(t => t.Name).Distinct(), null);
|
||||
using (var file = new StreamWriter(filename))
|
||||
file.WriteLine(yaml.WriteToString());
|
||||
}
|
||||
|
||||
// TODO: Properties can also be translated.
|
||||
}
|
||||
|
||||
public 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;
|
||||
var parentLabel = parent != null ? parentNode.Last() : null;
|
||||
|
||||
if ((parentType == "Background" || parentType == "Container") && parentLabel.IsUppercase())
|
||||
container = parentLabel;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var alreadyTranslated = node.Value.Value != null && node.Value.Value.Contains('@');
|
||||
if (translatables.Contains(node.Key) && !alreadyTranslated)
|
||||
{
|
||||
var translationKey = "{0}-{1}-{2}".F(container.Replace('_', '-'), parentLabel.Replace('_', '-'), node.Key.ToUpper());
|
||||
Console.WriteLine("\t{0}: {1}", translationKey, node.Value.Value);
|
||||
node.Value.Value = "@{0}@".F(translationKey);
|
||||
}
|
||||
|
||||
FromChromeLayout(ref node.Value.Nodes, node, translatables, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Utility
|
||||
{
|
||||
public static class Glob
|
||||
{
|
||||
public static bool Enabled = true;
|
||||
|
||||
static readonly char[] GlobChars = new char[] { '*', '?' };
|
||||
static readonly char[] DirectorySeparators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
|
||||
|
||||
static bool NeedsExpansion(string filePath)
|
||||
{
|
||||
if (!Enabled)
|
||||
return false;
|
||||
|
||||
return filePath.IndexOfAny(GlobChars) >= 0;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Expand(string filePath)
|
||||
{
|
||||
if (!NeedsExpansion(filePath))
|
||||
{
|
||||
yield return filePath;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Split using DirectorySeparators but keep the separators
|
||||
var parts = new List<string>();
|
||||
|
||||
for (var startIndex = 0; startIndex < filePath.Length;)
|
||||
{
|
||||
var index = filePath.IndexOfAny(DirectorySeparators, startIndex);
|
||||
if (index == -1)
|
||||
{
|
||||
parts.Add(filePath.Substring(startIndex));
|
||||
break;
|
||||
}
|
||||
|
||||
parts.Add(filePath.Substring(startIndex, index - startIndex + 1));
|
||||
startIndex = index + 1;
|
||||
}
|
||||
|
||||
if (parts.Count > 0 && (parts[0] == "." || parts[0] == ".."))
|
||||
parts[0] += Path.DirectorySeparatorChar;
|
||||
|
||||
// If it's empty
|
||||
// or if
|
||||
// it's not rooted
|
||||
// and it doesn't start with "./" or "../"
|
||||
// prepend "./"
|
||||
if (parts.Count == 0
|
||||
|| (parts[0][0] != Path.DirectorySeparatorChar
|
||||
&& parts[0][0] != Path.AltDirectorySeparatorChar
|
||||
&& parts[0].Contains(':') == false
|
||||
&& parts[0] != "." + Path.DirectorySeparatorChar
|
||||
&& parts[0] != "." + Path.AltDirectorySeparatorChar
|
||||
&& parts[0] != ".." + Path.DirectorySeparatorChar
|
||||
&& parts[0] != ".." + Path.AltDirectorySeparatorChar))
|
||||
parts.Insert(0, "." + Path.DirectorySeparatorChar);
|
||||
|
||||
// If the last entry ends with a directory separator, append a '*'
|
||||
if (parts[parts.Count - 1][parts[parts.Count - 1].Length - 1] == Path.DirectorySeparatorChar
|
||||
|| parts[parts.Count - 1][parts[parts.Count - 1].Length - 1] == Path.AltDirectorySeparatorChar)
|
||||
parts.Add("*");
|
||||
|
||||
var root = parts[0];
|
||||
var dirs = parts.Skip(1).Take(parts.Count - 2).ToList();
|
||||
var file = parts[parts.Count - 1];
|
||||
|
||||
foreach (var path in Expand(root, dirs, 0, file))
|
||||
yield return path;
|
||||
}
|
||||
|
||||
static IEnumerable<string> Expand(string basePath, IList<string> dirs, int dirIndex, string file)
|
||||
{
|
||||
if (dirIndex < dirs.Count)
|
||||
{
|
||||
var dir = dirs[dirIndex];
|
||||
|
||||
if (!NeedsExpansion(dir))
|
||||
{
|
||||
var path = Path.Combine(basePath, dir);
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
yield break;
|
||||
else
|
||||
{
|
||||
foreach (var s in Expand(path, dirs, dirIndex + 1, file))
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dir[dir.Length - 1] == Path.DirectorySeparatorChar || dir[dir.Length - 1] == Path.AltDirectorySeparatorChar)
|
||||
dir = dir.Substring(0, dir.Length - 1);
|
||||
foreach (var subDir in Directory.EnumerateDirectories(basePath, dir, SearchOption.TopDirectoryOnly))
|
||||
foreach (var s in Expand(subDir, dirs, dirIndex + 1, file))
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!NeedsExpansion(file))
|
||||
yield return Path.Combine(basePath, file);
|
||||
else
|
||||
{
|
||||
foreach (var s in Directory.GetFiles(basePath, file, SearchOption.TopDirectoryOnly))
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,507 +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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Utility
|
||||
{
|
||||
public class LegacyMapImporter
|
||||
{
|
||||
// Mapping from ra overlay index to type string
|
||||
static string[] redAlertOverlayNames =
|
||||
{
|
||||
"sbag", "cycl", "brik", "fenc", "wood",
|
||||
"gold01", "gold02", "gold03", "gold04",
|
||||
"gem01", "gem02", "gem03", "gem04",
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
|
||||
"fpls", "wcrate", "scrate", "barb", "sbag",
|
||||
};
|
||||
|
||||
static Dictionary<string, Pair<byte, byte>> overlayResourceMapping = new Dictionary<string, Pair<byte, byte>>()
|
||||
{
|
||||
// RA Gold & Gems
|
||||
{ "gold01", new Pair<byte, byte>(1, 0) },
|
||||
{ "gold02", new Pair<byte, byte>(1, 1) },
|
||||
{ "gold03", new Pair<byte, byte>(1, 2) },
|
||||
{ "gold04", new Pair<byte, byte>(1, 3) },
|
||||
{ "gem01", new Pair<byte, byte>(2, 0) },
|
||||
{ "gem02", new Pair<byte, byte>(2, 1) },
|
||||
{ "gem03", new Pair<byte, byte>(2, 2) },
|
||||
{ "gem04", new Pair<byte, byte>(2, 3) },
|
||||
|
||||
// CnC Tiberium
|
||||
{ "ti1", new Pair<byte, byte>(1, 0) },
|
||||
{ "ti2", new Pair<byte, byte>(1, 1) },
|
||||
{ "ti3", new Pair<byte, byte>(1, 2) },
|
||||
{ "ti4", new Pair<byte, byte>(1, 3) },
|
||||
{ "ti5", new Pair<byte, byte>(1, 4) },
|
||||
{ "ti6", new Pair<byte, byte>(1, 5) },
|
||||
{ "ti7", new Pair<byte, byte>(1, 6) },
|
||||
{ "ti8", new Pair<byte, byte>(1, 7) },
|
||||
{ "ti9", new Pair<byte, byte>(1, 8) },
|
||||
{ "ti10", new Pair<byte, byte>(1, 9) },
|
||||
{ "ti11", new Pair<byte, byte>(1, 10) },
|
||||
{ "ti12", new Pair<byte, byte>(1, 11) },
|
||||
};
|
||||
|
||||
static Dictionary<string, string> overlayActorMapping = new Dictionary<string, string>() {
|
||||
// Fences
|
||||
{ "sbag", "sbag" },
|
||||
{ "cycl", "cycl" },
|
||||
{ "brik", "brik" },
|
||||
{ "fenc", "fenc" },
|
||||
{ "wood", "wood" },
|
||||
|
||||
// Fields
|
||||
{ "v12", "v12" },
|
||||
{ "v13", "v13" },
|
||||
{ "v14", "v14" },
|
||||
{ "v15", "v15" },
|
||||
{ "v16", "v16" },
|
||||
{ "v17", "v17" },
|
||||
{ "v18", "v18" },
|
||||
|
||||
// Crates
|
||||
// { "wcrate", "crate" },
|
||||
// { "scrate", "crate" },
|
||||
};
|
||||
|
||||
// TODO: fix this -- will have bitrotted pretty badly.
|
||||
static Dictionary<string, HSLColor> namedColorMapping = new Dictionary<string, HSLColor>()
|
||||
{
|
||||
{ "gold", HSLColor.FromRGB(246, 214, 121) },
|
||||
{ "blue", HSLColor.FromRGB(226, 230, 246) },
|
||||
{ "red", HSLColor.FromRGB(255, 20, 0) },
|
||||
{ "neutral", HSLColor.FromRGB(238, 238, 238) },
|
||||
{ "orange", HSLColor.FromRGB(255, 230, 149) },
|
||||
{ "teal", HSLColor.FromRGB(93, 194, 165) },
|
||||
{ "salmon", HSLColor.FromRGB(210, 153, 125) },
|
||||
{ "green", HSLColor.FromRGB(160, 240, 140) },
|
||||
{ "white", HSLColor.FromRGB(255, 255, 255) },
|
||||
{ "black", HSLColor.FromRGB(80, 80, 80) },
|
||||
};
|
||||
|
||||
static string Truncate(string s, int maxLength)
|
||||
{
|
||||
return s.Length <= maxLength ? s : s.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
int mapSize;
|
||||
int actorCount = 0;
|
||||
Map map;
|
||||
Ruleset rules;
|
||||
List<string> players = new List<string>();
|
||||
Action<string> errorHandler;
|
||||
|
||||
LegacyMapImporter(string filename, Ruleset rules, Action<string> errorHandler)
|
||||
{
|
||||
this.rules = rules;
|
||||
this.errorHandler = errorHandler;
|
||||
|
||||
ConvertIniMap(filename);
|
||||
}
|
||||
|
||||
public static Map Import(string filename, string mod, Ruleset rules, Action<string> errorHandler)
|
||||
{
|
||||
var map = new LegacyMapImporter(filename, rules, errorHandler).map;
|
||||
map.RequiresMod = mod;
|
||||
map.MakeDefaultPlayers();
|
||||
map.FixOpenAreas(rules);
|
||||
return map;
|
||||
}
|
||||
|
||||
enum IniMapFormat { RedAlert = 3 } // otherwise, cnc (2 variants exist, we don't care to differentiate)
|
||||
|
||||
public void ConvertIniMap(string iniFile)
|
||||
{
|
||||
var file = new IniFile(GlobalFileSystem.Open(iniFile));
|
||||
var basic = file.GetSection("Basic");
|
||||
var mapSection = file.GetSection("Map");
|
||||
var legacyMapFormat = (IniMapFormat)Exts.ParseIntegerInvariant(basic.GetValue("NewINIFormat", "0"));
|
||||
var offsetX = Exts.ParseIntegerInvariant(mapSection.GetValue("X", "0"));
|
||||
var offsetY = Exts.ParseIntegerInvariant(mapSection.GetValue("Y", "0"));
|
||||
var width = Exts.ParseIntegerInvariant(mapSection.GetValue("Width", "0"));
|
||||
var height = Exts.ParseIntegerInvariant(mapSection.GetValue("Height", "0"));
|
||||
mapSize = (legacyMapFormat == IniMapFormat.RedAlert) ? 128 : 64;
|
||||
var size = new Size(mapSize, mapSize);
|
||||
|
||||
var tileset = Truncate(mapSection.GetValue("Theater", "TEMPERAT"), 8);
|
||||
map = Map.FromTileset(rules.TileSets[tileset]);
|
||||
map.Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(iniFile));
|
||||
map.Author = "Westwood Studios";
|
||||
map.MapSize.X = mapSize;
|
||||
map.MapSize.Y = mapSize;
|
||||
map.Bounds = Rectangle.FromLTRB(offsetX, offsetY, offsetX + width, offsetY + height);
|
||||
map.Selectable = true;
|
||||
|
||||
map.Smudges = Exts.Lazy(() => new List<SmudgeReference>());
|
||||
map.Actors = Exts.Lazy(() => new Dictionary<string, ActorReference>());
|
||||
map.MapResources = Exts.Lazy(() => new CellLayer<ResourceTile>(TileShape.Rectangle, size));
|
||||
map.MapTiles = Exts.Lazy(() => new CellLayer<TerrainTile>(TileShape.Rectangle, size));
|
||||
|
||||
map.Options = new MapOptions();
|
||||
|
||||
if (legacyMapFormat == IniMapFormat.RedAlert)
|
||||
{
|
||||
UnpackRATileData(ReadPackedSection(file.GetSection("MapPack")));
|
||||
UnpackRAOverlayData(ReadPackedSection(file.GetSection("OverlayPack")));
|
||||
ReadRATrees(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
// CnC
|
||||
using (var s = GlobalFileSystem.Open(iniFile.Substring(0, iniFile.Length - 4) + ".bin"))
|
||||
UnpackCncTileData(s);
|
||||
ReadCncOverlay(file);
|
||||
ReadCncTrees(file);
|
||||
}
|
||||
|
||||
LoadActors(file, "STRUCTURES");
|
||||
LoadActors(file, "UNITS");
|
||||
LoadActors(file, "INFANTRY");
|
||||
LoadSmudges(file, "SMUDGE");
|
||||
|
||||
foreach (var p in players)
|
||||
LoadPlayer(file, p, legacyMapFormat == IniMapFormat.RedAlert);
|
||||
|
||||
var wps = file.GetSection("Waypoints")
|
||||
.Where(kv => Exts.ParseIntegerInvariant(kv.Value) > 0)
|
||||
.Select(kv => Pair.New(Exts.ParseIntegerInvariant(kv.Key),
|
||||
LocationFromMapOffset(Exts.ParseIntegerInvariant(kv.Value), mapSize)))
|
||||
.ToArray();
|
||||
|
||||
// Add waypoint actors
|
||||
foreach (var kv in wps)
|
||||
{
|
||||
if (kv.First <= 7)
|
||||
{
|
||||
var a = new ActorReference("mpspawn");
|
||||
a.Add(new LocationInit((CPos)kv.Second));
|
||||
a.Add(new OwnerInit("Neutral"));
|
||||
map.Actors.Value.Add("Actor" + map.Actors.Value.Count.ToString(), a);
|
||||
}
|
||||
else
|
||||
{
|
||||
var a = new ActorReference("waypoint");
|
||||
a.Add(new LocationInit((CPos)kv.Second));
|
||||
a.Add(new OwnerInit("Neutral"));
|
||||
map.Actors.Value.Add("waypoint" + kv.First, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int2 LocationFromMapOffset(int offset, int mapSize)
|
||||
{
|
||||
return new int2(offset % mapSize, offset / mapSize);
|
||||
}
|
||||
|
||||
static MemoryStream ReadPackedSection(IniSection mapPackSection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 1;; i++)
|
||||
{
|
||||
var line = mapPackSection.GetValue(i.ToString(), null);
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
sb.Append(line.Trim());
|
||||
}
|
||||
|
||||
var data = Convert.FromBase64String(sb.ToString());
|
||||
var chunks = new List<byte[]>();
|
||||
var reader = new BinaryReader(new MemoryStream(data));
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var length = reader.ReadUInt32() & 0xdfffffff;
|
||||
var dest = new byte[8192];
|
||||
var src = reader.ReadBytes((int)length);
|
||||
|
||||
/*int actualLength =*/
|
||||
Format80.DecodeInto(src, dest);
|
||||
|
||||
chunks.Add(dest);
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException) { }
|
||||
|
||||
var ms = new MemoryStream();
|
||||
foreach (var chunk in chunks)
|
||||
ms.Write(chunk, 0, chunk.Length);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
void UnpackRATileData(MemoryStream ms)
|
||||
{
|
||||
var types = new ushort[mapSize, mapSize];
|
||||
for (var j = 0; j < mapSize; j++)
|
||||
{
|
||||
for (var i = 0; i < mapSize; i++)
|
||||
{
|
||||
var tileID = ms.ReadUInt16();
|
||||
types[i, j] = tileID == (ushort)0 ? (ushort)255 : tileID; // RAED weirdness
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < mapSize; j++)
|
||||
for (var i = 0; i < mapSize; i++)
|
||||
map.MapTiles.Value[new CPos(i, j)] = new TerrainTile(types[i, j], ms.ReadUInt8());
|
||||
|
||||
}
|
||||
|
||||
void UnpackRAOverlayData(MemoryStream ms)
|
||||
{
|
||||
for (var j = 0; j < mapSize; j++)
|
||||
{
|
||||
for (var i = 0; i < mapSize; i++)
|
||||
{
|
||||
var o = ms.ReadUInt8();
|
||||
var res = Pair.New((byte)0, (byte)0);
|
||||
|
||||
if (o != 255 && overlayResourceMapping.ContainsKey(redAlertOverlayNames[o]))
|
||||
res = overlayResourceMapping[redAlertOverlayNames[o]];
|
||||
|
||||
var cell = new CPos(i, j);
|
||||
map.MapResources.Value[cell] = new ResourceTile(res.First, res.Second);
|
||||
|
||||
if (o != 255 && overlayActorMapping.ContainsKey(redAlertOverlayNames[o]))
|
||||
{
|
||||
map.Actors.Value.Add("Actor" + actorCount++,
|
||||
new ActorReference(overlayActorMapping[redAlertOverlayNames[o]])
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadRATrees(IniFile file)
|
||||
{
|
||||
var terrain = file.GetSection("TERRAIN", true);
|
||||
if (terrain == null)
|
||||
return;
|
||||
|
||||
foreach (var kv in terrain)
|
||||
{
|
||||
var loc = Exts.ParseIntegerInvariant(kv.Key);
|
||||
map.Actors.Value.Add("Actor" + actorCount++,
|
||||
new ActorReference(kv.Value.ToLowerInvariant())
|
||||
{
|
||||
new LocationInit(new CPos(loc % mapSize, loc / mapSize)),
|
||||
new OwnerInit("Neutral")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void UnpackCncTileData(Stream ms)
|
||||
{
|
||||
for (var j = 0; j < mapSize; j++)
|
||||
{
|
||||
for (var i = 0; i < mapSize; i++)
|
||||
{
|
||||
var type = ms.ReadUInt8();
|
||||
var index = ms.ReadUInt8();
|
||||
map.MapTiles.Value[new CPos(i, j)] = new TerrainTile(type, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadCncOverlay(IniFile file)
|
||||
{
|
||||
var overlay = file.GetSection("OVERLAY", true);
|
||||
if (overlay == null)
|
||||
return;
|
||||
|
||||
foreach (var kv in overlay)
|
||||
{
|
||||
var loc = Exts.ParseIntegerInvariant(kv.Key);
|
||||
var cell = new CPos(loc % mapSize, loc / mapSize);
|
||||
|
||||
var res = Pair.New((byte)0, (byte)0);
|
||||
if (overlayResourceMapping.ContainsKey(kv.Value.ToLower()))
|
||||
res = overlayResourceMapping[kv.Value.ToLower()];
|
||||
|
||||
map.MapResources.Value[cell] = new ResourceTile(res.First, res.Second);
|
||||
|
||||
if (overlayActorMapping.ContainsKey(kv.Value.ToLower()))
|
||||
map.Actors.Value.Add("Actor" + actorCount++,
|
||||
new ActorReference(overlayActorMapping[kv.Value.ToLower()])
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ReadCncTrees(IniFile file)
|
||||
{
|
||||
var terrain = file.GetSection("TERRAIN", true);
|
||||
if (terrain == null)
|
||||
return;
|
||||
|
||||
foreach (var kv in terrain)
|
||||
{
|
||||
var loc = Exts.ParseIntegerInvariant(kv.Key);
|
||||
map.Actors.Value.Add("Actor" + actorCount++,
|
||||
new ActorReference(kv.Value.Split(',')[0].ToLowerInvariant())
|
||||
{
|
||||
new LocationInit(new CPos(loc % mapSize, loc / mapSize)),
|
||||
new OwnerInit("Neutral")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void LoadActors(IniFile file, string section)
|
||||
{
|
||||
foreach (var s in file.GetSection(section, true))
|
||||
{
|
||||
// Structures: num=owner,type,health,location,turret-facing,trigger
|
||||
// Units: num=owner,type,health,location,facing,action,trigger
|
||||
// Infantry: num=owner,type,health,location,subcell,action,facing,trigger
|
||||
try
|
||||
{
|
||||
var parts = s.Value.Split(',');
|
||||
var loc = Exts.ParseIntegerInvariant(parts[3]);
|
||||
if (parts[0] == "")
|
||||
parts[0] = "Neutral";
|
||||
|
||||
if (!players.Contains(parts[0]))
|
||||
players.Add(parts[0]);
|
||||
|
||||
var actor = new ActorReference(parts[1].ToLowerInvariant())
|
||||
{
|
||||
new LocationInit(new CPos(loc % mapSize, loc / mapSize)),
|
||||
new OwnerInit(parts[0]),
|
||||
new HealthInit(float.Parse(parts[2], NumberFormatInfo.InvariantInfo) / 256),
|
||||
new FacingInit((section == "INFANTRY")
|
||||
? Exts.ParseIntegerInvariant(parts[6])
|
||||
: Exts.ParseIntegerInvariant(parts[4])),
|
||||
};
|
||||
|
||||
if (section == "INFANTRY")
|
||||
actor.Add(new SubCellInit(Exts.ParseIntegerInvariant(parts[4])));
|
||||
|
||||
if (!rules.Actors.ContainsKey(parts[1].ToLowerInvariant()))
|
||||
errorHandler("Ignoring unknown actor type: `{0}`".F(parts[1].ToLowerInvariant()));
|
||||
else
|
||||
map.Actors.Value.Add("Actor" + actorCount++, actor);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
errorHandler("Malformed actor definition: `{0}`".F(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSmudges(IniFile file, string section)
|
||||
{
|
||||
foreach (var s in file.GetSection(section, true))
|
||||
{
|
||||
// loc=type,loc,depth
|
||||
var parts = s.Value.Split(',');
|
||||
var loc = Exts.ParseIntegerInvariant(parts[1]);
|
||||
map.Smudges.Value.Add(new SmudgeReference(parts[0].ToLowerInvariant(), new int2(loc % mapSize, loc / mapSize), Exts.ParseIntegerInvariant(parts[2])));
|
||||
}
|
||||
}
|
||||
|
||||
void LoadPlayer(IniFile file, string section, bool isRA)
|
||||
{
|
||||
string c;
|
||||
string race;
|
||||
switch (section)
|
||||
{
|
||||
case "Spain":
|
||||
c = "gold";
|
||||
race = "allies";
|
||||
break;
|
||||
case "England":
|
||||
c = "green";
|
||||
race = "allies";
|
||||
break;
|
||||
case "Ukraine":
|
||||
c = "orange";
|
||||
race = "soviet";
|
||||
break;
|
||||
case "Germany":
|
||||
c = "black";
|
||||
race = "allies";
|
||||
break;
|
||||
case "France":
|
||||
c = "teal";
|
||||
race = "allies";
|
||||
break;
|
||||
case "Turkey":
|
||||
c = "salmon";
|
||||
race = "allies";
|
||||
break;
|
||||
case "Greece":
|
||||
case "GoodGuy":
|
||||
c = isRA ? "blue" : "gold";
|
||||
race = isRA ? "allies" : "gdi";
|
||||
break;
|
||||
case "USSR":
|
||||
case "BadGuy":
|
||||
c = "red";
|
||||
race = isRA ? "soviet" : "nod";
|
||||
break;
|
||||
case "Special":
|
||||
case "Neutral":
|
||||
default:
|
||||
c = "neutral";
|
||||
race = isRA ? "allies" : "gdi";
|
||||
break;
|
||||
}
|
||||
|
||||
var pr = new PlayerReference
|
||||
{
|
||||
Name = section,
|
||||
OwnsWorld = section == "Neutral",
|
||||
NonCombatant = section == "Neutral",
|
||||
Race = race,
|
||||
Color = namedColorMapping[c]
|
||||
};
|
||||
|
||||
var neutral = new[] { "Neutral" };
|
||||
foreach (var s in file.GetSection(section, true))
|
||||
{
|
||||
switch (s.Key)
|
||||
{
|
||||
case "Allies":
|
||||
pr.Allies = s.Value.Split(',').Intersect(players).Except(neutral).ToArray();
|
||||
pr.Enemies = s.Value.Split(',').SymmetricDifference(players).Except(neutral).ToArray();
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("Ignoring unknown {0}={1} for player {2}", s.Key, s.Value, pr.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
map.Players.Add(section, pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -18,36 +18,39 @@ namespace OpenRA.Utility
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static readonly Dictionary<string, Action<string[]>> Actions = new Dictionary<string, Action<string[]>>()
|
||||
{
|
||||
{ "--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 },
|
||||
{ "--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)
|
||||
{
|
||||
@@ -60,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);
|
||||
|
||||
@@ -1,977 +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.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Utility
|
||||
{
|
||||
public static class UpgradeRules
|
||||
{
|
||||
static void ConvertFloatToRange(ref string input)
|
||||
{
|
||||
var value = float.Parse(input);
|
||||
var cells = (int)value;
|
||||
var subcells = (int)(1024 * value) - 1024 * cells;
|
||||
|
||||
input = "{0}c{1}".F(cells, subcells);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ConvertPxToRange(ref input, 1, 1);
|
||||
}
|
||||
|
||||
static void ConvertPxToRange(ref string input, int scaleMult, int scaleDiv)
|
||||
{
|
||||
var value = Exts.ParseIntegerInvariant(input);
|
||||
var ts = Game.modData.Manifest.TileSize;
|
||||
var world = value * 1024 * scaleMult / (scaleDiv * ts.Height);
|
||||
var cells = world / 1024;
|
||||
var subcells = world - 1024 * cells;
|
||||
|
||||
input = cells != 0 ? "{0}c{1}".F(cells, subcells) : subcells.ToString();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var offset = FieldLoader.GetValue<int2>("(value)", input);
|
||||
var ts = Game.modData.Manifest.TileSize;
|
||||
var world = new WVec(offset.X * 1024 / ts.Width, offset.Y * 1024 / ts.Height, 0);
|
||||
input = world.ToString();
|
||||
}
|
||||
|
||||
static void UpgradeActorRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
|
||||
{
|
||||
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Weapon definitions were converted to world coordinates
|
||||
if (engineVersion < 20131226)
|
||||
{
|
||||
if (depth == 2 && parentKey == "Exit" && node.Key == "SpawnOffset")
|
||||
ConvertInt2ToWVec(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && (parentKey == "Aircraft" || parentKey == "Helicopter" || parentKey == "Plane"))
|
||||
{
|
||||
if (node.Key == "CruiseAltitude")
|
||||
ConvertPxToRange(ref node.Value.Value);
|
||||
|
||||
if (node.Key == "Speed")
|
||||
ConvertPxToRange(ref node.Value.Value, 7, 32);
|
||||
}
|
||||
|
||||
if (depth == 2 && parentKey == "Mobile" && node.Key == "Speed")
|
||||
ConvertPxToRange(ref node.Value.Value, 1, 3);
|
||||
|
||||
if (depth == 2 && parentKey == "Health" && node.Key == "Radius")
|
||||
ConvertPxToRange(ref node.Value.Value);
|
||||
}
|
||||
|
||||
// CrateDrop was replaced with CrateSpawner
|
||||
if (engineVersion < 20131231)
|
||||
{
|
||||
if (depth == 1 && parentKey == "World")
|
||||
{
|
||||
if (node.Key == "CrateDrop")
|
||||
node.Key = "CrateSpawner";
|
||||
|
||||
if (node.Key == "-CrateDrop")
|
||||
node.Key = "-CrateSpawner";
|
||||
}
|
||||
}
|
||||
|
||||
// AttackTesla was replaced with AttackCharge
|
||||
if (engineVersion < 20140307)
|
||||
{
|
||||
if (depth == 1)
|
||||
{
|
||||
if (node.Key == "AttackTesla")
|
||||
node.Key = "AttackCharge";
|
||||
|
||||
if (node.Key == "-AttackTesla")
|
||||
node.Key = "-AttackCharge";
|
||||
}
|
||||
}
|
||||
|
||||
// AttackMove was generalized to support all moveable actor types
|
||||
if (engineVersion < 20140116)
|
||||
{
|
||||
if (depth == 1 && node.Key == "AttackMove")
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "JustMove");
|
||||
}
|
||||
|
||||
// UnloadFacing was removed from Cargo
|
||||
if (engineVersion < 20140212)
|
||||
{
|
||||
if (depth == 1 && node.Key == "Cargo")
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "UnloadFacing");
|
||||
}
|
||||
|
||||
// RevealShroud was updated to use world units.
|
||||
if (engineVersion < 20140220)
|
||||
{
|
||||
if (depth == 2 && parentKey == "RevealsShroud" && node.Key == "Range")
|
||||
ConvertFloatToRange(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && parentKey == "CreatesShroud" && node.Key == "Range")
|
||||
ConvertFloatToRange(ref node.Value.Value);
|
||||
}
|
||||
|
||||
// Waypoint was renamed to Immobile
|
||||
if (engineVersion < 20140312)
|
||||
{
|
||||
if (depth == 1 && node.Key == "Waypoint")
|
||||
node.Key = "Immobile";
|
||||
}
|
||||
|
||||
// Spy was renamed to Disguise
|
||||
if (engineVersion < 20140314)
|
||||
{
|
||||
if (depth == 1 && node.Key == "Spy")
|
||||
node.Key = "Disguise";
|
||||
|
||||
if (depth == 1 && node.Key == "SpyToolTip")
|
||||
node.Key = "DisguiseToolTip";
|
||||
|
||||
if (depth == 1 && node.Key == "RenderSpy")
|
||||
node.Key = "RenderDisguise";
|
||||
}
|
||||
|
||||
// IOccupySpace was removed from Mine
|
||||
if (engineVersion < 20140320)
|
||||
{
|
||||
if (depth == 0 && node.Value.Nodes.Any(n => n.Key == "Mine"))
|
||||
node.Value.Nodes.Add(new MiniYamlNode("Immobile", new MiniYaml("", new List<MiniYamlNode>() { new MiniYamlNode("OccupiesSpace", "true") })));
|
||||
else
|
||||
foreach (var i in nodes.Where(n => n.Key == "Immobile"))
|
||||
if (!i.Value.Nodes.Any(n => n.Key == "OccupiesSpace"))
|
||||
i.Value.Nodes.Add(new MiniYamlNode("OccupiesSpace", "false"));
|
||||
}
|
||||
|
||||
// Armaments and muzzleflashes were reworked to support garrisoning
|
||||
if (engineVersion < 20140321)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
var muzzles = node.Value.Nodes.Where(n => n.Key.StartsWith("WithMuzzleFlash"));
|
||||
var armaments = node.Value.Nodes.Where(n => n.Key.StartsWith("Armament"));
|
||||
|
||||
// Shift muzzle flash definitions to Armament
|
||||
foreach (var m in muzzles)
|
||||
{
|
||||
var muzzleArmNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Armament");
|
||||
var muzzleSequenceNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Sequence");
|
||||
var muzzleSplitFacingsNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "SplitFacings");
|
||||
var muzzleFacingsCountNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "FacingCount");
|
||||
|
||||
var muzzleArmName = muzzleArmNode != null ? muzzleArmNode.Value.Value.Trim() : "primary";
|
||||
var muzzleSequence = muzzleSequenceNode != null ? muzzleSequenceNode.Value.Value.Trim() : "muzzle";
|
||||
var muzzleSplitFacings = muzzleSplitFacingsNode != null ? FieldLoader.GetValue<bool>("SplitFacings", muzzleSplitFacingsNode.Value.Value) : false;
|
||||
var muzzleFacingsCount = muzzleFacingsCountNode != null ? FieldLoader.GetValue<int>("FacingsCount", muzzleFacingsCountNode.Value.Value) : 8;
|
||||
|
||||
foreach (var a in armaments)
|
||||
{
|
||||
var armNameNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Name");
|
||||
var armName = armNameNode != null ? armNameNode.Value.Value.Trim() : "primary";
|
||||
|
||||
if (muzzleArmName == armName)
|
||||
{
|
||||
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSequence", muzzleSequence));
|
||||
if (muzzleSplitFacings)
|
||||
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSplitFacings", muzzleFacingsCount.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var m in muzzles.ToList().Skip(1))
|
||||
node.Value.Nodes.Remove(m);
|
||||
}
|
||||
|
||||
// Remove all but the first muzzle flash definition
|
||||
if (depth == 1 && node.Key.StartsWith("WithMuzzleFlash"))
|
||||
{
|
||||
node.Key = "WithMuzzleFlash";
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "Armament");
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "Sequence");
|
||||
}
|
||||
}
|
||||
|
||||
// "disabled" palette overlay has been moved into it's own DisabledOverlay trait
|
||||
if (engineVersion < 20140305)
|
||||
{
|
||||
if (node.Value.Nodes.Any(n => n.Key.StartsWith("RequiresPower"))
|
||||
&& !node.Value.Nodes.Any(n => n.Key.StartsWith("DisabledOverlay")))
|
||||
{
|
||||
node.Value.Nodes.Add(new MiniYamlNode("DisabledOverlay", new MiniYaml("")));
|
||||
}
|
||||
}
|
||||
|
||||
// ChronoshiftDeploy was replaced with PortableChrono
|
||||
if (engineVersion < 20140321)
|
||||
{
|
||||
if (depth == 1 && node.Key == "ChronoshiftDeploy")
|
||||
node.Key = "PortableChrono";
|
||||
|
||||
if (depth == 2 && parentKey == "PortableChrono" && node.Key == "JumpDistance")
|
||||
node.Key = "MaxDistance";
|
||||
}
|
||||
|
||||
// Added new Lua API
|
||||
if (engineVersion < 20140421)
|
||||
{
|
||||
if (depth == 0 && node.Value.Nodes.Any(n => n.Key == "LuaScriptEvents"))
|
||||
node.Value.Nodes.Add(new MiniYamlNode("ScriptTriggers", ""));
|
||||
}
|
||||
|
||||
if (engineVersion < 20140517)
|
||||
{
|
||||
if (depth == 0)
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "TeslaInstantKills");
|
||||
}
|
||||
|
||||
if (engineVersion < 20140615)
|
||||
{
|
||||
if (depth == 1 && node.Key == "StoresOre")
|
||||
node.Key = "StoresResources";
|
||||
}
|
||||
|
||||
// make animation is now its own trait
|
||||
if (engineVersion < 20140621)
|
||||
{
|
||||
if (depth == 1 && (node.Key.StartsWith("RenderBuilding")))
|
||||
node.Value.Nodes.RemoveAll(n => n.Key == "HasMakeAnimation");
|
||||
|
||||
if (node.Value.Nodes.Any(n => n.Key.StartsWith("RenderBuilding"))
|
||||
&& !node.Value.Nodes.Any(n => n.Key == "RenderBuildingWall")
|
||||
&& !node.Value.Nodes.Any(n => n.Key == "WithMakeAnimation"))
|
||||
{
|
||||
node.Value.Nodes.Add(new MiniYamlNode("WithMakeAnimation", new MiniYaml("")));
|
||||
}
|
||||
}
|
||||
|
||||
// ParachuteAttachment was merged into Parachutable
|
||||
if (engineVersion < 20140701)
|
||||
{
|
||||
if (depth == 1 && node.Key == "ParachuteAttachment")
|
||||
{
|
||||
node.Key = "Parachutable";
|
||||
|
||||
foreach (var subnode in node.Value.Nodes)
|
||||
if (subnode.Key == "Offset")
|
||||
subnode.Key = "ParachuteOffset";
|
||||
}
|
||||
|
||||
if (depth == 2 && node.Key == "ParachuteSprite")
|
||||
node.Key = "ParachuteSequence";
|
||||
}
|
||||
|
||||
// SonarPulsePower was implemented as a generic SpawnActorPower
|
||||
if (engineVersion < 20140703)
|
||||
{
|
||||
if (depth == 1 && node.Key == "SonarPulsePower")
|
||||
node.Key = "SpawnActorPower";
|
||||
}
|
||||
|
||||
if (engineVersion < 20140707)
|
||||
{
|
||||
// SpyPlanePower was removed (use AirstrikePower instead)
|
||||
if (depth == 1 && node.Key == "SpyPlanePower")
|
||||
{
|
||||
node.Key = "AirstrikePower";
|
||||
|
||||
var revealTime = 6 * 25;
|
||||
var revealNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "RevealTime");
|
||||
if (revealNode != null)
|
||||
{
|
||||
revealTime = int.Parse(revealNode.Value.Value) * 25;
|
||||
node.Value.Nodes.Remove(revealNode);
|
||||
}
|
||||
|
||||
node.Value.Nodes.Add(new MiniYamlNode("CameraActor", new MiniYaml("camera")));
|
||||
node.Value.Nodes.Add(new MiniYamlNode("CameraRemoveDelay", new MiniYaml(revealTime.ToString())));
|
||||
node.Value.Nodes.Add(new MiniYamlNode("UnitType", new MiniYaml("u2")));
|
||||
}
|
||||
|
||||
if (depth == 2 && node.Key == "LZRange" && parentKey == "ParaDrop")
|
||||
{
|
||||
node.Key = "DropRange";
|
||||
ConvertFloatToRange(ref node.Value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// GiveUnitCrateAction and GiveMcvCrateAction were updated to allow multiple units
|
||||
if (engineVersion < 20140723)
|
||||
{
|
||||
if (depth == 2 && parentKey.Contains("GiveMcvCrateAction"))
|
||||
if (node.Key == "Unit")
|
||||
node.Key = "Units";
|
||||
|
||||
if (depth == 2 && parentKey.Contains("GiveUnitCrateAction"))
|
||||
if (node.Key == "Unit")
|
||||
node.Key = "Units";
|
||||
}
|
||||
|
||||
// Power from Building was moved out into Power and ScalePowerWithHealth traits
|
||||
if (engineVersion < 20140823)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
var actorTraits = node.Value.Nodes;
|
||||
var building = actorTraits.FirstOrDefault(t => t.Key == "Building");
|
||||
if (building != null)
|
||||
{
|
||||
var buildingFields = building.Value.Nodes;
|
||||
var power = buildingFields.FirstOrDefault(n => n.Key == "Power");
|
||||
if (power != null)
|
||||
{
|
||||
buildingFields.Remove(power);
|
||||
|
||||
var powerFields = new List<MiniYamlNode> { new MiniYamlNode("Amount", power.Value) };
|
||||
actorTraits.Add(new MiniYamlNode("Power", new MiniYaml("", powerFields)));
|
||||
|
||||
if (FieldLoader.GetValue<int>("Power", power.Value.Value) > 0)
|
||||
actorTraits.Add(new MiniYamlNode("ScaleWithHealth", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140803)
|
||||
{
|
||||
// ContainsCrate was removed (use LeavesHusk instead)
|
||||
if (depth == 1 && node.Key == "ContainsCrate")
|
||||
{
|
||||
node.Key = "LeavesHusk";
|
||||
node.Value.Nodes.Add(new MiniYamlNode("HuskActor", new MiniYaml("crate")));
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140806)
|
||||
{
|
||||
// remove ConquestVictoryConditions when StrategicVictoryConditions is set
|
||||
if (depth == 0 && node.Key == "Player" && node.Value.Nodes.Any(n => n.Key == "StrategicVictoryConditions"))
|
||||
node.Value.Nodes.Add(new MiniYamlNode("-ConquestVictoryConditions", ""));
|
||||
|
||||
// the objectives panel trait and its properties have been renamed
|
||||
if (depth == 1 && node.Key == "ConquestObjectivesPanel")
|
||||
{
|
||||
node.Key = "ObjectivesPanel";
|
||||
node.Value.Nodes.RemoveAll(_ => true);
|
||||
node.Value.Nodes.Add(new MiniYamlNode("PanelName", new MiniYaml("SKIRMISH_STATS")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Veterancy was changed to use the upgrades system
|
||||
if (engineVersion < 20140807)
|
||||
{
|
||||
if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("GainsExperience")))
|
||||
node.Value.Nodes.Add(new MiniYamlNode("GainsStatUpgrades", new MiniYaml("")));
|
||||
|
||||
if (depth == 1 && node.Key == "-CloakCrateAction")
|
||||
node.Key = "-UnitUpgradeCrateAction@cloak";
|
||||
|
||||
if (depth == 1 && node.Key == "CloakCrateAction")
|
||||
{
|
||||
node.Key = "UnitUpgradeCrateAction@cloak";
|
||||
node.Value.Nodes.Add(new MiniYamlNode("Upgrades", new MiniYaml("cloak")));
|
||||
}
|
||||
|
||||
if (depth == 2 && node.Key == "RequiresCrate" && parentKey == "Cloak")
|
||||
{
|
||||
node.Key = "RequiresUpgrade";
|
||||
node.Value.Value = "cloak";
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiers were changed to integer percentages
|
||||
if (engineVersion < 20140812)
|
||||
{
|
||||
if (depth == 2 && node.Key == "ClosedDamageMultiplier" && parentKey == "AttackPopupTurreted")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && node.Key == "ArmorModifier" && parentKey == "GainsStatUpgrades")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && node.Key == "FullyLoadedSpeed" && parentKey == "Harvester")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && node.Key == "PanicSpeedModifier" && parentKey == "ScaredyCat")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && node.Key == "ProneSpeed" && parentKey == "TakeCover")
|
||||
{
|
||||
node.Key = "SpeedModifier";
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
}
|
||||
|
||||
if (depth == 2 && node.Key == "SpeedModifier" && parentKey == "GainsStatUpgrades")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
|
||||
if (depth == 2 && node.Key == "FirepowerModifier" && parentKey == "GainsStatUpgrades")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
}
|
||||
|
||||
// RemoveImmediately was replaced with RemoveOnConditions
|
||||
if (engineVersion < 20140821)
|
||||
{
|
||||
if (depth == 1)
|
||||
{
|
||||
if (node.Key == "RemoveImmediately")
|
||||
node.Key = "RemoveOnConditions";
|
||||
|
||||
if (node.Key == "-RemoveImmediately")
|
||||
node.Key = "-RemoveOnConditions";
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140823)
|
||||
{
|
||||
if (depth == 2 && node.Key == "ArmorUpgrade" && parentKey == "GainsStatUpgrades")
|
||||
node.Key = "DamageUpgrade";
|
||||
|
||||
if (depth == 2 && node.Key == "ArmorModifier" && parentKey == "GainsStatUpgrades")
|
||||
{
|
||||
node.Key = "DamageModifier";
|
||||
node.Value.Value = string.Join(", ", node.Value.Value.Split(',')
|
||||
.Select(s => ((int)(100 * 100 / float.Parse(s))).ToString()));
|
||||
}
|
||||
if (depth == 3 && parentKey == "Upgrades")
|
||||
node.Value.Value = node.Value.Value.Replace("armor", "damage");
|
||||
}
|
||||
|
||||
// RenderInfantryProne and RenderInfantryPanic was merged into RenderInfantry
|
||||
if (engineVersion < 20140824)
|
||||
{
|
||||
var renderInfantryRemoval = node.Value.Nodes.FirstOrDefault(n => n.Key == "-RenderInfantry");
|
||||
if (depth == 0 && renderInfantryRemoval != null && !node.Value.Nodes.Any(n => n.Key == "RenderDisguise"))
|
||||
node.Value.Nodes.Remove(renderInfantryRemoval);
|
||||
|
||||
if (depth == 1 && (node.Key == "RenderInfantryProne" || node.Key == "RenderInfantryPanic"))
|
||||
node.Key = "RenderInfantry";
|
||||
}
|
||||
|
||||
// InfDeath was renamed to DeathType
|
||||
if (engineVersion < 20140830)
|
||||
{
|
||||
|
||||
if (depth == 2 && parentKey.StartsWith("DeathSounds") && node.Key == "InfDeaths")
|
||||
node.Key = "DeathTypes";
|
||||
|
||||
if (depth == 2 && parentKey == "SpawnsViceroid" && node.Key == "InfDeath")
|
||||
node.Key = "DeathType";
|
||||
|
||||
if (depth == 2 && parentKey == "Explodes" && node.Key == "InfDeath")
|
||||
node.Key = "DeathType";
|
||||
}
|
||||
|
||||
// SellSounds from Building was moved into Sellable
|
||||
if (engineVersion < 20140904)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
var actorTraits = node.Value.Nodes;
|
||||
var building = actorTraits.FirstOrDefault(t => t.Key == "Building");
|
||||
if (building != null)
|
||||
{
|
||||
var buildingFields = building.Value.Nodes;
|
||||
var sellSounds = buildingFields.FirstOrDefault(n => n.Key == "SellSounds");
|
||||
if (sellSounds != null)
|
||||
{
|
||||
buildingFields.Remove(sellSounds);
|
||||
var sellable = actorTraits.FirstOrDefault(t => t.Key == "Sellable");
|
||||
if (sellable != null)
|
||||
sellable.Value.Nodes.Add(sellSounds);
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Warning: Adding Sellable trait to {0} in {1}".F(node.Key, node.Location.Filename));
|
||||
actorTraits.Add(new MiniYamlNode("Sellable", new MiniYaml("", new List<MiniYamlNode> { sellSounds })));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DuplicateUnitCrateAction was tidied up
|
||||
if (engineVersion < 20140912)
|
||||
{
|
||||
if (depth == 2 && node.Key == "MaxDuplicatesWorth" && parentKey == "DuplicateUnitCrateAction")
|
||||
node.Key = "MaxDuplicateValue";
|
||||
|
||||
if (depth == 2 && node.Key == "ValidDuplicateTypes" && parentKey == "DuplicateUnitCrateAction")
|
||||
node.Key = "ValidTargets";
|
||||
}
|
||||
|
||||
// Added WithDeathAnimation
|
||||
if (engineVersion < 20140913)
|
||||
{
|
||||
var spawnsCorpseRemoval = node.Value.Nodes.FirstOrDefault(n => n.Key == "SpawnsCorpse");
|
||||
|
||||
if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("RenderInfantry")) && spawnsCorpseRemoval == null)
|
||||
node.Value.Nodes.Add(new MiniYamlNode("WithDeathAnimation", new MiniYaml("")));
|
||||
|
||||
if (depth == 2 && node.Key == "SpawnsCorpse" && parentKey == "RenderInfantry")
|
||||
node.Value.Nodes.Remove(spawnsCorpseRemoval);
|
||||
|
||||
// CrushableInfantry renamed to Crushable
|
||||
if (depth == 1)
|
||||
{
|
||||
if (node.Key == "CrushableInfantry")
|
||||
node.Key = "Crushable";
|
||||
|
||||
if (node.Key == "-CrushableInfantry")
|
||||
node.Key = "-Crushable";
|
||||
}
|
||||
}
|
||||
|
||||
// Replaced Wall with Crushable + BlocksBullets
|
||||
if (engineVersion < 20140914)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
var actorTraits = node.Value.Nodes;
|
||||
var wall = actorTraits.FirstOrDefault(t => t.Key == "Wall");
|
||||
if (wall != null)
|
||||
node.Value.Nodes.Add(new MiniYamlNode("BlocksBullets", new MiniYaml("")));
|
||||
|
||||
var blocksBullets = actorTraits.FirstOrDefault(t => t.Key == "BlocksBullets");
|
||||
if (depth == 1 && node.Key == "Wall" && blocksBullets != null)
|
||||
node.Key = "Crushable";
|
||||
}
|
||||
}
|
||||
|
||||
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void UpgradeWeaponRules(int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
|
||||
{
|
||||
var parentKey = parent != null ? parent.Key.Split('@').First() : null;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Weapon definitions were converted to world coordinates
|
||||
if (engineVersion < 20131226)
|
||||
{
|
||||
if (depth == 1)
|
||||
{
|
||||
switch (node.Key)
|
||||
{
|
||||
case "Range":
|
||||
case "MinRange":
|
||||
ConvertFloatToRange(ref node.Value.Value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth == 2 && parentKey == "Projectile")
|
||||
{
|
||||
switch (node.Key)
|
||||
{
|
||||
case "Inaccuracy":
|
||||
ConvertPxToRange(ref node.Value.Value);
|
||||
break;
|
||||
case "Angle":
|
||||
ConvertAngle(ref node.Value.Value);
|
||||
break;
|
||||
case "Speed":
|
||||
{
|
||||
if (parent.Value.Value == "Missile")
|
||||
ConvertPxToRange(ref node.Value.Value, 1, 5);
|
||||
if (parent.Value.Value == "Bullet")
|
||||
ConvertPxToRange(ref node.Value.Value, 2, 5);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth == 2 && parentKey == "Warhead")
|
||||
{
|
||||
switch (node.Key)
|
||||
{
|
||||
case "Spread":
|
||||
ConvertPxToRange(ref node.Value.Value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140615)
|
||||
{
|
||||
if (depth == 2 && parentKey == "Warhead" && node.Key == "Ore")
|
||||
node.Key = "DestroyResources";
|
||||
}
|
||||
|
||||
if (engineVersion < 20140720)
|
||||
{
|
||||
// Split out the warheads to individual warhead types.
|
||||
if (depth == 0)
|
||||
{
|
||||
var validTargets = node.Value.Nodes.FirstOrDefault(n => n.Key == "ValidTargets"); // Weapon's ValidTargets need to be copied to the warheads, so find it
|
||||
var invalidTargets = node.Value.Nodes.FirstOrDefault(n => n.Key == "InvalidTargets"); // Weapon's InvalidTargets need to be copied to the warheads, so find it
|
||||
|
||||
var warheadCounter = 0;
|
||||
foreach(var curNode in node.Value.Nodes.ToArray())
|
||||
{
|
||||
if (curNode.Key.Contains("Warhead") && curNode.Value.Value == null)
|
||||
{
|
||||
var newNodes = new List<MiniYamlNode>();
|
||||
var oldNodeAtName = "";
|
||||
if (curNode.Key.Contains('@'))
|
||||
oldNodeAtName = "_" + curNode.Key.Split('@')[1];
|
||||
|
||||
// Per Cell Damage Model
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DamageModel") &&
|
||||
n.Value.Value.Contains("PerCell")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Size"); // New PerCell warhead allows 2 sizes, as opposed to 1 size
|
||||
if (temp != null)
|
||||
{
|
||||
var newValue = temp.Value.Value.Split(',').First();
|
||||
newYaml.Add(new MiniYamlNode("Size", newValue));
|
||||
}
|
||||
|
||||
var keywords = new List<string> { "Damage", "InfDeath", "PreventProne", "ProneModifier", "Delay" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp2 = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp2 != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp2.Value.Value));
|
||||
}
|
||||
|
||||
if (validTargets != null)
|
||||
newYaml.Add(validTargets);
|
||||
if (invalidTargets != null)
|
||||
newYaml.Add(invalidTargets);
|
||||
|
||||
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
|
||||
if (tempVersus != null)
|
||||
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "PerCellDamage", newYaml));
|
||||
}
|
||||
|
||||
// HealthPercentage damage model
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DamageModel") &&
|
||||
n.Value.Value.Contains("HealthPercentage")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Size"); // New HealthPercentage warhead allows 2 spreads, as opposed to 1 size
|
||||
if (temp != null)
|
||||
{
|
||||
var newValue = temp.Value.Value.Split(',').First() + "c0";
|
||||
newYaml.Add(new MiniYamlNode("Spread", newValue));
|
||||
}
|
||||
|
||||
var keywords = new List<string> { "Damage", "InfDeath", "PreventProne", "ProneModifier", "Delay" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp2 = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp2 != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp2.Value.Value));
|
||||
}
|
||||
|
||||
if (validTargets != null)
|
||||
newYaml.Add(validTargets);
|
||||
if (invalidTargets != null)
|
||||
newYaml.Add(invalidTargets);
|
||||
|
||||
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
|
||||
if (tempVersus != null)
|
||||
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "HealthPercentageDamage", newYaml));
|
||||
}
|
||||
|
||||
// SpreadDamage
|
||||
{ // Always occurs, since by definition all warheads were SpreadDamage warheads before
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "Spread", "Damage", "InfDeath", "PreventProne", "ProneModifier", "Delay" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
|
||||
}
|
||||
|
||||
if (validTargets != null)
|
||||
newYaml.Add(validTargets);
|
||||
if (invalidTargets != null)
|
||||
newYaml.Add(invalidTargets);
|
||||
|
||||
var tempVersus = curNode.Value.Nodes.FirstOrDefault(n => n.Key == "Versus");
|
||||
if (tempVersus != null)
|
||||
newYaml.Add(new MiniYamlNode("Versus", tempVersus.Value));
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Dam" + oldNodeAtName, "SpreadDamage", newYaml));
|
||||
}
|
||||
|
||||
// DestroyResource
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("DestroyResources") ||
|
||||
n.Key.Contains("Ore")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "Size", "Delay", "ValidTargets", "InvalidTargets" };
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
|
||||
}
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Res" + oldNodeAtName, "DestroyResource", newYaml));
|
||||
}
|
||||
|
||||
// CreateResource
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("AddsResourceType")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "AddsResourceType", "Size", "Delay", "ValidTargets", "InvalidTargets" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
|
||||
}
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Res" + oldNodeAtName, "CreateResource", newYaml));
|
||||
}
|
||||
|
||||
// LeaveSmudge
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("SmudgeType")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "SmudgeType", "Size", "Delay", "ValidTargets", "InvalidTargets" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
|
||||
}
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Smu" + oldNodeAtName, "LeaveSmudge", newYaml));
|
||||
}
|
||||
|
||||
// CreateEffect - Explosion
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("Explosion") ||
|
||||
n.Key.Contains("ImpactSound")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "Explosion", "ImpactSound", "Delay", "ValidTargets", "InvalidTargets", "ValidImpactTypes", "InvalidImpactTypes" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
newYaml.Add(new MiniYamlNode(keyword, temp.Value.Value));
|
||||
}
|
||||
newYaml.Add(new MiniYamlNode("InvalidImpactTypes", "Water"));
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Eff" + oldNodeAtName, "CreateEffect", newYaml));
|
||||
}
|
||||
|
||||
// CreateEffect - Water Explosion
|
||||
if (curNode.Value.Nodes.Where(n => n.Key.Contains("WaterExplosion") ||
|
||||
n.Key.Contains("WaterImpactSound")).Any())
|
||||
{
|
||||
warheadCounter++;
|
||||
|
||||
var newYaml = new List<MiniYamlNode>();
|
||||
|
||||
var keywords = new List<string> { "WaterExplosion", "WaterImpactSound", "Delay", "ValidTargets", "InvalidTargets", "ValidImpactTypes", "InvalidImpactTypes" };
|
||||
|
||||
foreach(var keyword in keywords)
|
||||
{
|
||||
var temp = curNode.Value.Nodes.FirstOrDefault(n => n.Key == keyword);
|
||||
if (temp != null)
|
||||
{
|
||||
if (temp.Key == "WaterExplosion")
|
||||
temp.Key = "Explosion";
|
||||
if (temp.Key == "WaterImpactSound")
|
||||
temp.Key = "ImpactSound";
|
||||
newYaml.Add(new MiniYamlNode(temp.Key, temp.Value.Value));
|
||||
}
|
||||
}
|
||||
newYaml.Add(new MiniYamlNode("ValidImpactTypes", "Water"));
|
||||
|
||||
newNodes.Add(new MiniYamlNode("Warhead@" + warheadCounter.ToString() + "Eff" + oldNodeAtName, "CreateEffect", newYaml));
|
||||
}
|
||||
node.Value.Nodes.InsertRange(node.Value.Nodes.IndexOf(curNode),newNodes);
|
||||
node.Value.Nodes.Remove(curNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140818)
|
||||
{
|
||||
if (depth == 1)
|
||||
{
|
||||
if (node.Key == "ROF")
|
||||
node.Key = "ReloadDelay";
|
||||
}
|
||||
}
|
||||
|
||||
if (engineVersion < 20140821)
|
||||
{
|
||||
// Converted versus definitions to integers
|
||||
if (depth == 3 && parentKey == "Versus")
|
||||
ConvertFloatArrayToPercentArray(ref node.Value.Value);
|
||||
}
|
||||
|
||||
if (engineVersion < 20140830)
|
||||
{
|
||||
if (depth == 2)
|
||||
{
|
||||
if (node.Key == "InfDeath")
|
||||
node.Key = "DeathType";
|
||||
}
|
||||
}
|
||||
|
||||
UpgradeWeaponRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (engineVersion < 20140104)
|
||||
{
|
||||
if (depth == 2 && parentKey == "TerrainType" && node.Key.Split('@').First() == "Type")
|
||||
addNodes.Add(new MiniYamlNode("TargetTypes", node.Value.Value == "Water" ? "Water" : "Ground"));
|
||||
}
|
||||
UpgradeTileset(engineVersion, ref node.Value.Nodes, node, depth + 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user