001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.generator;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.configuration.Settings;
023import com.plotsquared.core.events.PlotFlagAddEvent;
024import com.plotsquared.core.events.Result;
025import com.plotsquared.core.listener.WEExtent;
026import com.plotsquared.core.location.Location;
027import com.plotsquared.core.plot.Plot;
028import com.plotsquared.core.plot.PlotArea;
029import com.plotsquared.core.plot.PlotAreaType;
030import com.plotsquared.core.plot.PlotId;
031import com.plotsquared.core.plot.PlotManager;
032import com.plotsquared.core.plot.expiration.PlotAnalysis;
033import com.plotsquared.core.plot.flag.GlobalFlagContainer;
034import com.plotsquared.core.plot.flag.PlotFlag;
035import com.plotsquared.core.plot.flag.implementations.AnalysisFlag;
036import com.plotsquared.core.plot.world.PlotAreaManager;
037import com.plotsquared.core.queue.BlockArrayCacheScopedQueueCoordinator;
038import com.plotsquared.core.queue.GlobalBlockQueue;
039import com.plotsquared.core.queue.QueueCoordinator;
040import com.plotsquared.core.util.ChunkManager;
041import com.plotsquared.core.util.EventDispatcher;
042import com.plotsquared.core.util.MathMan;
043import com.plotsquared.core.util.RegionManager;
044import com.plotsquared.core.util.RegionUtil;
045import com.plotsquared.core.util.SchematicHandler;
046import com.plotsquared.core.util.WorldUtil;
047import com.plotsquared.core.util.task.RunnableVal;
048import com.plotsquared.core.util.task.TaskManager;
049import com.plotsquared.core.util.task.TaskTime;
050import com.sk89q.worldedit.math.BlockVector2;
051import com.sk89q.worldedit.math.BlockVector3;
052import com.sk89q.worldedit.regions.CuboidRegion;
053import com.sk89q.worldedit.world.biome.BiomeType;
054import com.sk89q.worldedit.world.block.BaseBlock;
055import com.sk89q.worldedit.world.block.BlockState;
056import com.sk89q.worldedit.world.block.BlockType;
057import com.sk89q.worldedit.world.block.BlockTypes;
058import org.apache.logging.log4j.LogManager;
059import org.apache.logging.log4j.Logger;
060import org.checkerframework.checker.nullness.qual.NonNull;
061import org.checkerframework.checker.nullness.qual.Nullable;
062
063import java.io.File;
064import java.util.ArrayDeque;
065import java.util.ArrayList;
066import java.util.Arrays;
067import java.util.Collections;
068import java.util.HashSet;
069import java.util.Iterator;
070import java.util.LinkedHashSet;
071import java.util.List;
072import java.util.Set;
073import java.util.concurrent.atomic.AtomicBoolean;
074import java.util.concurrent.atomic.AtomicInteger;
075
076public class HybridUtils {
077
078    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + HybridUtils.class.getSimpleName());
079    private static final BlockState AIR = BlockTypes.AIR.getDefaultState();
080
081    public static HybridUtils manager;
082    public static Set<BlockVector2> regions;
083    public static int height;
084    // Use ordered for reasonable chunk loading order to reduce paper unloading neighbour chunks and then us attempting to load
085    // them again, causing errors
086    public static Set<BlockVector2> chunks = new LinkedHashSet<>();
087    public static PlotArea area;
088    public static boolean UPDATE = false;
089
090    private final PlotAreaManager plotAreaManager;
091    private final ChunkManager chunkManager;
092    private final GlobalBlockQueue blockQueue;
093    private final WorldUtil worldUtil;
094    private final SchematicHandler schematicHandler;
095    private final EventDispatcher eventDispatcher;
096
097    @Inject
098    public HybridUtils(
099            final @NonNull PlotAreaManager plotAreaManager,
100            final @NonNull ChunkManager chunkManager,
101            final @NonNull GlobalBlockQueue blockQueue,
102            final @NonNull WorldUtil worldUtil,
103            final @NonNull SchematicHandler schematicHandler,
104            final @NonNull EventDispatcher eventDispatcher
105    ) {
106        this.plotAreaManager = plotAreaManager;
107        this.chunkManager = chunkManager;
108        this.blockQueue = blockQueue;
109        this.worldUtil = worldUtil;
110        this.schematicHandler = schematicHandler;
111        this.eventDispatcher = eventDispatcher;
112    }
113
114    public void regeneratePlotWalls(final PlotArea area) {
115        PlotManager plotManager = area.getPlotManager();
116        plotManager.regenerateAllPlotWalls(null);
117    }
118
119    public void analyzeRegion(final String world, final CuboidRegion region, final RunnableVal<PlotAnalysis> whenDone) {
120        // int diff, int variety, int vertices, int rotation, int height_sd
121        /*
122         * diff: compare to base by looping through all blocks
123         * variety: add to HashSet for each BlockState
124         * height_sd: loop over all blocks and get top block
125         *
126         * vertices: store air map and compare with neighbours
127         * for each block check the adjacent
128         *  - Store all blocks then go through in second loop
129         *  - recheck each block
130         *
131         */
132        TaskManager.runTaskAsync(() -> {
133            final PlotArea area = this.plotAreaManager.getPlotArea(world, null);
134            if (!(area instanceof HybridPlotWorld hpw)) {
135                return;
136            }
137
138            final BlockVector3 bot = region.getMinimumPoint();
139            final BlockVector3 top = region.getMaximumPoint();
140
141            final int bx = bot.getX();
142            final int bz = bot.getZ();
143            final int tx = top.getX();
144            final int tz = top.getZ();
145            final int cbx = bx >> 4;
146            final int cbz = bz >> 4;
147            final int ctx = tx >> 4;
148            final int ctz = tz >> 4;
149            final int width = tx - bx + 1;
150            final int length = tz - bz + 1;
151            final int height = area.getMaxGenHeight() - area.getMinGenHeight() + 1;
152            final int minHeight = area.getMinGenHeight();
153
154            final BlockState[][][] newBlocks = new BlockState[height][width][length];
155
156            BlockArrayCacheScopedQueueCoordinator oldBlockQueue = new BlockArrayCacheScopedQueueCoordinator(
157                    Location.at("", region.getMinimumPoint().withY(hpw.getMinGenHeight())),
158                    Location.at("", region.getMaximumPoint().withY(hpw.getMaxGenHeight()))
159            );
160
161            region.getChunks().forEach(chunkPos -> {
162                int relChunkX = chunkPos.getX() - cbx;
163                int relChunkZ = chunkPos.getZ() - cbz;
164                oldBlockQueue.setOffsetX(relChunkX << 4);
165                oldBlockQueue.setOffsetZ(relChunkZ << 4);
166                hpw.getGenerator().generateChunk(oldBlockQueue, hpw);
167            });
168
169            final BlockState[][][] oldBlocks = oldBlockQueue.getBlockStates();
170
171            QueueCoordinator queue = area.getQueue();
172            queue.addReadChunks(region.getChunks());
173            queue.setChunkConsumer(chunkPos -> {
174                int X = chunkPos.getX();
175                int Z = chunkPos.getZ();
176                int minX;
177                if (X == cbx) {
178                    minX = bx & 15;
179                } else {
180                    minX = 0;
181                }
182                int minZ;
183                if (Z == cbz) {
184                    minZ = bz & 15;
185                } else {
186                    minZ = 0;
187                }
188                int maxX;
189                if (X == ctx) {
190                    maxX = tx & 15;
191                } else {
192                    maxX = 15;
193                }
194                int maxZ;
195                if (Z == ctz) {
196                    maxZ = tz & 15;
197                } else {
198                    maxZ = 15;
199                }
200
201                int chunkBlockX = X << 4;
202                int chunkBlockZ = Z << 4;
203
204                int xb = chunkBlockX - bx;
205                int zb = chunkBlockZ - bz;
206                for (int x = minX; x <= maxX; x++) {
207                    int xx = chunkBlockX + x;
208                    for (int z = minZ; z <= maxZ; z++) {
209                        int zz = chunkBlockZ + z;
210                        for (int yIndex = 0; yIndex < height; yIndex++) {
211                            int y = yIndex + minHeight;
212                            BlockState block = queue.getBlock(xx, y, zz);
213                            if (block == null) {
214                                block = AIR;
215                            }
216                            int xr = xb + x;
217                            int zr = zb + z;
218                            newBlocks[yIndex][xr][zr] = block;
219                        }
220                    }
221                }
222            });
223
224            final Runnable run = () -> {
225                int size = width * length;
226                int[] changes = new int[size];
227                int[] faces = new int[size];
228                int[] data = new int[size];
229                int[] air = new int[size];
230                int[] variety = new int[size];
231                int i = 0;
232                for (int x = 0; x < width; x++) {
233                    for (int z = 0; z < length; z++) {
234                        Set<BlockType> types = new HashSet<>();
235                        for (int yIndex = 0; yIndex < height; yIndex++) {
236                            BlockState old = oldBlocks[yIndex][x][z]; // Nullable
237                            BlockState now = newBlocks[yIndex][x][z]; // Not null
238                            if (now == null) {
239                                throw new NullPointerException(String.format(
240                                        "\"now\" block null attempting to perform plot analysis. Indexes: x=%d of %d, yIndex=%d" +
241                                                " of %d, z=%d of %d",
242                                        x,
243                                        width,
244                                        yIndex,
245                                        height,
246                                        z,
247                                        length
248                                ));
249                            }
250                            if (!now.equals(old) && !(old == null && now.getBlockType().equals(BlockTypes.AIR))) {
251                                changes[i]++;
252                            }
253                            if (now.getBlockType().getMaterial().isAir()) {
254                                air[i]++;
255                            } else {
256                                // check vertices
257                                // modifications_adjacent
258                                if (x > 0 && z > 0 && yIndex > 0 && x < width - 1 && z < length - 1 && yIndex < (height - 1)) {
259                                    if (newBlocks[yIndex - 1][x][z].getBlockType().getMaterial().isAir()) {
260                                        faces[i]++;
261                                    }
262                                    if (newBlocks[yIndex][x - 1][z].getBlockType().getMaterial().isAir()) {
263                                        faces[i]++;
264                                    }
265                                    if (newBlocks[yIndex][x][z - 1].getBlockType().getMaterial().isAir()) {
266                                        faces[i]++;
267                                    }
268                                    if (newBlocks[yIndex + 1][x][z].getBlockType().getMaterial().isAir()) {
269                                        faces[i]++;
270                                    }
271                                    if (newBlocks[yIndex][x + 1][z].getBlockType().getMaterial().isAir()) {
272                                        faces[i]++;
273                                    }
274                                    if (newBlocks[yIndex][x][z + 1].getBlockType().getMaterial().isAir()) {
275                                        faces[i]++;
276                                    }
277                                }
278
279                                if (!now.equals(now.getBlockType().getDefaultState())) {
280                                    data[i]++;
281                                }
282                                types.add(now.getBlockType());
283                            }
284                        }
285                        variety[i] = types.size();
286                        i++;
287                    }
288                }
289                // analyze plot
290                // put in analysis obj
291
292                // run whenDone
293                PlotAnalysis analysis = new PlotAnalysis();
294                analysis.changes = (int) (MathMan.getMean(changes) * 100);
295                analysis.faces = (int) (MathMan.getMean(faces) * 100);
296                analysis.data = (int) (MathMan.getMean(data) * 100);
297                analysis.air = (int) (MathMan.getMean(air) * 100);
298                analysis.variety = (int) (MathMan.getMean(variety) * 100);
299
300                analysis.changes_sd = (int) (MathMan.getSD(changes, analysis.changes) * 100);
301                analysis.faces_sd = (int) (MathMan.getSD(faces, analysis.faces) * 100);
302                analysis.data_sd = (int) (MathMan.getSD(data, analysis.data) * 100);
303                analysis.air_sd = (int) (MathMan.getSD(air, analysis.air) * 100);
304                analysis.variety_sd = (int) (MathMan.getSD(variety, analysis.variety) * 100);
305                whenDone.value = analysis;
306                whenDone.run();
307            };
308            queue.setCompleteTask(run);
309            queue.enqueue();
310        });
311    }
312
313    public void analyzePlot(final Plot origin, final RunnableVal<PlotAnalysis> whenDone) {
314        final ArrayDeque<CuboidRegion> zones = new ArrayDeque<>(origin.getRegions());
315        final ArrayList<PlotAnalysis> analysis = new ArrayList<>();
316        Runnable run = new Runnable() {
317            @Override
318            public void run() {
319                if (zones.isEmpty()) {
320                    if (!analysis.isEmpty()) {
321                        whenDone.value = new PlotAnalysis();
322                        for (PlotAnalysis data : analysis) {
323                            whenDone.value.air += data.air;
324                            whenDone.value.air_sd += data.air_sd;
325                            whenDone.value.changes += data.changes;
326                            whenDone.value.changes_sd += data.changes_sd;
327                            whenDone.value.data += data.data;
328                            whenDone.value.data_sd += data.data_sd;
329                            whenDone.value.faces += data.faces;
330                            whenDone.value.faces_sd += data.faces_sd;
331                            whenDone.value.variety += data.variety;
332                            whenDone.value.variety_sd += data.variety_sd;
333                        }
334                        whenDone.value.air /= analysis.size();
335                        whenDone.value.air_sd /= analysis.size();
336                        whenDone.value.changes /= analysis.size();
337                        whenDone.value.changes_sd /= analysis.size();
338                        whenDone.value.data /= analysis.size();
339                        whenDone.value.data_sd /= analysis.size();
340                        whenDone.value.faces /= analysis.size();
341                        whenDone.value.faces_sd /= analysis.size();
342                        whenDone.value.variety /= analysis.size();
343                        whenDone.value.variety_sd /= analysis.size();
344                    } else {
345                        whenDone.value = analysis.get(0);
346                    }
347                    List<Integer> result = new ArrayList<>();
348                    result.add(whenDone.value.changes);
349                    result.add(whenDone.value.faces);
350                    result.add(whenDone.value.data);
351                    result.add(whenDone.value.air);
352                    result.add(whenDone.value.variety);
353
354                    result.add(whenDone.value.changes_sd);
355                    result.add(whenDone.value.faces_sd);
356                    result.add(whenDone.value.data_sd);
357                    result.add(whenDone.value.air_sd);
358                    result.add(whenDone.value.variety_sd);
359                    PlotFlag<?, ?> plotFlag = GlobalFlagContainer.getInstance().getFlag(AnalysisFlag.class).createFlagInstance(
360                            result);
361                    PlotFlagAddEvent event = eventDispatcher.callFlagAdd(plotFlag, origin);
362                    if (event.getEventResult() == Result.DENY) {
363                        return;
364                    }
365                    origin.setFlag(event.getFlag());
366                    TaskManager.runTask(whenDone);
367                    return;
368                }
369                CuboidRegion region = zones.poll();
370                final Runnable task = this;
371                analyzeRegion(origin.getWorldName(), region, new RunnableVal<>() {
372                    @Override
373                    public void run(PlotAnalysis value) {
374                        analysis.add(value);
375                        TaskManager.runTaskLater(task, TaskTime.ticks(1L));
376                    }
377                });
378            }
379        };
380        run.run();
381    }
382
383    /**
384     * @deprecated Unused internally and poor implementation. Scheduled for removal in next major release.
385     */
386    @Deprecated(forRemoval = true, since = "6.11.1")
387    public int checkModified(QueueCoordinator queue, int x1, int x2, int y1, int y2, int z1, int z2, BlockState[] blocks) {
388        int count = 0;
389        for (int y = y1; y <= y2; y++) {
390            for (int x = x1; x <= x2; x++) {
391                for (int z = z1; z <= z2; z++) {
392                    BlockState block = queue.getBlock(x, y, z);
393                    boolean same = Arrays.stream(blocks).anyMatch(p -> this.worldUtil.isBlockSame(block, p));
394                    if (!same) {
395                        count++;
396                    }
397                }
398            }
399        }
400        return count;
401    }
402
403    public final ArrayList<BlockVector2> getChunks(BlockVector2 region) {
404        ArrayList<BlockVector2> chunks = new ArrayList<>();
405        int sx = region.getX() << 5;
406        int sz = region.getZ() << 5;
407        for (int x = sx; x < sx + 32; x++) {
408            for (int z = sz; z < sz + 32; z++) {
409                chunks.add(BlockVector2.at(x, z));
410            }
411        }
412        return chunks;
413    }
414
415    public boolean scheduleRoadUpdate(PlotArea area, int extend) {
416        if (HybridUtils.UPDATE) {
417            return false;
418        }
419        HybridUtils.UPDATE = true;
420        Set<BlockVector2> regions = this.worldUtil.getChunkChunks(area.getWorldName());
421        return scheduleRoadUpdate(area, regions, extend, new LinkedHashSet<>());
422    }
423
424    public boolean scheduleSingleRegionRoadUpdate(Plot plot, int extend) {
425        if (HybridUtils.UPDATE) {
426            return false;
427        }
428        HybridUtils.UPDATE = true;
429        Set<BlockVector2> regions = new HashSet<>();
430        regions.add(RegionManager.getRegion(plot.getCenterSynchronous()));
431        return scheduleRoadUpdate(plot.getArea(), regions, extend, new LinkedHashSet<>());
432    }
433
434    public boolean scheduleRoadUpdate(
435            final PlotArea area,
436            Set<BlockVector2> regions,
437            final int extend,
438            Set<BlockVector2> chunks
439    ) {
440        HybridUtils.regions = regions;
441        HybridUtils.area = area;
442        HybridUtils.height = extend;
443        HybridUtils.chunks = chunks;
444        final int initial = 1024 * regions.size() + chunks.size();
445        final AtomicInteger count = new AtomicInteger(0);
446        TaskManager.runTask(new Runnable() {
447            @Override
448            public void run() {
449                if (!UPDATE) {
450                    Iterator<BlockVector2> iter = chunks.iterator();
451                    QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
452                    while (iter.hasNext()) {
453                        BlockVector2 chunk = iter.next();
454                        iter.remove();
455                        boolean regenedRoad = regenerateRoad(area, chunk, extend, queue);
456                        if (!regenedRoad) {
457                            LOGGER.info("Failed to regenerate roads in chunk {}", chunk);
458                        }
459                    }
460                    queue.enqueue();
461                    LOGGER.info("Cancelled road task");
462                    return;
463                }
464                count.incrementAndGet();
465                if (count.intValue() % 10 == 0) {
466                    LOGGER.info("Progress: {}%", 100 * (initial - (chunks.size() + 1024 * regions.size())) / initial);
467                }
468                if (HybridUtils.regions.isEmpty() && chunks.isEmpty()) {
469                    regeneratePlotWalls(area);
470
471                    HybridUtils.UPDATE = false;
472                    LOGGER.info("Finished road conversion");
473                    // CANCEL TASK
474                } else {
475                    final Runnable task = this;
476                    TaskManager.runTaskAsync(() -> {
477                        try {
478                            if (chunks.size() < 64) {
479                                if (!HybridUtils.regions.isEmpty()) {
480                                    Iterator<BlockVector2> iterator = HybridUtils.regions.iterator();
481                                    BlockVector2 loc = iterator.next();
482                                    iterator.remove();
483                                    LOGGER.info("Updating .mcr: {}, {} (approx 1024 chunks)", loc.getX(), loc.getZ());
484                                    LOGGER.info("- Remaining: {}", HybridUtils.regions.size());
485                                    chunks.addAll(getChunks(loc));
486                                    System.gc();
487                                }
488                            }
489                            if (!chunks.isEmpty()) {
490                                TaskManager.getPlatformImplementation().sync(() -> {
491                                    Iterator<BlockVector2> iterator = chunks.iterator();
492                                    if (chunks.size() >= 32) {
493                                        QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
494                                        for (int i = 0; i < 32; i++) {
495                                            final BlockVector2 chunk = iterator.next();
496                                            iterator.remove();
497                                            boolean regenedRoads = regenerateRoad(area, chunk, extend, queue);
498                                            if (!regenedRoads) {
499                                                LOGGER.info("Failed to regenerate the road in chunk {}", chunk);
500                                            }
501                                        }
502                                        queue.setCompleteTask(task);
503                                        queue.enqueue();
504                                        return null;
505                                    }
506                                    QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
507                                    while (!chunks.isEmpty()) {
508                                        final BlockVector2 chunk = iterator.next();
509                                        iterator.remove();
510                                        boolean regenedRoads = regenerateRoad(area, chunk, extend, queue);
511                                        if (!regenedRoads) {
512                                            LOGGER.info("Failed to regenerate road in chunk {}", chunk);
513                                        }
514                                    }
515                                    queue.setCompleteTask(task);
516                                    queue.enqueue();
517                                    return null;
518                                });
519                                return;
520                            }
521                        } catch (Exception e) {
522                            e.printStackTrace();
523                            Iterator<BlockVector2> iterator = HybridUtils.regions.iterator();
524                            BlockVector2 loc = iterator.next();
525                            iterator.remove();
526                            LOGGER.error(
527                                    "Error! Could not update '{}/region/r.{}.{}.mca' (Corrupt chunk?)",
528                                    area.getWorldHash(),
529                                    loc.getX(),
530                                    loc.getZ()
531                            );
532                        }
533                        TaskManager.runTaskLater(task, TaskTime.seconds(1L));
534                    });
535                }
536            }
537        });
538        return true;
539    }
540
541    public boolean setupRoadSchematic(Plot plot) {
542        final String world = plot.getWorldName();
543        final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(world));
544        Location bot = plot.getBottomAbs().subtract(1, 0, 1);
545        Location top = plot.getTopAbs();
546        final HybridPlotWorld plotworld = (HybridPlotWorld) plot.getArea();
547        // Do not use plotworld#schematicStartHeight() here as we want to restore the pre 6.1.4 way of doing it if
548        //  USE_WALL_IN_ROAD_SCHEM_HEIGHT is false
549        int schemY = Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT ?
550                Math.min(plotworld.PLOT_HEIGHT, Math.min(plotworld.WALL_HEIGHT, plotworld.ROAD_HEIGHT)) : plotworld.ROAD_HEIGHT;
551        int sx = bot.getX() - plotworld.ROAD_WIDTH + 1;
552        int sz = bot.getZ() + 1;
553        int sy = Settings.Schematics.PASTE_ROAD_ON_TOP ? schemY : plot.getArea().getMinBuildHeight();
554        int ex = bot.getX();
555        int ez = top.getZ();
556        int ey = get_ey(plotworld, queue, sx, ex, sz, ez, sy);
557        int bz = sz - plotworld.ROAD_WIDTH;
558        int tz = sz - 1;
559        int ty = get_ey(plotworld, queue, sx, ex, bz, tz, sy);
560
561        final Set<CuboidRegion> sideRoad = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ey, sz, ez));
562        final Set<CuboidRegion> intersection = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ty, bz, tz));
563
564        final String dir = Settings.Paths.SCHEMATICS + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + plot
565                .getArea()
566                .toString() + File.separator;
567
568        this.schematicHandler.getCompoundTag(world, sideRoad)
569                .whenComplete((compoundTag, throwable) -> {
570                    schematicHandler.save(compoundTag, dir + "sideroad.schem");
571                    schematicHandler.getCompoundTag(world, intersection)
572                            .whenComplete((c, t) -> {
573                                schematicHandler.save(c, dir + "intersection.schem");
574                                plotworld.ROAD_SCHEMATIC_ENABLED = true;
575                                try {
576                                    plotworld.setupSchematics();
577                                } catch (SchematicHandler.UnsupportedFormatException e) {
578                                    e.printStackTrace();
579                                }
580                            });
581                });
582        return true;
583    }
584
585    private int get_ey(final HybridPlotWorld hpw, QueueCoordinator queue, int sx, int ex, int sz, int ez, int sy) {
586        int ey = sy;
587        for (int x = sx; x <= ex; x++) {
588            for (int z = sz; z <= ez; z++) {
589                for (int y = sy; y <= hpw.getMaxGenHeight(); y++) {
590                    if (y > ey) {
591                        BlockState block = queue.getBlock(x, y, z);
592                        if (!block.getBlockType().getMaterial().isAir()) {
593                            ey = y;
594                        }
595                    }
596                }
597            }
598        }
599        return ey;
600    }
601
602    /**
603     * Regenerate the road in a chunk in a plot area.
604     *
605     * @param area   Plot area to regenerate road for
606     * @param chunk  Chunk location to regenerate
607     * @param extend How far to extend setting air above the road
608     * @return if successful
609     * @deprecated use {@link HybridUtils#regenerateRoad(PlotArea, BlockVector2, int, QueueCoordinator)}
610     */
611    @Deprecated(forRemoval = true, since = "6.6.0")
612    public boolean regenerateRoad(final PlotArea area, final BlockVector2 chunk, int extend) {
613        return regenerateRoad(area, chunk, extend, null);
614    }
615
616    /**
617     * Regenerate the road in a chunk in a plot area.
618     *
619     * @param area             Plot area to regenerate road for
620     * @param chunk            Chunk location to regenerate
621     * @param extend           How far to extend setting air above the road
622     * @param queueCoordinator {@link QueueCoordinator} to use to set the blocks. Null if one should be created and enqueued
623     * @return if successful
624     * @since 6.6.0
625     */
626    public boolean regenerateRoad(
627            final PlotArea area,
628            final BlockVector2 chunk,
629            int extend,
630            @Nullable QueueCoordinator queueCoordinator
631    ) {
632        int x = chunk.getX() << 4;
633        int z = chunk.getZ() << 4;
634        int ex = x + 15;
635        int ez = z + 15;
636        HybridPlotWorld plotWorld = (HybridPlotWorld) area;
637        if (!plotWorld.ROAD_SCHEMATIC_ENABLED) {
638            return false;
639        }
640        AtomicBoolean toCheck = new AtomicBoolean(false);
641        if (plotWorld.getType() == PlotAreaType.PARTIAL) {
642            boolean chunk1 = area.contains(x, z);
643            boolean chunk2 = area.contains(ex, ez);
644            if (!chunk1 && !chunk2) {
645                return false;
646            } else {
647                toCheck.set(chunk1 ^ chunk2);
648            }
649        }
650        PlotManager manager = area.getPlotManager();
651        PlotId id1 = manager.getPlotId(x, 0, z);
652        PlotId id2 = manager.getPlotId(ex, 0, ez);
653        x = x - plotWorld.ROAD_OFFSET_X;
654        z -= plotWorld.ROAD_OFFSET_Z;
655        final int finalX = x;
656        final int finalZ = z;
657        final boolean enqueue;
658        final QueueCoordinator queue;
659        if (queueCoordinator == null) {
660            queue = this.blockQueue.getNewQueue(worldUtil.getWeWorld(plotWorld.getWorldName()));
661            enqueue = true;
662        } else {
663            queue = queueCoordinator;
664            enqueue = false;
665        }
666        if (id1 == null || id2 == null || id1 != id2) {
667            if (id1 != null) {
668                Plot p1 = area.getPlotAbs(id1);
669                if (p1 != null && p1.hasOwner() && p1.isMerged()) {
670                    toCheck.set(true);
671                }
672            }
673            if (id2 != null && !toCheck.get()) {
674                Plot p2 = area.getPlotAbs(id2);
675                if (p2 != null && p2.hasOwner() && p2.isMerged()) {
676                    toCheck.set(true);
677                }
678            }
679            short size = plotWorld.SIZE;
680            for (int X = 0; X < 16; X++) {
681                short absX = (short) ((finalX + X) % size);
682                for (int Z = 0; Z < 16; Z++) {
683                    short absZ = (short) ((finalZ + Z) % size);
684                    if (absX < 0) {
685                        absX += size;
686                    }
687                    if (absZ < 0) {
688                        absZ += size;
689                    }
690                    boolean condition;
691                    if (toCheck.get()) {
692                        condition = manager.getPlotId(
693                                finalX + X + plotWorld.ROAD_OFFSET_X,
694                                1,
695                                finalZ + Z + plotWorld.ROAD_OFFSET_Z
696                        ) == null;
697                    } else {
698                        boolean gx = absX > plotWorld.PATH_WIDTH_LOWER;
699                        boolean gz = absZ > plotWorld.PATH_WIDTH_LOWER;
700                        boolean lx = absX < plotWorld.PATH_WIDTH_UPPER;
701                        boolean lz = absZ < plotWorld.PATH_WIDTH_UPPER;
702                        condition = !gx || !gz || !lx || !lz;
703                    }
704                    if (condition) {
705                        BaseBlock[] blocks = plotWorld.G_SCH.get(MathMan.pair(absX, absZ));
706                        int minY = Settings.Schematics.PASTE_ROAD_ON_TOP ? plotWorld.SCHEM_Y : area.getMinGenHeight() + 1;
707                        int maxDy = Math.max(extend, blocks.length);
708                        for (int dy = 0; dy < maxDy; dy++) {
709                            if (dy > blocks.length - 1) {
710                                queue.setBlock(
711                                        finalX + X + plotWorld.ROAD_OFFSET_X,
712                                        minY + dy,
713                                        finalZ + Z + plotWorld.ROAD_OFFSET_Z,
714                                        WEExtent.AIRBASE
715                                );
716                            } else {
717                                BaseBlock block = blocks[dy];
718                                if (block != null) {
719                                    queue.setBlock(
720                                            finalX + X + plotWorld.ROAD_OFFSET_X,
721                                            minY + dy,
722                                            finalZ + Z + plotWorld.ROAD_OFFSET_Z,
723                                            block
724                                    );
725                                } else {
726                                    queue.setBlock(
727                                            finalX + X + plotWorld.ROAD_OFFSET_X,
728                                            minY + dy,
729                                            finalZ + Z + plotWorld.ROAD_OFFSET_Z,
730                                            WEExtent.AIRBASE
731                                    );
732                                }
733                            }
734                        }
735                        BiomeType biome = plotWorld.G_SCH_B.get(MathMan.pair(absX, absZ));
736                        if (biome != null) {
737                            queue.setBiome(finalX + X + plotWorld.ROAD_OFFSET_X, finalZ + Z + plotWorld.ROAD_OFFSET_Z, biome);
738                        } else {
739                            queue.setBiome(
740                                    finalX + X + plotWorld.ROAD_OFFSET_X,
741                                    finalZ + Z + plotWorld.ROAD_OFFSET_Z,
742                                    plotWorld.getPlotBiome()
743                            );
744                        }
745                    }
746                }
747            }
748            if (enqueue) {
749                queue.enqueue();
750            }
751            return true;
752        }
753        return false;
754    }
755
756}