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