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.queue;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.location.Location;
024import com.plotsquared.core.queue.subscriber.ProgressSubscriber;
025import com.plotsquared.core.util.PatternUtil;
026import com.sk89q.jnbt.CompoundTag;
027import com.sk89q.worldedit.entity.Entity;
028import com.sk89q.worldedit.function.pattern.Pattern;
029import com.sk89q.worldedit.math.BlockVector2;
030import com.sk89q.worldedit.regions.CuboidRegion;
031import com.sk89q.worldedit.util.SideEffectSet;
032import com.sk89q.worldedit.world.World;
033import com.sk89q.worldedit.world.biome.BiomeType;
034import com.sk89q.worldedit.world.block.BaseBlock;
035import com.sk89q.worldedit.world.block.BlockState;
036import org.checkerframework.checker.nullness.qual.NonNull;
037import org.checkerframework.checker.nullness.qual.Nullable;
038
039import java.util.List;
040import java.util.Set;
041import java.util.concurrent.atomic.AtomicBoolean;
042import java.util.function.Consumer;
043
044public abstract class QueueCoordinator {
045
046    private boolean forceSync = false;
047    @Nullable
048    private Object chunkObject;
049    private final AtomicBoolean enqueued = new AtomicBoolean();
050
051    @SuppressWarnings({"unused", "FieldCanBeLocal"})
052    @Inject
053    private GlobalBlockQueue blockQueue;
054
055    /**
056     * Default constructor requires world to indicate any extents given to {@link QueueCoordinator} also need this constructor.
057     *
058     * @param world world as all queues should have this constructor
059     */
060    public QueueCoordinator(@Nullable World world) {
061        PlotSquared.platform().injector().injectMembers(this);
062    }
063
064    /**
065     * Get a {@link ScopedQueueCoordinator} limited to the chunk at the specific chunk Coordinates
066     *
067     * @param x chunk x coordinate
068     * @param z chunk z coordinate
069     * @return a new {@link ScopedQueueCoordinator}
070     * @deprecated Use {@link ScopedQueueCoordinator#getForChunk(int, int, int, int)}
071     */
072    @Deprecated(forRemoval = true, since = "6.6.0")
073    public ScopedQueueCoordinator getForChunk(int x, int z) {
074        if (getWorld() == null) {
075            return getForChunk(x, z, PlotSquared.platform().versionMinHeight(), PlotSquared.platform().versionMaxHeight());
076        }
077        return getForChunk(x, z, getWorld().getMinY(), getWorld().getMaxY());
078    }
079
080    /**
081     * Get a {@link ScopedQueueCoordinator} limited to the chunk at the specific chunk Coordinates
082     *
083     * @param x chunk x coordinate
084     * @param z chunk z coordinate
085     * @return a new {@link ScopedQueueCoordinator}
086     * @since 6.6.0
087     * @deprecated {@link ScopedQueueCoordinator} will be renamed in v7.
088     */
089    @Deprecated(forRemoval = true, since = "6.9.0")
090    public ScopedQueueCoordinator getForChunk(int x, int z, int minY, int maxY) {
091        int bx = x << 4;
092        int bz = z << 4;
093        return new ScopedQueueCoordinator(this, Location.at(getWorld().getName(), bx, minY, bz),
094                Location.at(getWorld().getName(), bx + 15, maxY, bz + 15)
095        );
096    }
097
098    /**
099     * Get the size of the queue in chunks
100     *
101     * @return size
102     */
103    public abstract int size();
104
105    /**
106     * Set when the queue was last modified
107     *
108     * @param modified long of system millis
109     */
110    public abstract void setModified(long modified);
111
112    /**
113     * Returns true if the queue should be forced to be synchronous when enqueued. This is not necessarily synchronous to the
114     * server, and simply effectively makes {@link QueueCoordinator#enqueue()} a blocking operation.
115     *
116     * @return is force sync
117     */
118    public boolean isForceSync() {
119        return forceSync;
120    }
121
122    /**
123     * Set whether the queue should be forced to be synchronous. This is not necessarily synchronous to the server, and simply
124     * effectively makes {@link QueueCoordinator#enqueue()} a blocking operation.
125     *
126     * @param forceSync force sync or not
127     */
128    public void setForceSync(boolean forceSync) {
129        this.forceSync = forceSync;
130    }
131
132    /**
133     * Get the Chunk Object set to the queue
134     *
135     * @return chunk object. Usually the implementation-specific chunk (e.g. bukkit Chunk)
136     */
137    public @Nullable Object getChunkObject() {
138        return chunkObject;
139    }
140
141    /**
142     * Set a chunk object (e.g. the Bukkit Chunk object) to the queue. This will be used as fallback in case of WNA failure.
143     * Should ONLY be used in specific cases (i.e. generation, where a chunk is being populated)
144     *
145     * @param chunkObject chunk object. Usually the implementation-specific chunk (e.g. bukkit Chunk)
146     */
147    public void setChunkObject(@NonNull Object chunkObject) {
148        this.chunkObject = chunkObject;
149    }
150
151    /**
152     * Sets the block at the coordinates provided to the given id.
153     *
154     * @param x  the x coordinate from from 0 to 15 inclusive
155     * @param y  the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
156     * @param z  the z coordinate from 0 to 15 inclusive
157     * @param id the BlockState to set the block to
158     * @return success or not
159     */
160    public abstract boolean setBlock(final int x, final int y, final int z, final @NonNull BlockState id);
161
162    /**
163     * Sets the block at the coordinates provided to the given id.
164     *
165     * @param x  the x coordinate from from 0 to 15 inclusive
166     * @param y  the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
167     * @param z  the z coordinate from 0 to 15 inclusive
168     * @param id the BaseBlock to set the block to
169     * @return success or not
170     */
171    public abstract boolean setBlock(final int x, final int y, final int z, final @NonNull BaseBlock id);
172
173    /**
174     * Sets the block at the coordinates provided to the given id.
175     *
176     * @param x       the x coordinate from from 0 to 15 inclusive
177     * @param y       the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
178     * @param z       the z coordinate from 0 to 15 inclusive
179     * @param pattern the pattern to set the block to
180     * @return success or not
181     */
182    public boolean setBlock(final int x, final int y, final int z, final @NonNull Pattern pattern) {
183        return setBlock(x, y, z, PatternUtil.apply(pattern, x, y, z));
184    }
185
186    /**
187     * Sets a tile entity at the coordinates provided to the given CompoundTag
188     *
189     * @param x   the x coordinate from from 0 to 15 inclusive
190     * @param y   the y coordinate from from 0 (inclusive) - maxHeight(exclusive)
191     * @param z   the z coordinate from 0 to 15 inclusive
192     * @param tag the CompoundTag to set the tile to
193     * @return success or not
194     */
195    public abstract boolean setTile(int x, int y, int z, @NonNull CompoundTag tag);
196
197    /**
198     * Whether the queue has any tiles being set
199     *
200     * @return if setting tiles
201     */
202    public abstract boolean isSettingTiles();
203
204    /**
205     * Get a block at the given coordinates.
206     *
207     * @param x block x
208     * @param y block y
209     * @param z block z
210     * @return WorldEdit BlockState
211     */
212    public @Nullable
213    abstract BlockState getBlock(int x, int y, int z);
214
215    /**
216     * Set a biome in XZ. This will likely set to the whole column
217     *
218     * @param x     x coordinate
219     * @param z     z coordinate
220     * @param biome biome
221     * @return success or not
222     * @deprecated Biomes now take XYZ, see {@link #setBiome(int, int, int, BiomeType)}
223     *         <br>
224     *         Scheduled for removal once we drop the support for versions not supporting 3D biomes, 1.18 and earlier.
225     */
226    @Deprecated(forRemoval = true, since = "6.0.0")
227    public abstract boolean setBiome(int x, int z, @NonNull BiomeType biome);
228
229    /**
230     * Set a biome in XYZ
231     *
232     * @param x     x coordinate
233     * @param y     y coordinate
234     * @param z     z coordinate
235     * @param biome biome
236     * @return success or not
237     */
238    public abstract boolean setBiome(int x, int y, int z, @NonNull BiomeType biome);
239
240    /**
241     * Whether the queue has any biomes to be set
242     *
243     * @return if setting biomes
244     */
245    public abstract boolean isSettingBiomes();
246
247    /**
248     * If the queue should accept biome placement
249     *
250     * @param enabled If biomes should be enabled
251     * @since 6.8.0
252     */
253    public abstract void setBiomesEnabled(boolean enabled);
254
255    /**
256     * Add entities to be created
257     *
258     * @param entities list of entities to add to queue
259     */
260    public void addEntities(@NonNull List<? extends Entity> entities) {
261        for (Entity e : entities) {
262            this.setEntity(e);
263        }
264    }
265
266    /**
267     * Add an entity to be created
268     *
269     * @param entity entity to add to queue
270     * @return success or not
271     */
272    public abstract boolean setEntity(@NonNull Entity entity);
273
274    /**
275     * Get the list of chunks that are added manually. This usually indicated the queue is "read only".
276     *
277     * @return list of BlockVector2 of chunks that are to be "read"
278     */
279    public @NonNull
280    abstract List<BlockVector2> getReadChunks();
281
282    /**
283     * Add a set of {@link BlockVector2} Chunk coordinates to the Read Chunks list
284     *
285     * @param readChunks set of BlockVector2 to add to "read" chunks
286     */
287    public abstract void addReadChunks(@NonNull Set<BlockVector2> readChunks);
288
289    /**
290     * Add a {@link BlockVector2} Chunk coordinate to the Read Chunks list
291     *
292     * @param chunk BlockVector2 to add to "read" chunks
293     */
294    public abstract void addReadChunk(@NonNull BlockVector2 chunk);
295
296    /**
297     * Whether chunks should be unloaded after being accessed
298     *
299     * @return if is unloading chunks after accessing them
300     */
301    public abstract boolean isUnloadAfter();
302
303    /**
304     * Set whether chunks should be unloaded after being accessed
305     *
306     * @param unloadAfter if to unload chunks after being accessed
307     */
308    public abstract void setUnloadAfter(boolean unloadAfter);
309
310    /**
311     * Get the {@link CuboidRegion} designated for direct regeneration
312     *
313     * @return CuboidRegion to regenerate
314     */
315    public @Nullable
316    abstract CuboidRegion getRegenRegion();
317
318    /**
319     * Set the {@link CuboidRegion} designated for direct regeneration
320     *
321     * @param regenRegion CuboidRegion to regenerate
322     */
323    public abstract void setRegenRegion(@NonNull CuboidRegion regenRegion);
324
325    /**
326     * Set a specific chunk at the chunk coordinates XZ to be regenerated.
327     *
328     * @param x chunk x
329     * @param z chunk z
330     */
331    public abstract void regenChunk(int x, int z);
332
333    /**
334     * Get the world the queue is writing to
335     *
336     * @return world of the queue
337     */
338    public @Nullable
339    abstract World getWorld();
340
341    /**
342     * Set the queue as having been modified now
343     */
344    public final void setModified() {
345        setModified(System.currentTimeMillis());
346    }
347
348    /**
349     * Enqueue the queue to start it
350     *
351     * @return success or not
352     * @since 6.0.10
353     */
354    public boolean enqueue() {
355        boolean success = false;
356        if (enqueued.compareAndSet(false, true)) {
357            success = true;
358            start();
359        }
360        return success;
361    }
362
363    /**
364     * Start the queue
365     */
366    public abstract void start();
367
368    /**
369     * Cancel the queue
370     */
371    public abstract void cancel();
372
373    /**
374     * Get the task to be run when all chunks have been accessed
375     *
376     * @return task to be run when queue is complete
377     */
378    public abstract Runnable getCompleteTask();
379
380    /**
381     * Set the task to be run when all chunks have been accessed
382     *
383     * @param whenDone task to be run when queue is complete
384     */
385    public abstract void setCompleteTask(@Nullable Runnable whenDone);
386
387    /**
388     * Return the chunk consumer set to the queue or null if one is not set
389     *
390     * @return Consumer to be executed on each chunk in queue
391     */
392    public @Nullable
393    abstract Consumer<BlockVector2> getChunkConsumer();
394
395    /**
396     * Set the Consumer that will be executed on each chunk in queue
397     *
398     * @param consumer Consumer to be executed on each chunk in queue
399     */
400    public abstract void setChunkConsumer(@NonNull Consumer<BlockVector2> consumer);
401
402    /**
403     * Add a {@link ProgressSubscriber} to the Queue to subscribe to the relevant Chunk Processor
404     */
405    public abstract void addProgressSubscriber(@NonNull ProgressSubscriber progressSubscriber);
406
407    /**
408     * Get the {@link LightingMode} to be used when setting blocks
409     */
410    public @NonNull
411    abstract LightingMode getLightingMode();
412
413    /**
414     * Set the {@link LightingMode} to be used when setting blocks
415     *
416     * @param mode lighting mode. Null to use default.
417     */
418    public abstract void setLightingMode(@Nullable LightingMode mode);
419
420    /**
421     * Get the overriding {@link SideEffectSet} to be used by the queue if it exists, else null
422     *
423     * @return Overriding {@link SideEffectSet} or null
424     */
425    public abstract @Nullable SideEffectSet getSideEffectSet();
426
427    /**
428     * Set the overriding {@link SideEffectSet} to be used by the queue. Null to use default side effects.
429     *
430     * @param sideEffectSet side effects to override with, or null to use default
431     */
432    public abstract void setSideEffectSet(@Nullable SideEffectSet sideEffectSet);
433
434    /**
435     * Fill a cuboid between two positions with a BlockState
436     *
437     * @param pos1  1st cuboid position
438     * @param pos2  2nd cuboid position
439     * @param block block to fill
440     */
441    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BlockState block) {
442        int yMin = Math.min(pos1.getY(), pos2.getY());
443        int yMax = Math.max(pos1.getY(), pos2.getY());
444        int xMin = Math.min(pos1.getX(), pos2.getX());
445        int xMax = Math.max(pos1.getX(), pos2.getX());
446        int zMin = Math.min(pos1.getZ(), pos2.getZ());
447        int zMax = Math.max(pos1.getZ(), pos2.getZ());
448        for (int y = yMin; y <= yMax; y++) {
449            for (int x = xMin; x <= xMax; x++) {
450                for (int z = zMin; z <= zMax; z++) {
451                    setBlock(x, y, z, block);
452                }
453            }
454        }
455    }
456
457    /**
458     * Fill a cuboid between two positions with a Pattern
459     *
460     * @param pos1   1st cuboid position
461     * @param pos2   2nd cuboid position
462     * @param blocks pattern to fill
463     */
464    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull Pattern blocks) {
465        int yMin = Math.min(pos1.getY(), pos2.getY());
466        int yMax = Math.max(pos1.getY(), pos2.getY());
467        int xMin = Math.min(pos1.getX(), pos2.getX());
468        int xMax = Math.max(pos1.getX(), pos2.getX());
469        int zMin = Math.min(pos1.getZ(), pos2.getZ());
470        int zMax = Math.max(pos1.getZ(), pos2.getZ());
471        for (int y = yMin; y <= yMax; y++) {
472            for (int x = xMin; x <= xMax; x++) {
473                for (int z = zMin; z <= zMax; z++) {
474                    setBlock(x, y, z, blocks);
475                }
476            }
477        }
478    }
479
480    /**
481     * Fill a cuboid between two positions with a BiomeType
482     *
483     * @param pos1  1st cuboid position
484     * @param pos2  2nd cuboid position
485     * @param biome biome to fill
486     */
487    public void setBiomeCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BiomeType biome) {
488        int yMin = Math.min(pos1.getY(), pos2.getY());
489        int yMax = Math.max(pos1.getY(), pos2.getY());
490        int xMin = Math.min(pos1.getX(), pos2.getX());
491        int xMax = Math.max(pos1.getX(), pos2.getX());
492        int zMin = Math.min(pos1.getZ(), pos2.getZ());
493        int zMax = Math.max(pos1.getZ(), pos2.getZ());
494        for (int y = yMin; y <= yMax; y++) {
495            for (int x = xMin; x <= xMax; x++) {
496                for (int z = zMin; z <= zMax; z++) {
497                    setBiome(x, y, z, biome);
498                }
499            }
500        }
501    }
502
503    /**
504     * Get the min Y limit associated with the queue
505     */
506    protected int getMinY() {
507        return getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMinHeight();
508    }
509
510    /**
511     * Get the max Y limit associated with the queue
512     */
513    protected int getMaxY() {
514        return getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMaxHeight();
515    }
516
517    /**
518     * Get the min chunk layer associated with the queue. Usually 0 or -4;
519     */
520    protected int getMinLayer() {
521        return (getWorld() != null ? getWorld().getMinY() : PlotSquared.platform().versionMinHeight()) >> 4;
522    }
523
524    /**
525     * Get the max chunk layer associated with the queue. Usually 15 or 19
526     */
527    protected int getMaxLayer() {
528        return (getWorld() != null ? getWorld().getMaxY() : PlotSquared.platform().versionMaxHeight()) >> 4;
529    }
530
531}