Compare commits

...

9 Commits

Author SHA1 Message Date
Adrian d2960720bd Update command line app
* bmp2map: Assign unit IDs
* bmp2map: Check bitmap dimensions
* bmp2map, convert: Change random tree frame code
* convert: Insert values from src to ref, instead of vice-versa (more robust)
* convert: Move buildings is better
* convert: Small API changes
* convert/dumpunits: Correct rounding for tile position underneath a unit
* dumpunits: List player number for each unit
2016-04-26 15:17:34 +02:00
Adrian e56d02ae18 Rename Resources2 2016-04-26 15:16:34 +02:00
Adrian e1af13f4a9 Rename ConvertCommandHelper 2016-04-26 00:56:26 +02:00
Adrian e580e0d8ed Add reading of PlayerSettings2
Additionally:

* Reorder fields in Scenario for more convenient serialization order in JSON
* Use lists instead of arrays
2016-04-25 17:13:59 +02:00
Adrian 11f9804f24 Fix scenario compression commands 2016-04-25 17:11:49 +02:00
Adrian 01a1739496 Fix scenario compression 2016-04-25 17:11:21 +02:00
Adrian 5a19ea7103 Small fix
* Use specific bool reading/writing methods
* Fix reading of ResourceCopies
2016-04-25 17:09:12 +02:00
Adrian ad4bd15136 Refactor commands for new CmdApp interface 2016-04-23 02:42:51 +02:00
Adrian 621336afec Small syntactic changes 2016-04-23 02:42:14 +02:00
24 changed files with 411 additions and 415 deletions

View File

@ -32,20 +32,19 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="src\Program.cs" />
<Compile Include="src\BaseCommand.cs" />
<Compile Include="src\ExtractCommand.cs" />
<Compile Include="src\ToJsonCommand.cs" />
<Compile Include="src\FromJsonCommand.cs" />
<Compile Include="src\CompressCommand.cs" />
<Compile Include="src\DecompressCommand.cs" />
<Compile Include="src\DumpunitsCommand.cs" />
<Compile Include="src\CopymapConverter.cs" />
<Compile Include="src\ConvertCommand.cs" />
<Compile Include="src\AddBitmapCommand.cs" />
<Compile Include="src\DelBitmapCommand.cs" />
<Compile Include="src\PackCommand.cs" />
<Compile Include="src\Bmp2MapCommand.cs" />
<Compile Include="src\BitmapHelpers.cs" />
<Compile Include="src\ConvertCommandHelper.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@ -66,12 +65,11 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Web.Extensions" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@ -1,13 +1,12 @@
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class AddBitmapCommand : BaseCommand
public class AddBitmapCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
@ -23,20 +22,11 @@ namespace AdrianKousz.GenieEngine
GenieFile.Serialize(scn, output);
}
}
override public int GetArgumentCount()
public AddBitmapCommand()
{
return 3;
}
override public string GetDescription()
{
return "Add a pregame bitmap";
}
override public string GetHelp()
{
return "Add a pregame bitmap to a scenario."
MinArgumentCount = 3;
Description = "Add a pregame bitmap";
Help = "Add a pregame bitmap to a scenario."
+ "\nThe bitmap has to be indexed, otherwise, the game will probably crash."
+ "\nAdditionally, a game-specific palette will be used to display the image."
+ "\n"

View File

@ -1,36 +0,0 @@
using System;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class BaseCommand : ICommand<string[]>
{
protected string[] args;
virtual public string GetDescription()
{
return "(Empty description)";
}
virtual public string GetHelp()
{
return "(Empty help text)";
}
virtual public int GetArgumentCount()
{
return 1;
}
virtual public void SetArguments(string[] args)
{
this.args = args;
}
virtual public void Run()
{
foreach (var str in args)
Console.WriteLine(str);
}
}
}

View File

