package com.bergerkiller.bukkit.common.utils; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.logging.Level; import net.minecraft.server.Block; import net.minecraft.server.Chunk; import net.minecraft.server.ChunkCoordIntPair; import net.minecraft.server.ChunkSection; import net.minecraft.server.WorldServer; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.util.LongHash; import org.bukkit.craftbukkit.util.LongHashSet; import org.bukkit.craftbukkit.util.LongObjectHashMap; import org.bukkit.entity.Player; import com.bergerkiller.bukkit.common.collections.FilteredCollection; import com.bergerkiller.bukkit.common.collections.List2D; import com.bergerkiller.bukkit.common.conversion.Conversion; import com.bergerkiller.bukkit.common.conversion.ConversionPairs; import com.bergerkiller.bukkit.common.conversion.util.ConvertingList; import com.bergerkiller.bukkit.common.internal.CommonNMS; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.reflection.FieldAccessor; import com.bergerkiller.bukkit.common.reflection.SafeField; import com.bergerkiller.bukkit.common.reflection.classes.ChunkProviderServerRef; import com.bergerkiller.bukkit.common.reflection.classes.ChunkRef; import com.bergerkiller.bukkit.common.reflection.classes.ChunkRegionLoaderRef; import com.bergerkiller.bukkit.common.reflection.classes.ChunkSectionRef; import com.bergerkiller.bukkit.common.reflection.classes.EntityPlayerRef; import com.bergerkiller.bukkit.common.reflection.classes.WorldServerRef; /** * Contains utilities to get and set chunks of a world */ public class ChunkUtil { private static boolean canUseLongObjectHashMap = CommonUtil.getCBClass("util.LongObjectHashMap") != null; private static boolean canUseLongHashSet = CommonUtil.getCBClass("util.LongHashSet") != null; public static final FieldAccessor<List<Object>> chunkListField; static { Field f; try { f = ChunkProviderServerRef.TEMPLATE.getType().getField("chunkList"); } catch (Throwable t) { f = null; } chunkListField = f == null ? null : new SafeField<List<Object>>(f); } /** * Gets the height of a given column in a chunk * * @param chunk the column is in * @param x - coordinate of the block column * @param z - coordinate of the block column * @return column height */ public static int getHeight(org.bukkit.Chunk chunk, int x, int z) { return ChunkRef.getHeight(CommonNMS.getNative(chunk), x, z); } /** * Gets the block light level * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @return Block light level */ public static int getBlockLight(org.bukkit.Chunk chunk, int x, int y, int z) { return ChunkRef.getBlockLight(CommonNMS.getNative(chunk), x, y, z); } /** * Gets the sky light level * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @return Sky light level */ public static int getSkyLight(org.bukkit.Chunk chunk, int x, int y, int z) { return ChunkRef.getSkyLight(CommonNMS.getNative(chunk), x, y, z); } /** * Gets the block data * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @return block data */ public static int getBlockData(org.bukkit.Chunk chunk, int x, int y, int z) { return ChunkRef.getData(CommonNMS.getNative(chunk), x, y, z); } /** * Gets the block type Id * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @return block type Id */ @Deprecated public static int getBlockTypeId(org.bukkit.Chunk chunk, int x, int y, int z) { return ChunkRef.getTypeId(CommonNMS.getNative(chunk), x, y, z); } /** * Gets the block type Id * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @return block type */ @SuppressWarnings("deprecation") public static Material getBlockType(org.bukkit.Chunk chunk, int x, int y, int z) { return MaterialUtil.getType(ChunkRef.getTypeId(CommonNMS.getNative(chunk), x, y, z)); } /** * Sets a block type id and data without causing physics or lighting updates * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @param typeId to set to * @param data to set to */ public static void setBlockFast(org.bukkit.Chunk chunk, int x, int y, int z, int typeId, int data) { if (y < 0 || y >= chunk.getWorld().getMaxHeight()) { return; } Object[] sections = ChunkRef.getSections(CommonNMS.getNative(chunk)); final int secIndex = y >> 4; Object section = sections[secIndex]; if (section == null) { section = sections[secIndex] = new ChunkSection(y >> 4 << 4, !CommonNMS.getNative(chunk.getWorld()).worldProvider.g); } ChunkSectionRef.setTypeId(section, x, y, z, typeId); ChunkSectionRef.setData(section, x, y, z, data); } /** * Sets a block type id and data, causing physics and lighting updates * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @param typeId to set to * @param data to set to * @return True if a block got changed, False if not */ @Deprecated public static boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, int typeId, int data) { return setBlock(chunk, x, y, z, MaterialUtil.getType(typeId), data); } /** * Sets a block type id and data, causing physics and lighting updates * * @param chunk the block is in * @param x - coordinate of the block * @param y - coordinate of the block * @param z - coordinate of the block * @param type to set to * @param data to set to * @return True if a block got changed, False if not */ public static boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, Material type, int data) { boolean result = y >= 0 && y <= chunk.getWorld().getMaxHeight(); WorldServer world = CommonNMS.getNative(chunk.getWorld()); Block typeBlock = CommonNMS.getBlock(type); if (result) { result = ChunkRef.setBlock(Conversion.toChunkHandle.convert(chunk), x, y, z, typeBlock, data); world.methodProfiler.a("checkLight"); world.z(x, y, z); world.methodProfiler.b(); } if (result) { world.applyPhysics(x, y, z, typeBlock); } return result; } /** * Gets a live collection of all the entities in a chunk<br> * Changes to this collection are reflected back in the chunk * * @param chunk for which to get the entities * @return Live collection of entities in the chunk */ public static List<org.bukkit.entity.Entity> getEntities(org.bukkit.Chunk chunk) { List<Object>[] entitySlices = ChunkRef.entitySlices.get(Conversion.toChunkHandle.convert(chunk)); return new ConvertingList<org.bukkit.entity.Entity>(new List2D<Object>(entitySlices), ConversionPairs.entity); } /** * Checks if a chunk is about be loaded * * @param player who will receive the chunk * @param cx location for the chunk X * @param cz location for the chunk Z * @return chunk being loaded soon? */ public static boolean isLoadRequested(Player player, int cx, int cz) { List<?> chunkQue = EntityPlayerRef.chunkQueue.get(Conversion.toEntityHandle.convert(player)); for (Object location : chunkQue) { ChunkCoordIntPair loc = (ChunkCoordIntPair) location; if (loc.x == cx && loc.z == cz) { return true; } } return false; } /** * Gets whether a given chunk is readily available. * If this method returns False, the chunk is not yet generated. * * @param world the chunk is in * @param x - coordinate of the chunk * @param z - coordinate of the chunk * @return True if the chunk can be obtained without generating it, False if not */ public static boolean isChunkAvailable(World world, int x, int z) { Object cps = WorldServerRef.chunkProviderServer.get(Conversion.toWorldHandle.convert(world)); if (ChunkProviderServerRef.isChunkLoaded.invoke(cps, x, z)) { // Chunk is loaded into memory, True return true; } else { Object chunkLoader = ChunkProviderServerRef.chunkLoader.get(cps); if (ChunkRegionLoaderRef.TEMPLATE.isInstance(chunkLoader)) { // Chunk can be loaded from file return ChunkRegionLoaderRef.chunkExists(chunkLoader, world, x, z); } else { // Unable to find out... return false; } } } /** * Gets all the chunks loaded on a given world * * @param world to get the loaded chunks from * @return Loaded chunks */ @SuppressWarnings("rawtypes") public static Collection<org.bukkit.Chunk> getChunks(World world) { // ChunkList field (if available) if (chunkListField != null) { Object cps = CommonNMS.getNative(world).chunkProviderServer; List<Object> rawChunkList = chunkListField.get(cps); Collection<org.bukkit.Chunk> chunkList = CommonNMS.getChunks(rawChunkList); return FilteredCollection.createNullFilter(chunkList); } // LongObjectHashMap mirror if (canUseLongObjectHashMap) { Object chunks = ChunkProviderServerRef.chunks.get(CommonNMS.getNative(world).chunkProviderServer); if (chunks != null) { try { if (canUseLongObjectHashMap && chunks instanceof LongObjectHashMap) { return FilteredCollection.createNullFilter(CommonNMS.getChunks(((LongObjectHashMap) chunks).values())); } } catch (Throwable t) { canUseLongObjectHashMap = false; CommonPlugin.getInstance().log(Level.WARNING, "Failed to access chunks using CraftBukkit's long object hashmap, support disabled"); CommonUtil.filterStackTrace(t).printStackTrace(); } } } // Bukkit alternative return FilteredCollection.createNullFilter(Arrays.asList(world.getLoadedChunks())); } /** * Gets a chunk from a world without loading or generating it * * @param world to obtain the chunk from * @param x - coordinate of the chunk * @param z - coordinate of the chunk * @return The chunk, or null if it is not loaded */ @SuppressWarnings("rawtypes") public static org.bukkit.Chunk getChunk(World world, final int x, final int z) { final long key = LongHash.toLong(x, z); Object chunks = ChunkProviderServerRef.chunks.get(CommonNMS.getNative(world).chunkProviderServer); if (chunks != null) { if (canUseLongObjectHashMap && chunks instanceof LongObjectHashMap) { try { return CommonNMS.getChunk(((Chunk) ((LongObjectHashMap) chunks).get(key))); } catch (Throwable t) { canUseLongObjectHashMap = false; CommonPlugin.getInstance().log(Level.WARNING, "Failed to access chunks using CraftBukkit's long object hashmap, support disabled"); CommonUtil.filterStackTrace(t).printStackTrace(); } } } // Bukkit alternative if (world.isChunkLoaded(x, z)) { return world.getChunkAt(x, z); } else { return null; } } /** * Gets, loads or generated a chunk without loading or generating it on the main thread. * Allows the lazy-loading of chunks without locking the server. * * @param world to obtain the chunk from * @param x - coordinate of the chunk * @param z - coordinate of the chunk * @param runnable to execute once the chunk is loaded or obtained */ public static void getChunkAsync(World world, final int x, final int z, Runnable runnable) { CommonNMS.getNative(world).chunkProviderServer.getChunkAt(x, z, runnable); } /** * Sets a given chunk coordinate to contain the chunk specified * * @param world to set the chunk in * @param x - coordinate of the chunk * @param z - coordinate of the chunk * @param chunk to set to (use null to remove) */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void setChunk(World world, final int x, final int z, final org.bukkit.Chunk chunk) { final Chunk handle = CommonNMS.getNative(chunk); if (handle != null && handle.bukkitChunk == null) { throw new RuntimeException("Can not put a chunk that has no BukkitChunk"); } if (canUseLongObjectHashMap) { Object cps = CommonNMS.getNative(world).chunkProviderServer; Object chunks = ChunkProviderServerRef.chunks.get(cps); if (chunks != null) { final long key = LongHash.toLong(x, z); try { if (canUseLongObjectHashMap && chunks instanceof LongObjectHashMap) { if (handle == null) { // Remove the chunk Object removed = ((LongObjectHashMap) chunks).remove(key); if (removed != null && chunkListField != null) { chunkListField.get(cps).remove(removed); } } else { // Add the chunk Object oldChunk = ((LongObjectHashMap) chunks).put(key, handle); if (chunkListField != null) { List<Object> chunkList = chunkListField.get(cps); if (oldChunk != null) { chunkList.remove(oldChunk); } chunkList.add(handle); } } return; } } catch (Throwable t) { canUseLongObjectHashMap = false; CommonPlugin.LOGGER.log(Level.WARNING, "Failed to access chunks using CraftBukkit's long object hashmap, support disabled"); CommonUtil.filterStackTrace(t).printStackTrace(); } } } throw new RuntimeException("Failed to set chunk using a known method"); } /** * Saves a single chunk to disk * * @param chunk to save */ public static void saveChunk(org.bukkit.Chunk chunk) { CommonNMS.getNative(chunk.getWorld()).chunkProviderServer.saveChunk(CommonNMS.getNative(chunk)); } /** * Gets whether a chunk needs to be saved * * @param chunk to check * @return True if it needs to be saved, False if not */ public static boolean needsSaving(org.bukkit.Chunk chunk) { return ChunkRef.needsSaving(Conversion.toChunkHandle.convert(chunk)); } /** * Sets whether a given chunk coordinate has to be unloaded * * @param world to set the unload request for * @param x - coordinate of the chunk * @param z - coordinate of the chunk * @param unload state to set to */ public static void setChunkUnloading(World world, final int x, final int z, boolean unload) { if (canUseLongHashSet) { Object unloadQueue = ChunkProviderServerRef.unloadQueue.get(CommonNMS.getNative(world).chunkProviderServer); if (unloadQueue != null) { try { if (canUseLongHashSet && unloadQueue instanceof LongHashSet) { if (unload) { ((LongHashSet) unloadQueue).add(x, z); } else { ((LongHashSet) unloadQueue).remove(x, z); } return; } } catch (Throwable t) { canUseLongHashSet = false; CommonPlugin.LOGGER.log(Level.WARNING, "Failed to access chunks using CraftBukkit's long object hashmap, support disabled"); CommonUtil.filterStackTrace(t).printStackTrace(); } } } throw new RuntimeException("Failed to set unload queue using a known method"); } /** * Obtains all the Block State tile entities available in a Chunk * * @param chunk to get the Block States for * @return collection of Block States (mutual) */ public static Collection<BlockState> getBlockStates(org.bukkit.Chunk chunk) { return ConversionPairs.blockState.convertAll(CommonNMS.getNative(chunk).tileEntities.values()); } /** * Adds an Entity to a Chunk * * @param chunk to add an entity to * @param entity to add */ public static void addEntity(org.bukkit.Chunk chunk, org.bukkit.entity.Entity entity) { CommonNMS.getNative(chunk).a(CommonNMS.getNative(entity)); } /** * Removes an Entity from a Chunk * * @param chunk to remove an entity from * @param entity to remove * @return True if the entity has been removed, False if not (not found) */ @SuppressWarnings("unchecked") public static boolean removeEntity(org.bukkit.Chunk chunk, org.bukkit.entity.Entity entity) { final List<Object>[] slices = CommonNMS.getNative(chunk).entitySlices; final int sliceY = MathUtil.clamp(MathUtil.toChunk(EntityUtil.getLocY(entity)), 0, slices.length - 1); final Object handle = Conversion.toEntityHandle.convert(entity); if (slices[sliceY].remove(handle)) { return true; } else { for (int y = 0; y < slices.length; y++) { if (y != sliceY && slices[y].remove(handle)) { return true; } } return false; } } }