chunk additions

This commit is contained in:
sylvessa 2026-04-13 19:43:48 -05:00
parent ff7c04f253
commit 6e6697dbc2
19 changed files with 1605 additions and 5 deletions

View file

@ -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);

View file

@ -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();
}
}

View file

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

View file

@ -134,6 +134,15 @@ public class Block
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getX() >> 4, getZ() >> 4);
}
/// <summary>
/// Gets the block at the given face
/// <para>This method is equal to getRelative(face, 1)</para>
@ -162,5 +171,93 @@ public class Block
{
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
/// <summary>
/// Returns the biome that this block resides in.
/// </summary>
/// <returns>Biome type containing this block.</returns>
public Biome getBiome()
{
if (NativeBridge.GetBiomeId != null)
return BiomeHelper.fromId(NativeBridge.GetBiomeId(_world.getDimensionId(), _x, _z));
return Biome.PLAINS;
}
/// <summary>
/// Sets the biome that this block resides in.
/// </summary>
/// <param name="bio">New Biome type for this block.</param>
public void setBiome(Biome bio)
{
NativeBridge.SetBiomeId?.Invoke(_world.getDimensionId(), _x, _z, (int)bio);
}
/// <summary>
/// Gets the humidity of the biome of this block.
/// </summary>
/// <returns>Humidity of this block.</returns>
public double getHumidity()
{
return getBiome().getRainfall();
}
/// <summary>
/// Gets the temperature of the biome of this block.
/// </summary>
/// <returns>Temperature of this block.</returns>
public double getTemperature()
{
return getBiome().getTemperature();
}
/// <summary>
/// Checks if this block is liquid.
/// <para>A block is considered liquid when <see cref="getType()"/> returns
/// <see cref="Material.WATER"/>, <see cref="Material.STATIONARY_WATER"/>,
/// <see cref="Material.LAVA"/> or <see cref="Material.STATIONARY_LAVA"/>.</para>
/// </summary>
/// <returns>true if this block is liquid.</returns>
public bool isLiquid()
{
Material type = getType();
return type == Material.WATER || type == Material.STATIONARY_WATER ||
type == Material.LAVA || type == Material.STATIONARY_LAVA;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
int sky = getLightFromSky();
int block = getLightFromBlocks();
return (byte)(sky > block ? sky : block);
}
/// <summary>
/// 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.
/// </summary>
/// <returns>Sky light level.</returns>
public byte getLightFromSky()
{
if (NativeBridge.GetSkyLight != null)
return (byte)NativeBridge.GetSkyLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
/// <summary>
/// Get the amount of light at this block from nearby blocks.
/// Any light given from other sources (such as the sun) will be ignored.
/// </summary>
/// <returns>Block light level.</returns>
public byte getLightFromBlocks()
{
if (NativeBridge.GetBlockLight != null)
return (byte)NativeBridge.GetBlockLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
}

View file

@ -92,6 +92,15 @@ public class BlockState
/// <returns>Z-coordinate.</returns>
public int getZ() => _z;
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return _world.getChunkAt(_x >> 4, _z >> 4);
}
/// <summary>
/// Gets the location of this block.
/// </summary>
@ -194,4 +203,13 @@ public class BlockState
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data);
return true;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
return getBlock().getLightLevel();
}
}

View file

