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