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.plotsquared.core.configuration.Settings; 022import com.plotsquared.core.queue.subscriber.ProgressSubscriber; 023import com.plotsquared.core.util.PatternUtil; 024import com.sk89q.jnbt.CompoundTag; 025import com.sk89q.worldedit.entity.Entity; 026import com.sk89q.worldedit.function.pattern.Pattern; 027import com.sk89q.worldedit.math.BlockVector2; 028import com.sk89q.worldedit.regions.CuboidRegion; 029import com.sk89q.worldedit.util.Location; 030import com.sk89q.worldedit.util.SideEffectSet; 031import com.sk89q.worldedit.world.World; 032import com.sk89q.worldedit.world.biome.BiomeType; 033import com.sk89q.worldedit.world.block.BaseBlock; 034import com.sk89q.worldedit.world.block.BlockState; 035import com.sk89q.worldedit.world.entity.EntityTypes; 036import org.checkerframework.checker.nullness.qual.NonNull; 037import org.checkerframework.checker.nullness.qual.Nullable; 038 039import java.util.ArrayList; 040import java.util.List; 041import java.util.Set; 042import java.util.concurrent.ConcurrentHashMap; 043import java.util.function.Consumer; 044 045/** 046 * Standard block setting queue that allows block setting across numerous chunks, without limits. 047 */ 048public abstract class BasicQueueCoordinator extends QueueCoordinator { 049 050 private final World world; 051 private final ConcurrentHashMap<BlockVector2, LocalChunk> blockChunks = new ConcurrentHashMap<>(); 052 private final List<BlockVector2> readRegion = new ArrayList<>(); 053 private final List<ProgressSubscriber> progressSubscribers = new ArrayList<>(); 054 private LocalChunk lastWrappedChunk; 055 private int lastX = Integer.MIN_VALUE; 056 private int lastZ = Integer.MIN_VALUE; 057 private boolean settingBiomes = false; 058 private boolean disableBiomes = false; 059 private boolean settingTiles = false; 060 private boolean regen = false; 061 private int[] regenStart; 062 private int[] regenEnd; 063 private CuboidRegion regenRegion = null; 064 private Consumer<BlockVector2> consumer = null; 065 private boolean unloadAfter = true; 066 private Runnable whenDone = null; 067 private SideEffectSet sideEffectSet = null; 068 @Nullable 069 private LightingMode lightingMode = LightingMode.valueOf(Settings.QUEUE.LIGHTING_MODE); 070 071 public BasicQueueCoordinator(@NonNull World world) { 072 super(world); 073 this.world = world; 074 } 075 076 @Override 077 public abstract BlockState getBlock(int x, int y, int z); 078 079 @Override 080 public final @NonNull World getWorld() { 081 return world; 082 } 083 084 @Override 085 public final int size() { 086 return blockChunks.size() + readRegion.size(); 087 } 088 089 @Override 090 public final void setModified(long modified) { 091 } 092 093 @Override 094 public boolean setBlock(int x, int y, int z, @NonNull Pattern pattern) { 095 return setBlock(x, y, z, PatternUtil.apply(pattern, x, y, z)); 096 } 097 098 @Override 099 public boolean setBlock(int x, int y, int z, @NonNull BaseBlock id) { 100 if ((y > world.getMaxY()) || (y < world.getMinY())) { 101 return false; 102 } 103 LocalChunk chunk = getChunk(x >> 4, z >> 4); 104 chunk.setBlock(x & 15, y, z & 15, id); 105 return true; 106 } 107 108 @Override 109 public boolean setBlock(int x, int y, int z, @NonNull BlockState id) { 110 // Trying to mix BlockState and BaseBlock leads to all kinds of issues. 111 // Since BaseBlock has more features than BlockState, simply convert 112 // all BlockStates to BaseBlocks 113 return setBlock(x, y, z, id.toBaseBlock()); 114 } 115 116 @SuppressWarnings("removal") 117 @Override 118 public boolean setBiome(int x, int z, @NonNull BiomeType biomeType) { 119 if (disableBiomes) { 120 return false; 121 } 122 LocalChunk chunk = getChunk(x >> 4, z >> 4); 123 for (int y = world.getMinY(); y <= world.getMaxY(); y++) { 124 chunk.setBiome(x & 15, y, z & 15, biomeType); 125 } 126 settingBiomes = true; 127 return true; 128 } 129 130 @Override 131 public final boolean setBiome(int x, int y, int z, @NonNull BiomeType biomeType) { 132 if (disableBiomes) { 133 return false; 134 } 135 LocalChunk chunk = getChunk(x >> 4, z >> 4); 136 chunk.setBiome(x & 15, y, z & 15, biomeType); 137 settingBiomes = true; 138 return true; 139 } 140 141 @Override 142 public boolean isSettingBiomes() { 143 return this.settingBiomes; 144 } 145 146 @Override 147 public void setBiomesEnabled(boolean settingBiomes) { 148 this.settingBiomes = settingBiomes; 149 this.disableBiomes = true; 150 } 151 152 @Override 153 public boolean setTile(int x, int y, int z, @NonNull CompoundTag tag) { 154 LocalChunk chunk = getChunk(x >> 4, z >> 4); 155 chunk.setTile(x, y, z, tag); 156 settingTiles = true; 157 return true; 158 } 159 160 @Override 161 public boolean isSettingTiles() { 162 return this.settingTiles; 163 } 164 165 @Override 166 public boolean setEntity(@NonNull Entity entity) { 167 if (entity.getState() == null || entity.getState().getType() == EntityTypes.PLAYER) { 168 return false; 169 } 170 Location location = entity.getLocation(); 171 LocalChunk chunk = getChunk(location.getBlockX() >> 4, location.getBlockZ() >> 4); 172 chunk.setEntity(location, entity.getState()); 173 return true; 174 } 175 176 @Override 177 public @NonNull List<BlockVector2> getReadChunks() { 178 return this.readRegion; 179 } 180 181 @Override 182 public void addReadChunk(@NonNull BlockVector2 chunk) { 183 this.readRegion.add(chunk); 184 } 185 186 @Override 187 public void addReadChunks(@NonNull Set<BlockVector2> readRegion) { 188 this.readRegion.addAll(readRegion); 189 } 190 191 @Override 192 public CuboidRegion getRegenRegion() { 193 return this.regenRegion != null ? this.regenRegion.clone() : null; 194 } 195 196 @Override 197 public void setRegenRegion(@NonNull CuboidRegion regenRegion) { 198 this.regenRegion = regenRegion; 199 } 200 201 @Override 202 public void regenChunk(int x, int z) { 203 regen = true; 204 // There will never only be one nullified coordinate pair 205 if (regenStart == null) { 206 regenStart = new int[]{x, z}; 207 regenEnd = new int[]{x, z}; 208 return; 209 } 210 if (x < regenStart[0]) { 211 regenStart[0] = x; 212 } 213 if (z < regenStart[1]) { 214 regenStart[1] = z; 215 } 216 if (x > regenEnd[0]) { 217 regenEnd[0] = x; 218 } 219 if (z > regenEnd[1]) { 220 regenEnd[1] = z; 221 } 222 } 223 224 @Override 225 public boolean isUnloadAfter() { 226 return this.unloadAfter; 227 } 228 229 @Override 230 public void setUnloadAfter(boolean unloadAfter) { 231 this.unloadAfter = unloadAfter; 232 } 233 234 /** 235 * Gets the int[x,z] chunk coordinates where regeneration should start from 236 * 237 * @return int[x, z] of regen start 238 */ 239 public int[] getRegenStart() { 240 return regenStart; 241 } 242 243 /** 244 * Gets the int[x,z] chunk coordinates where regeneration should finish 245 * 246 * @return int[x, z] of regen end 247 */ 248 public int[] getRegenEnd() { 249 return regenEnd; 250 } 251 252 /** 253 * Whether the queue has a start/end to chunk regeneration 254 * 255 * @return if is regenerating queue with int[x,z] start and end 256 */ 257 public boolean isRegen() { 258 return regen; 259 } 260 261 /** 262 * Gets the map of ChunkCoordinates in {@link BlockVector2} form against the {@link LocalChunk} of cached chunks to be written 263 * 264 * @return ConcurrentHashMap of chunks to be accessed 265 */ 266 public @NonNull ConcurrentHashMap<BlockVector2, LocalChunk> getBlockChunks() { 267 return this.blockChunks; 268 } 269 270 /** 271 * Forces an {@link LocalChunk} into the list of chunks to be written. Overwrites existing chunks in the map 272 * 273 * @param chunk add a LocalChunk to be written to by the queue 274 */ 275 public final void setChunk(@NonNull LocalChunk chunk) { 276 this.blockChunks.put(BlockVector2.at(chunk.getX(), chunk.getZ()), chunk); 277 } 278 279 @Override 280 public @Nullable 281 final Consumer<BlockVector2> getChunkConsumer() { 282 return this.consumer; 283 } 284 285 @Override 286 public final void setChunkConsumer(@NonNull Consumer<BlockVector2> consumer) { 287 this.consumer = consumer; 288 } 289 290 /** 291 * Get the list of progress subscribers currently added to the queue to be added to the Chunk Coordinator 292 */ 293 public final List<ProgressSubscriber> getProgressSubscribers() { 294 return this.progressSubscribers; 295 } 296 297 @Override 298 public final void addProgressSubscriber(@NonNull ProgressSubscriber progressSubscriber) { 299 this.progressSubscribers.add(progressSubscriber); 300 } 301 302 @Override 303 public @NonNull 304 final LightingMode getLightingMode() { 305 if (lightingMode == null) { 306 return LightingMode.valueOf(Settings.QUEUE.LIGHTING_MODE); 307 } 308 return this.lightingMode; 309 } 310 311 @Override 312 public final void setLightingMode(@Nullable LightingMode mode) { 313 this.lightingMode = mode; 314 } 315 316 @Override 317 public Runnable getCompleteTask() { 318 return this.whenDone; 319 } 320 321 @Override 322 public void setCompleteTask(Runnable whenDone) { 323 this.whenDone = whenDone; 324 } 325 326 @Override 327 public SideEffectSet getSideEffectSet() { 328 return sideEffectSet; 329 } 330 331 @Override 332 public void setSideEffectSet(SideEffectSet sideEffectSet) { 333 this.sideEffectSet = sideEffectSet; 334 } 335 336 // Don't ask about the @NonNull placement. That's how it needs to be else it errors. 337 @Override 338 public void setBiomeCuboid( 339 final com.plotsquared.core.location.@NonNull Location pos1, 340 final com.plotsquared.core.location.@NonNull Location pos2, 341 @NonNull final BiomeType biome 342 ) { 343 if (disableBiomes) { 344 return; 345 } 346 super.setBiomeCuboid(pos1, pos2, biome); 347 } 348 349 /** 350 * Get the {@link LocalChunk} from the queue at the given chunk coordinates. Returns a new instance if one doesn't exist 351 */ 352 @NonNull 353 private LocalChunk getChunk(final int chunkX, final int chunkZ) { 354 if (chunkX != lastX || chunkZ != lastZ) { 355 lastX = chunkX; 356 lastZ = chunkZ; 357 BlockVector2 pair = BlockVector2.at(chunkX, chunkZ); 358 lastWrappedChunk = this.blockChunks.get(pair); 359 if (lastWrappedChunk == null) { 360 lastWrappedChunk = new LocalChunk(this, chunkX, chunkZ); 361 LocalChunk previous = this.blockChunks.put(pair, lastWrappedChunk); 362 if (previous == null) { 363 return lastWrappedChunk; 364 } 365 lastWrappedChunk = previous; 366 } 367 } 368 return lastWrappedChunk; 369 } 370 371}