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.bukkit.queue; 020 021import com.google.inject.Inject; 022import com.plotsquared.bukkit.schematic.StateWrapper; 023import com.plotsquared.bukkit.util.BukkitBlockUtil; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.inject.factory.ChunkCoordinatorBuilderFactory; 026import com.plotsquared.core.inject.factory.ChunkCoordinatorFactory; 027import com.plotsquared.core.queue.BasicQueueCoordinator; 028import com.plotsquared.core.queue.ChunkCoordinator; 029import com.plotsquared.core.queue.LocalChunk; 030import com.plotsquared.core.util.ChunkUtil; 031import com.sk89q.jnbt.CompoundTag; 032import com.sk89q.worldedit.WorldEditException; 033import com.sk89q.worldedit.bukkit.BukkitAdapter; 034import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; 035import com.sk89q.worldedit.extent.clipboard.Clipboard; 036import com.sk89q.worldedit.math.BlockVector2; 037import com.sk89q.worldedit.math.BlockVector3; 038import com.sk89q.worldedit.regions.CuboidRegion; 039import com.sk89q.worldedit.regions.Region; 040import com.sk89q.worldedit.util.SideEffect; 041import com.sk89q.worldedit.util.SideEffectSet; 042import com.sk89q.worldedit.world.World; 043import com.sk89q.worldedit.world.biome.BiomeType; 044import com.sk89q.worldedit.world.block.BaseBlock; 045import com.sk89q.worldedit.world.block.BlockState; 046import org.bukkit.Bukkit; 047import org.bukkit.Chunk; 048import org.bukkit.block.Block; 049import org.bukkit.block.Container; 050import org.bukkit.block.data.BlockData; 051import org.checkerframework.checker.nullness.qual.NonNull; 052 053import java.util.ArrayList; 054import java.util.Collection; 055import java.util.function.Consumer; 056 057public class BukkitQueueCoordinator extends BasicQueueCoordinator { 058 059 private static final SideEffectSet NO_SIDE_EFFECT_SET; 060 private static final SideEffectSet EDGE_SIDE_EFFECT_SET; 061 private static final SideEffectSet LIGHTING_SIDE_EFFECT_SET; 062 private static final SideEffectSet EDGE_LIGHTING_SIDE_EFFECT_SET; 063 064 static { 065 NO_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.LIGHTING, SideEffect.State.OFF).with( 066 SideEffect.NEIGHBORS, 067 SideEffect.State.OFF 068 ); 069 EDGE_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.UPDATE, SideEffect.State.ON).with( 070 SideEffect.NEIGHBORS, 071 SideEffect.State.ON 072 ); 073 LIGHTING_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.NEIGHBORS, SideEffect.State.OFF); 074 EDGE_LIGHTING_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.UPDATE, SideEffect.State.ON).with( 075 SideEffect.NEIGHBORS, 076 SideEffect.State.ON 077 ); 078 } 079 080 private org.bukkit.World bukkitWorld; 081 @Inject 082 private ChunkCoordinatorBuilderFactory chunkCoordinatorBuilderFactory; 083 @Inject 084 private ChunkCoordinatorFactory chunkCoordinatorFactory; 085 private ChunkCoordinator chunkCoordinator; 086 087 @Inject 088 public BukkitQueueCoordinator(@NonNull World world) { 089 super(world); 090 } 091 092 @Override 093 public BlockState getBlock(int x, int y, int z) { 094 Block block = getBukkitWorld().getBlockAt(x, y, z); 095 return BukkitBlockUtil.get(block); 096 } 097 098 @Override 099 public void start() { 100 chunkCoordinator.start(); 101 } 102 103 @Override 104 public void cancel() { 105 chunkCoordinator.cancel(); 106 } 107 108 @Override 109 public boolean enqueue() { 110 final Clipboard regenClipboard; 111 if (isRegen()) { 112 BlockVector3 start = BlockVector3.at(getRegenStart()[0] << 4, getMinY(), getRegenStart()[1] << 4); 113 BlockVector3 end = BlockVector3.at((getRegenEnd()[0] << 4) + 15, getMaxY(), (getRegenEnd()[1] << 4) + 15); 114 Region region = new CuboidRegion(start, end); 115 regenClipboard = new BlockArrayClipboard(region); 116 regenClipboard.setOrigin(start); 117 getWorld().regenerate(region, regenClipboard); 118 } else if (getRegenRegion() != null) { 119 regenClipboard = new BlockArrayClipboard(getRegenRegion()); 120 regenClipboard.setOrigin(getRegenRegion().getMinimumPoint()); 121 getWorld().regenerate(getRegenRegion(), regenClipboard); 122 } else { 123 regenClipboard = null; 124 } 125 Consumer<BlockVector2> consumer = getChunkConsumer(); 126 if (consumer == null) { 127 consumer = blockVector2 -> { 128 LocalChunk localChunk = getBlockChunks().get(blockVector2); 129 boolean isRegenChunk = 130 regenClipboard != null && blockVector2.getBlockX() > getRegenStart()[0] && blockVector2.getBlockZ() > getRegenStart()[1] 131 && blockVector2.getBlockX() < getRegenEnd()[0] && blockVector2.getBlockZ() < getRegenEnd()[1]; 132 int sx = blockVector2.getX() << 4; 133 int sz = blockVector2.getZ() << 4; 134 if (isRegenChunk) { 135 for (int layer = getMinLayer(); layer <= getMaxLayer(); layer++) { 136 for (int y = 0; y < 16; y++) { 137 for (int x = 0; x < 16; x++) { 138 for (int z = 0; z < 16; z++) { 139 x += sx; 140 y += layer << 4; 141 z += sz; 142 BaseBlock block = regenClipboard.getFullBlock(BlockVector3.at(x, y, z)); 143 if (block != null) { 144 boolean edge = Settings.QUEUE.UPDATE_EDGES && isEdgeRegen(x & 15, z & 15, blockVector2); 145 setWorldBlock(x, y, z, block, blockVector2, edge); 146 } 147 } 148 } 149 } 150 } 151 } 152 // Allow regen and then blocks to be placed (plot schematic etc) 153 if (localChunk == null) { 154 return; 155 } 156 for (int layer = 0; layer < localChunk.getBaseblocks().length; layer++) { 157 BaseBlock[] blocksLayer = localChunk.getBaseblocks()[layer]; 158 if (blocksLayer == null) { 159 continue; 160 } 161 for (int j = 0; j < blocksLayer.length; j++) { 162 if (blocksLayer[j] == null) { 163 continue; 164 } 165 BaseBlock block = blocksLayer[j]; 166 167 if (block != null) { 168 int lx = ChunkUtil.getX(j); 169 int lz = ChunkUtil.getZ(j); 170 int x = sx + lx; 171 int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j); 172 int z = sz + lz; 173 boolean edge = Settings.QUEUE.UPDATE_EDGES && isEdge(y >> 4, lx, y & 15, lz, blockVector2, 174 localChunk 175 ); 176 setWorldBlock(x, y, z, block, blockVector2, edge); 177 } 178 } 179 } 180 for (int layer = 0; layer < localChunk.getBiomes().length; layer++) { 181 BiomeType[] biomesLayer = localChunk.getBiomes()[layer]; 182 if (biomesLayer == null) { 183 continue; 184 } 185 for (int j = 0; j < biomesLayer.length; j++) { 186 if (biomesLayer[j] == null) { 187 continue; 188 } 189 BiomeType biome = biomesLayer[j]; 190 if (biome != null) { 191 int x = sx + ChunkUtil.getX(j); 192 int y = ChunkUtil.getY(layer, j); 193 int z = sz + ChunkUtil.getZ(j); 194 getWorld().setBiome(BlockVector3.at(x, y, z), biome); 195 } 196 } 197 } 198 if (localChunk.getTiles().size() > 0) { 199 localChunk.getTiles().forEach((blockVector3, tag) -> { 200 try { 201 BaseBlock block = getWorld().getBlock(blockVector3).toBaseBlock(tag); 202 getWorld().setBlock(blockVector3, block, getSideEffectSet(SideEffectState.NONE)); 203 } catch (WorldEditException ignored) { 204 StateWrapper sw = new StateWrapper(tag); 205 sw.restoreTag(getWorld().getName(), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ()); 206 } 207 }); 208 } 209 if (localChunk.getEntities().size() > 0) { 210 localChunk.getEntities().forEach((location, entity) -> getWorld().createEntity(location, entity)); 211 } 212 }; 213 } 214 Collection<BlockVector2> read = new ArrayList<>(); 215 if (getReadChunks().size() > 0) { 216 read.addAll(getReadChunks()); 217 } 218 chunkCoordinator = 219 chunkCoordinatorBuilderFactory 220 .create(chunkCoordinatorFactory) 221 .inWorld(getWorld()) 222 .withChunks(getBlockChunks().keySet()) 223 .withChunks(read) 224 .withInitialBatchSize(3) 225 .withMaxIterationTime(40) 226 .withThrowableConsumer(Throwable::printStackTrace) 227 .withFinalAction(getCompleteTask()) 228 .withConsumer(consumer) 229 .unloadAfter(isUnloadAfter()) 230 .withProgressSubscribers(getProgressSubscribers()) 231 .forceSync(isForceSync()) 232 .build(); 233 return super.enqueue(); 234 } 235 236 /** 237 * Set a block to the world. First tries WNA but defaults to normal block setting methods if that fails 238 */ 239 @SuppressWarnings("unused") 240 private void setWorldBlock(int x, int y, int z, @NonNull BaseBlock block, @NonNull BlockVector2 blockVector2, boolean edge) { 241 try { 242 BlockVector3 loc = BlockVector3.at(x, y, z); 243 boolean lighting = false; 244 switch (getLightingMode()) { 245 case NONE: 246 break; 247 case PLACEMENT: 248 lighting = block.getBlockType().getMaterial().getLightValue() > 0; 249 break; 250 case REPLACEMENT: 251 lighting = block.getBlockType().getMaterial().getLightValue() > 0 252 || getWorld().getBlock(loc).getBlockType().getMaterial().getLightValue() > 0; 253 break; 254 default: 255 // Can only be "all" 256 lighting = true; 257 } 258 SideEffectSet sideEffectSet; 259 if (lighting) { 260 sideEffectSet = getSideEffectSet(edge ? SideEffectState.EDGE_LIGHTING : SideEffectState.LIGHTING); 261 } else { 262 sideEffectSet = getSideEffectSet(edge ? SideEffectState.EDGE : SideEffectState.NONE); 263 } 264 getWorld().setBlock(loc, block, sideEffectSet); 265 } catch (WorldEditException ignored) { 266 // Fallback to not so nice method 267 BlockData blockData = BukkitAdapter.adapt(block); 268 Block existing; 269 // Assume a chunk object has been given only when it should have been. 270 if (getChunkObject() instanceof Chunk chunkObject) { 271 existing = chunkObject.getBlock(x & 15, y, z & 15); 272 } else { 273 existing = getBukkitWorld().getBlockAt(x, y, z); 274 } 275 final BlockState existingBaseBlock = BukkitAdapter.adapt(existing.getBlockData()); 276 if (BukkitBlockUtil.get(existing).equals(existingBaseBlock) && existing.getBlockData().matches(blockData)) { 277 return; 278 } 279 280 if (existing.getState() instanceof Container) { 281 ((Container) existing.getState()).getInventory().clear(); 282 } 283 284 existing.setType(BukkitAdapter.adapt(block.getBlockType()), false); 285 existing.setBlockData(blockData, false); 286 if (block.hasNbtData()) { 287 CompoundTag tag = block.getNbtData(); 288 StateWrapper sw = new StateWrapper(tag); 289 290 sw.restoreTag(existing); 291 } 292 } 293 } 294 295 private org.bukkit.World getBukkitWorld() { 296 if (bukkitWorld == null) { 297 bukkitWorld = Bukkit.getWorld(getWorld().getName()); 298 } 299 return bukkitWorld; 300 } 301 302 private boolean isEdge(int layer, int x, int y, int z, BlockVector2 blockVector2, LocalChunk localChunk) { 303 int layerIndex = (layer - localChunk.getMinSection()); 304 if (layer == localChunk.getMinSection() || layerIndex == localChunk.getBaseblocks().length - 1) { 305 return false; 306 } 307 if (x == 0) { 308 LocalChunk localChunkX = getBlockChunks().get(blockVector2.withX(blockVector2.getX() - 1)); 309 if (localChunkX == null || localChunkX.getBaseblocks()[layerIndex] == null || 310 localChunkX.getBaseblocks()[layerIndex][ChunkUtil.getJ(15, y, z)] != null) { 311 return true; 312 } 313 } else if (x == 15) { 314 LocalChunk localChunkX = getBlockChunks().get(blockVector2.withX(blockVector2.getX() + 1)); 315 if (localChunkX == null || localChunkX.getBaseblocks()[layerIndex] == null || 316 localChunkX.getBaseblocks()[layerIndex][ChunkUtil.getJ(0, y, z)] != null) { 317 return true; 318 } 319 } 320 if (z == 0) { 321 LocalChunk localChunkZ = getBlockChunks().get(blockVector2.withZ(blockVector2.getZ() - 1)); 322 if (localChunkZ == null || localChunkZ.getBaseblocks()[layerIndex] == null || 323 localChunkZ.getBaseblocks()[layerIndex][ChunkUtil.getJ(x, y, 15)] != null) { 324 return true; 325 } 326 } else if (z == 15) { 327 LocalChunk localChunkZ = getBlockChunks().get(blockVector2.withZ(blockVector2.getZ() + 1)); 328 if (localChunkZ == null || localChunkZ.getBaseblocks()[layerIndex] == null || 329 localChunkZ.getBaseblocks()[layerIndex][ChunkUtil.getJ(x, y, 0)] != null) { 330 return true; 331 } 332 } 333 if (y == 0) { 334 if (localChunk.getBaseblocks()[layerIndex - 1] == null || 335 localChunk.getBaseblocks()[layerIndex][ChunkUtil.getJ(x, 15, z)] != null) { 336 return true; 337 } 338 } else if (y == 15) { 339 if (localChunk.getBaseblocks()[layerIndex + 1] == null || 340 localChunk.getBaseblocks()[layerIndex][ChunkUtil.getJ(x, 0, z)] != null) { 341 return true; 342 } 343 } 344 BaseBlock[] baseBlocks = localChunk.getBaseblocks()[layerIndex]; 345 if (x > 0 && baseBlocks[ChunkUtil.getJ(x - 1, y, z)] == null) { 346 return true; 347 } 348 if (x < 15 && baseBlocks[ChunkUtil.getJ(x + 1, y, z)] == null) { 349 return true; 350 } 351 if (y > 0 && baseBlocks[ChunkUtil.getJ(x, y - 1, z)] == null) { 352 return true; 353 } 354 if (y < 15 && baseBlocks[ChunkUtil.getJ(x, y + 1, z)] == null) { 355 return true; 356 } 357 if (z > 0 && baseBlocks[ChunkUtil.getJ(x, y, z - 1)] == null) { 358 return true; 359 } 360 return z < 15 && baseBlocks[ChunkUtil.getJ(x, y, z + 1)] == null; 361 } 362 363 private boolean isEdgeRegen(int x, int z, BlockVector2 blockVector2) { 364 if (x == 0) { 365 LocalChunk localChunkX = getBlockChunks().get(blockVector2.withX(blockVector2.getX() - 1)); 366 if (localChunkX == null) { 367 return true; 368 } 369 } else if (x == 15) { 370 LocalChunk localChunkX = getBlockChunks().get(blockVector2.withX(blockVector2.getX() + 1)); 371 if (localChunkX == null) { 372 return true; 373 } 374 } 375 if (z == 0) { 376 return getBlockChunks().get(blockVector2.withZ(blockVector2.getZ() - 1)) == null; 377 } else if (z == 15) { 378 return getBlockChunks().get(blockVector2.withZ(blockVector2.getZ() + 1)) == null; 379 } 380 return false; 381 } 382 383 private SideEffectSet getSideEffectSet(SideEffectState state) { 384 if (getSideEffectSet() != null) { 385 return getSideEffectSet(); 386 } 387 return switch (state) { 388 case NONE -> NO_SIDE_EFFECT_SET; 389 case EDGE -> EDGE_SIDE_EFFECT_SET; 390 case LIGHTING -> LIGHTING_SIDE_EFFECT_SET; 391 case EDGE_LIGHTING -> EDGE_LIGHTING_SIDE_EFFECT_SET; 392 }; 393 } 394 395 private enum SideEffectState { 396 NONE, 397 EDGE, 398 LIGHTING, 399 EDGE_LIGHTING 400 } 401 402}