using System; using System.Drawing; using System.Drawing.Imaging; using System.Collections; using System.IO; using SpiffLib; using System.Diagnostics; using System.Text; using m; namespace m { public class OutputTools { public static void ImportExportPdbs(string[] astr) { // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 1; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); foreach (string filename in astrFiles) { if (filename.EndsWith(".pdb")) { alsFiles.Add(filename); } } } // Import, then save each pdb foreach (string filename in alsFiles) { try { if (File.Exists(filename + "_new")) { Console.WriteLine("Exists: " + filename + "_new"); continue; } ImportExpansionPdb(filename); LevelDocTemplate doctLevel = (LevelDocTemplate)DocManager.FindDocTemplate(typeof(LevelDoc)); Document[] adoc = doctLevel.GetDocuments(); SaveExpansionPdb(filename + "_new", adoc, "1.1"); foreach (Document doc in adoc) { doc.SetModified(false); } doctLevel.CloseAllDocuments(); } catch { Console.WriteLine("Error loading " + filename + ", skipping"); } } } public static void ImportExpansionPdb(string strFile) { PdbPacker pdbp = new PdbPacker(strFile); ArrayList alsTileSets = new ArrayList(); for (int i = 0; i < pdbp.Count; i++) { PdbPacker.File file = pdbp[i]; if (!file.str.EndsWith(".lvl")) { continue; } // Load up the pieces Ini ini = Ini.LoadBinary(new MemoryStream(file.ab)); string strTileMapFilename = ini["General"]["TileMap"].Value; TileMap tmap = TileMap.Load(new MemoryStream(pdbp[strTileMapFilename].ab)); // First, tell the active LevelDoc not to switch its templates based on the following // template load LevelDoc lvldActive = (LevelDoc)DocManager.GetActiveDocument(typeof(LevelDoc)); if (lvldActive != null) { lvldActive.SwitchTemplatesEnabled = false; } // If the TileSet for this level is not yet available, load it now TemplateDoc tmpd = (TemplateDoc)DocManager.OpenDocument(tmap.Filename.Replace(".tset", ".tc")); TileSet tset = null; foreach (TileSet tsetT in alsTileSets) { if (tsetT.FileName == tmap.Filename) { tset = tsetT; break; } } if (tset == null) { tset = new TileSet(tmpd, tmap.Filename); alsTileSets.Add(tset); } // Re-enable template switching if (lvldActive != null) { lvldActive.SwitchTemplatesEnabled = true; } // Create a new level description, and deduce which templates are in it, with what visibility LevelDoc lvld = (LevelDoc)DocManager.NewDocument(typeof(LevelDoc), null); lvld.OutputFilename = file.str; ImportTileMap(tmap, tset, tmpd, lvld); // Walls are stored in the terrain map. Load them. string strTrmapFilename = ini["General"]["TerrainMap"].Value; TerrainMap trmap = TerrainMap.Load(new MemoryStream(pdbp[strTrmapFilename].ab)); ImportWalls(trmap, lvld); // Load everything else lvld.LoadIni(ini); } } static void ImportWalls(TerrainMap trmap, LevelDoc lvld) { ArrayList alsmi = new ArrayList(); for (int ty = 0; ty < trmap.Map.GetLength(0); ty++) { for (int tx = 0; tx < trmap.Map.GetLength(1); tx++) { if (trmap.Map[ty, tx] == TerrainTypes.Wall) { IMapItem mi = new Wall(100, lvld.Bounds.Left + tx, lvld.Bounds.Top + ty); alsmi.Add(mi); } } } lvld.AddMapItems((IMapItem[])alsmi.ToArray(typeof(IMapItem))); } class TemplatePos { public Template tmpl; public int txOrigin; public int tyOrigin; public bool[,] afMapped; public TemplatePos(Template tmpl, int txOrigin, int tyOrigin, bool[,] afMapped) { this.tmpl = tmpl; this.txOrigin = txOrigin; this.tyOrigin = tyOrigin; this.afMapped = afMapped; } } static void ImportTileMap(TileMap tmap, TileSet tset, TemplateDoc tmpd, LevelDoc lvld) { // The TileMap is a list of indexes into a tile set. A Tileset is a list of tiles compiled // from Templates. Reverse the tilemap into Templates, and set into lvld. bool[,] afCellTaken = new bool[64, 64]; ArrayList alsTemplPos = new ArrayList(); Template[] atmpl = tmpd.GetTemplates(); for (int ty = 0; ty < tmap.Height; ty++) { for (int tx = 0; tx < tmap.Width; tx++) { // Cell mapped already? if (afCellTaken[ty, tx]) { continue; } // Cell not mapped. Create TemplatePos. int iTile = tmap.GetTileIndex(tx, ty); TileData tdata = tset.GetTileData(iTile); Template tmpl = atmpl[tdata.iTemplate]; // Don't bother with background tiles if (tmpl == tmpd.GetBackgroundTemplate()) { continue; } int txOrigin = tx - tdata.txTemplate; int tyOrigin = ty - tdata.tyTemplate; bool[,] afMapped = new bool[tmpl.Cty, tmpl.Ctx]; for (int tyTmpl = 0; tyTmpl < tmpl.Cty; tyTmpl++) { for (int txTmpl = 0; txTmpl < tmpl.Ctx; txTmpl++) { int txT = txOrigin + txTmpl; int tyT = tyOrigin + tyTmpl; if (txT < 0 || txT >= 64 || tyT < 0 || tyT >= 64) { continue; } if (afCellTaken[tyT, txT]) { continue; } int iTileT = tmap.GetTileIndex(txT, tyT); if (iTileT != -1) { TileData tdataT = tset.GetTileData(iTileT); if (tdataT.iTemplate != tdata.iTemplate) { continue; } if (tdataT.txTemplate != txTmpl || tdataT.tyTemplate != tyTmpl) { continue; } } afMapped[tyTmpl, txTmpl] = true; afCellTaken[tyT, txT] = true; } } alsTemplPos.Add(new TemplatePos(tmpl, txOrigin, tyOrigin, afMapped)); } } // Figure out the bounds. Rectangle rcBounds = new Rectangle((64 - tmap.Width) / 2, (64 - tmap.Height) / 2, tmap.Width, tmap.Height); lvld.Bounds = rcBounds; // The list of TemplatePos's has been created. Add to LevelDoc. ArrayList alsTiles = new ArrayList(); foreach (TemplatePos tpos in alsTemplPos) { Tile tile = new Tile(tpos.tmpl.Name, tpos.txOrigin + rcBounds.Left, tpos.tyOrigin + rcBounds.Top, tpos.afMapped, tpos.tmpl.OccupancyMap); alsTiles.Add(tile); } lvld.AddMapItems((IMapItem[])alsTiles.ToArray(typeof(IMapItem))); } public static void SaveExpansionPdb(string strFile, Document[] adoc, string strVersion) { // First save all level docs //annoying //DocManager.SaveAllModified(typeof(LevelDoc)); // Remember active document LevelDoc lvldActive = (LevelDoc)DocManager.GetActiveDocument(typeof(LevelDoc)); // Save documents adoc in an expansion .pdb. Expansion .pdbs need: // - .lvl, .tmap, .trmap, but not .tsets since these come from game .pdbs // - need a version.txt // - need an if demo trigger check "inserted" at save time // - .pdb needs WARI creator, ADD1 type // - data should be compressed for obfuscation purposes PdbPacker pdbp = new PdbPacker(); // Add version.txt byte[] abT = new byte[strVersion.Length + 1]; for (int n = 0; n < strVersion.Length; n++) abT[n] = (byte)strVersion[n]; abT[abT.Length - 1] = 0; pdbp.Add(new PdbPacker.File("version.txt", abT)); // Load res.h from embedded resource in prep for pre-process System.Reflection.Assembly ass = typeof(GobImage).Module.Assembly; Stream stmResDotH = ass.GetManifestResourceStream("m.EmbeddedResources." + "res.h"); if (stmResDotH == null) throw new Exception("Cannot load res.h"); // Compile levels Random rand = new Random(); ArrayList alsTileSets = new ArrayList(); foreach (LevelDoc lvld in adoc) { // Need to do this unfortunately; some of the "saving" code relies on what the "active" document // is!! DocManager.SetActiveDocument(typeof(LevelDoc), lvld); TemplateDoc tmpd = lvld.GetTemplateDoc(); // Get appropriate TileSet, or make one if this map // uses a new tile collection TileSet tset = null; foreach (TileSet tsetT in alsTileSets) { if (tsetT.TemplateDoc == tmpd) { tset = tsetT; break; } } // Create new tile set if none found if (tset == null) { tset = new TileSet(tmpd, tmpd.GetName() + ".tset"); alsTileSets.Add(tset); } #if false // Generate base file name for this level (this is never user-visible, but it should be // as unique as possible char[] achBase = new char[16]; for (int n = 0; n < achBase.Length; n++) { int nT = rand.Next() % (26 + 10); if (nT < 26) { achBase[n] = (char)(nT + 97); } else { achBase[n] = (char)(nT + 48 - 26); } } if (lvld.MaxPlayers > 1) { achBase[0] = 'm'; achBase[1] = '_'; } string strBase = new String(achBase); #else // This isn't unique which can cause problems due to how packfiles work. // Could change packfile api to accept .pdb parameter. // Note1: set next mission action requires predictable filename // Note2: mission sorting is based on filename, not title // Could put lots of "support" in to fix these problems, or just ship // it like this. // // Hack: filename length 29 // Maximum extension on filename: 7 string strBase = lvld.Title; if (strBase.Length > 29 - 7) strBase = strBase.Substring(0, 29 - 7); // If multiplayer, add "m_" to the name by losing the last two characters // so sort order is preserved if (lvld.MaxPlayers > 1) strBase = "m_" + strBase.Substring(0, strBase.Length - 2); #endif // Get tile map file for this level MemoryStream stmTmap = new MemoryStream(); TileMap tmap = TileMap.CreateFromImage(tset, lvld.GetMapBitmap(tmpd.TileSize, tmpd, true), tmpd.TileSize); tmap.Save(stmTmap); string strTmap = strBase + ".tmap"; pdbp.Add(new PdbPacker.File(strTmap, stmTmap.ToArray())); stmTmap.Close(); // Get the terrain map file for this level MemoryStream stmTRmap = new MemoryStream(); TerrainMap trmap = new TerrainMap(lvld.GetTerrainMap(tmpd.TileSize, tmpd, false)); trmap.Save(stmTRmap); string strTRmap = strBase + ".trmap"; pdbp.Add(new PdbPacker.File(strTRmap, stmTRmap.ToArray())); stmTRmap.Close(); // Save .ini file for this level doc MemoryStream stmLvld = new MemoryStream(); lvld.SaveIni(stmLvld, -1, strTmap, strTRmap, tmpd.GetName() + ".palbin", true); // Pre-process stmLvld.Seek(0, SeekOrigin.Begin); MemoryStream stmPreprocessed = Misc.PreprocessStream(stmLvld, stmResDotH); stmPreprocessed.Seek(0, SeekOrigin.Begin); Ini iniPreProcessed = new Ini(stmPreprocessed); MemoryStream stmLvldPreProcessedBinary = new MemoryStream(); iniPreProcessed.SaveBinary(stmLvldPreProcessedBinary); stmLvldPreProcessedBinary.Close(); string strLvlName = lvld.OutputFilename; if (strLvlName == null) { strLvlName = strBase + ".lvl"; } pdbp.Add(new PdbPacker.File(strLvlName, stmLvldPreProcessedBinary.ToArray())); stmLvldPreProcessedBinary.Close(); } stmResDotH.Close(); // Restore active document if (lvldActive != null) DocManager.SetActiveDocument(typeof(LevelDoc), lvldActive); // Now save out pdb pdbp.Save(strFile, "WARI", "ADD2"); } public static void MixMapImportSpecial(Theater theater, TemplateDoc tmpdCopyTerrain, string strFileSave) { TemplateDoc tmpd24 = (TemplateDoc)DocManager.NewDocument(typeof(TemplateDoc), new Object[] { new Size(24, 24) }); MixTemplate[] amixt = MixSuck.LoadTemplates(theater); MixSuck.ImportTemplates(amixt, tmpd24); Template[] atmpl24 = tmpd24.GetTemplates(); Template[] atmpl16 = tmpdCopyTerrain.GetTemplates(); for (int n = 0; n < atmpl24.Length; n++) { Template tmpl24 = atmpl24[n]; Template tmpl16 = atmpl16[n]; tmpl24.TerrainMap = tmpl16.TerrainMap; } tmpd24.SetBackgroundTemplate(atmpl24[0]); tmpd24.SaveAs(strFileSave); } public static void MakePalette(string[] astr) { // -makepal 16 templates.tc palsize fixpalsize backgroundpalsize fixed.pal out.pal // tile size Size sizTile = new Size(0, 0); sizTile.Width = int.Parse(astr[1]); sizTile.Height = sizTile.Width; // Load template collection TemplateDoc tmpd = (TemplateDoc)DocManager.OpenDocument(astr[2]); // palette size int cPalEntries = int.Parse(astr[3]); // entries fixed int cPalEntriesFixed = int.Parse(astr[4]); // entries for background int cPalEntriesBackground = int.Parse(astr[5]); // fixed palette Palette palFixed = new Palette(astr[6]); // output palette string strFilePalOut = astr[7]; // If this template collection already has a palette it has already been quantized; we don't // want that. if (tmpd.GetPalette() != null) new Exception("Template collection has already been quantized!"); // Scale templates if needed if (sizTile.Width != tmpd.TileSize.Width || sizTile.Height != tmpd.TileSize.Height) TemplateTools.ScaleTemplates(tmpd, sizTile); // Quantize TemplateTools.QuantizeTemplates(tmpd, palFixed, cPalEntries, cPalEntriesFixed, cPalEntriesBackground); // Save the new palette out Palette palNew = tmpd.GetPalette(); palNew.SaveJasc(strFilePalOut); } public static void ExportMixMaps(string[] astr) { // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 1; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); alsFiles.AddRange(astrFiles); } foreach (string strFile in alsFiles) { LevelDoc lvld = (LevelDoc)DocManager.NewDocument(typeof(LevelDoc), null); Console.Write("Exporting " + strFile + " as "); string strFileExport = MixSuck.ImportExportMixMap(strFile, lvld); if (strFileExport == null) { Console.Write("Error exporting!\n"); } else { Console.Write(strFileExport + "\n"); } lvld.Dispose(); } } public static void ExportImages(string[] astr) { // Get directory //string strDir = Path.GetFullPath(astr[1]); string strDir = "."; string strPrefix = astr[1]; // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 2; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); alsFiles.AddRange(astrFiles); } // Attempt to process these level docs foreach (string strFile in alsFiles) { Console.Write("Map of " + strFile + " -> "); LevelDoc lvld = (LevelDoc)DocManager.OpenDocument(strFile); if (lvld == null) throw new Exception("Could not load level doc " + strFile); string strPng = strDir + Path.DirectorySeparatorChar + strPrefix + Path.GetFileName(strFile).Replace(".ld", ".png"); Console.Write(strPng + "..."); TemplateDoc tmpd = lvld.GetTemplateDoc(); Bitmap bm = lvld.GetMapBitmap(tmpd.TileSize, tmpd, true); bm.Save(strPng, ImageFormat.Png); bm.Dispose(); lvld.Dispose(); Console.Write(" Done.\n"); } } public static void ExportLevels(string[] astr, int nVersion) { // Get tile size Size sizTile = new Size(0, 0); sizTile.Width = int.Parse(astr[1]); sizTile.Height = sizTile.Width; // Get depth int nDepth = Int32.Parse(astr[2]); // Get background threshold double nAreaBackgroundThreshold = double.Parse(astr[3]); // Background luminance multiplier double nLuminanceMultBackground = double.Parse(astr[4]); // Background saturation multiplier double nSaturationMultBackground = double.Parse(astr[5]); // Foreground luminance multiplier double nLuminanceMultForeground = double.Parse(astr[6]); // Foreground saturation multiplier double nSaturationMultForeground = double.Parse(astr[7]); // Palette directory string strPalDir = astr[8]; // Get output directory string strDir = Path.GetFullPath(astr[9]); // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 9; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); alsFiles.AddRange(astrFiles); } // Attempt to process these level doc ArrayList alsTileSets = new ArrayList(); foreach (string strFile in alsFiles) { Console.Write("Loading " + strFile + "..."); LevelDoc lvld = (LevelDoc)DocManager.OpenDocument(strFile); if (lvld == null) throw new Exception("Could not load level doc " + strFile); Console.Write(" Done.\n"); // Size this template collection if necessary TemplateDoc tmpd = lvld.GetTemplateDoc(); if (sizTile.Width != tmpd.TileSize.Width || sizTile.Height != tmpd.TileSize.Height) TemplateTools.ScaleTemplates(tmpd, sizTile); // Get appropriate TileSet, or make one if this map // uses a new tile collection TileSet tset = null; foreach (TileSet tsetT in alsTileSets) { if (tsetT.TileCollectionFileName == Path.GetFileName(lvld.GetTemplateDoc().GetPath())) { tset = tsetT; break; } } // Create new tile set if none found if (tset == null) { string strTcPath = lvld.GetTemplateDoc().GetPath(); string strTcName = Path.GetFileNameWithoutExtension(strTcPath); string strTcDir = Path.GetDirectoryName(strTcPath); string strT3 = strTcDir + Path.DirectorySeparatorChar + strPalDir + Path.DirectorySeparatorChar + strTcName; tset = new TileSet(lvld.GetTemplateDoc(), Path.GetFileName(strTcPath), strT3, nDepth, sizTile); alsTileSets.Add(tset); } // Get and save a tile map for this level string strTmap = Path.GetFileName(lvld.GetPath().Replace(".ld", ".tmap")); string strFileTmap = strDir + Path.DirectorySeparatorChar + strTmap; Console.Write("Creating & writing " + strFileTmap + "..."); TileMap tmap = TileMap.CreateFromImage(tset, lvld.GetMapBitmap(tmpd.TileSize, tmpd, true), tmpd.TileSize); tmap.Save(strFileTmap); Console.Write(" Done.\n"); // Get and save terrain map for this level string strTrmap = Path.GetFileName(lvld.GetPath().Replace(".ld", ".trmap")); string strFileTrmap = strDir + Path.DirectorySeparatorChar + strTrmap; Console.Write("Creating & writing " + strFileTrmap + "..."); TerrainMap trmap = new TerrainMap(lvld.GetTerrainMap(tmpd.TileSize, tmpd, false)); trmap.Save(strFileTrmap); Console.Write(" Done.\n"); // Save .ini for this level doc string strFileIni = strDir + Path.DirectorySeparatorChar + Path.GetFileName(strFile).Replace(".ld", ".lvl"); Console.Write("Writing " + strFileIni + "..."); lvld.SaveIni(strFileIni, nVersion, strTmap, strTrmap, tset.PalBinFileName); Console.Write(" Done.\n"); lvld.Dispose(); } // Now write out tilesets foreach (TileSet tset in alsTileSets) { // Save tile set string strFileTset = strDir + Path.DirectorySeparatorChar + tset.FileName; Console.Write("Creating & writing " + strFileTset + ", " + tset.Count.ToString() + " tiles..."); tset.Save(strFileTset); tset.SaveMini(strFileTset, nAreaBackgroundThreshold, nLuminanceMultBackground, nSaturationMultBackground, nLuminanceMultForeground, nSaturationMultForeground); Console.Write(" Done.\n"); } } public static void ExportText(string[] astr) { // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 1; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); alsFiles.AddRange(astrFiles); } Console.WriteLine("Exporting text from {0} files", alsFiles.Count); // Attempt to process these level docs foreach (string strFile in alsFiles) { Console.Write("Writing text of " + strFile + "..."); LevelDoc lvld = (LevelDoc)DocManager.OpenDocument(strFile); if (lvld == null) throw new Exception("Could not load level doc " + strFile); string strTextFile = "." + Path.DirectorySeparatorChar + Path.GetFileName(strFile).Replace(".ld", ".txt"); string str = lvld.GetLevelText(); str = str.Replace("\n", "\r\n"); StreamWriter stmw = new StreamWriter(strTextFile); stmw.Write(str); stmw.Close(); lvld.Dispose(); Console.WriteLine(" done"); } } public static void ImportText(string[] astr) { // Expand filespecs ArrayList alsFiles = new ArrayList(); for (int n = 1; n < astr.Length; n++) { string strFileT = Path.GetFileName(astr[n]); string strDirT = Path.GetDirectoryName(astr[n]); if (strDirT == "") strDirT = "."; string[] astrFiles = Directory.GetFiles(strDirT, strFileT); alsFiles.AddRange(astrFiles); } Console.WriteLine("Importing text from {0} files", alsFiles.Count); // Attempt to process these text files/level docs foreach (string strTextFile in alsFiles) { string strFile = "." + Path.DirectorySeparatorChar + Path.GetFileName(strTextFile).Replace(".txt", ".ld"); Console.Write("Writing " + strTextFile + " to " + strFile + "..."); LevelDoc lvld = (LevelDoc)DocManager.OpenDocument(strFile); if (lvld == null) throw new Exception("Could not load level doc " + strFile); StreamReader stmr = new StreamReader(strTextFile); string str = stmr.ReadToEnd(); stmr.Close(); str = str.Replace("\r\n", "\n"); int ichErrorPos; if (!lvld.SetLevelText(str, out ichErrorPos)) { Console.WriteLine(" error at char " + ichErrorPos); } else { lvld.Save(); Console.WriteLine(" saved"); } lvld.Dispose(); } } } class TerrainMap { private TerrainTypes[,] m_aterMap; public TerrainMap(TerrainTypes[,] aterMap) { m_aterMap = aterMap; } public void Save(string strFileTrmap) { Stream stm = new FileStream(strFileTrmap, FileMode.Create, FileAccess.Write, FileShare.None); Save(stm); } public void Save(Stream stm) { BinaryWriter bwtr = new BinaryWriter(stm); //struct TerrainMapHeader { // trmhdr // word ctx; // word cty; //}; bwtr.Write(Misc.SwapUShort((ushort)m_aterMap.GetLength(1))); bwtr.Write(Misc.SwapUShort((ushort)m_aterMap.GetLength(0))); for (int ty = 0; ty < m_aterMap.GetLength(0); ty++) { for (int tx = 0; tx < m_aterMap.GetLength(1); tx++) { byte b = 0; switch (m_aterMap[ty, tx]) { default: case TerrainTypes.Open: b = 1; break; case TerrainTypes.Area: b = 0; break; case TerrainTypes.Wall: b = 2; break; case TerrainTypes.Blocked: b = 3; break; } bwtr.Write(b); } } bwtr.Close(); } public static TerrainMap Load(MemoryStream stm) { BinaryReader brdr = new BinaryReader(stm); int ctx = (int)Misc.SwapUShort(brdr.ReadUInt16()); int cty = (int)Misc.SwapUShort(brdr.ReadUInt16()); TerrainTypes[,] map = new TerrainTypes[cty, ctx]; for (int ty = 0; ty < cty; ty++) { for (int tx = 0; tx < ctx; tx++) { byte b = brdr.ReadByte(); switch (b) { case 0: // Area map[ty, tx] = TerrainTypes.Area; break; case 1: // Open map[ty, tx] = TerrainTypes.Open; break; case 2: // Wall map[ty, tx] = TerrainTypes.Wall; break; case 3: // Blocked map[ty, tx] = TerrainTypes.Blocked; break; default: // Default to Open map[ty, tx] = TerrainTypes.Open; break; } } } brdr.Close(); return new TerrainMap(map); } public TerrainTypes[,] Map { get { return m_aterMap; } } } class TileMap { int m_ctx; int m_cty; Size m_sizTile; int[,] m_aiTile; string m_strTSetFileName; public TileMap(string strTSetFileName, int ctx, int cty, Size sizTile) { m_strTSetFileName = strTSetFileName; m_ctx = ctx; m_cty = cty; m_sizTile = sizTile; m_aiTile = new int[cty, ctx]; } public TileMap(string strTSetFileName, int ctx, int cty, int[,] aiTile, Size sizTile) { m_strTSetFileName = strTSetFileName; m_ctx = ctx; m_cty = cty; m_aiTile = aiTile; m_sizTile = sizTile; } public string Filename { get { return m_strTSetFileName; } } public int Width { get { return m_ctx; } } public int Height { get { return m_cty; } } public static TileMap CreateFromImage(TileSet tset, Bitmap bm, Size sizTile) { int ctx = bm.Width / sizTile.Width; int cty = bm.Height / sizTile.Height; TileMap tmap = new TileMap(tset.FileName, ctx, cty, sizTile); tmap.InitFromImage(tset, bm); return tmap; } public void InitFromImage(TileSet tset, Bitmap bm) { Color[] aclrTile = new Color[m_sizTile.Width * m_sizTile.Height]; for (int ty = 0; ty < m_cty; ty++) { for (int tx = 0; tx < m_ctx; tx++) { TileSet.ExtractTilePixels(bm, tx, ty, m_sizTile, ref aclrTile); int index = tset.FindTileIndex(aclrTile); if (index == -1) throw new Exception("Couldn't find tile index!"); m_aiTile[ty, tx] = index; } } } public void Save(string strFileTmap) { Stream stm = new FileStream(strFileTmap, FileMode.Create, FileAccess.Write, FileShare.None); Save(stm); } public void Save(Stream stm) { BinaryWriter bwtr = new BinaryWriter(stm); // #define kcbFilename 28 // struct TileMapHeader { // thdr // char szFnTset[kcbFilename]; // word ctx; // word cty; // }; char[] szFnTset = new char[28]; m_strTSetFileName.CopyTo(0, szFnTset, 0, m_strTSetFileName.Length); bwtr.Write(szFnTset); bwtr.Write(Misc.SwapUShort((ushort)m_ctx)); bwtr.Write(Misc.SwapUShort((ushort)m_cty)); for (int ty = 0; ty < m_cty; ty++) { for (int tx = 0; tx < m_ctx; tx++) { bwtr.Write(Misc.SwapUShort((ushort)(m_aiTile[ty, tx] << 2))); } } bwtr.Close(); } public static TileMap Load(MemoryStream stm) { BinaryReader brdr = new BinaryReader(stm); byte[] ab = brdr.ReadBytes(28); int i = 0; for (; i < ab.Length; i++) { if (ab[i] == 0) { break; } } string strFilename = Encoding.ASCII.GetString(ab, 0, i); int ctx = Misc.SwapUShort(brdr.ReadUInt16()); int cty = Misc.SwapUShort(brdr.ReadUInt16()); int [,] aiTile = new int[cty, ctx]; for (int ty = 0; ty < cty; ty++) { for (int tx = 0; tx < ctx; tx++) { aiTile[ty, tx] = Misc.SwapUShort(brdr.ReadUInt16()) >> 2; } } brdr.Close(); Size sizTile = new Size(24, 24); // hardwired return new TileMap(strFilename, ctx, cty, aiTile, sizTile); } public int GetTileIndex(int tx, int ty) { if (tx < 0 || tx >= m_aiTile.GetLength(1)) { return -1; } if (ty < 0 || ty >= m_aiTile.GetLength(0)) { return -1; } return m_aiTile[ty, tx]; } } struct TileData { public int hash; public Color[] aclr; public int iTemplate; public int txTemplate; public int tyTemplate; } class TileSet { public string FileName; public string TileCollectionFileName; public string PalBinFileName; public TemplateDoc TemplateDoc; private ArrayList m_alsTileData = new ArrayList(); private Palette m_pal = null; private static int s_cbFileMax = 32000; Size m_sizTile; public TileSet(TemplateDoc tmpd, string strFile) { TemplateDoc = tmpd; m_pal = tmpd.GetPalette(); m_sizTile = tmpd.TileSize; FileName = strFile.Replace(".tc", ".tset"); SuckTemplates(); } public TileSet(TemplateDoc tmpd, string strFile, string strFilePal, int nDepth, Size sizTile) { TemplateDoc = tmpd; m_pal = new Palette(strFilePal + "_" + nDepth.ToString() + "bpp.pal"); TileCollectionFileName = strFile; FileName = strFile.Replace(".tc", ".tset"); PalBinFileName = Path.GetFileName(strFilePal) + ".palbin"; m_sizTile = sizTile; SuckTemplates(); } private void SuckTemplates() { // Suck all the tiles in Template[] atmpl = TemplateDoc.GetTemplates(); int iTemplate = 0; foreach (Template tmpl in atmpl) { bool[,] afOccupancy = tmpl.OccupancyMap; for (int ty = 0; ty < afOccupancy.GetLength(0); ty++) { for (int tx = 0; tx < afOccupancy.GetLength(1); tx++) { if (!afOccupancy[ty, tx]) continue; TileData tdata = new TileData(); tdata.iTemplate = iTemplate; tdata.txTemplate = tx; tdata.tyTemplate = ty; tdata.aclr = new Color[m_sizTile.Width * m_sizTile.Height]; ExtractTilePixels(tmpl.Bitmap, tx, ty, m_sizTile, ref tdata.aclr); tdata.hash = HashTile(tdata.aclr); m_alsTileData.Add(tdata); } } iTemplate++; } } public static unsafe void ExtractTilePixels(Bitmap bm, int tx, int ty, Size sizTile, ref Color[] aclrTile) { Rectangle rcSrc = new Rectangle(tx * sizTile.Width, ty * sizTile.Height, sizTile.Width, sizTile.Height); BitmapData bmd = bm.LockBits(rcSrc, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); IntPtr p = bmd.Scan0; byte *pbBase = (byte *)p.ToPointer(); for (int y = 0; y < sizTile.Height; y++) { for (int x = 0; x < sizTile.Width; x++) { byte *pb = pbBase + y * bmd.Stride + x * 3; aclrTile[y * sizTile.Width + x] = Color.FromArgb(pb[2], pb[1], pb[0]); } } bm.UnlockBits(bmd); } public int FindTileIndex(Color[] aclrTile) { int hash = HashTile(aclrTile); for (int n = 0; n < m_alsTileData.Count; n++) { TileData td = (TileData)m_alsTileData[n]; if (hash == td.hash && CheckTileMatch(aclrTile, td.aclr)) return n; } return -1; } int HashTile(Color[] aclrTile) { int hash = 0; for (int n = 0; n < aclrTile.Length; n++) { Color clr = aclrTile[n]; hash += (hash << 1) | (clr.R << 16) + (clr.G << 8) + clr.B; } return hash; } bool CheckTileMatch(Color[] aclr1, Color[] aclr2) { for (int n = 0; n < aclr1.Length; n++) { if (aclr1[n] != aclr2[n]) return false; } return true; } public void Save(string strFileTset) { // struct TileSetHeader { // tshdr // ushort cTiles; // ushort cxTile; // ushort cyTile; // }; int cbTile = m_sizTile.Width * m_sizTile.Height; int cTilesFit = (s_cbFileMax - 6) / cbTile; int cTilesLeft = m_alsTileData.Count; int nFile = 0; int nTile = 0; while (cTilesLeft != 0) { // Get filename string strT = strFileTset; if (nFile > 0) strT = strFileTset.Replace(".tset", ".tset." + nFile.ToString()); nFile++; // Open file for writing Stream stm = new FileStream(strT, FileMode.Create, FileAccess.Write, FileShare.None); BinaryWriter bwtr = new BinaryWriter(stm); // Figure out how many tiles will go in this file int cTilesWrite = Math.Min(cTilesFit, cTilesLeft); // Write out the header bwtr.Write(Misc.SwapUShort((ushort)cTilesWrite)); bwtr.Write(Misc.SwapUShort((ushort)m_sizTile.Width)); bwtr.Write(Misc.SwapUShort((ushort)m_sizTile.Height)); // Write out the tiles for (int n = 0; n < cTilesWrite; n++) { bwtr.Write(GetTileBytes(nTile)); nTile++; } // Next file bwtr.Close(); cTilesLeft -= cTilesWrite; } } public void SaveMini(string strFileTset, double nAreaBackgroundThreshold, double nLuminanceMultBackground, double nSaturationMultBackground, double nLuminanceMultForeground, double nSaturationMultForeground) { // Now write out minimap tiles, 2x2 and 1x1 Stream stmT = new FileStream(strFileTset.Replace(".tset", ".tsetmini"), FileMode.Create, FileAccess.Write, FileShare.None); BinaryWriter bwtrMini = new BinaryWriter(stmT); WriteMiniTiles(bwtrMini, m_pal, TemplateDoc, 2, true, nAreaBackgroundThreshold, nLuminanceMultBackground, nSaturationMultBackground, nLuminanceMultForeground, nSaturationMultForeground); WriteMiniTiles(bwtrMini, m_pal, TemplateDoc, 1, false, nAreaBackgroundThreshold, nLuminanceMultBackground, nSaturationMultBackground, nLuminanceMultForeground, nSaturationMultForeground); bwtrMini.Close(); } void WriteMiniTiles(BinaryWriter bwtr, Palette pal, TemplateDoc tmpd, int cx, bool fNext, double nAreaBackgroundThreshold, double nLuminanceMultBackground, double nSaturationMultBackground, double nLuminanceMultForeground, double nSaturationMultForeground) { // struct MiniTileSetHeader { // mtshdr // ushort offNext; // ushort cTiles; // ushort cxTile; // ushort cyTile; // }; ushort offNext = 0; if (fNext) offNext = (ushort)(8 + m_alsTileData.Count * cx * cx); bwtr.Write(Misc.SwapUShort(offNext)); bwtr.Write(Misc.SwapUShort((ushort)m_alsTileData.Count)); bwtr.Write(Misc.SwapUShort((ushort)cx)); bwtr.Write(Misc.SwapUShort((ushort)cx)); // If a background template exists, use it to distinguish foreground from background objects for better minimaps Size sizTile = tmpd.TileSize; ArrayList alsColors = new ArrayList(); Template tmplBackground = tmpd.GetBackgroundTemplate(); if (tmplBackground != null && nAreaBackgroundThreshold >= 0.0) { // Get despeckled hue map of background, calc mean and // std dev for filtering purposes Bitmap bmHueBackground = TemplateTools.MakeHueMap(tmplBackground.Bitmap); TemplateTools.DespeckleGrayscaleBitmap(bmHueBackground, 9, 50); double nMean = TemplateTools.CalcGrayscaleMean(bmHueBackground); double nStdDev = TemplateTools.CalcGrayscaleStandardDeviation(bmHueBackground, nMean); // Go through each tile, first make a mask that'll delineate foreground from background Bitmap bmTile = new Bitmap(sizTile.Width, sizTile.Height); foreach (TileData td in m_alsTileData) { // Need to turn data back into a bitmap - doh! for (int y = 0; y < sizTile.Height; y++) { for (int x = 0; x < sizTile.Width; x++) { Color clr = (Color)td.aclr[y * sizTile.Width + x]; bmTile.SetPixel(x, y, clr); } } // Create mask which'll replace background with transparent color (255, 0, 255) Bitmap bmMask = TemplateTools.MakeHueMap(bmTile); TemplateTools.DespeckleGrayscaleBitmap(bmMask, 9, 50); TemplateTools.SubtractGrayscaleDistribution(bmMask, nMean, nStdDev); // Now scale tile down to desired size, using mask as input Bitmap bmScaled = TemplateTools.ScaleTemplateBitmap(bmTile, bmMask, cx, cx, nAreaBackgroundThreshold, nLuminanceMultBackground, nSaturationMultBackground, nLuminanceMultForeground, nSaturationMultForeground); // Grab the data for (int y = 0; y < cx; y++) { for (int x = 0; x < cx; x++) { alsColors.Add(bmScaled.GetPixel(x, y)); } } bmScaled.Dispose(); bmMask.Dispose(); } } else { // No background template; just scale Bitmap bmTile = new Bitmap(sizTile.Width, sizTile.Height); foreach (TileData td in m_alsTileData) { // Need to turn data back into a bitmap - doh! for (int y = 0; y < sizTile.Height; y++) { for (int x = 0; x < sizTile.Width; x++) { Color clr = (Color)td.aclr[y * sizTile.Width + x]; bmTile.SetPixel(x, y, clr); } } // Now scale tile down to desired size, using mask as input Bitmap bmScaled = TemplateTools.ScaleTemplateBitmap(bmTile, null, cx, cx, 1.0, 1.0, 1.0, 1.0, 1.0); // Grab the data for (int y = 0; y < cx; y++) { for (int x = 0; x < cx; x++) { alsColors.Add(bmScaled.GetPixel(x, y)); } } bmScaled.Dispose(); } } // Palette match and write results foreach (Color clr in alsColors) bwtr.Write((byte)pal.FindClosestEntry(clr)); } byte[] GetTileBytes(int nTile) { byte[] ab = new byte[m_sizTile.Width * m_sizTile.Height]; TileData tdata = (TileData)m_alsTileData[nTile]; for (int n = 0; n < tdata.aclr.Length; n++) ab[n] = (byte)m_pal.FindClosestEntry(tdata.aclr[n]); return ab; } public int Count { get { return m_alsTileData.Count; } } public TileData GetTileData(int iTile) { return (TileData)m_alsTileData[iTile]; } } }