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