public ChunkColumn GenerateChunkColumn(ChunkCoordinates chunkCoordinates) { ChunkColumn chunk = new ChunkColumn(); chunk.x = chunkCoordinates.X; chunk.z = chunkCoordinates.Z; PopulateChunk(chunk); Random random = new Random((chunk.x * 397) ^ chunk.z); if (random.NextDouble() > 0.99) { GenerateLake(random, chunk, Dimension == Dimension.Overworld ? new Water() : Dimension == Dimension.Nether ? (Block) new Lava() : new Air()); } else if (random.NextDouble() > 0.97) { GenerateGlowStone(random, chunk); } return(chunk); }
public McpeBatch GenerateChunk(ChunkCoordinates chunkPosition) { if (_worldProvider == null) { return(null); } ChunkColumn chunkColumn = _worldProvider.GenerateChunkColumn(chunkPosition); if (chunkColumn == null) { return(null); } McpeBatch chunk = chunkColumn.GetBatch(); if (chunk == null) { return(null); } return(chunk); }
public bool RecalcSkyLight(ChunkColumn chunk, Level level) { if (chunk == null) { return(false); } for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { if (chunk.isAllAir && !IsOnChunkBorder(x, z)) { continue; } int height = GetHigestSurrounding(x, z, chunk, level); if (height == 0) { continue; } //var skyLight = chunk.GetSkyLight(x, height, z); //if (skyLight == 15) { Block block = level.GetBlock(new BlockCoordinates(x + (chunk.x * 16), height, z + (chunk.z * 16))); Calculate(level, block); } //else //{ // Log.Error($"Block with wrong light level. Expected 15 but was {skyLight}"); //} } } return(true); }
public object Clone() { ChunkColumn cc = (ChunkColumn)MemberwiseClone(); cc.chunks = new Chunk[16]; for (int i = 0; i < chunks.Length; i++) { cc.chunks[i] = (Chunk)chunks[i].Clone(); } cc.biomeId = (byte[])biomeId.Clone(); cc.biomeColor = (int[])biomeColor.Clone(); cc.height = (byte[])height.Clone(); cc.BlockEntities = new Dictionary <BlockCoordinates, NbtCompound>(); foreach (KeyValuePair <BlockCoordinates, NbtCompound> blockEntityPair in BlockEntities) { cc.BlockEntities.Add(blockEntityPair.Key, (NbtCompound)blockEntityPair.Value.Clone()); } if (_cache != null) { cc._cache = (byte[])_cache.Clone(); } McpeBatch batch = McpeBatch.CreateObject(); batch.payload = _cachedBatch.payload; batch.Encode(); batch.MarkPermanent(); cc._cachedBatch = batch; _cacheSync = new object(); return(cc); }
public BlockEntity GetBlockEntity(BlockCoordinates blockCoordinates) { var blockEntity = BlockEntities.FirstOrDefault(entity => entity.Coordinates == blockCoordinates); if (blockEntity != null) { return(blockEntity); } ChunkColumn chunk = _worldProvider.GenerateChunkColumn(new ChunkCoordinates(blockCoordinates.X >> 4, blockCoordinates.Z >> 4)); NbtCompound nbt = chunk.GetBlockEntity(blockCoordinates); if (nbt == null) { return(null); } string id = null; var idTag = nbt.Get("id"); if (idTag != null) { id = idTag.StringValue; } if (string.IsNullOrEmpty(id)) { return(null); } blockEntity = BlockEntityFactory.GetBlockEntityById(id); blockEntity.Coordinates = blockCoordinates; blockEntity.SetCompound(nbt); return(blockEntity); }
public void SetBlock(Block block, bool broadcast = true, bool applyPhysics = true) { ChunkColumn chunk = _worldProvider.GenerateChunkColumn(new ChunkCoordinates(block.Coordinates.X >> 4, block.Coordinates.Z >> 4)); chunk.SetBlock(block.Coordinates.X & 0x0f, block.Coordinates.Y & 0x7f, block.Coordinates.Z & 0x0f, block.Id); chunk.SetMetadata(block.Coordinates.X & 0x0f, block.Coordinates.Y & 0x7f, block.Coordinates.Z & 0x0f, block.Metadata); if (applyPhysics) { ApplyPhysics(block.Coordinates.X, block.Coordinates.Y, block.Coordinates.Z); } if (!broadcast) { return; } var message = McpeUpdateBlock.CreateObject(); message.blockId = block.Id; message.coordinates = block.Coordinates; message.blockMetaAndPriority = (byte)(0xb << 4 | (block.Metadata & 0xf)); RelayBroadcast(message); }
private void GenerateGlowStone(Random random, ChunkColumn chunk) { if (Dimension != Dimension.Nether) { return; } int h = FindGroundLevel(); if (h < 0) { return; } Vector2 center = new Vector2(7, 8); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { Vector2 v = new Vector2(x, z); if (random.Next((int)Vector2.DistanceSquared(center, v)) < 1) { chunk.SetBlock(x, BlockLayers.Count - 2, z, new Glowstone().Id); if (random.NextDouble() > 0.85) { chunk.SetBlock(x, BlockLayers.Count - 3, z, new Glowstone().Id); if (random.NextDouble() > 0.50) { chunk.SetBlock(x, BlockLayers.Count - 4, z, new Glowstone().Id); } } } } } }
public void ShowHeights(ChunkColumn chunk) { if (chunk == null) { return; } for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { var y = chunk.GetHeight(x, z); chunk.SetBlock(x, y, z, 41); //for (byte y = 255; y > 0; y--) //{ // if (chunk.GetSkylight(x, y, z) == 0) // { // chunk.SetBlock(x, y, z, 41); // break; // } //} } } }
public void SetSkyLight(BlockCoordinates coordinates, byte skyLight) { ChunkColumn chunk = GetChunk(coordinates, true); chunk?.SetSkyLight(coordinates.X & 0x0f, coordinates.Y & 0xff, coordinates.Z & 0x0f, skyLight); }
private static void Test(Level level, BlockCoordinates coord, BlockCoordinates newCoord, Queue <BlockCoordinates> lightBfsQueue, ChunkColumn chunk, int lightLevel) { //Interlocked.Add(ref touches, 1); var newChunkCoord = (ChunkCoordinates)newCoord; if (chunk.x != newChunkCoord.X || chunk.z != newChunkCoord.Z) { chunk = GetChunk(level, newCoord); } if (chunk == null) { return; } if (chunk.GetBlock(newCoord.X & 0x0f, newCoord.Y & 0xff, newCoord.Z & 0x0f) == 0) { SetLightLevel(chunk, lightBfsQueue, newCoord, lightLevel); } else { //if (BlockFactory.LuminousBlocks.ContainsKey(chunk.GetBlocklight(newCoord.X & 0x0f, newCoord.Y & 0xff, newCoord.Z & 0x0f))) //{ //} //else { SetLightLevel(chunk, lightBfsQueue, level.GetBlock(newCoord, chunk), lightLevel); } } }
public void SetSkyLight(Block block) { ChunkColumn chunk = _worldProvider.GenerateChunkColumn(new ChunkCoordinates(block.Coordinates.X >> 4, block.Coordinates.Z >> 4)); chunk.SetSkyLight(block.Coordinates.X & 0x0f, block.Coordinates.Y & 0xff, block.Coordinates.Z & 0x0f, block.SkyLight); }
public IEnumerable <McpeBatch> GenerateChunks(ChunkCoordinates chunkPosition, Dictionary <Tuple <int, int>, McpeBatch> chunksUsed, double radius) { lock (chunksUsed) { Dictionary <Tuple <int, int>, double> newOrders = new Dictionary <Tuple <int, int>, double>(); double radiusSquared = Math.Pow(radius, 2); int centerX = chunkPosition.X; int centerZ = chunkPosition.Z; for (double x = -radius; x <= radius; ++x) { for (double z = -radius; z <= radius; ++z) { var distance = (x * x) + (z * z); if (distance > radiusSquared) { //continue; } int chunkX = (int)(x + centerX); int chunkZ = (int)(z + centerZ); Tuple <int, int> index = new Tuple <int, int>(chunkX, chunkZ); newOrders[index] = distance; } } //if (newOrders.Count > viewArea) //{ // foreach (var pair in newOrders.OrderByDescending(pair => pair.Value)) // { // if (newOrders.Count <= viewArea) break; // newOrders.Remove(pair.Key); // } //} foreach (var chunkKey in chunksUsed.Keys.ToArray()) { if (!newOrders.ContainsKey(chunkKey)) { chunksUsed.Remove(chunkKey); } } foreach (var pair in newOrders.OrderBy(pair => pair.Value)) { if (chunksUsed.ContainsKey(pair.Key)) { continue; } if (_worldProvider == null) { continue; } ChunkColumn chunkColumn = _worldProvider.GenerateChunkColumn(new ChunkCoordinates(pair.Key.Item1, pair.Key.Item2)); McpeBatch chunk = null; if (chunkColumn != null) { chunk = chunkColumn.GetBatch(); } chunksUsed.Add(pair.Key, chunk); yield return(chunk); } } }
private void ReadSection(NbtTag sectionTag, ChunkColumn chunkColumn, bool convertBid = true) { int sectionIndex = sectionTag["Y"].ByteValue; byte[] blocks = sectionTag["Blocks"].ByteArrayValue; byte[] data = sectionTag["Data"].ByteArrayValue; NbtTag addTag = sectionTag["Add"]; byte[] adddata = new byte[2048]; if (addTag != null) { adddata = addTag.ByteArrayValue; } byte[] blockLight = sectionTag["BlockLight"].ByteArrayValue; byte[] skyLight = sectionTag["SkyLight"].ByteArrayValue; var chunk = chunkColumn.chunks[sectionIndex]; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int yi = (sectionIndex << 4) + y; int anvilIndex = (y << 8) + (z << 4) + x; int blockId = blocks[anvilIndex] + (Nibble4(adddata, anvilIndex) << 8); // Anvil to PE friendly converstion Func <int, byte, byte> dataConverter = (i, b) => b; // Default no-op converter if (convertBid && Convert.ContainsKey(blockId)) { dataConverter = Convert[blockId].Item2; blockId = Convert[blockId].Item1; } //else //{ // if (BlockFactory.GetBlockById((byte)blockId).GetType() == typeof(Block)) // { // Log.Warn($"No block implemented for block ID={blockId}, Meta={data}"); // //blockId = 57; // } //} chunkColumn.isAllAir &= blockId == 0; if (blockId > 255) { Log.Warn($"Failed mapping for block ID={blockId}, Meta={data}"); blockId = 41; } if (yi == 0 && (blockId == 8 || blockId == 9)) { blockId = 7; // Bedrock under water } chunk.SetBlock(x, y, z, (byte)blockId); byte metadata = Nibble4(data, anvilIndex); metadata = dataConverter(blockId, metadata); chunk.SetMetadata(x, y, z, metadata); if (ReadBlockLight) { chunk.SetBlocklight(x, y, z, Nibble4(blockLight, anvilIndex)); } if (ReadSkyLight) { chunk.SetSkylight(x, y, z, Nibble4(skyLight, anvilIndex)); } else { chunk.SetSkylight(x, y, z, 0); } if (blockId == 0) { continue; } if (convertBid && blockId == 3 && metadata == 2) { // Dirt Podzol => (Podzol) chunk.SetBlock(x, y, z, 243); chunk.SetMetadata(x, y, z, 0); blockId = 243; } if (BlockFactory.LuminousBlocks[blockId] != 0) { var block = BlockFactory.GetBlockById(chunk.GetBlock(x, y, z)); block.Coordinates = new BlockCoordinates(x + (chunkColumn.x << 4), yi, z + (chunkColumn.z << 4)); chunk.SetBlocklight(x, y, z, (byte)block.LightLevel); lock (LightSources) LightSources.Enqueue(block); } } } } }
public static NbtFile CreateNbtFromChunkColumn(ChunkColumn chunk, int yoffset) { var nbt = new NbtFile(); NbtCompound levelTag = new NbtCompound("Level"); nbt.RootTag.Add(levelTag); levelTag.Add(new NbtInt("xPos", chunk.x)); levelTag.Add(new NbtInt("zPos", chunk.z)); levelTag.Add(new NbtByteArray("Biomes", chunk.biomeId)); NbtList sectionsTag = new NbtList("Sections"); levelTag.Add(sectionsTag); for (int i = 0; i < 8; i++) { NbtCompound sectionTag = new NbtCompound(); sectionsTag.Add(sectionTag); sectionTag.Add(new NbtByte("Y", (byte)i)); int sy = i * 16; byte[] blocks = new byte[4096]; byte[] data = new byte[2048]; byte[] blockLight = new byte[2048]; byte[] skyLight = new byte[2048]; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int yi = sy + y; if (yi < 0 || yi >= 256) { continue; // ? } int anvilIndex = (y + yoffset) * 16 * 16 + z * 16 + x; byte blockId = chunk.GetBlock(x, yi, z); // PE to Anvil friendly converstion if (blockId == 5) { blockId = 125; } else if (blockId == 158) { blockId = 126; } else if (blockId == 50) { blockId = 75; } else if (blockId == 50) { blockId = 76; } else if (blockId == 89) { blockId = 123; } else if (blockId == 89) { blockId = 124; } else if (blockId == 73) { blockId = 152; } blocks[anvilIndex] = blockId; SetNibble4(data, anvilIndex, chunk.GetMetadata(x, yi, z)); SetNibble4(blockLight, anvilIndex, chunk.GetBlocklight(x, yi, z)); SetNibble4(skyLight, anvilIndex, chunk.GetSkylight(x, yi, z)); } } } sectionTag.Add(new NbtByteArray("Blocks", blocks)); sectionTag.Add(new NbtByteArray("Data", data)); sectionTag.Add(new NbtByteArray("BlockLight", blockLight)); sectionTag.Add(new NbtByteArray("SkyLight", skyLight)); } // TODO: Save entities NbtList entitiesTag = new NbtList("Entities", NbtTagType.Compound); levelTag.Add(entitiesTag); NbtList blockEntitiesTag = new NbtList("TileEntities", NbtTagType.Compound); levelTag.Add(blockEntitiesTag); foreach (NbtCompound blockEntityNbt in chunk.BlockEntities.Values) { NbtCompound nbtClone = (NbtCompound)blockEntityNbt.Clone(); nbtClone.Name = null; blockEntitiesTag.Add(nbtClone); } levelTag.Add(new NbtList("TileTicks", NbtTagType.Compound)); return(nbt); }
private void GenerateTree(ChunkColumn chunk, int x, int treebase, int z, WoodType woodType) { new OakTree().Create(chunk, x, treebase, z); }
public static void SetSkyLight(BlockCoordinates coordinates, byte skyLight, ChunkColumn chunk) { chunk?.SetSkyLight(coordinates.X & 0x0f, coordinates.Y & 0xff, coordinates.Z & 0x0f, skyLight); }
public SkyLightBlockAccess(IWorldProvider worldProvider, ChunkColumn chunk) { _worldProvider = worldProvider; _chunk = chunk; _coord = new ChunkCoordinates(chunk.x, chunk.z); }
public static void SaveChunk(ChunkColumn chunk, string basePath, int yoffset) { var coordinates = new ChunkCoordinates(chunk.x, chunk.z); int width = 32; int depth = 32; int rx = coordinates.X >> 5; int rz = coordinates.Z >> 5; string filePath = Path.Combine(basePath, string.Format(@"region\r.{0}.{1}.mca", rx, rz)); if (!File.Exists(filePath)) { // Make sure directory exist Directory.CreateDirectory(Path.Combine(basePath, "region")); // Create empty region file using (var regionFile = File.Open(filePath, FileMode.CreateNew)) { byte[] buffer = new byte[8192]; regionFile.Write(buffer, 0, buffer.Length); } return; } using (var regionFile = File.Open(filePath, FileMode.Open)) { byte[] buffer = new byte[8192]; regionFile.Read(buffer, 0, buffer.Length); 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; int length = regionFile.ReadByte(); if (offset == 0 || length == 0) { regionFile.Seek(0, SeekOrigin.End); offset = (int)regionFile.Position; regionFile.Seek(tableOffset, SeekOrigin.Begin); byte[] bytes = BitConverter.GetBytes(offset >> 4); Array.Reverse(bytes); regionFile.Write(bytes, 0, 3); regionFile.WriteByte(1); } // Write NBT NbtFile nbt = CreateNbtFromChunkColumn(chunk, yoffset); byte[] nbtBuf = nbt.SaveToBuffer(NbtCompression.ZLib); int lenght = nbtBuf.Length; byte[] lenghtBytes = BitConverter.GetBytes(lenght + 1); Array.Reverse(lenghtBytes); regionFile.Seek(offset, SeekOrigin.Begin); regionFile.Write(lenghtBytes, 0, 4); // Lenght regionFile.WriteByte(0x02); // Compression mode regionFile.Write(nbtBuf, 0, nbtBuf.Length); int reminder; Math.DivRem(lenght + 4, 4096, out reminder); byte[] padding = new byte[4096 - reminder]; if (padding.Length > 0) { regionFile.Write(padding, 0, padding.Length); } } }
public static ChunkColumn GetChunk(ChunkCoordinates coordinates, string basePath, IWorldProvider generator, int yoffset) { int width = 32; int depth = 32; int rx = coordinates.X >> 5; int rz = coordinates.Z >> 5; string filePath = Path.Combine(basePath, string.Format(@"region\r.{0}.{1}.mca", rx, rz)); if (!File.Exists(filePath)) { return(generator.GenerateChunkColumn(coordinates)); } 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; int length = regionFile.ReadByte(); if (offset == 0 || length == 0) { return(generator.GenerateChunkColumn(coordinates)); } regionFile.Seek(offset, SeekOrigin.Begin); byte[] waste = new byte[4]; regionFile.Read(waste, 0, 4); int compressionMode = regionFile.ReadByte(); var nbt = new NbtFile(); nbt.LoadFromStream(regionFile, NbtCompression.ZLib); NbtTag dataTag = nbt.RootTag["Level"]; NbtList sections = dataTag["Sections"] as NbtList; ChunkColumn chunk = new ChunkColumn { x = coordinates.X, z = coordinates.Z, biomeId = dataTag["Biomes"].ByteArrayValue }; for (int i = 0; i < chunk.biomeId.Length; i++) { if (chunk.biomeId[i] > 22) { chunk.biomeId[i] = 0; } } if (chunk.biomeId.Length > 256) { throw new Exception(); } // This will turn into a full chunk column foreach (NbtTag sectionTag in sections) { int sy = sectionTag["Y"].ByteValue * 16; byte[] blocks = sectionTag["Blocks"].ByteArrayValue; byte[] data = sectionTag["Data"].ByteArrayValue; NbtTag addTag = sectionTag["Add"]; byte[] adddata = new byte[2048]; if (addTag != null) { adddata = addTag.ByteArrayValue; } byte[] blockLight = sectionTag["BlockLight"].ByteArrayValue; byte[] skyLight = sectionTag["SkyLight"].ByteArrayValue; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int yi = sy + y - yoffset; if (yi < 0 || yi >= 128) { continue; } int anvilIndex = y * 16 * 16 + z * 16 + x; int blockId = blocks[anvilIndex] + (Nibble4(adddata, anvilIndex) << 8); // Anvil to PE friendly converstion if (blockId == 125) { blockId = 5; } else if (blockId == 126) { blockId = 158; } else if (blockId == 75) { blockId = 50; } else if (blockId == 76) { blockId = 50; } else if (blockId == 123) { blockId = 89; } else if (blockId == 124) { blockId = 89; } else if (blockId == 152) { blockId = 73; } else if (_ignore.BinarySearch(blockId) >= 0) { blockId = 0; } else if (_gaps.BinarySearch(blockId) >= 0) { Debug.WriteLine("Missing material: " + blockId); blockId = 133; } if (blockId > 255) { blockId = 41; } if (yi == 127 && blockId != 0) { blockId = 30; } if (yi == 0 && (blockId == 8 || blockId == 9 /*|| blockId == 0*/)) { blockId = 7; } //if (blockId != 0) blockId = 41; chunk.SetBlock(x, yi, z, (byte)blockId); chunk.SetMetadata(x, yi, z, Nibble4(data, anvilIndex)); chunk.SetBlocklight(x, yi, z, Nibble4(blockLight, anvilIndex)); chunk.SetSkylight(x, yi, z, Nibble4(skyLight, anvilIndex)); } } } } NbtList entities = dataTag["Entities"] as NbtList; NbtList blockEntities = dataTag["TileEntities"] as NbtList; if (blockEntities != null) { foreach (var nbtTag in blockEntities) { var blockEntityTag = (NbtCompound)nbtTag; string entityId = blockEntityTag["id"].StringValue; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue - yoffset; int z = blockEntityTag["z"].IntValue; blockEntityTag["y"] = new NbtInt("y", y); BlockEntity blockEntity = BlockEntityFactory.GetBlockEntityById(entityId); if (blockEntity != null) { blockEntityTag.Name = string.Empty; chunk.SetBlockEntity(new BlockCoordinates(x, y, z), blockEntityTag); } } } NbtList tileTicks = dataTag["TileTicks"] as NbtList; chunk.isDirty = false; return(chunk); } }
public static void SaveChunk(ChunkColumn chunk, string basePath) { // WARNING: This method does not consider growing size of the chunks. Needs refactoring to find // free sectors and clear up old ones. It works fine as long as no dynamic data is written // like block entity data (signs etc). Stopwatch time = new Stopwatch(); time.Restart(); chunk.NeedSave = false; var coordinates = new ChunkCoordinates(chunk.x, chunk.z); 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)); Log.Debug($"Save chunk X={chunk.x}, Z={chunk.z} to {filePath}"); if (!File.Exists(filePath)) { // Make sure directory exist Directory.CreateDirectory(Path.Combine(basePath, "region")); // Create empty region file using (var regionFile = File.Open(filePath, FileMode.CreateNew)) { byte[] buffer = new byte[8192]; regionFile.Write(buffer, 0, buffer.Length); } } Stopwatch testTime = new Stopwatch(); using (var regionFile = File.Open(filePath, FileMode.Open)) { 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(); testTime.Restart(); // RESTART // Seriaize NBT to get lenght NbtFile nbt = CreateNbtFromChunkColumn(chunk); testTime.Stop(); byte[] nbtBuf = nbt.SaveToBuffer(NbtCompression.ZLib); int nbtLength = nbtBuf.Length; byte nbtSectorCount = (byte)Math.Ceiling(nbtLength / 4096d); // Don't write yet, just use the lenght //TODO: Search for available sectors if (offset == 0 || sectorCount == 0 || nbtSectorCount > sectorCount) { if (Log.IsDebugEnabled) { if (sectorCount != 0) { Log.Warn($"Creating new sectors for this chunk even tho it existed. Old sector count={sectorCount}, new sector count={nbtSectorCount} (lenght={nbtLength})"); } } regionFile.Seek(0, SeekOrigin.End); offset = (int)((int)regionFile.Position & 0xfffffff0); regionFile.Seek(locationIndex, SeekOrigin.Begin); byte[] bytes = BitConverter.GetBytes(offset >> 4); Array.Reverse(bytes); regionFile.Write(bytes, 0, 3); regionFile.WriteByte(nbtSectorCount); regionFile.Seek(4096 + locationIndex, SeekOrigin.Begin); bytes = BitConverter.GetBytes((int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds); Array.Reverse(bytes); regionFile.Write(bytes, 0, bytes.Length); } byte[] lenghtBytes = BitConverter.GetBytes(nbtLength + 1); Array.Reverse(lenghtBytes); regionFile.Seek(offset, SeekOrigin.Begin); regionFile.Write(lenghtBytes, 0, 4); // Lenght regionFile.WriteByte(0x02); // Compression mode regionFile.Write(nbtBuf, 0, nbtBuf.Length); int reminder; Math.DivRem(nbtLength + 4, 4096, out reminder); byte[] padding = new byte[4096 - reminder]; if (padding.Length > 0) { regionFile.Write(padding, 0, padding.Length); } testTime.Stop(); // STOP Log.Warn($"Took {time.ElapsedMilliseconds}ms to save. And {testTime.ElapsedMilliseconds}ms to generate bytes from NBT"); } }
public IEnumerable <McpeBatch> GenerateChunks(ChunkCoordinates chunkPosition, Dictionary <Tuple <int, int>, McpeBatch> chunksUsed) { lock (chunksUsed) { Dictionary <Tuple <int, int>, double> newOrders = new Dictionary <Tuple <int, int>, double>(); // ViewDistance is actually ViewArea // A = pi r^2 // sqrt(A/pi) = r double radiusSquared = ViewDistance / Math.PI; double radius = Math.Ceiling(Math.Sqrt(radiusSquared)); int centerX = chunkPosition.X; int centerZ = chunkPosition.Z; for (double x = -radius; x <= radius; ++x) { for (double z = -radius; z <= radius; ++z) { var distance = (x * x) + (z * z); if (distance > radiusSquared) { continue; } int chunkX = (int)(x + centerX); int chunkZ = (int)(z + centerZ); Tuple <int, int> index = new Tuple <int, int>(chunkX, chunkZ); newOrders[index] = distance; } } if (newOrders.Count > ViewDistance) { foreach (var pair in newOrders.OrderByDescending(pair => pair.Value)) { if (newOrders.Count <= ViewDistance) { break; } newOrders.Remove(pair.Key); } } foreach (var chunkKey in chunksUsed.Keys.ToArray()) { if (!newOrders.ContainsKey(chunkKey)) { chunksUsed.Remove(chunkKey); } } foreach (var pair in newOrders.OrderBy(pair => pair.Value)) { if (chunksUsed.ContainsKey(pair.Key)) { continue; } if (_worldProvider == null) { continue; } ChunkColumn chunkColumn = _worldProvider.GenerateChunkColumn(new ChunkCoordinates(pair.Key.Item1, pair.Key.Item2)); McpeBatch chunk = null; if (chunkColumn != null) { chunk = chunkColumn.GetBatch(); } chunksUsed.Add(pair.Key, chunk); yield return(chunk); } if (chunksUsed.Count > ViewDistance) { Debug.WriteLine("Too many chunks used: {0}", chunksUsed.Count); } } }
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 Block GetBlock(BlockCoordinates coord, ChunkColumn tryChunk = null) { return(null); }
public static NbtFile CreateNbtFromChunkColumn(ChunkColumn chunk) { var nbt = new NbtFile(); NbtCompound levelTag = new NbtCompound("Level"); nbt.RootTag.Add(levelTag); levelTag.Add(new NbtByte("MCPE BID", 1)); // Indicate that the chunks contain PE block ID's. levelTag.Add(new NbtInt("xPos", chunk.x)); levelTag.Add(new NbtInt("zPos", chunk.z)); levelTag.Add(new NbtByteArray("Biomes", chunk.biomeId)); NbtList sectionsTag = new NbtList("Sections", NbtTagType.Compound); levelTag.Add(sectionsTag); for (int i = 0; i < 16; i++) { var section = chunk.chunks[i]; if (section.IsAllAir()) { continue; } NbtCompound sectionTag = new NbtCompound(); sectionsTag.Add(sectionTag); sectionTag.Add(new NbtByte("Y", (byte)i)); byte[] blocks = new byte[4096]; byte[] data = new byte[2048]; byte[] blockLight = new byte[2048]; byte[] skyLight = new byte[2048]; { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int anvilIndex = y * 16 * 16 + z * 16 + x; byte blockId = section.GetBlock(x, y, z); blocks[anvilIndex] = blockId; SetNibble4(data, anvilIndex, section.GetMetadata(x, y, z)); SetNibble4(blockLight, anvilIndex, section.GetBlocklight(x, y, z)); SetNibble4(skyLight, anvilIndex, section.GetSkylight(x, y, z)); } } } } sectionTag.Add(new NbtByteArray("Blocks", blocks)); sectionTag.Add(new NbtByteArray("Data", data)); sectionTag.Add(new NbtByteArray("BlockLight", blockLight)); sectionTag.Add(new NbtByteArray("SkyLight", skyLight)); } int[] heights = new int[256]; for (int h = 0; h < heights.Length; h++) { heights[h] = chunk.height[h]; } levelTag.Add(new NbtIntArray("HeightMap", heights)); // TODO: Save entities NbtList entitiesTag = new NbtList("Entities", NbtTagType.Compound); //foreach(var entity in ) levelTag.Add(entitiesTag); NbtList blockEntitiesTag = new NbtList("TileEntities", NbtTagType.Compound); foreach (NbtCompound blockEntityNbt in chunk.BlockEntities.Values) { NbtCompound nbtClone = (NbtCompound)blockEntityNbt.Clone(); nbtClone.Name = null; blockEntitiesTag.Add(nbtClone); } levelTag.Add(blockEntitiesTag); levelTag.Add(new NbtList("TileTicks", NbtTagType.Compound)); return(nbt); }
private byte SetLightLevel(IBlockAccess level, ChunkColumn chunk, Chunk section, int sectionIdx, Queue <BlockCoordinates> lightBfsQueue, HashSet <BlockCoordinates> lightBfSet, BlockCoordinates coordinates, byte lightLevel, bool down = false, bool up = false) { //Interlocked.Add(ref visits, 1); if (TrackResults) { MakeVisit(coordinates); } if (!(up || down) && (chunk.x != coordinates.X >> 4 || chunk.z != coordinates.Z >> 4)) { chunk = level.GetChunk((ChunkCoordinates)coordinates); section = null; } else { if ((up || down) && coordinates.Y >> 4 != sectionIdx) { section = null; } } if (chunk?.chunks == null) { return(lightLevel); } if (!down && !up && coordinates.Y >= GetHeight(coordinates, chunk)) { if (GetSkyLight(coordinates, section) != 15) { SetSkyLight(coordinates, 15, chunk); if (!lightBfSet.Contains(coordinates)) { lightBfsQueue.Enqueue(coordinates); lightBfSet.Add(coordinates); } } return(15); } if (section == null) { section = chunk.chunks[coordinates.Y >> 4]; } bool isTransparent = IsTransparent(coordinates, section); byte skyLight = GetSkyLight(coordinates, section); if (down && isTransparent && lightLevel == 15) { if (IsNotBlockingSkylight(coordinates, chunk)) { if (skyLight != 15) { SetSkyLight(coordinates, 15, chunk); } if (!lightBfSet.Contains(coordinates)) { lightBfsQueue.Enqueue(coordinates); lightBfSet.Add(coordinates); } return(15); } } if (isTransparent) { int diffuseLevel = GetDiffuseLevel(coordinates, section); if (skyLight + 1 + diffuseLevel <= lightLevel) { byte newLevel = (byte)(lightLevel - diffuseLevel); SetSkyLight(coordinates, newLevel, chunk); if (!lightBfSet.Contains(coordinates)) { lightBfsQueue.Enqueue(coordinates); lightBfSet.Add(coordinates); } return(newLevel); } } return(skyLight); }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, string basePath, IWorldProvider generator, int yoffset) { 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); //return new ChunkColumn //{ // x = coordinates.X, // z = coordinates.Z, //}; } 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); //return new ChunkColumn //{ // x = coordinates.X, // z = coordinates.Z, //}; } 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); NbtTag dataTag = nbt.RootTag["Level"]; 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(); } // This will turn into a full chunk column foreach (NbtTag sectionTag in sections) { ReadSection(yoffset, sectionTag, chunk); } 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 - yoffset; int z = blockEntityTag["z"].IntValue; blockEntityTag["y"] = new NbtInt("y", y); 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); } } } //NbtList tileTicks = dataTag["TileTicks"] as NbtList; chunk.isDirty = false; return(chunk); } }
private void PopulateChunk(ChunkColumn chunk) { int trees = new Random().Next(0, 10); int[,] treeBasePositions = new int[trees, 2]; for (int t = 0; t < trees; t++) { int x = new Random().Next(1, 16); int z = new Random().Next(1, 16); treeBasePositions[t, 0] = x; treeBasePositions[t, 1] = z; } var bottom = new SimplexOctaveGenerator(_seed.GetHashCode(), 8); var overhang = new SimplexOctaveGenerator(_seed.GetHashCode(), 8); overhang.SetScale(1 / 64.0); bottom.SetScale(1 / 128.0); double overhangsMagnitude = 16; double bottomsMagnitude = 32; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { float ox = x + chunk.x * 16; float oz = z + chunk.z * 16; int bottomHeight = (int)((bottom.Noise(ox, oz, 0.5, 0.5) * bottomsMagnitude) + 64.0); int maxHeight = (int)((overhang.Noise(ox, oz, 0.5, 0.5) * overhangsMagnitude) + bottomHeight + 32.0); double threshold = 0.0; maxHeight = Math.Max(1, maxHeight); for (int y = 0; y < maxHeight && y < 255; y++) { if (y <= 1) { chunk.SetBlock(x, y, z, 7); continue; } if (y > bottomHeight) { //part where we do the overhangs double density = overhang.Noise(ox, y, oz, 0.5, 0.5); if (density > threshold) { chunk.SetBlock(x, y, z, (byte)Material.Stone); } } else { chunk.SetBlock(x, y, z, (byte)Material.Stone); } } //turn the tops into grass chunk.SetBlock(x, bottomHeight, z, (byte)Material.Grass); //the top of the base hills chunk.SetBlock(x, bottomHeight - 1, z, (byte)Material.Dirt); chunk.SetBlock(x, bottomHeight - 2, z, (byte)Material.Dirt); for (int y = bottomHeight + 1; y > bottomHeight && y < maxHeight && y < 255; y++) { //the overhang int thisblock = chunk.GetBlock(x, y, z); int blockabove = chunk.GetBlock(x, y + 1, z); if (thisblock != (decimal)Material.Air && blockabove == (decimal)Material.Air) { if (chunk.GetBlock(x, y, z) == (byte)Material.Dirt || chunk.GetBlock(x, y, z) == (byte)Material.Air || chunk.GetBlock(x, y, z) == (byte)Material.Stone) { chunk.SetBlock(x, y, z, (byte)Material.Grass); } if (chunk.GetBlock(x, y - 1, z) != (decimal)Material.Air) { chunk.SetBlock(x, y - 1, z, (byte)Material.Dirt); } if (chunk.GetBlock(x, y - 2, z) != (decimal)Material.Air) { chunk.SetBlock(x, y - 2, z, (byte)Material.Dirt); } } } for (int y = 0; y < WaterLevel; y++) { //Lake generation if (y < WaterLevel) { if (chunk.GetBlock(x, y, z) == (decimal)Material.Grass || chunk.GetBlock(x, y, z) == (decimal)Material.Dirt) //Grass or Dirt? { if (GetRandomNumber(1, 40) == 1 && y < WaterLevel - 4) { chunk.SetBlock(x, y, z, 82); //Clay } else { chunk.SetBlock(x, y, z, 12); //Sand } } if (chunk.GetBlock(x, y + 1, z) == (decimal)Material.Air) { if (y < WaterLevel - 3) { chunk.SetBlock(x, y + 1, z, 8); //FlowingWater } } } } for (int y = 0; y < 255; y++) { int thisblock = chunk.GetBlock(x, y, z); int blockabove = chunk.GetBlock(x, y + 1, z); if (thisblock == (decimal)Material.Grass && blockabove == (decimal)Material.Air && y > WaterLevel) { //Grass if (GetRandomNumber(0, 5) == 1) { chunk.SetBlock(x, y + 1, z, 31); chunk.SetMetadata(x, y + 1, z, 1); } //Flowers if (GetRandomNumber(0, 65) == 1) { int meta = GetRandomNumber(0, 8); chunk.SetBlock(x, y + 1, z, 38); chunk.SetMetadata(x, y + 1, z, (byte)meta); } //Trees for (int pos = 0; pos < trees; pos++) { if (treeBasePositions[pos, 0] < 14 && treeBasePositions[pos, 0] > 4 && treeBasePositions[pos, 1] < 14 && treeBasePositions[pos, 1] > 4) { if (chunk.GetBlock(treeBasePositions[pos, 0], y + 1, treeBasePositions[pos, 1]) == 2) { if (y >= bottomHeight) { GenerateTree(chunk, treeBasePositions[pos, 0], y + 1, treeBasePositions[pos, 1], WoodType.Oak); } } } } } } } } }
private void ReadSection(int yoffset, NbtTag sectionTag, ChunkColumn chunk) { int sy = sectionTag["Y"].ByteValue * 16; byte[] blocks = sectionTag["Blocks"].ByteArrayValue; byte[] data = sectionTag["Data"].ByteArrayValue; NbtTag addTag = sectionTag["Add"]; byte[] adddata = new byte[2048]; if (addTag != null) { adddata = addTag.ByteArrayValue; } byte[] blockLight = sectionTag["BlockLight"].ByteArrayValue; byte[] skyLight = sectionTag["SkyLight"].ByteArrayValue; for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int yi = sy + y - yoffset; if (yi < 0 || yi >= 256) { continue; } int anvilIndex = y * 16 * 16 + z * 16 + x; int blockId = blocks[anvilIndex] + (Nibble4(adddata, anvilIndex) << 8); // Anvil to PE friendly converstion Func <int, byte, byte> dataConverter = (i, b) => b; // Default no-op converter if (Convert.ContainsKey(blockId)) { dataConverter = Convert[blockId].Item2; blockId = Convert[blockId].Item1; } else { if (BlockFactory.GetBlockById((byte)blockId).GetType() == typeof(Block)) { Log.Warn($"No block implemented for block ID={blockId}, Meta={data}"); //blockId = 57; } } chunk.isAllAir = chunk.isAllAir && blockId == 0; if (blockId > 255) { Log.Warn($"Failed mapping for block ID={blockId}, Meta={data}"); blockId = 41; } //if (yi == 127 && blockId != 0) blockId = 30; if (yi == 0 && (blockId == 8 || blockId == 9)) { blockId = 7; } chunk.SetBlock(x, yi, z, (byte)blockId); byte metadata = Nibble4(data, anvilIndex); metadata = dataConverter(blockId, metadata); chunk.SetMetadata(x, yi, z, metadata); chunk.SetBlocklight(x, yi, z, Nibble4(blockLight, anvilIndex)); chunk.SetSkyLight(x, yi, z, Nibble4(skyLight, anvilIndex)); //if (block is BlockStairs || block is StoneSlab || block is WoodSlab) //{ // chunk.SetSkylight(x, yi, z, 0xff); //} if (blockId == 43 && chunk.GetMetadata(x, yi, z) == 7) { chunk.SetMetadata(x, yi, z, 6); } else if (blockId == 44 && chunk.GetMetadata(x, yi, z) == 7) { chunk.SetMetadata(x, yi, z, 6); } else if (blockId == 44 && chunk.GetMetadata(x, yi, z) == 15) { chunk.SetMetadata(x, yi, z, 14); } else if (blockId == 3 && chunk.GetMetadata(x, yi, z) == 1) { // Dirt Course => (Grass Path) chunk.SetBlock(x, yi, z, 198); chunk.SetMetadata(x, yi, z, 0); } else if (blockId == 3 && chunk.GetMetadata(x, yi, z) == 2) { // Dirt Podzol => (Podzol) chunk.SetBlock(x, yi, z, 243); chunk.SetMetadata(x, yi, z, 0); } var block = BlockFactory.GetBlockById(chunk.GetBlock(x, yi, z)); if (block.LightLevel > 0) { block.Coordinates = new BlockCoordinates(x + (16 * chunk.x), yi, z + (16 * chunk.z)); LightSources.Enqueue(block); } } } } }
public ChunkColumn GenerateChunkColumn(ChunkCoordinates chunkCoordinates) { lock (_chunkCache) { ChunkColumn cachedChunk; if (_chunkCache.TryGetValue(chunkCoordinates, out cachedChunk)) { return(cachedChunk); } ChunkColumn chunk = new ChunkColumn(); chunk.x = chunkCoordinates.X; chunk.z = chunkCoordinates.Z; //chunk.biomeId = ArrayOf<byte>.Create(256, (byte) rand.Next(0, 37)); int h = PopulateChunk(chunk); //BuildStructures(chunk); //chunk.SetBlock(0, h + 1, 0, 7); //chunk.SetBlock(1, h + 1, 0, 41); //chunk.SetBlock(2, h + 1, 0, 41); //chunk.SetBlock(3, h + 1, 0, 41); //chunk.SetBlock(3, h + 1, 0, 41); ////chunk.SetBlock(6, h + 1, 6, 57); //chunk.SetBlock(9, h, 3, 31); //chunk.SetBiome(9, 3, 30); //chunk.SetBlock(0, h, 1, 161); //chunk.SetBlock(0, h, 2, 18); //chunk.SetBlock(0, h, 15, 31); //chunk.SetBlock(0, h, 14, 161); //chunk.SetBlock(5, h, 13, 18); //chunk.SetBiome(5, 13, 30); //chunk.SetBlock(6, h, 9, 63); //chunk.SetMetadata(6, h, 9, 12); //var blockEntity = GetBlockEntity((chunkCoordinates.X*16) + 6, h, (chunkCoordinates.Z*16) + 9); //chunk.SetBlockEntity(blockEntity.Coordinates, blockEntity.GetCompound()); //if (chunkCoordinates.X == 1 && chunkCoordinates.Z == 1) //{ // for (int x = 0; x < 10; x++) // { // for (int z = 0; z < 10; z++) // { // for (int y = h - 2; y < h; y++) // { // chunk.SetBlock(x, y, z, 8); // } // } // } //} //if (chunkCoordinates.X == 3 && chunkCoordinates.Z == 1) //{ // for (int x = 0; x < 10; x++) // { // for (int z = 0; z < 10; z++) // { // for (int y = h - 1; y < h; y++) // { // chunk.SetBlock(x, y, z, 10); // } // } // } //} //for (int x = 0; x < 16; x++) //{ // for (int z = 0; z < 16; z++) // { // for (int y = 15; y > 0; y--) // { // if (chunk.GetBlock(x, y, z) == 0x00) // { // //chunk.SetSkylight(x, y, z, 0xff); // } // else // { // //chunk.SetSkylight(x, y, z, 0x00); // } // } // } //} chunk.RecalcHeight(); _spawnPoint.Y = h + 2; // Cache chunk.GetBatch(); _chunkCache[chunkCoordinates] = chunk; return(chunk); } }
public static void SaveChunk(ChunkColumn chunk, string basePath, int yoffset) { // WARNING: This method does not consider growing size of the chunks. Needs refactoring to find // free sectors and clear up old ones. It works fine as long as no dynamic data is written // like block entity data (signs etc). Log.Debug($"Save chunk X={chunk.x}, Z={chunk.z} to {basePath}"); chunk.NeedSave = false; var coordinates = new ChunkCoordinates(chunk.x, chunk.z); 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)) { // Make sure directory exist Directory.CreateDirectory(Path.Combine(basePath, "region")); // Create empty region file using (var regionFile = File.Open(filePath, FileMode.CreateNew)) { byte[] buffer = new byte[8192]; regionFile.Write(buffer, 0, buffer.Length); } return; } using (var regionFile = File.Open(filePath, FileMode.Open)) { byte[] buffer = new byte[8192]; regionFile.Read(buffer, 0, buffer.Length); 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; int length = regionFile.ReadByte(); // Seriaize NBT to get lenght NbtFile nbt = CreateNbtFromChunkColumn(chunk, yoffset); byte[] nbtBuf = nbt.SaveToBuffer(NbtCompression.ZLib); int nbtLength = nbtBuf.Length; // Don't write yet, just use the lenght if (offset == 0 || length == 0 || nbtLength < length) { if (length != 0) { Log.Debug("Creating new sectors for this chunk even tho it existed"); } regionFile.Seek(0, SeekOrigin.End); offset = (int)((int)regionFile.Position & 0xfffffff0); regionFile.Seek(tableOffset, SeekOrigin.Begin); byte[] bytes = BitConverter.GetBytes(offset >> 4); Array.Reverse(bytes); regionFile.Write(bytes, 0, 3); regionFile.WriteByte(1); } byte[] lenghtBytes = BitConverter.GetBytes(nbtLength + 1); Array.Reverse(lenghtBytes); regionFile.Seek(offset, SeekOrigin.Begin); regionFile.Write(lenghtBytes, 0, 4); // Lenght regionFile.WriteByte(0x02); // Compression mode regionFile.Write(nbtBuf, 0, nbtBuf.Length); int reminder; Math.DivRem(nbtLength + 4, 4096, out reminder); byte[] padding = new byte[4096 - reminder]; if (padding.Length > 0) { regionFile.Write(padding, 0, padding.Length); } } }