diff --git a/README.md b/README.md
index 675ddfe..5b30cc2 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,61 @@ Command line application and library to edit scenario and campaign files.
**Alpha version**
+## Usage
+
+ Available commands are:
+ copymap Copy parts of scenarios
+ compress Recompress a decompressed scenario file
+ decompress Decompress a scenario file for hex editing
+ extract Extract scenarios from campaign file
+ dump Dump a scenario file to JSON
+ dumpunits Dump units and map tiles below
+
+The command `copymap` is able convert an AOE1 to an AOE2 scenario.
+There seem to be some issues with cliffs.
+
+[OpeningMoves-AOE2.scx](files/OpeningMoves-AOE2.scx)
+is a fully automated conversion of a scenario from the AOE1 demo version:
+
+ $ GenieEdit.exe copymap files/examples/aoe2x-Empty.scx "files/examples/aoe1demo-Reigno_1-Opening Moves.scn" files/OpeningMoves-AOE2.scx
+
+(Stone are set to 100 to be able to build a town center from the beginning.)
+
+It can also be used to dump units from any version of a scenario:
+
+ $ GenieEdit.exe dumpunits Debug2.scx
+ Scenario Editor
+
+ ID UnitID TileID Position
+
+ Units of player 0:
+ 0 69 23 ( 1,5, 11,5)
+ 1 69 23 ( 2,5, 11,5)
+ ...
+
+### Code
+
+Have a look at the command implementations. They are very short.
+
## Build
Clone my projects MainUtil.cs ExUtil.cs and this repository
and build `src/GenieEdit.sln`.
+
+## Contributing
+
+Contributions are very welcome. ☺
+Keep in mind that the API is still very unstable.
+
+Suggestions:
+
+1. Gather UnitIDs of AOE1
+2. Much help is appreciated to figure out ID mappings
+ for the conversion process.
+3. Ideas how to configure the converter (mapping tables).
+ There are some notes in German:
+ [AOE1](files/units-aoe1.txt),
+ [AOE2](files/units-aoe2.txt)
+3. The application should be able to generate maps from images
+ in the future. How should units be placed using the image?
+4. Feedback about the API.
diff --git a/files/OpeningMoves-AOE2.scx b/files/OpeningMoves-AOE2.scx
new file mode 100644
index 0000000..2f1bf01
Binary files /dev/null and b/files/OpeningMoves-AOE2.scx differ
diff --git a/files/examples/aoe1x-Empty.scx b/files/examples/aoe1x-Empty.scx
new file mode 100644
index 0000000..6f40b16
Binary files /dev/null and b/files/examples/aoe1x-Empty.scx differ
diff --git a/files/examples/aoe2x-Empty.scx b/files/examples/aoe2x-Empty.scx
new file mode 100644
index 0000000..7652ee5
Binary files /dev/null and b/files/examples/aoe2x-Empty.scx differ
diff --git a/files/units-aoe1.txt b/files/units-aoe1.txt
new file mode 100644
index 0000000..ee59d99
--- /dev/null
+++ b/files/units-aoe1.txt
@@ -0,0 +1,59 @@
+Gold 66
+Bush 59
+Stone 102
+Klippe 3x3 264
+Dorfbewohner 83
+
+Kleiner Wall 72
+Mittelgrosser Wall 117
+Befestigungsanlage 155
+
+Beobachtungsturm 2x2 79
+Abwehrturm 2x2 199
+Wachturm 2x2 69
+Geschützturm 2x2 278
+
+Haus 2x2 70
+Artefakt 159
+Bauernhof=>Feld 3x3 50
+Dorfzentrum 3x3 109
+Kaserne 3x3 12
+Kornspeicher 3x3 68
+Lagergrube 3x3 103
+Stall 101
+
+Baum 134
+Buche 140
+Eiche 141 144 145 146
+Esche, Fichte 148 136
+Kiefer 197 194 193 192 135 137 138 139 391 392
+Palme 150-153
+
+Wald Gelaende TileID=10:
+142 143 147 195 365 366 367 393
+
+Palmenhain Gelaende TileID=13:
+113 114 121 129
+
+Dschungel TileID=20:
+113 114 121 129
+
+Kiefernwald TileID=19:
+161 198 203 226 391 392
+
+Fisch Kueste 260
+Fisch Kueste 263
+Fisch Lachs 53
+Fisch Thun 52
+Fisch Wal 370
+
+Aligator 1
+Aligator Koenig 362
+Elefant 48
+Elefant Koenig 90
+Gazelle 65
+Gazelle Koenig 384
+Loewe 126
+Loewe Koenig 89
+Vogel Adler 96
+Vogel Habicht 95
diff --git a/files/units-aoe2.txt b/files/units-aoe2.txt
new file mode 100644
index 0000000..2ed0091
--- /dev/null
+++ b/files/units-aoe2.txt
@@ -0,0 +1,74 @@
+Gold 66
+Bush 59
+Stone 102
+Klippe 3x3 264
+Dorfbewohner 83
+
+Palisadenwall 72
+Steinwallall 117
+Befestigter Wall 155
+
+Aussenposten 1x1 598
+Beobachtungsturm 1x1 79
+Wachturm 1x1 79
+Hauptturm 1x1 79
+
+Haus 2x2 70
+Reliquie 285
+Bauernhof=>Feld 3x3 50 (TileID=7)
+Dorfzentrum 4x4 109
+Kaserne 3x3 12
+Muehle 2x2 68
+Holzlager 2x2 562
+Stall 3x3 101
+
+Bambus 348
+Baum A-L 399-410
+Baum TD 248
+Dschungelbaum 414
+Eiche 349
+Kiefer 350
+Palme 351
+Verschneiter Kiefer 413
+Waldbaum 411
+
+Wald Gelaende TileID=10:
+411
+
+Palmenhain Gelaende TileID=13:
+351
+
+Dschungel TileID=17:
+414
+
+Kiefernwald TileID=19:
+350
+
+Bambus TileID=18:
+348
+
+Eichenwald TileID=20:
+349
+
+Fisch Barsch 53
+Fisch Dorado 455
+Fisch Lachs 456
+Fisch Schnapper 458
+Fisch Thun 457
+
+Fisch Marlin 1 451
+Fisch Marlin 2 450
+Fisch Kueste 69
+
+Tiere:
+Vogel Falke 96
+Jaguar 812
+Wildes Pferd 835
+Pferd 814
+Reh 65
+Schaf 594
+Truthahn 833
+Urwolf 89
+Wolf 126
+Wilder Eber 810
+Wildschwein 48
diff --git a/src/cmdapp/GenieEngineApp.csproj b/src/cmdapp/GenieEngineApp.csproj
index 067681b..b3128ef 100644
--- a/src/cmdapp/GenieEngineApp.csproj
+++ b/src/cmdapp/GenieEngineApp.csproj
@@ -36,6 +36,9 @@
+
+
+
@@ -50,6 +53,10 @@
{662FDCF9-3F6C-48DA-BFE9-F317BEFED692}
ExUtil
+
+ {8306E345-3A99-4F22-AF14-3CEA7425C2EF}
+ MainUtil
+
diff --git a/src/cmdapp/src/CompressCommand.cs b/src/cmdapp/src/CompressCommand.cs
index df42cb1..13a012e 100644
--- a/src/cmdapp/src/CompressCommand.cs
+++ b/src/cmdapp/src/CompressCommand.cs
@@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine
var reader = new ScnReader(file);
var writer = new ScnWriter(outfile);
- var header = reader.ReadScenarioHeader(null);
+ var header = reader.ReadScenarioHeader();
reader.ReadSeparator("Custom");
writer.Write(header);
writer.Flush();
diff --git a/src/cmdapp/src/CopymapCommand.cs b/src/cmdapp/src/CopymapCommand.cs
new file mode 100644
index 0000000..3129569
--- /dev/null
+++ b/src/cmdapp/src/CopymapCommand.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+
+namespace AdrianKousz.GenieEngine
+{
+ public class CopymapCommand : BaseCommand
+ {
+ override public void Run()
+ {
+ Data.Scenario result;
+ using (var dststream = File.OpenRead(args[0]))
+ using (var srcstream = File.OpenRead(args[1]))
+ {
+ var srcreader = new ScnReader(srcstream);
+ var src = srcreader.ReadScenario();
+ var dstreader = new ScnReader(dststream);
+ var dst = dstreader.ReadScenario();
+ var converter = new CopymapConverter();
+ dst.PlayerNames = src.PlayerNames;
+ dst.StringTablePlayerNames = src.StringTablePlayerNames;
+ dst.Players = src.Players;
+ dst.Resources = src.Resources;
+ dst.ResourcesCopy = src.ResourcesCopy;
+ dst.Messages = src.Messages;
+ converter.Convert(dst, src);
+ result = dst;
+ }
+ using (var outstream = File.OpenWrite(args[2])) {
+ var outwriter = new ScnWriter(outstream);
+ outwriter.Write(result);
+ }
+ }
+
+ override public int GetArgumentCount()
+ {
+ return 3;
+ }
+
+ override public string GetDescription()
+ {
+ return "Copy parts of scenarios";
+ }
+
+ override public string GetHelp()
+ {
+ return "Copy the parts below from scenario file 2 into scenario file 1."
+ + "\nThe result is written to scenario file 3."
+ + "\n- Messages"
+ + "\n- Player information"
+ + "\n- Resources"
+ + "\n- Map"
+ + "\n- Units"
+ ;
+ }
+ }
+}
+
diff --git a/src/cmdapp/src/CopymapConverter.cs b/src/cmdapp/src/CopymapConverter.cs
new file mode 100644
index 0000000..e752383
--- /dev/null
+++ b/src/cmdapp/src/CopymapConverter.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using AdrianKousz.Util;
+using AdrianKousz.GenieEngine.Data;
+
+namespace AdrianKousz.GenieEngine
+{
+ public class CopymapConverter
+ {
+ public void Convert(Scenario dst, Scenario src)
+ {
+ dst.Map = src.Map;
+ dst.Units = ChangeUnits_AOE1_AOE2(src.Units);
+ MoveUnits_AOE1_AOE2(dst.Units);
+ ChangeTiles_AOE1_AOE2(dst.Units, dst.Map);
+ RandomizeTrees_AOE2(dst.Units);
+ }
+
+ /**
+ * Some buildings are not the same size.
+ * They need to be moved by half a unit.
+ */
+ private void MoveUnits_AOE1_AOE2(IList[] units)
+ {
+ var mapping = new int[] {
+ // Towers
+ 79, 199, 69, 278,
+ // Other Buildings
+ 109, 68, 103,
+ };
+ units.ForEach((i, list) => {
+ list.ForEach((j, unit) => {
+ mapping.ForEach((k, buildingid) => {
+ if (unit.UnitId == buildingid) {
+ unit.PosX -= 0.5f;
+ unit.PosY -= 0.5f;
+ }
+ });
+ });
+ });
+ }
+
+ /**
+ * Simple mapping of unit IDs
+ */
+ private List[] ChangeUnits_AOE1_AOE2(IList[] units)
+ {
+ var array = new int[] {
+ 66, 66,
+ 59, 59,
+ 102, 102,
+ 264, 264,
+ 83, 83,
+ 125, 125,
+
+ // Buildings
+ 72, 72,
+ 117, 117,
+ 155, 155,
+ 79, 598,
+ 199, 79,
+ 69, 79,
+ 278, 79,
+ 70, 70,
+ 159, 285,
+ 109, 109,
+ 12, 12,
+ 68, 68,
+ 103, 562,
+ 101, 101,
+
+ // Trees
+ 141, 349,
+ 144, 349,
+ 145, 349,
+ 146, 349,
+ 197, 350,
+ 194, 350,
+ 193, 350,
+ 192, 350,
+ 135, 350,
+ 137, 350,
+ 138, 350,
+ 139, 350,
+ 161, 350,
+ 198, 350,
+ 203, 350,
+ 226, 350,
+ 391, 350,
+ 392, 350,
+ 150, 351,
+ 151, 351,
+ 152, 351,
+ 153, 351,
+ 134, 411,
+ 140, 411,
+ 148, 411,
+ 136, 411,
+ 142, 411,
+ 143, 411,
+ 147, 411,
+ 195, 411,
+ 365, 411,
+ 366, 411,
+ 367, 411,
+ 393, 411,
+
+ // Fish
+ 260, 69,
+ 263, 69,
+ 53, 456,
+ 52, 457,
+ 370, 451,
+
+ // Animals
+ 48, 48,
+ 90, 48,
+ 65, 65,
+ 384, 65,
+ 126, 126,
+ 89, 126,
+ 96, 96,
+
+ // Keep and process later
+ 50, 50,
+ 113, 113,
+ 114, 114,
+ 121, 121,
+ 129, 129,
+ };
+ var mapping = new Dictionary();
+ var ai = 0;
+ while (ai < array.Length)
+ mapping.Add(array[ai++], array[ai++]);
+
+ var result = new List[Scenario.NumPlayerSections];
+ result.Fill();
+
+ units.ForEach((i, list) => {
+ list.ForEach((j, unit) => {
+ if (mapping.ContainsKey(unit.UnitId)) {
+ unit.UnitId = (short)mapping[unit.UnitId];
+ result[i].Add(unit);
+ }
+ });
+ });
+
+ return result;
+ }
+
+ /**
+ * Farms need to be converted including the underlying terrain.
+ */
+ private void ChangeTiles_AOE1_AOE2(IList[] units, Map map)
+ {
+ units.ForEach((i, list) => {
+ list.ForEach((j, unit) => {
+ var posx = (int)(unit.PosX - 0.5);
+ var posy = (int)(unit.PosY - 0.5);
+ var linearpos = posy * map.SizeX + posx;
+ if (false) {
+ // switch
+ } else if (unit.UnitId == 50) {
+ map.LinearTiles[linearpos].Id = 7;
+ map.LinearTiles[linearpos - 1].Id = 7;
+ map.LinearTiles[linearpos + 1].Id = 7;
+ map.LinearTiles[linearpos - map.SizeX].Id = 7;
+ map.LinearTiles[linearpos - map.SizeX - 1].Id = 7;
+ map.LinearTiles[linearpos - map.SizeX + 1].Id = 7;
+ map.LinearTiles[linearpos + map.SizeX].Id = 7;
+ map.LinearTiles[linearpos + map.SizeX - 1].Id = 7;
+ map.LinearTiles[linearpos + map.SizeX + 1].Id = 7;
+ } else if (unit.UnitId == 113 || unit.UnitId == 114 || unit.UnitId == 121 || unit.UnitId == 129) {
+ if (map.LinearTiles[linearpos].Id == 20) {
+ map.LinearTiles[linearpos].Id = 17;
+ unit.UnitId = 414;
+ } else {
+ unit.UnitId = 351;
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * Forests would contain the same tree graphic
+ * without this function
+ */
+ private void RandomizeTrees_AOE2(IList[] units)
+ {
+ var treeids = new int[] {
+ 411, 351, 414, 350, 348, 349,
+ };
+ units.ForEach((i, list) => {
+ list.ForEach((j, unit) => {
+ treeids.ForEach((k, treeid) => {
+ if (unit.UnitId == treeid) {
+ unit.InitialFrame = (short)Util.Math.Rand(0, 13); // Whats the maximum?
+ unit.Rotation = unit.InitialFrame;
+ }
+ });
+ });
+ });
+ }
+ }
+}
diff --git a/src/cmdapp/src/DecompressCommand.cs b/src/cmdapp/src/DecompressCommand.cs
index 107474c..bc4f905 100644
--- a/src/cmdapp/src/DecompressCommand.cs
+++ b/src/cmdapp/src/DecompressCommand.cs
@@ -12,9 +12,9 @@ namespace AdrianKousz.GenieEngine
var reader = new ScnReader(file);
var writer = new ScnWriter(outfile);
- var header = reader.ReadScenarioHeader(null);
+ var header = reader.ReadScenarioHeader();
writer.Write(header);
- writer.WriteSeperator();
+ writer.WriteSeperator("Custom");
writer.Flush();
using (var comp = new DeflateStream(file, CompressionMode.Decompress)) {
diff --git a/src/cmdapp/src/DumpCommand.cs b/src/cmdapp/src/DumpCommand.cs
index e046eb6..19ef4cd 100644
--- a/src/cmdapp/src/DumpCommand.cs
+++ b/src/cmdapp/src/DumpCommand.cs
@@ -9,7 +9,7 @@ namespace AdrianKousz.GenieEngine
{
using (var stream = File.OpenRead(args[0])) {
var aoereader = new ScnReader(stream);
- var scn = aoereader.ReadScenario(null);
+ var scn = aoereader.ReadScenario();
var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
var result = ser.Serialize(scn);
Console.WriteLine(result);
@@ -23,7 +23,7 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription()
{
- return "Dump a scenario file to XML";
+ return "Dump a scenario file to JSON";
}
}
}
diff --git a/src/cmdapp/src/DumpunitsCommand.cs b/src/cmdapp/src/DumpunitsCommand.cs
new file mode 100644
index 0000000..d995615
--- /dev/null
+++ b/src/cmdapp/src/DumpunitsCommand.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using AdrianKousz.Util;
+using AdrianKousz.GenieEngine.Data;
+
+namespace AdrianKousz.GenieEngine
+{
+ public class DumpunitsCommand : BaseCommand
+ {
+ override public void Run()
+ {
+ Scenario scn;
+ using (var stream = File.OpenRead(args[0])) {
+ var aoereader = new ScnReader(stream);
+ scn = aoereader.ReadScenario();
+ }
+
+ 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 tilex = (int)(unit.PosX - 0.5);
+ var tiley = (int)(unit.PosY - 0.5);
+ 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();
+ });
+ }
+
+ override public int GetArgumentCount()
+ {
+ return 1;
+ }
+
+ override public string GetDescription()
+ {
+ return "Dump units and map tiles below";
+ }
+ }
+}
+
diff --git a/src/cmdapp/src/Program.cs b/src/cmdapp/src/Program.cs
index 83ec435..773b35a 100644
--- a/src/cmdapp/src/Program.cs
+++ b/src/cmdapp/src/Program.cs
@@ -9,6 +9,11 @@ namespace AdrianKousz.GenieEngine
{
System.Threading.Thread.CurrentThread.Name = "main";
+ if (System.Diagnostics.Debugger.IsAttached) {
+ debug();
+ return;
+ }
+
new CmdApp(new Program(), args).Run();
}
@@ -20,11 +25,23 @@ namespace AdrianKousz.GenieEngine
public IDictionary> GetCommands()
{
var result = new Dictionary>();
+ result.Add("copymap", new CopymapCommand());
result.Add("compress", new CompressCommand());
result.Add("decompress", new DecompressCommand());
result.Add("extract", new ExtractCommand());
result.Add("dump", new DumpCommand());
+ result.Add("dumpunits", new DumpunitsCommand());
return result;
}
+
+ private static void debug()
+ {
+ var fn = "";
+ using (var istream = System.IO.File.OpenRead(fn)) {
+ var ireader = new ScnReader(istream);
+ var scn = ireader.ReadScenario();
+ System.Diagnostics.Debugger.Break(); // Great to inspect scn
+ }
+ }
}
}
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs b/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs
index 72bc324..21d2011 100644
--- a/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs
+++ b/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs
@@ -11,7 +11,7 @@ namespace AdrianKousz.GenieEngine.Data
public String[] PlayerCustomAIs;
public String[] PlayerCustomCTYs;
public String[] PlayerCustomPERs;
- public Byte[] RawAITypes;
+ public Byte[] AITypes;
}
}
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs b/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs
index 33ff9d7..25411b9 100644
--- a/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs
+++ b/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs
@@ -9,6 +9,10 @@ namespace AdrianKousz.GenieEngine.Data
public String CinemaLossFn;
public String BackgroundFn;
+ public Int32 BitmapWidth;
+ public Int32 BitmapHeight;
+ public Int16 BitmapUnknown;
+
public Byte[] RawBitmap;
}
}
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs b/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs
deleted file mode 100644
index 9c8c3ad..0000000
--- a/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.Text;
-
-namespace AdrianKousz.GenieEngine.Data
-{
- public static class Consts
- {
- public const Int32 Separator = -99;
- public const Int32 NumPlayers = 16;
- public const Int32 NumUnitSections = 9;
- public static readonly Encoding Encoding = Encoding.GetEncoding(1252);
- public static readonly Char[] NullChar = new Char[] { '\0' };
- }
-}
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs
new file mode 100644
index 0000000..eb7a80b
--- /dev/null
+++ b/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace AdrianKousz.GenieEngine.Data
+{
+ public class GlobalVictoryInfo
+ {
+ public Boolean RequireConquest;
+ public Int32 Ruins;
+ public Int32 Artifacts;
+ public Int32 Discovery;
+ public Int32 PercentExplored;
+ public Int32 Unknown;
+ public Boolean RequireAllCustom;
+ public Int32 Mode;
+ public Int32 Score;
+ public Int32 Time;
+ }
+}
+
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs
index f48f24a..43050b3 100644
--- a/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs
+++ b/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs
@@ -6,8 +6,6 @@ namespace AdrianKousz.GenieEngine.Data
{
public const Int32 ExpectedUnknown = 4;
- public String Name;
- public Int32 StringTable;
public Boolean Active;
public Boolean Human;
public Int32 Civ;
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs b/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs
index abd8e66..053e1ff 100644
--- a/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs
+++ b/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
-using AdrianKousz.GenieEngine.Data;
+using System.Text;
namespace AdrianKousz.GenieEngine.Data
{
public class Scenario
{
- public const Int32 PlayerSections = 9;
+ public const Int32 Separator = -99;
+ public const Int32 NumPlayers = 16;
+ public const Int32 NumPlayerSections = 9;
+ public static readonly Encoding Encoding = Encoding.GetEncoding(1252);
public ScenarioHeader Header;
@@ -15,22 +18,36 @@ namespace AdrianKousz.GenieEngine.Data
public Int32 NextId;
public Single OriginalVersion;
+ public String[] PlayerNames;
+ public Int32[] StringTablePlayerNames;
public PlayerInfo[] Players;
public Messages Messages;
public Cinematics Cinematics;
public AIData AIData;
+
+ // Seperator "Resources"
+
public ResourceInfo[] Resources;
+
+ // Seperator "Victory"
+
+ public GlobalVictoryInfo GlobalVictory;
+ public Int32[] LinearDiplomacy;
+ public Byte[] RawIndividualVictory;
+
+ // Seperator "Player Settings"
+
+ public Int32[] AlliedVictory;
+ public Byte[] RawDisables;
+ public Int32[] StartingAges;
+
+ // Seperator "Map"
+
public Map Map;
public ResourceInfoCopy[] ResourcesCopy;
public IList[] Units;
- public Byte[] RawGlobalVictory;
- public Byte[] RawDiplomacy;
- public Byte[] RawIndividualVictory;
- public Byte[] RawAlliedVictory;
- public Byte[] RawDisables;
-
- public Int32[] StartingAges;
+ public byte[] RawRemaining;
}
}
diff --git a/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs
index 295c603..e6531b3 100644
--- a/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs
+++ b/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs
@@ -4,13 +4,14 @@ namespace AdrianKousz.GenieEngine.Data
{
public class UnitInfo
{
- public const Single ExpectedUnknown = 2f;
+ public const Single ExpectedUnknown1 = 1f;
+ public const Byte ExpectedUnknown2 = 2;
public Single PosX;
public Single PosY;
public Single Unknown1;
public Int32 Id;
- public Int32 UnitId;
+ public Int16 UnitId;
public Byte Unknown2;
public Single Rotation;
public Int16 InitialFrame;
diff --git a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs
index 159d138..197ebc0 100644
--- a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs
+++ b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs
@@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine
public CpnReader(Stream input)
{
- reader = new ExtendedBinaryReader(input, Data.Consts.Encoding);
+ reader = new ExtendedBinaryReader(input, Scenario.Encoding);
}
public void ExtractFiles()
diff --git a/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs
new file mode 100644
index 0000000..229a636
--- /dev/null
+++ b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs
@@ -0,0 +1,20 @@
+using System;
+using AdrianKousz.GenieEngine.Data;
+
+namespace AdrianKousz.GenieEngine
+{
+ public interface IScnFactory
+ {
+ Scenario MakeScenario(bool deep);
+ ScenarioHeader MakeScenarioHeader();
+ PlayerInfo MakePlayerInfo();
+ Messages MakeMessages();
+ Cinematics MakeCinematics();
+ AIData MakeAIData();
+ ResourceInfo MakeResourceInfo();
+ ResourceInfoCopy MakeResourceInfoCopy();
+ GlobalVictoryInfo MakeGlobalVictoryInfo();
+ Map MakeMap();
+ UnitInfo MakeUnitInfo();
+ }
+}
diff --git a/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs b/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs
new file mode 100644
index 0000000..a0375bb
--- /dev/null
+++ b/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs
@@ -0,0 +1,22 @@
+using System;
+using AdrianKousz.Util;
+using AdrianKousz.GenieEngine.Data;
+
+namespace AdrianKousz.GenieEngine
+{
+ public class ScnConvert
+ {
+ public void Convert(Scenario value, float version)
+ {
+
+ }
+ public void Convert(UnitInfo value, float from, float to)
+ {
+ if (from < 1.18f && to >= 1.18f) {
+ value.InitialFrame = 0;
+ value.GarrisonnedInId = -1;
+ }
+ }
+ }
+}
+
diff --git a/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs b/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs
new file mode 100644
index 0000000..0288cb4
--- /dev/null
+++ b/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs
@@ -0,0 +1,148 @@
+using System.Collections.Generic;
+using AdrianKousz.Util;
+using AdrianKousz.GenieEngine.Data;
+
+namespace AdrianKousz.GenieEngine
+{
+ public class ScnDefaultFactory : IScnFactory
+ {
+ public Scenario MakeScenario(bool deep)
+ {
+ var result = new Scenario();
+ result.OriginalVersion = 0;
+ result.NextId = 1;
+
+ result.StringTablePlayerNames = new int[Scenario.NumPlayers];
+
+ result.Units = new IList[Scenario.NumPlayerSections]
+ .Fill(() => new List());
+
+ return result;
+ }
+
+ public ScenarioHeader MakeScenarioHeader()
+ {
+ var result = new ScenarioHeader();
+ result.Unknown1 = ScenarioHeader.ExpectedUnknown1;
+ result.Unknown2 = ScenarioHeader.ExpectedUnknown2;
+ result.OriginalVersion = "0.00";
+ result.Timestamp = System.DateTime.Now;
+ result.Instructions = "";
+ result.PlayerCount = 2;
+ return result;
+ }
+
+ public PlayerInfo MakePlayerInfo()
+ {
+ var result = new PlayerInfo();
+ result.Unknown = PlayerInfo.ExpectedUnknown;
+ result.Active = false;
+ result.Human = false;
+ result.Civ = 0;
+ return result;
+ }
+
+ public Messages MakeMessages()
+ {
+ var result = new Messages();
+ result.Unknown1 = Messages.ExpectedUnknown1;
+ result.Unknown2 = Messages.ExpectedUnknown2;
+ result.Unknown3 = Messages.ExpectedUnknown3;
+ result.OriginalFilename = "";
+ result.TextInstructions = "";
+ result.TextHints = "";
+ result.TextVictory = "";
+ result.TextLoss = "";
+ result.TextHistory = "";
+ result.TextScouts = "";
+ result.StringTableInstructions = -1;
+ result.StringTableHints = -1;
+ result.StringTableVictory = -1;
+ result.StringTableLoss = -1;
+ result.StringTableHistory = -1;
+ result.StringTableScouts = -1;
+
+ return result;
+ }
+
+ public Cinematics MakeCinematics()
+ {
+ var result = new Cinematics();
+ result.CinemaPregameFn = "";
+ result.CinemaVictoryFn = "";
+ result.CinemaLossFn = "";
+ result.BackgroundFn = "";
+ result.RawBitmap = new byte[0];
+ return result;
+ }
+
+ public AIData MakeAIData()
+ {
+ var result = new AIData();
+ result.PlayerAIs = new string[Scenario.NumPlayers].Fill("");
+ result.PlayerCTYs = new string[Scenario.NumPlayers].Fill("");
+ result.PlayerPERs = new string[Scenario.NumPlayers].Fill("");
+ result.PlayerCustomAIs = new string[Scenario.NumPlayers].Fill("");
+ result.PlayerCustomCTYs = new string[Scenario.NumPlayers].Fill("");
+ result.PlayerCustomPERs = new string[Scenario.NumPlayers].Fill("");
+ result.AITypes = new byte[Scenario.NumPlayers].Fill((byte)1);
+ return result;
+ }
+
+ public GlobalVictoryInfo MakeGlobalVictoryInfo()
+ {
+ var result = new GlobalVictoryInfo();
+ result.RequireConquest = true;
+ result.RequireAllCustom = false;
+ return result;
+ }
+
+ public ResourceInfo MakeResourceInfo()
+ {
+ // AOE2 standard
+ var result = new ResourceInfo();
+ result.Unknown = ResourceInfo.ExpectedUnknown;
+ result.Food = 200;
+ result.Wood = 200;
+ result.Gold = 100;
+ result.Stone = 200;
+ return result;
+ }
+
+ public ResourceInfoCopy MakeResourceInfoCopy()
+ {
+ // AOE2 standard
+ var result = new ResourceInfoCopy();
+ result.Unknown = ResourceInfoCopy.ExpectedUnknown;
+ result.Food = 200;
+ result.Wood = 200;
+ result.Gold = 100;
+ result.Stone = 200;
+ result.PopulationLimit = 75;
+ return result;
+ }
+
+ public Map MakeMap()
+ {
+ var result = new Map();
+ var size = 144;
+ result.CameraX = -1;
+ result.CameraY = -1;
+ result.GeneratorId = 2;
+ result.SizeX = size;
+ result.SizeY = size;
+ result.LinearTiles = new Map.Tile[size*size].Fill(() => new Map.Tile(0, 1, 0));
+ return result;
+ }
+
+ public UnitInfo MakeUnitInfo()
+ {
+ var result = new UnitInfo();
+ result.Unknown1 = UnitInfo.ExpectedUnknown1;
+ result.Unknown2 = UnitInfo.ExpectedUnknown2;
+ result.GarrisonnedInId = -1;
+ return result;
+ }
+ }
+}
+
diff --git a/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs b/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs
deleted file mode 100644
index 7c4ead8..0000000
--- a/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-using System.Collections.Generic;
-using AdrianKousz.Util;
-using AdrianKousz.GenieEngine.Data;
-
-namespace AdrianKousz.GenieEngine
-{
- public class ScnFactory
- {
- public Scenario GetScenario()
- {
- var result = new Scenario();
- result.OriginalVersion = -1;
- result.NextId = 1;
-
- result.Header = GetScenarioHeader();
-
- result.Players = new PlayerInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.Players.Fill(GetPlayerInfo);
-
- result.Messages = GetMessages();
- result.Cinematics = GetCinematics();
- result.AIData = GetAIData();
-
- result.Resources = new ResourceInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.Resources.Fill(GetResourceInfo);
- result.Units = new List[Scenario.PlayerSections];
- result.Units.Fill(() => new List());
-
- return result;
- }
-
- public ScenarioHeader GetScenarioHeader()
- {
- var result = new ScenarioHeader();
- result.Unknown1 = ScenarioHeader.ExpectedUnknown1;
- result.Unknown2 = ScenarioHeader.ExpectedUnknown2;
- result.OriginalVersion = "0.99";
- result.Timestamp = System.DateTime.Now;
- result.Instructions = "";
- result.PlayerCount = 0;
- return result;
- }
-
- public PlayerInfo GetPlayerInfo(int number)
- {
- var result = new PlayerInfo();
- result.Unknown = PlayerInfo.ExpectedUnknown;
- result.Name = "Player " + number;
- result.StringTable = -1;
- result.Active = true;
- result.Human = true;
- result.Civ = number;
- return result;
- }
-
- public Messages GetMessages()
- {
- var result = new Messages();
- result.Unknown1 = Messages.ExpectedUnknown1;
- result.Unknown2 = Messages.ExpectedUnknown2;
- result.Unknown3 = Messages.ExpectedUnknown3;
- result.OriginalFilename = "";
- result.TextInstructions = "";
- result.TextHints = "";
- result.TextVictory = "";
- result.TextLoss = "";
- result.TextHistory = "";
- result.TextScouts = "";
- result.StringTableInstructions = -1;
- result.StringTableHints = -1;
- result.StringTableVictory = -1;
- result.StringTableLoss = -1;
- result.StringTableHistory = -1;
- result.StringTableScouts = -1;
-
- return result;
- }
-
- public Cinematics GetCinematics()
- {
- var result = new Cinematics();
- result.CinemaPregameFn = "";
- result.CinemaVictoryFn = "";
- result.CinemaLossFn = "";
- result.BackgroundFn = "";
- return result;
- }
-
- public AIData GetAIData()
- {
- var result = new AIData();
- result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerAIs.Fill("");
- result.PlayerCTYs.Fill("");
- result.PlayerPERs.Fill("");
- result.PlayerCustomAIs.Fill("");
- result.PlayerCustomCTYs.Fill("");
- result.PlayerCustomPERs.Fill("");
- result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.RawAITypes.Fill((byte)1);
- return result;
- }
-
- public ResourceInfo GetResourceInfo()
- {
- // AOE2 standard
- var result = new ResourceInfo();
- result.Unknown = ResourceInfo.ExpectedUnknown;
- result.Food = 200;
- result.Wood = 200;
- result.Gold = 100;
- result.Stone = 200;
- result.Ore = 0;
- return result;
- }
-
- public Map GetMap()
- {
- var result = new Map();
- var size = 200;
- result.CameraX = 0;
- result.CameraY = 0;
- result.GeneratorId = 0;
- result.SizeX = size;
- result.SizeY = size;
- result.LinearTiles = new Map.Tile[size*size];
- result.LinearTiles.Fill();
- return result;
- }
- }
-}
-
diff --git a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs b/src/lib/AdrianKousz.GenieEngine/ScnReader.cs
index f6e6164..bf7811b 100644
--- a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs
+++ b/src/lib/AdrianKousz.GenieEngine/ScnReader.cs
@@ -8,81 +8,90 @@ namespace AdrianKousz.GenieEngine
{
public class ScnReader
{
- private static readonly string TAG = typeof(ScnReader).Name;
+ public float Version = 0;
- private float version = 0.99f;
private ExtendedBinaryReader reader;
+ private IScnFactory factory;
- public ScnReader(Stream input)
+ public ScnReader(Stream input) : this (input, new ScnDefaultFactory())
{
- reader = new ExtendedBinaryReader(input, Data.Consts.Encoding);
+ }
+
+ public ScnReader(Stream input, IScnFactory factory)
+ {
+ reader = new ExtendedBinaryReader(input, Scenario.Encoding);
+ this.factory = factory;
}
#region Reader Methods
- public Scenario ReadScenario(Scenario result)
+ public Scenario ReadScenario()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeScenario(false);
int number = 0;
- result.Header = ReadScenarioHeader(result.Header);
+ result.Header = ReadScenarioHeader();
var comp = new DeflateStream(reader.BaseStream, CompressionMode.Decompress);
- reader = new ExtendedBinaryReader(comp, Data.Consts.Encoding);
+ reader = new ExtendedBinaryReader(comp, Scenario.Encoding);
result.NextId = reader.ReadInt32();
- version = reader.ReadSingle();
- result.OriginalVersion = version;
- Log.i(TAG, "Detected version {0}", version);
+ Version = reader.ReadSingle();
+ result.OriginalVersion = Version;
-
- result.Players = FillArray(ReadPlayerInfo, result.Players, AdrianKousz.GenieEngine.Data.Consts.NumPlayers);
- result.Messages = ReadMessages(result.Messages);
- result.Cinematics = ReadCinematics(result.Cinematics);
- result.AIData = ReadAIData(result.AIData);
+ result.PlayerNames = FillArray(() => reader.ReadZString(256), result.PlayerNames, Scenario.NumPlayers);
+ if (Version >= 1.18f) {
+ result.StringTablePlayerNames = FillArray(reader.ReadInt32, result.StringTablePlayerNames, Scenario.NumPlayers);
+ }
+ result.Players = FillArray(ReadPlayerInfo, result.Players, Scenario.NumPlayers);
+ result.Messages = ReadMessages();
+ result.Cinematics = ReadCinematics();
+ result.AIData = ReadAIData();
ReadSeparator("Resources");
- result.Resources = FillArray(ReadResourceInfo, result.Resources, AdrianKousz.GenieEngine.Data.Consts.NumPlayers);
+ result.Resources = FillArray(ReadResourceInfo, result.Resources, Scenario.NumPlayers);
ReadSeparator("Victory");
- result.RawGlobalVictory = reader.ReadBytes(10 * 4);
- result.RawDiplomacy = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4);
+ result.GlobalVictory = ReadGlobalVictoryInfo();
+ result.LinearDiplomacy = FillArray(reader.ReadInt32, result.LinearDiplomacy, Scenario.NumPlayers * Scenario.NumPlayers);
result.RawIndividualVictory = reader.ReadBytes(11520);
ReadSeparator("Player Settings");
- result.RawAlliedVictory = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4);
+ result.AlliedVictory = FillArray(reader.ReadInt32, result.AlliedVictory, Scenario.NumPlayers);
- if (1.15f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
- if (1.18f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
- if (1.30f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
+ if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
+ if (Version >= 1.18f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
+ if (Version >= 1.30f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
result.RawDisables = reader.ReadBytes(number);
- result.StartingAges = new int[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.StartingAges.Fill(reader.ReadInt32);
+ result.StartingAges = FillArray(reader.ReadInt32, result.StartingAges, Scenario.NumPlayers);
ReadSeparator("Map");
- result.Map = ReadMap(result.Map);
+ result.Map = ReadMap();
number = reader.ReadInt32();
result.ResourcesCopy = FillArray(ReadResourceInfoCopy, result.ResourcesCopy, number - 1);
- result.Units = new IList[number];
- result.Units.Fill(ReadUnitInfo);
+ result.Units = FillArray(ReadUnitInfoList, result.Units, number);
+
+ var bytestream = new MemoryStream();
+ reader.BaseStream.CopyTo(bytestream);
+ result.RawRemaining = bytestream.ToArray();
return result;
}
- public ScenarioHeader ReadScenarioHeader(ScenarioHeader result)
+ public ScenarioHeader ReadScenarioHeader()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeScenarioHeader();
result.OriginalVersion = reader.ReadString(4);
- Skip(4, "Header Length");
+ reader.ReadInt32(); // Header Length
result.Unknown1 = reader.ReadInt32();
result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32());
result.Instructions = reader.ReadZStringInt32();
@@ -92,14 +101,10 @@ namespace AdrianKousz.GenieEngine
return result;
}
- public PlayerInfo ReadPlayerInfo(PlayerInfo result)
+ public PlayerInfo ReadPlayerInfo()
{
- result = Misc.MayNew(result);
+ var result = factory.MakePlayerInfo();
- result.Name = reader.ReadZString(256);
- if (1.18f <= version) {
- result.StringTable = reader.ReadInt32();
- }
result.Active = reader.ReadBoolean();
result.Human = reader.ReadBoolean();
result.Civ = reader.ReadInt32();
@@ -108,23 +113,23 @@ namespace AdrianKousz.GenieEngine
return result;
}
- public Messages ReadMessages(Messages result)
+ public Messages ReadMessages()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeMessages();
result.Unknown1 = reader.ReadInt32();
result.Unknown2 = reader.ReadByte();
result.Unknown3 = reader.ReadSingle();
result.OriginalFilename = reader.ReadStringInt16();
- if (1.18f <= version) {
+ if (Version >= 1.18f) {
result.StringTableInstructions = reader.ReadInt32();
result.StringTableHints = reader.ReadInt32();
result.StringTableVictory = reader.ReadInt32();
result.StringTableLoss = reader.ReadInt32();
result.StringTableHistory = reader.ReadInt32();
}
- if (1.22f <= version) {
+ if (Version >= 1.22f) {
result.StringTableScouts = reader.ReadInt32();
}
@@ -133,16 +138,16 @@ namespace AdrianKousz.GenieEngine
result.TextVictory = reader.ReadZStringInt16();
result.TextLoss = reader.ReadZStringInt16();
result.TextHistory = reader.ReadZStringInt16();
- if (1.22f <= version) {
+ if (Version >= 1.22f) {
result.TextScouts = reader.ReadZStringInt16();
}
return result;
}
- public Cinematics ReadCinematics(Cinematics result)
+ public Cinematics ReadCinematics()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeCinematics();
result.CinemaPregameFn = reader.ReadStringInt16();
result.CinemaVictoryFn = reader.ReadStringInt16();
@@ -151,37 +156,31 @@ namespace AdrianKousz.GenieEngine
var hasBitmap = reader.ReadBoolean();
- Skip(4, "Bitmap width");
- Skip(4, "Bitmap height");
- Skip(2, "Bitmap Unknown");
+ result.BitmapWidth = reader.ReadInt32();
+ result.BitmapHeight = reader.ReadInt32();
+ result.BitmapUnknown = reader.ReadInt16();
if (hasBitmap) {
result.RawBitmap = BitmapUtil.ReadRawBitmap(reader.BaseStream);
- } else {
- result.RawBitmap = new byte[0];
}
return result;
}
- public AIData ReadAIData(AIData result)
+ public AIData ReadAIData()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeAIData();
- result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
- result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
+ result.PlayerAIs = FillArray(reader.ReadStringInt16, result.PlayerAIs, Scenario.NumPlayers);
+ result.PlayerCTYs = FillArray(reader.ReadStringInt16, result.PlayerCTYs, Scenario.NumPlayers);
+ result.PlayerPERs = FillArray(reader.ReadStringInt16, result.PlayerPERs, Scenario.NumPlayers);
- result.PlayerAIs.Fill(reader.ReadStringInt16);
- result.PlayerCTYs.Fill(reader.ReadStringInt16);
- result.PlayerPERs.Fill(reader.ReadStringInt16);
+ result.PlayerCustomAIs = new string[Scenario.NumPlayers];
+ result.PlayerCustomCTYs = new string[Scenario.NumPlayers];
+ result.PlayerCustomPERs = new string[Scenario.NumPlayers];
int l1, l2, l3;
- for (var i = 0; i < AdrianKousz.GenieEngine.Data.Consts.NumPlayers; ++i) {
+ for (var i = 0; i < Scenario.NumPlayers; ++i) {
l1 = reader.ReadInt32();
l2 = reader.ReadInt32();
l3 = reader.ReadInt32();
@@ -190,88 +189,112 @@ namespace AdrianKousz.GenieEngine
result.PlayerCustomPERs[i] = reader.ReadString(l3);
}
- if (1.18f <= version) {
- result.RawAITypes.Fill(reader.ReadByte);
+ if (Version >= 1.18f) {
+ result.AITypes = FillArray(reader.ReadByte, result.AITypes, Scenario.NumPlayers);
}
return result;
}
- public ResourceInfo ReadResourceInfo(ResourceInfo result)
+ public ResourceInfo ReadResourceInfo()
{
- result = Misc.MayNew(result);
+ var result = factory.MakeResourceInfo();
result.Gold = reader.ReadInt32();
result.Wood = reader.ReadInt32();
result.Food = reader.ReadInt32();
result.Stone = reader.ReadInt32();
- if (1.18f <= version) {
+ if (Version >= 1.18f) {
result.Ore = reader.ReadInt32();
- result.Unknown = reader.ReadInt32();
+ if (Version < 1.3f) {
+ result.Unknown = reader.ReadInt32();
+ }
}
return result;
}
- public Map ReadMap(Map result)
+ public ResourceInfoCopy ReadResourceInfoCopy()
{
- result = Misc.MayNew(result);
-
- if (1.18f <= version) {
- result.CameraX = reader.ReadInt32();
- result.CameraY = reader.ReadInt32();
- }
- if (1.22f <= version) {
- result.GeneratorId = reader.ReadInt32();
- }
-
- result.SizeX = reader.ReadInt32();
- result.SizeY = reader.ReadInt32();
-
- result.LinearTiles = new Map.Tile[result.SizeX * result.SizeY];
- result.LinearTiles.Fill(() => new Map.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()));
-
- return result;
- }
-
- public ResourceInfoCopy ReadResourceInfoCopy(ResourceInfoCopy result)
- {
- result = Misc.MayNew(result);
+ var result = factory.MakeResourceInfoCopy();
result.Food = reader.ReadSingle();
result.Wood = reader.ReadSingle();
result.Gold = reader.ReadSingle();
result.Stone = reader.ReadSingle();
- if (1.18f <= version) {
+ if (Version >= 1.18f) {
result.Ore = reader.ReadInt32();
- result.Unknown = reader.ReadInt32();
+ if (Version < 1.3f) {
+ result.Unknown = reader.ReadInt32();
+ }
}
- if (1.22f <= version) {
+ if (Version >= 1.22f) {
result.PopulationLimit = reader.ReadSingle();
}
return result;
}
- public IList ReadUnitInfo()
+ public GlobalVictoryInfo ReadGlobalVictoryInfo()
{
- var number = reader.ReadInt32();
- var result = new List(number);
+ var result = factory.MakeGlobalVictoryInfo();
- for (var i = 0; i < number; i++) {
- var unit = new UnitInfo();
- unit.PosX = reader.ReadSingle();
- unit.PosY = reader.ReadSingle();
- unit.Unknown1 = reader.ReadSingle();
- unit.Id = reader.ReadInt32();
- unit.UnitId = reader.ReadInt16();
- unit.Unknown2 = reader.ReadByte();
- unit.Rotation = reader.ReadSingle();
- if (1.18f <= version) {
- unit.InitialFrame = reader.ReadInt16();
- unit.GarrisonnedInId = reader.ReadInt32();
- }
- result.Add(unit);
+ result.RequireConquest = reader.ReadBoolean();
+ result.Ruins = reader.ReadInt32();
+ result.Artifacts = reader.ReadInt32();
+ result.Discovery = reader.ReadInt32();
+ result.PercentExplored = reader.ReadInt32();
+ result.Unknown = reader.ReadInt32();
+ result.RequireAllCustom = reader.ReadBoolean();
+ result.Mode = reader.ReadInt32();
+ result.Score = reader.ReadInt32();
+ result.Time = reader.ReadInt32();
+
+ return result;
+ }
+
+ public Map ReadMap()
+ {
+ var result = factory.MakeMap();
+
+ if (Version >= 1.18f) {
+ result.CameraX = reader.ReadInt32();
+ result.CameraY = reader.ReadInt32();
+ }
+ if (Version >= 1.22f) {
+ result.GeneratorId = reader.ReadInt32();
+ }
+
+ result.SizeX = reader.ReadInt32();
+ result.SizeY = reader.ReadInt32();
+
+ result.LinearTiles = new Map.Tile[result.SizeX * result.SizeY]
+ .Fill(() => new Map.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()));
+
+ return result;
+ }
+
+ public IList ReadUnitInfoList()
+ {
+ var count = reader.ReadInt32();
+ var result = new List().Fill(ReadUnitInfo, count);
+ return result;
+ }
+
+ public UnitInfo ReadUnitInfo()
+ {
+ var result = factory.MakeUnitInfo();
+
+ result.PosX = reader.ReadSingle();
+ result.PosY = reader.ReadSingle();
+ result.Unknown1 = reader.ReadSingle();
+ result.Id = reader.ReadInt32();
+ result.UnitId = reader.ReadInt16();
+ result.Unknown2 = reader.ReadByte();
+ result.Rotation = reader.ReadSingle();
+ if (Version >= 1.18f) {
+ result.InitialFrame = reader.ReadInt16();
+ result.GarrisonnedInId = reader.ReadInt32();
}
return result;
@@ -279,7 +302,7 @@ namespace AdrianKousz.GenieEngine
public void ReadSeparator(string name) {
int v = reader.ReadInt32();
- if (v != AdrianKousz.GenieEngine.Data.Consts.Separator) {
+ if (v != Scenario.Separator) {
var msg = "Separator \"{0}\" = {1}";
msg = string.Format(msg, name, v);
throw new InvalidDataException(msg);
@@ -290,26 +313,14 @@ namespace AdrianKousz.GenieEngine
#region Debug Methods
- private T[] FillArray(System.Func f, T[] array, int length) where T : new()
+ private T[] FillArray(System.Func f, T[] array, int length)
{
- if (array == null) {
+ if (array == null || array.Length != length)
array = new T[length];
- } else if (array.Length != length) {
- var temp = new T[length];
- System.Array.Copy(array, temp, System.Math.Min(array.Length, temp.Length));
- }
-
array.Fill(f);
-
return array;
}
- private void Skip(int count, string name)
- {
- Log.d(TAG, "Skipping {0} bytes in \"{1}\"", count, name);
- reader.ReadBytes(count);
- }
-
#endregion
}
}
diff --git a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs b/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs
index f4876c5..2e78042 100644
--- a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs
+++ b/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.IO.Compression;
+using System.Collections.Generic;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
@@ -6,18 +8,76 @@ namespace AdrianKousz.GenieEngine
{
public class ScnWriter
{
- private static readonly string TAG = typeof(ScnWriter).Name;
+ public float Version = 0;
private ExtendedBinaryWriter writer;
- public ScnWriter(Stream input)
+ public ScnWriter(Stream output)
{
- writer = new ExtendedBinaryWriter(input, Data.Consts.Encoding);
+ writer = new ExtendedBinaryWriter(output, Scenario.Encoding);
+ }
+
+ public void Write(Scenario value)
+ {
+ Version = Version == 0 ? value.OriginalVersion : Version;
+
+ Write(value.Header);
+ Flush();
+
+ // TODO: Make wrapper.
+ // This is a workaround for now. A wrapper for the reader and writer will be created.
+ var originalWriter = writer;
+ var compressedPart = new MemoryStream();
+ var comp = new DeflateStream(compressedPart, CompressionMode.Compress);
+ writer = new ExtendedBinaryWriter(comp, Scenario.Encoding);
+
+ writer.Write(value.NextId);
+ writer.Write(Version);
+
+ value.PlayerNames.ForEach((x) => writer.WriteStringPadded(x, 256));
+ if (Version >= 1.18f) {
+ value.StringTablePlayerNames.ForEach(writer.Write);
+ }
+ value.Players.ForEach(Write);
+ Write(value.Messages);
+ Write(value.Cinematics);
+ Write(value.AIData);
+
+ WriteSeperator("Resources");
+
+ value.Resources.ForEach(Write);
+
+ WriteSeperator("Victory");
+
+ Write(value.GlobalVictory);
+ value.LinearDiplomacy.ForEach(writer.Write);
+ writer.Write(value.RawIndividualVictory);
+
+ WriteSeperator("Player Settings");
+
+ value.AlliedVictory.ForEach(writer.Write);
+ writer.Write(value.RawDisables);
+ value.StartingAges.ForEach(writer.Write);
+
+ WriteSeperator("Map");
+
+ Write(value.Map);
+
+ writer.Write((int)value.Units.Length);
+ value.ResourcesCopy.ForEach(Write);
+ value.Units.ForEach(Write);
+
+ writer.Write(value.RawRemaining);
+
+ Flush();
+
+ comp.Dispose();
+ originalWriter.Write(compressedPart.ToArray());
}
public void Write(ScenarioHeader value)
{
- var instructionlength = writer.GetByteCount(value.Instructions + " ");
+ var instructionlength = writer.GetZByteCount(value.Instructions);
writer.WriteStringRaw(value.OriginalVersion);
writer.Write(20 + instructionlength);
writer.Write(value.Unknown1);
@@ -27,15 +87,180 @@ namespace AdrianKousz.GenieEngine
writer.Write(value.PlayerCount);
}
- public void WriteSeperator()
+ public void Write(PlayerInfo value)
{
- writer.Write(AdrianKousz.GenieEngine.Data.Consts.Separator);
+ writer.Write(value.Active);
+ writer.Write(value.Human);
+ writer.Write(value.Civ);
+ writer.Write(value.Unknown);
+ }
+
+ public void Write(Messages value)
+ {
+ writer.Write(value.Unknown1);
+ writer.Write(value.Unknown2);
+ writer.Write(value.Unknown3);
+ writer.WriteStringInt16(value.OriginalFilename);
+
+ if (Version >= 1.18f) {
+ writer.Write(value.StringTableInstructions);
+ writer.Write(value.StringTableHints);
+ writer.Write(value.StringTableVictory);
+ writer.Write(value.StringTableLoss);
+ writer.Write(value.StringTableHistory);
+ }
+ if (Version >= 1.22f) {
+ writer.Write(value.StringTableScouts);
+ }
+
+ writer.WriteZStringInt16(value.TextInstructions);
+ writer.WriteZStringInt16(value.TextHints);
+ writer.WriteZStringInt16(value.TextVictory);
+ writer.WriteZStringInt16(value.TextLoss);
+ writer.WriteZStringInt16(value.TextHistory);
+ if (Version >= 1.22f) {
+ writer.WriteZStringInt16(value.TextScouts);
+ }
+ }
+
+ public void Write(Cinematics value)
+ {
+ writer.WriteStringInt16(value.CinemaPregameFn);
+ writer.WriteStringInt16(value.CinemaVictoryFn);
+ writer.WriteStringInt16(value.CinemaLossFn);
+ writer.WriteStringInt16(value.BackgroundFn);
+
+ var hasBitmap = value.RawBitmap.Length > 0;
+
+ writer.Write(hasBitmap);
+ writer.Write(value.BitmapWidth);
+ writer.Write(value.BitmapHeight);
+ writer.Write(value.BitmapUnknown);
+
+ if (hasBitmap)
+ writer.Write(value.RawBitmap);
+ }
+
+ public void Write(AIData value)
+ {
+ value.PlayerAIs.ForEach(writer.WriteStringInt16);
+ value.PlayerCTYs.ForEach(writer.WriteStringInt16);
+ value.PlayerPERs.ForEach(writer.WriteStringInt16);
+
+ for (var i = 0; i < Scenario.NumPlayers; ++i) {
+ var str1 = value.PlayerCustomAIs[i];
+ var str2 = value.PlayerCustomCTYs[i];
+ var str3 = value.PlayerCustomPERs[i];
+ writer.Write(writer.GetByteCount(str1));
+ writer.Write(writer.GetByteCount(str2));
+ writer.Write(writer.GetByteCount(str3));
+ writer.WriteStringRaw(str1);
+ writer.WriteStringRaw(str2);
+ writer.WriteStringRaw(str3);
+ }
+
+ if (Version >= 1.18f) {
+ value.AITypes.ForEach(writer.Write);
+ }
+ }
+
+ public void Write(ResourceInfo value)
+ {
+ writer.Write(value.Gold);
+ writer.Write(value.Wood);
+ writer.Write(value.Food);
+ writer.Write(value.Stone);
+ if (Version >= 1.18f) {
+ writer.Write(value.Ore);
+ if (Version < 1.3f) {
+ writer.Write(value.Unknown);
+ }
+ }
+ }
+
+ public void Write(ResourceInfoCopy value)
+ {
+ writer.Write(value.Food);
+ writer.Write(value.Wood);
+ writer.Write(value.Gold);
+ writer.Write(value.Stone);
+ if (Version >= 1.18f) {
+ writer.Write(value.Ore);
+ if (Version < 1.3f) {
+ writer.Write(value.Unknown);
+ }
+ }
+ if (Version >= 1.22f) {
+ writer.Write(value.PopulationLimit);
+ }
+ }
+
+ public void Write(GlobalVictoryInfo value)
+ {
+ writer.Write(value.RequireConquest);
+ writer.Write(value.Ruins);
+ writer.Write(value.Artifacts);
+ writer.Write(value.Discovery);
+ writer.Write(value.PercentExplored);
+ writer.Write(value.Unknown);
+ writer.Write(value.RequireAllCustom);
+ writer.Write(value.Mode);
+ writer.Write(value.Score);
+ writer.Write(value.Time);
+ }
+
+ public void Write(Map value)
+ {
+ if (Version >= 1.18f) {
+ writer.Write(value.CameraX);
+ writer.Write(value.CameraY);
+ }
+ if (Version >= 1.22f) {
+ writer.Write(value.GeneratorId);
+ }
+ writer.Write(value.SizeX);
+ writer.Write(value.SizeY);
+
+ value.LinearTiles.ForEach(x => { writer.Write(x.Id); writer.Write(x.Elevation); writer.Write(x.Unknown); });
+ }
+
+ public void Write(IList value)
+ {
+ int number = value.Count;
+ writer.Write(number);
+ value.ForEach(Write);
+ }
+
+ public void Write(UnitInfo value)
+ {
+ writer.Write(value.PosX);
+ writer.Write(value.PosY);
+ writer.Write(value.Unknown1);
+ writer.Write(value.Id);
+ writer.Write(value.UnitId);
+ writer.Write(value.Unknown2);
+ writer.Write(value.Rotation);
+ if (Version >= 1.18f) {
+ writer.Write(value.InitialFrame);
+ writer.Write(value.GarrisonnedInId);
+ }
+ }
+
+ public void WriteSeperator(string name)
+ {
+ writer.Write(Scenario.Separator);
}
public void Flush()
{
writer.Flush();
}
+
+ private void EnsureSetVersion()
+ {
+ if (Version <= 1)
+ throw new System.InvalidOperationException("Set output version");
+ }
}
}
diff --git a/src/lib/GenieEngineLib.csproj b/src/lib/GenieEngineLib.csproj
index c5e45d1..e62c696 100644
--- a/src/lib/GenieEngineLib.csproj
+++ b/src/lib/GenieEngineLib.csproj
@@ -34,13 +34,11 @@
-
-
@@ -50,6 +48,10 @@
+
+
+
+