using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace MinecraftClient.Mapping { /// /// Represents a Minecraft World /// public class World { /// /// The chunks contained into the Minecraft world /// Tuple: Tuple /// private ConcurrentDictionary, ChunkColumn> chunks = new(); /// /// The dimension info of the world /// private static Dimension curDimension = new(); private static readonly Dictionary dimensionList = new(); /// /// Chunk data parsing progress /// public int chunkCnt = 0; public int chunkLoadNotCompleted = 0; /// /// Read, set or unload the specified chunk column /// /// ChunkColumn X /// ChunkColumn Z /// chunk at the given location public ChunkColumn? this[int chunkX, int chunkZ] { get { chunks.TryGetValue(new(chunkX, chunkZ), out ChunkColumn? chunkColumn); return chunkColumn; } set { Tuple chunkCoord = new(chunkX, chunkZ); if (value == null) chunks.TryRemove(chunkCoord, out _); else chunks.AddOrUpdate(chunkCoord, value, (_, _) => value); } } /// /// Storage of all dimensional data - 1.19.1 and above /// /// Registry Codec nbt data public static void StoreDimensionList(Dictionary registryCodec) { var dimensionListNbt = (object[])(((Dictionary)registryCodec["minecraft:dimension_type"])["value"]); foreach (var (dimensionName, dimensionType) in from Dictionary dimensionNbt in dimensionListNbt let dimensionName = (string)dimensionNbt["name"] let dimensionType = (Dictionary)dimensionNbt["element"] select (dimensionName, dimensionType)) { StoreOneDimension(dimensionName, dimensionType); } } /// /// Store one dimension - Directly used in 1.16.2 to 1.18.2 /// /// Dimension name /// Dimension Type nbt data public static void StoreOneDimension(string dimensionName, Dictionary dimensionType) { if (dimensionList.ContainsKey(dimensionName)) dimensionList.Remove(dimensionName); dimensionList.Add(dimensionName, new Dimension(dimensionName, dimensionType)); } /// /// Set current dimension - 1.16 and above /// /// The name of the dimension type /// The dimension type (NBT Tag Compound) public static void SetDimension(string name) { curDimension = dimensionList[name]; // Should not fail } /// /// Get current dimension /// /// Current dimension public static Dimension GetDimension() { return curDimension; } /// /// Set chunk column at the specified location /// /// ChunkColumn X /// ChunkColumn Y /// ChunkColumn Z /// ChunkColumn size /// Chunk data /// Whether the ChunkColumn has been fully loaded public void StoreChunk(int chunkX, int chunkY, int chunkZ, int chunkColumnSize, Chunk? chunk, bool loadCompleted) { ChunkColumn chunkColumn = chunks.GetOrAdd(new(chunkX, chunkZ), (_) => new(chunkColumnSize)); chunkColumn[chunkY] = chunk; if (loadCompleted) chunkColumn.FullyLoaded = true; } /// /// Get chunk column at the specified location /// /// Location to retrieve chunk column /// The chunk column public ChunkColumn? GetChunkColumn(Location location) { return this[location.ChunkX, location.ChunkZ]; } /// /// Get block at the specified location /// /// Location to retrieve block from /// Block at specified location or Air if the location is not loaded public Block GetBlock(Location location) { ChunkColumn? column = GetChunkColumn(location); if (column != null) { Chunk? chunk = column.GetChunk(location); if (chunk != null) return chunk.GetBlock(location); } return Block.Air; } /// /// Look for a block around the specified location /// /// Start location /// Block type /// Search radius - larger is slower: O^3 complexity /// Block matching the specified block type public List FindBlock(Location from, Material block, double radius) { return FindBlock(from, block, radius, radius, radius); } /// /// Look for a block around the specified location /// /// Start location /// Block type /// Search radius on the X axis /// Search radius on the Y axis /// Search radius on the Z axis /// Block matching the specified block type public List FindBlock(Location from, Material block, double radiusx, double radiusy, double radiusz) { Location minPoint = new Location(from.X - radiusx, from.Y - radiusy, from.Z - radiusz); Location maxPoint = new Location(from.X + radiusx, from.Y + radiusy, from.Z + radiusz); List xRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.X)), Convert.ToInt32(Math.Floor(maxPoint.X - minPoint.X)) + 1).ToList(); List yRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.Y)), Convert.ToInt32(Math.Floor(maxPoint.Y - minPoint.Y)) + 1).ToList(); List zRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.Z)), Convert.ToInt32(Math.Floor(maxPoint.Z - minPoint.Z)) + 1).ToList(); List listOfBlocks = xRange.SelectMany(x => yRange.SelectMany(y => zRange.Select(z => new Location(x, y, z)))).ToList(); return listOfBlocks.Where(loc => GetBlock(loc).Type == block).ToList(); } /// /// Set block at the specified location /// /// Location to set block to /// Block to set public void SetBlock(Location location, Block block) { ChunkColumn? column = this[location.ChunkX, location.ChunkZ]; if (column != null && column.ColumnSize >= location.ChunkY) { Chunk? chunk = column.GetChunk(location); if (chunk == null) column[location.ChunkY] = chunk = new Chunk(); chunk[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ] = block; } } /// /// Clear all terrain data from the world /// public void Clear() { chunks = new(); chunkCnt = 0; chunkLoadNotCompleted = 0; } public static string GetChunkLoadingStatus(World world) { double chunkLoadedRatio; if (world.chunkCnt == 0) chunkLoadedRatio = 0; else chunkLoadedRatio = (world.chunkCnt - world.chunkLoadNotCompleted) / (double)world.chunkCnt; string status = string.Format(Translations.cmd_move_chunk_loading_status, chunkLoadedRatio, world.chunkCnt - world.chunkLoadNotCompleted, world.chunkCnt); return status; } } }