package com.forgeessentials.commands.world; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.AnvilChunkLoader; import net.minecraft.world.chunk.storage.RegionFileCache; import net.minecraft.world.gen.ChunkProviderServer; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.permission.PermissionLevel; import com.forgeessentials.api.APIRegistry; import com.forgeessentials.api.permissions.FEPermissions; import com.forgeessentials.commands.ModuleCommands; import com.forgeessentials.commons.selections.AreaShape; import com.forgeessentials.core.commands.ParserCommandBase; import com.forgeessentials.core.misc.TaskRegistry; import com.forgeessentials.core.misc.TaskRegistry.TickTask; import com.forgeessentials.core.misc.TranslatedCommandException; import com.forgeessentials.util.CommandParserArgs; import com.forgeessentials.util.ServerUtil; import com.forgeessentials.util.output.ChatOutputHandler; import com.forgeessentials.worldborder.ModuleWorldBorder; import com.forgeessentials.worldborder.WorldBorder; public class CommandPregen extends ParserCommandBase implements TickTask { private boolean running = false; private WorldServer world; private boolean fullPregen; private int speed; private AreaShape shape; private int minX; private int minZ; private int maxX; private int maxZ; private int centerX; private int centerZ; private int sizeX; private int sizeZ; private int x; private int z; private int totalTicks; private int totalChunks; private boolean autoSpeed; @Override public String getCommandName() { return "fepregen"; } @Override public String[] getDefaultAliases() { return new String[] { "pregen", "filler" }; } @Override public String getCommandUsage(ICommandSender sender) { return "/pregen start [speed] [true|false] [dim]"; } @Override public String getPermissionNode() { return ModuleCommands.PERM + ".pregen"; } @Override public PermissionLevel getPermissionLevel() { return PermissionLevel.OP; } @Override public boolean canConsoleUseCommand() { return true; } @Override public void parse(CommandParserArgs arguments) { if (arguments.isEmpty()) { if (running) { arguments.confirm("Pregen running"); } else { arguments.confirm("No pregen running"); arguments.notify("/pregen start [<speed>|auto] [dim]"); } return; } arguments.tabComplete("start", "stop"); String subCmd = arguments.remove().toLowerCase(); switch (subCmd) { case "start": parseStart(arguments); break; case "stop": parseStop(arguments); break; case "flush": flush(arguments); break; default: throw new TranslatedCommandException(FEPermissions.MSG_UNKNOWN_SUBCOMMAND, subCmd); } } /* ------------------------------------------------------------ */ private void parseStart(CommandParserArgs arguments) { if (running) { arguments.error("Pregen already running"); return; } world = null; speed = 1; autoSpeed = false; fullPregen = true; if (!arguments.isEmpty()) { autoSpeed = arguments.peek().equalsIgnoreCase("auto"); if (!autoSpeed) speed = arguments.parseInt(); if (!arguments.isEmpty()) { world = DimensionManager.getWorld(arguments.parseInt()); if (world == null) throw new TranslatedCommandException("This world does not exist"); if (!arguments.isEmpty()) { fullPregen = arguments.parseBoolean(); } } } if (world == null) { if (arguments.senderPlayer == null) throw new TranslatedCommandException(FEPermissions.MSG_NOT_ENOUGH_ARGUMENTS); else world = (WorldServer) arguments.senderPlayer.worldObj; } WorldBorder border = ModuleWorldBorder.getInstance().getBorder(world); if (border == null) throw new TranslatedCommandException("No worldborder defined"); centerX = border.getCenter().getX() / 16; centerZ = border.getCenter().getZ() / 16; sizeX = border.getSize().getX() / 16; sizeZ = border.getSize().getZ() / 16; minX = border.getArea().getLowPoint().getX() / 16; minZ = border.getArea().getLowPoint().getZ() / 16; maxX = border.getArea().getHighPoint().getX() / 16; maxZ = border.getArea().getHighPoint().getZ() / 16; shape = border.getShape(); x = minX - 1; z = minZ; running = true; totalTicks = 0; totalChunks = 0; TaskRegistry.getInstance().schedule(this); arguments.confirm("Pregen started"); } private void parseStop(CommandParserArgs arguments) { if (!running) { arguments.error("No pregen running"); return; } running = false; } private void flush(CommandParserArgs arguments) { if (!running) { arguments.error("No pregen running"); return; } ChunkProviderServer providerServer = (ChunkProviderServer) world.getChunkProvider(); providerServer.unloadAllChunks(); arguments.confirm("Queued all chunks for unloading"); } @Override public boolean tick() { if (!running) { notifyPlayers("Pregen stopped"); return true; } totalTicks++; ChunkProviderServer providerServer = (ChunkProviderServer) world.getChunkProvider(); double tps = ServerUtil.getTPS(); if (totalTicks % 80 == 0) notifyPlayers(String.format("Pregen: %d/%d chunks, s:%d, tps:%.1f, lc:%d", totalChunks, sizeX * sizeZ * 4, speed, tps, providerServer.getLoadedChunkCount())); if (autoSpeed && totalTicks % 160 == 0 && tps >= 60 && speed < 16) speed++; if (autoSpeed && totalTicks % 20 == 0 && tps < 25 && speed > 1) speed--; for (int i = 0; i < speed; i++) { int skippedChunks = 0; while (true) { totalChunks++; if (!next()) { running = false; notifyPlayers("World pregen finished"); return true; } if (RegionFileCache.createOrLoadRegionFile(world.getChunkSaveLocation(), x, z).chunkExists(x & 0x1F, z & 0x1F)) { skippedChunks++; if (skippedChunks > 100) break; else continue; } if (fullPregen) { providerServer.provideChunk(x, z); } else { Chunk chunk = providerServer.currentChunkProvider.loadChunk(x, z); chunk.populateChunk(providerServer, providerServer, x, z); saveChunk(providerServer, chunk); } if (providerServer.getLoadedChunkCount() > 256) providerServer.unloadChunksIfNotNearSpawn(x, z); break; } } return false; } private boolean next() { switch (shape) { default: case BOX: if (++x > maxX) { x = minX; if (++z > maxZ) return false; } return true; case CYLINDER: case ELLIPSOID: while (true) { if (++x > maxX) { x = minX; if (++z > maxZ) return false; } double dx = (double) (centerX - x) / sizeX; double dz = (double) (centerZ - z) / sizeZ; if (dx * dx + dz * dz <= 1) return true; } } } @Override public boolean editsBlocks() { return true; } public void notifyPlayers(String message) { for (EntityPlayerMP player : ServerUtil.getPlayerList()) if (APIRegistry.perms.checkPermission(player, getPermissionNode())) ChatOutputHandler.chatNotification(player, message); } /* ------------------------------------------------------------ */ private static Method writeChunkToNBT; static { Class<?>[] cArg = new Class[] { Chunk.class, World.class, NBTTagCompound.class }; try { writeChunkToNBT = AnvilChunkLoader.class.getDeclaredMethod("func_75820_a", cArg); // writeChunkToNBT } catch (NoSuchMethodException e) { try { writeChunkToNBT = AnvilChunkLoader.class.getDeclaredMethod("writeChunkToNBT", cArg); } catch (NoSuchMethodException e1) { throw new RuntimeException("Pregen: Unable to obtain access to private method AnvilChunkLoader.writeChunkToNBT"); } } writeChunkToNBT.setAccessible(true); } private static void saveChunk(ChunkProviderServer provider, Chunk chunk) { AnvilChunkLoader loader = (AnvilChunkLoader) provider.currentChunkLoader; try { NBTTagCompound chunkTag = new NBTTagCompound(); NBTTagCompound levelTag = new NBTTagCompound(); chunkTag.setTag("Level", levelTag); writeChunkToNBT(provider.worldObj, loader, chunk, levelTag); writeChunkData(provider.worldObj, loader, chunk, chunkTag); } catch (Exception exception) { exception.printStackTrace(); } } /* wrapper for AnvilChunkLoader.writeChunkToNBT */ private static void writeChunkToNBT(WorldServer world, AnvilChunkLoader loader, Chunk chunk, NBTTagCompound tag) { Object[] args = new Object[] { chunk, world, tag }; try { writeChunkToNBT.invoke(loader, args); } catch (IllegalAccessException | IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } private static void writeChunkData(WorldServer world, AnvilChunkLoader loader, Chunk chunk, NBTTagCompound tag) throws IOException { try (DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(world.getChunkSaveLocation(), chunk.xPosition, chunk.zPosition)) { CompressedStreamTools.write(tag, dataoutputstream); } } }