diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 1e3ed74ef..27ee68b65 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -1795,6 +1795,7 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
chunkPacketManagement_PostTick();
}
+ lastTime = getCurrentTimeMillis();
// int64_t afterall = System::currentTimeMillis();
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
// PIXReportCounter(L"Server ticks",(float)tickcount);
diff --git a/Minecraft.Client/ServerChunkCache.cpp b/Minecraft.Client/ServerChunkCache.cpp
index c7d70c7d3..c9292a207 100644
--- a/Minecraft.Client/ServerChunkCache.cpp
+++ b/Minecraft.Client/ServerChunkCache.cpp
@@ -12,6 +12,9 @@
#include "..\Minecraft.World\compression.h"
#include "..\Minecraft.World\OldChunkStorage.h"
#include "..\Minecraft.World\Tile.h"
+#ifdef MINECRAFT_SERVER_BUILD
+#include "..\Minecraft.Server\FourKitBridge.h"
+#endif
ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source)
{
@@ -152,7 +155,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
{
EnterCriticalSection(&m_csLoadCreate);
chunk = load(x, z);
- if (chunk == nullptr)
+#ifdef MINECRAFT_SERVER_BUILD
+ bool isNewChunk = (chunk == nullptr);
+#endif
+ if (chunk == nullptr)
{
if (source == nullptr)
{
@@ -231,6 +237,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
if( hasChunk( x - 1, z ) && hasChunk( x + 1, z ) && hasChunk ( x, z - 1 ) && hasChunk( x, z + 1 ) ) chunk->checkChests( this, x, z );
LeaveCriticalSection(&m_csLoadCreate);
+
+#ifdef MINECRAFT_SERVER_BUILD
+ FourKitBridge::FireChunkLoad(level->dimension->id, x, z, isNewChunk);
+#endif
}
else
{
@@ -941,23 +951,30 @@ bool ServerChunkCache::tick()
// player's tick is called to remove them from the chunk they used to be in, and add them to their current chunk. This will only be a temporary state and
// we should be able to unload the chunk on the next call to this tick.
if( !chunk->containsPlayer() )
- {
+ {
+#ifdef MINECRAFT_SERVER_BUILD
+ if (!FourKitBridge::FireChunkUnload(level->dimension->id, chunk->x, chunk->z))
+ {
+#endif
save(chunk);
saveEntities(chunk);
chunk->unload(true);
//loadedChunks.remove(cp);
//loadedChunkList.remove(chunk);
- auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
- if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
+ auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
+ if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
int ix = chunk->x + XZOFFSET;
int iz = chunk->z + XZOFFSET;
int idx = ix * XZSIZE + iz;
m_unloadedCache[idx] = chunk;
cache[idx] = nullptr;
+ }
}
+#ifdef MINECRAFT_SERVER_BUILD
}
+#endif
m_toDrop.pop_front();
}
}
diff --git a/Minecraft.Server.FourKit/Block/Biome.cs b/Minecraft.Server.FourKit/Block/Biome.cs
new file mode 100644
index 000000000..82c49f641
--- /dev/null
+++ b/Minecraft.Server.FourKit/Block/Biome.cs
@@ -0,0 +1,109 @@
+namespace Minecraft.Server.FourKit.Block;
+
+
+public enum Biome
+{
+ OCEAN = 0,
+ PLAINS = 1,
+ DESERT = 2,
+ EXTREME_HILLS = 3,
+ FOREST = 4,
+ TAIGA = 5,
+ SWAMPLAND = 6,
+ RIVER = 7,
+ HELL = 8,
+ SKY = 9,
+ FROZEN_OCEAN = 10,
+ FROZEN_RIVER = 11,
+ ICE_PLAINS = 12,
+ ICE_MOUNTAINS = 13,
+ MUSHROOM_ISLAND = 14,
+ MUSHROOM_SHORE = 15,
+ BEACH = 16,
+ DESERT_HILLS = 17,
+ FOREST_HILLS = 18,
+ TAIGA_HILLS = 19,
+ SMALL_MOUNTAINS = 20,
+ JUNGLE = 21,
+ JUNGLE_HILLS = 22,
+}
+
+
+// more for internal
+// eliminates unnecessary overhead
+internal static class BiomeHelper
+{
+ private static readonly double[] _temperatures = new double[23];
+ private static readonly double[] _rainfalls = new double[23];
+
+ static BiomeHelper()
+ {
+ _temperatures[(int)Biome.OCEAN] = 0.5;
+ _temperatures[(int)Biome.PLAINS] = 0.8;
+ _temperatures[(int)Biome.DESERT] = 2.0;
+ _temperatures[(int)Biome.EXTREME_HILLS] = 0.2;
+ _temperatures[(int)Biome.FOREST] = 0.7;
+ _temperatures[(int)Biome.TAIGA] = 0.05;
+ _temperatures[(int)Biome.SWAMPLAND] = 0.8;
+ _temperatures[(int)Biome.RIVER] = 0.5;
+ _temperatures[(int)Biome.HELL] = 2.0;
+ _temperatures[(int)Biome.SKY] = 0.5;
+ _temperatures[(int)Biome.FROZEN_OCEAN] = 0.0;
+ _temperatures[(int)Biome.FROZEN_RIVER] = 0.0;
+ _temperatures[(int)Biome.ICE_PLAINS] = 0.0;
+ _temperatures[(int)Biome.ICE_MOUNTAINS] = 0.0;
+ _temperatures[(int)Biome.MUSHROOM_ISLAND] = 0.9;
+ _temperatures[(int)Biome.MUSHROOM_SHORE] = 0.9;
+ _temperatures[(int)Biome.BEACH] = 0.8;
+ _temperatures[(int)Biome.DESERT_HILLS] = 2.0;
+ _temperatures[(int)Biome.FOREST_HILLS] = 0.7;
+ _temperatures[(int)Biome.TAIGA_HILLS] = 0.05;
+ _temperatures[(int)Biome.SMALL_MOUNTAINS] = 0.2;
+ _temperatures[(int)Biome.JUNGLE] = 1.2;
+ _temperatures[(int)Biome.JUNGLE_HILLS] = 1.2;
+
+ _rainfalls[(int)Biome.OCEAN] = 0.5;
+ _rainfalls[(int)Biome.PLAINS] = 0.4;
+ _rainfalls[(int)Biome.DESERT] = 0.0;
+ _rainfalls[(int)Biome.EXTREME_HILLS] = 0.3;
+ _rainfalls[(int)Biome.FOREST] = 0.8;
+ _rainfalls[(int)Biome.TAIGA] = 0.8;
+ _rainfalls[(int)Biome.SWAMPLAND] = 0.9;
+ _rainfalls[(int)Biome.RIVER] = 0.5;
+ _rainfalls[(int)Biome.HELL] = 0.0;
+ _rainfalls[(int)Biome.SKY] = 0.5;
+ _rainfalls[(int)Biome.FROZEN_OCEAN] = 0.5;
+ _rainfalls[(int)Biome.FROZEN_RIVER] = 0.5;
+ _rainfalls[(int)Biome.ICE_PLAINS] = 0.5;
+ _rainfalls[(int)Biome.ICE_MOUNTAINS] = 0.5;
+ _rainfalls[(int)Biome.MUSHROOM_ISLAND] = 1.0;
+ _rainfalls[(int)Biome.MUSHROOM_SHORE] = 1.0;
+ _rainfalls[(int)Biome.BEACH] = 0.4;
+ _rainfalls[(int)Biome.DESERT_HILLS] = 0.0;
+ _rainfalls[(int)Biome.FOREST_HILLS] = 0.8;
+ _rainfalls[(int)Biome.TAIGA_HILLS] = 0.8;
+ _rainfalls[(int)Biome.SMALL_MOUNTAINS] = 0.3;
+ _rainfalls[(int)Biome.JUNGLE] = 0.9;
+ _rainfalls[(int)Biome.JUNGLE_HILLS] = 0.9;
+ }
+
+ public static double getTemperature(this Biome biome)
+ {
+ int id = (int)biome;
+ if (id >= 0 && id < _temperatures.Length) return _temperatures[id];
+ return 0.5;
+ }
+
+ public static double getRainfall(this Biome biome)
+ {
+ int id = (int)biome;
+ if (id >= 0 && id < _rainfalls.Length) return _rainfalls[id];
+ return 0.5;
+ }
+
+ public static Biome fromId(int id)
+ {
+ if (Enum.IsDefined(typeof(Biome), id)) return (Biome)id;
+ return Biome.PLAINS;
+ }
+}
diff --git a/Minecraft.Server.FourKit/Block/Block.cs b/Minecraft.Server.FourKit/Block/Block.cs
index 1a31081a5..ab427c715 100644
--- a/Minecraft.Server.FourKit/Block/Block.cs
+++ b/Minecraft.Server.FourKit/Block/Block.cs
@@ -134,6 +134,15 @@ public class Block
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
+ ///
+ /// Gets the chunk which contains this block.
+ ///
+ /// Containing Chunk.
+ public Chunk.Chunk getChunk()
+ {
+ return getWorld().getChunkAt(getX() >> 4, getZ() >> 4);
+ }
+
///
/// Gets the block at the given face
/// This method is equal to getRelative(face, 1)
@@ -162,5 +171,93 @@ public class Block
{
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
-
+
+ ///
+ /// Returns the biome that this block resides in.
+ ///
+ /// Biome type containing this block.
+ public Biome getBiome()
+ {
+ if (NativeBridge.GetBiomeId != null)
+ return BiomeHelper.fromId(NativeBridge.GetBiomeId(_world.getDimensionId(), _x, _z));
+ return Biome.PLAINS;
+ }
+
+ ///
+ /// Sets the biome that this block resides in.
+ ///
+ /// New Biome type for this block.
+ public void setBiome(Biome bio)
+ {
+ NativeBridge.SetBiomeId?.Invoke(_world.getDimensionId(), _x, _z, (int)bio);
+ }
+
+ ///
+ /// Gets the humidity of the biome of this block.
+ ///
+ /// Humidity of this block.
+ public double getHumidity()
+ {
+ return getBiome().getRainfall();
+ }
+
+ ///
+ /// Gets the temperature of the biome of this block.
+ ///
+ /// Temperature of this block.
+ public double getTemperature()
+ {
+ return getBiome().getTemperature();
+ }
+
+ ///
+ /// Checks if this block is liquid.
+ /// A block is considered liquid when returns
+ /// , ,
+ /// or .
+ ///
+ /// true if this block is liquid.
+ public bool isLiquid()
+ {
+ Material type = getType();
+ return type == Material.WATER || type == Material.STATIONARY_WATER ||
+ type == Material.LAVA || type == Material.STATIONARY_LAVA;
+ }
+
+
+ ///
+ /// Gets the light level between 0-15.
+ ///
+ /// Light level.
+ public byte getLightLevel()
+ {
+ int sky = getLightFromSky();
+ int block = getLightFromBlocks();
+ return (byte)(sky > block ? sky : block);
+ }
+
+ ///
+ /// Get the amount of light at this block from the sky.
+ /// Any light given from other sources (such as blocks like torches) will be ignored.
+ ///
+ /// Sky light level.
+ public byte getLightFromSky()
+ {
+ if (NativeBridge.GetSkyLight != null)
+ return (byte)NativeBridge.GetSkyLight(_world.getDimensionId(), _x, _y, _z);
+ return 0;
+ }
+
+ ///
+ /// Get the amount of light at this block from nearby blocks.
+ /// Any light given from other sources (such as the sun) will be ignored.
+ ///
+ /// Block light level.
+ public byte getLightFromBlocks()
+ {
+ if (NativeBridge.GetBlockLight != null)
+ return (byte)NativeBridge.GetBlockLight(_world.getDimensionId(), _x, _y, _z);
+ return 0;
+ }
+
}
diff --git a/Minecraft.Server.FourKit/Block/BlockState.cs b/Minecraft.Server.FourKit/Block/BlockState.cs
index 343abbb15..8420064c4 100644
--- a/Minecraft.Server.FourKit/Block/BlockState.cs
+++ b/Minecraft.Server.FourKit/Block/BlockState.cs
@@ -92,6 +92,15 @@ public class BlockState
/// Z-coordinate.
public int getZ() => _z;
+ ///
+ /// Gets the chunk which contains this block.
+ ///
+ /// Containing Chunk.
+ public Chunk.Chunk getChunk()
+ {
+ return _world.getChunkAt(_x >> 4, _z >> 4);
+ }
+
///
/// Gets the location of this block.
///
@@ -194,4 +203,13 @@ public class BlockState
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data);
return true;
}
+
+ ///
+ /// Gets the light level between 0-15.
+ ///
+ /// Light level.
+ public byte getLightLevel()
+ {
+ return getBlock().getLightLevel();
+ }
}
diff --git a/Minecraft.Server.FourKit/Chunk/Chunk.cs b/Minecraft.Server.FourKit/Chunk/Chunk.cs
new file mode 100644
index 000000000..4824cc609
--- /dev/null
+++ b/Minecraft.Server.FourKit/Chunk/Chunk.cs
@@ -0,0 +1,260 @@
+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()
+ {
+ return Array.Empty();
+ }
+
+ ///
+ /// 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);
+ }
+}
diff --git a/Minecraft.Server.FourKit/Chunk/ChunkSnapshot.cs b/Minecraft.Server.FourKit/Chunk/ChunkSnapshot.cs
new file mode 100644
index 000000000..fc1169254
--- /dev/null
+++ b/Minecraft.Server.FourKit/Chunk/ChunkSnapshot.cs
@@ -0,0 +1,209 @@
+namespace Minecraft.Server.FourKit.Chunk;
+
+using Minecraft.Server.FourKit.Block;
+
+///
+/// Represents a static, thread-safe snapshot of chunk of blocks.
+/// Purpose is to allow clean, efficient copy of a chunk data to be made, and
+/// then handed off for processing in another thread (e.g. map rendering).
+///
+public class ChunkSnapshot
+{
+ private readonly int _chunkX;
+ private readonly int _chunkZ;
+ private readonly string _worldName;
+ private readonly long _captureFullTime;
+ private readonly int[] _blockIds;
+ private readonly int[] _blockData;
+ private readonly int[] _maxBlockY;
+ private readonly int[]? _skyLight;
+ private readonly int[]? _blockLight;
+ private readonly int[]? _biome;
+ private readonly double[]? _biomeTemp;
+ private readonly double[]? _biomeRainfall;
+
+ internal ChunkSnapshot(int chunkX, int chunkZ, string worldName, long captureFullTime,
+ int[] blockIds, int[] blockData, int[] maxBlockY,
+ int[]? skyLight = null, int[]? blockLight = null,
+ int[]? biome = null, double[]? biomeTemp = null, double[]? biomeRainfall = null)
+ {
+ _chunkX = chunkX;
+ _chunkZ = chunkZ;
+ _worldName = worldName;
+ _captureFullTime = captureFullTime;
+ _blockIds = blockIds;
+ _blockData = blockData;
+ _maxBlockY = maxBlockY;
+ _skyLight = skyLight;
+ _blockLight = blockLight;
+ _biome = biome;
+ _biomeTemp = biomeTemp;
+ _biomeRainfall = biomeRainfall;
+ }
+
+ ///
+ /// 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 name of the world containing this chunk.
+ ///
+ /// Parent World Name.
+ public string getWorldName() => _worldName;
+
+ ///
+ /// Get block type for block at corresponding coordinate in the chunk.
+ ///
+ /// 0-15
+ /// 0-127
+ /// 0-15
+ /// 0-255
+ public int getBlockTypeId(int x, int y, int z)
+ {
+ int idx = (x * 128 * 16) + (y * 16) + z;
+ if (idx < 0 || idx >= _blockIds.Length) return 0;
+ return _blockIds[idx];
+ }
+
+ ///
+ /// Get block data for block at corresponding coordinate in the chunk.
+ ///
+ /// 0-15
+ /// 0-127
+ /// 0-15
+ /// 0-15
+ public int getBlockData(int x, int y, int z)
+ {
+ int idx = (x * 128 * 16) + (y * 16) + z;
+ if (idx < 0 || idx >= _blockData.Length) return 0;
+ return _blockData[idx];
+ }
+
+ ///
+ /// Gets the highest non-air coordinate at the given coordinates.
+ ///
+ /// X-coordinate of the blocks.
+ /// Z-coordinate of the blocks.
+ /// Y-coordinate of the highest non-air block.
+ public int getHighestBlockYAt(int x, int z)
+ {
+ int idx = x * 16 + z;
+ if (idx < 0 || idx >= _maxBlockY.Length) return 0;
+ return _maxBlockY[idx];
+ }
+
+ ///
+ /// Get world full time when chunk snapshot was captured.
+ ///
+ /// Time in ticks.
+ public long getCaptureFullTime() => _captureFullTime;
+
+ ///
+ /// Test if section is empty.
+ ///
+ /// Section Y coordinate (block Y / 16).
+ /// true if empty, false if not.
+ public bool isSectionEmpty(int sy)
+ {
+ int startY = sy * 16;
+ int endY = startY + 16;
+ if (endY > 128) endY = 128;
+ for (int x = 0; x < 16; x++)
+ {
+ for (int z = 0; z < 16; z++)
+ {
+ for (int y = startY; y < endY; y++)
+ {
+ int idx = (x * 128 * 16) + (y * 16) + z;
+ if (idx >= 0 && idx < _blockIds.Length && _blockIds[idx] != 0)
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ ///
+ /// Get sky light level for block at corresponding coordinate in the chunk.
+ ///
+ /// 0-15
+ /// 0-127
+ /// 0-15
+ /// 0-15
+ public int getBlockSkyLight(int x, int y, int z)
+ {
+ if (_skyLight == null) return 0;
+ int idx = (x * 128 * 16) + (y * 16) + z;
+ if (idx < 0 || idx >= _skyLight.Length) return 0;
+ return _skyLight[idx];
+ }
+
+ ///
+ /// Get light level emitted by block at corresponding coordinate in the chunk.
+ ///
+ /// 0-15
+ /// 0-127
+ /// 0-15
+ /// 0-15
+ public int getBlockEmittedLight(int x, int y, int z)
+ {
+ if (_blockLight == null) return 0;
+ int idx = (x * 128 * 16) + (y * 16) + z;
+ if (idx < 0 || idx >= _blockLight.Length) return 0;
+ return _blockLight[idx];
+ }
+
+ ///
+ /// Get biome at given coordinates.
+ ///
+ /// X-coordinate (0-15)
+ /// Z-coordinate (0-15)
+ /// Biome at given coordinate.
+ public Biome getBiome(int x, int z)
+ {
+ if (_biome == null) return Biome.PLAINS;
+ int idx = x * 16 + z;
+ if (idx < 0 || idx >= _biome.Length) return Biome.PLAINS;
+ return BiomeHelper.fromId(_biome[idx]);
+ }
+
+ ///
+ /// Get raw biome temperature (0.0-1.0) at given coordinate.
+ ///
+ /// X-coordinate (0-15)
+ /// Z-coordinate (0-15)
+ /// Temperature at given coordinate.
+ public double getRawBiomeTemperature(int x, int z)
+ {
+ if (_biomeTemp != null)
+ {
+ int idx = x * 16 + z;
+ if (idx >= 0 && idx < _biomeTemp.Length) return _biomeTemp[idx];
+ }
+ return getBiome(x, z).getTemperature();
+ }
+
+ ///
+ /// Get raw biome rainfall (0.0-1.0) at given coordinate.
+ ///
+ /// X-coordinate (0-15)
+ /// Z-coordinate (0-15)
+ /// Rainfall at given coordinate.
+ public double getRawBiomeRainfall(int x, int z)
+ {
+ if (_biomeRainfall != null)
+ {
+ int idx = x * 16 + z;
+ if (idx >= 0 && idx < _biomeRainfall.Length) return _biomeRainfall[idx];
+ }
+ return getBiome(x, z).getRainfall();
+ }
+}
diff --git a/Minecraft.Server.FourKit/Event/World/ChunkEvent.cs b/Minecraft.Server.FourKit/Event/World/ChunkEvent.cs
new file mode 100644
index 000000000..debc24b76
--- /dev/null
+++ b/Minecraft.Server.FourKit/Event/World/ChunkEvent.cs
@@ -0,0 +1,22 @@
+namespace Minecraft.Server.FourKit.Event.World;
+
+using Minecraft.Server.FourKit.Chunk;
+
+///
+/// Represents a Chunk related event.
+///
+public abstract class ChunkEvent : WorldEvent
+{
+ protected Chunk chunk;
+
+ protected ChunkEvent(Chunk chunk) : base(chunk.getWorld())
+ {
+ this.chunk = chunk;
+ }
+
+ ///
+ /// Gets the chunk being loaded/unloaded.
+ ///
+ /// Chunk that triggered this event.
+ public Chunk getChunk() => chunk;
+}
diff --git a/Minecraft.Server.FourKit/Event/World/ChunkLoadEvent.cs b/Minecraft.Server.FourKit/Event/World/ChunkLoadEvent.cs
new file mode 100644
index 000000000..14f72b150
--- /dev/null
+++ b/Minecraft.Server.FourKit/Event/World/ChunkLoadEvent.cs
@@ -0,0 +1,23 @@
+namespace Minecraft.Server.FourKit.Event.World;
+
+using Minecraft.Server.FourKit.Chunk;
+
+///
+/// Called when a chunk is loaded.
+///
+public class ChunkLoadEvent : ChunkEvent
+{
+ private readonly bool _newChunk;
+
+ internal ChunkLoadEvent(Chunk chunk, bool newChunk) : base(chunk)
+ {
+ _newChunk = newChunk;
+ }
+
+ ///
+ /// Gets if this chunk was newly created or not. Note that if this chunk is
+ /// new, it will not be populated at this time.
+ ///
+ /// true if the chunk is new, otherwise false.
+ public bool isNewChunk() => _newChunk;
+}
diff --git a/Minecraft.Server.FourKit/Event/World/ChunkUnloadEvent.cs b/Minecraft.Server.FourKit/Event/World/ChunkUnloadEvent.cs
new file mode 100644
index 000000000..e6272e598
--- /dev/null
+++ b/Minecraft.Server.FourKit/Event/World/ChunkUnloadEvent.cs
@@ -0,0 +1,33 @@
+namespace Minecraft.Server.FourKit.Event.World;
+
+using Minecraft.Server.FourKit.Chunk;
+
+///
+/// Called when a chunk is unloaded.
+///
+public class ChunkUnloadEvent : ChunkEvent, Cancellable
+{
+ private bool _cancel;
+
+ internal ChunkUnloadEvent(Chunk chunk) : base(chunk)
+ {
+ _cancel = false;
+ }
+
+ ///
+ /// Gets the cancellation state of this event. A cancelled event will not
+ /// be executed in the server, but will still pass to other plugins.
+ ///
+ /// true if this event is cancelled.
+ public bool isCancelled() => _cancel;
+
+ ///
+ /// Sets the cancellation state of this event. A cancelled event will not
+ /// be executed in the server, but will still pass to other plugins.
+ ///
+ /// true if you wish to cancel this event.
+ public void setCancelled(bool cancel)
+ {
+ _cancel = cancel;
+ }
+}
diff --git a/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
index f4e17ed1c..e9d2bf7c4 100644
--- a/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
@@ -121,4 +121,43 @@ public static partial class FourKitHost
ServerLog.Error("fourkit", $"SetVehicleCallbacks error: {ex}");
}
}
+
+ [UnmanagedCallersOnly]
+ public static void SetChunkCallbacks(IntPtr isChunkLoaded, IntPtr loadChunk, IntPtr unloadChunk, IntPtr getLoadedChunks, IntPtr isChunkInUse, IntPtr getChunkSnapshot, IntPtr unloadChunkRequest, IntPtr regenerateChunk, IntPtr refreshChunk)
+ {
+ try
+ {
+ NativeBridge.SetChunkCallbacks(isChunkLoaded, loadChunk, unloadChunk, getLoadedChunks, isChunkInUse, getChunkSnapshot, unloadChunkRequest, regenerateChunk, refreshChunk);
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"SetChunkCallbacks error: {ex}");
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ public static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
+ {
+ try
+ {
+ NativeBridge.SetBlockInfoCallbacks(getSkyLight, getBlockLight, getBiomeId, setBiomeId);
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"SetBlockInfoCallbacks error: {ex}");
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ public static void SetWorldEntityCallbacks(IntPtr getWorldEntities)
+ {
+ try
+ {
+ NativeBridge.SetWorldEntityCallbacks(getWorldEntities);
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"SetWorldEntityCallbacks error: {ex}");
+ }
+ }
}
diff --git a/Minecraft.Server.FourKit/FourKitHost.Events.cs b/Minecraft.Server.FourKit/FourKitHost.Events.cs
index 08329ebd0..f6c6ef253 100644
--- a/Minecraft.Server.FourKit/FourKitHost.Events.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.Events.cs
@@ -1250,4 +1250,38 @@ public static partial class FourKitHost
return 0;
}
}
+
+ [UnmanagedCallersOnly]
+ public static void FireChunkLoad(int dimId, int chunkX, int chunkZ, int isNewChunk)
+ {
+ try
+ {
+ var world = FourKit.getWorld(dimId);
+ var chunk = new Chunk.Chunk(world, chunkX, chunkZ);
+ var evt = new Event.World.ChunkLoadEvent(chunk, isNewChunk != 0);
+ FourKit.FireEvent(evt);
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"FireChunkLoad error: {ex}");
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ public static int FireChunkUnload(int dimId, int chunkX, int chunkZ)
+ {
+ try
+ {
+ var world = FourKit.getWorld(dimId);
+ var chunk = new Chunk.Chunk(world, chunkX, chunkZ);
+ var evt = new Event.World.ChunkUnloadEvent(chunk);
+ FourKit.FireEvent(evt);
+ return evt.isCancelled() ? 1 : 0;
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"FireChunkUnload error: {ex}");
+ return 0;
+ }
+ }
}
diff --git a/Minecraft.Server.FourKit/Location.cs b/Minecraft.Server.FourKit/Location.cs
index 6b8e314af..be513b673 100644
--- a/Minecraft.Server.FourKit/Location.cs
+++ b/Minecraft.Server.FourKit/Location.cs
@@ -186,6 +186,15 @@ public class Location
public Location clone() => new Location(LocationWorld, X, Y, Z, Yaw, Pitch);
+ ///
+ /// Gets the chunk at the represented location.
+ ///
+ /// Chunk at the represented location.
+ public Chunk.Chunk getChunk()
+ {
+ return getWorld().getChunkAt(getBlockX() >> 4, getBlockZ() >> 4);
+ }
+
///
public override string ToString() => $"Location(world={LocationWorld.getName()}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
}
diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs
index 2a38c788d..2c29d3b6d 100644
--- a/Minecraft.Server.FourKit/NativeBridge.cs
+++ b/Minecraft.Server.FourKit/NativeBridge.cs
@@ -180,7 +180,47 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetEntityInfoDelegate(int entityId, IntPtr outBuf);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeIsChunkLoadedDelegate(int dimId, int chunkX, int chunkZ);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeLoadChunkDelegate(int dimId, int chunkX, int chunkZ, int generate);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeUnloadChunkDelegate(int dimId, int chunkX, int chunkZ, int save, int safe);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetLoadedChunksDelegate(int dimId, out IntPtr coordBuf);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeIsChunkInUseDelegate(int dimId, int chunkX, int chunkZ);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeGetChunkSnapshotDelegate(int dimId, int chunkX, int chunkZ, IntPtr blockIds, IntPtr blockData, IntPtr maxBlockY);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeUnloadChunkRequestDelegate(int dimId, int chunkX, int chunkZ, int safe);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeRegenerateChunkDelegate(int dimId, int chunkX, int chunkZ);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeRefreshChunkDelegate(int dimId, int chunkX, int chunkZ);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetWorldEntitiesDelegate(int dimId, out IntPtr outBuf);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetSkyLightDelegate(int dimId, int x, int y, int z);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetBlockLightDelegate(int dimId, int x, int y, int z);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetBiomeIdDelegate(int dimId, int x, int z);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetBiomeIdDelegate(int dimId, int x, int z, int biomeId);
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
internal static NativeTeleportDelegate? TeleportPlayer;
@@ -240,6 +280,20 @@ internal static class NativeBridge
internal static NativeGetVehicleIdDelegate? GetVehicleId;
internal static NativeGetPassengerIdDelegate? GetPassengerId;
internal static NativeGetEntityInfoDelegate? GetEntityInfo;
+ internal static NativeIsChunkLoadedDelegate? IsChunkLoaded;
+ internal static NativeLoadChunkDelegate? LoadChunk;
+ internal static NativeUnloadChunkDelegate? UnloadChunk;
+ internal static NativeGetLoadedChunksDelegate? GetLoadedChunks;
+ internal static NativeIsChunkInUseDelegate? IsChunkInUse;
+ internal static NativeGetChunkSnapshotDelegate? GetChunkSnapshot;
+ internal static NativeUnloadChunkRequestDelegate? UnloadChunkRequest;
+ internal static NativeRegenerateChunkDelegate? RegenerateChunk;
+ internal static NativeRefreshChunkDelegate? RefreshChunk;
+ internal static NativeGetWorldEntitiesDelegate? GetWorldEntities;
+ internal static NativeGetSkyLightDelegate? GetSkyLight;
+ internal static NativeGetBlockLightDelegate? GetBlockLight;
+ internal static NativeGetBiomeIdDelegate? GetBiomeId;
+ internal static NativeSetBiomeIdDelegate? SetBiomeId;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@@ -334,4 +388,30 @@ internal static class NativeBridge
GetPassengerId = Marshal.GetDelegateForFunctionPointer(getPassengerId);
GetEntityInfo = Marshal.GetDelegateForFunctionPointer(getEntityInfo);
}
+
+ internal static void SetChunkCallbacks(IntPtr isChunkLoaded, IntPtr loadChunk, IntPtr unloadChunk, IntPtr getLoadedChunks, IntPtr isChunkInUse, IntPtr getChunkSnapshot, IntPtr unloadChunkRequest, IntPtr regenerateChunk, IntPtr refreshChunk)
+ {
+ IsChunkLoaded = Marshal.GetDelegateForFunctionPointer(isChunkLoaded);
+ LoadChunk = Marshal.GetDelegateForFunctionPointer(loadChunk);
+ UnloadChunk = Marshal.GetDelegateForFunctionPointer(unloadChunk);
+ GetLoadedChunks = Marshal.GetDelegateForFunctionPointer(getLoadedChunks);
+ IsChunkInUse = Marshal.GetDelegateForFunctionPointer(isChunkInUse);
+ GetChunkSnapshot = Marshal.GetDelegateForFunctionPointer(getChunkSnapshot);
+ UnloadChunkRequest = Marshal.GetDelegateForFunctionPointer(unloadChunkRequest);
+ RegenerateChunk = Marshal.GetDelegateForFunctionPointer(regenerateChunk);
+ RefreshChunk = Marshal.GetDelegateForFunctionPointer(refreshChunk);
+ }
+
+ internal static void SetWorldEntityCallbacks(IntPtr getWorldEntities)
+ {
+ GetWorldEntities = Marshal.GetDelegateForFunctionPointer(getWorldEntities);
+ }
+
+ internal static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
+ {
+ GetSkyLight = Marshal.GetDelegateForFunctionPointer(getSkyLight);
+ GetBlockLight = Marshal.GetDelegateForFunctionPointer(getBlockLight);
+ GetBiomeId = Marshal.GetDelegateForFunctionPointer(getBiomeId);
+ SetBiomeId = Marshal.GetDelegateForFunctionPointer(setBiomeId);
+ }
}
diff --git a/Minecraft.Server.FourKit/World.cs b/Minecraft.Server.FourKit/World.cs
index 1d85fe9cc..4abec4ca1 100644
--- a/Minecraft.Server.FourKit/World.cs
+++ b/Minecraft.Server.FourKit/World.cs
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
+using Minecraft.Server.FourKit.Chunk;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
@@ -245,6 +246,115 @@ public class World
return result;
}
+ ///
+ /// Get a list of all entities in this World.
+ ///
+ /// A list of all Entities currently residing in this world.
+ public List getEntities()
+ {
+ var result = new List();
+ if (NativeBridge.GetWorldEntities == null) return result;
+
+ int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
+ if (count <= 0 || buf == IntPtr.Zero) return result;
+
+ 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.Add(player);
+ continue;
+ }
+ }
+
+ if (isLiving == 1)
+ {
+ result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
+ }
+ else
+ {
+ var entity = new Entity.Entity();
+ entity.SetEntityIdInternal(entityId);
+ entity.SetEntityTypeInternal(entityType);
+ entity.SetDimensionInternal(_dimensionId);
+ result.Add(entity);
+ }
+ }
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(buf);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Get a list of all living entities in this World.
+ ///
+ /// A list of all LivingEntities currently residing in this world.
+ public List getLivingEntities()
+ {
+ var result = new List();
+ if (NativeBridge.GetWorldEntities == null) return result;
+
+ int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
+ if (count <= 0 || buf == IntPtr.Zero) return result;
+
+ 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];
+
+ if (isLiving != 1) continue;
+
+ 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.Add(player);
+ continue;
+ }
+ }
+
+ result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
+ }
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(buf);
+ }
+
+ return result;
+ }
+
///
/// Creates explosion at given coordinates with given power.
///
@@ -374,4 +484,240 @@ public class World
{
NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 1);
}
+
+ ///
+ /// Gets the Chunk at the given coordinates.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// Chunk at the given coordinates.
+ public Chunk.Chunk getChunkAt(int x, int z)
+ {
+ return new Chunk.Chunk(this, x, z);
+ }
+
+ ///
+ /// Gets the Chunk at the given Location.
+ ///
+ /// Location of the chunk.
+ /// Chunk at the given location.
+ public Chunk.Chunk getChunkAt(Location location)
+ {
+ return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
+ }
+
+ ///
+ /// Gets the Chunk that contains the given Block.
+ ///
+ /// Block to get the containing chunk from.
+ /// The chunk that contains the given block.
+ public Chunk.Chunk getChunkAt(Block.Block block)
+ {
+ return getChunkAt(block.getX() >> 4, block.getZ() >> 4);
+ }
+
+ ///
+ /// Checks if the specified Chunk is loaded.
+ ///
+ /// The chunk to check.
+ /// true if the chunk is loaded, otherwise false.
+ public bool isChunkLoaded(Chunk.Chunk chunk)
+ {
+ return isChunkLoaded(chunk.getX(), chunk.getZ());
+ }
+
+ ///
+ /// Checks if the Chunk at the specified coordinates is loaded.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// true if the chunk is loaded, otherwise false.
+ public bool isChunkLoaded(int x, int z)
+ {
+ if (NativeBridge.IsChunkLoaded != null)
+ return NativeBridge.IsChunkLoaded(_dimensionId, x, z) != 0;
+ return false;
+ }
+
+ ///
+ /// Gets an array of all loaded Chunks.
+ ///
+ /// Chunk[] containing all loaded chunks.
+ public Chunk.Chunk[] getLoadedChunks()
+ {
+ if (NativeBridge.GetLoadedChunks == null)
+ return Array.Empty();
+
+ int count = NativeBridge.GetLoadedChunks(_dimensionId, out IntPtr buf);
+ if (count <= 0 || buf == IntPtr.Zero)
+ return Array.Empty();
+
+ try
+ {
+ int[] coords = new int[count * 2];
+ Marshal.Copy(buf, coords, 0, count * 2);
+ var chunks = new Chunk.Chunk[count];
+ for (int i = 0; i < count; i++)
+ chunks[i] = new Chunk.Chunk(this, coords[i * 2], coords[i * 2 + 1]);
+ return chunks;
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(buf);
+ }
+ }
+
+ ///
+ /// Loads the specified Chunk.
+ ///
+ /// The chunk to load.
+ public void loadChunk(Chunk.Chunk chunk)
+ {
+ loadChunk(chunk.getX(), chunk.getZ());
+ }
+
+ ///
+ /// Loads the Chunk at the specified coordinates.
+ /// If the chunk does not exist, it will be generated. This method is
+ /// analogous to loadChunk(int, int, boolean) where generate is true.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ public void loadChunk(int x, int z)
+ {
+ loadChunk(x, z, true);
+ }
+
+ ///
+ /// Loads the Chunk at the specified coordinates.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of 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 loadChunk(int x, int z, bool generate)
+ {
+ if (NativeBridge.LoadChunk != null)
+ return NativeBridge.LoadChunk(_dimensionId, x, z, generate ? 1 : 0) != 0;
+ return false;
+ }
+
+ ///
+ /// Checks if the Chunk at the specified coordinates is loaded and in use
+ /// by one or more players.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// true if the chunk is loaded and in use by one or more players, otherwise false.
+ public bool isChunkInUse(int x, int z)
+ {
+ if (NativeBridge.IsChunkInUse != null)
+ return NativeBridge.IsChunkInUse(_dimensionId, x, z) != 0;
+ return false;
+ }
+
+ ///
+ /// Safely unloads and saves the Chunk at the specified coordinates.
+ /// This method is analogous to unloadChunk(int, int, boolean, boolean)
+ /// where safe and save is true.
+ ///
+ /// The chunk to unload.
+ /// true if the chunk has unloaded successfully, otherwise false.
+ public bool unloadChunk(Chunk.Chunk chunk)
+ {
+ return unloadChunk(chunk.getX(), chunk.getZ());
+ }
+
+ ///
+ /// Safely unloads and saves the Chunk at the specified coordinates.
+ /// This method is analogous to unloadChunk(int, int, boolean, boolean)
+ /// where safe and save is true.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// true if the chunk has unloaded successfully, otherwise false.
+ public bool unloadChunk(int x, int z)
+ {
+ return unloadChunk(x, z, true, true);
+ }
+
+ ///
+ /// Safely unloads and optionally saves the Chunk at the specified coordinates.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// Whether or not to save the chunk.
+ /// true if the chunk has unloaded successfully, otherwise false.
+ public bool unloadChunk(int x, int z, bool save)
+ {
+ return unloadChunk(x, z, save, true);
+ }
+
+ ///
+ /// Unloads and optionally saves the Chunk at the specified coordinates.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of 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 unloadChunk(int x, int z, bool save, bool safe)
+ {
+ if (NativeBridge.UnloadChunk != null)
+ return NativeBridge.UnloadChunk(_dimensionId, x, z, save ? 1 : 0, safe ? 1 : 0) != 0;
+ return false;
+ }
+
+ ///
+ /// Safely queues the Chunk at the specified coordinates for unloading.
+ /// This method is analogous to unloadChunkRequest(int, int, boolean)
+ /// where safe is true.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// true is the queue attempt was successful, otherwise false.
+ public bool unloadChunkRequest(int x, int z)
+ {
+ return unloadChunkRequest(x, z, true);
+ }
+
+ ///
+ /// Queues the Chunk at the specified coordinates for unloading.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// Controls whether to queue the chunk when players are nearby.
+ /// Whether the chunk was actually queued.
+ public bool unloadChunkRequest(int x, int z, bool safe)
+ {
+ if (NativeBridge.UnloadChunkRequest != null)
+ return NativeBridge.UnloadChunkRequest(_dimensionId, x, z, safe ? 1 : 0) != 0;
+ return false;
+ }
+
+ ///
+ /// Regenerates the Chunk at the specified coordinates.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// Whether the chunk was actually regenerated.
+ public bool regenerateChunk(int x, int z)
+ {
+ if (NativeBridge.RegenerateChunk != null)
+ return NativeBridge.RegenerateChunk(_dimensionId, x, z) != 0;
+ return false;
+ }
+
+ ///
+ /// Resends the Chunk to all clients.
+ ///
+ /// X-coordinate of the chunk.
+ /// Z-coordinate of the chunk.
+ /// Whether the chunk was actually refreshed.
+ public bool refreshChunk(int x, int z)
+ {
+ if (NativeBridge.RefreshChunk != null)
+ return NativeBridge.RefreshChunk(_dimensionId, x, z) != 0;
+ return false;
+ }
}
diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp
index 81c24111f..5b04cad1e 100644
--- a/Minecraft.Server/FourKitBridge.cpp
+++ b/Minecraft.Server/FourKitBridge.cpp
@@ -103,6 +103,11 @@ typedef int(__stdcall *fn_fire_piston_extend)(int dimId, int x, int y, int z, in
typedef int(__stdcall *fn_fire_piston_retract)(int dimId, int x, int y, int z, int direction);
typedef int(__stdcall *fn_fire_command_preprocess)(int entityId, const char *cmdUtf8, int cmdByteLen, char *outBuf, int outBufSize, int *outLen);
typedef int(__stdcall *fn_fire_block_from_to)(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
+typedef void(__stdcall *fn_set_chunk_callbacks)(void *isChunkLoaded, void *loadChunk, void *unloadChunk, void *getLoadedChunks, void *isChunkInUse, void *getChunkSnapshot, void *unloadChunkRequest, void *regenerateChunk, void *refreshChunk);
+typedef void(__stdcall *fn_set_block_info_callbacks)(void *getSkyLight, void *getBlockLight, void *getBiomeId, void *setBiomeId);
+typedef void(__stdcall *fn_set_world_entity_callbacks)(void *getWorldEntities);
+typedef void(__stdcall *fn_fire_chunk_load)(int dimId, int chunkX, int chunkZ, int isNewChunk);
+typedef int(__stdcall *fn_fire_chunk_unload)(int dimId, int chunkX, int chunkZ);
struct OpenContainerInfo
{
@@ -160,6 +165,11 @@ static fn_fire_piston_extend s_managedFirePistonExtend = nullptr;
static fn_fire_piston_retract s_managedFirePistonRetract = nullptr;
static fn_fire_command_preprocess s_managedFireCommandPreprocess = nullptr;
static fn_fire_block_from_to s_managedFireBlockFromTo = nullptr;
+static fn_set_chunk_callbacks s_managedSetChunkCallbacks = nullptr;
+static fn_set_block_info_callbacks s_managedSetBlockInfoCallbacks = nullptr;
+static fn_set_world_entity_callbacks s_managedSetWorldEntityCallbacks = nullptr;
+static fn_fire_chunk_load s_managedFireChunkLoad = nullptr;
+static fn_fire_chunk_unload s_managedFireChunkUnload = nullptr;
static bool s_initialized = false;
@@ -238,6 +248,11 @@ void Initialize()
{L"FirePistonRetract", (void **)&s_managedFirePistonRetract},
{L"FireCommandPreprocess", (void **)&s_managedFireCommandPreprocess},
{L"FireBlockFromTo", (void **)&s_managedFireBlockFromTo},
+ {L"SetChunkCallbacks", (void **)&s_managedSetChunkCallbacks},
+ {L"SetBlockInfoCallbacks", (void **)&s_managedSetBlockInfoCallbacks},
+ {L"SetWorldEntityCallbacks", (void **)&s_managedSetWorldEntityCallbacks},
+ {L"FireChunkLoad", (void **)&s_managedFireChunkLoad},
+ {L"FireChunkUnload", (void **)&s_managedFireChunkUnload},
};
bool ok = true;
@@ -332,6 +347,26 @@ void Initialize()
(void *)&NativeGetPassengerId,
(void *)&NativeGetEntityInfo);
+ s_managedSetChunkCallbacks(
+ (void *)&NativeIsChunkLoaded,
+ (void *)&NativeLoadChunk,
+ (void *)&NativeUnloadChunk,
+ (void *)&NativeGetLoadedChunks,
+ (void *)&NativeIsChunkInUse,
+ (void *)&NativeGetChunkSnapshot,
+ (void *)&NativeUnloadChunkRequest,
+ (void *)&NativeRegenerateChunk,
+ (void *)&NativeRefreshChunk);
+
+ s_managedSetBlockInfoCallbacks(
+ (void *)&NativeGetSkyLight,
+ (void *)&NativeGetBlockLight,
+ (void *)&NativeGetBiomeId,
+ (void *)&NativeSetBiomeId);
+
+ s_managedSetWorldEntityCallbacks(
+ (void *)&NativeGetWorldEntities);
+
LogInfo("fourkit", "FourKit initialized successfully.");
}
@@ -1010,4 +1045,18 @@ bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int to
return false;
return s_managedFireBlockFromTo(dimId, fromX, fromY, fromZ, toX, toY, toZ, face) != 0;
}
+
+void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk)
+{
+ if (!s_initialized || !s_managedFireChunkLoad)
+ return;
+ s_managedFireChunkLoad(dimId, chunkX, chunkZ, isNewChunk ? 1 : 0);
+}
+
+bool FireChunkUnload(int dimId, int chunkX, int chunkZ)
+{
+ if (!s_initialized || !s_managedFireChunkUnload)
+ return false;
+ return s_managedFireChunkUnload(dimId, chunkX, chunkZ) != 0;
+}
} // namespace FourKitBridge
diff --git a/Minecraft.Server/FourKitBridge.h b/Minecraft.Server/FourKitBridge.h
index 98894bf63..eafb3c827 100644
--- a/Minecraft.Server/FourKitBridge.h
+++ b/Minecraft.Server/FourKitBridge.h
@@ -92,4 +92,6 @@ namespace FourKitBridge
bool FirePistonRetract(int dimId, int x, int y, int z, int direction);
bool FireCommandPreprocess(int entityId, const std::wstring &commandLine, std::wstring &outCommand);
bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
+ void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk);
+ bool FireChunkUnload(int dimId, int chunkX, int chunkZ);
}
diff --git a/Minecraft.Server/FourKitNatives.cpp b/Minecraft.Server/FourKitNatives.cpp
index 5d50dfc38..04678bbeb 100644
--- a/Minecraft.Server/FourKitNatives.cpp
+++ b/Minecraft.Server/FourKitNatives.cpp
@@ -13,6 +13,10 @@
#include "..\Minecraft.Client\ServerLevel.h"
#include "..\Minecraft.Client\ServerPlayer.h"
#include "..\Minecraft.Client\ServerPlayerGameMode.h"
+#include "..\Minecraft.Client\ServerChunkCache.h"
+#include "..\Minecraft.World\LevelChunk.h"
+#include "..\Minecraft.World\Biome.h"
+#include "..\Minecraft.World\LightLayer.h"
#include "..\Minecraft.Client\Windows64\Network\WinsockNetLayer.h"
#include "..\Minecraft.World\AbstractContainerMenu.h"
#include "..\Minecraft.World\AddGlobalEntityPacket.h"
@@ -1268,4 +1272,232 @@ void __cdecl NativeGetEntityInfo(int entityId, double *outData)
outData[4] = (double)entity->dimension;
}
+int __cdecl NativeGetWorldEntities(int dimId, int **outBuf)
+{
+ *outBuf = nullptr;
+ ServerLevel *level = GetLevel(dimId);
+ if (!level)
+ return 0;
+
+ EnterCriticalSection(&level->m_entitiesCS);
+ int total = (int)level->entities.size();
+ int *buf = (int *)CoTaskMemAlloc(total * 3 * sizeof(int));
+ int count = 0;
+ if (buf)
+ {
+ for (auto &entity : level->entities)
+ {
+ if (!entity)
+ continue;
+ int idx = count * 3;
+ buf[idx] = entity->entityId;
+ buf[idx + 1] = MapEntityType((int)entity->GetType());
+ buf[idx + 2] = entity->instanceof(eTYPE_LIVINGENTITY) ? 1 : 0;
+ count++;
+ }
+ }
+ LeaveCriticalSection(&level->m_entitiesCS);
+ *outBuf = buf;
+ return count;
+}
+
+int __cdecl NativeIsChunkLoaded(int dimId, int chunkX, int chunkZ)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ return 0;
+ return level->cache->hasChunk(chunkX, chunkZ) ? 1 : 0;
+}
+
+int __cdecl NativeLoadChunk(int dimId, int chunkX, int chunkZ, int generate)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ return 0;
+ LevelChunk *chunk = level->cache->create(chunkX, chunkZ);
+ return (chunk != nullptr) ? 1 : 0;
+}
+
+int __cdecl NativeUnloadChunk(int dimId, int chunkX, int chunkZ, int save, int safe)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ return 0;
+ if (safe)
+ {
+ if (!level->cache->hasChunk(chunkX, chunkZ))
+ return 0;
+ LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
+ if (chunk && chunk->containsPlayer())
+ return 0;
+ }
+ level->cache->drop(chunkX, chunkZ);
+ return 1;
+}
+
+int __cdecl NativeGetLoadedChunks(int dimId, int **coordBuf)
+{
+ // wow gay
+ *coordBuf = nullptr;
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ return 0;
+
+ std::vector *list = level->cache->getLoadedChunkList();
+
+ if (!list)
+ return 0;
+
+
+
+ int total = (int)list->size();
+ int *buf = (int *)CoTaskMemAlloc(total * 2 * sizeof(int));
+ int count = 0;
+
+ if (buf)
+ {
+ for (auto *chunk : *list)
+ {
+ if (chunk)
+ {
+ buf[count * 2] = chunk->x;
+ buf[count * 2 + 1] = chunk->z;
+ count++;
+ }
+ }
+ }
+
+ *coordBuf = buf;
+ return count;
+}
+
+int __cdecl NativeIsChunkInUse(int dimId, int chunkX, int chunkZ)
+{
+ PlayerList *list = MinecraftServer::getPlayerList();
+ if (!list)
+ return 0;
+ for (auto &p : list->players)
+ {
+ if (p && p->dimension == dimId)
+ {
+ int px = (int)floor(p->x) >> 4;
+ int pz = (int)floor(p->z) >> 4;
+ if (px == chunkX && pz == chunkZ)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void __cdecl NativeGetChunkSnapshot(int dimId, int chunkX, int chunkZ, int *blockIds, int *blockData, int *maxBlockY)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ {
+ memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
+ memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
+ memset(maxBlockY, 0, 16 * 16 * sizeof(int));
+ return;
+ }
+ if (!level->cache->hasChunk(chunkX, chunkZ))
+ {
+ memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
+ memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
+ memset(maxBlockY, 0, 16 * 16 * sizeof(int));
+ return;
+ }
+ LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
+ if (!chunk)
+ {
+ memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
+ memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
+ memset(maxBlockY, 0, 16 * 16 * sizeof(int));
+ return;
+ }
+ for (int lx = 0; lx < 16; lx++)
+ {
+ for (int lz = 0; lz < 16; lz++)
+ {
+ int highest = 0;
+ for (int ly = 0; ly < 128; ly++)
+ {
+ int idx = (lx * 128 * 16) + (ly * 16) + lz;
+ blockIds[idx] = chunk->getTile(lx, ly, lz);
+ blockData[idx] = chunk->getData(lx, ly, lz);
+ if (blockIds[idx] != 0)
+ highest = ly;
+ }
+ maxBlockY[lx * 16 + lz] = highest;
+ }
+ }
+}
+
+int __cdecl NativeUnloadChunkRequest(int dimId, int chunkX, int chunkZ, int safe)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level || !level->cache)
+ return 0;
+ if (safe)
+ {
+ if (!level->cache->hasChunk(chunkX, chunkZ))
+ return 0;
+ LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
+ if (chunk && chunk->containsPlayer())
+ return 0;
+ }
+ level->cache->drop(chunkX, chunkZ);
+ return 1;
+}
+
+int __cdecl NativeRegenerateChunk(int dimId, int chunkX, int chunkZ)
+{
+ return 0;
+}
+
+int __cdecl NativeRefreshChunk(int dimId, int chunkX, int chunkZ)
+{
+ return 0;
+}
+
+int __cdecl NativeGetSkyLight(int dimId, int x, int y, int z)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level)
+ return 0;
+ return level->getBrightness(LightLayer::Sky, x, y, z);
+}
+
+int __cdecl NativeGetBlockLight(int dimId, int x, int y, int z)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level)
+ return 0;
+ return level->getBrightness(LightLayer::Block, x, y, z);
+}
+
+int __cdecl NativeGetBiomeId(int dimId, int x, int z)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level)
+ return 1;
+ Biome *biome = level->getBiome(x, z);
+ return biome ? biome->id : 1;
+}
+
+void __cdecl NativeSetBiomeId(int dimId, int x, int z, int biomeId)
+{
+ ServerLevel *level = GetLevel(dimId);
+ if (!level)
+ return;
+ LevelChunk *chunk = level->getChunk(x >> 4, z >> 4);
+ if (!chunk)
+ return;
+ byteArray biomes = chunk->getBiomes();
+ if (biomes.data == nullptr)
+ return;
+ int lx = x & 0xf;
+ int lz = z & 0xf;
+ biomes.data[(lz << 4) | lx] = static_cast(biomeId & 0xff);
+}
+
} // namespace FourKitBridge
diff --git a/Minecraft.Server/FourKitNatives.h b/Minecraft.Server/FourKitNatives.h
index df96d9182..644b6116d 100644
--- a/Minecraft.Server/FourKitNatives.h
+++ b/Minecraft.Server/FourKitNatives.h
@@ -78,4 +78,24 @@ namespace FourKitBridge
int __cdecl NativeGetVehicleId(int entityId);
int __cdecl NativeGetPassengerId(int entityId);
void __cdecl NativeGetEntityInfo(int entityId, double *outData);
+
+ // chunk
+ int __cdecl NativeIsChunkLoaded(int dimId, int chunkX, int chunkZ);
+ int __cdecl NativeLoadChunk(int dimId, int chunkX, int chunkZ, int generate);
+ int __cdecl NativeUnloadChunk(int dimId, int chunkX, int chunkZ, int save, int safe);
+ int __cdecl NativeGetLoadedChunks(int dimId, int **coordBuf);
+ int __cdecl NativeIsChunkInUse(int dimId, int chunkX, int chunkZ);
+ void __cdecl NativeGetChunkSnapshot(int dimId, int chunkX, int chunkZ, int *blockIds, int *blockData, int *maxBlockY);
+ int __cdecl NativeUnloadChunkRequest(int dimId, int chunkX, int chunkZ, int safe);
+ int __cdecl NativeRegenerateChunk(int dimId, int chunkX, int chunkZ);
+ int __cdecl NativeRefreshChunk(int dimId, int chunkX, int chunkZ);
+
+ // world entity bs
+ int __cdecl NativeGetWorldEntities(int dimId, int **outBuf);
+
+ // block info (light, biome)
+ int __cdecl NativeGetSkyLight(int dimId, int x, int y, int z);
+ int __cdecl NativeGetBlockLight(int dimId, int x, int y, int z);
+ int __cdecl NativeGetBiomeId(int dimId, int x, int z);
+ void __cdecl NativeSetBiomeId(int dimId, int x, int z, int biomeId);
}