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(
276                        swapPos.getX() + pos2.getX() - pos1.getX(),
277                        pos1.getY(),
278                        swapPos.getZ() + pos2.getZ() - pos1.getZ()
279                )
280        ).getChunks());
281        QueueCoordinator toQueue1 = blockQueue.getNewQueue(world1);
282        QueueCoordinator toQueue2 = blockQueue.getNewQueue(world2);
283
284        setCopyFromToConsumer(pos1, pos2, relX, relZ, world1, fromQueue1, toQueue2, true);
285        setCopyFromToConsumer(pos1.add(relX, 0, relZ), pos2.add(relX, 0, relZ), -relX, -relZ, world1, fromQueue2, toQueue1,
286                true
287        );
288
289        toQueue2.setCompleteTask(whenDone);
290        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
291            toQueue2.addProgressSubscriber(subscriberFactory.createFull(
292                    actor,
293                    Settings.QUEUE.NOTIFY_INTERVAL,
294                    Settings.QUEUE.NOTIFY_WAIT,
295                    TranslatableCaption.of("swap.progress_region2_paste")
296            ));
297        }
298
299        toQueue1.setCompleteTask(toQueue2::enqueue);
300        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
301            toQueue1.addProgressSubscriber(subscriberFactory.createFull(
302                    actor,
303                    Settings.QUEUE.NOTIFY_INTERVAL,
304                    Settings.QUEUE.NOTIFY_WAIT,
305                    TranslatableCaption.of("swap.progress_region1_paste")
306            ));
307        }
308
309        fromQueue2.setCompleteTask(toQueue1::enqueue);
310        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
311            fromQueue2.addProgressSubscriber(subscriberFactory
312                    .createFull(
313                            actor,
314                            Settings.QUEUE.NOTIFY_INTERVAL,
315                            Settings.QUEUE.NOTIFY_WAIT,
316                            TranslatableCaption.of("swap.progress_region2_copy")
317                    ));
318        }
319
320        fromQueue1.setCompleteTask(fromQueue2::enqueue);
321        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
322            fromQueue1.addProgressSubscriber(subscriberFactory
323                    .createFull(
324                            actor,
325                            Settings.QUEUE.NOTIFY_INTERVAL,
326                            Settings.QUEUE.NOTIFY_WAIT,
327                            TranslatableCaption.of("swap.progress_region1_copy")
328                    ));
329        }
330        fromQueue1.enqueue();
331    }
332
333    private void setCopyFromToConsumer(
334            final Location pos1,
335            final Location pos2,
336            int relX,
337            int relZ,
338            final World world1,
339            final QueueCoordinator fromQueue,
340            final QueueCoordinator toQueue,
341            boolean removeEntities
342    ) {
343        fromQueue.setChunkConsumer(chunk -> {
344            int cx = chunk.getX();
345            int cz = chunk.getZ();
346            int cbx = cx << 4;
347            int cbz = cz << 4;
348            int bx = Math.max(pos1.getX(), cbx) & 15;
349            int bz = Math.max(pos1.getZ(), cbz) & 15;
350            int tx = Math.min(pos2.getX(), cbx + 15) & 15;
351            int tz = Math.min(pos2.getZ(), cbz + 15) & 15;
352            for (int y = world1.getMinY(); y <= world1.getMaxY(); y++) {
353                for (int x = bx; x <= tx; x++) {
354                    for (int z = bz; z <= tz; z++) {
355                        int rx = cbx + x;
356                        int rz = cbz + z;
357                        BlockVector3 loc = BlockVector3.at(rx, y, rz);
358                        toQueue.setBlock(rx + relX, y, rz + relZ, world1.getFullBlock(loc));
359                        toQueue.setBiome(rx + relX, y, rz + relZ, world1.getBiome(loc));
360                    }
361                }
362            }
363            Region region = new CuboidRegion(
364                    BlockVector3.at(cbx + bx, world1.getMinY(), cbz + bz),
365                    BlockVector3.at(cbx + tx, world1.getMaxY(), cbz + tz)
366            );
367            toQueue.addEntities(world1.getEntities(region));
368            if (removeEntities) {
369                for (Entity entity : world1.getEntities(region)) {
370                    entity.remove();
371                }
372            }
373        });
374    }
375
376    /**
377     * Set a region to a biome type.
378     *
379     * @param region      region to set
380     * @param extendBiome how far outside the region to extent setting the biome too account for 3D biomes being 4x4
381     * @param biome       biome to set
382     * @param area        {@link PlotArea} in which the biome is being set
383     * @param whenDone    task to run when complete
384     * @since 6.6.0
385     */
386    public void setBiome(
387            final CuboidRegion region,
388            final int extendBiome,
389            final BiomeType biome,
390            final PlotArea area,
391            final Runnable whenDone
392    ) {
393        final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
394        queue.addReadChunks(region.getChunks());
395        final BlockVector3 regionMin = region.getMinimumPoint();
396        final BlockVector3 regionMax = region.getMaximumPoint();
397        queue.setChunkConsumer(chunkPos -> {
398            BlockVector3 chunkMin = BlockVector3.at(
399                    Math.max(chunkPos.getX() << 4, regionMin.getBlockX()),
400                    regionMin.getBlockY(),
401                    Math.max(chunkPos.getZ() << 4, regionMin.getBlockZ())
402            );
403            BlockVector3 chunkMax = BlockVector3.at(
404                    Math.min((chunkPos.getX() << 4) + 15, regionMax.getBlockX()),
405                    regionMax.getBlockY(),
406                    Math.min((chunkPos.getZ() << 4) + 15, regionMax.getBlockZ())
407            );
408            CuboidRegion chunkRegion = new CuboidRegion(region.getWorld(), chunkMin, chunkMax);
409            WorldUtil.setBiome(
410                    area.getWorldName(),
411                    chunkRegion,
412                    biome
413            );
414            worldUtil.refreshChunk(chunkPos.getBlockX(), chunkPos.getBlockZ(), area.getWorldName());
415        });
416        queue.setCompleteTask(whenDone);
417        queue.enqueue();
418    }
419
420}