static MapFile() { for (var i = 0; i < SharedConst.ADT_CELLS_PER_GRID; ++i) { AreaIDs[i] = new ushort[SharedConst.ADT_CELLS_PER_GRID]; LiquidEntries[i] = new ushort[SharedConst.ADT_CELLS_PER_GRID]; LiquidFlags[i] = new LiquidHeaderTypeFlags[SharedConst.ADT_CELLS_PER_GRID]; } for (var i = 0; i < SharedConst.ADT_CELLS_PER_GRID; ++i) { Holes[i] = new byte[SharedConst.ADT_CELLS_PER_GRID][]; for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { Holes[i][x] = new byte[8]; } } for (var i = 0; i < SharedConst.ADT_GRID_SIZE; ++i) { V8[i] = new float[SharedConst.ADT_GRID_SIZE]; UInt16_V8[i] = new ushort[SharedConst.ADT_GRID_SIZE]; UInt8_V8[i] = new byte[SharedConst.ADT_GRID_SIZE]; LiquidSnow[i] = new bool[SharedConst.ADT_GRID_SIZE]; } for (var i = 0; i < SharedConst.ADT_GRID_SIZE + 1; ++i) { V9[i] = new float[SharedConst.ADT_GRID_SIZE + 1]; UInt16_V9[i] = new ushort[SharedConst.ADT_GRID_SIZE + 1]; UInt8_V9[i] = new byte[SharedConst.ADT_GRID_SIZE + 1]; LiquidHeight[i] = new float[SharedConst.ADT_GRID_SIZE + 1]; } for (var i = 0; i < 3; ++i) { FlightBoxMax[i] = new short[3]; FlightBoxMin[i] = new short[3]; } LoadRequiredDb2Files(); }
// Get water state on map public ZLiquidStatus GetLiquidStatus(float x, float y, float z, LiquidHeaderTypeFlags?reqLiquidType, LiquidData data, float collisionHeight) { // Check water type (if no water return) if (_liquidGlobalFlags == LiquidHeaderTypeFlags.NoWater && _liquidFlags == null) { return(ZLiquidStatus.NoWater); } // Get cell float cx = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); float cy = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); int x_int = (int)cx & (MapConst.MapResolution - 1); int y_int = (int)cy & (MapConst.MapResolution - 1); // Check water type in cell int idx = (x_int >> 3) * 16 + (y_int >> 3); LiquidHeaderTypeFlags type = _liquidFlags != null ? (LiquidHeaderTypeFlags)_liquidFlags[idx] : _liquidGlobalFlags; uint entry = _liquidEntry != null ? _liquidEntry[idx] : _liquidGlobalEntry; LiquidTypeRecord liquidEntry = CliDB.LiquidTypeStorage.LookupByKey(entry); if (liquidEntry != null) { type &= LiquidHeaderTypeFlags.DarkWater; uint liqTypeIdx = liquidEntry.SoundBank; if (entry < 21) { var area = CliDB.AreaTableStorage.LookupByKey(GetArea(x, y)); if (area != null) { uint overrideLiquid = area.LiquidTypeID[liquidEntry.SoundBank]; if (overrideLiquid == 0 && area.ParentAreaID == 0) { area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); if (area != null) { overrideLiquid = area.LiquidTypeID[liquidEntry.SoundBank]; } } var liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid); if (liq != null) { entry = overrideLiquid; liqTypeIdx = liq.SoundBank; } } } type |= (LiquidHeaderTypeFlags)(1 << (int)liqTypeIdx); } if (type == LiquidHeaderTypeFlags.NoWater) { return(ZLiquidStatus.NoWater); } // Check req liquid type mask if (reqLiquidType.HasValue && (reqLiquidType & type) == LiquidHeaderTypeFlags.NoWater) { return(ZLiquidStatus.NoWater); } // Check water level: // Check water height map int lx_int = x_int - _liquidOffY; int ly_int = y_int - _liquidOffX; if (lx_int < 0 || lx_int >= _liquidHeight) { return(ZLiquidStatus.NoWater); } if (ly_int < 0 || ly_int >= _liquidWidth) { return(ZLiquidStatus.NoWater); } // Get water level float liquid_level = _liquidMap != null ? _liquidMap[lx_int * _liquidWidth + ly_int] : _liquidLevel; // Get ground level (sub 0.2 for fix some errors) float ground_level = GetHeight(x, y); // Check water level and ground level if (liquid_level < ground_level || z < ground_level) { return(ZLiquidStatus.NoWater); } // All ok in water . store data if (data != null) { data.entry = entry; data.type_flags = type; data.level = liquid_level; data.depth_level = ground_level; } // For speed check as int values float delta = liquid_level - z; if (delta > collisionHeight) // Under water { return(ZLiquidStatus.UnderWater); } if (delta > 0.0f) // In water { return(ZLiquidStatus.InWater); } if (delta > -0.1f) // Walk on water { return(ZLiquidStatus.WaterWalk); } // Above water return(ZLiquidStatus.AboveWater); }
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); }
/**************************************************************************/ bool loadMap(uint mapID, uint tileX, uint tileY, MeshData meshData, Spot portion) { string mapFileName = $"maps/{mapID:D4}_{tileY:D2}_{tileX:D2}.map"; if (!File.Exists(mapFileName)) { int parentMapId = vmapManager.GetParentMapId(mapID); if (parentMapId != -1) { mapFileName = $"maps/{parentMapId:D4}_{tileY:D2}_{tileX:D2}.map"; } } if (!File.Exists(mapFileName)) { return(false); } using (BinaryReader reader = new(File.Open(mapFileName, FileMode.Open, FileAccess.Read, FileShare.Read))) { map_fileheader fheader = reader.Read <map_fileheader>(); if (fheader.versionMagic != SharedConst.MAP_VERSION_MAGIC) { Console.WriteLine($"{mapFileName} is the wrong version, please extract new .map files"); return(false); } bool haveTerrain = false; bool haveLiquid = false; reader.BaseStream.Seek(fheader.heightMapOffset, SeekOrigin.Begin); map_heightHeader hheader = reader.Read <map_heightHeader>(); if (hheader.fourcc == 1413957709) { haveTerrain = !Convert.ToBoolean(hheader.flags & (uint)HeightHeaderFlags.NoHeight); haveLiquid = fheader.liquidMapOffset != 0;// && !m_skipLiquid; } // no data in this map file if (!haveTerrain && !haveLiquid) { return(false); } // data used later byte[][][] holes = new byte[16][][]; for (var i = 0; i < 16; ++i) { holes[i] = new byte[16][]; for (var x = 0; x < 16; ++x) { holes[i][x] = new byte[8]; } } ushort[][] liquid_entry = new ushort[16][]; LiquidHeaderTypeFlags[][] liquid_flags = new LiquidHeaderTypeFlags[16][]; for (var i = 0; i < 16; ++i) { liquid_entry[i] = new ushort[16]; liquid_flags[i] = new LiquidHeaderTypeFlags[16]; } List <int> ltriangles = new(); List <int> ttriangles = new(); // terrain data if (haveTerrain) { float heightMultiplier; float[] V9 = new float[SharedConst.V9_SIZE_SQ]; float[] V8 = new float[SharedConst.V8_SIZE_SQ]; int expected = SharedConst.V9_SIZE_SQ + SharedConst.V8_SIZE_SQ; if (Convert.ToBoolean(hheader.flags & (uint)HeightHeaderFlags.AsInt8)) { byte[] v9 = new byte[SharedConst.V9_SIZE_SQ]; byte[] v8 = new byte[SharedConst.V8_SIZE_SQ]; int count = 0; count += reader.Read(v9, 0, SharedConst.V9_SIZE_SQ); count += reader.Read(v8, 0, SharedConst.V8_SIZE_SQ); if (count != expected) { Console.WriteLine($"TerrainBuilder.loadMap: Failed to read some data expected {expected}, read {count}"); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else if (Convert.ToBoolean(hheader.flags & (uint)HeightHeaderFlags.AsInt16)) { ushort[] v9 = new ushort[SharedConst.V9_SIZE_SQ]; ushort[] v8 = new ushort[SharedConst.V8_SIZE_SQ]; for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { v9[i] = reader.ReadUInt16(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { v8[i] = reader.ReadUInt16(); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else { for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = reader.ReadSingle(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = reader.ReadSingle(); } } // hole data if (fheader.holesSize != 0) { reader.BaseStream.Seek(fheader.holesOffset, SeekOrigin.Begin); int readCount = 0; for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { for (var c = 0; c < 8; ++c) { if (readCount == fheader.holesSize) { break; } holes[i][x][c] = reader.ReadByte(); } } } } int count1 = meshData.solidVerts.Count / 3; float xoffset = ((float)tileX - 32) * SharedConst.GRID_SIZE; float yoffset = ((float)tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V9, xoffset, yoffset, ref coord, ref V9); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V8, xoffset, yoffset, ref coord, ref V8); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += 1) { getHeightTriangle(i, j, indices); ttriangles.Add(indices[2] + count1); ttriangles.Add(indices[1] + count1); ttriangles.Add(indices[0] + count1); } } } // liquid data if (haveLiquid) { reader.BaseStream.Seek(fheader.liquidMapOffset, SeekOrigin.Begin); MapLiquidHeader lheader = reader.Read <MapLiquidHeader>(); float[] liquid_map = null; if (!lheader.flags.HasFlag(LiquidHeaderFlags.NoType)) { for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_entry[i][x] = reader.ReadUInt16(); } } for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_flags[i][x] = (LiquidHeaderTypeFlags)reader.ReadByte(); } } } else { for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_entry[i][x] = lheader.liquidType; liquid_flags[i][x] = lheader.liquidFlags; } } } if (!lheader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { int toRead = lheader.width * lheader.height; liquid_map = new float[toRead]; for (var i = 0; i < toRead; ++i) { liquid_map[i] = reader.ReadSingle(); } } int count = meshData.liquidVerts.Count / 3; float xoffset = (tileX - 32) * SharedConst.GRID_SIZE; float yoffset = (tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; int row, col; // generate coordinates if (!lheader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { int j = 0; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height || col < lheader.offsetX || col >= lheader.offsetX + lheader.width) { // dummy vert using invalid height meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(SharedConst.INVALID_MAP_LIQ_HEIGHT); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); continue; } getLiquidCoord(i, j, xoffset, yoffset, ref coord, ref liquid_map); meshData.liquidVerts.Add(coord[0]); meshData.liquidVerts.Add(coord[2]); meshData.liquidVerts.Add(coord[1]); j++; } } else { for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(lheader.liquidLevel); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); } } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0, triInc = Spot.Bottom - Spot.Top; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); // generate triangles for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += triInc) { getHeightTriangle(i, j, indices, true); ltriangles.Add(indices[2] + count); ltriangles.Add(indices[1] + count); ltriangles.Add(indices[0] + count); } } } // now that we have gathered the data, we can figure out which parts to keep: // liquid above ground, ground above liquid int loopStart1 = 0, loopEnd1 = 0, loopInc1 = 0, tTriCount = 4; bool useTerrain, useLiquid; float[] lverts = meshData.liquidVerts.ToArray(); int[] ltris = ltriangles.ToArray(); int currentLtrisIndex = 0; float[] tverts = meshData.solidVerts.ToArray(); int[] ttris = ttriangles.ToArray(); int currentTtrisIndex = 0; if ((ltriangles.Count + ttriangles.Count) == 0) { return(false); } // make a copy of liquid vertices // used to pad right-bottom frame due to lost vertex data at extraction float[] lverts_copy = null; if (meshData.liquidVerts.Count != 0) { lverts_copy = new float[meshData.liquidVerts.Count]; Array.Copy(lverts, lverts_copy, meshData.liquidVerts.Count); } getLoopVars(portion, ref loopStart1, ref loopEnd1, ref loopInc1); for (int i = loopStart1; i < loopEnd1; i += loopInc1) { for (int j = 0; j < 2; ++j) { // default is true, will change to false if needed useTerrain = true; useLiquid = true; byte navLiquidType = 0; // if there is no liquid, don't use liquid if (meshData.liquidVerts.Count == 0 || ltriangles.Count == 0) { useLiquid = false; } else { LiquidHeaderTypeFlags liquidType = getLiquidType(i, liquid_flags); if (liquidType.HasFlag(LiquidHeaderTypeFlags.DarkWater)) { // players should not be here, so logically neither should creatures useTerrain = false; useLiquid = false; } else if (liquidType.HasAnyFlag(LiquidHeaderTypeFlags.Water | LiquidHeaderTypeFlags.Ocean)) { navLiquidType = (byte)NavArea.Water; } else if (liquidType.HasAnyFlag(LiquidHeaderTypeFlags.Magma | LiquidHeaderTypeFlags.Slime)) { navLiquidType = (byte)NavArea.MagmaSlime; } else { useLiquid = false; } } // if there is no terrain, don't use terrain if (ttriangles.Count == 0) { useTerrain = false; } // while extracting ADT data we are losing right-bottom vertices // this code adds fair approximation of lost data if (useLiquid) { float quadHeight = 0; uint validCount = 0; for (uint idx = 0; idx < 3; idx++) { float h = lverts_copy[ltris[currentLtrisIndex + idx] * 3 + 1]; if (h != SharedConst.INVALID_MAP_LIQ_HEIGHT && h < SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { quadHeight += h; validCount++; } } // update vertex height data if (validCount > 0 && validCount < 3) { quadHeight /= validCount; for (uint idx = 0; idx < 3; idx++) { float h = lverts[ltris[currentLtrisIndex + idx] * 3 + 1]; if (h == SharedConst.INVALID_MAP_LIQ_HEIGHT || h > SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { lverts[ltris[currentLtrisIndex + idx] * 3 + 1] = quadHeight; } } } // no valid vertexes - don't use this poly at all if (validCount == 0) { useLiquid = false; } } // if there is a hole here, don't use the terrain if (useTerrain && fheader.holesSize != 0) { useTerrain = !isHole(i, holes); } // we use only one terrain kind per quad - pick higher one if (useTerrain && useLiquid) { float minLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; float maxLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; for (uint x = 0; x < 3; x++) { float h = lverts[ltris[currentLtrisIndex + x] * 3 + 1]; if (minLLevel > h) { minLLevel = h; } if (maxLLevel < h) { maxLLevel = h; } } float maxTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; float minTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; for (uint x = 0; x < 6; x++) { float h = tverts[ttris[currentTtrisIndex + x] * 3 + 1]; if (maxTLevel < h) { maxTLevel = h; } if (minTLevel > h) { minTLevel = h; } } // terrain under the liquid? if (minLLevel > maxTLevel) { useTerrain = false; } //liquid under the terrain? if (minTLevel > maxLLevel) { useLiquid = false; } } // store the result if (useLiquid) { meshData.liquidType.Add(navLiquidType); for (int k = 0; k < 3; ++k) { meshData.liquidTris.Add(ltris[currentLtrisIndex + k]); } } if (useTerrain) { for (int k = 0; k < 3 * tTriCount / 2; ++k) { meshData.solidTris.Add(ttris[currentTtrisIndex + k]); } } currentLtrisIndex += 3; //ltris = ltris.Skip(3).ToArray(); currentTtrisIndex += 3 * tTriCount / 2; //ttris = ttris.Skip(3 * tTriCount / 2).ToArray(); } } } return(meshData.solidTris.Count != 0 || meshData.liquidTris.Count != 0); }