@ -9,25 +9,24 @@ namespace AdrianKousz.GenieEngine
{
public static class BitmapHelpers
{
public static unsafe void FillMap(Scenario scn, Bitmap bmp, Bitmap bmp2)
public static unsafe void FillMap(Scenario scn, Bitmap bmp1, Bitmap bmp2)
{
using (var ubmp = new UnsafeBitmap(bmp))
using (var ubmp1 = new UnsafeBitmap(bmp1))
using (var ubmp2 = new UnsafeBitmap(bmp2))
{
var factory = new ScnDefaultFactory();
Check(ubmp);
Check(ubmp2);
EnsureValid(ubmp1, ubmp2);
var ptr = ubmp.Scan0;
var last = ptr + ubmp.Stride * ubmp.Height;
var ptr1 = ubmp1.Scan0;
var last1 = ptr1 + ubmp1.Stride * ubmp1.Height;
var ptr2 = ubmp2.Scan0;
var last2 = ptr2 + ubmp2.Stride * ubmp2.Height;
var map = factory.MakeMap();
map.SizeX = ubmp.Width;
map.SizeY = ubmp.Height;
map.SizeX = ubmp1.Width;
map.SizeY = ubmp1.Height;
map.LinearTiles = new Scenario.ScnMap.Tile[map.SizeX * map.SizeY];
var gaiaunits = new List<Scenario.ScnUnit>();
@ -41,26 +40,37 @@ namespace AdrianKousz.GenieEngine
treemapping[20] = 349;
var i = 0;
while (ptr < last && ptr2 < last2) {
var treeid = treemapping[*ptr];
var id = 0;
while (ptr1 < last1 && ptr2 < last2) {
var treeid = treemapping[*ptr1];
if (treeid != 0) {
var unit = factory.MakeUnit();
var posy = i / map.SizeX;
var posx = i - (posy * map.SizeX);
unit.Id = id++;
unit.UnitId = treeid;
unit.PosX = posx + 0.5f;
unit.PosY = posy + 0.5f;
unit.Rotation = (short)Util.Math.Rand(0, 13);
unit.Rotation = unit.InitialFrame = (short)Util.Math.Rand(0, 14);
gaiaunits.Add(unit);
}
map.LinearTiles[i++] = new Scenario.ScnMap.Tile(*ptr++, (byte)(*ptr2++ / 37), 0);
map.LinearTiles[i++] = new Scenario.ScnMap.Tile(*ptr1++, (byte)(*ptr2++ / 40), 0);
}
scn.NextId = id;
scn.Map = map;
scn.Units[0] = gaiaunits;
}
}
private static void EnsureValid(UnsafeBitmap ubmp1, UnsafeBitmap ubmp2)
{
if (ubmp1.Width != ubmp2.Width || ubmp1.Height != ubmp2.Height)
throw new ArgumentException("Bitmaps have unequal dimensions");
Check(ubmp1);
Check(ubmp2);
}
private static void Check(UnsafeBitmap ubmp)
{
if (ubmp.PixelFormat != PixelFormat.Format8bppIndexed)

View File

@ -1,13 +1,12 @@
using System;
using System.IO;
using System.IO;
using System.Drawing;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class Bmp2MapCommand : BaseCommand
public class Bmp2MapCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var srcstream = File.OpenRead(args[0]))
using (var outstream = File.OpenWrite(args[1]))
@ -23,24 +22,16 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public Bmp2MapCommand()
{
return 4;
}
override public string GetDescription()
{
return "Generate a scenario map based on bitmaps";
}
override public string GetHelp()
{
return "Generate a scenario map based on bitmaps with automatic forest generation."
MinArgumentCount = 4;
Description = "Generate a scenario map based on bitmaps";
Help = "Generate a scenario map based on bitmaps with automatic forest generation."
+ "\n"
+ "\nTwo bitmaps need to be specified: One terrain map and one elevation map."
+ "\nBoth bitmaps need to be indexed or grayscale."
+ "\nThe index/gray values of the first bitmap are translated directly to a terrain ID."
+ "\nThe index/gray values of the second bitmap are divided by 37 to generate elevations 0-6."
+ "\nThe index/gray values of the second bitmap are divided by 40 to generate elevations 0-6."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output scenario> <terrain bitmap> <elevation bitmap>"

View File

@ -1,31 +1,24 @@
using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class CompressCommand : BaseCommand
public class CompressCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
{
GenieFile.ScenarioCompression(input, output, false);
GenieFile.ScenarioCompress(input, output);
}
}
override public int GetArgumentCount()
public CompressCommand()
{
return 2;
}
override public string GetDescription()
{
return "Recompress a decompressed scenario file";
}
override public string GetHelp()
{
return "Recompress a previously decompressed scenario file."
MinArgumentCount = 2;
Description = "Recompress a decompressed scenario file";
Help = "Recompress a previously decompressed scenario file."
+ "\n"
+ "\nParameters:"
+ "\n<input binary file> <output scenario>"

View File

@ -1,12 +1,11 @@
using System;
using System.IO;
using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class ConvertCommand : BaseCommand
public class ConvertCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var srcstream = File.OpenRead(args[0]))
using (var outstream = File.OpenWrite(args[1]))
@ -15,37 +14,46 @@ namespace AdrianKousz.GenieEngine
var refscn = GenieFile.Deserialize<Scenario>(refstream);
var srcscn = GenieFile.Deserialize<Scenario>(srcstream);
srcscn.VersionString = refscn.VersionString;
srcscn.VersionNumber = refscn.VersionNumber;
srcscn.RawIndividualVictory = refscn.RawIndividualVictory;
srcscn.RawDisables = refscn.RawDisables;
srcscn.RawRemaining = refscn.RawRemaining;
refscn.Filename = srcscn.Filename;
refscn.InstructionsUncompressed = srcscn.InstructionsUncompressed;
refscn.NextId = srcscn.NextId;
refscn.PlayerCount = srcscn.PlayerCount;
srcscn.PlayerSettings.ForEach(x => {
x.FilenameAI = x.FilenameCTY = x.FilenamePER = "RandomGame";
x.ScriptAI = x.ScriptCTY = x.ScriptPER = "";
refscn.PlayerSettings = srcscn.PlayerSettings;
refscn.Resources2 = srcscn.Resources2;
refscn.Messages = srcscn.Messages;
refscn.Cinematics = srcscn.Cinematics;
refscn.GlobalVictory = srcscn.GlobalVictory;
refscn.Map = srcscn.Map;
refscn.Units = srcscn.Units;
refscn.PlayerSettings.ForEach(x => {
x.NameStringID = -1;
x.FilenameAI = "RandomGame"; x.FilenameCTY = ""; x.FilenamePER = "";
x.ScriptAI = ""; x.ScriptCTY = ""; x.ScriptPER = "";
});
var converter = new CopymapConverter();
converter.Convert(srcscn);
Enumerables.ForBoth(refscn.PlayerSettings2, srcscn.PlayerSettings2, (r, s) => {
r.Name = s.Name;
r.CameraX = s.CameraX;
r.CameraY = s.CameraY;
r.AlliedVictory = s.AlliedVictory;
r.Stances = s.Stances;
});
GenieFile.Serialize(srcscn, outstream);
var converter = new ConvertCommandHelper();
converter.Convert(refscn);
GenieFile.Serialize(refscn, outstream);
}
}
override public int GetArgumentCount()
public ConvertCommand()
{
return 3;
}
override public string GetDescription()
{
return "Convert AOE1 scenario";
}
override public string GetHelp()
{
return "Convert AOE1 scenario to AOE2"
MinArgumentCount = 3;
Description = "Convert AOE1 scenario";
Help = "Convert AOE1 scenario to AOE2"
+ "\n"
+ "\nThe converter needs an existing AOE2 scenario to start with."
+ "\n"

View File

@ -4,44 +4,20 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class CopymapConverter
public class ConvertCommandHelper
{
public void Convert(Scenario scn)
{
scn.Units = ChangeUnits_AOE1_AOE2(scn.Units);
MoveUnits_AOE1_AOE2(scn.Units);
ChangeTiles_AOE1_AOE2(scn.Units, scn.Map);
RandomizeTrees_AOE2(scn.Units);
}
/**
* Some buildings are not the same size.
* They need to be moved by half a unit.
*/
private void MoveUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units)
{
var mapping = new int[] {
// Towers
79, 199, 69, 278,
// Other Buildings
109, 68, 103,
};
units.ForEach(list => {
list.ForEach(unit => {
mapping.ForEach(buildingid => {
if (unit.UnitId == buildingid) {
unit.PosX -= 0.5f;
unit.PosY -= 0.5f;
}
});
});
});
ChangeUnits_AOE1_AOE2(scn);
MoveUnits_AOE1_AOE2(scn);
ChangeTiles_AOE1_AOE2(scn);
RandomizeTrees_AOE2(scn);
}
/**
* Simple mapping of unit IDs
*/
private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units)
private void ChangeUnits_AOE1_AOE2(Scenario scn)
{
var mapping = new short[] {
// Resources
@ -141,26 +117,43 @@ namespace AdrianKousz.GenieEngine
129, 129,
}.ToDictionary();
var result = units.Map(list => {
return list
scn.Units = scn.Units.Map(list => {
return (IList<Scenario.ScnUnit>)list
.Filter(unit => mapping.ContainsKey(unit.UnitId))
.ForEach(unit => {
.Map(unit => {
unit.UnitId = mapping[unit.UnitId];
return unit;
})
.ToList();
}).ToList();
}).ToArray();
}
return result;
/**
* Some buildings are not the same size.
* They need to be moved by half a unit.
*/
private void MoveUnits_AOE1_AOE2(Scenario scn)
{
var minus = new int[] { 79, 68, 562, };
var plus = new int[] { 109, };
scn.Units.ForEach(list => {
list.ForEach(unit => {
minus.ForEach(buildingid => { if (unit.UnitId != buildingid) return; unit.PosX -= 0.5f; unit.PosY -= 0.5f; });
plus.ForEach(buildingid => { if (unit.UnitId != buildingid) return; unit.PosX += 0.5f; unit.PosY += 0.5f; });
});
});
}
/**
* Farms need to be converted including the underlying terrain.
*/
private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] units, Scenario.ScnMap map)
private void ChangeTiles_AOE1_AOE2(Scenario scn)
{
units.ForEach(list => {
var map = scn.Map;
scn.Units.ForEach(list => {
list.ForEach(unit => {
var posx = (int)(unit.PosX - 0.5);
var posy = (int)(unit.PosY - 0.5);
var posx = (int)unit.PosX;
var posy = (int)unit.PosY;
var linearpos = posy * map.SizeX + posx;
if (false) {
// switch
@ -191,16 +184,16 @@ namespace AdrianKousz.GenieEngine
* Forests would contain the same tree graphic
* without this function
*/
private void RandomizeTrees_AOE2(IList<Scenario.ScnUnit>[] units)
private void RandomizeTrees_AOE2(Scenario scn)
{
var treeids = new int[] {
411, 351, 414, 350, 348, 349,
};
units.ForEach(list => {
scn.Units.ForEach(list => {
list.ForEach(unit => {
treeids.ForEach(treeid => {
if (unit.UnitId == treeid) {
unit.Rotation = (short)Util.Math.Rand(0, 13); // Whats the maximum?
unit.Rotation = unit.InitialFrame = (short)Util.Math.Rand(0, 14);
}
});
});

View File

@ -1,31 +1,24 @@
using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class DecompressCommand : BaseCommand
public class DecompressCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
{
GenieFile.ScenarioCompression(input, output, true);
GenieFile.ScenarioDecompress(input, output);
}
}
override public int GetArgumentCount()
public DecompressCommand()
{
return 2;
}
override public string GetDescription()
{
return "Decompress a scenario file";
}
override public string GetHelp()
{
return "Decompress a scenario file for hex editing."
MinArgumentCount = 2;
Description = "Decompress a scenario file";
Help = "Decompress a scenario file for hex editing."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output binary file>"

View File

@ -1,12 +1,11 @@
using System.IO;
using System.Drawing;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class DelBitmapCommand : BaseCommand
public class DelBitmapCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
@ -21,19 +20,11 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public DelBitmapCommand()
{
return 2;
}
override public string GetDescription()
{
return "Remove the pregame bitmap";
}
override public string GetHelp()
{
return "Remove the pregame bitmap from a scenario."
MinArgumentCount = 2;
Description = "Remove the pregame bitmap";
Help = "Remove the pregame bitmap from a scenario."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output scenario>"

View File

@ -1,13 +1,12 @@
using System;
using System.IO;
using System.Collections.Generic;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class DumpunitsCommand : BaseCommand
public class DumpunitsCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
Scenario scn;
using (var input = File.OpenRead(args[0]))
@ -15,29 +14,24 @@ namespace AdrianKousz.GenieEngine
scn = GenieFile.Deserialize<Scenario>(input);
}
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position");
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3,-8} {4}", "Player", "ID", "UnitID", "TileID", "Position");
Console.WriteLine();
var i = 0;
scn.Units.ForEach(list => {
Console.WriteLine("Units of player {0}:", i++);
list.ForEach(unit => {
var tilex = (int)(unit.PosX - 0.5);
var tiley = (int)(unit.PosY - 0.5);
var tilex = (int)unit.PosX;
var tiley = (int)unit.PosY;
var tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex];
Console.WriteLine("{0,-8} {1,-8} {2,-8} ({3,6:0.##},{4,6:0.##})", unit.Id, unit.UnitId, tile.Id, unit.PosX, unit.PosY);
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3,-8} ({4,6:0.##},{5,6:0.##})", i, unit.Id, unit.UnitId, tile.Id, unit.PosX, unit.PosY);
});
Console.WriteLine();
i++;
});
}
override public int GetArgumentCount()
public DumpunitsCommand()
{
return 1;
}
override public string GetDescription()
{
return "Dump units and map tiles below";
MinArgumentCount = 1;
Description = "Dump units and underlying map tiles";
}
}
}

View File

@ -4,9 +4,9 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class ExtractCommand : BaseCommand
public class ExtractCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var file = File.OpenRead(args[0]))
{
@ -22,14 +22,10 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public ExtractCommand()
{
return 1;
}
override public string GetDescription()
{
return "Extract files from campaign file";
MinArgumentCount = 1;
Description = "Extract files from campaign file";
}
}
}

View File

@ -4,9 +4,9 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class FromJsonCommand : BaseCommand
public class FromJsonCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var reader = input.GetReader())
@ -18,19 +18,11 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public FromJsonCommand()
{
return 2;
}
override public string GetDescription()
{
return "Convert a JSON file to a scenario";
}
override public string GetHelp()
{
return "Read a Scenario structure formatted as JSON and save it as a Scenario."
MinArgumentCount = 2;
Description = "Convert a JSON file to a scenario";
Help = "Read a Scenario structure formatted as JSON and save it as a Scenario."
+ "\n"
+ "\nParameters:"
+ "\n<input json> <output scenario>"

View File

@ -4,9 +4,9 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class PackCommand : BaseCommand
public class PackCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
var files = new List<Campaign.CpnFile>();
for (var i = 2; i < args.Length; i++) {
@ -33,14 +33,10 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public PackCommand()
{
return 3;
}
override public string GetDescription()
{
return "Pack files into a campaign file";
MinArgumentCount = 3;
Description = "Pack files into a campaign file";
}
}
}

View File

@ -1,51 +1,39 @@
using AdrianKousz.Util;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Windows.Forms;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class Program : IApp<string, string[]>
public class Program
{
public static void Main(string[] args)
{
System.Threading.Thread.CurrentThread.Name = "main";
Log.SetUncaughtExceptionHandler();
if (System.Diagnostics.Debugger.IsAttached) {
debug();
return;
new CmdApp.Builder()
.Header("Scenario Editor")
.NoArg(new MessageBoxCommand())
.Add("convert", new ConvertCommand())
.Add("bmp2map", new Bmp2MapCommand())
.Add("addbmp", new AddBitmapCommand())
.Add("delbmp", new DelBitmapCommand())
.Add("compress", new CompressCommand())
.Add("decompress", new DecompressCommand())
.Add("tojson", new ToJsonCommand())
.Add("fromjson", new FromJsonCommand())
.Add("dumpunits", new DumpunitsCommand())
.Add("extract", new ExtractCommand())
.Add("pack", new PackCommand())
.Build()
.Run(args);
}
new CmdApp(new Program(), args).Run();
}
public string GetHeader()
private class MessageBoxCommand : CmdApp.Command
{
return "Scenario Editor";
}
public IDictionary<string, ICommand<string[]>> GetCommands()
public override void Run(string[] args)
{
var result = new Dictionary<string, ICommand<string[]>>();
result.Add("convert", new ConvertCommand());
result.Add("bmp2map", new Bmp2MapCommand());
result.Add("addbmp", new AddBitmapCommand());
result.Add("delbmp", new DelBitmapCommand());
result.Add("compress", new CompressCommand());
result.Add("decompress", new DecompressCommand());
result.Add("tojson", new ToJsonCommand());
result.Add("fromjson", new FromJsonCommand());
result.Add("dumpunits", new DumpunitsCommand());
result.Add("extract", new ExtractCommand());
result.Add("pack", new PackCommand());
return result;
}
private static void debug()
{
var fn = "";
using (var istream = System.IO.File.OpenRead(fn)) {
var scn = GenieFile.Deserialize<Scenario>(istream);
System.Diagnostics.Debugger.Break(); // Great to inspect scn
MessageBox.Show("Start the program on the command line like \n> GenieEdit.exe help");
}
}
}

View File

@ -4,9 +4,9 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class ToJsonCommand : BaseCommand
public class ToJsonCommand : CmdApp.Command
{
override public void Run()
override public void Run(string[] args)
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
@ -18,19 +18,11 @@ namespace AdrianKousz.GenieEngine
}
}
override public int GetArgumentCount()
public ToJsonCommand()
{
return 2;
}
override public string GetDescription()
{
return "Convert a scenario to a JSON file";
}
override public string GetHelp()
{
return "Save a Scenario structure formatted as JSON."
MinArgumentCount = 2;
Description = "Convert a scenario to a JSON file";
Help = "Save a Scenario structure formatted as JSON."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output json>"

View File

@ -31,14 +31,12 @@ namespace AdrianKousz.GenieEngine
number = reader.ReadInt32();
result.Files = new PositionInfo()
.CreateList()
.Fill(number, () => {
return new PositionInfo {
.CreateArray(number)
.Fill(() => new PositionInfo {
Size = reader.ReadInt32(),
Offset = reader.ReadInt32(),
Name = reader.ReadZString(255),
Filename = reader.ReadZString(257),
};
})
.Map(x => {
var file = new Campaign.CpnFile();
@ -48,7 +46,7 @@ namespace AdrianKousz.GenieEngine
file.Filename = x.Filename;
return file;
})
.ToArray();
.ToList();
return result;
}

View File

@ -101,13 +101,29 @@ namespace AdrianKousz.GenieEngine
return new ExtendedBinaryWriter(new NonDisposingStreamWrapper(stream), Scenario.Encoding);
}
public static void ScenarioCompression(Stream input, Stream output, bool decompress)
public static void ScenarioDecompress(Stream input, Stream output)
{
var mode = decompress ? CompressionMode.Decompress : CompressionMode.Compress;
CopyHeader(input, output);
byte[] buffer;
using (var comp = new DeflateStream(input, CompressionMode.Decompress))
{
comp.CopyTo(output);
}
}
buffer = input.ReadBytes(8);
public static void ScenarioCompress(Stream input, Stream output)
{
CopyHeader(input, output);
using (var comp = new DeflateStream(output, CompressionMode.Compress))
{
input.CopyTo(comp);
}
}
private static void CopyHeader(Stream input, Stream output)
{
var buffer = input.ReadBytes(8);
output.Write(buffer);
var headerLength = buffer[4]
@ -118,11 +134,6 @@ namespace AdrianKousz.GenieEngine
buffer = input.ReadBytes(headerLength);
output.Write(buffer);
using (var comp = new DeflateStream(input, mode))
{
comp.CopyTo(output);
}
}
#endregion

View File

@ -4,10 +4,11 @@
{
Scenario MakeScenario();
Scenario.ScnPlayerSettings MakePlayerSettings();
Scenario.ScnResource2 MakeResources2();
Scenario.ScnPlayerSettings2 MakePlayerSettings2();
Scenario.ScnMessages MakeMessages();
Scenario.ScnCinematics MakeCinematics();
Scenario.ScnGlobalVictory MakeGlobalVictory();
Scenario.ScnResourceCopy MakeResourceCopy();
Scenario.ScnMap MakeMap();
Scenario.ScnUnit MakeUnit();
}

View File

@ -22,30 +22,30 @@ namespace AdrianKousz.GenieEngine
public String Filename;
public DateTime Timestamp;
public String InstructionsUncompressed;
public Int32 NextId;
public Int32 PlayerCount;
public IList<ScnPlayerSettings> PlayerSettings;
public IList<ScnResource2> Resources2;
public IList<ScnPlayerSettings2> PlayerSettings2;
public ScnMessages Messages;
public ScnCinematics Cinematics;
public ScnGlobalVictory GlobalVictory;
public ScnMap Map;
public IList<IList<ScnUnit>> Units;
public ScnPlayerSettings[] PlayerSettings;
public ScnResourceCopy[] ResourceCopies;
public IList<ScnUnit>[] Units;
// NOTE: Raw byte arrays that do not have a parser (yet?)
public Byte[] RawIndividualVictory;
public Byte[] RawDisables;
public Byte[] RawRemaining;
// NOTE: Unknown values
public Int32 Unknown1;
public Int32 Unknown2;
public Int32 Unknown3;
public Byte Unknown4;
public Single Unknown5;
public Byte[] RawIndividualVictory;
public Byte[] RawDisables;
public Byte[] RawRemaining;
public class ScnPlayerSettings
{
public const Int32 ExpectedUnknownInfo = 4;
@ -55,16 +55,10 @@ namespace AdrianKousz.GenieEngine
public Int32 NameStringID;
public Boolean Active;
public Boolean Human;
public Int32 Civ;
public Int32 UnknownInfo;
public String FilenameAI;
public String FilenameCTY;
public String FilenamePER;
public String ScriptAI;
public String ScriptCTY;
public String ScriptPER;
public Byte AIType;
public Int32 Civ;
public Int32 StartingAge;
public Int32 UnknownInfo;
public Int32 ResourceWood;
public Int32 ResourceFood;
@ -73,9 +67,51 @@ namespace AdrianKousz.GenieEngine
public Int32 ResourceOre;
public Int32 UnknownResource;
public Int32[] Diplomacy;
public Boolean AlliedVictory;
public Int32 StartingAge;
public Int32[] Diplomacy;
public String FilenameAI;
public String FilenameCTY;
public String FilenamePER;
public String ScriptAI;
public String ScriptCTY;
public String ScriptPER;
}
public class ScnResource2
{
public const Single ExpectedUnknown = 0;
public Single Wood;
public Single Food;
public Single Gold;
public Single Stone;
public Single Ore;
public Single Unknown;
public Single PopulationLimit;
}
public class ScnPlayerSettings2
{
public String Name;
public Single CameraX;
public Single CameraY;
public Int16 UnknownX;
public Int16 UnknownY;
public Int32 Color;
public Byte AlliedVictory;
public Stance[] Stances;
public Single Unknown1;
public Int32 Unknown2;
public Byte[] Unknown;
public class Stance
{
public Byte Stance1;
public Int32 Stance2;
}
}
public class ScnMessages
@ -127,19 +163,6 @@ namespace AdrianKousz.GenieEngine
public Int32 Unknown;
}
public class ScnResourceCopy
{
public const Single ExpectedUnknownResource = 0;
public Single ResourceGold;
public Single ResourceWood;
public Single ResourceFood;
public Single ResourceStone;
public Single ResourceOre;
public Single UnknownResource;
public Single PopulationLimit;
}
public class ScnMap
{
public Int32 CameraX;

View File

@ -32,13 +32,10 @@ namespace AdrianKousz.GenieEngine
result.Cinematics = MakeCinematics();
result.GlobalVictory = MakeGlobalVictory();
result.Map = MakeMap();
result.ResourceCopies = new Scenario.ScnResourceCopy[Scenario.NumPlayerSections - 1]
.Fill(MakeResourceCopy);
result.PlayerSettings = new Scenario.ScnPlayerSettings[Scenario.NumPlayers]
.Fill(MakePlayerSettings);
result.Units = new List<Scenario.ScnUnit>[Scenario.NumPlayerSections]
.Fill();
result.PlayerSettings = result.PlayerSettings.NewList().Fill(Scenario.NumPlayers, MakePlayerSettings);
result.Resources2 = result.Resources2.NewList().Fill(Scenario.NumPlayerSections - 1, MakeResources2);
result.PlayerSettings2 = result.PlayerSettings2.NewList().Fill(Scenario.NumPlayerSections - 1, MakePlayerSettings2);
result.Units = MakeUnitList(Scenario.NumPlayerSections);
}
return result;
@ -63,6 +60,25 @@ namespace AdrianKousz.GenieEngine
return result;
}
public Scenario.ScnResource2 MakeResources2()
{
// AOE2 standard
var result = new Scenario.ScnResource2();
result.Unknown = Scenario.ScnResource2.ExpectedUnknown;
result.Food = 200;
result.Wood = 200;
result.Gold = 100;
result.Stone = 200;
result.PopulationLimit = 75;
return result;
}
public Scenario.ScnPlayerSettings2 MakePlayerSettings2()
{
var result = new Scenario.ScnPlayerSettings2();
return result;
}
public Scenario.ScnMessages MakeMessages()
{
var result = new Scenario.ScnMessages();
@ -101,19 +117,6 @@ namespace AdrianKousz.GenieEngine
return result;
}
public Scenario.ScnResourceCopy MakeResourceCopy()
{
// AOE2 standard
var result = new Scenario.ScnResourceCopy();
result.UnknownResource = Scenario.ScnResourceCopy.ExpectedUnknownResource;
result.ResourceFood = 200;
result.ResourceWood = 200;
result.ResourceGold = 100;
result.ResourceStone = 200;
result.PopulationLimit = 75;
return result;
}
public Scenario.ScnMap MakeMap()
{
var result = new Scenario.ScnMap();
@ -135,6 +138,13 @@ namespace AdrianKousz.GenieEngine
result.GarrisonnedInId = -1;
return result;
}
public static IList<IList<Scenario.ScnUnit>> MakeUnitList(int count = Scenario.NumPlayerSections)
{
IList<IList<Scenario.ScnUnit>> result = new List<IList<Scenario.ScnUnit>>()
.Fill(count, () => (IList<Scenario.ScnUnit>)new List<Scenario.ScnUnit>());
return result;
}
}
}

View File

@ -33,7 +33,7 @@ namespace AdrianKousz.GenieEngine
result.VersionString = reader.ReadString(4);
reader.ReadInt32(); // Header Length
result.Unknown1 = reader.ReadInt32();
result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32());
result.Timestamp = reader.ReadInt32().ToDateTime();
result.InstructionsUncompressed = reader.ReadZStringInt32();
result.Unknown2 = reader.ReadInt32();
result.PlayerCount = reader.ReadInt32();
@ -49,8 +49,7 @@ namespace AdrianKousz.GenieEngine
result.VersionNumber = reader.ReadSingle();
Version = result.VersionNumber;
result.PlayerSettings = new Scenario.ScnPlayerSettings[Scenario.NumPlayers]
.Fill(factory.MakePlayerSettings);
result.PlayerSettings = result.PlayerSettings.NewList().Fill(Scenario.NumPlayers, factory.MakePlayerSettings);
// PlayerSettings 1
@ -65,8 +64,8 @@ namespace AdrianKousz.GenieEngine
}
result.PlayerSettings.ForEach(x => {
x.Active = reader.ReadBoolean();
x.Human = reader.ReadBoolean();
x.Active = reader.ReadBooleanInt32();
x.Human = reader.ReadBooleanInt32();
x.Civ = reader.ReadInt32();
x.UnknownInfo = reader.ReadInt32();
});
@ -110,7 +109,7 @@ namespace AdrianKousz.GenieEngine
result.Cinematics.FilenameLoss = reader.ReadStringInt16();
result.Cinematics.FilenameBitmap = reader.ReadStringInt16();
var hasBitmap = reader.ReadBoolean();
var hasBitmap = reader.ReadBooleanInt32();
result.Cinematics.BitmapWidth = reader.ReadInt32();
result.Cinematics.BitmapHeight = reader.ReadInt32();
@ -166,13 +165,13 @@ namespace AdrianKousz.GenieEngine
result.GlobalVictory = factory.MakeGlobalVictory();
result.GlobalVictory.RequireConquest = reader.ReadBoolean();
result.GlobalVictory.RequireConquest = reader.ReadBooleanInt32();
result.GlobalVictory.Ruins = reader.ReadInt32();
result.GlobalVictory.Artifacts = reader.ReadInt32();
result.GlobalVictory.Discovery = reader.ReadInt32();
result.GlobalVictory.PercentExplored = reader.ReadInt32();
result.GlobalVictory.Unknown = reader.ReadInt32();
result.GlobalVictory.RequireAllCustom = reader.ReadBoolean();
result.GlobalVictory.RequireAllCustom = reader.ReadBooleanInt32();
result.GlobalVictory.Mode = reader.ReadInt32();
result.GlobalVictory.Score = reader.ReadInt32();
result.GlobalVictory.Time = reader.ReadInt32();
@ -188,7 +187,7 @@ namespace AdrianKousz.GenieEngine
ReadSeparator(reader, "Player Environment");
result.PlayerSettings.ForEach(x => {
x.AlliedVictory = reader.ReadBoolean();
x.AlliedVictory = reader.ReadBooleanInt32();
});
if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
@ -223,18 +222,17 @@ namespace AdrianKousz.GenieEngine
number = reader.ReadInt32();
result.ResourceCopies = new Scenario.ScnResourceCopy[number - 1]
.Fill(factory.MakeResourceCopy);
result.Resources2 = result.Resources2.NewList().Fill(number - 1, factory.MakeResources2);
result.ResourceCopies.ForEach(x => {
x.ResourceFood = reader.ReadSingle();
x.ResourceWood = reader.ReadSingle();
x.ResourceGold = reader.ReadSingle();
x.ResourceStone = reader.ReadSingle();
result.Resources2.ForEach(x => {
x.Food = reader.ReadSingle();
x.Wood = reader.ReadSingle();
x.Gold = reader.ReadSingle();
x.Stone = reader.ReadSingle();
if (Version >= 1.18f) {
x.ResourceOre = reader.ReadInt32();
x.Ore = reader.ReadSingle();
if (Version < 1.3f) {
x.UnknownResource = reader.ReadInt32();
x.Unknown = reader.ReadSingle();
}
}
if (Version >= 1.22f) {
@ -244,7 +242,7 @@ namespace AdrianKousz.GenieEngine
// Units
result.Units = new List<Scenario.ScnUnit>[number].Fill();
result.Units = ScnDefaultFactory.MakeUnitList(number);
result.Units.ForEach(x => {
var count = reader.ReadInt32();
@ -265,7 +263,39 @@ namespace AdrianKousz.GenieEngine
});
});
// More Player Settings and Triggers
// PlayerSettings2
number = reader.ReadInt32();
result.PlayerSettings2 = result.PlayerSettings2.NewList().Fill(number - 1, factory.MakePlayerSettings2);
result.PlayerSettings2.ForEach(x => {
x.Name = reader.ReadZStringInt16();
x.CameraX = reader.ReadSingle();
x.CameraY = reader.ReadSingle();
x.UnknownX = reader.ReadInt16();
x.UnknownY = reader.ReadInt16();
x.AlliedVictory = reader.ReadByte();
number = reader.ReadInt16();
x.Stances = new Scenario.ScnPlayerSettings2.Stance[number].Fill();
x.Stances.ForEach(y => { y.Stance1 = reader.ReadByte(); });
x.Stances.ForEach(y => { y.Stance2 = reader.ReadInt32(); });
if (Version >= 1.18f) {
x.Color = reader.ReadInt32();
}
x.Unknown1 = reader.ReadSingle();
x.Unknown2 = reader.ReadInt32();
if (x.Unknown1 == 1.0f || x.Unknown1 == 2.0f) {
x.Unknown = reader.ReadBytes(1 + (int)x.Unknown1 * 8 + x.Unknown2 * 44);
} else {
var msg = string.Format("Numbers {0}/{1} for {2}", x.Unknown1, x.Unknown2, x.Name);
throw new System.NotImplementedException(msg);
}
});
// Triggers
var bytestream = new MemoryStream();
reader.BaseStream.CopyTo(bytestream);

View File

@ -17,13 +17,15 @@ namespace AdrianKousz.GenieEngine
public void Write(ExtendedBinaryWriter writer, Scenario value)
{
EnsureValid(value);
var Version = value.VersionNumber;
var UncompressedHeaderLength = 20 + writer.GetZByteCount(value.InstructionsUncompressed);
// Uncompressed header
writer.WriteStringRaw(value.VersionString);
writer.Write((int)UncompressedHeaderLength);
writer.Write((System.Int32)UncompressedHeaderLength);
writer.Write(value.Unknown1);
writer.Write((System.Int32)DateTimes.ToUnixTime(value.Timestamp));
writer.WriteZStringInt32(value.InstructionsUncompressed);
@ -53,8 +55,8 @@ namespace AdrianKousz.GenieEngine
}
value.PlayerSettings.ForEach(x => {
writer.Write(x.Active);
writer.Write(x.Human);
writer.WriteBooleanInt32(x.Active);
writer.WriteBooleanInt32(x.Human);
writer.Write(x.Civ);
writer.Write(x.UnknownInfo);
});
@ -96,7 +98,7 @@ namespace AdrianKousz.GenieEngine
var hasBitmap = value.Cinematics.RawBitmap.Length > 0;
writer.Write(hasBitmap);
writer.WriteBooleanInt32(hasBitmap);
writer.Write(value.Cinematics.BitmapWidth);
writer.Write(value.Cinematics.BitmapHeight);
writer.Write(value.Cinematics.BitmapUnknown);
@ -113,9 +115,9 @@ namespace AdrianKousz.GenieEngine
});
value.PlayerSettings.ForEach(x => {
writer.Write((int)writer.GetByteCount(x.ScriptAI));
writer.Write((int)writer.GetByteCount(x.ScriptCTY));
writer.Write((int)writer.GetByteCount(x.ScriptPER));
writer.Write((System.Int32)writer.GetByteCount(x.ScriptAI));
writer.Write((System.Int32)writer.GetByteCount(x.ScriptCTY));
writer.Write((System.Int32)writer.GetByteCount(x.ScriptPER));
writer.WriteStringRaw(x.ScriptAI);
writer.WriteStringRaw(x.ScriptCTY);
writer.WriteStringRaw(x.ScriptPER);
@ -148,13 +150,13 @@ namespace AdrianKousz.GenieEngine
// Diplomacy
writer.Write(value.GlobalVictory.RequireConquest);
writer.WriteBooleanInt32(value.GlobalVictory.RequireConquest);
writer.Write(value.GlobalVictory.Ruins);
writer.Write(value.GlobalVictory.Artifacts);
writer.Write(value.GlobalVictory.Discovery);
writer.Write(value.GlobalVictory.PercentExplored);
writer.Write(value.GlobalVictory.Unknown);
writer.Write(value.GlobalVictory.RequireAllCustom);
writer.WriteBooleanInt32(value.GlobalVictory.RequireAllCustom);
writer.Write(value.GlobalVictory.Mode);
writer.Write(value.GlobalVictory.Score);
writer.Write(value.GlobalVictory.Time);
@ -172,7 +174,7 @@ namespace AdrianKousz.GenieEngine
WriteSeparator(writer, "Player Environment");
value.PlayerSettings.ForEach(x => {
writer.Write(x.AlliedVictory);
writer.WriteBooleanInt32(x.AlliedVictory);
});
writer.Write(value.RawDisables);
@ -203,17 +205,17 @@ namespace AdrianKousz.GenieEngine
// ResourceCopies
writer.Write((int)value.Units.Length);
writer.Write((System.Int32)value.Units.Count);
value.ResourceCopies.ForEach(x => {
writer.Write(x.ResourceFood);
writer.Write(x.ResourceWood);
writer.Write(x.ResourceGold);
writer.Write(x.ResourceStone);
value.Resources2.ForEach(x => {
writer.Write(x.Food);
writer.Write(x.Wood);
writer.Write(x.Gold);
writer.Write(x.Stone);
if (Version >= 1.18f) {
writer.Write(x.ResourceOre);
writer.Write(x.Ore);
if (Version < 1.3f) {
writer.Write(x.UnknownResource);
writer.Write(x.Unknown);
}
}
if (Version >= 1.22f) {
@ -224,7 +226,7 @@ namespace AdrianKousz.GenieEngine
// Units
value.Units.ForEach(x => {
writer.Write((int)x.Count);
writer.Write((System.Int32)x.Count);
x.ForEach(y => {
writer.Write(y.PosX);
writer.Write(y.PosY);
@ -240,7 +242,31 @@ namespace AdrianKousz.GenieEngine
});
});
// More Player Settings and Triggers
// PlayerSettings2
writer.Write((System.Int32)(value.PlayerSettings2.Count + 1));
value.PlayerSettings2.ForEach(x => {
writer.WriteZStringInt16(x.Name);
writer.Write(x.CameraX);
writer.Write(x.CameraY);
writer.Write(x.UnknownX);
writer.Write(x.UnknownY);
writer.Write(x.AlliedVictory);
writer.Write((System.Int16)x.Stances.Length);
x.Stances.ForEach(y => { writer.Write(y.Stance1); });
x.Stances.ForEach(y => { writer.Write(y.Stance2); });
if (Version >= 1.18f) {
writer.Write(x.Color);
}
writer.Write(x.Unknown1);
writer.Write(x.Unknown2);
writer.Write(x.Unknown);
});
// Triggers
writer.Write(value.RawRemaining);
@ -256,6 +282,14 @@ namespace AdrianKousz.GenieEngine
{
writer.Write(Scenario.Separator);
}
private void EnsureValid(Scenario value)
{
if (value.PlayerSettings.Count != Scenario.NumPlayers)
throw new System.InvalidOperationException("PlayerSettings.Count != 16");
if (value.Units.Count - 1 != value.Resources2.Count)
throw new System.InvalidOperationException("Conflicting unit and resource copy count");
}
}
}

View File

@ -2,4 +2,4 @@
[assembly: AssemblyTitle("ScenLib")]
[assembly: AssemblyDescription("Read and write scenario data from Genie Engine games")]
[assembly: AssemblyVersion("0.5")]
[assembly: AssemblyVersion("0.6")]