Added IUtilityCommand and move default commands into Mods.Common.

This commit is contained in:
Alexander Fast
2014-08-20 16:15:18 +02:00
committed by Paul Chote
parent ff7b3541f4
commit 08890b0994
25 changed files with 982 additions and 674 deletions

View File

@@ -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.");
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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);
}
}
}

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,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);

View File

@@ -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);
}
}
}
}