package com.bergerkiller.bukkit.common.utils; import java.io.File; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Random; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.block.Block; import org.bukkit.craftbukkit.CraftTravelAgent; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import com.bergerkiller.bukkit.common.Common; import com.bergerkiller.bukkit.common.bases.IntVector3; 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.reflection.classes.CraftServerRef; import com.bergerkiller.bukkit.common.reflection.classes.EntityPlayerRef; import com.bergerkiller.bukkit.common.reflection.classes.PlayerChunkMapRef; import com.bergerkiller.bukkit.common.reflection.classes.PlayerChunkRef; import com.bergerkiller.bukkit.common.reflection.classes.WorldServerRef; import com.bergerkiller.bukkit.common.wrappers.EntityTracker; import net.minecraft.server.Entity; import net.minecraft.server.IDataManager; import net.minecraft.server.MovingObjectPosition; import net.minecraft.server.Vec3D; import net.minecraft.server.World; import net.minecraft.server.WorldNBTStorage; import net.minecraft.server.WorldServer; public class WorldUtil extends ChunkUtil { private static final Object findSpawnDummyEntity = EntityPlayerRef.TEMPLATE.newInstanceNull(); /** * Gets the block type Id * * @param world the block is in * @param blockPos of the block * @return block type Id */ public static int getBlockTypeId(org.bukkit.World world, IntVector3 blockPos) { return getBlockTypeId(world, blockPos.x, blockPos.y, blockPos.z); } /** * Gets the block data * * @param world the block is in * @param blockPos of the block * @return block data */ public static int getBlockData(org.bukkit.World world, IntVector3 blockPos) { return getBlockData(world, blockPos.x, blockPos.y, blockPos.z); } /** * Gets the block data * * @param world 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.World world, int x, int y, int z) { return CommonNMS.getNative(world).getData(x, y, z); } /** * Gets the block type Id * * @param world 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.World world, int x, int y, int z) { return CommonNMS.getNative(world).getTypeId(x, y, z); } /** * Gets the block type * * @param world 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 */ public static org.bukkit.Material getBlockType(org.bukkit.World world, int x, int y, int z) { return MaterialUtil.getType(CommonNMS.getNative(world).getTypeId(x, y, z)); } /** * Gets the shared Random of a world * * @param world to get the Random of * @return Random generator of a world */ public static Random getRandom(org.bukkit.World world) { return CommonNMS.getNative(world).random; } /** * Sets if the spawn chunk area should be kept in memory * * @param world World to apply value on * @param value Keep in memory or not? */ public static void setKeepSpawnInMemory(org.bukkit.World world, boolean value) { CommonNMS.getNative(world).keepSpawnInMemory = value; } /** * Removes a single entity from the world * * @param entity to remove */ public static void removeEntity(org.bukkit.entity.Entity entity) { Entity e = CommonNMS.getNative(entity); e.world.removeEntity(e); WorldServerRef.entityTracker.get(e.world).stopTracking(entity); } /** * Removes a world from all global locations where worlds are mapped * * @param world to remove */ public static void removeWorld(org.bukkit.World world) { // Remove the world from the Bukkit worlds mapping Iterator<org.bukkit.World> iter = CraftServerRef.worlds.values().iterator(); while (iter.hasNext()) { if (iter.next() == world) { iter.remove(); } } // Remove the world from the MinecraftServer worlds mapping CommonNMS.getWorlds().remove(CommonNMS.getNative(world)); } /** * Obtains the internally stored collection of worlds<br> * Gets the values from the CraftServer.worlds map * * @return A collection of World instances */ public static Collection<org.bukkit.World> getWorlds() { return CraftServerRef.worlds.values(); } /** * Gets a live collection (allows modification in the world) of entities on a given world * * @param world the entities are on * @return collection of entities on the world */ public static Collection<org.bukkit.entity.Entity> getEntities(org.bukkit.World world) { return CommonNMS.getEntities(CommonNMS.getNative(world).entityList); } /** * Gets a live collection (allows modification in the world) of players on a given world * * @param world the players are on * @return collection of players on the world */ public static Collection<Player> getPlayers(org.bukkit.World world) { return CommonNMS.getPlayers(CommonNMS.getNative(world).players); } /** * Gets the folder where world data of a certain world is saved in * * @param world (can not be null) * @return world folder */ public static File getWorldFolder(org.bukkit.World world) { return getWorldFolder(world.getName()); } /** * Checks whether a given world name can be loaded * * @param worldName to check * @return True if the world at this world name is loadable, False if not */ public static boolean isLoadableWorld(String worldName) { return Common.SERVER.isLoadableWorld(worldName); } /** * Gets the folder where world data for a certain world name is saved in * * @param worldName (can not be null) * @return world folder */ public static File getWorldFolder(String worldName) { return Common.SERVER.getWorldFolder(worldName); } /** * Gets the File Location where the regions of a world are contained * * @param worldName to get the regions folder for * @return Region folder */ public static File getWorldRegionFolder(String worldName) { return Common.SERVER.getWorldRegionFolder(worldName); } /** * Obtains a Collection of world names that can be loaded without creating it * * @return Loadable worlds */ public static Collection<String> getLoadableWorlds() { return Common.SERVER.getLoadableWorlds(); } /** * Attempts to find a suitable spawn location, searching from the startLocation specified. * Note that portals are created if no position can be found. * * @param startLocation to find a spawn from * @return suitable spawn location, or the input startLocation if this failed */ public static Location findSpawnLocation(Location startLocation) { return findSpawnLocation(startLocation, true); } /** * Attempts to find a suitable spawn location, searching from the startLocation specified. * If specified, portals will be created if none are found. * * @param startLocation to find a spawn from * @param createPortals - True to create a portal if not found, False not to * @return suitable spawn location, or the input startLocation if this failed */ public static Location findSpawnLocation(Location startLocation, boolean createPortals) { WorldServer ws = CommonNMS.getNative(startLocation.getWorld()); // Use a new travel agent to designate a proper position CraftTravelAgent travelAgent = new CraftTravelAgent(ws); travelAgent.setCanCreatePortal(createPortals); Location exit = travelAgent.findOrCreate(startLocation); // Adjust the exit to make it suitable for players // Note: this will raise an NPE while trying to fire the PortalExit event // This is expected behavior try { travelAgent.adjustExit((Entity) findSpawnDummyEntity, exit, new Vector(0, 0, 0)); } catch (NullPointerException ex) { } // Done! return exit; } /** * Gets the folder where player data of a certain world is saved in * * @param world (can not be null) * @return players folder */ public static File getPlayersFolder(org.bukkit.World world) { IDataManager man = CommonNMS.getNative(world).getDataManager(); if (man instanceof WorldNBTStorage) { return ((WorldNBTStorage) man).getPlayerDir(); } return new File(getWorldFolder(world), "playerdata"); } /** * Gets the dimension Id of a world * * @param world to get from * @return world dimension Id */ public static int getDimension(org.bukkit.World world) { return ((World) Conversion.toWorldHandle.convert(world)).worldProvider.dimension; } /** * Gets the server a world object is running on * * @param world to get the server of * @return server */ public static Server getServer(org.bukkit.World world) { return WorldServerRef.getServer(Conversion.toWorldHandle.convert(world)); } /** * Gets the Entity Tracker for the world specified * * @param world to get the tracker for * @return world Entity Tracker */ public static EntityTracker getTracker(org.bukkit.World world) { return WorldServerRef.entityTracker.get(Conversion.toWorldHandle.convert(world)); } /** * Gets the tracker entry of the entity specified * * @param entity to get it for * @return entity tracker entry, or null if none is set */ public static Object getTrackerEntry(org.bukkit.entity.Entity entity) { return getTracker(entity.getWorld()).getEntry(entity); } /** * Sets a new entity tracker entry for the entity specified * * @param entity to set it for * @param entityTrackerEntry to set to (can be null to remove only) * @return the previous tracker entry for the entity, or null if there was none */ public static Object setTrackerEntry(org.bukkit.entity.Entity entity, Object entityTrackerEntry) { return getTracker(entity.getWorld()).setEntry(entity, entityTrackerEntry); } /** * Gets all the entities in the given cuboid area * * @param world to get the entities in * @param ignore entity to ignore (do not return) * @param xmin of the cuboid to check * @param ymin of the cuboid to check * @param zmin of the cuboid to check * @param xmax of the cuboid to check * @param ymax of the cuboid to check * @param zmax of the cuboid to check * @return A (referenced) list of entities in the cuboid */ public static List<org.bukkit.entity.Entity> getEntities(org.bukkit.World world, org.bukkit.entity.Entity ignore, double xmin, double ymin, double zmin, double xmax, double ymax, double zmax) { List<Entity> list = CommonNMS.getEntities(CommonNMS.getNative(world), CommonNMS.getNative(ignore), xmin, ymin, zmin, xmax, ymax, zmax); return new ConvertingList<org.bukkit.entity.Entity>(list, ConversionPairs.entity); } /** * Gets all the entities nearby an entity * * @param entity to get the nearby entities of * @param radX to look for entities * @param radY to look for entities * @param radZ to look for entities * @return A (referenced) list of entities nearby */ public static List<org.bukkit.entity.Entity> getNearbyEntities(org.bukkit.entity.Entity entity, double radX, double radY, double radZ) { return CommonNMS.getEntities(entity.getWorld(), entity, CommonNMS.getNative(entity).boundingBox.grow(radX, radY, radZ)); } /** * Gets all the entities nearby a Location * * @param location to get the nearby entities of * @param radX to look for entities * @param radY to look for entities * @param radZ to look for entities * @return A (referenced) list of entities nearby */ public static List<org.bukkit.entity.Entity> getNearbyEntities(Location location, double radX, double radY, double radZ) { final double xmin = location.getX() - radX; final double ymin = location.getY() - radY; final double zmin = location.getZ() - radZ; final double xmax = location.getX() + radX; final double ymax = location.getY() + radY; final double zmax = location.getZ() + radZ; return getEntities(location.getWorld(), null, xmin, ymin, zmin, xmax, ymax, zmax); } /** * Calculates the damage factor for an entity exposed to an explosion * * @param explosionPosition of the explosion * @param entity that was damaged * @return damage factor */ public static float getExplosionDamageFactor(Location explosionPosition, org.bukkit.entity.Entity entity) { final Vec3D vec = (Vec3D) Conversion.toVec3DHandle.convert(explosionPosition); return CommonNMS.getNative(explosionPosition.getWorld()).a(vec, CommonNMS.getNative(entity).boundingBox); } /** * Saves a world to disk, waiting until saving has completed before returning. * This may take significantly long. This method is cross-thread supported. * * @param world to be saved */ public static synchronized void saveToDisk(org.bukkit.World world) { CommonNMS.getNative(world).saveLevel(); } public static void loadChunks(Location location, final int radius) { loadChunks(location.getWorld(), location.getX(), location.getZ(), radius); } public static void loadChunks(org.bukkit.World world, double xmid, double zmid, final int radius) { loadChunks(world, MathUtil.toChunk(xmid), MathUtil.toChunk(zmid), radius); } public static void loadChunks(org.bukkit.World world, final int xmid, final int zmid, final int radius) { for (int cx = xmid - radius; cx <= xmid + radius; cx++) { for (int cz = zmid - radius; cz <= zmid + radius; cz++) { world.getChunkAt(cx, cz); } } } public static boolean isLoaded(Location location) { return isLoaded(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); } public static boolean isLoaded(Block block) { return isLoaded(block.getWorld(), block.getX(), block.getY(), block.getZ()); } public static boolean isLoaded(org.bukkit.World world, double x, double y, double z) { return isLoaded(world, MathUtil.toChunk(x), MathUtil.toChunk(z)); } public static boolean isLoaded(org.bukkit.World world, int x, int y, int z) { return isLoaded(world, x >> 4, z >> 4); } public static boolean isLoaded(org.bukkit.World world, int chunkX, int chunkZ) { if (world == null) { return false; } return world.isChunkLoaded(chunkX, chunkZ); } public static boolean areChunksLoaded(org.bukkit.World world, int chunkCenterX, int chunkCenterZ, int chunkDistance) { return areBlocksLoaded(world, chunkCenterX << 4, chunkCenterZ << 4, chunkDistance << 4); } public static boolean areBlocksLoaded(org.bukkit.World world, int blockCenterX, int blockCenterZ, int distance) { return CommonNMS.getNative(world).areChunksLoaded(blockCenterX, 0, blockCenterZ, distance); } public static void queueChunkSend(org.bukkit.Chunk chunk) { queueChunkSend(chunk.getWorld(), chunk.getX(), chunk.getZ()); } /** * Queues a chunk for sending to all players in view * * @param world the chunk is in * @param chunkX of the chunk * @param chunkZ of the chunk */ public static void queueChunkSend(org.bukkit.World world, int chunkX, int chunkZ) { Object playerChunkMap = WorldServerRef.playerChunkMap.get(Conversion.toWorldHandle.convert(world)); Object playerChunk = PlayerChunkMapRef.getPlayerChunk(playerChunkMap, chunkX, chunkZ); if (playerChunk == null) { return; } for (Player player : PlayerChunkRef.players.get(playerChunk)) { PlayerUtil.queueChunkSend(player, chunkX, chunkZ); } } /** * Queues a block for sending to all players in view * * @param world the block is in * @param blockX of the block * @param blockY of the block * @param blockZ of the block */ public static void queueBlockSend(org.bukkit.World world, int blockX, int blockY, int blockZ) { Object playerChunkMap = WorldServerRef.playerChunkMap.get(Conversion.toWorldHandle.convert(world)); PlayerChunkMapRef.flagBlockDirty(playerChunkMap, blockX, blockY, blockZ); } /** * Performs a ray tracing operation from one point to the other, and obtains the (first) block hit * * @param world to ray trace in * @param startX to start ray tracing from * @param startY to start ray tracing from * @param startZ to start ray tracing from * @param endX to stop ray tracing (outer limit) * @param endY to stop ray tracing (outer limit) * @param endZ to stop ray tracing (outer limit) * @return the hit Block, or null if none was found (AIR) */ public static Block rayTraceBlock(org.bukkit.World world, double startX, double startY, double startZ, double endX, double endY, double endZ) { MovingObjectPosition mop = CommonNMS.getNative(world).rayTrace(CommonNMS.newVec3D(startX, startY, startZ), CommonNMS.newVec3D(endX, endY, endZ), false); return mop == null ? null : world.getBlockAt(mop.b, mop.c, mop.d); } /** * Performs a ray tracing operation from one point to the other, and obtains the (first) block hit * * @param startLocation to start ray tracing from * @param direction to which to ray trace * @param maxLength limit of ray tracing * @return the hit Block, or null if none was found (AIR) */ public static Block rayTraceBlock(Location startLocation, Vector direction, double maxLength) { final double startX = startLocation.getX(); final double startY = startLocation.getY(); final double startZ = startLocation.getZ(); final double endX = startX + direction.getX() * maxLength; final double endY = startY + direction.getY() * maxLength; final double endZ = startZ + direction.getZ() * maxLength; return rayTraceBlock(startLocation.getWorld(), startX, startY, startZ, endX, endY, endZ); } /** * Performs a ray tracing operation from one point to the other, and obtains the (first) block hit * * @param startLocation to start ray tracing from, direction from Location is used * @param maxLength limit of ray tracing * @return the hit Block, or null if none was found (AIR) */ public static Block rayTraceBlock(Location startLocation, double maxLength) { return rayTraceBlock(startLocation, startLocation.getDirection(), maxLength); } }