package com.bergerkiller.bukkit.common.internal; import java.util.List; import java.util.Random; import org.bukkit.Server; import org.bukkit.event.world.ChunkPopulateEvent; import org.bukkit.generator.BlockPopulator; import com.bergerkiller.bukkit.common.conversion.Conversion; 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.WorldServerRef; import com.bergerkiller.bukkit.common.utils.CommonUtil; import com.bergerkiller.bukkit.common.utils.MathUtil; import com.bergerkiller.bukkit.common.utils.WorldUtil; import net.minecraft.server.BiomeMeta; import net.minecraft.server.BlockSand; import net.minecraft.server.Chunk; import net.minecraft.server.ChunkProviderServer; import net.minecraft.server.CrashReport; import net.minecraft.server.CrashReportSystemDetails; import net.minecraft.server.EnumCreatureType; import net.minecraft.server.IChunkLoader; import net.minecraft.server.IChunkProvider; import net.minecraft.server.ReportedException; import net.minecraft.server.WorldServer; /** * A CPS Hook class that provides various new events, timings and other useful utilities. * This is here so that other plugins can safely control internal behavior (NoLagg mainly). */ public class ChunkProviderServerHook extends ChunkProviderServer { public ChunkProviderServerHook(WorldServer worldserver, IChunkLoader ichunkloader, IChunkProvider ichunkprovider) { super(worldserver, ichunkloader, ichunkprovider); } public org.bukkit.World getWorld() { return super.world.getWorld(); } @Override @SuppressWarnings("unchecked") public List<BiomeMeta> getMobsFor(EnumCreatureType enumcreaturetype, int x, int y, int z) { List<BiomeMeta> mobs = super.getMobsFor(enumcreaturetype, x, y, z); if (CommonPlugin.hasInstance()) { org.bukkit.World world = this.world.getWorld(); return CommonPlugin.getInstance().getEventFactory().handleCreaturePreSpawn(world, x, y, z, mobs); } else { return mobs; } } @Override public Chunk loadChunk(int x, int z) { // Perform chunk load from file timings if (!CommonPlugin.TIMINGS.isActive()) { return super.loadChunk(x, z); } else { long time = System.nanoTime(); Chunk nmsChunk = super.loadChunk(x, z); if (nmsChunk != null) { time = System.nanoTime() - time; CommonPlugin.TIMINGS.onChunkLoad(Conversion.toChunk.convert(nmsChunk), time); } return nmsChunk; } } @Override public void getChunkAt(IChunkProvider ichunkprovider, int i, int j) { // Perform chunk population timings if (!CommonPlugin.TIMINGS.isActive()) { super.getChunkAt(ichunkprovider, i, j); return; } Chunk chunk = this.getOrCreateChunk(i, j); if (!chunk.done) { chunk.done = true; this.chunkProvider.getChunkAt(ichunkprovider, i, j); // CraftBukkit start BlockSand.instaFall = true; final Random random = new Random(); random.setSeed(world.getSeed()); long xRand = random.nextLong() / 2L * 2L + 1L; long zRand = random.nextLong() / 2L * 2L + 1L; random.setSeed((long) i * xRand + (long) j * zRand ^ world.getSeed()); // Call populators long time; org.bukkit.World bWorld = getWorld(); org.bukkit.Chunk bChunk = CommonNMS.getChunk(chunk); for (BlockPopulator populator : bWorld.getPopulators()) { time = System.nanoTime(); try { populator.populate(bWorld, random, bChunk); } finally { time = System.nanoTime() - time; CommonPlugin.TIMINGS.onChunkPopulate(bChunk, populator, time); } } // Done BlockSand.instaFall = false; super.world.getServer().getPluginManager().callEvent(new ChunkPopulateEvent(chunk.bukkitChunk)); // CraftBukkit end chunk.e(); } } @Override public Chunk getChunkAt(int x, int z, Runnable runnable) { // Perform chunk generation timings if (!CommonPlugin.TIMINGS.isActive()) { return super.getChunkAt(x, z, runnable); } WorldUtil.setChunkUnloading(getWorld(), x, z, false); org.bukkit.Chunk chunk = WorldUtil.getChunk(getWorld(), x, z); // Deal with delayed (async) loading accordingly if (chunk != null) { if (runnable != null) { runnable.run(); } return CommonNMS.getNative(chunk); } else if (runnable != null) { // Queue chunk for loading Async final Object chunkRegionLoader = CommonUtil.tryCast(ChunkProviderServerRef.chunkLoader.get(this), ChunkRegionLoaderRef.TEMPLATE.getType()); if (chunkRegionLoader != null && ChunkRegionLoaderRef.chunkExists(chunkRegionLoader, getWorld(), x, z)) { // Schedule for loading Async - return null to indicate that no chunk is loaded yet ChunkRegionLoaderRef.queueChunkLoad(chunkRegionLoader, getWorld(), this, x, z, runnable); return null; } } // Try to load the chunk from file chunk = CommonNMS.getChunk(this.loadChunk(x, z)); // Try to generate the chunk instead boolean newChunk; if (newChunk = (chunk == null)) { if (this.chunkProvider == null) { // Nothing to do here // Don't even fire a chunk load event - empty chunk is not a valid Chunk // Registering it is also a bad idea... return this.emptyChunk; } // Generate the chunk long time = System.nanoTime(); try { chunk = CommonNMS.getChunk(this.chunkProvider.getOrCreateChunk(x, z)); } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk"); CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated"); crashreportsystemdetails.a("Location", String.format("%d,%d", x, z)); crashreportsystemdetails.a("Position hash", Long.valueOf(MathUtil.longHashToLong(x, z))); crashreportsystemdetails.a("Generator", this.chunkProvider.getName()); throw new ReportedException(crashreport); } if (chunk != null) { time = System.nanoTime() - time; CommonPlugin.TIMINGS.onChunkGenerate(chunk, time); } } // Sadly, loading failed...how did this happen?! if (chunk == null) { return null; } // Initial registration of the chunk on the server WorldUtil.setChunk(getWorld(), x, z, chunk); Chunk chunkHandle = CommonNMS.getNative(chunk); ChunkRef.addEntities(chunkHandle); // CraftBukkit start Server server = WorldUtil.getServer(getWorld()); if (server != null) { /* * If it's a new world, the first few chunks are generated * inside the World constructor. We can't reliably alter * that, so we have no way of creating a * CraftWorld/CraftServer at that point. */ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk, newChunk)); } // CraftBukkit end // Perhaps load some neighboring chunks? (population related) ChunkRef.loadNeighbours(chunkHandle, this, this, x, z); // Successful load! return chunkHandle; } @Override public boolean unloadChunks() { // Perform chunk unload timings if (!CommonPlugin.TIMINGS.isActive()) { return super.unloadChunks(); } else { long time = System.nanoTime(); try { return super.unloadChunks(); } finally { time = System.nanoTime() - time; CommonPlugin.TIMINGS.onChunkUnloading(getWorld(), time); } } } private static <T> T getCPS(org.bukkit.World world, Class<T> type) { return CommonUtil.tryCast(WorldServerRef.chunkProviderServer.get(Conversion.toWorldHandle.convert(world)), type); } private static IChunkLoader getLoader(Object cps) { return (IChunkLoader) ChunkProviderServerRef.chunkLoader.get(cps); } public static void hook(org.bukkit.World world) { ChunkProviderServer oldCPS = getCPS(world, ChunkProviderServer.class); if (oldCPS instanceof ChunkProviderServerHook) { return; } ChunkProviderServerHook newCPS = new ChunkProviderServerHook(oldCPS.world, getLoader(oldCPS), oldCPS.chunkProvider); ChunkProviderServerRef.TEMPLATE.transfer(oldCPS, newCPS); WorldServerRef.chunkProviderServer.set(newCPS.world, newCPS); } public static void unhook(org.bukkit.World world) { ChunkProviderServerHook oldCPS = getCPS(world, ChunkProviderServerHook.class); if (oldCPS == null) { return; } ChunkProviderServer newCPS = new ChunkProviderServer(oldCPS.world, getLoader(oldCPS), oldCPS.chunkProvider); ChunkProviderServerRef.TEMPLATE.transfer(oldCPS, newCPS); WorldServerRef.chunkProviderServer.set(newCPS.world, newCPS); } }