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;
}
}
}