From 9cc13f0e8d112d24083be566a9829aeee938c409 Mon Sep 17 00:00:00 2001 From: Adrian <adrian.dev@kousz.ch> Date: Mon, 22 Feb 2016 17:02:44 +0100 Subject: [PATCH] Campaign support and bitmap to map conversion in application * Full Campaign reading/writing support * Convert bitmaps to maps * Command arguments reordering: input and output are always first * Adapt usage of utils API --- src/cmdapp/GenieEngineApp.csproj | 6 ++- src/cmdapp/src/AddBitmapCommand.cs | 6 +-- src/cmdapp/src/BitmapHelpers.cs | 73 ++++++++++++++++++++++++++++++ src/cmdapp/src/Bmp2MapCommand.cs | 51 +++++++++++++++++++++ src/cmdapp/src/ConvertCommand.cs | 8 ++-- src/cmdapp/src/CopymapConverter.cs | 44 ++++++++---------- src/cmdapp/src/DumpunitsCommand.cs | 7 +-- src/cmdapp/src/ExtractCommand.cs | 18 ++++++-- src/cmdapp/src/PackCommand.cs | 47 +++++++++++++++++++ src/cmdapp/src/Program.cs | 3 ++ 10 files changed, 222 insertions(+), 41 deletions(-) create mode 100644 src/cmdapp/src/BitmapHelpers.cs create mode 100644 src/cmdapp/src/Bmp2MapCommand.cs create mode 100644 src/cmdapp/src/PackCommand.cs diff --git a/src/cmdapp/GenieEngineApp.csproj b/src/cmdapp/GenieEngineApp.csproj index e1b6083..c09987b 100644 --- a/src/cmdapp/GenieEngineApp.csproj +++ b/src/cmdapp/GenieEngineApp.csproj @@ -19,15 +19,16 @@ <WarningLevel>4</WarningLevel> <Externalconsole>true</Externalconsole> <PlatformTarget>x86</PlatformTarget> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> - <DebugType>full</DebugType> <Optimize>true</Optimize> <OutputPath>..\..\bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <Externalconsole>true</Externalconsole> <PlatformTarget>x86</PlatformTarget> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <ItemGroup> <Compile Include="src\Program.cs" /> @@ -42,6 +43,9 @@ <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" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/src/cmdapp/src/AddBitmapCommand.cs b/src/cmdapp/src/AddBitmapCommand.cs index a779271..c910fd9 100644 --- a/src/cmdapp/src/AddBitmapCommand.cs +++ b/src/cmdapp/src/AddBitmapCommand.cs @@ -10,8 +10,8 @@ namespace AdrianKousz.GenieEngine override public void Run() { using (var input = File.OpenRead(args[0])) - using (var bmpstream = File.OpenRead(args[1])) - using (var output = File.OpenWrite(args[2])) + using (var output = File.OpenWrite(args[1])) + using (var bmpstream = File.OpenRead(args[2])) { var scn = GenieFile.Deserialize<Scenario>(input); var bmp = (Bitmap)Image.FromStream(bmpstream); @@ -41,7 +41,7 @@ namespace AdrianKousz.GenieEngine + "\nAdditionally, a game-specific palette will be used to display the image." + "\n" + "\nParameters:" - + "\n<input scenario> <bitmap> <output scenario>" + + "\n<input scenario> <output scenario> <bitmap>" ; } } diff --git a/src/cmdapp/src/BitmapHelpers.cs b/src/cmdapp/src/BitmapHelpers.cs new file mode 100644 index 0000000..f4dc23a --- /dev/null +++ b/src/cmdapp/src/BitmapHelpers.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public static class BitmapHelpers + { + public static unsafe void FillMap(Scenario scn, Bitmap bmp, Bitmap bmp2) + { + using (var ubmp = new UnsafeBitmap(bmp)) + using (var ubmp2 = new UnsafeBitmap(bmp2)) + { + var factory = new ScnDefaultFactory(); + + Check(ubmp); + Check(ubmp2); + + var ptr = ubmp.Scan0; + var last = ptr + ubmp.Stride * ubmp.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.LinearTiles = new Scenario.ScnMap.Tile[map.SizeX * map.SizeY]; + + var gaiaunits = new List<Scenario.ScnUnit>(); + + var treemapping = new short[256]; + treemapping[10] = 411; + treemapping[13] = 351; + treemapping[17] = 414; + treemapping[18] = 348; + treemapping[19] = 350; + treemapping[20] = 349; + + var i = 0; + while (ptr < last && ptr2 < last2) { + var treeid = treemapping[*ptr]; + if (treeid != 0) { + var unit = factory.MakeUnit(); + var posy = i / map.SizeX; + var posx = i - (posy * map.SizeX); + unit.UnitId = treeid; + unit.PosX = posx + 0.5f; + unit.PosY = posy + 0.5f; + unit.Rotation = (short)Util.Math.Rand(0, 13); + gaiaunits.Add(unit); + } + map.LinearTiles[i++] = new Scenario.ScnMap.Tile(*ptr++, (byte)(*ptr2++ / 37), 0); + } + + scn.Map = map; + scn.Units[0] = gaiaunits; + } + } + + private static void Check(UnsafeBitmap ubmp) + { + if (ubmp.PixelFormat != PixelFormat.Format8bppIndexed) + throw new ArgumentException("Unsupported PixelFormat"); + if (ubmp.Stride != ubmp.Width) + throw new NotImplementedException("bmpbit.Stride != bmpbit.Width"); + } + } +} + diff --git a/src/cmdapp/src/Bmp2MapCommand.cs b/src/cmdapp/src/Bmp2MapCommand.cs new file mode 100644 index 0000000..9946a28 --- /dev/null +++ b/src/cmdapp/src/Bmp2MapCommand.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Drawing; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public class Bmp2MapCommand : BaseCommand + { + override public void Run() + { + using (var srcstream = File.OpenRead(args[0])) + using (var outstream = File.OpenWrite(args[1])) + using (var bmpstream = File.OpenRead(args[2])) + using (var bmp2stream = File.OpenRead(args[3])) + { + var bmp = (Bitmap)Image.FromStream(bmpstream); + var bmp2 = (Bitmap)Image.FromStream(bmp2stream); + + var scn = GenieFile.Deserialize<Scenario>(srcstream); + BitmapHelpers.FillMap(scn, bmp, bmp2); + GenieFile.Serialize(scn, outstream); + } + } + + override public int GetArgumentCount() + { + 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." + + "\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." + + "\n" + + "\nParameters:" + + "\n<input scenario> <output scenario> <terrain bitmap> <elevation bitmap>" + ; + } + } +} + diff --git a/src/cmdapp/src/ConvertCommand.cs b/src/cmdapp/src/ConvertCommand.cs index a1e3fdd..723b9a4 100644 --- a/src/cmdapp/src/ConvertCommand.cs +++ b/src/cmdapp/src/ConvertCommand.cs @@ -8,9 +8,9 @@ namespace AdrianKousz.GenieEngine { override public void Run() { - using (var refstream = File.OpenRead(args[0])) - using (var srcstream = File.OpenRead(args[1])) - using (var outstream = File.OpenWrite(args[2])) + using (var srcstream = File.OpenRead(args[0])) + using (var outstream = File.OpenWrite(args[1])) + using (var refstream = File.OpenRead(args[2])) { var refscn = GenieFile.Deserialize<Scenario>(refstream); var srcscn = GenieFile.Deserialize<Scenario>(srcstream); @@ -50,7 +50,7 @@ namespace AdrianKousz.GenieEngine + "\nThe converter needs an existing AOE2 scenario to start with." + "\n" + "\nParameters:" - + "\n<AOE2 reference scenario> <AOE1 scenario> <output scenario>" + + "\n<AOE1 scenario> <output scenario> <AOE2 reference scenario>" ; } } diff --git a/src/cmdapp/src/CopymapConverter.cs b/src/cmdapp/src/CopymapConverter.cs index 2bd5d17..d2d74f3 100644 --- a/src/cmdapp/src/CopymapConverter.cs +++ b/src/cmdapp/src/CopymapConverter.cs @@ -26,9 +26,9 @@ namespace AdrianKousz.GenieEngine // Other Buildings 109, 68, 103, }; - units.ForEach((i, list) => { - list.ForEach((j, unit) => { - mapping.ForEach((k, buildingid) => { + units.ForEach(list => { + list.ForEach(unit => { + mapping.ForEach(buildingid => { if (unit.UnitId == buildingid) { unit.PosX -= 0.5f; unit.PosY -= 0.5f; @@ -43,7 +43,7 @@ namespace AdrianKousz.GenieEngine */ private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units) { - var array = new int[] { + var mapping = new short[] { // Resources 66, 66, 59, 59, @@ -139,22 +139,15 @@ namespace AdrianKousz.GenieEngine 114, 114, 121, 121, 129, 129, - }; - var mapping = new Dictionary<short, short>(); - var ai = 0; - while (ai < array.Length) - mapping.Add((short)array[ai++], (short)array[ai++]); + }.ToDictionary(); - var result = new List<Scenario.ScnUnit>[Scenario.NumPlayerSections].Fill(); - - units.ForEach((i, list) => { - list.ForEach((j, unit) => { - if (mapping.ContainsKey(unit.UnitId)) { - unit.UnitId = mapping[unit.UnitId]; - result[i].Add(unit); - } - }); - }); + var result = units.Map(list => { + return list + .Filter(unit => mapping.ContainsKey(unit.UnitId)) + .ForEach(unit => { + unit.UnitId = mapping[unit.UnitId]; + }).ToList(); + }).ToArray(); return result; } @@ -164,8 +157,8 @@ namespace AdrianKousz.GenieEngine */ private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] units, Scenario.ScnMap map) { - units.ForEach((i, list) => { - list.ForEach((j, unit) => { + units.ForEach(list => { + list.ForEach(unit => { var posx = (int)(unit.PosX - 0.5); var posy = (int)(unit.PosY - 0.5); var linearpos = posy * map.SizeX + posx; @@ -203,12 +196,11 @@ namespace AdrianKousz.GenieEngine var treeids = new int[] { 411, 351, 414, 350, 348, 349, }; - units.ForEach((i, list) => { - list.ForEach((j, unit) => { - treeids.ForEach((k, treeid) => { + units.ForEach(list => { + list.ForEach(unit => { + treeids.ForEach(treeid => { if (unit.UnitId == treeid) { - unit.InitialFrame = (short)Util.Math.Rand(0, 13); // Whats the maximum? - unit.Rotation = unit.InitialFrame; + unit.Rotation = (short)Util.Math.Rand(0, 13); // Whats the maximum? } }); }); diff --git a/src/cmdapp/src/DumpunitsCommand.cs b/src/cmdapp/src/DumpunitsCommand.cs index a85101a..56eca5e 100644 --- a/src/cmdapp/src/DumpunitsCommand.cs +++ b/src/cmdapp/src/DumpunitsCommand.cs @@ -17,9 +17,10 @@ namespace AdrianKousz.GenieEngine Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position"); Console.WriteLine(); - scn.Units.ForEach((i, list) => { - Console.WriteLine("Units of player {0}:", i); - list.ForEach((j, unit) => { + 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 tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex]; diff --git a/src/cmdapp/src/ExtractCommand.cs b/src/cmdapp/src/ExtractCommand.cs index 7745852..514d895 100644 --- a/src/cmdapp/src/ExtractCommand.cs +++ b/src/cmdapp/src/ExtractCommand.cs @@ -1,4 +1,6 @@ +using System; using System.IO; +using AdrianKousz.Util; namespace AdrianKousz.GenieEngine { @@ -6,9 +8,17 @@ namespace AdrianKousz.GenieEngine { override public void Run() { - using (var file = File.OpenRead(args[0])) { - var reader = new CpnReader(file); - reader.ExtractFiles(); + using (var file = File.OpenRead(args[0])) + { + var cpn = GenieFile.Deserialize<Campaign>(file); + Console.WriteLine("Extracting \"{0}\"...", cpn.Name); + cpn.Files.ForEach(x => { + using (var outfile = File.OpenWrite(x.Filename)) + { + Console.WriteLine("Extracting \"{0}\" to \"{1}\"...", x.Name, x.Filename); + outfile.Write(x.RawData); + } + }); } } @@ -19,7 +29,7 @@ namespace AdrianKousz.GenieEngine override public string GetDescription() { - return "Extract scenarios from campaign file"; + return "Extract files from campaign file"; } } } diff --git a/src/cmdapp/src/PackCommand.cs b/src/cmdapp/src/PackCommand.cs new file mode 100644 index 0000000..57acae8 --- /dev/null +++ b/src/cmdapp/src/PackCommand.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Collections.Generic; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public class PackCommand : BaseCommand + { + override public void Run() + { + var files = new List<Campaign.CpnFile>(); + for (var i = 2; i < args.Length; i++) { + var file = new Campaign.CpnFile(); + file.Filename = args[i]; + file.Name = Path.GetFileNameWithoutExtension(file.Filename); + using (var stream = File.OpenRead(file.Filename)) + using (var memorystream = new MemoryStream()) + { + stream.CopyTo(memorystream); + file.RawData = memorystream.ToArray(); + } + files.Add(file); + } + + var cpn = new Campaign(); + cpn.Files = files; + cpn.Name = args[1]; + cpn.Version = "1.00"; + + using (var file = File.OpenWrite(args[0])) + { + GenieFile.Serialize(cpn, file); + } + } + + override public int GetArgumentCount() + { + return 3; + } + + override public string GetDescription() + { + return "Pack files into a campaign file"; + } + } +} + diff --git a/src/cmdapp/src/Program.cs b/src/cmdapp/src/Program.cs index 94b62a9..346cafd 100644 --- a/src/cmdapp/src/Program.cs +++ b/src/cmdapp/src/Program.cs @@ -8,6 +8,7 @@ namespace AdrianKousz.GenieEngine public static void Main(string[] args) { System.Threading.Thread.CurrentThread.Name = "main"; + Log.SetUncaughtExceptionHandler(); if (System.Diagnostics.Debugger.IsAttached) { debug(); @@ -26,6 +27,7 @@ namespace AdrianKousz.GenieEngine { 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()); @@ -34,6 +36,7 @@ namespace AdrianKousz.GenieEngine result.Add("fromjson", new FromJsonCommand()); result.Add("dumpunits", new DumpunitsCommand()); result.Add("extract", new ExtractCommand()); + result.Add("pack", new PackCommand()); return result; }