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