using Minecraft.Server.FourKit.Block; using Minecraft.Server.FourKit.Entity; using System.Runtime.InteropServices; namespace Minecraft.Server.FourKit.Chunk; /// /// Represents a chunk of blocks. /// public class Chunk { private readonly World _world; private readonly int _chunkX; private readonly int _chunkZ; internal Chunk(World world, int chunkX, int chunkZ) { _world = world; _chunkX = chunkX; _chunkZ = chunkZ; } /// /// Gets the X-coordinate of this chunk. /// /// X-coordinate. public int getX() => _chunkX; /// /// Gets the Z-coordinate of this chunk. /// /// Z-coordinate. public int getZ() => _chunkZ; /// /// Gets the world containing this chunk. /// /// Parent World. public World getWorld() => _world; /// /// Gets a block from this chunk. /// /// 0-15 /// 0-127 /// 0-15 /// The Block. public Block.Block getBlock(int x, int y, int z) { return _world.getBlockAt((_chunkX << 4) + x, y, (_chunkZ << 4) + z); } /// /// Capture thread-safe read-only snapshot of chunk data. /// /// ChunkSnapshot. public ChunkSnapshot getChunkSnapshot() { return getChunkSnapshot(false, false); } /// /// Capture thread-safe read-only snapshot of chunk data. /// /// If true, snapshot includes per-coordinate biome type. /// If true, snapshot includes per-coordinate raw biome temperature and rainfall. /// ChunkSnapshot. public ChunkSnapshot getChunkSnapshot(bool includeBiome, bool includeBiomeTempRain) { // this has a lot of overhead // (SYLV)todo: clean this up int dimId = _world.getDimensionId(); int[] blockIds = new int[16 * 128 * 16]; int[] blockData = new int[16 * 128 * 16]; int[] maxBlockY = new int[16 * 16]; int[] skyLight = new int[16 * 128 * 16]; int[] blockLight = new int[16 * 128 * 16]; int[]? biomeIds = includeBiome ? new int[16 * 16] : null; double[]? biomeTemp = includeBiomeTempRain ? new double[16 * 16] : null; double[]? biomeRainfall = includeBiomeTempRain ? new double[16 * 16] : null; if (NativeBridge.GetChunkSnapshot != null) { var hIds = GCHandle.Alloc(blockIds, GCHandleType.Pinned); var hData = GCHandle.Alloc(blockData, GCHandleType.Pinned); var hMaxY = GCHandle.Alloc(maxBlockY, GCHandleType.Pinned); try { NativeBridge.GetChunkSnapshot(dimId, _chunkX, _chunkZ, hIds.AddrOfPinnedObject(), hData.AddrOfPinnedObject(), hMaxY.AddrOfPinnedObject()); } finally { hIds.Free(); hData.Free(); hMaxY.Free(); } } else { for (int lx = 0; lx < 16; lx++) { for (int lz = 0; lz < 16; lz++) { int worldX = (_chunkX << 4) + lx; int worldZ = (_chunkZ << 4) + lz; int highest = 0; for (int ly = 0; ly < 128; ly++) { int idx = (lx * 128 * 16) + (ly * 16) + lz; if (NativeBridge.GetTileId != null) blockIds[idx] = NativeBridge.GetTileId(dimId, worldX, ly, worldZ); if (NativeBridge.GetTileData != null) blockData[idx] = NativeBridge.GetTileData(dimId, worldX, ly, worldZ); if (blockIds[idx] != 0) highest = ly; } maxBlockY[lx * 16 + lz] = highest; } } } for (int lx = 0; lx < 16; lx++) { for (int lz = 0; lz < 16; lz++) { int worldX = (_chunkX << 4) + lx; int worldZ = (_chunkZ << 4) + lz; for (int ly = 0; ly < 128; ly++) { int idx = (lx * 128 * 16) + (ly * 16) + lz; if (NativeBridge.GetSkyLight != null) skyLight[idx] = NativeBridge.GetSkyLight(dimId, worldX, ly, worldZ); if (NativeBridge.GetBlockLight != null) blockLight[idx] = NativeBridge.GetBlockLight(dimId, worldX, ly, worldZ); } if (includeBiome && NativeBridge.GetBiomeId != null) { int colIdx = lx * 16 + lz; int biomeId = NativeBridge.GetBiomeId(dimId, worldX, worldZ); biomeIds![colIdx] = biomeId; if (includeBiomeTempRain) { var biome = Block.BiomeHelper.fromId(biomeId); biomeTemp![colIdx] = biome.getTemperature(); biomeRainfall![colIdx] = biome.getRainfall(); } } } } long captureTime = 0; if (NativeBridge.GetWorldInfo != null) { double[] info = new double[7]; var hInfo = GCHandle.Alloc(info, GCHandleType.Pinned); try { NativeBridge.GetWorldInfo(dimId, hInfo.AddrOfPinnedObject()); } finally { hInfo.Free(); } captureTime = (long)info[4]; } return new ChunkSnapshot(_chunkX, _chunkZ, _world.getName(), captureTime, blockIds, blockData, maxBlockY, skyLight, blockLight, biomeIds, biomeTemp, biomeRainfall); } /// /// Capture thread-safe read-only snapshot of chunk data. /// /// (NONFUNCTIONAL) Only here for parity. /// If true, snapshot includes per-coordinate biome type. /// If true, snapshot includes per-coordinate raw biome temperature and rainfall. /// ChunkSnapshot. public ChunkSnapshot getChunkSnapshot(bool includeMaxblocky, bool includeBiome, bool includeBiomeTempRain) { return getChunkSnapshot(includeBiome, includeBiomeTempRain); } /// /// Get a list of all entities in the chunk. /// /// The entities. public Entity.Entity[] getEntities() { if (NativeBridge.GetChunkEntities == null) return Array.Empty(); int dimId = _world.getDimensionId(); int count = NativeBridge.GetChunkEntities(dimId, _chunkX, _chunkZ, out IntPtr buf); if (count <= 0 || buf == IntPtr.Zero) return Array.Empty(); var result = new Entity.Entity[count]; try { int[] data = new int[count * 3]; Marshal.Copy(buf, data, 0, count * 3); for (int i = 0; i < count; i++) { int entityId = data[i * 3]; int mappedType = data[i * 3 + 1]; int isLiving = data[i * 3 + 2]; var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType) ? (Entity.EntityType)mappedType : Entity.EntityType.UNKNOWN; if (entityType == Entity.EntityType.PLAYER) { var player = FourKit.GetPlayerByEntityId(entityId); if (player != null) { result[i] = player; continue; } } if (isLiving == 1) { result[i] = new Entity.LivingEntity(entityId, entityType, dimId, 0, 0, 0); } else { var entity = new Entity.Entity(); entity.SetEntityIdInternal(entityId); entity.SetEntityTypeInternal(entityType); entity.SetDimensionInternal(dimId); result[i] = entity; } } } finally { Marshal.FreeCoTaskMem(buf); } return result; } /// /// Checks if the chunk is loaded. /// /// True if it is loaded. public bool isLoaded() { if (NativeBridge.IsChunkLoaded != null) return NativeBridge.IsChunkLoaded(_world.getDimensionId(), _chunkX, _chunkZ) != 0; return false; } /// /// Loads the chunk. /// /// Whether or not to generate a chunk if it doesn't already exist. /// true if the chunk has loaded successfully, otherwise false. public bool load(bool generate) { if (NativeBridge.LoadChunk != null) return NativeBridge.LoadChunk(_world.getDimensionId(), _chunkX, _chunkZ, generate ? 1 : 0) != 0; return false; } /// /// Loads the chunk. /// /// true if the chunk has loaded successfully, otherwise false. public bool load() { return load(true); } /// /// Unloads and optionally saves the Chunk. /// /// Controls whether the chunk is saved. /// Controls whether to unload the chunk when players are nearby. /// true if the chunk has unloaded successfully, otherwise false. public bool unload(bool save, bool safe) { if (NativeBridge.UnloadChunk != null) return NativeBridge.UnloadChunk(_world.getDimensionId(), _chunkX, _chunkZ, save ? 1 : 0, safe ? 1 : 0) != 0; return false; } /// /// Unloads and optionally saves the Chunk. /// /// Controls whether the chunk is saved. /// true if the chunk has unloaded successfully, otherwise false. public bool unload(bool save) { return unload(save, true); } /// /// Unloads and optionally saves the Chunk. /// /// true if the chunk has unloaded successfully, otherwise false. public bool unload() { return unload(true, true); } }