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