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