2015-11-30 15:30:49 +01:00
|
|
|
|
using System;
|
2022-08-31 18:00:00 +08:00
|
|
|
|
using System.Collections.Concurrent;
|
2015-11-30 15:30:49 +01:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MinecraftClient.Mapping
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Represents a Minecraft World
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class World
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The chunks contained into the Minecraft world
|
2022-08-31 18:00:00 +08:00
|
|
|
|
/// Tuple<int, int>: Tuple<chunkX, chunkZ>
|
2015-11-30 15:30:49 +01:00
|
|
|
|
/// </summary>
|
2022-08-31 18:00:00 +08:00
|
|
|
|
private ConcurrentDictionary<Tuple<int, int>, ChunkColumn> chunks = new();
|
2015-11-30 15:30:49 +01:00
|
|
|
|
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The dimension info of the world
|
|
|
|
|
|
/// </summary>
|
2022-09-07 00:02:09 +08:00
|
|
|
|
private static Dimension curDimension = new();
|
2022-08-28 22:27:21 +08:00
|
|
|
|
|
2022-10-02 18:31:08 +08:00
|
|
|
|
private static readonly Dictionary<string, Dimension> dimensionList = new();
|
2022-07-24 21:41:56 +08:00
|
|
|
|
|
2022-07-25 03:19:24 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Chunk data parsing progress
|
|
|
|
|
|
/// </summary>
|
2022-08-18 20:58:49 +02:00
|
|
|
|
public int chunkCnt = 0;
|
|
|
|
|
|
public int chunkLoadNotCompleted = 0;
|
2022-07-25 03:19:24 +08:00
|
|
|
|
|
2015-11-30 15:30:49 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Read, set or unload the specified chunk column
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="chunkX">ChunkColumn X</param>
|
2022-08-24 18:16:16 +08:00
|
|
|
|
/// <param name="chunkZ">ChunkColumn Z</param>
|
2015-11-30 15:30:49 +01:00
|
|
|
|
/// <returns>chunk at the given location</returns>
|
2022-08-24 18:16:16 +08:00
|
|
|
|
public ChunkColumn? this[int chunkX, int chunkZ]
|
2015-11-30 15:30:49 +01:00
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
2022-08-31 18:00:00 +08:00
|
|
|
|
chunks.TryGetValue(new(chunkX, chunkZ), out ChunkColumn? chunkColumn);
|
|
|
|
|
|
return chunkColumn;
|
2015-11-30 15:30:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2022-08-31 18:00:00 +08:00
|
|
|
|
Tuple<int, int> chunkCoord = new(chunkX, chunkZ);
|
|
|
|
|
|
if (value == null)
|
2022-08-31 19:50:11 +08:00
|
|
|
|
chunks.TryRemove(chunkCoord, out _);
|
2022-08-31 18:00:00 +08:00
|
|
|
|
else
|
|
|
|
|
|
chunks.AddOrUpdate(chunkCoord, value, (_, _) => value);
|
2015-11-30 15:30:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 22:27:21 +08:00
|
|
|
|
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// <summary>
|
2022-08-28 22:27:21 +08:00
|
|
|
|
/// Storage of all dimensional data - 1.19.1 and above
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="registryCodec">Registry Codec nbt data</param>
|
|
|
|
|
|
public static void StoreDimensionList(Dictionary<string, object> registryCodec)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dimensionListNbt = (object[])(((Dictionary<string, object>)registryCodec["minecraft:dimension_type"])["value"]);
|
2022-10-02 18:31:08 +08:00
|
|
|
|
foreach (var (dimensionName, dimensionType) in from Dictionary<string, object> dimensionNbt in dimensionListNbt
|
|
|
|
|
|
let dimensionName = (string)dimensionNbt["name"]
|
|
|
|
|
|
let dimensionType = (Dictionary<string, object>)dimensionNbt["element"]
|
|
|
|
|
|
select (dimensionName, dimensionType))
|
2022-08-28 22:27:21 +08:00
|
|
|
|
{
|
2022-09-07 00:08:31 +08:00
|
|
|
|
StoreOneDimension(dimensionName, dimensionType);
|
2022-08-28 22:27:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-07 00:02:09 +08:00
|
|
|
|
/// <summary>
|
2022-09-07 00:08:31 +08:00
|
|
|
|
/// Store one dimension - Directly used in 1.16.2 to 1.18.2
|
2022-09-07 00:02:09 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dimensionName">Dimension name</param>
|
|
|
|
|
|
/// <param name="dimensionType">Dimension Type nbt data</param>
|
2022-09-07 00:08:31 +08:00
|
|
|
|
public static void StoreOneDimension(string dimensionName, Dictionary<string, object> dimensionType)
|
2022-09-07 00:02:09 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (dimensionList.ContainsKey(dimensionName))
|
2022-10-02 18:31:08 +08:00
|
|
|
|
dimensionList.Remove(dimensionName);
|
2022-09-07 00:02:09 +08:00
|
|
|
|
dimensionList.Add(dimensionName, new Dimension(dimensionName, dimensionType));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 22:27:21 +08:00
|
|
|
|
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// <summary>
|
2022-08-28 22:27:21 +08:00
|
|
|
|
/// Set current dimension - 1.16 and above
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name"> The name of the dimension type</param>
|
|
|
|
|
|
/// <param name="nbt">The dimension type (NBT Tag Compound)</param>
|
2022-08-28 22:27:21 +08:00
|
|
|
|
public static void SetDimension(string name)
|
2022-07-24 21:41:56 +08:00
|
|
|
|
{
|
2022-09-07 00:02:09 +08:00
|
|
|
|
curDimension = dimensionList[name]; // Should not fail
|
2022-07-24 21:41:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-02 22:38:59 +08:00
|
|
|
|
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// <summary>
|
2022-08-18 20:58:49 +02:00
|
|
|
|
/// Get current dimension
|
2022-07-24 21:41:56 +08:00
|
|
|
|
/// </summary>
|
2022-08-18 20:58:49 +02:00
|
|
|
|
/// <returns>Current dimension</returns>
|
|
|
|
|
|
public static Dimension GetDimension()
|
2022-07-24 21:41:56 +08:00
|
|
|
|
{
|
2022-08-28 22:27:21 +08:00
|
|
|
|
return curDimension;
|
2022-07-24 21:41:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-25 01:34:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Set chunk column at the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="chunkX">ChunkColumn X</param>
|
|
|
|
|
|
/// <param name="chunkY">ChunkColumn Y</param>
|
|
|
|
|
|
/// <param name="chunkZ">ChunkColumn Z</param>
|
|
|
|
|
|
/// <param name="chunkColumnSize">ChunkColumn size</param>
|
|
|
|
|
|
/// <param name="chunk">Chunk data</param>
|
|
|
|
|
|
/// <param name="loadCompleted">Whether the ChunkColumn has been fully loaded</param>
|
2022-08-25 10:40:55 +08:00
|
|
|
|
public void StoreChunk(int chunkX, int chunkY, int chunkZ, int chunkColumnSize, Chunk? chunk, bool loadCompleted)
|
2022-08-25 01:34:07 +08:00
|
|
|
|
{
|
2022-09-02 21:02:25 +08:00
|
|
|
|
ChunkColumn chunkColumn = chunks.GetOrAdd(new(chunkX, chunkZ), (_) => new(chunkColumnSize));
|
|
|
|
|
|
chunkColumn[chunkY] = chunk;
|
|
|
|
|
|
if (loadCompleted)
|
|
|
|
|
|
chunkColumn.FullyLoaded = true;
|
2022-08-25 01:34:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-30 15:30:49 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get chunk column at the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="location">Location to retrieve chunk column</param>
|
|
|
|
|
|
/// <returns>The chunk column</returns>
|
2022-08-24 18:16:16 +08:00
|
|
|
|
public ChunkColumn? GetChunkColumn(Location location)
|
2015-11-30 15:30:49 +01:00
|
|
|
|
{
|
|
|
|
|
|
return this[location.ChunkX, location.ChunkZ];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get block at the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="location">Location to retrieve block from</param>
|
|
|
|
|
|
/// <returns>Block at specified location or Air if the location is not loaded</returns>
|
|
|
|
|
|
public Block GetBlock(Location location)
|
|
|
|
|
|
{
|
2022-08-24 18:16:16 +08:00
|
|
|
|
ChunkColumn? column = GetChunkColumn(location);
|
2015-11-30 15:30:49 +01:00
|
|
|
|
if (column != null)
|
|
|
|
|
|
{
|
2022-08-24 18:16:16 +08:00
|
|
|
|
Chunk? chunk = column.GetChunk(location);
|
2015-11-30 15:30:49 +01:00
|
|
|
|
if (chunk != null)
|
|
|
|
|
|
return chunk.GetBlock(location);
|
|
|
|
|
|
}
|
2022-10-02 13:49:36 +08:00
|
|
|
|
return Block.Air;
|
2015-11-30 15:30:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-30 00:49:16 +05:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Look for a block around the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="from">Start location</param>
|
|
|
|
|
|
/// <param name="block">Block type</param>
|
|
|
|
|
|
/// <param name="radius">Search radius - larger is slower: O^3 complexity</param>
|
|
|
|
|
|
/// <returns>Block matching the specified block type</returns>
|
|
|
|
|
|
public List<Location> FindBlock(Location from, Material block, int radius)
|
|
|
|
|
|
{
|
|
|
|
|
|
return FindBlock(from, block, radius, radius, radius);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Look for a block around the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="from">Start location</param>
|
|
|
|
|
|
/// <param name="block">Block type</param>
|
|
|
|
|
|
/// <param name="radiusx">Search radius on the X axis</param>
|
|
|
|
|
|
/// <param name="radiusy">Search radius on the Y axis</param>
|
|
|
|
|
|
/// <param name="radiusz">Search radius on the Z axis</param>
|
|
|
|
|
|
/// <returns>Block matching the specified block type</returns>
|
|
|
|
|
|
public List<Location> FindBlock(Location from, Material block, int radiusx, int radiusy, int radiusz)
|
|
|
|
|
|
{
|
2022-10-18 22:39:48 +02:00
|
|
|
|
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<int> xRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.X)), Convert.ToInt32(Math.Floor(maxPoint.X - minPoint.X)) + 1).ToList();
|
|
|
|
|
|
List<int> yRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.Y)), Convert.ToInt32(Math.Floor(maxPoint.Y - minPoint.Y)) + 1).ToList();
|
|
|
|
|
|
List<int> zRange = Enumerable.Range(Convert.ToInt32(Math.Floor(minPoint.Z)), Convert.ToInt32(Math.Floor(maxPoint.Z - minPoint.Z)) + 1).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
List<Location> 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();
|
2020-07-30 00:49:16 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-30 15:30:49 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Set block at the specified location
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="location">Location to set block to</param>
|
|
|
|
|
|
/// <param name="block">Block to set</param>
|
|
|
|
|
|
public void SetBlock(Location location, Block block)
|
|
|
|
|
|
{
|
2022-08-24 18:16:16 +08:00
|
|
|
|
ChunkColumn? column = this[location.ChunkX, location.ChunkZ];
|
|
|
|
|
|
if (column != null && column.ColumnSize >= location.ChunkY)
|
2015-11-30 15:30:49 +01:00
|
|
|
|
{
|
2022-08-24 18:16:16 +08:00
|
|
|
|
Chunk? chunk = column.GetChunk(location);
|
2015-11-30 15:30:49 +01:00
|
|
|
|
if (chunk == null)
|
|
|
|
|
|
column[location.ChunkY] = chunk = new Chunk();
|
|
|
|
|
|
chunk[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ] = block;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-04-28 21:32:03 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Clear all terrain data from the world
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
2022-08-31 18:00:00 +08:00
|
|
|
|
chunks = new();
|
|
|
|
|
|
chunkCnt = 0;
|
|
|
|
|
|
chunkLoadNotCompleted = 0;
|
2019-04-28 21:32:03 +02:00
|
|
|
|
}
|
2022-10-02 18:31:08 +08:00
|
|
|
|
|
|
|
|
|
|
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 = Translations.Get("cmd.move.chunk_loading_status",
|
|
|
|
|
|
chunkLoadedRatio, world.chunkCnt - world.chunkLoadNotCompleted, world.chunkCnt);
|
|
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
|
}
|
2015-11-30 15:30:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|