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(swapPos.getX() + pos2.getX() - pos1.getX(), 276 pos1.getY(), 277 swapPos.getZ() + pos2.getZ() - pos1.getZ() 278 ) 279 ).getChunks()); 280 QueueCoordinator toQueue1 = blockQueue.getNewQueue(world1); 281 QueueCoordinator toQueue2 = blockQueue.getNewQueue(world2); 282 283 setCopyFromToConsumer(pos1, pos2, relX, relZ, world1, fromQueue1, toQueue2, true); 284 setCopyFromToConsumer(pos1.add(relX, 0, relZ), pos2.add(relX, 0, relZ), -relX, -relZ, world1, fromQueue2, toQueue1, 285 true 286 ); 287 288 toQueue2.setCompleteTask(whenDone); 289 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 290 toQueue2.addProgressSubscriber(subscriberFactory.createFull( 291 actor, 292 Settings.QUEUE.NOTIFY_INTERVAL, 293 Settings.QUEUE.NOTIFY_WAIT, 294 TranslatableCaption.of("swap.progress_region2_paste") 295 )); 296 } 297 298 toQueue1.setCompleteTask(toQueue2::enqueue); 299 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 300 toQueue1.addProgressSubscriber(subscriberFactory.createFull( 301 actor, 302 Settings.QUEUE.NOTIFY_INTERVAL, 303 Settings.QUEUE.NOTIFY_WAIT, 304 TranslatableCaption.of("swap.progress_region1_paste") 305 )); 306 } 307 308 fromQueue2.setCompleteTask(toQueue1::enqueue); 309 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 310 fromQueue2.addProgressSubscriber(subscriberFactory 311 .createFull( 312 actor, 313 Settings.QUEUE.NOTIFY_INTERVAL, 314 Settings.QUEUE.NOTIFY_WAIT, 315 TranslatableCaption.of("swap.progress_region2_copy") 316 )); 317 } 318 319 fromQueue1.setCompleteTask(fromQueue2::enqueue); 320 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 321 fromQueue1.addProgressSubscriber(subscriberFactory 322 .createFull( 323 actor, 324 Settings.QUEUE.NOTIFY_INTERVAL, 325 Settings.QUEUE.NOTIFY_WAIT, 326 TranslatableCaption.of("swap.progress_region1_copy") 327 )); 328 } 329 fromQueue1.enqueue(); 330 } 331 332 private void setCopyFromToConsumer( 333 final Location pos1, 334 final Location pos2, 335 int relX, 336 int relZ, 337 final World world1, 338 final QueueCoordinator fromQueue, 339 final QueueCoordinator toQueue, 340 boolean removeEntities 341 ) { 342 fromQueue.setChunkConsumer(chunk -> { 343 int cx = chunk.getX(); 344 int cz = chunk.getZ(); 345 int cbx = cx << 4; 346 int cbz = cz << 4; 347 int bx = Math.max(pos1.getX(), cbx) & 15; 348 int bz = Math.max(pos1.getZ(), cbz) & 15; 349 int tx = Math.min(pos2.getX(), cbx + 15) & 15; 350 int tz = Math.min(pos2.getZ(), cbz + 15) & 15; 351 for (int y = world1.getMinY(); y <= world1.getMaxY(); y++) { 352 for (int x = bx; x <= tx; x++) { 353 for (int z = bz; z <= tz; z++) { 354 int rx = cbx + x; 355 int rz = cbz + z; 356 BlockVector3 loc = BlockVector3.at(rx, y, rz); 357 toQueue.setBlock(rx + relX, y, rz + relZ, world1.getFullBlock(loc)); 358 toQueue.setBiome(rx + relX, y, rz + relZ, world1.getBiome(loc)); 359 } 360 } 361 } 362 Region region = new CuboidRegion( 363 BlockVector3.at(cbx + bx, world1.getMinY(), cbz + bz), 364 BlockVector3.at(cbx + tx, world1.getMaxY(), cbz + tz) 365 ); 366 toQueue.addEntities(world1.getEntities(region)); 367 if (removeEntities) { 368 for (Entity entity : world1.getEntities(region)) { 369 entity.remove(); 370 } 371 } 372 }); 373 } 374 375 @Deprecated(forRemoval = true, since = "6.6.0") 376 public void setBiome( 377 final CuboidRegion region, 378 final int extendBiome, 379 final BiomeType biome, 380 final String world, 381 final Runnable whenDone 382 ) { 383 setBiome(region, extendBiome, biome, PlotSquared.get().getPlotAreaManager().getPlotAreas(world, region)[0], whenDone); 384 } 385 386 /** 387 * Set a region to a biome type. 388 * 389 * @param region region to set 390 * @param extendBiome how far outside the region to extent setting the biome too account for 3D biomes being 4x4 391 * @param biome biome to set 392 * @param area {@link PlotArea} in which the biome is being set 393 * @param whenDone task to run when complete 394 * @since 6.6.0 395 */ 396 public void setBiome( 397 final CuboidRegion region, 398 final int extendBiome, 399 final BiomeType biome, 400 final PlotArea area, 401 final Runnable whenDone 402 ) { 403 final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); 404 queue.addReadChunks(region.getChunks()); 405 final BlockVector3 regionMin = region.getMinimumPoint(); 406 final BlockVector3 regionMax = region.getMaximumPoint(); 407 queue.setChunkConsumer(chunkPos -> { 408 BlockVector3 chunkMin = BlockVector3.at( 409 Math.max(chunkPos.getX() << 4, regionMin.getBlockX()), 410 regionMin.getBlockY(), 411 Math.max(chunkPos.getZ() << 4, regionMin.getBlockZ()) 412 ); 413 BlockVector3 chunkMax = BlockVector3.at( 414 Math.min((chunkPos.getX() << 4) + 15, regionMax.getBlockX()), 415 regionMax.getBlockY(), 416 Math.min((chunkPos.getZ() << 4) + 15, regionMax.getBlockZ()) 417 ); 418 CuboidRegion chunkRegion = new CuboidRegion(region.getWorld(), chunkMin, chunkMax); 419 WorldUtil.setBiome( 420 area.getWorldName(), 421 chunkRegion, 422 biome 423 ); 424 worldUtil.refreshChunk(chunkPos.getBlockX(), chunkPos.getBlockZ(), area.getWorldName()); 425 }); 426 queue.setCompleteTask(whenDone); 427 queue.enqueue(); 428 } 429 430}