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.util; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.configuration.caption.TranslatableCaption; 025import com.plotsquared.core.inject.factory.ProgressSubscriberFactory; 026import com.plotsquared.core.location.Location; 027import com.plotsquared.core.player.PlotPlayer; 028import com.plotsquared.core.plot.Plot; 029import com.plotsquared.core.plot.PlotArea; 030import com.plotsquared.core.plot.PlotManager; 031import com.plotsquared.core.queue.BasicQueueCoordinator; 032import com.plotsquared.core.queue.GlobalBlockQueue; 033import com.plotsquared.core.queue.QueueCoordinator; 034import com.plotsquared.core.util.task.TaskManager; 035import com.sk89q.worldedit.entity.Entity; 036import com.sk89q.worldedit.function.pattern.Pattern; 037import com.sk89q.worldedit.math.BlockVector2; 038import com.sk89q.worldedit.math.BlockVector3; 039import com.sk89q.worldedit.regions.CuboidRegion; 040import com.sk89q.worldedit.regions.Region; 041import com.sk89q.worldedit.world.World; 042import com.sk89q.worldedit.world.biome.BiomeType; 043import org.apache.logging.log4j.LogManager; 044import org.apache.logging.log4j.Logger; 045import org.checkerframework.checker.nullness.qual.NonNull; 046import org.checkerframework.checker.nullness.qual.Nullable; 047 048import java.io.IOException; 049import java.nio.file.Files; 050import java.nio.file.Path; 051import java.util.Collection; 052import java.util.Set; 053 054public abstract class RegionManager { 055 056 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + RegionManager.class.getSimpleName()); 057 058 public static RegionManager manager = null; 059 protected final WorldUtil worldUtil; 060 private final GlobalBlockQueue blockQueue; 061 private final ProgressSubscriberFactory subscriberFactory; 062 063 @Inject 064 public RegionManager( 065 @NonNull WorldUtil worldUtil, 066 @NonNull GlobalBlockQueue blockQueue, 067 @NonNull ProgressSubscriberFactory subscriberFactory 068 ) { 069 this.worldUtil = worldUtil; 070 this.blockQueue = blockQueue; 071 this.subscriberFactory = subscriberFactory; 072 } 073 074 public static BlockVector2 getRegion(Location location) { 075 int x = location.getX() >> 9; 076 int z = location.getZ() >> 9; 077 return BlockVector2.at(x, z); 078 } 079 080 /** 081 * 0 = Entity 082 * 1 = Animal 083 * 2 = Monster 084 * 3 = Mob 085 * 4 = Boat 086 * 5 = Misc 087 * 088 * @param plot plot 089 * @return array of counts of entity types 090 */ 091 public abstract int[] countEntities(Plot plot); 092 093 public void deleteRegionFiles(final String worldName, final Collection<BlockVector2> chunks, final Runnable whenDone) { 094 com.plotsquared.core.location.World<?> world = PlotSquared.platform().getPlatformWorld(worldName); 095 Path regionRoot = world.getWorldFolder().resolve("region"); 096 TaskManager.runTaskAsync(() -> { 097 for (BlockVector2 loc : chunks) { 098 Path path = regionRoot.resolve(String.format("r.%s.%s.mca", loc.getX(), loc.getZ())); 099 LOGGER.info("- Deleting file: {} (max 1024 chunks)", path.getFileName()); 100 try { 101 Files.deleteIfExists(path); 102 } catch (IOException e) { 103 LOGGER.error("Failed to delete region file", e); 104 } 105 } 106 TaskManager.runTask(whenDone); 107 }); 108 } 109 110 /** 111 * Set a number of cuboids to a certain block between two y values. 112 * 113 * @param area plot area 114 * @param regions cuboid regions 115 * @param blocks pattern 116 * @param minY y to set from 117 * @param maxY y to set to 118 * @param actor the actor associated with the cuboid set 119 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 120 * otherwise writes to the queue but does not enqueue. 121 * @return {@code true} if not enqueued, otherwise whether the created queue enqueued. 122 */ 123 public boolean setCuboids( 124 final @NonNull PlotArea area, 125 final @NonNull Set<CuboidRegion> regions, 126 final @NonNull Pattern blocks, 127 int minY, 128 int maxY, 129 @Nullable PlotPlayer<?> actor, 130 @Nullable QueueCoordinator queue 131 ) { 132 boolean enqueue = false; 133 if (queue == null) { 134 queue = area.getQueue(); 135 enqueue = true; 136 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 137 queue.addProgressSubscriber(subscriberFactory.createWithActor(actor)); 138 } 139 } 140 for (CuboidRegion region : regions) { 141 Location pos1 = Location.at( 142 area.getWorldName(), 143 region.getMinimumPoint().getX(), 144 minY, 145 region.getMinimumPoint().getZ() 146 ); 147 Location pos2 = Location.at( 148 area.getWorldName(), 149 region.getMaximumPoint().getX(), 150 maxY, 151 region.getMaximumPoint().getZ() 152 ); 153 queue.setCuboid(pos1, pos2, blocks); 154 } 155 return !enqueue || queue.enqueue(); 156 } 157 158 /** 159 * Notify any plugins that may want to modify clear behaviour that a clear is occuring 160 * 161 * @param manager plot manager 162 * @return {@code true} if the notified will accept the clear task 163 */ 164 public boolean notifyClear(PlotManager manager) { 165 return false; 166 } 167 168 /** 169 * Only called when {@link RegionManager#notifyClear(PlotManager)} returns true in specific PlotManagers 170 * 171 * @param plot plot 172 * @param whenDone task to run when complete 173 * @param manager plot manager 174 * @param actor the player running the clear 175 * @return {@code true} if the clear worked. {@code false} if someone went wrong so PlotSquared can then handle the clear 176 */ 177 public abstract boolean handleClear( 178 @NonNull Plot plot, 179 final @Nullable Runnable whenDone, 180 @NonNull PlotManager manager, 181 @Nullable PlotPlayer<?> actor 182 ); 183 184 /** 185 * Copy a region to a new location (in the same world) 186 * 187 * @param pos1 position 1 188 * @param pos2 position 2 189 * @param newPos position to move pos1 to 190 * @param actor the actor associated with the region copy 191 * @param whenDone task to run when complete 192 * @return success or not 193 */ 194 public boolean copyRegion( 195 final @NonNull Location pos1, 196 final @NonNull Location pos2, 197 final @NonNull Location newPos, 198 final @Nullable PlotPlayer<?> actor, 199 final @NonNull Runnable whenDone 200 ) { 201 final int relX = newPos.getX() - pos1.getX(); 202 final int relZ = newPos.getZ() - pos1.getZ(); 203 final com.sk89q.worldedit.world.World oldWorld = worldUtil.getWeWorld(pos1.getWorldName()); 204 final com.sk89q.worldedit.world.World newWorld = worldUtil.getWeWorld(newPos.getWorldName()); 205 final QueueCoordinator copyFrom = blockQueue.getNewQueue(oldWorld); 206 final BasicQueueCoordinator copyTo = (BasicQueueCoordinator) blockQueue.getNewQueue(newWorld); 207 setCopyFromToConsumer(pos1, pos2, relX, relZ, oldWorld, copyFrom, copyTo, false); 208 copyFrom.setCompleteTask(copyTo::enqueue); 209 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 210 copyFrom.addProgressSubscriber(subscriberFactory 211 .createFull( 212 actor, 213 Settings.QUEUE.NOTIFY_INTERVAL, 214 Settings.QUEUE.NOTIFY_WAIT, 215 TranslatableCaption.of("swap.progress_region_copy") 216 )); 217 } 218 copyFrom 219 .addReadChunks(new CuboidRegion( 220 BlockVector3.at(pos1.getX(), 0, pos1.getZ()), 221 BlockVector3.at(pos2.getX(), 0, pos2.getZ()) 222 ).getChunks()); 223 copyTo.setCompleteTask(whenDone); 224 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 225 copyTo.addProgressSubscriber(subscriberFactory 226 .createFull( 227 actor, 228 Settings.QUEUE.NOTIFY_INTERVAL, 229 Settings.QUEUE.NOTIFY_WAIT, 230 TranslatableCaption.of("swap.progress_region_paste") 231 )); 232 } 233 return copyFrom.enqueue(); 234 } 235 236 /** 237 * Assumptions:<br> 238 * - pos1 and pos2 are in the same plot<br> 239 * It can be harmful to the world if parameters outside this scope are provided 240 * 241 * @param pos1 position 1 242 * @param pos2 position 2 243 * @param ignoreAugment if to bypass synchronisation ish thing 244 * @param whenDone task to run when regeneration completed 245 * @return success or not 246 */ 247 public abstract boolean regenerateRegion(Location pos1, Location pos2, boolean ignoreAugment, Runnable whenDone); 248 249 public abstract void clearAllEntities(Location pos1, Location pos2); 250 251 /** 252 * Swap two regions within the same world 253 * 254 * @param pos1 position 1 255 * @param pos2 position 2 256 * @param swapPos position to swap with 257 * @param actor the actor associated with the region copy 258 * @param whenDone task to run when complete 259 */ 260 public void swap( 261 Location pos1, 262 Location pos2, 263 Location swapPos, 264 final @Nullable PlotPlayer<?> actor, 265 final Runnable whenDone 266 ) { 267 int relX = swapPos.getX() - pos1.getX(); 268 int relZ = swapPos.getZ() - pos1.getZ(); 269 270 World world1 = worldUtil.getWeWorld(pos1.getWorldName()); 271 World world2 = worldUtil.getWeWorld(swapPos.getWorldName()); 272 273 QueueCoordinator fromQueue1 = blockQueue.getNewQueue(world1); 274 QueueCoordinator fromQueue2 = blockQueue.getNewQueue(world2); 275 fromQueue1.setUnloadAfter(false); 276 fromQueue2.setUnloadAfter(false); 277 fromQueue1.addReadChunks(new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()).getChunks()); 278 fromQueue2.addReadChunks(new CuboidRegion( 279 swapPos.getBlockVector3(), 280 BlockVector3.at( 281 swapPos.getX() + pos2.getX() - pos1.getX(), 282 pos1.getY(), 283 swapPos.getZ() + pos2.getZ() - pos1.getZ() 284 ) 285 ).getChunks()); 286 QueueCoordinator toQueue1 = blockQueue.getNewQueue(world1); 287 QueueCoordinator toQueue2 = blockQueue.getNewQueue(world2); 288 289 setCopyFromToConsumer(pos1, pos2, relX, relZ, world1, fromQueue1, toQueue2, true); 290 setCopyFromToConsumer(pos1.add(relX, 0, relZ), pos2.add(relX, 0, relZ), -relX, -relZ, world1, fromQueue2, toQueue1, 291 true 292 ); 293 294 toQueue2.setCompleteTask(whenDone); 295 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 296 toQueue2.addProgressSubscriber(subscriberFactory.createFull( 297 actor, 298 Settings.QUEUE.NOTIFY_INTERVAL, 299 Settings.QUEUE.NOTIFY_WAIT, 300 TranslatableCaption.of("swap.progress_region2_paste") 301 )); 302 } 303 304 toQueue1.setCompleteTask(toQueue2::enqueue); 305 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 306 toQueue1.addProgressSubscriber(subscriberFactory.createFull( 307 actor, 308 Settings.QUEUE.NOTIFY_INTERVAL, 309 Settings.QUEUE.NOTIFY_WAIT, 310 TranslatableCaption.of("swap.progress_region1_paste") 311 )); 312 } 313 314 fromQueue2.setCompleteTask(toQueue1::enqueue); 315 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 316 fromQueue2.addProgressSubscriber(subscriberFactory 317 .createFull( 318 actor, 319 Settings.QUEUE.NOTIFY_INTERVAL, 320 Settings.QUEUE.NOTIFY_WAIT, 321 TranslatableCaption.of("swap.progress_region2_copy") 322 )); 323 } 324 325 fromQueue1.setCompleteTask(fromQueue2::enqueue); 326 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 327 fromQueue1.addProgressSubscriber(subscriberFactory 328 .createFull( 329 actor, 330 Settings.QUEUE.NOTIFY_INTERVAL, 331 Settings.QUEUE.NOTIFY_WAIT, 332 TranslatableCaption.of("swap.progress_region1_copy") 333 )); 334 } 335 fromQueue1.enqueue(); 336 } 337 338 private void setCopyFromToConsumer( 339 final Location pos1, 340 final Location pos2, 341 int relX, 342 int relZ, 343 final World world1, 344 final QueueCoordinator fromQueue, 345 final QueueCoordinator toQueue, 346 boolean removeEntities 347 ) { 348 fromQueue.setChunkConsumer(chunk -> { 349 int cx = chunk.getX(); 350 int cz = chunk.getZ(); 351 int cbx = cx << 4; 352 int cbz = cz << 4; 353 int bx = Math.max(pos1.getX(), cbx) & 15; 354 int bz = Math.max(pos1.getZ(), cbz) & 15; 355 int tx = Math.min(pos2.getX(), cbx + 15) & 15; 356 int tz = Math.min(pos2.getZ(), cbz + 15) & 15; 357 for (int y = world1.getMinY(); y <= world1.getMaxY(); y++) { 358 for (int x = bx; x <= tx; x++) { 359 for (int z = bz; z <= tz; z++) { 360 int rx = cbx + x; 361 int rz = cbz + z; 362 BlockVector3 loc = BlockVector3.at(rx, y, rz); 363 toQueue.setBlock(rx + relX, y, rz + relZ, world1.getFullBlock(loc)); 364 toQueue.setBiome(rx + relX, y, rz + relZ, world1.getBiome(loc)); 365 } 366 } 367 } 368 Region region = new CuboidRegion( 369 BlockVector3.at(cbx + bx, world1.getMinY(), cbz + bz), 370 BlockVector3.at(cbx + tx, world1.getMaxY(), cbz + tz) 371 ); 372 toQueue.addEntities(world1.getEntities(region)); 373 if (removeEntities) { 374 for (Entity entity : world1.getEntities(region)) { 375 entity.remove(); 376 } 377 } 378 }); 379 } 380 381 /** 382 * Set a region to a biome type. 383 * 384 * @param region region to set 385 * @param extendBiome how far outside the region to extent setting the biome too account for 3D biomes being 4x4 386 * @param biome biome to set 387 * @param area {@link PlotArea} in which the biome is being set 388 * @param whenDone task to run when complete 389 * @since 6.6.0 390 */ 391 public void setBiome( 392 final CuboidRegion region, 393 final int extendBiome, 394 final BiomeType biome, 395 final PlotArea area, 396 final Runnable whenDone 397 ) { 398 final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); 399 queue.addReadChunks(region.getChunks()); 400 final BlockVector3 regionMin = region.getMinimumPoint(); 401 final BlockVector3 regionMax = region.getMaximumPoint(); 402 queue.setChunkConsumer(chunkPos -> { 403 BlockVector3 chunkMin = BlockVector3.at( 404 Math.max(chunkPos.getX() << 4, regionMin.getBlockX()), 405 regionMin.getBlockY(), 406 Math.max(chunkPos.getZ() << 4, regionMin.getBlockZ()) 407 ); 408 BlockVector3 chunkMax = BlockVector3.at( 409 Math.min((chunkPos.getX() << 4) + 15, regionMax.getBlockX()), 410 regionMax.getBlockY(), 411 Math.min((chunkPos.getZ() << 4) + 15, regionMax.getBlockZ()) 412 ); 413 CuboidRegion chunkRegion = new CuboidRegion(region.getWorld(), chunkMin, chunkMax); 414 WorldUtil.setBiome( 415 area.getWorldName(), 416 chunkRegion, 417 biome 418 ); 419 worldUtil.refreshChunk(chunkPos.getBlockX(), chunkPos.getBlockZ(), area.getWorldName()); 420 }); 421 queue.setCompleteTask(whenDone); 422 queue.enqueue(); 423 } 424 425}