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