using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace MinecraftClient.Mapping { /// /// Represents a Minecraft World /// public class World { /// /// The chunks contained into the Minecraft world /// private Dictionary> chunks = new(); /// /// The dimension info of the world /// private static Dimension dimension = new(); /// /// Lock for thread safety /// private readonly ReaderWriterLockSlim chunksLock = 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 { chunksLock.EnterReadLock(); try { //Read a chunk if (chunks.ContainsKey(chunkX)) if (chunks[chunkX].ContainsKey(chunkZ)) return chunks[chunkX][chunkZ]; return null; } finally { chunksLock.ExitReadLock(); } } set { chunksLock.EnterWriteLock(); try { if (value != null) { //Update a chunk column if (!chunks.ContainsKey(chunkX)) chunks[chunkX] = new Dictionary(); chunks[chunkX][chunkZ] = value; } else { //Unload a chunk column if (chunks.ContainsKey(chunkX)) { if (chunks[chunkX].ContainsKey(chunkZ)) { chunks[chunkX].Remove(chunkZ); if (chunks[chunkX].Count == 0) chunks.Remove(chunkX); } } } } finally { chunksLock.ExitWriteLock(); } } } /// /// Set dimension type /// /// The name of the dimension type /// The dimension type (NBT Tag Compound) public static void SetDimension(string name, Dictionary nbt) { // will change in 1.19 and above dimension = new Dimension(name, nbt); } /// /// Get current dimension /// /// Current dimension public static Dimension GetDimension() { return dimension; } /// /// 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 = null; chunksLock.EnterUpgradeableReadLock(); try { //Read a chunk if (chunks.ContainsKey(chunkX)) if (chunks[chunkX].ContainsKey(chunkZ)) chunkColumn = chunks[chunkX][chunkZ]; if (chunkColumn == null) { chunkColumn = new ChunkColumn(chunkColumnSize); chunksLock.EnterWriteLock(); try { //Update a chunk column if (!chunks.ContainsKey(chunkX)) chunks[chunkX] = new Dictionary(); chunks[chunkX][chunkZ] = chunkColumn; } finally { chunksLock.ExitWriteLock(); } } } finally { chunksLock.ExitUpgradeableReadLock(); } 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 new Block(0); //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, int 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, int radiusx, int radiusy, int 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 list = new List { }; for (double x = minPoint.X; x <= maxPoint.X; x++) { for (double y = minPoint.Y; y <= maxPoint.Y; y++) { for (double z = minPoint.Z; z <= maxPoint.Z; z++) { Location doneloc = new Location(x, y, z); Block doneblock = GetBlock(doneloc); Material blockType = doneblock.Type; if (blockType == block) { list.Add(doneloc); } } } } return list; } /// /// 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() { chunksLock.EnterWriteLock(); try { chunks = new Dictionary>(); chunkCnt = 0; chunkLoadNotCompleted = 0; } finally { chunksLock.ExitWriteLock(); } } /// /// Get the location of block of the entity is looking /// /// Location of the entity /// Yaw of the entity /// Pitch of the entity /// Location of the block or empty Location if no block was found public Location GetLookingBlockLocation(Location location, double yaw, double pitch) { double rotX = (Math.PI / 180) * yaw; double rotY = (Math.PI / 180) * pitch; double x = -Math.Cos(rotY) * Math.Sin(rotX); double y = -Math.Sin(rotY); double z = Math.Cos(rotY) * Math.Cos(rotX); Location vector = new Location(x, y, z); for (int i = 0; i < 5; i++) { Location newVector = vector * i; Location blockLocation = location.EyesLocation() + new Location(newVector.X, newVector.Y, newVector.Z); blockLocation.X = Math.Floor(blockLocation.X); blockLocation.Y = Math.Floor(blockLocation.Y); blockLocation.Z = Math.Floor(blockLocation.Z); Block b = GetBlock(blockLocation); if (b.Type != Material.Air) { return blockLocation; } } return new Location(); } } }