private IEnumerable <IChunkColumn> GenerateChunks(ChunkCoordinates center, int renderDistance) { var oldChunks = _loadedChunks.ToArray(); double radiusSquared = Math.Pow(renderDistance, 2); List <ChunkCoordinates> newChunkCoordinates = new List <ChunkCoordinates>(); List <ChunkCoordinates> results = new List <ChunkCoordinates>((renderDistance * 2) * (renderDistance * 2)); for (int y = -renderDistance; y <= renderDistance; y++) { for (int x = -renderDistance; x <= renderDistance; x++) { results.Add(new ChunkCoordinates(x, y)); } } foreach (var cc in results.OrderBy(p => { int dx = p.X; int dy = p.Z; return(dx * dx + dy * dy); }) .TakeWhile(p => { int dx = p.X; int dy = p.Z; var r = dx * dx + dy * dy; return(r < radiusSquared); })) { var acc = center + cc; newChunkCoordinates.Add(acc); if (!_loadedChunks.Contains(acc)) { IChunkColumn chunk = _generator.GenerateChunkColumn(acc); if (chunk == null) { continue; } _loadedChunks.Add(acc); yield return(chunk); } } foreach (var chunk in oldChunks) { if (!newChunkCoordinates.Contains((ChunkCoordinates)chunk)) { UnloadChunk(chunk.X, chunk.Z); _loadedChunks.Remove(chunk); } } }
private IEnumerable <ChunkColumn> GenerateChunks(ChunkCoordinates center, int renderDistance) { var oldChunks = _loadedChunks.ToArray(); double radiusSquared = Math.Pow(renderDistance, 2); List <ChunkCoordinates> newChunkCoordinates = new List <ChunkCoordinates>(); List <ChunkCoordinates> results = new List <ChunkCoordinates>(); for (int y = center.Z - renderDistance; y <= center.Z + renderDistance; y++) { for (int x = center.X - renderDistance; x <= center.X + renderDistance; x++) { var cc = new ChunkCoordinates(x, y); newChunkCoordinates.Add(cc); if (!_loadedChunks.Contains(cc)) { ChunkColumn chunk = _generator.GenerateChunkColumn(cc); if (chunk == null) { continue; } _loadedChunks.Add(cc); yield return(chunk); } } } foreach (var chunk in oldChunks) { if (!newChunkCoordinates.Contains(chunk) && chunk != center) { //UnloadChunk(chunk.X, chunk.Z); ChunkUnloadEvent unloadEvent = new ChunkUnloadEvent(chunk); EventDispatcher.DispatchEvent(unloadEvent); if (!unloadEvent.IsCancelled) { _loadedChunks.Remove(chunk); } } } }
private IEnumerable <ChunkColumn> GenerateChunks(ChunkCoordinates center, int renderDistance) { var oldChunks = _loadedChunks.ToArray(); List <ChunkCoordinates> newChunkCoordinates = new List <ChunkCoordinates>(); int minZ = Math.Min(center.Z - renderDistance, center.Z + renderDistance); int maxZ = Math.Max(center.Z - renderDistance, center.Z + renderDistance); int minX = Math.Min(center.X - renderDistance, center.X + renderDistance); int maxX = Math.Max(center.X - renderDistance, center.X + renderDistance); for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { var cc = new ChunkCoordinates(x, z); newChunkCoordinates.Add(cc); if (!_loadedChunks.Contains(cc)) { _loadedChunks.Add(cc); ChunkColumn chunk = _generator.GenerateChunkColumn(cc); if (chunk == null) { continue; } base.World.ChunkManager.AddChunk(chunk, cc, false); LoadEntities(chunk); yield return(chunk); } } } foreach (var chunk in oldChunks) { if (!newChunkCoordinates.Contains(chunk) && chunk != center) { //World.UnloadChunk(chunk); World.ChunkManager.RemoveChunk(chunk, false); _loadedChunks.Remove(chunk); } } }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, string basePath, IWorldGenerator generator) { try { const int width = 32; const int depth = 32; int rx = coordinates.X >> 5; int rz = coordinates.Z >> 5; string filePath = Path.Combine(basePath, string.Format(@"region{2}r.{0}.{1}.mca", rx, rz, Path.DirectorySeparatorChar)); if (!File.Exists(filePath)) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunkColumn); //new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); } return(chunkColumn); } using (var regionFile = File.OpenRead(filePath)) { int locationIndex = ((coordinates.X & (width - 1)) + (coordinates.Z & (depth - 1)) * width) << 2; regionFile.Seek(locationIndex, SeekOrigin.Begin); byte[] offsetBuffer = new byte[4]; regionFile.Read(offsetBuffer, 0, 3); Array.Reverse(offsetBuffer); int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4; int sectorCount = regionFile.ReadByte(); if (offset == 0 || sectorCount == 0) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { chunkColumn.NeedSave = true; } return(chunkColumn); } /*regionFile.Seek(4096 + locationIndex, SeekOrigin.Begin); * regionFile.Read(offsetBuffer, 0, 4); * Array.Reverse(offsetBuffer); * int lastModified = BitConverter.ToInt32(offsetBuffer, 0); * Log.Warn("Last modified: " + lastModified);*/ regionFile.Seek(offset + 4, SeekOrigin.Begin); //Offset + the length header int compressionMode = regionFile.ReadByte(); if (compressionMode != 0x02) { throw new Exception($"CX={coordinates.X}, CZ={coordinates.Z}, NBT wrong compression. Expected 0x02, got 0x{compressionMode:X2}. " + $"Offset={offset}, Sectors={sectorCount}"); } var nbt = new NbtFile(); nbt.LoadFromStream(regionFile, NbtCompression.ZLib); NbtCompound dataTag = (NbtCompound)nbt.RootTag["Level"]; bool isPocketEdition = false; if (dataTag.Contains("MCPE BID")) { isPocketEdition = dataTag["MCPE BID"].ByteValue == 1; } NbtList sections = dataTag["Sections"] as NbtList; ChunkColumn chunk = new ChunkColumn { x = coordinates.X, z = coordinates.Z, biomeId = dataTag["Biomes"].ByteArrayValue, isAllAir = true }; if (chunk.biomeId.Length > 256) { throw new Exception(); } NbtTag heights = dataTag["HeightMap"] as NbtIntArray; if (heights != null) { int[] intHeights = heights.IntArrayValue; for (int i = 0; i < 256; i++) { chunk.height[i] = (short)intHeights[i]; } } // This will turn into a full chunk column foreach (NbtTag sectionTag in sections) { ReadSection(sectionTag, chunk, !isPocketEdition); } NbtList entities = dataTag["Entities"] as NbtList; if (entities != null) { chunk.Entities = entities.ToArray <NbtCompound>(); } NbtList blockEntities = dataTag["TileEntities"] as NbtList; if (blockEntities != null) { foreach (var nbtTag in blockEntities) { var blockEntityTag = (NbtCompound)nbtTag.Clone(); string entityId = blockEntityTag["id"].StringValue; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; if (entityId.StartsWith("minecraft:")) { var id = entityId.Split(':')[1]; entityId = id.First().ToString().ToUpper() + id.Substring(1); blockEntityTag["id"] = new NbtString("id", entityId); } BlockEntity blockEntity = BlockEntityFactory.GetBlockEntityById(entityId); if (blockEntity != null) { blockEntityTag.Name = string.Empty; if (blockEntity is Sign) { // Remove the JSON stuff and get the text out of extra data. // TAG_String("Text2"): "{"extra":["10c a loaf!"],"text":""}" CleanSignText(blockEntityTag, "Text1"); CleanSignText(blockEntityTag, "Text2"); CleanSignText(blockEntityTag, "Text3"); CleanSignText(blockEntityTag, "Text4"); } else if (blockEntity is ChestBlockEntity) { NbtList items = (NbtList)blockEntityTag["Items"]; if (items != null) { //for (byte i = 0; i < items.Count; i++) //{ // NbtCompound item = (NbtCompound) items[i]; // item.Add(new NbtShort("OriginalDamage", item["Damage"].ShortValue)); // byte metadata = (byte) (item["Damage"].ShortValue & 0xff); // item.Remove("Damage"); // item.Add(new NbtByte("Damage", metadata)); //} } } chunk.SetBlockEntity(new BlockCoordinates(x, y, z), blockEntityTag); } else { if (Log.IsDebugEnabled) { Log.Debug($"Loaded unknown block entity: {blockEntityTag}"); } } } } //NbtList tileTicks = dataTag["TileTicks"] as NbtList; chunk.RecalcHeight(); chunk.isDirty = false; chunk.NeedSave = false; if (Config.GetProperty("CalculateLights", false)) { SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunk); new SkyLightCalculations().RecalcSkyLight(chunk, blockAccess); //TODO: Block lights. } return(chunk); } } catch (Exception e) { Log.Error($"Loading chunk {coordinates}", e); var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //chunkColumn.NeedSave = true; } return(chunkColumn); } }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, string basePath, IWorldGenerator generator) { try { int width = 32; int depth = 32; int rx = coordinates.X >> 5; int rz = coordinates.Z >> 5; string filePath = Path.Combine(basePath, string.Format(@"region{2}r.{0}.{1}.mca", rx, rz, Path.DirectorySeparatorChar)); if (!File.Exists(filePath)) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //chunkColumn.NeedSave = true; } return(chunkColumn); } using (var regionFile = File.OpenRead(filePath)) { byte[] buffer = new byte[8192]; regionFile.Read(buffer, 0, 8192); int xi = (coordinates.X % width); if (xi < 0) { xi += 32; } int zi = (coordinates.Z % depth); if (zi < 0) { zi += 32; } int tableOffset = (xi + zi * width) * 4; regionFile.Seek(tableOffset, SeekOrigin.Begin); byte[] offsetBuffer = new byte[4]; regionFile.Read(offsetBuffer, 0, 3); Array.Reverse(offsetBuffer); int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4; byte[] bytes = BitConverter.GetBytes(offset >> 4); Array.Reverse(bytes); if (offset != 0 && offsetBuffer[0] != bytes[0] && offsetBuffer[1] != bytes[1] && offsetBuffer[2] != bytes[2]) { throw new Exception($"Not the same buffer\n{Package.HexDump(offsetBuffer)}\n{Package.HexDump(bytes)}"); } int length = regionFile.ReadByte(); if (offset == 0 || length == 0) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //chunkColumn.NeedSave = true; } return(chunkColumn); } regionFile.Seek(offset, SeekOrigin.Begin); byte[] waste = new byte[4]; regionFile.Read(waste, 0, 4); int compressionMode = regionFile.ReadByte(); if (compressionMode != 0x02) { throw new Exception($"CX={coordinates.X}, CZ={coordinates.Z}, NBT wrong compression. Expected 0x02, got 0x{compressionMode:X2}. " + $"Offset={offset}, length={length}\n{Package.HexDump(waste)}"); } var nbt = new NbtFile(); nbt.LoadFromStream(regionFile, NbtCompression.ZLib); NbtCompound dataTag = (NbtCompound)nbt.RootTag["Level"]; bool isPocketEdition = false; if (dataTag.Contains("MCPE BID")) { isPocketEdition = dataTag["MCPE BID"].ByteValue == 1; } NbtList sections = dataTag["Sections"] as NbtList; ChunkColumn chunk = new ChunkColumn { x = coordinates.X, z = coordinates.Z, biomeId = dataTag["Biomes"].ByteArrayValue, isAllAir = true }; if (chunk.biomeId.Length > 256) { throw new Exception(); } NbtTag heights = dataTag["HeightMap"] as NbtIntArray; if (heights != null) { int[] intHeights = heights.IntArrayValue; for (int i = 0; i < 256; i++) { chunk.height[i] = (short)intHeights[i]; } } // This will turn into a full chunk column foreach (NbtTag sectionTag in sections) { ReadSection(sectionTag, chunk, !isPocketEdition); } NbtList entities = dataTag["Entities"] as NbtList; NbtList blockEntities = dataTag["TileEntities"] as NbtList; if (blockEntities != null) { foreach (var nbtTag in blockEntities) { var blockEntityTag = (NbtCompound)nbtTag.Clone(); string entityId = blockEntityTag["id"].StringValue; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; if (entityId.StartsWith("minecraft:")) { var id = entityId.Split(':')[1]; entityId = id.First().ToString().ToUpper() + id.Substring(1); blockEntityTag["id"] = new NbtString("id", entityId); } BlockEntity blockEntity = BlockEntityFactory.GetBlockEntityById(entityId); if (blockEntity != null) { blockEntityTag.Name = string.Empty; if (blockEntity is Sign) { // Remove the JSON stuff and get the text out of extra data. // TAG_String("Text2"): "{"extra":["10c a loaf!"],"text":""}" CleanSignText(blockEntityTag, "Text1"); CleanSignText(blockEntityTag, "Text2"); CleanSignText(blockEntityTag, "Text3"); CleanSignText(blockEntityTag, "Text4"); } else if (blockEntity is ChestBlockEntity) { NbtList items = (NbtList)blockEntityTag["Items"]; if (items != null) { //for (byte i = 0; i < items.Count; i++) //{ // NbtCompound item = (NbtCompound) items[i]; // item.Add(new NbtShort("OriginalDamage", item["Damage"].ShortValue)); // byte metadata = (byte) (item["Damage"].ShortValue & 0xff); // item.Remove("Damage"); // item.Add(new NbtByte("Damage", metadata)); //} } } chunk.SetBlockEntity(new BlockCoordinates(x, y, z), blockEntityTag); } else { if (Log.IsDebugEnabled) { Log.Debug($"Loaded unknown block entity: {blockEntityTag}"); } } } } //NbtList tileTicks = dataTag["TileTicks"] as NbtList; chunk.isDirty = false; chunk.NeedSave = false; return(chunk); } } catch (Exception e) { Log.Error($"Loading chunk {coordinates}", e); var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //chunkColumn.NeedSave = true; } return(chunkColumn); } }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, IWorldGenerator generator) { var index = BitConverter.GetBytes(coordinates.X).Concat(BitConverter.GetBytes(coordinates.Z)).ToArray(); var versionKey = index.Concat(new byte[] { 0x76 }).ToArray(); var version = _db.Get(versionKey); ChunkColumn chunkColumn = null; if (version != null && version.First() == 10) { chunkColumn = new ChunkColumn(); chunkColumn.X = coordinates.X; chunkColumn.Z = coordinates.Z; for (byte y = 0; y < 16; y++) { var chunkDataKey = index.Concat(new byte[] { 0x2f, y }).ToArray(); var sectionBytes = _db.Get(chunkDataKey); if (sectionBytes == null) { for (; y < 16; y++) { chunkColumn[y]?.PutPool(); chunkColumn[y] = null; } break; } ParseSection((SubChunk)chunkColumn[y], sectionBytes); } // Biomes var flatDataBytes = _db.Get(index.Concat(new byte[] { 0x2D }).ToArray()); if (flatDataBytes != null) { chunkColumn.biomeId = flatDataBytes.AsSpan().Slice(512, 256).ToArray(); } // Block entities var blockEntityBytes = _db.Get(index.Concat(new byte[] { 0x31 }).ToArray()); if (blockEntityBytes != null) { var data = blockEntityBytes.AsSpan(); var file = new NbtFile { BigEndian = false, UseVarInt = false }; int position = 0; do { position += (int)file.LoadFromStream(new MemoryStream(data.Slice(position).ToArray()), NbtCompression.None); var blockEntityTag = file.RootTag; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; chunkColumn.SetBlockEntity(new BlockCoordinates(x, y, z), (NbtCompound)file.RootTag); } while (position < data.Length); } } if (chunkColumn == null) { if (version != null) { Log.Error($"Expected other version, but got version={version.First()}"); } chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { var blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); } chunkColumn.IsDirty = false; chunkColumn.NeedSave = false; } } chunkColumn?.RecalcHeight(); if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); //TODO: Block lights. } return(chunkColumn); }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, IWorldGenerator generator) { var sw = Stopwatch.StartNew(); byte[] index = Combine(BitConverter.GetBytes(coordinates.X), BitConverter.GetBytes(coordinates.Z)); byte[] versionKey = Combine(index, 0x76); byte[] version = _db.Get(versionKey); ChunkColumn chunkColumn = null; if (version != null && version.First() >= 10) { chunkColumn = new ChunkColumn { X = coordinates.X, Z = coordinates.Z }; var chunkDataKey = Combine(index, new byte[] { 0x2f, 0 }); for (byte y = 0; y < 16; y++) { chunkDataKey[9] = y; byte[] sectionBytes = _db.Get(chunkDataKey); if (sectionBytes == null) { chunkColumn[y]?.PutPool(); chunkColumn[y] = null; continue; } ParseSection(chunkColumn[y], sectionBytes); } // Biomes var flatDataBytes = _db.Get(Combine(index, 0x2D)); if (flatDataBytes != null) { chunkColumn.biomeId = flatDataBytes.AsSpan().Slice(512, 256).ToArray(); } // Block entities byte[] blockEntityBytes = _db.Get(Combine(index, 0x31)); if (blockEntityBytes != null) { var data = blockEntityBytes.AsMemory(); var file = new NbtFile { BigEndian = false, UseVarInt = false }; int position = 0; do { position += (int)file.LoadFromStream(new MemoryStreamReader(data.Slice(position)), NbtCompression.None); NbtTag blockEntityTag = file.RootTag; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; chunkColumn.SetBlockEntity(new BlockCoordinates(x, y, z), (NbtCompound)blockEntityTag); } while (position < data.Length); } } if (chunkColumn == null) { if (version != null) { Log.Error($"Expected other version, but got version={version.First()}"); } chunkColumn = generator?.GenerateChunkColumn(coordinates); } if (chunkColumn != null) { if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { chunkColumn.RecalcHeight(); var blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); //TODO: Block lights. } chunkColumn.IsDirty = false; chunkColumn.NeedSave = false; } //Log.Debug($"Read chunk {coordinates.X}, {coordinates.Z} in {sw.ElapsedMilliseconds}ms"); return(chunkColumn); }