@ -0,0 +1,260 @@
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
namespace Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Represents a chunk of blocks.
/// </summary>
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;
}
/// <summary>
/// Gets the X-coordinate of this chunk.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _chunkX;
/// <summary>
/// Gets the Z-coordinate of this chunk.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _chunkZ;
/// <summary>
/// Gets the world containing this chunk.
/// </summary>
/// <returns>Parent World.</returns>
public World getWorld() => _world;
/// <summary>
/// Gets a block from this chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>The Block.</returns>
public Block.Block getBlock(int x, int y, int z)
{
return _world.getBlockAt((_chunkX << 4) + x, y, (_chunkZ << 4) + z);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot()
{
return getChunkSnapshot(false, false);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
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);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeMaxblocky">(NONFUNCTIONAL) Only here for parity.</param>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot(bool includeMaxblocky, bool includeBiome, bool includeBiomeTempRain)
{
return getChunkSnapshot(includeBiome, includeBiomeTempRain);
}
/// <summary>
/// Get a list of all entities in the chunk.
/// </summary>
/// <returns>The entities.</returns>
public Entity.Entity[] getEntities()
{
return Array.Empty<Entity.Entity>();
}
/// <summary>
/// Checks if the chunk is loaded.
/// </summary>
/// <returns>True if it is loaded.</returns>
public bool isLoaded()
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_world.getDimensionId(), _chunkX, _chunkZ) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load(bool generate)
{
if (NativeBridge.LoadChunk != null)
return NativeBridge.LoadChunk(_world.getDimensionId(), _chunkX, _chunkZ, generate ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load()
{
return load(true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
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;
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload(bool save)
{
return unload(save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload()
{
return unload(true, true);
}
}

View file

@ -0,0 +1,209 @@
namespace Minecraft.Server.FourKit.Chunk;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// 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).
/// </summary>
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;
}
/// <summary>
/// Gets the X-coordinate of this chunk.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _chunkX;
/// <summary>
/// Gets the Z-coordinate of this chunk.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _chunkZ;
/// <summary>
/// Gets name of the world containing this chunk.
/// </summary>
/// <returns>Parent World Name.</returns>
public string getWorldName() => _worldName;
/// <summary>
/// Get block type for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-255</returns>
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];
}
/// <summary>
/// Get block data for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
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];
}
/// <summary>
/// Gets the highest non-air coordinate at the given coordinates.
/// </summary>
/// <param name="x">X-coordinate of the blocks.</param>
/// <param name="z">Z-coordinate of the blocks.</param>
/// <returns>Y-coordinate of the highest non-air block.</returns>
public int getHighestBlockYAt(int x, int z)
{
int idx = x * 16 + z;
if (idx < 0 || idx >= _maxBlockY.Length) return 0;
return _maxBlockY[idx];
}
/// <summary>
/// Get world full time when chunk snapshot was captured.
/// </summary>
/// <returns>Time in ticks.</returns>
public long getCaptureFullTime() => _captureFullTime;
/// <summary>
/// Test if section is empty.
/// </summary>
/// <param name="sy">Section Y coordinate (block Y / 16).</param>
/// <returns>true if empty, false if not.</returns>
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;
}
/// <summary>
/// Get sky light level for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
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];
}
/// <summary>
/// Get light level emitted by block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
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];
}
/// <summary>
/// Get biome at given coordinates.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Biome at given coordinate.</returns>
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]);
}
/// <summary>
/// Get raw biome temperature (0.0-1.0) at given coordinate.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Temperature at given coordinate.</returns>
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();
}
/// <summary>
/// Get raw biome rainfall (0.0-1.0) at given coordinate.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Rainfall at given coordinate.</returns>
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();
}
}

View file

@ -0,0 +1,22 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Represents a Chunk related event.
/// </summary>
public abstract class ChunkEvent : WorldEvent
{
protected Chunk chunk;
protected ChunkEvent(Chunk chunk) : base(chunk.getWorld())
{
this.chunk = chunk;
}
/// <summary>
/// Gets the chunk being loaded/unloaded.
/// </summary>
/// <returns>Chunk that triggered this event.</returns>
public Chunk getChunk() => chunk;
}

View file

@ -0,0 +1,23 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Called when a chunk is loaded.
/// </summary>
public class ChunkLoadEvent : ChunkEvent
{
private readonly bool _newChunk;
internal ChunkLoadEvent(Chunk chunk, bool newChunk) : base(chunk)
{
_newChunk = newChunk;
}
/// <summary>
/// Gets if this chunk was newly created or not. Note that if this chunk is
/// new, it will not be populated at this time.
/// </summary>
/// <returns>true if the chunk is new, otherwise false.</returns>
public bool isNewChunk() => _newChunk;
}

View file

@ -0,0 +1,33 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Called when a chunk is unloaded.
/// </summary>
public class ChunkUnloadEvent : ChunkEvent, Cancellable
{
private bool _cancel;
internal ChunkUnloadEvent(Chunk chunk) : base(chunk)
{
_cancel = false;
}
/// <summary>
/// Gets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <returns>true if this event is cancelled.</returns>
public bool isCancelled() => _cancel;
/// <summary>
/// Sets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <param name="cancel">true if you wish to cancel this event.</param>
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
}

View file

@ -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}");
}
}
}

View file

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

View file

@ -186,6 +186,15 @@ public class Location
public Location clone() => new Location(LocationWorld, X, Y, Z, Yaw, Pitch);
/// <summary>
/// Gets the chunk at the represented location.
/// </summary>
/// <returns>Chunk at the represented location.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getBlockX() >> 4, getBlockZ() >> 4);
}
/// <inheritdoc/>
public override string ToString() => $"Location(world={LocationWorld.getName()}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
}

View file

@ -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<NativeGetPassengerIdDelegate>(getPassengerId);
GetEntityInfo = Marshal.GetDelegateForFunctionPointer<NativeGetEntityInfoDelegate>(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<NativeIsChunkLoadedDelegate>(isChunkLoaded);
LoadChunk = Marshal.GetDelegateForFunctionPointer<NativeLoadChunkDelegate>(loadChunk);
UnloadChunk = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkDelegate>(unloadChunk);
GetLoadedChunks = Marshal.GetDelegateForFunctionPointer<NativeGetLoadedChunksDelegate>(getLoadedChunks);
IsChunkInUse = Marshal.GetDelegateForFunctionPointer<NativeIsChunkInUseDelegate>(isChunkInUse);
GetChunkSnapshot = Marshal.GetDelegateForFunctionPointer<NativeGetChunkSnapshotDelegate>(getChunkSnapshot);
UnloadChunkRequest = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkRequestDelegate>(unloadChunkRequest);
RegenerateChunk = Marshal.GetDelegateForFunctionPointer<NativeRegenerateChunkDelegate>(regenerateChunk);
RefreshChunk = Marshal.GetDelegateForFunctionPointer<NativeRefreshChunkDelegate>(refreshChunk);
}
internal static void SetWorldEntityCallbacks(IntPtr getWorldEntities)
{
GetWorldEntities = Marshal.GetDelegateForFunctionPointer<NativeGetWorldEntitiesDelegate>(getWorldEntities);
}
internal static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
{
GetSkyLight = Marshal.GetDelegateForFunctionPointer<NativeGetSkyLightDelegate>(getSkyLight);
GetBlockLight = Marshal.GetDelegateForFunctionPointer<NativeGetBlockLightDelegate>(getBlockLight);
GetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeGetBiomeIdDelegate>(getBiomeId);
SetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeSetBiomeIdDelegate>(setBiomeId);
}
}

