package com.intellectualcrafters.plot.generator; import com.intellectualcrafters.jnbt.CompoundTag; import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.config.C; import com.intellectualcrafters.plot.flag.FlagManager; import com.intellectualcrafters.plot.flag.Flags; import com.intellectualcrafters.plot.object.ChunkLoc; import com.intellectualcrafters.plot.object.Location; import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.PlotArea; import com.intellectualcrafters.plot.object.PlotBlock; import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.PlotManager; import com.intellectualcrafters.plot.object.RegionWrapper; import com.intellectualcrafters.plot.object.RunnableVal; import com.intellectualcrafters.plot.util.ChunkManager; import com.intellectualcrafters.plot.util.MathMan; import com.intellectualcrafters.plot.util.SchematicHandler; import com.intellectualcrafters.plot.util.TaskManager; import com.intellectualcrafters.plot.util.block.GlobalBlockQueue; import com.intellectualcrafters.plot.util.block.LocalBlockQueue; import com.intellectualcrafters.plot.util.expiry.PlotAnalysis; import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public abstract class HybridUtils { public static HybridUtils manager; public static Set<ChunkLoc> regions; public static Set<ChunkLoc> chunks = new HashSet<>(); public static PlotArea area; public static boolean UPDATE = false; public abstract void analyzeRegion(String world, RegionWrapper region, RunnableVal<PlotAnalysis> whenDone); public void analyzePlot(final Plot origin, final RunnableVal<PlotAnalysis> whenDone) { final ArrayDeque<RegionWrapper> zones = new ArrayDeque<>(origin.getRegions()); final ArrayList<PlotAnalysis> analysis = new ArrayList<>(); Runnable run = new Runnable() { @Override public void run() { if (zones.isEmpty()) { if (!analysis.isEmpty()) { whenDone.value = new PlotAnalysis(); for (PlotAnalysis data : analysis) { whenDone.value.air += data.air; whenDone.value.air_sd += data.air_sd; whenDone.value.changes += data.changes; whenDone.value.changes_sd += data.changes_sd; whenDone.value.data += data.data; whenDone.value.data_sd += data.data_sd; whenDone.value.faces += data.faces; whenDone.value.faces_sd += data.faces_sd; whenDone.value.variety += data.variety; whenDone.value.variety_sd += data.variety_sd; } whenDone.value.air /= analysis.size(); whenDone.value.air_sd /= analysis.size(); whenDone.value.changes /= analysis.size(); whenDone.value.changes_sd /= analysis.size(); whenDone.value.data /= analysis.size(); whenDone.value.data_sd /= analysis.size(); whenDone.value.faces /= analysis.size(); whenDone.value.faces_sd /= analysis.size(); whenDone.value.variety /= analysis.size(); whenDone.value.variety_sd /= analysis.size(); } else { whenDone.value = analysis.get(0); } List<Integer> result = new ArrayList<>(); result.add(whenDone.value.changes); result.add(whenDone.value.faces); result.add(whenDone.value.data); result.add(whenDone.value.air); result.add(whenDone.value.variety); result.add(whenDone.value.changes_sd); result.add(whenDone.value.faces_sd); result.add(whenDone.value.data_sd); result.add(whenDone.value.air_sd); result.add(whenDone.value.variety_sd); FlagManager.addPlotFlag(origin, Flags.ANALYSIS, result); TaskManager.runTask(whenDone); return; } RegionWrapper region = zones.poll(); final Runnable task = this; analyzeRegion(origin.getWorldName(), region, new RunnableVal<PlotAnalysis>() { @Override public void run(PlotAnalysis value) { analysis.add(value); TaskManager.runTaskLater(task, 1); } }); } }; run.run(); } public int checkModified(LocalBlockQueue queue, int x1, int x2, int y1, int y2, int z1, int z2, PlotBlock[] blocks) { int count = 0; for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { for (int z = z1; z <= z2; z++) { PlotBlock block = queue.getBlock(x, y, z); boolean same = false; for (PlotBlock p : blocks) { if (block.id == p.id) { same = true; break; } } if (!same) { count++; } } } } return count; } public final ArrayList<ChunkLoc> getChunks(ChunkLoc region) { ArrayList<ChunkLoc> chunks = new ArrayList<>(); int sx = region.x << 5; int sz = region.z << 5; for (int x = sx; x < sx + 32; x++) { for (int z = sz; z < sz + 32; z++) { chunks.add(new ChunkLoc(x, z)); } } return chunks; } /** * Checks all connected plots. * @param plot * @param whenDone */ public void checkModified(final Plot plot, final RunnableVal<Integer> whenDone) { if (whenDone == null) { return; } PlotArea plotArea = plot.getArea(); if (!(plotArea instanceof ClassicPlotWorld)) { whenDone.value = -1; TaskManager.runTask(whenDone); return; } whenDone.value = 0; final ClassicPlotWorld cpw = (ClassicPlotWorld) plotArea; final ArrayDeque<RegionWrapper> zones = new ArrayDeque<>(plot.getRegions()); final LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(cpw.worldname, false); Runnable run = new Runnable() { @Override public void run() { if (zones.isEmpty()) { TaskManager.runTask(whenDone); return; } RegionWrapper region = zones.poll(); Location pos1 = new Location(plot.getWorldName(), region.minX, region.minY, region.minZ); Location pos2 = new Location(plot.getWorldName(), region.maxX, region.maxY, region.maxZ); ChunkManager.chunkTask(pos1, pos2, new RunnableVal<int[]>() { @Override public void run(int[] value) { ChunkLoc loc = new ChunkLoc(value[0], value[1]); ChunkManager.manager.loadChunk(plot.getWorldName(), loc, false); int bx = value[2]; int bz = value[3]; int ex = value[4]; int ez = value[5]; whenDone.value += checkModified(queue, bx, ex, 1, cpw.PLOT_HEIGHT - 1, bz, ez, cpw.MAIN_BLOCK); whenDone.value += checkModified(queue, bx, ex, cpw.PLOT_HEIGHT, cpw.PLOT_HEIGHT, bz, ez, cpw.TOP_BLOCK); whenDone.value += checkModified( queue, bx, ex, cpw.PLOT_HEIGHT + 1, 255, bz, ez, new PlotBlock[]{PlotBlock.get((short) 0, (byte) 0)}); } }, this, 5); } }; run.run(); } public boolean scheduleRoadUpdate(PlotArea area, int extend) { if (HybridUtils.UPDATE) { return false; } HybridUtils.UPDATE = true; Set<ChunkLoc> regions = ChunkManager.manager.getChunkChunks(area.worldname); return scheduleRoadUpdate(area, regions, extend); } public boolean scheduleRoadUpdate(final PlotArea area, Set<ChunkLoc> rgs, final int extend) { HybridUtils.regions = rgs; HybridUtils.area = area; chunks = new HashSet<>(); final AtomicInteger count = new AtomicInteger(0); final long baseTime = System.currentTimeMillis(); final AtomicInteger last = new AtomicInteger(); TaskManager.runTask(new Runnable() { @Override public void run() { if (!UPDATE) { last.set(0); Iterator<ChunkLoc> iter = chunks.iterator(); while (iter.hasNext()) { ChunkLoc chunk = iter.next(); iter.remove(); regenerateRoad(area, chunk, extend); ChunkManager.manager.unloadChunk(area.worldname, chunk, true, true); } PS.debug("&cCancelled road task"); return; } count.incrementAndGet(); if (count.intValue() % 20 == 0) { PS.debug("PROGRESS: " + 100 * (2048 - chunks.size()) / 2048 + "%"); } if (regions.isEmpty() && chunks.isEmpty()) { HybridUtils.UPDATE = false; PS.debug(C.PREFIX.s() + "Finished road conversion"); // CANCEL TASK } else { final Runnable task = this; TaskManager.runTaskAsync(new Runnable() { @Override public void run() { try { if (last.get() == 0) { last.set((int) (System.currentTimeMillis() - baseTime)); } if (chunks.size() < 1024) { if (!regions.isEmpty()) { Iterator<ChunkLoc> iterator = regions.iterator(); ChunkLoc loc = iterator.next(); iterator.remove(); PS.debug("&3Updating .mcr: " + loc.x + ", " + loc.z + " (aprrox 1024 chunks)"); PS.debug(" - Remaining: " + regions.size()); chunks.addAll(getChunks(loc)); System.gc(); } } if (!chunks.isEmpty()) { long diff = System.currentTimeMillis() + 1; if (System.currentTimeMillis() - baseTime - last.get() > 2000 && last.get() != 0) { last.set(0); PS.debug(C.PREFIX.s() + "Detected low TPS. Rescheduling in 30s"); Iterator<ChunkLoc> iterator = chunks.iterator(); final ChunkLoc chunk = iterator.next(); iterator.remove(); TaskManager.runTask(new Runnable() { @Override public void run() { regenerateRoad(area, chunk, extend); } }); // DELAY TASK TaskManager.runTaskLater(task, 600); return; } if (System.currentTimeMillis() - baseTime - last.get() < 1500 && last.get() != 0) { while (System.currentTimeMillis() < diff && !chunks.isEmpty()) { Iterator<ChunkLoc> iterator = chunks.iterator(); final ChunkLoc chunk = iterator.next(); iterator.remove(); TaskManager.runTask(new Runnable() { @Override public void run() { regenerateRoad(area, chunk, extend); } }); } } last.set((int) (System.currentTimeMillis() - baseTime)); } } catch (Exception e) { e.printStackTrace(); Iterator<ChunkLoc> iterator = regions.iterator(); ChunkLoc loc = iterator.next(); iterator.remove(); PS.debug("&c[ERROR]&7 Could not update '" + area.worldname + "/region/r." + loc.x + "." + loc.z + ".mca' (Corrupt chunk?)"); int sx = loc.x << 5; int sz = loc.z << 5; for (int x = sx; x < sx + 32; x++) { for (int z = sz; z < sz + 32; z++) { ChunkManager.manager.unloadChunk(area.worldname, new ChunkLoc(x, z), true, true); } } PS.debug("&d - Potentially skipping 1024 chunks"); PS.debug("&d - TODO: recommend chunkster if corrupt"); } GlobalBlockQueue.IMP.addTask(new Runnable() { @Override public void run() { TaskManager.runTaskLater(task, 20); } }); } }); } } }); return true; } public boolean setupRoadSchematic(Plot plot) { final String world = plot.getWorldName(); final LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(world, false); Location bot = plot.getBottomAbs().subtract(1, 0, 1); Location top = plot.getTopAbs(); final HybridPlotWorld plotworld = (HybridPlotWorld) plot.getArea(); PlotManager plotManager = plotworld.getPlotManager(); int sx = bot.getX() - plotworld.ROAD_WIDTH + 1; int sz = bot.getZ() + 1; int sy = plotworld.ROAD_HEIGHT; int ex = bot.getX(); int ez = top.getZ(); int ey = get_ey(plotManager, queue, sx, ex, sz, ez, sy); int bz = sz - plotworld.ROAD_WIDTH; int tz = sz - 1; int ty = get_ey(plotManager, queue, sx, ex, bz, tz, sy); Set<RegionWrapper> sideRoad = new HashSet<>(Collections.singletonList(new RegionWrapper(sx, ex, sy, ey, sz, ez))); final Set<RegionWrapper> intersection = new HashSet<>(Collections.singletonList(new RegionWrapper(sx, ex, sy, ty, bz, tz))); final String dir = "schematics" + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + plot .getArea().toString() + File.separator; SchematicHandler.manager.getCompoundTag(world, sideRoad, new RunnableVal<CompoundTag>() { @Override public void run(CompoundTag value) { SchematicHandler.manager.save(value, dir + "sideroad.schematic"); SchematicHandler.manager.getCompoundTag(world, intersection, new RunnableVal<CompoundTag>() { @Override public void run(CompoundTag value) { SchematicHandler.manager.save(value, dir + "intersection.schematic"); plotworld.ROAD_SCHEMATIC_ENABLED = true; plotworld.setupSchematics(); } }); } }); return true; } public int get_ey(final PlotManager pm, LocalBlockQueue queue, int sx, int ex, int sz, int ez, int sy) { int ey = sy; for (int x = sx; x <= ex; x++) { for (int z = sz; z <= ez; z++) { for (int y = sy; y <= pm.getWorldHeight(); y++) { if (y > ey) { PlotBlock block = queue.getBlock(x, y, z); if (block.id != 0) { ey = y; } } } } } return ey; } public boolean regenerateRoad(final PlotArea area, final ChunkLoc chunk, int extend) { int x = chunk.x << 4; int z = chunk.z << 4; int ex = x + 15; int ez = z + 15; HybridPlotWorld plotWorld = (HybridPlotWorld) area; if (!plotWorld.ROAD_SCHEMATIC_ENABLED) { return false; } boolean toCheck = false; if (plotWorld.TYPE == 2) { boolean c1 = area.contains(x, z); boolean c2 = area.contains(ex, ez); if (!c1 && !c2) { return false; } else { toCheck = c1 ^ c2; } } PlotManager manager = area.getPlotManager(); PlotId id1 = manager.getPlotId(plotWorld, x, 0, z); PlotId id2 = manager.getPlotId(plotWorld, ex, 0, ez); x -= plotWorld.ROAD_OFFSET_X; z -= plotWorld.ROAD_OFFSET_Z; LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(plotWorld.worldname, false); if (id1 == null || id2 == null || id1 != id2) { boolean result = ChunkManager.manager.loadChunk(area.worldname, chunk, false); if (result) { if (id1 != null) { Plot p1 = area.getPlotAbs(id1); if (p1 != null && p1.hasOwner() && p1.isMerged()) { toCheck = true; } } if (id2 != null && !toCheck) { Plot p2 = area.getPlotAbs(id2); if (p2 != null && p2.hasOwner() && p2.isMerged()) { toCheck = true; } } int size = plotWorld.SIZE; for (int X = 0; X < 16; X++) { short absX = (short) ((x + X) % size); for (int Z = 0; Z < 16; Z++) { short absZ = (short) ((z + Z) % size); if (absX < 0) { absX += size; } if (absZ < 0) { absZ += size; } boolean condition; if (toCheck) { condition = manager.getPlotId(plotWorld, x + X + plotWorld.ROAD_OFFSET_X, 1, z + Z + plotWorld.ROAD_OFFSET_Z) == null; // condition = MainUtil.isPlotRoad(new Location(plotworld.worldname, x + X, 1, z + Z)); } else { boolean gx = absX > plotWorld.PATH_WIDTH_LOWER; boolean gz = absZ > plotWorld.PATH_WIDTH_LOWER; boolean lx = absX < plotWorld.PATH_WIDTH_UPPER; boolean lz = absZ < plotWorld.PATH_WIDTH_UPPER; condition = !gx || !gz || !lx || !lz; } if (condition) { char[] blocks = plotWorld.G_SCH.get(MathMan.pair(absX, absZ)); int minY = Math.min(plotWorld.PLOT_HEIGHT, plotWorld.ROAD_HEIGHT); if (blocks != null) { for (int y = 0; y < blocks.length; y++) { PlotBlock block = PlotBlock.get(blocks[y]); if (block != null) { queue.setBlock(x + X + plotWorld.ROAD_OFFSET_X, minY + y, z + Z + plotWorld.ROAD_OFFSET_Z, block); } } } } } } queue.enqueue(); return true; } } return false; } }