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.util;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.configuration.Settings;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.inject.factory.ProgressSubscriberFactory;
026import com.plotsquared.core.location.Location;
027import com.plotsquared.core.player.PlotPlayer;
028import com.plotsquared.core.plot.Plot;
029import com.plotsquared.core.plot.PlotArea;
030import com.plotsquared.core.plot.PlotManager;
031import com.plotsquared.core.queue.BasicQueueCoordinator;
032import com.plotsquared.core.queue.GlobalBlockQueue;
033import com.plotsquared.core.queue.QueueCoordinator;
034import com.plotsquared.core.util.task.TaskManager;
035import com.sk89q.worldedit.entity.Entity;
036import com.sk89q.worldedit.function.pattern.Pattern;
037import com.sk89q.worldedit.math.BlockVector2;
038import com.sk89q.worldedit.math.BlockVector3;
039import com.sk89q.worldedit.regions.CuboidRegion;
040import com.sk89q.worldedit.regions.Region;
041import com.sk89q.worldedit.world.World;
042import com.sk89q.worldedit.world.biome.BiomeType;
043import org.apache.logging.log4j.LogManager;
044import org.apache.logging.log4j.Logger;
045import org.checkerframework.checker.nullness.qual.NonNull;
046import org.checkerframework.checker.nullness.qual.Nullable;
047
048import java.io.IOException;
049import java.nio.file.Files;
050import java.nio.file.Path;
051import java.util.Collection;
052import java.util.Set;
053
054public abstract class RegionManager {
055
056    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + RegionManager.class.getSimpleName());
057
058    public static RegionManager manager = null;
059    protected final WorldUtil worldUtil;
060    private final GlobalBlockQueue blockQueue;
061    private final ProgressSubscriberFactory subscriberFactory;
062
063    @Inject
064    public RegionManager(
065            @NonNull WorldUtil worldUtil,
066            @NonNull GlobalBlockQueue blockQueue,
067            @NonNull ProgressSubscriberFactory subscriberFactory
068    ) {
069        this.worldUtil = worldUtil;
070        this.blockQueue = blockQueue;
071        this.subscriberFactory = subscriberFactory;
072    }
073
074    public static BlockVector2 getRegion(Location location) {
075        int x = location.getX() >> 9;
076        int z = location.getZ() >> 9;
077        return BlockVector2.at(x, z);
078    }
079
080    /**
081     * 0 = Entity
082     * 1 = Animal
083     * 2 = Monster
084     * 3 = Mob
085     * 4 = Boat
086     * 5 = Misc
087     *
088     * @param plot plot
089     * @return array of counts of entity types
090     */
091    public abstract int[] countEntities(Plot plot);
092
093    public void deleteRegionFiles(final String worldName, final Collection<BlockVector2> chunks, final Runnable whenDone) {
094        com.plotsquared.core.location.World<?> world = PlotSquared.platform().getPlatformWorld(worldName);
095        Path regionRoot = world.getWorldFolder().resolve("region");
096        TaskManager.runTaskAsync(() -> {
097            for (BlockVector2 loc : chunks) {
098                Path path = regionRoot.resolve(String.format("r.%s.%s.mca", loc.getX(), loc.getZ()));
099                LOGGER.info("- Deleting file: {} (max 1024 chunks)", path.getFileName());
100                try {
101                    Files.deleteIfExists(path);
102                } catch (IOException e) {
103                    LOGGER.error("Failed to delete region file", e);
104                }
105            }
106            TaskManager.runTask(whenDone);
107        });
108    }
109
110    /**
111     * Set a number of cuboids to a certain block between two y values.
112     *
113     * @param area    plot area
114     * @param regions cuboid regions
115     * @param blocks  pattern
116     * @param minY    y to set from
117     * @param maxY    y to set to
118     * @param actor   the actor associated with the cuboid set
119     * @param queue   Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues,
120     *                otherwise writes to the queue but does not enqueue.
121     * @return {@code true} if not enqueued, otherwise whether the created queue enqueued.
122     */
123    public boolean setCuboids(
124            final @NonNull PlotArea area,
125            final @NonNull Set<CuboidRegion> regions,
126            final @NonNull Pattern blocks,
127            int minY,
128            int maxY,
129            @Nullable PlotPlayer<?> actor,
130            @Nullable QueueCoordinator queue
131    ) {
132        boolean enqueue = false;
133        if (queue == null) {
134            queue = area.getQueue();
135            enqueue = true;
136            if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
137                queue.addProgressSubscriber(subscriberFactory.createWithActor(actor));
138            }
139        }
140        for (CuboidRegion region : regions) {
141            Location pos1 = Location.at(
142                    area.getWorldName(),
143                    region.getMinimumPoint().getX(),
144                    minY,
145                    region.getMinimumPoint().getZ()
146            );
147            Location pos2 = Location.at(
148                    area.getWorldName(),
149                    region.getMaximumPoint().getX(),
150                    maxY,
151                    region.getMaximumPoint().getZ()
152            );
153            queue.setCuboid(pos1, pos2, blocks);
154        }
155        return !enqueue || queue.enqueue();
156    }
157
158    /**
159     * Notify any plugins that may want to modify clear behaviour that a clear is occuring
160     *
161     * @param manager plot manager
162     * @return {@code true} if the notified will accept the clear task
163     */
164    public boolean notifyClear(PlotManager manager) {
165        return false;
166    }
167
168    /**
169     * Only called when {@link RegionManager#notifyClear(PlotManager)} returns true in specific PlotManagers
170     *
171     * @param plot     plot
172     * @param whenDone task to run when complete
173     * @param manager  plot manager
174     * @param actor    the player running the clear
175     * @return {@code true} if the clear worked. {@code false} if someone went wrong so PlotSquared can then handle the clear
176     */
177    public abstract boolean handleClear(
178            @NonNull Plot plot,
179            final @Nullable Runnable whenDone,
180            @NonNull PlotManager manager,
181            @Nullable PlotPlayer<?> actor
182    );
183
184    /**
185     * Copy a region to a new location (in the same world)
186     *
187     * @param pos1     position 1
188     * @param pos2     position 2
189     * @param newPos   position to move pos1 to
190     * @param actor    the actor associated with the region copy
191     * @param whenDone task to run when complete
192     * @return success or not
193     */
194    public boolean copyRegion(
195            final @NonNull Location pos1,
196            final @NonNull Location pos2,
197            final @NonNull Location newPos,
198            final @Nullable PlotPlayer<?> actor,
199            final @NonNull Runnable whenDone
200    ) {
201        final int relX = newPos.getX() - pos1.getX();
202        final int relZ = newPos.getZ() - pos1.getZ();
203        final com.sk89q.worldedit.world.World oldWorld = worldUtil.getWeWorld(pos1.getWorldName());
204        final com.sk89q.worldedit.world.World newWorld = worldUtil.getWeWorld(newPos.getWorldName());
205        final QueueCoordinator copyFrom = blockQueue.getNewQueue(oldWorld);
206        final BasicQueueCoordinator copyTo = (BasicQueueCoordinator) blockQueue.getNewQueue(newWorld);
207        setCopyFromToConsumer(pos1, pos2, relX, relZ, oldWorld, copyFrom, copyTo, false);
208        copyFrom.setCompleteTask(copyTo::enqueue);
209        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
210            copyFrom.addProgressSubscriber(subscriberFactory
211                    .createFull(
212                            actor,
213                            Settings.QUEUE.NOTIFY_INTERVAL,
214                            Settings.QUEUE.NOTIFY_WAIT,
215                            TranslatableCaption.of("swap.progress_region_copy")
216                    ));
217        }
218        copyFrom
219                .addReadChunks(new CuboidRegion(
220                        BlockVector3.at(pos1.getX(), 0, pos1.getZ()),
221                        BlockVector3.at(pos2.getX(), 0, pos2.getZ())
222                ).getChunks());
223        copyTo.setCompleteTask(whenDone);
224        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
225            copyTo.addProgressSubscriber(subscriberFactory
226                    .createFull(
227                            actor,
228                            Settings.QUEUE.NOTIFY_INTERVAL,
229                            Settings.QUEUE.NOTIFY_WAIT,
230                            TranslatableCaption.of("swap.progress_region_paste")
231                    ));
232        }
233        return copyFrom.enqueue();
234    }
235
236    /**
237     * Assumptions:<br>
238     * - pos1 and pos2 are in the same plot<br>
239     * It can be harmful to the world if parameters outside this scope are provided
240     *
241     * @param pos1          position 1
242     * @param pos2          position 2
243     * @param ignoreAugment if to bypass synchronisation ish thing
244     * @param whenDone      task to run when regeneration completed
245     * @return success or not
246     */
247    public abstract boolean regenerateRegion(Location pos1, Location pos2, boolean ignoreAugment, Runnable whenDone);
248
249    public abstract void clearAllEntities(Location pos1, Location pos2);
250
251    /**
252     * Swap two regions within the same world
253     *
254     * @param pos1     position 1
255     * @param pos2     position 2
256     * @param swapPos  position to swap with
257     * @param actor    the actor associated with the region copy
258     * @param whenDone task to run when complete
259     */
260    public void swap(
261            Location pos1,
262            Location pos2,
263            Location swapPos,
264            final @Nullable PlotPlayer<?> actor,
265            final Runnable whenDone
266    ) {
267        int relX = swapPos.getX() - pos1.getX();
268        int relZ = swapPos.getZ() - pos1.getZ();
269
270        World world1 = worldUtil.getWeWorld(pos1.getWorldName());
271        World world2 = worldUtil.getWeWorld(swapPos.getWorldName());
272
273        QueueCoordinator fromQueue1 = blockQueue.getNewQueue(world1);
274        QueueCoordinator fromQueue2 = blockQueue.getNewQueue(world2);
275        fromQueue1.setUnloadAfter(false);
276        fromQueue2.setUnloadAfter(false);
277        fromQueue1.addReadChunks(new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()).getChunks());
278        fromQueue2.addReadChunks(new CuboidRegion(
279                swapPos.getBlockVector3(),
280                BlockVector3.at(
281                        swapPos.getX() + pos2.getX() - pos1.getX(),
282                        pos1.getY(),
283                        swapPos.getZ() + pos2.getZ() - pos1.getZ()
284                )
285        ).getChunks());
286        QueueCoordinator toQueue1 = blockQueue.getNewQueue(world1);
287        QueueCoordinator toQueue2 = blockQueue.getNewQueue(world2);
288
289        setCopyFromToConsumer(pos1, pos2, relX, relZ, world1, fromQueue1, toQueue2, true);
290        setCopyFromToConsumer(pos1.add(relX, 0, relZ), pos2.add(relX, 0, relZ), -relX, -relZ, world1, fromQueue2, toQueue1,
291                true
292        );
293
294        toQueue2.setCompleteTask(whenDone);
295        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
296            toQueue2.addProgressSubscriber(subscriberFactory.createFull(
297                    actor,
298                    Settings.QUEUE.NOTIFY_INTERVAL,
299                    Settings.QUEUE.NOTIFY_WAIT,
300                    TranslatableCaption.of("swap.progress_region2_paste")
301            ));
302        }
303
304        toQueue1.setCompleteTask(toQueue2::enqueue);
305        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
306            toQueue1.addProgressSubscriber(subscriberFactory.createFull(
307                    actor,
308                    Settings.QUEUE.NOTIFY_INTERVAL,
309                    Settings.QUEUE.NOTIFY_WAIT,
310                    TranslatableCaption.of("swap.progress_region1_paste")
311            ));
312        }
313
314        fromQueue2.setCompleteTask(toQueue1::enqueue);
315        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
316            fromQueue2.addProgressSubscriber(subscriberFactory
317                    .createFull(
318                            actor,
319                            Settings.QUEUE.NOTIFY_INTERVAL,
320                            Settings.QUEUE.NOTIFY_WAIT,
321                            TranslatableCaption.of("swap.progress_region2_copy")
322                    ));
323        }
324
325        fromQueue1.setCompleteTask(fromQueue2::enqueue);
326        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
327            fromQueue1.addProgressSubscriber(subscriberFactory
328                    .createFull(
329                            actor,
330                            Settings.QUEUE.NOTIFY_INTERVAL,
331                            Settings.QUEUE.NOTIFY_WAIT,
332                            TranslatableCaption.of("swap.progress_region1_copy")
333                    ));
334        }
335        fromQueue1.enqueue();
336    }
337
338    private void setCopyFromToConsumer(
339            final Location pos1,
340            final Location pos2,
341            int relX,
342            int relZ,
343            final World world1,
344            final QueueCoordinator fromQueue,
345            final QueueCoordinator toQueue,
346            boolean removeEntities
347    ) {
348        fromQueue.setChunkConsumer(chunk -> {
349            int cx = chunk.getX();
350            int cz = chunk.getZ();
351            int cbx = cx << 4;
352            int cbz = cz << 4;
353            int bx = Math.max(pos1.getX(), cbx) & 15;
354            int bz = Math.max(pos1.getZ(), cbz) & 15;
355            int tx = Math.min(pos2.getX(), cbx + 15) & 15;
356            int tz = Math.min(pos2.getZ(), cbz + 15) & 15;
357            for (int y = world1.getMinY(); y <= world1.getMaxY(); y++) {
358                for (int x = bx; x <= tx; x++) {
359                    for (int z = bz; z <= tz; z++) {
360                        int rx = cbx + x;
361                        int rz = cbz + z;
362                        BlockVector3 loc = BlockVector3.at(rx, y, rz);
363                        toQueue.setBlock(rx + relX, y, rz + relZ, world1.getFullBlock(loc));
364                        toQueue.setBiome(rx + relX, y, rz + relZ, world1.getBiome(loc));
365                    }
366                }
367            }
368            Region region = new CuboidRegion(
369                    BlockVector3.at(cbx + bx, world1.getMinY(), cbz + bz),
370                    BlockVector3.at(cbx + tx, world1.getMaxY(), cbz + tz)
371            );
372            toQueue.addEntities(world1.getEntities(region));
373            if (removeEntities) {
374                for (Entity entity : world1.getEntities(region)) {
375                    entity.remove();
376                }
377            }
378        });
379    }
380
381    /**
382     * Set a region to a biome type.
383     *
384     * @param region      region to set
385     * @param extendBiome how far outside the region to extent setting the biome too account for 3D biomes being 4x4
386     * @param biome       biome to set
387     * @param area        {@link PlotArea} in which the biome is being set
388     * @param whenDone    task to run when complete
389     * @since 6.6.0
390     */
391    public void setBiome(
392            final CuboidRegion region,
393            final int extendBiome,
394            final BiomeType biome,
395            final PlotArea area,
396            final Runnable whenDone
397    ) {
398        final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
399        queue.addReadChunks(region.getChunks());
400        final BlockVector3 regionMin = region.getMinimumPoint();
401        final BlockVector3 regionMax = region.getMaximumPoint();
402        queue.setChunkConsumer(chunkPos -> {
403            BlockVector3 chunkMin = BlockVector3.at(
404                    Math.max(chunkPos.getX() << 4, regionMin.getBlockX()),
405                    regionMin.getBlockY(),
406                    Math.max(chunkPos.getZ() << 4, regionMin.getBlockZ())
407            );
408            BlockVector3 chunkMax = BlockVector3.at(
409                    Math.min((chunkPos.getX() << 4) + 15, regionMax.getBlockX()),
410                    regionMax.getBlockY(),
411                    Math.min((chunkPos.getZ() << 4) + 15, regionMax.getBlockZ())
412            );
413            CuboidRegion chunkRegion = new CuboidRegion(region.getWorld(), chunkMin, chunkMax);
414            WorldUtil.setBiome(
415                    area.getWorldName(),
416                    chunkRegion,
417                    biome
418            );
419            worldUtil.refreshChunk(chunkPos.getBlockX(), chunkPos.getBlockZ(), area.getWorldName());
420        });
421        queue.setCompleteTask(whenDone);
422        queue.enqueue();
423    }
424
425}