View file

@ -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;
}
/// <summary>
/// Get a list of all entities in this World.
/// </summary>
/// <returns>A list of all Entities currently residing in this world.</returns>
public List<Entity.Entity> getEntities()
{
var result = new List<Entity.Entity>();
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;
}
/// <summary>
/// Get a list of all living entities in this World.
/// </summary>
/// <returns>A list of all LivingEntities currently residing in this world.</returns>
public List<Entity.LivingEntity> getLivingEntities()
{
var result = new List<Entity.LivingEntity>();
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;
}
/// <summary>
/// Creates explosion at given coordinates with given power.
/// </summary>
@ -374,4 +484,240 @@ public class World
{
NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 1);
}
/// <summary>
/// Gets the Chunk at the given coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Chunk at the given coordinates.</returns>
public Chunk.Chunk getChunkAt(int x, int z)
{
return new Chunk.Chunk(this, x, z);
}
/// <summary>
/// Gets the Chunk at the given Location.
/// </summary>
/// <param name="location">Location of the chunk.</param>
/// <returns>Chunk at the given location.</returns>
public Chunk.Chunk getChunkAt(Location location)
{
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
/// <summary>
/// Gets the Chunk that contains the given Block.
/// </summary>
/// <param name="block">Block to get the containing chunk from.</param>
/// <returns>The chunk that contains the given block.</returns>
public Chunk.Chunk getChunkAt(Block.Block block)
{
return getChunkAt(block.getX() >> 4, block.getZ() >> 4);
}
/// <summary>
/// Checks if the specified Chunk is loaded.
/// </summary>
/// <param name="chunk">The chunk to check.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(Chunk.Chunk chunk)
{
return isChunkLoaded(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(int x, int z)
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Gets an array of all loaded Chunks.
/// </summary>
/// <returns>Chunk[] containing all loaded chunks.</returns>
public Chunk.Chunk[] getLoadedChunks()
{
if (NativeBridge.GetLoadedChunks == null)
return Array.Empty<Chunk.Chunk>();
int count = NativeBridge.GetLoadedChunks(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero)
return Array.Empty<Chunk.Chunk>();
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);
}
}
/// <summary>
/// Loads the specified Chunk.
/// </summary>
/// <param name="chunk">The chunk to load.</param>
public void loadChunk(Chunk.Chunk chunk)
{
loadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// 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.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
public void loadChunk(int x, int z)
{
loadChunk(x, z, true);
}
/// <summary>
/// Loads the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
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;
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded and in use
/// by one or more players.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded and in use by one or more players, otherwise false.</returns>
public bool isChunkInUse(int x, int z)
{
if (NativeBridge.IsChunkInUse != null)
return NativeBridge.IsChunkInUse(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="chunk">The chunk to unload.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(Chunk.Chunk chunk)
{
return unloadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// 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.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z)
{
return unloadChunk(x, z, true, true);
}
/// <summary>
/// Safely unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Whether or not to save the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z, bool save)
{
return unloadChunk(x, z, save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
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;
}
/// <summary>
/// Safely queues the Chunk at the specified coordinates for unloading.
/// This method is analogous to unloadChunkRequest(int, int, boolean)
/// where safe is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true is the queue attempt was successful, otherwise false.</returns>
public bool unloadChunkRequest(int x, int z)
{
return unloadChunkRequest(x, z, true);
}
/// <summary>
/// Queues the Chunk at the specified coordinates for unloading.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="safe">Controls whether to queue the chunk when players are nearby.</param>
/// <returns>Whether the chunk was actually queued.</returns>
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;
}
/// <summary>
/// Regenerates the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually regenerated.</returns>
public bool regenerateChunk(int x, int z)
{
if (NativeBridge.RegenerateChunk != null)
return NativeBridge.RegenerateChunk(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Resends the Chunk to all clients.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually refreshed.</returns>
public bool refreshChunk(int x, int z)
{
if (NativeBridge.RefreshChunk != null)
return NativeBridge.RefreshChunk(_dimensionId, x, z) != 0;
return false;
}
}

View file

@ -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

View file

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

View file

@ -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<LevelChunk *> *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<unsigned char>(biomeId & 0xff);
}
} // namespace FourKitBridge

View file

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