static void ExtractMaps() { ExtractDbcFiles(); ExtractCameraFiles(); ExtractGameTablesFiles(); Console.WriteLine("Extracting maps..."); string path = $"{BaseDirectory}/maps"; CreateDirectory(path); Console.WriteLine("Convert map files"); int count = 1; Console.WriteLine("Loading DB2 files"); var mapStorage = DBReader.Read <MapRecord>("DBFilesClient\\Map.db2"); if (mapStorage == null) { Console.WriteLine("Fatal error: Invalid Map.db2 file format!"); return; } foreach (var record in mapStorage.Values) { Console.Write($"Extract {record.Directory} ({count++}/{mapStorage.Count}) \n"); // Loadup map grid data string storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}.wdt"; ChunkedFile wdt = new ChunkedFile(); if (wdt.loadFile(CascHandler, storagePath)) { wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>(); for (int y = 0; y < 64; ++y) { for (int x = 0; x < 64; ++x) { if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1)) { storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt"; string outputFileName = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map"; MapFile.ConvertADT(CascHandler, storagePath, outputFileName, y, x, MapFile.IsDeepWaterIgnored(record.Id, y, x)); } } // draw progress bar Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r"); } } } Console.WriteLine("\n"); }
static void ExtractMaps(uint build) { Console.WriteLine("Extracting maps...\n"); string path = $"{baseDirectory}/maps"; CreateDirectory(path); Console.WriteLine("Convert map files\n"); int count = 1; foreach (var record in CliDB.MapStorage.Values) { Console.Write($"Extract {record.Directory} ({count++}/{CliDB.MapStorage.Count}) \n"); // Loadup map grid data string storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}.wdt"; ChunkedFile wdt = new ChunkedFile(); if (wdt.loadFile(cascHandler, storagePath)) { wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>(); for (int y = 0; y < 64; ++y) { for (int x = 0; x < 64; ++x) { if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1)) { storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt"; string outputFileName = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map"; bool ignoreDeepWater = MapFile.IsDeepWaterIgnored(record.Id, y, x); MapFile.ConvertADT(cascHandler, storagePath, outputFileName, y, x, build, ignoreDeepWater); } } // draw progress bar Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r"); } } } Console.WriteLine("\n"); }
public static bool ConvertADT(CASCHandler cascHandler, string inputPath, string outputPath, int cell_y, int cell_x, uint build) { ChunkedFile adt = new ChunkedFile(); if (!adt.loadFile(cascHandler, inputPath)) { return(false); } // Prepare map header map_fileheader map; map.mapMagic = MAP_MAGIC; map.versionMagic = MAP_VERSION_MAGIC; map.buildMagic = build; // Get area flags data for (var x = 0; x < ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < ADT_CELLS_PER_GRID; ++y) { area_ids[x][y] = 0; liquid_entry[x][y] = 0; liquid_flags[x][y] = 0; } } for (var x = 0; x < ADT_GRID_SIZE; ++x) { for (var y = 0; y < ADT_GRID_SIZE; ++y) { V8[x][y] = 0; liquid_show[x][y] = false; } } for (var x = 0; x < ADT_GRID_SIZE + 1; ++x) { for (var y = 0; y < ADT_GRID_SIZE + 1; ++y) { V9[x][y] = 0; } } for (var x = 0; x < ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < ADT_CELLS_PER_GRID; ++y) { for (var z = 0; z < 8; ++z) { holes[x][y][z] = 0; } } } bool hasHoles = false; bool hasFlightBox = false; foreach (var fileChunk in adt.chunks.LookupByKey("MCNK")) { adt_MCNK mcnk = fileChunk.As <adt_MCNK>(); // Area data area_ids[mcnk.iy][mcnk.ix] = (ushort)mcnk.areaid; // Height // Height values for triangles stored in order: // 1 2 3 4 5 6 7 8 9 // 10 11 12 13 14 15 16 17 // 18 19 20 21 22 23 24 25 26 // 27 28 29 30 31 32 33 34 // . . . . . . . . // For better get height values merge it to V9 and V8 map // V9 height map: // 1 2 3 4 5 6 7 8 9 // 18 19 20 21 22 23 24 25 26 // . . . . . . . . // V8 height map: // 10 11 12 13 14 15 16 17 // 27 28 29 30 31 32 33 34 // . . . . . . . . // Set map height as grid height for (int y = 0; y <= ADT_CELL_SIZE; y++) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x <= ADT_CELL_SIZE; x++) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; V9[cy][cx] = mcnk.ypos; } } for (int y = 0; y < ADT_CELL_SIZE; y++) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x < ADT_CELL_SIZE; x++) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; V8[cy][cx] = mcnk.ypos; } } // Get custom height FileChunk chunk = fileChunk.GetSubChunk("MCVT"); if (chunk != null) { adt_MCVT mcvt = chunk.As <adt_MCVT>(); // get V9 height map for (int y = 0; y <= ADT_CELL_SIZE; y++) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x <= ADT_CELL_SIZE; x++) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; V9[cy][cx] += mcvt.height_map[y * (ADT_CELL_SIZE * 2 + 1) + x]; } } // get V8 height map for (int y = 0; y < ADT_CELL_SIZE; y++) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x < ADT_CELL_SIZE; x++) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; V8[cy][cx] += mcvt.height_map[y * (ADT_CELL_SIZE * 2 + 1) + ADT_CELL_SIZE + 1 + x]; } } } // Liquid data if (mcnk.sizeMCLQ > 8) { FileChunk LiquidChunk = fileChunk.GetSubChunk("MCLQ"); if (LiquidChunk != null) { adt_MCLQ liquid = LiquidChunk.As <adt_MCLQ>(); int count = 0; for (int y = 0; y < ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x < ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; if (liquid.flags[y][x] != 0x0F) { liquid_show[cy][cx] = true; if (Convert.ToBoolean(liquid.flags[y][x] & (1 << 7))) { liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.DarkWater; } ++count; } } } uint c_flag = mcnk.flags; if (Convert.ToBoolean(c_flag & (1 << 2))) { liquid_entry[mcnk.iy][mcnk.ix] = 1; liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Water; // water } if (Convert.ToBoolean(c_flag & (1 << 3))) { liquid_entry[mcnk.iy][mcnk.ix] = 2; liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Ocean; // ocean } if (Convert.ToBoolean(c_flag & (1 << 4))) { liquid_entry[mcnk.iy][mcnk.ix] = 3; liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Magma; // magma/slime } if (count == 0 && liquid_flags[mcnk.iy][mcnk.ix] != 0) { Console.WriteLine("Wrong liquid detect in MCLQ chunk"); } for (int y = 0; y <= ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.iy * ADT_CELL_SIZE + y; for (int x = 0; x <= ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.ix * ADT_CELL_SIZE + x; liquid_height[cy][cx] = liquid.liquid[y][x].height; } } } } // Hole data if (!Convert.ToBoolean(mcnk.flags & 0x10000)) { uint hole = mcnk.holes; if (hole != 0) { if (TransformToHighRes((ushort)hole, holes[mcnk.iy][mcnk.ix])) { hasHoles = true; } } } else { Buffer.BlockCopy(mcnk.HighResHoles, 0, holes[mcnk.iy][mcnk.ix], 0, 8); if (BitConverter.ToUInt64(holes[mcnk.iy][mcnk.ix], 0) != 0) { hasHoles = true; } } } // Get liquid map for grid (in WOTLK used MH2O chunk) FileChunk chunkMH20 = adt.GetChunk("MH2O"); if (chunkMH20 != null) { adt_MH2O h2o = chunkMH20.As <adt_MH2O>(); for (int i = 0; i < ADT_CELLS_PER_GRID; i++) { for (int j = 0; j < ADT_CELLS_PER_GRID; j++) { adt_liquid_header?h = h2o.getLiquidData(i, j); if (!h.HasValue) { continue; } adt_liquid_header adtLiquidHeader = h.Value; int count = 0; ulong show = h2o.getLiquidShowMap(adtLiquidHeader); for (int y = 0; y < adtLiquidHeader.height; y++) { int cy = i * ADT_CELL_SIZE + y + adtLiquidHeader.yOffset; for (int x = 0; x < adtLiquidHeader.width; x++) { int cx = j * ADT_CELL_SIZE + x + adtLiquidHeader.xOffset; if (Convert.ToBoolean(show & 1)) { liquid_show[cy][cx] = true; ++count; } show >>= 1; } } liquid_entry[i][j] = adtLiquidHeader.liquidType; var liquidTypeRecord = Program.liquidTypeStorage[adtLiquidHeader.liquidType]; switch ((LiquidType)liquidTypeRecord.LiquidType) { case LiquidType.Water: liquid_flags[i][j] |= (byte)LiquidTypeMask.Water; break; case LiquidType.Ocean: liquid_flags[i][j] |= (byte)LiquidTypeMask.Ocean; break; case LiquidType.Magma: liquid_flags[i][j] |= (byte)LiquidTypeMask.Magma; break; case LiquidType.Slime: liquid_flags[i][j] |= (byte)LiquidTypeMask.Slime; break; default: Console.WriteLine($"\nCan't find Liquid type {adtLiquidHeader.liquidType} for map {inputPath}\nchunk {i},{j}\n"); break; } // Dark water detect if ((LiquidType)liquidTypeRecord.LiquidType == LiquidType.Ocean) { byte[] lm = h2o.getLiquidLightMap(adtLiquidHeader); if (lm == null) { liquid_flags[i][j] |= (byte)LiquidTypeMask.DarkWater; } } if (count == 0 && liquid_flags[i][j] != 0) { Console.WriteLine("Wrong liquid detect in MH2O chunk"); } float[] height = h2o.getLiquidHeightMap(adtLiquidHeader); int pos = 0; for (int y = 0; y <= adtLiquidHeader.height; y++) { int cy = i * ADT_CELL_SIZE + y + adtLiquidHeader.yOffset; for (int x = 0; x <= adtLiquidHeader.width; x++) { int cx = j * ADT_CELL_SIZE + x + adtLiquidHeader.xOffset; if (height != null) { liquid_height[cy][cx] = height[pos]; } else { liquid_height[cy][cx] = adtLiquidHeader.heightLevel1; } pos++; } } } } } FileChunk chunkMFBO = adt.GetChunk("MFBO"); if (chunkMFBO != null) { adt_MFBO mfbo = chunkMFBO.As <adt_MFBO>(); for (var i = 0; i < 3; ++i) { flight_box_max[i][0] = mfbo.max.coords[0 + i * 3]; flight_box_max[i][1] = mfbo.max.coords[1 + i * 3]; flight_box_max[i][2] = mfbo.max.coords[2 + i * 3]; flight_box_min[i][0] = mfbo.min.coords[0 + i * 3]; flight_box_min[i][1] = mfbo.min.coords[1 + i * 3]; flight_box_min[i][2] = mfbo.min.coords[2 + i * 3]; } hasFlightBox = true; } //============================================ // Try pack area data //============================================ bool fullAreaData = false; uint areaId = area_ids[0][0]; for (int y = 0; y < ADT_CELLS_PER_GRID; ++y) { for (int x = 0; x < ADT_CELLS_PER_GRID; ++x) { if (area_ids[y][x] != areaId) { fullAreaData = true; break; } } } map.areaMapOffset = 44; map.areaMapSize = 8; map_areaHeader areaHeader; areaHeader.fourcc = MAP_AREA_MAGIC; areaHeader.flags = 0; if (fullAreaData) { areaHeader.gridArea = 0; map.areaMapSize += 512; } else { areaHeader.flags |= 0x0001; areaHeader.gridArea = (ushort)areaId; } //============================================ // Try pack height data //============================================ float maxHeight = -20000; float minHeight = 20000; for (int y = 0; y < ADT_GRID_SIZE; y++) { for (int x = 0; x < ADT_GRID_SIZE; x++) { float h = V8[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } for (int y = 0; y <= ADT_GRID_SIZE; y++) { for (int x = 0; x <= ADT_GRID_SIZE; x++) { float h = V9[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } // Check for allow limit minimum height (not store height in deep ochean - allow save some memory) if (minHeight < -500.0f) { for (int y = 0; y < ADT_GRID_SIZE; y++) { for (int x = 0; x < ADT_GRID_SIZE; x++) { if (V8[y][x] < -500.0f) { V8[y][x] = -500.0f; } } } for (int y = 0; y <= ADT_GRID_SIZE; y++) { for (int x = 0; x <= ADT_GRID_SIZE; x++) { if (V9[y][x] < -500.0f) { V9[y][x] = -500.0f; } } } if (minHeight < -500.0f) { minHeight = -500.0f; } if (maxHeight < -500.0f) { maxHeight = -500.0f; } } map.heightMapOffset = map.areaMapOffset + map.areaMapSize; map.heightMapSize = (uint)Marshal.SizeOf <map_heightHeader>(); map_heightHeader heightHeader; heightHeader.fourcc = MAP_HEIGHT_MAGIC; heightHeader.flags = 0; heightHeader.gridHeight = minHeight; heightHeader.gridMaxHeight = maxHeight; if (maxHeight == minHeight) { heightHeader.flags |= (uint)MapHeightFlags.NoHeight; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.005f) { heightHeader.flags |= (uint)MapHeightFlags.NoHeight; } if (hasFlightBox) { heightHeader.flags |= (uint)MapHeightFlags.HasFlightBounds; map.heightMapSize += 18 + 18; } // Try store as packed in uint16 or uint8 values if (!Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.NoHeight)) { float step = 0; // Try Store as uint values if (true)//CONF_allow_float_to_int { float diff = maxHeight - minHeight; if (diff < 2.0f) // As uint8 (max accuracy = CONF_float_to_int8_limit/256) { heightHeader.flags |= (uint)MapHeightFlags.AsInt8; step = 255 / diff; } else if (diff < 2048.0f) // As uint16 (max accuracy = CONF_float_to_int16_limit/65536) { heightHeader.flags |= (uint)MapHeightFlags.AsInt16; step = 65535 / diff; } } // Pack it to int values if need if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt8)) { for (int y = 0; y < ADT_GRID_SIZE; y++) { for (int x = 0; x < ADT_GRID_SIZE; x++) { uint8_V8[y][x] = (byte)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= ADT_GRID_SIZE; y++) { for (int x = 0; x <= ADT_GRID_SIZE; x++) { uint8_V9[y][x] = (byte)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 16641 + 16384; } else if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt16)) { for (int y = 0; y < ADT_GRID_SIZE; y++) { for (int x = 0; x < ADT_GRID_SIZE; x++) { uint16_V8[y][x] = (ushort)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= ADT_GRID_SIZE; y++) { for (int x = 0; x <= ADT_GRID_SIZE; x++) { uint16_V9[y][x] = (ushort)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 33282 + 32768; } else { map.heightMapSize += 66564 + 65536; } } //============================================ // Pack liquid data //============================================ byte type = liquid_flags[0][0]; bool fullType = false; for (int y = 0; y < ADT_CELLS_PER_GRID; y++) { for (int x = 0; x < ADT_CELLS_PER_GRID; x++) { if (liquid_flags[y][x] != type) { fullType = true; y = ADT_CELLS_PER_GRID; break; } } } map_liquidHeader mapLiquidHeader = new map_liquidHeader(); // no water data (if all grid have 0 liquid type) if (type == 0 && !fullType) { // No liquid data map.liquidMapOffset = 0; map.liquidMapSize = 0; } else { int minX = 255, minY = 255; int maxX = 0, maxY = 0; maxHeight = -20000; minHeight = 20000; for (int y = 0; y < ADT_GRID_SIZE; y++) { for (int x = 0; x < ADT_GRID_SIZE; x++) { if (liquid_show[y][x]) { if (minX > x) { minX = x; } if (maxX < x) { maxX = x; } if (minY > y) { minY = y; } if (maxY < y) { maxY = y; } float h = liquid_height[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } else { liquid_height[y][x] = -500.0f; } } } map.liquidMapOffset = map.heightMapOffset + map.heightMapSize; map.liquidMapSize = (uint)Marshal.SizeOf <map_liquidHeader>(); mapLiquidHeader.fourcc = MAP_LIQUID_MAGIC; mapLiquidHeader.flags = 0; mapLiquidHeader.liquidType = 0; mapLiquidHeader.offsetX = (byte)minX; mapLiquidHeader.offsetY = (byte)minY; mapLiquidHeader.width = (byte)(maxX - minX + 1 + 1); mapLiquidHeader.height = (byte)(maxY - minY + 1 + 1); mapLiquidHeader.liquidLevel = minHeight; if (maxHeight == minHeight) { mapLiquidHeader.flags |= 0x0002; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.001f) { mapLiquidHeader.flags |= 0x0002; } if (!fullType) { mapLiquidHeader.flags |= 0x0001; } if (Convert.ToBoolean(mapLiquidHeader.flags & 0x0001)) { mapLiquidHeader.liquidType = type; } else { map.liquidMapSize += 512 + 256; } if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0002)) { map.liquidMapSize += (uint)(sizeof(float) * mapLiquidHeader.width * mapLiquidHeader.height); } } if (map.liquidMapOffset != 0) { map.holesOffset = map.liquidMapOffset + map.liquidMapSize; } else { map.holesOffset = map.heightMapOffset + map.heightMapSize; } if (hasHoles) { map.holesSize = 2048;// (uint)(1 * holes.Length); } else { map.holesSize = 0; } // Ok all data prepared - store it using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(outputPath, FileMode.Create, FileAccess.Write))) { binaryWriter.WriteStruct(map); // Store area data binaryWriter.WriteStruct(areaHeader); if (!Convert.ToBoolean(areaHeader.flags & 0x0001)) { for (var x = 0; x < area_ids.Length; ++x) { for (var y = 0; y < area_ids[x].Length; ++y) { binaryWriter.Write(area_ids[x][y]); } } } // Store height data binaryWriter.WriteStruct(heightHeader); if (!Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.NoHeight)) { if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt16)) { for (var x = 0; x < uint16_V9.Length; ++x) { for (var y = 0; y < uint16_V9[x].Length; ++y) { binaryWriter.Write(uint16_V9[x][y]); } } for (var x = 0; x < uint16_V8.Length; ++x) { for (var y = 0; y < uint16_V8[x].Length; ++y) { binaryWriter.Write(uint16_V8[x][y]); } } } else if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt8)) { for (var x = 0; x < uint8_V9.Length; ++x) { for (var y = 0; y < uint8_V9[x].Length; ++y) { binaryWriter.Write(uint8_V9[x][y]); } } for (var x = 0; x < uint8_V8.Length; ++x) { for (var y = 0; y < uint8_V8[x].Length; ++y) { binaryWriter.Write(uint8_V8[x][y]); } } } else { for (var x = 0; x < V9.Length; ++x) { for (var y = 0; y < V9[x].Length; ++y) { binaryWriter.Write(V9[x][y]); } } for (var x = 0; x < V8.Length; ++x) { for (var y = 0; y < V8[x].Length; ++y) { binaryWriter.Write(V8[x][y]); } } } } if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.HasFlightBounds)) { for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(flight_box_max[x][y]); } } for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(flight_box_min[x][y]); } } } // Store liquid data if need if (map.liquidMapOffset != 0) { binaryWriter.WriteStruct(mapLiquidHeader); if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0001)) { for (var x = 0; x < liquid_entry.Length; ++x) { for (var y = 0; y < liquid_entry[x].Length; ++y) { binaryWriter.Write(liquid_entry[x][y]); } } for (var x = 0; x < liquid_flags.Length; ++x) { for (var y = 0; y < liquid_flags[x].Length; ++y) { binaryWriter.Write(liquid_flags[x][y]); } } } if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0002)) { for (int y = 0; y < mapLiquidHeader.height; y++) { for (int x = 0; x < mapLiquidHeader.width; x++) { binaryWriter.Write(liquid_height[y + mapLiquidHeader.offsetY][x + mapLiquidHeader.offsetX]); } } } } // store hole data if (hasHoles) { for (var x = 0; x < holes.Length; ++x) { for (var y = 0; y < holes[x].Length; ++y) { for (var z = 0; z < holes[x][y].Length; ++z) { binaryWriter.Write(holes[x][y][z]); } } } } } return(true); }
static void ExtractMaps() { ExtractDbcFiles(); ExtractCameraFiles(); ExtractGameTablesFiles(); Console.WriteLine("Extracting maps..."); string path = $"{BaseDirectory}/maps"; CreateDirectory(path); Console.WriteLine("Convert map files"); int count = 1; Console.WriteLine("Loading DB2 files"); var mapStorage = DBReader.Read <MapRecord>(1349477); if (mapStorage == null) { Console.WriteLine("Fatal error: Invalid Map.db2 file format!"); return; } foreach (var record in mapStorage.Values) { Console.Write($"Extract {record.Directory} ({count++}/{mapStorage.Count}) \n"); // Loadup map grid data ChunkedFile wdt = new ChunkedFile(); if (wdt.loadFile(CascHandler, record.WdtFileDataID, $"WDT for map {record.Id}")) { wdt_MPHD mphd = wdt.GetChunk("MPHD").As <wdt_MPHD>(); wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>(); for (int y = 0; y < 64; ++y) { for (int x = 0; x < 64; ++x) { if (!Convert.ToBoolean(main.adt_list[y][x].flag & 0x1)) { continue; } string outputFileName = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map"; bool ignoreDeepWater = MapFile.IsDeepWaterIgnored(record.Id, y, x); if (mphd != null && (mphd.flags & 0x200) != 0) { wdt_MAID maid = wdt.GetChunk("MAID").As <wdt_MAID>(); MapFile.ConvertADT(CascHandler, maid.adt_files[y][x].rootADT, record.MapName, outputFileName, y, x, ignoreDeepWater); } else { string storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt"; MapFile.ConvertADT(CascHandler, storagePath, record.MapName, outputFileName, y, x, ignoreDeepWater); } } // draw progress bar Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r"); } } } Console.WriteLine("\n"); }
static void ExtractMaps(uint build) { Console.WriteLine("Extracting maps...\n"); Console.WriteLine("Loading db2 files...\n"); var stream = cascHandler.ReadFile("DBFilesClient\\Map.db2"); if (stream == null) { Console.WriteLine("Unable to open file DBFilesClient\\Map.db2 in the archive\n"); return; } mapStorage = DB6Reader.Read <MapRecord>(stream, DB6Metas.MapMeta); if (mapStorage == null) { Console.WriteLine("Fatal error: Invalid Map.db2 file format!\n"); return; } stream = cascHandler.ReadFile("DBFilesClient\\LiquidType.db2"); if (stream == null) { Console.WriteLine("Unable to open file DBFilesClient\\LiquidType.db2 in the archive\n"); return; } liquidTypeStorage = DB6Reader.Read <LiquidTypeRecord>(stream, DB6Metas.LiquidTypeMeta); if (liquidTypeStorage == null) { Console.WriteLine("Fatal error: Invalid LiquidType.db2 file format!\n"); return; } string path = $"{baseDirectory}/maps"; CreateDirectory(path); Console.WriteLine("Convert map files\n"); int count = 1; foreach (var map in mapStorage.Values) { Console.Write($"Extract {map.MapName} ({count}/{mapStorage.Count}) \n"); // Loadup map grid data string storagePath = $"World\\Maps\\{map.Directory}\\{map.Directory}.wdt"; ChunkedFile wdt = new ChunkedFile(); if (wdt.loadFile(cascHandler, storagePath)) { wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>(); for (int y = 0; y < 64; ++y) { for (int x = 0; x < 64; ++x) { if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1)) { storagePath = $"World\\Maps\\{map.Directory}\\{map.Directory}_{x}_{y}.adt"; string outputFileName = $"{path}/{map.Id:D4}_{y:D2}_{x:D2}.map"; MapFile.ConvertADT(cascHandler, storagePath, outputFileName, y, x, build); } } // draw progress bar Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r"); } count++; } } Console.WriteLine("\n"); }
public static bool ConvertADT(ChunkedFile adt, string mapName, string outputPath, int gx, int gy, uint build, bool ignoreDeepWater) { // Prepare map header MapFileHeader map; map.mapMagic = SharedConst.MAP_MAGIC; map.versionMagic = SharedConst.MAP_VERSION_MAGIC; map.buildMagic = build; // Get area flags data for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { AreaIDs[x][y] = 0; LiquidEntries[x][y] = 0; LiquidFlags[x][y] = 0; } } for (var x = 0; x < SharedConst.ADT_GRID_SIZE; ++x) { for (var y = 0; y < SharedConst.ADT_GRID_SIZE; ++y) { V8[x][y] = 0; LiquidSnow[x][y] = false; } } for (var x = 0; x < SharedConst.ADT_GRID_SIZE + 1; ++x) { for (var y = 0; y < SharedConst.ADT_GRID_SIZE + 1; ++y) { V9[x][y] = 0; } } for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { for (var z = 0; z < 8; ++z) { Holes[x][y][z] = 0; } } } bool hasHoles = false; bool hasFlightBox = false; foreach (var fileChunk in adt.chunks.LookupByKey("MCNK")) { MCNK mcnk = fileChunk.As <MCNK>(); // Area data AreaIDs[mcnk.IndexY][mcnk.IndexX] = (ushort)mcnk.AreaID; // Height // Height values for triangles stored in order: // 1 2 3 4 5 6 7 8 9 // 10 11 12 13 14 15 16 17 // 18 19 20 21 22 23 24 25 26 // 27 28 29 30 31 32 33 34 // . . . . . . . . // For better get height values merge it to V9 and V8 map // V9 height map: // 1 2 3 4 5 6 7 8 9 // 18 19 20 21 22 23 24 25 26 // . . . . . . . . // V8 height map: // 10 11 12 13 14 15 16 17 // 27 28 29 30 31 32 33 34 // . . . . . . . . // Set map height as grid height for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V9[cy][cx] = mcnk.ypos; } } for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V8[cy][cx] = mcnk.ypos; } } // Get custom height FileChunk chunk = fileChunk.GetSubChunk("MCVT"); if (chunk != null) { MCVT mcvt = chunk.As <MCVT>(); // get V9 height map for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V9[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + x]; } } // get V8 height map for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V8[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + SharedConst.ADT_CELL_SIZE + 1 + x]; } } } // Liquid data if (mcnk.MCLQCount > 8) { FileChunk lquidChunk = fileChunk.GetSubChunk("MCLQ"); if (lquidChunk != null) { MCLQ liquid = lquidChunk.As <MCLQ>(); int count = 0; for (int y = 0; y < SharedConst.ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; if (liquid.Flags[y][x] != 0x0F) { LiquidSnow[cy][cx] = true; if (!ignoreDeepWater && Convert.ToBoolean(liquid.Flags[y][x] & (1 << 7))) { LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.DarkWater; } ++count; } } } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidRiver)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 1; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Water; // water } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidOcean)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 2; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Ocean; // ocean } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidMagma)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 3; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Magma; // magma } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidSlime)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 4; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Slime; // slime } if (count == 0 && LiquidFlags[mcnk.IndexY][mcnk.IndexX] != 0) { Console.WriteLine("Wrong liquid detect in MCLQ chunk"); } for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; LiquidHeight[cy][cx] = liquid.Liquid[y][x].Height; } } } } // Hole data if (!mcnk.Flags.HasAnyFlag(MCNKFlags.HighResHoles)) { uint hole = mcnk.HolesLowRes; if (hole != 0) { if (TransformToHighRes((ushort)hole, Holes[mcnk.IndexY][mcnk.IndexX])) { hasHoles = true; } } } else { Buffer.BlockCopy(mcnk.HighResHoles, 0, Holes[mcnk.IndexY][mcnk.IndexX], 0, 8); if (BitConverter.ToUInt64(Holes[mcnk.IndexY][mcnk.IndexX], 0) != 0) { hasHoles = true; } } } // Get liquid map for grid (in WOTLK used MH2O chunk) FileChunk chunkMH2O = adt.GetChunk("MH2O"); if (chunkMH2O != null) { MH2O h2o = chunkMH2O.As <MH2O>(); for (int i = 0; i < SharedConst.ADT_CELLS_PER_GRID; i++) { for (int j = 0; j < SharedConst.ADT_CELLS_PER_GRID; j++) { MH2OInstance?h = h2o.GetLiquidInstance(i, j); if (!h.HasValue) { continue; } MH2OInstance adtLiquidHeader = h.Value; MH2OChunkAttribute?attrs = h2o.GetLiquidAttributes(i, j); int count = 0; ulong existsMask = h2o.GetLiquidExistsBitmap(adtLiquidHeader); for (int y = 0; y < adtLiquidHeader.GetHeight(); y++) { int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY(); for (int x = 0; x < adtLiquidHeader.GetWidth(); x++) { int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX(); if (Convert.ToBoolean(existsMask & 1)) { LiquidSnow[cy][cx] = true; ++count; } existsMask >>= 1; } } LiquidEntries[i][j] = h2o.GetLiquidType(adtLiquidHeader); if (LiquidEntries[i][j] != 0) { var liquidTypeRecord = LiquidTypeStorage[LiquidEntries[i][j]]; switch ((LiquidType)liquidTypeRecord.SoundBank) { case LiquidType.Water: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Water; break; case LiquidType.Ocean: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Ocean; if (!ignoreDeepWater && attrs.Value.Deep != 0) { LiquidFlags[i][j] |= LiquidHeaderTypeFlags.DarkWater; } break; case LiquidType.Magma: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Magma; break; case LiquidType.Slime: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Slime; break; default: Console.WriteLine($"\nCan't find Liquid type {adtLiquidHeader.LiquidType} for map {mapName}\nchunk {i},{j}\n"); break; } if (count == 0 && LiquidFlags[i][j] != 0) { Console.WriteLine("Wrong liquid detect in MH2O chunk"); } } else { Console.WriteLine($"LiquidEntries is 0 for [{i}][{j}] ({i * j})"); } int pos = 0; for (int y = 0; y <= adtLiquidHeader.GetHeight(); y++) { int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY(); for (int x = 0; x <= adtLiquidHeader.GetWidth(); x++) { int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX(); LiquidHeight[cy][cx] = h2o.GetLiquidHeight(adtLiquidHeader, i, j, pos); pos++; } } } } } FileChunk chunkMFBO = adt.GetChunk("MFBO"); if (chunkMFBO != null) { MFBO mfbo = chunkMFBO.As <MFBO>(); for (var i = 0; i < 3; ++i) { FlightBoxMax[i][0] = mfbo.Max.coords[0 + i * 3]; FlightBoxMax[i][1] = mfbo.Max.coords[1 + i * 3]; FlightBoxMax[i][2] = mfbo.Max.coords[2 + i * 3]; FlightBoxMin[i][0] = mfbo.Min.coords[0 + i * 3]; FlightBoxMin[i][1] = mfbo.Min.coords[1 + i * 3]; FlightBoxMin[i][2] = mfbo.Min.coords[2 + i * 3]; } hasFlightBox = true; } //============================================ // Try pack area data //============================================ bool fullAreaData = false; uint areaId = AreaIDs[0][0]; for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { if (AreaIDs[y][x] != areaId) { fullAreaData = true; break; } } } map.areaMapOffset = 44; map.areaMapSize = 8; MapAreaHeader areaHeader; areaHeader.fourcc = SharedConst.MAP_AREA_MAGIC; areaHeader.flags = 0; if (fullAreaData) { areaHeader.gridArea = 0; map.areaMapSize += 512; } else { areaHeader.flags |= AreaHeaderFlags.NoArea; areaHeader.gridArea = (ushort)areaId; } //============================================ // Try pack height data //============================================ float maxHeight = -20000; float minHeight = 20000; for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { float h = V8[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { float h = V9[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } // Check for allow limit minimum height (not store height in deep ochean - allow save some memory) if (minHeight < -2000.0f) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { if (V8[y][x] < -2000.0f) { V8[y][x] = -2000.0f; } } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { if (V9[y][x] < -2000.0f) { V9[y][x] = -2000.0f; } } } if (minHeight < -2000.0f) { minHeight = -2000.0f; } if (maxHeight < -2000.0f) { maxHeight = -2000.0f; } } map.heightMapOffset = map.areaMapOffset + map.areaMapSize; map.heightMapSize = (uint)Marshal.SizeOf <MapHeightHeader>(); MapHeightHeader heightHeader; heightHeader.fourcc = SharedConst.MAP_HEIGHT_MAGIC; heightHeader.flags = 0; heightHeader.gridHeight = minHeight; heightHeader.gridMaxHeight = maxHeight; if (maxHeight == minHeight) { heightHeader.flags |= HeightHeaderFlags.NoHeight; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.005f) { heightHeader.flags |= HeightHeaderFlags.NoHeight; } if (hasFlightBox) { heightHeader.flags |= HeightHeaderFlags.HasFlightBounds; map.heightMapSize += 18 + 18; } // Try store as packed in uint16 or uint8 values if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight)) { float step = 0; // Try Store as uint values if (true)//CONF_allow_float_to_int { float diff = maxHeight - minHeight; if (diff < 2.0f) // As uint8 (max accuracy = CONF_float_to_int8_limit/256) { heightHeader.flags |= HeightHeaderFlags.AsInt8; step = 255 / diff; } else if (diff < 2048.0f) // As uint16 (max accuracy = CONF_float_to_int16_limit/65536) { heightHeader.flags |= HeightHeaderFlags.AsInt16; step = 65535 / diff; } } // Pack it to int values if need if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8)) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { UInt8_V8[y][x] = (byte)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { UInt8_V9[y][x] = (byte)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 16641 + 16384; } else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16)) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { UInt16_V8[y][x] = (ushort)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { UInt16_V9[y][x] = (ushort)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 33282 + 32768; } else { map.heightMapSize += 66564 + 65536; } } //============================================ // Pack liquid data //============================================ ushort firstLiquidType = LiquidEntries[0][0]; LiquidHeaderTypeFlags firstLiquidFlag = LiquidFlags[0][0]; bool fullType = false; for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; y++) { for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; x++) { if (LiquidEntries[y][x] != firstLiquidType || LiquidFlags[y][x] != firstLiquidFlag) { fullType = true; y = SharedConst.ADT_CELLS_PER_GRID; break; } } } MapLiquidHeader mapLiquidHeader = new(); // no water data (if all grid have 0 liquid type) if (firstLiquidFlag == 0 && !fullType) { // No liquid data map.liquidMapOffset = 0; map.liquidMapSize = 0; } else { int minX = 255, minY = 255; int maxX = 0, maxY = 0; maxHeight = -20000; minHeight = 20000; for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { if (LiquidSnow[y][x]) { if (minX > x) { minX = x; } if (maxX < x) { maxX = x; } if (minY > y) { minY = y; } if (maxY < y) { maxY = y; } float h = LiquidHeight[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } else { LiquidHeight[y][x] = -2000.0f; } } } map.liquidMapOffset = map.heightMapOffset + map.heightMapSize; map.liquidMapSize = (uint)Marshal.SizeOf <MapLiquidHeader>(); mapLiquidHeader.fourcc = SharedConst.MAP_LIQUID_MAGIC; mapLiquidHeader.flags = LiquidHeaderFlags.None; mapLiquidHeader.liquidType = 0; mapLiquidHeader.offsetX = (byte)minX; mapLiquidHeader.offsetY = (byte)minY; mapLiquidHeader.width = (byte)(maxX - minX + 1 + 1); mapLiquidHeader.height = (byte)(maxY - minY + 1 + 1); mapLiquidHeader.liquidLevel = minHeight; if (maxHeight == minHeight) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.001f) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight; } if (!fullType) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoType; } if (mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType)) { mapLiquidHeader.liquidFlags = firstLiquidFlag; mapLiquidHeader.liquidType = firstLiquidType; } else { map.liquidMapSize += 512 + 256; } if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { map.liquidMapSize += (uint)(sizeof(float) * mapLiquidHeader.width * mapLiquidHeader.height); } } if (hasHoles) { if (map.liquidMapOffset != 0) { map.holesOffset = map.liquidMapOffset + map.liquidMapSize; } else { map.holesOffset = map.heightMapOffset + map.heightMapSize; } map.holesSize = 2048; } else { map.holesOffset = 0; map.holesSize = 0; } // Ok all data prepared - store it using (BinaryWriter binaryWriter = new(File.Open(outputPath, FileMode.Create, FileAccess.Write))) { binaryWriter.WriteStruct(map); // Store area data binaryWriter.WriteStruct(areaHeader); if (!areaHeader.flags.HasFlag(AreaHeaderFlags.NoArea)) { for (var x = 0; x < AreaIDs.Length; ++x) { for (var y = 0; y < AreaIDs[x].Length; ++y) { binaryWriter.Write(AreaIDs[x][y]); } } } // Store height data binaryWriter.WriteStruct(heightHeader); if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight)) { if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16)) { for (var x = 0; x < UInt16_V9.Length; ++x) { for (var y = 0; y < UInt16_V9[x].Length; ++y) { binaryWriter.Write(UInt16_V9[x][y]); } } for (var x = 0; x < UInt16_V8.Length; ++x) { for (var y = 0; y < UInt16_V8[x].Length; ++y) { binaryWriter.Write(UInt16_V8[x][y]); } } } else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8)) { for (var x = 0; x < UInt8_V9.Length; ++x) { for (var y = 0; y < UInt8_V9[x].Length; ++y) { binaryWriter.Write(UInt8_V9[x][y]); } } for (var x = 0; x < UInt8_V8.Length; ++x) { for (var y = 0; y < UInt8_V8[x].Length; ++y) { binaryWriter.Write(UInt8_V8[x][y]); } } } else { for (var x = 0; x < V9.Length; ++x) { for (var y = 0; y < V9[x].Length; ++y) { binaryWriter.Write(V9[x][y]); } } for (var x = 0; x < V8.Length; ++x) { for (var y = 0; y < V8[x].Length; ++y) { binaryWriter.Write(V8[x][y]); } } } } if (heightHeader.flags.HasFlag(HeightHeaderFlags.HasFlightBounds)) { for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(FlightBoxMax[x][y]); } } for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(FlightBoxMin[x][y]); } } } // Store liquid data if need if (map.liquidMapOffset != 0) { binaryWriter.WriteStruct(mapLiquidHeader); if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType)) { for (var x = 0; x < LiquidEntries.Length; ++x) { for (var y = 0; y < LiquidEntries[x].Length; ++y) { binaryWriter.Write(LiquidEntries[x][y]); } } for (var x = 0; x < LiquidFlags.Length; ++x) { for (var y = 0; y < LiquidFlags[x].Length; ++y) { binaryWriter.Write((byte)LiquidFlags[x][y]); } } } if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { for (int y = 0; y < mapLiquidHeader.height; y++) { for (int x = 0; x < mapLiquidHeader.width; x++) { binaryWriter.Write(LiquidHeight[y + mapLiquidHeader.offsetY][x + mapLiquidHeader.offsetX]); } } } } // store hole data if (hasHoles) { for (var x = 0; x < Holes.Length; ++x) { for (var y = 0; y < Holes[x].Length; ++y) { for (var z = 0; z < Holes[x][y].Length; ++z) { binaryWriter.Write(Holes[x][y][z]); } } } } } return(true); }